diff --git a/.github/workflows/build-deb.yml b/.github/workflows/build-deb.yml
index 597dc211..ebc3df4d 100644
--- a/.github/workflows/build-deb.yml
+++ b/.github/workflows/build-deb.yml
@@ -1,24 +1,6 @@
 name: Build Debian packages
 
-on:
-  push:
-    branches:
-    - '*'
-    paths:
-    - .github/workflows/build-deb.yml
-    - contrib/**
-    - daemon/**
-    - debian/**
-    - i18n/**
-    - libi2pd/**
-    - libi2pd_client/**
-    - Makefile
-    - Makefile.linux
-    tags:
-    - '*'
-  pull_request:
-    branches:
-    - '*'
+on: [push, pull_request]
 
 jobs:
   build:
@@ -32,30 +14,26 @@ jobs:
 
     steps:
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v3
       with:
         fetch-depth: 0
 
-    - name: Commit Hash
-      id: commit
-      uses: prompt/actions-commit-hash@v3.0.0
-
     - name: Build package
       uses: jtdor/build-deb-action@v1
       with:
         docker-image: debian:${{ matrix.dist }}-slim
         buildpackage-opts: --build=binary --no-sign
-        before-build-hook: debchange --controlmaint --local "+${{ steps.commit.outputs.short }}~${{ matrix.dist }}" -b --distribution ${{ matrix.dist }} "CI build"
+        before-build-hook: debchange --controlmaint --local "+${{ github.sha }}~${{ matrix.dist }}" -b --distribution ${{ matrix.dist }} "CI build"
         extra-build-deps: devscripts git
 
     - name: Upload package
-      uses: actions/upload-artifact@v4
+      uses: actions/upload-artifact@v3
       with:
         name: i2pd_${{ matrix.dist }}
         path: debian/artifacts/i2pd_*.deb
 
     - name: Upload debugging symbols
-      uses: actions/upload-artifact@v4
+      uses: actions/upload-artifact@v3
       with:
         name: i2pd-dbgsym_${{ matrix.dist }}
         path: debian/artifacts/i2pd-dbgsym_*.deb
diff --git a/.github/workflows/build-freebsd.yml b/.github/workflows/build-freebsd.yml
index a4a7566a..6dc46a39 100644
--- a/.github/workflows/build-freebsd.yml
+++ b/.github/workflows/build-freebsd.yml
@@ -1,24 +1,6 @@
 name: Build on FreeBSD
 
-on:
-  push:
-    branches:
-    - '*'
-    paths:
-    - .github/workflows/build-freebsd.yml
-    - build/CMakeLists.txt
-    - build/cmake_modules/**
-    - daemon/**
-    - i18n/**
-    - libi2pd/**
-    - libi2pd_client/**
-    - Makefile
-    - Makefile.bsd
-    tags:
-    - '*'
-  pull_request:
-    branches:
-    - '*'
+on: [push, pull_request]
 
 jobs:
   build:
@@ -27,7 +9,7 @@ jobs:
 
     steps:
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v3
 
     - name: Test in FreeBSD
       id: test
@@ -44,7 +26,7 @@ jobs:
           gmake -j2
 
     - name: Upload artifacts
-      uses: actions/upload-artifact@v4
+      uses: actions/upload-artifact@v3
       with:
         name: i2pd-freebsd
         path: build/i2pd
diff --git a/.github/workflows/build-osx.yml b/.github/workflows/build-osx.yml
index 31f0b90d..266f2c54 100644
--- a/.github/workflows/build-osx.yml
+++ b/.github/workflows/build-osx.yml
@@ -1,22 +1,6 @@
 name: Build on OSX
 
-on:
-  push:
-    branches:
-    - '*'
-    paths:
-    - .github/workflows/build-osx.yml
-    - daemon/**
-    - i18n/**
-    - libi2pd/**
-    - libi2pd_client/**
-    - Makefile
-    - Makefile.homebrew
-    tags:
-    - '*'
-  pull_request:
-    branches:
-    - '*'
+on: [push, pull_request]
 
 jobs:
   build:
@@ -30,16 +14,13 @@ jobs:
 
     steps:
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v3
 
-    - name: Install required formulae
+    - name: install packages
       run: |
         find /usr/local/bin -lname '*/Library/Frameworks/Python.framework/*' -delete
         brew update
         brew install boost miniupnpc openssl@1.1
 
-    - name: List installed formulae
-      run: brew list
-
-    - name: Build application
+    - name: build application
       run: make HOMEBREW=1 USE_UPNP=${{ matrix.with_upnp }} PREFIX=$GITHUB_WORKSPACE/output -j3
diff --git a/.github/workflows/build-windows-msvc.yml b/.github/workflows/build-windows-msvc.yml
new file mode 100644
index 00000000..172e0596
--- /dev/null
+++ b/.github/workflows/build-windows-msvc.yml
@@ -0,0 +1,52 @@
+name: Build on Windows with MSVC
+
+on: [push, pull_request]
+
+jobs:
+  build:
+    name: Build
+    runs-on: windows-latest
+
+    strategy:
+      fail-fast: false
+
+    steps:
+    - name: Checkout
+      uses: actions/checkout@v3
+      with:
+        fetch-depth: 0
+
+    - name: Build and install zlib
+      run: |
+        powershell -Command "(Invoke-WebRequest -Uri https://raw.githubusercontent.com/r4sas/zlib.install/master/install.bat -OutFile install_zlib.bat)"
+        powershell -Command "(Get-Content install_zlib.bat) | Set-Content install_zlib.bat" # fixing line endings
+        set BUILD_TYPE=Debug
+        ./install_zlib.bat
+        set BUILD_TYPE=Release
+        ./install_zlib.bat
+        del install_zlib.bat
+
+    - name: Install Boost
+      uses: crazy-max/ghaction-chocolatey@v2
+      with:
+        args: install boost-msvc-14.3 --version=1.81.0
+
+    - name: Install OpenSSL
+      uses: crazy-max/ghaction-chocolatey@v2
+      with:
+        args: install openssl
+
+    - name: Configure
+      working-directory: build
+      run: cmake -DWITH_STATIC=ON .
+
+    - name: Build
+      working-directory: build
+      run: cmake --build . --config Debug -- -m
+
+    - name: Upload artifacts
+      uses: actions/upload-artifact@v3
+      with:
+        name: i2pd-msvc
+        path: build/Debug/i2pd.*
+
diff --git a/.github/workflows/build-windows-msvc.yml-disabled b/.github/workflows/build-windows-msvc.yml-disabled
deleted file mode 100644
index 922ebd0d..00000000
--- a/.github/workflows/build-windows-msvc.yml-disabled
+++ /dev/null
@@ -1,80 +0,0 @@
-name: Build on Windows with MSVC
-
-on:
-  push:
-    branches:
-    - '*'
-    paths:
-    - .github/workflows/build-windows-msvc.yml
-    - build/CMakeLists.txt
-    - build/cmake_modules/**
-    - daemon/**
-    - i18n/**
-    - libi2pd/**
-    - libi2pd_client/**
-    - Win32/**
-    tags:
-    - '*'
-  pull_request:
-    branches:
-    - '*'
-
-jobs:
-  build:
-    name: Build
-    runs-on: windows-latest
-    env:
-      boost_path: ${{ github.workspace }}\boost_1_83_0
-      openssl_path: ${{ github.workspace }}\openssl_3_2_1
-
-    strategy:
-      fail-fast: false
-
-    steps:
-    - name: Checkout
-      uses: actions/checkout@v4
-      with:
-        fetch-depth: 0
-
-    - name: Build and install zlib
-      run: |
-        powershell -Command "(Invoke-WebRequest -Uri https://raw.githubusercontent.com/r4sas/zlib.install/master/install.bat -OutFile install_zlib.bat)"
-        powershell -Command "(Get-Content install_zlib.bat) | Set-Content install_zlib.bat" # fixing line endings
-        set BUILD_TYPE=Debug
-        ./install_zlib.bat
-        set BUILD_TYPE=Release
-        ./install_zlib.bat
-        del install_zlib.bat
-
-    - name: Install Boost
-      run: |
-        powershell -Command "(Start-BitsTransfer -Source https://sourceforge.net/projects/boost/files/boost-binaries/1.83.0/boost_1_83_0-msvc-14.3-64.exe/download -Destination boost_1_83_0-msvc-14.3-64.exe)"
-        ./boost_1_83_0-msvc-14.3-64.exe /DIR="${{env.boost_path}}" /VERYSILENT /SUPPRESSMSGBOXES /SP-
-
-    - name: Install OpenSSL
-      run: |
-        powershell -Command "(Start-BitsTransfer -Source https://slproweb.com/download/Win64OpenSSL-3_2_1.exe -Destination Win64OpenSSL-3_2_1.exe)"
-        ./Win64OpenSSL-3_2_1.exe /DIR="${{env.openssl_path}}" /TASKS="copytobin" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-
-
-    - name: Make copy of the OpenSSL libraries for CMake
-      run: |
-        dir ${{ github.workspace }}
-        dir ${{env.openssl_path}}\lib\VC
-        dir ${{env.openssl_path}}\lib\VC\x64\
-        dir ${{env.openssl_path}}\lib\VC\x64\MTd\
-        xcopy /s /y "${{env.openssl_path}}\lib\VC\x64\MTd" "${{env.openssl_path}}\lib"
-
-    - name: Configure
-      working-directory: build
-      run: cmake -DBoost_ROOT="${{env.boost_path}}" -DOPENSSL_ROOT_DIR="${{env.openssl_path}}" -DWITH_STATIC=ON .
-
-    - name: Build
-      working-directory: build
-      run: cmake --build . --config Debug -- -m
-
-    - name: Upload artifacts
-      uses: actions/upload-artifact@v4
-      with:
-        name: i2pd-msvc
-        path: build/Debug/i2pd.*
-
diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml
index 6f10e62b..66c846c1 100644
--- a/.github/workflows/build-windows.yml
+++ b/.github/workflows/build-windows.yml
@@ -1,25 +1,6 @@
 name: Build on Windows
 
-on:
-  push:
-    branches:
-    - '*'
-    paths:
-    - .github/workflows/build-windows.yml
-    - build/CMakeLists.txt
-    - build/cmake_modules/**
-    - daemon/**
-    - i18n/**
-    - libi2pd/**
-    - libi2pd_client/**
-    - Win32/**
-    - Makefile
-    - Makefile.mingw
-    tags:
-    - '*'
-  pull_request:
-    branches:
-    - '*'
+on: [push, pull_request]
 
 defaults:
   run:
@@ -42,7 +23,7 @@ jobs:
 
     steps:
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v3
       with:
         fetch-depth: 0
 
@@ -63,7 +44,7 @@ jobs:
         make USE_UPNP=yes DEBUG=no USE_GIT_VERSION=yes -j3
 
     - name: Upload artifacts
-      uses: actions/upload-artifact@v4
+      uses: actions/upload-artifact@v3
       with:
         name: i2pd-${{ matrix.arch_short }}.exe
         path: i2pd.exe
@@ -84,7 +65,7 @@ jobs:
 
     steps:
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v3
       with:
         fetch-depth: 0
 
@@ -102,7 +83,7 @@ jobs:
         cmake --build . -- -j3
 
     - name: Upload artifacts
-      uses: actions/upload-artifact@v4
+      uses: actions/upload-artifact@v3
       with:
         name: i2pd-cmake-${{ matrix.arch_short }}.exe
         path: build/i2pd.exe
@@ -116,7 +97,7 @@ jobs:
 
     steps:
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v3
       with:
         fetch-depth: 0
 
@@ -125,126 +106,34 @@ jobs:
       with:
         msystem: MINGW32
         install: base-devel git mingw-w64-i686-gcc mingw-w64-i686-boost mingw-w64-i686-openssl mingw-w64-i686-miniupnpc
-        cache: true
         update: true
 
-    - name: Clone MinGW packages repository and revert boost to 1.85.0
+    - name: Build WinXP-capable CRT packages
       run: |
         git clone https://github.com/msys2/MINGW-packages
-        cd MINGW-packages
-        git checkout 4cbb366edf2f268ac3146174b40ce38604646fc5 mingw-w64-boost
-        cd mingw-w64-boost
-        sed -i 's/boostorg.jfrog.io\/artifactory\/main/archives.boost.io/' PKGBUILD
-
-    # headers
-    - name: Get headers package version
-      id:  version-headers
-      run: |
-        echo "version=$(pacman -Si mingw-w64-i686-headers-git | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT
-    - name: Cache headers package
-      uses: actions/cache@v4
-      id: cache-headers
-      with:
-        path: MINGW-packages/mingw-w64-headers-git/*.zst
-        key: winxp-headers-${{ steps.version-headers.outputs.version }}
-    - name: Build WinXP-capable headers package
-      if: steps.cache-headers.outputs.cache-hit != 'true'
-      run: |
-        cd MINGW-packages/mingw-w64-headers-git
+        pushd MINGW-packages
+        pushd mingw-w64-headers-git
         sed -i 's/0x601/0x501/' PKGBUILD
-        MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck
-    - name: Install headers package
-      run: pacman --noconfirm -U MINGW-packages/mingw-w64-headers-git/mingw-w64-i686-*-any.pkg.tar.zst
+        MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm
+        pacman --noconfirm -U mingw-w64-i686-headers-git-*-any.pkg.tar.zst
+        popd
+        pushd mingw-w64-crt-git
+        MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm
+        pacman --noconfirm -U mingw-w64-i686-crt-git-*-any.pkg.tar.zst
+        popd
+        pushd mingw-w64-winpthreads-git
+        MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm
+        pacman --noconfirm -U mingw-w64-i686-libwinpthread-git-*-any.pkg.tar.zst mingw-w64-i686-winpthreads-git-*-any.pkg.tar.zst
+        popd
+        popd
 
-    # CRT
-    - name: Get crt package version
-      id:  version-crt
-      run: |
-        echo "version=$(pacman -Si mingw-w64-i686-crt-git | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT
-    - name: Cache crt package
-      uses: actions/cache@v4
-      id: cache-crt
-      with:
-        path: MINGW-packages/mingw-w64-crt-git/*.zst
-        key: winxp-crt-${{ steps.version-crt.outputs.version }}
-    - name: Build WinXP-capable crt package
-      if: steps.cache-crt.outputs.cache-hit != 'true'
-      run: |
-        cd MINGW-packages/mingw-w64-crt-git
-        MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck
-    - name: Install crt package
-      run: pacman --noconfirm -U MINGW-packages/mingw-w64-crt-git/mingw-w64-i686-*-any.pkg.tar.zst
-
-    # winpthreads
-    - name: Get winpthreads package version
-      id:  version-winpthreads
-      run: |
-        echo "version=$(pacman -Si mingw-w64-i686-winpthreads-git | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT
-    - name: Cache winpthreads package
-      uses: actions/cache@v4
-      id: cache-winpthreads
-      with:
-        path: MINGW-packages/mingw-w64-winpthreads-git/*.zst
-        key: winxp-winpthreads-${{ steps.version-winpthreads.outputs.version }}
-    - name: Build WinXP-capable winpthreads package
-      if: steps.cache-winpthreads.outputs.cache-hit != 'true'
-      run: |
-        cd MINGW-packages/mingw-w64-winpthreads-git
-        MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck
-    - name: Install winpthreads package
-      run: pacman --noconfirm -U MINGW-packages/mingw-w64-winpthreads-git/mingw-w64-i686-*-any.pkg.tar.zst
-
-    # OpenSSL
-    - name: Get openssl package version
-      id:  version-openssl
-      run: |
-        echo "version=$(pacman -Si mingw-w64-i686-openssl | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT
-    - name: Cache openssl package
-      uses: actions/cache@v4
-      id: cache-openssl
-      with:
-        path: MINGW-packages/mingw-w64-openssl/*.zst
-        key: winxp-openssl-${{ steps.version-openssl.outputs.version }}
-    - name: Build WinXP-capable openssl package
-      if: steps.cache-openssl.outputs.cache-hit != 'true'
-      run: |
-        cd MINGW-packages/mingw-w64-openssl
-        gpg --recv-keys D894E2CE8B3D79F5
-        gpg --recv-keys 216094DFD0CB81EF
-        MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck
-    - name: Install openssl package
-      run: pacman --noconfirm -U MINGW-packages/mingw-w64-openssl/mingw-w64-i686-*-any.pkg.tar.zst
-
-    # Boost
-    #- name: Get boost package version
-    #  id:  version-boost
-    #  run: |
-    #    echo "version=$(pacman -Si mingw-w64-i686-boost | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT
-    - name: Cache boost package
-      uses: actions/cache@v4
-      id: cache-boost
-      with:
-        path: MINGW-packages/mingw-w64-boost/*.zst
-        key: winxp-boost-1.85.0+crt-${{ steps.version-headers.outputs.version }}+ossl-${{ steps.version-openssl.outputs.version }}
-    # Rebuild package if packages above has changed
-    - name: Build WinXP-capable boost package
-      if: steps.cache-boost.outputs.cache-hit != 'true'
-      run: |
-        cd MINGW-packages/mingw-w64-boost
-        MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck
-    - name: Remove boost packages
-      run: pacman --noconfirm -R mingw-w64-i686-boost mingw-w64-i686-boost-libs
-    - name: Install boost package
-      run: pacman --noconfirm -U MINGW-packages/mingw-w64-boost/mingw-w64-i686-*-any.pkg.tar.zst
-
-    # Building i2pd
     - name: Build application
       run: |
         mkdir -p obj/Win32 obj/libi2pd obj/libi2pd_client obj/daemon
         make USE_UPNP=yes DEBUG=no USE_GIT_VERSION=yes USE_WINXP_FLAGS=yes -j3
 
     - name: Upload artifacts
-      uses: actions/upload-artifact@v4
+      uses: actions/upload-artifact@v3
       with:
         name: i2pd-xp.exe
         path: i2pd.exe
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 0b65ec9d..935c2f93 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,24 +1,6 @@
 name: Build on Ubuntu
 
-on:
-  push:
-    branches:
-    - '*'
-    paths:
-    - .github/workflows/build.yml
-    - build/CMakeLists.txt
-    - build/cmake_modules/**
-    - daemon/**
-    - i18n/**
-    - libi2pd/**
-    - libi2pd_client/**
-    - Makefile
-    - Makefile.linux
-    tags:
-    - '*'
-  pull_request:
-    branches:
-    - '*'
+on: [push, pull_request]
 
 jobs:
   build-make:
@@ -32,7 +14,7 @@ jobs:
 
     steps:
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v3
 
     - name: install packages
       run: |
@@ -53,7 +35,7 @@ jobs:
 
     steps:
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v3
 
     - name: install packages
       run: |
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index c6d55664..41fe859e 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -37,29 +37,29 @@ jobs:
 
     steps:
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v3
 
     - name: Set up QEMU
-      uses: docker/setup-qemu-action@v3
+      uses: docker/setup-qemu-action@v2
 
     - name: Set up Docker Buildx
-      uses: docker/setup-buildx-action@v3
+      uses: docker/setup-buildx-action@v2
 
     - name: Login to DockerHub
-      uses: docker/login-action@v3
+      uses: docker/login-action@v2
       with:
         username: ${{ secrets.DOCKERHUB_USERNAME }}
         password: ${{ secrets.DOCKERHUB_TOKEN }}
 
     - name: Login to GitHub Container registry
-      uses: docker/login-action@v3
+      uses: docker/login-action@v2
       with:
         registry: ghcr.io
         username: ${{ github.actor }}
         password: ${{ secrets.GITHUB_TOKEN }}
 
     - name: Build container for ${{ matrix.archname }}
-      uses: docker/build-push-action@v5
+      uses: docker/build-push-action@v3
       with:
         context: ./contrib/docker
         file: ./contrib/docker/Dockerfile
@@ -82,22 +82,22 @@ jobs:
 
     steps:
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v3
 
     - name: Set up QEMU
-      uses: docker/setup-qemu-action@v3
+      uses: docker/setup-qemu-action@v2
 
     - name: Set up Docker Buildx
-      uses: docker/setup-buildx-action@v3
+      uses: docker/setup-buildx-action@v2
 
     - name: Login to DockerHub
-      uses: docker/login-action@v3
+      uses: docker/login-action@v2
       with:
         username: ${{ secrets.DOCKERHUB_USERNAME }}
         password: ${{ secrets.DOCKERHUB_TOKEN }}
 
     - name: Login to GitHub Container registry
-      uses: docker/login-action@v3
+      uses: docker/login-action@v2
       with:
         registry: ghcr.io
         username: ${{ github.actor }}
@@ -108,7 +108,7 @@ jobs:
       uses: Noelware/docker-manifest-action@master
       with:
         inputs: purplei2p/i2pd:latest
-        tags: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7
+        images: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7
         push: true
 
     - name: Create and push latest manifest image to GHCR
@@ -116,7 +116,7 @@ jobs:
       uses: Noelware/docker-manifest-action@master
       with:
         inputs: ghcr.io/purplei2p/i2pd:latest
-        tags: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7
+        images: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7
         push: true
 
     - name: Store release version to env
@@ -128,7 +128,7 @@ jobs:
       uses: Noelware/docker-manifest-action@master
       with:
         inputs: purplei2p/i2pd:latest,purplei2p/i2pd:latest-release,purplei2p/i2pd:release-${{ env.RELEASE_VERSION }}
-        tags: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7
+        images: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7
         push: true
 
     - name: Create and push release manifest to GHCR
@@ -136,5 +136,5 @@ jobs:
       uses: Noelware/docker-manifest-action@master
       with:
         inputs: ghcr.io/purplei2p/i2pd:latest,ghcr.io/purplei2p/i2pd:latest-release,ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }}
-        tags: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7
+        images: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7
         push: true
diff --git a/ChangeLog b/ChangeLog
index 23864c0e..6534a9b5 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,228 +1,6 @@
 # for this file format description,
 # see https://github.com/olivierlacan/keep-a-changelog
 
-## [2.56.0] - 2025-02-11
-### Added
-- Config params for shared local destination
-- AddressBook full addresses cache
-- Decline transit tunnel to duplicated router
-- Recreate tunnels in random order
-### Changed
-- Exclude disk operations from SSU2 and NTCP2 threads
-- Set minimal version for peer test to 0.9.62
-- Send ack requested flag after second SSU2 resend attempt
-- Shorter ECIESx25519 ack request interval for datagram and I2CP sessions
-- Don't change datagram routing path too often if unidirectional data stream
-- Reduce LeaseSet and local RouterInfo publishing confirmation intervals
-- Don't delete buffer of connected routers or if an update received
-- Smaller RouterInfo request timeout if sent directly
-- Persist local RouterInfo in separate thread
-- Don't recalculate and process ranges for every SSU2 Ack block
-- Reseeds list
-### Fixed
-- Termination deadlock if SAM session is active
-- Race condition at tunnel endpoint
-- Inbound tunnel build encryption
-
-## [2.55.0] - 2024-12-30
-### Added
-- Support boost 1.87
-- "i2p.streaming.maxConcurrentStreams" tunnel's param to limit number of simultaneous streams
-- Separate thread for tunnel build requests
-- Show next peer and connectivity on "Transit tunnels" page
-- Tunnel name for local destination thread
-- Throttle incoming ECIESx25519 sessions
-- Send tunnel data to transport session directly if possible
-- Publish 'R' cap for yggdrasil-only routers, and 'U' cap for routers through proxy
-- Random tunnel rejection when medium congestion
-- Save unreachable router's endpoint to use it next time without introducers
-- Recognize symmetric NAT from peer test message 7
-- Resend HolePunch and RelayResponse messages
-### Changed
-- Removed own implementation of AESNI and always use one from openssl
-- Renamed main thread to i2pd-daemon
-- Set i2p.streaming.profile=2 for shared local destination
-- Reduced LeaseSet and RouterInfo lookup timeouts
-- Cleanup ECIES sessions and tags more often
-- Check LeaseSet expiration time
-- Handle NTCP2 session handshakes in separate thread
-- Limit last decline time by 1.5 hours in router's profile
-- Don't handle RelayRequest and RelayIntro with same nonce twice
-- Increased hole punch expiration interval
-- Send peer test message 6 with delay if message 4 was received before message 5
-- Pre-calculate more x25519 keys for transports in runtime
-- Don't request LeaseSet for incoming stream
-- Terminate incoming stream right away if no remote LeaseSet
-- Handle choked, new RTO and window size calculation and resetting algorithm for streams
-### Fixed
-- Empty string in addressbook subscriptions
-- ECIESx25519 sessions without destination
-- Missing RouterInfo buffer in NetDb
-- Invalid I2PControl certificate
-- Routers disappear from NetDb when offline
-- Peer test message 6 sent to unknown endpoint
-- Race condition with LeaseSet update
-- Excessive CPU usage by streams
-- Crash on shutdown
-
-## [2.54.0] - 2024-10-06
-### Added
-- Maintain recently connected routers list to avoid false-positive peer test
-- Limited connectivity mode(through proxy)
-- "i2p.streaming.profile" tunnel's param to let tunnel select also low-bandwidth routers
-- Limit stream's inbound speed
-- Periodic ack requests in ratchets session
-- Set congestion cap G immediately if through proxy
-- Show tunnel's routers bandwidth caps in web console
-- Handle immediate ack requested flag in SSU2 data packets
-- Resend and ack peer test and relay messages
-- "senduseragent" HTTP proxy's param to pass through user's User-Agent
-### Changed
-- Exclude 'N' routers from high-bandwidth routers for client tunnels
-- C++11 support has been dropped, the minimal requirement is C++17 now, C++20 for some compilers
-- Removed dependency from boost::date_time and boost::filesystem
-- Set default i2cp.leaseSetEncType to 0,4 and to 4 for server tunnels
-- Handle i2cp.inboundlimit and i2cp.outboundlimit params in I2CP
-- Publish LeaseSet with new timestamp update if tunnel was replaced in the same second
-- Increase max number of generated tags to 800 per tagset
-- Routing path expiration by time instead num attempts
-- Save timestamp from epoch instead local time to profiles
-- Update introducer's iTag if session to introducer was replaced to new one
-- RTT, window size and number of NACKs calculation for streaming
-- Don't select same peer for tunnel too often
-- Use WinApi for data path UTF-8 conversion for Windows
-### Fixed
-- Jump link crash if address book is disabled
-- Race condition if connect through an introducer
-- "Date" header in I2PControl response
-- Incomplete response from web console
-- AEAD verification with LibreSSL
-- Number of generated tags and new keys for follow-on tagsets
-- Expired leases in LeaseSet
-- Attempts to send HolePunch to 0.0.0.0
-- Incorrect options size in quick ack streaming packet
-- Low bandwidth router appeared as first peer in high-bandwidth client tunnel
-
-## [2.53.1] - 2024-07-29
-### Changed
-- I2CP performance improvement
-### Fixed
-- 100% CPU usage after I2CP/SAM/BOB session termination
-- Incorrect client limits returned through I2CP
-- Build with LibreSSL
-
-## [2.53.0] - 2024-07-19
-### Added
-- New congestion control algorithm for streaming
-- Support miniupnp-2.2.8
-- Limit stream's outbound speed
-- Flood to next day closest floodfills before UTC midnight
-- Recognize duplicated routers and bypass them
-- Random SSU2 resend interval
-### Changed
-- Set minimal version to 0.9.69 for floodfills and 0.9.58 for client tunnels
-- Removed openssl 1.0.2 support
-- Move unsent I2NP messages to the new session if replaced
-- Use mt19937 RNG instead rand()
-- Update router's congestion caps before initial publishing
-- Don't try introducer with invalid address
-- Select newest introducers to publish
-- Don't request relay tag for every session if we have enough introducers
-- Update timestamp for non-reachable or hidden router
-- Reset streaming routing path if duplicated SYN received
-- Update LeaseSet if inbound tunnel failed
-- Reseeds list
-### Fixed
-- Crash when a destination gets terminated
-- Expired offline signature upon destination creation
-- Race condition between local RouterInfo buffer creation and sending it through the transports
-
-## [2.52.0] - 2024-05-12
-### Added
-- Separate threads for persisting RouterInfos and profiles to disk
-- Give preference to address with direct connection
-- Exclude addresses with incorrect static or intro key
-- Avoid two firewalled routers in the row in tunnel
-- Drop unsolicited database search replies
-### Changed
-- Increase number of hashes to 16 in exploratory lookup reply
-- Reduce number of a RouterInfo lookup attempts to 5
-- Reset stream RTO if outbound tunnel was changed
-- Insert previously excluded floodfill back when successfully connected
-- Increase maximum stream resend attempts to 9
-- Reply to exploratory lookups with only confirmed routers if low tunnel build rate
-- Don't accept too old RouterInfo
-- Build client tunnels through confirmed routers only if low tunnel build rate
-- Manage netDb requests more frequently
-- Don't reply with closer than us only floodfills for lookup
-### Fixed
-- Crash on router lookup if exploratory pool is not ready
-- Race condition in excluded peers for next lookup
-- Excessive number of lookups for same destination
-- Race condition with transport peers during shutdown
-- Corrupted RouterInfo files
-
-## [2.51.0] - 2024-04-06
-### Added
-- Non-blocking mode for UDP sockets
-- Set SSU2 socket buffer size based on bandwidth limit
-- Encrypted tunnel tests
-- Support for multiple UDP server tunnels on one destination
-- Publish medium congestion indication
-- Local domain sockets for SOCKS proxy upstream
-- Tunnel status "declined" in web console
-- SAM error reply "Incompatible crypto" if remote destination has incompatible crypto
-- Reduce amount of traffic by handling local message drops
-- Keep SSU2 socket open even if it fails to bind
-- Lower SSU2 resend traffic spikes
-- Expiration for messages in SSU2 send queue
-- Use EWMA for stream RTT estimation
-- Request choking delay if too many NACKs in stream
-- Allow 0ms latency for tunnel
-- Randomize tunnels selection for tests
-### Changed
-- Upstream SOCKS proxy from SOCKS4 to SOCKS5
-- Transit tunnels limit to 4 bytes. Default value to 10K
-- Reply CANT_REACH_PEER if connect to ourselves in SAM
-- Don't send already expired I2NP messages
-- Use monotonic timer to measure tunnel test latency
-- Standard NTCP2 frame doesn't exceed 16K
-- Always send request through tunnels in case of restricted routes
-- Don't delete connected routers from NetDb
-- Send lookup reply directly to reply tunnel gateway if possible
-- Reduce unreachable router ban interval to 8 minutes
-- Don't request banned routers / don't try to connect to unreachable router
-- Consider 'M' routers as low bandwidth
-- Limit minimal received SSU2 packet size to 40 bytes
-- Bob picks peer test session only if Charlie's address supports peer testing
-- Reject peer test msg 2 if peer testing is not supported
-- Don't request termination if SSU2 session was not established
-- Set maximum SSU2 queue size depending on RTT value
-- New streaming RTT calculation algorithm
-- Don't double initial RTO for streams when changing tunnels
-- Restore failed tunnel if test or data for inbound tunnel received
-- Don't fail last remaining tunnel in pool
-- Publish LeasetSet again if local destination was not ready or no tunnels
-- Make more attempts to pick high bandwidth hop for client tunnel
-- Reduced SSU2 session termination timeout to 165 seconds
-- Reseeds list
-### Fixed
-- ECIESx25519 symmetric key tagset early expiration
-- Encrypted LeaseSet lookup
-- Outbound tunnel build fails if it's endpoint is the same as reply tunnel gateway
-- I2PControl RouterManager returns invalid JSON when unknown params are passed
-- Mix of data between different UDP sessions on the same server
-- TARGET_OS_SIMULATOR check
-- Handling of "reservedrange" param
-- New NTCP2 session gets teminated upon termination of old one
-- New SSU2 session gets teminated upon termination of old one
-- Peer test to non-supporting router
-- Streaming ackThrough off 1 if number of NACKs exceeds 255
-- Race condition in ECIESx25519 tags table
-- Good tunnel becomes failed
-- Crash when packet comes to terminated stream
-- Stream hangs during LeaseSet update
-
 ## [2.50.2] - 2024-01-06
 ###Fixed
 - Crash with OpenSSL 3.2.0
diff --git a/LICENSE b/LICENSE
index f59491f5..93280084 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2013-2025, The PurpleI2P Project
+Copyright (c) 2013-2023, The PurpleI2P Project
 
 All rights reserved.
 
diff --git a/Makefile b/Makefile
index 0d4ca48c..3998beb0 100644
--- a/Makefile
+++ b/Makefile
@@ -29,6 +29,7 @@ DAEMON_SRC_DIR := daemon
 # import source files lists
 include filelist.mk
 
+USE_AESNI       := $(or $(USE_AESNI),yes)
 USE_STATIC      := $(or $(USE_STATIC),no)
 USE_UPNP        := $(or $(USE_UPNP),no)
 DEBUG           := $(or $(DEBUG),yes)
@@ -69,9 +70,6 @@ else ifneq (, $(findstring freebsd, $(SYS))$(findstring openbsd, $(SYS)))
 else ifneq (, $(findstring haiku, $(SYS)))
 	DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
 	include Makefile.haiku
-else ifneq (, $(findstring solaris, $(SYS)))
-	DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
-	include Makefile.solaris
 else # not supported
 	$(error Not supported platform)
 endif
diff --git a/Makefile.bsd b/Makefile.bsd
index 1c911802..00543193 100644
--- a/Makefile.bsd
+++ b/Makefile.bsd
@@ -1,22 +1,13 @@
 CXX = clang++
 CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misleading-indentation
-DEFINES = -D_GLIBCXX_USE_NANOSLEEP=1
-INCFLAGS = -I/usr/include/ -I/usr/local/include/
-LDFLAGS = ${LD_DEBUG} -Wl,-rpath,/usr/local/lib -L/usr/local/lib
-LDLIBS = -lssl -lcrypto -lz -lpthread -lboost_system -lboost_program_options
-
 ## NOTE: NEEDED_CXXFLAGS is here so that custom CXXFLAGS can be specified at build time
 ## **without** overwriting the CXXFLAGS which we need in order to build.
 ## For example, when adding 'hardening flags' to the build
 ## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove
 ## -std=c++11. If you want to remove this variable please do so in a way that allows setting
 ## custom FLAGS to work at build-time.
-CXXVER := $(shell $(CXX) -dumpversion|cut -c 1-2)
-ifeq (${CXXVER}, "4.") # older clang always returned 4.2.1
-	$(error Compiler too old)
-else ifeq (${CXXVER}, ${filter ${CXXVER},16 17 18 19})  # clang 16 - 19
-	NEEDED_CXXFLAGS = -std=c++20
-else
-	NEEDED_CXXFLAGS = -std=c++17
-endif
-
+NEEDED_CXXFLAGS = -std=c++11
+DEFINES = -D_GLIBCXX_USE_NANOSLEEP=1
+INCFLAGS = -I/usr/include/ -I/usr/local/include/
+LDFLAGS = ${LD_DEBUG} -Wl,-rpath,/usr/local/lib -L/usr/local/lib
+LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread
diff --git a/Makefile.haiku b/Makefile.haiku
index eb56a207..85c2835d 100644
--- a/Makefile.haiku
+++ b/Makefile.haiku
@@ -1,12 +1,8 @@
-ifeq ($(shell $(CXX) -dumpmachine | cut -c 1-4), i586)
-CXX = g++-x86
-else
 CXX = g++
-endif
-CXXFLAGS := -Wall -std=c++20
+CXXFLAGS := -Wall -std=c++11
 INCFLAGS = -I/system/develop/headers
 DEFINES = -D_DEFAULT_SOURCE -D_GNU_SOURCE
-LDLIBS = -lbe -lbsd -lnetwork -lz -lssl -lcrypto -lboost_program_options -lpthread
+LDLIBS = -lbe -lbsd -lnetwork -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread
 
 ifeq ($(USE_UPNP),yes)
 	DEFINES += -DUSE_UPNP
diff --git a/Makefile.homebrew b/Makefile.homebrew
index 706f9811..88b2a9e2 100644
--- a/Makefile.homebrew
+++ b/Makefile.homebrew
@@ -1,33 +1,41 @@
 # root directory holding homebrew
-BREWROOT = /opt/homebrew
+BREWROOT = /usr/local
 BOOSTROOT = ${BREWROOT}/opt/boost
 SSLROOT = ${BREWROOT}/opt/openssl@1.1
 UPNPROOT = ${BREWROOT}/opt/miniupnpc
+CXXFLAGS = ${CXX_DEBUG} -Wall -std=c++11 -DMAC_OSX -Wno-overloaded-virtual
+INCFLAGS = -I${SSLROOT}/include -I${BOOSTROOT}/include
+LDFLAGS = ${LD_DEBUG}
 
-CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wno-overloaded-virtual
-NEEDED_CXXFLAGS ?= -std=c++17
-INCFLAGS ?= -I${SSLROOT}/include -I${BOOSTROOT}/include
-LDFLAGS ?= ${LD_DEBUG}
-DEFINES += -DMAC_OSX
+ifndef TRAVIS
+	CXX = clang++
+endif
 
 ifeq ($(USE_STATIC),yes)
-	LDLIBS = -lz ${SSLROOT}/lib/libcrypto.a ${SSLROOT}/lib/libssl.a ${BOOSTROOT}/lib/libboost_system.a ${BOOSTROOT}/lib/libboost_filesystem.a ${BOOSTROOT}/lib/libboost_program_options.a
-ifeq ($(USE_UPNP),yes)
-	LDLIBS += ${UPNPROOT}/lib/libminiupnpc.a
-endif
-	LDLIBS +=  -lpthread -ldl
+	LDLIBS = -lz ${SSLROOT}/lib/libcrypto.a ${SSLROOT}/lib/libssl.a ${BOOSTROOT}/lib/libboost_system.a ${BOOSTROOT}/lib/libboost_date_time.a ${BOOSTROOT}/lib/libboost_filesystem.a ${BOOSTROOT}/lib/libboost_program_options.a -lpthread
 else
 	LDFLAGS += -L${SSLROOT}/lib -L${BOOSTROOT}/lib
-	LDLIBS = -lz -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_program_options -lpthread
-ifeq ($(USE_UPNP),yes)
-	LDFLAGS += -L${UPNPROOT}/lib
-	LDLIBS += -lminiupnpc
-endif
+	LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread
 endif
 
 ifeq ($(USE_UPNP),yes)
-	DEFINES += -DUSE_UPNP
+	LDFLAGS += -ldl
+	CXXFLAGS += -DUSE_UPNP
 	INCFLAGS += -I${UPNPROOT}/include
+	ifeq ($(USE_STATIC),yes)
+		LDLIBS += ${UPNPROOT}/lib/libminiupnpc.a
+	else
+		LDFLAGS += -L${UPNPROOT}/lib
+		LDLIBS += -lminiupnpc
+	endif
+endif
+
+# OSX Notes
+# http://www.hutsby.net/2011/08/macs-with-aes-ni.html
+# Seems like all recent Mac's have AES-NI, after firmware upgrade 2.2
+# Found no good way to detect it from command line. TODO: Might be some osx sysinfo magic
+ifeq ($(USE_AESNI),yes)
+	CXXFLAGS += -D__AES__ -maes
 endif
 
 install: all
diff --git a/Makefile.linux b/Makefile.linux
index 4ea39e22..6c7a4619 100644
--- a/Makefile.linux
+++ b/Makefile.linux
@@ -9,17 +9,24 @@ LDFLAGS ?= ${LD_DEBUG}
 ## -std=c++11. If you want to remove this variable please do so in a way that allows setting
 ## custom FDLAGS to work at build-time.
 
-# detect proper flag for c++17 support by compilers
+# detect proper flag for c++11 support by compilers
 CXXVER := $(shell $(CXX) -dumpversion)
 ifeq ($(shell expr match $(CXX) 'clang'),5)
+	NEEDED_CXXFLAGS += -std=c++11
+else ifeq ($(shell expr match ${CXXVER} "4\.[0-9][0-9]"),4) # gcc >= 4.10
+	NEEDED_CXXFLAGS += -std=c++11
+else ifeq ($(shell expr match ${CXXVER} "4\.[8-9]"),3) # gcc 4.8 - 4.9
+	NEEDED_CXXFLAGS += -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1
+else ifeq ($(shell expr match ${CXXVER} "[5-6]"),1) # gcc 5 - 6
+	NEEDED_CXXFLAGS += -std=c++11
+	LDLIBS = -latomic
+else ifeq ($(shell expr match ${CXXVER} "[7-9]"),1) # gcc 7 - 9
 	NEEDED_CXXFLAGS += -std=c++17
-else ifeq ($(shell expr match ${CXXVER} "[8-9]"),1) # gcc 8 - 9
+	LDLIBS = -latomic
+else ifeq ($(shell expr match ${CXXVER} "1[0-9]"),2) # gcc 10+
+#	NEEDED_CXXFLAGS += -std=c++20
 	NEEDED_CXXFLAGS += -std=c++17
-	LDLIBS = -lboost_system -lstdc++fs
-else ifeq ($(shell expr match ${CXXVER} "1[0-2]"),2) # gcc 10 - 12
-	NEEDED_CXXFLAGS += -std=c++17
-else ifeq ($(shell expr match ${CXXVER} "1[3-9]"),2) # gcc 13+
-	NEEDED_CXXFLAGS += -std=c++20
+	LDLIBS = -latomic
 else # not supported
 $(error Compiler too old)
 endif
@@ -31,6 +38,9 @@ ifeq ($(USE_STATIC),yes)
 #   Using 'getaddrinfo' in statically linked applications requires at runtime
 #   the shared libraries from the glibc version used for linking
 	LIBDIR := /usr/lib/$(SYS)
+	LDLIBS += $(LIBDIR)/libboost_system.a
+	LDLIBS += $(LIBDIR)/libboost_date_time.a
+	LDLIBS += $(LIBDIR)/libboost_filesystem.a
 	LDLIBS += $(LIBDIR)/libboost_program_options.a
 	LDLIBS += $(LIBDIR)/libssl.a
 	LDLIBS += $(LIBDIR)/libcrypto.a
@@ -40,7 +50,7 @@ ifeq ($(USE_UPNP),yes)
 endif
 	LDLIBS += -lpthread -ldl
 else
-	LDLIBS += -lssl -lcrypto -lz -lboost_program_options -lpthread -latomic
+	LDLIBS += -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread
 ifeq ($(USE_UPNP),yes)
 	LDLIBS += -lminiupnpc
 endif
@@ -51,6 +61,13 @@ ifeq ($(USE_UPNP),yes)
 	DEFINES += -DUSE_UPNP
 endif
 
+ifeq ($(USE_AESNI),yes)
+ifneq (, $(findstring i386, $(SYS))$(findstring i686, $(SYS))$(findstring x86_64, $(SYS))) # only x86-based CPU supports that
+	NEEDED_CXXFLAGS += -maes
+	DEFINES += -D__AES__
+endif
+endif
+
 install: all
 	install -d ${PREFIX}/bin
 	install -m 755 ${I2PD} ${PREFIX}/bin
diff --git a/Makefile.mingw b/Makefile.mingw
index 32d60764..38da225a 100644
--- a/Makefile.mingw
+++ b/Makefile.mingw
@@ -7,7 +7,7 @@ CXXFLAGS := $(CXX_DEBUG) -fPIC -msse
 INCFLAGS := -I$(DAEMON_SRC_DIR) -IWin32
 LDFLAGS := ${LD_DEBUG} -static -fPIC -msse
 
-NEEDED_CXXFLAGS += -std=c++20
+NEEDED_CXXFLAGS += -std=c++17
 DEFINES += -DWIN32_LEAN_AND_MEAN
 
 # UPNP Support
@@ -16,11 +16,9 @@ ifeq ($(USE_UPNP),yes)
 	LDLIBS = -lminiupnpc
 endif
 
-ifeq ($(USE_WINXP_FLAGS), yes)
-	DEFINES += -DWINVER=0x0501 -D_WIN32_WINNT=0x0501
-endif
-
 LDLIBS += \
+	$(MINGW_PREFIX)/lib/libboost_system-mt.a \
+	$(MINGW_PREFIX)/lib/libboost_date_time-mt.a \
 	$(MINGW_PREFIX)/lib/libboost_filesystem-mt.a \
 	$(MINGW_PREFIX)/lib/libboost_program_options-mt.a \
 	$(MINGW_PREFIX)/lib/libssl.a \
@@ -42,6 +40,16 @@ ifeq ($(USE_WIN32_APP), yes)
 	DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC))
 endif
 
+ifeq ($(USE_WINXP_FLAGS), yes)
+	DEFINES += -DWINVER=0x0501 -D_WIN32_WINNT=0x0501
+endif
+
+ifeq ($(USE_AESNI),yes)
+	NEEDED_CXXFLAGS += -maes
+	LDFLAGS += -maes
+	DEFINES += -D__AES__
+endif
+
 ifeq ($(USE_ASLR),yes)
 	LDFLAGS += -Wl,--nxcompat -Wl,--high-entropy-va -Wl,--dynamicbase,--export-all-symbols
 endif
diff --git a/Makefile.osx b/Makefile.osx
index 52282307..467c9fdd 100644
--- a/Makefile.osx
+++ b/Makefile.osx
@@ -1,5 +1,5 @@
 CXX = clang++
-CXXFLAGS := ${CXX_DEBUG} -Wall -std=c++17
+CXXFLAGS := ${CXX_DEBUG} -Wall -std=c++11
 INCFLAGS = -I/usr/local/include
 DEFINES := -DMAC_OSX
 LDFLAGS := -Wl,-rpath,/usr/local/lib -L/usr/local/lib
@@ -7,9 +7,9 @@ LDFLAGS += -Wl,-dead_strip
 LDFLAGS += -Wl,-dead_strip_dylibs
 
 ifeq ($(USE_STATIC),yes)
-	LDLIBS = -lz /usr/local/lib/libssl.a /usr/local/lib/libcrypto.a /usr/local/lib/libboost_system.a /usr/local/lib/libboost_filesystem.a /usr/local/lib/libboost_program_options.a -lpthread
+	LDLIBS = -lz /usr/local/lib/libcrypto.a /usr/local/lib/libssl.a /usr/local/lib/libboost_system.a /usr/local/lib/libboost_date_time.a /usr/local/lib/libboost_filesystem.a /usr/local/lib/libboost_program_options.a -lpthread
 else
-	LDLIBS = -lz -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_program_options -lpthread
+	LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread
 endif
 
 ifeq ($(USE_UPNP),yes)
@@ -25,5 +25,9 @@ endif
 OSARCH = $(shell uname -p)
 
 ifneq ($(OSARCH),powerpc)
-	CXXFLAGS += -msse
+	ifeq ($(USE_AESNI),yes)
+		CXXFLAGS += -D__AES__ -maes
+	else
+		CXXFLAGS += -msse
+	endif
 endif
diff --git a/Makefile.solaris b/Makefile.solaris
deleted file mode 100644
index 77d34114..00000000
--- a/Makefile.solaris
+++ /dev/null
@@ -1,9 +0,0 @@
-CXX = g++
-INCFLAGS = -I/usr/openssl/3/include
-CXXFLAGS := -Wall -std=c++20
-LDLIBS = -L/usr/openssl/3/lib/64 -lssl -lcrypto -lboost_program_options -lz -lpthread -lsocket
-
-ifeq ($(USE_UPNP),yes)
-	DEFINES += -DUSE_UPNP
-	LDLIBS += -lminiupnpc
-endif
\ No newline at end of file
diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp
index 0e29c517..9f750c4c 100644
--- a/Win32/Win32App.cpp
+++ b/Win32/Win32App.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2022, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -145,7 +145,7 @@ namespace win32
 		s << bytes << " Bytes\n";
 	}
 
-	static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, bool testing, RouterError error)
+	static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, bool testing)
 	{
 		switch (status)
 		{
@@ -158,24 +158,18 @@ namespace win32
 		};
 		if (testing)
 			s << " (Test)";
-		if (error != eRouterErrorNone)
+		if (i2p::context.GetError () != eRouterErrorNone)
 		{
-			switch (error)
+			switch (i2p::context.GetError ())
 			{
 				case eRouterErrorClockSkew:
-					s << " - " << tr("Clock skew");
+					s << " - Clock skew";
 				break;
 				case eRouterErrorOffline:
-					s << " - " << tr("Offline");
+					s << " - Offline";
 				break;
 				case eRouterErrorSymmetricNAT:
-					s << " - " << tr("Symmetric NAT");
-				break;
-				case eRouterErrorFullConeNAT:
-					s << " - " << tr("Full cone NAT");
-				break;
-				case eRouterErrorNoDescriptors:
-					s << " - " << tr("No Descriptors");
+					s << " - Symmetric NAT";
 				break;
 				default: ;
 			}
@@ -186,11 +180,11 @@ namespace win32
 	{
 		s << "\n";
 		s << "Status: ";
-		ShowNetworkStatus (s, i2p::context.GetStatus (), i2p::context.GetTesting(), i2p::context.GetError ());
+		ShowNetworkStatus (s, i2p::context.GetStatus (), i2p::context.GetTesting ());
 		if (i2p::context.SupportsV6 ())
 		{
 			s << " / ";
-			ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetTestingV6(), i2p::context.GetErrorV6 ());
+			ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetTestingV6 ());
 		}
 		s << "; ";
 		s << "Success Rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate() << "%\n";
@@ -313,7 +307,7 @@ namespace win32
 					}
 					case ID_DATADIR:
 					{
-						std::string datadir(i2p::fs::GetDataDir());
+						std::string datadir(i2p::fs::GetUTF8DataDir());
 						ShellExecute(NULL, "explore", datadir.c_str(), NULL, NULL, SW_SHOWNORMAL);
 						return 0;
 					}
@@ -355,7 +349,9 @@ namespace win32
 						}
 					}
 				}
+#if (__cplusplus >= 201703L) // C++ 17 or higher
 				[[fallthrough]];
+#endif
 			}
 			case WM_TRAYICON:
 			{
diff --git a/Win32/Win32NetState.cpp b/Win32/Win32NetState.cpp
index 4ef768c8..794dc4b9 100644
--- a/Win32/Win32NetState.cpp
+++ b/Win32/Win32NetState.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2020, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -73,24 +73,16 @@ void UnSubscribeFromEvents()
 		}
 
 		if (pNetEvent)
-		{
 			pNetEvent->Release();
-		}
 
 		if (pCPContainer)
-		{
 			pCPContainer->Release();
-		}
 
 		if (pNetworkListManager)
-		{
 			pNetworkListManager->Release();
-		}
 
 		if (pUnknown)
-		{
 			pUnknown->Release();
-		}
 
 		CoUninitialize();
 	}
diff --git a/Win32/Win32NetState.h b/Win32/Win32NetState.h
index c1f47a24..1414a324 100644
--- a/Win32/Win32NetState.h
+++ b/Win32/Win32NetState.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2020, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -15,11 +15,10 @@
 #include "Log.h"
 #include "Transports.h"
 
-class CNetworkListManagerEvent final : public INetworkListManagerEvents
+class CNetworkListManagerEvent : public INetworkListManagerEvents
 {
 public:
 	CNetworkListManagerEvent() : m_ref(1) { }
-	~CNetworkListManagerEvent() { }
 
 	HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
 	{
diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt
index bc936e18..232e427f 100644
--- a/build/CMakeLists.txt
+++ b/build/CMakeLists.txt
@@ -29,6 +29,7 @@ project(
 )
 
 # configurable options
+option(WITH_AESNI           "Use AES-NI instructions set"             ON)
 option(WITH_HARDENING       "Use hardening compiler flags"            OFF)
 option(WITH_LIBRARY         "Build library"                           ON)
 option(WITH_BINARY          "Build binary"                            ON)
@@ -155,6 +156,20 @@ else()
   endif()
   set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -ffunction-sections -fdata-sections")
   set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "-Wl,--gc-sections") # -flto is added from above
+
+  # check for c++17 & c++11 support
+  include(CheckCXXCompilerFlag)
+
+  CHECK_CXX_COMPILER_FLAG("-std=c++17" CXX17_SUPPORTED)
+  CHECK_CXX_COMPILER_FLAG("-std=c++11" CXX11_SUPPORTED)
+
+  if(CXX17_SUPPORTED)
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
+  elseif(CXX11_SUPPORTED)
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
+  else()
+    message(SEND_ERROR "C++17 nor C++11 standard not seems to be supported by compiler. Too old version?")
+  endif()
 endif()
 
 if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
@@ -184,6 +199,16 @@ if(UNIX)
   endif()
 endif()
 
+# Note: AES-NI and AVX is available on x86-based CPU's.
+# Here also ARM64 implementation, but currently we don't support it.
+# MSVC is not supported due to different ASM processing, so we hope OpenSSL has its own checks to run optimized code.
+if(WITH_AESNI AND (ARCHITECTURE MATCHES "x86_64" OR ARCHITECTURE MATCHES "i386"))
+  if(NOT MSVC)
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maes")
+  endif()
+  add_definitions(-D__AES__)
+endif()
+
 if(WITH_ADDRSANITIZER)
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
   set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
@@ -198,10 +223,6 @@ if(WITH_THREADSANITIZER)
   endif()
 endif()
 
-if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10.0 AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 8.0) # gcc 8-9
-    list(APPEND CMAKE_REQUIRED_LIBRARIES "stdc++fs")
-endif()
-
 # Use std::atomic instead of GCC builtins on macOS PowerPC:
 # For more information refer to: https://github.com/PurpleI2P/i2pd/issues/1726#issuecomment-1306335111
 # This has been fixed in Boost 1.81, nevertheless we retain the setting for the sake of compatibility.
@@ -256,14 +277,14 @@ else()
   if(NOT MSVC)
     set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
   endif()
-  add_definitions(-DBOOST_ATOMIC_DYN_LINK -DBOOST_SYSTEM_DYN_LINK -DBOOST_FILESYSTEM_DYN_LINK -DBOOST_PROGRAM_OPTIONS_DYN_LINK)
+  add_definitions(-DBOOST_ATOMIC_DYN_LINK -DBOOST_SYSTEM_DYN_LINK -DBOOST_FILESYSTEM_DYN_LINK -DBOOST_PROGRAM_OPTIONS_DYN_LINK -DBOOST_DATE_TIME_DYN_LINK -DBOOST_REGEX_DYN_LINK)
   if(WIN32)
     set(Boost_USE_STATIC_LIBS OFF)
     set(Boost_USE_STATIC_RUNTIME OFF)
   endif()
 endif()
 
-find_package(Boost REQUIRED COMPONENTS system filesystem program_options)
+find_package(Boost REQUIRED COMPONENTS system filesystem program_options date_time OPTIONAL_COMPONENTS atomic)
 if(NOT DEFINED Boost_FOUND)
   message(SEND_ERROR "Boost is not found, or your boost version was below 1.46. Please download Boost!")
 endif()
@@ -291,26 +312,6 @@ if(ZLIB_FOUND)
   link_directories(${ZLIB_ROOT}/lib)
 endif()
 
-# C++ standard to use, based on compiler and version of boost
-if(NOT MSVC)
-# check for c++20 & c++17 support
-  include(CheckCXXCompilerFlag)
-
-  if(Boost_VERSION VERSION_GREATER_EQUAL "1.83") # min boost version for c++20
-    CHECK_CXX_COMPILER_FLAG("-std=c++20" CXX20_SUPPORTED)
-  endif()
-  CHECK_CXX_COMPILER_FLAG("-std=c++17" CXX17_SUPPORTED)
- 
-
-  if(CXX20_SUPPORTED)
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20")
-  elseif(CXX17_SUPPORTED)
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
-  else()
-    message(SEND_ERROR "C++20 nor C++17 standard not seems to be supported by compiler. Too old version?")
-  endif()
-endif()
-
 # load includes
 include_directories(SYSTEM ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR})
 
@@ -321,9 +322,9 @@ message(STATUS "Compiler vendor    : ${CMAKE_CXX_COMPILER_ID}")
 message(STATUS "Compiler version   : ${CMAKE_CXX_COMPILER_VERSION}")
 message(STATUS "Compiler path      : ${CMAKE_CXX_COMPILER}")
 message(STATUS "Architecture       : ${ARCHITECTURE}")
-message(STATUS "Compiler flags     : ${CMAKE_CXX_FLAGS}")
 message(STATUS "Install prefix:    : ${CMAKE_INSTALL_PREFIX}")
 message(STATUS "Options:")
+message(STATUS "  AESNI            : ${WITH_AESNI}")
 message(STATUS "  HARDENING        : ${WITH_HARDENING}")
 message(STATUS "  LIBRARY          : ${WITH_LIBRARY}")
 message(STATUS "  BINARY           : ${WITH_BINARY}")
diff --git a/build/cmake_modules/CheckAtomic.cmake b/build/cmake_modules/CheckAtomic.cmake
index 4954e3e5..d5ec6a0a 100644
--- a/build/cmake_modules/CheckAtomic.cmake
+++ b/build/cmake_modules/CheckAtomic.cmake
@@ -8,7 +8,7 @@ INCLUDE(CheckLibraryExists)
 
 function(check_working_cxx_atomics varname)
   set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
-  set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++17")
+  set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++11")
   CHECK_CXX_SOURCE_COMPILES("
 #include <atomic>
 std::atomic<int> x;
@@ -25,7 +25,7 @@ endfunction(check_working_cxx_atomics)
 
 function(check_working_cxx_atomics64 varname)
   set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
-  set(CMAKE_REQUIRED_FLAGS "-std=c++17 ${CMAKE_REQUIRED_FLAGS}")
+  set(CMAKE_REQUIRED_FLAGS "-std=c++11 ${CMAKE_REQUIRED_FLAGS}")
   CHECK_CXX_SOURCE_COMPILES("
 #include <atomic>
 #include <cstdint>
diff --git a/build/cmake_modules/GetGitRevisionDescription.cmake b/build/cmake_modules/GetGitRevisionDescription.cmake
index a08895c6..4fbd90db 100644
--- a/build/cmake_modules/GetGitRevisionDescription.cmake
+++ b/build/cmake_modules/GetGitRevisionDescription.cmake
@@ -59,7 +59,7 @@ get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
 # function returns an empty string via _git_dir_var.
 #
 # Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and
-# neither foo nor bar contain a file/directory .git. This will return
+# neither foo nor bar contain a file/directory .git. This wil return
 # C:/bla/.git
 #
 function(_git_find_closest_git_dir _start_dir _git_dir_var)
diff --git a/build/win_installer.iss b/build/win_installer.iss
index a4b67ad2..cfeff812 100644
--- a/build/win_installer.iss
+++ b/build/win_installer.iss
@@ -24,7 +24,7 @@ ExtraDiskSpaceRequired=15
 
 AppID={{621A23E0-3CF4-4BD6-97BC-4835EA5206A2}
 AppVerName={#I2Pd_AppName}
-AppCopyright=Copyright (c) 2013-2024, The PurpleI2P Project
+AppCopyright=Copyright (c) 2013-2022, The PurpleI2P Project
 AppPublisherURL=http://i2pd.website/
 AppSupportURL=https://github.com/PurpleI2P/i2pd/issues
 AppUpdatesURL=https://github.com/PurpleI2P/i2pd/releases
diff --git a/contrib/apparmor/usr.bin.i2pd b/contrib/apparmor/usr.sbin.i2pd
similarity index 85%
rename from contrib/apparmor/usr.bin.i2pd
rename to contrib/apparmor/usr.sbin.i2pd
index 4d370f3c..1e47cd74 100644
--- a/contrib/apparmor/usr.bin.i2pd
+++ b/contrib/apparmor/usr.sbin.i2pd
@@ -4,7 +4,7 @@
 #
 #include <tunables/global>
 
-profile i2pd /{usr/,}bin/i2pd {
+profile i2pd /{usr/,}sbin/i2pd {
   #include <abstractions/base>
   #include <abstractions/openssl>
   #include <abstractions/nameservice>
@@ -14,12 +14,12 @@ profile i2pd /{usr/,}bin/i2pd {
   /var/lib/i2pd/** rw,
   /var/log/i2pd/i2pd.log w,
   /{var/,}run/i2pd/i2pd.pid rwk,
-  /{usr/,}bin/i2pd mr,
+  /{usr/,}sbin/i2pd mr,
   @{system_share_dirs}/i2pd/** r,
 
   # user homedir (if started not by init.d or systemd)
   owner @{HOME}/.i2pd/   rw,
   owner @{HOME}/.i2pd/** rwk,
 
-  #include if exists <local/usr.bin.i2pd>
+  #include if exists <local/usr.sbin.i2pd>
 }
diff --git a/contrib/certificates/reseed/admin_at_stormycloud.org.crt b/contrib/certificates/reseed/admin_at_stormycloud.org.crt
deleted file mode 100644
index ae44521b..00000000
--- a/contrib/certificates/reseed/admin_at_stormycloud.org.crt
+++ /dev/null
@@ -1,34 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIF1zCCA7+gAwIBAgIRAMDqFR09Xuj8ZUu+oetSvAEwDQYJKoZIhvcNAQELBQAw
-dTELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE
-ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxHjAcBgNVBAMM
-FWFkbWluQHN0b3JteWNsb3VkLm9yZzAeFw0yNDAxMjUxNDE1MzBaFw0zNDAxMjUx
-NDE1MzBaMHUxCzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgx
-HjAcBgNVBAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMR4w
-HAYDVQQDDBVhZG1pbkBzdG9ybXljbG91ZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUA
-A4ICDwAwggIKAoICAQDbGX+GikPzQXr9zvkrhfO9g0l49KHLNQhUKYqd6T+PfnGo
-Fm0d3ZZVVQZ045vWgroOXDGGZZWxUIlb2inRaR2DF1TxN3pPYt59RgY9ZQ9+TL7o
-isY91krCRygY8EcAmHIjlfZQ9dBVcL7CfyT0MYZA5Efee9+NDHSewTfQP9T2faIE
-83Fcyd93a2mIHYjKUbJnojng/wgsy8srbsEuuTok4MIQmDj+B5nz+za2FgI0/ydh
-srlMt4aGJF4/DIem9z9d0zBCOkwrmtFIzjNF1mOSA8ES4m5YnKA/y9rZlRidLPGu
-prbXhPVnqHeOnHMz2QCw1wbVo504kl0bMqyEz2tVWsO9ep7iZoQs2xkFAEaegYNT
-QLUpwVGlyuq3wXXwopFRffOSimGSazICwWI6j+K0pOtgefNJaWrqKYvtkj1SbK2L
-LBNUIENz6VnB7KPRckuX6zxC8PpOiBK9BcftfO+xAz/wC6qq3riBPw30KKSym0nC
-Zp5KciDn4Phtw9PGq8Bkl8SyWl0jtFnfTB1tzJkisf2qKcNHaFTEe2JW763YLbh/
-AU+8X8evFu40qLgvOgKoyy5DLy6i8zetX+3t9K0Fxt9+Vzzq6lm5V/RS8iIPPn+M
-q1/3Z5kD0KQBG9h/Gl8BH+lB71ZxPAOZ3SMu8DJZcxBLVmDWqQPCr5CKnoz0swID
-AQABo2IwYDAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsG
-AQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wHgYDVR0OBBcEFWFkbWluQHN0b3JteWNs
-b3VkLm9yZzANBgkqhkiG9w0BAQsFAAOCAgEARWOJ69vTHMneSXYscha+4Ytjg0RM
-faewJNEGj8qy/Qvh9si2bWYNPRK6BlbHFS7pRYBLAnhaeLBGVv1CCR6GUMMe74zQ
-UuMeAoWU6qMDmB3GfYoZJh8sIxpwHqyJeTdeccRbZ4sX4F6u3IHPXYiU/AgbYqH7
-pYXQg2lCjXZYaDFAlEf5SlYUDOhhXe5kR8Edhlrsu32/JzA1DQK0JjxKCBp+DQmA
-ltdOpQtAg03fHP4ssdj7VvjIDl28iIlATwBvHrdNm7T0tYWn6TWhvxbRqvfTxfaH
-MvxnPdIJwNP4/9TyQkwjwHb1h+ucho3CnxI/AxspdOvT1ElMhP6Ce6rcS9pk11Rl
-x0ChsqpWwDg7KYpg0qZFSKCTBp4zBq9xoMJ6BQcgMfyl736WbsCzFTEyfifp8beg
-NxUa/Qk7w7cuSPGyMIKNOmOR7FLlFbtocy8sXVsUQdqnp/edelufdNe39U9uNtY6
-yoXI9//Tc6NgOwy2Oyia0slZ5qHRkB7e4USXMRzJ3p4q9eCVKjAJs81Utp7O2U+9
-vhbhwWP8CAnNTT1E5WS6EKtfrdqF7wjkV+noPGLDGmrXi01J1fSMAjMfVO+7/LOL
-UN+G4ybKWnEhhOO27yidN8Xx6UrCS23DBlPPQAeA74dTsTExiOxf1o1EXzcQiMyO
-LAj3/Ojbi1xkWhI=
------END CERTIFICATE-----
diff --git a/contrib/certificates/reseed/hiduser0_at_mail.i2p.crt b/contrib/certificates/reseed/hiduser0_at_mail.i2p.crt
new file mode 100644
index 00000000..a332805a
--- /dev/null
+++ b/contrib/certificates/reseed/hiduser0_at_mail.i2p.crt
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFgTCCA2mgAwIBAgIETWAY1DANBgkqhkiG9w0BAQ0FADBxMQswCQYDVQQGEwJY
+WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5vbnlt
+b3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEaMBgGA1UEAwwRaGlkdXNlcjBAbWFp
+bC5pMnAwHhcNMjExMjEzMTU0MDI3WhcNMzExMjExMTU0MDI3WjBxMQswCQYDVQQG
+EwJYWDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5v
+bnltb3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEaMBgGA1UEAwwRaGlkdXNlcjBA
+bWFpbC5pMnAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXnjJ8UQ0f
+lHHpfPMiHofBPSuL4sbOJY6fOXwPhSg/h6THh9DS/ZWmJXQ3qRD0glDVtv4/Dr/9
+ldGQ5eltF9iCFXCQlMEy2HjQrBKq0nsl7RpYK12cyMaod0kkzCUk9ITLi9CmHM3Z
+gQZcmG8TWjFEpDR+idx/QkQt2pcO4vzWlDit3Vh4ivnbX5jGQHbsVjQEMQWxr+pX
+dsS+YQpjZ6RBmrooGTPO8QDOOeYLAn0lCjmffc/kzIH9E/p4/O0rOpyhVYbdxUD1
+5wkqN9l4yrtxmORG/PudnRQQ0r4TUq8vsxfGY0Euo9IbhgXF2Parel1ZhDxB1WZV
+VwWtgLIh9jGA1UMa8SYKnEfp8LWNZ3b3mUUnZb3kMrLk6jGYRWNsHmamhd4mC7AZ
+qf/8lOkEIw3bPd3YguCDRVcLui5BwIEZmqXg8uoESxfO/sW3pBrN/8M7MkTex9kN
+vjitGDDXvenK27qmNgZxbBlX72yTSfys7XTYTLnxZC8AwdAo2Wz9Z6HhGiPonf2h
+vZkc9ZxuE0jFIrsbJra4X7iyjXgi4vV4ARNg/9Ft6F4/OIbECgeDcBQqq4TlT2bZ
+EfWVrBbqXoj5vNsLigIkd+AyUNwPYEcB5IFSiiOh98pC7BH3pg0m8U5YBjxe1i+9
+EQOOG0Qtx+JigXZHu6bGE0Twy9zy+UzoKQIDAQABoyEwHzAdBgNVHQ4EFgQUGK1b
+0DkL6aLalcfBc/Uj/SF08C0wDQYJKoZIhvcNAQENBQADggIBAMpXM82bJDpH1TlH
+TvhU3Z7nfZdvEhOQfujaFUYiuNripuEKcFGn948+DvAG0FUN+uNlJoqOVs8D7InD
+gWlA9zpqw5Cl5Hij/Wns9QbXuAHJeA23fVUoaM2A6v9ifcIQ1A+rDuRQAo6/64KW
+ChTg2e99RBpfGOyqgeh7tLLe0lPPekVpKHFuXabokaKRDuBcVHcUL4tWXe3dcyqa
+Ej/PJrrS+nWL0EGZ4q80CEd2LPuDzPxNGCJt/R7ZfadENWajcgcXGceh1QBzozrB
+SL/Ya6wF9SrsB7V/r5wX0LM4ZdDaLWbtmUe5Op0h/ZMH25Sa8xAXVz+O9L6sWSoO
+FaiYTOvAiyyPz+nsxKa3xYryDHno7eKSt+hGOcaurhxbdZaEFY/CegEc73tCt9xK
+e9qF8O/WkDLmixuErw3f5en4IfzGR7p3lJAwW/8WD8C6HS39h/eE7dVZNaWgtQnZ
+SgGjgZMTJqTcQ3aZmfuCZefxGFok8w6AIkdbnd1pdMBRjYu8aXgl2hQSB9ZADDE9
+R5d3rXi0PkSFLIvsNjVa5KXrZk/tB0Hpfmepq7CufBqjP/LG9TieRoXzLYUKFF74
+QRwjP+y7AJ+VDUTpY1NV1P+k+2raubU2bOnLF3zL5DtyoyieGPhyeMMvp0fRIxdg
+bSl5VHgPXHNM8mcnndMAuzvl7jEK
+-----END CERTIFICATE-----
diff --git a/contrib/certificates/reseed/ls_at_mail.i2p.crt b/contrib/certificates/reseed/ls_at_mail.i2p.crt
new file mode 100644
index 00000000..8e0902d7
--- /dev/null
+++ b/contrib/certificates/reseed/ls_at_mail.i2p.crt
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFdTCCA12gAwIBAgIEQ5vCxzANBgkqhkiG9w0BAQ0FADBrMQswCQYDVQQGEwJY
+WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5vbnlt
+b3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEUMBIGA1UEAwwLbHNAbWFpbC5pMnAw
+HhcNMjMxMDE2MjAwNTA5WhcNMzMxMDEzMjAwNTA5WjBrMQswCQYDVQQGEwJYWDEL
+MAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5vbnltb3Vz
+IE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEUMBIGA1UEAwwLbHNAbWFpbC5pMnAwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDPcbKRtf4PzrDa0iRit0XrwnmA
+2c1fJhkBipdPor7gMOAlkR82H1lkZSizR7kTZnr7vYqjDrOQr7bl5Dy3qo8/YCbZ
+jsnUCTIIgIJQUxUlR40RjaSXphqzUEiXKHR6b0RahhFisQ3hlbbgzSch5YgSLKws
+hOLi+eDSXw+HlwHlWFlT1XOKxSTJ/F3Bv40gxqZVC2pbxiPOeRZHQ6Ojw75lxTSF
+gww2WzgztiWt4X9BO1yepnVqhAVRPmTfGUMfKzq9jkMzZKeQFV4uZSP9nCqzEpYd
+WNDUfpTWiAQ9F+BwFXGusXXA3tGVwS7s6IEoiJFM5fsoJYfRoWGh3/1eirhBXW7U
+M6oubMSTADyrvfjLfJBMmMnc2hNblRlKr0ZKUjMfv8cnyT4kQxlXLAHHXY2P89TM
+TEVODkU48gnv6tC4t1JCb1/Da+3yVMjNX6rCzQfUwnLFrWthrwiI0NivAKFtiZjq
+w1/ZQcYke2YyeqcfXMn+NTUA22Sm2mJoMo7jUf+rbM9Pi27/DncJgRGj5qwY0D3S
+gc7829EjuZNPttGBmae1EmO7WQMB32cqdmItnV2FXpMhnn9h0u5H52kYqwn+mdtc
+dTJRcbfKG1RTr3UjFISaTwL8qigMIkVXIzcpnr/R/sSeEs8xCqfsJ6rb4dCyFx+M
+hqQcOCL5tumyd4W/LQIDAQABoyEwHzAdBgNVHQ4EFgQUgfaOG5HCnlW82wZ5BahL
+GRO06igwDQYJKoZIhvcNAQENBQADggIBAKdVpqS9qF7gGotgXaVA1iP5YNsWlTvG
+daGqeA/87//U21W6gpq82FhzsmsvUtXZfIeVIlDPI7WNDzS+A3K/KKrwM7dLgSie
+r9eMl3D8WYPU95QF4mAlRyl7PCCsYoVjyvfro0iq3/iudIA5476rjfLdTXRi5hAT
+qemPj0S+6sRjKEldRtGXrQATFlvLIWVYpgHijdDDx5M2hAz2y0mFxlDZTlA4BhL4
+DwtGlVKmbc2x5MvIQM4UhbQqkxYS4gXnzf5Qx9QIytHfTr/hmbrkhKR1GCO31BSk
+x9LhZxdI8LlwKSo6YgwXEB9E0M/tplaK9iZJFv4HPYLZrVJpb4IklMumyLMrgW5P
+fR0dgKn+R9lk0emJ1Cu+qyyzf1vsLycYBwaEztINn4VK+/HfDFpnVCvJOyNuDmj5
+KBLIoGdGoVfylmnc+e8zAXe+DY41fgniHMISOO78P8Bx9vTB+rhqnOUr9MzlUxPB
+sKGjbXy2YynEqiGb+9g344v/+ukTSDenqTPHVzJ5uOi0iedy+3ASzUNN6GJocovP
+167VOhwaETM0FwiKe0VdZRLLbbZ79CtJC0tmgcgPQPRa9Ldr6KN7u1J3D6lUp6zl
+byPom10ueKONRb36t7ai79l2SEUZRSMkx6AXIU0JJ1SMtQtav7b5LkpYJfdL7+vO
+dDx2/Za0VmdD
+-----END CERTIFICATE-----
diff --git a/contrib/certificates/reseed/null_at_i2pmail.org.crt b/contrib/certificates/reseed/null_at_i2pmail.org.crt
new file mode 100644
index 00000000..d736a95e
--- /dev/null
+++ b/contrib/certificates/reseed/null_at_i2pmail.org.crt
@@ -0,0 +1,33 @@
+-----BEGIN CERTIFICATE-----
+MIIFyDCCA7CgAwIBAgIRAO8lBnTo+hlvglQwug2jHZkwDQYJKoZIhvcNAQELBQAw
+cDELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE
+ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxGTAXBgNVBAMM
+EG51bGxAaTJwbWFpbC5vcmcwHhcNMjMwOTIxMjIzMTM2WhcNMzMwOTIxMjIzMTM2
+WjBwMQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYD
+VQQKExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEZMBcGA1UE
+AwwQbnVsbEBpMnBtYWlsLm9yZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
+ggIBAMMpAvaHwzuZZ6qelRU4jcgpuAIZFH++F1Te4b1t02pRfnQ0Eeh04VC1JxO0
+XjUr1/iszEyvrI4+AdxaobDyRFPylkOLtfec4d2ciDc1cupj6y2vyYhMVN31rrvE
+ve7sKoTHJ5Dx+UPGOVZZsSsmK9TXIU23W2bo7k2VnjVBXdWZyNE4twfTYCosDnYA
+1HIEaIUFVv+COqw2pktxkMmfUAlnDLeVSfsAzEr37K+x0Xk5hO8m6GWQx0NRjjYp
+gyEcFhWAJjAYaF3gUVR9rVVky1OeFhZgxE/KzVrW7uc84ZCMKITEPwT0qqIpsTJp
+486YXzuPSc+ef78cKSQf5992l7imySJ24I/5H73HkovGAFGZdwvl6V6Ta5YqO7RR
+gVDOL1EIVUnMCqFBCE6RmyZqXBVrv4Cacdc6lZ4fj42SRtWZfe6rNCpJzTRtbOyW
+DBmYpK6q/jddfqI1sX0PXIn9U+Rod5Z4uz82PAjhamqyr5fpAnoQxKppBvQ3tNfn
+KQhmP73Hdpvl24pRyQLBIRUL86i7TPBBn7n3XZlQfXP7lp8+KJYLkL2/zCVDrwLX
+kC9hRIxCU9bZbXlkRE2R/PrK53LZecjk2KcgINA4ZlguNgze/Qj8BXelUF4izbpV
+bTSvniTM46AECvjDcICAOky9Ku4RnmUJxQVf3ahDEuso7/N7AgMBAAGjXTBbMA4G
+A1UdDwEB/wQEAwIChDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYD
+VR0TAQH/BAUwAwEB/zAZBgNVHQ4EEgQQbnVsbEBpMnBtYWlsLm9yZzANBgkqhkiG
+9w0BAQsFAAOCAgEAEUfYJTdDH7uCojnpF0Gs2tXxPJ22UhdqEsXfqR7KhhmmApss
+q5kiiPIYoy5T/4IM7NVyeeJAMYwQsdJjwZ4QyxLBb9EqMS2krREcPZNRfFzBr2Wj
+EBhJEYTnbIn4docwJWyXsJVG0CqFXPF1qGd0Sc2u87yj2xZNTnloWKAEQAO7DE39
+gWfDH6slM/3h3WD3Mjuk7JoYSYmBfvvm2hkBbC6lzD7XY7rdSmIUwJ050e9UrJaV
+La51dd5r4q8d1cHrVUwLiACAaXJ15AEqUDLHQcvKvyfhkabwRy+v0wsodSMgSMEH
+xA+kGhkIW7yV7o2exYOYypHCca3IA+pimMpEseNNrHSwbHOMfauiN7jiZLEPg6D6
+a8XwK7qmMYUq7j6QWuIqI81o29WZRf4LZ0GFoVce+e5VxkVKSItKcJoedIAp1ML8
+NhFwd9s/nqWidu/StscEEbGzz6ZuDXwshERXC0QR8HjHEPi4U8220juf4cxUahxK
+heEU91l7VksSZYRUN98h28vovGcukLcnVoLj5H/+Z4r/BgxMrOUJKetxf8fU7FjO
+j1U6XV36tGi+IOwYQb9D5fTVafC3hHkuUIjlOdUGYadse98ILhn9kaNtqkBtk/EU
+vK+McnrEv7tcKrbvYEop/KaUayhjFiL+wGWnpxt7gLhIiavnIeUyD7acltw=
+-----END CERTIFICATE-----
diff --git a/contrib/certificates/reseed/reheatedburger_at_protonmail.com.crt b/contrib/certificates/reseed/reheatedburger_at_protonmail.com.crt
new file mode 100644
index 00000000..d14c5fe1
--- /dev/null
+++ b/contrib/certificates/reseed/reheatedburger_at_protonmail.com.crt
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF7zCCA9egAwIBAgIRANVB/+wEuXS0Ttoh5teJt90wDQYJKoZIhvcNAQELBQAw
+fTELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE
+ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxJjAkBgNVBAMM
+HXJlaGVhdGVkYnVyZ2VyQHByb3Rvbm1haWwuY29tMB4XDTIzMDkyMTE4MDAyOVoX
+DTMzMDkyMTE4MDAyOVowfTELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYD
+VQQJEwJYWDEeMBwGA1UEChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQL
+EwNJMlAxJjAkBgNVBAMMHXJlaGVhdGVkYnVyZ2VyQHByb3Rvbm1haWwuY29tMIIC
+IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuNwmiIY3MLSBS5sL5PXRDVK6
+MoSNw4qx0o8nDHvVBxNtzgc0/qjYvsuUggY0tZbPpxhML6GHd4qo7Z3Ip1x0MxhI
+Ao5MJaflaEdm4+HeMy0IE3aU73KRUwp+nF3cUHZdlps+9mtYs4oncVEWkFQwGsgt
+4yrLtXf6PmPWfFH28ffeaev90e+hdhQpTvr54Ewx6NTaMQr8mkhXL2utvPpjnPM5
+UAhOeJCMgfhLzgS4rahG0O8CQMtH5gKZ+6zjoSRatnjj0j1mBO7+e1TL5O7dVS9k
+P83tmkIDDl4tXBzXr9aXQMJstbM2CEvinVcCsR74GjPcg4iB0Ift71Dx7oGKI06t
+3bSvll0GZm2mFhIba/4q6f4oAJ2aeq6ejt1Kcm8g5cxtwrRZnXv5JXHZqba3y8J5
+zWaRHzhc9tyEqRBRkc6c7xMdZQ31iJ6TlxUT8vAJ1N7OnX87oHrCjwyikpyOen4r
+Uvv1Ge054XPTeoHz+Jyt34t71ty1W13uPHpuvtPVR9MfgGrxd4Z9+LWvAjmMbFsZ
+lC3Ll+94nUk+O0puU6KisuCGP4hCtdEtebkIqT8zo8LicLAYUMjX7KwnS7681Cu1
+sY2mB2oZAytN9Zy42oOoNeY5x39kxfwuut/2E1kxKX75O0bwfIXr611abCKc3bbz
+euMrIsaB/2VFp9nAah8CAwEAAaNqMGgwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQW
+MBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCYGA1UdDgQf
+BB1yZWhlYXRlZGJ1cmdlckBwcm90b25tYWlsLmNvbTANBgkqhkiG9w0BAQsFAAOC
+AgEATuHi2Yz52OK7e+sKVdHu2KrSLCGm98BG1UIMHFi3WRBTOFyp+lZ519bJ1rFj
+tmP9E1a+k/vlbc7FbV4PcV6HJYfGEv/ImtJsEnrzbhrQphC1zMFv7q6JCTUbAzl6
+ySlJ++mVxQ6AzPNH3TQgL1wPKuLh76/Y4053fg+NI3PmzzhkTUheVDkg0/a9ENSf
+xMnCa3fIm869735qHk67QlikFvAfWwc4zT1Ncwodh8G4+oX0GFzIl+OZaM1GTMuD
+UCcFKoqwtjyLCr22xNk8CfyiExPJXQG1HzEvDcxyoxQtnh9occR9PgqXySz26/NM
+XDyM+l4utLMGBcVY4x9fksRiaWEfxiygYOxY9zDl6clh6S10b3CLut4UMiS1RTtE
+Mjx2BZN3p0nxpT2leJdGxtBPGrvxuiCOEmTbOMLc3DQtppXO97B3dVMtJ5Ee8Y6p
+Tq/8eiHI6eQXat6dgFT5X16vzF7w7XO7fAxuqk4Kx1D1aTVyikdo+Fcdg44dWOjq
+NZu8VcCzZij/Dfjlce6t6h8D+wvDD8AkiivaDljpvbNDx/QQlQXFgH98TZA8Rnvr
+QcyNNATfz+1yQUiyO6Lrjaw64OJwXYX/llgnDC+qQpP6kqZabi2TsG0EVPukVvr9
+0HyAUu4lnXtTIDq2yPNenegCloqDL1ZQdaYd2XIItnfZdTY=
+-----END CERTIFICATE-----
diff --git a/contrib/certificates/reseed/unixeno_at_cubicchaos.net.crt b/contrib/certificates/reseed/unixeno_at_cubicchaos.net.crt
deleted file mode 100644
index c94d319e..00000000
--- a/contrib/certificates/reseed/unixeno_at_cubicchaos.net.crt
+++ /dev/null
@@ -1,34 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIF2TCCA8GgAwIBAgIQVpTNnJZlUTDqmZiHRU4wCjANBgkqhkiG9w0BAQsFADB2
-MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK
-ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEfMB0GA1UEAwwW
-dW5peGVub0BjdWJpY2NoYW9zLm5ldDAeFw0yNTAzMDQxODU5NDZaFw0zNTAzMDQx
-ODU5NDZaMHYxCzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgx
-HjAcBgNVBAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMR8w
-HQYDVQQDDBZ1bml4ZW5vQGN1YmljY2hhb3MubmV0MIICIjANBgkqhkiG9w0BAQEF
-AAOCAg8AMIICCgKCAgEAr/JoAzLDtHXoAc7QcP4IxO+xNTeiYs78Wlg/Sl/sa6qz
-gJoGaKH/X++z4Xe9lBSZalXCamnO4QMTgsWOIeoMy6XVbGzNTXPl8JUcblTIXwkP
-pv848b1nxLfgLHzPRz1mJMpMikBugJ3Iz1sQzDVlUdye2fgbGChWliz9P4ClEODv
-A/4+7i6uvJgEZ7A+jx3vBCXhiJppE3wTuz5D9BQqG8NuEwwjwBTBByoCC4oxOe0h
-Qu1k7kEr+n4qpSEg/1eJ/RYSm+I8BftK1RUfykTwxlfmyEmKsfLBQWczE8Ca9nUB
-5V34UH2bRy1cvavJYcNW3EPsGNf4naRs+Gy8XIFrb315GgWC1Z6+tzk+QFli9YeF
-0DgtYEZciqu/407o8ZEURTnPjB7GhLDDp1LAQ7CQRhzaraXjHj0hyO+6rFpFdD0D
-mXhvI/Eph3QIldsgnQc7nPhU2csN8Vi6bNDgm0HZ8cdmIBpI2Uxn/acZX/9G40oj
-czrhsCBEecu/BluLJsfaWCYg90rvONP8Fc4edHAMonzYZR4r0q4hbv7AM8GmDRDN
-J9/DZFi+Qs9NAe06jJC3jSsj7IdIs8TMhw8FX3xWOlXwjmVETAgY/dta/MpLJ6tJ
-i+E+TH/Ndntj/D6WUwdQq+LyCR6gqHUWR6rl6EDQz+08DWb7j/72JSLb/DaXrDUC
-AwEAAaNjMGEwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr
-BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB8GA1UdDgQYBBZ1bml4ZW5vQGN1Ymlj
-Y2hhb3MubmV0MA0GCSqGSIb3DQEBCwUAA4ICAQBBVoPeuOkmJDUdzIrzmxTmjMyz
-gpfrZnjirTQKWhbv53sAWNeJ3kZ9l9m+0YpqEtFDrZPL5LTBXcSci5gGuxPkp+i/
-f/axsdcFMKbI9B/M53fyXLLJY0EM4mdhNAWtph1kTowFPhhReefCdqxYIy9uk2pL
-gfb6NYJf+w9//fKYFZXb9SsiRchfv81+lbN+PIprnCpV3cTZWmpLRi2hN86pMW20
-3rh7rqJ4dPnA/NoyM+JstL10IU/4StqInweEvoo4W44+cC0zYGvfkkrKL4LB8w5S
-6DKebtk7NQDtzuw2QnA9Ms4bmqWQpbL6/7uGaauS0+nmF+2gkqi9hcv0W5ZoBb/D
-IVRauySnCtp9PbYM7pIJP9a1U6naLj7L1VixqsJGfPQ8V9TCOOi5bDc3RTetI/DX
-bXHhAqHYzboakptylCp+Ao5h2hu0+w4rqnG63HwsHDJWcETbdVFQfzlzUmbx53yV
-GnBsUxDgMOiHTZdKLkEnH4Q/XI76uc0ntTRlK9ktKWZPSISUlHrFnFl6I5UdeBMy
-6vpB9sJO5L5RPRi4945K5Xdennywdi508mNXtMMmNCqrk1SMYbwaY6ZtIvXEGam9
-uHQTiTEX9LED/VXzFGqzdyDbG43HgS0PksgzedelHWfVAEnc06U3JX2lqUyihYHa
-N4jAXWQ7s5p4GYaf4Q==
------END CERTIFICATE-----
diff --git a/contrib/debian/trusty/patches/01-upnp.patch b/contrib/debian/trusty/patches/01-upnp.patch
index 74d36c06..bec8f2b0 100644
--- a/contrib/debian/trusty/patches/01-upnp.patch
+++ b/contrib/debian/trusty/patches/01-upnp.patch
@@ -2,13 +2,13 @@ Description: Enable UPnP usage in package
 Author: r4sas <r4sas@i2pmail.org>
 
 Reviewed-By: r4sas <r4sas@i2pmail.org>
-Last-Update: 2024-12-30
+Last-Update: 2022-03-23
 
 --- i2pd.orig/Makefile
 +++ i2pd/Makefile
-@@ -31,7 +31,7 @@ # import source files lists
- include filelist.mk
+@@ -31,7 +31,7 @@ include filelist.mk
  
+ USE_AESNI       := $(or $(USE_AESNI),yes)
  USE_STATIC      := $(or $(USE_STATIC),no)
 -USE_UPNP        := $(or $(USE_UPNP),no)
 +USE_UPNP        := $(or $(USE_UPNP),yes)
diff --git a/contrib/debian/trusty/patches/02-service.patch b/contrib/debian/trusty/patches/02-service.patch
index 546e252f..12b35525 100644
--- a/contrib/debian/trusty/patches/02-service.patch
+++ b/contrib/debian/trusty/patches/02-service.patch
@@ -2,7 +2,7 @@ Description: Disable LogsDirectory and LogsDirectoryMode options in service
 Author: r4sas <r4sas@i2pmail.org>
 
 Reviewed-By: r4sas <r4sas@i2pmail.org>
-Last-Update: 2024-07-19
+Last-Update: 2023-05-17
 
 --- a/contrib/i2pd.service
 +++ b/contrib/i2pd.service
@@ -15,5 +15,5 @@ Last-Update: 2024-07-19
 +#LogsDirectory=i2pd
 +#LogsDirectoryMode=0700
  Type=forking
- ExecStart=/usr/bin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --tunnelsdir=/etc/i2pd/tunnels.conf.d --pidfile=/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service
+ ExecStart=/usr/sbin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --tunnelsdir=/etc/i2pd/tunnels.conf.d --pidfile=/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service
  ExecReload=/bin/sh -c "kill -HUP $MAINPID"
diff --git a/contrib/debian/xenial/patches/01-upnp.patch b/contrib/debian/xenial/patches/01-upnp.patch
index 74d36c06..bec8f2b0 100644
--- a/contrib/debian/xenial/patches/01-upnp.patch
+++ b/contrib/debian/xenial/patches/01-upnp.patch
@@ -2,13 +2,13 @@ Description: Enable UPnP usage in package
 Author: r4sas <r4sas@i2pmail.org>
 
 Reviewed-By: r4sas <r4sas@i2pmail.org>
-Last-Update: 2024-12-30
+Last-Update: 2022-03-23
 
 --- i2pd.orig/Makefile
 +++ i2pd/Makefile
-@@ -31,7 +31,7 @@ # import source files lists
- include filelist.mk
+@@ -31,7 +31,7 @@ include filelist.mk
  
+ USE_AESNI       := $(or $(USE_AESNI),yes)
  USE_STATIC      := $(or $(USE_STATIC),no)
 -USE_UPNP        := $(or $(USE_UPNP),no)
 +USE_UPNP        := $(or $(USE_UPNP),yes)
diff --git a/contrib/debian/xenial/patches/02-service.patch b/contrib/debian/xenial/patches/02-service.patch
index 546e252f..12b35525 100644
--- a/contrib/debian/xenial/patches/02-service.patch
+++ b/contrib/debian/xenial/patches/02-service.patch
@@ -2,7 +2,7 @@ Description: Disable LogsDirectory and LogsDirectoryMode options in service
 Author: r4sas <r4sas@i2pmail.org>
 
 Reviewed-By: r4sas <r4sas@i2pmail.org>
-Last-Update: 2024-07-19
+Last-Update: 2023-05-17
 
 --- a/contrib/i2pd.service
 +++ b/contrib/i2pd.service
@@ -15,5 +15,5 @@ Last-Update: 2024-07-19
 +#LogsDirectory=i2pd
 +#LogsDirectoryMode=0700
  Type=forking
- ExecStart=/usr/bin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --tunnelsdir=/etc/i2pd/tunnels.conf.d --pidfile=/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service
+ ExecStart=/usr/sbin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --tunnelsdir=/etc/i2pd/tunnels.conf.d --pidfile=/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service
  ExecReload=/bin/sh -c "kill -HUP $MAINPID"
diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf
index c26f8af0..be4a6719 100644
--- a/contrib/i2pd.conf
+++ b/contrib/i2pd.conf
@@ -243,7 +243,7 @@ verify = true
 ## Default: reg.i2p at "mainline" I2P Network
 # defaulturl = http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt
 ## Optional subscriptions URLs, separated by comma
-# subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt
+# subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt,http://rus.i2p/hosts.txt
 
 [limits]
 ## Maximum active transit sessions (default: 5000)
@@ -277,3 +277,9 @@ verify = true
 ## Save full addresses on disk (default: true)
 # addressbook = true
 
+[cpuext]
+## Use CPU AES-NI instructions set when work with cryptography when available (default: true)
+# aesni = true
+## Force usage of CPU instructions set, even if they not found (default: false)
+## DO NOT TOUCH that option if you really don't know what are you doing!
+# force = false
diff --git a/contrib/i2pd.service b/contrib/i2pd.service
index 1ab46979..79bf15c8 100644
--- a/contrib/i2pd.service
+++ b/contrib/i2pd.service
@@ -1,8 +1,7 @@
 [Unit]
 Description=I2P Router written in C++
 Documentation=man:i2pd(1) https://i2pd.readthedocs.io/en/latest/
-Wants=network.target
-After=network.target network-online.target
+After=network.target
 
 [Service]
 User=i2pd
@@ -12,7 +11,7 @@ RuntimeDirectoryMode=0700
 LogsDirectory=i2pd
 LogsDirectoryMode=0700
 Type=forking
-ExecStart=/usr/bin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --tunnelsdir=/etc/i2pd/tunnels.conf.d --pidfile=/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service
+ExecStart=/usr/sbin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --tunnelsdir=/etc/i2pd/tunnels.conf.d --pidfile=/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service
 ExecReload=/bin/sh -c "kill -HUP $MAINPID"
 PIDFile=/run/i2pd/i2pd.pid
 ### Uncomment, if auto restart needed
diff --git a/contrib/openrc/i2pd.openrc b/contrib/openrc/i2pd.openrc
index 0233eed8..deca4625 100644
--- a/contrib/openrc/i2pd.openrc
+++ b/contrib/openrc/i2pd.openrc
@@ -7,7 +7,7 @@ tunconf="/etc/i2pd/tunnels.conf"
 tundir="/etc/i2pd/tunnels.conf.d"
 
 name="i2pd"
-command="/usr/bin/i2pd"
+command="/usr/sbin/i2pd"
 command_args="--service --daemon --log=file --logfile=$logfile --conf=$mainconf --tunconf=$tunconf --tunnelsdir=$tundir --pidfile=$pidfile"
 description="i2p router written in C++"
 required_dirs="/var/lib/i2pd"
diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec
index 2083ba18..0bc46eab 100644
--- a/contrib/rpm/i2pd-git.spec
+++ b/contrib/rpm/i2pd-git.spec
@@ -1,7 +1,7 @@
 %define git_hash %(git rev-parse HEAD | cut -c -7)
 
 Name:          i2pd-git
-Version:       2.56.0
+Version:       2.50.2
 Release:       git%{git_hash}%{?dist}
 Summary:       I2P router written in C++
 Conflicts:     i2pd
@@ -24,10 +24,6 @@ BuildRequires: openssl-devel
 BuildRequires: miniupnpc-devel
 BuildRequires: systemd-units
 
-%if 0%{?fedora} == 41
-BuildRequires: openssl-devel-engine
-%endif
-
 Requires:      logrotate
 Requires:      systemd
 Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd
@@ -97,7 +93,7 @@ pushd build
 %endif
 
 chrpath -d i2pd
-%{__install} -D -m 755 i2pd %{buildroot}%{_bindir}/i2pd
+%{__install} -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd
 %{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd
 %{__install} -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd
 %{__install} -d -m 700 %{buildroot}%{_localstatedir}/log/i2pd
@@ -133,7 +129,7 @@ getent passwd i2pd >/dev/null || \
 
 %files
 %doc LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf contrib/tunnels.d
-%{_bindir}/i2pd
+%{_sbindir}/i2pd
 %config(noreplace) %{_sysconfdir}/i2pd/*.conf
 %config(noreplace) %{_sysconfdir}/i2pd/tunnels.conf.d/*.conf
 %config %{_sysconfdir}/i2pd/subscriptions.txt
@@ -148,27 +144,6 @@ getent passwd i2pd >/dev/null || \
 
 
 %changelog
-* Tue Feb 11 2025 orignal <orignal@i2pmail.org> - 2.56.0
-- update to 2.56.0
-
-* Mon Dec 30 2024 orignal <orignal@i2pmail.org> - 2.55.0
-- update to 2.55.0
-
-* Sun Oct 6 2024 orignal <orignal@i2pmail.org> - 2.54.0
-- update to 2.54.0
-
-* Tue Jul 30 2024 orignal <orignal@i2pmail.org> - 2.53.1
-- update to 2.53.1
-
-* Fri Jul 19 2024 orignal <orignal@i2pmail.org> - 2.53.0
-- update to 2.53.0
-
-* Sun May 12 2024 orignal <orignal@i2pmail.org> - 2.52.0
-- update to 2.52.0
-
-* Sat Apr 06 2024 orignal <orignal@i2pmail.org> - 2.51.0
-- update to 2.51.0
-
 * Sat Jan 06 2024 orignal <orignal@i2pmail.org> - 2.50.2
 - update to 2.50.2
 
diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec
index 4eb558ba..1de4076e 100644
--- a/contrib/rpm/i2pd.spec
+++ b/contrib/rpm/i2pd.spec
@@ -1,5 +1,5 @@
 Name:          i2pd
-Version:       2.56.0
+Version:       2.50.2
 Release:       1%{?dist}
 Summary:       I2P router written in C++
 Conflicts:     i2pd-git
@@ -22,10 +22,6 @@ BuildRequires: openssl-devel
 BuildRequires: miniupnpc-devel
 BuildRequires: systemd-units
 
-%if 0%{?fedora} == 41
-BuildRequires: openssl-devel-engine
-%endif
-
 Requires:      logrotate
 Requires:      systemd
 Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd
@@ -95,7 +91,7 @@ pushd build
 %endif
 
 chrpath -d i2pd
-%{__install} -D -m 755 i2pd %{buildroot}%{_bindir}/i2pd
+%{__install} -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd
 %{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd
 %{__install} -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd
 %{__install} -d -m 700 %{buildroot}%{_localstatedir}/log/i2pd
@@ -131,7 +127,7 @@ getent passwd i2pd >/dev/null || \
 
 %files
 %doc LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf contrib/tunnels.d
-%{_bindir}/i2pd
+%{_sbindir}/i2pd
 %config(noreplace) %{_sysconfdir}/i2pd/*.conf
 %config(noreplace) %{_sysconfdir}/i2pd/tunnels.conf.d/*.conf
 %config %{_sysconfdir}/i2pd/subscriptions.txt
@@ -146,27 +142,6 @@ getent passwd i2pd >/dev/null || \
 
 
 %changelog
-* Tue Feb 11 2025 orignal <orignal@i2pmail.org> - 2.56.0
-- update to 2.56.0
-
-* Mon Dec 30 2024 orignal <orignal@i2pmail.org> - 2.55.0
-- update to 2.55.0
-
-* Sun Oct 6 2024 orignal <orignal@i2pmail.org> - 2.54.0
-- update to 2.54.0
-
-* Tue Jul 30 2024 orignal <orignal@i2pmail.org> - 2.53.1
-- update to 2.53.1
-
-* Fri Jul 19 2024 orignal <orignal@i2pmail.org> - 2.53.0
-- update to 2.53.0
-
-* Sun May 12 2024 orignal <orignal@i2pmail.org> - 2.52.0
-- update to 2.52.0
-
-* Sat Apr 06 2024 orignal <orignal@i2pmail.org> - 2.51.0
-- update to 2.51.0
-
 * Sat Jan 06 2024 orignal <orignal@i2pmail.org> - 2.50.2
 - update to 2.50.2
 
diff --git a/contrib/tunnels.conf b/contrib/tunnels.conf
index fc455e79..55723c43 100644
--- a/contrib/tunnels.conf
+++ b/contrib/tunnels.conf
@@ -5,7 +5,6 @@ port = 6668
 destination = irc.ilita.i2p
 destinationport = 6667
 keys = irc-keys.dat
-i2p.streaming.profile=2
 
 #[IRC-IRC2P]
 #type = client
diff --git a/contrib/upstart/i2pd.upstart b/contrib/upstart/i2pd.upstart
index d2cd4d5e..19b58958 100644
--- a/contrib/upstart/i2pd.upstart
+++ b/contrib/upstart/i2pd.upstart
@@ -8,4 +8,4 @@ env LOGFILE="/var/log/i2pd/i2pd.log"
 
 expect fork
 
-exec /usr/bin/i2pd --daemon --service --log=file --logfile=$LOGFILE
+exec /usr/sbin/i2pd --daemon --service --log=file --logfile=$LOGFILE
diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp
index e2fdf2d4..f04236fe 100644
--- a/daemon/Daemon.cpp
+++ b/daemon/Daemon.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -149,19 +149,17 @@ namespace util
 		LogPrint(eLogDebug, "FS: Certificates directory: ", certsdir);
 
 		bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation);
+		bool aesni; i2p::config::GetOption("cpuext.aesni", aesni);
+		bool forceCpuExt; i2p::config::GetOption("cpuext.force", forceCpuExt);
 		bool ssu; i2p::config::GetOption("ssu", ssu);
 		if (!ssu && i2p::config::IsDefault ("precomputation.elgamal"))
 			precomputation = false; // we don't elgamal table if no ssu, unless it's specified explicitly
-		i2p::crypto::InitCrypto (precomputation);
+		i2p::crypto::InitCrypto (precomputation, aesni, forceCpuExt);
 
 		i2p::transport::InitAddressFromIface (); // get address4/6 from interfaces
 
 		int netID; i2p::config::GetOption("netid", netID);
 		i2p::context.SetNetID (netID);
-
-		bool checkReserved; i2p::config::GetOption("reservedrange", checkReserved);
-		i2p::transport::transports.SetCheckReserved(checkReserved);
-
 		i2p::context.Init ();
 
 		i2p::transport::InitTransports ();
@@ -177,7 +175,7 @@ namespace util
 
 		bool transit; i2p::config::GetOption("notransit", transit);
 		i2p::context.SetAcceptsTunnels (!transit);
-		uint32_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels);
+		uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels);
 		if (isFloodfill && i2p::config::IsDefault ("limits.transittunnels"))
 			transitTunnels *= 2; // double default number of transit tunnels for floodfill
 		i2p::tunnel::tunnels.SetMaxNumTransitTunnels (transitTunnels);
@@ -186,7 +184,7 @@ namespace util
 		std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth);
 		if (bandwidth.length () > 0)
 		{
-			if (bandwidth.length () == 1 && ((bandwidth[0] >= 'K' && bandwidth[0] <= 'P') || bandwidth[0] == 'X' ))
+			if (bandwidth[0] >= 'K' && bandwidth[0] <= 'X')
 			{
 				i2p::context.SetBandwidth (bandwidth[0]);
 				LogPrint(eLogInfo, "Daemon: Bandwidth set to ", i2p::context.GetBandwidthLimit (), "KBps");
@@ -300,10 +298,12 @@ namespace util
 
 		bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2);
 		bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2);
+		bool checkInReserved; i2p::config::GetOption("reservedrange", checkInReserved);
 		LogPrint(eLogInfo, "Daemon: Starting Transports");
 		if(!ssu2) LogPrint(eLogInfo, "Daemon: SSU2 disabled");
 		if(!ntcp2) LogPrint(eLogInfo, "Daemon: NTCP2 disabled");
 
+		i2p::transport::transports.SetCheckReserved(checkInReserved);
 		i2p::transport::transports.Start(ntcp2, ssu2);
 		if (i2p::transport::transports.IsBoundSSU2() || i2p::transport::transports.IsBoundNTCP2())
 			LogPrint(eLogInfo, "Daemon: Transports started");
diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp
index dca545fe..2d6800b4 100644
--- a/daemon/HTTPServer.cpp
+++ b/daemon/HTTPServer.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -132,22 +132,25 @@ namespace http {
 
 	static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes)
 	{
-		std::string state;
-		std::string_view stateText;
-		switch (eState) 
-		{
+		std::string state, stateText;
+		switch (eState) {
 			case i2p::tunnel::eTunnelStateBuildReplyReceived :
 			case i2p::tunnel::eTunnelStatePending     : state = "building";    break;
-			case i2p::tunnel::eTunnelStateBuildFailed : state = "failed"; stateText = "declined"; break;
-			case i2p::tunnel::eTunnelStateTestFailed  : state = "failed"; stateText = "test failed";  break;
+			case i2p::tunnel::eTunnelStateBuildFailed :
+			case i2p::tunnel::eTunnelStateTestFailed  :
 			case i2p::tunnel::eTunnelStateFailed      : state = "failed";      break;
 			case i2p::tunnel::eTunnelStateExpiring    : state = "expiring";    break;
 			case i2p::tunnel::eTunnelStateEstablished : state = "established"; break;
 			default: state = "unknown"; break;
 		}
-		if (stateText.empty ()) stateText = tr(state);
-		
-		s << "<span class=\"tunnel " << state << "\"> " << stateText << ((explr) ? " (" + std::string(tr("exploratory")) + ")" : "") << "</span>, "; // TODO:
+
+		if      (state == "building")    stateText = tr("building");
+		else if (state == "failed")      stateText = tr("failed");
+		else if (state == "expiring")    stateText = tr("expiring");
+		else if (state == "established") stateText = tr("established");
+		else stateText = tr("unknown");
+
+		s << "<span class=\"tunnel " << state << "\"> " << stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << "</span>, ";
 		ShowTraffic(s, bytes);
 		s << "\r\n";
 	}
@@ -214,7 +217,7 @@ namespace http {
 			"</html>\r\n";
 	}
 
-	static void ShowError(std::stringstream& s, std::string_view string)
+	static void ShowError(std::stringstream& s, const std::string& string)
 	{
 		s << "<b>" << tr("ERROR") << ":</b>&nbsp;" << string << "<br>\r\n";
 	}
@@ -418,15 +421,6 @@ namespace http {
 		}
 	}
 
-	static void ShowHop(std::stringstream& s, const i2p::data::IdentityEx& ident)
-	{
-		auto identHash = ident.GetIdentHash();
-		auto router = i2p::data::netdb.FindRouter(identHash);
-		s << i2p::data::GetIdentHashAbbreviation(identHash);
-		if (router)
-			s << "<small style=\"color:gray\"> " << router->GetBandwidthCap() << "</small>";
-	}
-
 	static void ShowLeaseSetDestination (std::stringstream& s, std::shared_ptr<const i2p::client::LeaseSetDestination> dest, uint32_t token)
 	{
 		s << "<b>Base32:</b><br>\r\n<textarea readonly cols=\"80\" rows=\"1\">";
@@ -492,9 +486,7 @@ namespace http {
 					it->VisitTunnelHops(
 						[&s](std::shared_ptr<const i2p::data::IdentityEx> hopIdent)
 						{
-							s << "&#8658; ";
-							ShowHop(s, *hopIdent);
-							s << " ";
+							s << "&#8658; " << i2p::data::GetIdentHashAbbreviation (hopIdent->GetIdentHash ()) << " ";
 						}
 					);
 				}
@@ -515,9 +507,7 @@ namespace http {
 					it->VisitTunnelHops(
 						[&s](std::shared_ptr<const i2p::data::IdentityEx> hopIdent)
 						{
-							s << " ";
-							ShowHop(s, *hopIdent);
-							s << " &#8658;";
+							s << " " << i2p::data::GetIdentHashAbbreviation (hopIdent->GetIdentHash ()) << " &#8658;";
 						}
 					);
 				}
@@ -702,7 +692,6 @@ namespace http {
 	{
 		s << "<b>" << tr("Tunnels") << ":</b><br>\r\n";
 		s << "<b>" << tr("Queue size") << ":</b> " << i2p::tunnel::tunnels.GetQueueSize () << "<br>\r\n<br>\r\n";
-		s << "<b>" << tr("TBM Queue size") << ":</b> " << i2p::tunnel::tunnels.GetTBMQueueSize () << "<br>\r\n<br>\r\n";
 
 		auto ExplPool = i2p::tunnel::tunnels.GetExploratoryPool ();
 
@@ -714,9 +703,7 @@ namespace http {
 				it->VisitTunnelHops(
 					[&s](std::shared_ptr<const i2p::data::IdentityEx> hopIdent)
 					{
-						s << "&#8658; ";
-						ShowHop(s, *hopIdent);
-						s << " ";
+						s << "&#8658; " << i2p::data::GetIdentHashAbbreviation (hopIdent->GetIdentHash ()) << " ";
 					}
 				);
 			}
@@ -737,9 +724,7 @@ namespace http {
 				it->VisitTunnelHops(
 					[&s](std::shared_ptr<const i2p::data::IdentityEx> hopIdent)
 					{
-						s << " ";
-						ShowHop(s, *hopIdent);
-						s << " &#8658;";
+						s << " " << i2p::data::GetIdentHashAbbreviation (hopIdent->GetIdentHash ()) << " &#8658;";
 					}
 				);
 			}
@@ -791,7 +776,7 @@ namespace http {
 		s << "  <a class=\"button" << (loglevel == eLogInfo     ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=info&token=" << token << "\"> info </a> \r\n";
 		s << "  <a class=\"button" << (loglevel == eLogDebug    ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=debug&token=" << token << "\"> debug </a><br>\r\n<br>\r\n";
 
-		uint32_t maxTunnels = i2p::tunnel::tunnels.GetMaxNumTransitTunnels ();
+		uint16_t maxTunnels = i2p::tunnel::tunnels.GetMaxNumTransitTunnels ();
 		s << "<b>" << tr("Transit tunnels limit") << "</b><br>\r\n";
 		s << "<form method=\"get\" action=\"" << webroot << "\">\r\n";
 		s << "  <input type=\"hidden\" name=\"cmd\" value=\"" << HTTP_COMMAND_LIMITTRANSIT << "\">\r\n";
@@ -827,7 +812,7 @@ namespace http {
 		if (i2p::tunnel::tunnels.CountTransitTunnels())
 		{
 			s << "<b>" << tr("Transit Tunnels") << ":</b><br>\r\n";
-			s << "<table><thead><th>&#8658;</th><th>ID</th><th>&#8658;</th><th>" << tr("Amount") << "</th><th>" << tr("Next") << "</th></thead><tbody class=\"tableitem\">";
+			s << "<table><thead><th>&#8658;</th><th>ID</th><th>&#8658;</th><th>" << tr("Amount") << "</th></thead><tbody class=\"tableitem\">";
 			for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ())
 			{
 				if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelGateway>(it))
@@ -837,7 +822,7 @@ namespace http {
 				else
 					s << "<tr><td>&#8658;</td><td>" << it->GetTunnelID () << "</td><td>&#8658;</td><td>";
 				ShowTraffic(s, it->GetNumTransmittedBytes ());
-				s << "</td><td>" << it->GetNextPeerName () << "</td></tr>\r\n";
+				s << "</td></tr>\r\n";
 			}
 			s << "</tbody></table>\r\n";
 		}
@@ -1263,7 +1248,7 @@ namespace http {
 			ShowLeasesSets(s);
 		else {
 			res.code = 400;
-			ShowError(s, std::string (tr("Unknown page")) + ": " + page); // TODO
+			ShowError(s, tr("Unknown page") + ": " + page);
 			return;
 		}
 	}
@@ -1419,11 +1404,13 @@ namespace http {
 					{
 						auto signatureLen = dest->GetIdentity ()->GetSignatureLen ();
 						uint8_t * signature = new uint8_t[signatureLen];
+						char * sig = new char[signatureLen*2];
 						std::stringstream out;
 
 						out << name << "=" << dest->GetIdentity ()->ToBase64 ();
 						dest->Sign ((uint8_t *)out.str ().c_str (), out.str ().length (), signature);
-						auto sig = i2p::data::ByteStreamToBase64 (signature, signatureLen);
+						auto len = i2p::data::ByteStreamToBase64 (signature, signatureLen, sig, signatureLen*2);
+						sig[len] = 0;
 						out << "#!sig=" << sig;
 						s << "<b>" << tr("SUCCESS") << "</b>:<br>\r\n<form action=\"http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/add\" method=\"post\" rel=\"noreferrer\" target=\"_blank\">\r\n"
 						     "<textarea readonly name=\"record\" cols=\"80\" rows=\"10\">" << out.str () << "</textarea>\r\n<br>\r\n<br>\r\n"
@@ -1432,6 +1419,7 @@ namespace http {
 						     "<input type=\"submit\" value=\"" << tr("Submit") << "\">\r\n"
 						     "</form>\r\n<br>\r\n";
 						delete[] signature;
+						delete[] sig;
 					}
 					else
 						s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("Domain can't end with .b32.i2p") << "\r\n<br>\r\n<br>\r\n";
@@ -1460,7 +1448,7 @@ namespace http {
 		else
 		{
 			res.code = 400;
-			ShowError(s, std::string (tr("Unknown command")) + ": " + cmd); // TODO
+			ShowError(s, tr("Unknown command") + ": " + cmd);
 			return;
 		}
 
@@ -1479,13 +1467,13 @@ namespace http {
 		reply.body = content;
 
 		m_SendBuffer = reply.to_string();
-		boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer), boost::asio::transfer_all (), 
+		boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer),
 			std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1));
 	}
 
 	HTTPServer::HTTPServer (const std::string& address, int port):
-		m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service.get_executor ()),
-		m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::make_address(address), port)),
+		m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service),
+		m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port)),
 		m_Hostname(address)
 	{
 	}
diff --git a/daemon/HTTPServer.h b/daemon/HTTPServer.h
index 38b790d4..f41d925d 100644
--- a/daemon/HTTPServer.h
+++ b/daemon/HTTPServer.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -25,7 +25,7 @@ namespace http
 	const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192;
 	const int TOKEN_EXPIRATION_TIMEOUT = 30; // in seconds
 	const int COMMAND_REDIRECT_TIMEOUT = 5; // in seconds
-	const int TRANSIT_TUNNELS_LIMIT = 1000000;
+	const int TRANSIT_TUNNELS_LIMIT = 65535;
 
 	class HTTPConnection: public std::enable_shared_from_this<HTTPConnection>
 	{
@@ -83,8 +83,8 @@ namespace http
 
 			bool m_IsRunning;
 			std::unique_ptr<std::thread> m_Thread;
-			boost::asio::io_context m_Service;
-			boost::asio::executor_work_guard<boost::asio::io_context::executor_type> m_Work;
+			boost::asio::io_service m_Service;
+			boost::asio::io_service::work m_Work;
 			boost::asio::ip::tcp::acceptor m_Acceptor;
 			std::string m_Hostname;
 	};
diff --git a/daemon/I2PControl.cpp b/daemon/I2PControl.cpp
index 6261a14c..da2443fd 100644
--- a/daemon/I2PControl.cpp
+++ b/daemon/I2PControl.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2022, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -8,13 +8,16 @@
 
 #include <stdio.h>
 #include <sstream>
-#include <iomanip>
 #include <openssl/x509.h>
 #include <openssl/pem.h>
 
 // Use global placeholders from boost introduced when local_time.hpp is loaded
 #define BOOST_BIND_GLOBAL_PLACEHOLDERS
+
+#include <boost/date_time/local_time/local_time.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/property_tree/json_parser.hpp>
+#include <boost/lexical_cast.hpp>
 
 #include "FS.h"
 #include "Log.h"
@@ -29,24 +32,11 @@ namespace i2p
 namespace client
 {
 	I2PControlService::I2PControlService (const std::string& address, int port):
-		m_IsRunning (false),	
+		m_IsRunning (false), m_Thread (nullptr),
+		m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)),
 		m_SSLContext (boost::asio::ssl::context::sslv23),
 		m_ShutdownTimer (m_Service)
 	{
-		if (port)		
-			m_Acceptor = std::make_unique<boost::asio::ip::tcp::acceptor>(m_Service,
-				boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), port));
-		else
-#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
-		{		
-			std::remove (address.c_str ());	// just in case
-			m_LocalAcceptor = std::make_unique<boost::asio::local::stream_protocol::acceptor>(m_Service,
-				boost::asio::local::stream_protocol::endpoint(address));	
-		}	
-#else
-			LogPrint(eLogError, "I2PControl: Local sockets are not supported");
-#endif				
-				
 		i2p::config::GetOption("i2pcontrol.password", m_Password);
 
 		// certificate / keys
@@ -57,29 +47,15 @@ namespace client
 			i2pcp_crt = i2p::fs::DataDirPath(i2pcp_crt);
 		if (i2pcp_key.at(0) != '/')
 			i2pcp_key = i2p::fs::DataDirPath(i2pcp_key);
-		if (!i2p::fs::Exists (i2pcp_crt) || !i2p::fs::Exists (i2pcp_key)) 
-		{
+		if (!i2p::fs::Exists (i2pcp_crt) || !i2p::fs::Exists (i2pcp_key)) {
 			LogPrint (eLogInfo, "I2PControl: Creating new certificate for control connection");
 			CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str());
-		} 
-		else 
+		} else {
 			LogPrint(eLogDebug, "I2PControl: Using cert from ", i2pcp_crt);
+		}
 		m_SSLContext.set_options (boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use);
-		boost::system::error_code ec;
-		m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem, ec);
-		if (!ec)		
-			m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem, ec);
-		if (ec)
-		{
-			LogPrint (eLogInfo, "I2PControl: Failed to load ceritifcate: ", ec.message (), ". Recreating");
-			CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str());
-			m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem, ec);
-			if (!ec)		
-				m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem, ec);
-			if (ec) 
-				// give up
-				LogPrint (eLogError, "I2PControl: Can't load certificates");
-		}	
+		m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem);
+		m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem);
 
 		// handlers
 		m_MethodHandlers["Authenticate"]       = &I2PControlService::AuthenticateHandler;
@@ -110,7 +86,7 @@ namespace client
 		{
 			Accept ();
 			m_IsRunning = true;
-			m_Thread = std::make_unique<std::thread>(std::bind (&I2PControlService::Run, this));
+			m_Thread = new std::thread (std::bind (&I2PControlService::Run, this));
 		}
 	}
 
@@ -119,19 +95,12 @@ namespace client
 		if (m_IsRunning)
 		{
 			m_IsRunning = false;
-			if (m_Acceptor) m_Acceptor->cancel ();
-#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
-			if (m_LocalAcceptor) 
-			{
-				auto path = m_LocalAcceptor->local_endpoint().path();
-				m_LocalAcceptor->cancel ();
-				std::remove (path.c_str ());
-			}	
-#endif			
+			m_Acceptor.cancel ();
 			m_Service.stop ();
 			if (m_Thread)
 			{
 				m_Thread->join ();
+				delete m_Thread;
 				m_Thread = nullptr;
 			}
 		}
@@ -153,60 +122,40 @@ namespace client
 
 	void I2PControlService::Accept ()
 	{
-		if (m_Acceptor)
-		{	
-			auto newSocket = std::make_shared<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> > (m_Service, m_SSLContext);
-			m_Acceptor->async_accept (newSocket->lowest_layer(),
-				[this, newSocket](const boost::system::error_code& ecode)
-				{
-					HandleAccepted (ecode, newSocket);
-				});		
-		}	
-#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
-		else if (m_LocalAcceptor)
-		{
-			auto newSocket = std::make_shared<boost::asio::ssl::stream<boost::asio::local::stream_protocol::socket> > (m_Service, m_SSLContext);
-			m_LocalAcceptor->async_accept (newSocket->lowest_layer(),
-				[this, newSocket](const boost::system::error_code& ecode)
-				{
-					HandleAccepted (ecode, newSocket);
-				});		
-		}	
-#endif		
+		auto newSocket = std::make_shared<ssl_socket> (m_Service, m_SSLContext);
+		m_Acceptor.async_accept (newSocket->lowest_layer(), std::bind (&I2PControlService::HandleAccept, this,
+			std::placeholders::_1, newSocket));
 	}
 
-	template<typename ssl_socket> 
-	void I2PControlService::HandleAccepted (const boost::system::error_code& ecode,
-		std::shared_ptr<ssl_socket> newSocket)
+	void I2PControlService::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket)
 	{
 		if (ecode != boost::asio::error::operation_aborted)
 			Accept ();
 
-		if (ecode) 
-		{
+		if (ecode) {
 			LogPrint (eLogError, "I2PControl: Accept error: ", ecode.message ());
 			return;
 		}
-		LogPrint (eLogDebug, "I2PControl: New request from ", newSocket->lowest_layer ().remote_endpoint ());
-		Handshake (newSocket);
-	}	
-		
-	template<typename ssl_socket>
+		LogPrint (eLogDebug, "I2PControl: New request from ", socket->lowest_layer ().remote_endpoint ());
+		Handshake (socket);
+	}
+
 	void I2PControlService::Handshake (std::shared_ptr<ssl_socket> socket)
 	{
 		socket->async_handshake(boost::asio::ssl::stream_base::server,
-			[this, socket](const boost::system::error_code& ecode)
-		    {
-				if (ecode) 
-				{
-					LogPrint (eLogError, "I2PControl: Handshake error: ", ecode.message ());
-					return;
-				}
-				ReadRequest (socket);
-			});			                        
+		std::bind( &I2PControlService::HandleHandshake, this, std::placeholders::_1, socket));
+	}
+
+	void I2PControlService::HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket)
+	{
+		if (ecode) {
+			LogPrint (eLogError, "I2PControl: Handshake error: ", ecode.message ());
+			return;
+		}
+		//std::this_thread::sleep_for (std::chrono::milliseconds(5));
+		ReadRequest (socket);
 	}
 
-	template<typename ssl_socket>
 	void I2PControlService::ReadRequest (std::shared_ptr<ssl_socket> socket)
 	{
 		auto request = std::make_shared<I2PControlBuffer>();
@@ -216,13 +165,10 @@ namespace client
 #else
 			boost::asio::buffer (request->data (), request->size ()),
 #endif
-		    [this, socket, request](const boost::system::error_code& ecode, size_t bytes_transferred)
-		    {
-				HandleRequestReceived (ecode, bytes_transferred, socket, request);
-			});
+			std::bind(&I2PControlService::HandleRequestReceived, this,
+			std::placeholders::_1, std::placeholders::_2, socket, request));
 	}
 
-	template<typename ssl_socket>
 	void I2PControlService::HandleRequestReceived (const boost::system::error_code& ecode,
 		size_t bytes_transferred, std::shared_ptr<ssl_socket> socket,
 		std::shared_ptr<I2PControlBuffer> buf)
@@ -300,7 +246,6 @@ namespace client
 		}
 	}
 
-	template<typename ssl_socket>
 	void I2PControlService::SendResponse (std::shared_ptr<ssl_socket> socket,
 		std::shared_ptr<I2PControlBuffer> buf, std::ostringstream& response, bool isHtml)
 	{
@@ -310,12 +255,12 @@ namespace client
 			std::ostringstream header;
 			header << "HTTP/1.1 200 OK\r\n";
 			header << "Connection: close\r\n";
-			header << "Content-Length: " << std::to_string(len) << "\r\n";
+			header << "Content-Length: " << boost::lexical_cast<std::string>(len) << "\r\n";
 			header << "Content-Type: application/json\r\n";
 			header << "Date: ";
-			std::time_t t = std::time (nullptr);
-    		std::tm tm = *std::gmtime (&t);
-			header << std::put_time(&tm, "%a, %d %b %Y %T GMT") << "\r\n";
+			auto facet = new boost::local_time::local_time_facet ("%a, %d %b %Y %H:%M:%S GMT");
+			header.imbue(std::locale (header.getloc(), facet));
+			header << boost::posix_time::second_clock::local_time() << "\r\n";
 			header << "\r\n";
 			offset = header.str ().size ();
 			memcpy (buf->data (), header.str ().c_str (), offset);
@@ -323,11 +268,16 @@ namespace client
 		memcpy (buf->data () + offset, response.str ().c_str (), len);
 		boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len),
 			boost::asio::transfer_all (),
-		    [socket, buf](const boost::system::error_code& ecode, std::size_t bytes_transferred)
-		    {
-				if (ecode)
-					LogPrint (eLogError, "I2PControl: Write error: ", ecode.message ());
-			});	
+			std::bind(&I2PControlService::HandleResponseSent, this,
+				std::placeholders::_1, std::placeholders::_2, socket, buf));
+	}
+
+	void I2PControlService::HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred,
+		std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf)
+	{
+		if (ecode) {
+			LogPrint (eLogError, "I2PControl: Write error: ", ecode.message ());
+		}
 	}
 
 // handlers
@@ -388,11 +338,10 @@ namespace client
 	{
 		for (auto it = params.begin (); it != params.end (); it++)
 		{
+			if (it != params.begin ()) results << ",";
 			LogPrint (eLogDebug, "I2PControl: RouterManager request: ", it->first);
 			auto it1 = m_RouterManagerHandlers.find (it->first);
-			if (it1 != m_RouterManagerHandlers.end ()) 
-			{
-				if (it != params.begin ()) results << ",";
+			if (it1 != m_RouterManagerHandlers.end ()) {
 				(this->*(it1->second))(results);
 			} else
 				LogPrint (eLogError, "I2PControl: RouterManager unknown request: ", it->first);
@@ -455,7 +404,7 @@ namespace client
 			X509_NAME_add_entry_by_txt (name, "O",  MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_ORGANIZATION, -1, -1, 0); // organization
 			X509_NAME_add_entry_by_txt (name, "CN", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_COMMON_NAME, -1, -1, 0); // common name
 			X509_set_issuer_name (x509, name); // set issuer to ourselves
-			X509_sign (x509, pkey, EVP_sha1 ()); // sign, last param must be NULL for EdDSA
+			X509_sign (x509, pkey, EVP_sha1 ()); // sign
 
 			// save cert
 			if ((f = fopen (crt_path, "wb")) != NULL) {
diff --git a/daemon/I2PControl.h b/daemon/I2PControl.h
index 83dd6549..af152631 100644
--- a/daemon/I2PControl.h
+++ b/daemon/I2PControl.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2022, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -35,6 +35,8 @@ namespace client
 
 	class I2PControlService: public I2PControlHandlers
 	{
+		typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
+
 		public:
 
 			I2PControlService (const std::string& address, int port);
@@ -47,18 +49,16 @@ namespace client
 
 			void Run ();
 			void Accept ();
-			template<typename ssl_socket> 
-			void HandleAccepted (const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> newSocket);
-			template<typename ssl_socket>
+			void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket);
 			void Handshake (std::shared_ptr<ssl_socket> socket);
-			template<typename ssl_socket>
+			void HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket);
 			void ReadRequest (std::shared_ptr<ssl_socket> socket);
-			template<typename ssl_socket>
 			void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred,
 				std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf);
-			template<typename ssl_socket>
 			void SendResponse (std::shared_ptr<ssl_socket> socket,
 				std::shared_ptr<I2PControlBuffer> buf, std::ostringstream& response, bool isHtml);
+			void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred,
+				std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf);
 
 			void CreateCertificate (const char *crt_path, const char *key_path);
 
@@ -86,13 +86,10 @@ namespace client
 
 			std::string m_Password;
 			bool m_IsRunning;
-			std::unique_ptr<std::thread> m_Thread;
+			std::thread * m_Thread;
 
-			boost::asio::io_context m_Service;
-			std::unique_ptr<boost::asio::ip::tcp::acceptor> m_Acceptor;
-#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
-			std::unique_ptr<boost::asio::local::stream_protocol::acceptor> m_LocalAcceptor;
-#endif		
+			boost::asio::io_service m_Service;
+			boost::asio::ip::tcp::acceptor m_Acceptor;
 			boost::asio::ssl::context m_SSLContext;
 			boost::asio::deadline_timer m_ShutdownTimer;
 			std::set<std::string> m_Tokens;
diff --git a/daemon/UPnP.cpp b/daemon/UPnP.cpp
index 8e6dbcf6..dbaf864a 100644
--- a/daemon/UPnP.cpp
+++ b/daemon/UPnP.cpp
@@ -1,15 +1,10 @@
-/*
-* Copyright (c) 2013-2024, The PurpleI2P Project
-*
-* This file is part of Purple i2pd project and licensed under BSD3
-*
-* See full license text in LICENSE file at top of project tree
-*/
-
 #ifdef USE_UPNP
 #include <string>
 #include <thread>
 
+#include <boost/thread/thread.hpp>
+#include <boost/asio.hpp>
+
 #include "Log.h"
 
 #include "RouterContext.h"
@@ -52,7 +47,7 @@ namespace transport
 	{
 		m_IsRunning = true;
 		LogPrint(eLogInfo, "UPnP: Starting");
-		boost::asio::post (m_Service, std::bind (&UPnP::Discover, this));
+		m_Service.post (std::bind (&UPnP::Discover, this));
 		std::unique_lock<std::mutex> l(m_StartedMutex);
 		m_Thread.reset (new std::thread (std::bind (&UPnP::Run, this)));
 		m_Started.wait_for (l, std::chrono::seconds (5)); // 5 seconds maximum
@@ -115,16 +110,10 @@ namespace transport
 			return;
 		}
 
-#if (MINIUPNPC_API_VERSION >= 18)
-		err = UPNP_GetValidIGD (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr),
-					m_externalIPAddress, sizeof (m_externalIPAddress));
-#else
 		err = UPNP_GetValidIGD (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr));
-#endif
 		m_upnpUrlsInitialized=err!=0;
 		if (err == UPNP_IGD_VALID_CONNECTED)
 		{
-#if (MINIUPNPC_API_VERSION < 18)
 			err = UPNP_GetExternalIPAddress (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress);
 			if(err != UPNPCOMMAND_SUCCESS)
 			{
@@ -132,7 +121,6 @@ namespace transport
 				return;
 			}
 			else
-#endif
 			{
 				LogPrint (eLogError, "UPnP: Found Internet Gateway Device ", m_upnpUrls.controlURL);
 				if (!m_externalIPAddress[0])
@@ -150,7 +138,7 @@ namespace transport
 
 		// UPnP discovered
 		LogPrint (eLogDebug, "UPnP: ExternalIPAddress is ", m_externalIPAddress);
-		i2p::context.UpdateAddress (boost::asio::ip::make_address (m_externalIPAddress));
+		i2p::context.UpdateAddress (boost::asio::ip::address::from_string (m_externalIPAddress));
 		// port mapping
 		PortMapping ();
 	}
@@ -178,11 +166,11 @@ namespace transport
 			if (address && !address->host.is_v6 () && address->port)
 				TryPortMapping (address);
 		}
-		m_Timer.expires_from_now (boost::posix_time::minutes(UPNP_PORT_FORWARDING_INTERVAL)); // every 20 minutes
+		m_Timer.expires_from_now (boost::posix_time::minutes(20)); // every 20 minutes
 		m_Timer.async_wait ([this](const boost::system::error_code& ecode)
 		{
 			if (ecode != boost::asio::error::operation_aborted)
-				PortMapping ();
+			PortMapping ();
 		});
 	}
 
diff --git a/daemon/UPnP.h b/daemon/UPnP.h
index 2a5fe9f3..59f3b785 100644
--- a/daemon/UPnP.h
+++ b/daemon/UPnP.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2020, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -28,8 +28,7 @@ namespace i2p
 namespace transport
 {
 	const int UPNP_RESPONSE_TIMEOUT = 2000; // in milliseconds
-	const int UPNP_PORT_FORWARDING_INTERVAL = 20; // in minutes
-	
+
 	enum
 	{
 		UPNP_IGD_NONE = 0,
@@ -67,7 +66,7 @@ namespace transport
 			std::unique_ptr<std::thread> m_Thread;
 			std::condition_variable m_Started;
 			std::mutex m_StartedMutex;
-			boost::asio::io_context m_Service;
+			boost::asio::io_service m_Service;
 			boost::asio::deadline_timer m_Timer;
 			bool m_upnpUrlsInitialized = false;
 			struct UPNPUrls m_upnpUrls;
diff --git a/daemon/UnixDaemon.cpp b/daemon/UnixDaemon.cpp
index 66661e0f..bf4a7662 100644
--- a/daemon/UnixDaemon.cpp
+++ b/daemon/UnixDaemon.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2020, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -25,7 +25,6 @@
 #include "RouterContext.h"
 #include "ClientContext.h"
 #include "Transports.h"
-#include "util.h"
 
 void handle_signal(int sig)
 {
@@ -163,21 +162,12 @@ namespace i2p
 
 #ifndef ANDROID
 				if (lockf(pidFH, F_TLOCK, 0) != 0)
-#else
-				struct flock fl;
-				fl.l_len = 0;
-				fl.l_type = F_WRLCK;
-				fl.l_whence = SEEK_SET;
-				fl.l_start = 0;
-
-				if (fcntl(pidFH, F_SETLK, &fl) != 0)
-#endif
 				{
 					LogPrint(eLogError, "Daemon: Could not lock pid file ", pidfile, ": ", strerror(errno));
 					std::cerr << "i2pd: Could not lock pid file " << pidfile << ": " << strerror(errno) << std::endl;
 					return false;
 				}
-
+#endif
 				char pid[10];
 				sprintf(pid, "%d\n", getpid());
 				ftruncate(pidFH, 0);
@@ -221,7 +211,6 @@ namespace i2p
 
 		void DaemonLinux::run ()
 		{
-			i2p::util::SetThreadName ("i2pd-daemon");
 			while (running)
 			{
 				std::this_thread::sleep_for (std::chrono::seconds(1));
diff --git a/debian/NEWS b/debian/NEWS
deleted file mode 100644
index c0add110..00000000
--- a/debian/NEWS
+++ /dev/null
@@ -1,5 +0,0 @@
-i2pd (2.53.0-1) unstable; urgency=medium
-
-  i2pd binary moved from /usr/sbin to /usr/bin. Please check your scripts if you used the old path.
-
- -- r4sas <r4sas@i2pmail.org>  Fri, 19 Jul 2024 16:00:00 +0000
diff --git a/debian/changelog b/debian/changelog
index d170f534..56ab7c16 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,51 +1,8 @@
-i2pd (2.56.0-1) unstable; urgency=medium
-
-  * updated to version 2.56.0/0.9.65
-
- -- orignal <orignal@i2pmail.org>  Tue, 11 Feb 2025 16:00:00 +0000
-
-i2pd (2.55.0-1) unstable; urgency=medium
-
-  * updated to version 2.55.0
-
- -- orignal <orignal@i2pmail.org>  Mon, 30 Dec 2024 16:00:00 +0000
-
-i2pd (2.54.0-1) unstable; urgency=medium
-
-  * updated to version 2.54.0/0.9.64
-
- -- orignal <orignal@i2pmail.org>  Sun, 6 Oct 2024 16:00:00 +0000
-
-i2pd (2.53.1-1) unstable; urgency=medium
-
-  * updated to version 2.53.1
-
- -- orignal <orignal@i2pmail.org>  Tue, 30 Jul 2024 16:00:00 +0000
-
-i2pd (2.53.0-1) unstable; urgency=medium
-
-  * updated to version 2.53.0/0.9.63
-  * binary moved from /usr/sbin to /usr/bin
-
- -- r4sas <r4sas@i2pmail.org>  Sat, 20 Jul 2024 15:10:00 +0000
-
-i2pd (2.52.0-1) unstable; urgency=medium
-
-  * updated to version 2.52.0
-
- -- orignal <orignal@i2pmail.org>  Sun, 12 May 2024 16:00:00 +0000
-
-i2pd (2.51.0-1) unstable; urgency=medium
-
-  * updated to version 2.51.0/0.9.62
-
- -- orignal <orignal@i2pmail.org>  Sat, 06 Apr 2024 16:00:00 +0000
-
-i2pd (2.50.2-1) unstable; urgency=medium
+i2pd (2.50.2) unstable; urgency=medium
 
   * updated to version 2.50.2/0.9.61
 
- -- orignal <orignal@i2pmail.org>  Sat, 06 Jan 2024 16:00:00 +0000
+-- orignal <orignal@i2pmail.org>  Sat, 06 Jan 2024 16:00:00 +0000
 
 i2pd (2.50.1-1) unstable; urgency=medium
 
diff --git a/debian/i2pd.init b/debian/i2pd.init
index 9b5f7669..33fd80a5 100644
--- a/debian/i2pd.init
+++ b/debian/i2pd.init
@@ -13,7 +13,7 @@
 PATH=/sbin:/usr/sbin:/bin:/usr/bin
 DESC=i2pd                 # Introduce a short description here
 NAME=i2pd                 # Introduce the short server's name here
-DAEMON=/usr/bin/$NAME    # Introduce the server's location here
+DAEMON=/usr/sbin/$NAME    # Introduce the server's location here
 DAEMON_OPTS=""            # Arguments to run the daemon with
 PIDFILE=/var/run/$NAME/$NAME.pid
 I2PCONF=/etc/$NAME/i2pd.conf
diff --git a/debian/i2pd.install b/debian/i2pd.install
index bde52854..93eee7a1 100644
--- a/debian/i2pd.install
+++ b/debian/i2pd.install
@@ -1,6 +1,6 @@
-i2pd usr/bin/
+i2pd usr/sbin/
 contrib/i2pd.conf etc/i2pd/
 contrib/tunnels.conf etc/i2pd/
 contrib/certificates/ usr/share/i2pd/
 contrib/tunnels.d/README etc/i2pd/tunnels.conf.d/
-contrib/apparmor/usr.bin.i2pd etc/apparmor.d
+contrib/apparmor/usr.sbin.i2pd etc/apparmor.d
diff --git a/debian/patches/01-upnp.patch b/debian/patches/01-upnp.patch
index 74d36c06..bec8f2b0 100644
--- a/debian/patches/01-upnp.patch
+++ b/debian/patches/01-upnp.patch
@@ -2,13 +2,13 @@ Description: Enable UPnP usage in package
 Author: r4sas <r4sas@i2pmail.org>
 
 Reviewed-By: r4sas <r4sas@i2pmail.org>
-Last-Update: 2024-12-30
+Last-Update: 2022-03-23
 
 --- i2pd.orig/Makefile
 +++ i2pd/Makefile
-@@ -31,7 +31,7 @@ # import source files lists
- include filelist.mk
+@@ -31,7 +31,7 @@ include filelist.mk
  
+ USE_AESNI       := $(or $(USE_AESNI),yes)
  USE_STATIC      := $(or $(USE_STATIC),no)
 -USE_UPNP        := $(or $(USE_UPNP),no)
 +USE_UPNP        := $(or $(USE_UPNP),yes)
diff --git a/i18n/Afrikaans.cpp b/i18n/Afrikaans.cpp
index b69c42ef..b582a06a 100644
--- a/i18n/Afrikaans.cpp
+++ b/i18n/Afrikaans.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2021-2025, The PurpleI2P Project
+* Copyright (c) 2021, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -29,7 +29,7 @@ namespace afrikaans // language namespace
 		return n != 1 ? 1 : 0;
 	}
 
-	static const LocaleStrings strings
+	static std::map<std::string, std::string> strings
 	{
 		{"failed", "Het misluk"},
 		{"unknown", "onbekend"},
diff --git a/i18n/Armenian.cpp b/i18n/Armenian.cpp
index 67955d8a..b99e5032 100644
--- a/i18n/Armenian.cpp
+++ b/i18n/Armenian.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2021-2025, The PurpleI2P Project
+* Copyright (c) 2021-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -29,7 +29,7 @@ namespace armenian // language namespace
 		return n != 1 ? 1 : 0;
 	}
 
-	static const LocaleStrings strings
+	static std::map<std::string, std::string> strings
 	{
 		{"%.2f KiB", "%.2f ԿիԲ"},
 		{"%.2f MiB", "%.2f ՄիԲ"},
diff --git a/i18n/Chinese.cpp b/i18n/Chinese.cpp
index e3b63ebd..5ecfe067 100644
--- a/i18n/Chinese.cpp
+++ b/i18n/Chinese.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2022-2025, The PurpleI2P Project
+* Copyright (c) 2022-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -29,7 +29,7 @@ namespace chinese // language namespace
 		return 0;
 	}
 
-	static const LocaleStrings strings
+	static std::map<std::string, std::string> strings
 	{
 		{"%.2f KiB", "%.2f KiB"},
 		{"%.2f MiB", "%.2f MiB"},
@@ -64,12 +64,11 @@ namespace chinese // language namespace
 		{"Full cone NAT", "全锥型NAT"},
 		{"No Descriptors", "无描述符"},
 		{"Uptime", "运行时间"},
-		{"Network status", "网络状态"},
+		{"Network status", "IPv4 网络状态"},
 		{"Network status v6", "IPv6 网络状态"},
 		{"Stopping in", "距停止还有:"},
 		{"Family", "家族"},
 		{"Tunnel creation success rate", "隧道创建成功率"},
-		{"Total tunnel creation success rate", "当前隧道创建成功率"},
 		{"Received", "已接收"},
 		{"%.2f KiB/s", "%.2f KiB/s"},
 		{"Sent", "已发送"},
@@ -96,7 +95,6 @@ namespace chinese // language namespace
 		{"Address", "地址"},
 		{"Type", "类型"},
 		{"EncType", "加密类型"},
-		{"Expire LeaseSet", "到期租约集"},
 		{"Inbound tunnels", "入站隧道"},
 		{"%dms", "%dms"},
 		{"Outbound tunnels", "出站隧道"},
@@ -153,8 +151,6 @@ namespace chinese // language namespace
 		{"StreamID can't be null", "StreamID 不能为空"},
 		{"Return to destination page", "返回目标页面"},
 		{"You will be redirected in %d seconds", "您将在%d秒内被重定向"},
-		{"LeaseSet expiration time updated", "租约集到期时间已更新"},
-		{"LeaseSet is not found or already expired", "租约集未找到或已过期"},
 		{"Transit tunnels count must not exceed %d", "中转隧道数量限制为 %d"},
 		{"Back to commands list", "返回命令列表"},
 		{"Register at reg.i2p", "在 reg.i2p 注册域名"},
diff --git a/i18n/Czech.cpp b/i18n/Czech.cpp
index 94803354..09a56d50 100644
--- a/i18n/Czech.cpp
+++ b/i18n/Czech.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2022-2025, The PurpleI2P Project
+* Copyright (c) 2022-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -29,25 +29,25 @@ namespace czech // language namespace
 		return (n == 1) ? 0 : (n >= 2 && n <= 4) ? 1 : 2;
 	}
 
-	static const LocaleStrings strings
+	static std::map<std::string, std::string> strings
 	{
 		{"%.2f KiB", "%.2f KiB"},
 		{"%.2f MiB", "%.2f MiB"},
 		{"%.2f GiB", "%.2f GiB"},
 		{"building", "vytváří se"},
 		{"failed", "selhalo"},
-		{"expiring", "vyprší platnost"},
+		{"expiring", "končící"},
 		{"established", "vytvořeno"},
 		{"unknown", "neznámý"},
 		{"exploratory", "průzkumné"},
-		{"Purple I2P Webconsole", "Purple I2P webová konzole"},
-		{"<b>i2pd</b> webconsole", "<b>i2pd</b> webová konzole"},
+		{"Purple I2P Webconsole", "Purple I2P Webkonsole"},
+		{"<b>i2pd</b> webconsole", "<b>i2pd</b> webkonsole"},
 		{"Main page", "Hlavní stránka"},
 		{"Router commands", "Router příkazy"},
-		{"Local Destinations", "Místní cíle"},
-		{"LeaseSets", "Sety pronájmu"},
+		{"Local Destinations", "Lokální destinace"},
+		{"LeaseSets", "LeaseSety"},
 		{"Tunnels", "Tunely"},
-		{"Transit Tunnels", "Tranzitní tunely"},
+		{"Transit Tunnels", "Transitní tunely"},
 		{"Transports", "Transporty"},
 		{"I2P tunnels", "I2P tunely"},
 		{"SAM sessions", "SAM relace"},
@@ -61,21 +61,18 @@ namespace czech // language namespace
 		{"Clock skew", "Časová nesrovnalost"},
 		{"Offline", "Offline"},
 		{"Symmetric NAT", "Symetrický NAT"},
-		{"Full cone NAT", "Full cone NAT"},
-		{"No Descriptors", "Žádné popisovače"},
 		{"Uptime", "Doba provozu"},
-		{"Network status", "Stav sítě"},
-		{"Network status v6", "Stav sítě v6"},
+		{"Network status", "Status sítě"},
+		{"Network status v6", "Status sítě v6"},
 		{"Stopping in", "Zastavuji za"},
 		{"Family", "Rodina"},
 		{"Tunnel creation success rate", "Úspěšnost vytváření tunelů"},
-		{"Total tunnel creation success rate", "Celková míra úspěšnosti vytváření tunelů"},
 		{"Received", "Přijato"},
 		{"%.2f KiB/s", "%.2f KiB/s"},
 		{"Sent", "Odesláno"},
 		{"Transit", "Tranzit"},
-		{"Data path", "Cesta k datovým souborům"},
-		{"Hidden content. Press on text to see.", "Skrytý obsah. Pro zobrazení klikněte sem."},
+		{"Data path", "Cesta k data souborům"},
+		{"Hidden content. Press on text to see.", "Skrytý kontent. Pro zobrazení, klikni na text."},
 		{"Router Ident", "Routerová Identita"},
 		{"Router Family", "Rodina routerů"},
 		{"Router Caps", "Omezení Routerů"},
@@ -96,7 +93,6 @@ namespace czech // language namespace
 		{"Address", "Adresa"},
 		{"Type", "Typ"},
 		{"EncType", "EncType"},
-		{"Expire LeaseSet", "Zrušit platnost setu pronájmu"},
 		{"Inbound tunnels", "Příchozí tunely"},
 		{"%dms", "%dms"},
 		{"Outbound tunnels", "Odchozí tunely"},
@@ -107,24 +103,21 @@ namespace czech // language namespace
 		{"Amount", "Množství"},
 		{"Incoming Tags", "Příchozí štítky"},
 		{"Tags sessions", "Relace štítků"},
-		{"Status", "Stav"},
-		{"Local Destination", "Místní cíl"},
+		{"Status", "Status"},
+		{"Local Destination", "Lokální Destinace"},
 		{"Streams", "Toky"},
 		{"Close stream", "Uzavřít tok"},
-		{"Such destination is not found", "Takováto destinace nebyla nalezena"},
 		{"I2CP session not found", "I2CP relace nenalezena"},
 		{"I2CP is not enabled", "I2CP není zapnuto"},
 		{"Invalid", "Neplatný"},
 		{"Store type", "Druh uložení"},
 		{"Expires", "Vyprší"},
-		{"Non Expired Leases", "Pronájmy, kterým nevypršela platnost"},
+		{"Non Expired Leases", "Nevypršené Leasy"},
 		{"Gateway", "Brána"},
 		{"TunnelID", "ID tunelu"},
 		{"EndDate", "Datum ukončení"},
-		{"floodfill mode is disabled", "režim floodfill je vypnut"},
 		{"Queue size", "Velikost fronty"},
 		{"Run peer test", "Spustit peer test"},
-		{"Reload tunnels configuration", "Znovu načíst nastavení tunelů"},
 		{"Decline transit tunnels", "Odmítnout tranzitní tunely"},
 		{"Accept transit tunnels", "Přijmout tranzitní tunely"},
 		{"Cancel graceful shutdown", "Zrušit hladké vypnutí"},
@@ -152,17 +145,14 @@ namespace czech // language namespace
 		{"Destination not found", "Destinace nenalezena"},
 		{"StreamID can't be null", "StreamID nemůže být null"},
 		{"Return to destination page", "Zpět na stránku destinací"},
-		{"You will be redirected in %d seconds", "Budete přesměrováni za %d sekund"},
-		{"LeaseSet expiration time updated", "Aktualizován čas vypršení platnosti setu pronájmu"},
-		{"LeaseSet is not found or already expired", "Set pronájmu není k nalezení nebo již vypršela jeho platnost"},
-		{"Transit tunnels count must not exceed %d", "Počet tranzitních tunelů nesmí překročit %d"},
-		{"Back to commands list", "Zpět na seznam příkazů"},
+		{"Back to commands list", "Zpět na list příkazů"},
 		{"Register at reg.i2p", "Zaregistrovat na reg.i2p"},
 		{"Description", "Popis"},
 		{"A bit information about service on domain", "Trochu informací o službě na doméně"},
 		{"Submit", "Odeslat"},
 		{"Domain can't end with .b32.i2p", "Doména nesmí končit na .b32.i2p"},
 		{"Domain must end with .i2p", "Doména musí končit s .i2p"},
+		{"Such destination is not found", "Takováto destinace nebyla nalezena"},
 		{"Unknown command", "Neznámý příkaz"},
 		{"Command accepted", "Příkaz přijat"},
 		{"Proxy error", "Chyba proxy serveru"},
@@ -172,15 +162,6 @@ namespace czech // language namespace
 		{"You may try to find this host on jump services below", "Můžete se pokusit najít tohoto hostitele na startovacích službách níže"},
 		{"Invalid request", "Neplatný požadavek"},
 		{"Proxy unable to parse your request", "Proxy server nemohl zpracovat váš požadavek"},
-		{"Addresshelper is not supported", "Addresshelper není podporován"},
-		{"Host %s is <font color=red>already in router's addressbook</font>. <b>Be careful: source of this URL may be harmful!</b> Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.", "Hostitel %s je <font color=red>již v adresáři routeru</font>. <b>Buďte opatrní: zdroj této URL může být škodlivý!</b> Klikněte zde pro aktualizaci záznamu: <a href=\"%s%s%s&update=true\">Pokračovat</a>."},
-		{"Addresshelper forced update rejected", "Addresshelperem vynucená aktualizace zamítnuta"},
-		{"To add host <b>%s</b> in router's addressbook, click here: <a href=\"%s%s%s\">Continue</a>.", "Pro přidání hostitele <b>%s</b> do adresáře routeru, klikněte zde: <a href=\"%s%s%s\">Pokračovat</a>."},
-		{"Addresshelper request", "Požadavek Addresshelperu"},
-		{"Host %s added to router's addressbook from helper. Click here to proceed: <a href=\"%s\">Continue</a>.", "Hostitel %s přidán do adresáře routeru od pomocníka. Klikněte zde pro pokračování: <a href=\"%s\">Pokračovat</a>."},
-		{"Addresshelper adding", "Addresshelper přidávání"},
-		{"Host %s is <font color=red>already in router's addressbook</font>. Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.", "Hostitel %s je <font color=red>již v adresáři routeru</font>. Klikněte zde pro aktualizaci záznamu: <a href=\"%s%s%s&update=true\">Pokračovat</a>."},
-		{"Addresshelper update", "Addresshelper aktualizace"},
 		{"Invalid request URI", "Neplatný URI požadavek"},
 		{"Can't detect destination host from request", "Nelze zjistit cílového hostitele z požadavku"},
 		{"Outproxy failure", "Outproxy selhání"},
diff --git a/i18n/English.cpp b/i18n/English.cpp
index fb774527..2670e984 100644
--- a/i18n/English.cpp
+++ b/i18n/English.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2021-2025, The PurpleI2P Project
+* Copyright (c) 2021, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -30,7 +30,7 @@ namespace english // language namespace
 		return n != 1 ? 1 : 0;
 	}
 
-	static const LocaleStrings strings
+	static std::map<std::string, std::string> strings
 	{
 		{"", ""},
 	};
diff --git a/i18n/French.cpp b/i18n/French.cpp
index 985296a3..0a5b147e 100644
--- a/i18n/French.cpp
+++ b/i18n/French.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2022-2025, The PurpleI2P Project
+* Copyright (c) 2022-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -29,7 +29,7 @@ namespace french // language namespace
 		return n != 1 ? 1 : 0;
 	}
 
-	static const LocaleStrings strings
+	static std::map<std::string, std::string> strings
 	{
 		{"%.2f KiB", "%.2f Kio"},
 		{"%.2f MiB", "%.2f Mio"},
@@ -58,7 +58,7 @@ namespace french // language namespace
 		{"Unknown", "Inconnu"},
 		{"Proxy", "Proxy"},
 		{"Mesh", "Maillé"},
-		{"Clock skew", "Décalage de l'horloge"},
+		{"Clock skew", "Horloge décalée"},
 		{"Offline", "Hors ligne"},
 		{"Symmetric NAT", "NAT symétrique"},
 		{"Full cone NAT", "NAT à cône complet"},
@@ -68,8 +68,8 @@ namespace french // language namespace
 		{"Network status v6", "État du réseau v6"},
 		{"Stopping in", "Arrêt dans"},
 		{"Family", "Famille"},
-		{"Tunnel creation success rate", "Taux de création de tunnel réussie"},
-		{"Total tunnel creation success rate", "Taux total de création de tunnel réussie"},
+		{"Tunnel creation success rate", "Taux de succès de création de tunnels"},
+		{"Total tunnel creation success rate", "Taux de réussite de création de tunnel"},
 		{"Received", "Reçu"},
 		{"%.2f KiB/s", "%.2f Kio/s"},
 		{"Sent", "Envoyé"},
diff --git a/i18n/German.cpp b/i18n/German.cpp
index 90ce82f4..02662e8e 100644
--- a/i18n/German.cpp
+++ b/i18n/German.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2022-2025, The PurpleI2P Project
+* Copyright (c) 2022-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -29,7 +29,7 @@ namespace german // language namespace
 		return n != 1 ? 1 : 0;
 	}
 
-	static const LocaleStrings strings
+	static std::map<std::string, std::string> strings
 	{
 		{"%.2f KiB", "%.2f KiB"},
 		{"%.2f MiB", "%.2f MiB"},
diff --git a/i18n/I18N.cpp b/i18n/I18N.cpp
index 48a02357..cf4873eb 100644
--- a/i18n/I18N.cpp
+++ b/i18n/I18N.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2021-2025, The PurpleI2P Project
+* Copyright (c) 2021-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -30,12 +30,12 @@ namespace i18n
 		}
 	}
 
-	std::string_view translate (std::string_view arg)
+	std::string translate (const std::string& arg)
 	{
 		return i2p::client::context.GetLanguage ()->GetString (arg);
 	}
 
-	std::string translate (const std::string& arg, const std::string& arg2, const int n)
+	std::string translate (const std::string& arg, const std::string& arg2, const int& n)
 	{
 		return i2p::client::context.GetLanguage ()->GetPlural (arg, arg2, n);
 	}
diff --git a/i18n/I18N.h b/i18n/I18N.h
index 8ed77a6b..6ec5b16e 100644
--- a/i18n/I18N.h
+++ b/i18n/I18N.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2021-2025, The PurpleI2P Project
+* Copyright (c) 2021-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -10,7 +10,6 @@
 #define __I18N_H__
 
 #include <string>
-#include <string_view>
 #include <map>
 #include <utility>
 #include <functional>
@@ -19,13 +18,12 @@ namespace i2p
 {
 namespace i18n
 {
-	typedef std::map<std::string_view, std::string_view> LocaleStrings; 
 	class Locale
 	{
 		public:
 			Locale (
 				const std::string& language,
-				const LocaleStrings& strings,
+				const std::map<std::string, std::string>& strings,
 				const std::map<std::string, std::vector<std::string>>& plurals,
 				std::function<int(int)> formula
 			): m_Language (language), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { };
@@ -36,7 +34,7 @@ namespace i18n
 				return m_Language;
 			}
 
-			std::string_view GetString (std::string_view arg) const
+			std::string GetString (const std::string& arg) const
 			{
 				const auto it = m_Strings.find(arg);
 				if (it == m_Strings.end())
@@ -49,7 +47,7 @@ namespace i18n
 				}
 			}
 
-			std::string GetPlural (const std::string& arg, const std::string& arg2, int n) const
+			std::string GetPlural (const std::string& arg, const std::string& arg2, const int& n) const
 			{
 				const auto it = m_Plurals.find(arg2);
 				if (it == m_Plurals.end()) // not found, fallback to english
@@ -65,14 +63,14 @@ namespace i18n
 
 		private:
 			const std::string m_Language;
-			const LocaleStrings m_Strings;
+			const std::map<std::string, std::string> m_Strings;
 			const std::map<std::string, std::vector<std::string>> m_Plurals;
 			std::function<int(int)> m_Formula;
 	};
 
 	void SetLanguage(const std::string &lang);
-	std::string_view translate (std::string_view arg);
-	std::string translate (const std::string& arg, const std::string& arg2, int n);
+	std::string translate (const std::string& arg);
+	std::string translate (const std::string& arg, const std::string& arg2, const int& n);
 } // i18n
 } // i2p
 
@@ -81,7 +79,7 @@ namespace i18n
  * @param arg String with message
  */
 template<typename TValue>
-std::string_view tr (TValue&& arg)
+std::string tr (TValue&& arg)
 {
 	return i2p::i18n::translate(std::forward<TValue>(arg));
 }
@@ -94,7 +92,7 @@ std::string_view tr (TValue&& arg)
 template<typename TValue, typename... TArgs>
 std::string tr (TValue&& arg, TArgs&&... args)
 {
-	std::string tr_str = std::string (i2p::i18n::translate(std::forward<TValue>(arg))); // TODO:
+	std::string tr_str = i2p::i18n::translate(std::forward<TValue>(arg));
 
 	size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward<TArgs>(args)...);
 	std::string str(size, 0);
@@ -110,7 +108,7 @@ std::string tr (TValue&& arg, TArgs&&... args)
  * @param n Integer, used for selection of form
  */
 template<typename TValue, typename TValue2>
-std::string ntr (TValue&& arg, TValue2&& arg2, int n)
+std::string ntr (TValue&& arg, TValue2&& arg2, int& n)
 {
 	return i2p::i18n::translate(std::forward<TValue>(arg), std::forward<TValue2>(arg2), std::forward<int>(n));
 }
@@ -123,7 +121,7 @@ std::string ntr (TValue&& arg, TValue2&& arg2, int n)
  * @param args Array of arguments for string formatting
  */
 template<typename TValue, typename TValue2, typename... TArgs>
-std::string ntr (TValue&& arg, TValue2&& arg2, int n, TArgs&&... args)
+std::string ntr (TValue&& arg, TValue2&& arg2, int& n, TArgs&&... args)
 {
 	std::string tr_str = i2p::i18n::translate(std::forward<TValue>(arg), std::forward<TValue2>(arg2), std::forward<int>(n));
 
diff --git a/i18n/Italian.cpp b/i18n/Italian.cpp
index 0ae26f21..2dcaab5e 100644
--- a/i18n/Italian.cpp
+++ b/i18n/Italian.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2022-2025, The PurpleI2P Project
+* Copyright (c) 2022-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -29,7 +29,7 @@ namespace italian // language namespace
 		return n != 1 ? 1 : 0;
 	}
 
-	static const LocaleStrings strings
+	static std::map<std::string, std::string> strings
 	{
 		{"%.2f KiB", "%.2f KiB"},
 		{"%.2f MiB", "%.2f MiB"},
diff --git a/i18n/Polish.cpp b/i18n/Polish.cpp
index 0e8df096..26661231 100644
--- a/i18n/Polish.cpp
+++ b/i18n/Polish.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2023-2025, The PurpleI2P Project
+* Copyright (c) 2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -29,188 +29,24 @@ namespace polish // language namespace
 		return (n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
 	}
 
-	static const LocaleStrings strings
+	static std::map<std::string, std::string> strings
 	{
-		{"%.2f KiB", "%.2f KiB"},
-		{"%.2f MiB", "%.2f MiB"},
-		{"%.2f GiB", "%.2f GiB"},
 		{"building", "Kompilowanie"},
 		{"failed", "nieudane"},
 		{"expiring", "wygasający"},
 		{"established", "ustanowiony"},
-		{"unknown", "nieznany"},
-		{"exploratory", "eksploracyjny"},
-		{"Purple I2P Webconsole", "Konsola webowa Purple I2P"},
-		{"<b>i2pd</b> webconsole", "<b>i2pd</b> konsola webowa"},
 		{"Main page", "Strona główna"},
 		{"Router commands", "Komendy routera"},
-		{"Local Destinations", "Lokalne miejsca docelowe"},
-		{"LeaseSets", "ZestawyNajmu"},
 		{"Tunnels", "Tunele"},
-		{"Transit Tunnels", "Tunele Tranzytu"},
-		{"Transports", "Transportery"},
-		{"I2P tunnels", "Tunele I2P"},
-		{"SAM sessions", "Sesje SAM"},
-		{"ERROR", "BŁĄD"},
 		{"OK", "Ok"},
-		{"Testing", "Testowanie"},
-		{"Firewalled", "Za zaporą sieciową"},
-		{"Unknown", "Nieznany"},
-		{"Proxy", "Proxy"},
-		{"Mesh", "Sieć"},
-		{"Clock skew", "Przesunięcie czasu"},
-		{"Offline", "Offline"},
-		{"Symmetric NAT", "Symetryczny NAT"},
-		{"Full cone NAT", "Pełny stożek NAT"},
-		{"No Descriptors", "Brak deskryptorów"},
 		{"Uptime", "Czas pracy"},
-		{"Network status", "Stan sieci"},
-		{"Network status v6", "Stan sieci v6"},
-		{"Stopping in", "Zatrzymywanie za"},
-		{"Family", "Rodzina"},
-		{"Tunnel creation success rate", "Wskaźnik sukcesu tworzenia tunelu"},
-		{"Total tunnel creation success rate", "Całkowity wskaźnik sukcesu tworzenia tunelu"},
-		{"Received", "Odebrano"},
-		{"%.2f KiB/s", "%.2f KiB/s"},
 		{"Sent", "Wysłane"},
-		{"Transit", "Tranzyt"},
-		{"Data path", "Ścieżka do danych"},
-		{"Hidden content. Press on text to see.", "Ukryta zawartość. Naciśnij tekst, aby zobaczyć."},
-		{"Router Ident", "Identyfikator routera"},
-		{"Router Family", "Rodzina routera"},
-		{"Router Caps", "Możliwości routera"},
-		{"Version", "Wersja"},
-		{"Our external address", "Nasz zewnętrzny adres"},
-		{"supported", "wspierane"},
-		{"Routers", "Routery"},
-		{"Floodfills", "Floodfille"},
-		{"Client Tunnels", "Tunele Klienta"},
-		{"Services", "Usługi"},
-		{"Enabled", "Aktywny"},
-		{"Disabled", "Wyłączony"},
-		{"Encrypted B33 address", "Zaszyfrowany adres B33"},
-		{"Address registration line", "Linia rejestracji adresu"},
-		{"Domain", "Domena"},
-		{"Generate", "Generuj"},
-		{"<b>Note:</b> result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "<b>Uwaga:</b> wynik string może być używany tylko do rejestracji domen 2LD (przykład.i2p). Do rejestracji subdomen należy użyć narzędzi i2pd."},
-		{"Address", "Adres"},
-		{"Type", "Typ"},
-		{"EncType", "TypEnkrypcji"},
-		{"Expire LeaseSet", "Wygaśnij LeaseSet"},
-		{"Inbound tunnels", "Tunele przychodzące"},
-		{"%dms", "%dms"},
-		{"Outbound tunnels", "Tunele wychodzące"},
-		{"Tags", "Tagi"},
-		{"Incoming", "Przychodzące"},
-		{"Outgoing", "Wychodzące"},
-		{"Destination", "Miejsce docelowe"},
-		{"Amount", "Ilość"},
-		{"Incoming Tags", "Przychodzące tagi"},
-		{"Tags sessions", "Sesje tagów"},
-		{"Status", "Status"},
-		{"Local Destination", "Lokalne miejsce docelowe"},
-		{"Streams", "Strumienie"},
-		{"Close stream", "Zamknij strumień"},
-		{"Such destination is not found", "Nie znaleziono takiego miejsca docelowego"},
-		{"I2CP session not found", "Sesja I2CP nie została znaleziona"},
-		{"I2CP is not enabled", "I2CP nie jest włączone"},
-		{"Invalid", "Niepoprawny"},
-		{"Store type", "Rodzaj przechowywania"},
-		{"Expires", "Wygasa za"},
-		{"Non Expired Leases", "Leasingi niewygasłe"},
-		{"Gateway", "Brama"},
-		{"TunnelID", "IDTunelu"},
-		{"EndDate", "DataZakończenia"},
-		{"floodfill mode is disabled", "tryb floodfill jest wyłączony"},
-		{"Queue size", "Wielkość kolejki"},
-		{"Run peer test", "Wykonaj test peer"},
-		{"Reload tunnels configuration", "Załaduj ponownie konfigurację tuneli"},
-		{"Decline transit tunnels", "Odrzuć tunele tranzytowe"},
-		{"Accept transit tunnels", "Akceptuj tunele tranzytowe"},
-		{"Cancel graceful shutdown", "Anuluj łagodne wyłączenie"},
-		{"Start graceful shutdown", "Rozpocznij łagodne wyłączenie"},
-		{"Force shutdown", "Wymuś wyłączenie"},
-		{"Reload external CSS styles", "Odśwież zewnętrzne style CSS"},
-		{"<b>Note:</b> any action done here are not persistent and not changes your config files.", "<b>Uwaga:</b> każda akcja wykonana tutaj nie jest trwała i nie zmienia Twoich plików konfiguracyjnych."},
-		{"Logging level", "Poziom logowania"},
-		{"Transit tunnels limit", "Limit tuneli tranzytowych"},
-		{"Change", "Zmień"},
-		{"Change language", "Zmień język"},
-		{"no transit tunnels currently built", "brak obecnie zbudowanych tuneli tranzytowych"},
-		{"SAM disabled", "SAM wyłączony"},
-		{"no sessions currently running", "brak aktualnie uruchomionych sesji"},
-		{"SAM session not found", "Sesja SAM nie została znaleziona"},
-		{"SAM Session", "Sesja SAM"},
-		{"Server Tunnels", "Tunele Serwera"},
-		{"Client Forwards", "Przekierowania Klienta"},
-		{"Server Forwards", "Przekierowania Serwera"},
-		{"Unknown page", "Nieznana strona"},
-		{"Invalid token", "Nieprawidłowy token"},
-		{"SUCCESS", "SUKCES"},
-		{"Stream closed", "Strumień zamknięty"},
-		{"Stream not found or already was closed", "Strumień nie został znaleziony lub został już zamknięty"},
-		{"Destination not found", "Nie znaleziono punktu docelowego"},
-		{"StreamID can't be null", "StreamID nie może być null"},
-		{"Return to destination page", "Wróć do strony miejsca docelowego"},
-		{"You will be redirected in %d seconds", "Zostaniesz prekierowany za %d sekund"},
-		{"LeaseSet expiration time updated", "Zaktualizowano czas wygaśnięcia LeaseSet"},
-		{"LeaseSet is not found or already expired", "LeaseSet nie został znaleziony lub już wygasł"},
-		{"Transit tunnels count must not exceed %d", "Liczba tuneli tranzytowych nie może przekraczać %d"},
-		{"Back to commands list", "Powrót do listy poleceń"},
-		{"Register at reg.i2p", "Zarejestruj się na reg.i2p"},
-		{"Description", "Opis"},
-		{"A bit information about service on domain", "Trochę informacji o usłudze w domenie"},
-		{"Submit", "Zatwierdź"},
-		{"Domain can't end with .b32.i2p", "Domena nie może kończyć się na .b32.i2p"},
-		{"Domain must end with .i2p", "Domena musi kończyć się na .i2p"},
-		{"Unknown command", "Nieznana komenda"},
-		{"Command accepted", "Polecenie zaakceptowane"},
-		{"Proxy error", "Błąd serwera proxy"},
-		{"Proxy info", "Informacje o proxy"},
-		{"Proxy error: Host not found", "Błąd proxy: Nie znaleziono hosta"},
-		{"Remote host not found in router's addressbook", "Nie znaleziono zdalnego hosta w książce adresowej routera"},
-		{"You may try to find this host on jump services below", "Możesz znaleźć tego hosta na poniższych usługach skoku"},
-		{"Invalid request", "Nieprawidłowe żądanie"},
-		{"Proxy unable to parse your request", "Serwer proxy nie może przetworzyć Twojego żądania"},
-		{"Addresshelper is not supported", "Adresshelper nie jest obsługiwany"},
-		{"Host %s is <font color=red>already in router's addressbook</font>. <b>Be careful: source of this URL may be harmful!</b> Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.", "Host %s <font color=red>jest już w książce adresowej routera</font>. <b>Uważaj: źródło tego adresu URL może być szkodliwe!</b> Kliknij tutaj, aby zaktualizować rekord: <a href=\"%s%s%s&update=true\">Kontynuuj</a>."},
-		{"Addresshelper forced update rejected", "Wymuszona aktualizacja Addreshelper odrzucona"},
-		{"To add host <b>%s</b> in router's addressbook, click here: <a href=\"%s%s%s\">Continue</a>.", "Aby dodać host <b>%s</b> w książce adresowej routera, kliknij tutaj: <a href=\"%s%s%s\">Kontynuuj</a>."},
-		{"Addresshelper request", "Prośba Addresshelper"},
-		{"Host %s added to router's addressbook from helper. Click here to proceed: <a href=\"%s\">Continue</a>.", "Host %s dodany do książki adresowej routera od pomocnika. Kliknij tutaj, aby kontynuować: <a href=\"%s\">Kontynuuj</a>."},
-		{"Addresshelper adding", "Dodawanie Addresshelper"},
-		{"Host %s is <font color=red>already in router's addressbook</font>. Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.", "Host %s jest <font color=red>już w książce adresowej routera</font>. Kliknij tutaj, aby zaktualizować rekord: <a href=\"%s%s%s&update=true\">Kontynuuj</a>."},
-		{"Addresshelper update", "Aktualizacja Adresshelper"},
-		{"Invalid request URI", "Nieprawidłowe URI żądania"},
-		{"Can't detect destination host from request", "Nie można wykryć hosta docelowego z żądania"},
-		{"Outproxy failure", "Błąd proxy wyjściowego"},
-		{"Bad outproxy settings", "Błędne ustawienia proxy wyjściowych"},
-		{"Host %s is not inside I2P network, but outproxy is not enabled", "Host %s nie jest wewnątrz sieci I2P, a proxy wyjściowe nie jest włączone"},
-		{"Unknown outproxy URL", "Nieznany adres URL proxy wyjściowego"},
-		{"Cannot resolve upstream proxy", "Nie można rozwiązać serwera proxy upstream"},
-		{"Hostname is too long", "Nazwa hosta jest zbyt długa"},
-		{"Cannot connect to upstream SOCKS proxy", "Nie można połączyć się z proxy SOCKS upstream"},
-		{"Cannot negotiate with SOCKS proxy", "Nie można negocjować z proxy SOCKS"},
-		{"CONNECT error", "Błąd POŁĄCZENIE"},
-		{"Failed to connect", "Nie udało się połączyć"},
-		{"SOCKS proxy error", "Błąd proxy SOCKS"},
-		{"Failed to send request to upstream", "Nie udało się wysłać żądania do upstream"},
-		{"No reply from SOCKS proxy", "Brak odpowiedzi od serwera proxy SOCKS"},
-		{"Cannot connect", "Nie można się połączyć"},
-		{"HTTP out proxy not implemented", "Serwer wyjściowy proxy HTTP nie został zaimplementowany"},
-		{"Cannot connect to upstream HTTP proxy", "Nie można połączyć się z proxy HTTP upstream"},
-		{"Host is down", "Host jest niedostępny"},
-		{"Can't create connection to requested host, it may be down. Please try again later.", "Nie można utworzyć połączenia z żądanym hostem, może być wyłączony. Spróbuj ponownie później."},
 		{"", ""},
 	};
 
 	static std::map<std::string, std::vector<std::string>> plurals
 	{
-		{"%d days", {"%d dzień", "%d dni", "%d dni", "%d dni"}},
-		{"%d hours", {"%d godzina", "%d godziny", "%d godzin", "%d godzin"}},
-		{"%d minutes", {"%d minuta", "%d minuty", "%d minut", "%d minut"}},
-		{"%d seconds", {"%d sekunda", "%d sekundy", "%d sekund", "%d sekund"}},
-		{"", {"", "", "", ""}},
+		{"", {"", "", ""}},
 	};
 
 	std::shared_ptr<const i2p::i18n::Locale> GetLocale()
diff --git a/i18n/Portuguese.cpp b/i18n/Portuguese.cpp
index 26204dc3..4c6d749f 100644
--- a/i18n/Portuguese.cpp
+++ b/i18n/Portuguese.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2023-2025, The PurpleI2P Project
+* Copyright (c) 2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -29,7 +29,7 @@ namespace portuguese // language namespace
 		return n != 1 ? 1 : 0;
 	}
 
-	static const LocaleStrings strings
+	static std::map<std::string, std::string> strings
 	{
 		{"%.2f KiB", "%.2f KiB"},
 		{"%.2f MiB", "%.2f MiB"},
@@ -58,7 +58,7 @@ namespace portuguese // language namespace
 		{"Unknown", "Desconhecido"},
 		{"Proxy", "Proxy"},
 		{"Mesh", "Malha"},
-		{"Clock skew", "Desvio de Relógio"},
+		{"Clock skew", "Defasagem do Relógio"},
 		{"Offline", "Desligado"},
 		{"Symmetric NAT", "NAT Simétrico"},
 		{"Full cone NAT", "Full cone NAT"},
@@ -74,7 +74,7 @@ namespace portuguese // language namespace
 		{"%.2f KiB/s", "%.2f KiB/s"},
 		{"Sent", "Enviado"},
 		{"Transit", "Trânsito"},
-		{"Data path", "Diretório de dados"},
+		{"Data path", "Diretório dos dados"},
 		{"Hidden content. Press on text to see.", "Conteúdo oculto. Clique no texto para revelar."},
 		{"Router Ident", "Identidade do Roteador"},
 		{"Router Family", "Família do Roteador"},
@@ -106,9 +106,9 @@ namespace portuguese // language namespace
 		{"Destination", "Destinos"},
 		{"Amount", "Quantidade"},
 		{"Incoming Tags", "Etiquetas de Entrada"},
-		{"Tags sessions", "Sessões de Etiquetas"},
+		{"Tags sessions", "Sessões de etiquetas"},
 		{"Status", "Estado"},
-		{"Local Destination", "Destino Local"},
+		{"Local Destination", "Destinos Locais"},
 		{"Streams", "Fluxos"},
 		{"Close stream", "Fechar fluxo"},
 		{"Such destination is not found", "Tal destino não foi encontrado"},
@@ -148,7 +148,7 @@ namespace portuguese // language namespace
 		{"Invalid token", "Token Inválido"},
 		{"SUCCESS", "SUCESSO"},
 		{"Stream closed", "Fluxo fechado"},
-		{"Stream not found or already was closed", "Fluxo não encontrado ou já fechado"},
+		{"Stream not found or already was closed", "Fluxo não encontrado ou já encerrado"},
 		{"Destination not found", "Destino não encontrado"},
 		{"StreamID can't be null", "StreamID não pode ser nulo"},
 		{"Return to destination page", "Retornar para à página de destino"},
@@ -157,7 +157,7 @@ namespace portuguese // language namespace
 		{"LeaseSet is not found or already expired", "LeaseSet não foi encontrado ou já expirou"},
 		{"Transit tunnels count must not exceed %d", "A contagem de túneis de trânsito não deve exceder %d"},
 		{"Back to commands list", "Voltar para a lista de comandos"},
-		{"Register at reg.i2p", "Registrar em reg.i2p"},
+		{"Register at reg.i2p", "Registrar na reg.i2p"},
 		{"Description", "Descrição"},
 		{"A bit information about service on domain", "Algumas informações sobre o serviço no domínio"},
 		{"Submit", "Enviar"},
@@ -169,22 +169,22 @@ namespace portuguese // language namespace
 		{"Proxy info", "Informações do proxy"},
 		{"Proxy error: Host not found", "Erro no proxy: Host não encontrado"},
 		{"Remote host not found in router's addressbook", "O host remoto não foi encontrado no livro de endereços do roteador"},
-		{"You may try to find this host on jump services below", "Você pode tentar encontrar este host nos serviços de jump abaixo"},
+		{"You may try to find this host on jump services below", "Você pode tentar encontrar este host nos jump services abaixo"},
 		{"Invalid request", "Requisição inválida"},
 		{"Proxy unable to parse your request", "O proxy foi incapaz de processar a sua requisição"},
 		{"Addresshelper is not supported", "O Auxiliar de Endereços não é suportado"},
 		{"Host %s is <font color=red>already in router's addressbook</font>. <b>Be careful: source of this URL may be harmful!</b> Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.", "O host %s já <font color=red>está no catálogo de endereços do roteador</font>. <b>Cuidado: a fonte desta URL pode ser perigosa!</b> Clique aqui para atualizar o registro: <a href=\"%s%s%s&update=true\">Continuar</a>."},
 		{"Addresshelper forced update rejected", "A atualização forçada do Auxiliar de Endereços foi rejeitada"},
 		{"To add host <b>%s</b> in router's addressbook, click here: <a href=\"%s%s%s\">Continue</a>.", "Para adicionar o host <b> %s </b> ao catálogo de endereços do roteador, clique aqui: <a href='%s%s%s'>Continuar </a>."},
-		{"Addresshelper request", "Requisição ao Auxiliar de Endereços"},
-		{"Host %s added to router's addressbook from helper. Click here to proceed: <a href=\"%s\">Continue</a>.", "O host %s foi adicionado ao catálogo de endereços do roteador por um auxiliar. Clique aqui para prosseguir: <a href='%s'> Continuar </a>."},
+		{"Addresshelper request", "Requisição do Auxiliar de Endereços"},
+		{"Host %s added to router's addressbook from helper. Click here to proceed: <a href=\"%s\">Continue</a>.", "O host %s foi adicionado ao catálogo de endereços do roteador por um auxiliar. Clique aqui para proceder: <a href='%s'> Continuar </a>."},
 		{"Addresshelper adding", "Auxiliar de Endereço adicionando"},
 		{"Host %s is <font color=red>already in router's addressbook</font>. Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.", "O host %s já <font color=red>está no catálogo de endereços do roteador </font>. Clique aqui para atualizar o registro: <a href=\"%s%s%s&update=true\">Continuar</a>."},
 		{"Addresshelper update", "Atualização do Auxiliar de Endereços"},
 		{"Invalid request URI", "A URI de requisição é inválida"},
 		{"Can't detect destination host from request", "Incapaz de detectar o host de destino da requisição"},
 		{"Outproxy failure", "Falha no outproxy"},
-		{"Bad outproxy settings", "Má configurações do outproxy"},
+		{"Bad outproxy settings", "Configurações ruins de outproxy"},
 		{"Host %s is not inside I2P network, but outproxy is not enabled", "O host %s não está dentro da rede I2P, mas o outproxy não está ativado"},
 		{"Unknown outproxy URL", "URL de outproxy desconhecida"},
 		{"Cannot resolve upstream proxy", "Não é possível resolver o proxy de entrada"},
diff --git a/i18n/Russian.cpp b/i18n/Russian.cpp
index 235cc0ae..15952710 100644
--- a/i18n/Russian.cpp
+++ b/i18n/Russian.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2021-2025, The PurpleI2P Project
+* Copyright (c) 2021-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -29,7 +29,7 @@ namespace russian // language namespace
 		return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
 	}
 
-	static const LocaleStrings strings
+	static std::map<std::string, std::string> strings
 	{
 		{"%.2f KiB", "%.2f КиБ"},
 		{"%.2f MiB", "%.2f МиБ"},
diff --git a/i18n/Spanish.cpp b/i18n/Spanish.cpp
index 0e657fb4..a5ecc30a 100644
--- a/i18n/Spanish.cpp
+++ b/i18n/Spanish.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2022-2025, The PurpleI2P Project
+* Copyright (c) 2022-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -29,7 +29,7 @@ namespace spanish // language namespace
 		return n != 1 ? 1 : 0;
 	}
 
-	static const LocaleStrings strings
+	static std::map<std::string, std::string> strings
 	{
 		{"%.2f KiB", "%.2f KiB"},
 		{"%.2f MiB", "%.2f MiB"},
diff --git a/i18n/Swedish.cpp b/i18n/Swedish.cpp
index df13d22f..05ed1e18 100644
--- a/i18n/Swedish.cpp
+++ b/i18n/Swedish.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2023-2025, The PurpleI2P Project
+* Copyright (c) 2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -29,7 +29,7 @@ namespace swedish // language namespace
 		return n != 1 ? 1 : 0;
 	}
 
-	static const LocaleStrings strings
+	static std::map<std::string, std::string> strings
 	{
 		{"%.2f KiB", "%.2f KiB"},
 		{"%.2f MiB", "%.2f MiB"},
diff --git a/i18n/Turkish.cpp b/i18n/Turkish.cpp
index 9946b336..d4398ebe 100644
--- a/i18n/Turkish.cpp
+++ b/i18n/Turkish.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2023-2025, The PurpleI2P Project
+* Copyright (c) 2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -29,7 +29,7 @@ namespace turkish // language namespace
 		return n != 1 ? 1 : 0;
 	}
 
-	static const LocaleStrings strings
+	static std::map<std::string, std::string> strings
 	{
 		{"%.2f KiB", "%.2f KiB"},
 		{"%.2f MiB", "%.2f MiB"},
diff --git a/i18n/Turkmen.cpp b/i18n/Turkmen.cpp
index 7efb8891..35ee0f89 100644
--- a/i18n/Turkmen.cpp
+++ b/i18n/Turkmen.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2021-2025, The PurpleI2P Project
+* Copyright (c) 2021-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -29,7 +29,7 @@ namespace turkmen // language namespace
 		return n != 1 ? 1 : 0;
 	}
 
-	static const LocaleStrings strings
+	static std::map<std::string, std::string> strings
 	{
 		{"%.2f KiB", "%.2f KiB"},
 		{"%.2f MiB", "%.2f MiB"},
diff --git a/i18n/Ukrainian.cpp b/i18n/Ukrainian.cpp
index c1b6c772..d089c142 100644
--- a/i18n/Ukrainian.cpp
+++ b/i18n/Ukrainian.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2021-2025, The PurpleI2P Project
+* Copyright (c) 2021-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -29,7 +29,7 @@ namespace ukrainian // language namespace
 		return n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;
 	}
 
-	static const LocaleStrings strings
+	static std::map<std::string, std::string> strings
 	{
 		{"%.2f KiB", "%.2f КіБ"},
 		{"%.2f MiB", "%.2f МіБ"},
diff --git a/i18n/Uzbek.cpp b/i18n/Uzbek.cpp
index 8e870772..cf94a489 100644
--- a/i18n/Uzbek.cpp
+++ b/i18n/Uzbek.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2021-2025, The PurpleI2P Project
+* Copyright (c) 2021-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -29,7 +29,7 @@ namespace uzbek // language namespace
 		return n > 1 ? 1 : 0;
 	}
 
-	static const LocaleStrings strings
+	static std::map<std::string, std::string> strings
 	{
 		{"%.2f KiB", "%.2f KiB"},
 		{"%.2f MiB", "%.2f MiB"},
diff --git a/libi2pd/Base.cpp b/libi2pd/Base.cpp
index bc9da4fb..b8de571b 100644
--- a/libi2pd/Base.cpp
+++ b/libi2pd/Base.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -15,7 +15,7 @@ namespace i2p
 {
 namespace data
 {
-	static constexpr char T32[32] =
+	static const char T32[32] =
 	{
 		'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
 		'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
@@ -27,6 +27,11 @@ namespace data
 	{
 		return T32;
 	}
+
+	bool IsBase32 (char ch)
+	{
+		return (ch >= 'a' && ch <= 'z') || (ch >= '2' && ch <= '7');
+	}	
 	
 	static void iT64Build(void);
 
@@ -38,7 +43,7 @@ namespace data
 	* Direct Substitution Table
 	*/
 
-	static constexpr char T64[64] =
+	static const char T64[64] =
 	{
 		'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
 		'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
@@ -54,17 +59,24 @@ namespace data
 	{
 		return T64;
 	}
+
+	bool IsBase64 (char ch)
+	{
+		return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '-' || ch == '~';
+	}	
 	
 	/*
 	* Reverse Substitution Table (built in run time)
 	*/
+
 	static char iT64[256];
 	static int isFirstTime = 1;
 
 	/*
 	* Padding
 	*/
-	static constexpr char P64 = '=';
+
+	static char P64 = '=';
 
 	/*
 	*
@@ -74,112 +86,134 @@ namespace data
 	* Converts binary encoded data to BASE64 format.
 	*
 	*/
-	std::string ByteStreamToBase64 (// base64 encoded string 
-		const uint8_t * InBuffer, 	// Input buffer, binary data 
-	    size_t InCount 				// Number of bytes in the input buffer 
+
+	size_t ByteStreamToBase64 (   /* Number of bytes in the encoded buffer */
+		const uint8_t * InBuffer, /* Input buffer, binary data */
+		size_t InCount,           /* Number of bytes in the input buffer */
+		char * OutBuffer,         /* output buffer */
+		size_t len                /* length of output buffer */
 	)
 	{
 		unsigned char * ps;
+		unsigned char * pd;
 		unsigned char   acc_1;
 		unsigned char   acc_2;
 		int             i;
 		int             n;
 		int             m;
+		size_t          outCount;
 
 		ps = (unsigned char *)InBuffer;
 		n = InCount / 3;
 		m = InCount % 3;
-		size_t outCount = m ? (4 * (n + 1)) : (4 * n);
+		if (!m)
+			outCount = 4 * n;
+		else
+			outCount = 4 * (n + 1);
 
-		std::string out;
-		out.reserve (outCount);
+		if (outCount > len) return 0;
+
+		pd = (unsigned char *)OutBuffer;
 		for ( i = 0; i < n; i++ )
 		{
 			acc_1 = *ps++;
 			acc_2 = (acc_1 << 4) & 0x30;
-			acc_1 >>= 2;                 // base64 digit #1 
-			out.push_back (T64[acc_1]);
+			acc_1 >>= 2;                 /* base64 digit #1 */
+			*pd++ = T64[acc_1];
 			acc_1 = *ps++;
-			acc_2 |= acc_1 >> 4;         // base64 digit #2
-			out.push_back (T64[acc_2]);
+			acc_2 |= acc_1 >> 4;         /* base64 digit #2 */
+			*pd++ = T64[acc_2];
 			acc_1 &= 0x0f;
 			acc_1 <<= 2;
 			acc_2 = *ps++;
-			acc_1 |= acc_2 >> 6;         // base64 digit #3
-			out.push_back (T64[acc_1]);
-			acc_2 &= 0x3f;               // base64 digit #4
-			out.push_back (T64[acc_2]);
+			acc_1 |= acc_2 >> 6;         /* base64 digit #3 */
+			*pd++ = T64[acc_1];
+			acc_2 &= 0x3f;               /* base64 digit #4 */
+			*pd++ = T64[acc_2];
 		}
 		if ( m == 1 )
 		{
 			acc_1 = *ps++;
-			acc_2 = (acc_1 << 4) & 0x3f; // base64 digit #2
-			acc_1 >>= 2;                 // base64 digit #1
-			out.push_back (T64[acc_1]);
-			out.push_back (T64[acc_2]);
-			out.push_back (P64);
-			out.push_back (P64);
+			acc_2 = (acc_1 << 4) & 0x3f; /* base64 digit #2 */
+			acc_1 >>= 2;                 /* base64 digit #1 */
+			*pd++ = T64[acc_1];
+			*pd++ = T64[acc_2];
+			*pd++ = P64;
+			*pd++ = P64;
 
 		}
 		else if ( m == 2 )
 		{
 			acc_1 = *ps++;
 			acc_2 = (acc_1 << 4) & 0x3f;
-			acc_1 >>= 2;                 // base64 digit #1
-			out.push_back (T64[acc_1]);
+			acc_1 >>= 2;                 /* base64 digit #1 */
+			*pd++ = T64[acc_1];
 			acc_1 = *ps++;
-			acc_2 |= acc_1 >> 4;         // base64 digit #2
-			out.push_back (T64[acc_2]);
+			acc_2 |= acc_1 >> 4;         /* base64 digit #2 */
+			*pd++ = T64[acc_2];
 			acc_1 &= 0x0f;
-			acc_1 <<= 2;                 // base64 digit #3
-			out.push_back (T64[acc_1]);
-			out.push_back (P64);
+			acc_1 <<= 2;                 /* base64 digit #3 */
+			*pd++ = T64[acc_1];
+			*pd++ = P64;
 		}
 
-		return out;
-	}	
-	
+		return outCount;
+	}
+
 	/*
 	*
 	* Base64ToByteStream
 	* ------------------
 	*
-	* Converts BASE64 encoded string to binary format. If input buffer is
+	* Converts BASE64 encoded data to binary format. If input buffer is
 	* not properly padded, buffer of negative length is returned
 	*
 	*/
-	size_t Base64ToByteStream (		// Number of output bytes 
-		std::string_view base64Str,	// BASE64 encoded string  
-	    uint8_t * OutBuffer, 		// output buffer length 
-	    size_t len					// length of output buffer 
+
+	size_t Base64ToByteStream ( /* Number of output bytes */
+		const char * InBuffer,  /* BASE64 encoded buffer */
+		size_t InCount,         /* Number of input bytes */
+		uint8_t * OutBuffer,    /* output buffer length */
+		size_t len              /* length of output buffer */
 	)
 	{
+		unsigned char * ps;
 		unsigned char * pd;
 		unsigned char   acc_1;
 		unsigned char   acc_2;
+		int             i;
+		int             n;
+		int             m;
 		size_t          outCount;
 
-		if (base64Str.empty () || base64Str[0] == P64) return 0;
-		auto d = std::div (base64Str.length (), 4);
-		if (!d.rem)
-			outCount = 3 * d.quot;
+		if (isFirstTime)
+			iT64Build();
+
+		n = InCount / 4;
+		m = InCount % 4;
+
+		if (InCount && !m)
+			outCount = 3 * n;
 		else
 			return 0;
 
-		if (isFirstTime) iT64Build();
+		if(*InBuffer == P64)
+			return 0;
+
+		ps = (unsigned char *)(InBuffer + InCount - 1);
+		while ( *ps-- == P64 )
+			outCount--;
+		ps = (unsigned char *)InBuffer;
+
+		if (outCount > len)
+			return 0;
 
-		auto pos = base64Str.find_last_not_of (P64);
-		if (pos == base64Str.npos) return 0;
-		outCount -= (base64Str.length () - pos - 1);
-		if (outCount > len) return 0;
-		
-		auto ps = base64Str.begin ();
 		pd = OutBuffer;
 		auto endOfOutBuffer = OutBuffer + outCount;
-		for (int i = 0; i < d.quot; i++)
+		for ( i = 0; i < n; i++ )
 		{
-			acc_1 = iT64[int(*ps++)];
-			acc_2 = iT64[int(*ps++)];
+			acc_1 = iT64[*ps++];
+			acc_2 = iT64[*ps++];
 			acc_1 <<= 2;
 			acc_1 |= acc_2 >> 4;
 			*pd++ = acc_1;
@@ -187,30 +221,45 @@ namespace data
 				break;
 
 			acc_2 <<= 4;
-			acc_1 = iT64[int(*ps++)];
+			acc_1 = iT64[*ps++];
 			acc_2 |= acc_1 >> 2;
 			*pd++ = acc_2;
 			if (pd >= endOfOutBuffer)
 				break;
 
-			acc_2 = iT64[int(*ps++)];
+			acc_2 = iT64[*ps++];
 			acc_2 |= acc_1 << 6;
 			*pd++ = acc_2;
 		}
 
 		return outCount;
-	}	
-	
-	std::string ToBase64Standard (std::string_view in)
+	}
+
+	size_t Base64EncodingBufferSize (const size_t input_size)
 	{
-		auto str = ByteStreamToBase64 ((const uint8_t *)in.data (), in.length ());
+		auto d = div (input_size, 3);
+		if (d.rem)
+			d.quot++;
+
+		return 4 * d.quot;
+	}
+
+	std::string ToBase64Standard (const std::string& in)
+	{
+		auto len = Base64EncodingBufferSize (in.length ());
+		char * str = new char[len + 1];
+		auto l = ByteStreamToBase64 ((const uint8_t *)in.c_str (), in.length (), str, len);
+		str[l] = 0;
 		// replace '-' by '+' and '~' by '/'
-		for (auto& ch: str)
-			if (ch == '-')
-				ch = '+';
-			else if (ch == '~')
-				ch = '/';
-		return str;
+		for (size_t i = 0; i < l; i++)
+			if (str[i] == '-')
+				str[i] = '+';
+			else if (str[i] == '~')
+				str[i] = '/';
+
+		std::string s(str);
+		delete[] str;
+		return s;
 	}
 
 	/*
@@ -231,12 +280,13 @@ namespace data
 		iT64[(int)P64] = 0;
 	}
 
-	size_t Base32ToByteStream (std::string_view base32Str, uint8_t * outBuf, size_t outLen)
+	size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen)
 	{
 		unsigned int tmp = 0, bits = 0;
 		size_t ret = 0;
-		for (auto ch: base32Str)
+		for (size_t i = 0; i < len; i++)
 		{
+			char ch = inBuf[i];
 			if (ch >= '2' && ch <= '7') // digit
 				ch = (ch - '2') + 26; // 26 means a-z
 			else if (ch >= 'a' && ch <= 'z')
@@ -256,15 +306,13 @@ namespace data
 			tmp <<= 5;
 		}
 		return ret;
-	}	
-	
-	std::string ByteStreamToBase32 (const uint8_t * inBuf, size_t len)
+	}
+
+	size_t ByteStreamToBase32 (const uint8_t * inBuf, size_t len, char * outBuf, size_t outLen)
 	{
-		std::string out;
-		out.reserve ((len * 8 + 4) / 5);
-		size_t pos = 1;
+		size_t ret = 0, pos = 1;
 		unsigned int bits = 8, tmp = inBuf[0];
-		while (bits > 0 || pos < len)
+		while (ret < outLen && (bits > 0 || pos < len))
 		{
 			if (bits < 5)
 			{
@@ -284,9 +332,10 @@ namespace data
 
 			bits -= 5;
 			int ind = (tmp >> bits) & 0x1F;
-			out.push_back ((ind < 26) ? (ind + 'a') : ((ind - 26) + '2'));
+			outBuf[ret] = (ind < 26) ? (ind + 'a') : ((ind - 26) + '2');
+			ret++;
 		}
-		return out;
-	}	
+		return ret;
+	}
 }
 }
diff --git a/libi2pd/Base.h b/libi2pd/Base.h
index 945dc8b3..a163435c 100644
--- a/libi2pd/Base.h
+++ b/libi2pd/Base.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -11,42 +11,27 @@
 
 #include <inttypes.h>
 #include <string>
-#include <string_view>
-#include <cstdlib>
-
-namespace i2p 
-{
-namespace data 
-{
-	std::string ByteStreamToBase64 (const uint8_t * InBuffer, size_t InCount);
-	size_t Base64ToByteStream (std::string_view base64Str, uint8_t * OutBuffer, size_t len);
+#include <iostream>
 
+namespace i2p {
+namespace data {
+	size_t ByteStreamToBase64 (const uint8_t * InBuffer, size_t InCount, char * OutBuffer, size_t len);
+	size_t Base64ToByteStream (const char * InBuffer, size_t InCount, uint8_t * OutBuffer, size_t len );
 	const char * GetBase32SubstitutionTable ();
 	const char * GetBase64SubstitutionTable ();
-	constexpr bool IsBase64 (char ch)
-	{
-		return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '-' || ch == '~';
-	}	
+	bool IsBase64 (char ch);
 
-	size_t Base32ToByteStream (std::string_view base32Str, uint8_t * outBuf, size_t outLen);
-	std::string ByteStreamToBase32 (const uint8_t * inBuf, size_t len);	
-	constexpr bool IsBase32 (char ch)
-	{
-		return (ch >= 'a' && ch <= 'z') || (ch >= '2' && ch <= '7');
-	}	
+	size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen);
+	size_t ByteStreamToBase32 (const uint8_t * InBuf, size_t len, char * outBuf, size_t outLen);
+	bool IsBase32 (char ch);
 
 	/**
 	 * Compute the size for a buffer to contain encoded base64 given that the size of the input is input_size bytes
 	 */
-	inline size_t Base64EncodingBufferSize(size_t input_size)
-	{
-		auto d = std::div (input_size, 3);
-		if (d.rem) d.quot++;
-		return 4 * d.quot;
-	}	
+	size_t Base64EncodingBufferSize(const size_t input_size);
+
+	std::string ToBase64Standard (const std::string& in); // using standard table, for Proxy-Authorization
 
-	std::string ToBase64Standard (std::string_view in); // using standard table, for Proxy-Authorization
-	
 } // data
 } // i2p
 
diff --git a/libi2pd/Blinding.cpp b/libi2pd/Blinding.cpp
index a661b428..ced086e1 100644
--- a/libi2pd/Blinding.cpp
+++ b/libi2pd/Blinding.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2022, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -152,11 +152,11 @@ namespace data
 			m_BlindedSigType = m_SigType;
 	}
 
-	BlindedPublicKey::BlindedPublicKey (std::string_view b33):
+	BlindedPublicKey::BlindedPublicKey (const std::string& b33):
 		m_SigType (0) // 0 means invalid, we can't blind DSA, set it later
 	{
 		uint8_t addr[40]; // TODO: define length from b33
-		size_t l = i2p::data::Base32ToByteStream (b33, addr, 40);
+		size_t l = i2p::data::Base32ToByteStream (b33.c_str (), b33.length (), addr, 40);
 		if (l < 32)
 		{
 			LogPrint (eLogError, "Blinding: Malformed b33 ", b33);
@@ -198,7 +198,7 @@ namespace data
 	std::string BlindedPublicKey::ToB33 () const
 	{
 		if (m_PublicKey.size () > 32) return ""; // assume 25519
-		uint8_t addr[35];
+		uint8_t addr[35]; char str[60]; // TODO: define actual length
 		uint8_t flags = 0;
 		if (m_IsClientAuth) flags |= B33_PER_CLIENT_AUTH_FLAG;
 		addr[0] = flags; // flags
@@ -208,7 +208,8 @@ namespace data
 		uint32_t checksum = crc32 (0, addr + 3, m_PublicKey.size ());
 		// checksum is Little Endian
 		addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16);
-		return ByteStreamToBase32 (addr, m_PublicKey.size () + 3);
+		auto l = ByteStreamToBase32 (addr, m_PublicKey.size () + 3, str, 60);
+		return std::string (str, str + l);
 	}
 
 	void BlindedPublicKey::GetCredential (uint8_t * credential) const
diff --git a/libi2pd/Blinding.h b/libi2pd/Blinding.h
index fc11f613..c78db003 100644
--- a/libi2pd/Blinding.h
+++ b/libi2pd/Blinding.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2020, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -11,7 +11,6 @@
 
 #include <inttypes.h>
 #include <string>
-#include <string_view>
 #include <vector>
 #include "Identity.h"
 
@@ -24,7 +23,7 @@ namespace data
 		public:
 
 			BlindedPublicKey (std::shared_ptr<const IdentityEx> identity, bool clientAuth = false);
-			BlindedPublicKey (std::string_view b33); // from b33 without .b32.i2p
+			BlindedPublicKey (const std::string& b33); // from b33 without .b32.i2p
 			std::string ToB33 () const;
 
 			const uint8_t * GetPublicKey () const { return m_PublicKey.data (); };
diff --git a/libi2pd/CPU.cpp b/libi2pd/CPU.cpp
new file mode 100644
index 00000000..77820c88
--- /dev/null
+++ b/libi2pd/CPU.cpp
@@ -0,0 +1,68 @@
+/*
+* Copyright (c) 2013-2023, The PurpleI2P Project
+*
+* This file is part of Purple i2pd project and licensed under BSD3
+*
+* See full license text in LICENSE file at top of project tree
+*/
+
+#include "CPU.h"
+#include "Log.h"
+
+#ifndef bit_AES
+	#define bit_AES (1 << 25)
+#endif
+
+#if defined(__GNUC__) && __GNUC__ < 6 && IS_X86
+	#include <cpuid.h>
+#endif
+
+#ifdef _MSC_VER
+	#include <intrin.h>
+#endif
+
+namespace i2p
+{
+namespace cpu
+{
+	bool aesni = false;
+
+	inline bool cpu_support_aes()
+	{
+#if IS_X86
+#if defined(__clang__)
+#	if (__clang_major__ >= 6)
+		__builtin_cpu_init();
+#	endif
+		return __builtin_cpu_supports("aes");
+#elif (defined(__GNUC__) && __GNUC__ >= 6)
+		__builtin_cpu_init();
+		return __builtin_cpu_supports("aes");
+#elif (defined(__GNUC__) && __GNUC__ < 6)
+		int cpu_info[4];
+		bool flag = false;
+		__cpuid(0, cpu_info[0], cpu_info[1], cpu_info[2], cpu_info[3]);
+		if (cpu_info[0] >= 0x00000001) {
+			__cpuid(0x00000001, cpu_info[0], cpu_info[1], cpu_info[2], cpu_info[3]);
+			flag = ((cpu_info[2] & bit_AES) != 0);
+		}
+		return flag;
+#elif defined(_MSC_VER)
+		int cpu_info[4];
+		__cpuid(cpu_info, 1);
+		return ((cpu_info[2] & bit_AES) != 0);
+#endif
+#endif
+		return false;
+	}
+
+	void Detect(bool AesSwitch, bool force)
+	{
+		if ((cpu_support_aes() && AesSwitch) || (AesSwitch && force)) {
+			aesni = true;
+		}
+
+		LogPrint(eLogInfo, "AESNI ", (aesni ? "enabled" : "disabled"));
+	}
+}
+}
diff --git a/libi2pd/CPU.h b/libi2pd/CPU.h
index 3fc38d47..1c30db48 100644
--- a/libi2pd/CPU.h
+++ b/libi2pd/CPU.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -21,4 +21,20 @@
 #	define IS_X86_64 0
 #endif
 
+#if defined(__AES__) && !defined(_MSC_VER) && IS_X86
+#	define SUPPORTS_AES 1
+#else
+#	define SUPPORTS_AES 0
+#endif
+
+namespace i2p
+{
+namespace cpu
+{
+	extern bool aesni;
+
+	void Detect(bool AesSwitch, bool force);
+}
+}
+
 #endif
diff --git a/libi2pd/ChaCha20.cpp b/libi2pd/ChaCha20.cpp
new file mode 100644
index 00000000..66bc135f
--- /dev/null
+++ b/libi2pd/ChaCha20.cpp
@@ -0,0 +1,137 @@
+/*
+* Copyright (c) 2013-2020, The PurpleI2P Project
+*
+* This file is part of Purple i2pd project and licensed under BSD3
+*
+* See full license text in LICENSE file at top of project tree
+*
+* Kovri go write your own code
+*
+*/
+
+#include "I2PEndian.h"
+#include "ChaCha20.h"
+
+#if !OPENSSL_AEAD_CHACHA20_POLY1305
+namespace i2p
+{
+namespace crypto
+{
+namespace chacha
+{
+void u32t8le(uint32_t v, uint8_t * p)
+{
+	p[0] = v & 0xff;
+	p[1] = (v >> 8) & 0xff;
+	p[2] = (v >> 16) & 0xff;
+	p[3] = (v >> 24) & 0xff;
+}
+
+uint32_t u8t32le(const uint8_t * p)
+{
+	uint32_t value = p[3];
+
+	value = (value << 8) | p[2];
+	value = (value << 8) | p[1];
+	value = (value << 8) | p[0];
+
+	return value;
+}
+
+uint32_t rotl32(uint32_t x, int n)
+{
+	return x << n | (x >> (-n & 31));
+}
+
+void quarterround(uint32_t *x, int a, int b, int c, int d)
+{
+	x[a] += x[b]; x[d] = rotl32(x[d] ^ x[a], 16);
+	x[c] += x[d]; x[b] = rotl32(x[b] ^ x[c], 12);
+	x[a] += x[b]; x[d] = rotl32(x[d] ^ x[a],  8);
+	x[c] += x[d]; x[b] = rotl32(x[b] ^ x[c],  7);
+}
+
+
+void Chacha20Block::operator << (const Chacha20State & st)
+{
+	int i;
+	for (i = 0; i < 16; i++)
+		u32t8le(st.data[i], data + (i << 2));
+}
+
+void block (Chacha20State &input, int rounds)
+{
+	int i;
+	Chacha20State x;
+	x.Copy(input);
+
+	for (i = rounds; i > 0; i -= 2)
+	{
+		quarterround(x.data, 0, 4,  8, 12);
+		quarterround(x.data, 1, 5,  9, 13);
+		quarterround(x.data, 2, 6, 10, 14);
+		quarterround(x.data, 3, 7, 11, 15);
+		quarterround(x.data, 0, 5, 10, 15);
+		quarterround(x.data, 1, 6, 11, 12);
+		quarterround(x.data, 2, 7,  8, 13);
+		quarterround(x.data, 3, 4,  9, 14);
+	}
+	x += input;
+	input.block << x;
+}
+
+void Chacha20Init (Chacha20State& state, const uint8_t * nonce, const uint8_t * key, uint32_t counter)
+{
+	state.data[0] = 0x61707865;
+	state.data[1] = 0x3320646e;
+	state.data[2] = 0x79622d32;
+	state.data[3] = 0x6b206574;
+	for (size_t i = 0; i < 8; i++)
+		state.data[4 + i] = chacha::u8t32le(key + i * 4);
+
+	state.data[12] = htole32 (counter);
+	for (size_t i = 0; i < 3; i++)
+		state.data[13 + i] = chacha::u8t32le(nonce + i * 4);
+}
+
+void Chacha20SetCounter (Chacha20State& state, uint32_t counter)
+{
+	state.data[12] = htole32 (counter);
+	state.offset = 0;
+}
+
+void Chacha20Encrypt (Chacha20State& state, uint8_t * buf, size_t sz)
+{
+	if (state.offset > 0)
+	{
+		// previous block if any
+		auto s = chacha::blocksize - state.offset;
+		if (sz < s) s = sz;
+		for (size_t i = 0; i < s; i++)
+			buf[i] ^= state.block.data[state.offset + i];
+		buf += s;
+		sz -= s;
+		state.offset += s;
+		if (state.offset >= chacha::blocksize) state.offset = 0;
+	}
+	for (size_t i = 0; i < sz; i += chacha::blocksize)
+	{
+		chacha::block(state, chacha::rounds);
+		state.data[12]++;
+		for (size_t j = i; j < i + chacha::blocksize; j++)
+		{
+			if (j >= sz)
+			{
+				state.offset = j & 0x3F; // % 64
+				break;
+			}
+			buf[j] ^= state.block.data[j - i];
+		}
+	}
+}
+
+} // namespace chacha
+} // namespace crypto
+} // namespace i2p
+
+#endif
diff --git a/libi2pd/ChaCha20.h b/libi2pd/ChaCha20.h
new file mode 100644
index 00000000..4364024b
--- /dev/null
+++ b/libi2pd/ChaCha20.h
@@ -0,0 +1,72 @@
+/*
+* Copyright (c) 2013-2020, The PurpleI2P Project
+*
+* This file is part of Purple i2pd project and licensed under BSD3
+*
+* See full license text in LICENSE file at top of project tree
+*
+* Kovri go write your own code
+*
+*/
+#ifndef LIBI2PD_CHACHA20_H
+#define LIBI2PD_CHACHA20_H
+#include <cstdint>
+#include <cstring>
+#include <inttypes.h>
+#include <string.h>
+#include "Crypto.h"
+
+#if !OPENSSL_AEAD_CHACHA20_POLY1305
+namespace i2p
+{
+namespace crypto
+{
+	const std::size_t CHACHA20_KEY_BYTES = 32;
+	const std::size_t CHACHA20_NOUNCE_BYTES = 12;
+
+namespace chacha
+{
+	constexpr std::size_t blocksize = 64;
+	constexpr int rounds = 20;
+
+	struct Chacha20State;
+	struct Chacha20Block
+	{
+		Chacha20Block () {};
+		Chacha20Block (Chacha20Block &&) = delete;
+
+		uint8_t data[blocksize];
+
+		void operator << (const Chacha20State & st);
+	};
+
+	struct Chacha20State
+	{
+		Chacha20State (): offset (0) {};
+		Chacha20State (Chacha20State &&) = delete;
+
+		Chacha20State & operator += (const Chacha20State & other)
+		{
+			for(int i = 0; i < 16; i++)
+				data[i] += other.data[i];
+			return *this;
+		}
+
+		void Copy(const Chacha20State & other)
+		{
+			memcpy(data, other.data, sizeof(uint32_t) * 16);
+		}
+		uint32_t data[16];
+		Chacha20Block block;
+		size_t offset;
+	};
+
+	void Chacha20Init (Chacha20State& state, const uint8_t * nonce, const uint8_t * key, uint32_t counter);
+	void Chacha20SetCounter (Chacha20State& state, uint32_t counter);
+	void Chacha20Encrypt (Chacha20State& state, uint8_t * buf, size_t sz); // encrypt buf in place
+} // namespace chacha
+} // namespace crypto
+} // namespace i2p
+
+#endif
+#endif
diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp
index bd9329a1..4deb59ac 100644
--- a/libi2pd/Config.cpp
+++ b/libi2pd/Config.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -77,7 +77,7 @@ namespace config {
 		limits.add_options()
 			("limits.coresize", value<uint32_t>()->default_value(0),          "Maximum size of corefile in Kb (0 - use system limit)")
 			("limits.openfiles", value<uint16_t>()->default_value(0),         "Maximum number of open files (0 - use system default)")
-			("limits.transittunnels", value<uint32_t>()->default_value(10000), "Maximum active transit tunnels (default:10000)")
+			("limits.transittunnels", value<uint16_t>()->default_value(5000), "Maximum active transit tunnels (default:5000)")
 			("limits.zombies", value<double>()->default_value(0),             "Minimum percentage of successfully created tunnels under which tunnel cleanup is paused (default [%]: 0.00)")
 			("limits.ntcpsoft", value<uint16_t>()->default_value(0),          "Ignored")
 			("limits.ntcphard", value<uint16_t>()->default_value(0),          "Ignored")
@@ -117,14 +117,9 @@ namespace config {
 			("httpproxy.latency.max", value<std::string>()->default_value("0"),       "HTTP proxy max latency for tunnels")
 			("httpproxy.outproxy", value<std::string>()->default_value(""),           "HTTP proxy upstream out proxy url")
 			("httpproxy.addresshelper", value<bool>()->default_value(true),           "Enable or disable addresshelper")
-			("httpproxy.senduseragent", value<bool>()->default_value(false),          "Pass through user's User-Agent if enabled. Disabled by default")
 			("httpproxy.i2cp.leaseSetType", value<std::string>()->default_value("3"), "Local destination's LeaseSet type")
 			("httpproxy.i2cp.leaseSetEncType", value<std::string>()->default_value("0,4"), "Local destination's LeaseSet encryption type")
 			("httpproxy.i2cp.leaseSetPrivKey", value<std::string>()->default_value(""), "LeaseSet private key")
-			("httpproxy.i2p.streaming.maxOutboundSpeed", value<std::string>()->default_value("1730000000"), "Max outbound speed of HTTP proxy stream in bytes/sec")
-			("httpproxy.i2p.streaming.maxInboundSpeed", value<std::string>()->default_value("1730000000"), "Max inbound speed of HTTP proxy stream in bytes/sec")
-			("httpproxy.i2p.streaming.profile", value<std::string>()->default_value("1"), "HTTP Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)")
-
 		;
 
 		options_description socksproxy("SOCKS Proxy options");
@@ -149,22 +144,8 @@ namespace config {
 			("socksproxy.i2cp.leaseSetType", value<std::string>()->default_value("3"), "Local destination's LeaseSet type")
 			("socksproxy.i2cp.leaseSetEncType", value<std::string>()->default_value("0,4"), "Local destination's LeaseSet encryption type")
 			("socksproxy.i2cp.leaseSetPrivKey", value<std::string>()->default_value(""), "LeaseSet private key")
-			("socksproxy.i2p.streaming.maxOutboundSpeed", value<std::string>()->default_value("1730000000"), "Max outbound speed of SOCKS proxy stream in bytes/sec")
-			("socksproxy.i2p.streaming.maxInboundSpeed", value<std::string>()->default_value("1730000000"), "Max inbound speed of SOCKS proxy stream in bytes/sec")
-			("socksproxy.i2p.streaming.profile", value<std::string>()->default_value("1"), "SOCKS Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)")
 		;
 
-		options_description shareddest("Shared local destination options");
-		shareddest.add_options()
-			("shareddest.inbound.length", value<std::string>()->default_value("3"),    "Shared local destination inbound tunnel length")
-			("shareddest.outbound.length", value<std::string>()->default_value("3"),   "Shared local destination outbound tunnel length")
-			("shareddest.inbound.quantity", value<std::string>()->default_value("3"),  "Shared local destination inbound tunnels quantity")
-			("shareddest.outbound.quantity", value<std::string>()->default_value("3"), "Shared local destination outbound tunnels quantity")
-			("shareddest.i2cp.leaseSetType", value<std::string>()->default_value("3"), "Shared local destination's LeaseSet type")
-			("shareddest.i2cp.leaseSetEncType", value<std::string>()->default_value("0,4"), "Shared local destination's LeaseSet encryption type")
-			("shareddest.i2p.streaming.profile", value<std::string>()->default_value("2"), "Shared local destination bandwidth usage profile. 1 - bulk(high), 2- interactive(low)")
-		;	
-		
 		options_description sam("SAM bridge options");
 		sam.add_options()
 			("sam.enabled", value<bool>()->default_value(true),               "Enable or disable SAM Application bridge")
@@ -187,8 +168,6 @@ namespace config {
 			("i2cp.address", value<std::string>()->default_value("127.0.0.1"), "I2CP listen address")
 			("i2cp.port", value<uint16_t>()->default_value(7654),              "I2CP listen port")
 			("i2cp.singlethread", value<bool>()->default_value(true),          "Destinations run in the I2CP server's thread")
-			("i2cp.inboundlimit", value<uint32_t>()->default_value(0),         "Client inbound limit in KBps to return in BandwidthLimitsMessage. Router's bandwidth by default")
-			("i2cp.outboundlimit", value<uint32_t>()->default_value(0),        "Client outbound limit in KBps to return in BandwidthLimitsMessage. Router's bandwidth by default")
 		;
 
 		options_description i2pcontrol("I2PControl options");
@@ -226,7 +205,7 @@ namespace config {
 		reseed.add_options()
 			("reseed.verify", value<bool>()->default_value(false),        "Verify .su3 signature")
 			("reseed.threshold", value<uint16_t>()->default_value(25),    "Minimum number of known routers before requesting reseed")
-			("reseed.floodfill", value<std::string>()->default_value(""), "Ignored. Always empty")
+			("reseed.floodfill", value<std::string>()->default_value(""), "Path to router info of floodfill to reseed from")
 			("reseed.file", value<std::string>()->default_value(""),      "Path to local .su3 file or HTTPS URL to reseed from")
 			("reseed.zipfile", value<std::string>()->default_value(""),   "Path to local .zip file to reseed from")
 			("reseed.proxy", value<std::string>()->default_value(""),     "url for reseed proxy, supports http/socks")
@@ -238,19 +217,21 @@ namespace config {
 				"https://reseed.onion.im/,"
 				"https://i2pseed.creativecowpat.net:8443/,"
 				"https://reseed.i2pgit.org/,"
-				"https://coconut.incognet.io/,"
+				"https://banana.incognet.io/,"
 				"https://reseed-pl.i2pd.xyz/,"
 				"https://www2.mk16.de/,"
 			    "https://i2p.ghativega.in/,"
 			    "https://i2p.novg.net/,"
-            	"https://reseed.stormycloud.org/,"
-			    "https://cubicchaos.net:8443/"                                                
+			    "https://reseed.is.prestium.org/,"
+				"https://reseed.us.prestium.org/"
 			),                                                            "Reseed URLs, separated by comma")
 			("reseed.yggurls", value<std::string>()->default_value(
 				"http://[324:71e:281a:9ed3::ace]:7070/,"
 				"http://[301:65b9:c7cd:9a36::1]:18801/,"
 				"http://[320:8936:ec1a:31f1::216]/,"
-				"http://[316:f9e0:f22e:a74f::216]/"
+				"http://[306:3834:97b9:a00a::1]/,"
+				"http://[316:f9e0:f22e:a74f::216]/,"
+			    "http://[300:eaff:7fab:181b::e621]:7170"
 			),                                                            "Reseed URLs through the Yggdrasil, separated by comma")
 		;
 
@@ -327,11 +308,11 @@ namespace config {
 			("persist.addressbook", value<bool>()->default_value(true),    "Persist full addresses (default: true)")
 		;
 
-		options_description cpuext("CPU encryption extensions options. Deprecated");
+		options_description cpuext("CPU encryption extensions options");
 		cpuext.add_options()
-			("cpuext.aesni", bool_switch()->default_value(true),                     "Deprecated option")
+			("cpuext.aesni", bool_switch()->default_value(true),                     "Use auto detection for AESNI CPU extensions. If false, AESNI will be not used")
 			("cpuext.avx", bool_switch()->default_value(false),                      "Deprecated option")
-			("cpuext.force", bool_switch()->default_value(false),                    "Deprecated option")
+			("cpuext.force", bool_switch()->default_value(false),                    "Force usage of CPU extensions. Useful when cpuinfo is not available on virtual machines")
 		;
 
 		options_description meshnets("Meshnet transports options");
@@ -353,7 +334,6 @@ namespace config {
 			.add(httpserver)
 			.add(httpproxy)
 			.add(socksproxy)
-			.add(shareddest)
 			.add(sam)
 			.add(bob)
 			.add(i2cp)
diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp
index c41b4c10..3e5bdd35 100644
--- a/libi2pd/Crypto.cpp
+++ b/libi2pd/Crypto.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -19,21 +19,21 @@
 #if OPENSSL_HKDF
 #include <openssl/kdf.h>
 #endif
-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
-#include <openssl/param_build.h>
-#include <openssl/core_names.h>
+#if !OPENSSL_AEAD_CHACHA20_POLY1305
+#include "ChaCha20.h"
+#include "Poly1305.h"
 #endif
-#include "CPU.h"
 #include "Crypto.h"
 #include "Ed25519.h"
 #include "I2PEndian.h"
 #include "Log.h"
 
+
 namespace i2p
 {
 namespace crypto
 {
-	constexpr uint8_t elgp_[256]=
+	const uint8_t elgp_[256]=
 	{
 		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34,
 		0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74,
@@ -53,9 +53,9 @@ namespace crypto
 		0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
 	};
 
-	constexpr int elgg_ = 2;
+	const int elgg_ = 2;
 
-	constexpr uint8_t dsap_[128]=
+	const uint8_t dsap_[128]=
 	{
 		0x9c, 0x05, 0xb2, 0xaa, 0x96, 0x0d, 0x9b, 0x97, 0xb8, 0x93, 0x19, 0x63, 0xc9, 0xcc, 0x9e, 0x8c,
 		0x30, 0x26, 0xe9, 0xb8, 0xed, 0x92, 0xfa, 0xd0, 0xa6, 0x9c, 0xc8, 0x86, 0xd5, 0xbf, 0x80, 0x15,
@@ -67,13 +67,13 @@ namespace crypto
 		0x28, 0x5d, 0x4c, 0xf2, 0x95, 0x38, 0xd9, 0xe3, 0xb6, 0x05, 0x1f, 0x5b, 0x22, 0xcc, 0x1c, 0x93
 	};
 
-	constexpr uint8_t dsaq_[20]=
+	const uint8_t dsaq_[20]=
 	{
 		0xa5, 0xdf, 0xc2, 0x8f, 0xef, 0x4c, 0xa1, 0xe2, 0x86, 0x74, 0x4c, 0xd8, 0xee, 0xd9, 0xd2, 0x9d,
 		0x68, 0x40, 0x46, 0xb7
 	};
 
-	constexpr uint8_t dsag_[128]=
+	const uint8_t dsag_[128]=
 	{
 		0x0c, 0x1f, 0x4d, 0x27, 0xd4, 0x00, 0x93, 0xb4, 0x29, 0xe9, 0x62, 0xd7, 0x22, 0x38, 0x24, 0xe0,
 		0xbb, 0xc4, 0x7e, 0x7c, 0x83, 0x2a, 0x39, 0x23, 0x6f, 0xc6, 0x83, 0xaf, 0x84, 0x88, 0x95, 0x81,
@@ -85,7 +85,7 @@ namespace crypto
 		0xb3, 0xdb, 0xb1, 0x4a, 0x90, 0x5e, 0x7b, 0x2b, 0x3e, 0x93, 0xbe, 0x47, 0x08, 0xcb, 0xcc, 0x82
 	};
 
-	constexpr int rsae_ = 65537;
+	const int rsae_ = 65537;
 
 	struct CryptoConstants
 	{
@@ -150,37 +150,6 @@ namespace crypto
 	#define dsap GetCryptoConstants ().dsap
 	#define dsaq GetCryptoConstants ().dsaq
 	#define dsag GetCryptoConstants ().dsag
-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
-	EVP_PKEY * CreateDSA (BIGNUM * pubKey, BIGNUM * privKey)
-	{
-		EVP_PKEY * pkey = nullptr;
-		int selection = EVP_PKEY_KEY_PARAMETERS;
-		auto bld = OSSL_PARAM_BLD_new();
-		OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_P, dsap);
-		OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_Q, dsaq);
-		OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_G, dsag);
-		if (pubKey)
-		{	
-			OSSL_PARAM_BLD_push_BN (bld, OSSL_PKEY_PARAM_PUB_KEY, pubKey);
-			selection = EVP_PKEY_PUBLIC_KEY;
-		}	
-		if (privKey)
-		{	
-			OSSL_PARAM_BLD_push_BN (bld, OSSL_PKEY_PARAM_PRIV_KEY, privKey);
-			selection = EVP_PKEY_KEYPAIR;
-		}	
-		auto params = OSSL_PARAM_BLD_to_param(bld);
-		
-		EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "DSA", NULL);
-		EVP_PKEY_fromdata_init(ctx);
-        EVP_PKEY_fromdata(ctx, &pkey, selection, params);
-		
-		EVP_PKEY_CTX_free(ctx);
-		OSSL_PARAM_free(params);
-		OSSL_PARAM_BLD_free(bld);
-		return pkey;
-	}	
-#else	
 	DSA * CreateDSA ()
 	{
 		DSA * dsa = DSA_new ();
@@ -188,8 +157,7 @@ namespace crypto
 		DSA_set0_key (dsa, NULL, NULL);
 		return dsa;
 	}
-#endif
-	
+
 // DH/ElGamal
 
 #if !IS_X86_64
@@ -277,12 +245,17 @@ namespace crypto
 // x25519
 	X25519Keys::X25519Keys ()
 	{
+#if OPENSSL_X25519
 		m_Ctx = EVP_PKEY_CTX_new_id (NID_X25519, NULL);
 		m_Pkey = nullptr;
+#else
+		m_Ctx = BN_CTX_new ();
+#endif
 	}
 
 	X25519Keys::X25519Keys (const uint8_t * priv, const uint8_t * pub)
 	{
+#if OPENSSL_X25519
 		m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_X25519, NULL, priv, 32);
 		m_Ctx = EVP_PKEY_CTX_new (m_Pkey, NULL);
 		if (pub)
@@ -292,16 +265,29 @@ namespace crypto
 			size_t len = 32;
 			EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len);
 		}
+#else
+		m_Ctx = BN_CTX_new ();
+		memcpy (m_PrivateKey, priv, 32);
+		if (pub)
+			memcpy (m_PublicKey, pub, 32);
+		else
+			GetEd25519 ()->ScalarMulB (m_PrivateKey, m_PublicKey, m_Ctx);
+#endif
 	}
 
 	X25519Keys::~X25519Keys ()
 	{
+#if OPENSSL_X25519
 		EVP_PKEY_CTX_free (m_Ctx);
 		if (m_Pkey) EVP_PKEY_free (m_Pkey);
+#else
+		BN_CTX_free (m_Ctx);
+#endif
 	}
 
 	void X25519Keys::GenerateKeys ()
 	{
+#if OPENSSL_X25519
 		if (m_Pkey)
 		{
 			EVP_PKEY_free (m_Pkey);
@@ -313,11 +299,16 @@ namespace crypto
 		m_Ctx = EVP_PKEY_CTX_new (m_Pkey, NULL); // TODO: do we really need to re-create m_Ctx?
 		size_t len = 32;
 		EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len);
+#else
+		RAND_bytes (m_PrivateKey, 32);
+		GetEd25519 ()->ScalarMulB (m_PrivateKey, m_PublicKey, m_Ctx);
+#endif
 	}
 
 	bool X25519Keys::Agree (const uint8_t * pub, uint8_t * shared)
 	{
 		if (!pub || (pub[31] & 0x80)) return false; // not x25519 key
+#if OPENSSL_X25519
 		EVP_PKEY_derive_init (m_Ctx);
 		auto pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_X25519, NULL, pub, 32);
 		if (!pkey) return false;
@@ -325,17 +316,25 @@ namespace crypto
 		size_t len = 32;
 		EVP_PKEY_derive (m_Ctx, shared, &len);
 		EVP_PKEY_free (pkey);
+#else
+		GetEd25519 ()->ScalarMul (pub, m_PrivateKey, shared, m_Ctx);
+#endif
 		return true;
 	}
 
 	void X25519Keys::GetPrivateKey (uint8_t * priv) const
 	{
+#if OPENSSL_X25519
 		size_t len = 32;
 		EVP_PKEY_get_raw_private_key (m_Pkey, priv, &len);
+#else
+		memcpy (priv, m_PrivateKey, 32);
+#endif
 	}
 
 	void X25519Keys::SetPrivateKey (const uint8_t * priv, bool calculatePublic)
 	{
+#if OPENSSL_X25519
 		if (m_Ctx) EVP_PKEY_CTX_free (m_Ctx);
 		if (m_Pkey) EVP_PKEY_free (m_Pkey);
 		m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_X25519, NULL, priv, 32);
@@ -345,6 +344,11 @@ namespace crypto
 			size_t len = 32;
 			EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len);
 		}
+#else
+		memcpy (m_PrivateKey, priv, 32);
+		if (calculatePublic)
+			GetEd25519 ()->ScalarMulB (m_PrivateKey, m_PublicKey, m_Ctx);
+#endif
 	}
 
 // ElGamal
@@ -478,8 +482,9 @@ namespace crypto
 		// encrypt
 		CBCEncryption encryption;
 		encryption.SetKey (shared);
+		encryption.SetIV (iv);
 		encrypted[257] = 0;
-		encryption.Encrypt (m, 256, iv, encrypted + 258);
+		encryption.Encrypt (m, 256, encrypted + 258);
 		EC_POINT_free (p);
 		BN_CTX_end (ctx);
 		BN_CTX_free (ctx);
@@ -512,7 +517,8 @@ namespace crypto
 			uint8_t m[256];
 			CBCDecryption decryption;
 			decryption.SetKey (shared);
-			decryption.Decrypt (encrypted + 258, 256, iv, m);
+			decryption.SetIV (iv);
+			decryption.Decrypt (encrypted + 258, 256, m);
 			// verify and copy
 			uint8_t hash[32];
 			SHA256 (m + 33, 222, hash);
@@ -550,114 +556,441 @@ namespace crypto
 	}
 
 // AES
-	ECBEncryption::ECBEncryption ()
-	{
-		m_Ctx = EVP_CIPHER_CTX_new ();
-	}
-	
-	ECBEncryption::~ECBEncryption ()
-	{
-		if (m_Ctx)
-			EVP_CIPHER_CTX_free (m_Ctx);
-	}	
-	
-	void ECBEncryption::Encrypt (const uint8_t * in, uint8_t * out)
-	{
-		EVP_EncryptInit_ex (m_Ctx, EVP_aes_256_ecb(), NULL, m_Key, NULL);
-		EVP_CIPHER_CTX_set_padding (m_Ctx, 0);
-		int len;
-		EVP_EncryptUpdate (m_Ctx, out, &len, in, 16);
-		EVP_EncryptFinal_ex (m_Ctx, out + len, &len);
-	}
+#if SUPPORTS_AES
+	#define KeyExpansion256(round0,round1) \
+		"pshufd $0xff, %%xmm2, %%xmm2 \n" \
+		"movaps %%xmm1, %%xmm4 \n" \
+		"pslldq $4, %%xmm4 \n" \
+		"pxor %%xmm4, %%xmm1 \n" \
+		"pslldq $4, %%xmm4 \n" \
+		"pxor %%xmm4, %%xmm1 \n" \
+		"pslldq $4, %%xmm4 \n" \
+		"pxor %%xmm4, %%xmm1 \n" \
+		"pxor %%xmm2, %%xmm1 \n" \
+		"movaps %%xmm1, "#round0"(%[sched]) \n" \
+		"aeskeygenassist $0, %%xmm1, %%xmm4 \n" \
+		"pshufd $0xaa, %%xmm4, %%xmm2 \n" \
+		"movaps %%xmm3, %%xmm4 \n" \
+		"pslldq $4, %%xmm4 \n" \
+		"pxor %%xmm4, %%xmm3 \n" \
+		"pslldq $4, %%xmm4 \n" \
+		"pxor %%xmm4, %%xmm3 \n" \
+		"pslldq $4, %%xmm4 \n" \
+		"pxor %%xmm4, %%xmm3 \n" \
+		"pxor %%xmm2, %%xmm3 \n" \
+		"movaps %%xmm3, "#round1"(%[sched]) \n"
+#endif
 
-	ECBDecryption::ECBDecryption ()
+#if SUPPORTS_AES
+	void ECBCryptoAESNI::ExpandKey (const AESKey& key)
 	{
-		m_Ctx = EVP_CIPHER_CTX_new ();
-	}
-	
-	ECBDecryption::~ECBDecryption ()
-	{
-		if (m_Ctx)
-			EVP_CIPHER_CTX_free (m_Ctx);
-	}	
-	
-	void ECBDecryption::Decrypt (const uint8_t * in, uint8_t * out)
-	{
-		EVP_DecryptInit_ex (m_Ctx, EVP_aes_256_ecb(), NULL, m_Key, NULL);
-		EVP_CIPHER_CTX_set_padding (m_Ctx, 0);
-		int len;
-		EVP_DecryptUpdate (m_Ctx, out, &len, in, 16);
-		EVP_DecryptFinal_ex (m_Ctx, out + len, &len);
+		__asm__
+		(
+			"movups (%[key]), %%xmm1 \n"
+			"movups 16(%[key]), %%xmm3 \n"
+			"movaps %%xmm1, (%[sched]) \n"
+			"movaps %%xmm3, 16(%[sched]) \n"
+			"aeskeygenassist $1, %%xmm3, %%xmm2 \n"
+			KeyExpansion256(32,48)
+			"aeskeygenassist $2, %%xmm3, %%xmm2 \n"
+			KeyExpansion256(64,80)
+			"aeskeygenassist $4, %%xmm3, %%xmm2 \n"
+			KeyExpansion256(96,112)
+			"aeskeygenassist $8, %%xmm3, %%xmm2 \n"
+			KeyExpansion256(128,144)
+			"aeskeygenassist $16, %%xmm3, %%xmm2 \n"
+			KeyExpansion256(160,176)
+			"aeskeygenassist $32, %%xmm3, %%xmm2 \n"
+			KeyExpansion256(192,208)
+			"aeskeygenassist $64, %%xmm3, %%xmm2 \n"
+			// key expansion final
+			"pshufd $0xff, %%xmm2, %%xmm2 \n"
+			"movaps %%xmm1, %%xmm4 \n"
+			"pslldq $4, %%xmm4 \n"
+			"pxor %%xmm4, %%xmm1 \n"
+			"pslldq $4, %%xmm4 \n"
+			"pxor %%xmm4, %%xmm1 \n"
+			"pslldq $4, %%xmm4 \n"
+			"pxor %%xmm4, %%xmm1 \n"
+			"pxor %%xmm2, %%xmm1 \n"
+			"movups %%xmm1, 224(%[sched]) \n"
+			: // output
+			: [key]"r"((const uint8_t *)key), [sched]"r"(GetKeySchedule ()) // input
+			: "%xmm1", "%xmm2", "%xmm3", "%xmm4", "memory" // clogged
+		);
 	}
+#endif
 
 
-	CBCEncryption::CBCEncryption () 
-	{ 
-		m_Ctx = EVP_CIPHER_CTX_new ();
-	}
-	
-	CBCEncryption::~CBCEncryption ()
+#if SUPPORTS_AES
+	#define EncryptAES256(sched) \
+		"pxor (%["#sched"]), %%xmm0 \n" \
+		"aesenc	16(%["#sched"]), %%xmm0 \n" \
+		"aesenc	32(%["#sched"]), %%xmm0 \n" \
+		"aesenc	48(%["#sched"]), %%xmm0 \n" \
+		"aesenc	64(%["#sched"]), %%xmm0 \n" \
+		"aesenc	80(%["#sched"]), %%xmm0 \n" \
+		"aesenc	96(%["#sched"]), %%xmm0 \n" \
+		"aesenc	112(%["#sched"]), %%xmm0 \n" \
+		"aesenc	128(%["#sched"]), %%xmm0 \n" \
+		"aesenc	144(%["#sched"]), %%xmm0 \n" \
+		"aesenc	160(%["#sched"]), %%xmm0 \n" \
+		"aesenc	176(%["#sched"]), %%xmm0 \n" \
+		"aesenc	192(%["#sched"]), %%xmm0 \n" \
+		"aesenc	208(%["#sched"]), %%xmm0 \n" \
+		"aesenclast	224(%["#sched"]), %%xmm0 \n"
+#endif
+
+	void ECBEncryption::Encrypt (const ChipherBlock * in, ChipherBlock * out)
 	{
-		if (m_Ctx)
-			EVP_CIPHER_CTX_free (m_Ctx);
-	}	
-	
-	void CBCEncryption::Encrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out)
+#if SUPPORTS_AES
+		if(i2p::cpu::aesni)
+		{
+			__asm__
+			(
+				"movups (%[in]), %%xmm0 \n"
+				EncryptAES256(sched)
+				"movups %%xmm0, (%[out]) \n"
+				:
+				: [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out)
+				: "%xmm0", "memory"
+			);
+		}
+		else
+#endif
+		{
+			AES_encrypt (in->buf, out->buf, &m_Key);
+		}
+	}
+
+#if SUPPORTS_AES
+	#define DecryptAES256(sched) \
+		"pxor 224(%["#sched"]), %%xmm0 \n" \
+		"aesdec	208(%["#sched"]), %%xmm0 \n" \
+		"aesdec	192(%["#sched"]), %%xmm0 \n" \
+		"aesdec	176(%["#sched"]), %%xmm0 \n" \
+		"aesdec	160(%["#sched"]), %%xmm0 \n" \
+		"aesdec	144(%["#sched"]), %%xmm0 \n" \
+		"aesdec	128(%["#sched"]), %%xmm0 \n" \
+		"aesdec	112(%["#sched"]), %%xmm0 \n" \
+		"aesdec	96(%["#sched"]), %%xmm0 \n" \
+		"aesdec	80(%["#sched"]), %%xmm0 \n" \
+		"aesdec	64(%["#sched"]), %%xmm0 \n" \
+		"aesdec	48(%["#sched"]), %%xmm0 \n" \
+		"aesdec	32(%["#sched"]), %%xmm0 \n" \
+		"aesdec	16(%["#sched"]), %%xmm0 \n" \
+		"aesdeclast (%["#sched"]), %%xmm0 \n"
+#endif
+
+	void ECBDecryption::Decrypt (const ChipherBlock * in, ChipherBlock * out)
+	{
+#if SUPPORTS_AES
+		if(i2p::cpu::aesni)
+		{
+			__asm__
+			(
+				"movups (%[in]), %%xmm0 \n"
+				DecryptAES256(sched)
+				"movups %%xmm0, (%[out]) \n"
+				:
+				: [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out)
+				: "%xmm0", "memory"
+			);
+		}
+		else
+#endif
+		{
+			AES_decrypt (in->buf, out->buf, &m_Key);
+		}
+	}
+
+#if SUPPORTS_AES
+	#define CallAESIMC(offset) \
+		"movaps "#offset"(%[shed]), %%xmm0 \n" \
+		"aesimc %%xmm0, %%xmm0 \n" \
+		"movaps %%xmm0, "#offset"(%[shed]) \n"
+#endif
+
+	void ECBEncryption::SetKey (const AESKey& key)
+	{
+#if SUPPORTS_AES
+		if(i2p::cpu::aesni)
+		{
+			ExpandKey (key);
+		}
+		else
+#endif
+		{
+			AES_set_encrypt_key (key, 256, &m_Key);
+		}
+	}
+
+	void ECBDecryption::SetKey (const AESKey& key)
+	{
+#if SUPPORTS_AES
+		if(i2p::cpu::aesni)
+		{
+			ExpandKey (key); // expand encryption key first
+			// then invert it using aesimc
+			__asm__
+			(
+				CallAESIMC(16)
+				CallAESIMC(32)
+				CallAESIMC(48)
+				CallAESIMC(64)
+				CallAESIMC(80)
+				CallAESIMC(96)
+				CallAESIMC(112)
+				CallAESIMC(128)
+				CallAESIMC(144)
+				CallAESIMC(160)
+				CallAESIMC(176)
+				CallAESIMC(192)
+				CallAESIMC(208)
+				:
+				: [shed]"r"(GetKeySchedule ())
+				: "%xmm0", "memory"
+			);
+		}
+		else
+#endif
+		{
+			AES_set_decrypt_key (key, 256, &m_Key);
+		}
+	}
+
+	void CBCEncryption::Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out)
+	{
+#if SUPPORTS_AES
+		if(i2p::cpu::aesni)
+		{
+			__asm__
+			(
+				"movups (%[iv]), %%xmm1 \n"
+				"1: \n"
+				"movups (%[in]), %%xmm0 \n"
+				"pxor %%xmm1, %%xmm0 \n"
+				EncryptAES256(sched)
+				"movaps %%xmm0, %%xmm1 \n"
+				"movups %%xmm0, (%[out]) \n"
+				"add $16, %[in] \n"
+				"add $16, %[out] \n"
+				"dec %[num] \n"
+				"jnz 1b \n"
+				"movups %%xmm1, (%[iv]) \n"
+				:
+				: [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()),
+					[in]"r"(in), [out]"r"(out), [num]"r"(numBlocks)
+				: "%xmm0", "%xmm1", "cc", "memory"
+			);
+		}
+		else
+#endif
+		{
+			for (int i = 0; i < numBlocks; i++)
+			{
+				*m_LastBlock.GetChipherBlock () ^= in[i];
+				m_ECBEncryption.Encrypt (m_LastBlock.GetChipherBlock (), m_LastBlock.GetChipherBlock ());
+				out[i] = *m_LastBlock.GetChipherBlock ();
+			}
+		}
+	}
+
+	void CBCEncryption::Encrypt (const uint8_t * in, std::size_t len, uint8_t * out)
 	{
 		// len/16
-		EVP_EncryptInit_ex (m_Ctx, EVP_aes_256_cbc(), NULL, m_Key, iv);
-		EVP_CIPHER_CTX_set_padding (m_Ctx, 0);
-		int l;
-		EVP_EncryptUpdate (m_Ctx, out, &l, in, len);
-		EVP_EncryptFinal_ex (m_Ctx, out + l, &l);
+		int numBlocks = len >> 4;
+		if (numBlocks > 0)
+			Encrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out);
 	}
 
-	CBCDecryption::CBCDecryption () 
-	{ 
-		m_Ctx = EVP_CIPHER_CTX_new ();
+	void CBCEncryption::Encrypt (const uint8_t * in, uint8_t * out)
+	{
+#if SUPPORTS_AES
+		if(i2p::cpu::aesni)
+		{
+			__asm__
+			(
+				"movups (%[iv]), %%xmm1 \n"
+				"movups (%[in]), %%xmm0 \n"
+				"pxor %%xmm1, %%xmm0 \n"
+				EncryptAES256(sched)
+				"movups %%xmm0, (%[out]) \n"
+				"movups %%xmm0, (%[iv]) \n"
+				:
+				: [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()),
+					[in]"r"(in), [out]"r"(out)
+				: "%xmm0", "%xmm1", "memory"
+			);
+		}
+		else
+#endif
+			Encrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out);
 	}
-	
-	CBCDecryption::~CBCDecryption ()
+
+	void CBCDecryption::Decrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out)
 	{
-		if (m_Ctx)
-			EVP_CIPHER_CTX_free (m_Ctx);
-	}	
-	
-	void CBCDecryption::Decrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out)
+#if SUPPORTS_AES
+		if(i2p::cpu::aesni)
+		{
+			__asm__
+			(
+				"movups (%[iv]), %%xmm1 \n"
+				"1: \n"
+				"movups (%[in]), %%xmm0 \n"
+				"movaps %%xmm0, %%xmm2 \n"
+				DecryptAES256(sched)
+				"pxor %%xmm1, %%xmm0 \n"
+				"movups %%xmm0, (%[out]) \n"
+				"movaps %%xmm2, %%xmm1 \n"
+				"add $16, %[in] \n"
+				"add $16, %[out] \n"
+				"dec %[num] \n"
+				"jnz 1b \n"
+				"movups %%xmm1, (%[iv]) \n"
+				:
+				: [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()),
+					[in]"r"(in), [out]"r"(out), [num]"r"(numBlocks)
+				: "%xmm0", "%xmm1", "%xmm2", "cc", "memory"
+			);
+		}
+		else
+#endif
+		{
+			for (int i = 0; i < numBlocks; i++)
+			{
+				ChipherBlock tmp = in[i];
+				m_ECBDecryption.Decrypt (in + i, out + i);
+				out[i] ^= *m_IV.GetChipherBlock ();
+				*m_IV.GetChipherBlock () = tmp;
+			}
+		}
+	}
+
+	void CBCDecryption::Decrypt (const uint8_t * in, std::size_t len, uint8_t * out)
 	{
-		// len/16
-		EVP_DecryptInit_ex (m_Ctx, EVP_aes_256_cbc(), NULL, m_Key, iv);
-		EVP_CIPHER_CTX_set_padding (m_Ctx, 0);
-		int l;
-		EVP_DecryptUpdate (m_Ctx, out, &l, in, len);
-		EVP_DecryptFinal_ex (m_Ctx, out + l, &l);
+		int numBlocks = len >> 4;
+		if (numBlocks > 0)
+			Decrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out);
+	}
+
+	void CBCDecryption::Decrypt (const uint8_t * in, uint8_t * out)
+	{
+#if SUPPORTS_AES
+		if(i2p::cpu::aesni)
+		{
+			__asm__
+			(
+				"movups (%[iv]), %%xmm1 \n"
+				"movups (%[in]), %%xmm0 \n"
+				"movups %%xmm0, (%[iv]) \n"
+				DecryptAES256(sched)
+				"pxor %%xmm1, %%xmm0 \n"
+				"movups %%xmm0, (%[out]) \n"
+				:
+				: [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()),
+					[in]"r"(in), [out]"r"(out)
+				: "%xmm0", "%xmm1", "memory"
+			);
+		}
+		else
+#endif
+			Decrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out);
 	}
 
 	void TunnelEncryption::Encrypt (const uint8_t * in, uint8_t * out)
 	{
-		uint8_t iv[16];
-		m_IVEncryption.Encrypt (in, iv); // iv
-		m_LayerEncryption.Encrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, iv, out + 16); // data
-		m_IVEncryption.Encrypt (iv, out); // double iv
+#if SUPPORTS_AES
+		if(i2p::cpu::aesni)
+		{
+			__asm__
+			(
+				// encrypt IV
+				"movups (%[in]), %%xmm0 \n"
+				EncryptAES256(sched_iv)
+				"movaps %%xmm0, %%xmm1 \n"
+				// double IV encryption
+				EncryptAES256(sched_iv)
+				"movups %%xmm0, (%[out]) \n"
+				// encrypt data, IV is xmm1
+				"1: \n"
+				"add $16, %[in] \n"
+				"add $16, %[out] \n"
+				"movups (%[in]), %%xmm0 \n"
+				"pxor %%xmm1, %%xmm0 \n"
+				EncryptAES256(sched_l)
+				"movaps %%xmm0, %%xmm1 \n"
+				"movups %%xmm0, (%[out]) \n"
+				"dec %[num] \n"
+				"jnz 1b \n"
+				:
+				: [sched_iv]"r"(m_IVEncryption.GetKeySchedule ()), [sched_l]"r"(m_LayerEncryption.ECB().GetKeySchedule ()),
+					[in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes
+				: "%xmm0", "%xmm1", "cc", "memory"
+			);
+		}
+		else
+#endif
+		{
+			m_IVEncryption.Encrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv
+			m_LayerEncryption.SetIV (out);
+			m_LayerEncryption.Encrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data
+			m_IVEncryption.Encrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv
+		}
 	}
 
 	void TunnelDecryption::Decrypt (const uint8_t * in, uint8_t * out)
 	{
-		uint8_t iv[16];
-		m_IVDecryption.Decrypt (in, iv); // iv
-		m_LayerDecryption.Decrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, iv, out + 16); // data
-		m_IVDecryption.Decrypt (iv, out); // double iv
+#if SUPPORTS_AES
+		if(i2p::cpu::aesni)
+		{
+			__asm__
+			(
+				// decrypt IV
+				"movups	(%[in]), %%xmm0 \n"
+				DecryptAES256(sched_iv)
+				"movaps %%xmm0, %%xmm1 \n"
+				// double IV encryption
+				DecryptAES256(sched_iv)
+				"movups %%xmm0, (%[out]) \n"
+				// decrypt data, IV is xmm1
+				"1: \n"
+				"add $16, %[in] \n"
+				"add $16, %[out] \n"
+				"movups (%[in]), %%xmm0 \n"
+				"movaps %%xmm0, %%xmm2 \n"
+				DecryptAES256(sched_l)
+				"pxor %%xmm1, %%xmm0 \n"
+				"movups %%xmm0, (%[out]) \n"
+				"movaps %%xmm2, %%xmm1 \n"
+				"dec %[num] \n"
+				"jnz 1b \n"
+				:
+				: [sched_iv]"r"(m_IVDecryption.GetKeySchedule ()), [sched_l]"r"(m_LayerDecryption.ECB().GetKeySchedule ()),
+					[in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes
+				: "%xmm0", "%xmm1", "%xmm2", "cc", "memory"
+			);
+		}
+		else
+#endif
+		{
+			m_IVDecryption.Decrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv
+			m_LayerDecryption.SetIV (out);
+			m_LayerDecryption.Decrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data
+			m_IVDecryption.Decrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv
+		}
 	}
 
 // AEAD/ChaCha20/Poly1305
 
-	static bool AEADChaCha20Poly1305 (EVP_CIPHER_CTX * ctx, const uint8_t * msg, size_t msgLen, 
-		const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt)
+	bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt)
 	{
-		if (!ctx || len < msgLen) return false;
+		if (len < msgLen) return false;
 		if (encrypt && len < msgLen + 16) return false;
 		bool ret = true;
+#if OPENSSL_AEAD_CHACHA20_POLY1305
 		int outlen = 0;
+		EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new ();
 		if (encrypt)
 		{
 			EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), 0, 0, 0);
@@ -670,15 +1003,6 @@ namespace crypto
 		}
 		else
 		{
-#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x4000000fL
-			std::vector<uint8_t> m(msgLen + 16);
-			if (msg == buf)
-			{	
-				// we have to use different buffers otherwise verification fails
-				memcpy (m.data (), msg, msgLen + 16);
-				msg = m.data ();
-			}	
-#endif			
 			EVP_DecryptInit_ex(ctx, EVP_chacha20_poly1305(), 0, 0, 0);
 			EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, 0);
 			EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, (uint8_t *)(msg + msgLen));
@@ -687,100 +1011,140 @@ namespace crypto
 			EVP_DecryptUpdate(ctx, buf, &outlen, msg, msgLen);
 			ret = EVP_DecryptFinal_ex(ctx, buf + outlen, &outlen) > 0;
 		}
-		return ret;
-	}
 
-	bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, 
-		const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt)
-	{
-		EVP_CIPHER_CTX * ctx = EVP_CIPHER_CTX_new ();
-		auto ret = AEADChaCha20Poly1305 (ctx, msg, msgLen, ad, adLen, key, nonce, buf, len, encrypt);
 		EVP_CIPHER_CTX_free (ctx);
+#else
+		chacha::Chacha20State state;
+		// generate one time poly key
+		chacha::Chacha20Init (state, nonce, key, 0);
+		uint64_t polyKey[8];
+		memset(polyKey, 0, sizeof(polyKey));
+		chacha::Chacha20Encrypt (state, (uint8_t *)polyKey, 64);
+		// create Poly1305 hash
+		Poly1305 polyHash (polyKey);
+		if (!ad) adLen = 0;
+		uint8_t padding[16]; memset (padding, 0, 16);
+		if (ad)
+		{
+			polyHash.Update (ad, adLen);// additional authenticated data
+			auto rem = adLen & 0x0F; // %16
+			if (rem)
+			{
+				// padding1
+				rem = 16 - rem;
+				polyHash.Update (padding, rem);
+			}
+		}
+		// encrypt/decrypt data and add to hash
+		Chacha20SetCounter (state, 1);
+		if (buf != msg)
+			memcpy (buf, msg, msgLen);
+		if (encrypt)
+		{
+			chacha::Chacha20Encrypt (state, buf, msgLen); // encrypt
+			polyHash.Update (buf, msgLen); // after encryption
+		}
+		else
+		{
+			polyHash.Update (buf, msgLen); // before decryption
+			chacha::Chacha20Encrypt (state, buf, msgLen); // decrypt
+		}
+
+		auto rem = msgLen & 0x0F; // %16
+		if (rem)
+		{
+			// padding2
+			rem = 16 - rem;
+			polyHash.Update (padding, rem);
+		}
+		// adLen and msgLen
+		htole64buf (padding, adLen);
+		htole64buf (padding + 8, msgLen);
+		polyHash.Update (padding, 16);
+
+		if (encrypt)
+			// calculate Poly1305 tag and write in after encrypted data
+			polyHash.Finish ((uint64_t *)(buf + msgLen));
+		else
+		{
+			uint64_t tag[4];
+			// calculate Poly1305 tag
+			polyHash.Finish (tag);
+			if (memcmp (tag, msg + msgLen, 16)) ret = false; // compare with provided
+		}
+#endif
 		return ret;
 	}
 
-	AEADChaCha20Poly1305Encryptor::AEADChaCha20Poly1305Encryptor ()
-	{
-		m_Ctx = EVP_CIPHER_CTX_new ();
-	}
-	
-	AEADChaCha20Poly1305Encryptor::~AEADChaCha20Poly1305Encryptor ()
-	{
-		if (m_Ctx)
-			EVP_CIPHER_CTX_free (m_Ctx);
-	}	
-
-	bool AEADChaCha20Poly1305Encryptor::Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
-		const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len)
-	{
-		return AEADChaCha20Poly1305 (m_Ctx, msg, msgLen, ad, adLen, key, nonce, buf, len, true);
-	}	
-
-	void AEADChaCha20Poly1305Encryptor::Encrypt (const std::vector<std::pair<uint8_t *, size_t> >& bufs, 
-		const uint8_t * key, const uint8_t * nonce, uint8_t * mac)
+	void AEADChaCha20Poly1305Encrypt (const std::vector<std::pair<uint8_t *, size_t> >& bufs, const uint8_t * key, const uint8_t * nonce, uint8_t * mac)
 	{
 		if (bufs.empty ()) return;
+#if OPENSSL_AEAD_CHACHA20_POLY1305
 		int outlen = 0;
-		EVP_EncryptInit_ex(m_Ctx, EVP_chacha20_poly1305(), 0, 0, 0);
-		EVP_CIPHER_CTX_ctrl(m_Ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, 0);
-		EVP_EncryptInit_ex(m_Ctx, NULL, NULL, key, nonce);
+		EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new ();
+		EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), 0, 0, 0);
+		EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, 0);
+		EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce);
 		for (const auto& it: bufs)
-			EVP_EncryptUpdate(m_Ctx, it.first, &outlen, it.first, it.second);
-		EVP_EncryptFinal_ex(m_Ctx, NULL, &outlen);
-		EVP_CIPHER_CTX_ctrl(m_Ctx, EVP_CTRL_AEAD_GET_TAG, 16, mac);
-	}	
-	
-	AEADChaCha20Poly1305Decryptor::AEADChaCha20Poly1305Decryptor ()
-	{
-		m_Ctx = EVP_CIPHER_CTX_new ();
-	}
-	
-	AEADChaCha20Poly1305Decryptor::~AEADChaCha20Poly1305Decryptor ()
-	{
-		if (m_Ctx)
-			EVP_CIPHER_CTX_free (m_Ctx);
-	}	
-
-	bool AEADChaCha20Poly1305Decryptor::Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
-		const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len)
-	{
-		return AEADChaCha20Poly1305 (m_Ctx, msg, msgLen, ad, adLen, key, nonce, buf, len, false);
+			EVP_EncryptUpdate(ctx, it.first, &outlen, it.first, it.second);
+		EVP_EncryptFinal_ex(ctx, NULL, &outlen);
+		EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, mac);
+		EVP_CIPHER_CTX_free (ctx);
+#else
+		chacha::Chacha20State state;
+		// generate one time poly key
+		chacha::Chacha20Init (state, nonce, key, 0);
+		uint64_t polyKey[8];
+		memset(polyKey, 0, sizeof(polyKey));
+		chacha::Chacha20Encrypt (state, (uint8_t *)polyKey, 64);
+		Poly1305 polyHash (polyKey);
+		// encrypt buffers
+		Chacha20SetCounter (state, 1);
+		size_t size = 0;
+		for (const auto& it: bufs)
+		{
+			chacha::Chacha20Encrypt (state, it.first, it.second);
+			polyHash.Update (it.first, it.second); // after encryption
+			size += it.second;
+		}
+		// padding
+		uint8_t padding[16];
+		memset (padding, 0, 16);
+		auto rem = size & 0x0F; // %16
+		if (rem)
+		{
+			// padding2
+			rem = 16 - rem;
+			polyHash.Update (padding, rem);
+		}
+		// adLen and msgLen
+		// adLen is always zero
+		htole64buf (padding + 8, size);
+		polyHash.Update (padding, 16);
+		// MAC
+		polyHash.Finish ((uint64_t *)mac);
+#endif
 	}
 
-	static void ChaCha20 (EVP_CIPHER_CTX *ctx, const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out)
+	void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out)
 	{
+#if OPENSSL_AEAD_CHACHA20_POLY1305
+		EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new ();
 		uint32_t iv[4];
 		iv[0] = htole32 (1); memcpy (iv + 1, nonce, 12); // counter | nonce
 		EVP_EncryptInit_ex(ctx, EVP_chacha20 (), NULL, key, (const uint8_t *)iv);
 		int outlen = 0;
 		EVP_EncryptUpdate(ctx, out, &outlen, msg, msgLen);
 		EVP_EncryptFinal_ex(ctx, NULL, &outlen);
-	}
-	
-	void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out)
-	{
-		EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new ();
-		ChaCha20 (ctx, msg, msgLen, key, nonce, out);
 		EVP_CIPHER_CTX_free (ctx);
+#else
+		chacha::Chacha20State state;
+		chacha::Chacha20Init (state, nonce, key, 1);
+		if (out != msg) memcpy (out, msg, msgLen);
+		chacha::Chacha20Encrypt (state, out, msgLen);
+#endif
 	}
 
-	
-	ChaCha20Context::ChaCha20Context ()
-	{
-		m_Ctx = EVP_CIPHER_CTX_new ();
-	}
-	
-	ChaCha20Context::~ChaCha20Context ()
-	{
-		if (m_Ctx)
-			EVP_CIPHER_CTX_free (m_Ctx);
-	}
-	
-	void ChaCha20Context::operator ()(const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out)
-	{
-		ChaCha20 (m_Ctx, msg, msgLen, key, nonce, out);
-	}	
-	
 	void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info,
 		uint8_t * out, size_t outLen)
 	{
@@ -821,18 +1185,6 @@ namespace crypto
 
 // Noise
 
-	void NoiseSymmetricState::Init (const uint8_t * ck, const uint8_t * hh, const uint8_t * pub)
-	{
-		// pub is Bob's public static key, hh = SHA256(h)
-		memcpy (m_CK, ck, 32);
-		SHA256_CTX ctx;
-		SHA256_Init (&ctx);
-		SHA256_Update (&ctx, hh, 32);
-		SHA256_Update (&ctx, pub, 32);
-		SHA256_Final (m_H, &ctx); // h = MixHash(pub) = SHA256(hh || pub)
-		m_N = 0;
-	}	
-	
 	void NoiseSymmetricState::MixHash (const uint8_t * buf, size_t len)
 	{
 		SHA256_CTX ctx;
@@ -856,95 +1208,76 @@ namespace crypto
 	{
 		HKDF (m_CK, sharedSecret, 32, "", m_CK);
 		// new ck is m_CK[0:31], key is m_CK[32:63]
-		m_N = 0;
 	}
 
-	bool NoiseSymmetricState::Encrypt (const uint8_t * in, uint8_t * out, size_t len)
+	static void InitNoiseState (NoiseSymmetricState& state, const uint8_t * ck,
+		const uint8_t * hh, const uint8_t * pub)
 	{
-		uint8_t nonce[12];
-		if (m_N)
-		{	
-			memset (nonce, 0, 4);
-			htole64buf (nonce + 4, m_N);
-		}	
-		else
-			memset (nonce, 0, 12);
-		auto ret = AEADChaCha20Poly1305 (in, len, m_H, 32, m_CK + 32, nonce, out, len + 16, true);
-		if (ret) m_N++;
-		return ret;
+		// pub is Bob's public static key, hh = SHA256(h)
+		memcpy (state.m_CK, ck, 32);
+		SHA256_CTX ctx;
+		SHA256_Init (&ctx);
+		SHA256_Update (&ctx, hh, 32);
+		SHA256_Update (&ctx, pub, 32);
+		SHA256_Final (state.m_H, &ctx); // h = MixHash(pub) = SHA256(hh || pub)
 	}
 
-	bool NoiseSymmetricState::Decrypt (const uint8_t * in, uint8_t * out, size_t len)
-	{
-		uint8_t nonce[12];
-		if (m_N)
-		{	
-			memset (nonce, 0, 4);
-			htole64buf (nonce + 4, m_N);
-		}
-		else
-			memset (nonce, 0, 12);
-		auto ret = AEADChaCha20Poly1305 (in, len, m_H, 32, m_CK + 32, nonce, out, len, false);
-		if (ret) m_N++;
-		return ret;
-	}	
-	
 	void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub)
 	{
-		static constexpr char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars
-		static constexpr uint8_t hh[32] =
+		static const char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars
+		static const uint8_t hh[32] =
 		{
 			0x69, 0x4d, 0x52, 0x44, 0x5a, 0x27, 0xd9, 0xad, 0xfa, 0xd2, 0x9c, 0x76, 0x32, 0x39, 0x5d, 0xc1,
 			0xe4, 0x35, 0x4c, 0x69, 0xb4, 0xf9, 0x2e, 0xac, 0x8a, 0x1e, 0xe4, 0x6a, 0x9e, 0xd2, 0x15, 0x54
 		}; // hh = SHA256(protocol_name || 0)
-		state.Init ((const uint8_t *)protocolName, hh, pub); // ck = protocol_name || 0
+		InitNoiseState (state, (const uint8_t *)protocolName, hh, pub); // ck = protocol_name || 0
 	}
 
 	void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub)
 	{
-		static constexpr uint8_t protocolNameHash[32] =
+		static const uint8_t protocolNameHash[32] =
 		{
 			0x72, 0xe8, 0x42, 0xc5, 0x45, 0xe1, 0x80, 0x80, 0xd3, 0x9c, 0x44, 0x93, 0xbb, 0x91, 0xd7, 0xed,
 			0xf2, 0x28, 0x98, 0x17, 0x71, 0x21, 0x8c, 0x1f, 0x62, 0x4e, 0x20, 0x6f, 0x28, 0xd3, 0x2f, 0x71
 		}; // SHA256 ("Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256")
-		static constexpr uint8_t hh[32] =
+		static const uint8_t hh[32] =
 		{
 			0x49, 0xff, 0x48, 0x3f, 0xc4, 0x04, 0xb9, 0xb2, 0x6b, 0x11, 0x94, 0x36, 0x72, 0xff, 0x05, 0xb5,
 			0x61, 0x27, 0x03, 0x31, 0xba, 0x89, 0xb8, 0xfc, 0x33, 0x15, 0x93, 0x87, 0x57, 0xdd, 0x3d, 0x1e
 		}; // SHA256 (protocolNameHash)
-		state.Init (protocolNameHash, hh, pub);
+		InitNoiseState (state, protocolNameHash, hh, pub);
 	}
 
 	void InitNoiseXKState1 (NoiseSymmetricState& state, const uint8_t * pub)
 	{
-		static constexpr uint8_t protocolNameHash[32] =
+		static const uint8_t protocolNameHash[32] =
 		{
 			0xb1, 0x37, 0x22, 0x81, 0x74, 0x23, 0xa8, 0xfd, 0xf4, 0x2d, 0xf2, 0xe6, 0x0e, 0xd1, 0xed, 0xf4,
 			0x1b, 0x93, 0x07, 0x1d, 0xb1, 0xec, 0x24, 0xa3, 0x67, 0xf7, 0x84, 0xec, 0x27, 0x0d, 0x81, 0x32
 		}; // SHA256 ("Noise_XKchaobfse+hs1+hs2+hs3_25519_ChaChaPoly_SHA256")
-		static constexpr uint8_t hh[32] =
+		static const uint8_t hh[32] =
 		{
 			0xdc, 0x85, 0xe6, 0xaf, 0x7b, 0x02, 0x65, 0x0c, 0xf1, 0xf9, 0x0d, 0x71, 0xfb, 0xc6, 0xd4, 0x53,
 			0xa7, 0xcf, 0x6d, 0xbf, 0xbd, 0x52, 0x5e, 0xa5, 0xb5, 0x79, 0x1c, 0x47, 0xb3, 0x5e, 0xbc, 0x33
 		}; // SHA256 (protocolNameHash)
-		state.Init (protocolNameHash, hh, pub);
+		InitNoiseState (state, protocolNameHash, hh, pub);
 	}
 
 	void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub)
 	{
-		static constexpr uint8_t protocolNameHash[32] =
+		static const uint8_t protocolNameHash[32] =
 		{
 			0x4c, 0xaf, 0x11, 0xef, 0x2c, 0x8e, 0x36, 0x56, 0x4c, 0x53, 0xe8, 0x88, 0x85, 0x06, 0x4d, 0xba,
 			0xac, 0xbe, 0x00, 0x54, 0xad, 0x17, 0x8f, 0x80, 0x79, 0xa6, 0x46, 0x82, 0x7e, 0x6e, 0xe4, 0x0c
 		}; // SHA256("Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256"), 40 bytes
-		static constexpr uint8_t hh[32] =
+		static const uint8_t hh[32] =
 		{
 			0x9c, 0xcf, 0x85, 0x2c, 0xc9, 0x3b, 0xb9, 0x50, 0x44, 0x41, 0xe9, 0x50, 0xe0, 0x1d, 0x52, 0x32,
 			0x2e, 0x0d, 0x47, 0xad, 0xd1, 0xe9, 0xa5, 0x55, 0xf7, 0x55, 0xb5, 0x69, 0xae, 0x18, 0x3b, 0x5c
 		}; // SHA256 (protocolNameHash)
-		state.Init (protocolNameHash, hh, pub);
+		InitNoiseState (state, protocolNameHash, hh, pub);
 	}
-		
+
 // init and terminate
 
 /*	std::vector <std::unique_ptr<std::mutex> > m_OpenSSLMutexes;
@@ -959,8 +1292,12 @@ namespace crypto
 		}
 	}*/
 
-	void InitCrypto (bool precomputation)
+	void InitCrypto (bool precomputation, bool aesni, bool force)
 	{
+		i2p::cpu::Detect (aesni, force);
+#if LEGACY_OPENSSL
+		SSL_library_init ();
+#endif
 /*		auto numLocks = CRYPTO_num_locks();
 		for (int i = 0; i < numLocks; i++)
 			m_OpenSSLMutexes.emplace_back (new std::mutex);
diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h
index 125a217c..816d79fd 100644
--- a/libi2pd/Crypto.h
+++ b/libi2pd/Crypto.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -21,20 +21,32 @@
 #include <openssl/sha.h>
 #include <openssl/evp.h>
 #include <openssl/rand.h>
+#include <openssl/engine.h>
 #include <openssl/opensslv.h>
 
 #include "Base.h"
 #include "Tag.h"
+#include "CPU.h"
 
 // recognize openssl version and features
-#if (OPENSSL_VERSION_NUMBER >= 0x010101000) // 1.1.1
-#	define OPENSSL_HKDF 1
-#	define OPENSSL_EDDSA 1
-#	if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER != 0x030000000)) // 3.0.0, regression in SipHash, not implemented in LibreSSL
-#		define OPENSSL_SIPHASH 1
+#if (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER >= 0x3050200fL)) // LibreSSL 3.5.2 and above
+#	define LEGACY_OPENSSL 0
+#elif ((OPENSSL_VERSION_NUMBER < 0x010100000) || defined(LIBRESSL_VERSION_NUMBER)) // 1.0.2 and below or LibreSSL
+#	define LEGACY_OPENSSL 1
+#	define X509_getm_notBefore X509_get_notBefore
+#	define X509_getm_notAfter X509_get_notAfter
+#else
+#	define LEGACY_OPENSSL 0
+#	if (OPENSSL_VERSION_NUMBER >= 0x010101000) // 1.1.1
+#		define OPENSSL_HKDF 1
+#		define OPENSSL_EDDSA 1
+#		define OPENSSL_X25519 1
+#		if (OPENSSL_VERSION_NUMBER != 0x030000000) // 3.0.0, regression in SipHash
+#			define OPENSSL_SIPHASH 1
+#		endif
 #	endif
-#	if (OPENSSL_VERSION_NUMBER >= 0x030500000) // 3.5.0
-#		define OPENSSL_PQ 1
+#	if !defined OPENSSL_NO_CHACHA && !defined OPENSSL_NO_POLY1305 // some builds might not include them
+#		define OPENSSL_AEAD_CHACHA20_POLY1305 1
 #	endif
 #endif
 
@@ -45,11 +57,7 @@ namespace crypto
 	bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len);
 
 	// DSA
-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
-	EVP_PKEY * CreateDSA (BIGNUM * pubKey = nullptr, BIGNUM * privKey = nullptr);
-#else
 	DSA * CreateDSA ();
-#endif	
 
 	// RSA
 	const BIGNUM * GetRSAE ();
@@ -75,8 +83,13 @@ namespace crypto
 		private:
 
 			uint8_t m_PublicKey[32];
+#if OPENSSL_X25519
 			EVP_PKEY_CTX * m_Ctx;
 			EVP_PKEY * m_Pkey;
+#else
+			BN_CTX * m_Ctx;
+			uint8_t m_PrivateKey[32];
+#endif
 			bool m_IsElligatorIneligible = false; // true if definitely ineligible
 	};
 
@@ -91,70 +104,142 @@ namespace crypto
 	void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub);
 
 	// AES
-	typedef i2p::data::Tag<32> AESKey;
-	
-	class ECBEncryption
+	struct ChipherBlock
 	{
-		public:
+		uint8_t buf[16];
 
-			ECBEncryption ();
-			~ECBEncryption ();
-			
-			void SetKey (const uint8_t * key) { m_Key = key; };
-			void Encrypt(const uint8_t * in, uint8_t * out);
-
-		private:
-
-			AESKey m_Key;
-			EVP_CIPHER_CTX * m_Ctx;	
+		void operator^=(const ChipherBlock& other) // XOR
+		{
+			if (!(((size_t)buf | (size_t)other.buf) & 0x03)) // multiple of 4 ?
+			{
+				for (int i = 0; i < 4; i++)
+					reinterpret_cast<uint32_t *>(buf)[i] ^= reinterpret_cast<const uint32_t *>(other.buf)[i];
+			}
+			else
+			{
+				for (int i = 0; i < 16; i++)
+					buf[i] ^= other.buf[i];
+			}
+		}
 	};
 
-	class ECBDecryption
+	typedef i2p::data::Tag<32> AESKey;
+
+	template<size_t sz>
+	class AESAlignedBuffer // 16 bytes alignment
 	{
 		public:
 
-			ECBDecryption ();
-			~ECBDecryption ();
-			
-			void SetKey (const uint8_t * key) { m_Key = key; };
-			void Decrypt (const uint8_t * in, uint8_t * out);
-			
+			AESAlignedBuffer ()
+			{
+				m_Buf = m_UnalignedBuffer;
+				uint8_t rem = ((size_t)m_Buf) & 0x0f;
+				if (rem)
+					m_Buf += (16 - rem);
+			}
+
+			operator uint8_t * () { return m_Buf; };
+			operator const uint8_t * () const { return m_Buf; };
+			ChipherBlock * GetChipherBlock () { return (ChipherBlock *)m_Buf; };
+			const ChipherBlock * GetChipherBlock () const { return (const ChipherBlock *)m_Buf; };
+
 		private:
-			
-			AESKey m_Key;
-			EVP_CIPHER_CTX * m_Ctx;	
+
+			uint8_t m_UnalignedBuffer[sz + 15]; // up to 15 bytes alignment
+			uint8_t * m_Buf;
+	};
+
+
+#if SUPPORTS_AES
+	class ECBCryptoAESNI
+	{
+		public:
+
+			uint8_t * GetKeySchedule () { return m_KeySchedule; };
+
+		protected:
+
+			void ExpandKey (const AESKey& key);
+
+		private:
+
+			AESAlignedBuffer<240> m_KeySchedule;	// 14 rounds for AES-256, 240 bytes
+	};
+#endif
+
+#if SUPPORTS_AES
+	class ECBEncryption: public ECBCryptoAESNI
+#else
+	class ECBEncryption
+#endif
+	{
+		public:
+
+		void SetKey (const AESKey& key);
+
+		void Encrypt(const ChipherBlock * in, ChipherBlock * out);
+
+	private:
+		AES_KEY m_Key;
+	};
+
+#if SUPPORTS_AES
+	class ECBDecryption: public ECBCryptoAESNI
+#else
+	class ECBDecryption
+#endif
+	{
+		public:
+
+			void SetKey (const AESKey& key);
+			void Decrypt (const ChipherBlock * in, ChipherBlock * out);
+		private:
+			AES_KEY m_Key;
 	};
 
 	class CBCEncryption
 	{
 		public:
 
-			CBCEncryption ();
-			~CBCEncryption ();
+			CBCEncryption () { memset ((uint8_t *)m_LastBlock, 0, 16); };
+
+			void SetKey (const AESKey& key) { m_ECBEncryption.SetKey (key); }; // 32 bytes
+			void SetIV (const uint8_t * iv) { memcpy ((uint8_t *)m_LastBlock, iv, 16); }; // 16 bytes
+			void GetIV (uint8_t * iv) const { memcpy (iv, (const uint8_t *)m_LastBlock, 16); };
+
+			void Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out);
+			void Encrypt (const uint8_t * in, std::size_t len, uint8_t * out);
+			void Encrypt (const uint8_t * in, uint8_t * out); // one block
+
+			ECBEncryption & ECB() { return m_ECBEncryption; }
 
-			void SetKey (const uint8_t * key) { m_Key = key; }; // 32 bytes		
-			void Encrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out);
-			
 		private:
 
-			AESKey m_Key;
-			EVP_CIPHER_CTX * m_Ctx;	
+			AESAlignedBuffer<16> m_LastBlock;
+
+			ECBEncryption m_ECBEncryption;
 	};
 
 	class CBCDecryption
 	{
 		public:
 
-			CBCDecryption ();
-			~CBCDecryption ();
-			
-			void SetKey (const uint8_t * key) { m_Key = key; }; // 32 bytes
-			void Decrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out);
+			CBCDecryption () { memset ((uint8_t *)m_IV, 0, 16); };
+
+			void SetKey (const AESKey& key) { m_ECBDecryption.SetKey (key); }; // 32 bytes
+			void SetIV (const uint8_t * iv) { memcpy ((uint8_t *)m_IV, iv, 16); }; // 16 bytes
+			void GetIV (uint8_t * iv) const { memcpy (iv, (const uint8_t *)m_IV, 16); };
+
+			void Decrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out);
+			void Decrypt (const uint8_t * in, std::size_t len, uint8_t * out);
+			void Decrypt (const uint8_t * in, uint8_t * out); // one block
+
+			ECBDecryption & ECB() { return m_ECBDecryption; }
 
 		private:
 
-			AESKey m_Key;
-			EVP_CIPHER_CTX * m_Ctx;	
+			AESAlignedBuffer<16> m_IV;
+			ECBDecryption m_ECBDecryption;
 	};
 
 	class TunnelEncryption // with double IV encryption
@@ -194,58 +279,13 @@ namespace crypto
 	};
 
 // AEAD/ChaCha20/Poly1305
+	bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt); // msgLen is len without tag
 
-	class AEADChaCha20Poly1305Encryptor
-	{
-		public:
+	void AEADChaCha20Poly1305Encrypt (const std::vector<std::pair<uint8_t *, size_t> >& bufs, const uint8_t * key, const uint8_t * nonce, uint8_t * mac); // encrypt multiple buffers with zero ad
 
-			AEADChaCha20Poly1305Encryptor ();
-			~AEADChaCha20Poly1305Encryptor ();
-
-			bool Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
-				const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); // msgLen is len without tag
-
-			void Encrypt (const std::vector<std::pair<uint8_t *, size_t> >& bufs, const uint8_t * key, const uint8_t * nonce, uint8_t * mac); // encrypt multiple buffers with zero ad
-			
-		private:
-
-			EVP_CIPHER_CTX * m_Ctx;	
-	};	
-
-	class AEADChaCha20Poly1305Decryptor
-	{
-		public:
-
-			AEADChaCha20Poly1305Decryptor ();
-			~AEADChaCha20Poly1305Decryptor ();
-
-			bool Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
-				const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); // msgLen is len without tag
-			
-		private:
-
-			EVP_CIPHER_CTX * m_Ctx;	
-	};	
-	
-	bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
-		const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt); // msgLen is len without tag
-	
 // ChaCha20
 	void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out);
 
-	class ChaCha20Context
-	{
-		public:
-
-			ChaCha20Context ();
-			~ChaCha20Context ();
-			void operator ()(const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out);
-			
-		private:
-
-			EVP_CIPHER_CTX * m_Ctx;	
-	};
-	
 // HKDF
 
 	void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out, size_t outLen = 64); // salt - 32, out - 32 or 64, info <= 32
@@ -255,27 +295,96 @@ namespace crypto
 	struct NoiseSymmetricState
 	{
 		uint8_t m_H[32] /*h*/, m_CK[64] /*[ck, k]*/;
-		uint64_t m_N;
 
-		void Init (const uint8_t * ck, const uint8_t * hh, const uint8_t * pub);
-		
 		void MixHash (const uint8_t * buf, size_t len);
 		void MixHash (const std::vector<std::pair<uint8_t *, size_t> >& bufs);
 		void MixKey (const uint8_t * sharedSecret);
-
-		bool Encrypt (const uint8_t * in, uint8_t * out, size_t len); // out length = len + 16
-		bool Decrypt (const uint8_t * in, uint8_t * out, size_t len); // len without 16 bytes tag
 	};
 
 	void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_N (tunnels, router)
 	void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_XK (NTCP2)
 	void InitNoiseXKState1 (NoiseSymmetricState& state, const uint8_t * pub); // Noise_XK (SSU2)
 	void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_IK (ratchets)
-	
+
 // init and terminate
-	void InitCrypto (bool precomputation);
+	void InitCrypto (bool precomputation, bool aesni, bool force);
 	void TerminateCrypto ();
 }
 }
 
+// take care about openssl below 1.1.0
+#if LEGACY_OPENSSL
+// define getters and setters introduced in 1.1.0
+inline int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g)
+	{
+		if (d->p) BN_free (d->p);
+		if (d->q) BN_free (d->q);
+		if (d->g) BN_free (d->g);
+		d->p = p; d->q = q; d->g = g; return 1;
+	}
+inline int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key)
+	{
+		if (d->pub_key) BN_free (d->pub_key);
+		if (d->priv_key) BN_free (d->priv_key);
+		d->pub_key = pub_key; d->priv_key = priv_key; return 1;
+	}
+inline void DSA_get0_key(const DSA *d, const BIGNUM **pub_key, const BIGNUM **priv_key)
+	{ *pub_key = d->pub_key; *priv_key = d->priv_key; }
+inline int DSA_SIG_set0(DSA_SIG *sig, BIGNUM *r, BIGNUM *s)
+	{
+		if (sig->r) BN_free (sig->r);
+		if (sig->s) BN_free (sig->s);
+		sig->r = r; sig->s = s; return 1;
+	}
+inline void DSA_SIG_get0(const DSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps)
+	{ *pr = sig->r; *ps = sig->s; }
+
+inline int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s)
+	{
+		if (sig->r) BN_free (sig->r);
+		if (sig->s) BN_free (sig->s);
+		sig->r = r; sig->s = s; return 1;
+	}
+inline void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps)
+	{ *pr = sig->r; *ps = sig->s; }
+
+inline int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d)
+	{
+		if (r->n) BN_free (r->n);
+		if (r->e) BN_free (r->e);
+		if (r->d) BN_free (r->d);
+		r->n = n; r->e = e; r->d = d; return 1;
+	}
+inline void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d)
+	{ *n = r->n; *e = r->e; *d = r->d; }
+
+inline int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g)
+	{
+		if (dh->p) BN_free (dh->p);
+		if (dh->q) BN_free (dh->q);
+		if (dh->g) BN_free (dh->g);
+		dh->p = p; dh->q = q; dh->g = g; return 1;
+	}
+inline int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key)
+	{
+		if (dh->pub_key) BN_free (dh->pub_key);
+		if (dh->priv_key) BN_free (dh->priv_key);
+		dh->pub_key = pub_key; dh->priv_key = priv_key; return 1;
+	}
+inline void DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key)
+	{ *pub_key = dh->pub_key; *priv_key = dh->priv_key; }
+
+inline RSA *EVP_PKEY_get0_RSA(EVP_PKEY *pkey)
+	{ return pkey->pkey.rsa; }
+
+inline EVP_MD_CTX *EVP_MD_CTX_new ()
+	{ return EVP_MD_CTX_create(); }
+inline void EVP_MD_CTX_free (EVP_MD_CTX *ctx)
+	{ EVP_MD_CTX_destroy (ctx); }
+
+// ssl
+#define TLS_method TLSv1_method
+
+#endif
+
 #endif
diff --git a/libi2pd/CryptoKey.cpp b/libi2pd/CryptoKey.cpp
index e37d4039..ad986129 100644
--- a/libi2pd/CryptoKey.cpp
+++ b/libi2pd/CryptoKey.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2021, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -181,21 +181,5 @@ namespace crypto
 		k.GetPrivateKey (priv);
 		memcpy (pub, k.GetPublicKey (), 32);
 	}
-
-	LocalEncryptionKey::LocalEncryptionKey (i2p::data::CryptoKeyType t): keyType(t) 
-	{ 
-		pub.resize (GetCryptoPublicKeyLen (keyType)); 
-		priv.resize (GetCryptoPrivateKeyLen (keyType));
-	}
-	
-	void LocalEncryptionKey::GenerateKeys () 
-	{ 
-		i2p::data::PrivateKeys::GenerateCryptoKeyPair (keyType, priv.data (), pub.data ()); 
-	}
-	
-	void LocalEncryptionKey::CreateDecryptor () 
-	{ 
-		decryptor = i2p::data::PrivateKeys::CreateDecryptor (keyType, priv.data ()); 
-	}
 }
 }
diff --git a/libi2pd/CryptoKey.h b/libi2pd/CryptoKey.h
index b6c37ddf..a7d86d09 100644
--- a/libi2pd/CryptoKey.h
+++ b/libi2pd/CryptoKey.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2021, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -11,7 +11,6 @@
 
 #include <inttypes.h>
 #include "Crypto.h"
-#include "Identity.h"
 
 namespace i2p
 {
@@ -158,50 +157,7 @@ namespace crypto
 			X25519Keys m_StaticKeys;
 	};
 
-	void CreateECIESX25519AEADRatchetRandomKeys (uint8_t * priv, uint8_t * pub); // including hybrid
-
-	constexpr size_t GetCryptoPrivateKeyLen (i2p::data::CryptoKeyType type)
-	{
-		switch (type)
-		{
-			case i2p::data::CRYPTO_KEY_TYPE_ELGAMAL: return 256;
-			case i2p::data::CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: return 32;
-			case i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: return 32;
-			// ML-KEM hybrid
-			case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD:
-			case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD:
-			case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD:	
-				return 32;
-		};	
-		return 0;
-	}
-
-	constexpr size_t GetCryptoPublicKeyLen (i2p::data::CryptoKeyType type)
-	{
-		switch (type)
-		{
-			case i2p::data::CRYPTO_KEY_TYPE_ELGAMAL: return 256;
-			case i2p::data::CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: return 32;
-			case i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: return 32;
-			// ML-KEM hybrid
-			case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD:
-			case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD:
-			case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD:	
-				return 32;
-		};	
-		return 0;
-	}
-
-	struct LocalEncryptionKey
-	{
-		std::vector<uint8_t> pub, priv;
-		i2p::data::CryptoKeyType keyType;
-		std::shared_ptr<CryptoKeyDecryptor> decryptor;
-
-		LocalEncryptionKey (i2p::data::CryptoKeyType t);
-		void GenerateKeys ();
-		void CreateDecryptor (); 
-	};
+	void CreateECIESX25519AEADRatchetRandomKeys (uint8_t * priv, uint8_t * pub);
 }
 }
 
diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp
index 732efca7..64738ebe 100644
--- a/libi2pd/Datagram.cpp
+++ b/libi2pd/Datagram.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -19,7 +19,7 @@ namespace i2p
 namespace datagram
 {
 	DatagramDestination::DatagramDestination (std::shared_ptr<i2p::client::ClientDestination> owner, bool gzip):
-		m_Owner (owner), m_DefaultReceiver (nullptr), m_DefaultRawReceiver (nullptr), m_Gzip (gzip)
+		m_Owner (owner), m_Receiver (nullptr), m_RawReceiver (nullptr), m_Gzip (gzip)
 	{
 		if (m_Gzip)
 			m_Deflator.reset (new i2p::data::GzipDeflator);
@@ -104,7 +104,8 @@ namespace datagram
 
 		if (verified)
 		{
-			auto session = ObtainSession (identity.GetIdentHash());
+			auto h = identity.GetIdentHash();
+			auto session = ObtainSession(h);
 			session->Ack();
 			auto r = FindReceiver(toPort);
 			if(r)
@@ -118,79 +119,19 @@ namespace datagram
 
 	void DatagramDestination::HandleRawDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
 	{
-		auto r = FindRawReceiver(toPort);
-
-		if (r)
-			r (fromPort, toPort, buf, len);
+		if (m_RawReceiver)
+			m_RawReceiver (fromPort, toPort, buf, len);
 		else
 			LogPrint (eLogWarning, "DatagramDestination: no receiver for raw datagram");
 	}
 
-	void DatagramDestination::SetReceiver (const Receiver& receiver, uint16_t port)
-	{
-		std::lock_guard<std::mutex> lock(m_ReceiversMutex);
-		m_ReceiversByPorts[port] = receiver;
-		if (!m_DefaultReceiver) {
-			m_DefaultReceiver = receiver;
-			m_DefaultReceiverPort = port;
-		}
-	}
-
-	void DatagramDestination::ResetReceiver (uint16_t port)
-	{
-		std::lock_guard<std::mutex> lock(m_ReceiversMutex);
-		m_ReceiversByPorts.erase (port);
-		if (m_DefaultReceiverPort == port) {
-			m_DefaultReceiver = nullptr;
-			m_DefaultReceiverPort = 0;
-		}
-	}
-
-
-	void DatagramDestination::SetRawReceiver (const RawReceiver& receiver, uint16_t port)
-	{
-		std::lock_guard<std::mutex> lock(m_RawReceiversMutex);
-		m_RawReceiversByPorts[port] = receiver;
-		if (!m_DefaultRawReceiver) {
-			m_DefaultRawReceiver = receiver;
-			m_DefaultRawReceiverPort = port;
-		}
-	}
-
-	void DatagramDestination::ResetRawReceiver (uint16_t port)
-	{
-		std::lock_guard<std::mutex> lock(m_RawReceiversMutex);
-		m_RawReceiversByPorts.erase (port);
-		if (m_DefaultRawReceiverPort == port) {
-			m_DefaultRawReceiver = nullptr;
-			m_DefaultRawReceiverPort = 0;
-		}
-	}
-
-
 	DatagramDestination::Receiver DatagramDestination::FindReceiver(uint16_t port)
 	{
 		std::lock_guard<std::mutex> lock(m_ReceiversMutex);
-		Receiver r = nullptr;
+		Receiver r = m_Receiver;
 		auto itr = m_ReceiversByPorts.find(port);
 		if (itr != m_ReceiversByPorts.end())
 			r = itr->second;
-		else {
-			r = m_DefaultReceiver;
-		}
-		return r;
-	}
-
-	DatagramDestination::RawReceiver DatagramDestination::FindRawReceiver(uint16_t port)
-	{
-		std::lock_guard<std::mutex> lock(m_RawReceiversMutex);
-		RawReceiver r = nullptr;
-		auto itr = m_RawReceiversByPorts.find(port);
-		if (itr != m_RawReceiversByPorts.end())
-			r = itr->second;
-		else {
-			r = m_DefaultRawReceiver;
-		}
 		return r;
 	}
 
@@ -287,8 +228,8 @@ namespace datagram
 
 	DatagramSession::DatagramSession(std::shared_ptr<i2p::client::ClientDestination> localDestination,
 		const i2p::data::IdentHash & remoteIdent) :
-		m_LocalDestination(localDestination), m_RemoteIdent(remoteIdent),
-		m_LastUse (0), m_LastFlush (0),
+		m_LocalDestination(localDestination),
+		m_RemoteIdent(remoteIdent),
 		m_RequestingLS(false)
 	{
 	}
@@ -309,12 +250,8 @@ namespace datagram
 		if (msg || m_SendQueue.empty ())
 			m_SendQueue.push_back(msg);
 		// flush queue right away if full
-		if (!msg || m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE || 
-		    m_LastUse > m_LastFlush + DATAGRAM_MAX_FLUSH_INTERVAL)
-		{	
+		if (!msg || m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE)
 			FlushSendQueue();
-			m_LastFlush =  m_LastUse;
-		}
 	}
 
 	DatagramSession::Info DatagramSession::GetSessionInfo() const
@@ -347,7 +284,7 @@ namespace datagram
 		if(path)
 			path->updateTime = i2p::util::GetSecondsSinceEpoch ();
 		if (IsRatchets ())
-			SendMsg (nullptr); // send empty message in case if we don't have some data to send
+			SendMsg (nullptr); // send empty message in case if we have some data to send
 	}
 
 	std::shared_ptr<i2p::garlic::GarlicRoutingPath> DatagramSession::GetSharedRoutingPath ()
@@ -380,19 +317,15 @@ namespace datagram
 			if (!found)
 			{
 				m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true);
-				if (m_RoutingSession)
-				{	
-					m_RoutingSession->SetAckRequestInterval (DATAGRAM_SESSION_ACK_REQUEST_INTERVAL);
-					if (!m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ())
-						m_PendingRoutingSessions.push_back (m_RoutingSession);
-				}	
+				if (!m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ())
+					m_PendingRoutingSessions.push_back (m_RoutingSession);
 			}
 		}
 
 		auto path = m_RoutingSession->GetSharedRoutingPath();
-		if (path && m_RoutingSession->IsRatchets () && m_RoutingSession->CleanupUnconfirmedTags ())
+		if (path && m_RoutingSession->IsRatchets () &&
+			m_LastUse > m_RoutingSession->GetLastActivityTimestamp ()*1000 + DATAGRAM_SESSION_PATH_TIMEOUT)
 		{
-			LogPrint (eLogDebug, "Datagram: path reset");
 			m_RoutingSession->SetSharedRoutingPath (nullptr);
 			path = nullptr;
 		}
@@ -420,14 +353,7 @@ namespace datagram
 					auto sz = ls.size();
 					if (sz)
 					{
-						int idx = -1;
-						if (m_LocalDestination)
-						{
-							auto pool = m_LocalDestination->GetTunnelPool ();
-							if (pool)
-								idx = pool->GetRng ()() % sz;
-						}
-						if (idx < 0) idx = rand () % sz;
+						auto idx = rand() % sz;
 						path->remoteLease = ls[idx];
 					}
 					else
@@ -453,14 +379,7 @@ namespace datagram
 				auto sz = ls.size();
 				if (sz)
 				{
-					int idx = -1;
-					if (m_LocalDestination)
-					{
-						auto pool = m_LocalDestination->GetTunnelPool ();
-						if (pool)
-							idx = pool->GetRng ()() % sz;
-					}
-					if (idx < 0) idx = rand () % sz;
+					auto idx = rand() % sz;
 					path->remoteLease = ls[idx];
 				}
 				else
diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h
index dd358434..8f3d5ceb 100644
--- a/libi2pd/Datagram.h
+++ b/libi2pd/Datagram.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2022, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -31,6 +31,8 @@ namespace datagram
 {
 	// milliseconds for max session idle time
 	const uint64_t DATAGRAM_SESSION_MAX_IDLE = 10 * 60 * 1000;
+	// milliseconds for how long we try sticking to a dead routing path before trying to switch
+	const uint64_t DATAGRAM_SESSION_PATH_TIMEOUT = 10 * 1000;
 	// milliseconds interval a routing path is used before switching
 	const uint64_t DATAGRAM_SESSION_PATH_SWITCH_INTERVAL = 20 * 60 * 1000;
 	// milliseconds before lease expire should we try switching leases
@@ -41,8 +43,6 @@ namespace datagram
 	const uint64_t DATAGRAM_SESSION_PATH_MIN_LIFETIME = 5 * 1000;
 	// max 64 messages buffered in send queue for each datagram session
 	const size_t DATAGRAM_SEND_QUEUE_MAX_SIZE = 64;
-	const uint64_t DATAGRAM_MAX_FLUSH_INTERVAL = 5; // in milliseconds
-	const int DATAGRAM_SESSION_ACK_REQUEST_INTERVAL = 5500; // in milliseconds
 
 	class DatagramSession : public std::enable_shared_from_this<DatagramSession>
 	{
@@ -98,7 +98,7 @@ namespace datagram
 			std::shared_ptr<i2p::garlic::GarlicRoutingSession> m_RoutingSession;
 			std::vector<std::shared_ptr<i2p::garlic::GarlicRoutingSession> > m_PendingRoutingSessions;
 			std::vector<std::shared_ptr<I2NPMessage> > m_SendQueue;
-			uint64_t m_LastUse, m_LastFlush; // milliseconds
+			uint64_t m_LastUse;
 			bool m_RequestingLS;
 	};
 
@@ -126,12 +126,14 @@ namespace datagram
 
 			void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, bool isRaw = false);
 
+			void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; };
+			void ResetReceiver () { m_Receiver = nullptr; };
 
-			void SetReceiver (const Receiver& receiver, uint16_t port);
-			void ResetReceiver (uint16_t port);
+			void SetReceiver (const Receiver& receiver, uint16_t port) { std::lock_guard<std::mutex> lock(m_ReceiversMutex); m_ReceiversByPorts[port] = receiver; };
+			void ResetReceiver (uint16_t port) { std::lock_guard<std::mutex> lock(m_ReceiversMutex); m_ReceiversByPorts.erase (port); };
 
-			void SetRawReceiver (const RawReceiver& receiver, uint16_t port);
-			void ResetRawReceiver (uint16_t port);
+			void SetRawReceiver (const RawReceiver& receiver) { m_RawReceiver = receiver; };
+			void ResetRawReceiver () { m_RawReceiver = nullptr; };
 
 			std::shared_ptr<DatagramSession::Info> GetInfoForRemote(const i2p::data::IdentHash & remote);
 
@@ -148,26 +150,20 @@ namespace datagram
 			void HandleDatagram (uint16_t fromPort, uint16_t toPort, uint8_t *const& buf, size_t len);
 			void HandleRawDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
 
+			/** find a receiver by port, if none by port is found try default receiever, otherwise returns nullptr */
 			Receiver FindReceiver(uint16_t port);
-			RawReceiver FindRawReceiver(uint16_t port);
 
 		private:
 
 			std::shared_ptr<i2p::client::ClientDestination> m_Owner;
-
+			Receiver m_Receiver; // default
+			RawReceiver m_RawReceiver; // default
+			bool m_Gzip; // gzip compression of data messages
 			std::mutex m_SessionsMutex;
 			std::map<i2p::data::IdentHash, DatagramSession_ptr > m_Sessions;
-
-			Receiver m_DefaultReceiver;
-			RawReceiver m_DefaultRawReceiver;
-			uint16_t m_DefaultReceiverPort;
-			uint16_t m_DefaultRawReceiverPort;
 			std::mutex m_ReceiversMutex;
-			std::mutex m_RawReceiversMutex;
-			std::unordered_map<uint16_t, Receiver> m_ReceiversByPorts;
-			std::unordered_map<uint16_t, RawReceiver> m_RawReceiversByPorts;
+			std::map<uint16_t, Receiver> m_ReceiversByPorts;
 
-			bool m_Gzip; // gzip compression of data messages
 			i2p::data::GzipInflator m_Inflator;
 			std::unique_ptr<i2p::data::GzipDeflator> m_Deflator;
 			std::vector<uint8_t> m_From, m_Signature;
diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp
index fd23e228..b9555abe 100644
--- a/libi2pd/Destination.cpp
+++ b/libi2pd/Destination.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -13,7 +13,6 @@
 #include <vector>
 #include <boost/algorithm/string.hpp>
 #include "Crypto.h"
-#include "ECIESX25519AEADRatchetSession.h"
 #include "Log.h"
 #include "FS.h"
 #include "Timestamp.h"
@@ -24,7 +23,7 @@ namespace i2p
 {
 namespace client
 {
-	LeaseSetDestination::LeaseSetDestination (boost::asio::io_context& service,
+	LeaseSetDestination::LeaseSetDestination (boost::asio::io_service& service,
 		bool isPublic, const std::map<std::string, std::string> * params):
 		m_Service (service), m_IsPublic (isPublic), m_PublishReplyToken (0),
 		m_LastSubmissionTime (0), m_PublishConfirmationTimer (m_Service),
@@ -38,7 +37,6 @@ namespace client
 		int inVar   = DEFAULT_INBOUND_TUNNELS_LENGTH_VARIANCE;
 		int outVar  = DEFAULT_OUTBOUND_TUNNELS_LENGTH_VARIANCE;
 		int numTags = DEFAULT_TAGS_TO_SEND;
-		bool isHighBandwidth = true;
 		std::shared_ptr<std::vector<i2p::data::IdentHash> > explicitPeers;
 		try
 		{
@@ -94,7 +92,7 @@ namespace client
 				it = params->find (I2CP_PARAM_DONT_PUBLISH_LEASESET);
 				if (it != params->end ())
 				{
-					// override isPublic
+					// oveeride isPublic
 					m_IsPublic = (it->second != "true");
 				}
 				it = params->find (I2CP_PARAM_LEASESET_TYPE);
@@ -123,9 +121,6 @@ namespace client
 						m_LeaseSetPrivKey.reset (nullptr);
 					}
 				}
-				it = params->find (I2CP_PARAM_STREAMING_PROFILE);
-				if (it != params->end ())
-					isHighBandwidth = std::stoi (it->second) != STREAMING_PROFILE_INTERACTIVE;
 			}
 		}
 		catch (std::exception & ex)
@@ -133,7 +128,7 @@ namespace client
 			LogPrint(eLogError, "Destination: Unable to parse parameters for destination: ", ex.what());
 		}
 		SetNumTags (numTags);
-		m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty, inVar, outVar, isHighBandwidth);
+		m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty, inVar, outVar);
 		if (explicitPeers)
 			m_Pool->SetExplicitPeers (explicitPeers);
 		if(params)
@@ -169,7 +164,7 @@ namespace client
 		LoadTags ();
 		m_Pool->SetLocalDestination (shared_from_this ());
 		m_Pool->SetActive (true);
-		m_CleanupTimer.expires_from_now (boost::posix_time::seconds (DESTINATION_CLEANUP_TIMEOUT));
+		m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT));
 		m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer,
 			shared_from_this (), std::placeholders::_1));
 	}
@@ -196,7 +191,7 @@ namespace client
 			m_IsPublic = itr->second != "true";
 		}
 
-		int inLen = 0, outLen = 0, inQuant = 0, outQuant = 0, numTags = 0, minLatency = 0, maxLatency = 0;
+		int inLen, outLen, inQuant, outQuant, numTags, minLatency, maxLatency;
 		std::map<std::string, int&> intOpts = {
 			{I2CP_PARAM_INBOUND_TUNNEL_LENGTH, inLen},
 			{I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, outLen},
@@ -295,7 +290,7 @@ namespace client
 		if (m_IsPublic)
 		{
 			auto s = shared_from_this ();
-			boost::asio::post (m_Service, [s](void)
+			m_Service.post ([s](void)
 			{
 				s->m_PublishVerificationTimer.cancel ();
 				s->Publish ();
@@ -323,7 +318,7 @@ namespace client
 		memcpy (data.k, key, 32);
 		memcpy (data.t, tag, 32);
 		auto s = shared_from_this ();
-		boost::asio::post (m_Service, [s,data](void)
+		m_Service.post ([s,data](void)
 			{
 				s->AddSessionKey (data.k, data.t);
 			});
@@ -340,7 +335,7 @@ namespace client
 		memcpy (data.k, key, 32);
 		data.t = tag;
 		auto s = shared_from_this ();
-		boost::asio::post (m_Service, [s,data](void)
+		m_Service.post ([s,data](void)
 			{
 				s->AddECIESx25519Key (data.k, data.t);
 			});
@@ -348,42 +343,23 @@ namespace client
 
 	void LeaseSetDestination::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
 	{
-		if (!msg) return;
-		bool empty = false;
-		{
-			std::lock_guard<std::mutex> l(m_IncomingMsgsQueueMutex);
-			empty = m_IncomingMsgsQueue.empty ();
-			m_IncomingMsgsQueue.push_back (msg);
-		}
-		if (empty)
-			boost::asio::post (m_Service, [s = shared_from_this ()]() 
-			{ 
-				std::list<std::shared_ptr<I2NPMessage> > receivedMsgs;
-				{
-					std::lock_guard<std::mutex> l(s->m_IncomingMsgsQueueMutex);
-					s->m_IncomingMsgsQueue.swap (receivedMsgs);
-				}
-			    for (auto& it: receivedMsgs)
-			    	s->HandleGarlicMessage (it);               
-			});
+		m_Service.post (std::bind (&LeaseSetDestination::HandleGarlicMessage, shared_from_this (), msg));
 	}
 
 	void LeaseSetDestination::ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
 	{
 		uint32_t msgID = bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET);
-		boost::asio::post (m_Service, std::bind (&LeaseSetDestination::HandleDeliveryStatusMessage, shared_from_this (), msgID));
+		m_Service.post (std::bind (&LeaseSetDestination::HandleDeliveryStatusMessage, shared_from_this (), msgID));
 	}
 
 	void LeaseSetDestination::HandleI2NPMessage (const uint8_t * buf, size_t len)
 	{
 		I2NPMessageType typeID = (I2NPMessageType)(buf[I2NP_HEADER_TYPEID_OFFSET]);
 		uint32_t msgID = bufbe32toh (buf + I2NP_HEADER_MSGID_OFFSET);
-		LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE, 
-			GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE, msgID, nullptr);
+		LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE, GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE, msgID);
 	}
 
-	bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload,
-		size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from)
+	bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID)
 	{
 		switch (typeID)
 		{
@@ -391,14 +367,11 @@ namespace client
 				HandleDataMessage (payload, len);
 			break;
 			case eI2NPDeliveryStatus:
+				// we assume tunnel tests non-encrypted
 				HandleDeliveryStatusMessage (bufbe32toh (payload + DELIVERY_STATUS_MSGID_OFFSET));
 			break;
-			case eI2NPTunnelTest:
-				if (m_Pool)
-					m_Pool->ProcessTunnelTest (bufbe32toh (payload + TUNNEL_TEST_MSGID_OFFSET), bufbe64toh (payload + TUNNEL_TEST_TIMESTAMP_OFFSET));
-			break;
 			case eI2NPDatabaseStore:
-				HandleDatabaseStoreMessage (payload, len, from);
+				HandleDatabaseStoreMessage (payload, len);
 			break;
 			case eI2NPDatabaseSearchReply:
 				HandleDatabaseSearchReplyMessage (payload, len);
@@ -413,8 +386,7 @@ namespace client
 		return true;
 	}
 
-	void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len,
-		i2p::garlic::ECIESX25519AEADRatchetSession * from)
+	void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len)
 	{
 		if (len < DATABASE_STORE_HEADER_SIZE)
 		{
@@ -435,7 +407,6 @@ namespace client
 		}
 		i2p::data::IdentHash key (buf + DATABASE_STORE_KEY_OFFSET);
 		std::shared_ptr<i2p::data::LeaseSet> leaseSet;
-		std::shared_ptr<LeaseSetRequest> request;
 		switch (buf[DATABASE_STORE_TYPE_OFFSET])
 		{
 			case i2p::data::NETDB_STORE_TYPE_LEASESET: // 1
@@ -469,21 +440,8 @@ namespace client
 					if (buf[DATABASE_STORE_TYPE_OFFSET] == i2p::data::NETDB_STORE_TYPE_LEASESET)
 						leaseSet = std::make_shared<i2p::data::LeaseSet> (buf + offset, len - offset); // LeaseSet
 					else
-					{	
-						leaseSet = std::make_shared<i2p::data::LeaseSet2> (buf[DATABASE_STORE_TYPE_OFFSET], 
-							buf + offset, len - offset, true, from ? from->GetRemoteStaticKeyType () : GetPreferredCryptoType () ); // LeaseSet2
-						if (from)
-						{
-							uint8_t pub[32];
-							leaseSet->Encrypt (nullptr, pub);
-							if (memcmp (from->GetRemoteStaticKey (), pub, 32))
-							{
-								LogPrint (eLogError, "Destination: Remote LeaseSet static key mismatch");
-								leaseSet = nullptr;
-							}	
-						}	
-					}		
-					if (leaseSet && leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ())
+						leaseSet = std::make_shared<i2p::data::LeaseSet2> (buf[DATABASE_STORE_TYPE_OFFSET], buf + offset, len - offset, true, GetPreferredCryptoType () ); // LeaseSet2
+					if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ())
 					{
 						if (leaseSet->GetIdentHash () != GetIdentHash ())
 						{
@@ -504,60 +462,34 @@ namespace client
 			case i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2: // 5
 			{
 				auto it2 = m_LeaseSetRequests.find (key);
-				if (it2 != m_LeaseSetRequests.end ())
+				if (it2 != m_LeaseSetRequests.end () && it2->second->requestedBlindedKey)
 				{
-					request = it2->second;
-					m_LeaseSetRequests.erase (it2);
-					if (request->requestedBlindedKey)
+					auto ls2 = std::make_shared<i2p::data::LeaseSet2> (buf + offset, len - offset,
+						it2->second->requestedBlindedKey, m_LeaseSetPrivKey ? ((const uint8_t *)*m_LeaseSetPrivKey) : nullptr , GetPreferredCryptoType ());
+					if (ls2->IsValid () && !ls2->IsExpired ())
 					{
-						auto ls2 = std::make_shared<i2p::data::LeaseSet2> (buf + offset, len - offset,
-							request->requestedBlindedKey, m_LeaseSetPrivKey ? ((const uint8_t *)*m_LeaseSetPrivKey) : nullptr, 
-						    GetPreferredCryptoType ());
-						if (ls2->IsValid () && !ls2->IsExpired ())
-						{
-							leaseSet = ls2;
-							std::lock_guard<std::mutex> lock(m_RemoteLeaseSetsMutex);
-							m_RemoteLeaseSets[ls2->GetIdentHash ()] = ls2; // ident is not key
-							m_RemoteLeaseSets[key] = ls2; // also store as key for next lookup
-						}
-						else
-							LogPrint (eLogError, "Destination: New remote encrypted LeaseSet2 failed");
+						leaseSet = ls2;
+						std::lock_guard<std::mutex> lock(m_RemoteLeaseSetsMutex);
+						m_RemoteLeaseSets[ls2->GetIdentHash ()] = ls2; // ident is not key
+						m_RemoteLeaseSets[key] = ls2; // also store as key for next lookup
 					}
 					else
-					{
-						// publishing verification doesn't have requestedBlindedKey
-						auto localLeaseSet = GetLeaseSetMt ();
-						if (localLeaseSet->GetStoreHash () == key)
-						{
-							auto ls = std::make_shared<i2p::data::LeaseSet2> (i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2,
-								localLeaseSet->GetBuffer (), localLeaseSet->GetBufferLen (), false);
-							leaseSet = ls;
-						}
-						else
-							LogPrint (eLogWarning, "Destination: Encrypted LeaseSet2 received for request without blinded key");
-					}
+						LogPrint (eLogError, "Destination: New remote encrypted LeaseSet2 failed");
 				}
 				else
-					LogPrint (eLogWarning, "Destination: Couldn't find request for encrypted LeaseSet2");
+					LogPrint (eLogInfo, "Destination: Couldn't find request for encrypted LeaseSet2");
 				break;
 			}
 			default:
 				LogPrint (eLogError, "Destination: Unexpected client's DatabaseStore type ", buf[DATABASE_STORE_TYPE_OFFSET], ", dropped");
 		}
 
-		if (!request)
+		auto it1 = m_LeaseSetRequests.find (key);
+		if (it1 != m_LeaseSetRequests.end ())
 		{
-			auto it1 = m_LeaseSetRequests.find (key);
-			if (it1 != m_LeaseSetRequests.end ())
-			{
-				request = it1->second;
-				m_LeaseSetRequests.erase (it1);
-			}
-		}
-		if (request)
-		{
-			request->requestTimeoutTimer.cancel ();
-			request->Complete (leaseSet);
+			it1->second->requestTimeoutTimer.cancel ();
+			if (it1->second) it1->second->Complete (leaseSet);
+			m_LeaseSetRequests.erase (it1);
 		}
 	}
 
@@ -570,43 +502,38 @@ namespace client
 		if (it != m_LeaseSetRequests.end ())
 		{
 			auto request = it->second;
-			for (int i = 0; i < num; i++)
+			bool found = false;
+			if (request->excluded.size () < MAX_NUM_FLOODFILLS_PER_REQUEST)
 			{
-				i2p::data::IdentHash peerHash (buf + 33 + i*32);
-				if (!request->excluded.count (peerHash) && !i2p::data::netdb.FindRouter (peerHash))
+				for (int i = 0; i < num; i++)
 				{
-					LogPrint (eLogInfo, "Destination: Found new floodfill, request it");
-					i2p::data::netdb.RequestDestination (peerHash, nullptr, false); // through exploratory
+					i2p::data::IdentHash peerHash (buf + 33 + i*32);
+					if (!request->excluded.count (peerHash) && !i2p::data::netdb.FindRouter (peerHash))
+					{
+						LogPrint (eLogInfo, "Destination: Found new floodfill, request it");
+						i2p::data::netdb.RequestDestination (peerHash, nullptr, false); // through exploratory
+					}
+				}
+
+				auto floodfill = i2p::data::netdb.GetClosestFloodfill (key, request->excluded);
+				if (floodfill)
+				{
+					LogPrint (eLogInfo, "Destination: Requesting ", key.ToBase64 (), " at ", floodfill->GetIdentHash ().ToBase64 ());
+					if (SendLeaseSetRequest (key, floodfill, request))
+						found = true;
 				}
 			}
-			SendNextLeaseSetRequest (key, request);
+			if (!found)
+			{
+				LogPrint (eLogInfo, "Destination: ", key.ToBase64 (), " was not found on ", MAX_NUM_FLOODFILLS_PER_REQUEST, " floodfills");
+				request->Complete (nullptr);
+				m_LeaseSetRequests.erase (key);
+			}
 		}
 		else
 			LogPrint (eLogWarning, "Destination: Request for ", key.ToBase64 (), " not found");
 	}
 
-	void LeaseSetDestination::SendNextLeaseSetRequest (const i2p::data::IdentHash& key,
-		std::shared_ptr<LeaseSetRequest> request)
-	{
-		bool found = false;
-		if (request->excluded.size () < MAX_NUM_FLOODFILLS_PER_REQUEST)
-		{
-			auto floodfill = i2p::data::netdb.GetClosestFloodfill (key, request->excluded);
-			if (floodfill)
-			{
-				LogPrint (eLogInfo, "Destination: Requesting ", key.ToBase64 (), " at ", floodfill->GetIdentHash ().ToBase64 ());
-				if (SendLeaseSetRequest (key, floodfill, request))
-					found = true;
-			}
-		}
-		if (!found)
-		{
-			LogPrint (eLogInfo, "Destination: ", key.ToBase64 (), " was not found on ", MAX_NUM_FLOODFILLS_PER_REQUEST, " floodfills");
-			request->Complete (nullptr);
-			m_LeaseSetRequests.erase (key);
-		}
-	}
-
 	void LeaseSetDestination::HandleDeliveryStatusMessage (uint32_t msgID)
 	{
 		if (msgID == m_PublishReplyToken)
@@ -615,8 +542,7 @@ namespace client
 			m_ExcludedFloodfills.clear ();
 			m_PublishReplyToken = 0;
 			// schedule verification
-			m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT +
-				(m_Pool ? m_Pool->GetRng ()() % PUBLISH_VERIFICATION_TIMEOUT_VARIANCE : 0)));
+			m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT));
 			m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer,
 			shared_from_this (), std::placeholders::_1));
 		}
@@ -624,12 +550,9 @@ namespace client
 			i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msgID);
 	}
 
-	void LeaseSetDestination::SetLeaseSetUpdated (bool post)
+	void LeaseSetDestination::SetLeaseSetUpdated ()
 	{
-		if (post)
-			boost::asio::post (m_Service, [s = shared_from_this ()]() { s->UpdateLeaseSet (); });
-		else
-			UpdateLeaseSet ();
+		UpdateLeaseSet ();
 	}
 
 	void LeaseSetDestination::Publish ()
@@ -655,7 +578,12 @@ namespace client
 				shared_from_this (), std::placeholders::_1));
 			return;
 		}
-		auto floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetStoreHash (), m_ExcludedFloodfills);
+		if (!m_Pool->GetInboundTunnels ().size () || !m_Pool->GetOutboundTunnels ().size ())
+		{
+			LogPrint (eLogError, "Destination: Can't publish LeaseSet. Destination is not ready");
+			return;
+		}
+		auto floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetIdentHash (), m_ExcludedFloodfills);
 		if (!floodfill)
 		{
 			LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found");
@@ -666,39 +594,26 @@ namespace client
 		auto inbound = m_Pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true));
 		if (!outbound || !inbound)
 		{
-			if (!m_Pool->GetInboundTunnels ().empty () && !m_Pool->GetOutboundTunnels ().empty ())
+			LogPrint (eLogInfo, "Destination: No compatible tunnels with ", floodfill->GetIdentHash ().ToBase64 (), ". Trying another floodfill");
+			m_ExcludedFloodfills.insert (floodfill->GetIdentHash ());
+			floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetIdentHash (), m_ExcludedFloodfills);
+			if (floodfill)
 			{
-				LogPrint (eLogInfo, "Destination: No compatible tunnels with ", floodfill->GetIdentHash ().ToBase64 (), ". Trying another floodfill");
-				m_ExcludedFloodfills.insert (floodfill->GetIdentHash ());
-				floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetStoreHash (), m_ExcludedFloodfills);
-				if (floodfill)
+				outbound = m_Pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false));
+				if (outbound)
 				{
-					outbound = m_Pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false));
-					if (outbound)
-					{
-						inbound = m_Pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true));
-						if (!inbound)
-							LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels");
-					}
-					else
-						LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels");
+					inbound = m_Pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true));
+					if (!inbound)
+						LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels");
 				}
 				else
-					LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found");
+					LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels");
 			}
 			else
-				LogPrint (eLogDebug, "Destination: No tunnels in pool");
-
+				LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found");
 			if (!floodfill || !outbound || !inbound)
 			{
-				// we can't publish now
 				m_ExcludedFloodfills.clear ();
-				m_PublishReplyToken = 1; // dummy non-zero value
-				// try again after a while
-				LogPrint (eLogInfo, "Destination: Can't publish LeasetSet because destination is not ready. Try publishing again after ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds");
-				m_PublishConfirmationTimer.expires_from_now (boost::posix_time::milliseconds(PUBLISH_CONFIRMATION_TIMEOUT));
-				m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer,
-					shared_from_this (), std::placeholders::_1));
 				return;
 			}
 		}
@@ -706,16 +621,7 @@ namespace client
 		LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ());
 		RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4);
 		auto msg = WrapMessageForRouter (floodfill, i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound));
-		auto s = shared_from_this ();
-		msg->onDrop = [s]()
-			{
-				boost::asio::post (s->GetService (), [s]()
-					{
-						s->m_PublishConfirmationTimer.cancel ();
-						s->HandlePublishConfirmationTimer (boost::system::error_code());
-					});
-			};
-		m_PublishConfirmationTimer.expires_from_now (boost::posix_time::milliseconds(PUBLISH_CONFIRMATION_TIMEOUT));
+		m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT));
 		m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer,
 			shared_from_this (), std::placeholders::_1));
 		outbound->SendTunnelDataMsgTo (floodfill->GetIdentHash (), 0, msg);
@@ -731,15 +637,15 @@ namespace client
 				m_PublishReplyToken = 0;
 				if (GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL)
 				{
-					LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds or failed. will try again");
+					LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds, will try again");
 					Publish ();
 				}
 				else
 				{
-					LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds from Java floodfill for crypto type ", (int)GetIdentity ()->GetCryptoKeyType ());
+					LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds from Java floodfill for crypto type ", (int)GetIdentity ()->GetCryptoKeyType ());
 					// Java floodfill never sends confirmation back for unknown crypto type
 					// assume it successive and try to verify
-					m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT + PUBLISH_VERIFICATION_TIMEOUT_VARIANCE)); // always max
+					m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT));
 					m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer,
 			shared_from_this (), std::placeholders::_1));
 
@@ -794,10 +700,10 @@ namespace client
 		if (!m_Pool || !IsReady ())
 		{
 			if (requestComplete)
-				boost::asio::post (m_Service, [requestComplete](void){requestComplete (nullptr);});
+				m_Service.post ([requestComplete](void){requestComplete (nullptr);});
 			return false;
 		}
-		boost::asio::post (m_Service, std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete, nullptr));
+		m_Service.post (std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete, nullptr));
 		return true;
 	}
 
@@ -806,7 +712,7 @@ namespace client
 		if (!dest || !m_Pool || !IsReady ())
 		{
 			if (requestComplete)
-				boost::asio::post (m_Service, [requestComplete](void){requestComplete (nullptr);});
+				m_Service.post ([requestComplete](void){requestComplete (nullptr);});
 			return false;
 		}
 		auto storeHash = dest->GetStoreHash ();
@@ -814,17 +720,17 @@ namespace client
 		if (leaseSet)
 		{
 			if (requestComplete)
-				boost::asio::post (m_Service, [requestComplete, leaseSet](void){requestComplete (leaseSet);});
+				m_Service.post ([requestComplete, leaseSet](void){requestComplete (leaseSet);});
 			return true;
 		}
-		boost::asio::post (m_Service, std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), storeHash, requestComplete, dest));
+		m_Service.post (std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), storeHash, requestComplete, dest));
 		return true;
 	}
 
 	void LeaseSetDestination::CancelDestinationRequest (const i2p::data::IdentHash& dest, bool notify)
 	{
 		auto s = shared_from_this ();
-		boost::asio::post (m_Service, [dest, notify, s](void)
+		m_Service.post ([dest, notify, s](void)
 			{
 				auto it = s->m_LeaseSetRequests.find (dest);
 				if (it != s->m_LeaseSetRequests.end ())
@@ -844,7 +750,7 @@ namespace client
 
 	void LeaseSetDestination::RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete, std::shared_ptr<const i2p::data::BlindedPublicKey> requestedBlindedKey)
 	{
-		std::unordered_set<i2p::data::IdentHash> excluded;
+		std::set<i2p::data::IdentHash> excluded;
 		auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, excluded);
 		if (floodfill)
 		{
@@ -852,7 +758,7 @@ namespace client
 			request->requestedBlindedKey = requestedBlindedKey; // for encrypted LeaseSet2
 			if (requestComplete)
 				request->requestComplete.push_back (requestComplete);
-			auto ts = i2p::util::GetMillisecondsSinceEpoch ();
+			auto ts = i2p::util::GetSecondsSinceEpoch ();
 			auto ret = m_LeaseSetRequests.insert (std::pair<i2p::data::IdentHash, std::shared_ptr<LeaseSetRequest> >(dest,request));
 			if (ret.second) // inserted
 			{
@@ -916,17 +822,8 @@ namespace client
 				AddECIESx25519Key (replyKey, replyTag);
 			else
 				AddSessionKey (replyKey, replyTag);
-
-			auto msg = WrapMessageForRouter (nextFloodfill,
-				CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, request->replyTunnel, replyKey, replyTag, isECIES));
-			auto s = shared_from_this ();
-			msg->onDrop = [s, dest, request]()
-				{
-					boost::asio::post (s->GetService (), [s, dest, request]()
-						{
-							s->SendNextLeaseSetRequest (dest, request);
-						});
-				};
+			auto msg = WrapMessageForRouter (nextFloodfill, CreateLeaseSetDatabaseLookupMsg (dest,
+				request->excluded, request->replyTunnel, replyKey, replyTag, isECIES));
 			request->outboundTunnel->SendTunnelDataMsgs (
 				{
 					i2p::tunnel::TunnelMessageBlock
@@ -935,7 +832,7 @@ namespace client
 						nextFloodfill->GetIdentHash (), 0, msg
 					}
 				});
-			request->requestTimeoutTimer.expires_from_now (boost::posix_time::milliseconds(LEASESET_REQUEST_TIMEOUT));
+			request->requestTimeoutTimer.expires_from_now (boost::posix_time::seconds(LEASESET_REQUEST_TIMEOUT));
 			request->requestTimeoutTimer.async_wait (std::bind (&LeaseSetDestination::HandleRequestTimoutTimer,
 				shared_from_this (), std::placeholders::_1, dest));
 		}
@@ -952,7 +849,7 @@ namespace client
 			if (it != m_LeaseSetRequests.end ())
 			{
 				bool done = false;
-				uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
+				uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
 				if (ts < it->second->requestTime + MAX_LEASESET_REQUEST_TIMEOUT)
 				{
 					auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, it->second->excluded);
@@ -989,8 +886,7 @@ namespace client
 			CleanupExpiredTags ();
 			CleanupRemoteLeaseSets ();
 			CleanupDestination ();
-			m_CleanupTimer.expires_from_now (boost::posix_time::seconds (DESTINATION_CLEANUP_TIMEOUT +
-				(m_Pool ? m_Pool->GetRng ()() % DESTINATION_CLEANUP_TIMEOUT_VARIANCE : 0)));
+			m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT));
 			m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer,
 				shared_from_this (), std::placeholders::_1));
 		}
@@ -1004,7 +900,7 @@ namespace client
 		{
 			if (it->second->IsEmpty () || ts > it->second->GetExpirationTime ()) // leaseset expired
 			{
-				LogPrint (eLogDebug, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired");
+				LogPrint (eLogWarning, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired");
 				it = m_RemoteLeaseSets.erase (it);
 			}
 			else
@@ -1012,15 +908,19 @@ namespace client
 		}
 	}
 
-	ClientDestination::ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys,
+	i2p::data::CryptoKeyType LeaseSetDestination::GetPreferredCryptoType () const
+	{
+		if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
+			return i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD;
+		return i2p::data::CRYPTO_KEY_TYPE_ELGAMAL;
+	}
+
+	ClientDestination::ClientDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys,
 		bool isPublic, const std::map<std::string, std::string> * params):
 		LeaseSetDestination (service, isPublic, params),
-		m_Keys (keys), m_PreferredCryptoType (0), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY),
-		m_StreamingOutboundSpeed (DEFAULT_MAX_OUTBOUND_SPEED),
-		m_StreamingInboundSpeed (DEFAULT_MAX_INBOUND_SPEED),
-		m_StreamingMaxConcurrentStreams (DEFAULT_MAX_CONCURRENT_STREAMS),
+		m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY),
 		m_IsStreamingAnswerPings (DEFAULT_ANSWER_PINGS), m_LastPort (0),
-		m_DatagramDestination (nullptr), m_RefCounter (0), m_LastPublishedTimestamp (0),
+		m_DatagramDestination (nullptr), m_RefCounter (0),
 		m_ReadyChecker(service)
 	{
 		if (keys.IsOfflineSignature () && GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET)
@@ -1040,15 +940,7 @@ namespace client
 				{
 					try
 					{
-						i2p::data::CryptoKeyType cryptoType = std::stoi(it1);
-#if !OPENSSL_PQ
-						if (cryptoType <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // skip PQ keys if not supported
-#endif					
-						{
-							if (!m_PreferredCryptoType && cryptoType)
-								m_PreferredCryptoType = cryptoType; // first non-zero in the list
-							encryptionKeyTypes.insert (cryptoType);
-						}	
+						encryptionKeyTypes.insert (std::stoi(it1));
 					}
 					catch (std::exception& ex)
 					{
@@ -1059,21 +951,29 @@ namespace client
 			}
 		}
 		// if no param or valid crypto type use from identity
+		bool isSingleKey = false;
 		if (encryptionKeyTypes.empty ())
-			encryptionKeyTypes.insert ( { GetIdentity ()->GetCryptoKeyType (),
-				i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD }); // usually 0,4
+		{
+			isSingleKey = true;
+			encryptionKeyTypes.insert (GetIdentity ()->GetCryptoKeyType ());
+		}
 
 		for (auto& it: encryptionKeyTypes)
 		{
-			auto encryptionKey = std::make_shared<i2p::crypto::LocalEncryptionKey> (it);
+			auto encryptionKey = new EncryptionKey (it);
 			if (IsPublic ())
-				PersistTemporaryKeys (encryptionKey);
+				PersistTemporaryKeys (encryptionKey, isSingleKey);
 			else
 				encryptionKey->GenerateKeys ();
 			encryptionKey->CreateDecryptor ();
-			if (it > i2p::data::CRYPTO_KEY_TYPE_ELGAMAL && GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET)
-				SetLeaseSetType (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // Only DSA can use LeaseSet1
-			m_EncryptionKeys.emplace (it, encryptionKey);
+			if (it == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
+			{
+				m_ECIESx25519EncryptionKey.reset (encryptionKey);
+				if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET)
+					SetLeaseSetType (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // Rathets must use LeaseSet2
+			}
+			else
+				m_StandardEncryptionKey.reset (encryptionKey);
 		}
 
 		if (IsPublic ())
@@ -1087,14 +987,6 @@ namespace client
 				auto it = params->find (I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY);
 				if (it != params->end ())
 					m_StreamingAckDelay = std::stoi(it->second);
-				it = params->find (I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED);
-				if (it != params->end ())
-					m_StreamingOutboundSpeed = std::stoi(it->second);
-				it = params->find (I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED);
-				if (it != params->end ())
-					m_StreamingInboundSpeed = std::stoi(it->second);
-				if (it != params->end ())
-					m_StreamingMaxConcurrentStreams = std::stoi(it->second);
 				it = params->find (I2CP_PARAM_STREAMING_ANSWER_PINGS);
 				if (it != params->end ())
 					m_IsStreamingAnswerPings = std::stoi (it->second); // 1 for true
@@ -1145,6 +1037,7 @@ namespace client
 	void ClientDestination::Stop ()
 	{
 		LogPrint(eLogDebug, "Destination: Stopping destination ", GetIdentHash().ToBase32(), ".b32.i2p");
+		LeaseSetDestination::Stop ();
 		m_ReadyChecker.cancel();
 		LogPrint(eLogDebug, "Destination: -> Stopping Streaming Destination");
 		m_StreamingDestination->Stop ();
@@ -1166,7 +1059,6 @@ namespace client
 			delete m_DatagramDestination;
 			m_DatagramDestination = nullptr;
 		}
-		LeaseSetDestination::Stop ();
 		LogPrint(eLogDebug, "Destination: -> Stopping done");
 	}
 
@@ -1230,7 +1122,7 @@ namespace client
 		if (leaseSet)
 		{
 			auto stream = CreateStream (leaseSet, port);
-			boost::asio::post (GetService (), [streamRequestComplete, stream]()
+			GetService ().post ([streamRequestComplete, stream]()
 				{
 					streamRequestComplete(stream);
 				});
@@ -1423,56 +1315,32 @@ namespace client
 		return ret;
 	}
 
-	void ClientDestination::PersistTemporaryKeys (std::shared_ptr<i2p::crypto::LocalEncryptionKey> keys)
+	void ClientDestination::PersistTemporaryKeys (EncryptionKey * keys, bool isSingleKey)
 	{
 		if (!keys) return;
 		std::string ident = GetIdentHash().ToBase32();
-		std::string path  = i2p::fs::DataDirPath("destinations", ident + "." + std::to_string (keys->keyType) + ".dat");
+		std::string path  = i2p::fs::DataDirPath("destinations",
+			isSingleKey ? (ident + ".dat") : (ident + "." + std::to_string (keys->keyType) + ".dat"));
 		std::ifstream f(path, std::ifstream::binary);
-		if (f) 
-		{
-			size_t len = 0;
-			if (keys->keyType == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL)
-				len = 512;
-			else if (keys->keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
-			{	
-				f.seekg (0, std::ios::end);
-				len = f.tellg();
-				f.seekg (0, std::ios::beg);
-			}
-				
-			if (len == 512)
-			{    
-				char pub[256], priv[256];
-				f.read (pub, 256);
-				memcpy (keys->pub.data(), pub, keys->pub.size());
-				f.read (priv, 256);
-				memcpy (keys->priv.data (), priv, keys->priv.size ());
-			}
-			else
-			{
-				f.read ((char *)keys->pub.data(), keys->pub.size());
-				f.read ((char *)keys->priv.data(), keys->priv.size());
-			}	
-			if (f) 
-				return;
-			else
-				LogPrint(eLogWarning, "Destination: Can't read keys from ", path);
+
+		if (f) {
+			f.read ((char *)keys->pub, 256);
+			f.read ((char *)keys->priv, 256);
+			return;
 		}
 
-		LogPrint (eLogInfo, "Destination: Creating new temporary keys of type ", keys->keyType, " for address ", ident, ".b32.i2p");
-		memset (keys->priv.data (), 0, keys->priv.size ());
-		memset (keys->pub.data (), 0, keys->pub.size ());
+		LogPrint (eLogInfo, "Destination: Creating new temporary keys of type for address ", ident, ".b32.i2p");
+		memset (keys->priv, 0, 256);
+		memset (keys->pub, 0, 256);
 		keys->GenerateKeys ();
-		
+		// TODO:: persist crypto key type
 		std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out);
-		if (f1) 
-		{
-			f1.write ((char *)keys->pub.data (), keys->pub.size ());
-			f1.write ((char *)keys->priv.data (), keys->priv.size ());
+		if (f1) {
+			f1.write ((char *)keys->pub, 256);
+			f1.write ((char *)keys->priv, 256);
+			return;
 		}
-		if (!f1)
-			LogPrint(eLogError, "Destination: Can't save keys to ", path);
+		LogPrint(eLogCritical, "Destinations: Can't save keys to ", path);
 	}
 
 	void ClientDestination::CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels)
@@ -1480,10 +1348,9 @@ namespace client
 		std::shared_ptr<i2p::data::LocalLeaseSet> leaseSet;
 		if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET)
 		{
-			auto it = m_EncryptionKeys.find (i2p::data::CRYPTO_KEY_TYPE_ELGAMAL);
-			if (it != m_EncryptionKeys.end ())
+			if (m_StandardEncryptionKey)
 			{
-				leaseSet = std::make_shared<i2p::data::LocalLeaseSet> (GetIdentity (), it->second->pub.data (), tunnels);
+				leaseSet = std::make_shared<i2p::data::LocalLeaseSet> (GetIdentity (), m_StandardEncryptionKey->pub, tunnels);
 				// sign
 				Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ());
 			}
@@ -1493,40 +1360,18 @@ namespace client
 		else
 		{
 			// standard LS2 (type 3) first
-			if (m_EncryptionKeys.empty ())
-			{
-				LogPrint (eLogError, "Destinations: No encryption keys");
-				return;
-			}	
-			
-			i2p::data::LocalLeaseSet2::EncryptionKeys keySections;
-			std::shared_ptr<const i2p::crypto::LocalEncryptionKey> preferredSection;
-			if (m_EncryptionKeys.size () == 1)
-				preferredSection = m_EncryptionKeys.begin ()->second; // only key
-			else
-			{	
-				for (const auto& it: m_EncryptionKeys)
-					if (it.first == m_PreferredCryptoType)
-						preferredSection = it.second;
-					else
-						keySections.push_back (it.second);
-			}	
-			if (preferredSection) 
-				keySections.push_front (preferredSection); // make preferred first
-			
-			auto publishedTimestamp = i2p::util::GetSecondsSinceEpoch ();
-			if (publishedTimestamp <= m_LastPublishedTimestamp)
-			{
-				LogPrint (eLogDebug, "Destination: LeaseSet update at the same second");
-				publishedTimestamp++; // force newer timestamp
-			}
+			i2p::data::LocalLeaseSet2::KeySections keySections;
+			if (m_ECIESx25519EncryptionKey)
+				keySections.push_back ({m_ECIESx25519EncryptionKey->keyType, 32, m_ECIESx25519EncryptionKey->pub} );
+			if (m_StandardEncryptionKey)
+				keySections.push_back ({m_StandardEncryptionKey->keyType, (uint16_t)m_StandardEncryptionKey->decryptor->GetPublicKeyLen (), m_StandardEncryptionKey->pub} );
+
 			bool isPublishedEncrypted = GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2;
 			auto ls2 = std::make_shared<i2p::data::LocalLeaseSet2> (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2,
-				m_Keys, keySections, tunnels, IsPublic (), publishedTimestamp, isPublishedEncrypted);
+				m_Keys, keySections, tunnels, IsPublic (), isPublishedEncrypted);
 			if (isPublishedEncrypted) // encrypt if type 5
 				ls2 = std::make_shared<i2p::data::LocalEncryptedLeaseSet2> (ls2, m_Keys, GetAuthType (), m_AuthKeys);
 			leaseSet = ls2;
-			m_LastPublishedTimestamp = publishedTimestamp;
 		}
 		SetLeaseSet (leaseSet);
 	}
@@ -1538,22 +1383,11 @@ namespace client
 
 	bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const
 	{
-		std::shared_ptr<i2p::crypto::LocalEncryptionKey> encryptionKey;
-		if (!m_EncryptionKeys.empty ())
-		{
-			if (m_EncryptionKeys.rbegin ()->first == preferredCrypto)
-				encryptionKey = m_EncryptionKeys.rbegin ()->second;
-			else
-			{
-				auto it = m_EncryptionKeys.find (preferredCrypto);
-				if (it != m_EncryptionKeys.end ())
-					encryptionKey = it->second;
-			}	
-			if (!encryptionKey)
-				encryptionKey = m_EncryptionKeys.rbegin ()->second;
-		}	
-		if (encryptionKey)
-			return encryptionKey->decryptor->Decrypt (encrypted, data);
+		if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
+			if (m_ECIESx25519EncryptionKey && m_ECIESx25519EncryptionKey->decryptor)
+				return m_ECIESx25519EncryptionKey->decryptor->Decrypt (encrypted, data);
+		if (m_StandardEncryptionKey && m_StandardEncryptionKey->decryptor)
+			return m_StandardEncryptionKey->decryptor->Decrypt (encrypted, data);
 		else
 			LogPrint (eLogError, "Destinations: Decryptor is not set");
 		return false;
@@ -1561,26 +1395,14 @@ namespace client
 
 	bool ClientDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const
 	{
-#if __cplusplus >= 202002L // C++20
-		return m_EncryptionKeys.contains (keyType);
-#else		
-		return m_EncryptionKeys.count (keyType) > 0;
-#endif		
+		return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? (bool)m_ECIESx25519EncryptionKey : (bool)m_StandardEncryptionKey;
 	}
 
-	i2p::data::CryptoKeyType ClientDestination::GetRatchetsHighestCryptoType () const
-	{
-		if (m_EncryptionKeys.empty ()) return 0;
-		auto cryptoType = m_EncryptionKeys.rbegin ()->first; 
-		return cryptoType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? cryptoType : 0;
-	}
-		
 	const uint8_t * ClientDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const
 	{
-		auto it = m_EncryptionKeys.find (keyType);
-		if (it != m_EncryptionKeys.end ())
-			return it->second->pub.data ();
-		return nullptr;
+		if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
+			return m_ECIESx25519EncryptionKey ? m_ECIESx25519EncryptionKey->pub : nullptr;
+		return m_StandardEncryptionKey ? m_StandardEncryptionKey->pub : nullptr;
 	}
 
 	void ClientDestination::ReadAuthKey (const std::string& group, const std::map<std::string, std::string> * params)
@@ -1614,8 +1436,6 @@ namespace client
 		RunnableService ("Destination"),
 		ClientDestination (GetIOService (), keys, isPublic, params)
 	{
-		if (!GetNickname ().empty ())
-			RunnableService::SetName (GetNickname ());
 	}
 
 	RunnableClientDestination::~RunnableClientDestination ()
diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h
index 35557859..3b395f4d 100644
--- a/libi2pd/Destination.h
+++ b/libi2pd/Destination.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -15,14 +15,13 @@
 #include <memory>
 #include <map>
 #include <unordered_map>
-#include <unordered_set>
+#include <set>
 #include <string>
 #include <functional>
 #include <boost/asio.hpp>
 #include "Identity.h"
 #include "TunnelPool.h"
 #include "Crypto.h"
-#include "CryptoKey.h"
 #include "LeaseSet.h"
 #include "Garlic.h"
 #include "NetDb.hpp"
@@ -37,15 +36,13 @@ namespace client
 	const uint8_t PROTOCOL_TYPE_STREAMING = 6;
 	const uint8_t PROTOCOL_TYPE_DATAGRAM = 17;
 	const uint8_t PROTOCOL_TYPE_RAW = 18;
-	const int PUBLISH_CONFIRMATION_TIMEOUT = 1800; // in milliseconds
-	const int PUBLISH_VERIFICATION_TIMEOUT = 5; // in seconds after successful publish
-	const int PUBLISH_VERIFICATION_TIMEOUT_VARIANCE = 3; // in seconds
+	const int PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds
+	const int PUBLISH_VERIFICATION_TIMEOUT = 10; // in seconds after successful publish
 	const int PUBLISH_MIN_INTERVAL = 20; // in seconds
 	const int PUBLISH_REGULAR_VERIFICATION_INTERNAL = 100; // in seconds periodically
-	const int LEASESET_REQUEST_TIMEOUT = 1600; // in milliseconds
-	const int MAX_LEASESET_REQUEST_TIMEOUT = 12000; // in milliseconds
-	const int DESTINATION_CLEANUP_TIMEOUT = 44; // in seconds
-	const int DESTINATION_CLEANUP_TIMEOUT_VARIANCE = 30; // in seconds
+	const int LEASESET_REQUEST_TIMEOUT = 5; // in seconds
+	const int MAX_LEASESET_REQUEST_TIMEOUT = 40; // in seconds
+	const int DESTINATION_CLEANUP_TIMEOUT = 3; // in minutes
 	const unsigned int MAX_NUM_FLOODFILLS_PER_REQUEST = 7;
 
 	// I2CP
@@ -87,19 +84,9 @@ namespace client
 	// streaming
 	const char I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY[] = "i2p.streaming.initialAckDelay";
 	const int DEFAULT_INITIAL_ACK_DELAY = 200; // milliseconds
-	const char I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED[] = "i2p.streaming.maxOutboundSpeed"; // bytes/sec
-	const int DEFAULT_MAX_OUTBOUND_SPEED = 1730000000; // no more than 1.73 Gbytes/s
-	const char I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED[] = "i2p.streaming.maxInboundSpeed"; // bytes/sec
-	const int DEFAULT_MAX_INBOUND_SPEED = 1730000000; // no more than 1.73 Gbytes/s
 	const char I2CP_PARAM_STREAMING_ANSWER_PINGS[] = "i2p.streaming.answerPings";
 	const int DEFAULT_ANSWER_PINGS = true;
-	const char I2CP_PARAM_STREAMING_PROFILE[] = "i2p.streaming.profile";
-	const int STREAMING_PROFILE_BULK = 1; // high bandwidth
-	const int STREAMING_PROFILE_INTERACTIVE = 2; // low bandwidth
-	const int DEFAULT_STREAMING_PROFILE = STREAMING_PROFILE_BULK;
-	const char I2CP_PARAM_STREAMING_MAX_CONCURRENT_STREAMS[] = "i2p.streaming.maxConcurrentStreams";
-	const int DEFAULT_MAX_CONCURRENT_STREAMS = 2048;
-	
+
 	typedef std::function<void (std::shared_ptr<i2p::stream::Stream> stream)> StreamRequestComplete;
 
 	class LeaseSetDestination: public i2p::garlic::GarlicDestination,
@@ -109,8 +96,8 @@ namespace client
 		// leaseSet = nullptr means not found
 		struct LeaseSetRequest
 		{
-			LeaseSetRequest (boost::asio::io_context& service): requestTime (0), requestTimeoutTimer (service) {};
-			std::unordered_set<i2p::data::IdentHash> excluded;
+			LeaseSetRequest (boost::asio::io_service& service): requestTime (0), requestTimeoutTimer (service) {};
+			std::set<i2p::data::IdentHash> excluded;
 			uint64_t requestTime;
 			boost::asio::deadline_timer requestTimeoutTimer;
 			std::list<RequestComplete> requestComplete;
@@ -127,10 +114,10 @@ namespace client
 
 		public:
 
-			LeaseSetDestination (boost::asio::io_context& service, bool isPublic, const std::map<std::string, std::string> * params = nullptr);
+			LeaseSetDestination (boost::asio::io_service& service, bool isPublic, const std::map<std::string, std::string> * params = nullptr);
 			~LeaseSetDestination ();
 			const std::string& GetNickname () const { return m_Nickname; };
-			auto& GetService () { return m_Service; };
+			boost::asio::io_service& GetService () { return m_Service; };
 
 			virtual void Start ();
 			virtual void Stop ();
@@ -147,15 +134,15 @@ namespace client
 			void CancelDestinationRequestWithEncryptedLeaseSet (std::shared_ptr<const i2p::data::BlindedPublicKey> dest, bool notify = true);
 
 			// implements GarlicDestination
-			std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet () override;
-			std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const override { return m_Pool; }
+			std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet ();
+			std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const { return m_Pool; }
 
 			// override GarlicDestination
-			bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag) override;
-			void SubmitECIESx25519Key (const uint8_t * key, uint64_t tag) override;
-			void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg) override;
-			void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg) override;
-			void SetLeaseSetUpdated (bool post) override;
+			bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag);
+			void SubmitECIESx25519Key (const uint8_t * key, uint64_t tag);
+			void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
+			void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
+			void SetLeaseSetUpdated ();
 
 			bool IsPublic () const { return m_IsPublic; };
 			void SetPublic (bool pub) { m_IsPublic = pub; };
@@ -163,20 +150,18 @@ namespace client
 		protected:
 
 			// implements GarlicDestination
-			void HandleI2NPMessage (const uint8_t * buf, size_t len) override;
-			bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, 
-				size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from) override;
+			void HandleI2NPMessage (const uint8_t * buf, size_t len);
+			bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID);
 
 			void SetLeaseSet (std::shared_ptr<const i2p::data::LocalLeaseSet> newLeaseSet);
 			int GetLeaseSetType () const { return m_LeaseSetType; };
 			void SetLeaseSetType (int leaseSetType) { m_LeaseSetType = leaseSetType; };
 			int GetAuthType () const { return m_AuthType; };
 			virtual void CleanupDestination () {}; // additional clean up in derived classes
-			virtual i2p::data::CryptoKeyType GetPreferredCryptoType () const = 0;
 			// I2CP
 			virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0;
 			virtual void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels) = 0;
-			
+
 		private:
 
 			void UpdateLeaseSet ();
@@ -185,34 +170,31 @@ namespace client
 			void HandlePublishConfirmationTimer (const boost::system::error_code& ecode);
 			void HandlePublishVerificationTimer (const boost::system::error_code& ecode);
 			void HandlePublishDelayTimer (const boost::system::error_code& ecode);
-			void HandleDatabaseStoreMessage (const uint8_t * buf, size_t len, i2p::garlic::ECIESX25519AEADRatchetSession * from);
+			void HandleDatabaseStoreMessage (const uint8_t * buf, size_t len);
 			void HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len);
 			void HandleDeliveryStatusMessage (uint32_t msgID);
 
 			void RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete, std::shared_ptr<const i2p::data::BlindedPublicKey> requestedBlindedKey = nullptr);
 			bool SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr<const i2p::data::RouterInfo> nextFloodfill, std::shared_ptr<LeaseSetRequest> request);
-			void SendNextLeaseSetRequest (const i2p::data::IdentHash& key, std::shared_ptr<LeaseSetRequest> request);
 			void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest);
 			void HandleCleanupTimer (const boost::system::error_code& ecode);
 			void CleanupRemoteLeaseSets ();
+			i2p::data::CryptoKeyType GetPreferredCryptoType () const;
 
 		private:
 
-			boost::asio::io_context& m_Service;
+			boost::asio::io_service& m_Service;
 			mutable std::mutex m_RemoteLeaseSetsMutex;
 			std::unordered_map<i2p::data::IdentHash, std::shared_ptr<i2p::data::LeaseSet> > m_RemoteLeaseSets;
 			std::unordered_map<i2p::data::IdentHash, std::shared_ptr<LeaseSetRequest> > m_LeaseSetRequests;
 
-			std::list<std::shared_ptr<I2NPMessage> > m_IncomingMsgsQueue;
-			mutable std::mutex m_IncomingMsgsQueueMutex;
-			
 			std::shared_ptr<i2p::tunnel::TunnelPool> m_Pool;
 			std::mutex m_LeaseSetMutex;
 			std::shared_ptr<const i2p::data::LocalLeaseSet> m_LeaseSet;
 			bool m_IsPublic;
 			uint32_t m_PublishReplyToken;
 			uint64_t m_LastSubmissionTime; // in seconds
-			std::unordered_set<i2p::data::IdentHash> m_ExcludedFloodfills; // for publishing
+			std::set<i2p::data::IdentHash> m_ExcludedFloodfills; // for publishing
 
 			boost::asio::deadline_timer m_PublishConfirmationTimer, m_PublishVerificationTimer,
 				m_PublishDelayTimer, m_CleanupTimer;
@@ -231,14 +213,25 @@ namespace client
 
 	class ClientDestination: public LeaseSetDestination
 	{
+		struct EncryptionKey
+		{
+			uint8_t pub[256], priv[256];
+			i2p::data::CryptoKeyType keyType;
+			std::shared_ptr<i2p::crypto::CryptoKeyDecryptor> decryptor;
+
+			EncryptionKey (i2p::data::CryptoKeyType t):keyType(t) { memset (pub, 0, 256); memset (priv, 0, 256);	};
+			void GenerateKeys () { i2p::data::PrivateKeys::GenerateCryptoKeyPair (keyType, priv, pub); };
+			void CreateDecryptor () { decryptor = i2p::data::PrivateKeys::CreateDecryptor (keyType, priv); };
+		};
+
 		public:
 
-			ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys,
+			ClientDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys,
 				bool isPublic, const std::map<std::string, std::string> * params = nullptr);
 			~ClientDestination ();
 
-			void Start () override;
-			void Stop () override;
+			void Start ();
+			void Stop ();
 
 			const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; };
 			void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); };
@@ -265,9 +258,6 @@ namespace client
 			bool IsAcceptingStreams () const;
 			void AcceptOnce (const i2p::stream::StreamingDestination::Acceptor& acceptor);
 			int GetStreamingAckDelay () const { return m_StreamingAckDelay; }
-			int GetStreamingOutboundSpeed () const { return m_StreamingOutboundSpeed; }
-			int GetStreamingInboundSpeed () const { return m_StreamingInboundSpeed; }
-			int GetStreamingMaxConcurrentStreams () const { return m_StreamingMaxConcurrentStreams; }
 			bool IsStreamingAnswerPings () const { return m_IsStreamingAnswerPings; }
 
 			// datagram
@@ -275,28 +265,24 @@ namespace client
 			i2p::datagram::DatagramDestination * CreateDatagramDestination (bool gzip = true);
 
 			// implements LocalDestination
-			bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const override;
-			std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const override { return m_Keys.GetPublic (); };
-			bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const override;
-			const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const override;
+			bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const;
+			std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const { return m_Keys.GetPublic (); };
+			bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const;
+			const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const;
 
 		protected:
 
-			// GarlicDestionation
-			i2p::data::CryptoKeyType GetRatchetsHighestCryptoType () const override;
-			// LeaseSetDestination
-			void CleanupDestination () override;
-			i2p::data::CryptoKeyType GetPreferredCryptoType () const override { return m_PreferredCryptoType; }
+			void CleanupDestination ();
 			// I2CP
-			void HandleDataMessage (const uint8_t * buf, size_t len) override;
-			void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels) override;
-						
+			void HandleDataMessage (const uint8_t * buf, size_t len);
+			void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels);
+
 		private:
 
 			std::shared_ptr<ClientDestination> GetSharedFromThis () {
 				return std::static_pointer_cast<ClientDestination>(shared_from_this ());
 			}
-			void PersistTemporaryKeys (std::shared_ptr<i2p::crypto::LocalEncryptionKey> keys);
+			void PersistTemporaryKeys (EncryptionKey * keys, bool isSingleKey);
 			void ReadAuthKey (const std::string& group, const std::map<std::string, std::string> * params);
 
 			template<typename Dest>
@@ -305,17 +291,16 @@ namespace client
 		private:
 
 			i2p::data::PrivateKeys m_Keys;
-			std::map<i2p::data::CryptoKeyType, std::shared_ptr<i2p::crypto::LocalEncryptionKey> > m_EncryptionKeys; // last is most preferable
-			i2p::data::CryptoKeyType m_PreferredCryptoType;
-			
-			int m_StreamingAckDelay,m_StreamingOutboundSpeed, m_StreamingInboundSpeed, m_StreamingMaxConcurrentStreams;
+			std::unique_ptr<EncryptionKey> m_StandardEncryptionKey;
+			std::unique_ptr<EncryptionKey> m_ECIESx25519EncryptionKey;
+
+			int m_StreamingAckDelay;
 			bool m_IsStreamingAnswerPings;
 			std::shared_ptr<i2p::stream::StreamingDestination> m_StreamingDestination; // default
 			std::map<uint16_t, std::shared_ptr<i2p::stream::StreamingDestination> > m_StreamingDestinationsByPorts;
 			std::shared_ptr<i2p::stream::StreamingDestination> m_LastStreamingDestination; uint16_t m_LastPort; // for server tunnels
 			i2p::datagram::DatagramDestination * m_DatagramDestination;
 			int m_RefCounter; // how many clients(tunnels) use this destination
-			uint64_t m_LastPublishedTimestamp;
 
 			boost::asio::deadline_timer m_ReadyChecker;
 
diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp
index 08af4be3..3b3ed485 100644
--- a/libi2pd/ECIESX25519AEADRatchetSession.cpp
+++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -11,7 +11,6 @@
 #include "Log.h"
 #include "util.h"
 #include "Crypto.h"
-#include "PostQuantum.h"
 #include "Elligator.h"
 #include "Tag.h"
 #include "I2PEndian.h"
@@ -95,17 +94,6 @@ namespace garlic
 		m_ItermediateSymmKeys.erase (index);
 	}
 
-	ReceiveRatchetTagSet::ReceiveRatchetTagSet (std::shared_ptr<ECIESX25519AEADRatchetSession> session, bool isNS):
-		m_Session (session), m_IsNS (isNS) 
-	{
-	}
-		
-	ReceiveRatchetTagSet::~ReceiveRatchetTagSet ()
-	{
-		if (m_IsNS && m_Session)
-			m_Session->CleanupReceiveNSRKeys ();
-	}	
-	
 	void ReceiveRatchetTagSet::Expire ()
 	{
 		if (!m_ExpirationTimestamp)
@@ -129,12 +117,6 @@ namespace garlic
 		return session->HandleNextMessage (buf, len, shared_from_this (), index);
 	}
 
-	bool ReceiveRatchetTagSet::IsSessionTerminated () const 
-	{ 
-		return !m_Session || m_Session->IsTerminated (); 
-	}
-
-	
 	SymmetricKeyTagSet::SymmetricKeyTagSet (GarlicDestination * destination, const uint8_t * key):
 		ReceiveRatchetTagSet (nullptr), m_Destination (destination)
 	{
@@ -174,12 +156,12 @@ namespace garlic
 			return false;
 		}
 		if (m_Destination)
-			m_Destination->HandleECIESx25519GarlicClove (buf + offset, size, nullptr);
+			m_Destination->HandleECIESx25519GarlicClove (buf + offset, size);
 		return true;
 	}
 
 	ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSetNS):
-		GarlicRoutingSession (owner, true), m_RemoteStaticKeyType (0)
+		GarlicRoutingSession (owner, true)
 	{
 		if (!attachLeaseSetNS) SetLeaseSetUpdateStatus (eLeaseSetUpToDate);
 		RAND_bytes (m_PaddingSizes, 32); m_NextPaddingSize = 0;
@@ -241,104 +223,33 @@ namespace garlic
 		tagsetNsr->NextSessionTagRatchet ();
 	}
 
-	bool ECIESX25519AEADRatchetSession::MessageConfirmed (uint32_t msgID)
-	{
-		auto ret = GarlicRoutingSession::MessageConfirmed (msgID); // LeaseSet
-		if (m_AckRequestMsgID && m_AckRequestMsgID == msgID)
-		{
-			m_AckRequestMsgID = 0;
-			m_AckRequestNumAttempts = 0;
-			ret = true;
-		}	
-		return ret;
-	}	
-
-	bool ECIESX25519AEADRatchetSession::CleanupUnconfirmedTags ()
-	{
-		if (m_AckRequestMsgID && m_AckRequestNumAttempts > ECIESX25519_ACK_REQUEST_MAX_NUM_ATTEMPTS)
-		{
-			m_AckRequestMsgID = 0;
-			m_AckRequestNumAttempts = 0;
-			return true;	
-		}
-		return false;
-	}	
-
-	void ECIESX25519AEADRatchetSession::CleanupReceiveNSRKeys ()
-	{
-		m_EphemeralKeys = nullptr;
-#if OPENSSL_PQ
-		m_PQKeys = nullptr;
-#endif	
-	}	
-		
 	bool ECIESX25519AEADRatchetSession::HandleNewIncomingSession (const uint8_t * buf, size_t len)
 	{
 		if (!GetOwner ()) return false;
 		// we are Bob
 		// KDF1
-		
+		i2p::crypto::InitNoiseIKState (GetNoiseState (), GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)); // bpk
+
 		if (!i2p::crypto::GetElligator ()->Decode (buf, m_Aepk))
 		{
 			LogPrint (eLogError, "Garlic: Can't decode elligator");
 			return false;
 		}
 		buf += 32; len -= 32;
+		MixHash (m_Aepk, 32); // h = SHA256(h || aepk)
 
 		uint8_t sharedSecret[32];
-		bool decrypted = false;
-		auto cryptoType = GetOwner ()->GetRatchetsHighestCryptoType ();
-#if OPENSSL_PQ
-		if (cryptoType > i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // we support post quantum
+		if (!GetOwner ()->Decrypt (m_Aepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk)
 		{
-			i2p::crypto::InitNoiseIKStateMLKEM (GetNoiseState (), cryptoType, GetOwner ()->GetEncryptionPublicKey (cryptoType)); // bpk
-			MixHash (m_Aepk, 32); // h = SHA256(h || aepk)
-
-			if (GetOwner ()->Decrypt (m_Aepk, sharedSecret, cryptoType)) // x25519(bsk, aepk)
-			{
-				MixKey (sharedSecret);
-
-				auto keyLen = i2p::crypto::GetMLKEMPublicKeyLen (cryptoType);
-				std::vector<uint8_t> encapsKey(keyLen);
-				if (Decrypt (buf, encapsKey.data (), keyLen))
-				{
-					decrypted = true; // encaps section has right hash 
-					MixHash (buf, keyLen + 16);
-					buf += keyLen + 16;
-					len -= keyLen + 16;
-					
-					m_PQKeys = i2p::crypto::CreateMLKEMKeys (cryptoType);
-					m_PQKeys->SetPublicKey (encapsKey.data ());
-				}
-			}	
-		}	
-#endif
-		if (!decrypted)
-		{	
-			if (cryptoType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
-			    GetOwner ()->SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
-			{
-				cryptoType = i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD;
-				i2p::crypto::InitNoiseIKState (GetNoiseState (), GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)); // bpk
-				MixHash (m_Aepk, 32); // h = SHA256(h || aepk)
-
-				if (!GetOwner ()->Decrypt (m_Aepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk)
-				{
-					LogPrint (eLogWarning, "Garlic: Incorrect Alice ephemeral key");
-					return false;
-				}
-				MixKey (sharedSecret);	
-			}	
-			else
-			{
-				LogPrint (eLogWarning, "Garlic: No supported encryption type");
-				return false;
-			}	
-		}	
+			LogPrint (eLogWarning, "Garlic: Incorrect Alice ephemeral key");
+			return false;
+		}
+		MixKey (sharedSecret);
 
 		// decrypt flags/static
-		uint8_t fs[32];
-		if (!Decrypt (buf, fs, 32))
+		uint8_t nonce[12], fs[32];
+		CreateNonce (0, nonce);
+		if (!i2p::crypto::AEADChaCha20Poly1305 (buf, 32, m_H, 32, m_CK + 32, nonce, fs, 32, false)) // decrypt
 		{
 			LogPrint (eLogWarning, "Garlic: Flags/static section AEAD verification failed ");
 			return false;
@@ -350,19 +261,21 @@ namespace garlic
 		bool isStatic = !i2p::data::Tag<32> (fs).IsZero ();
 		if (isStatic)
 		{
-			// static key, fs is apk	
-			SetRemoteStaticKey (cryptoType, fs); 
-			if (!GetOwner ()->Decrypt (fs, sharedSecret, m_RemoteStaticKeyType)) // x25519(bsk, apk)
+			// static key, fs is apk
+			memcpy (m_RemoteStaticKey, fs, 32);
+			if (!GetOwner ()->Decrypt (fs, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, apk)
 			{
 				LogPrint (eLogWarning, "Garlic: Incorrect Alice static key");
 				return false;
 			}
 			MixKey (sharedSecret);
 		}
+		else // all zeros flags
+			CreateNonce (1, nonce);
 
 		// decrypt payload
 		std::vector<uint8_t> payload (len - 16); // we must save original ciphertext
-		if (!Decrypt (buf, payload.data (), len - 16))
+		if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, m_CK + 32, nonce, payload.data (), len - 16, false)) // decrypt
 		{
 			LogPrint (eLogWarning, "Garlic: Payload section AEAD verification failed");
 			return false;
@@ -398,7 +311,7 @@ namespace garlic
 			{
 				case eECIESx25519BlkGalicClove:
 					if (GetOwner ())
-						GetOwner ()->HandleECIESx25519GarlicClove (buf + offset, size, this);
+						GetOwner ()->HandleECIESx25519GarlicClove (buf + offset, size);
 				break;
 				case eECIESx25519BlkNextKey:
 					LogPrint (eLogDebug, "Garlic: Next key");
@@ -414,9 +327,8 @@ namespace garlic
 					auto offset1 = offset;
 					for (auto i = 0; i < numAcks; i++)
 					{
-						uint32_t tagsetid = bufbe16toh (buf + offset1); offset1 += 2; // tagsetid
-						uint16_t n = bufbe16toh (buf + offset1); offset1 += 2; // N
-						MessageConfirmed ((tagsetid << 16) + n); // msgid = (tagsetid << 16) + N
+						offset1 += 2; // tagsetid
+						MessageConfirmed (bufbe16toh (buf + offset1)); offset1 += 2; // N
 					}
 					break;
 				}
@@ -479,6 +391,7 @@ namespace garlic
 		{
 			uint16_t keyID = bufbe16toh (buf); buf += 2; // keyID
 			bool newKey = flag & ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG;
+			m_SendReverseKey = true;
 			if (!m_NextReceiveRatchet)
 				m_NextReceiveRatchet.reset (new DHRatchet ());
 			else
@@ -490,14 +403,15 @@ namespace garlic
 				}
 				m_NextReceiveRatchet->keyID = keyID;
 			}
+			int tagsetID = 2*keyID;
 			if (newKey)
 			{
 				m_NextReceiveRatchet->key = i2p::transport::transports.GetNextX25519KeysPair ();
 				m_NextReceiveRatchet->newKey = true;
+				tagsetID++;
 			}
 			else
 				m_NextReceiveRatchet->newKey = false;
-			auto tagsetID = m_NextReceiveRatchet->GetReceiveTagSetID ();
 			if (flag & ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG)
 				memcpy (m_NextReceiveRatchet->remote, buf, 32);
 
@@ -511,9 +425,7 @@ namespace garlic
 			GenerateMoreReceiveTags (newTagset, (GetOwner () && GetOwner ()->GetNumRatchetInboundTags () > 0) ?
 				GetOwner ()->GetNumRatchetInboundTags () : ECIESX25519_MAX_NUM_GENERATED_TAGS);
 			receiveTagset->Expire ();
-			
 			LogPrint (eLogDebug, "Garlic: Next receive tagset ", tagsetID, " created");
-			m_SendReverseKey = true;
 		}
 	}
 
@@ -550,16 +462,7 @@ namespace garlic
 		offset += 32;
 
 		// KDF1
-#if OPENSSL_PQ		
-		if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD)
-		{
-			i2p::crypto::InitNoiseIKStateMLKEM (GetNoiseState (), m_RemoteStaticKeyType, m_RemoteStaticKey); // bpk
-			m_PQKeys = i2p::crypto::CreateMLKEMKeys (m_RemoteStaticKeyType);
-			m_PQKeys->GenerateKeys ();
-		}	
-		else	
-#endif			
-			i2p::crypto::InitNoiseIKState (GetNoiseState (), m_RemoteStaticKey); // bpk
+		i2p::crypto::InitNoiseIKState (GetNoiseState (), m_RemoteStaticKey); // bpk
 		MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || aepk)
 		uint8_t sharedSecret[32];
 		if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // x25519(aesk, bpk)
@@ -568,32 +471,18 @@ namespace garlic
 			return false;
 		}
 		MixKey (sharedSecret);
-#if OPENSSL_PQ
-		if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD)
-		{
-			auto keyLen = i2p::crypto::GetMLKEMPublicKeyLen (m_RemoteStaticKeyType);
-			std::vector<uint8_t> encapsKey(keyLen);
-			m_PQKeys->GetPublicKey (encapsKey.data ());
-			// encrypt encapsKey 
-			if (!Encrypt (encapsKey.data (), out + offset, keyLen))
-			{
-				LogPrint (eLogWarning, "Garlic: ML-KEM encap_key section AEAD encryption failed ");
-				return false;
-			}
-			MixHash (out + offset, keyLen + 16); // h = SHA256(h || ciphertext)
-			offset += keyLen + 16;
-		}	
-#endif		
 		// encrypt flags/static key section
+		uint8_t nonce[12];
+		CreateNonce (0, nonce);
 		const uint8_t * fs;
 		if (isStatic)
-			fs = GetOwner ()->GetEncryptionPublicKey (m_RemoteStaticKeyType);
+			fs = GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD);
 		else
 		{
 			memset (out + offset, 0, 32); // all zeros flags section
 			fs = out + offset;
 		}
-		if (!Encrypt (fs, out + offset, 32)) 
+		if (!i2p::crypto::AEADChaCha20Poly1305 (fs, 32, m_H, 32, m_CK + 32, nonce, out + offset, 48, true)) // encrypt
 		{
 			LogPrint (eLogWarning, "Garlic: Flags/static section AEAD encryption failed ");
 			return false;
@@ -604,11 +493,13 @@ namespace garlic
 		// KDF2
 		if (isStatic)
 		{
-			GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, m_RemoteStaticKeyType); // x25519 (ask, bpk)
-			MixKey (sharedSecret);	
+			GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bpk)
+			MixKey (sharedSecret);
 		}
+		else
+			CreateNonce (1, nonce);
 		// encrypt payload
-		if (!Encrypt (payload, out + offset, len))
+		if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt
 		{
 			LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed");
 			return false;
@@ -646,7 +537,7 @@ namespace garlic
 		}
 		memcpy (m_NSREncodedKey, out + offset, 32); // for possible next NSR
 		memcpy (m_NSRH, m_H, 32);
-		offset += 32;	
+		offset += 32;
 		// KDF for Reply Key Section
 		MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag)
 		MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || bepk)
@@ -657,33 +548,16 @@ namespace garlic
 			return false;
 		}
 		MixKey (sharedSecret);
-#if OPENSSL_PQ
-		if (m_PQKeys)
-		{
-			size_t cipherTextLen = i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType);
-			std::vector<uint8_t> kemCiphertext(cipherTextLen);
-			m_PQKeys->Encaps (kemCiphertext.data (), sharedSecret);
-			
-			if (!Encrypt (kemCiphertext.data (), out + offset, cipherTextLen))
-			{
-				LogPrint (eLogWarning, "Garlic: NSR ML-KEM ciphertext section AEAD encryption failed");
-				return false;
-			}
-			m_NSREncodedPQKey = std::make_unique<std::vector<uint8_t> > (cipherTextLen + 16);
-			memcpy (m_NSREncodedPQKey->data (), out + offset, cipherTextLen + 16);
-			MixHash (out + offset, cipherTextLen + 16);
-			MixKey (sharedSecret);
-			offset += cipherTextLen + 16;
-		}	
-#endif		
 		if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // sharedSecret = x25519(besk, apk)
 		{
 			LogPrint (eLogWarning, "Garlic: Incorrect Alice static key");
 			return false;
 		}
 		MixKey (sharedSecret);
+		uint8_t nonce[12];
+		CreateNonce (0, nonce);
 		// calculate hash for zero length
-		if (!Encrypt (sharedSecret /* can be anything */, out + offset, 0)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad)
+		if (!i2p::crypto::AEADChaCha20Poly1305 (nonce /* can be anything */, 0, m_H, 32, m_CK + 32, nonce, out + offset, 16, true)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad)
 		{
 			LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed");
 			return false;
@@ -704,7 +578,6 @@ namespace garlic
 			GetOwner ()->GetNumRatchetInboundTags () : ECIESX25519_MIN_NUM_GENERATED_TAGS);
 		i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", m_NSRKey, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32)
 		// encrypt payload
-		uint8_t nonce[12]; memset (nonce, 0, 12); // seqn = 0
 		if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + offset, len + 16, true)) // encrypt
 		{
 			LogPrint (eLogWarning, "Garlic: NSR payload section AEAD encryption failed");
@@ -726,34 +599,16 @@ namespace garlic
 		memcpy (m_H, m_NSRH, 32);
 		MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag)
 		MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || bepk)
-		m_N = 0;
-		size_t offset = 40;
-#if OPENSSL_PQ		
-		if (m_PQKeys)
-		{	
-			if (m_NSREncodedPQKey)
-			{	
-				size_t cipherTextLen = i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType);
-				memcpy (out + offset, m_NSREncodedPQKey->data (), cipherTextLen + 16);
-				MixHash (out + offset, cipherTextLen + 16);
-				offset += cipherTextLen + 16;
-			}
-			else
-			{
-				LogPrint (eLogWarning, "Garlic: No stored ML-KEM keys");
-				return false;
-			}
-		}
-#endif
-		if (!Encrypt (m_NSRH /* can be anything */, out + offset, 0)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad)
+		uint8_t nonce[12];
+		CreateNonce (0, nonce);
+		if (!i2p::crypto::AEADChaCha20Poly1305 (nonce /* can be anything */, 0, m_H, 32, m_CK + 32, nonce, out + 40, 16, true)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad)
 		{
 			LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed");
 			return false;
 		}
-		MixHash (out + offset, 16); // h = SHA256(h || ciphertext)
+		MixHash (out + 40, 16); // h = SHA256(h || ciphertext)
 		// encrypt payload
-		uint8_t nonce[12]; memset (nonce, 0, 12);
-		if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + offset + 16, len + 16, true)) // encrypt
+		if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + 56, len + 16, true)) // encrypt
 		{
 			LogPrint (eLogWarning, "Garlic: Next NSR payload section AEAD encryption failed");
 			return false;
@@ -785,30 +640,13 @@ namespace garlic
 			return false;
 		}
 		MixKey (sharedSecret);
-#if OPENSSL_PQ
-		if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD)
-		{
-			// decrypt kem_ciphertext section
-			size_t cipherTextLen = i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType);
-			std::vector<uint8_t> kemCiphertext(cipherTextLen);
-			if (!Decrypt (buf, kemCiphertext.data (), cipherTextLen))
-			{
-				LogPrint (eLogWarning, "Garlic: Reply ML-KEM ciphertext section AEAD decryption failed");
-				return false;
-			}
-			MixHash (buf, cipherTextLen + 16);
-			buf += cipherTextLen + 16;
-			len -= cipherTextLen + 16;
-			// decaps
-			m_PQKeys->Decaps (kemCiphertext.data (), sharedSecret);
-			MixKey (sharedSecret);
-		}
-#endif	
-		GetOwner ()->Decrypt (bepk, sharedSecret, m_RemoteStaticKeyType); // x25519 (ask, bepk)
+		GetOwner ()->Decrypt (bepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bepk)
 		MixKey (sharedSecret);
-		
+
+		uint8_t nonce[12];
+		CreateNonce (0, nonce);
 		// calculate hash for zero length
-		if (!Decrypt (buf, sharedSecret/* can be anything */, 0)) // decrypt, DECRYPT(k, n, ZEROLEN, ad) verification only
+		if (!i2p::crypto::AEADChaCha20Poly1305 (buf, 0, m_H, 32, m_CK + 32, nonce, sharedSecret/* can be anything */, 0, false)) // decrypt, DECRYPT(k, n, ZEROLEN, ad) verification only
 		{
 			LogPrint (eLogWarning, "Garlic: Reply key section AEAD decryption failed");
 			return false;
@@ -833,7 +671,6 @@ namespace garlic
 		}
 		i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", keydata, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32)
 		// decrypt payload
-		uint8_t nonce[12]; memset (nonce, 0, 12); // seqn = 0
 		if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, keydata, nonce, buf, len - 16, false)) // decrypt
 		{
 			LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed");
@@ -843,8 +680,7 @@ namespace garlic
 		if (m_State == eSessionStateNewSessionSent)
 		{
 			m_State = eSessionStateEstablished;
-			// don't delete m_EpehemralKey and m_PQKeys because delayd NSR's migth come
-			// done in CleanupReceiveNSRKeys called from NSR tagset destructor		
+			//m_EphemeralKeys = nullptr; // TODO: delete after a while
 			m_SessionCreatedTimestamp = i2p::util::GetSecondsSinceEpoch ();
 			GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ());
 		}
@@ -859,8 +695,6 @@ namespace garlic
 
 	bool ECIESX25519AEADRatchetSession::NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen)
 	{
-		auto owner = GetOwner ();
-		if (!owner) return false;
 		uint8_t nonce[12];
 		auto index = m_SendTagset->GetNextIndex ();
 		CreateNonce (index, nonce); // tag's index
@@ -868,7 +702,8 @@ namespace garlic
 		if (!tag)
 		{
 			LogPrint (eLogError, "Garlic: Can't create new ECIES-X25519-AEAD-Ratchet tag for send tagset");
-			owner->RemoveECIESx25519Session (m_RemoteStaticKey);
+			if (GetOwner ())
+				GetOwner ()->RemoveECIESx25519Session (m_RemoteStaticKey);
 			return false;
 		}
 		memcpy (out, &tag, 8);
@@ -876,7 +711,7 @@ namespace garlic
 		// ciphertext = ENCRYPT(k, n, payload, ad)
 		uint8_t key[32];
 		m_SendTagset->GetSymmKey (index, key);
-		if (!owner->AEADChaCha20Poly1305Encrypt (payload, len, out, 8, key, nonce, out + 8, outLen - 8))
+		if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, out, 8, key, nonce, out + 8, outLen - 8, true)) // encrypt
 		{
 			LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed");
 			return false;
@@ -895,35 +730,33 @@ namespace garlic
 		uint8_t * payload = buf + 8;
 		uint8_t key[32];
 		receiveTagset->GetSymmKey (index, key);
-		auto owner = GetOwner ();
-		if (!owner) return true; // drop message
-		
-		if (!owner->AEADChaCha20Poly1305Decrypt (payload, len - 16, buf, 8, key, nonce, payload, len - 16))
+		if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 16, buf, 8, key, nonce, payload, len - 16, false)) // decrypt
 		{
 			LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed");
 			return false;
 		}
 		HandlePayload (payload, len - 16, receiveTagset, index);
-		
-		int moreTags = 0;
-		if (owner->GetNumRatchetInboundTags () > 0) // override in settings?
+		if (GetOwner ())
 		{
-			if (receiveTagset->GetNextIndex () - index < owner->GetNumRatchetInboundTags ()/2)
-				moreTags = owner->GetNumRatchetInboundTags ();
-			index -= owner->GetNumRatchetInboundTags (); // trim behind
+			int moreTags = 0;
+			if (GetOwner ()->GetNumRatchetInboundTags () > 0) // override in settings?
+			{
+				if (receiveTagset->GetNextIndex () - index < GetOwner ()->GetNumRatchetInboundTags ()/2)
+					moreTags = GetOwner ()->GetNumRatchetInboundTags ();
+				index -= GetOwner ()->GetNumRatchetInboundTags (); // trim behind
+			}
+			else
+			{
+				moreTags = ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 2); // N/4
+				if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS;
+				moreTags -= (receiveTagset->GetNextIndex () - index);
+				index -= ECIESX25519_MAX_NUM_GENERATED_TAGS; // trim behind
+			}
+			if (moreTags > 0)
+				GenerateMoreReceiveTags (receiveTagset, moreTags);
+			if (index > 0)
+				receiveTagset->SetTrimBehind (index);
 		}
-		else
-		{
-			moreTags = (receiveTagset->GetTagSetID () > 0) ? ECIESX25519_MAX_NUM_GENERATED_TAGS : // for non first tagset
-				(ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 1)); // N/2
-			if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS;
-			moreTags -= (receiveTagset->GetNextIndex () - index);
-			index -= ECIESX25519_MAX_NUM_GENERATED_TAGS; // trim behind
-		}
-		if (moreTags > 0)
-			GenerateMoreReceiveTags (receiveTagset, moreTags);
-		if (index > 0)
-			receiveTagset->SetTrimBehind (index);
 		return true;
 	}
 
@@ -937,14 +770,10 @@ namespace garlic
 				m_State = eSessionStateEstablished;
 				m_NSRSendTagset = nullptr;
 				m_EphemeralKeys = nullptr;
-#if OPENSSL_PQ
-				m_PQKeys = nullptr;
-				m_NSREncodedPQKey = nullptr;
-#endif				
+#if (__cplusplus >= 201703L) // C++ 17 or higher
 				[[fallthrough]];
+#endif
 			case eSessionStateEstablished:
-				if (m_SendReverseKey && receiveTagset->GetTagSetID () == m_NextReceiveRatchet->GetReceiveTagSetID ())
-					m_SendReverseKey = false; // tag received on new tagset	
 				if (receiveTagset->IsNS ())
 				{
 					// our of sequence NSR
@@ -971,12 +800,7 @@ namespace garlic
 		if (!payload) return nullptr;
 		size_t len = CreatePayload (msg, m_State != eSessionStateEstablished, payload);
 		if (!len) return nullptr;
-#if OPENSSL_PQ
-		auto m = NewI2NPMessage (len + (m_State == eSessionStateEstablished ? 28 :
-			i2p::crypto::GetMLKEMPublicKeyLen (m_RemoteStaticKeyType) + 116));
-#else		
 		auto m = NewI2NPMessage (len + 100); // 96 + 4
-#endif		
 		m->Align (12); // in order to get buf aligned to 16 (12 + 4)
 		uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length
 
@@ -991,28 +815,16 @@ namespace garlic
 				if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen))
 					return nullptr;
 				len += 96;
-#if OPENSSL_PQ
-				if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD)
-					len += i2p::crypto::GetMLKEMPublicKeyLen (m_RemoteStaticKeyType) + 16;
-#endif				
 			break;
 			case eSessionStateNewSessionReceived:
 				if (!NewSessionReplyMessage (payload, len, buf, m->maxLen))
 					return nullptr;
 				len += 72;
-#if OPENSSL_PQ
-				if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD)
-					len += i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType) + 16;
-#endif				
 			break;
 			case eSessionStateNewSessionReplySent:
 				if (!NextNewSessionReplyMessage (payload, len, buf, m->maxLen))
 					return nullptr;
 				len += 72;
-#if OPENSSL_PQ
-				if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD)
-					len += i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType) + 16;
-#endif				
 			break;
 			case eSessionStateOneTime:
 				if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen, false))
@@ -1039,14 +851,13 @@ namespace garlic
 	{
 		uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
 		size_t payloadLen = 0;
-		bool sendAckRequest = false;
 		if (first) payloadLen += 7;// datatime
 		if (msg)
 		{
 			payloadLen += msg->GetPayloadLength () + 13;
 			if (m_Destination) payloadLen += 32;
 		}
-		if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASESET_CONFIRMATION_TIMEOUT)
+		if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT)
 		{
 			// resubmit non-confirmed LeaseSet
 			SetLeaseSetUpdateStatus (eLeaseSetUpdated);
@@ -1058,28 +869,13 @@ namespace garlic
 			payloadLen += leaseSet->GetBufferLen () + DATABASE_STORE_HEADER_SIZE + 13;
 			if (!first)
 			{
-				// ack request for LeaseSet
-				m_AckRequestMsgID = m_SendTagset->GetMsgID ();
-				sendAckRequest = true;
-				// update LeaseSet status
+				// ack request
 				SetLeaseSetUpdateStatus (eLeaseSetSubmitted);
-				SetLeaseSetUpdateMsgID (m_AckRequestMsgID);
+				SetLeaseSetUpdateMsgID (m_SendTagset->GetNextIndex ());
 				SetLeaseSetSubmissionTime (ts);
+				payloadLen += 4;
 			}
 		}
-		if (!sendAckRequest && !first &&
-		    ((!m_AckRequestMsgID && ts > m_LastAckRequestSendTime + m_AckRequestInterval) || // regular request
-		     (m_AckRequestMsgID && ts > m_LastAckRequestSendTime + LEASESET_CONFIRMATION_TIMEOUT))) // previous request failed. try again
-		{	
-			// not LeaseSet
-			m_AckRequestMsgID = m_SendTagset->GetMsgID ();
-			if (m_AckRequestMsgID)
-			{	
-				m_AckRequestNumAttempts++;
-				sendAckRequest = true;
-			}	
-		}	
-		if (sendAckRequest) payloadLen += 4;
 		if (m_AckRequests.size () > 0)
 			payloadLen += m_AckRequests.size ()*4 + 3;
 		if (m_SendReverseKey)
@@ -1131,15 +927,16 @@ namespace garlic
 			}
 			// LeaseSet
 			if (leaseSet)
-				offset += CreateLeaseSetClove (leaseSet, ts, payload + offset, payloadLen - offset);
-			// ack request
-			if (sendAckRequest)
 			{
-				payload[offset] = eECIESx25519BlkAckRequest; offset++;
-				htobe16buf (payload + offset, 1); offset += 2;
-				payload[offset] = 0; offset++; // flags
-				m_LastAckRequestSendTime = ts;
-			}	
+				offset += CreateLeaseSetClove (leaseSet, ts, payload + offset, payloadLen - offset);
+				if (!first)
+				{
+					// ack request
+					payload[offset] = eECIESx25519BlkAckRequest; offset++;
+					htobe16buf (payload + offset, 1); offset += 2;
+					payload[offset] = 0; offset++; // flags
+				}
+			}
 			// msg
 			if (msg)
 				offset += CreateGarlicClove (msg, payload + offset, payloadLen - offset);
@@ -1174,6 +971,7 @@ namespace garlic
 					memcpy (payload + offset, m_NextReceiveRatchet->key->GetPublicKey (), 32);
 					offset += 32; // public key
 				}
+				m_SendReverseKey = false;
 			}
 			if (m_SendForwardKey)
 			{
@@ -1269,8 +1067,6 @@ namespace garlic
 	bool ECIESX25519AEADRatchetSession::CheckExpired (uint64_t ts)
 	{
 		CleanupUnconfirmedLeaseSet (ts);
-		if (!m_Destination && ts > m_LastActivityTimestamp + ECIESX25519_SESSION_CREATE_TIMEOUT) return true; // m_LastActivityTimestamp is NS receive time 
-		if (m_State != eSessionStateEstablished && m_SessionCreatedTimestamp && ts > m_SessionCreatedTimestamp + ECIESX25519_SESSION_ESTABLISH_TIMEOUT) return true; 
 		return ts > m_LastActivityTimestamp + ECIESX25519_RECEIVE_EXPIRATION_TIMEOUT && // seconds
 			ts*1000 > m_LastSentTimestamp + ECIESX25519_SEND_EXPIRATION_TIMEOUT*1000; // milliseconds
 	}
@@ -1352,7 +1148,7 @@ namespace garlic
 		return len;
 	}
 
-	std::shared_ptr<I2NPMessage> WrapECIESX25519Message (std::shared_ptr<I2NPMessage> msg, const uint8_t * key, uint64_t tag)
+	std::shared_ptr<I2NPMessage> WrapECIESX25519Message (std::shared_ptr<const I2NPMessage> msg, const uint8_t * key, uint64_t tag)
 	{
 		auto m = NewI2NPMessage ((msg ? msg->GetPayloadLength () : 0) + 128);
 		m->Align (12); // in order to get buf aligned to 16 (12 + 4)
@@ -1372,16 +1168,10 @@ namespace garlic
 		htobe32buf (m->GetPayload (), offset);
 		m->len += offset + 4;
 		m->FillI2NPMessageHeader (eI2NPGarlic);
-		if (msg->onDrop)
-		{
-			// move onDrop to the wrapping I2NP messages
-			m->onDrop = msg->onDrop;
-			msg->onDrop = nullptr;
-		}
 		return m;
 	}
 
-	std::shared_ptr<I2NPMessage> WrapECIESX25519MessageForRouter (std::shared_ptr<I2NPMessage> msg, const uint8_t * routerPublicKey)
+	std::shared_ptr<I2NPMessage> WrapECIESX25519MessageForRouter (std::shared_ptr<const I2NPMessage> msg, const uint8_t * routerPublicKey)
 	{
 		// Noise_N, we are Alice, routerPublicKey is Bob's
 		i2p::crypto::NoiseSymmetricState noiseState;
@@ -1415,12 +1205,6 @@ namespace garlic
 		htobe32buf (m->GetPayload (), offset);
 		m->len += offset + 4;
 		m->FillI2NPMessageHeader (eI2NPGarlic);
-		if (msg->onDrop)
-		{
-			// move onDrop to the wrapping I2NP messages
-			m->onDrop = msg->onDrop;
-			msg->onDrop = nullptr;
-		}	
 		return m;
 	}
 }
diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h
index fd9cc45d..301f597a 100644
--- a/libi2pd/ECIESX25519AEADRatchetSession.h
+++ b/libi2pd/ECIESX25519AEADRatchetSession.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2021, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -14,12 +14,10 @@
 #include <functional>
 #include <memory>
 #include <vector>
-#include <array>
 #include <list>
 #include <unordered_map>
 #include "Identity.h"
 #include "Crypto.h"
-#include "PostQuantum.h"
 #include "Garlic.h"
 #include "Tag.h"
 
@@ -32,14 +30,10 @@ namespace garlic
 	const int ECIESX25519_SEND_INACTIVITY_TIMEOUT = 5000; // number of milliseconds we can send empty(pyaload only) packet after
 	const int ECIESX25519_SEND_EXPIRATION_TIMEOUT = 480; // in seconds
 	const int ECIESX25519_RECEIVE_EXPIRATION_TIMEOUT = 600; // in seconds
-	const int ECIESX25519_SESSION_CREATE_TIMEOUT = 3; // in seconds, NSR must be send after NS received
-	const int ECIESX25519_SESSION_ESTABLISH_TIMEOUT = 15; // in seconds 
-	const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // in seconds
-	const int ECIESX25519_DEFAULT_ACK_REQUEST_INTERVAL = 33000; // in milliseconds
-	const int ECIESX25519_ACK_REQUEST_MAX_NUM_ATTEMPTS = 3;
+	const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // 180
 	const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 8192; // number of tags we request new tagset after
 	const int ECIESX25519_MIN_NUM_GENERATED_TAGS = 24;
-	const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 800;
+	const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 320;
 	const int ECIESX25519_NSR_NUM_GENERATED_TAGS = 12;
 
 	const size_t ECIESX25519_OPTIMAL_PAYLOAD_SIZE = 1912; // 1912 = 1956 /* to fit 2 tunnel messages */
@@ -63,8 +57,6 @@ namespace garlic
 			int GetTagSetID () const { return m_TagSetID; };
 			void SetTagSetID (int tagsetID) { m_TagSetID = tagsetID; };
 
-			uint32_t GetMsgID () const { return (m_TagSetID << 16) + m_NextIndex; }; // (tagsetid << 16) + N
-			
 		private:
 
 			i2p::data::Tag<64> m_SessionTagKeyData;
@@ -81,8 +73,8 @@ namespace garlic
 	{
 		public:
 
-			ReceiveRatchetTagSet (std::shared_ptr<ECIESX25519AEADRatchetSession> session, bool isNS = false);
-			~ReceiveRatchetTagSet () override;
+			ReceiveRatchetTagSet (std::shared_ptr<ECIESX25519AEADRatchetSession> session, bool isNS = false):
+				m_Session (session), m_IsNS (isNS) {};
 
 			bool IsNS () const { return m_IsNS; };
 			std::shared_ptr<ECIESX25519AEADRatchetSession> GetSession () { return m_Session; };
@@ -94,8 +86,7 @@ namespace garlic
 
 			virtual bool IsIndexExpired (int index) const;
 			virtual bool HandleNextMessage (uint8_t * buf, size_t len, int index);
-			virtual bool IsSessionTerminated () const;
-			
+
 		private:
 
 			int m_TrimBehindIndex = 0;
@@ -110,10 +101,9 @@ namespace garlic
 
 			SymmetricKeyTagSet (GarlicDestination * destination, const uint8_t * key);
 
-			bool IsIndexExpired (int index) const override { return false; };
-			bool HandleNextMessage (uint8_t * buf, size_t len, int index) override;
-			bool IsSessionTerminated () const override { return false; }
-			
+			bool IsIndexExpired (int index) const { return false; };
+			bool HandleNextMessage (uint8_t * buf, size_t len, int index);
+
 		private:
 
 			GarlicDestination * m_Destination;
@@ -157,7 +147,6 @@ namespace garlic
 			std::shared_ptr<i2p::crypto::X25519Keys> key;
 			uint8_t remote[32]; // last remote public key
 			bool newKey = true;
-			int GetReceiveTagSetID () const { return newKey ? (2*keyID + 1) : 2*keyID; }
 		};
 
 		public:
@@ -166,41 +155,34 @@ namespace garlic
 			~ECIESX25519AEADRatchetSession ();
 
 			bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr<ReceiveRatchetTagSet> receiveTagset, int index = 0);
-			std::shared_ptr<I2NPMessage> WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg) override;
+			std::shared_ptr<I2NPMessage> WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg);
 			std::shared_ptr<I2NPMessage> WrapOneTimeMessage (std::shared_ptr<const I2NPMessage> msg);
 
 			const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; }
-			i2p::data::CryptoKeyType GetRemoteStaticKeyType () const { return m_RemoteStaticKeyType; }
-			void SetRemoteStaticKey (i2p::data::CryptoKeyType keyType, const uint8_t * key) 
-			{ 
-				m_RemoteStaticKeyType = keyType;
-				memcpy (m_RemoteStaticKey, key, 32); 
-			}
+			void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); }
+
 			void Terminate () { m_IsTerminated = true; }
-			void SetDestination (const i2p::data::IdentHash& dest)
+			void SetDestination (const i2p::data::IdentHash& dest) // TODO:
 			{
 				if (!m_Destination) m_Destination.reset (new i2p::data::IdentHash (dest));
 			}
+
 			bool CheckExpired (uint64_t ts); // true is expired
 			bool CanBeRestarted (uint64_t ts) const { return ts > m_SessionCreatedTimestamp + ECIESX25519_RESTART_TIMEOUT; }
 			bool IsInactive (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_INACTIVITY_TIMEOUT && CanBeRestarted (ts); }
-			void CleanupReceiveNSRKeys (); // called from ReceiveRatchetTagSet at Alice's side
-			
-			bool IsRatchets () const override { return true; };
-			bool IsReadyToSend () const override { return m_State != eSessionStateNewSessionSent; };
-			bool IsTerminated () const override { return m_IsTerminated; }
-			uint64_t GetLastActivityTimestamp () const override { return m_LastActivityTimestamp; };
-			void SetAckRequestInterval (int interval) override { m_AckRequestInterval = interval; };
-			bool CleanupUnconfirmedTags () override; // return true if unaswered Ack requests, called from I2CP
-			
+
+			bool IsRatchets () const { return true; };
+			bool IsReadyToSend () const { return m_State != eSessionStateNewSessionSent; };
+			bool IsTerminated () const { return m_IsTerminated; }
+			uint64_t GetLastActivityTimestamp () const { return m_LastActivityTimestamp; };
+
 		protected:
 
 			i2p::crypto::NoiseSymmetricState& GetNoiseState () { return *this; };
 			void SetNoiseState (const i2p::crypto::NoiseSymmetricState& state) { GetNoiseState () = state; };
 			void CreateNonce (uint64_t seqn, uint8_t * nonce);
 			void HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr<ReceiveRatchetTagSet>& receiveTagset, int index);
-			bool MessageConfirmed (uint32_t msgID) override;
-			
+
 		private:
 
 			bool GenerateEphemeralKeysAndEncode (uint8_t * buf); // buf is 32 bytes
@@ -225,30 +207,20 @@ namespace garlic
 
 		private:
 
-			i2p::data::CryptoKeyType m_RemoteStaticKeyType;
 			uint8_t m_RemoteStaticKey[32];
 			uint8_t m_Aepk[32]; // Alice's ephemeral keys, for incoming only
 			uint8_t m_NSREncodedKey[32], m_NSRH[32], m_NSRKey[32]; // new session reply, for incoming only
 			std::shared_ptr<i2p::crypto::X25519Keys> m_EphemeralKeys;
-#if OPENSSL_PQ	
-			std::unique_ptr<i2p::crypto::MLKEMKeys> m_PQKeys;
-			std::unique_ptr<std::vector<uint8_t> > m_NSREncodedPQKey;
-#endif			
 			SessionState m_State = eSessionStateNew;
 			uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0, // incoming (in seconds)
 				m_LastSentTimestamp = 0; // in milliseconds
 			std::shared_ptr<RatchetTagSet> m_SendTagset, m_NSRSendTagset;
-			std::unique_ptr<i2p::data::IdentHash> m_Destination;// must be set for NS if outgoing and NSR if incoming
-			std::list<std::pair<uint16_t, int> > m_AckRequests; // incoming (tagsetid, index)
+			std::unique_ptr<i2p::data::IdentHash> m_Destination;// TODO: might not need it
+			std::list<std::pair<uint16_t, int> > m_AckRequests; // (tagsetid, index)
 			bool m_SendReverseKey = false, m_SendForwardKey = false, m_IsTerminated = false;
 			std::unique_ptr<DHRatchet> m_NextReceiveRatchet, m_NextSendRatchet;
 			uint8_t m_PaddingSizes[32], m_NextPaddingSize;
 
-			uint64_t m_LastAckRequestSendTime = 0; // milliseconds
-			uint32_t m_AckRequestMsgID = 0; 
-			int m_AckRequestNumAttempts = 0;
-			int m_AckRequestInterval = ECIESX25519_DEFAULT_ACK_REQUEST_INTERVAL; // milliseconds
-			
 		public:
 
 			// for HTTP only
@@ -273,8 +245,8 @@ namespace garlic
 			i2p::crypto::NoiseSymmetricState m_CurrentNoiseState;
 	};
 
-	std::shared_ptr<I2NPMessage> WrapECIESX25519Message (std::shared_ptr<I2NPMessage> msg, const uint8_t * key, uint64_t tag);
-	std::shared_ptr<I2NPMessage> WrapECIESX25519MessageForRouter (std::shared_ptr<I2NPMessage> msg, const uint8_t * routerPublicKey);
+	std::shared_ptr<I2NPMessage> WrapECIESX25519Message (std::shared_ptr<const I2NPMessage> msg, const uint8_t * key, uint64_t tag);
+	std::shared_ptr<I2NPMessage> WrapECIESX25519MessageForRouter (std::shared_ptr<const I2NPMessage> msg, const uint8_t * routerPublicKey);
 }
 }
 
diff --git a/libi2pd/Ed25519.cpp b/libi2pd/Ed25519.cpp
index 47edb755..3e0795d5 100644
--- a/libi2pd/Ed25519.cpp
+++ b/libi2pd/Ed25519.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -457,6 +457,86 @@ namespace crypto
 		}
 	}
 
+#if !OPENSSL_X25519
+	BIGNUM * Ed25519::ScalarMul (const BIGNUM * u, const BIGNUM * k, BN_CTX * ctx) const
+	{
+		BN_CTX_start (ctx);
+		auto x1 = BN_CTX_get (ctx); BN_copy (x1, u);
+		auto x2 = BN_CTX_get (ctx); BN_one (x2);
+		auto z2 = BN_CTX_get (ctx); BN_zero (z2);
+		auto x3 = BN_CTX_get (ctx); BN_copy (x3, u);
+		auto z3 = BN_CTX_get (ctx); BN_one (z3);
+		auto c121666 = BN_CTX_get (ctx); BN_set_word (c121666, 121666);
+		auto tmp0 = BN_CTX_get (ctx); auto tmp1 = BN_CTX_get (ctx);
+		unsigned int swap = 0;
+		auto bits = BN_num_bits (k);
+		while(bits)
+		{
+			--bits;
+			auto k_t = BN_is_bit_set(k, bits) ? 1 : 0;
+			swap ^= k_t;
+			if (swap)
+			{
+				std::swap (x2, x3);
+				std::swap (z2, z3);
+			}
+			swap = k_t;
+			BN_mod_sub(tmp0, x3, z3, q, ctx);
+			BN_mod_sub(tmp1, x2, z2, q, ctx);
+			BN_mod_add(x2, x2, z2, q, ctx);
+			BN_mod_add(z2, x3, z3, q, ctx);
+			BN_mod_mul(z3, tmp0, x2, q, ctx);
+			BN_mod_mul(z2, z2, tmp1, q, ctx);
+			BN_mod_sqr(tmp0, tmp1, q, ctx);
+			BN_mod_sqr(tmp1, x2, q, ctx);
+			BN_mod_add(x3, z3, z2, q, ctx);
+			BN_mod_sub(z2, z3, z2, q, ctx);
+			BN_mod_mul(x2, tmp1, tmp0, q, ctx);
+			BN_mod_sub(tmp1, tmp1, tmp0, q, ctx);
+			BN_mod_sqr(z2, z2, q, ctx);
+			BN_mod_mul(z3, tmp1, c121666, q, ctx);
+			BN_mod_sqr(x3, x3, q, ctx);
+			BN_mod_add(tmp0, tmp0, z3, q, ctx);
+			BN_mod_mul(z3, x1, z2, q, ctx);
+			BN_mod_mul(z2, tmp1, tmp0, q, ctx);
+		}
+		if (swap)
+		{
+			std::swap (x2, x3);
+			std::swap (z2, z3);
+		}
+		BN_mod_inverse (z2, z2, q, ctx);
+		BIGNUM * res = BN_new (); // not from ctx
+		BN_mod_mul(res, x2, z2, q, ctx);
+		BN_CTX_end (ctx);
+		return res;
+	}
+
+	void Ed25519::ScalarMul (const uint8_t * p, const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const
+	{
+		BIGNUM * p1 = DecodeBN<32> (p);
+		uint8_t k[32];
+		memcpy (k, e, 32);
+		k[0] &= 248; k[31] &= 127; k[31] |= 64;
+		BIGNUM * n = DecodeBN<32> (k);
+		BIGNUM * q1 = ScalarMul (p1, n, ctx);
+		EncodeBN (q1, buf, 32);
+		BN_free (p1); BN_free (n); BN_free (q1);
+	}
+
+	void Ed25519::ScalarMulB (const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const
+	{
+		BIGNUM *p1 = BN_new (); BN_set_word (p1, 9);
+		uint8_t k[32];
+		memcpy (k, e, 32);
+		k[0] &= 248; k[31] &= 127; k[31] |= 64;
+		BIGNUM * n = DecodeBN<32> (k);
+		BIGNUM * q1 = ScalarMul (p1, n, ctx);
+		EncodeBN (q1, buf, 32);
+		BN_free (p1); BN_free (n); BN_free (q1);
+	}
+#endif
+
 	void Ed25519::BlindPublicKey (const uint8_t * pub, const uint8_t * seed, uint8_t * blinded)
 	{
 		BN_CTX * ctx = BN_CTX_new ();
diff --git a/libi2pd/Ed25519.h b/libi2pd/Ed25519.h
index 9c0ad801..470d802f 100644
--- a/libi2pd/Ed25519.h
+++ b/libi2pd/Ed25519.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2020, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -84,7 +84,10 @@ namespace crypto
 			EDDSAPoint GeneratePublicKey (const uint8_t * expandedPrivateKey, BN_CTX * ctx) const;
 			EDDSAPoint DecodePublicKey (const uint8_t * buf, BN_CTX * ctx) const;
 			void EncodePublicKey (const EDDSAPoint& publicKey, uint8_t * buf, BN_CTX * ctx) const;
-
+#if !OPENSSL_X25519
+			void ScalarMul (const uint8_t * p, const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const; // p is point, e is number for x25519
+			void ScalarMulB (const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const;
+#endif
 			void BlindPublicKey (const uint8_t * pub, const uint8_t * seed, uint8_t * blinded); // for encrypted LeaseSet2, pub - 32, seed - 64, blinded - 32
 			void BlindPrivateKey (const uint8_t * priv, const uint8_t * seed, uint8_t * blindedPriv, uint8_t * blindedPub); // for encrypted LeaseSet2, pub - 32, seed - 64, blinded - 32
 
@@ -112,6 +115,11 @@ namespace crypto
 			BIGNUM * DecodeBN (const uint8_t * buf) const;
 			void EncodeBN (const BIGNUM * bn, uint8_t * buf, size_t len) const;
 
+#if !OPENSSL_X25519
+			// for x25519
+			BIGNUM * ScalarMul (const BIGNUM * p, const BIGNUM * e, BN_CTX * ctx) const;
+#endif
+
 		private:
 
 			BIGNUM * q, * l, * d, * I;
diff --git a/libi2pd/FS.cpp b/libi2pd/FS.cpp
index 3f5fc6b9..d38bcc2f 100644
--- a/libi2pd/FS.cpp
+++ b/libi2pd/FS.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2022, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -7,17 +7,7 @@
 */
 
 #include <algorithm>
-
-#if defined(MAC_OSX)
-#if !STD_FILESYSTEM
-#include <boost/system/system_error.hpp>
-#endif
-#include <TargetConditionals.h>
-#endif
-
-#if defined(__HAIKU__)
-#include <FindDirectory.h>
-#endif
+#include <boost/filesystem.hpp>
 
 #ifdef _WIN32
 #include <shlobj.h>
@@ -30,14 +20,6 @@
 #include "Log.h"
 #include "Garlic.h"
 
-#if STD_FILESYSTEM
-#include <filesystem>
-namespace fs_lib = std::filesystem;
-#else
-#include <boost/filesystem.hpp>
-namespace fs_lib = boost::filesystem;
-#endif
-
 namespace i2p {
 namespace fs {
 	std::string appName = "i2pd";
@@ -67,17 +49,11 @@ namespace fs {
 
 	const std::string GetUTF8DataDir () {
 #ifdef _WIN32
-		int size = MultiByteToWideChar(CP_ACP, 0,
-			dataDir.c_str(), dataDir.size(), nullptr, 0);
-		std::wstring utf16Str(size, L'\0');
-		MultiByteToWideChar(CP_ACP, 0,
-			dataDir.c_str(), dataDir.size(), &utf16Str[0], size);
-		int utf8Size = WideCharToMultiByte(CP_UTF8, 0,
-			utf16Str.c_str(), utf16Str.size(), nullptr, 0, nullptr, nullptr);
-		std::string utf8Str(utf8Size, '\0');
-		WideCharToMultiByte(CP_UTF8, 0,
-			utf16Str.c_str(), utf16Str.size(), &utf8Str[0], utf8Size, nullptr, nullptr);
-		return utf8Str;
+		boost::filesystem::wpath path (dataDir);
+		auto loc = boost::filesystem::path::imbue(std::locale( std::locale(), new std::codecvt_utf8_utf16<wchar_t>() ) ); // convert path to UTF-8
+		auto dataDirUTF8 = path.string();
+		boost::filesystem::path::imbue(loc); // Return locale settings back
+		return dataDirUTF8;
 #else
 		return dataDir; // linux, osx, android uses UTF-8 by default
 #endif
@@ -106,11 +82,7 @@ namespace fs {
 			}
 			else
 			{
-#if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM)
-				dataDir = fs_lib::path(commonAppData).string() + "\\" + appName;
-#else
-				dataDir = fs_lib::wpath(commonAppData).string() + "\\" + appName;
-#endif
+				dataDir = boost::filesystem::wpath(commonAppData).string() + "\\" + appName;
 			}
 #else
 			dataDir = "/var/lib/" + appName;
@@ -135,14 +107,10 @@ namespace fs {
 		}
 		else
 		{
-#if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM)
-			auto execPath = fs_lib::path(localAppData).parent_path();
-#else
-			auto execPath = fs_lib::wpath(localAppData).parent_path();
-#endif
+			auto execPath = boost::filesystem::wpath(localAppData).parent_path();
 
 			// if config file exists in .exe's folder use it
-			if(fs_lib::exists(execPath/"i2pd.conf")) // TODO: magic string
+			if(boost::filesystem::exists(execPath/"i2pd.conf")) // TODO: magic string
 			{
 				dataDir = execPath.string ();
 			} else // otherwise %appdata%
@@ -158,11 +126,7 @@ namespace fs {
 				}
 				else
 				{
-#if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM)
-					dataDir = fs_lib::path(localAppData).string() + "\\" + appName;
-#else
-					dataDir = fs_lib::wpath(localAppData).string() + "\\" + appName;
-#endif
+					dataDir = boost::filesystem::wpath(localAppData).string() + "\\" + appName;
 				}
 			}
 		}
@@ -173,17 +137,18 @@ namespace fs {
 		dataDir += "/Library/Application Support/" + appName;
 		return;
 #elif defined(__HAIKU__)
-		char home[PATH_MAX]; // /boot/home/config/settings
-		if (find_directory(B_USER_SETTINGS_DIRECTORY, -1, false, home, PATH_MAX) == B_OK)	
-			dataDir = std::string(home) + "/" + appName;
-		else
+		char *home = getenv("HOME");
+		if (home != NULL && strlen(home) > 0) {
+			dataDir = std::string(home) + "/config/settings/" + appName;
+		} else {
 			dataDir = "/tmp/" + appName;
+		}
 		return;
 #else /* other unix */
 #if defined(ANDROID)
 		const char * ext = getenv("EXTERNAL_STORAGE");
 		if (!ext) ext = "/sdcard";
-		if (fs_lib::exists(ext))
+		if (boost::filesystem::exists(ext))
 		{
 			dataDir = std::string (ext) + "/" + appName;
 			return;
@@ -216,16 +181,16 @@ namespace fs {
 	}
 
 	bool Init() {
-		if (!fs_lib::exists(dataDir))
-			fs_lib::create_directory(dataDir);
+		if (!boost::filesystem::exists(dataDir))
+			boost::filesystem::create_directory(dataDir);
 
 		std::string destinations = DataDirPath("destinations");
-		if (!fs_lib::exists(destinations))
-			fs_lib::create_directory(destinations);
+		if (!boost::filesystem::exists(destinations))
+			boost::filesystem::create_directory(destinations);
 
 		std::string tags = DataDirPath("tags");
-		if (!fs_lib::exists(tags))
-			fs_lib::create_directory(tags);
+		if (!boost::filesystem::exists(tags))
+			boost::filesystem::create_directory(tags);
 		else
 			i2p::garlic::CleanUpTagsFiles ();
 
@@ -233,13 +198,13 @@ namespace fs {
 	}
 
 	bool ReadDir(const std::string & path, std::vector<std::string> & files) {
-		if (!fs_lib::exists(path))
+		if (!boost::filesystem::exists(path))
 			return false;
-		fs_lib::directory_iterator it(path);
-		fs_lib::directory_iterator end;
+		boost::filesystem::directory_iterator it(path);
+		boost::filesystem::directory_iterator end;
 
 		for ( ; it != end; it++) {
-			if (!fs_lib::is_regular_file(it->status()))
+			if (!boost::filesystem::is_regular_file(it->status()))
 				continue;
 			files.push_back(it->path().string());
 		}
@@ -248,42 +213,29 @@ namespace fs {
 	}
 
 	bool Exists(const std::string & path) {
-		return fs_lib::exists(path);
+		return boost::filesystem::exists(path);
 	}
 
 	uint32_t GetLastUpdateTime (const std::string & path)
 	{
-		if (!fs_lib::exists(path))
+		if (!boost::filesystem::exists(path))
 			return 0;
-#if STD_FILESYSTEM
-		std::error_code ec;
-		auto t = std::filesystem::last_write_time (path, ec);
-		if (ec) return 0;
-/*#if __cplusplus >= 202002L // C++ 20 or higher
-		const auto sctp = std::chrono::clock_cast<std::chrono::system_clock>(t);
-#else	*/	// TODO: wait until implemented
-		const auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
-		    t - decltype(t)::clock::now() + std::chrono::system_clock::now());
-/*#endif */
-		return std::chrono::system_clock::to_time_t(sctp);
-#else
 		boost::system::error_code ec;
 		auto t = boost::filesystem::last_write_time (path, ec);
 		return ec ? 0 : t;
-#endif
 	}
 
 	bool Remove(const std::string & path) {
-		if (!fs_lib::exists(path))
+		if (!boost::filesystem::exists(path))
 			return false;
-		return fs_lib::remove(path);
+		return boost::filesystem::remove(path);
 	}
 
 	bool CreateDirectory (const std::string& path)
 	{
-		if (fs_lib::exists(path) && fs_lib::is_directory (fs_lib::status (path)))
+		if (boost::filesystem::exists(path) && boost::filesystem::is_directory (boost::filesystem::status (path)))
 			return true;
-		return fs_lib::create_directory(path);
+		return boost::filesystem::create_directory(path);
 	}
 
 	void HashedStorage::SetPlace(const std::string &path) {
@@ -291,30 +243,16 @@ namespace fs {
 	}
 
 	bool HashedStorage::Init(const char * chars, size_t count) {
-		if (!fs_lib::exists(root)) {
-			fs_lib::create_directories(root);
+		if (!boost::filesystem::exists(root)) {
+			boost::filesystem::create_directories(root);
 		}
 
 		for (size_t i = 0; i < count; i++) {
 			auto p = root + i2p::fs::dirSep + prefix1 + chars[i];
-			if (fs_lib::exists(p))
+			if (boost::filesystem::exists(p))
 				continue;
-#if TARGET_OS_SIMULATOR
-			// ios simulator fs says it is case sensitive, but it is not
-			boost::system::error_code ec;
-			if (fs_lib::create_directory(p, ec))
-				continue;
-			switch (ec.value()) {
-				case boost::system::errc::file_exists:
-				case boost::system::errc::success:
-					continue;
-				default:
-					throw boost::system::system_error( ec, __func__ );
-			}
-#else
-			if (fs_lib::create_directory(p))
+			if (boost::filesystem::create_directory(p))
 				continue; /* ^ throws exception on failure */
-#endif
 			return false;
 		}
 		return true;
@@ -335,9 +273,9 @@ namespace fs {
 
 	void HashedStorage::Remove(const std::string & ident) {
 		std::string path = Path(ident);
-		if (!fs_lib::exists(path))
+		if (!boost::filesystem::exists(path))
 			return;
-		fs_lib::remove(path);
+		boost::filesystem::remove(path);
 	}
 
 	void HashedStorage::Traverse(std::vector<std::string> & files) {
@@ -348,12 +286,12 @@ namespace fs {
 
 	void HashedStorage::Iterate(FilenameVisitor v)
 	{
-		fs_lib::path p(root);
-		fs_lib::recursive_directory_iterator it(p);
-		fs_lib::recursive_directory_iterator end;
+		boost::filesystem::path p(root);
+		boost::filesystem::recursive_directory_iterator it(p);
+		boost::filesystem::recursive_directory_iterator end;
 
 		for ( ; it != end; it++) {
-			if (!fs_lib::is_regular_file( it->status() ))
+			if (!boost::filesystem::is_regular_file( it->status() ))
 				continue;
 			const std::string & t = it->path().string();
 			v(t);
diff --git a/libi2pd/FS.h b/libi2pd/FS.h
index 7af8f494..7911c6a0 100644
--- a/libi2pd/FS.h
+++ b/libi2pd/FS.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2020, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -15,16 +15,6 @@
 #include <sstream>
 #include <functional>
 
-#ifndef STD_FILESYSTEM
-#	if (_WIN32 && __GNUG__) // MinGW GCC somehow incorrectly converts paths
-#		define STD_FILESYSTEM 0
-#	elif (!TARGET_OS_SIMULATOR && __has_include(<filesystem>)) // supports std::filesystem
-#		define STD_FILESYSTEM 1
-#	else
-#		define STD_FILESYSTEM 0
-#	endif
-#endif
-
 namespace i2p {
 namespace fs {
 	extern std::string dirSep;
diff --git a/libi2pd/Family.cpp b/libi2pd/Family.cpp
index 300a50ab..8c6d3ba4 100644
--- a/libi2pd/Family.cpp
+++ b/libi2pd/Family.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -7,6 +7,7 @@
 */
 
 #include <string.h>
+#include <openssl/evp.h>
 #include <openssl/ssl.h>
 #include "Crypto.h"
 #include "FS.h"
@@ -24,8 +25,6 @@ namespace data
 
 	Families::~Families ()
 	{
-		for (auto it : m_SigningKeys)
-			if (it.second.first) EVP_PKEY_free (it.second.first);
 	}
 
 	void Families::LoadCertificate (const std::string& filename)
@@ -48,16 +47,48 @@ namespace data
 					cn += 3;
 					char * family = strstr (cn, ".family");
 					if (family) family[0] = 0;
-					auto pkey = X509_get_pubkey (cert);
-					if (pkey)
-					{	
-						if (!m_SigningKeys.emplace (cn, std::make_pair(pkey, (int)m_SigningKeys.size () + 1)).second)
-						{
-							EVP_PKEY_free (pkey);
-							LogPrint (eLogError, "Family: Duplicated family name ", cn);
-						}	
-					}	
 				}
+				auto pkey = X509_get_pubkey (cert);
+				int keyType = EVP_PKEY_base_id (pkey);
+				switch (keyType)
+				{
+					case EVP_PKEY_DSA:
+						// TODO:
+					break;
+					case EVP_PKEY_EC:
+					{
+						EC_KEY * ecKey = EVP_PKEY_get1_EC_KEY (pkey);
+						if (ecKey)
+						{
+							auto group = EC_KEY_get0_group (ecKey);
+							if (group)
+							{
+								int curve = EC_GROUP_get_curve_name (group);
+								if (curve == NID_X9_62_prime256v1)
+								{
+									uint8_t signingKey[64];
+									BIGNUM * x = BN_new(), * y = BN_new();
+									EC_POINT_get_affine_coordinates_GFp (group,
+										EC_KEY_get0_public_key (ecKey), x, y, NULL);
+									i2p::crypto::bn2buf (x, signingKey, 32);
+									i2p::crypto::bn2buf (y, signingKey + 32, 32);
+									BN_free (x); BN_free (y);
+									verifier = std::make_shared<i2p::crypto::ECDSAP256Verifier>();
+									verifier->SetPublicKey (signingKey);
+								}
+								else
+									LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported");
+							}
+							EC_KEY_free (ecKey);
+						}
+						break;
+					}
+					default:
+						LogPrint (eLogWarning, "Family: Certificate key type ", keyType, " is not supported");
+				}
+				EVP_PKEY_free (pkey);
+				if (verifier && cn)
+					m_SigningKeys.emplace (cn, std::make_pair(verifier, (int)m_SigningKeys.size () + 1));
 			}
 			SSL_free (ssl);
 		}
@@ -90,31 +121,23 @@ namespace data
 	}
 
 	bool Families::VerifyFamily (const std::string& family, const IdentHash& ident,
-		std::string_view signature, const char * key) const
+		const char * signature, const char * key) const
 	{
 		uint8_t buf[100], signatureBuf[64];
-		size_t len = family.length ();
+		size_t len = family.length (), signatureLen = strlen (signature);
 		if (len + 32 > 100)
 		{
 			LogPrint (eLogError, "Family: ", family, " is too long");
 			return false;
 		}
+
+		memcpy (buf, family.c_str (), len);
+		memcpy (buf + len, (const uint8_t *)ident, 32);
+		len += 32;
+		Base64ToByteStream (signature, signatureLen, signatureBuf, 64);
 		auto it = m_SigningKeys.find (family);
-		if (it != m_SigningKeys.end () && it->second.first)
-		{	
-			memcpy (buf, family.c_str (), len);
-			memcpy (buf + len, (const uint8_t *)ident, 32);
-			len += 32;
-			auto signatureBufLen = Base64ToByteStream (signature, signatureBuf, 64);
-			if (signatureBufLen)
-			{
-				EVP_MD_CTX * ctx = EVP_MD_CTX_create ();
-				EVP_DigestVerifyInit (ctx, NULL, NULL, NULL, it->second.first);
-				auto ret = EVP_DigestVerify (ctx, signatureBuf, signatureBufLen, buf, len);
-				EVP_MD_CTX_destroy (ctx);
-				return ret;
-			}	
-		}	
+		if (it != m_SigningKeys.end ())
+			return it->second.first->Verify (buf, len, signatureBuf);
 		// TODO: process key
 		return true;
 	}
@@ -154,7 +177,12 @@ namespace data
 						memcpy (buf + len, (const uint8_t *)ident, 32);
 						len += 32;
 						signer.Sign (buf, len, signature);
-						sig = ByteStreamToBase64 (signature, 64);
+						len = Base64EncodingBufferSize (64);
+						char * b64 = new char[len+1];
+						len = ByteStreamToBase64 (signature, 64, b64, len);
+						b64[len] = 0;
+						sig = b64;
+						delete[] b64;
 					}
 					else
 						LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported");
diff --git a/libi2pd/Family.h b/libi2pd/Family.h
index fcf61082..b19ea142 100644
--- a/libi2pd/Family.h
+++ b/libi2pd/Family.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2022, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -11,9 +11,8 @@
 
 #include <map>
 #include <string>
-#include <string_view>
 #include <memory>
-#include <openssl/evp.h>
+#include "Signature.h"
 #include "Identity.h"
 
 namespace i2p
@@ -29,7 +28,7 @@ namespace data
 			~Families ();
 			void LoadCertificates ();
 			bool VerifyFamily (const std::string& family, const IdentHash& ident,
-				std::string_view signature, const char * key = nullptr) const;
+				const char * signature, const char * key = nullptr) const;
 			FamilyID GetFamilyID (const std::string& family) const;
 
 		private:
@@ -38,7 +37,7 @@ namespace data
 
 		private:
 
-			std::map<std::string, std::pair<EVP_PKEY *, FamilyID> > m_SigningKeys; // family -> (verification pkey, id)
+			std::map<std::string, std::pair<std::shared_ptr<i2p::crypto::Verifier>, FamilyID> > m_SigningKeys; // family -> (verifier, id)
 	};
 
 	std::string CreateFamilySignature (const std::string& family, const IdentHash& ident);
diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp
index 8c8602e8..3f885186 100644
--- a/libi2pd/Garlic.cpp
+++ b/libi2pd/Garlic.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -45,17 +45,22 @@ namespace garlic
 	{
 		if (!m_SharedRoutingPath) return nullptr;
 		uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
-		if (!m_SharedRoutingPath->outboundTunnel->IsEstablished () ||
+		if (m_SharedRoutingPath->numTimesUsed >= ROUTING_PATH_MAX_NUM_TIMES_USED ||
+			!m_SharedRoutingPath->outboundTunnel->IsEstablished () ||
 			ts*1000LL > m_SharedRoutingPath->remoteLease->endDate ||
 			ts > m_SharedRoutingPath->updateTime + ROUTING_PATH_EXPIRATION_TIMEOUT)
 				m_SharedRoutingPath = nullptr;
+		if (m_SharedRoutingPath) m_SharedRoutingPath->numTimesUsed++;
 		return m_SharedRoutingPath;
 	}
 
 	void GarlicRoutingSession::SetSharedRoutingPath (std::shared_ptr<GarlicRoutingPath> path)
 	{
 		if (path && path->outboundTunnel && path->remoteLease)
+		{
 			path->updateTime = i2p::util::GetSecondsSinceEpoch ();
+			path->numTimesUsed = 0;
+		}
 		else
 			path = nullptr;
 		m_SharedRoutingPath = path;
@@ -75,7 +80,7 @@ namespace garlic
 
 	void GarlicRoutingSession::CleanupUnconfirmedLeaseSet (uint64_t ts)
 	{
-		if (m_LeaseSetUpdateMsgID && ts*1000LL > m_LeaseSetSubmissionTime + LEASESET_CONFIRMATION_TIMEOUT)
+		if (m_LeaseSetUpdateMsgID && ts*1000LL > m_LeaseSetSubmissionTime + LEASET_CONFIRMATION_TIMEOUT)
 		{
 			if (GetOwner ())
 				GetOwner ()->RemoveDeliveryStatusSession (m_LeaseSetUpdateMsgID);
@@ -160,7 +165,7 @@ namespace garlic
 			uint8_t iv[32]; // IV is first 16 bytes
 			SHA256(elGamal.preIV, 32, iv);
 			m_Destination->Encrypt ((uint8_t *)&elGamal, buf);
-			m_IV = iv;
+			m_Encryption.SetIV (iv);
 			buf += 514;
 			len += 514;
 		}
@@ -170,7 +175,7 @@ namespace garlic
 			memcpy (buf, tag, 32);
 			uint8_t iv[32]; // IV is first 16 bytes
 			SHA256(tag, 32, iv);
-			m_IV = iv;
+			m_Encryption.SetIV (iv);
 			buf += 32;
 			len += 32;
 		}
@@ -210,7 +215,7 @@ namespace garlic
 		size_t rem = blockSize % 16;
 		if (rem)
 			blockSize += (16-rem); //padding
-		m_Encryption.Encrypt(buf, blockSize, m_IV, buf);
+		m_Encryption.Encrypt(buf, blockSize, buf);
 		return blockSize;
 	}
 
@@ -227,7 +232,7 @@ namespace garlic
 		if (GetOwner ())
 		{
 			// resubmit non-confirmed LeaseSet
-			if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASESET_CONFIRMATION_TIMEOUT)
+			if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT)
 			{
 				SetLeaseSetUpdateStatus (eLeaseSetUpdated);
 				SetSharedRoutingPath (nullptr); // invalidate path since leaseset was not confirmed
@@ -426,8 +431,7 @@ namespace garlic
 	}
 
 	GarlicDestination::GarlicDestination (): m_NumTags (32), // 32 tags by default
-		m_PayloadBuffer (nullptr), m_LastIncomingSessionTimestamp (0), 
-		m_NumRatchetInboundTags (0) // 0 means standard
+		m_PayloadBuffer (nullptr), m_NumRatchetInboundTags (0) // 0 means standard
 	{
 	}
 
@@ -498,8 +502,7 @@ namespace garlic
 		buf += 4; // length
 
 		bool found = false;
-		bool supportsRatchets = SupportsRatchets ();
-		if (supportsRatchets)
+		if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
 			// try ECIESx25519 tag
 			found = HandleECIESx25519TagMessage (buf, length);
 		if (!found)
@@ -515,7 +518,8 @@ namespace garlic
 				{
 					uint8_t iv[32]; // IV is first 16 bytes
 					SHA256(buf, 32, iv);
-					decryption->Decrypt (buf + 32, length - 32, iv, buf + 32);
+					decryption->SetIV (iv);
+					decryption->Decrypt (buf + 32, length - 32, buf + 32);
 					HandleAESBlock (buf + 32, length - 32, decryption, msg->from);
 					found = true;
 				}
@@ -533,23 +537,43 @@ namespace garlic
 					auto decryption = std::make_shared<AESDecryption>(elGamal.sessionKey);
 					uint8_t iv[32]; // IV is first 16 bytes
 					SHA256(elGamal.preIV, 32, iv);
-					decryption->Decrypt(buf + 514, length - 514, iv, buf + 514);
+					decryption->SetIV (iv);
+					decryption->Decrypt(buf + 514, length - 514, buf + 514);
 					HandleAESBlock (buf + 514, length - 514, decryption, msg->from);
 				}
-				else if (supportsRatchets)
+				else if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
 				{
 					// otherwise ECIESx25519
-					auto ts = i2p::util::GetMillisecondsSinceEpoch ();
-					if (ts > m_LastIncomingSessionTimestamp + INCOMING_SESSIONS_MINIMAL_INTERVAL) 
-					{	
-						auto session = std::make_shared<ECIESX25519AEADRatchetSession> (this, false); // incoming
-						if (session->HandleNextMessage (buf, length, nullptr, 0))
-							m_LastIncomingSessionTimestamp = ts;
-						else
+					auto session = std::make_shared<ECIESX25519AEADRatchetSession> (this, false); // incoming
+					if (!session->HandleNextMessage (buf, length, nullptr, 0))
+					{
+						// try to generate more tags for last tagset
+						if (m_LastTagset && (m_LastTagset->GetNextIndex () - m_LastTagset->GetTrimBehind () < 3*ECIESX25519_MAX_NUM_GENERATED_TAGS))
+						{
+							uint64_t missingTag; memcpy (&missingTag, buf, 8);
+							auto maxTags = std::max (m_NumRatchetInboundTags, ECIESX25519_MAX_NUM_GENERATED_TAGS);
+							LogPrint (eLogWarning, "Garlic: Trying to generate more ECIES-X25519-AEAD-Ratchet tags");
+							for (int i = 0; i < maxTags; i++)
+							{
+								auto nextTag = AddECIESx25519SessionNextTag (m_LastTagset);
+								if (!nextTag)
+								{
+									LogPrint (eLogError, "Garlic: Can't create new ECIES-X25519-AEAD-Ratchet tag for last tagset");
+									break;
+								}
+								if (nextTag == missingTag)
+								{
+									LogPrint (eLogDebug, "Garlic: Missing ECIES-X25519-AEAD-Ratchet tag was generated");
+									if (m_LastTagset->HandleNextMessage (buf, length, m_ECIESx25519Tags[nextTag].index))
+										found = true;
+									break;
+								}
+							}
+							if (!found) m_LastTagset = nullptr;
+						}
+						if (!found)
 							LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message");
 					}
-					else
-						LogPrint (eLogWarning, "Garlic: Incoming sessions come too often");
 				}
 				else
 					LogPrint (eLogError, "Garlic: Failed to decrypt message");
@@ -564,7 +588,9 @@ namespace garlic
 		auto it = m_ECIESx25519Tags.find (tag);
 		if (it != m_ECIESx25519Tags.end ())
 		{
-			if (!it->second.tagset || !it->second.tagset->HandleNextMessage (buf, len, it->second.index))
+			if (it->second.tagset && it->second.tagset->HandleNextMessage (buf, len, it->second.index))
+				m_LastTagset = it->second.tagset;
+			else
 				LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message");
 			m_ECIESx25519Tags.erase (it);
 			return true;
@@ -745,38 +771,32 @@ namespace garlic
 	}
 
 	std::shared_ptr<GarlicRoutingSession> GarlicDestination::GetRoutingSession (
-		std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet,
-	    bool requestNewIfNotFound)
+		std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet)
 	{
-		if (destination->GetEncryptionType () >= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
+		if (destination->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD &&
+			SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
 		{
-			if (SupportsEncryptionType (destination->GetEncryptionType ()))
-			{	
-				ECIESX25519AEADRatchetSessionPtr session;
-				uint8_t staticKey[32];
-				destination->Encrypt (nullptr, staticKey); // we are supposed to get static key
-				auto it = m_ECIESx25519Sessions.find (staticKey);
-				if (it != m_ECIESx25519Sessions.end ())
+			ECIESX25519AEADRatchetSessionPtr session;
+			uint8_t staticKey[32];
+			destination->Encrypt (nullptr, staticKey); // we are supposed to get static key
+			auto it = m_ECIESx25519Sessions.find (staticKey);
+			if (it != m_ECIESx25519Sessions.end ())
+			{
+				session = it->second;
+				if (session->IsInactive (i2p::util::GetSecondsSinceEpoch ()))
 				{
-					session = it->second;
-					if (session->IsInactive (i2p::util::GetSecondsSinceEpoch ()))
-					{
-						LogPrint (eLogDebug, "Garlic: Session restarted");
-						requestNewIfNotFound = true; // it's not a new session
-						session = nullptr;
-					}
+					LogPrint (eLogDebug, "Garlic: Session restarted");
+					session = nullptr;
 				}
-				if (!session && requestNewIfNotFound)
-				{
-					session = std::make_shared<ECIESX25519AEADRatchetSession> (this, true);
-					session->SetRemoteStaticKey (destination->GetEncryptionType (), staticKey);
-				}
-				if (session && destination->IsDestination ())
-					session->SetDestination (destination->GetIdentHash ()); // NS or NSR
-				return session;
 			}
-			else
-				LogPrint (eLogError, "Garlic: Non-supported encryption type ", destination->GetEncryptionType ());
+			if (!session)
+			{
+				session = std::make_shared<ECIESX25519AEADRatchetSession> (this, true);
+				session->SetRemoteStaticKey (staticKey);
+			}
+			if (destination->IsDestination ())
+				session->SetDestination (destination->GetIdentHash ()); // TODO: remove
+			return session;
 		}
 		else
 		{
@@ -867,7 +887,8 @@ namespace garlic
 			}
 			else
 			{
-				if (it->second.tagset->IsSessionTerminated ())
+				auto session = it->second.tagset->GetSession ();
+				if (!session || session->IsTerminated())
 				{
 					it = m_ECIESx25519Tags.erase (it);
 					numExpiredTags++;
@@ -878,6 +899,8 @@ namespace garlic
 		}
 		if (numExpiredTags > 0)
 			LogPrint (eLogDebug, "Garlic: ", numExpiredTags, " ECIESx25519 tags expired for ", GetIdentHash().ToBase64 ());
+		if (m_LastTagset && m_LastTagset->IsExpired (ts))
+			m_LastTagset = nullptr;
 	}
 
 	void GarlicDestination::RemoveDeliveryStatusSession (uint32_t msgID)
@@ -911,7 +934,7 @@ namespace garlic
 		}
 	}
 
-	void GarlicDestination::SetLeaseSetUpdated (bool post)
+	void GarlicDestination::SetLeaseSetUpdated ()
 	{
 		{
 			std::unique_lock<std::mutex> l(m_SessionsMutex);
@@ -1004,8 +1027,7 @@ namespace garlic
 				i2p::fs::Remove (it);
 	}
 
-	void GarlicDestination::HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len,
-		ECIESX25519AEADRatchetSession * from)
+	void GarlicDestination::HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len)
 	{
 		const uint8_t * buf1 = buf;
 		uint8_t flag = buf[0]; buf++; // flag
@@ -1015,7 +1037,9 @@ namespace garlic
 			case eGarlicDeliveryTypeDestination:
 				LogPrint (eLogDebug, "Garlic: Type destination");
 				buf += 32; // TODO: check destination
+#if (__cplusplus >= 201703L) // C++ 17 or higher
 				[[fallthrough]];
+#endif
 				// no break here
 			case eGarlicDeliveryTypeLocal:
 			{
@@ -1025,7 +1049,7 @@ namespace garlic
 				buf += 4; // expiration
 				ptrdiff_t offset = buf - buf1;
 				if (offset <= (int)len)
-					HandleCloveI2NPMessage (typeID, buf, len - offset, msgID, from);
+					HandleCloveI2NPMessage (typeID, buf, len - offset, msgID);
 				else
 					LogPrint (eLogError, "Garlic: Clove is too long");
 				break;
@@ -1109,17 +1133,5 @@ namespace garlic
 			m_PayloadBuffer = new uint8_t[I2NP_MAX_MESSAGE_SIZE];
 		return m_PayloadBuffer;
 	}
-
-	bool GarlicDestination::AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
-		const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len)
-	{
-		return m_Encryptor.Encrypt (msg, msgLen, ad, adLen, key, nonce, buf, len);
-	}
-		
-	bool GarlicDestination::AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
-		const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len)
-	{
-		return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len);
-	}	
 }
 }
diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h
index 25106c45..b926abda 100644
--- a/libi2pd/Garlic.h
+++ b/libi2pd/Garlic.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2022, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -50,9 +50,9 @@ namespace garlic
 	const int INCOMING_TAGS_EXPIRATION_TIMEOUT = 960; // 16 minutes
 	const int OUTGOING_TAGS_EXPIRATION_TIMEOUT = 720; // 12 minutes
 	const int OUTGOING_TAGS_CONFIRMATION_TIMEOUT = 10; // 10 seconds
-	const int LEASESET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds
-	const int ROUTING_PATH_EXPIRATION_TIMEOUT = 120; // in seconds
-	const int INCOMING_SESSIONS_MINIMAL_INTERVAL = 200; // in milliseconds
+	const int LEASET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds
+	const int ROUTING_PATH_EXPIRATION_TIMEOUT = 30; // 30 seconds
+	const int ROUTING_PATH_MAX_NUM_TIMES_USED = 100; // how many times might be used
 
 	struct SessionTag: public i2p::data::Tag<32>
 	{
@@ -89,6 +89,7 @@ namespace garlic
 		std::shared_ptr<const i2p::data::Lease> remoteLease;
 		int rtt; // RTT
 		uint32_t updateTime; // seconds since epoch
+		int numTimesUsed;
 	};
 
 	class GarlicDestination;
@@ -110,14 +111,13 @@ namespace garlic
 			GarlicRoutingSession ();
 			virtual ~GarlicRoutingSession ();
 			virtual std::shared_ptr<I2NPMessage> WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg) = 0;
-			virtual bool CleanupUnconfirmedTags () { return false; }; // for I2CP, override in ElGamalAESSession and ECIESX25519AEADRatchetSession
+			virtual bool CleanupUnconfirmedTags () { return false; }; // for I2CP, override in ElGamalAESSession
 			virtual bool MessageConfirmed (uint32_t msgID);
 			virtual bool IsRatchets () const { return false; };
 			virtual bool IsReadyToSend () const { return true; };
 			virtual bool IsTerminated () const { return !GetOwner (); };
 			virtual uint64_t GetLastActivityTimestamp () const { return 0; }; // non-zero for rathets only
-			virtual void SetAckRequestInterval (int interval) {}; // in milliseconds, override in ECIESX25519AEADRatchetSession
-			
+
 			void SetLeaseSetUpdated ()
 			{
 				if (m_LeaseSetUpdateStatus != eLeaseSetDoNotSend) m_LeaseSetUpdateStatus = eLeaseSetUpdated;
@@ -206,7 +206,6 @@ namespace garlic
 			std::map<uint32_t, std::unique_ptr<UnconfirmedTags> > m_UnconfirmedTagsMsgs; // msgID->tags
 
 			i2p::crypto::CBCEncryption m_Encryption;
-			i2p::data::Tag<16> m_IV;
 
 		public:
 
@@ -222,7 +221,7 @@ namespace garlic
 	struct ECIESX25519AEADRatchetIndexTagset
 	{
 		int index;
-		ReceiveRatchetTagSetPtr tagset; // null if used
+		ReceiveRatchetTagSetPtr tagset;
 	};
 
 	class GarlicDestination: public i2p::data::LocalDestination
@@ -237,17 +236,11 @@ namespace garlic
 			int GetNumTags () const { return m_NumTags; };
 			void SetNumRatchetInboundTags (int numTags) { m_NumRatchetInboundTags = numTags; };
 			int GetNumRatchetInboundTags () const { return m_NumRatchetInboundTags; };
-			std::shared_ptr<GarlicRoutingSession> GetRoutingSession (std::shared_ptr<const i2p::data::RoutingDestination> destination,
-				bool attachLeaseSet, bool requestNewIfNotFound = true);
+			std::shared_ptr<GarlicRoutingSession> GetRoutingSession (std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet);
 			void CleanupExpiredTags ();
 			void RemoveDeliveryStatusSession (uint32_t msgID);
 			std::shared_ptr<I2NPMessage> WrapMessageForRouter (std::shared_ptr<const i2p::data::RouterInfo> router,
 				std::shared_ptr<I2NPMessage> msg);
-			
-			bool AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
-				const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); 
-			bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
-				const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); 
 
 			void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag
 			void AddECIESx25519Key (const uint8_t * key, uint64_t tag); // one tag
@@ -257,36 +250,30 @@ namespace garlic
 			uint64_t AddECIESx25519SessionNextTag (ReceiveRatchetTagSetPtr tagset);
 			void AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session);
 			void RemoveECIESx25519Session (const uint8_t * staticKey);
-			void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len, ECIESX25519AEADRatchetSession * from);
+			void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len);
 			uint8_t * GetPayloadBuffer ();
 
 			virtual void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
 			virtual void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
-			virtual void SetLeaseSetUpdated (bool post = false);
+			virtual void SetLeaseSetUpdated ();
 
 			virtual std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet () = 0; // TODO
 			virtual std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const = 0;
-			virtual i2p::data::CryptoKeyType GetRatchetsHighestCryptoType () const 
-			{
-				return GetIdentity ()->GetCryptoKeyType () >= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? GetIdentity ()->GetCryptoKeyType () : 0;
-			}	
 
 		protected:
 
 			void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag
 			bool HandleECIESx25519TagMessage (uint8_t * buf, size_t len); // return true if found
 			virtual void HandleI2NPMessage (const uint8_t * buf, size_t len) = 0; // called from clove only
-			virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, 
-				size_t len, uint32_t msgID, ECIESX25519AEADRatchetSession * from) = 0;
+			virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) = 0;
 			void HandleGarlicMessage (std::shared_ptr<I2NPMessage> msg);
 			void HandleDeliveryStatusMessage (uint32_t msgID);
 
 			void SaveTags ();
 			void LoadTags ();
-			
+
 		private:
 
-			bool SupportsRatchets () const { return GetRatchetsHighestCryptoType () > 0; }
 			void HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<AESDecryption> decryption,
 				std::shared_ptr<i2p::tunnel::InboundTunnel> from);
 			void HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);
@@ -299,18 +286,15 @@ namespace garlic
 			std::unordered_map<i2p::data::IdentHash, ElGamalAESSessionPtr> m_Sessions;
 			std::unordered_map<i2p::data::Tag<32>, ECIESX25519AEADRatchetSessionPtr> m_ECIESx25519Sessions; // static key -> session
 			uint8_t * m_PayloadBuffer; // for ECIESX25519AEADRatchet
-			uint64_t m_LastIncomingSessionTimestamp; // in milliseconds
 			// incoming
 			int m_NumRatchetInboundTags;
 			std::unordered_map<SessionTag, std::shared_ptr<AESDecryption>, std::hash<i2p::data::Tag<32> > > m_Tags;
 			std::unordered_map<uint64_t, ECIESX25519AEADRatchetIndexTagset> m_ECIESx25519Tags; // session tag -> session
+			ReceiveRatchetTagSetPtr m_LastTagset; // tagset last message came for
 			// DeliveryStatus
 			std::mutex m_DeliveryStatusSessionsMutex;
 			std::unordered_map<uint32_t, GarlicRoutingSessionPtr> m_DeliveryStatusSessions; // msgID -> session
-			// encryption
-			i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor;
-			i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor;
-			
+
 		public:
 
 			// for HTTP only
diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp
index 3cd5c193..f4c3dcb9 100644
--- a/libi2pd/HTTP.cpp
+++ b/libi2pd/HTTP.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -10,7 +10,6 @@
 #include <utility>
 #include <stdio.h>
 #include <ctime>
-#include <charconv>
 #include "util.h"
 #include "Base.h"
 #include "HTTP.h"
@@ -19,68 +18,58 @@ namespace i2p
 {
 namespace http
 {
-	// list of valid HTTP methods
-	static constexpr std::array<std::string_view, 16> HTTP_METHODS = 
-	{
+	const std::vector<std::string> HTTP_METHODS = {
 		"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "CONNECT", // HTTP basic methods
 		"COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK", "SEARCH" // WebDAV methods, for SEARCH see rfc5323
 	};
-
-	// list of valid HTTP versions
-	static constexpr std::array<std::string_view, 2> HTTP_VERSIONS = 
-	{
+	const std::vector<std::string> HTTP_VERSIONS = {
 		"HTTP/1.0", "HTTP/1.1"
 	};
-	
-	static constexpr std::array<const char *, 7> weekdays = 
-	{
+	const std::vector<const char *> weekdays = {
 		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
 	};
-	
-	static constexpr std::array<const char *, 12> months = 
-	{
+	const std::vector<const char *> months = {
 		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
 		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
 	};
 
-	static inline bool is_http_version(std::string_view str) 
-	{
+	inline bool is_http_version(const std::string & str) {
 		return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS);
 	}
 
-	static inline bool is_http_method(std::string_view str) 
-	{
+	inline bool is_http_method(const std::string & str) {
 		return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS);
 	}
-	
-	static void strsplit(std::string_view line, std::vector<std::string_view> &tokens, char delim, std::size_t limit = 0) 
-	{	
-		size_t count = 0, pos;
-		while ((pos = line.find (delim)) != line.npos)
-		{
+
+	void strsplit(const std::string & line, std::vector<std::string> &tokens, char delim, std::size_t limit = 0) {
+		std::size_t count = 0;
+		std::stringstream ss(line);
+		std::string token;
+		while (1) {
 			count++;
-			if (limit > 0 && count >= limit) delim = '\n'; // reset delimiter
-			tokens.push_back (line.substr (0, pos));
-			line = line.substr (pos + 1);	
+			if (limit > 0 && count >= limit)
+				delim = '\n'; /* reset delimiter */
+			if (!std::getline(ss, token, delim))
+				break;
+			tokens.push_back(token);
 		}
-		if (!line.empty ()) tokens.push_back (line);
 	}
-	
-	static std::pair<std::string, std::string> parse_header_line(std::string_view line)
+
+	static std::pair<std::string, std::string> parse_header_line(const std::string& line)
 	{
 		std::size_t pos = 0;
 		std::size_t len = 1; /*: */
 		std::size_t max = line.length();
 		if ((pos = line.find(':', pos)) == std::string::npos)
-			return std::pair{"", ""}; // no ':' found
+			return std::make_pair("", ""); // no ':' found
 		if (pos + 1 < max) // ':' at the end of header is valid
 		{
 			while ((pos + len) < max && isspace(line.at(pos + len)))
 				len++;
 			if (len == 1)
-				return std::pair{"", ""}; // no following space, but something else
+				return std::make_pair("", ""); // no following space, but something else
 		}
-		return std::pair{std::string (line.substr(0, pos)), std::string (line.substr(pos + len))};
+		return std::make_pair(line.substr(0, pos), line.substr(pos + len));
 	}
 
 	void gen_rfc7231_date(std::string & out) {
@@ -94,18 +83,15 @@ namespace http
 		out = buf;
 	}
 
-	bool URL::parse(const char *str, std::size_t len) 
-	{
-		return parse({str, len ? len : strlen(str)});
+	bool URL::parse(const char *str, std::size_t len) {
+		std::string url(str, len ? len : strlen(str));
+		return parse(url);
 	}
 
-	bool URL::parse(std::string_view url) 
-	{
-		if (url.empty ()) return false;
+	bool URL::parse(const std::string& url) {
 		std::size_t pos_p = 0; /* < current parse position */
 		std::size_t pos_c = 0; /* < work position */
-		if(url.at(0) != '/' || pos_p > 0) 
-		{
+		if(url.at(0) != '/' || pos_p > 0) {
 			std::size_t pos_s = 0;
 
 			/* schema */
@@ -155,7 +141,7 @@ namespace http
 				/* port[/path] */
 				pos_p = pos_c + 1;
 				pos_c = url.find('/', pos_p);
-				std::string_view port_str = (pos_c == std::string::npos)
+				std::string port_str = (pos_c == std::string::npos)
 					? url.substr(pos_p, std::string::npos)
 					: url.substr(pos_p, pos_c - pos_p);
 				/* stoi throws exception on failure, we don't need it */
@@ -209,9 +195,8 @@ namespace http
 		return true;
 	}
 
-	bool URL::parse_query(std::map<std::string, std::string> & params) 
-	{
-		std::vector<std::string_view> tokens;
+	bool URL::parse_query(std::map<std::string, std::string> & params) {
+		std::vector<std::string> tokens;
 		strsplit(query, tokens, '&');
 
 		params.clear();
@@ -268,7 +253,7 @@ namespace http
 		return host.rfind(".i2p") == ( host.size() - 4 );
 	}
 
-	void HTTPMsg::add_header(const char *name, const std::string & value, bool replace) {
+	void HTTPMsg::add_header(const char *name, std::string & value, bool replace) {
 		add_header(name, value.c_str(), replace);
 	}
 
@@ -287,13 +272,12 @@ namespace http
 		headers.erase(name);
 	}
 
-	int HTTPReq::parse(const char *buf, size_t len) 
-	{
-		return parse({buf, len});
+	int HTTPReq::parse(const char *buf, size_t len) {
+		std::string str(buf, len);
+		return parse(str);
 	}
 
-	int HTTPReq::parse(std::string_view str) 
-	{
+	int HTTPReq::parse(const std::string& str) {
 		enum { REQ_LINE, HEADER_LINE } expect = REQ_LINE;
 		std::size_t eoh = str.find(HTTP_EOH); /* request head size */
 		std::size_t eol = 0, pos = 0;
@@ -302,14 +286,11 @@ namespace http
 		if (eoh == std::string::npos)
 			return 0; /* str not contains complete request */
 
-		while ((eol = str.find(CRLF, pos)) != std::string::npos) 
-		{
-			if (expect == REQ_LINE) 
-			{
-				std::string_view line = str.substr(pos, eol - pos);
-				std::vector<std::string_view> tokens;
+		while ((eol = str.find(CRLF, pos)) != std::string::npos) {
+			if (expect == REQ_LINE) {
+				std::string line = str.substr(pos, eol - pos);
+				std::vector<std::string> tokens;
 				strsplit(line, tokens, ' ');
-				
 				if (tokens.size() != 3)
 					return -1;
 				if (!is_http_method(tokens[0]))
@@ -326,18 +307,18 @@ namespace http
 			}
 			else
 			{
-				std::string_view line = str.substr(pos, eol - pos);
+				std::string line = str.substr(pos, eol - pos);
 				auto p = parse_header_line(line);
 				if (p.first.length () > 0)
 					headers.push_back (p);
 				else
 					return -1;
 			}
-			pos = eol + CRLF.length();
+			pos = eol + strlen(CRLF);
 			if (pos >= eoh)
 				break;
 		}
-		return eoh + HTTP_EOH.length();
+		return eoh + strlen(HTTP_EOH);
 	}
 
 	void HTTPReq::write(std::ostream & o)
@@ -381,7 +362,7 @@ namespace http
 		}
 	}
 
-	std::string HTTPReq::GetHeader (std::string_view name) const
+	std::string HTTPReq::GetHeader (const std::string& name) const
 	{
 		for (auto& it : headers)
 			if (it.first == name)
@@ -389,7 +370,7 @@ namespace http
 		return "";
 	}
 
-	size_t HTTPReq::GetNumHeaders (std::string_view name) const
+	size_t HTTPReq::GetNumHeaders (const std::string& name) const
 	{
 		size_t num = 0;
 		for (auto& it : headers)
@@ -432,13 +413,12 @@ namespace http
 		return length;
 	}
 
-	int HTTPRes::parse(const char *buf, size_t len) 
-	{
-		return parse({buf,len});
+	int HTTPRes::parse(const char *buf, size_t len) {
+		std::string str(buf, len);
+		return parse(str);
 	}
 
-	int HTTPRes::parse(std::string_view str) 
-	{
+	int HTTPRes::parse(const std::string& str) {
 		enum { RES_LINE, HEADER_LINE } expect = RES_LINE;
 		std::size_t eoh = str.find(HTTP_EOH); /* request head size */
 		std::size_t eol = 0, pos = 0;
@@ -446,41 +426,35 @@ namespace http
 		if (eoh == std::string::npos)
 			return 0; /* str not contains complete request */
 
-		while ((eol = str.find(CRLF, pos)) != std::string::npos) 
-		{
-			if (expect == RES_LINE) 
-			{
-				std::string_view line = str.substr(pos, eol - pos);
-				std::vector<std::string_view> tokens;
+		while ((eol = str.find(CRLF, pos)) != std::string::npos) {
+			if (expect == RES_LINE) {
+				std::string line = str.substr(pos, eol - pos);
+				std::vector<std::string> tokens;
 				strsplit(line, tokens, ' ', 3);
 				if (tokens.size() != 3)
 					return -1;
 				if (!is_http_version(tokens[0]))
 					return -1;
-				auto res = std::from_chars(tokens[1].data (), tokens[1].data() + tokens[1].size(), code);
-				if (res.ec != std::errc())
-					return -1;
+				code = atoi(tokens[1].c_str());
 				if (code < 100 || code >= 600)
 					return -1;
 				/* all ok */
 				version = tokens[0];
 				status  = tokens[2];
 				expect  = HEADER_LINE;
-			} 
-			else 
-			{
-				std::string_view line = str.substr(pos, eol - pos);
+			} else {
+				std::string line = str.substr(pos, eol - pos);
 				auto p = parse_header_line(line);
 				if (p.first.length () > 0)
 					headers.insert (p);
 				else
 					return -1;
 			}
-			pos = eol + CRLF.length();
+			pos = eol + strlen(CRLF);
 			if (pos >= eoh)
 				break;
 		}
-		return eoh + HTTP_EOH.length();
+		return eoh + strlen(HTTP_EOH);
 	}
 
 	std::string HTTPRes::to_string() {
@@ -505,11 +479,9 @@ namespace http
 		return ss.str();
 	}
 
-	std::string_view HTTPCodeToStatus(int code) 
-	{
-		std::string_view ptr;
-		switch (code) 
-		{
+	const char * HTTPCodeToStatus(int code) {
+		const char *ptr;
+		switch (code) {
 			case 105: ptr = "Name Not Resolved"; break;
 			/* success */
 			case 200: ptr = "OK"; break;
@@ -536,14 +508,14 @@ namespace http
 		return ptr;
 	}
 
-	std::string UrlDecode(std::string_view data, bool allow_null)
+	std::string UrlDecode(const std::string& data, bool allow_null)
 	{
 		std::string decoded(data);
 		size_t pos = 0;
 		while ((pos = decoded.find('%', pos)) != std::string::npos)
 		{
-			char c = std::stol(decoded.substr(pos + 1, 2), nullptr, 16);
-			if (!c && !allow_null)
+			char c = strtol(decoded.substr(pos + 1, 2).c_str(), NULL, 16);
+			if (c == '\0' && !allow_null)
 			{
 				pos += 3;
 				continue;
diff --git a/libi2pd/HTTP.h b/libi2pd/HTTP.h
index c65c1ce4..41f0560a 100644
--- a/libi2pd/HTTP.h
+++ b/libi2pd/HTTP.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -14,15 +14,16 @@
 #include <list>
 #include <sstream>
 #include <string>
-#include <string_view>
 #include <vector>
 
 namespace i2p
 {
 namespace http
 {
-	constexpr std::string_view CRLF = "\r\n";         /**< HTTP line terminator */
-	constexpr std::string_view HTTP_EOH = "\r\n\r\n"; /**< HTTP end-of-headers mark */
+	const char CRLF[] = "\r\n";         /**< HTTP line terminator */
+	const char HTTP_EOH[] = "\r\n\r\n"; /**< HTTP end-of-headers mark */
+	extern const std::vector<std::string> HTTP_METHODS;  /**< list of valid HTTP methods */
+	extern const std::vector<std::string> HTTP_VERSIONS; /**< list of valid HTTP versions */
 
 	struct URL
 	{
@@ -44,7 +45,7 @@ namespace http
 		 * @return true on success, false on invalid url
 		 */
 		bool parse (const char *str, std::size_t len = 0);
-		bool parse (std::string_view url);
+		bool parse (const std::string& url);
 
 		/**
 		 * @brief Parse query part of url to key/value map
@@ -68,7 +69,7 @@ namespace http
 	{
 		std::map<std::string, std::string> headers;
 
-		void add_header(const char *name, const std::string & value, bool replace = false);
+		void add_header(const char *name, std::string & value, bool replace = false);
 		void add_header(const char *name, const char *value, bool replace = false);
 		void del_header(const char *name);
 
@@ -91,7 +92,7 @@ namespace http
 		 * @note Positive return value is a size of header
 		 */
 		int parse(const char *buf, size_t len);
-		int parse(std::string_view buf);
+		int parse(const std::string& buf);
 
 		/** @brief Serialize HTTP request to string */
 		std::string to_string();
@@ -101,8 +102,8 @@ namespace http
 		void UpdateHeader (const std::string& name, const std::string& value);
 		void RemoveHeader (const std::string& name, const std::string& exempt); // remove all headers starting with name, but exempt
 		void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); };
-		std::string GetHeader (std::string_view name) const;
-		size_t GetNumHeaders (std::string_view name) const;
+		std::string GetHeader (const std::string& name) const;
+		size_t GetNumHeaders (const std::string& name) const;
 		size_t GetNumHeaders () const { return headers.size (); };
 	};
 
@@ -127,7 +128,7 @@ namespace http
 		 * @note Positive return value is a size of header
 		 */
 		int parse(const char *buf, size_t len);
-		int parse(const std::string_view buf);
+		int parse(const std::string& buf);
 
 		/**
 		 * @brief Serialize HTTP response to string
@@ -152,7 +153,7 @@ namespace http
 	 * @param code HTTP code [100, 599]
 	 * @return Immutable string with status
 	 */
-	std::string_view HTTPCodeToStatus(int code);
+	const char * HTTPCodeToStatus(int code);
 
 	/**
 	 * @brief Replaces %-encoded characters in string with their values
@@ -160,7 +161,7 @@ namespace http
 	 * @param null If set to true - decode also %00 sequence, otherwise - skip
 	 * @return Decoded string
 	 */
-	std::string UrlDecode(std::string_view data, bool null = false);
+	std::string UrlDecode(const std::string& data, bool null = false);
 
 	/**
 	 * @brief Merge HTTP response content with Transfer-Encoding: chunked
diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp
index e97a3596..b8147555 100644
--- a/libi2pd/I2NPProtocol.cpp
+++ b/libi2pd/I2NPProtocol.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -10,15 +10,20 @@
 #include <atomic>
 #include "Base.h"
 #include "Log.h"
+#include "Crypto.h"
 #include "I2PEndian.h"
 #include "Timestamp.h"
 #include "RouterContext.h"
 #include "NetDb.hpp"
 #include "Tunnel.h"
-#include "TransitTunnel.h"
+#include "Transports.h"
+#include "Garlic.h"
+#include "ECIESX25519AEADRatchetSession.h"
 #include "I2NPProtocol.h"
 #include "version.h"
 
+using namespace i2p::transport;
+
 namespace i2p
 {
 	std::shared_ptr<I2NPMessage> NewI2NPMessage ()
@@ -67,15 +72,11 @@ namespace i2p
 		SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + I2NP_MESSAGE_EXPIRATION_TIMEOUT);
 	}
 
-	bool I2NPMessage::IsExpired (uint64_t ts) const
-	{
-		auto exp = GetExpiration ();
-		return (ts > exp + I2NP_MESSAGE_CLOCK_SKEW) || (ts < exp - 3*I2NP_MESSAGE_CLOCK_SKEW); // check if expired or too far in future
-	}	
-	
 	bool I2NPMessage::IsExpired () const
 	{
-		return IsExpired (i2p::util::GetMillisecondsSinceEpoch ());
+		auto ts = i2p::util::GetMillisecondsSinceEpoch ();
+		auto exp = GetExpiration ();
+		return (ts > exp + I2NP_MESSAGE_CLOCK_SKEW) || (ts < exp - 3*I2NP_MESSAGE_CLOCK_SKEW); // check if expired or too far in future
 	}
 
 	std::shared_ptr<I2NPMessage> CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID)
@@ -110,17 +111,6 @@ namespace i2p
 		return newMsg;
 	}
 
-	std::shared_ptr<I2NPMessage> CreateTunnelTestMsg (uint32_t msgID)
-	{
-		auto m = NewI2NPShortMessage ();
-		uint8_t * buf = m->GetPayload ();
-		htobe32buf (buf + TUNNEL_TEST_MSGID_OFFSET, msgID);
-		htobe64buf (buf + TUNNEL_TEST_TIMESTAMP_OFFSET, i2p::util::GetMonotonicMicroseconds ());
-		m->len += TUNNEL_TEST_SIZE;
-		m->FillI2NPMessageHeader (eI2NPTunnelTest);
-		return m;
-	}
-
 	std::shared_ptr<I2NPMessage> CreateDeliveryStatusMsg (uint32_t msgID)
 	{
 		auto m = NewI2NPShortMessage ();
@@ -142,7 +132,7 @@ namespace i2p
 	}
 
 	std::shared_ptr<I2NPMessage> CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from,
-		uint32_t replyTunnelID, bool exploratory, std::unordered_set<i2p::data::IdentHash> * excludedPeers)
+		uint32_t replyTunnelID, bool exploratory, std::set<i2p::data::IdentHash> * excludedPeers)
 	{
 		int cnt = excludedPeers ? excludedPeers->size () : 0;
 		auto m = cnt > 7 ? NewI2NPMessage () : NewI2NPShortMessage ();
@@ -187,7 +177,7 @@ namespace i2p
 	}
 
 	std::shared_ptr<I2NPMessage> CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest,
-		const std::unordered_set<i2p::data::IdentHash>& excludedFloodfills,
+		const std::set<i2p::data::IdentHash>& excludedFloodfills,
 		std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel, const uint8_t * replyKey,
 			const uint8_t * replyTag, bool replyECIES)
 	{
@@ -371,6 +361,302 @@ namespace i2p
 		return !msg->GetPayload ()[DATABASE_STORE_TYPE_OFFSET]; // 0- RouterInfo
 	}
 
+	static bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText)
+	{
+		for (int i = 0; i < num; i++)
+		{
+			uint8_t * record = records + i*TUNNEL_BUILD_RECORD_SIZE;
+			if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16))
+			{
+				LogPrint (eLogDebug, "I2NP: Build request record ", i, " is ours");
+				if (!i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) return false;
+				uint8_t retCode = 0;
+				// replace record to reply
+				if (i2p::context.AcceptsTunnels () && !i2p::context.IsHighCongestion ())
+				{
+					auto transitTunnel = i2p::tunnel::CreateTransitTunnel (
+							bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET),
+							clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
+							bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
+							clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET,
+							clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET,
+							clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG,
+							clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG);
+					if (!i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel))
+						retCode = 30;
+				}
+				else
+					retCode = 30; // always reject with bandwidth reason (30)
+
+				memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options
+				record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode;
+				// encrypt reply
+				i2p::crypto::CBCEncryption encryption;
+				for (int j = 0; j < num; j++)
+				{
+					uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE;
+					if (j == i)
+					{
+						uint8_t nonce[12];
+						memset (nonce, 0, 12);
+						auto& noiseState = i2p::context.GetCurrentNoiseState ();
+						if (!i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16,
+							noiseState.m_H, 32, noiseState.m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt
+						{
+							LogPrint (eLogWarning, "I2NP: Reply AEAD encryption failed");
+							return false;
+						}
+					}
+					else
+					{
+						encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET);
+						encryption.SetIV (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET);
+						encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply);
+					}
+				}
+				return true;
+			}
+		}
+		return false;
+	}
+
+	static void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len)
+	{
+		int num = buf[0];
+		LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records");
+		if (num > i2p::tunnel::MAX_NUM_RECORDS)
+		{
+			LogPrint (eLogError, "I2NP: Too many records in VaribleTunnelBuild message ", num);
+			return;
+		}
+		if (len < num*TUNNEL_BUILD_RECORD_SIZE + 1)
+		{
+			LogPrint (eLogError, "I2NP: VaribleTunnelBuild message of ", num, " records is too short ", len);
+			return;
+		}
+
+		auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID);
+		if (tunnel)
+		{
+			// endpoint of inbound tunnel
+			LogPrint (eLogDebug, "I2NP: VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ());
+			if (tunnel->HandleTunnelBuildResponse (buf, len))
+			{
+				LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created");
+				tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
+				i2p::tunnel::tunnels.AddInboundTunnel (tunnel);
+			}
+			else
+			{
+				LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined");
+				tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
+			}
+		}
+		else
+		{
+			uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE];
+			if (HandleBuildRequestRecords (num, buf + 1, clearText))
+			{
+				if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel
+				{
+					// so we send it to reply tunnel
+					transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
+						CreateTunnelGatewayMsg (bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
+							eI2NPVariableTunnelBuildReply, buf, len,
+							bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
+				}
+				else
+					transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
+						CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len,
+							bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
+			}
+		}
+	}
+
+	static void HandleTunnelBuildMsg (uint8_t * buf, size_t len)
+	{
+		LogPrint (eLogWarning, "I2NP: TunnelBuild is too old for ECIES router");
+	}
+
+	static void HandleTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len, bool isShort)
+	{
+		int num = buf[0];
+		LogPrint (eLogDebug, "I2NP: TunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID);
+		if (num > i2p::tunnel::MAX_NUM_RECORDS)
+		{
+			LogPrint (eLogError, "I2NP: Too many records in TunnelBuildReply message ", num);
+			return;
+		}
+		size_t recordSize = isShort ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE;
+		if (len < num*recordSize + 1)
+		{
+			LogPrint (eLogError, "I2NP: TunnelBuildReply message of ", num, " records is too short ", len);
+			return;
+		}
+
+		auto tunnel = i2p::tunnel::tunnels.GetPendingOutboundTunnel (replyMsgID);
+		if (tunnel)
+		{
+			// reply for outbound tunnel
+			if (tunnel->HandleTunnelBuildResponse (buf, len))
+			{
+				LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been created");
+				tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
+				i2p::tunnel::tunnels.AddOutboundTunnel (tunnel);
+			}
+			else
+			{
+				LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been declined");
+				tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
+			}
+		}
+		else
+			LogPrint (eLogWarning, "I2NP: Pending tunnel for message ", replyMsgID, " not found");
+	}
+
+	static void HandleShortTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len)
+	{
+		int num = buf[0];
+		LogPrint (eLogDebug, "I2NP: ShortTunnelBuild ", num, " records");
+		if (num > i2p::tunnel::MAX_NUM_RECORDS)
+		{
+			LogPrint (eLogError, "I2NP: Too many records in ShortTunnelBuild message ", num);
+			return;
+		}
+		if (len < num*SHORT_TUNNEL_BUILD_RECORD_SIZE + 1)
+		{
+			LogPrint (eLogError, "I2NP: ShortTunnelBuild message of ", num, " records is too short ", len);
+			return;
+		}
+		auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID);
+		if (tunnel)
+		{
+			// endpoint of inbound tunnel
+			LogPrint (eLogDebug, "I2NP: ShortTunnelBuild reply for tunnel ", tunnel->GetTunnelID ());
+			if (tunnel->HandleTunnelBuildResponse (buf, len))
+			{
+				LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created");
+				tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
+				i2p::tunnel::tunnels.AddInboundTunnel (tunnel);
+			}
+			else
+			{
+				LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined");
+				tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
+			}
+			return;
+		}
+		const uint8_t * record = buf + 1;
+		for (int i = 0; i < num; i++)
+		{
+			if (!memcmp (record, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16))
+			{
+				LogPrint (eLogDebug, "I2NP: Short request record ", i, " is ours");
+				uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE];
+				if (!i2p::context.DecryptTunnelShortRequestRecord (record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText))
+				{
+					LogPrint (eLogWarning, "I2NP: Can't decrypt short request record ", i);
+					return;
+				}
+				if (clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE]) // not AES
+				{
+					LogPrint (eLogWarning, "I2NP: Unknown layer encryption type ", clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE], " in short request record");
+					return;
+				}
+				auto& noiseState = i2p::context.GetCurrentNoiseState ();
+				uint8_t replyKey[32]; // AEAD/Chacha20/Poly1305
+				i2p::crypto::AESKey layerKey, ivKey; // AES
+				i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelReplyKey", noiseState.m_CK);
+				memcpy (replyKey, noiseState.m_CK + 32, 32);
+				i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelLayerKey", noiseState.m_CK);
+				memcpy (layerKey, noiseState.m_CK + 32, 32);
+				bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG;
+				if (isEndpoint)
+				{
+					i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "TunnelLayerIVKey", noiseState.m_CK);
+					memcpy (ivKey, noiseState.m_CK + 32, 32);
+				}
+				else
+					memcpy (ivKey, noiseState.m_CK , 32);
+
+				// check if we accept this tunnel
+				uint8_t retCode = 0;
+				if (!i2p::context.AcceptsTunnels () || i2p::context.IsHighCongestion ())
+					retCode = 30;
+				if (!retCode)
+				{
+					// create new transit tunnel
+					auto transitTunnel = i2p::tunnel::CreateTransitTunnel (
+						bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET),
+						clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET,
+						bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
+						layerKey, ivKey,
+						clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG,
+						clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG);
+					if (!i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel))
+						retCode = 30;
+				}
+
+				// encrypt reply
+				uint8_t nonce[12];
+				memset (nonce, 0, 12);
+				uint8_t * reply = buf + 1;
+				for (int j = 0; j < num; j++)
+				{
+					nonce[4] = j; // nonce is record #
+					if (j == i)
+					{
+						memset (reply + SHORT_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options
+						reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = retCode;
+						if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16,
+							noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt
+						{
+							LogPrint (eLogWarning, "I2NP: Short reply AEAD encryption failed");
+							return;
+						}
+					}
+					else
+						i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, reply);
+					reply += SHORT_TUNNEL_BUILD_RECORD_SIZE;
+				}
+				// send reply
+				if (isEndpoint)
+				{
+					auto replyMsg = NewI2NPShortMessage ();
+					replyMsg->Concat (buf, len);
+					replyMsg->FillI2NPMessageHeader (eI2NPShortTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET));
+					if (memcmp ((const uint8_t *)i2p::context.GetIdentHash (),
+						clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // reply IBGW is not local?
+					{
+						i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK);
+						uint64_t tag;
+						memcpy (&tag, noiseState.m_CK, 8);
+						// we send it to reply tunnel
+						transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET,
+						CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
+							i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag)));
+					}
+					else
+					{
+						// IBGW is local
+						uint32_t tunnelID = bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET);
+						auto tunnel = i2p::tunnel::tunnels.GetTunnel (tunnelID);
+						if (tunnel)
+							tunnel->SendTunnelDataMsg (replyMsg);
+						else
+							LogPrint (eLogWarning, "I2NP: Tunnel ", tunnelID, " not found for short tunnel build reply");
+					}
+				}
+				else
+					transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET,
+						CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len,
+							bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
+				return;
+			}
+			record += SHORT_TUNNEL_BUILD_RECORD_SIZE;
+		}
+	}
+
 	std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (const uint8_t * buf)
 	{
 		auto msg = NewI2NPTunnelMessage (false);
@@ -424,11 +710,7 @@ namespace i2p
 			return msg;
 		}
 		else
-		{	
-			auto newMsg = CreateTunnelGatewayMsg (tunnelID, msg->GetBuffer (), msg->GetLength ());
-			if (msg->onDrop) newMsg->onDrop = msg->onDrop; 
-			return newMsg;
-		}	
+			return CreateTunnelGatewayMsg (tunnelID, msg->GetBuffer (), msg->GetLength ());
 	}
 
 	std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType,
@@ -466,6 +748,41 @@ namespace i2p
 		return l;
 	}
 
+	void HandleTunnelBuildI2NPMessage (std::shared_ptr<I2NPMessage> msg)
+	{
+		if (msg)
+		{
+			uint8_t typeID = msg->GetTypeID();
+			uint32_t msgID = msg->GetMsgID();
+			LogPrint (eLogDebug, "I2NP: Handling tunnel build message with len=", msg->GetLength(),", type=", (int)typeID, ", msgID=", (unsigned int)msgID);
+			uint8_t * payload = msg->GetPayload();
+			auto size = msg->GetPayloadLength();
+			switch (typeID)
+			{
+				case eI2NPVariableTunnelBuild:
+					HandleVariableTunnelBuildMsg (msgID, payload, size);
+					break;
+				case eI2NPShortTunnelBuild:
+					HandleShortTunnelBuildMsg (msgID, payload, size);
+					break;
+				case eI2NPVariableTunnelBuildReply:
+					HandleTunnelBuildReplyMsg (msgID, payload, size, false);
+					break;
+				case eI2NPShortTunnelBuildReply:
+					HandleTunnelBuildReplyMsg (msgID, payload, size, true);
+					break;
+				case eI2NPTunnelBuild:
+					HandleTunnelBuildMsg (payload, size);
+					break;
+				case eI2NPTunnelBuildReply:
+					// TODO:
+					break;
+				default:
+					LogPrint (eLogError, "I2NP: Unexpected message with type", (int)typeID, " during handling TBM; skipping");
+			}
+		}
+	}
+
 	void HandleI2NPMessage (std::shared_ptr<I2NPMessage> msg)
 	{
 		if (msg)
@@ -491,14 +808,12 @@ namespace i2p
 					break;
 				}
 				case eI2NPDatabaseStore:
+				case eI2NPDatabaseSearchReply:	
 					// forward to netDb if came directly or through exploratory tunnel as response to our request
 					if (!msg->from || !msg->from->GetTunnelPool () || msg->from->GetTunnelPool ()->IsExploratory ())
 						i2p::data::netdb.PostI2NPMsg (msg);
 				break;
-				case eI2NPDatabaseSearchReply:
-					if (!msg->from || !msg->from->GetTunnelPool () || msg->from->GetTunnelPool ()->IsExploratory ())
-						i2p::data::netdb.PostDatabaseSearchReplyMsg (msg);
-				break;	
+				
 				case eI2NPDatabaseLookup:
 					// forward to netDb if floodfill and came directly
 					if (!msg->from && i2p::context.IsFloodfill ())
@@ -512,10 +827,6 @@ namespace i2p
 						i2p::context.ProcessDeliveryStatusMessage (msg);
 					break;
 				}
-				case eI2NPTunnelTest:
-					if (msg->from && msg->from->GetTunnelPool ())
-						msg->from->GetTunnelPool ()->ProcessTunnelTest (msg);
-				break;
 				case eI2NPVariableTunnelBuild:
 				case eI2NPTunnelBuild:
 				case eI2NPShortTunnelBuild:
@@ -561,8 +872,14 @@ namespace i2p
 	void I2NPMessagesHandler::Flush ()
 	{
 		if (!m_TunnelMsgs.empty ())
+		{
 			i2p::tunnel::tunnels.PostTunnelData (m_TunnelMsgs);
+			m_TunnelMsgs.clear ();
+		}
 		if (!m_TunnelGatewayMsgs.empty ())
+		{
 			i2p::tunnel::tunnels.PostTunnelData (m_TunnelGatewayMsgs);
+			m_TunnelGatewayMsgs.clear ();
+		}
 	}
 }
diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h
index 911a53bf..6c64f2ab 100644
--- a/libi2pd/I2NPProtocol.h
+++ b/libi2pd/I2NPProtocol.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -11,10 +11,8 @@
 
 #include <inttypes.h>
 #include <string.h>
-#include <unordered_set>
+#include <set>
 #include <memory>
-#include <list>
-#include <functional>
 #include "Crypto.h"
 #include "I2PEndian.h"
 #include "Identity.h"
@@ -49,11 +47,6 @@ namespace i2p
 	const size_t DELIVERY_STATUS_TIMESTAMP_OFFSET = DELIVERY_STATUS_MSGID_OFFSET + 4;
 	const size_t DELIVERY_STATUS_SIZE = DELIVERY_STATUS_TIMESTAMP_OFFSET + 8;
 
-	// TunnelTest
-	const size_t TUNNEL_TEST_MSGID_OFFSET = 0;
-	const size_t TUNNEL_TEST_TIMESTAMP_OFFSET = TUNNEL_TEST_MSGID_OFFSET + 4;
-	const size_t TUNNEL_TEST_SIZE = TUNNEL_TEST_TIMESTAMP_OFFSET + 8;
-
 	// DatabaseStore
 	const size_t DATABASE_STORE_KEY_OFFSET = 0;
 	const size_t DATABASE_STORE_TYPE_OFFSET = DATABASE_STORE_KEY_OFFSET + 32;
@@ -108,6 +101,7 @@ namespace i2p
 
 	enum I2NPMessageType
 	{
+		eI2NPDummyMsg = 0,
 		eI2NPDatabaseStore = 1,
 		eI2NPDatabaseLookup = 2,
 		eI2NPDatabaseSearchReply = 3,
@@ -121,8 +115,7 @@ namespace i2p
 		eI2NPVariableTunnelBuild = 23,
 		eI2NPVariableTunnelBuildReply = 24,
 		eI2NPShortTunnelBuild = 25,
-		eI2NPShortTunnelBuildReply = 26,
-		eI2NPTunnelTest = 231
+		eI2NPShortTunnelBuildReply = 26
 	};
 
 	const uint8_t TUNNEL_BUILD_RECORD_GATEWAY_FLAG = 0x80;
@@ -145,16 +138,9 @@ namespace tunnel
 	class TunnelPool;
 }
 
-	const int CONGESTION_LEVEL_MEDIUM = 70;
-	const int CONGESTION_LEVEL_HIGH = 90;
-	const int CONGESTION_LEVEL_FULL = 100;
-
 	const size_t I2NP_MAX_MESSAGE_SIZE = 62708;
 	const size_t I2NP_MAX_SHORT_MESSAGE_SIZE = 4096;
 	const size_t I2NP_MAX_MEDIUM_MESSAGE_SIZE = 16384;
-	const unsigned int I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_FACTOR = 3; // multiples of RTT
-	const unsigned int I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MIN = 200000; // in microseconds
-	const unsigned int I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MAX = 2000000; // in microseconds
 	const unsigned int I2NP_MESSAGE_EXPIRATION_TIMEOUT = 8000; // in milliseconds (as initial RTT)
 	const unsigned int I2NP_MESSAGE_CLOCK_SKEW = 60*1000; // 1 minute in milliseconds
 
@@ -163,11 +149,9 @@ namespace tunnel
 		uint8_t * buf;
 		size_t len, offset, maxLen;
 		std::shared_ptr<i2p::tunnel::InboundTunnel> from;
-		std::function<void ()> onDrop;
-		uint64_t enqueueTime; // monotonic microseconds
 
-		I2NPMessage (): buf (nullptr), len (I2NP_HEADER_SIZE + 2),
-			offset(2), maxLen (0), from (nullptr), enqueueTime (0) {}; // reserve 2 bytes for NTCP header
+		I2NPMessage (): buf (nullptr),len (I2NP_HEADER_SIZE + 2),
+			offset(2), maxLen (0), from (nullptr) {}; // reserve 2 bytes for NTCP header
 
 		// header accessors
 		uint8_t * GetHeader () { return GetBuffer (); };
@@ -177,9 +161,7 @@ namespace tunnel
 		void SetMsgID (uint32_t msgID) { htobe32buf (GetHeader () + I2NP_HEADER_MSGID_OFFSET, msgID); };
 		uint32_t GetMsgID () const { return bufbe32toh (GetHeader () + I2NP_HEADER_MSGID_OFFSET); };
 		void SetExpiration (uint64_t expiration) { htobe64buf (GetHeader () + I2NP_HEADER_EXPIRATION_OFFSET, expiration); };
-		void SetEnqueueTime (uint64_t mts) { enqueueTime = mts; };
 		uint64_t GetExpiration () const { return bufbe64toh (GetHeader () + I2NP_HEADER_EXPIRATION_OFFSET); };
-		uint64_t GetEnqueueTime () const { return enqueueTime; };
 		void SetSize (uint16_t size) { htobe16buf (GetHeader () + I2NP_HEADER_SIZE_OFFSET, size); };
 		uint16_t GetSize () const { return bufbe16toh (GetHeader () + I2NP_HEADER_SIZE_OFFSET); };
 		void UpdateSize () { SetSize (GetPayloadLength ()); };
@@ -259,6 +241,7 @@ namespace tunnel
 			SetSize (len - offset - I2NP_HEADER_SIZE);
 			SetChks (0);
 		}
+
 		void ToNTCP2 ()
 		{
 			uint8_t * ntcp2 = GetNTCP2Header ();
@@ -269,9 +252,6 @@ namespace tunnel
 		void FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID = 0, bool checksum = true);
 		void RenewI2NPMessageHeader ();
 		bool IsExpired () const;
-		bool IsExpired (uint64_t ts) const; // in milliseconds
-
-		void Drop () { if (onDrop) { onDrop (); onDrop = nullptr; }; }
 	};
 
 	template<int sz>
@@ -291,12 +271,11 @@ namespace tunnel
 	std::shared_ptr<I2NPMessage> CreateI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from = nullptr);
 	std::shared_ptr<I2NPMessage> CopyI2NPMessage (std::shared_ptr<I2NPMessage> msg);
 
-	std::shared_ptr<I2NPMessage> CreateTunnelTestMsg (uint32_t msgID);
 	std::shared_ptr<I2NPMessage> CreateDeliveryStatusMsg (uint32_t msgID);
 	std::shared_ptr<I2NPMessage> CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from,
-		uint32_t replyTunnelID, bool exploratory = false, std::unordered_set<i2p::data::IdentHash> * excludedPeers = nullptr);
+		uint32_t replyTunnelID, bool exploratory = false, std::set<i2p::data::IdentHash> * excludedPeers = nullptr);
 	std::shared_ptr<I2NPMessage> CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest,
-		const std::unordered_set<i2p::data::IdentHash>& excludedFloodfills,
+		const std::set<i2p::data::IdentHash>& excludedFloodfills,
 		std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel,
 		const uint8_t * replyKey, const uint8_t * replyTag, bool replyECIES = false);
 	std::shared_ptr<I2NPMessage> CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector<i2p::data::IdentHash> routers);
@@ -316,6 +295,7 @@ namespace tunnel
 	std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr<I2NPMessage> msg);
 
 	size_t GetI2NPMessageLength (const uint8_t * msg, size_t len);
+	void HandleTunnelBuildI2NPMessage (std::shared_ptr<I2NPMessage> msg);
 	void HandleI2NPMessage (std::shared_ptr<I2NPMessage> msg);
 
 	class I2NPMessagesHandler
@@ -328,7 +308,7 @@ namespace tunnel
 
 		private:
 
-			std::list<std::shared_ptr<I2NPMessage> > m_TunnelMsgs, m_TunnelGatewayMsgs;
+			std::vector<std::shared_ptr<I2NPMessage> > m_TunnelMsgs, m_TunnelGatewayMsgs;
 	};
 }
 
diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp
index 865beeb8..3a659c11 100644
--- a/libi2pd/Identity.cpp
+++ b/libi2pd/Identity.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -10,7 +10,6 @@
 #include "I2PEndian.h"
 #include "Log.h"
 #include "Timestamp.h"
-#include "CryptoKey.h"
 #include "Identity.h"
 
 namespace i2p
@@ -28,15 +27,18 @@ namespace data
 
 	size_t Identity::FromBuffer (const uint8_t * buf, size_t len)
 	{
-		if (len < DEFAULT_IDENTITY_SIZE) return 0; // buffer too small, don't overflow
-		memcpy (this, buf, DEFAULT_IDENTITY_SIZE);
+		if ( len < DEFAULT_IDENTITY_SIZE ) {
+			// buffer too small, don't overflow
+			return 0;
+		}
+		memcpy (publicKey, buf, DEFAULT_IDENTITY_SIZE);
 		return DEFAULT_IDENTITY_SIZE;
 	}
 
 	IdentHash Identity::Hash () const
 	{
 		IdentHash hash;
-		SHA256((const uint8_t *)this, DEFAULT_IDENTITY_SIZE, hash);
+		SHA256(publicKey, DEFAULT_IDENTITY_SIZE, hash);
 		return hash;
 	}
 
@@ -120,17 +122,6 @@ namespace data
 					memcpy (m_StandardIdentity.signingKey, signingKey, i2p::crypto::GOSTR3410_512_PUBLIC_KEY_LENGTH);
 					break;
 				}
-#if OPENSSL_PQ
-				case SIGNING_KEY_TYPE_MLDSA44:
-				{
-					memcpy (m_StandardIdentity, signingKey, 384);
-					excessLen = i2p::crypto::MLDSA44_PUBLIC_KEY_LENGTH - 384;
-					excessBuf = new uint8_t[excessLen];
-					memcpy (excessBuf, signingKey + 384, excessLen);
-					cryptoType = 0xFF; // crypto key is not used
-					break;
-				}	
-#endif					
 				default:
 					LogPrint (eLogError, "Identity: Signing key type ", (int)type, " is not supported");
 			}
@@ -143,15 +134,12 @@ namespace data
 			htobe16buf (m_ExtendedBuffer + 2, cryptoType);
 			if (excessLen && excessBuf)
 			{
-				if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE)
+				if (excessLen > MAX_EXTENDED_BUFFER_SIZE - 4)
 				{
-					auto newBuf = new uint8_t[m_ExtendedLen];
-					memcpy (newBuf, m_ExtendedBuffer, 4);
-					memcpy (newBuf + 4, excessBuf, excessLen);
-					m_ExtendedBufferPtr = newBuf;
+					LogPrint (eLogError, "Identity: Unexpected excessive signing key len ", excessLen);
+					excessLen = MAX_EXTENDED_BUFFER_SIZE - 4;
 				}
-				else
-					memcpy (m_ExtendedBuffer + 4, excessBuf, excessLen);
+				memcpy (m_ExtendedBuffer + 4, excessBuf, excessLen);
 				delete[] excessBuf;
 			}
 			// calculate ident hash
@@ -199,8 +187,6 @@ namespace data
 
 	IdentityEx::~IdentityEx ()
 	{
-		if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE)
-			delete[] m_ExtendedBufferPtr;
 	}
 
 	IdentityEx& IdentityEx::operator=(const IdentityEx& other)
@@ -208,29 +194,11 @@ namespace data
 		memcpy (&m_StandardIdentity, &other.m_StandardIdentity, DEFAULT_IDENTITY_SIZE);
 		m_IdentHash = other.m_IdentHash;
 
-		size_t oldLen = m_ExtendedLen;
 		m_ExtendedLen = other.m_ExtendedLen;
 		if (m_ExtendedLen > 0)
 		{
-			if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) 
-			{
-				if (oldLen > MAX_EXTENDED_BUFFER_SIZE) 
-				{
-					if (m_ExtendedLen > oldLen)
-					{	
-						delete[] m_ExtendedBufferPtr;
-						m_ExtendedBufferPtr = new uint8_t[m_ExtendedLen];
-					}	
-				}
-				else
-					m_ExtendedBufferPtr = new uint8_t[m_ExtendedLen];
-				memcpy (m_ExtendedBufferPtr, other.m_ExtendedBufferPtr, m_ExtendedLen);	
-			}	
-			else
-			{
-				if (oldLen > MAX_EXTENDED_BUFFER_SIZE) delete[] m_ExtendedBufferPtr;
-				memcpy (m_ExtendedBuffer, other.m_ExtendedBuffer, m_ExtendedLen);
-			}	
+			if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) m_ExtendedLen = MAX_EXTENDED_BUFFER_SIZE;
+			memcpy (m_ExtendedBuffer, other.m_ExtendedBuffer, m_ExtendedLen);
 		}
 		m_Verifier = nullptr;
 		CreateVerifier ();
@@ -259,28 +227,13 @@ namespace data
 		}
 		memcpy (&m_StandardIdentity, buf, DEFAULT_IDENTITY_SIZE);
 
-		size_t oldLen = m_ExtendedLen;
 		m_ExtendedLen = bufbe16toh (m_StandardIdentity.certificate + 1);
 		if (m_ExtendedLen)
 		{
 			if (m_ExtendedLen + DEFAULT_IDENTITY_SIZE <= len)
 			{
-				if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE)
-				{
-					if (oldLen > MAX_EXTENDED_BUFFER_SIZE) 
-					{
-						if (m_ExtendedLen > oldLen)
-						{	
-							delete[] m_ExtendedBufferPtr;
-							m_ExtendedBufferPtr = new uint8_t[m_ExtendedLen];
-						}	
-					}
-					else
-						m_ExtendedBufferPtr = new uint8_t[m_ExtendedLen];
-					memcpy (m_ExtendedBufferPtr, buf + DEFAULT_IDENTITY_SIZE, m_ExtendedLen);
-				}	
-				else	
-					memcpy (m_ExtendedBuffer, buf + DEFAULT_IDENTITY_SIZE, m_ExtendedLen);
+				if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) m_ExtendedLen = MAX_EXTENDED_BUFFER_SIZE;
+				memcpy (m_ExtendedBuffer, buf + DEFAULT_IDENTITY_SIZE, m_ExtendedLen);
 			}
 			else
 			{
@@ -305,28 +258,27 @@ namespace data
 		if (fullLen > len) return 0; // buffer is too small and may overflow somewhere else
 		memcpy (buf, &m_StandardIdentity, DEFAULT_IDENTITY_SIZE);
 		if (m_ExtendedLen > 0)
-		{	
-			if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE)
-				memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBufferPtr, m_ExtendedLen);
-			else	
-				memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBuffer, m_ExtendedLen);
-		}	
+			memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBuffer, m_ExtendedLen);
 		return fullLen;
 	}
 
-	size_t IdentityEx::FromBase64(std::string_view s)
+	size_t IdentityEx::FromBase64(const std::string& s)
 	{
-		std::vector<uint8_t> buf(s.length ()); // binary data can't exceed base64
-		auto len = Base64ToByteStream (s, buf.data(), buf.size ());
+		const size_t slen = s.length();
+		std::vector<uint8_t> buf(slen); // binary data can't exceed base64
+		const size_t len = Base64ToByteStream (s.c_str(), slen, buf.data(), slen);
 		return FromBuffer (buf.data(), len);
 	}
 
 	std::string IdentityEx::ToBase64 () const
 	{
 		const size_t bufLen = GetFullLen();
+		const size_t strLen = Base64EncodingBufferSize(bufLen);
 		std::vector<uint8_t> buf(bufLen);
+		std::vector<char> str(strLen);
 		size_t l = ToBuffer (buf.data(), bufLen);
-		return i2p::data::ByteStreamToBase64 (buf.data(), l);
+		size_t l1 = i2p::data::ByteStreamToBase64 (buf.data(), l, str.data(), strLen);
+		return std::string (str.data(), l1);
 	}
 
 	size_t IdentityEx::GetSigningPublicKeyLen () const
@@ -339,7 +291,7 @@ namespace data
 	const uint8_t * IdentityEx::GetSigningPublicKeyBuffer () const
 	{
 		auto keyLen = GetSigningPublicKeyLen ();
-		if (keyLen > 128) return nullptr; // P521 or PQ
+		if (keyLen > 128) return nullptr; // P521
 		return m_StandardIdentity.signingKey + 128 - keyLen;
 	}
 
@@ -366,7 +318,7 @@ namespace data
 	SigningKeyType IdentityEx::GetSigningKeyType () const
 	{
 		if (m_StandardIdentity.certificate[0] == CERTIFICATE_TYPE_KEY && m_ExtendedLen >= 2)
-			return bufbe16toh (m_ExtendedLen <= MAX_EXTENDED_BUFFER_SIZE ? m_ExtendedBuffer : m_ExtendedBufferPtr); // signing key
+			return bufbe16toh (m_ExtendedBuffer); // signing key
 		return SIGNING_KEY_TYPE_DSA_SHA1;
 	}
 
@@ -379,7 +331,7 @@ namespace data
 	CryptoKeyType IdentityEx::GetCryptoKeyType () const
 	{
 		if (m_StandardIdentity.certificate[0] == CERTIFICATE_TYPE_KEY && m_ExtendedLen >= 4)
-			return bufbe16toh (m_ExtendedLen <= MAX_EXTENDED_BUFFER_SIZE ? m_ExtendedBuffer + 2 : m_ExtendedBufferPtr + 2); // crypto key
+			return bufbe16toh (m_ExtendedBuffer + 2); // crypto key
 		return CRYPTO_KEY_TYPE_ELGAMAL;
 	}
 
@@ -403,10 +355,6 @@ namespace data
 				return new i2p::crypto::GOSTR3410_512_Verifier (i2p::crypto::eGOSTR3410TC26A512);
 			case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
 				return new i2p::crypto::RedDSA25519Verifier ();
-#if OPENSSL_PQ
-			case SIGNING_KEY_TYPE_MLDSA44:
-				return new i2p::crypto::MLDSA44Verifier ();
-#endif				
 			case SIGNING_KEY_TYPE_RSA_SHA256_2048:
 			case SIGNING_KEY_TYPE_RSA_SHA384_3072:
 			case SIGNING_KEY_TYPE_RSA_SHA512_4096:
@@ -428,18 +376,6 @@ namespace data
 				auto keyLen = verifier->GetPublicKeyLen ();
 				if (keyLen <= 128)
 					verifier->SetPublicKey (m_StandardIdentity.signingKey + 128 - keyLen);
-#if OPENSSL_PQ
-				else if (keyLen > 384)
-				{
-					// for post-quantum
-					uint8_t * signingKey = new uint8_t[keyLen];
-					memcpy (signingKey, m_StandardIdentity, 384);
-					size_t excessLen = keyLen - 384;
-					memcpy (signingKey + 384, m_ExtendedBufferPtr + 4, excessLen); // right after signing and crypto key types
-					verifier->SetPublicKey (signingKey);
-					delete[] signingKey;
-				}	
-#endif				
 				else
 				{
 					// for P521
@@ -463,14 +399,15 @@ namespace data
 				return std::make_shared<i2p::crypto::ElGamalEncryptor>(key);
 			break;
 			case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD:
-			case CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD:
-			case CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD:
-			case CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD:	
 				return std::make_shared<i2p::crypto::ECIESX25519AEADRatchetEncryptor>(key);
 			break;
 			case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC:
+			case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST:
 				return std::make_shared<i2p::crypto::ECIESP256Encryptor>(key);
 			break;
+			case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC:
+				return std::make_shared<i2p::crypto::ECIESGOSTR3410Encryptor>(key);
+			break;
 			default:
 				LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)keyType);
 		};
@@ -483,21 +420,11 @@ namespace data
 		return CreateEncryptor (GetCryptoKeyType (), key);
 	}
 
-	size_t GetIdentityBufferLen (const uint8_t * buf, size_t len)
-	{
-		if (len < DEFAULT_IDENTITY_SIZE) return 0;
-		size_t l = DEFAULT_IDENTITY_SIZE + bufbe16toh (buf + DEFAULT_IDENTITY_SIZE - 2);
-		if (l > len) return 0;
-		return l;
-	}	
-		
 	PrivateKeys& PrivateKeys::operator=(const Keys& keys)
 	{
 		m_Public = std::make_shared<IdentityEx>(Identity (keys));
 		memcpy (m_PrivateKey, keys.privateKey, 256); // 256
-		size_t keyLen = m_Public->GetSigningPrivateKeyLen ();
-		if (keyLen > 128) m_SigningPrivateKey.resize (keyLen);
-		memcpy (m_SigningPrivateKey.data (), keys.signingPrivateKey, keyLen);
+		memcpy (m_SigningPrivateKey, keys.signingPrivateKey, m_Public->GetSigningPrivateKeyLen ());
 		m_OfflineSignature.resize (0);
 		m_TransientSignatureLen = 0;
 		m_TransientSigningPrivateKeyLen = 0;
@@ -513,7 +440,7 @@ namespace data
 		m_OfflineSignature = other.m_OfflineSignature;
 		m_TransientSignatureLen = other.m_TransientSignatureLen;
 		m_TransientSigningPrivateKeyLen = other.m_TransientSigningPrivateKeyLen;
-		m_SigningPrivateKey = other.m_SigningPrivateKey;
+		memcpy (m_SigningPrivateKey, other.m_SigningPrivateKey, m_TransientSigningPrivateKeyLen > 0 ? m_TransientSigningPrivateKeyLen : m_Public->GetSigningPrivateKeyLen ());
 		m_Signer = nullptr;
 		CreateSigner ();
 		return *this;
@@ -536,9 +463,8 @@ namespace data
 		memcpy (m_PrivateKey, buf + ret, cryptoKeyLen);
 		ret += cryptoKeyLen;
 		size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen ();
-		if (signingPrivateKeySize + ret > len) return 0; // overflow
-		m_SigningPrivateKey.resize (signingPrivateKeySize);
-		memcpy (m_SigningPrivateKey.data (), buf + ret, signingPrivateKeySize);
+		if(signingPrivateKeySize + ret > len || signingPrivateKeySize > 128) return 0; // overflow
+		memcpy (m_SigningPrivateKey, buf + ret, signingPrivateKeySize);
 		ret += signingPrivateKeySize;
 		m_Signer = nullptr;
 		// check if signing private key is all zeros
@@ -553,12 +479,7 @@ namespace data
 		{
 			// offline information
 			const uint8_t * offlineInfo = buf + ret;
-			uint32_t expires = bufbe32toh (buf + ret); ret += 4; // expires timestamp
-			if (expires < i2p::util::GetSecondsSinceEpoch ())
-			{
-				LogPrint (eLogError, "Identity: Offline signature expired");
-				return 0;
-			}	
+			ret += 4; // expires timestamp
 			SigningKeyType keyType = bufbe16toh (buf + ret); ret += 2; // key type
 			std::unique_ptr<i2p::crypto::Verifier> transientVerifier (IdentityEx::CreateVerifier (keyType));
 			if (!transientVerifier) return 0;
@@ -579,9 +500,8 @@ namespace data
 			memcpy (m_OfflineSignature.data (), offlineInfo, offlineInfoLen);
 			// override signing private key
 			m_TransientSigningPrivateKeyLen = transientVerifier->GetPrivateKeyLen ();
-			if (m_TransientSigningPrivateKeyLen + ret > len) return 0;
-			if (m_TransientSigningPrivateKeyLen > 128) m_SigningPrivateKey.resize (m_TransientSigningPrivateKeyLen);
-			memcpy (m_SigningPrivateKey.data (), buf + ret, m_TransientSigningPrivateKeyLen);
+			if (m_TransientSigningPrivateKeyLen + ret > len || m_TransientSigningPrivateKeyLen > 128) return 0;
+			memcpy (m_SigningPrivateKey, buf + ret, m_TransientSigningPrivateKeyLen);
 			ret += m_TransientSigningPrivateKeyLen;
 			CreateSigner (keyType);
 		}
@@ -601,7 +521,7 @@ namespace data
 		if (IsOfflineSignature ())
 			memset (buf + ret, 0, signingPrivateKeySize);
 		else
-			memcpy (buf + ret, m_SigningPrivateKey.data (), signingPrivateKeySize);
+			memcpy (buf + ret, m_SigningPrivateKey, signingPrivateKeySize);
 		ret += signingPrivateKeySize;
 		if (IsOfflineSignature ())
 		{
@@ -612,24 +532,32 @@ namespace data
 			ret += offlineSignatureLen;
 			// transient private key
 			if (ret + m_TransientSigningPrivateKeyLen > len) return 0;
-			memcpy (buf + ret, m_SigningPrivateKey.data (), m_TransientSigningPrivateKeyLen);
+			memcpy (buf + ret, m_SigningPrivateKey, m_TransientSigningPrivateKeyLen);
 			ret += m_TransientSigningPrivateKeyLen;
 		}
 		return ret;
 	}
 
-	size_t PrivateKeys::FromBase64(std::string_view s)
+	size_t PrivateKeys::FromBase64(const std::string& s)
 	{
-		std::vector<uint8_t> buf(s.length ());
-		size_t l = i2p::data::Base64ToByteStream (s, buf.data (), buf.size ());
-		return FromBuffer (buf.data (), l);
+		uint8_t * buf = new uint8_t[s.length ()];
+		size_t l = i2p::data::Base64ToByteStream (s.c_str (), s.length (), buf, s.length ());
+		size_t ret = FromBuffer (buf, l);
+		delete[] buf;
+		return ret;
 	}
 
 	std::string PrivateKeys::ToBase64 () const
 	{
-		std::vector<uint8_t> buf(GetFullLen ());
-		size_t l = ToBuffer (buf.data (), buf.size ());
-		return i2p::data::ByteStreamToBase64 (buf.data (), l);
+		uint8_t * buf = new uint8_t[GetFullLen ()];
+		char * str = new char[GetFullLen ()*2];
+		size_t l = ToBuffer (buf, GetFullLen ());
+		size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, str, GetFullLen ()*2);
+		str[l1] = 0;
+		delete[] buf;
+		std::string ret(str);
+		delete[] str;
+		return ret;
 	}
 
 	void PrivateKeys::Sign (const uint8_t * buf, int len, uint8_t * signature) const
@@ -651,13 +579,13 @@ namespace data
 	{
 		if (m_Signer) return;
 		if (keyType == SIGNING_KEY_TYPE_DSA_SHA1)
-			m_Signer.reset (new i2p::crypto::DSASigner (m_SigningPrivateKey.data (), m_Public->GetStandardIdentity ().signingKey));
+			m_Signer.reset (new i2p::crypto::DSASigner (m_SigningPrivateKey, m_Public->GetStandardIdentity ().signingKey));
 		else if (keyType == SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519 && !IsOfflineSignature ())
-			m_Signer.reset (new i2p::crypto::EDDSA25519Signer (m_SigningPrivateKey.data (), m_Public->GetStandardIdentity ().signingKey + (sizeof(Identity::signingKey) - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH))); // TODO: remove public key check
+			m_Signer.reset (new i2p::crypto::EDDSA25519Signer (m_SigningPrivateKey, m_Public->GetStandardIdentity ().signingKey + (sizeof(Identity::signingKey) - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH))); // TODO: remove public key check
 		else
 		{
 			// public key is not required
-			auto signer = CreateSigner (keyType, m_SigningPrivateKey.data ());
+			auto signer = CreateSigner (keyType, m_SigningPrivateKey);
 			if (signer) m_Signer.reset (signer);
 		}
 	}
@@ -692,11 +620,6 @@ namespace data
 			case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
 				return new i2p::crypto::RedDSA25519Signer (priv);
 			break;
-#if OPENSSL_PQ
-			case SIGNING_KEY_TYPE_MLDSA44:
-				return new i2p::crypto::MLDSA44Signer (priv);
-			break;	
-#endif				
 			default:
 				LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported");
 		}
@@ -710,7 +633,8 @@ namespace data
 
 	size_t PrivateKeys::GetPrivateKeyLen () const
 	{
-		return i2p::crypto::GetCryptoPrivateKeyLen (m_Public->GetCryptoKeyType ());
+		// private key length always 256, but type 4
+		return (m_Public->GetCryptoKeyType () == CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) ? 32 : 256;
 	}
 
 	uint8_t * PrivateKeys::GetPadding()
@@ -736,14 +660,15 @@ namespace data
 				return std::make_shared<i2p::crypto::ElGamalDecryptor>(key);
 			break;
 			case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD:
-			case CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD:
-			case CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD:
-			case CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD:	
 				return std::make_shared<i2p::crypto::ECIESX25519AEADRatchetDecryptor>(key);
 			break;
 			case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC:
+			case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST:
 				return std::make_shared<i2p::crypto::ECIESP256Decryptor>(key);
 			break;
+			case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC:
+				return std::make_shared<i2p::crypto::ECIESGOSTR3410Decryptor>(key);
+			break;
 			default:
 				LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)cryptoType);
 		};
@@ -756,10 +681,8 @@ namespace data
 		{
 			PrivateKeys keys;
 			// signature
-			std::unique_ptr<i2p::crypto::Verifier> verifier (IdentityEx::CreateVerifier (type));
-			std::vector<uint8_t> signingPublicKey(verifier->GetPublicKeyLen ()); 
-			keys.m_SigningPrivateKey.resize (verifier->GetPrivateKeyLen ());
-			GenerateSigningKeyPair (type, keys.m_SigningPrivateKey.data (), signingPublicKey.data ());
+			uint8_t signingPublicKey[512]; // signing public key is 512 bytes max
+			GenerateSigningKeyPair (type, keys.m_SigningPrivateKey, signingPublicKey);
 			// encryption
 			uint8_t publicKey[256];
 			if (isDestination)
@@ -767,7 +690,7 @@ namespace data
 			else
 				GenerateCryptoKeyPair (cryptoType, keys.m_PrivateKey, publicKey);
 			// identity
-			keys.m_Public = std::make_shared<IdentityEx> (isDestination ? nullptr : publicKey, signingPublicKey.data (), type, cryptoType);
+			keys.m_Public = std::make_shared<IdentityEx> (isDestination ? nullptr : publicKey, signingPublicKey, type, cryptoType);
 
 			keys.CreateSigner ();
 			return keys;
@@ -792,7 +715,9 @@ namespace data
 			case SIGNING_KEY_TYPE_RSA_SHA384_3072:
 			case SIGNING_KEY_TYPE_RSA_SHA512_4096:
 				LogPrint (eLogWarning, "Identity: RSA signature type is not supported. Creating EdDSA");
+#if (__cplusplus >= 201703L) // C++ 17 or higher
 				[[fallthrough]];
+#endif
 				// no break here
 			case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
 				i2p::crypto::CreateEDDSA25519RandomKeys (priv, pub);
@@ -806,11 +731,6 @@ namespace data
 			case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
 				i2p::crypto::CreateRedDSA25519RandomKeys (priv, pub);
 			break;
-#if OPENSSL_PQ				
-			case SIGNING_KEY_TYPE_MLDSA44:
-				i2p::crypto::CreateMLDSA44RandomKeys (priv, pub);
-			break;	
-#endif				
 			default:
 				LogPrint (eLogWarning, "Identity: Signing key type ", (int)type, " is not supported. Create DSA-SHA1");
 				i2p::crypto::CreateDSARandomKeys (priv, pub); // DSA-SHA1
@@ -825,12 +745,13 @@ namespace data
 				i2p::crypto::GenerateElGamalKeyPair(priv, pub);
 			break;
 			case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC:
+			case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST:
 				i2p::crypto::CreateECIESP256RandomKeys (priv, pub);
 			break;
+			case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC:
+				i2p::crypto::CreateECIESGOSTR3410RandomKeys (priv, pub);
+			break;
 			case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD:
-			case CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD:
-			case CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD:
-			case CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD:	
 				i2p::crypto::CreateECIESX25519AEADRatchetRandomKeys (priv, pub);
 			break;
 			default:
@@ -848,10 +769,9 @@ namespace data
 			keys.m_TransientSigningPrivateKeyLen = verifier->GetPrivateKeyLen ();
 			keys.m_TransientSignatureLen = verifier->GetSignatureLen ();
 			keys.m_OfflineSignature.resize (pubKeyLen + m_Public->GetSignatureLen () + 6);
-			keys.m_SigningPrivateKey.resize (verifier->GetPrivateKeyLen ());
 			htobe32buf (keys.m_OfflineSignature.data (), expires); // expires
 			htobe16buf (keys.m_OfflineSignature.data () + 4, type); // type
-			GenerateSigningKeyPair (type, keys.m_SigningPrivateKey.data (), keys.m_OfflineSignature.data () + 6); // public key
+			GenerateSigningKeyPair (type, keys.m_SigningPrivateKey, keys.m_OfflineSignature.data () + 6); // public key
 			Sign (keys.m_OfflineSignature.data (), pubKeyLen + 6, keys.m_OfflineSignature.data () + 6 + pubKeyLen); // signature
 			// recreate signer
 			keys.m_Signer = nullptr;
@@ -870,14 +790,11 @@ namespace data
 		return keys;
 	}
 
-	IdentHash CreateRoutingKey (const IdentHash& ident, bool nextDay)
+	IdentHash CreateRoutingKey (const IdentHash& ident)
 	{
 		uint8_t buf[41]; // ident + yyyymmdd
 		memcpy (buf, (const uint8_t *)ident, 32);
-		if (nextDay)
-			i2p::util::GetNextDayDate ((char *)(buf + 32));
-		else	
-			i2p::util::GetCurrentDate ((char *)(buf + 32));
+		i2p::util::GetCurrentDate ((char *)(buf + 32));
 		IdentHash key;
 		SHA256(buf, 40, key);
 		return key;
diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h
index c95ce000..97d596d8 100644
--- a/libi2pd/Identity.h
+++ b/libi2pd/Identity.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -12,19 +12,14 @@
 #include <inttypes.h>
 #include <string.h>
 #include <string>
-#include <string_view>
 #include <memory>
 #include <vector>
 #include "Base.h"
 #include "Signature.h"
+#include "CryptoKey.h"
 
 namespace i2p
 {
-namespace crypto
-{
-	class CryptoKeyEncryptor;
-	class CryptoKeyDecryptor;
-}	
 namespace data
 {
 	typedef Tag<32> IdentHash;
@@ -59,8 +54,6 @@ namespace data
 		Identity& operator=(const Keys& keys);
 		size_t FromBuffer (const uint8_t * buf, size_t len);
 		IdentHash Hash () const;
-		operator uint8_t * () { return reinterpret_cast<uint8_t *>(this); }
-		operator const uint8_t * () const { return reinterpret_cast<const uint8_t *>(this); }
 	};
 
 	Keys CreateRandomKeys ();
@@ -70,10 +63,9 @@ namespace data
 	const uint16_t CRYPTO_KEY_TYPE_ELGAMAL = 0;
 	const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC = 1;
 	const uint16_t CRYPTO_KEY_TYPE_ECIES_X25519_AEAD = 4;
-	const uint16_t CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD = 5;
-	const uint16_t CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD = 6;
-	const uint16_t CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD = 7;
-	
+	const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST = 65280; // TODO: remove later
+	const uint16_t CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC = 65281; // TODO: use GOST R 34.11 instead SHA256 and GOST 28147-89 instead AES
+
 	const uint16_t SIGNING_KEY_TYPE_DSA_SHA1 = 0;
 	const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA256_P256 = 1;
 	const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA384_P384 = 2;
@@ -82,12 +74,11 @@ namespace data
 	const uint16_t SIGNING_KEY_TYPE_RSA_SHA384_3072 = 5;
 	const uint16_t SIGNING_KEY_TYPE_RSA_SHA512_4096 = 6;
 	const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519 = 7;
-	const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519ph = 8; // since openssl 3.0.0
+	const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519ph = 8; // not implemented
 	const uint16_t SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256 = 9;
 	const uint16_t SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512 = 10; // approved by FSB
 	const uint16_t SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519 = 11; // for LeaseSet2 only
-	const uint16_t SIGNING_KEY_TYPE_MLDSA44 = 12;
-	
+
 	typedef uint16_t SigningKeyType;
 	typedef uint16_t CryptoKeyType;
 
@@ -108,7 +99,7 @@ namespace data
 
 			size_t FromBuffer (const uint8_t * buf, size_t len);
 			size_t ToBuffer (uint8_t * buf, size_t len) const;
-			size_t FromBase64(std::string_view s);
+			size_t FromBase64(const std::string& s);
 			std::string ToBase64 () const;
 			const Identity& GetStandardIdentity () const { return m_StandardIdentity; };
 
@@ -142,15 +133,9 @@ namespace data
 			IdentHash m_IdentHash;
 			std::unique_ptr<i2p::crypto::Verifier> m_Verifier;
 			size_t m_ExtendedLen;
-			union
-			{	
-				uint8_t m_ExtendedBuffer[MAX_EXTENDED_BUFFER_SIZE]; 
-				uint8_t * m_ExtendedBufferPtr;
-			};	
+			uint8_t m_ExtendedBuffer[MAX_EXTENDED_BUFFER_SIZE];
 	};
 
-	size_t GetIdentityBufferLen (const uint8_t * buf, size_t len); // return actual identity length in buffer
-
 	class PrivateKeys // for eepsites
 	{
 		public:
@@ -164,7 +149,7 @@ namespace data
 
 			std::shared_ptr<const IdentityEx> GetPublic () const { return m_Public; };
 			const uint8_t * GetPrivateKey () const { return m_PrivateKey; };
-			const uint8_t * GetSigningPrivateKey () const { return m_SigningPrivateKey.data (); };
+			const uint8_t * GetSigningPrivateKey () const { return m_SigningPrivateKey; };
 			size_t GetSignatureLen () const; // might not match identity
 			bool IsOfflineSignature () const { return m_TransientSignatureLen > 0; };
 			uint8_t * GetPadding();
@@ -175,7 +160,7 @@ namespace data
 			size_t FromBuffer (const uint8_t * buf, size_t len);
 			size_t ToBuffer (uint8_t * buf, size_t len) const;
 
-			size_t FromBase64(std::string_view s);
+			size_t FromBase64(const std::string& s);
 			std::string ToBase64 () const;
 
 			std::shared_ptr<i2p::crypto::CryptoKeyDecryptor> CreateDecryptor (const uint8_t * key) const;
@@ -200,7 +185,7 @@ namespace data
 
 			std::shared_ptr<IdentityEx> m_Public;
 			uint8_t m_PrivateKey[256];
-			std::vector<uint8_t> m_SigningPrivateKey;
+			uint8_t m_SigningPrivateKey[128]; // assume private key doesn't exceed 128 bytes
 			mutable std::unique_ptr<i2p::crypto::Signer> m_Signer;
 			std::vector<uint8_t> m_OfflineSignature; // non zero length, if applicable
 			size_t m_TransientSignatureLen = 0;
@@ -221,7 +206,7 @@ namespace data
 		bool operator< (const XORMetric& other) const { return memcmp (metric, other.metric, 32) < 0; };
 	};
 
-	IdentHash CreateRoutingKey (const IdentHash& ident, bool nextDay = false);
+	IdentHash CreateRoutingKey (const IdentHash& ident);
 	XORMetric operator^(const IdentHash& key1, const IdentHash& key2);
 
 	// destination for delivery instructions
diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp
index fc0e722d..675f6503 100644
--- a/libi2pd/LeaseSet.cpp
+++ b/libi2pd/LeaseSet.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -14,7 +14,6 @@
 #include "Timestamp.h"
 #include "NetDb.hpp"
 #include "Tunnel.h"
-#include "CryptoKey.h"
 #include "LeaseSet.h"
 
 namespace i2p
@@ -400,7 +399,6 @@ namespace data
 		offset += propertiesLen; // skip for now. TODO: implement properties
 		// key sections
 		CryptoKeyType preferredKeyType = m_EncryptionType;
-		m_EncryptionType = 0;
 		bool preferredKeyFound = false;
 		if (offset + 1 > len) return 0;
 		int numKeySections = buf[offset]; offset++;
@@ -412,22 +410,14 @@ namespace data
 			if (offset + encryptionKeyLen > len) return 0;
 			if (IsStoreLeases () && !preferredKeyFound) // create encryptor with leases only
 			{
-				// we pick max key type if preferred not found
-#if !OPENSSL_PQ
-				if (keyType <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // skip PQ keys if not supported
-#endif			
-				{	
-					if (keyType == preferredKeyType || !m_Encryptor || keyType > m_EncryptionType)
-					{
-						auto encryptor = i2p::data::IdentityEx::CreateEncryptor (keyType, buf + offset);
-						if (encryptor)
-						{
-							m_Encryptor = encryptor; // TODO: atomic
-							m_EncryptionType = keyType;
-							if (keyType == preferredKeyType) preferredKeyFound = true;
-						}	
-					}
-				}	
+				// we pick first valid key if preferred not found
+				auto encryptor = i2p::data::IdentityEx::CreateEncryptor (keyType, buf + offset);
+				if (encryptor && (!m_Encryptor || keyType == preferredKeyType))
+				{
+					m_Encryptor = encryptor; // TODO: atomic
+					m_EncryptionType = keyType;
+					if (keyType == preferredKeyType) preferredKeyFound = true;
+				}
 			}
 			offset += encryptionKeyLen;
 		}
@@ -435,16 +425,6 @@ namespace data
 		if (offset + 1 > len) return 0;
 		int numLeases = buf[offset]; offset++;
 		auto ts = i2p::util::GetMillisecondsSinceEpoch ();
-		if (GetExpirationTime () > ts + LEASESET_EXPIRATION_TIME_THRESHOLD)
-		{
-			LogPrint (eLogWarning, "LeaseSet2: Expiration time is from future ", GetExpirationTime ()/1000LL);
-			return 0;
-		}	
-		if (ts > m_PublishedTimestamp*1000LL + LEASESET_EXPIRATION_TIME_THRESHOLD)
-		{
-			LogPrint (eLogWarning, "LeaseSet2: Published time is too old ", m_PublishedTimestamp);
-			return 0;
-		}	
 		if (IsStoreLeases ())
 		{
 			UpdateLeasesBegin ();
@@ -455,11 +435,6 @@ namespace data
 				lease.tunnelGateway = buf + offset; offset += 32; // gateway
 				lease.tunnelID = bufbe32toh (buf + offset); offset += 4; // tunnel ID
 				lease.endDate = bufbe32toh (buf + offset)*1000LL; offset += 4; // end date
-				if (lease.endDate > ts + LEASESET_EXPIRATION_TIME_THRESHOLD)
-				{
-					LogPrint (eLogWarning, "LeaseSet2: Lease end date is from future ", lease.endDate);
-					return 0;
-				}	
 				UpdateLease (lease, ts);
 			}
 			UpdateLeasesEnd ();
@@ -753,41 +728,25 @@ namespace data
 		memset (m_Buffer + offset, 0, signingKeyLen);
 		offset += signingKeyLen;
 		// num leases
-		auto numLeasesPos = offset;	
 		m_Buffer[offset] = num;
 		offset++;
 		// leases
 		m_Leases = m_Buffer + offset;
 		auto currentTime = i2p::util::GetMillisecondsSinceEpoch ();
-		int skipped = 0;
 		for (int i = 0; i < num; i++)
 		{
-			uint64_t ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration
-			ts *= 1000; // in milliseconds
-			if (ts <= currentTime)
-			{
-				// already expired, skip
-				skipped++;
-				continue;
-			}	
-			if (ts > m_ExpirationTime) m_ExpirationTime = ts;
-			// make sure leaseset is newer than previous, but adding some time to expiration date
-			ts += (currentTime - tunnels[i]->GetCreationTime ()*1000LL)*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2 secs
 			memcpy (m_Buffer + offset, tunnels[i]->GetNextIdentHash (), 32);
 			offset += 32; // gateway id
 			htobe32buf (m_Buffer + offset, tunnels[i]->GetNextTunnelID ());
 			offset += 4; // tunnel id
+			uint64_t ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration
+			ts *= 1000; // in milliseconds
+			if (ts > m_ExpirationTime) m_ExpirationTime = ts;
+			// make sure leaseset is newer than previous, but adding some time to expiration date
+			ts += (currentTime - tunnels[i]->GetCreationTime ()*1000LL)*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2 secs
 			htobe64buf (m_Buffer + offset, ts);
 			offset += 8; // end date
 		}
-		if (skipped > 0)
-		{
-			// adjust num leases
-			if (skipped > num) skipped = num;
-			num -= skipped;
-			m_BufferLen -= skipped*LEASE_SIZE;
-			m_Buffer[numLeasesPos] = num;
-		}	
 		// we don't sign it yet. must be signed later on
 	}
 
@@ -848,8 +807,8 @@ namespace data
 	}
 
 	LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys,
-		const EncryptionKeys& encryptionKeys, const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels,
-		bool isPublic, uint64_t publishedTimestamp, bool isPublishedEncrypted):
+		const KeySections& encryptionKeys, const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels,
+		bool isPublic, bool isPublishedEncrypted):
 		LocalLeaseSet (keys.GetPublic (), nullptr, 0)
 	{
 		auto identity = keys.GetPublic ();
@@ -858,7 +817,7 @@ namespace data
 		if (num > MAX_NUM_LEASES) num = MAX_NUM_LEASES;
 		size_t keySectionsLen = 0;
 		for (const auto& it: encryptionKeys)
-			keySectionsLen += 2/*key type*/ + 2/*key len*/ + it->pub.size()/*key*/;
+			keySectionsLen += 2/*key type*/ + 2/*key len*/ + it.keyLen/*key*/;
 		m_BufferLen = identity->GetFullLen () + 4/*published*/ + 2/*expires*/ + 2/*flag*/ + 2/*properties len*/ +
 			1/*num keys*/ + keySectionsLen + 1/*num leases*/ + num*LEASE2_SIZE + keys.GetSignatureLen ();
 		uint16_t flags = 0;
@@ -878,7 +837,8 @@ namespace data
 		m_Buffer[0] = storeType;
 		// LS2 header
 		auto offset = identity->ToBuffer (m_Buffer + 1, m_BufferLen) + 1;
-		htobe32buf (m_Buffer + offset, publishedTimestamp); offset += 4; // published timestamp (seconds)
+		auto timestamp = i2p::util::GetSecondsSinceEpoch ();
+		htobe32buf (m_Buffer + offset, timestamp); offset += 4; // published timestamp (seconds)
 		uint8_t * expiresBuf = m_Buffer + offset; offset += 2; // expires, fill later
 		htobe16buf (m_Buffer + offset, flags); offset += 2; // flags
 		if (keys.IsOfflineSignature ())
@@ -893,50 +853,35 @@ namespace data
 		m_Buffer[offset] = encryptionKeys.size (); offset++; // 1 key
 		for (const auto& it: encryptionKeys)
 		{
-			htobe16buf (m_Buffer + offset, it->keyType); offset += 2; // key type
-			htobe16buf (m_Buffer + offset, it->pub.size()); offset += 2; // key len
-			memcpy (m_Buffer + offset, it->pub.data(), it->pub.size()); offset += it->pub.size(); // key
+			htobe16buf (m_Buffer + offset, it.keyType); offset += 2; // key type
+			htobe16buf (m_Buffer + offset, it.keyLen); offset += 2; // key len
+			memcpy (m_Buffer + offset, it.encryptionPublicKey, it.keyLen); offset += it.keyLen; // key
 		}
 		// leases
 		uint32_t expirationTime = 0; // in seconds
-		int skipped = 0; auto numLeasesPos = offset;
 		m_Buffer[offset] = num; offset++; // num leases
 		for (int i = 0; i < num; i++)
 		{
-			auto ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // in seconds, 1 minute before expiration
-			if (ts <= publishedTimestamp) 
-			{	
-				// already expired, skip
-				skipped++;
-				continue; 
-			}	
-			if (ts > expirationTime) expirationTime = ts;
 			memcpy (m_Buffer + offset, tunnels[i]->GetNextIdentHash (), 32);
 			offset += 32; // gateway id
 			htobe32buf (m_Buffer + offset, tunnels[i]->GetNextTunnelID ());
 			offset += 4; // tunnel id
+			auto ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // in seconds, 1 minute before expiration
+			if (ts > expirationTime) expirationTime = ts;
 			htobe32buf (m_Buffer + offset, ts);
 			offset += 4; // end date
 		}
-		if (skipped > 0)
-		{
-			// adjust num leases
-			if (skipped > num) skipped = num;
-			num -= skipped;
-			m_BufferLen -= skipped*LEASE2_SIZE;
-			m_Buffer[numLeasesPos] = num;
-		}	
 		// update expiration
 		if (expirationTime)
 		{
 			SetExpirationTime (expirationTime*1000LL);
-			auto expires = (int)expirationTime - publishedTimestamp;
+			auto expires = (int)expirationTime - timestamp;
 			htobe16buf (expiresBuf, expires > 0 ? expires : 0);
 		}
 		else
 		{
 			// no tunnels or withdraw
-			SetExpirationTime (publishedTimestamp*1000LL);
+			SetExpirationTime (timestamp*1000LL);
 			memset (expiresBuf, 0, 2); // expires immeditely
 		}
 		// sign
diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h
index f5197eb5..4b1311a5 100644
--- a/libi2pd/LeaseSet.h
+++ b/libi2pd/LeaseSet.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -12,14 +12,12 @@
 #include <inttypes.h>
 #include <string.h>
 #include <vector>
-#include <list>
 #include <set>
 #include <memory>
 #include "Identity.h"
 #include "Timestamp.h"
 #include "I2PEndian.h"
 #include "Blinding.h"
-#include "CryptoKey.h"
 
 namespace i2p
 {
@@ -59,16 +57,12 @@ namespace data
 	};
 
 	typedef std::function<bool(const Lease & l)> LeaseInspectFunc;
-#if OPENSSL_PQ
-	const size_t MAX_LS_BUFFER_SIZE = 8192;
-#else
-	const size_t MAX_LS_BUFFER_SIZE = 4096;
-#endif	
+
+	const size_t MAX_LS_BUFFER_SIZE = 3072;
 	const size_t LEASE_SIZE = 44; // 32 + 4 + 8
 	const size_t LEASE2_SIZE = 40; // 32 + 4 + 4
 	const uint8_t MAX_NUM_LEASES = 16;
-	const uint64_t LEASESET_EXPIRATION_TIME_THRESHOLD = 12*60*1000; // in milliseconds
-	
+
 	const uint8_t NETDB_STORE_TYPE_LEASESET = 1;
 	class LeaseSet: public RoutingDestination
 	{
@@ -155,8 +149,8 @@ namespace data
 		public:
 
 			LeaseSet2 (uint8_t storeType): LeaseSet (true), m_StoreType (storeType) {}; // for update
-			LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases = true, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ECIES_X25519_AEAD);
-			LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key, const uint8_t * secret = nullptr, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // store type 5, called from local netdb only
+			LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases = true, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL);
+			LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key, const uint8_t * secret = nullptr, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL); // store type 5, called from local netdb only
 			uint8_t GetStoreType () const { return m_StoreType; };
 			uint32_t GetPublishedTimestamp () const { return m_PublishedTimestamp; };
 			bool IsPublic () const { return m_IsPublic; };
@@ -186,7 +180,7 @@ namespace data
 		private:
 
 			uint8_t m_StoreType;
-			uint32_t m_PublishedTimestamp = 0; // seconds
+			uint32_t m_PublishedTimestamp = 0;
 			bool m_IsPublic = true, m_IsPublishedEncrypted = false;
 			std::shared_ptr<i2p::crypto::Verifier> m_TransientVerifier;
 			CryptoKeyType m_EncryptionType;
@@ -252,13 +246,17 @@ namespace data
 	{
 		public:
 
-			typedef std::list<std::shared_ptr<const i2p::crypto::LocalEncryptionKey> > EncryptionKeys;
+			struct KeySection
+			{
+				uint16_t keyType, keyLen;
+				const uint8_t * encryptionPublicKey;
+			};
+			typedef std::vector<KeySection> KeySections;
 
 			LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys,
-				const EncryptionKeys& encryptionKeys,
+				const KeySections& encryptionKeys,
 				const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels,
-				bool isPublic, uint64_t publishedTimestamp,
-			    bool isPublishedEncrypted = false);
+				bool isPublic, bool isPublishedEncrypted = false);
 
 			LocalLeaseSet2 (uint8_t storeType, std::shared_ptr<const IdentityEx> identity, const uint8_t * buf, size_t len); // from I2CP
 
diff --git a/libi2pd/Log.h b/libi2pd/Log.h
index 18592c9e..1ec0c5fe 100644
--- a/libi2pd/Log.h
+++ b/libi2pd/Log.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2020, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -87,8 +87,8 @@ namespace log {
 			Log ();
 			~Log ();
 
-			LogType GetLogType () const { return m_Destination; };
-			LogLevel GetLogLevel () const { return m_MinLevel; };
+			LogType GetLogType () { return m_Destination; };
+			LogLevel GetLogLevel () { return m_MinLevel; };
 
 			void Start ();
 			void Stop ();
@@ -160,11 +160,6 @@ namespace log {
 } // log
 } // i2p
 
-inline bool CheckLogLevel (LogLevel level) noexcept
-{
-	return level <= i2p::log::Logger().GetLogLevel ();
-}	
-
 /** internal usage only -- folding args array to single string */
 template<typename TValue>
 void LogPrint (std::stringstream& s, TValue&& arg) noexcept
@@ -172,6 +167,16 @@ void LogPrint (std::stringstream& s, TValue&& arg) noexcept
 	s << std::forward<TValue>(arg);
 }
 
+#if (__cplusplus < 201703L) // below C++ 17
+/** internal usage only -- folding args array to single string */
+template<typename TValue, typename... TArgs>
+void LogPrint (std::stringstream& s, TValue&& arg, TArgs&&... args) noexcept
+{
+	LogPrint (s, std::forward<TValue>(arg));
+	LogPrint (s, std::forward<TArgs>(args)...);
+}
+#endif
+
 /**
  * @brief Create log message and send it to queue
  * @param level Message level (eLogError, eLogInfo, ...)
@@ -180,14 +185,22 @@ void LogPrint (std::stringstream& s, TValue&& arg) noexcept
 template<typename... TArgs>
 void LogPrint (LogLevel level, TArgs&&... args) noexcept
 {
-	if (!CheckLogLevel (level)) return; 
+	i2p::log::Log &log = i2p::log::Logger();
+	if (level > log.GetLogLevel ())
+		return;
 
 	// fold message to single string
 	std::stringstream ss;
+
+#if (__cplusplus >= 201703L) // C++ 17 or higher
 	(LogPrint (ss, std::forward<TArgs>(args)), ...);
+#else
+	LogPrint (ss, std::forward<TArgs>(args)...);
+#endif
+
 	auto msg = std::make_shared<i2p::log::LogMsg>(level, std::time(nullptr), std::move(ss).str());
 	msg->tid = std::this_thread::get_id();
-	i2p::log::Logger().Append(msg);
+	log.Append(msg);
 }
 
 /**
@@ -201,7 +214,11 @@ void ThrowFatal (TArgs&&... args) noexcept
 	if (!f) return;
 	// fold message to single string
 	std::stringstream ss("");
+#if (__cplusplus >= 201703L) // C++ 17 or higher
 	(LogPrint (ss, std::forward<TArgs>(args)), ...);
+#else
+	LogPrint (ss, std::forward<TArgs>(args)...);
+#endif
 	f (ss.str ());
 }
 
diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp
index b3a51488..0a23f07e 100644
--- a/libi2pd/NTCP2.cpp
+++ b/libi2pd/NTCP2.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -19,10 +19,9 @@
 #include "RouterContext.h"
 #include "Transports.h"
 #include "NetDb.hpp"
+#include "NTCP2.h"
 #include "HTTP.h"
 #include "util.h"
-#include "Socks5.h"
-#include "NTCP2.h"
 
 #if defined(__linux__) && !defined(_NETINET_IN_H)
 	#include <linux/in6.h>
@@ -42,29 +41,28 @@ namespace transport
 		delete[] m_SessionConfirmedBuffer;
 	}
 
-	bool NTCP2Establisher::KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub)
+	void NTCP2Establisher::KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub)
 	{
 		i2p::crypto::InitNoiseXKState (*this, rs);
 		// h = SHA256(h || epub)
 		MixHash (epub, 32);
 		// x25519 between pub and priv
 		uint8_t inputKeyMaterial[32];
-		if (!priv.Agree (pub, inputKeyMaterial)) return false;
+		priv.Agree (pub, inputKeyMaterial);
 		MixKey (inputKeyMaterial);
-		return true;
 	}
 
-	bool NTCP2Establisher::KDF1Alice ()
+	void NTCP2Establisher::KDF1Alice ()
 	{
-		return KeyDerivationFunction1 (m_RemoteStaticKey, *m_EphemeralKeys, m_RemoteStaticKey, GetPub ());
+		KeyDerivationFunction1 (m_RemoteStaticKey, *m_EphemeralKeys, m_RemoteStaticKey, GetPub ());
 	}
 
-	bool NTCP2Establisher::KDF1Bob ()
+	void NTCP2Establisher::KDF1Bob ()
 	{
-		return KeyDerivationFunction1 (GetRemotePub (), i2p::context.GetNTCP2StaticKeys (), i2p::context.GetNTCP2StaticPublicKey (), GetRemotePub ());
+		KeyDerivationFunction1 (GetRemotePub (), i2p::context.GetNTCP2StaticKeys (), i2p::context.GetNTCP2StaticPublicKey (), GetRemotePub ());
 	}
 
-	bool NTCP2Establisher::KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub)
+	void NTCP2Establisher::KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub)
 	{
 		MixHash (sessionRequest + 32, 32); // encrypted payload
 
@@ -75,35 +73,33 @@ namespace transport
 
 		// x25519 between remote pub and ephemaral priv
 		uint8_t inputKeyMaterial[32];
-		if (!m_EphemeralKeys->Agree (GetRemotePub (), inputKeyMaterial)) return false;
+		m_EphemeralKeys->Agree (GetRemotePub (), inputKeyMaterial);
+
 		MixKey (inputKeyMaterial);
-		return true;
 	}
 
-	bool NTCP2Establisher::KDF2Alice ()
+	void NTCP2Establisher::KDF2Alice ()
 	{
-		return KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetRemotePub ());
+		KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetRemotePub ());
 	}
 
-	bool NTCP2Establisher::KDF2Bob ()
+	void NTCP2Establisher::KDF2Bob ()
 	{
-		return KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetPub ());
+		KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetPub ());
 	}
 
-	bool NTCP2Establisher::KDF3Alice ()
+	void NTCP2Establisher::KDF3Alice ()
 	{
 		uint8_t inputKeyMaterial[32];
-		if (!i2p::context.GetNTCP2StaticKeys ().Agree (GetRemotePub (), inputKeyMaterial)) return false;
+		i2p::context.GetNTCP2StaticKeys ().Agree (GetRemotePub (), inputKeyMaterial);
 		MixKey (inputKeyMaterial);
-		return true;
 	}
 
-	bool NTCP2Establisher::KDF3Bob ()
+	void NTCP2Establisher::KDF3Bob ()
 	{
 		uint8_t inputKeyMaterial[32];
-		if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, inputKeyMaterial)) return false;
+		m_EphemeralKeys->Agree (m_RemoteStaticKey, inputKeyMaterial);
 		MixKey (inputKeyMaterial);
-		return true;
 	}
 
 	void NTCP2Establisher::CreateEphemeralKey ()
@@ -111,19 +107,20 @@ namespace transport
 		m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair ();
 	}
 
-	bool NTCP2Establisher::CreateSessionRequestMessage (std::mt19937& rng)
+	void NTCP2Establisher::CreateSessionRequestMessage ()
 	{
 		// create buffer and fill padding
-		auto paddingLength = rng () % (NTCP2_SESSION_REQUEST_MAX_SIZE - 64); // message length doesn't exceed 287 bytes
+		auto paddingLength = rand () % (NTCP2_SESSION_REQUEST_MAX_SIZE - 64); // message length doesn't exceed 287 bytes
 		m_SessionRequestBufferLen = paddingLength + 64;
 		RAND_bytes (m_SessionRequestBuffer + 64, paddingLength);
 		// encrypt X
 		i2p::crypto::CBCEncryption encryption;
 		encryption.SetKey (m_RemoteIdentHash);
-		encryption.Encrypt (GetPub (), 32, m_IV, m_SessionRequestBuffer); // X
-		memcpy (m_IV, m_SessionRequestBuffer + 16, 16); // save last block as IV for SessionCreated
+		encryption.SetIV (m_IV);
+		encryption.Encrypt (GetPub (), 32, m_SessionRequestBuffer); // X
+		encryption.GetIV (m_IV); // save IV for SessionCreated
 		// encryption key for next block
-		if (!KDF1Alice ()) return false;
+		KDF1Alice ();
 		// fill options
 		uint8_t options[32]; // actual options size is 16 bytes
 		memset (options, 0, 16);
@@ -131,8 +128,7 @@ namespace transport
 		options[1] = 2; // ver
 		htobe16buf (options + 2, paddingLength); // padLen
 		// m3p2Len
-		auto riBuffer = i2p::context.CopyRouterInfoBuffer ();
-		auto bufLen = riBuffer->GetBufferLen ();
+		auto bufLen = i2p::context.GetRouterInfo ().GetBufferLen ();
 		m3p2Len = bufLen + 4 + 16; // (RI header + RI + MAC for now) TODO: implement options
 		htobe16buf (options + 4, m3p2Len);
 		// fill m3p2 payload (RouterInfo block)
@@ -141,44 +137,40 @@ namespace transport
 		m3p2[0] = eNTCP2BlkRouterInfo; // block
 		htobe16buf (m3p2 + 1, bufLen + 1); // flag + RI
 		m3p2[3] = 0; // flag
-		memcpy (m3p2 + 4, riBuffer->data (), bufLen); // TODO: eliminate extra copy
+		memcpy (m3p2 + 4, i2p::context.GetRouterInfo ().GetBuffer (), bufLen); // TODO: own RI should be protected by mutex
 		// 2 bytes reserved
 		htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsA, rounded to seconds
 		// 4 bytes reserved
-		// encrypt options
-		if (!Encrypt (options, m_SessionRequestBuffer + 32, 16))
-		{
-			LogPrint (eLogWarning, "NTCP2: SessionRequest failed to encrypt options");
-			return false;
-		}	
-		return true;
+		// sign and encrypt options, use m_H as AD
+		uint8_t nonce[12];
+		memset (nonce, 0, 12); // set nonce to zero
+		i2p::crypto::AEADChaCha20Poly1305 (options, 16, GetH (), 32, GetK (), nonce, m_SessionRequestBuffer + 32, 32, true); // encrypt
 	}
 
-	bool NTCP2Establisher::CreateSessionCreatedMessage (std::mt19937& rng)
+	void NTCP2Establisher::CreateSessionCreatedMessage ()
 	{
-		auto paddingLen = rng () % (NTCP2_SESSION_CREATED_MAX_SIZE - 64);
+		auto paddingLen = rand () % (NTCP2_SESSION_CREATED_MAX_SIZE - 64);
 		m_SessionCreatedBufferLen = paddingLen + 64;
 		RAND_bytes (m_SessionCreatedBuffer + 64, paddingLen);
 		// encrypt Y
 		i2p::crypto::CBCEncryption encryption;
 		encryption.SetKey (i2p::context.GetIdentHash ());
-		encryption.Encrypt (GetPub (), 32, m_IV, m_SessionCreatedBuffer); // Y
+		encryption.SetIV (m_IV);
+		encryption.Encrypt (GetPub (), 32, m_SessionCreatedBuffer); // Y
 		// encryption key for next block (m_K)
-		if (!KDF2Bob ()) return false;
+		KDF2Bob ();
 		uint8_t options[16];
 		memset (options, 0, 16);
 		htobe16buf (options + 2, paddingLen); // padLen
 		htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsB, rounded to seconds
-		// encrypt options
-		if (!Encrypt (options, m_SessionCreatedBuffer + 32, 16))
-		{
-			LogPrint (eLogWarning, "NTCP2: SessionCreated failed to encrypt options");
-			return false;
-		}	
-		return true;
+		// sign and encrypt options, use m_H as AD
+		uint8_t nonce[12];
+		memset (nonce, 0, 12); // set nonce to zero
+		i2p::crypto::AEADChaCha20Poly1305 (options, 16, GetH (), 32, GetK (), nonce, m_SessionCreatedBuffer + 32, 32, true); // encrypt
+
 	}
 
-	bool NTCP2Establisher::CreateSessionConfirmedMessagePart1 ()
+	void NTCP2Establisher::CreateSessionConfirmedMessagePart1 (const uint8_t * nonce)
 	{
 		// update AD
 		MixHash (m_SessionCreatedBuffer + 32, 32);	// encrypted payload
@@ -186,31 +178,21 @@ namespace transport
 		if (paddingLength > 0)
 			MixHash (m_SessionCreatedBuffer + 64, paddingLength);
 
-		// part1 48 bytes, n = 1
-		if (!Encrypt (i2p::context.GetNTCP2StaticPublicKey (), m_SessionConfirmedBuffer, 32))
-		{
-			LogPrint (eLogWarning, "NTCP2: SessionConfirmed failed to encrypt part1");
-			return false;
-		}	
-		return true;		
+		// part1 48 bytes
+		i2p::crypto::AEADChaCha20Poly1305 (i2p::context.GetNTCP2StaticPublicKey (), 32, GetH (), 32, GetK (), nonce, m_SessionConfirmedBuffer, 48, true); // encrypt
 	}
 
-	bool NTCP2Establisher::CreateSessionConfirmedMessagePart2 ()
+	void NTCP2Establisher::CreateSessionConfirmedMessagePart2 (const uint8_t * nonce)
 	{
 		// part 2
 		// update AD again
 		MixHash (m_SessionConfirmedBuffer, 48);
 		// encrypt m3p2, it must be filled in SessionRequest
-		if (!KDF3Alice ()) return false; // MixKey, n = 0
+		KDF3Alice ();
 		uint8_t * m3p2 = m_SessionConfirmedBuffer + 48;
-		if (!Encrypt (m3p2, m3p2, m3p2Len - 16))
-		{
-			LogPrint (eLogWarning, "NTCP2: SessionConfirmed failed to encrypt part2");
-			return false;
-		}	
+		i2p::crypto::AEADChaCha20Poly1305 (m3p2, m3p2Len - 16, GetH (), 32, GetK (), nonce, m3p2, m3p2Len, true); // encrypt
 		// update h again
 		MixHash (m3p2, m3p2Len); //h = SHA256(h || ciphertext)
-		return true;
 	}
 
 	bool NTCP2Establisher::ProcessSessionRequestMessage (uint16_t& paddingLen, bool& clockSkew)
@@ -219,17 +201,15 @@ namespace transport
 		// decrypt X
 		i2p::crypto::CBCDecryption decryption;
 		decryption.SetKey (i2p::context.GetIdentHash ());
-		decryption.Decrypt (m_SessionRequestBuffer, 32, i2p::context.GetNTCP2IV (), GetRemotePub ());
-		memcpy (m_IV, m_SessionRequestBuffer + 16, 16); // save last block as IV for SessionCreated
+		decryption.SetIV (i2p::context.GetNTCP2IV ());
+		decryption.Decrypt (m_SessionRequestBuffer, 32, GetRemotePub ());
+		decryption.GetIV (m_IV); // save IV for SessionCreated
 		// decryption key for next block
-		if (!KDF1Bob ())
-		{
-			LogPrint (eLogWarning, "NTCP2: SessionRequest KDF failed");
-			return false;
-		}	
-		// verify MAC and decrypt options block (32 bytes)
-		uint8_t options[16];
-		if (Decrypt (m_SessionRequestBuffer + 32, options, 16)) 
+		KDF1Bob ();
+		// verify MAC and decrypt options block (32 bytes), use m_H as AD
+		uint8_t nonce[12], options[16];
+		memset (nonce, 0, 12); // set nonce to zero
+		if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionRequestBuffer + 32, 16, GetH (), 32, GetK (), nonce, options, 16, false)) // decrypt
 		{
 			// options
 			if (options[0] && options[0] != i2p::context.GetNetID ())
@@ -277,16 +257,15 @@ namespace transport
 		// decrypt Y
 		i2p::crypto::CBCDecryption decryption;
 		decryption.SetKey (m_RemoteIdentHash);
-		decryption.Decrypt (m_SessionCreatedBuffer, 32, m_IV, GetRemotePub ());
+		decryption.SetIV (m_IV);
+		decryption.Decrypt (m_SessionCreatedBuffer, 32, GetRemotePub ());
 		// decryption key for next block (m_K)
-		if (!KDF2Alice ())
-		{
-			LogPrint (eLogWarning, "NTCP2: SessionCreated KDF failed");
-			return false;
-		}	
+		KDF2Alice ();
 		// decrypt and verify MAC
 		uint8_t payload[16];
-		if (Decrypt (m_SessionCreatedBuffer + 32, payload, 16)) 
+		uint8_t nonce[12];
+		memset (nonce, 0, 12); // set nonce to zero
+		if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionCreatedBuffer + 32, 16, GetH (), 32, GetK (), nonce, payload, 16, false)) // decrypt
 		{
 			// options
 			paddingLen = bufbe16toh(payload + 2);
@@ -307,7 +286,7 @@ namespace transport
 		return true;
 	}
 
-	bool NTCP2Establisher::ProcessSessionConfirmedMessagePart1 ()
+	bool NTCP2Establisher::ProcessSessionConfirmedMessagePart1 (const uint8_t * nonce)
 	{
 		// update AD
 		MixHash (m_SessionCreatedBuffer + 32, 32);	// encrypted payload
@@ -315,8 +294,7 @@ namespace transport
 		if (paddingLength > 0)
 			MixHash (m_SessionCreatedBuffer + 64, paddingLength);
 
-		// decrypt S, n = 1
-		if (!Decrypt (m_SessionConfirmedBuffer, m_RemoteStaticKey, 32))
+		if (!i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer, 32, GetH (), 32, GetK (), nonce, m_RemoteStaticKey, 32, false)) // decrypt S
 		{
 			LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part1 AEAD verification failed ");
 			return false;
@@ -324,17 +302,13 @@ namespace transport
 		return true;
 	}
 
-	bool NTCP2Establisher::ProcessSessionConfirmedMessagePart2 (uint8_t * m3p2Buf)
+	bool NTCP2Establisher::ProcessSessionConfirmedMessagePart2 (const uint8_t * nonce, uint8_t * m3p2Buf)
 	{
 		// update AD again
 		MixHash (m_SessionConfirmedBuffer, 48);
 
-		if (!KDF3Bob ()) // MixKey, n = 0
-		{
-			LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part2 KDF failed");
-			return false;
-		}	
-		if (Decrypt (m_SessionConfirmedBuffer + 48, m3p2Buf, m3p2Len - 16))
+		KDF3Bob ();
+		if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer + 48, m3p2Len - 16, GetH (), 32, GetK (), nonce, m3p2Buf, m3p2Len - 16, false)) // decrypt
 			// calculate new h again for KDF data
 			MixHash (m_SessionConfirmedBuffer + 48, m3p2Len); // h = SHA256(h || ciphertext)
 		else
@@ -351,7 +325,6 @@ namespace transport
 		m_Server (server), m_Socket (m_Server.GetService ()),
 		m_IsEstablished (false), m_IsTerminated (false),
 		m_Establisher (new NTCP2Establisher),
-		m_SendKey (nullptr), m_ReceiveKey (nullptr),
 #if OPENSSL_SIPHASH
 		m_SendMDCtx(nullptr), m_ReceiveMDCtx (nullptr),
 #else
@@ -374,7 +347,7 @@ namespace transport
 				LogPrint (eLogWarning, "NTCP2: Missing NTCP2 address");
 		}
 		m_NextRouterInfoResendTime = i2p::util::GetSecondsSinceEpoch () + NTCP2_ROUTERINFO_RESEND_INTERVAL +
-			m_Server.GetRng ()() % NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD;
+			rand ()%NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD;
 	}
 
 	NTCP2Session::~NTCP2Session ()
@@ -400,17 +373,13 @@ namespace transport
 			m_Socket.close ();
 			transports.PeerDisconnected (shared_from_this ());
 			m_Server.RemoveNTCP2Session (shared_from_this ());
-			if (!m_IntermediateQueue.empty ())
-				m_SendQueue.splice (m_SendQueue.end (), m_IntermediateQueue);
-			for (auto& it: m_SendQueue)
-				it->Drop ();
 			m_SendQueue.clear ();
 			SetSendQueueSize (0);
 			auto remoteIdentity = GetRemoteIdentity ();
 			if (remoteIdentity)
 			{
 				LogPrint (eLogDebug, "NTCP2: Session with ", GetRemoteEndpoint (),
-					" (", i2p::data::GetIdentHashAbbreviation (remoteIdentity->GetIdentHash ()), ") terminated");
+					" (", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()), ") terminated");
 			}
 			else
 			{
@@ -431,15 +400,14 @@ namespace transport
 
 	void NTCP2Session::Done ()
 	{
-		boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
+		m_Server.GetService ().post (std::bind (&NTCP2Session::Terminate, shared_from_this ()));
 	}
 
 	void NTCP2Session::Established ()
 	{
 		m_IsEstablished = true;
 		m_Establisher.reset (nullptr);
-		SetTerminationTimeout (NTCP2_TERMINATION_TIMEOUT + m_Server.GetRng ()() % NTCP2_TERMINATION_TIMEOUT_VARIANCE);
-		SendQueue ();
+		SetTerminationTimeout (NTCP2_TERMINATION_TIMEOUT);
 		transports.PeerConnected (shared_from_this ());
 	}
 
@@ -491,12 +459,7 @@ namespace transport
 
 	void NTCP2Session::SendSessionRequest ()
 	{
-		if (!m_Establisher->CreateSessionRequestMessage (m_Server.GetRng ()))
-		{
-			LogPrint (eLogWarning, "NTCP2: Send SessionRequest KDF failed");
-			boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
-			return;
-		}	
+		m_Establisher->CreateSessionRequestMessage ();
 		// send message
 		m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch ();
 		boost::asio::async_write (m_Socket, boost::asio::buffer (m_Establisher->m_SessionRequestBuffer, m_Establisher->m_SessionRequestBufferLen), boost::asio::transfer_all (),
@@ -521,6 +484,7 @@ namespace transport
 
 	void NTCP2Session::HandleSessionRequestReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
 	{
+		(void) bytes_transferred;
 		if (ecode)
 		{
 			LogPrint (eLogWarning, "NTCP2: SessionRequest read error: ", ecode.message ());
@@ -528,47 +492,38 @@ namespace transport
 		}
 		else
 		{
-			boost::asio::post (m_Server.GetEstablisherService (), 
-				[s = shared_from_this (), bytes_transferred] ()
+			LogPrint (eLogDebug, "NTCP2: SessionRequest received ", bytes_transferred);
+			uint16_t paddingLen = 0;
+			bool clockSkew = false;
+			if (m_Establisher->ProcessSessionRequestMessage (paddingLen, clockSkew))
+			{
+				if (clockSkew)
 				{
-					s->ProcessSessionRequest (bytes_transferred);;
-				});	
+					// we don't care about padding, send SessionCreated and close session
+					SendSessionCreated ();
+					m_Server.GetService ().post (std::bind (&NTCP2Session::Terminate, shared_from_this ()));
+				}
+				else if (paddingLen > 0)
+				{
+					if (paddingLen <= NTCP2_SESSION_REQUEST_MAX_SIZE - 64) // session request is 287 bytes max
+					{
+						boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer + 64, paddingLen), boost::asio::transfer_all (),
+							std::bind(&NTCP2Session::HandleSessionRequestPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
+					}
+					else
+					{
+						LogPrint (eLogWarning, "NTCP2: SessionRequest padding length ", (int)paddingLen, " is too long");
+						Terminate ();
+					}
+				}
+				else
+					SendSessionCreated ();
+			}
+			else
+				Terminate ();
 		}
 	}
 
-	void NTCP2Session::ProcessSessionRequest (size_t len)
-	{
-		LogPrint (eLogDebug, "NTCP2: SessionRequest received ", len);
-		uint16_t paddingLen = 0;
-		bool clockSkew = false;
-		if (m_Establisher->ProcessSessionRequestMessage (paddingLen, clockSkew))
-		{
-			if (clockSkew)
-			{
-				// we don't care about padding, send SessionCreated and close session
-				SendSessionCreated ();
-				boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
-			}
-			else if (paddingLen > 0)
-			{
-				if (paddingLen <= NTCP2_SESSION_REQUEST_MAX_SIZE - 64) // session request is 287 bytes max
-				{
-					boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer + 64, paddingLen), boost::asio::transfer_all (),
-						std::bind(&NTCP2Session::HandleSessionRequestPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
-				}
-				else
-				{
-					LogPrint (eLogWarning, "NTCP2: SessionRequest padding length ", (int)paddingLen, " is too long");
-					boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
-				}
-			}
-			else
-				SendSessionCreated ();
-		}
-		else
-			boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
-	}	
-		
 	void NTCP2Session::HandleSessionRequestPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
 	{
 		if (ecode)
@@ -577,23 +532,12 @@ namespace transport
 			Terminate ();
 		}
 		else
-		{
-			boost::asio::post (m_Server.GetEstablisherService (), 
-				[s = shared_from_this ()] ()
-				{
-					s->SendSessionCreated ();
-				});	
-		}	
+			SendSessionCreated ();
 	}
 
 	void NTCP2Session::SendSessionCreated ()
 	{
-		if (!m_Establisher->CreateSessionCreatedMessage (m_Server.GetRng ()))
-		{
-			LogPrint (eLogWarning, "NTCP2: Send SessionCreated KDF failed");
-			boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
-			return;
-		}	
+		m_Establisher->CreateSessionCreatedMessage ();
 		// send message
 		m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch ();
 		boost::asio::async_write (m_Socket, boost::asio::buffer (m_Establisher->m_SessionCreatedBuffer, m_Establisher->m_SessionCreatedBufferLen), boost::asio::transfer_all (),
@@ -610,44 +554,35 @@ namespace transport
 		else
 		{
 			m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval;
-			boost::asio::post (m_Server.GetEstablisherService (), 
-				[s = shared_from_this (), bytes_transferred] ()
+			LogPrint (eLogDebug, "NTCP2: SessionCreated received ", bytes_transferred);
+			uint16_t paddingLen = 0;
+			if (m_Establisher->ProcessSessionCreatedMessage (paddingLen))
+			{
+				if (paddingLen > 0)
 				{
-					s->ProcessSessionCreated (bytes_transferred);
-				});	
+					if (paddingLen <= NTCP2_SESSION_CREATED_MAX_SIZE - 64) // session created is 287 bytes max
+					{
+						boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionCreatedBuffer + 64, paddingLen), boost::asio::transfer_all (),
+							std::bind(&NTCP2Session::HandleSessionCreatedPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
+					}
+					else
+					{
+						LogPrint (eLogWarning, "NTCP2: SessionCreated padding length ", (int)paddingLen, " is too long");
+						Terminate ();
+					}
+				}
+				else
+					SendSessionConfirmed ();
+			}
+			else
+			{
+				if (GetRemoteIdentity ())
+					i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true);  // assume wrong s key
+				Terminate ();
+			}	
 		}
 	}
 
-	void NTCP2Session::ProcessSessionCreated (size_t len)
-	{
-		LogPrint (eLogDebug, "NTCP2: SessionCreated received ", len);
-		uint16_t paddingLen = 0;
-		if (m_Establisher->ProcessSessionCreatedMessage (paddingLen))
-		{
-			if (paddingLen > 0)
-			{
-				if (paddingLen <= NTCP2_SESSION_CREATED_MAX_SIZE - 64) // session created is 287 bytes max
-				{
-					boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionCreatedBuffer + 64, paddingLen), boost::asio::transfer_all (),
-						std::bind(&NTCP2Session::HandleSessionCreatedPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
-				}
-				else
-				{
-					LogPrint (eLogWarning, "NTCP2: SessionCreated padding length ", (int)paddingLen, " is too long");
-					boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
-				}
-			}
-			else
-				SendSessionConfirmed ();
-		}
-		else
-		{
-			if (GetRemoteIdentity ())
-				i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true);  // assume wrong s key
-			boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
-		}	
-	}	
-		
 	void NTCP2Session::HandleSessionCreatedPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
 	{
 		if (ecode)
@@ -658,27 +593,17 @@ namespace transport
 		else
 		{
 			m_Establisher->m_SessionCreatedBufferLen += bytes_transferred;
-			boost::asio::post (m_Server.GetEstablisherService (), 
-				[s = shared_from_this ()] ()
-				{
-					s->SendSessionConfirmed ();
-				});	
+			SendSessionConfirmed ();
 		}
 	}
 
 	void NTCP2Session::SendSessionConfirmed ()
 	{
-		if (!m_Establisher->CreateSessionConfirmedMessagePart1 ()) 
-		{
-			boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
-			return;
-		}	
-		if (!m_Establisher->CreateSessionConfirmedMessagePart2 ())
-		{
-			LogPrint (eLogWarning, "NTCP2: Send SessionConfirmed Part2 KDF failed");
-			boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
-			return;
-		}	
+		uint8_t nonce[12];
+		CreateNonce (1, nonce); // set nonce to 1
+		m_Establisher->CreateSessionConfirmedMessagePart1 (nonce);
+		memset (nonce, 0, 12); // set nonce back to 0
+		m_Establisher->CreateSessionConfirmedMessagePart2 (nonce);
 		// send message
 		boost::asio::async_write (m_Socket, boost::asio::buffer (m_Establisher->m_SessionConfirmedBuffer, m_Establisher->m3p2Len + 48), boost::asio::transfer_all (),
 			std::bind(&NTCP2Session::HandleSessionConfirmedSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
@@ -730,7 +655,6 @@ namespace transport
 
 	void NTCP2Session::HandleSessionConfirmedReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
 	{
-		(void) bytes_transferred;
 		if (ecode)
 		{
 			LogPrint (eLogWarning, "NTCP2: SessionConfirmed read error: ", ecode.message ());
@@ -739,149 +663,101 @@ namespace transport
 		else
 		{
 			m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval;
-			boost::asio::post (m_Server.GetEstablisherService (), 
-				[s = shared_from_this ()] ()
+			LogPrint (eLogDebug, "NTCP2: SessionConfirmed received");
+			// part 1
+			uint8_t nonce[12];
+			CreateNonce (1, nonce);
+			if (m_Establisher->ProcessSessionConfirmedMessagePart1 (nonce))
+			{
+				// part 2
+				std::vector<uint8_t> buf(m_Establisher->m3p2Len - 16); // -MAC
+				memset (nonce, 0, 12); // set nonce to 0 again
+				if (m_Establisher->ProcessSessionConfirmedMessagePart2 (nonce, buf.data ()))
 				{
-					s->ProcessSessionConfirmed ();;
-				});	
+					KeyDerivationFunctionDataPhase ();
+					// Bob data phase keys
+					m_SendKey = m_Kba;
+					m_ReceiveKey = m_Kab;
+					SetSipKeys (m_Sipkeysba, m_Sipkeysab);
+					memcpy (m_ReceiveIV.buf, m_Sipkeysab + 16, 8);
+					memcpy (m_SendIV.buf, m_Sipkeysba + 16, 8);
+					// payload
+					// process RI
+					if (buf[0] != eNTCP2BlkRouterInfo)
+					{
+						LogPrint (eLogWarning, "NTCP2: Unexpected block ", (int)buf[0], " in SessionConfirmed");
+						Terminate ();
+						return;
+					}
+					auto size = bufbe16toh (buf.data () + 1);
+					if (size > buf.size () - 3)
+					{
+						LogPrint (eLogError, "NTCP2: Unexpected RouterInfo size ", size, " in SessionConfirmed");
+						Terminate ();
+						return;
+					}
+					// TODO: check flag
+					i2p::data::RouterInfo ri (buf.data () + 4, size - 1); // 1 byte block type + 2 bytes size + 1 byte flag
+					if (ri.IsUnreachable ())
+					{
+						LogPrint (eLogError, "NTCP2: RouterInfo verification failed in SessionConfirmed from ", GetRemoteEndpoint ());
+						SendTerminationAndTerminate (eNTCP2RouterInfoSignatureVerificationFail);
+						return;
+					}
+					LogPrint(eLogDebug, "NTCP2: SessionConfirmed from ", GetRemoteEndpoint (),
+						" (", i2p::data::GetIdentHashAbbreviation (ri.GetIdentHash ()), ")");
+					auto ts = i2p::util::GetMillisecondsSinceEpoch ();
+					if (ts > ri.GetTimestamp () + i2p::data::NETDB_MIN_EXPIRATION_TIMEOUT*1000LL) // 90 minutes
+					{
+						LogPrint (eLogError, "NTCP2: RouterInfo is too old in SessionConfirmed for ", (ts - ri.GetTimestamp ())/1000LL, " seconds");
+						SendTerminationAndTerminate (eNTCP2Message3Error);
+						return;
+					}
+					if (ts + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri.GetTimestamp ()) // 2 minutes
+					{
+						LogPrint (eLogError, "NTCP2: RouterInfo is from future for ", (ri.GetTimestamp () - ts)/1000LL, " seconds");
+						SendTerminationAndTerminate (eNTCP2Message3Error);
+						return;
+					}	
+					auto addr = m_RemoteEndpoint.address ().is_v4 () ? ri.GetNTCP2V4Address () :
+						(i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? ri.GetYggdrasilAddress () : ri.GetNTCP2V6Address ());
+					if (!addr || memcmp (m_Establisher->m_RemoteStaticKey, addr->s, 32))
+					{
+						LogPrint (eLogError, "NTCP2: Wrong static key in SessionConfirmed");
+						Terminate ();
+						return;
+					}
+					if (addr->IsPublishedNTCP2 () && m_RemoteEndpoint.address () != addr->host &&
+					    (!m_RemoteEndpoint.address ().is_v6 () || (i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ?
+					     memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data () + 1, addr->host.to_v6 ().to_bytes ().data () + 1, 7) : // from the same yggdrasil subnet
+					     memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), addr->host.to_v6 ().to_bytes ().data (), 8)))) // temporary address
+					{
+						LogPrint (eLogError, "NTCP2: Host mismatch between published address ", addr->host, " and actual endpoint ", m_RemoteEndpoint.address ());
+						Terminate ();
+						return;
+					}
+					i2p::data::netdb.PostI2NPMsg (CreateI2NPMessage (eI2NPDummyMsg, buf.data () + 3, size)); // TODO: should insert ri and not parse it twice
+					// TODO: process options
+
+					// ready to communicate
+					auto existing = i2p::data::netdb.FindRouter (ri.GetRouterIdentity ()->GetIdentHash ()); // check if exists already
+					SetRemoteIdentity (existing ? existing->GetRouterIdentity () : ri.GetRouterIdentity ());
+					if (m_Server.AddNTCP2Session (shared_from_this (), true))
+					{
+						Established ();
+						ReceiveLength ();
+					}
+					else
+						Terminate ();
+				}
+				else
+					Terminate ();
+			}
+			else
+				Terminate ();
 		}
 	}
 
-	void NTCP2Session::ProcessSessionConfirmed ()
-	{
-		// run on establisher thread
-		LogPrint (eLogDebug, "NTCP2: SessionConfirmed received");
-		// part 1
-		if (m_Establisher->ProcessSessionConfirmedMessagePart1 ())
-		{
-			// part 2
-			auto buf = std::make_shared<std::vector<uint8_t> > (m_Establisher->m3p2Len - 16); // -MAC
-			if (m_Establisher->ProcessSessionConfirmedMessagePart2 (buf->data ())) // TODO:handle in establisher thread
-			{
-				// payload 
-				// RI block must be first 
-				if ((*buf)[0] != eNTCP2BlkRouterInfo)
-				{
-					LogPrint (eLogWarning, "NTCP2: Unexpected block ", (int)(*buf)[0], " in SessionConfirmed");
-					boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
-					return;
-				}
-				auto size = bufbe16toh (buf->data () + 1);
-				if (size > buf->size () - 3 || size > i2p::data::MAX_RI_BUFFER_SIZE + 1)
-				{
-					LogPrint (eLogError, "NTCP2: Unexpected RouterInfo size ", size, " in SessionConfirmed");
-					boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
-					return;
-				}
-				boost::asio::post (m_Server.GetService (), 
-					[s = shared_from_this (), buf, size] ()
-					{
-						s->EstablishSessionAfterSessionConfirmed (buf, size);
-					});
-			}
-			else
-				boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
-		}
-		else
-			boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));	
-	}	
-
-	void NTCP2Session::EstablishSessionAfterSessionConfirmed (std::shared_ptr<std::vector<uint8_t> > buf, size_t size)
-	{
-		// run on main NTCP2 thread
-		KeyDerivationFunctionDataPhase ();
-		// Bob data phase keys
-		m_SendKey = m_Kba;
-		m_ReceiveKey = m_Kab;
-		SetSipKeys (m_Sipkeysba, m_Sipkeysab);
-		memcpy (m_ReceiveIV.buf, m_Sipkeysab + 16, 8);
-		memcpy (m_SendIV.buf, m_Sipkeysba + 16, 8);
-		// we need to set keys for SendTerminationAndTerminate
-		// TODO: check flag
-		i2p::data::RouterInfo ri (buf->data () + 4, size - 1); // 1 byte block type + 2 bytes size + 1 byte flag
-		if (ri.IsUnreachable ())
-		{
-			LogPrint (eLogError, "NTCP2: RouterInfo verification failed in SessionConfirmed from ", GetRemoteEndpoint ());
-			SendTerminationAndTerminate (eNTCP2RouterInfoSignatureVerificationFail);
-			return;
-		}
-		LogPrint(eLogDebug, "NTCP2: SessionConfirmed from ", GetRemoteEndpoint (),
-			" (", i2p::data::GetIdentHashAbbreviation (ri.GetIdentHash ()), ")");
-		auto ts = i2p::util::GetMillisecondsSinceEpoch ();
-		if (ts > ri.GetTimestamp () + i2p::data::NETDB_MIN_EXPIRATION_TIMEOUT*1000LL) // 90 minutes
-		{
-			LogPrint (eLogError, "NTCP2: RouterInfo is too old in SessionConfirmed for ", (ts - ri.GetTimestamp ())/1000LL, " seconds");
-			SendTerminationAndTerminate (eNTCP2Message3Error);
-			return;
-		}
-		if (ts + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri.GetTimestamp ()) // 2 minutes
-		{
-			LogPrint (eLogError, "NTCP2: RouterInfo is from future for ", (ri.GetTimestamp () - ts)/1000LL, " seconds");
-			SendTerminationAndTerminate (eNTCP2Message3Error);
-			return;
-		}	
-		// update RouterInfo in netdb
-		auto ri1 = i2p::data::netdb.AddRouterInfo (ri.GetBuffer (), ri.GetBufferLen ()); // ri1 points to one from netdb now
-		if (!ri1)
-		{
-			LogPrint (eLogError, "NTCP2: Couldn't update RouterInfo from SessionConfirmed in netdb");
-			Terminate ();
-			return;
-		}
-		
-		bool isOlder = false;
-		if (ri.GetTimestamp () + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri1->GetTimestamp ())
-		{	
-			// received RouterInfo is older than one in netdb
-			isOlder = true;
-			if (ri1->HasProfile ())
-			{	
-				auto profile = i2p::data::GetRouterProfile (ri1->GetIdentHash ()); // retrieve profile	
-				if (profile && profile->IsDuplicated ())
-				{	
-					SendTerminationAndTerminate (eNTCP2Banned);
-					return;
-				}	
-			}	
-		}
-		
-		auto addr = m_RemoteEndpoint.address ().is_v4 () ? ri1->GetNTCP2V4Address () :
-			(i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? ri1->GetYggdrasilAddress () : ri1->GetNTCP2V6Address ());
-		if (!addr || memcmp (m_Establisher->m_RemoteStaticKey, addr->s, 32))
-		{
-			LogPrint (eLogError, "NTCP2: Wrong static key in SessionConfirmed");
-			Terminate ();
-			return;
-		}
-		if (addr->IsPublishedNTCP2 () && m_RemoteEndpoint.address () != addr->host &&
-		    (!m_RemoteEndpoint.address ().is_v6 () || (i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ?
-		     memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data () + 1, addr->host.to_v6 ().to_bytes ().data () + 1, 7) : // from the same yggdrasil subnet
-		     memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), addr->host.to_v6 ().to_bytes ().data (), 8)))) // temporary address
-		{
-			if (isOlder) // older router?
-				i2p::data::UpdateRouterProfile (ri1->GetIdentHash (),
-					[](std::shared_ptr<i2p::data::RouterProfile> profile)
-					{
-						if (profile) profile->Duplicated (); // mark router as duplicated in profile
-					});
-			else
-				LogPrint (eLogInfo, "NTCP2: Host mismatch between published address ", addr->host, " and actual endpoint ", m_RemoteEndpoint.address ());
-			SendTerminationAndTerminate (eNTCP2Banned);
-			return;
-		}
-		// TODO: process options block
-
-		// ready to communicate
-		SetRemoteIdentity (ri1->GetRouterIdentity ());
-		if (m_Server.AddNTCP2Session (shared_from_this (), true))
-		{
-			Established ();
-			ReceiveLength ();
-		}
-		else
-			Terminate ();
-	}	
-		
 	void NTCP2Session::SetSipKeys (const uint8_t * sendSipKey, const uint8_t * receiveSipKey)
 	{
 #if OPENSSL_SIPHASH
@@ -907,11 +783,7 @@ namespace transport
 	void NTCP2Session::ClientLogin ()
 	{
 		m_Establisher->CreateEphemeralKey ();
-		boost::asio::post (m_Server.GetEstablisherService (), 
-		    [s = shared_from_this ()] ()
-			{
-				s->SendSessionRequest ();
-			});	
+		SendSessionRequest ();
 	}
 
 	void NTCP2Session::ServerLogin ()
@@ -961,19 +833,14 @@ namespace transport
 				CreateNextReceivedBuffer (m_NextReceivedLen);
 				boost::system::error_code ec;
 				size_t moreBytes = m_Socket.available(ec);
-				if (!ec)
-				{	
-					if (moreBytes >= m_NextReceivedLen)
-					{
-						// read and process message immediately if available
-						moreBytes = boost::asio::read (m_Socket, boost::asio::buffer(m_NextReceivedBuffer, m_NextReceivedLen), boost::asio::transfer_all (), ec);
-						HandleReceived (ec, moreBytes);
-					}
-					else
-						Receive ();
-				}	
+				if (!ec && moreBytes >= m_NextReceivedLen)
+				{
+					// read and process message immediately if available
+					moreBytes = boost::asio::read (m_Socket, boost::asio::buffer(m_NextReceivedBuffer, m_NextReceivedLen), boost::asio::transfer_all (), ec);
+					HandleReceived (ec, moreBytes);
+				}
 				else
-					LogPrint (eLogWarning, "NTCP2: Socket error: ", ec.message ());
+					Receive ();
 			}
 			else
 			{
@@ -999,8 +866,8 @@ namespace transport
 	{
 		if (ecode)
 		{
-			if (ecode != boost::asio::error::operation_aborted)	
-				LogPrint (eLogWarning, "NTCP2: Receive read error: ", ecode.message ());	
+			if (ecode != boost::asio::error::operation_aborted)
+				LogPrint (eLogWarning, "NTCP2: Receive read error: ", ecode.message ());
 			Terminate ();
 		}
 		else
@@ -1009,7 +876,7 @@ namespace transport
 			i2p::transport::transports.UpdateReceivedBytes (bytes_transferred + 2);
 			uint8_t nonce[12];
 			CreateNonce (m_ReceiveSequenceNumber, nonce); m_ReceiveSequenceNumber++;
-			if (m_Server.AEADChaCha20Poly1305Decrypt (m_NextReceivedBuffer, m_NextReceivedLen-16, nullptr, 0, m_ReceiveKey, nonce, m_NextReceivedBuffer, m_NextReceivedLen))
+			if (i2p::crypto::AEADChaCha20Poly1305 (m_NextReceivedBuffer, m_NextReceivedLen-16, nullptr, 0, m_ReceiveKey, nonce, m_NextReceivedBuffer, m_NextReceivedLen, false))
 			{
 				LogPrint (eLogDebug, "NTCP2: Received message decrypted");
 				ProcessNextFrame (m_NextReceivedBuffer, m_NextReceivedLen-16);
@@ -1061,20 +928,8 @@ namespace transport
 				break;
 				case eNTCP2BlkRouterInfo:
 				{
-					LogPrint (eLogDebug, "NTCP2: RouterInfo flag=", (int)frame[offset]);	
-					if (size <= i2p::data::MAX_RI_BUFFER_SIZE + 1)
-					{		
-						auto newRi = i2p::data::netdb.AddRouterInfo (frame + offset + 1, size - 1);
-						if (newRi)
-						{
-							auto remoteIdentity = GetRemoteIdentity ();
-							if (remoteIdentity && remoteIdentity->GetIdentHash () == newRi->GetIdentHash ())
-								// peer's RouterInfo update
-								SetRemoteIdentity (newRi->GetIdentity ());
-						}	
-					}	
-					else
-						LogPrint (eLogInfo, "NTCP2: RouterInfo block is too long ", size);
+					LogPrint (eLogDebug, "NTCP2: RouterInfo flag=", (int)frame[offset]);
+					i2p::data::netdb.PostI2NPMsg (CreateI2NPMessage (eI2NPDummyMsg, frame + offset, size));
 					break;
 				}
 				case eNTCP2BlkI2NPMessage:
@@ -1191,14 +1046,9 @@ namespace transport
 			macBuf = m_NextSendBuffer + paddingLen;
 			totalLen += paddingLen;
 		}
-		if (totalLen > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE)
-		{
-			LogPrint (eLogError, "NTCP2: Frame to send is too long ", totalLen);
-			return;
-		}	
 		uint8_t nonce[12];
 		CreateNonce (m_SendSequenceNumber, nonce); m_SendSequenceNumber++;
-		m_Server.AEADChaCha20Poly1305Encrypt (encryptBufs, m_SendKey, nonce, macBuf); // encrypt buffers
+		i2p::crypto::AEADChaCha20Poly1305Encrypt (encryptBufs, m_SendKey, nonce, macBuf); // encrypt buffers
 		SetNextSentFrameLength (totalLen + 16, first->GetNTCP2Header () - 5); // frame length right before first block
 
 		// send buffers
@@ -1220,16 +1070,10 @@ namespace transport
 			delete[] m_NextSendBuffer; m_NextSendBuffer = nullptr;
 			return;
 		}
-		if (payloadLen > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE)
-		{
-			LogPrint (eLogError, "NTCP2: Buffer to send is too long ", payloadLen);
-			delete[] m_NextSendBuffer; m_NextSendBuffer = nullptr;
-			return;
-		}	
 		// encrypt
 		uint8_t nonce[12];
 		CreateNonce (m_SendSequenceNumber, nonce); m_SendSequenceNumber++;
-		m_Server.AEADChaCha20Poly1305Encrypt ({ {m_NextSendBuffer + 2, payloadLen} }, m_SendKey, nonce, m_NextSendBuffer + payloadLen + 2);
+		i2p::crypto::AEADChaCha20Poly1305Encrypt ({ {m_NextSendBuffer + 2, payloadLen} }, m_SendKey, nonce, m_NextSendBuffer + payloadLen + 2);
 		SetNextSentFrameLength (payloadLen + 16, m_NextSendBuffer);
 		// send
 		m_IsSending = true;
@@ -1256,7 +1100,7 @@ namespace transport
 			if (GetLastActivityTimestamp () > m_NextRouterInfoResendTime)
 			{
 				m_NextRouterInfoResendTime += NTCP2_ROUTERINFO_RESEND_INTERVAL +
-					m_Server.GetRng ()() % NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD;
+					rand ()%NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD;
 				SendRouterInfo ();
 			}
 			else
@@ -1269,34 +1113,23 @@ namespace transport
 
 	void NTCP2Session::SendQueue ()
 	{
-		if (!m_SendQueue.empty () && m_IsEstablished)
+		if (!m_SendQueue.empty ())
 		{
 			std::vector<std::shared_ptr<I2NPMessage> > msgs;
-			auto ts = i2p::util::GetMillisecondsSinceEpoch ();
 			size_t s = 0;
 			while (!m_SendQueue.empty ())
 			{
 				auto msg = m_SendQueue.front ();
-				if (!msg || msg->IsExpired (ts))
-				{
-					// drop null or expired message
-					if (msg) msg->Drop ();
-					m_SendQueue.pop_front ();
-					continue;
-				}	
 				size_t len = msg->GetNTCP2Length ();
 				if (s + len + 3 <= NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) // 3 bytes block header
 				{
 					msgs.push_back (msg);
 					s += (len + 3);
 					m_SendQueue.pop_front ();
-					if (s >= NTCP2_SEND_AFTER_FRAME_SIZE)
-						break; // send frame right a way
 				}
 				else if (len + 3 > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE)
 				{
 					LogPrint (eLogError, "NTCP2: I2NP message of size ", len, " can't be sent. Dropped");
-					msg->Drop ();
 					m_SendQueue.pop_front ();
 				}
 				else
@@ -1306,33 +1139,13 @@ namespace transport
 		}
 	}
 
-	void NTCP2Session::MoveSendQueue (std::shared_ptr<NTCP2Session> other)
-	{
-		if (!other || m_SendQueue.empty ()) return;
-		std::list<std::shared_ptr<I2NPMessage> > msgs;
-		auto ts = i2p::util::GetMillisecondsSinceEpoch ();
-		for (auto it: m_SendQueue)
-			if (!it->IsExpired (ts))
-				msgs.push_back (it);
-			else
-				it->Drop ();
-		m_SendQueue.clear ();
-		if (!msgs.empty ())
-			other->SendI2NPMessages (msgs);
-	}	
-		
 	size_t NTCP2Session::CreatePaddingBlock (size_t msgLen, uint8_t * buf, size_t len)
 	{
 		if (len < 3) return 0;
 		len -= 3;
 		if (msgLen < 256) msgLen = 256; // for short message padding should not be always zero
 		size_t paddingSize = (msgLen*NTCP2_MAX_PADDING_RATIO)/100;
-		if (msgLen + paddingSize + 3 > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) 
-		{	
-			int l = (int)NTCP2_UNENCRYPTED_FRAME_MAX_SIZE - msgLen -3;
-			if (l <= 0) return 0;
-			paddingSize = l;
-		}	
+		if (msgLen + paddingSize + 3 > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) paddingSize = NTCP2_UNENCRYPTED_FRAME_MAX_SIZE - msgLen -3;
 		if (paddingSize > len) paddingSize = len;
 		if (paddingSize)
 		{
@@ -1341,7 +1154,7 @@ namespace transport
 				RAND_bytes ((uint8_t *)m_PaddingSizes, sizeof (m_PaddingSizes));
 				m_NextPaddingSize = 0;
 			}
-			paddingSize = m_PaddingSizes[m_NextPaddingSize++] % (paddingSize + 1);
+			paddingSize = m_PaddingSizes[m_NextPaddingSize++] % paddingSize;
 		}
 		buf[0] = eNTCP2BlkPadding; // blk
 		htobe16buf (buf + 1, paddingSize); // size
@@ -1352,8 +1165,7 @@ namespace transport
 	void NTCP2Session::SendRouterInfo ()
 	{
 		if (!IsEstablished ()) return;
-		auto riBuffer =  i2p::context.CopyRouterInfoBuffer ();
-		auto riLen = riBuffer->GetBufferLen ();
+		auto riLen = i2p::context.GetRouterInfo ().GetBufferLen ();
 		size_t payloadLen = riLen + 3 + 1 + 7; // 3 bytes block header + 1 byte RI flag + 7 bytes DateTime
 		m_NextSendBuffer = new uint8_t[payloadLen + 16 + 2 + 64]; // up to 64 bytes padding
 		// DateTime	block
@@ -1364,7 +1176,7 @@ namespace transport
 		m_NextSendBuffer[9] = eNTCP2BlkRouterInfo;
 		htobe16buf (m_NextSendBuffer + 10, riLen + 1); // size
 		m_NextSendBuffer[12] = 0; // flag
-		memcpy (m_NextSendBuffer + 13, riBuffer->data (), riLen); // TODO: eliminate extra copy
+		memcpy (m_NextSendBuffer + 13, i2p::context.GetRouterInfo ().GetBuffer (), riLen);
 		// padding block
 		auto paddingSize = CreatePaddingBlock (payloadLen, m_NextSendBuffer + 2 + payloadLen, 64);
 		payloadLen += paddingSize;
@@ -1396,47 +1208,20 @@ namespace transport
 	void NTCP2Session::SendTerminationAndTerminate (NTCP2TerminationReason reason)
 	{
 		SendTermination (reason);
-		boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); // let termination message go
+		m_Server.GetService ().post (std::bind (&NTCP2Session::Terminate, shared_from_this ())); // let termination message go
 	}
 
-	void NTCP2Session::SendI2NPMessages (std::list<std::shared_ptr<I2NPMessage> >& msgs)
+	void NTCP2Session::SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs)
 	{
-		if (m_IsTerminated || msgs.empty ()) 
-		{
-			msgs.clear ();
-			return;
-		}	
-		bool empty = false;
-		{
-			std::lock_guard<std::mutex> l(m_IntermediateQueueMutex);
-			empty = m_IntermediateQueue.empty ();
-			m_IntermediateQueue.splice (m_IntermediateQueue.end (), msgs);
-		}
-		if (empty)
-			boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::PostI2NPMessages, shared_from_this ()));
+		m_Server.GetService ().post (std::bind (&NTCP2Session::PostI2NPMessages, shared_from_this (), msgs));
 	}
 
-	void NTCP2Session::PostI2NPMessages ()
+	void NTCP2Session::PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs)
 	{
 		if (m_IsTerminated) return;
-		std::list<std::shared_ptr<I2NPMessage> > msgs;
-		{
-			std::lock_guard<std::mutex> l(m_IntermediateQueueMutex);
-			m_IntermediateQueue.swap (msgs);		
-		}	
-		bool isSemiFull = m_SendQueue.size () > NTCP2_MAX_OUTGOING_QUEUE_SIZE/2;
-		if (isSemiFull)
-		{	
-			for (auto it: msgs)
-				if (it->onDrop)
-					it->Drop (); // drop earlier because we can handle it
-				else
-					m_SendQueue.push_back (std::move (it));
-		}	
-		else
-			m_SendQueue.splice (m_SendQueue.end (), msgs);
-		
-		if (!m_IsSending && m_IsEstablished)
+		for (auto it: msgs)
+			m_SendQueue.push_back (std::move (it));
+		if (!m_IsSending)
 			SendQueue ();
 		else if (m_SendQueue.size () > NTCP2_MAX_OUTGOING_QUEUE_SIZE)
 		{
@@ -1449,20 +1234,13 @@ namespace transport
 
 	void NTCP2Session::SendLocalRouterInfo (bool update)
 	{
-		if (update || !IsOutgoing ()) // we send it in SessionConfirmed for outgoing session
-			boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::SendRouterInfo, shared_from_this ()));
+		if (update || !IsOutgoing ()) // we send it in SessionConfirmed for ougoing session
+			m_Server.GetService ().post (std::bind (&NTCP2Session::SendRouterInfo, shared_from_this ()));
 	}
 
-	i2p::data::RouterInfo::SupportedTransports NTCP2Session::GetTransportType () const
-	{
-		if (m_RemoteEndpoint.address ().is_v4 ()) return i2p::data::RouterInfo::eNTCP2V4;
-		return i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? i2p::data::RouterInfo::eNTCP2V6Mesh : i2p::data::RouterInfo::eNTCP2V6;
-	}	
-		
 	NTCP2Server::NTCP2Server ():
 		RunnableServiceWithWork ("NTCP2"), m_TerminationTimer (GetService ()),
-		m_ProxyType(eNoProxy), m_Resolver(GetService ()),
-		m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL)
+			m_ProxyType(eNoProxy), m_Resolver(GetService ())
 	{
 	}
 
@@ -1473,7 +1251,6 @@ namespace transport
 
 	void NTCP2Server::Start ()
 	{
-		m_EstablisherService.Start ();
 		if (!IsRunning ())
 		{
 			StartIOService ();
@@ -1481,13 +1258,14 @@ namespace transport
 			{
 				LogPrint(eLogInfo, "NTCP2: Using proxy to connect to peers");
 				// TODO: resolve proxy until it is resolved
+				boost::asio::ip::tcp::resolver::query q(m_ProxyAddress, std::to_string(m_ProxyPort));
 				boost::system::error_code e;
-				auto itr = m_Resolver.resolve(m_ProxyAddress, std::to_string(m_ProxyPort), e);
+				auto itr = m_Resolver.resolve(q, e);
 				if(e)
 					LogPrint(eLogCritical, "NTCP2: Failed to resolve proxy ", e.message());
 				else
 				{
-					m_ProxyEndpoint.reset (new boost::asio::ip::tcp::endpoint(*itr.begin ()));
+					m_ProxyEndpoint.reset (new boost::asio::ip::tcp::endpoint(*itr));
 					if (m_ProxyEndpoint)
 						LogPrint(eLogDebug, "NTCP2: m_ProxyEndpoint ", *m_ProxyEndpoint);
 				}
@@ -1568,7 +1346,6 @@ namespace transport
 
 	void NTCP2Server::Stop ()
 	{
-		m_EstablisherService.Stop ();
 		{
 			// we have to copy it because Terminate changes m_NTCP2Sessions
 			auto ntcpSessions = m_NTCP2Sessions;
@@ -1607,7 +1384,6 @@ namespace transport
 			{
 				// replace by new session
 				auto s = it->second;
-				s->MoveSendQueue (session);
 				m_NTCP2Sessions.erase (it);
 				s->Terminate ();
 			}
@@ -1624,11 +1400,7 @@ namespace transport
 	void NTCP2Server::RemoveNTCP2Session (std::shared_ptr<NTCP2Session> session)
 	{
 		if (session && session->GetRemoteIdentity ())
-		{
-			auto it = m_NTCP2Sessions.find (session->GetRemoteIdentity ()->GetIdentHash ());
-			if (it != m_NTCP2Sessions.end () && it->second == session)
-				m_NTCP2Sessions.erase (it);
-		}	
+			m_NTCP2Sessions.erase (session->GetRemoteIdentity ()->GetIdentHash ());
 	}
 
 	std::shared_ptr<NTCP2Session> NTCP2Server::FindNTCP2Session (const i2p::data::IdentHash& ident)
@@ -1648,7 +1420,7 @@ namespace transport
 		}
 		LogPrint (eLogDebug, "NTCP2: Connecting to ", conn->GetRemoteEndpoint (),
 			" (", i2p::data::GetIdentHashAbbreviation (conn->GetRemoteIdentity ()->GetIdentHash ()), ")");
-		boost::asio::post (GetService (), [this, conn]()
+		GetService ().post([this, conn]()
 			{
 				if (this->AddNTCP2Session (conn))
 				{
@@ -1718,7 +1490,7 @@ namespace transport
 			if (!ec)
 			{
 				LogPrint (eLogDebug, "NTCP2: Connected from ", ep);
-				if (!i2p::transport::transports.IsInReservedRange(ep.address ()))
+				if (!i2p::util::net::IsInReservedRange(ep.address ()))
 				{
 					if (m_PendingIncomingSessions.emplace (ep.address (), conn).second)
 					{
@@ -1765,7 +1537,7 @@ namespace transport
 			if (!ec)
 			{
 				LogPrint (eLogDebug, "NTCP2: Connected from ", ep);
-				if (!i2p::transport::transports.IsInReservedRange(ep.address ()) ||
+				if (!i2p::util::net::IsInReservedRange(ep.address ()) ||
 				    i2p::util::net::IsYggdrasilAddress (ep.address ()))
 				{
 					if (m_PendingIncomingSessions.emplace (ep.address (), conn).second)
@@ -1806,8 +1578,7 @@ namespace transport
 
 	void NTCP2Server::ScheduleTermination ()
 	{
-		m_TerminationTimer.expires_from_now (boost::posix_time::seconds(
-			NTCP2_TERMINATION_CHECK_TIMEOUT + m_Rng () % NTCP2_TERMINATION_CHECK_TIMEOUT_VARIANCE));
+		m_TerminationTimer.expires_from_now (boost::posix_time::seconds(NTCP2_TERMINATION_CHECK_TIMEOUT));
 		m_TerminationTimer.async_wait (std::bind (&NTCP2Server::HandleTerminationTimer,
 			this, std::placeholders::_1));
 	}
@@ -1869,7 +1640,7 @@ namespace transport
 			LogPrint (eLogError, "NTCP2: Can't connect to unspecified address");
 			return;
 		}
-		boost::asio::post (GetService(), [this, conn]()
+		GetService().post([this, conn]()
 		{
 			if (this->AddNTCP2Session (conn))
 			{
@@ -1914,18 +1685,47 @@ namespace transport
 			case eSocksProxy:
 			{
 				// TODO: support username/password auth etc
-				Socks5Handshake (conn->GetSocket(), conn->GetRemoteEndpoint (),
-					[conn, timer](const boost::system::error_code& ec)
-				    {
-						timer->cancel();
-						if (!ec)
-							conn->ClientLogin();
-						else
+				static const uint8_t buff[3] = {SOCKS5_VER, 0x01, 0x00};
+				boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff, 3), boost::asio::transfer_all(),
+					[] (const boost::system::error_code & ec, std::size_t transferred)
+					{
+						(void) transferred;
+						if(ec)
 						{
-							LogPrint(eLogError, "NTCP2: SOCKS proxy handshake error ", ec.message());
-							conn->Terminate();	
-						}		
-					});	
+							LogPrint(eLogWarning, "NTCP2: SOCKS5 write error ", ec.message());
+						}
+					});
+				auto readbuff = std::make_shared<std::vector<uint8_t> >(2);
+				boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff->data (), 2),
+					[this, readbuff, timer, conn](const boost::system::error_code & ec, std::size_t transferred)
+					{
+						if(ec)
+						{
+							LogPrint(eLogError, "NTCP2: SOCKS5 read error ", ec.message());
+							timer->cancel();
+							conn->Terminate();
+							return;
+						}
+						else if(transferred == 2)
+						{
+							if((*readbuff)[1] == 0x00)
+							{
+								AfterSocksHandshake(conn, timer);
+								return;
+							}
+							else if ((*readbuff)[1] == 0xff)
+							{
+								LogPrint(eLogError, "NTCP2: SOCKS5 proxy rejected authentication");
+								timer->cancel();
+								conn->Terminate();
+								return;
+							}
+							LogPrint(eLogError, "NTCP2:", (int)(*readbuff)[1]);
+						}
+						LogPrint(eLogError, "NTCP2: SOCKS5 server gave invalid response");
+						timer->cancel();
+						conn->Terminate();
+					});
 				break;
 			}
 			case eHTTPProxy:
@@ -1953,7 +1753,7 @@ namespace transport
 							LogPrint(eLogError, "NTCP2: HTTP proxy write error ", ec.message());
 					});
 
-				auto readbuff = std::make_shared<boost::asio::streambuf>();
+				boost::asio::streambuf * readbuff = new boost::asio::streambuf;
 				boost::asio::async_read_until(conn->GetSocket(), *readbuff, "\r\n\r\n",
 					[readbuff, timer, conn] (const boost::system::error_code & ec, std::size_t transferred)
 					{
@@ -1967,12 +1767,13 @@ namespace transport
 						{
 							readbuff->commit(transferred);
 							i2p::http::HTTPRes res;
-							if(res.parse(std::string {boost::asio::buffers_begin(readbuff->data ()), boost::asio::buffers_begin(readbuff->data ()) + readbuff->size ()}) > 0)
+							if(res.parse(boost::asio::buffer_cast<const char*>(readbuff->data()), readbuff->size()) > 0)
 							{
 								if(res.code == 200)
 								{
 									timer->cancel();
 									conn->ClientLogin();
+									delete readbuff;
 									return;
 								}
 								else
@@ -1982,6 +1783,7 @@ namespace transport
 								LogPrint(eLogError, "NTCP2: HTTP proxy gave malformed response");
 							timer->cancel();
 							conn->Terminate();
+							delete readbuff;
 						}
 					});
 				break;
@@ -1991,6 +1793,71 @@ namespace transport
 		}
 	}
 
+	void NTCP2Server::AfterSocksHandshake(std::shared_ptr<NTCP2Session> conn, std::shared_ptr<boost::asio::deadline_timer> timer)
+	{
+		// build request
+		size_t sz = 6; // header + port
+		auto buff = std::make_shared<std::vector<int8_t> >(256);
+		auto readbuff = std::make_shared<std::vector<int8_t> >(256);
+		(*buff)[0] = SOCKS5_VER;
+		(*buff)[1] = SOCKS5_CMD_CONNECT;
+		(*buff)[2] = 0x00;
+
+		auto& ep = conn->GetRemoteEndpoint ();
+		if(ep.address ().is_v4 ())
+		{
+			(*buff)[3] = SOCKS5_ATYP_IPV4;
+			auto addrbytes = ep.address ().to_v4().to_bytes();
+			sz += 4;
+			memcpy(buff->data () + 4, addrbytes.data(), 4);
+		}
+		else if (ep.address ().is_v6 ())
+		{
+			(*buff)[3] = SOCKS5_ATYP_IPV6;
+			auto addrbytes = ep.address ().to_v6().to_bytes();
+			sz += 16;
+			memcpy(buff->data () + 4, addrbytes.data(), 16);
+		}
+		else
+		{
+			// We mustn't really fall here because all connections are made to IP addresses
+			LogPrint(eLogError, "NTCP2: Tried to connect to unexpected address via proxy");
+			return;
+		}
+		htobe16buf(buff->data () + sz - 2, ep.port ());
+		boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff->data (), sz), boost::asio::transfer_all(),
+			[buff](const boost::system::error_code & ec, std::size_t written)
+			{
+				if(ec)
+				{
+					LogPrint(eLogError, "NTCP2: Failed to write handshake to socks proxy ", ec.message());
+					return;
+				}
+			});
+
+		boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff->data (), SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE), // read min reply size
+		    boost::asio::transfer_all(),
+			[timer, conn, readbuff](const boost::system::error_code & e, std::size_t transferred)
+			{
+				if (e)
+					LogPrint(eLogError, "NTCP2: SOCKS proxy read error ", e.message());
+				else if (!(*readbuff)[1]) // succeeded
+				{
+					boost::system::error_code ec;
+					size_t moreBytes = conn->GetSocket ().available(ec);
+					if (moreBytes) // read remaining portion of reply if ipv6 received
+						boost::asio::read (conn->GetSocket (), boost::asio::buffer(readbuff->data (), moreBytes), boost::asio::transfer_all (), ec);
+					timer->cancel();
+					conn->ClientLogin();
+					return;
+				}
+				else
+					LogPrint(eLogError, "NTCP2: Proxy reply error ", (int)(*readbuff)[1]);
+				timer->cancel();
+				conn->Terminate();
+			});
+	}
+
 	void NTCP2Server::SetLocalAddress (const boost::asio::ip::address& localAddress)
 	{
 		auto addr = std::make_shared<boost::asio::ip::tcp::endpoint>(boost::asio::ip::tcp::endpoint(localAddress, 0));
@@ -2004,17 +1871,5 @@ namespace transport
 		else
 			m_Address4 = addr;
 	}
-
-	void NTCP2Server::AEADChaCha20Poly1305Encrypt (const std::vector<std::pair<uint8_t *, size_t> >& bufs, 
-		const uint8_t * key, const uint8_t * nonce, uint8_t * mac)
-	{
-		return m_Encryptor.Encrypt (bufs, key, nonce, mac);
-	}	
-		
-	bool NTCP2Server::AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen,
-		const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len)
-	{
-		return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len);
-	}	
 }
 }
diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h
index 5ad5b955..ba1380c3 100644
--- a/libi2pd/NTCP2.h
+++ b/libi2pd/NTCP2.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2022, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -14,7 +14,6 @@
 #include <list>
 #include <map>
 #include <array>
-#include <random>
 #include <openssl/bn.h>
 #include <openssl/evp.h>
 #include <boost/asio.hpp>
@@ -29,17 +28,14 @@ namespace transport
 {
 
 	const size_t NTCP2_UNENCRYPTED_FRAME_MAX_SIZE = 65519;
-	const size_t NTCP2_SEND_AFTER_FRAME_SIZE = 16386; // send frame when exceeds this size
 	const size_t NTCP2_SESSION_REQUEST_MAX_SIZE = 287;
 	const size_t NTCP2_SESSION_CREATED_MAX_SIZE = 287;
 	const int NTCP2_MAX_PADDING_RATIO = 6; // in %
 
 	const int NTCP2_CONNECT_TIMEOUT = 5; // 5 seconds
 	const int NTCP2_ESTABLISH_TIMEOUT = 10; // 10 seconds
-	const int NTCP2_TERMINATION_TIMEOUT = 115; // 2 minutes - 5 seconds
-	const int NTCP2_TERMINATION_TIMEOUT_VARIANCE = 10; // 10 seconds
-	const int NTCP2_TERMINATION_CHECK_TIMEOUT = 28; // 28 seconds
-	const int NTCP2_TERMINATION_CHECK_TIMEOUT_VARIANCE = 5; // 5 seconds
+	const int NTCP2_TERMINATION_TIMEOUT = 120; // 2 minutes
+	const int NTCP2_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds
 	const int NTCP2_RECEIVE_BUFFER_DELETION_TIMEOUT = 3; // 3 seconds
 	const int NTCP2_ROUTERINFO_RESEND_INTERVAL = 25*60; // 25 minuntes in seconds
 	const int NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD = 25*60; // 25 minuntes
@@ -91,29 +87,30 @@ namespace transport
 		const uint8_t * GetRemotePub () const { return m_RemoteEphemeralPublicKey; }; // Y for Alice and X for Bob
 		uint8_t * GetRemotePub () { return m_RemoteEphemeralPublicKey; }; // to set
 
+		const uint8_t * GetK () const { return m_CK + 32; };
 		const uint8_t * GetCK () const { return m_CK; };
 		const uint8_t * GetH () const { return m_H; };
 
-		bool KDF1Alice ();
-		bool KDF1Bob ();
-		bool KDF2Alice ();
-		bool KDF2Bob ();
-		bool KDF3Alice (); // for SessionConfirmed part 2
-		bool KDF3Bob ();
+		void KDF1Alice ();
+		void KDF1Bob ();
+		void KDF2Alice ();
+		void KDF2Bob ();
+		void KDF3Alice (); // for SessionConfirmed part 2
+		void KDF3Bob ();
 
-		bool KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub); // for SessionRequest, (pub, priv) for DH
-		bool KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub); // for SessionCreate
+		void KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub); // for SessionRequest, (pub, priv) for DH
+		void KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub); // for SessionCreate
 		void CreateEphemeralKey ();
 
-		bool CreateSessionRequestMessage (std::mt19937& rng);
-		bool CreateSessionCreatedMessage (std::mt19937& rng);
-		bool CreateSessionConfirmedMessagePart1 ();
-		bool CreateSessionConfirmedMessagePart2 ();
+		void CreateSessionRequestMessage ();
+		void CreateSessionCreatedMessage ();
+		void CreateSessionConfirmedMessagePart1 (const uint8_t * nonce);
+		void CreateSessionConfirmedMessagePart2 (const uint8_t * nonce);
 
 		bool ProcessSessionRequestMessage (uint16_t& paddingLen, bool& clockSkew);
 		bool ProcessSessionCreatedMessage (uint16_t& paddingLen);
-		bool ProcessSessionConfirmedMessagePart1 ();
-		bool ProcessSessionConfirmedMessagePart2 (uint8_t * m3p2Buf);
+		bool ProcessSessionConfirmedMessagePart1 (const uint8_t * nonce);
+		bool ProcessSessionConfirmedMessagePart2 (const uint8_t * nonce, uint8_t * m3p2Buf);
 
 		std::shared_ptr<i2p::crypto::X25519Keys> m_EphemeralKeys;
 		uint8_t m_RemoteEphemeralPublicKey[32]; // x25519
@@ -146,16 +143,14 @@ namespace transport
 			void SetRemoteEndpoint (const boost::asio::ip::tcp::endpoint& ep) { m_RemoteEndpoint = ep; };
 
 			bool IsEstablished () const override { return m_IsEstablished; };
-			i2p::data::RouterInfo::SupportedTransports GetTransportType () const override;
 			bool IsTerminated () const { return m_IsTerminated; };
 
 			void ClientLogin (); // Alice
 			void ServerLogin (); // Bob
 
 			void SendLocalRouterInfo (bool update) override; // after handshake or by update
-			void SendI2NPMessages (std::list<std::shared_ptr<I2NPMessage> >& msgs) override;
-			void MoveSendQueue (std::shared_ptr<NTCP2Session> other);
-			
+			void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs) override;
+
 		private:
 
 			void Established ();
@@ -172,17 +167,13 @@ namespace transport
 
 			void HandleSessionRequestSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
 			void HandleSessionRequestReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
-			void ProcessSessionRequest (size_t len);
 			void HandleSessionRequestPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
 			void HandleSessionCreatedSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
 			void HandleSessionCreatedReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
-			void ProcessSessionCreated (size_t len);
 			void HandleSessionCreatedPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
 			void HandleSessionConfirmedSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
 			void HandleSessionConfirmedReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
-			void ProcessSessionConfirmed ();
-			void EstablishSessionAfterSessionConfirmed (std::shared_ptr<std::vector<uint8_t> > buf, size_t size);
-			
+
 			// data
 			void ReceiveLength ();
 			void HandleReceivedLength (const boost::system::error_code& ecode, std::size_t bytes_transferred);
@@ -200,7 +191,7 @@ namespace transport
 			void SendRouterInfo ();
 			void SendTermination (NTCP2TerminationReason reason);
 			void SendTerminationAndTerminate (NTCP2TerminationReason reason);
-			void PostI2NPMessages ();
+			void PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs);
 
 		private:
 
@@ -233,28 +224,13 @@ namespace transport
 			bool m_IsSending, m_IsReceiving;
 			std::list<std::shared_ptr<I2NPMessage> > m_SendQueue;
 			uint64_t m_NextRouterInfoResendTime; // seconds since epoch
-			
-			std::list<std::shared_ptr<I2NPMessage> > m_IntermediateQueue; // from transports
-			mutable std::mutex m_IntermediateQueueMutex;
-			
+
 			uint16_t m_PaddingSizes[16];
 			int m_NextPaddingSize;
 	};
 
 	class NTCP2Server: private i2p::util::RunnableServiceWithWork
 	{
-		private:
-
-			class EstablisherService: public i2p::util::RunnableServiceWithWork
-			{
-				public:
-
-					EstablisherService (): RunnableServiceWithWork ("NTCP2e") {};
-					auto& GetService () { return GetIOService (); };
-					void Start () { StartIOService (); };
-					void Stop () { StopIOService (); };
-			};
-			
 		public:
 
 			enum ProxyType
@@ -263,20 +239,13 @@ namespace transport
 				eSocksProxy,
 				eHTTPProxy
 			};
-			
+
 			NTCP2Server ();
 			~NTCP2Server ();
 
 			void Start ();
 			void Stop ();
-			auto& GetService () { return GetIOService (); };
-			auto& GetEstablisherService () { return m_EstablisherService.GetService (); };
-			std::mt19937& GetRng () { return m_Rng; };
-			void AEADChaCha20Poly1305Encrypt (const std::vector<std::pair<uint8_t *, size_t> >& bufs, 
-				const uint8_t * key, const uint8_t * nonce, uint8_t * mac);
-			bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
-				const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); 
-			
+			boost::asio::io_service& GetService () { return GetIOService (); };
 
 			bool AddNTCP2Session (std::shared_ptr<NTCP2Session> session, bool incoming = false);
 			void RemoveNTCP2Session (std::shared_ptr<NTCP2Session> session);
@@ -297,7 +266,8 @@ namespace transport
 
 			void HandleConnect (const boost::system::error_code& ecode, std::shared_ptr<NTCP2Session> conn, std::shared_ptr<boost::asio::deadline_timer> timer);
 			void HandleProxyConnect(const boost::system::error_code& ecode, std::shared_ptr<NTCP2Session> conn, std::shared_ptr<boost::asio::deadline_timer> timer);
-			
+			void AfterSocksHandshake(std::shared_ptr<NTCP2Session> conn, std::shared_ptr<boost::asio::deadline_timer> timer);
+
 			// timer
 			void ScheduleTermination ();
 			void HandleTerminationTimer (const boost::system::error_code& ecode);
@@ -314,13 +284,8 @@ namespace transport
 			uint16_t m_ProxyPort;
 			boost::asio::ip::tcp::resolver m_Resolver;
 			std::unique_ptr<boost::asio::ip::tcp::endpoint> m_ProxyEndpoint;
-			
 			std::shared_ptr<boost::asio::ip::tcp::endpoint> m_Address4, m_Address6, m_YggdrasilAddress;
-			std::mt19937 m_Rng;
-			EstablisherService m_EstablisherService;
-			i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor;
-			i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor;
-			
+
 		public:
 
 			// for HTTP/I2PControl
diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp
index e53738e5..6e023c45 100644
--- a/libi2pd/NetDb.cpp
+++ b/libi2pd/NetDb.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2024, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -9,7 +9,6 @@
 #include <string.h>
 #include <fstream>
 #include <vector>
-#include <map>
 #include <boost/asio.hpp>
 #include <stdexcept>
 
@@ -37,9 +36,7 @@ namespace data
 {
 	NetDb netdb;
 
-	NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), 
-		m_Storage("netDb", "r", "routerInfo-", "dat"), m_PersistProfiles (true),
-		m_LastExploratorySelectionUpdateTime (0), m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL)
+	NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), m_Storage("netDb", "r", "routerInfo-", "dat"), m_PersistProfiles (true)
 	{
 	}
 
@@ -57,18 +54,12 @@ namespace data
 		m_Families.LoadCertificates ();
 		Load ();
 
-		if (!m_Requests)
-		{	
-			m_Requests = std::make_shared<NetDbRequests>();
-			m_Requests->Start ();
-		}
-		
 		uint16_t threshold; i2p::config::GetOption("reseed.threshold", threshold);
 		if (m_RouterInfos.size () < threshold || m_Floodfills.GetSize () < NETDB_MIN_FLOODFILLS) // reseed if # of router less than threshold or too few floodfiils
 		{
 			Reseed ();
 		}
-		else if (!GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, false, false))
+		else if (!GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, false))
 			Reseed (); // we don't have a router we can connect to. Trying to reseed
 
 		auto it = m_RouterInfos.find (i2p::context.GetIdentHash ());
@@ -84,15 +75,13 @@ namespace data
 			m_Floodfills.Insert (i2p::context.GetSharedRouterInfo ());
 
 		i2p::config::GetOption("persist.profiles", m_PersistProfiles);
-		
+
 		m_IsRunning = true;
 		m_Thread = new std::thread (std::bind (&NetDb::Run, this));
 	}
 
 	void NetDb::Stop ()
 	{
-		if (m_Requests)
-			m_Requests->Stop ();
 		if (m_IsRunning)
 		{
 			if (m_PersistProfiles)
@@ -109,109 +98,108 @@ namespace data
 				m_Thread = 0;
 			}
 			m_LeaseSets.clear();
+			m_Requests.Stop ();
 		}
-		m_Requests = nullptr;
 	}
 
 	void NetDb::Run ()
 	{
 		i2p::util::SetThreadName("NetDB");
 
-		uint64_t lastManage = 0;
-		uint64_t lastProfilesCleanup = i2p::util::GetMonotonicMilliseconds (), 
-			lastObsoleteProfilesCleanup = lastProfilesCleanup, lastApplyingProfileUpdates = lastProfilesCleanup;
-		int16_t profilesCleanupVariance = 0, obsoleteProfilesCleanVariance = 0, applyingProfileUpdatesVariance = 0;
+		uint64_t lastManage = 0, lastExploratory = 0, lastManageRequest = 0, lastDestinationCleanup = 0;
+		uint64_t lastProfilesCleanup = i2p::util::GetSecondsSinceEpoch ();
+		int16_t profilesCleanupVariance = 0;
 
-		std::list<std::shared_ptr<const I2NPMessage> > msgs;
 		while (m_IsRunning)
 		{
 			try
 			{
-				if (m_Queue.Wait (1,0)) // 1 sec
+				auto msg = m_Queue.GetNextWithTimeout (15000); // 15 sec
+				if (msg)
 				{
-					m_Queue.GetWholeQueue (msgs);
-					while (!msgs.empty ())
+					int numMsgs = 0;
+					while (msg)
 					{
-						auto msg = msgs.front (); msgs.pop_front ();
-						if (!msg) continue;
 						LogPrint(eLogDebug, "NetDb: Got request with type ", (int) msg->GetTypeID ());
 						switch (msg->GetTypeID ())
 						{
 							case eI2NPDatabaseStore:
 								HandleDatabaseStoreMsg (msg);
 							break;
+							case eI2NPDatabaseSearchReply:
+								HandleDatabaseSearchReplyMsg (msg);
+							break;
 							case eI2NPDatabaseLookup:
 								HandleDatabaseLookupMsg (msg);
 							break;
+							case eI2NPDummyMsg:
+								// plain RouterInfo from NTCP2 with flags for now
+								HandleNTCP2RouterInfoMsg (msg);
+							break;
 							default: // WTF?
 								LogPrint (eLogError, "NetDb: Unexpected message type ", (int) msg->GetTypeID ());
 								//i2p::HandleI2NPMessage (msg);
 						}
+						if (numMsgs > 100) break;
+						msg = m_Queue.Get ();
+						numMsgs++;
 					}
 				}
 				if (!m_IsRunning) break;
-				if (!i2p::transport::transports.IsOnline () || !i2p::transport::transports.IsRunning ()) 
-					continue; // don't manage netdb when offline or transports are not running
+				if (!i2p::transport::transports.IsOnline ()) continue; // don't manage netdb when offline
 
-				uint64_t mts = i2p::util::GetMonotonicMilliseconds ();
-				if (mts >= lastManage + 60000) // manage routers and leasesets every minute
+				uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
+				if (ts - lastManageRequest >= 15 || ts + 15 < lastManageRequest) // manage requests every 15 seconds
+				{
+					m_Requests.ManageRequests ();
+					lastManageRequest = ts;
+				}
+
+				if (ts - lastManage >= 60 || ts + 60 < lastManage) // manage routers and leasesets every minute
 				{
 					if (lastManage)
 					{
 						ManageRouterInfos ();
 						ManageLeaseSets ();
 					}
-					lastManage = mts;
+					lastManage = ts;
 				}
 
-				if (mts >= lastProfilesCleanup + (uint64_t)(i2p::data::PEER_PROFILE_AUTOCLEAN_TIMEOUT + profilesCleanupVariance)*1000)
+				if (ts - lastDestinationCleanup >= i2p::garlic::INCOMING_TAGS_EXPIRATION_TIMEOUT ||
+				    ts + i2p::garlic::INCOMING_TAGS_EXPIRATION_TIMEOUT < lastDestinationCleanup)
+				{
+					i2p::context.CleanupDestination ();
+					lastDestinationCleanup = ts;
+				}
+
+				if (ts - lastProfilesCleanup >= (uint64_t)(i2p::data::PEER_PROFILE_AUTOCLEAN_TIMEOUT + profilesCleanupVariance) ||
+				    ts + i2p::data::PEER_PROFILE_AUTOCLEAN_TIMEOUT < lastProfilesCleanup)
 				{
 					m_RouterProfilesPool.CleanUpMt ();
-					if (m_PersistProfiles)
-					{	
-						bool isSaving = m_SavingProfiles.valid ();
-						if (isSaving && m_SavingProfiles.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // still active?
-						{
-							m_SavingProfiles.get ();
-							isSaving = false;
-						}	
-						if (!isSaving)
-							m_SavingProfiles = PersistProfiles ();
-						else
-							LogPrint (eLogWarning, "NetDb: Can't persist profiles. Profiles are being saved to disk");
-					}	
-					lastProfilesCleanup = mts;
-					profilesCleanupVariance = m_Rng () % i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE;
+					if (m_PersistProfiles) PersistProfiles ();
+					DeleteObsoleteProfiles ();
+					lastProfilesCleanup = ts;
+					profilesCleanupVariance = (rand () % (2 * i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE) - i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE);
 				}
 
-				if (mts >= lastObsoleteProfilesCleanup + (uint64_t)(i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_TIMEOUT + obsoleteProfilesCleanVariance)*1000)
+				if (ts - lastExploratory >= 30 || ts + 30 < lastExploratory) // exploratory every 30 seconds
 				{
-					bool isDeleting = m_DeletingProfiles.valid ();
-					if (isDeleting && m_DeletingProfiles.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // still active?
+					auto numRouters = m_RouterInfos.size ();
+					if (!numRouters)
+						throw std::runtime_error("No known routers, reseed seems to be totally failed");
+					else // we have peers now
+						m_FloodfillBootstrap = nullptr;
+					if (numRouters < 2500 || ts - lastExploratory >= 90)
 					{
-						m_DeletingProfiles.get ();
-						isDeleting = false;
-					}	
-					if (!isDeleting)
-						m_DeletingProfiles = DeleteObsoleteProfiles ();	
-					else
-						LogPrint (eLogWarning, "NetDb: Can't delete profiles. Profiles are being deleted from disk");
-					lastObsoleteProfilesCleanup = mts;
-					obsoleteProfilesCleanVariance = m_Rng () % i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE;
-				}	
-				if (mts >= lastApplyingProfileUpdates + i2p::data::PEER_PROFILE_APPLY_POSTPONED_TIMEOUT + applyingProfileUpdatesVariance)
-				{
-					bool isApplying = m_ApplyingProfileUpdates.valid ();
-					if (isApplying && m_ApplyingProfileUpdates.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // still active?
-					{
-						m_ApplyingProfileUpdates.get ();
-						isApplying = false;
-					}	
-					if (!isApplying)
-						m_ApplyingProfileUpdates = i2p::data::FlushPostponedRouterProfileUpdates ();
-					lastApplyingProfileUpdates = mts;
-					applyingProfileUpdatesVariance = m_Rng () % i2p::data::PEER_PROFILE_APPLY_POSTPONED_TIMEOUT_VARIANCE;
-				}	
+						numRouters = 800/numRouters;
+						if (numRouters < 1) numRouters = 1;
+						if (numRouters > 9) numRouters = 9;
+						m_Requests.ManageRequests ();
+						if(!i2p::context.IsHidden ())
+							Explore (numRouters);
+						lastExploratory = ts;
+					}
+				}
 			}
 			catch (std::exception& ex)
 			{
@@ -238,8 +226,7 @@ namespace data
 	bool NetDb::AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len)
 	{
 		bool updated;
-		if (!AddRouterInfo (ident, buf, len, updated)) 
-			updated = false;
+		AddRouterInfo (ident, buf, len, updated);
 		return updated;
 	}
 
@@ -253,11 +240,11 @@ namespace data
 			{
 				bool wasFloodfill = r->IsFloodfill ();
 				{
-					std::lock_guard<std::mutex> l(m_RouterInfosMutex);
+					std::unique_lock<std::mutex> l(m_RouterInfosMutex);
 					if (!r->Update (buf, len))
 					{
 						updated = false;
-						m_Requests->RequestComplete (ident, r);
+						m_Requests.RequestComplete (ident, r);
 						return r;
 					}
 					if (r->IsUnreachable () ||
@@ -267,20 +254,18 @@ namespace data
 						m_RouterInfos.erase (ident);
 						if (wasFloodfill)
 						{
-							std::lock_guard<std::mutex> l(m_FloodfillsMutex);
+							std::unique_lock<std::mutex> l(m_FloodfillsMutex);
 							m_Floodfills.Remove (r->GetIdentHash ());
 						}
-						m_Requests->RequestComplete (ident, nullptr);
+						m_Requests.RequestComplete (ident, nullptr);
 						return nullptr;
 					}
 				}
-				if (CheckLogLevel (eLogInfo))
-					LogPrint (eLogInfo, "NetDb: RouterInfo updated: ", ident.ToBase64());
+				LogPrint (eLogInfo, "NetDb: RouterInfo updated: ", ident.ToBase64());
 				if (wasFloodfill != r->IsFloodfill ()) // if floodfill status updated
 				{
-					if (CheckLogLevel (eLogDebug))
-						LogPrint (eLogDebug, "NetDb: RouterInfo floodfill status updated: ", ident.ToBase64());
-					std::lock_guard<std::mutex> l(m_FloodfillsMutex);
+					LogPrint (eLogDebug, "NetDb: RouterInfo floodfill status updated: ", ident.ToBase64());
+					std::unique_lock<std::mutex> l(m_FloodfillsMutex);
 					if (wasFloodfill)
 						m_Floodfills.Remove (r->GetIdentHash ());
 					else if (r->IsEligibleFloodfill ())
@@ -288,50 +273,40 @@ namespace data
 						if (m_Floodfills.GetSize () < NETDB_NUM_FLOODFILLS_THRESHOLD || r->GetProfile ()->IsReal ())
 							m_Floodfills.Insert (r);
 						else
-							r->ResetFloodfill ();
+							r->ResetFlooldFill ();
 					}
 				}
 			}
 			else
 			{
-				r->CancelBufferToDelete (); // since an update received
-				if (CheckLogLevel (eLogDebug))
-					LogPrint (eLogDebug, "NetDb: RouterInfo is older: ", ident.ToBase64());
+				LogPrint (eLogDebug, "NetDb: RouterInfo is older: ", ident.ToBase64());
 				updated = false;
 			}
 		}
 		else
 		{
 			r = std::make_shared<RouterInfo> (buf, len);
-			bool isValid = !r->IsUnreachable () && r->HasValidAddresses () && (!r->IsFloodfill () || !r->GetProfile ()->IsUnreachable ());
-			if (isValid)
-			{
-				auto mts = i2p::util::GetMillisecondsSinceEpoch ();
-			    isValid = mts + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL > r->GetTimestamp () && // from future
-					(mts < r->GetTimestamp () + NETDB_MAX_EXPIRATION_TIMEOUT*1000LL || // too old
-					 context.GetUptime () < NETDB_CHECK_FOR_EXPIRATION_UPTIME/10); // enough uptime
-			}
-			if (isValid)	
+			if (!r->IsUnreachable () && r->HasValidAddresses () && (!r->IsFloodfill () || !r->GetProfile ()->IsUnreachable ()) &&
+			    i2p::util::GetMillisecondsSinceEpoch () + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL > r->GetTimestamp ())
 			{
 				bool inserted = false;
 				{
-					std::lock_guard<std::mutex> l(m_RouterInfosMutex);
+					std::unique_lock<std::mutex> l(m_RouterInfosMutex);
 					inserted = m_RouterInfos.insert ({r->GetIdentHash (), r}).second;
 				}
 				if (inserted)
 				{
-					if (CheckLogLevel (eLogInfo))
-						LogPrint (eLogInfo, "NetDb: RouterInfo added: ", ident.ToBase64());
+					LogPrint (eLogInfo, "NetDb: RouterInfo added: ", ident.ToBase64());
 					if (r->IsFloodfill () && r->IsEligibleFloodfill ())
 					{
 						if (m_Floodfills.GetSize () < NETDB_NUM_FLOODFILLS_THRESHOLD ||
 						 r->GetProfile ()->IsReal ()) // don't insert floodfill until it's known real if we have enough
 						{
-							std::lock_guard<std::mutex> l(m_FloodfillsMutex);
+							std::unique_lock<std::mutex> l(m_FloodfillsMutex);
 							m_Floodfills.Insert (r);
 						}
 						else
-							r->ResetFloodfill ();
+							r->ResetFlooldFill ();
 					}
 				}
 				else
@@ -344,13 +319,13 @@ namespace data
 				updated = false;
 		}
 		// take care about requested destination
-		m_Requests->RequestComplete (ident, r);
+		m_Requests.RequestComplete (ident, r);
 		return r;
 	}
 
 	bool NetDb::AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len)
 	{
-		std::lock_guard<std::mutex> lock(m_LeaseSetsMutex);
+		std::unique_lock<std::mutex> lock(m_LeaseSetsMutex);
 		bool updated = false;
 		auto it = m_LeaseSets.find(ident);
 		if (it != m_LeaseSets.end () && it->second->GetStoreType () == i2p::data::NETDB_STORE_TYPE_LEASESET)
@@ -362,11 +337,10 @@ namespace data
 				if(it->second->GetExpirationTime() < expires)
 				{
 					it->second->Update (buf, len, false); // signature is verified already
-					if (CheckLogLevel (eLogInfo))
-						LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase32());
+					LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase32());
 					updated = true;
 				}
-				else if (CheckLogLevel (eLogDebug))
+				else
 					LogPrint(eLogDebug, "NetDb: LeaseSet is older: ", ident.ToBase32());
 			}
 			else
@@ -377,8 +351,7 @@ namespace data
 			auto leaseSet = std::make_shared<LeaseSet> (buf, len, false); // we don't need leases in netdb
 			if (leaseSet->IsValid ())
 			{
-				if (CheckLogLevel (eLogInfo))
-					LogPrint (eLogInfo, "NetDb: LeaseSet added: ", ident.ToBase32());
+				LogPrint (eLogInfo, "NetDb: LeaseSet added: ", ident.ToBase32());
 				m_LeaseSets[ident] = leaseSet;
 				updated = true;
 			}
@@ -393,16 +366,16 @@ namespace data
 		auto leaseSet = std::make_shared<LeaseSet2> (storeType, buf, len, false); // we don't need leases in netdb
 		if (leaseSet->IsValid ())
 		{
-			std::lock_guard<std::mutex> lock(m_LeaseSetsMutex);
+			std::unique_lock<std::mutex> lock(m_LeaseSetsMutex);
 			auto it = m_LeaseSets.find(ident);
 			if (it == m_LeaseSets.end () || it->second->GetStoreType () != storeType ||
 				leaseSet->GetPublishedTimestamp () > it->second->GetPublishedTimestamp ())
 			{
-				if (leaseSet->IsPublic () && !leaseSet->IsExpired ())
+				if (leaseSet->IsPublic () && !leaseSet->IsExpired () &&
+				     i2p::util::GetSecondsSinceEpoch () + NETDB_EXPIRATION_TIMEOUT_THRESHOLD > leaseSet->GetPublishedTimestamp ())
 				{
 					// TODO: implement actual update
-					if (CheckLogLevel (eLogInfo))
-						LogPrint (eLogInfo, "NetDb: LeaseSet2 updated: ", ident.ToBase32());
+					LogPrint (eLogInfo, "NetDb: LeaseSet2 updated: ", ident.ToBase32());
 					m_LeaseSets[ident] = leaseSet;
 					return true;
 				}
@@ -420,7 +393,7 @@ namespace data
 
 	std::shared_ptr<RouterInfo> NetDb::FindRouter (const IdentHash& ident) const
 	{
-		std::lock_guard<std::mutex> l(m_RouterInfosMutex);
+		std::unique_lock<std::mutex> l(m_RouterInfosMutex);
 		auto it = m_RouterInfos.find (ident);
 		if (it != m_RouterInfos.end ())
 			return it->second;
@@ -430,7 +403,7 @@ namespace data
 
 	std::shared_ptr<LeaseSet> NetDb::FindLeaseSet (const IdentHash& destination) const
 	{
-		std::lock_guard<std::mutex> lock(m_LeaseSetsMutex);
+		std::unique_lock<std::mutex> lock(m_LeaseSetsMutex);
 		auto it = m_LeaseSets.find (destination);
 		if (it != m_LeaseSets.end ())
 			return it->second;
@@ -455,17 +428,7 @@ namespace data
 			r->SetUnreachable (unreachable);
 			auto profile = r->GetProfile ();
 			if (profile)
-			{	
 				profile->Unreachable (unreachable);
-				if (!unreachable && r->IsDeclaredFloodfill () && !r->IsFloodfill () && 
-				    r->IsEligibleFloodfill () && profile->IsReal ())
-				{
-					// enable previously disabled floodfill
-					r->SetFloodfill ();
-					std::lock_guard<std::mutex> l(m_FloodfillsMutex);
-					m_Floodfills.Insert (r);
-				}	
-			}	
 		}
 	}
 
@@ -474,7 +437,7 @@ namespace data
 		auto r = FindRouter (ident);
 		if (r)
 		{
-			std::lock_guard<std::mutex> l(m_RouterInfosMutex);
+			std::unique_lock<std::mutex> l(m_RouterInfosMutex);
 			r->ExcludeReachableTransports (transports);
 		}
 	}
@@ -487,13 +450,35 @@ namespace data
 			m_Reseeder->LoadCertificates (); // we need certificates for SU3 verification
 		}
 
+		// try reseeding from floodfill first if specified
+		std::string riPath; i2p::config::GetOption("reseed.floodfill", riPath);
+		if (!riPath.empty())
+		{
+			auto ri = std::make_shared<RouterInfo>(riPath);
+			if (ri->IsFloodfill())
+			{
+				const uint8_t * riData = ri->GetBuffer();
+				int riLen = ri->GetBufferLen();
+				if (!i2p::data::netdb.AddRouterInfo(riData, riLen))
+				{
+					// bad router info
+					LogPrint(eLogError, "NetDb: Bad router info");
+					return;
+				}
+				m_FloodfillBootstrap = ri;
+				ReseedFromFloodfill(*ri);
+				// don't try reseed servers if trying to bootstrap from floodfill
+				return;
+			}
+		}
+
 		m_Reseeder->Bootstrap ();
 	}
 
 	void NetDb::ReseedFromFloodfill(const RouterInfo & ri, int numRouters, int numFloodfills)
 	{
 		LogPrint(eLogInfo, "NetDB: Reseeding from floodfill ", ri.GetIdentHashBase64());
-		std::list<std::shared_ptr<i2p::I2NPMessage> > requests;
+		std::vector<std::shared_ptr<i2p::I2NPMessage> > requests;
 
 		i2p::data::IdentHash ourIdent = i2p::context.GetIdentHash();
 		i2p::data::IdentHash ih = ri.GetIdentHash();
@@ -516,7 +501,7 @@ namespace data
 		}
 
 		// send them off
-		i2p::transport::transports.SendMessages(ih, std::move (requests));
+		i2p::transport::transports.SendMessages(ih, requests);
 	}
 
 	bool NetDb::LoadRouterInfo (const std::string& path, uint64_t ts)
@@ -542,7 +527,7 @@ namespace data
 
 	void NetDb::VisitLeaseSets(LeaseSetVisitor v)
 	{
-		std::lock_guard<std::mutex> lock(m_LeaseSetsMutex);
+		std::unique_lock<std::mutex> lock(m_LeaseSetsMutex);
 		for ( auto & entry : m_LeaseSets)
 			v(entry.first, entry.second);
 	}
@@ -558,7 +543,7 @@ namespace data
 
 	void NetDb::VisitRouterInfos(RouterInfoVisitor v)
 	{
-		std::lock_guard<std::mutex> lock(m_RouterInfosMutex);
+		std::unique_lock<std::mutex> lock(m_RouterInfosMutex);
 		for ( const auto & item : m_RouterInfos )
 			v(item.second);
 	}
@@ -570,8 +555,8 @@ namespace data
 		size_t iters = max_iters_per_cyle;
 		while(n > 0)
 		{
-			std::lock_guard<std::mutex> lock(m_RouterInfosMutex);
-			uint32_t idx = m_Rng () % m_RouterInfos.size ();
+			std::unique_lock<std::mutex> lock(m_RouterInfosMutex);
+			uint32_t idx = rand () % m_RouterInfos.size ();
 			uint32_t i = 0;
 			for (const auto & it : m_RouterInfos) {
 				if(i >= idx) // are we at the random start point?
@@ -626,17 +611,6 @@ namespace data
 
 	void NetDb::SaveUpdated ()
 	{
-		if (m_PersistingRouters.valid ())
-		{
-			if (m_PersistingRouters.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
-				m_PersistingRouters.get ();
-			else
-			{	
-				LogPrint (eLogWarning, "NetDb: Can't save updated routers. Routers are being saved to disk");
-				return;
-			}	
-		}	
-
 		int updatedCount = 0, deletedCount = 0, deletedFloodfillsCount = 0;
 		auto total = m_RouterInfos.size ();
 		auto totalFloodfills = m_Floodfills.GetSize ();
@@ -647,95 +621,69 @@ namespace data
 		i2p::config::GetOption("limits.zombies", minTunnelCreationSuccessRate);
 		bool isLowRate = i2p::tunnel::tunnels.GetPreciseTunnelCreationSuccessRate () < minTunnelCreationSuccessRate;
 		// routers don't expire if less than 90 or uptime is less than 1 hour
-		bool checkForExpiration = total > NETDB_MIN_ROUTERS && uptime > NETDB_CHECK_FOR_EXPIRATION_UPTIME; // 10 minutes
-		if (checkForExpiration && uptime > i2p::transport::SSU2_TO_INTRODUCER_SESSION_DURATION) // 1 hour
+		bool checkForExpiration = total > NETDB_MIN_ROUTERS && uptime > 600; // 10 minutes
+		if (checkForExpiration && uptime > 3600) // 1 hour
 			expirationTimeout = i2p::context.IsFloodfill () ? NETDB_FLOODFILL_EXPIRATION_TIMEOUT*1000LL :
 				NETDB_MIN_EXPIRATION_TIMEOUT*1000LL + (NETDB_MAX_EXPIRATION_TIMEOUT - NETDB_MIN_EXPIRATION_TIMEOUT)*1000LL*NETDB_MIN_ROUTERS/total;
-		bool isOffline = checkForExpiration && i2p::transport::transports.GetNumPeers () < NETDB_MIN_TRANSPORTS; // enough routers and uptime, but no transports
-			
-		std::list<std::pair<std::string, std::shared_ptr<RouterInfo::Buffer> > > saveToDisk;
-		std::list<std::string> removeFromDisk;	
-			
+
 		auto own = i2p::context.GetSharedRouterInfo ();
-		for (auto [ident, r]: m_RouterInfos)
+		for (auto& it: m_RouterInfos)
 		{
-			if (!r || r == own) continue; // skip own
-			if (r->IsBufferScheduledToDelete ()) // from previous SaveUpdated, we assume m_PersistingRouters complete
+			if (!it.second || it.second == own) continue; // skip own
+			std::string ident = it.second->GetIdentHashBase64();
+			if (it.second->IsUpdated ())
 			{
-				std::lock_guard<std::mutex> l(m_RouterInfosMutex); // possible collision between DeleteBuffer and Update
-				r->DeleteBuffer ();
-			}	
-			if (r->IsUpdated ())
-			{
-				if (r->GetBuffer () && !r->IsUnreachable ())
+				if (it.second->GetBuffer ())
 				{
 					// we have something to save
-					std::shared_ptr<RouterInfo::Buffer> buffer;
-					{
-						std::lock_guard<std::mutex> l(m_RouterInfosMutex); // possible collision between DeleteBuffer and Update
-						buffer = r->CopyBuffer ();
-					}
-					if (!i2p::transport::transports.IsConnected (ident))
-						r->ScheduleBufferToDelete ();
-					if (buffer)
-						saveToDisk.emplace_back(ident.ToBase64 (), buffer);
+					it.second->SaveToFile (m_Storage.Path(ident));
+					it.second->SetUnreachable (false);
+					it.second->DeleteBuffer ();
 				}
-				r->SetUpdated (false);
+				it.second->SetUpdated (false);
 				updatedCount++;
 				continue;
 			}
-			else if (r->GetBuffer () && ts > r->GetTimestamp () + NETDB_MIN_EXPIRATION_TIMEOUT*1000LL)
-				// since update was long time ago we assume that router is not connected anymore
-				r->ScheduleBufferToDelete ();
-			
-			if (r->HasProfile () && r->GetProfile ()->IsUnreachable ())
-				r->SetUnreachable (true);
+			if (it.second->GetProfile ()->IsUnreachable ())
+				it.second->SetUnreachable (true);
 			// make router reachable back if too few routers or floodfills
-			if (r->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || isLowRate || isOffline ||
-				(r->IsFloodfill () && totalFloodfills - deletedFloodfillsCount < NETDB_MIN_FLOODFILLS)))
-				r->SetUnreachable (false);
-			if (!r->IsUnreachable ())
+			if (it.second->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || isLowRate ||
+				(it.second->IsFloodfill () && totalFloodfills - deletedFloodfillsCount < NETDB_MIN_FLOODFILLS)))
+				it.second->SetUnreachable (false);
+			if (!it.second->IsUnreachable ())
 			{
 				// find & mark expired routers
-				if (!r->GetCompatibleTransports (true)) // non reachable by any transport
-					r->SetUnreachable (true);
-				else if (ts + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < r->GetTimestamp ())
+				if (!it.second->GetCompatibleTransports (true)) // non reachable by any transport
+					it.second->SetUnreachable (true);
+				else if (ts + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < it.second->GetTimestamp ())
 				{
-					LogPrint (eLogWarning, "NetDb: RouterInfo is from future for ", (r->GetTimestamp () - ts)/1000LL, " seconds");
-					r->SetUnreachable (true);
+					LogPrint (eLogWarning, "NetDb: RouterInfo is from future for ", (it.second->GetTimestamp () - ts)/1000LL, " seconds");
+					it.second->SetUnreachable (true);
 				}
 				else if (checkForExpiration) 
 				{	
-					if (ts > r->GetTimestamp () + expirationTimeout)
-						r->SetUnreachable (true);
-					else if ((ts > r->GetTimestamp () + expirationTimeout/2) && // more than half of expiration
-						total > NETDB_NUM_ROUTERS_THRESHOLD && !r->IsHighBandwidth() &&  // low bandwidth
-						!r->IsFloodfill() && (!i2p::context.IsFloodfill () || // non floodfill 
-					    (CreateRoutingKey (ident) ^ i2p::context.GetIdentHash ()).metric[0] >= 0x02)) // different first 7 bits 
-							r->SetUnreachable (true);
+					if (ts > it.second->GetTimestamp () + expirationTimeout)
+						it.second->SetUnreachable (true);
+					else if ((ts > it.second->GetTimestamp () + expirationTimeout/2) && // more than half of expiration
+						total > NETDB_NUM_ROUTERS_THRESHOLD && !it.second->IsHighBandwidth() &&  // low bandwidth
+						!it.second->IsFloodfill() && (!i2p::context.IsFloodfill () || // non floodfill 
+					    (CreateRoutingKey (it.second->GetIdentHash ()) ^ i2p::context.GetIdentHash ()).metric[0] >= 0x02)) // different first 7 bits 
+							it.second->SetUnreachable (true);
 				}	
+				if (it.second->IsUnreachable () && i2p::transport::transports.IsConnected (it.second->GetIdentHash ()))
+					it.second->SetUnreachable (false); // don't expire connected router
 			}
-			// make router reachable back if connected now or trusted router
-			if (r->IsUnreachable () && (i2p::transport::transports.IsConnected (ident) ||
-				i2p::transport::transports.IsTrustedRouter (ident)))
-				r->SetUnreachable (false);
-			
-			if (r->IsUnreachable ())
+
+			if (it.second->IsUnreachable ())
 			{
-				if (r->IsFloodfill ()) deletedFloodfillsCount++;
+				if (it.second->IsFloodfill ()) deletedFloodfillsCount++;
 				// delete RI file
-				removeFromDisk.emplace_back (ident.ToBase64());
+				m_Storage.Remove(ident);
 				deletedCount++;
 				if (total - deletedCount < NETDB_MIN_ROUTERS) checkForExpiration = false;
 			}
 		} // m_RouterInfos iteration
 
-		if (!saveToDisk.empty () || !removeFromDisk.empty ())
-		{
-			m_PersistingRouters = std::async (std::launch::async, &NetDb::PersistRouters,
-				this, std::move (saveToDisk), std::move (removeFromDisk));
-		}	
-			
 		m_RouterInfoBuffersPool.CleanUpMt ();
 		m_RouterInfoAddressesPool.CleanUpMt ();
 		m_RouterInfoAddressVectorsPool.CleanUpMt ();
@@ -748,7 +696,7 @@ namespace data
 			LogPrint (eLogInfo, "NetDb: Deleting ", deletedCount, " unreachable routers");
 			// clean up RouterInfos table
 			{
-				std::lock_guard<std::mutex> l(m_RouterInfosMutex);
+				std::unique_lock<std::mutex> l(m_RouterInfosMutex);
 				for (auto it = m_RouterInfos.begin (); it != m_RouterInfos.end ();)
 				{
 					if (!it->second || it->second->IsUnreachable ())
@@ -762,7 +710,7 @@ namespace data
 			}
 			// clean up expired floodfills or not floodfills anymore
 			{
-				std::lock_guard<std::mutex> l(m_FloodfillsMutex);
+				std::unique_lock<std::mutex> l(m_FloodfillsMutex);
 				m_Floodfills.Cleanup ([](const std::shared_ptr<RouterInfo>& r)->bool
 					{
 						return r && r->IsFloodfill () && !r->IsUnreachable ();
@@ -771,23 +719,60 @@ namespace data
 		}
 	}
 
-	void NetDb::PersistRouters (std::list<std::pair<std::string, std::shared_ptr<RouterInfo::Buffer> > >&& update, 
-		std::list<std::string>&& remove)
-	{
-		for (auto it: update)
-			RouterInfo::SaveToFile (m_Storage.Path(it.first), it.second);
-		for (auto it: remove)
-			m_Storage.Remove (it);
-	}	
-	
 	void NetDb::RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete, bool direct)
 	{
-		if (direct && (i2p::transport::transports.RoutesRestricted () || i2p::context.IsLimitedConnectivity ())) 
-		    direct = false; // always use tunnels for restricted routes or limited connectivity
-		if (m_Requests)
-			m_Requests->PostRequestDestination (destination, requestComplete, direct);
+		auto dest = m_Requests.CreateRequest (destination, false, requestComplete); // non-exploratory
+		if (!dest)
+		{
+			LogPrint (eLogWarning, "NetDb: Destination ", destination.ToBase64(), " is requested already");
+			return;
+		}
+
+		auto floodfill = GetClosestFloodfill (destination, dest->GetExcludedPeers ());
+		if (floodfill)
+		{
+			if (direct && !floodfill->IsReachableFrom (i2p::context.GetRouterInfo ()) &&
+				!i2p::transport::transports.IsConnected (floodfill->GetIdentHash ()))
+				direct = false; // floodfill can't be reached directly
+			if (direct)
+				transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ()));
+			else
+			{
+				auto pool = i2p::tunnel::tunnels.GetExploratoryPool ();
+				auto outbound = pool ? pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)) : nullptr;
+				auto inbound = pool ? pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)) : nullptr;
+				if (outbound &&	inbound)
+				{
+					auto msg = dest->CreateRequestMessage (floodfill, inbound);
+					outbound->SendTunnelDataMsgTo (floodfill->GetIdentHash (), 0,
+						i2p::garlic::WrapECIESX25519MessageForRouter (msg, floodfill->GetIdentity ()->GetEncryptionPublicKey ()));
+				}
+				else
+				{
+					LogPrint (eLogError, "NetDb: ", destination.ToBase64(), " destination requested, but no tunnels found");
+					m_Requests.RequestComplete (destination, nullptr);
+				}
+			}
+		}
 		else
-			LogPrint (eLogError, "NetDb: Requests is null");
+		{
+			LogPrint (eLogError, "NetDb: ", destination.ToBase64(), " destination requested, but no floodfills found");
+			m_Requests.RequestComplete (destination, nullptr);
+		}
+	}
+
+	void NetDb::RequestDestinationFrom (const IdentHash& destination, const IdentHash & from, bool exploritory, RequestedDestination::RequestComplete requestComplete)
+	{
+
+		auto dest = m_Requests.CreateRequest (destination, exploritory, requestComplete); // non-exploratory
+		if (!dest)
+		{
+			LogPrint (eLogWarning, "NetDb: Destination ", destination.ToBase64(), " is requested already");
+			return;
+		}
+		LogPrint(eLogInfo, "NetDb: Destination ", destination.ToBase64(), " being requested directly from ", from.ToBase64());
+		// direct
+		transports.SendMessage (from, dest->CreateRequestMessage (nullptr, nullptr));
 	}
 
 	void NetDb::HandleNTCP2RouterInfoMsg (std::shared_ptr<const I2NPMessage> m)
@@ -830,31 +815,17 @@ namespace data
 			offset += 4;
 			if (replyToken != 0xFFFFFFFFU) // if not caught on OBEP or IBGW
 			{
-				IdentHash replyIdent(buf + offset);
 				auto deliveryStatus = CreateDeliveryStatusMsg (replyToken);
 				if (!tunnelID) // send response directly
-					transports.SendMessage (replyIdent, deliveryStatus);
+					transports.SendMessage (buf + offset, deliveryStatus);
 				else
 				{
-					bool direct = true;
-					if (!i2p::transport::transports.IsConnected (replyIdent))
-					{
-						auto r = FindRouter (replyIdent);
-						if (r && !r->IsReachableFrom (i2p::context.GetRouterInfo ()))
-							direct = false;
-					}	
-					if (direct) // send response directly to IBGW
-						transports.SendMessage (replyIdent, i2p::CreateTunnelGatewayMsg (tunnelID, deliveryStatus));
+					auto pool = i2p::tunnel::tunnels.GetExploratoryPool ();
+					auto outbound = pool ? pool->GetNextOutboundTunnel () : nullptr;
+					if (outbound)
+						outbound->SendTunnelDataMsgTo (buf + offset, tunnelID, deliveryStatus);
 					else
-					{		
-						// send response through exploratory tunnel
-						auto pool = i2p::tunnel::tunnels.GetExploratoryPool ();
-						auto outbound = pool ? pool->GetNextOutboundTunnel () : nullptr;
-						if (outbound)
-							outbound->SendTunnelDataMsgTo (replyIdent, tunnelID, deliveryStatus);
-						else
-							LogPrint (eLogWarning, "NetDb: No outbound tunnels for DatabaseStore reply found");
-					}		
+						LogPrint (eLogWarning, "NetDb: No outbound tunnels for DatabaseStore reply found");
 				}
 			}
 			offset += 32;
@@ -885,22 +856,19 @@ namespace data
 			{
 				if (storeType == NETDB_STORE_TYPE_LEASESET) // 1
 				{
-					if (CheckLogLevel (eLogDebug))
-						LogPrint (eLogDebug, "NetDb: Store request: LeaseSet for ", ident.ToBase32());
+					LogPrint (eLogDebug, "NetDb: Store request: LeaseSet for ", ident.ToBase32());
 					updated = AddLeaseSet (ident, buf + offset, len - offset);
 				}
 				else // all others are considered as LeaseSet2
 				{
-					if (CheckLogLevel (eLogDebug))
-						LogPrint (eLogDebug, "NetDb: Store request: LeaseSet2 of type ", int(storeType), " for ", ident.ToBase32());
+					LogPrint (eLogDebug, "NetDb: Store request: LeaseSet2 of type ", int(storeType), " for ", ident.ToBase32());
 					updated = AddLeaseSet2 (ident, buf + offset, len - offset, storeType);
 				}
 			}
 		}
 		else // RouterInfo
 		{
-			if (CheckLogLevel (eLogDebug))
-				LogPrint (eLogDebug, "NetDb: Store request: RouterInfo ", ident.ToBase64());
+			LogPrint (eLogDebug, "NetDb: Store request: RouterInfo");
 			size_t size = bufbe16toh (buf + offset);
 			offset += 2;
 			if (size > MAX_RI_BUFFER_SIZE || size > len - offset)
@@ -932,16 +900,89 @@ namespace data
 			{
 				memcpy (payload + DATABASE_STORE_HEADER_SIZE, buf + payloadOffset, msgLen);
 				floodMsg->FillI2NPMessageHeader (eI2NPDatabaseStore);
-				int minutesBeforeMidnight = 24*60 - i2p::util::GetMinutesSinceEpoch () % (24*60);
-				bool andNextDay = storeType ? minutesBeforeMidnight < NETDB_NEXT_DAY_LEASESET_THRESHOLD:
-					minutesBeforeMidnight < NETDB_NEXT_DAY_ROUTER_INFO_THRESHOLD;
-				Flood (ident, floodMsg, andNextDay);
+				Flood (ident, floodMsg);
 			}
 			else
 				LogPrint (eLogError, "NetDb: Database store message is too long ", floodMsg->len);
 		}
 	}
-	
+
+	void NetDb::HandleDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg)
+	{
+		const uint8_t * buf = msg->GetPayload ();
+		char key[48];
+		int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48);
+		key[l] = 0;
+		int num = buf[32]; // num
+		LogPrint (eLogDebug, "NetDb: DatabaseSearchReply for ", key, " num=", num);
+		IdentHash ident (buf);
+		auto dest = m_Requests.FindRequest (ident);
+		if (dest)
+		{
+			bool deleteDest = true;
+			if (num > 0)
+			{
+				auto pool = i2p::tunnel::tunnels.GetExploratoryPool ();
+				auto outbound = pool ? pool->GetNextOutboundTunnel () : nullptr;
+				auto inbound = pool ? pool->GetNextInboundTunnel () : nullptr;
+				if (!dest->IsExploratory ())
+				{
+					// reply to our destination. Try other floodfills
+					if (outbound && inbound)
+					{
+						auto count = dest->GetExcludedPeers ().size ();
+						if (count < 7)
+						{
+							auto nextFloodfill = GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ());
+							if (nextFloodfill)
+							{
+								// request destination
+								LogPrint (eLogDebug, "NetDb: Try ", key, " at ", count, " floodfill ", nextFloodfill->GetIdentHash ().ToBase64 ());
+								outbound->SendTunnelDataMsgTo (nextFloodfill->GetIdentHash (), 0,
+									dest->CreateRequestMessage (nextFloodfill, inbound));
+								deleteDest = false;
+							}
+						}
+						else
+							LogPrint (eLogWarning, "NetDb: ", key, " was not found on ", count, " floodfills");
+					}
+				}
+
+				if (deleteDest)
+					// no more requests for the destinationation. delete it
+					m_Requests.RequestComplete (ident, nullptr);
+			}
+			else
+				// no more requests for destination possible. delete it
+				m_Requests.RequestComplete (ident, nullptr);
+		}
+		else if(!m_FloodfillBootstrap)
+			LogPrint (eLogWarning, "NetDb: Requested destination for ", key, " not found");
+
+		// try responses
+		for (int i = 0; i < num; i++)
+		{
+			const uint8_t * router = buf + 33 + i*32;
+			char peerHash[48];
+			int l1 = i2p::data::ByteStreamToBase64 (router, 32, peerHash, 48);
+			peerHash[l1] = 0;
+			LogPrint (eLogDebug, "NetDb: ", i, ": ", peerHash);
+
+			auto r = FindRouter (router);
+			if (!r || i2p::util::GetMillisecondsSinceEpoch () > r->GetTimestamp () + 3600*1000LL)
+			{
+				// router with ident not found or too old (1 hour)
+				LogPrint (eLogDebug, "NetDb: Found new/outdated router. Requesting RouterInfo...");
+				if(m_FloodfillBootstrap)
+					RequestDestinationFrom(router, m_FloodfillBootstrap->GetIdentHash(), true);
+				else
+					RequestDestination (router);
+			}
+			else
+				LogPrint (eLogDebug, "NetDb: [:|||:]");
+		}
+	}
+
 	void NetDb::HandleDatabaseLookupMsg (std::shared_ptr<const I2NPMessage> msg)
 	{
 		const uint8_t * buf = msg->GetPayload ();
@@ -951,13 +992,14 @@ namespace data
 			LogPrint (eLogError, "NetDb: DatabaseLookup for zero ident. Ignored");
 			return;
 		}
-		std::string key;
-		if (CheckLogLevel (eLogInfo))
-			key = i2p::data::ByteStreamToBase64 (buf, 32);
+		char key[48];
+		int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48);
+		key[l] = 0;
 
 		IdentHash replyIdent(buf + 32);
 		uint8_t flag = buf[64];
 
+
 		LogPrint (eLogDebug, "NetDb: DatabaseLookup for ", key, " received flags=", (int)flag);
 		uint8_t lookupType = flag & DATABASE_LOOKUP_TYPE_FLAGS_MASK;
 		const uint8_t * excluded = buf + 65;
@@ -984,15 +1026,24 @@ namespace data
 				return;
 			}	
 			LogPrint (eLogInfo, "NetDb: Exploratory close to ", key, " ", numExcluded, " excluded");
-			std::unordered_set<IdentHash> excludedRouters;
+			std::set<IdentHash> excludedRouters;
 			const uint8_t * excluded_ident = excluded;
 			for (int i = 0; i < numExcluded; i++)
 			{
 				excludedRouters.insert (excluded_ident);
 				excluded_ident += 32;
 			}
-			replyMsg = CreateDatabaseSearchReply (ident, GetExploratoryNonFloodfill (ident, 
-				NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES, excludedRouters));
+			std::vector<IdentHash> routers;
+			for (int i = 0; i < 3; i++)
+			{
+				auto r = GetClosestNonFloodfill (ident, excludedRouters);
+				if (r)
+				{
+					routers.push_back (r->GetIdentHash ());
+					excludedRouters.insert (r->GetIdentHash ());
+				}
+			}
+			replyMsg = CreateDatabaseSearchReply (ident, routers);
 		}
 		else
 		{
@@ -1036,16 +1087,16 @@ namespace data
 
 			if (!replyMsg)
 			{
-				std::unordered_set<IdentHash> excludedRouters;
+				std::set<IdentHash> excludedRouters;
 				const uint8_t * exclude_ident = excluded;
 				for (int i = 0; i < numExcluded; i++)
 				{
 					excludedRouters.insert (exclude_ident);
 					exclude_ident += 32;
 				}
-				auto closestFloodfills = GetClosestFloodfills (ident, 3, excludedRouters, false);
+				auto closestFloodfills = GetClosestFloodfills (ident, 3, excludedRouters, true);
 				if (closestFloodfills.empty ())
-					LogPrint (eLogWarning, "NetDb: No more floodfills for ", key, " found. ", numExcluded, " peers excluded");
+					LogPrint (eLogWarning, "NetDb: Requested ", key, " not found, ", numExcluded, " peers excluded");
 				replyMsg = CreateDatabaseSearchReply (ident, closestFloodfills);
 			}
 		}
@@ -1079,67 +1130,86 @@ namespace data
 					else
 						LogPrint(eLogWarning, "NetDb: Encrypted reply requested but no tags provided");
 				}
-				bool direct = true;
-				if (!i2p::transport::transports.IsConnected (replyIdent))
-				{
-					auto r = FindRouter (replyIdent);
-					if (r && !r->IsReachableFrom (i2p::context.GetRouterInfo ()))
-						direct = false;
-				}	
-				if (direct)
-					transports.SendMessage (replyIdent, i2p::CreateTunnelGatewayMsg (replyTunnelID, replyMsg));
+				auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool ();
+				auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr;
+				if (outbound)
+					outbound->SendTunnelDataMsgTo (replyIdent, replyTunnelID, replyMsg);
 				else
-				{	
-					auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool ();
-					auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr;
-					if (outbound)
-						outbound->SendTunnelDataMsgTo (replyIdent, replyTunnelID, replyMsg);
-					else
-						LogPrint (eLogWarning, "NetDb: Can't send lookup reply to ", replyIdent.ToBase64 (), ". Non reachable and no outbound tunnels");
-				}	
+					transports.SendMessage (replyIdent, i2p::CreateTunnelGatewayMsg (replyTunnelID, replyMsg));
 			}
 			else
 				transports.SendMessage (replyIdent, replyMsg);
 		}
 	}
 
-	void NetDb::Flood (const IdentHash& ident, std::shared_ptr<I2NPMessage> floodMsg, bool andNextDay)
+	void NetDb::Explore (int numDestinations)
 	{
-		std::unordered_set<IdentHash> excluded;
+		// new requests
+		auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool ();
+		auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr;
+		auto inbound = exploratoryPool ? exploratoryPool->GetNextInboundTunnel () : nullptr;
+		bool throughTunnels = outbound && inbound;
+
+		uint8_t randomHash[32];
+		std::vector<i2p::tunnel::TunnelMessageBlock> msgs;
+		LogPrint (eLogInfo, "NetDb: Exploring new ", numDestinations, " routers ...");
+		for (int i = 0; i < numDestinations; i++)
+		{
+			RAND_bytes (randomHash, 32);
+			auto dest = m_Requests.CreateRequest (randomHash, true); // exploratory
+			if (!dest)
+			{
+				LogPrint (eLogWarning, "NetDb: Exploratory destination is requested already");
+				return;
+			}
+			auto floodfill = GetClosestFloodfill (randomHash, dest->GetExcludedPeers ());
+			if (floodfill)
+			{
+				if (i2p::transport::transports.IsConnected (floodfill->GetIdentHash ()))
+					throughTunnels = false;
+				if (throughTunnels)
+				{
+					msgs.push_back (i2p::tunnel::TunnelMessageBlock
+						{
+							i2p::tunnel::eDeliveryTypeRouter,
+							floodfill->GetIdentHash (), 0,
+							CreateDatabaseStoreMsg () // tell floodfill about us
+						});
+					msgs.push_back (i2p::tunnel::TunnelMessageBlock
+						{
+							i2p::tunnel::eDeliveryTypeRouter,
+							floodfill->GetIdentHash (), 0,
+							dest->CreateRequestMessage (floodfill, inbound) // explore
+						});
+				}
+				else
+					i2p::transport::transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ()));
+			}
+			else
+				m_Requests.RequestComplete (randomHash, nullptr);
+		}
+		if (throughTunnels && msgs.size () > 0)
+			outbound->SendTunnelDataMsgs (msgs);
+	}
+
+	void NetDb::Flood (const IdentHash& ident, std::shared_ptr<I2NPMessage> floodMsg)
+	{
+		std::set<IdentHash> excluded;
 		excluded.insert (i2p::context.GetIdentHash ()); // don't flood to itself
 		excluded.insert (ident); // don't flood back
 		for (int i = 0; i < 3; i++)
 		{
-			auto floodfill = GetClosestFloodfill (ident, excluded, false); // current day
+			auto floodfill = GetClosestFloodfill (ident, excluded);
 			if (floodfill)
 			{
-				const auto& h = floodfill->GetIdentHash();
+				auto h = floodfill->GetIdentHash();
+				LogPrint(eLogDebug, "NetDb: Flood lease set for ", ident.ToBase32(), " to ", h.ToBase64());
 				transports.SendMessage (h, CopyI2NPMessage(floodMsg));
 				excluded.insert (h);
 			}
 			else
-				return; // no more floodfills
+				break;
 		}
-		if (andNextDay)
-		{
-			// flood to two more closest flodfills for next day 
-			std::unordered_set<IdentHash> excluded1;
-			excluded1.insert (i2p::context.GetIdentHash ()); // don't flood to itself
-			excluded1.insert (ident); // don't flood back
-			for (int i = 0; i < 2; i++)
-			{
-				auto floodfill = GetClosestFloodfill (ident, excluded1, true); // next day
-				if (floodfill)
-				{
-					const auto& h = floodfill->GetIdentHash();
-					if (!excluded.count (h)) // we didn't send for current day, otherwise skip
-						transports.SendMessage (h, CopyI2NPMessage(floodMsg));
-					excluded1.insert (h);
-				}
-				else
-					return;
-			}	
-		}	
 	}
 
 	std::shared_ptr<const RouterInfo> NetDb::GetRandomRouter () const
@@ -1152,23 +1222,20 @@ namespace data
 	}
 
 	std::shared_ptr<const RouterInfo> NetDb::GetRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith,
-		bool reverse, bool endpoint, bool clientTunnel) const
+		bool reverse, bool endpoint) const
 	{
-		bool checkIsReal = clientTunnel && i2p::tunnel::tunnels.GetPreciseTunnelCreationSuccessRate () < NETDB_TUNNEL_CREATION_RATE_THRESHOLD && // too low rate
-			context.GetUptime () > NETDB_CHECK_FOR_EXPIRATION_UPTIME; // after 10 minutes uptime
 		return GetRandomRouter (
-			[compatibleWith, reverse, endpoint, clientTunnel, checkIsReal](std::shared_ptr<const RouterInfo> router)->bool
+			[compatibleWith, reverse, endpoint](std::shared_ptr<const RouterInfo> router)->bool
 			{
 				return !router->IsHidden () && router != compatibleWith &&
 					(reverse ? (compatibleWith->IsReachableFrom (*router) && router->GetCompatibleTransports (true)):
-						router->IsReachableFrom (*compatibleWith)) && !router->IsNAT2NATOnly (*compatibleWith) &&
-					router->IsECIES () && !router->IsHighCongestion (clientTunnel) &&
-					(!checkIsReal || router->GetProfile ()->IsReal ()) &&
+						router->IsReachableFrom (*compatibleWith)) &&
+					router->IsECIES () && !router->IsHighCongestion (false) &&
 					(!endpoint || (router->IsV4 () && (!reverse || router->IsPublished (true)))); // endpoint must be ipv4 and published if inbound(reverse)
 			});
 	}
 
-	std::shared_ptr<const RouterInfo> NetDb::GetRandomSSU2PeerTestRouter (bool v4, const std::unordered_set<IdentHash>& excluded) const
+	std::shared_ptr<const RouterInfo> NetDb::GetRandomSSU2PeerTestRouter (bool v4, const std::set<IdentHash>& excluded) const
 	{
 		return GetRandomRouter (
 			[v4, &excluded](std::shared_ptr<const RouterInfo> router)->bool
@@ -1178,7 +1245,7 @@ namespace data
 			});
 	}
 
-	std::shared_ptr<const RouterInfo> NetDb::GetRandomSSU2Introducer (bool v4, const std::unordered_set<IdentHash>& excluded) const
+	std::shared_ptr<const RouterInfo> NetDb::GetRandomSSU2Introducer (bool v4, const std::set<IdentHash>& excluded) const
 	{
 		return GetRandomRouter (
 			[v4, &excluded](std::shared_ptr<const RouterInfo> router)->bool
@@ -1191,18 +1258,15 @@ namespace data
 	std::shared_ptr<const RouterInfo> NetDb::GetHighBandwidthRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith, 
 		bool reverse, bool endpoint) const
 	{
-		bool checkIsReal = i2p::tunnel::tunnels.GetPreciseTunnelCreationSuccessRate () < NETDB_TUNNEL_CREATION_RATE_THRESHOLD && // too low rate
-			context.GetUptime () > NETDB_CHECK_FOR_EXPIRATION_UPTIME; // after 10 minutes uptime
 		return GetRandomRouter (
-			[compatibleWith, reverse, endpoint, checkIsReal](std::shared_ptr<const RouterInfo> router)->bool
+			[compatibleWith, reverse, endpoint](std::shared_ptr<const RouterInfo> router)->bool
 			{
 				return !router->IsHidden () && router != compatibleWith &&
 					(reverse ? (compatibleWith->IsReachableFrom (*router) && router->GetCompatibleTransports (true)) :
-						router->IsReachableFrom (*compatibleWith)) && !router->IsNAT2NATOnly (*compatibleWith) &&
+						router->IsReachableFrom (*compatibleWith)) &&
 					(router->GetCaps () & RouterInfo::eHighBandwidth) &&
 					router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION &&
 					router->IsECIES () && !router->IsHighCongestion (true) &&
-					(!checkIsReal || router->GetProfile ()->IsReal ()) &&
 					(!endpoint || (router->IsV4 () && (!reverse || router->IsPublished (true)))); // endpoint must be ipv4 and published if inbound(reverse)
 
 			});
@@ -1212,10 +1276,10 @@ namespace data
 	std::shared_ptr<const RouterInfo> NetDb::GetRandomRouter (Filter filter) const
 	{
 		if (m_RouterInfos.empty())
-			return nullptr;
+			return 0;
 		uint16_t inds[3];
 		RAND_bytes ((uint8_t *)inds, sizeof (inds));
-		std::lock_guard<std::mutex> l(m_RouterInfosMutex);
+		std::unique_lock<std::mutex> l(m_RouterInfosMutex);
 		auto count = m_RouterInfos.size ();
 		if(count == 0) return nullptr;
 		inds[0] %= count;
@@ -1273,17 +1337,11 @@ namespace data
 		if (msg) m_Queue.Put (msg);
 	}
 
-	void NetDb::PostDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg)
-	{
-		if (msg && m_Requests)
-			m_Requests->PostDatabaseSearchReplyMsg (msg);
-	}	
-	
 	std::shared_ptr<const RouterInfo> NetDb::GetClosestFloodfill (const IdentHash& destination,
-		const std::unordered_set<IdentHash>& excluded, bool nextDay) const
+		const std::set<IdentHash>& excluded) const
 	{
-		IdentHash destKey = CreateRoutingKey (destination, nextDay);
-		std::lock_guard<std::mutex> l(m_FloodfillsMutex);
+		IdentHash destKey = CreateRoutingKey (destination);
+		std::unique_lock<std::mutex> l(m_FloodfillsMutex);
 		return m_Floodfills.FindClosest (destKey, [&excluded](const std::shared_ptr<RouterInfo>& r)->bool
 			{
 				return r && !r->IsUnreachable () && !r->GetProfile ()->IsUnreachable () &&
@@ -1292,13 +1350,13 @@ namespace data
 	}
 
 	std::vector<IdentHash> NetDb::GetClosestFloodfills (const IdentHash& destination, size_t num,
-		std::unordered_set<IdentHash>& excluded, bool closeThanUsOnly) const
+		std::set<IdentHash>& excluded, bool closeThanUsOnly) const
 	{
 		std::vector<IdentHash> res;
 		IdentHash destKey = CreateRoutingKey (destination);
 		std::vector<std::shared_ptr<RouterInfo> > v;
 		{
-			std::lock_guard<std::mutex> l(m_FloodfillsMutex);
+			std::unique_lock<std::mutex> l(m_FloodfillsMutex);
 			v = m_Floodfills.FindClosest (destKey, num, [&excluded](const std::shared_ptr<RouterInfo>& r)->bool
 				{
 					return r && !r->IsUnreachable () && !r->GetProfile ()->IsUnreachable () &&
@@ -1326,57 +1384,34 @@ namespace data
 		});
 	}
 
-	std::vector<IdentHash> NetDb::GetExploratoryNonFloodfill (const IdentHash& destination, 
-		size_t num, const std::unordered_set<IdentHash>& excluded)
+	std::shared_ptr<const RouterInfo> NetDb::GetClosestNonFloodfill (const IdentHash& destination,
+		const std::set<IdentHash>& excluded) const
 	{
-		std::vector<IdentHash> ret;
-		if (!num || m_RouterInfos.empty ()) return ret; // empty list
-		auto ts = i2p::util::GetMonotonicSeconds ();
-		if (ts > m_LastExploratorySelectionUpdateTime +	NETDB_EXPLORATORY_SELECTION_UPDATE_INTERVAL)
-		{
-			// update selection
-			m_ExploratorySelection.clear ();
-			std::vector<std::shared_ptr<const RouterInfo> > eligible;
-			eligible.reserve (m_RouterInfos.size ());		
-			{
-				// collect eligible from current netdb
-				bool checkIsReal = i2p::tunnel::tunnels.GetPreciseTunnelCreationSuccessRate () < NETDB_TUNNEL_CREATION_RATE_THRESHOLD; // too low rate
-				std::lock_guard<std::mutex> l(m_RouterInfosMutex);
-				for (const auto& it: m_RouterInfos)
-					if (!it.second->IsDeclaredFloodfill () &&
-					 	(!checkIsReal || (it.second->HasProfile () && it.second->GetProfile ()->IsReal ())))
-							eligible.push_back (it.second);
-			}
-			if (eligible.size () > NETDB_MAX_EXPLORATORY_SELECTION_SIZE)
-			{
-				 std::sample (eligible.begin(), eligible.end(), std::back_inserter(m_ExploratorySelection),
-				 	NETDB_MAX_EXPLORATORY_SELECTION_SIZE, m_Rng);
-			}	
-			else
-				std::swap (m_ExploratorySelection, eligible);	
-			m_LastExploratorySelectionUpdateTime = ts;
-		}	
-		
-		// sort by distance
+		std::shared_ptr<const RouterInfo> r;
+		XORMetric minMetric;
 		IdentHash destKey = CreateRoutingKey (destination);
-		std::map<XORMetric, std::shared_ptr<const RouterInfo> > sorted;
-		for (const auto& it: m_ExploratorySelection)
-			if (!excluded.count (it->GetIdentHash ())) 
-				sorted.emplace (destKey ^ it->GetIdentHash (), it);
-		// return first num closest routers
-		for (const auto& it: sorted)
+		minMetric.SetMax ();
+		// must be called from NetDb thread only
+		for (const auto& it: m_RouterInfos)
 		{
-			ret.push_back (it.second->GetIdentHash ());
-			if (ret.size () >= num) break;
-		}	
-		return ret;
+			if (!it.second->IsFloodfill ())
+			{
+				XORMetric m = destKey ^ it.first;
+				if (m < minMetric && !excluded.count (it.first))
+				{
+					minMetric = m;
+					r = it.second;
+				}
+			}
+		}
+		return r;
 	}
 
 	void NetDb::ManageRouterInfos ()
 	{
 		auto ts = i2p::util::GetSecondsSinceEpoch ();
 		{
-			std::lock_guard<std::mutex> l(m_RouterInfosMutex);
+			std::unique_lock<std::mutex> l(m_RouterInfosMutex);
 			for (auto& it: m_RouterInfos)
 				it.second->UpdateIntroducers (ts);
 		}
diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp
index f2a7019b..8edc94d6 100644
--- a/libi2pd/NetDb.hpp
+++ b/libi2pd/NetDb.hpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -10,13 +10,11 @@
 #define NETDB_H__
 // this file is called NetDb.hpp to resolve conflict with libc's netdb.h on case insensitive fs
 #include <inttypes.h>
-#include <unordered_set>
+#include <set>
 #include <unordered_map>
 #include <string>
 #include <thread>
 #include <mutex>
-#include <future>
-#include <random>
 
 #include "Base.h"
 #include "Gzip.h"
@@ -40,25 +38,16 @@ namespace data
 {
 	const int NETDB_MIN_ROUTERS = 90;
 	const int NETDB_MIN_FLOODFILLS = 5;
-	const int NETDB_MIN_TRANSPORTS = 10 ; // otherwise assume offline
-	const int NETDB_NUM_FLOODFILLS_THRESHOLD = 1200;
+	const int NETDB_NUM_FLOODFILLS_THRESHOLD = 1500;
 	const int NETDB_NUM_ROUTERS_THRESHOLD = 4*NETDB_NUM_FLOODFILLS_THRESHOLD;
-	const int NETDB_TUNNEL_CREATION_RATE_THRESHOLD = 10; // in %
-	const int NETDB_CHECK_FOR_EXPIRATION_UPTIME = 600; // 10 minutes, in seconds  
 	const int NETDB_FLOODFILL_EXPIRATION_TIMEOUT = 60 * 60; // 1 hour, in seconds
 	const int NETDB_MIN_EXPIRATION_TIMEOUT = 90 * 60; // 1.5 hours
 	const int NETDB_MAX_EXPIRATION_TIMEOUT = 27 * 60 * 60; // 27 hours
 	const int NETDB_MAX_OFFLINE_EXPIRATION_TIMEOUT = 180; // in days
 	const int NETDB_EXPIRATION_TIMEOUT_THRESHOLD = 2*60; // 2 minutes
-	const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 58); // 0.9.58
-	const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 59); // 0.9.59
+	const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51
+	const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51
 	const int NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51
-	const int NETDB_MIN_PEER_TEST_VERSION = MAKE_VERSION_NUMBER(0, 9, 62); // 0.9.62
-	const size_t NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES = 16;
-	const size_t NETDB_MAX_EXPLORATORY_SELECTION_SIZE = 500;
-	const int NETDB_EXPLORATORY_SELECTION_UPDATE_INTERVAL = 82; // in seconds. for floodfill
-	const int NETDB_NEXT_DAY_ROUTER_INFO_THRESHOLD = 45; // in minutes
-	const int NETDB_NEXT_DAY_LEASESET_THRESHOLD = 10; // in minutes
 
 	/** function for visiting a leaseset stored in a floodfill */
 	typedef std::function<void(const IdentHash, std::shared_ptr<LeaseSet>)> LeaseSetVisitor;
@@ -88,22 +77,27 @@ namespace data
 			std::shared_ptr<RouterProfile> FindRouterProfile (const IdentHash& ident) const;
 
 			void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr, bool direct = true);
-			
+			void RequestDestinationFrom (const IdentHash& destination, const IdentHash & from, bool exploritory, RequestedDestination::RequestComplete requestComplete = nullptr);
+
+			void HandleDatabaseStoreMsg (std::shared_ptr<const I2NPMessage> msg);
+			void HandleDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg);
+			void HandleDatabaseLookupMsg (std::shared_ptr<const I2NPMessage> msg);
+			void HandleNTCP2RouterInfoMsg (std::shared_ptr<const I2NPMessage> m);
+
 			std::shared_ptr<const RouterInfo> GetRandomRouter () const;
-			std::shared_ptr<const RouterInfo> GetRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith, bool reverse, bool endpoint, bool clientTunnel) const;
+			std::shared_ptr<const RouterInfo> GetRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith, bool reverse, bool endpoint) const;
 			std::shared_ptr<const RouterInfo> GetHighBandwidthRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith, bool reverse, bool endpoint) const;
-			std::shared_ptr<const RouterInfo> GetRandomSSU2PeerTestRouter (bool v4, const std::unordered_set<IdentHash>& excluded) const;
-			std::shared_ptr<const RouterInfo> GetRandomSSU2Introducer (bool v4, const std::unordered_set<IdentHash>& excluded) const;
-			std::shared_ptr<const RouterInfo> GetClosestFloodfill (const IdentHash& destination, const std::unordered_set<IdentHash>& excluded, bool nextDay = false) const;
+			std::shared_ptr<const RouterInfo> GetRandomSSU2PeerTestRouter (bool v4, const std::set<IdentHash>& excluded) const;
+			std::shared_ptr<const RouterInfo> GetRandomSSU2Introducer (bool v4, const std::set<IdentHash>& excluded) const;
+			std::shared_ptr<const RouterInfo> GetClosestFloodfill (const IdentHash& destination, const std::set<IdentHash>& excluded) const;
 			std::vector<IdentHash> GetClosestFloodfills (const IdentHash& destination, size_t num,
-				std::unordered_set<IdentHash>& excluded, bool closeThanUsOnly = false) const;
-			std::vector<IdentHash> GetExploratoryNonFloodfill (const IdentHash& destination, size_t num, const std::unordered_set<IdentHash>& excluded);
+				std::set<IdentHash>& excluded, bool closeThanUsOnly = false) const;
+			std::shared_ptr<const RouterInfo> GetClosestNonFloodfill (const IdentHash& destination, const std::set<IdentHash>& excluded) const;
 			std::shared_ptr<const RouterInfo> GetRandomRouterInFamily (FamilyID fam) const;
 			void SetUnreachable (const IdentHash& ident, bool unreachable);
 			void ExcludeReachableTransports (const IdentHash& ident, RouterInfo::CompatibleTransports transports);
 
 			void PostI2NPMsg (std::shared_ptr<const I2NPMessage> msg);
-			void PostDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg); // to NetdbReq thread
 
 			void Reseed ();
 			Families& GetFamilies () { return m_Families; };
@@ -123,33 +117,30 @@ namespace data
 			size_t VisitRandomRouterInfos(RouterInfoFilter f, RouterInfoVisitor v, size_t n);
 
 			void ClearRouterInfos () { m_RouterInfos.clear (); };
-			template<typename... TArgs>
-			std::shared_ptr<RouterInfo::Buffer> NewRouterInfoBuffer (TArgs&&... args) 
-			{ 
-				return m_RouterInfoBuffersPool.AcquireSharedMt (std::forward<TArgs>(args)...); 
-			}
+			std::shared_ptr<RouterInfo::Buffer> NewRouterInfoBuffer () { return m_RouterInfoBuffersPool.AcquireSharedMt (); };
 			bool PopulateRouterInfoBuffer (std::shared_ptr<RouterInfo> r);
 			std::shared_ptr<RouterInfo::Address> NewRouterInfoAddress () { return m_RouterInfoAddressesPool.AcquireSharedMt (); };
-			RouterInfo::AddressesPtr NewRouterInfoAddresses ()
+			boost::shared_ptr<RouterInfo::Addresses> NewRouterInfoAddresses ()
 			{
-				return RouterInfo::AddressesPtr{m_RouterInfoAddressVectorsPool.AcquireMt (),
+				return boost::shared_ptr<RouterInfo::Addresses>(m_RouterInfoAddressVectorsPool.AcquireMt (),
 					std::bind <void (i2p::util::MemoryPoolMt<RouterInfo::Addresses>::*)(RouterInfo::Addresses *)>
 						(&i2p::util::MemoryPoolMt<RouterInfo::Addresses>::ReleaseMt,
-						&m_RouterInfoAddressVectorsPool, std::placeholders::_1)};
+						&m_RouterInfoAddressVectorsPool, std::placeholders::_1));
 			};
 			std::shared_ptr<Lease> NewLease (const Lease& lease) { return m_LeasesPool.AcquireSharedMt (lease); };
 			std::shared_ptr<IdentityEx> NewIdentity (const uint8_t * buf, size_t len) { return m_IdentitiesPool.AcquireSharedMt (buf, len); };
 			std::shared_ptr<RouterProfile> NewRouterProfile () { return m_RouterProfilesPool.AcquireSharedMt (); };
 
+			uint32_t GetPublishReplyToken () const { return m_PublishReplyToken; };
+
 		private:
 
 			void Load ();
 			bool LoadRouterInfo (const std::string& path, uint64_t ts);
 			void SaveUpdated ();
-			void PersistRouters (std::list<std::pair<std::string, std::shared_ptr<RouterInfo::Buffer> > >&& update, 
-				std::list<std::string>&& remove);
-			void Run (); 
-			void Flood (const IdentHash& ident, std::shared_ptr<I2NPMessage> floodMsg, bool andNextDay = false);
+			void Run (); // exploratory thread
+			void Explore (int numDestinations);
+			void Flood (const IdentHash& ident, std::shared_ptr<I2NPMessage> floodMsg);
 			void ManageRouterInfos ();
 			void ManageLeaseSets ();
 			void ManageRequests ();
@@ -162,10 +153,6 @@ namespace data
 			template<typename Filter>
 			std::shared_ptr<const RouterInfo> GetRandomRouter (Filter filter) const;
 
-			void HandleDatabaseStoreMsg (std::shared_ptr<const I2NPMessage> msg);
-			void HandleDatabaseLookupMsg (std::shared_ptr<const I2NPMessage> msg);
-			void HandleNTCP2RouterInfoMsg (std::shared_ptr<const I2NPMessage> m);
-
 		private:
 
 			mutable std::mutex m_LeaseSetsMutex;
@@ -184,14 +171,16 @@ namespace data
 			Families m_Families;
 			i2p::fs::HashedStorage m_Storage;
 
-			std::shared_ptr<NetDbRequests> m_Requests;
+			friend class NetDbRequests;
+			NetDbRequests m_Requests;
 
 			bool m_PersistProfiles;
-			std::future<void> m_SavingProfiles, m_DeletingProfiles, m_ApplyingProfileUpdates, m_PersistingRouters;
 
-			std::vector<std::shared_ptr<const RouterInfo> > m_ExploratorySelection;
-			uint64_t m_LastExploratorySelectionUpdateTime; // in monotonic seconds
-			std::mt19937 m_Rng;
+			/** router info we are bootstrapping from or nullptr if we are not currently doing that*/
+			std::shared_ptr<RouterInfo> m_FloodfillBootstrap;
+
+			std::set<IdentHash> m_PublishExcluded;
+			uint32_t m_PublishReplyToken = 0;
 
 			i2p::util::MemoryPoolMt<RouterInfo::Buffer> m_RouterInfoBuffersPool;
 			i2p::util::MemoryPoolMt<RouterInfo::Address> m_RouterInfoAddressesPool;
diff --git a/libi2pd/NetDbRequests.cpp b/libi2pd/NetDbRequests.cpp
index 94633e10..4011b8aa 100644
--- a/libi2pd/NetDbRequests.cpp
+++ b/libi2pd/NetDbRequests.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -10,30 +10,12 @@
 #include "I2NPProtocol.h"
 #include "Transports.h"
 #include "NetDb.hpp"
-#include "ECIESX25519AEADRatchetSession.h"
-#include "RouterContext.h"
-#include "Timestamp.h"
 #include "NetDbRequests.h"
 
 namespace i2p
 {
 namespace data
 {
-	RequestedDestination::RequestedDestination (const IdentHash& destination, bool isExploratory, bool direct):
-		m_Destination (destination), m_IsExploratory (isExploratory), m_IsDirect (direct), 
-		m_IsActive (true), m_IsSentDirectly (false),
-		m_CreationTime (i2p::util::GetMillisecondsSinceEpoch ()), 
-		m_LastRequestTime (0), m_NumAttempts (0)
-	{
-		if (i2p::context.IsFloodfill ())
-			m_ExcludedPeers.insert (i2p::context.GetIdentHash ()); // exclude self if floodfill
-	}
-		
-	RequestedDestination::~RequestedDestination () 
-	{ 
-		InvokeRequestComplete (nullptr);
-	}
-		
 	std::shared_ptr<I2NPMessage> RequestedDestination::CreateRequestMessage (std::shared_ptr<const RouterInfo> router,
 		std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel)
 	{
@@ -46,9 +28,7 @@ namespace data
 			msg = i2p::CreateRouterInfoDatabaseLookupMsg(m_Destination, i2p::context.GetIdentHash(), 0, m_IsExploratory, &m_ExcludedPeers);
 		if(router)
 			m_ExcludedPeers.insert (router->GetIdentHash ());
-		m_LastRequestTime = i2p::util::GetMillisecondsSinceEpoch ();
-		m_NumAttempts++;
-		m_IsSentDirectly = false;
+		m_CreationTime = i2p::util::GetSecondsSinceEpoch ();
 		return msg;
 	}
 
@@ -57,155 +37,80 @@ namespace data
 		auto msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination,
 			i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers);
 		m_ExcludedPeers.insert (floodfill);
-		m_NumAttempts++;
-		m_LastRequestTime = i2p::util::GetMillisecondsSinceEpoch ();
-		m_IsSentDirectly = true;
+		m_CreationTime = i2p::util::GetSecondsSinceEpoch ();
 		return msg;
 	}
 
-	bool RequestedDestination::IsExcluded (const IdentHash& ident) const 
-	{ 
-		return m_ExcludedPeers.count (ident); 
-	}
-		
 	void RequestedDestination::ClearExcludedPeers ()
 	{
 		m_ExcludedPeers.clear ();
 	}
 
-	void RequestedDestination::InvokeRequestComplete (std::shared_ptr<RouterInfo> r)
-	{
-		if (!m_RequestComplete.empty ())
-		{	
-			for (auto it: m_RequestComplete)
-				if (it != nullptr) it (r);
-			m_RequestComplete.clear ();
-		}	
-	}	
-		
 	void RequestedDestination::Success (std::shared_ptr<RouterInfo> r)
 	{
-		if (m_IsActive)
-		{	
-			m_IsActive = false;
-			InvokeRequestComplete (r);
-		}	
+		if (m_RequestComplete)
+		{
+			m_RequestComplete (r);
+			m_RequestComplete = nullptr;
+		}
 	}
 
 	void RequestedDestination::Fail ()
 	{
-		if (m_IsActive)
-		{	
-			m_IsActive = false;
-			InvokeRequestComplete (nullptr);
-		}	
+		if (m_RequestComplete)
+		{
+			m_RequestComplete (nullptr);
+			m_RequestComplete = nullptr;
+		}
 	}
 
-	NetDbRequests::NetDbRequests ():
-		RunnableServiceWithWork ("NetDbReq"),
-		m_ManageRequestsTimer (GetIOService ()), m_ExploratoryTimer (GetIOService ()),
-		m_CleanupTimer (GetIOService ()), m_DiscoveredRoutersTimer (GetIOService ()),
-		m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL) 
-	{
-	}
-		
-	NetDbRequests::~NetDbRequests ()
-	{
-		Stop ();
-	}	
-		
 	void NetDbRequests::Start ()
 	{
-		if (!IsRunning ())
-		{	
-			StartIOService ();
-			ScheduleManageRequests ();
-			ScheduleCleanup ();
-			if (!i2p::context.IsHidden ())
-				ScheduleExploratory (EXPLORATORY_REQUEST_INTERVAL);
-		}	
 	}
 
 	void NetDbRequests::Stop ()
 	{
-		if (IsRunning ())
-		{	
-			m_ManageRequestsTimer.cancel ();
-			m_ExploratoryTimer.cancel ();
-			m_CleanupTimer.cancel ();
-			StopIOService ();
-		
-			m_RequestedDestinations.clear ();
-			m_RequestedDestinationsPool.CleanUpMt ();
-		}	
+		m_RequestedDestinations.clear ();
 	}
 
-	void NetDbRequests::ScheduleCleanup ()
-	{
-		m_CleanupTimer.expires_from_now (boost::posix_time::seconds(REQUESTED_DESTINATIONS_POOL_CLEANUP_INTERVAL));
-		m_CleanupTimer.async_wait (std::bind (&NetDbRequests::HandleCleanupTimer,
-			this, std::placeholders::_1));
-	}	
-		
-	void NetDbRequests::HandleCleanupTimer (const boost::system::error_code& ecode)
-	{		
-		if (ecode != boost::asio::error::operation_aborted)
-		{
-			m_RequestedDestinationsPool.CleanUpMt ();
-			ScheduleCleanup ();
-		}	
-	}
-		
-	std::shared_ptr<RequestedDestination> NetDbRequests::CreateRequest (const IdentHash& destination, 
-		bool isExploratory, bool direct, RequestedDestination::RequestComplete requestComplete)
+
+	std::shared_ptr<RequestedDestination> NetDbRequests::CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete)
 	{
 		// request RouterInfo directly
-		auto dest = m_RequestedDestinationsPool.AcquireSharedMt (destination, isExploratory, direct);
-		if (requestComplete)
-			dest->AddRequestComplete (requestComplete);
-		
-		auto ret = m_RequestedDestinations.emplace (destination, dest);
-		if (!ret.second) // not inserted
-		{	
-			dest->ResetRequestComplete (); // don't call requestComplete in destructor	
-			dest = ret.first->second; // existing one
-			if (requestComplete)
-			{	
-				if (dest->IsActive ())
-					dest->AddRequestComplete (requestComplete);
-				else
-					requestComplete (nullptr);
-			}	
-			return nullptr;
-		}	
+		auto dest = std::make_shared<RequestedDestination> (destination, isExploratory);
+		dest->SetRequestComplete (requestComplete);
+		{
+			std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
+			if (!m_RequestedDestinations.insert (std::make_pair (destination, dest)).second) // not inserted
+				return nullptr;
+		}
 		return dest;
 	}
 
 	void NetDbRequests::RequestComplete (const IdentHash& ident, std::shared_ptr<RouterInfo> r)
 	{
-		boost::asio::post (GetIOService (), [this, ident, r]()
-			{                      
-				std::shared_ptr<RequestedDestination> request;
-				auto it = m_RequestedDestinations.find (ident);
-				if (it != m_RequestedDestinations.end ())
-				{
-					request = it->second;
-					if (request->IsExploratory ())
-						m_RequestedDestinations.erase (it);
-					// otherwise cache for a while
-				}
-				if (request)
-				{
-					if (r)
-						request->Success (r);
-					else
-						request->Fail ();
-				}
-			});
+		std::shared_ptr<RequestedDestination> request;
+		{
+			std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
+			auto it = m_RequestedDestinations.find (ident);
+			if (it != m_RequestedDestinations.end ())
+			{
+				request = it->second;
+				m_RequestedDestinations.erase (it);
+			}
+		}
+		if (request)
+		{
+			if (r)
+				request->Success (r);
+			else
+				request->Fail ();
+		}
 	}
 
 	std::shared_ptr<RequestedDestination> NetDbRequests::FindRequest (const IdentHash& ident) const
 	{
+		std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
 		auto it = m_RequestedDestinations.find (ident);
 		if (it != m_RequestedDestinations.end ())
 			return it->second;
@@ -214,349 +119,50 @@ namespace data
 
 	void NetDbRequests::ManageRequests ()
 	{
-		uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
+		uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
+		std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
 		for (auto it = m_RequestedDestinations.begin (); it != m_RequestedDestinations.end ();)
 		{
 			auto& dest = it->second;
-			if (dest->IsActive () || ts < dest->GetCreationTime () + REQUEST_CACHE_TIME)
-			{	
-				if (!dest->IsExploratory ())
-				{	
-					// regular request
-					bool done = false;
-					if (ts < dest->GetCreationTime () + MAX_REQUEST_TIME)
-					{
-						if (ts > dest->GetLastRequestTime () + (dest->IsSentDirectly () ? MIN_DIRECT_REQUEST_TIME : MIN_REQUEST_TIME)) 
-						// try next floodfill if no response after min interval
-							done = !SendNextRequest (dest);
-					}
-					else // request is expired
-						done = true;
-					if (done)
-						dest->Fail ();
-					it++;
-				}	
-				else
-				{	
-					// exploratory
-					if (ts >= dest->GetCreationTime () + MAX_EXPLORATORY_REQUEST_TIME)
-					{
-						dest->Fail ();
-						it = m_RequestedDestinations.erase (it); // delete expired exploratory request right a way
-					}	
-					else
-						it++;
-				}	
-			}	
-			else
-				it = m_RequestedDestinations.erase (it);
-		}
-	}
-
-	bool NetDbRequests::SendNextRequest (std::shared_ptr<RequestedDestination> dest)
-	{
-		if (!dest || !dest->IsActive ()) return false;
-		bool ret = true;
-		auto count = dest->GetNumAttempts ();
-		if (!dest->IsExploratory () && count < MAX_NUM_REQUEST_ATTEMPTS)
-		{
-			auto nextFloodfill = netdb.GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ());
-			if (nextFloodfill)
-			{	
-				bool direct = dest->IsDirect ();
-				if (direct && !nextFloodfill->IsReachableFrom (i2p::context.GetRouterInfo ()) &&
-					!i2p::transport::transports.IsConnected (nextFloodfill->GetIdentHash ()))
-					direct = false; // floodfill can't be reached directly
-				auto s = shared_from_this ();
-				auto onDrop = [s, dest]()
-					{
-						if (dest->IsActive ())
-						{
-							boost::asio::post (s->GetIOService (), [s, dest]()
-								{
-									if (dest->IsActive ()) s->SendNextRequest (dest);
-								});
-						}	
-					};		
-				if (direct)
+			bool done = false;
+			if (ts < dest->GetCreationTime () + 60) // request is worthless after 1 minute
+			{
+				if (ts > dest->GetCreationTime () + 5) // no response for 5 seconds
 				{
-					if (CheckLogLevel (eLogDebug))
-						LogPrint (eLogDebug, "NetDbReq: Try ", dest->GetDestination ().ToBase64 (), " at ", count, " floodfill ", nextFloodfill->GetIdentHash ().ToBase64 (), " directly");
-					auto msg = dest->CreateRequestMessage (nextFloodfill->GetIdentHash ());
-					msg->onDrop = onDrop; 
-					i2p::transport::transports.SendMessage (nextFloodfill->GetIdentHash (), msg);
-				}	
-				else
-				{	
-					auto pool = i2p::tunnel::tunnels.GetExploratoryPool ();
-					if (pool)
-					{	
+					auto count = dest->GetExcludedPeers ().size ();
+					if (!dest->IsExploratory () && count < 7)
+					{
+						auto pool = i2p::tunnel::tunnels.GetExploratoryPool ();
 						auto outbound = pool->GetNextOutboundTunnel ();
 						auto inbound = pool->GetNextInboundTunnel ();
+						auto nextFloodfill = netdb.GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ());
 						if (nextFloodfill && outbound && inbound)
-						{
-							if (CheckLogLevel (eLogDebug))
-								LogPrint (eLogDebug, "NetDbReq: Try ", dest->GetDestination ().ToBase64 (), " at ", count, " floodfill ", nextFloodfill->GetIdentHash ().ToBase64 (), " through tunnels");
-							auto msg = dest->CreateRequestMessage (nextFloodfill, inbound); 
-							msg->onDrop = onDrop;
 							outbound->SendTunnelDataMsgTo (nextFloodfill->GetIdentHash (), 0,
-								i2p::garlic::WrapECIESX25519MessageForRouter (msg, nextFloodfill->GetIdentity ()->GetEncryptionPublicKey ()));
-						}	
+								dest->CreateRequestMessage (nextFloodfill, inbound));
 						else
 						{
-							ret = false;
+							done = true;
 							if (!inbound) LogPrint (eLogWarning, "NetDbReq: No inbound tunnels");
 							if (!outbound) LogPrint (eLogWarning, "NetDbReq: No outbound tunnels");
+							if (!nextFloodfill) LogPrint (eLogWarning, "NetDbReq: No more floodfills");
 						}
-					}	
+					}
 					else
 					{
-						ret = false;
-						LogPrint (eLogWarning, "NetDbReq: Exploratory pool is not ready");
-					}	
-				}		
-			}
-			else
-			{
-				ret = false;
-				LogPrint (eLogWarning, "NetDbReq: No more floodfills for ", dest->GetDestination ().ToBase64 (), " after ", count, "attempts");
-			}	
-		}
-		else
-		{
-			if (!dest->IsExploratory ())
-				LogPrint (eLogWarning, "NetDbReq: ", dest->GetDestination ().ToBase64 (), " not found after ", MAX_NUM_REQUEST_ATTEMPTS," attempts");
-			ret = false;
-		}
-		return ret;
-	}	
-
-	void NetDbRequests::ScheduleManageRequests ()
-	{
-		m_ManageRequestsTimer.expires_from_now (boost::posix_time::milliseconds(MANAGE_REQUESTS_INTERVAL +
-			m_Rng () % MANAGE_REQUESTS_INTERVAL_VARIANCE));
-		m_ManageRequestsTimer.async_wait (std::bind (&NetDbRequests::HandleManageRequestsTimer,
-			this, std::placeholders::_1));
-	}
-		
-	void NetDbRequests::HandleManageRequestsTimer (const boost::system::error_code& ecode)
-	{
-		if (ecode != boost::asio::error::operation_aborted)
-		{
-			if (i2p::tunnel::tunnels.GetExploratoryPool ()) // expolratory pool is ready?
-				ManageRequests ();
-			ScheduleManageRequests ();
-		}	
-	}	
-
-	void NetDbRequests::PostDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg)
-	{
-		boost::asio::post (GetIOService (), [this, msg]()
-			{
-				HandleDatabaseSearchReplyMsg (msg);
-			});	
-	}	
-
-	void NetDbRequests::HandleDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg)
-	{
-		const uint8_t * buf = msg->GetPayload ();
-		std::string key;
-		size_t num = buf[32]; // num
-		if (CheckLogLevel (eLogInfo))
-			key = i2p::data::ByteStreamToBase64 (buf, 32);
-		LogPrint (eLogDebug, "NetDbReq: DatabaseSearchReply for ", key, " num=", num);
-
-		IdentHash ident (buf);
-		bool isExploratory = false;
-		auto dest = FindRequest (ident);
-		if (dest && dest->IsActive ())
-		{
-			isExploratory = dest->IsExploratory ();
-			if (!isExploratory && (num > 0 || dest->GetNumAttempts () < 3)) // before 3-rd attempt might be just bad luck
-			{	
-				// try to send next requests
-				if (!SendNextRequest (dest))
-					RequestComplete (ident, nullptr);
-			}	
-			else
-				// no more requests for destination possible. delete it
-				RequestComplete (ident, nullptr);
-		}
-		else /*if (!m_FloodfillBootstrap)*/
-		{	
-			LogPrint (eLogInfo, "NetDbReq: Unsolicited or late database search reply for ", key);
-			return;
-		}	
-
-		// try responses
-		if (num > NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES)
-		{
-			LogPrint (eLogWarning, "NetDbReq: Too many peer hashes ", num, " in database search reply, Reduced to ", NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES);
-			num = NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES;
-		}	
-		if (isExploratory && !m_DiscoveredRouterHashes.empty ())
-		{
-			// request outstanding routers
-			for (auto it: m_DiscoveredRouterHashes)
-				RequestRouter (it);
-			m_DiscoveredRouterHashes.clear ();
-			m_DiscoveredRoutersTimer.cancel ();
-		}	
-		for (size_t i = 0; i < num; i++)
-		{
-			IdentHash router (buf + 33 + i*32);
-			if (CheckLogLevel (eLogDebug))
-				LogPrint (eLogDebug, "NetDbReq: ", i, ": ", router.ToBase64 ());
-
-			if (isExploratory)
-				// postpone request
-				m_DiscoveredRouterHashes.push_back (router);
-			else	
-				// send request right a way
-				RequestRouter (router);
-		}
-		if (isExploratory && !m_DiscoveredRouterHashes.empty ())
-			ScheduleDiscoveredRoutersRequest (); 	
-	}	
-
-	void NetDbRequests::RequestRouter (const IdentHash& router)
-	{		
-		auto r = netdb.FindRouter (router);
-		if (!r || i2p::util::GetMillisecondsSinceEpoch () > r->GetTimestamp () + 3600*1000LL)
-		{
-			// router with ident not found or too old (1 hour)
-			LogPrint (eLogDebug, "NetDbReq: Found new/outdated router. Requesting RouterInfo...");
-			if (!IsRouterBanned (router))
-				RequestDestination (router, nullptr, true);
-			else
-				LogPrint (eLogDebug, "NetDbReq: Router ", router.ToBase64 (), " is banned. Skipped");
-		}
-		else
-			LogPrint (eLogDebug, "NetDbReq: [:|||:]");
-	}	
-		
-	void NetDbRequests::PostRequestDestination (const IdentHash& destination, 
-		const RequestedDestination::RequestComplete& requestComplete, bool direct)
-	{
-		boost::asio::post (GetIOService (), [this, destination, requestComplete, direct]()
-			{
-				RequestDestination (destination, requestComplete, direct);
-			});	
-	}
-		
-	void NetDbRequests::RequestDestination (const IdentHash& destination, const RequestedDestination::RequestComplete& requestComplete, bool direct)
-	{
-		auto dest = CreateRequest (destination, false, direct, requestComplete); // non-exploratory
-		if (dest)
-		{	
-			if (!SendNextRequest (dest))
-				RequestComplete (destination, nullptr);
-		}	
-		else
-			LogPrint (eLogWarning, "NetDbReq: Destination ", destination.ToBase64(), " is requested already or cached");
-	}	
-
-	void NetDbRequests::Explore (int numDestinations)
-	{
-		// new requests
-		auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool ();
-		auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr;
-		auto inbound = exploratoryPool ? exploratoryPool->GetNextInboundTunnel () : nullptr;
-		bool throughTunnels = outbound && inbound;
-
-		uint8_t randomHash[32];
-		std::vector<i2p::tunnel::TunnelMessageBlock> msgs;
-		LogPrint (eLogInfo, "NetDbReq: Exploring new ", numDestinations, " routers ...");
-		for (int i = 0; i < numDestinations; i++)
-		{
-			RAND_bytes (randomHash, 32);
-			auto dest = CreateRequest (randomHash, true, !throughTunnels); // exploratory
-			if (!dest)
-			{
-				LogPrint (eLogWarning, "NetDbReq: Exploratory destination is requested already");
-				return;
-			}
-			auto floodfill = netdb.GetClosestFloodfill (randomHash, dest->GetExcludedPeers ());
-			if (floodfill)
-			{
-				if (i2p::transport::transports.IsConnected (floodfill->GetIdentHash ()))
-					throughTunnels = false;
-				if (throughTunnels)
-				{
-					msgs.push_back (i2p::tunnel::TunnelMessageBlock
-						{
-							i2p::tunnel::eDeliveryTypeRouter,
-							floodfill->GetIdentHash (), 0,
-							CreateDatabaseStoreMsg () // tell floodfill about us
-						});
-					msgs.push_back (i2p::tunnel::TunnelMessageBlock
-						{
-							i2p::tunnel::eDeliveryTypeRouter,
-							floodfill->GetIdentHash (), 0,
-							dest->CreateRequestMessage (floodfill, inbound) // explore
-						});
+						if (!dest->IsExploratory ())
+							LogPrint (eLogWarning, "NetDbReq: ", dest->GetDestination ().ToBase64 (), " not found after 7 attempts");
+						done = true;
+					}
 				}
-				else
-					i2p::transport::transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ()));
 			}
+			else // delete obsolete request
+				done = true;
+
+			if (done)
+				it = m_RequestedDestinations.erase (it);
 			else
-				RequestComplete (randomHash, nullptr);
+				++it;
 		}
-		if (throughTunnels && msgs.size () > 0)
-			outbound->SendTunnelDataMsgs (msgs);
-	}	
-
-	void NetDbRequests::ScheduleExploratory (uint64_t interval)
-	{
-		m_ExploratoryTimer.expires_from_now (boost::posix_time::seconds(interval));
-		m_ExploratoryTimer.async_wait (std::bind (&NetDbRequests::HandleExploratoryTimer,
-			this, std::placeholders::_1));
 	}
-		
-	void NetDbRequests::HandleExploratoryTimer (const boost::system::error_code& ecode)
-	{
-		if (ecode != boost::asio::error::operation_aborted)
-		{
-			auto numRouters = netdb.GetNumRouters ();
-			auto nextExploratoryInterval = numRouters < 2500 ? (EXPLORATORY_REQUEST_INTERVAL + m_Rng () % EXPLORATORY_REQUEST_INTERVAL)/2 :
-				EXPLORATORY_REQUEST_INTERVAL + m_Rng () % EXPLORATORY_REQUEST_INTERVAL_VARIANCE;
-			if (numRouters)
-			{	
-				if (i2p::transport::transports.IsOnline () && i2p::transport::transports.IsRunning ()) 
-				{	
-					// explore only if online
-					numRouters = 800/numRouters;
-					if (numRouters < 1) numRouters = 1;
-					if (numRouters > 9) numRouters = 9;
-					Explore (numRouters);
-				}	
-			}	
-			else
-				LogPrint (eLogError, "NetDbReq: No known routers, reseed seems to be totally failed");
-			ScheduleExploratory (nextExploratoryInterval);
-		}	
-	}	
-
-	void NetDbRequests::ScheduleDiscoveredRoutersRequest ()
-	{
-		m_DiscoveredRoutersTimer.expires_from_now (boost::posix_time::milliseconds(
-			DISCOVERED_REQUEST_INTERVAL + m_Rng () % DISCOVERED_REQUEST_INTERVAL_VARIANCE));
-		m_DiscoveredRoutersTimer.async_wait (std::bind (&NetDbRequests::HandleDiscoveredRoutersTimer,
-			this, std::placeholders::_1));
-	}	
-
-	void NetDbRequests::HandleDiscoveredRoutersTimer (const boost::system::error_code& ecode)
-	{
-		if (ecode != boost::asio::error::operation_aborted)
-		{
-			if (!m_DiscoveredRouterHashes.empty ())
-			{
-				RequestRouter (m_DiscoveredRouterHashes.front ());
-				m_DiscoveredRouterHashes.pop_front ();
-				if (!m_DiscoveredRouterHashes.empty ()) // more hashes to request
-					ScheduleDiscoveredRoutersRequest ();
-			}	
-		}	
-	}	
 }
 }
diff --git a/libi2pd/NetDbRequests.h b/libi2pd/NetDbRequests.h
index 53af2c6a..cf2f0915 100644
--- a/libi2pd/NetDbRequests.h
+++ b/libi2pd/NetDbRequests.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2020, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -9,121 +9,66 @@
 #ifndef NETDB_REQUESTS_H__
 #define NETDB_REQUESTS_H__
 
-#include <inttypes.h>
 #include <memory>
-#include <random>
-#include <unordered_set>
-#include <unordered_map>
-#include <list>
+#include <set>
+#include <map>
 #include "Identity.h"
 #include "RouterInfo.h"
-#include "util.h"
 
 namespace i2p
 {
 namespace data
 {
-	const int MAX_NUM_REQUEST_ATTEMPTS = 5;
-	const uint64_t MANAGE_REQUESTS_INTERVAL = 400; // in milliseconds
-	const uint64_t MANAGE_REQUESTS_INTERVAL_VARIANCE = 300; // in milliseconds
-	const uint64_t MIN_REQUEST_TIME = 1200; // in milliseconds
-	const uint64_t MAX_REQUEST_TIME = MAX_NUM_REQUEST_ATTEMPTS * (MIN_REQUEST_TIME + MANAGE_REQUESTS_INTERVAL + MANAGE_REQUESTS_INTERVAL_VARIANCE);
-	const uint64_t MIN_DIRECT_REQUEST_TIME = 600; // in milliseconds
-	const uint64_t EXPLORATORY_REQUEST_INTERVAL = 55; // in seconds
-	const uint64_t EXPLORATORY_REQUEST_INTERVAL_VARIANCE = 170; // in seconds 
-	const uint64_t DISCOVERED_REQUEST_INTERVAL = 360; // in milliseconds
-	const uint64_t DISCOVERED_REQUEST_INTERVAL_VARIANCE = 540; // in milliseconds
-	const uint64_t MAX_EXPLORATORY_REQUEST_TIME = 30000; // in milliseconds
-	const uint64_t REQUEST_CACHE_TIME = MAX_REQUEST_TIME + 40000; // in milliseconds
-	const uint64_t REQUESTED_DESTINATIONS_POOL_CLEANUP_INTERVAL = 191; // in seconds
-	
 	class RequestedDestination
 	{
 		public:
 
 			typedef std::function<void (std::shared_ptr<RouterInfo>)> RequestComplete;
 
-			RequestedDestination (const IdentHash& destination, bool isExploratory = false, bool direct = true);
-			~RequestedDestination ();
+			RequestedDestination (const IdentHash& destination, bool isExploratory = false):
+				m_Destination (destination), m_IsExploratory (isExploratory), m_CreationTime (0) {};
+			~RequestedDestination () { if (m_RequestComplete) m_RequestComplete (nullptr); };
 
 			const IdentHash& GetDestination () const { return m_Destination; };
-			const std::unordered_set<IdentHash>& GetExcludedPeers () const { return m_ExcludedPeers; };
-			int GetNumAttempts () const { return m_NumAttempts; };
+			int GetNumExcludedPeers () const { return m_ExcludedPeers.size (); };
+			const std::set<IdentHash>& GetExcludedPeers () { return m_ExcludedPeers; };
 			void ClearExcludedPeers ();
 			bool IsExploratory () const { return m_IsExploratory; };
-			bool IsDirect () const { return m_IsDirect; };
-			bool IsActive () const { return m_IsActive; };
-			bool IsSentDirectly () const { return m_IsSentDirectly; };
-			bool IsExcluded (const IdentHash& ident) const;
+			bool IsExcluded (const IdentHash& ident) const { return m_ExcludedPeers.count (ident); };
 			uint64_t GetCreationTime () const { return m_CreationTime; };
-			uint64_t GetLastRequestTime () const { return m_LastRequestTime; };
 			std::shared_ptr<I2NPMessage> CreateRequestMessage (std::shared_ptr<const RouterInfo>, std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel);
 			std::shared_ptr<I2NPMessage> CreateRequestMessage (const IdentHash& floodfill);
 
-			void AddRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete.push_back (requestComplete); };
-			void ResetRequestComplete () { m_RequestComplete.clear (); };
+			void SetRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete = requestComplete; };
+			bool IsRequestComplete () const { return m_RequestComplete != nullptr; };
 			void Success (std::shared_ptr<RouterInfo> r);
 			void Fail ();
 
-		private:
-
-			void InvokeRequestComplete (std::shared_ptr<RouterInfo> r);
-			
 		private:
 
 			IdentHash m_Destination;
-			bool m_IsExploratory, m_IsDirect, m_IsActive, m_IsSentDirectly;
-			std::unordered_set<IdentHash> m_ExcludedPeers;
-			uint64_t m_CreationTime, m_LastRequestTime; // in milliseconds
-			std::list<RequestComplete> m_RequestComplete;
-			int m_NumAttempts;
+			bool m_IsExploratory;
+			std::set<IdentHash> m_ExcludedPeers;
+			uint64_t m_CreationTime;
+			RequestComplete m_RequestComplete;
 	};
 
-	class NetDbRequests: public std::enable_shared_from_this<NetDbRequests>,
-		 private i2p::util::RunnableServiceWithWork
+	class NetDbRequests
 	{
 		public:
 
-			NetDbRequests ();
-			~NetDbRequests ();
-			
 			void Start ();
 			void Stop ();
 
+			std::shared_ptr<RequestedDestination> CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete = nullptr);
 			void RequestComplete (const IdentHash& ident, std::shared_ptr<RouterInfo> r);
-			void PostDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg);
-			void PostRequestDestination (const IdentHash& destination, const RequestedDestination::RequestComplete& requestComplete, bool direct);
-			
-		private:	
-
-			std::shared_ptr<RequestedDestination> CreateRequest (const IdentHash& destination, bool isExploratory, 
-				bool direct = false, RequestedDestination::RequestComplete requestComplete = nullptr);
 			std::shared_ptr<RequestedDestination> FindRequest (const IdentHash& ident) const;
-			bool SendNextRequest (std::shared_ptr<RequestedDestination> dest);
-			
-			void HandleDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg);
-			void RequestRouter (const IdentHash& router);
-			void RequestDestination (const IdentHash& destination, const RequestedDestination::RequestComplete& requestComplete, bool direct);
-			void Explore (int numDestinations);
 			void ManageRequests ();
-			// timer
-			void ScheduleManageRequests ();
-			void HandleManageRequestsTimer (const boost::system::error_code& ecode);
-			void ScheduleExploratory (uint64_t interval);
-			void HandleExploratoryTimer (const boost::system::error_code& ecode);
-			void ScheduleCleanup ();
-			void HandleCleanupTimer (const boost::system::error_code& ecode);
-			void ScheduleDiscoveredRoutersRequest ();
-			void HandleDiscoveredRoutersTimer (const boost::system::error_code& ecode);
-			
+
 		private:
 
-			i2p::util::MemoryPoolMt<RequestedDestination> m_RequestedDestinationsPool;
-			std::unordered_map<IdentHash, std::shared_ptr<RequestedDestination> > m_RequestedDestinations;
-			std::list<IdentHash> m_DiscoveredRouterHashes;
-			boost::asio::deadline_timer m_ManageRequestsTimer, m_ExploratoryTimer,
-				m_CleanupTimer, m_DiscoveredRoutersTimer;
-			std::mt19937 m_Rng;
+			mutable std::mutex m_RequestedDestinationsMutex;
+			std::map<IdentHash, std::shared_ptr<RequestedDestination> > m_RequestedDestinations;
 	};
 }
 }
diff --git a/libi2pd/Poly1305.cpp b/libi2pd/Poly1305.cpp
new file mode 100644
index 00000000..20b3ab2a
--- /dev/null
+++ b/libi2pd/Poly1305.cpp
@@ -0,0 +1,25 @@
+/**
+ * This code is licensed under the MCGSI Public License
+ * Copyright 2018 Jeff Becker
+ *
+ *Kovri go write your own code
+ *
+ */
+
+#include "Poly1305.h"
+
+#if !OPENSSL_AEAD_CHACHA20_POLY1305
+namespace i2p
+{
+namespace crypto
+{
+	void Poly1305HMAC(uint64_t * out, const uint64_t * key, const uint8_t * buf, std::size_t sz)
+	{
+		Poly1305 p(key);
+		p.Update(buf, sz);
+		p.Finish(out);
+	}
+}
+}
+#endif
+
diff --git a/libi2pd/Poly1305.h b/libi2pd/Poly1305.h
new file mode 100644
index 00000000..db659b84
--- /dev/null
+++ b/libi2pd/Poly1305.h
@@ -0,0 +1,261 @@
+/**
+ * This code is licensed under the MCGSI Public License
+ * Copyright 2018 Jeff Becker
+ *
+ * Kovri go write your own code
+ *
+ */
+
+#ifndef LIBI2PD_POLY1305_H
+#define LIBI2PD_POLY1305_H
+#include <cstdint>
+#include <cstring>
+#include "Crypto.h"
+
+#if !OPENSSL_AEAD_CHACHA20_POLY1305
+namespace i2p
+{
+namespace crypto
+{
+	const std::size_t POLY1305_DIGEST_BYTES = 16;
+	const std::size_t POLY1305_DIGEST_DWORDS = 4;
+	const std::size_t POLY1305_KEY_BYTES = 32;
+	const std::size_t POLY1305_KEY_DWORDS = 8;
+	const std::size_t POLY1305_BLOCK_BYTES = 16;
+
+	namespace poly1305
+	{
+		struct LongBlock
+		{
+			unsigned long data[17];
+			operator unsigned long * ()
+			{
+				return data;
+			}
+		};
+
+		struct Block
+		{
+			unsigned char data[17];
+
+			void Zero()
+			{
+				memset(data, 0, sizeof(data));
+			}
+
+			operator uint8_t * ()
+			{
+				return data;
+			}
+
+			Block & operator += (const Block & other)
+			{
+				unsigned short u;
+				unsigned int i;
+				for(u = 0, i = 0; i < 17; i++)
+				{
+					u += (unsigned short) data[i] + (unsigned short) other.data[i];
+					data[i] = (unsigned char) u & 0xff;
+					u >>= 8;
+				}
+				return *this;
+			}
+
+			Block & operator %=(const LongBlock & other)
+			{
+				unsigned long u;
+				unsigned int i;
+				u = 0;
+				for (i = 0; i < 16; i++) {
+					u += other.data[i];
+					data[i] = (unsigned char)u & 0xff;
+					u >>= 8;
+				}
+				u += other.data[16];
+				data[16] = (unsigned char)u & 0x03;
+				u >>= 2;
+				u += (u << 2);
+				for (i = 0; i < 16; i++) {
+					u += data[i];
+					data[i] = (unsigned char)u & 0xff;
+					u >>= 8;
+				}
+				data[16] += (unsigned char)u;
+				return *this;
+			}
+
+			Block & operator = (const Block & other)
+			{
+				memcpy(data, other.data, sizeof(data));
+				return *this;
+			}
+
+			Block & operator ~ ()
+			{
+				static const Block minusp = {
+					0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+					0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+					0xfc
+				};
+				Block orig;
+				unsigned char neg;
+				unsigned int i;
+				orig = *this;
+				*this += minusp;
+				neg = -(data[16] >> 7);
+				for(i = 0; i < 17; i++)
+					data[i] ^= neg & (orig.data[i] ^ data[i]);
+
+				return *this;
+			}
+
+			void PutKey(const uint64_t * key_l)
+			{
+				const uint8_t * key = (const uint8_t*) key_l;
+				data[0] = key[0] & 0xff;
+				data[1] = key[1] & 0xff;
+				data[2] = key[2] & 0xff;
+				data[3] = key[3] & 0x0f;
+				data[4] = key[4] & 0xfc;
+				data[5] = key[5] & 0xff;
+				data[6] = key[6] & 0xff;
+				data[7] = key[7] & 0x0f;
+				data[8] = key[8] & 0xfc;
+				data[9] = key[9] & 0xff;
+				data[10] = key[10] & 0xff;
+				data[11] = key[11] & 0x0f;
+				data[12] = key[12] & 0xfc;
+				data[13] = key[13] & 0xff;
+				data[14] = key[14] & 0xff;
+				data[15] = key[15] & 0x0f;
+				data[16] = 0;
+			}
+
+			template<typename Int_t>
+			void Put(const Int_t * d, uint8_t last=0)
+			{
+				memcpy(data, d, 16);
+				data[16] = last;
+			}
+		};
+
+		struct Buffer
+		{
+			uint8_t data[POLY1305_BLOCK_BYTES];
+
+			operator uint8_t * ()
+			{
+				return data;
+			}
+		};
+	}
+
+	struct Poly1305
+	{
+		Poly1305(const uint64_t * key)
+		{
+			m_Leftover = 0;
+			m_H.Zero();
+			m_Final = 0;
+			m_R.PutKey(key);
+			m_Pad.Put(key + 2);
+		}
+
+		void Update(const uint8_t * buf, size_t sz)
+		{
+			// process leftover
+			if(m_Leftover)
+			{
+				size_t want = POLY1305_BLOCK_BYTES - m_Leftover;
+				if(want > sz) want = sz;
+				memcpy(m_Buffer + m_Leftover, buf, want);
+				sz -= want;
+				buf += want;
+				m_Leftover += want;
+				if(m_Leftover < POLY1305_BLOCK_BYTES) return;
+				Blocks(m_Buffer, POLY1305_BLOCK_BYTES);
+				m_Leftover = 0;
+			}
+			// process blocks
+			if(sz >= POLY1305_BLOCK_BYTES)
+			{
+				size_t want = (sz & ~(POLY1305_BLOCK_BYTES - 1));
+				Blocks(buf, want);
+				buf += want;
+				sz -= want;
+			}
+			// leftover
+			if(sz)
+			{
+				memcpy(m_Buffer+m_Leftover, buf, sz);
+				m_Leftover += sz;
+			}
+		}
+
+		void Blocks(const uint8_t * buf, size_t sz)
+		{
+			const unsigned char hi = m_Final ^ 1;
+			while (sz >= POLY1305_BLOCK_BYTES) {
+				unsigned long u;
+				unsigned int i, j;
+				m_Msg.Put(buf, hi);
+				/* h += m */
+				m_H += m_Msg;
+
+				/* h *= r */
+				for (i = 0; i < 17; i++) {
+					u = 0;
+					for (j = 0; j <= i ; j++) {
+						u += (unsigned short)m_H.data[j] * m_R.data[i - j];
+					}
+					for (j = i + 1; j < 17; j++) {
+						unsigned long v = (unsigned short)m_H.data[j] * m_R.data[i + 17 - j];
+						v = ((v << 8) + (v << 6)); /* v *= (5 << 6); */
+						u += v;
+					}
+					m_HR[i] = u;
+				}
+				/* (partial) h %= p */
+				m_H %= m_HR;
+				buf += POLY1305_BLOCK_BYTES;
+				sz -= POLY1305_BLOCK_BYTES;
+			}
+		}
+
+		void Finish(uint64_t * out)
+		{
+			// process leftovers
+			if(m_Leftover)
+			{
+				size_t idx = m_Leftover;
+				m_Buffer[idx++] = 1;
+				for(; idx < POLY1305_BLOCK_BYTES; idx++)
+					m_Buffer[idx] = 0;
+				m_Final = 1;
+				Blocks(m_Buffer, POLY1305_BLOCK_BYTES);
+			}
+
+			// freeze H
+			~m_H;
+			// add pad
+			m_H += m_Pad;
+			// copy digest
+			memcpy(out, m_H, 16);
+		}
+
+		size_t m_Leftover;
+		poly1305::Buffer m_Buffer;
+		poly1305::Block m_H;
+		poly1305::Block m_R;
+		poly1305::Block m_Pad;
+		poly1305::Block m_Msg;
+		poly1305::LongBlock m_HR;
+		uint8_t m_Final;
+	};
+
+	void Poly1305HMAC(uint64_t * out, const uint64_t * key, const uint8_t * buf, std::size_t sz);
+}
+}
+#endif
+
+#endif
diff --git a/libi2pd/PostQuantum.cpp b/libi2pd/PostQuantum.cpp
deleted file mode 100644
index fa268828..00000000
--- a/libi2pd/PostQuantum.cpp
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
-* Copyright (c) 2025, The PurpleI2P Project
-*
-* This file is part of Purple i2pd project and licensed under BSD3
-*
-* See full license text in LICENSE file at top of project tree
-*/
-
-#include "Log.h"
-#include "PostQuantum.h"
-
-#if OPENSSL_PQ
-
-#include <openssl/param_build.h>
-#include <openssl/core_names.h>
-
-namespace i2p
-{
-namespace crypto
-{
-	MLKEMKeys::MLKEMKeys (MLKEMTypes type):
-		m_Name (std::get<0>(MLKEMS[type])), m_KeyLen (std::get<1>(MLKEMS[type])), 
-		m_CTLen (std::get<2>(MLKEMS[type])), m_Pkey (nullptr)
-	{
-	}
-	
-	MLKEMKeys::~MLKEMKeys ()
-	{
-		if (m_Pkey) EVP_PKEY_free (m_Pkey);
-	}	
-
-	void MLKEMKeys::GenerateKeys ()
-	{
-		if (m_Pkey) EVP_PKEY_free (m_Pkey);
-		m_Pkey = EVP_PKEY_Q_keygen(NULL, NULL, m_Name.c_str ());
-	}
-
-	void MLKEMKeys::GetPublicKey (uint8_t * pub) const
-	{	
-		if (m_Pkey)
-		{	
-			size_t len = m_KeyLen;
-		    EVP_PKEY_get_octet_string_param (m_Pkey, OSSL_PKEY_PARAM_PUB_KEY, pub, m_KeyLen, &len);
-		}	
-	}
-
-	void MLKEMKeys::SetPublicKey (const uint8_t * pub)
-	{
-		if (m_Pkey)
-		{	
-			EVP_PKEY_free (m_Pkey);
-			m_Pkey = nullptr;
-		}	
-		OSSL_PARAM params[] =
-		{
-			OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PUB_KEY, (uint8_t *)pub, m_KeyLen),
-			OSSL_PARAM_END
-		};		
-		EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, m_Name.c_str (), NULL);
-		if (ctx)
-		{
-			EVP_PKEY_fromdata_init (ctx);
-			EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, params);
-			EVP_PKEY_CTX_free (ctx);
-		}
-		else
-			LogPrint (eLogError, "MLKEM can't create PKEY context");
-	}
-
-	void MLKEMKeys::Encaps (uint8_t * ciphertext, uint8_t * shared)
-	{
-		if (!m_Pkey) return;
-		auto ctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL);
-		if (ctx)
-		{	
-			EVP_PKEY_encapsulate_init (ctx, NULL);
-			size_t len = m_CTLen, sharedLen = 32;
-			EVP_PKEY_encapsulate (ctx, ciphertext, &len, shared, &sharedLen);
-			EVP_PKEY_CTX_free (ctx);
-		}
-		else
-			LogPrint (eLogError, "MLKEM can't create PKEY context");
-	}	
-
-	void MLKEMKeys::Decaps (const uint8_t * ciphertext, uint8_t * shared)
-	{
-		if (!m_Pkey) return;
-		auto ctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL);
-		if (ctx)
-		{	
-			EVP_PKEY_decapsulate_init (ctx, NULL);
-			size_t sharedLen = 32;
-			EVP_PKEY_decapsulate (ctx, shared, &sharedLen, ciphertext, m_CTLen);
-			EVP_PKEY_CTX_free (ctx);
-		}
-		else
-			LogPrint (eLogError, "MLKEM can't create PKEY context");
-	}	
-
-	std::unique_ptr<MLKEMKeys> CreateMLKEMKeys (i2p::data::CryptoKeyType type)
-	{
-		if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
-		    type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)MLKEMS.size ()) return nullptr;
-		return std::make_unique<MLKEMKeys>((MLKEMTypes)(type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1));
-	}	
-
-	static constexpr std::array<std::pair<std::array<uint8_t, 32>, std::array<uint8_t, 32> >, 3> NoiseIKInitMLKEMKeys =
-	{
-		std::make_pair
-		(
-			std::array<uint8_t, 32>
-		 	{
-				0xb0, 0x8f, 0xb1, 0x73, 0x92, 0x66, 0xc9, 0x90, 0x45, 0x7f, 0xdd, 0xc6, 0x4e, 0x55, 0x40, 0xd8, 
-				0x0a, 0x37, 0x99, 0x06, 0x92, 0x2a, 0x78, 0xc4, 0xb1, 0xef, 0x86, 0x06, 0xd0, 0x15, 0x9f, 0x4d
-			}, // SHA256("Noise_IKhfselg2_25519+MLKEM512_ChaChaPoly_SHA256")
-			std::array<uint8_t, 32>
-		 	{
-				0x95, 0x8d, 0xf6, 0x6c, 0x95, 0xce, 0xa9, 0xf7, 0x42, 0xfc, 0xfa, 0x62, 0x71, 0x36, 0x1e, 0xa7,
-				0xdc, 0x7a, 0xc0, 0x75, 0x01, 0xcf, 0xf9, 0xfc, 0x9f, 0xdb, 0x4c, 0x68, 0x3a, 0x53, 0x49, 0xeb
-			} // SHA256 (first)
-		),
-		std::make_pair
-		(
-			std::array<uint8_t, 32>
-		 	{
-				0x36, 0x03, 0x90, 0x2d, 0xf9, 0xa2, 0x2a, 0x5e, 0xc9, 0x3d, 0xdb, 0x8f, 0xa8, 0x1b, 0xdb, 0x4b,
-				0xae, 0x9d, 0x93, 0x9c, 0xdf, 0xaf, 0xde, 0x55, 0x49, 0x13, 0xfe, 0x98, 0xf8, 0x4a, 0xd4, 0xbd
-			}, // SHA256("Noise_IKhfselg2_25519+MLKEM768_ChaChaPoly_SHA256")
-		 	std::array<uint8_t, 32>
-		 	{
-				0x15, 0x44, 0x89, 0xbf, 0x30, 0xf0, 0xc9, 0x77, 0x66, 0x10, 0xcb, 0xb1, 0x57, 0x3f, 0xab, 0x68,
-				0x79, 0x57, 0x39, 0x57, 0x0a, 0xe7, 0xc0, 0x31, 0x8a, 0xa2, 0x96, 0xef, 0xbf, 0xa9, 0x6a, 0xbb
-			} // SHA256 (first)
-		),
-		std::make_pair
-		(
-			std::array<uint8_t, 32>
-		 	{
-				0x86, 0xa5, 0x36, 0x44, 0xc6, 0x12, 0xd5, 0x71, 0xa1, 0x2d, 0xd8, 0xb6, 0x0a, 0x00, 0x9f, 0x2c,
-				0x1a, 0xa8, 0x7d, 0x22, 0xa4, 0xff, 0x2b, 0xcd, 0x61, 0x34, 0x97, 0x6d, 0xa1, 0x49, 0xeb, 0x4a
-			}, // SHA256("Noise_IKhfselg2_25519+MLKEM1024_ChaChaPoly_SHA256")
-		 	std::array<uint8_t, 32>
-		 	{
-				0x42, 0x0d, 0xc2, 0x1c, 0x7b, 0x18, 0x61, 0xb7, 0x4a, 0x04, 0x3d, 0xae, 0x0f, 0xdc, 0xf2, 0x71,
-				0xb9, 0xba, 0x19, 0xbb, 0xbd, 0x5f, 0xd4, 0x9c, 0x3f, 0x4b, 0x01, 0xed, 0x6d, 0x13, 0x1d, 0xa2
-			} // SHA256 (first)
-		)
-	};
-		
-	void InitNoiseIKStateMLKEM (NoiseSymmetricState& state, i2p::data::CryptoKeyType type, const uint8_t * pub)
-	{
-		if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
-		    type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)NoiseIKInitMLKEMKeys.size ()) return;
-		auto ind = type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1;
-		state.Init (NoiseIKInitMLKEMKeys[ind].first.data(), NoiseIKInitMLKEMKeys[ind].second.data(), pub);
-	}		
-}	
-}	
-
-#endif
\ No newline at end of file
diff --git a/libi2pd/PostQuantum.h b/libi2pd/PostQuantum.h
deleted file mode 100644
index f426d661..00000000
--- a/libi2pd/PostQuantum.h
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
-* Copyright (c) 2025, The PurpleI2P Project
-*
-* This file is part of Purple i2pd project and licensed under BSD3
-*
-* See full license text in LICENSE file at top of project tree
-*/
-
-#ifndef POST_QUANTUM_H__
-#define POST_QUANTUM_H__
-
-#include <memory>
-#include <string_view>
-#include <array>
-#include <tuple>
-#include "Crypto.h"
-#include "Identity.h"
-
-#if OPENSSL_PQ
-
-namespace i2p
-{
-namespace crypto
-{
-	enum MLKEMTypes
-	{
-		eMLKEM512 = 0,
-		eMLKEM768,
-		eMLKEM1024
-	};	
-	
-	constexpr size_t MLKEM512_KEY_LENGTH = 800;
-	constexpr size_t MLKEM512_CIPHER_TEXT_LENGTH = 768;
-	constexpr size_t MLKEM768_KEY_LENGTH = 1184;
-	constexpr size_t MLKEM768_CIPHER_TEXT_LENGTH = 1088;
-	constexpr size_t MLKEM1024_KEY_LENGTH = 1568;
-	constexpr size_t MLKEM1024_CIPHER_TEXT_LENGTH = 1568;
-
-	constexpr std::array<std::tuple<std::string_view, size_t, size_t>, 3> MLKEMS =
-	{
-		std::make_tuple ("ML-KEM-512", MLKEM512_KEY_LENGTH, MLKEM512_CIPHER_TEXT_LENGTH),
-		std::make_tuple ("ML-KEM-768", MLKEM768_KEY_LENGTH, MLKEM768_CIPHER_TEXT_LENGTH),
-		std::make_tuple ("ML-KEM-1024", MLKEM1024_KEY_LENGTH, MLKEM1024_CIPHER_TEXT_LENGTH)
-	};	
-
-	constexpr size_t GetMLKEMPublicKeyLen (i2p::data::CryptoKeyType type)
-	{
-		if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
-		    type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)MLKEMS.size ()) return 0;
-		return std::get<1>(MLKEMS[type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1]);
-	}	
-
-	constexpr size_t GetMLKEMCipherTextLen (i2p::data::CryptoKeyType type)
-	{
-		if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
-		    type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)MLKEMS.size ()) return 0;
-		return std::get<2>(MLKEMS[type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1]);
-	}
-	
-	class MLKEMKeys
-	{
-		public:
-
-			MLKEMKeys (MLKEMTypes type);
-			~MLKEMKeys ();
-
-			void GenerateKeys ();
-			void GetPublicKey (uint8_t * pub) const;
-			void SetPublicKey (const uint8_t * pub);
-			void Encaps (uint8_t * ciphertext, uint8_t * shared);
-			void Decaps (const uint8_t * ciphertext, uint8_t * shared);
-			
-		private:
-
-			const std::string m_Name;
-			const size_t m_KeyLen, m_CTLen;
-			EVP_PKEY * m_Pkey;		
-	};
-
-	std::unique_ptr<MLKEMKeys> CreateMLKEMKeys (i2p::data::CryptoKeyType type);
-
-	void InitNoiseIKStateMLKEM (NoiseSymmetricState& state, i2p::data::CryptoKeyType type, const uint8_t * pub); // Noise_IK (ratchets) PQ ML-KEM5
-}	
-}	
-
-#endif
-
-#endif
diff --git a/libi2pd/Profiling.cpp b/libi2pd/Profiling.cpp
index fe7f9905..2031fa39 100644
--- a/libi2pd/Profiling.cpp
+++ b/libi2pd/Profiling.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -10,7 +10,6 @@
 #include <unordered_map>
 #include <list>
 #include <thread>
-#include <iomanip>
 #include <boost/property_tree/ptree.hpp>
 #include <boost/property_tree/ini_parser.hpp>
 #include "Base.h"
@@ -27,21 +26,23 @@ namespace data
 	static i2p::fs::HashedStorage g_ProfilesStorage("peerProfiles", "p", "profile-", "txt");
 	static std::unordered_map<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > g_Profiles;
 	static std::mutex g_ProfilesMutex;
-	static std::list<std::pair<i2p::data::IdentHash, std::function<void (std::shared_ptr<RouterProfile>)> > > g_PostponedUpdates;
-	static std::mutex g_PostponedUpdatesMutex;
-	
+
+	static boost::posix_time::ptime GetTime ()
+	{
+		return boost::posix_time::second_clock::local_time();
+	}
+
 	RouterProfile::RouterProfile ():
-		m_IsUpdated (false), m_LastDeclineTime (0), m_LastUnreachableTime (0),
-		m_LastUpdateTime (i2p::util::GetSecondsSinceEpoch ()), m_LastAccessTime (0),
-		m_LastPersistTime (0), m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0),
-		m_NumTunnelsNonReplied (0),m_NumTimesTaken (0), m_NumTimesRejected (0),
-		m_HasConnected (false), m_IsDuplicated (false)
+		m_LastUpdateTime (GetTime ()), m_IsUpdated (false),
+		m_LastDeclineTime (0), m_LastUnreachableTime (0),
+		m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0),
+		m_NumTimesTaken (0), m_NumTimesRejected (0), m_HasConnected (false)
 	{
 	}
 
 	void RouterProfile::UpdateTime ()
 	{
-		m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch ();
+		m_LastUpdateTime = GetTime ();
 		m_IsUpdated = true;
 	}
 
@@ -56,11 +57,9 @@ namespace data
 		usage.put (PEER_PROFILE_USAGE_TAKEN, m_NumTimesTaken);
 		usage.put (PEER_PROFILE_USAGE_REJECTED, m_NumTimesRejected);
 		usage.put (PEER_PROFILE_USAGE_CONNECTED, m_HasConnected);
-		if (m_IsDuplicated)
-			usage.put (PEER_PROFILE_USAGE_DUPLICATED, true);
 		// fill property tree
 		boost::property_tree::ptree pt;
-		pt.put (PEER_PROFILE_LAST_UPDATE_TIMESTAMP, m_LastUpdateTime);
+		pt.put (PEER_PROFILE_LAST_UPDATE_TIME, boost::posix_time::to_simple_string (m_LastUpdateTime));
 		if (m_LastUnreachableTime)
 			pt.put (PEER_PROFILE_LAST_UNREACHABLE_TIME, m_LastUnreachableTime);
 		pt.put_child (PEER_PROFILE_SECTION_PARTICIPATION, participation);
@@ -80,7 +79,6 @@ namespace data
 
 	void RouterProfile::Load (const IdentHash& identHash)
 	{
-		m_IsUpdated = false;
 		std::string ident = identHash.ToBase64 ();
 		std::string path = g_ProfilesStorage.Path(ident);
 		boost::property_tree::ptree pt;
@@ -103,22 +101,10 @@ namespace data
 
 		try
 		{
-			auto ts = pt.get (PEER_PROFILE_LAST_UPDATE_TIMESTAMP, 0);
-			if (ts)
-				m_LastUpdateTime = ts;
-			else	
-			{	
-				// try old lastupdatetime 
-				auto ut = pt.get (PEER_PROFILE_LAST_UPDATE_TIME, "");
-				if (ut.length () > 0)
-				{	
-					std::istringstream ss (ut); std::tm t;
-					ss >> std::get_time(&t, "%Y-%b-%d %H:%M:%S");
-					if (!ss.fail())
-						m_LastUpdateTime = mktime (&t); // t is local time
-				}	
-			}	
-			if (i2p::util::GetSecondsSinceEpoch () - m_LastUpdateTime < PEER_PROFILE_EXPIRATION_TIMEOUT)
+			auto t = pt.get (PEER_PROFILE_LAST_UPDATE_TIME, "");
+			if (t.length () > 0)
+				m_LastUpdateTime = boost::posix_time::time_from_string (t);
+			if ((GetTime () - m_LastUpdateTime).hours () < PEER_PROFILE_EXPIRATION_TIMEOUT)
 			{
 				m_LastUnreachableTime = pt.get (PEER_PROFILE_LAST_UNREACHABLE_TIME, 0);
 				try
@@ -140,7 +126,6 @@ namespace data
 					m_NumTimesTaken = usage.get (PEER_PROFILE_USAGE_TAKEN, 0);
 					m_NumTimesRejected = usage.get (PEER_PROFILE_USAGE_REJECTED, 0);
 					m_HasConnected = usage.get (PEER_PROFILE_USAGE_CONNECTED, false);
-					m_IsDuplicated = usage.get (PEER_PROFILE_USAGE_DUPLICATED, false);
 				}
 				catch (boost::property_tree::ptree_bad_path& ex)
 				{
@@ -193,11 +178,6 @@ namespace data
 		UpdateTime ();
 	}
 
-	void RouterProfile::Duplicated ()
-	{
-		m_IsDuplicated = true;
-	}	
-		
 	bool RouterProfile::IsLowPartcipationRate () const
 	{
 		return 4*m_NumTunnelsAgreed < m_NumTunnelsDeclined; // < 20% rate
@@ -209,9 +189,10 @@ namespace data
 		return m_NumTunnelsNonReplied > 10*(total + 1);
 	}
 
-	bool RouterProfile::IsDeclinedRecently (uint64_t ts)
+	bool RouterProfile::IsDeclinedRecently ()
 	{
 		if (!m_LastDeclineTime) return false;
+		auto ts = i2p::util::GetSecondsSinceEpoch ();
 		if (ts > m_LastDeclineTime + PEER_PROFILE_DECLINED_RECENTLY_INTERVAL ||
 		    ts + PEER_PROFILE_DECLINED_RECENTLY_INTERVAL < m_LastDeclineTime)
 			m_LastDeclineTime = 0;
@@ -220,10 +201,7 @@ namespace data
 
 	bool RouterProfile::IsBad ()
 	{
-		if (IsUnreachable () || m_IsDuplicated) return true;
-		auto ts = i2p::util::GetSecondsSinceEpoch ();
-		if (ts > PEER_PROFILE_MAX_DECLINED_INTERVAL + m_LastDeclineTime) return false;
-		if (IsDeclinedRecently (ts)) return true; 
+		if (IsDeclinedRecently () || IsUnreachable ()) return true;
 		auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () /*|| IsLowReplyRate ()*/;
 		if (isBad && m_NumTimesRejected > 10*(m_NumTimesTaken + 1))
 		{
@@ -258,165 +236,86 @@ namespace data
 			std::unique_lock<std::mutex> l(g_ProfilesMutex);
 			auto it = g_Profiles.find (identHash);
 			if (it != g_Profiles.end ())
-			{
-				it->second->SetLastAccessTime (i2p::util::GetSecondsSinceEpoch ());
 				return it->second;
-			}	
 		}
 		auto profile = netdb.NewRouterProfile ();
 		profile->Load (identHash); // if possible
-		std::lock_guard<std::mutex> l(g_ProfilesMutex);
+		std::unique_lock<std::mutex> l(g_ProfilesMutex);
 		g_Profiles.emplace (identHash, profile);
 		return profile;
 	}
 
-	bool IsRouterBanned (const IdentHash& identHash)
-	{
-		std::lock_guard<std::mutex> l(g_ProfilesMutex);
-		auto it = g_Profiles.find (identHash);
-		if (it != g_Profiles.end ())
-			return it->second->IsUnreachable ();
-		return false;
-	}	
-
-	bool IsRouterDuplicated (const IdentHash& identHash)
-	{
-		std::lock_guard<std::mutex> l(g_ProfilesMutex);
-		auto it = g_Profiles.find (identHash);
-		if (it != g_Profiles.end ())
-			return it->second->IsDuplicated ();
-		return false;
-	}	
-		
 	void InitProfilesStorage ()
 	{
 		g_ProfilesStorage.SetPlace(i2p::fs::GetDataDir());
 		g_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64);
 	}
-		
-	static void SaveProfilesToDisk (std::list<std::pair<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > >&& profiles)
+
+	void PersistProfiles ()
 	{
-		for (auto& it: profiles)
-			if (it.second) it.second->Save (it.first);
-	}	
-		
-	std::future<void> PersistProfiles ()
-	{
-		auto ts = i2p::util::GetSecondsSinceEpoch ();
+		auto ts = GetTime ();
 		std::list<std::pair<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > > tmp;
 		{
-			std::lock_guard<std::mutex> l(g_ProfilesMutex);
+			std::unique_lock<std::mutex> l(g_ProfilesMutex);
 			for (auto it = g_Profiles.begin (); it != g_Profiles.end ();)
 			{
-				if (it->second->IsUpdated () && ts > it->second->GetLastPersistTime () + PEER_PROFILE_PERSIST_INTERVAL)
+				if ((ts - it->second->GetLastUpdateTime ()).total_seconds () > PEER_PROFILE_PERSIST_INTERVAL)
 				{
-					tmp.push_back (*it);
-					it->second->SetLastPersistTime (ts);
-					it->second->SetUpdated (false);
-				}	
-				if (!it->second->IsUpdated () && ts > std::max (it->second->GetLastUpdateTime (), it->second->GetLastAccessTime ()) + PEER_PROFILE_PERSIST_INTERVAL)
+					if (it->second->IsUpdated ())
+						tmp.push_back (std::make_pair (it->first, it->second));
 					it = g_Profiles.erase (it);
+				}
 				else
 					it++;
 			}
 		}
-		if (!tmp.empty ())
-			return std::async (std::launch::async, SaveProfilesToDisk, std::move (tmp));
-		return std::future<void>();
+		for (auto& it: tmp)
+			if (it.second) it.second->Save (it.first);
 	}
 
 	void SaveProfiles ()
 	{
 		std::unordered_map<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > tmp;
 		{
-			std::lock_guard<std::mutex> l(g_ProfilesMutex);
-			std::swap (tmp, g_Profiles);
+			std::unique_lock<std::mutex> l(g_ProfilesMutex);
+			tmp = g_Profiles;
+			g_Profiles.clear ();
 		}
-		auto ts = i2p::util::GetSecondsSinceEpoch ();
+		auto ts = GetTime ();
 		for (auto& it: tmp)
-			if (it.second->IsUseful() && (it.second->IsUpdated () || ts - it.second->GetLastUpdateTime () < PEER_PROFILE_EXPIRATION_TIMEOUT))
+			if (it.second->IsUseful() && (it.second->IsUpdated () || (ts - it.second->GetLastUpdateTime ()).total_seconds () < PEER_PROFILE_EXPIRATION_TIMEOUT*3600))
 				it.second->Save (it.first);
 	}
 
-	static void DeleteFilesFromDisk ()
-	{
-		std::vector<std::string> files;
-		g_ProfilesStorage.Traverse(files);
-		
-		struct stat st;
-		std::time_t now = std::time(nullptr);
-		for (const auto& path: files) 
-		{
-			if (stat(path.c_str(), &st) != 0) 
-			{	
-				LogPrint(eLogWarning, "Profiling: Can't stat(): ", path);
-				continue;
-			}
-			if (now - st.st_mtime >= PEER_PROFILE_EXPIRATION_TIMEOUT) 
-			{
-				LogPrint(eLogDebug, "Profiling: Removing expired peer profile: ", path);
-				i2p::fs::Remove(path);
-			}
-		}
-	}	
-		
-	std::future<void> DeleteObsoleteProfiles ()
+	void DeleteObsoleteProfiles ()
 	{
 		{
-			auto ts = i2p::util::GetSecondsSinceEpoch ();
-			std::lock_guard<std::mutex> l(g_ProfilesMutex);
+			auto ts = GetTime ();
+			std::unique_lock<std::mutex> l(g_ProfilesMutex);
 			for (auto it = g_Profiles.begin (); it != g_Profiles.end ();)
 			{
-				if (ts - it->second->GetLastUpdateTime () >= PEER_PROFILE_EXPIRATION_TIMEOUT)
+				if ((ts - it->second->GetLastUpdateTime ()).total_seconds () >= PEER_PROFILE_EXPIRATION_TIMEOUT*3600)
 					it = g_Profiles.erase (it);
 				else
 					it++;
 			}
 		}
 
-		return std::async (std::launch::async, DeleteFilesFromDisk);
-	}
+		struct stat st;
+		std::time_t now = std::time(nullptr);
 
-	bool UpdateRouterProfile (const IdentHash& identHash, std::function<void (std::shared_ptr<RouterProfile>)> update)
-	{
-		if (!update) return true;
-		std::shared_ptr<RouterProfile> profile;
-		{
-			std::lock_guard<std::mutex> l(g_ProfilesMutex);
-			auto it = g_Profiles.find (identHash);
-			if (it != g_Profiles.end ())
-				profile = it->second;
+		std::vector<std::string> files;
+		g_ProfilesStorage.Traverse(files);
+		for (const auto& path: files) {
+			if (stat(path.c_str(), &st) != 0) {
+				LogPrint(eLogWarning, "Profiling: Can't stat(): ", path);
+				continue;
+			}
+			if (now - st.st_mtime >= PEER_PROFILE_EXPIRATION_TIMEOUT*3600) {
+				LogPrint(eLogDebug, "Profiling: Removing expired peer profile: ", path);
+				i2p::fs::Remove(path);
+			}
 		}
-		if (profile)
-		{
-			update (profile);
-			return true;
-		}	
-		// postpone
-		std::lock_guard<std::mutex> l(g_PostponedUpdatesMutex);
-		g_PostponedUpdates.emplace_back (identHash, update);
-		return false;
-	}	
-
-	static void ApplyPostponedUpdates (std::list<std::pair<i2p::data::IdentHash, std::function<void (std::shared_ptr<RouterProfile>)> > >&& updates)
-	{
-		for (const auto& [ident, update] : updates)
-		{
-			auto profile = GetRouterProfile (ident);
-			update (profile);
-		}	
-	}	
-		
-	std::future<void> FlushPostponedRouterProfileUpdates ()
-	{
-		if (g_PostponedUpdates.empty ()) return std::future<void>();
-
-		std::list<std::pair<i2p::data::IdentHash, std::function<void (std::shared_ptr<RouterProfile>)> > > updates;
-		{
-			std::lock_guard<std::mutex> l(g_PostponedUpdatesMutex);
-			g_PostponedUpdates.swap (updates);
-		}		
-		return std::async (std::launch::async, ApplyPostponedUpdates, std::move (updates));	
-	}	
+	}
 }
 }
diff --git a/libi2pd/Profiling.h b/libi2pd/Profiling.h
index 59995b3f..6531e060 100644
--- a/libi2pd/Profiling.h
+++ b/libi2pd/Profiling.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -10,9 +10,7 @@
 #define PROFILING_H__
 
 #include <memory>
-#include <future>
-#include <functional>
-#include <boost/asio.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
 #include "Identity.h"
 
 namespace i2p
@@ -23,8 +21,7 @@ namespace data
 	const char PEER_PROFILE_SECTION_PARTICIPATION[] = "participation";
 	const char PEER_PROFILE_SECTION_USAGE[] = "usage";
 	// params
-	const char PEER_PROFILE_LAST_UPDATE_TIME[] = "lastupdatetime"; // deprecated
-	const char PEER_PROFILE_LAST_UPDATE_TIMESTAMP[] = "lastupdatetimestamp";
+	const char PEER_PROFILE_LAST_UPDATE_TIME[] = "lastupdatetime";
 	const char PEER_PROFILE_LAST_UNREACHABLE_TIME[] = "lastunreachabletime";
 	const char PEER_PROFILE_PARTICIPATION_AGREED[] = "agreed";
 	const char PEER_PROFILE_PARTICIPATION_DECLINED[] = "declined";
@@ -32,22 +29,15 @@ namespace data
 	const char PEER_PROFILE_USAGE_TAKEN[] = "taken";
 	const char PEER_PROFILE_USAGE_REJECTED[] = "rejected";
 	const char PEER_PROFILE_USAGE_CONNECTED[] = "connected";
-	const char PEER_PROFILE_USAGE_DUPLICATED[] = "duplicated";
 	
-	const int PEER_PROFILE_EXPIRATION_TIMEOUT = 36*60*60; // in seconds (1.5 days)
-	const int PEER_PROFILE_AUTOCLEAN_TIMEOUT = 1500; // in seconds (25 minutes)
-	const int PEER_PROFILE_AUTOCLEAN_VARIANCE = 900; // in seconds (15 minutes)
-	const int PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_TIMEOUT = 5400; // in seconds (1.5 hours)
-	const int PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE = 2400; // in seconds (40 minutes)
-	const int PEER_PROFILE_DECLINED_RECENTLY_INTERVAL = 330; // in seconds (5.5 minutes)
-	const int PEER_PROFILE_MAX_DECLINED_INTERVAL = 4400; // in second (1.5 hours)
-	const int PEER_PROFILE_PERSIST_INTERVAL = 1320; // in seconds (22 minutes)
-	const int PEER_PROFILE_UNREACHABLE_INTERVAL = 480; // in seconds (8 minutes)
+	const int PEER_PROFILE_EXPIRATION_TIMEOUT = 36; // in hours (1.5 days)
+	const int PEER_PROFILE_AUTOCLEAN_TIMEOUT = 3 * 3600; // in seconds (3 hours)
+	const int PEER_PROFILE_AUTOCLEAN_VARIANCE = 3600; // in seconds (1 hour)
+	const int PEER_PROFILE_DECLINED_RECENTLY_INTERVAL = 150; // in seconds (2.5 minutes)
+	const int PEER_PROFILE_PERSIST_INTERVAL = 3300; // in seconds (55 minutes)
+	const int PEER_PROFILE_UNREACHABLE_INTERVAL = 2*3600; // on seconds (2 hours)
 	const int PEER_PROFILE_USEFUL_THRESHOLD = 3;
-	const int PEER_PROFILE_ALWAYS_DECLINING_NUM = 5; // num declines in row to consider always declined
-	const int PEER_PROFILE_APPLY_POSTPONED_TIMEOUT = 2100; // in milliseconds	
-	const int PEER_PROFILE_APPLY_POSTPONED_TIMEOUT_VARIANCE = 500; // in milliseconds	
-	
+
 	class RouterProfile
 	{
 		public:
@@ -66,23 +56,11 @@ namespace data
 
 			void Unreachable (bool unreachable);
 			void Connected ();
-			void Duplicated ();
 
-			uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; };
+			boost::posix_time::ptime GetLastUpdateTime () const { return m_LastUpdateTime; };
 			bool IsUpdated () const { return m_IsUpdated; };
-			void SetUpdated (bool updated) { m_IsUpdated = updated; }
-			uint64_t GetLastAccessTime () const { return m_LastAccessTime; };
-			void SetLastAccessTime (uint64_t ts) { m_LastAccessTime = ts; };
-			uint64_t GetLastPersistTime () const { return m_LastPersistTime; };
-			void SetLastPersistTime (uint64_t ts) { m_LastPersistTime = ts; };
 			
 			bool IsUseful() const;
-			bool IsDuplicated () const { return m_IsDuplicated; };
-
-			const boost::asio::ip::udp::endpoint& GetLastEndpoint () const { return m_LastEndpoint; }
-			void SetLastEndpoint (const boost::asio::ip::udp::endpoint& ep) { m_LastEndpoint = ep; }
-			bool HasLastEndpoint (bool v4) const { return !m_LastEndpoint.address ().is_unspecified () && m_LastEndpoint.port () && 
-				((v4 && m_LastEndpoint.address ().is_v4 ()) || (!v4 && m_LastEndpoint.address ().is_v6 ())); }
 			
 		private:
 
@@ -91,13 +69,13 @@ namespace data
 			bool IsAlwaysDeclining () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; };
 			bool IsLowPartcipationRate () const;
 			bool IsLowReplyRate () const;
-			bool IsDeclinedRecently (uint64_t ts);
+			bool IsDeclinedRecently ();
 
 		private:
 
+			boost::posix_time::ptime m_LastUpdateTime; // TODO: use std::chrono
 			bool m_IsUpdated;
-			uint64_t m_LastDeclineTime, m_LastUnreachableTime, m_LastUpdateTime, 
-				m_LastAccessTime, m_LastPersistTime; // in seconds
+			uint64_t m_LastDeclineTime, m_LastUnreachableTime; // in seconds
 			// participation
 			uint32_t m_NumTunnelsAgreed;
 			uint32_t m_NumTunnelsDeclined;
@@ -106,20 +84,13 @@ namespace data
 			uint32_t m_NumTimesTaken;
 			uint32_t m_NumTimesRejected;
 			bool m_HasConnected; // successful trusted(incoming or NTCP2) connection 
-			bool m_IsDuplicated;
-			// connectivity
-			boost::asio::ip::udp::endpoint m_LastEndpoint; // SSU2 for non-published addresses
 	};
 
 	std::shared_ptr<RouterProfile> GetRouterProfile (const IdentHash& identHash);
-	bool IsRouterBanned (const IdentHash& identHash); // check only existing profiles
-	bool IsRouterDuplicated (const IdentHash& identHash); // check only existing profiles
 	void InitProfilesStorage ();
-	std::future<void> DeleteObsoleteProfiles ();
+	void DeleteObsoleteProfiles ();
 	void SaveProfiles ();
-	std::future<void> PersistProfiles ();
-	bool UpdateRouterProfile (const IdentHash& identHash, std::function<void (std::shared_ptr<RouterProfile>)> update); // return true if updated immediately, and false if postponed
-	std::future<void> FlushPostponedRouterProfileUpdates ();
+	void PersistProfiles ();
 }
 }
 
diff --git a/libi2pd/Queue.h b/libi2pd/Queue.h
index 0e3e4fde..441f8c3a 100644
--- a/libi2pd/Queue.h
+++ b/libi2pd/Queue.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2020, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -9,7 +9,8 @@
 #ifndef QUEUE_H__
 #define QUEUE_H__
 
-#include <list>
+#include <queue>
+#include <vector>
 #include <mutex>
 #include <thread>
 #include <condition_variable>
@@ -28,20 +29,22 @@ namespace util
 			void Put (Element e)
 			{
 				std::unique_lock<std::mutex> l(m_QueueMutex);
-				m_Queue.push_back (std::move(e));
+				m_Queue.push (std::move(e));
 				m_NonEmpty.notify_one ();
 			}
 
-			void Put (std::list<Element>& list)
+			template<template<typename, typename...>class Container, typename... R>
+			void Put (const Container<Element, R...>& vec)
 			{
-				if (!list.empty ())
+				if (!vec.empty ())
 				{
 					std::unique_lock<std::mutex> l(m_QueueMutex);
-					m_Queue.splice (m_Queue.end (), list); 
+					for (const auto& it: vec)
+						m_Queue.push (std::move(it));
 					m_NonEmpty.notify_one ();
-				}	
-			}		
-		
+				}
+			}
+
 			Element GetNext ()
 			{
 				std::unique_lock<std::mutex> l(m_QueueMutex);
@@ -84,7 +87,7 @@ namespace util
 				return m_Queue.empty ();
 			}
 
-			int GetSize () const
+			int GetSize ()
 			{
 				std::unique_lock<std::mutex> l(m_QueueMutex);
 				return m_Queue.size ();
@@ -104,28 +107,15 @@ namespace util
 				return GetNonThreadSafe (true);
 			}
 
-			void GetWholeQueue (std::list<Element>& queue)
-			{
-				if (!queue.empty ())
-				{	
-					std::list<Element> newQueue;
-					queue.swap (newQueue);
-				}	
-				{
-					std::unique_lock<std::mutex> l(m_QueueMutex);
-					m_Queue.swap (queue);
-				}
-			}		
-
 		private:
-		
+
 			Element GetNonThreadSafe (bool peek = false)
 			{
 				if (!m_Queue.empty ())
 				{
 					auto el = m_Queue.front ();
 					if (!peek)
-						m_Queue.pop_front ();
+						m_Queue.pop ();
 					return el;
 				}
 				return nullptr;
@@ -133,8 +123,8 @@ namespace util
 
 		private:
 
-			std::list<Element> m_Queue;
-			mutable std::mutex m_QueueMutex;
+			std::queue<Element> m_Queue;
+			std::mutex m_QueueMutex;
 			std::condition_variable m_NonEmpty;
 	};
 }
diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp
index e58e898b..28e4db24 100644
--- a/libi2pd/Reseed.cpp
+++ b/libi2pd/Reseed.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -26,7 +26,6 @@
 #include "HTTP.h"
 #include "util.h"
 #include "Config.h"
-#include "Socks5.h"
 
 namespace i2p
 {
@@ -552,7 +551,7 @@ namespace data
 		if (!url.port)
 			url.port = 443;
 
-		boost::asio::io_context service;
+		boost::asio::io_service service;
 		boost::system::error_code ecode;
 
 		boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
@@ -562,10 +561,11 @@ namespace data
 		if(proxyUrl.schema.size())
 		{
 			// proxy connection
-			auto it = boost::asio::ip::tcp::resolver(service).resolve (proxyUrl.host, std::to_string(proxyUrl.port), ecode);
+			auto it = boost::asio::ip::tcp::resolver(service).resolve (
+				boost::asio::ip::tcp::resolver::query (proxyUrl.host, std::to_string(proxyUrl.port)), ecode);
 			if(!ecode)
 			{
-				s.lowest_layer().connect(*it.begin (), ecode);
+				s.lowest_layer().connect(*it, ecode);
 				if(!ecode)
 				{
 					auto & sock = s.next_layer();
@@ -598,7 +598,7 @@ namespace data
 							LogPrint(eLogError, "Reseed: HTTP CONNECT read error: ", ecode.message());
 							return "";
 						}
-						if(proxyRes.parse(std::string {boost::asio::buffers_begin(readbuf.data ()), boost::asio::buffers_begin(readbuf.data ()) + readbuf.size ()}) <= 0)
+						if(proxyRes.parse(boost::asio::buffer_cast<const char *>(readbuf.data()), readbuf.size()) <= 0)
 						{
 							sock.close();
 							LogPrint(eLogError, "Reseed: HTTP CONNECT malformed reply");
@@ -615,21 +615,62 @@ namespace data
 					{
 						// assume socks if not http, is checked before this for other types
 						// TODO: support username/password auth etc
-						bool success = false;
-						i2p::transport::Socks5Handshake (sock, std::make_pair(url.host, url.port),
-							[&success](const boost::system::error_code& ec) 
-						    { 
-								if (!ec)
-									success = true;
-								else
-									LogPrint (eLogError, "Reseed: SOCKS handshake failed: ", ec.message());
-							});	
-						service.run (); // execute all async operations
-						if (!success)
+						uint8_t hs_writebuf[3] = {0x05, 0x01, 0x00};
+						uint8_t hs_readbuf[2];
+						boost::asio::write(sock, boost::asio::buffer(hs_writebuf, 3), boost::asio::transfer_all(), ecode);
+						if(ecode)
 						{
 							sock.close();
+							LogPrint(eLogError, "Reseed: SOCKS handshake write failed: ", ecode.message());
 							return "";
-						}	
+						}
+						boost::asio::read(sock, boost::asio::buffer(hs_readbuf, 2), ecode);
+						if(ecode)
+						{
+							sock.close();
+							LogPrint(eLogError, "Reseed: SOCKS handshake read failed: ", ecode.message());
+							return "";
+						}
+						size_t sz = 0;
+						uint8_t buf[256];
+
+						buf[0] = 0x05;
+						buf[1] = 0x01;
+						buf[2] = 0x00;
+						buf[3] = 0x03;
+						sz += 4;
+						size_t hostsz = url.host.size();
+						if(1 + 2 + hostsz + sz > sizeof(buf))
+						{
+							sock.close();
+							LogPrint(eLogError, "Reseed: SOCKS handshake failed, hostname too big: ", url.host);
+							return "";
+						}
+						buf[4] = (uint8_t) hostsz;
+						memcpy(buf+5, url.host.c_str(), hostsz);
+						sz += hostsz + 1;
+						htobe16buf(buf+sz, url.port);
+						sz += 2;
+						boost::asio::write(sock, boost::asio::buffer(buf, sz), boost::asio::transfer_all(), ecode);
+						if(ecode)
+						{
+							sock.close();
+							LogPrint(eLogError, "Reseed: SOCKS handshake failed writing: ", ecode.message());
+							return "";
+						}
+						boost::asio::read(sock, boost::asio::buffer(buf, 10), ecode);
+						if(ecode)
+						{
+							sock.close();
+							LogPrint(eLogError, "Reseed: SOCKS handshake failed reading: ", ecode.message());
+							return "";
+						}
+						if(buf[1] != 0x00)
+						{
+							sock.close();
+							LogPrint(eLogError, "Reseed: SOCKS handshake bad reply code: ", std::to_string(buf[1]));
+							return "";
+						}
 					}
 				}
 			}
@@ -637,23 +678,27 @@ namespace data
 		else
 		{
 			// direct connection
-			auto endpoints = boost::asio::ip::tcp::resolver(service).resolve (url.host, std::to_string(url.port), ecode);
+			auto it = boost::asio::ip::tcp::resolver(service).resolve (
+				boost::asio::ip::tcp::resolver::query (url.host, std::to_string(url.port)), ecode);
 			if (!ecode)
 			{
 				bool connected = false;
-				for (const auto& it: endpoints)
+				boost::asio::ip::tcp::resolver::iterator end;
+				while (it != end)
 				{
-					boost::asio::ip::tcp::endpoint ep = it;
-					bool supported = false;
-					if (!ep.address ().is_unspecified ())
-					{
-						if (ep.address ().is_v4 ())
-							supported = i2p::context.SupportsV4 ();
-						else if (ep.address ().is_v6 ())
-							supported = i2p::util::net::IsYggdrasilAddress (ep.address ()) ? 
-								i2p::context.SupportsMesh () : i2p::context.SupportsV6 ();
-					}	
-					if (supported)
+					boost::asio::ip::tcp::endpoint ep = *it;
+					if (
+						(
+							!i2p::util::net::IsInReservedRange(ep.address ()) && (
+								(ep.address ().is_v4 () && i2p::context.SupportsV4 ()) ||
+								(ep.address ().is_v6 () && i2p::context.SupportsV6 ())
+							)
+						) ||
+						(
+							i2p::util::net::IsYggdrasilAddress (ep.address ()) &&
+							i2p::context.SupportsMesh ()
+						)
+					)
 					{
 						s.lowest_layer().connect (ep, ecode);
 						if (!ecode)
@@ -663,6 +708,7 @@ namespace data
 							break;
 						}
 					}
+					it++;
 				}
 				if (!connected)
 				{
@@ -742,16 +788,19 @@ namespace data
 		if (!url.port) url.port = 80;
 
 		boost::system::error_code ecode;
-		boost::asio::io_context service;
+		boost::asio::io_service service;
 		boost::asio::ip::tcp::socket s(service, boost::asio::ip::tcp::v6());
 
-		auto endpoints = boost::asio::ip::tcp::resolver(service).resolve (url.host, std::to_string(url.port), ecode);
+		auto it = boost::asio::ip::tcp::resolver(service).resolve (
+			boost::asio::ip::tcp::resolver::query (url.host, std::to_string(url.port)), ecode);
+
 		if (!ecode)
 		{
 			bool connected = false;
-			for (const auto& it: endpoints)
+			boost::asio::ip::tcp::resolver::iterator end;
+			while (it != end)
 			{
-				boost::asio::ip::tcp::endpoint ep = it;
+				boost::asio::ip::tcp::endpoint ep = *it;
 				if (
 					i2p::util::net::IsYggdrasilAddress (ep.address ()) &&
 					i2p::context.SupportsMesh ()
@@ -765,6 +814,7 @@ namespace data
 						break;
 					}
 				}
+				it++;
 			}
 			if (!connected)
 			{
diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp
index 33fb5487..f21c0592 100644
--- a/libi2pd/RouterContext.cpp
+++ b/libi2pd/RouterContext.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -22,7 +22,6 @@
 #include "ECIESX25519AEADRatchetSession.h"
 #include "Transports.h"
 #include "Tunnel.h"
-#include "CryptoKey.h"
 #include "RouterContext.h"
 
 namespace i2p
@@ -34,15 +33,14 @@ namespace i2p
 		m_ShareRatio (100), m_Status (eRouterStatusUnknown), m_StatusV6 (eRouterStatusUnknown),
 		m_Error (eRouterErrorNone), m_ErrorV6 (eRouterErrorNone),
 		m_Testing (false), m_TestingV6 (false), m_NetID (I2PD_NET_ID),
-		m_PublishReplyToken (0), m_IsHiddenMode (false), 
-		m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL), m_IsSaving (false)
+		m_PublishReplyToken (0), m_IsHiddenMode (false)
 	{
 	}
 
 	void RouterContext::Init ()
 	{
-		srand (m_Rng () % 1000);
-		m_StartupTime = i2p::util::GetMonotonicSeconds ();
+		srand (i2p::util::GetMillisecondsSinceEpoch () % 1000);
+		m_StartupTime = std::chrono::steady_clock::now();
 
 		if (!Load ())
 			CreateNewRouter ();
@@ -59,12 +57,13 @@ namespace i2p
 		{	
 			m_Service.reset (new RouterService);
 			m_Service->Start ();
-			m_PublishTimer.reset (new boost::asio::deadline_timer (m_Service->GetService ()));
-			ScheduleInitialPublish ();
-			m_CongestionUpdateTimer.reset (new boost::asio::deadline_timer (m_Service->GetService ()));
-			ScheduleCongestionUpdate ();
-			m_CleanupTimer.reset (new boost::asio::deadline_timer (m_Service->GetService ()));
-			ScheduleCleanupTimer ();
+			if (!m_IsHiddenMode)
+			{
+				m_PublishTimer.reset (new boost::asio::deadline_timer (m_Service->GetService ()));
+				ScheduleInitialPublish ();
+				m_CongestionUpdateTimer.reset (new boost::asio::deadline_timer (m_Service->GetService ()));
+				ScheduleCongestionUpdate ();
+			}	
 		}	
 	}
 	
@@ -77,16 +76,9 @@ namespace i2p
 			if (m_CongestionUpdateTimer)
 				m_CongestionUpdateTimer->cancel ();
 			m_Service->Stop ();
-			CleanUp (); // GarlicDestination
-		}
+		}	
 	}	
-
-	std::shared_ptr<i2p::data::RouterInfo::Buffer> RouterContext::CopyRouterInfoBuffer () const
-	{
-		std::lock_guard<std::mutex> l(m_RouterInfoMutex);
-		return m_RouterInfo.CopyBuffer ();
-	}	
-		
+	
 	void RouterContext::CreateNewRouter ()
 	{
 		m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519,
@@ -142,7 +134,7 @@ namespace i2p
 				{
 					boost::asio::ip::address addr;
 					if (!host.empty ())
-						addr = boost::asio::ip::make_address (host);
+						addr = boost::asio::ip::address::from_string (host);
 					if (!addr.is_v4())
 						addr = boost::asio::ip::address_v4 ();
 					routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port);
@@ -163,7 +155,7 @@ namespace i2p
 				{
 					boost::asio::ip::address addr;
 					if (!host.empty ())
-						addr = boost::asio::ip::make_address (host);
+						addr = boost::asio::ip::address::from_string (host);
 					if (!addr.is_v4())
 						addr = boost::asio::ip::address_v4 ();
 					routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port);
@@ -194,7 +186,7 @@ namespace i2p
 						ntcp2Host = host;
 					boost::asio::ip::address addr;
 					if (!ntcp2Host.empty ())
-						addr = boost::asio::ip::make_address (ntcp2Host);
+						addr = boost::asio::ip::address::from_string (ntcp2Host);
 					if (!addr.is_v6())
 						addr = boost::asio::ip::address_v6 ();
 					routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port);
@@ -213,7 +205,7 @@ namespace i2p
 				{
 					boost::asio::ip::address addr;
 					if (!host.empty ())
-						addr = boost::asio::ip::make_address (host);
+						addr = boost::asio::ip::address::from_string (host);
 					if (!addr.is_v6())
 						addr = boost::asio::ip::address_v6 ();
 					routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port);
@@ -255,36 +247,8 @@ namespace i2p
 
 	void RouterContext::UpdateRouterInfo ()
 	{
-		std::shared_ptr<i2p::data::RouterInfo::Buffer> buffer;
-		{
-			std::lock_guard<std::mutex> l(m_RouterInfoMutex);
-			m_RouterInfo.CreateBuffer (m_Keys);
-			buffer = m_RouterInfo.CopyBuffer ();
-		}
-		{
-			// update save buffer to latest
-			std::lock_guard<std::mutex> l(m_SaveBufferMutex);
-			m_SaveBuffer = buffer;
-		}
-		bool isSaving = false;
-		if (m_IsSaving.compare_exchange_strong (isSaving, true)) // try to save only if not being saved
-		{	
-			auto savingRouterInfo = std::async (std::launch::async, [this]() 
-				{
-					std::shared_ptr<i2p::data::RouterInfo::Buffer> buffer;
-					while (m_SaveBuffer)
-					{
-						{
-							std::lock_guard<std::mutex> l(m_SaveBufferMutex);
-							buffer = m_SaveBuffer;
-							m_SaveBuffer = nullptr;
-						}
-						if (buffer)
-							i2p::data::RouterInfo::SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO), buffer);
-					}	
-					m_IsSaving = false;
-				});
-		}	
+		m_RouterInfo.CreateBuffer (m_Keys);
+		m_RouterInfo.SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO));
 		m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch ();
 	}
 
@@ -339,8 +303,6 @@ namespace i2p
 		SetTesting (false);
 		if (status != m_Status)
 		{
-			LogPrint(eLogInfo, "Router: network status v4 changed ",
-				ROUTER_STATUS_NAMES[m_Status], " -> ", ROUTER_STATUS_NAMES[status]);
 			m_Status = status;
 			switch (m_Status)
 			{
@@ -350,12 +312,6 @@ namespace i2p
 				case eRouterStatusFirewalled:
 					SetUnreachable (true, false); // ipv4
 				break;
-				case eRouterStatusMesh:
-					m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eReachable);
-				break;	
-				case eRouterStatusProxy:
-					m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eUnreachable);
-				break;	
 				default:
 					;
 			}
@@ -367,8 +323,6 @@ namespace i2p
 		SetTestingV6 (false);
 		if (status != m_StatusV6)
 		{
-			LogPrint(eLogInfo, "Router: network status v6 changed ",
-				ROUTER_STATUS_NAMES[m_StatusV6], " -> ", ROUTER_STATUS_NAMES[status]);
 			m_StatusV6 = status;
 			switch (m_StatusV6)
 			{
@@ -586,12 +540,6 @@ namespace i2p
 			UpdateRouterInfo ();
 	}
 
-	void RouterContext::UpdateSSU2Introducer (const i2p::data::IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp)
-	{
-		if (m_RouterInfo.UpdateSSU2Introducer (h, v4, iTag, iExp))
-			UpdateRouterInfo ();
-	}	
-		
 	void RouterContext::ClearSSU2Introducers (bool v4)
 	{
 		auto addr = m_RouterInfo.GetSSU2Address (v4);
@@ -606,10 +554,10 @@ namespace i2p
 	{
 		m_IsFloodfill = floodfill;
 		if (floodfill)
-			m_RouterInfo.UpdateFloodfillProperty (true);
+			m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eFloodfill);
 		else
 		{
-			m_RouterInfo.UpdateFloodfillProperty (false);
+			m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eFloodfill);
 			// we don't publish number of routers and leaseset for non-floodfill
 			m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_LEASESETS);
 			m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_ROUTERS);
@@ -648,9 +596,9 @@ namespace i2p
 		{
 			case i2p::data::CAPS_FLAG_LOW_BANDWIDTH1   : limit = 12; type = low;   break;
 			case i2p::data::CAPS_FLAG_LOW_BANDWIDTH2   : limit = i2p::data::LOW_BANDWIDTH_LIMIT; type = low;   break; // 48
-			case i2p::data::CAPS_FLAG_LOW_BANDWIDTH3  : limit = 64; type = low;  break;
-			case i2p::data::CAPS_FLAG_LOW_BANDWIDTH4  : limit = 128; type = low;  break;
-			case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH  : limit = i2p::data::HIGH_BANDWIDTH_LIMIT; type = high;  break; // 256
+			case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH1  : limit = 64; type = high;  break;
+			case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH2  : limit = 128; type = high;  break;
+			case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH3  : limit = i2p::data::HIGH_BANDWIDTH_LIMIT; type = high;  break; // 256
 			case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1 : limit = i2p::data::EXTRA_BANDWIDTH_LIMIT; type = extra; break; // 2048
 			case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH2 : limit = 1000000; type = unlim; break; // 1Gbyte/s
 			default:
@@ -665,7 +613,9 @@ namespace i2p
 			case low   : /* not set */; break;
 			case extra : caps |= i2p::data::RouterInfo::eExtraBandwidth; break; // 'P'
 			case unlim : caps |= i2p::data::RouterInfo::eExtraBandwidth;
+#if (__cplusplus >= 201703L) // C++ 17 or higher
 			[[fallthrough]];
+#endif
 			// no break here, extra + high means 'X'
 			case high : caps |= i2p::data::RouterInfo::eHighBandwidth; break;
 		}
@@ -676,12 +626,12 @@ namespace i2p
 
 	void RouterContext::SetBandwidth (int limit)
 	{
-		if      (limit > (int)i2p::data::EXTRA_BANDWIDTH_LIMIT) { SetBandwidth('X'); }
-		else if (limit > (int)i2p::data::HIGH_BANDWIDTH_LIMIT) { SetBandwidth('P'); }
-		else if (limit > 128) { SetBandwidth('O'); }
-		else if (limit > 64) { SetBandwidth('N'); }
-		else if (limit > (int)i2p::data::LOW_BANDWIDTH_LIMIT) { SetBandwidth('M'); }
-		else if (limit > 12) { SetBandwidth('L'); }
+		if      (limit > 2000) { SetBandwidth('X'); }
+		else if (limit >  256) { SetBandwidth('P'); }
+		else if (limit >  128) { SetBandwidth('O'); }
+		else if (limit >   64) { SetBandwidth('N'); }
+		else if (limit >   48) { SetBandwidth('M'); }
+		else if (limit >   12) { SetBandwidth('L'); }
 		else                   { SetBandwidth('K'); }
 		m_BandwidthLimit = limit; // set precise limit
 	}
@@ -829,7 +779,7 @@ namespace i2p
 							i2p::config::GetOption("host", ntcp2Host);
 						if (!ntcp2Host.empty () && ntcp2Port)
 						{
-							auto addr = boost::asio::ip::make_address (ntcp2Host);
+							auto addr = boost::asio::ip::address::from_string (ntcp2Host);
 							if (addr.is_v6 ())
 							{
 								m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port);
@@ -858,7 +808,7 @@ namespace i2p
 						std::string host; i2p::config::GetOption("host", host);
 						if (!host.empty ())
 						{
-						    auto addr = boost::asio::ip::make_address (host);
+						    auto addr = boost::asio::ip::address::from_string (host);
 							if (addr.is_v6 ())
 							{
 								m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port);
@@ -927,7 +877,7 @@ namespace i2p
 						std::string host; i2p::config::GetOption("host", host);
 						if (!host.empty ())
 						{
-						    auto addr = boost::asio::ip::make_address (host);
+						    auto addr = boost::asio::ip::address::from_string (host);
 							if (addr.is_v4 ())
 							{
 								m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port);
@@ -957,7 +907,7 @@ namespace i2p
 						std::string host; i2p::config::GetOption("host", host);
 						if (!host.empty ())
 						{
-						    auto addr = boost::asio::ip::make_address (host);
+						    auto addr = boost::asio::ip::address::from_string (host);
 							if (addr.is_v4 ())
 							{
 								m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port);
@@ -1181,29 +1131,20 @@ namespace i2p
 		return i2p::tunnel::tunnels.GetExploratoryPool ();
 	}
 
-	int RouterContext::GetCongestionLevel (bool longTerm) const
+	bool RouterContext::IsHighCongestion () const
 	{
-		return std::max (
-			i2p::tunnel::tunnels.GetCongestionLevel (),
-			i2p::transport::transports.GetCongestionLevel (longTerm)
-		);
-	}
-	
+		return i2p::tunnel::tunnels.IsTooManyTransitTunnels () ||
+			i2p::transport::transports.IsBandwidthExceeded () ||
+			i2p::transport::transports.IsTransitBandwidthExceeded ();
+	}	
+		
 	void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len)
 	{
 		i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len)));
 	}
 
-	bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, 
-		size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from)
+	bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID)
 	{
-		if (typeID == eI2NPTunnelTest)
-		{
-			// try tunnel test
-			auto pool = GetTunnelPool ();
-			if (pool && pool->ProcessTunnelTest (bufbe32toh (payload + TUNNEL_TEST_MSGID_OFFSET), bufbe64toh (payload + TUNNEL_TEST_TIMESTAMP_OFFSET)))
-				return true;
-		}
 		auto msg = CreateI2NPMessage (typeID, payload, len, msgID);
 		if (!msg) return false;
 		i2p::HandleI2NPMessage (msg);
@@ -1213,7 +1154,7 @@ namespace i2p
 	void RouterContext::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
 	{
 		if (m_Service)
-			boost::asio::post (m_Service->GetService (), std::bind (&RouterContext::PostGarlicMessage, this, msg));
+			m_Service->GetService ().post (std::bind (&RouterContext::PostGarlicMessage, this, msg));
 		else
 			LogPrint (eLogError, "Router: service is NULL");
 	}
@@ -1241,7 +1182,7 @@ namespace i2p
 	void RouterContext::ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
 	{
 		if (m_Service)
-			boost::asio::post (m_Service->GetService (), std::bind (&RouterContext::PostDeliveryStatusMessage, this, msg));
+			m_Service->GetService ().post (std::bind (&RouterContext::PostDeliveryStatusMessage, this, msg));
 		else
 			LogPrint (eLogError, "Router: service is NULL");
 	}
@@ -1258,30 +1199,21 @@ namespace i2p
 		else	              
 			i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg);
 	}
-
-	void RouterContext::SubmitECIESx25519Key (const uint8_t * key, uint64_t tag)
+	
+	void RouterContext::CleanupDestination ()
 	{
 		if (m_Service)
-		{
-			struct
-			{
-				uint8_t k[32];
-				uint64_t t;
-			} data;
-			memcpy (data.k, key, 32);
-			data.t = tag;
-			boost::asio::post (m_Service->GetService (), [this,data](void)
-				{
-					AddECIESx25519Key (data.k, data.t);
-				});
-		}	
+			m_Service->GetService ().post ([this]()
+			{  
+				this->i2p::garlic::GarlicDestination::CleanupExpiredTags ();
+			});	
 		else
 			LogPrint (eLogError, "Router: service is NULL");
-	}	
+	}
 
 	uint32_t RouterContext::GetUptime () const
 	{
-		return i2p::util::GetMonotonicSeconds () - m_StartupTime;
+		return std::chrono::duration_cast<std::chrono::seconds> (std::chrono::steady_clock::now() - m_StartupTime).count ();
 	}
 
 	bool RouterContext::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const
@@ -1369,15 +1301,9 @@ namespace i2p
 		if (ecode != boost::asio::error::operation_aborted)
 		{	
 			if (m_RouterInfo.IsReachableBy (i2p::data::RouterInfo::eAllTransports))
-			{
-				UpdateCongestion ();
 				HandlePublishTimer (ecode);
-			}	
 			else
-			{	
-				UpdateTimestamp (i2p::util::GetSecondsSinceEpoch ());	
 				ScheduleInitialPublish ();
-			}		
 		}	
 	}	
 	
@@ -1387,7 +1313,7 @@ namespace i2p
 		{	
 			m_PublishTimer->cancel ();
 			m_PublishTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_PUBLISH_INTERVAL + 
-				m_Rng () % ROUTER_INFO_PUBLISH_INTERVAL_VARIANCE));
+				rand () % ROUTER_INFO_PUBLISH_INTERVAL_VARIANCE));
 			m_PublishTimer->async_wait (std::bind (&RouterContext::HandlePublishTimer,
 				this, std::placeholders::_1));
 		}	
@@ -1399,21 +1325,16 @@ namespace i2p
 	{
 		if (ecode != boost::asio::error::operation_aborted)
 		{
-			UpdateTimestamp (i2p::util::GetSecondsSinceEpoch ());
-			if (!m_IsHiddenMode)
+			m_PublishExcluded.clear ();
+			m_PublishReplyToken = 0;
+			if (IsFloodfill ())
 			{	
-				m_PublishExcluded.clear ();
-				m_PublishReplyToken = 0;
-				if (IsFloodfill ())
-				{	
-					UpdateStats (); // for floodfill
-					m_PublishExcluded.insert (i2p::context.GetIdentHash ()); // don't publish to ourselves
-				}		
-				Publish ();	
-				SchedulePublishResend ();
+				UpdateStats (); // for floodfill
+				m_PublishExcluded.insert (i2p::context.GetIdentHash ()); // don't publish to ourselves
 			}	
-			else
-				SchedulePublish ();
+			UpdateTimestamp (i2p::util::GetSecondsSinceEpoch ());
+			Publish ();	
+			SchedulePublishResend ();
 		}	
 	}	
 	
@@ -1433,20 +1354,10 @@ namespace i2p
 			uint32_t replyToken;
 			RAND_bytes ((uint8_t *)&replyToken, 4);
 			LogPrint (eLogInfo, "Router: Publishing our RouterInfo to ", i2p::data::GetIdentHashAbbreviation(floodfill->GetIdentHash ()), ". reply token=", replyToken);
-			auto onDrop = [this]()
-				{
-					if (m_Service)
-						boost::asio::post (m_Service->GetService (), [this]() { HandlePublishResendTimer (boost::system::error_code ()); });
-				};
-			if (i2p::transport::transports.IsConnected (floodfill->GetIdentHash ()) || // already connected
-				(floodfill->IsReachableFrom (i2p::context.GetRouterInfo ()) && // are we able to connect
-				 !i2p::transport::transports.RoutesRestricted ())) // and routes not restricted
-			{	
+			if (floodfill->IsReachableFrom (i2p::context.GetRouterInfo ()) || // are we able to connect?
+				i2p::transport::transports.IsConnected (floodfill->GetIdentHash ())) // already connected ?
 				// send directly
-				auto msg = CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken);
-				msg->onDrop = onDrop;
-				i2p::transport::transports.SendMessage (floodfill->GetIdentHash (), msg);
-			}	
+				i2p::transport::transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken));
 			else
 			{
 				// otherwise through exploratory
@@ -1457,12 +1368,11 @@ namespace i2p
 				{		
 					// encrypt for floodfill
 					auto msg = CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken, inbound);
-					msg->onDrop = onDrop;
 					outbound->SendTunnelDataMsgTo (floodfill->GetIdentHash (), 0, 
 						i2p::garlic::WrapECIESX25519MessageForRouter (msg, floodfill->GetIdentity ()->GetEncryptionPublicKey ()));
 				}	
 				else
-					LogPrint (eLogInfo, "Router: Can't publish our RouterInfo. No tunnels. Try again in ", ROUTER_INFO_CONFIRMATION_TIMEOUT, " milliseconds");
+					LogPrint (eLogInfo, "Router: Can't publish our RouterInfo. No tunnles. Try again in ", ROUTER_INFO_CONFIRMATION_TIMEOUT, " seconds");
 			}
 			m_PublishExcluded.insert (floodfill->GetIdentHash ());
 			m_PublishReplyToken = replyToken;
@@ -1476,7 +1386,7 @@ namespace i2p
 		if (m_PublishTimer)
 		{
 			m_PublishTimer->cancel ();
-			m_PublishTimer->expires_from_now (boost::posix_time::milliseconds(ROUTER_INFO_CONFIRMATION_TIMEOUT));
+			m_PublishTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_CONFIRMATION_TIMEOUT));
 			m_PublishTimer->async_wait (std::bind (&RouterContext::HandlePublishResendTimer,
 				this, std::placeholders::_1));
 		}	
@@ -1499,8 +1409,7 @@ namespace i2p
 		if (m_CongestionUpdateTimer)
 		{	
 			m_CongestionUpdateTimer->cancel ();
-			m_CongestionUpdateTimer->expires_from_now (boost::posix_time::seconds(
-				ROUTER_INFO_CONGESTION_UPDATE_INTERVAL + m_Rng () % ROUTER_INFO_CONGESTION_UPDATE_INTERVAL_VARIANCE));
+			m_CongestionUpdateTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_CONGESTION_UPDATE_INTERVAL));
 			m_CongestionUpdateTimer->async_wait (std::bind (&RouterContext::HandleCongestionUpdateTimer,
 				this, std::placeholders::_1));
 		}	
@@ -1512,47 +1421,14 @@ namespace i2p
 	{
 		if (ecode != boost::asio::error::operation_aborted)
 		{
-			UpdateCongestion ();
+			auto c = i2p::data::RouterInfo::eLowCongestion;
+			if (!AcceptsTunnels ()) 
+				c = i2p::data::RouterInfo::eRejectAll;
+			else if (IsHighCongestion ()) 
+				c = i2p::data::RouterInfo::eHighCongestion;
+			if (m_RouterInfo.UpdateCongestion (c))
+				UpdateRouterInfo ();
 			ScheduleCongestionUpdate ();
 		}	
 	}	
-
-	void RouterContext::UpdateCongestion ()
-	{
-		auto c = i2p::data::RouterInfo::eLowCongestion;
-		if (!AcceptsTunnels () || !m_ShareRatio)                                        	
-			c = i2p::data::RouterInfo::eRejectAll;
-		else
-		{
-			int congestionLevel = GetCongestionLevel (true);
-			if (congestionLevel > CONGESTION_LEVEL_HIGH)
-				c = i2p::data::RouterInfo::eHighCongestion;
-			else if (congestionLevel > CONGESTION_LEVEL_MEDIUM)
-				c = i2p::data::RouterInfo::eMediumCongestion;
-		}
-		if (m_RouterInfo.UpdateCongestion (c))
-			UpdateRouterInfo ();
-	}	
-		
-	void RouterContext::ScheduleCleanupTimer ()
-	{
-		if (m_CleanupTimer)
-		{	
-			m_CleanupTimer->cancel ();
-			m_CleanupTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_CLEANUP_INTERVAL));
-			m_CleanupTimer->async_wait (std::bind (&RouterContext::HandleCleanupTimer,
-				this, std::placeholders::_1));
-		}	
-		else
-			LogPrint (eLogError, "Router: Cleanup timer is NULL");
-	}	
-
-	void RouterContext::HandleCleanupTimer (const boost::system::error_code& ecode)
-	{
-		if (ecode != boost::asio::error::operation_aborted)
-		{
-			CleanupExpiredTags ();
-			ScheduleCleanupTimer ();
-		}	
-	}	
 }
diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h
index 754c49da..d49b5523 100644
--- a/libi2pd/RouterContext.h
+++ b/libi2pd/RouterContext.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -12,8 +12,8 @@
 #include <inttypes.h>
 #include <string>
 #include <memory>
-#include <random>
-#include <unordered_set>
+#include <chrono>
+#include <set>
 #include <boost/asio.hpp>
 #include "Identity.h"
 #include "RouterInfo.h"
@@ -35,11 +35,9 @@ namespace garlic
 	const int ROUTER_INFO_PUBLISH_INTERVAL = 39*60; // in seconds
 	const int ROUTER_INFO_INITIAL_PUBLISH_INTERVAL = 10; // in seconds
 	const int ROUTER_INFO_PUBLISH_INTERVAL_VARIANCE = 105;// in seconds
-	const int ROUTER_INFO_CONFIRMATION_TIMEOUT = 1600; // in milliseconds
+	const int ROUTER_INFO_CONFIRMATION_TIMEOUT = 5; // in seconds
 	const int ROUTER_INFO_MAX_PUBLISH_EXCLUDED_FLOODFILLS = 15;
-	const int ROUTER_INFO_CONGESTION_UPDATE_INTERVAL = 11*60; // in seconds
-	const int ROUTER_INFO_CONGESTION_UPDATE_INTERVAL_VARIANCE = 130; // in seconds 
-	const int ROUTER_INFO_CLEANUP_INTERVAL = 102; // in seconds
+	const int ROUTER_INFO_CONGESTION_UPDATE_INTERVAL = 12*60; // in seconds
 
 	enum RouterStatus
 	{
@@ -50,15 +48,6 @@ namespace garlic
 		eRouterStatusMesh = 4
 	};
 
-	const char* const ROUTER_STATUS_NAMES[] =
-	{
-		"OK", // 0
-		"Firewalled", // 1
-		"Unknown", // 2
-		"Proxy", // 3
-		"Mesh" // 4
-	};
-
 	enum RouterError
 	{
 		eRouterErrorNone = 0,
@@ -92,7 +81,7 @@ namespace garlic
 				public:
 
 					RouterService (): RunnableServiceWithWork ("Router") {};
-					auto& GetService () { return GetIOService (); };
+					boost::asio::io_service& GetService () { return GetIOService (); };
 					void Start () { StartIOService (); };
 					void Stop () { StopIOService (); };
 			};
@@ -116,8 +105,7 @@ namespace garlic
 				return std::shared_ptr<i2p::garlic::GarlicDestination> (this,
 					[](i2p::garlic::GarlicDestination *) {});
 			}
-			std::shared_ptr<i2p::data::RouterInfo::Buffer> CopyRouterInfoBuffer () const;
-			
+
 			const uint8_t * GetNTCP2StaticPublicKey () const { return m_NTCP2Keys ? m_NTCP2Keys->staticPublicKey : nullptr; };
 			const uint8_t * GetNTCP2StaticPrivateKey () const { return m_NTCP2Keys ? m_NTCP2Keys->staticPrivateKey : nullptr; };
 			const uint8_t * GetNTCP2IV () const { return m_NTCP2Keys ? m_NTCP2Keys->iv : nullptr; };
@@ -155,7 +143,6 @@ namespace garlic
 			void PublishSSU2Address (int port, bool publish, bool v4, bool v6);
 			bool AddSSU2Introducer (const i2p::data::RouterInfo::Introducer& introducer, bool v4);
 			void RemoveSSU2Introducer (const i2p::data::IdentHash& h, bool v4);
-			void UpdateSSU2Introducer (const i2p::data::IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp);
 			void ClearSSU2Introducers (bool v4);
 			bool IsUnreachable () const;
 			void SetUnreachable (bool v4, bool v6);
@@ -169,7 +156,7 @@ namespace garlic
 			void SetShareRatio (int percents); // 0 - 100
 			bool AcceptsTunnels () const { return m_AcceptsTunnels; };
 			void SetAcceptsTunnels (bool acceptsTunnels) { m_AcceptsTunnels = acceptsTunnels; };
-			int GetCongestionLevel (bool longTerm) const;
+			bool IsHighCongestion () const;
 			bool SupportsV6 () const { return m_RouterInfo.IsV6 (); };
 			bool SupportsV4 () const { return m_RouterInfo.IsV4 (); };
 			bool SupportsMesh () const { return m_RouterInfo.IsMesh (); };
@@ -179,33 +166,32 @@ namespace garlic
 			void SetMTU (int mtu, bool v4);
 			void SetHidden(bool hide) { m_IsHiddenMode = hide; };
 			bool IsHidden() const { return m_IsHiddenMode; };
-			bool IsLimitedConnectivity () const { return m_Status == eRouterStatusProxy; }; // TODO: implement other cases
 			i2p::crypto::NoiseSymmetricState& GetCurrentNoiseState () { return m_CurrentNoiseState; };
 
 			void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from Daemon. TODO: remove
 			void UpdateStats ();
 			void UpdateTimestamp (uint64_t ts); // in seconds, called from NetDb before publishing
+			void CleanupDestination (); // garlic destination
 
 			// implements LocalDestination
-			std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const override{ return m_Keys.GetPublic (); };
-			bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const override;
-			void SetLeaseSetUpdated (bool post) override {};
+			std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const { return m_Keys.GetPublic (); };
+			bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const;
+			void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); };
+			void SetLeaseSetUpdated () {};
 
 			// implements GarlicDestination
-			std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet () override { return nullptr; };
-			std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const override;
+			std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet () { return nullptr; };
+			std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const;
 
 			// override GarlicDestination
-			void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg) override;
-			void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg) override;
-			void SubmitECIESx25519Key (const uint8_t * key, uint64_t tag) override;
+			void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
+			void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
 
 		protected:
 
 			// implements GarlicDestination
-			void HandleI2NPMessage (const uint8_t * buf, size_t len) override;
-			bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload,
-				size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from) override;
+			void HandleI2NPMessage (const uint8_t * buf, size_t len);
+			bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID);
 
 		private:
 
@@ -218,7 +204,6 @@ namespace garlic
 			void UpdateSSU2Keys ();
 			bool Load ();
 			void SaveKeys ();
-			void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); };
 			uint16_t SelectRandomPort () const;
 			void PublishNTCP2Address (std::shared_ptr<i2p::data::RouterInfo::Address> address, int port, bool publish) const;
 
@@ -235,9 +220,6 @@ namespace garlic
 			void HandlePublishResendTimer (const boost::system::error_code& ecode);
 			void ScheduleCongestionUpdate ();
 			void HandleCongestionUpdateTimer (const boost::system::error_code& ecode);
-			void UpdateCongestion ();
-			void ScheduleCleanupTimer ();
-			void HandleCleanupTimer (const boost::system::error_code& ecode);
 			
 		private:
 
@@ -247,7 +229,7 @@ namespace garlic
 			std::shared_ptr<i2p::garlic::RouterIncomingRatchetSession> m_ECIESSession;
 			uint64_t m_LastUpdateTime; // in seconds
 			bool m_AcceptsTunnels, m_IsFloodfill;
-			uint64_t m_StartupTime; // monotonic seconds
+			std::chrono::time_point<std::chrono::steady_clock> m_StartupTime;
 			uint64_t m_BandwidthLimit; // allowed bandwidth
 			int m_ShareRatio;
 			RouterStatus m_Status, m_StatusV6;
@@ -261,15 +243,10 @@ namespace garlic
 			i2p::crypto::NoiseSymmetricState m_InitialNoiseState, m_CurrentNoiseState;
 			// publish
 			std::unique_ptr<RouterService> m_Service;
-			std::unique_ptr<boost::asio::deadline_timer> m_PublishTimer, m_CongestionUpdateTimer, m_CleanupTimer;
-			std::unordered_set<i2p::data::IdentHash> m_PublishExcluded;
+			std::unique_ptr<boost::asio::deadline_timer> m_PublishTimer, m_CongestionUpdateTimer;
+			std::set<i2p::data::IdentHash> m_PublishExcluded;
 			uint32_t m_PublishReplyToken;
 			bool m_IsHiddenMode; // not publish
-			mutable std::mutex m_RouterInfoMutex;
-			std::mt19937 m_Rng;
-			std::shared_ptr<i2p::data::RouterInfo::Buffer> m_SaveBuffer;
-			std::mutex m_SaveBufferMutex; // TODO: make m_SaveBuffer atomic
-			std::atomic<bool> m_IsSaving;
 	};
 
 	extern RouterContext context;
diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp
index 4af32f57..63cb79ef 100644
--- a/libi2pd/RouterInfo.cpp
+++ b/libi2pd/RouterInfo.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -10,10 +10,9 @@
 #include <string.h>
 #include "I2PEndian.h"
 #include <fstream>
-#include <memory>
-#include <charconv>
-#include <boost/algorithm/string.hpp> // for boost::to_lower
-#ifndef __cpp_lib_atomic_shared_ptr
+#include <boost/lexical_cast.hpp>
+#include <boost/make_shared.hpp>
+#if (BOOST_VERSION >= 105300)
 #include <boost/atomic.hpp>
 #endif
 #include "version.h"
@@ -22,10 +21,8 @@
 #include "Base.h"
 #include "Timestamp.h"
 #include "Log.h"
-#include "Transports.h"
 #include "NetDb.hpp"
 #include "RouterContext.h"
-#include "CryptoKey.h"
 #include "RouterInfo.h"
 
 namespace i2p
@@ -36,35 +33,33 @@ namespace data
 	{
 		if (len > size ()) len = size ();
 		memcpy (data (), buf, len);
-		m_BufferLen = len;
 	}
 
 	RouterInfo::RouterInfo (): m_Buffer (nullptr)
 	{
-		m_Addresses = AddressesPtr(new Addresses ()); // create empty list
+		m_Addresses = boost::make_shared<Addresses>(); // create empty list
 	}
 
 	RouterInfo::RouterInfo (const std::string& fullPath):
-		m_FamilyID (0), m_IsUpdated (false), m_IsUnreachable (false), m_IsFloodfill (false),
-		m_IsBufferScheduledToDelete (false), m_SupportedTransports (0), 
-		m_ReachableTransports (0), m_PublishedTransports (0), m_Caps (0), m_Version (0), 
-		m_Congestion (eLowCongestion)
+		m_FamilyID (0), m_IsUpdated (false), m_IsUnreachable (false),
+		m_SupportedTransports (0),m_ReachableTransports (0),
+		m_Caps (0), m_Version (0), m_Congestion (eLowCongestion)
 	{
-		m_Addresses = AddressesPtr(new Addresses ()); // create empty list
-		m_Buffer = RouterInfo::NewBuffer (); // always RouterInfo's
+		m_Addresses = boost::make_shared<Addresses>(); // create empty list
+		m_Buffer = NewBuffer (); // always RouterInfo's
 		ReadFromFile (fullPath);
 	}
 
 	RouterInfo::RouterInfo (std::shared_ptr<Buffer>&& buf, size_t len):
-		m_FamilyID (0), m_IsUpdated (true), m_IsUnreachable (false), m_IsFloodfill (false),
-		m_IsBufferScheduledToDelete (false), m_SupportedTransports (0), m_ReachableTransports (0), m_PublishedTransports (0),
+		m_FamilyID (0), m_IsUpdated (true), m_IsUnreachable (false),
+		m_SupportedTransports (0), m_ReachableTransports (0),
 		m_Caps (0), m_Version (0), m_Congestion (eLowCongestion)
 	{
 		if (len <= MAX_RI_BUFFER_SIZE)
 		{
-			m_Addresses = AddressesPtr(new Addresses ()); // create empty list
+			m_Addresses = boost::make_shared<Addresses>(); // create empty list
 			m_Buffer = buf;
-			if (m_Buffer) m_Buffer->SetBufferLen (len);
+			m_BufferLen = len;
 			ReadFromBuffer (true);
 		}
 		else
@@ -76,7 +71,7 @@ namespace data
 	}
 
 	RouterInfo::RouterInfo (const uint8_t * buf, size_t len):
-		RouterInfo (netdb.NewRouterInfoBuffer (buf, len), len)
+		RouterInfo (std::make_shared<Buffer> (buf, len), len)
 	{
 	}
 
@@ -100,14 +95,14 @@ namespace data
 			m_IsUnreachable = false;
 			m_SupportedTransports = 0;
 			m_ReachableTransports = 0;
-			m_PublishedTransports = 0;	
-			m_Caps = 0; m_IsFloodfill = false;
+			m_Caps = 0;
 			// don't clean up m_Addresses, it will be replaced in ReadFromStream
 			ClearProperties ();
 			// skip identity
 			size_t identityLen = m_RouterIdentity->GetFullLen ();
 			// read new RI
-			ReadFromBuffer (buf + identityLen, len - identityLen);
+			std::stringstream str (std::string ((char *)buf + identityLen, len - identityLen));
+			ReadFromStream (str);
 			if (!m_IsUnreachable)
 				UpdateBuffer (buf, len); // save buffer	
 			// don't delete buffer until saved to the file
@@ -132,17 +127,16 @@ namespace data
 		if (s.is_open ())
 		{
 			s.seekg (0,std::ios::end);
-			size_t bufferLen = s.tellg ();
-			if (bufferLen < 40 || bufferLen > MAX_RI_BUFFER_SIZE)
+			m_BufferLen = s.tellg ();
+			if (m_BufferLen < 40 || m_BufferLen > MAX_RI_BUFFER_SIZE)
 			{
-				LogPrint(eLogError, "RouterInfo: File ", fullPath, " is malformed");
+				LogPrint(eLogError, "RouterInfo: File", fullPath, " is malformed");
 				return false;
 			}
 			s.seekg(0, std::ios::beg);
 			if (!m_Buffer)
 				m_Buffer = NewBuffer ();
-			s.read((char *)m_Buffer->data (), bufferLen);
-			m_Buffer->SetBufferLen (bufferLen);
+			s.read((char *)m_Buffer->data (), m_BufferLen);
 		}
 		else
 		{
@@ -167,12 +161,11 @@ namespace data
 			m_IsUnreachable = true;
 			return;
 		}
-		size_t bufferLen = m_Buffer->GetBufferLen ();
-		m_RouterIdentity = NewIdentity (m_Buffer->data (), bufferLen);
+		m_RouterIdentity = NewIdentity (m_Buffer->data (), m_BufferLen);
 		size_t identityLen = m_RouterIdentity->GetFullLen ();
-		if (identityLen >= bufferLen)
+		if (identityLen >= m_BufferLen)
 		{
-			LogPrint (eLogError, "RouterInfo: Identity length ", identityLen, " exceeds buffer size ", bufferLen);
+			LogPrint (eLogError, "RouterInfo: Identity length ", identityLen, " exceeds buffer size ", m_BufferLen);
 			m_IsUnreachable = true;
 			return;
 		}
@@ -186,7 +179,7 @@ namespace data
 				return;
 			}
 			// verify signature
-			int l = bufferLen - m_RouterIdentity->GetSignatureLen ();
+			int l = m_BufferLen - m_RouterIdentity->GetSignatureLen ();
 			if (l < 0 || !m_RouterIdentity->Verify ((uint8_t *)m_Buffer->data (), l, (uint8_t *)m_Buffer->data () + l))
 			{
 				LogPrint (eLogError, "RouterInfo: Signature verification failed");
@@ -195,34 +188,39 @@ namespace data
 			}
 		}
 		// parse RI
-		if (!ReadFromBuffer (m_Buffer->data () + identityLen, bufferLen - identityLen))
+		std::stringstream str;
+		str.write ((const char *)m_Buffer->data () + identityLen, m_BufferLen - identityLen);
+		ReadFromStream (str);
+		if (!str)
 		{
 			LogPrint (eLogError, "RouterInfo: Malformed message");
 			m_IsUnreachable = true;
-		}	
+		}
 	}
 
-	bool RouterInfo::ReadFromBuffer (const uint8_t * buf, size_t len)
+	void RouterInfo::ReadFromStream (std::istream& s)
 	{
-		if (len < 9) return false;
+		if (!s) return;
 		m_Caps = 0; m_Congestion = eLowCongestion;
-		m_Timestamp = bufbe64toh (buf);
-		size_t offset = 8; // timestamp
+		s.read ((char *)&m_Timestamp, sizeof (m_Timestamp));
+		m_Timestamp = be64toh (m_Timestamp);
 		// read addresses
 		auto addresses = NewAddresses ();
-		uint8_t numAddresses = buf[offset]; offset++;
+		uint8_t numAddresses;
+		s.read ((char *)&numAddresses, sizeof (numAddresses));
 		for (int i = 0; i < numAddresses; i++)
 		{
-			if (offset + 9 > len) return false; // 1 byte cost + 8 bytes date
 			uint8_t supportedTransports = 0;
 			auto address = NewAddress ();
-			offset++; // cost, ignore
-			address->date = bufbe64toh (buf + offset); offset += 8; // date
-			bool isHost = false, isStaticKey = false, isV2 = false, isIntroKey = false;
-			auto transportStyle = ExtractString (buf + offset, len - offset); offset += transportStyle.length () + 1;
-			if (!transportStyle.compare (0, 4, "NTCP")) // NTCP or NTCP2
+			uint8_t cost; // ignore
+			s.read ((char *)&cost, sizeof (cost));
+			s.read ((char *)&address->date, sizeof (address->date));
+			bool isHost = false, isStaticKey = false, isV2 = false;
+			char transportStyle[6];
+			ReadString (transportStyle, 6, s);
+			if (!strncmp (transportStyle, "NTCP", 4)) // NTCP or NTCP2
 				address->transportStyle = eTransportNTCP2;
-			else if (!transportStyle.compare (0, 3, "SSU")) // SSU or SSU2
+			else if (!strncmp (transportStyle, "SSU", 3)) // SSU or SSU2
 			{
 				address->transportStyle = eTransportSSU2;
 				address->ssu.reset (new SSUExt ());
@@ -232,28 +230,30 @@ namespace data
 				address->transportStyle = eTransportUnknown;
 			address->caps = 0;
 			address->port = 0;
-			if (offset + 2 > len) return false;
-			uint16_t size = bufbe16toh (buf + offset); offset += 2; // size
-			if (offset + size >= len) return false;
+			uint16_t size, r = 0;
+			s.read ((char *)&size, sizeof (size)); if (!s) return;
+			size = be16toh (size);
 			if (address->transportStyle == eTransportUnknown)
 			{
 				// skip unknown address
-				offset += size;
-				continue;
+				s.seekg (size, std::ios_base::cur);
+				if (s) continue; else return;
 			}
-			size_t r = 0;
 			while (r < size)
 			{
-				auto [key, value, sz] = ExtractParam (buf + offset, len - offset);
-				r += sz; offset += sz;
-				if (key.empty ()) continue;
-				if (key == "host")
+				char key[255], value[255];
+				r += ReadString (key, 255, s);
+				s.seekg (1, std::ios_base::cur); r++; // =
+				r += ReadString (value, 255, s);
+				s.seekg (1, std::ios_base::cur); r++; // ;
+				if (!s) return;
+				if (!strcmp (key, "host"))
 				{
 					boost::system::error_code ecode;
-					address->host = boost::asio::ip::make_address (value, ecode);
+					address->host = boost::asio::ip::address::from_string (value, ecode);
 					if (!ecode && !address->host.is_unspecified ())
 					{
-						if (!i2p::transport::transports.IsInReservedRange (address->host) ||
+						if (!i2p::util::net::IsInReservedRange (address->host) ||
 						    i2p::util::net::IsYggdrasilAddress (address->host))
 							isHost = true;
 						else
@@ -261,59 +261,57 @@ namespace data
 							address->transportStyle = eTransportUnknown;
 					}
 				}
-				else if (key == "port")
+				else if (!strcmp (key, "port"))
 				{
-					auto res = std::from_chars(value.data(), value.data() + value.size(), address->port);
-					if (res.ec != std::errc())
-						LogPrint (eLogWarning, "RouterInfo: 'port' conversion error: ", std::make_error_code (res.ec).message ());
+					try
+					{
+						address->port = boost::lexical_cast<int>(value);
+					}
+					catch (std::exception& ex)
+					{
+						LogPrint (eLogWarning, "RouterInfo: 'port' exception ", ex.what ());
+					}
 				}
-				else if (key == "mtu")
+				else if (!strcmp (key, "mtu"))
 				{
 					if (address->ssu)
 					{
-						auto res = std::from_chars(value.data(), value.data() + value.size(), address->ssu->mtu);
-						if (res.ec != std::errc())
-							LogPrint (eLogWarning, "RouterInfo: 'mtu' conversion error: ", std::make_error_code (res.ec).message ());
+						try
+						{
+							address->ssu->mtu = boost::lexical_cast<int>(value);
+						}
+						catch (std::exception& ex)
+						{
+							LogPrint (eLogWarning, "RouterInfo: 'mtu' exception ", ex.what ());
+						}
 					}
 					else
 						LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP2");
 				}
-				else if (key == "caps")
+				else if (!strcmp (key, "caps"))
 					address->caps = ExtractAddressCaps (value);
-				else if (key == "s") // ntcp2 or ssu2 static key
+				else if (!strcmp (key, "s")) // ntcp2 or ssu2 static key
 				{
-					if (Base64ToByteStream (value, address->s, 32) == 32 &&
-						!(address->s[31] & 0x80)) // check if x25519 public key
-							isStaticKey = true;
-					else
-						address->transportStyle = eTransportUnknown; // invalid address
+					Base64ToByteStream (value, strlen (value), address->s, 32);
+					if (!(address->s[31] & 0x80)) // check if x25519 public key
+						isStaticKey = true;
 				}
-				else if (key == "i") // ntcp2 iv or ssu2 intro
+				else if (!strcmp (key, "i")) // ntcp2 iv or ssu2 intro
 				{
 					if (address->IsNTCP2 ())
 					{
-						if (Base64ToByteStream (value, address->i, 16) == 16)
-							address->published = true; // presence of "i" means "published" NTCP2
-						else
-							address->transportStyle = eTransportUnknown; // invalid address
+						Base64ToByteStream (value, strlen (value), address->i, 16);
+						address->published = true; // presence of "i" means "published" NTCP2
 					}
 					else if (address->IsSSU2 ())
-					{	
-						if (Base64ToByteStream (value, address->i, 32) == 32)
-							isIntroKey = true;
-						else
-							address->transportStyle = eTransportUnknown; // invalid address
-					}	
+						Base64ToByteStream (value, strlen (value), address->i, 32);
 				}
-				else if (key == "v")
+				else if (!strcmp (key, "v"))
 				{
-					if (value == "2")
+					if (!strcmp (value, "2"))
 						isV2 = true;
 					else
-					{	
 						LogPrint (eLogWarning, "RouterInfo: Unexpected value ", value, " for v");
-						address->transportStyle = eTransportUnknown; // invalid address
-					}	
 				}
 				else if (key[0] == 'i')
 				{
@@ -323,11 +321,13 @@ namespace data
 						LogPrint (eLogError, "RouterInfo: Introducer is presented for non-SSU address. Skipped");
 						continue;
 					}
-					unsigned char index = key[key.length () - 1] - '0'; // TODO:
+					size_t l = strlen(key);
+					unsigned char index = key[l-1] - '0'; // TODO:
+					key[l-1] = 0;
 					if (index > 9)
 					{
 						LogPrint (eLogError, "RouterInfo: Unexpected introducer's index ", index, " skipped");
-						continue;
+						if (s) continue; else return;
 					}
 					if (index >= address->ssu->introducers.size ())
 					{
@@ -336,23 +336,34 @@ namespace data
 						address->ssu->introducers.resize (index + 1);
 					}
 					Introducer& introducer = address->ssu->introducers.at (index);
-					auto key1 = key.substr(0, key.length () - 1);
-					if (key1 == "itag")
+					if (!strcmp (key, "itag"))
 					{
-						auto res = std::from_chars(value.data(), value.data() + value.size(), introducer.iTag);
-						if (res.ec != std::errc())
-							LogPrint (eLogWarning, "RouterInfo: 'itag' conversion error: ", std::make_error_code (res.ec).message ());
+						try
+						{
+							introducer.iTag = boost::lexical_cast<uint32_t>(value);
+						}
+						catch (std::exception& ex)
+						{
+							LogPrint (eLogWarning, "RouterInfo: 'itag' exception ", ex.what ());
+						}
 					}
-					else if (key1 == "ih")
-						Base64ToByteStream (value, introducer.iH, 32);
-					else if (key1 == "iexp")
+					else if (!strcmp (key, "ih"))
+						Base64ToByteStream (value, strlen (value), introducer.iH, 32);
+					else if (!strcmp (key, "iexp"))
 					{
-						auto res = std::from_chars(value.data(), value.data() + value.size(), introducer.iExp);
-						if (res.ec != std::errc())
-							LogPrint (eLogWarning, "RouterInfo: 'iexp' conversion error: ", std::make_error_code (res.ec).message ());
+						try
+						{
+							introducer.iExp = boost::lexical_cast<uint32_t>(value);
+						}
+						catch (std::exception& ex)
+						{
+							LogPrint (eLogWarning, "RouterInfo: 'iexp' exception ", ex.what ());
+						}
 					}
 				}
-			}	
+				if (!s) return;
+			}
+			
 			if (address->transportStyle == eTransportNTCP2)
 			{
 				if (isStaticKey)
@@ -363,7 +374,7 @@ namespace data
 							supportedTransports |= (i2p::util::net::IsYggdrasilAddress (address->host) ? eNTCP2V6Mesh : eNTCP2V6);
 						else
 							supportedTransports |= eNTCP2V4;
-						m_PublishedTransports |= supportedTransports;
+						m_ReachableTransports |= supportedTransports;
 					}
 					else
 					{
@@ -378,17 +389,17 @@ namespace data
 					}
 				}
 			}
-			else if (address->transportStyle == eTransportSSU2 && isV2 && isStaticKey && isIntroKey)
+			else if (address->transportStyle == eTransportSSU2 && isV2 && isStaticKey)
 			{
 				if (address->IsV4 ()) supportedTransports |= eSSU2V4;
 				if (address->IsV6 ()) supportedTransports |= eSSU2V6;
 				if (isHost && address->port)
 				{
-					if (address->host.is_v4 ()) m_PublishedTransports |= eSSU2V4;
-					if (address->host.is_v6 ()) m_PublishedTransports |= eSSU2V6;
+					if (address->host.is_v4 ()) m_ReachableTransports |= eSSU2V4;
+					if (address->host.is_v6 ()) m_ReachableTransports |= eSSU2V6;
 					address->published = true;
 				}
-				else if (address->ssu && !address->ssu->introducers.empty ())
+				if (address->ssu && !address->ssu->introducers.empty ())
 				{
 					// exclude invalid introducers
 					uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
@@ -408,81 +419,70 @@ namespace data
 				m_SupportedTransports |= supportedTransports;
 			}
 		}
-		m_ReachableTransports |= m_PublishedTransports;
 		// update addresses
-#ifdef __cpp_lib_atomic_shared_ptr
-		m_Addresses = addresses;
-#else		
+#if (BOOST_VERSION >= 105300)
 		boost::atomic_store (&m_Addresses, addresses);
+#else
+		m_Addresses = addresses; // race condition
 #endif
 		// read peers
-		if (offset + 1 > len) return false;
-		uint8_t numPeers = buf[offset]; offset++; // num peers
-		offset += numPeers*32; // TODO: read peers
+		uint8_t numPeers;
+		s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return;
+		s.seekg (numPeers*32, std::ios_base::cur); // TODO: read peers
 		// read properties
-		if (offset + 2 > len) return false;
 		m_Version = 0;
 		bool isNetId = false;
 		std::string family;
-		uint16_t size = bufbe16toh (buf + offset); offset += 2; // size
-		if (offset + size > len) return false;
-		size_t r = 0;
+		uint16_t size, r = 0;
+		s.read ((char *)&size, sizeof (size)); if (!s) return;
+		size = be16toh (size);
 		while (r < size)
 		{
-			auto [key, value, sz] = ExtractParam (buf + offset, len - offset);
-			r += sz; offset += sz;
-			if (key.empty ()) continue;
+			char key[255], value[255];
+			r += ReadString (key, 255, s);
+			s.seekg (1, std::ios_base::cur); r++; // =
+			r += ReadString (value, 255, s);
+			s.seekg (1, std::ios_base::cur); r++; // ;
+			if (!s) return;
 			SetProperty (key, value);
 
 			// extract caps
-			if (key == "caps")
-			{	
+			if (!strcmp (key, "caps"))
 				ExtractCaps (value);
-				m_IsFloodfill = IsDeclaredFloodfill ();
-			}	
 			// extract version
-			else if (key == ROUTER_INFO_PROPERTY_VERSION)
+			else if (!strcmp (key, ROUTER_INFO_PROPERTY_VERSION))
 			{
 				m_Version = 0;
-				for (auto ch: value)
+				char * ch = value;
+				while (*ch)
 				{
-					if (ch >= '0' && ch <= '9')
+					if (*ch >= '0' && *ch <= '9')
 					{
 						m_Version *= 10;
-						m_Version += (ch - '0');
+						m_Version += (*ch - '0');
 					}
+					ch++;
 				}
-				if (m_Version < NETDB_MIN_PEER_TEST_VERSION && (m_SupportedTransports & (eSSU2V4 | eSSU2V6)))
-				{
-					auto addresses = GetAddresses ();
-					if (addresses)
-					{
-						if ((*addresses)[eSSU2V4Idx]) (*addresses)[eSSU2V4Idx]->caps &= ~eSSUTesting;
-						if ((*addresses)[eSSU2V6Idx]) (*addresses)[eSSU2V6Idx]->caps &= ~eSSUTesting;
-					}	
-				}	
 			}
 			// check netId
-			else if (key == ROUTER_INFO_PROPERTY_NETID)
+			else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID))
 			{
 				isNetId = true;
-				int netID;
-				auto res = std::from_chars(value.data(), value.data() + value.size(), netID);
-				if (res.ec != std::errc() || netID != i2p::context.GetNetID ())
+				if (atoi (value) != i2p::context.GetNetID ())
 				{
 					LogPrint (eLogError, "RouterInfo: Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value);
 					m_IsUnreachable = true;
 				}
 			}
 			// family
-			else if (key == ROUTER_INFO_PROPERTY_FAMILY)
+			else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY))
 			{
 				family = value;
 				boost::to_lower (family);
 			}
-			else if (key == ROUTER_INFO_PROPERTY_FAMILY_SIG)
+			else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY_SIG))
 			{
-				if (netdb.GetFamilies ().VerifyFamily (family, GetIdentHash (), value)) // TODO
+				if (netdb.GetFamilies ().VerifyFamily (family, GetIdentHash (), value))
 					m_FamilyID = netdb.GetFamilies ().GetFamilyID (family);
 				else
 				{	
@@ -490,42 +490,37 @@ namespace data
 					SetUnreachable (true);	
 				}		
 			}
+
+			if (!s) return;
 		}
 
 		if (!m_SupportedTransports || !isNetId || !m_Version)
 			SetUnreachable (true);
-		
-		return true;
-	}	
-		
+	}
+
 	bool RouterInfo::IsFamily (FamilyID famid) const
 	{
 		return m_FamilyID == famid;
 	}
 
-	void RouterInfo::ExtractCaps (std::string_view value)
+	void RouterInfo::ExtractCaps (const char * value)
 	{
-		for (auto cap: value)
+		const char * cap = value;
+		while (*cap)
 		{
-			switch (cap)
+			switch (*cap)
 			{
 				case CAPS_FLAG_FLOODFILL:
 					m_Caps |= Caps::eFloodfill;
 				break;
-				case CAPS_FLAG_LOW_BANDWIDTH1:
-				case CAPS_FLAG_LOW_BANDWIDTH2:
-				case CAPS_FLAG_LOW_BANDWIDTH3:
-				case CAPS_FLAG_LOW_BANDWIDTH4:
-					m_BandwidthCap = cap;
-				break;
-				case CAPS_FLAG_HIGH_BANDWIDTH:
+				case CAPS_FLAG_HIGH_BANDWIDTH1:
+				case CAPS_FLAG_HIGH_BANDWIDTH2:
+				case CAPS_FLAG_HIGH_BANDWIDTH3:
 					m_Caps |= Caps::eHighBandwidth;
-					m_BandwidthCap = cap;
 				break;
 				case CAPS_FLAG_EXTRA_BANDWIDTH1:
 				case CAPS_FLAG_EXTRA_BANDWIDTH2:
 					m_Caps |= Caps::eExtraBandwidth | Caps::eHighBandwidth;
-					m_BandwidthCap = cap;
 				break;
 				case CAPS_FLAG_HIDDEN:
 					m_Caps |= Caps::eHidden;
@@ -547,15 +542,17 @@ namespace data
 				break;	
 				default: ;
 			}
+			cap++;
 		}
-	}	
-	
-	uint8_t RouterInfo::ExtractAddressCaps (std::string_view value) const
+	}
+
+	uint8_t RouterInfo::ExtractAddressCaps (const char * value) const
 	{
 		uint8_t caps = 0;
-		for (auto cap: value)
+		const char * cap = value;
+		while (*cap)
 		{
-			switch (cap)
+			switch (*cap)
 			{
 				case CAPS_FLAG_V4:
 					caps |= AddressCaps::eV4;
@@ -571,10 +568,11 @@ namespace data
 				break;
 				default: ;
 			}
+			cap++;
 		}
 		return caps;
-	}	
-		
+	}
+
 	void RouterInfo::UpdateIntroducers (std::shared_ptr<Address> address, uint64_t ts)
 	{
 		if (!address || !address->ssu) return;
@@ -610,19 +608,6 @@ namespace data
 		return m_Buffer->data ();
 	}
 
-	bool RouterInfo::SaveToFile (const std::string& fullPath, std::shared_ptr<Buffer> buf)
-	{
-		if (!buf) return false;
-		std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out);
-		if (!f.is_open ()) 
-		{
-			LogPrint (eLogError, "RouterInfo: Can't save to ", fullPath);
-			return false;
-		}
-		f.write ((char *)buf->data (), buf->GetBufferLen ());
-		return true;
-	}	
-		
 	bool RouterInfo::SaveToFile (const std::string& fullPath)
 	{
 		if (m_IsUnreachable) return false; // don't save bad router
@@ -631,44 +616,35 @@ namespace data
 			LogPrint (eLogWarning, "RouterInfo: Can't save, m_Buffer == NULL");
 			return false;
 		}
-		return SaveToFile (fullPath, m_Buffer);
+		std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out);
+		if (!f.is_open ()) 
+		{
+			LogPrint (eLogError, "RouterInfo: Can't save to ", fullPath);
+			return false;
+		}
+		f.write ((char *)m_Buffer->data (), m_BufferLen);
+		return true;
 	}
 
-	std::string_view RouterInfo::ExtractString (const uint8_t * buf, size_t len) const
+	size_t RouterInfo::ReadString (char * str, size_t len, std::istream& s) const
 	{
-		uint8_t l = buf[0];
-		if (l > len)
+		uint8_t l;
+		s.read ((char *)&l, 1);
+		if (l < len)
+		{
+			s.read (str, l);
+			if (!s) l = 0; // failed, return empty string
+			str[l] = 0;
+		}
+		else
 		{
 			LogPrint (eLogWarning, "RouterInfo: String length ", (int)l, " exceeds buffer size ", len);
-			l = len;
-		}	
-		return { (const char *)(buf + 1), l };
+			s.seekg (l, std::ios::cur); // skip
+			str[0] = 0;
+		}
+		return l+1;
 	}
 
-	std::tuple<std::string_view, std::string_view, size_t> RouterInfo::ExtractParam (const uint8_t * buf, size_t len) const
-	{
-		auto key = ExtractString (buf, len);
-		size_t offset = key.length () + 1;
-		if (offset >= len) return { std::string_view(), std::string_view(), len };
-		if (buf[offset] != '=')
-		{	
-			LogPrint (eLogWarning, "RouterInfo: Unexpected character ", buf[offset], " instead '=' after ", key);
-			key = std::string_view();
-		}	
-		offset++;
-		if (offset >= len) return { key, std::string_view(), len };
-		auto value = ExtractString (buf + offset, len - offset);
-		offset += value.length () + 1;
-		if (offset >= len) return { key, std::string_view(), len };
-		if (buf[offset] != ';')
-		{	
-			LogPrint (eLogWarning, "RouterInfo: Unexpected character ", buf[offset], " instead ';' after ", value);
-			value = std::string_view();
-		}	
-		offset++;
-		return { key, value, offset };
-	}	
-		
 	void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv,int port, uint8_t caps)
 	{
 		auto addr = std::make_shared<Address>();
@@ -682,12 +658,12 @@ namespace data
 		if (addr->IsV4 ())
 		{
 			m_SupportedTransports |= eNTCP2V4;
-			(*GetAddresses ())[eNTCP2V4Idx] = addr;
+			(*m_Addresses)[eNTCP2V4Idx] = addr;
 		}
 		if (addr->IsV6 ())
 		{
 			m_SupportedTransports |= eNTCP2V6;
-			(*GetAddresses ())[eNTCP2V6Idx] = addr;
+			(*m_Addresses)[eNTCP2V6Idx] = addr;
 		}
 	}
 
@@ -708,12 +684,11 @@ namespace data
 			if (host.is_v4 ()) addr->caps |= eV4;
 			if (host.is_v6 ()) addr->caps |= eV6;
 		}	
-		auto addresses = GetAddresses ();
 		if (addr->IsV4 ())
 		{
 			m_SupportedTransports |= eNTCP2V4;
 			m_ReachableTransports |= eNTCP2V4;
-			(*addresses)[eNTCP2V4Idx] = addr;
+			(*m_Addresses)[eNTCP2V4Idx] = addr;
 		}
 		if (addr->IsV6 ())
 		{
@@ -721,31 +696,30 @@ namespace data
 			{
 				m_SupportedTransports |= eNTCP2V6Mesh;
 				m_ReachableTransports |= eNTCP2V6Mesh;
-				(*addresses)[eNTCP2V6MeshIdx] = addr;
+				(*m_Addresses)[eNTCP2V6MeshIdx] = addr;
 			}
 			else
 			{
 				m_SupportedTransports |= eNTCP2V6;
 				m_ReachableTransports |= eNTCP2V6;
-				(*addresses)[eNTCP2V6Idx] = addr;
+				(*m_Addresses)[eNTCP2V6Idx] = addr;
 			}
 		}
 	}
 
 	void RouterInfo::RemoveNTCP2Address (bool v4)
 	{
-		auto addresses = GetAddresses ();
 		if (v4)
 		{
-			if ((*addresses)[eNTCP2V6Idx])
-				(*addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4;
-			(*addresses)[eNTCP2V4Idx].reset ();
+			if ((*m_Addresses)[eNTCP2V6Idx])
+				(*m_Addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4;
+			(*m_Addresses)[eNTCP2V4Idx].reset ();
 		}
 		else
 		{
-			if ((*addresses)[eNTCP2V4Idx])
-				(*addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6;
-			(*addresses)[eNTCP2V6Idx].reset ();
+			if ((*m_Addresses)[eNTCP2V4Idx])
+				(*m_Addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6;
+			(*m_Addresses)[eNTCP2V6Idx].reset ();
 		}
 		UpdateSupportedTransports ();
 	}
@@ -761,16 +735,15 @@ namespace data
 		addr->ssu->mtu = 0;
 		memcpy (addr->s, staticKey, 32);
 		memcpy (addr->i, introKey, 32);
-		auto addresses = GetAddresses ();
 		if (addr->IsV4 ())
 		{
 			m_SupportedTransports |= eSSU2V4;
-			(*addresses)[eSSU2V4Idx] = addr;
+			(*m_Addresses)[eSSU2V4Idx] = addr;
 		}
 		if (addr->IsV6 ())
 		{
 			m_SupportedTransports |= eSSU2V6;
-			(*addresses)[eSSU2V6Idx] = addr;
+			(*m_Addresses)[eSSU2V6Idx] = addr;
 		}
 	}
 
@@ -795,35 +768,33 @@ namespace data
 			if (host.is_v4 ()) addr->caps |= eV4;
 			if (host.is_v6 ()) addr->caps |= eV6;
 		}
-		auto addresses = GetAddresses ();
 		if (addr->IsV4 ())
 		{
 			m_SupportedTransports |= eSSU2V4;
 			m_ReachableTransports |= eSSU2V4;
-			(*addresses)[eSSU2V4Idx] = addr;
+			(*m_Addresses)[eSSU2V4Idx] = addr;
 		}
 		if (addr->IsV6 ())
 		{
 			m_SupportedTransports |= eSSU2V6;
 			m_ReachableTransports |= eSSU2V6;
-			(*addresses)[eSSU2V6Idx] = addr;
+			(*m_Addresses)[eSSU2V6Idx] = addr;
 		}
 	}
 
 	void RouterInfo::RemoveSSU2Address (bool v4)
 	{
-		auto addresses = GetAddresses ();
 		if (v4)
 		{
-			if ((*addresses)[eSSU2V6Idx])
-				(*addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4;
-			(*addresses)[eSSU2V4Idx].reset ();
+			if ((*m_Addresses)[eSSU2V6Idx])
+				(*m_Addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4;
+			(*m_Addresses)[eSSU2V4Idx].reset ();
 		}
 		else
 		{
-			if ((*addresses)[eSSU2V4Idx])
-				(*addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6;
-			(*addresses)[eSSU2V6Idx].reset ();
+			if ((*m_Addresses)[eSSU2V4Idx])
+				(*m_Addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6;
+			(*m_Addresses)[eSSU2V6Idx].reset ();
 		}
 		UpdateSupportedTransports ();
 	}
@@ -864,18 +835,17 @@ namespace data
 	{
 		if (IsV6 ())
 		{
-			auto addresses = GetAddresses ();
-			if ((*addresses)[eNTCP2V6Idx])
+			if ((*m_Addresses)[eNTCP2V6Idx])
 			{
-				if ((*addresses)[eNTCP2V6Idx]->IsV4 () && (*addresses)[eNTCP2V4Idx])
-					(*addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6;
-				(*addresses)[eNTCP2V6Idx].reset ();
+				if ((*m_Addresses)[eNTCP2V6Idx]->IsV4 () && (*m_Addresses)[eNTCP2V4Idx])
+					(*m_Addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6;
+				(*m_Addresses)[eNTCP2V6Idx].reset ();
 			}
-			if ((*addresses)[eSSU2V6Idx])
+			if ((*m_Addresses)[eSSU2V6Idx])
 			{
-				if ((*addresses)[eSSU2V6Idx]->IsV4 () && (*addresses)[eSSU2V4Idx])
-					(*addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6;
-				(*addresses)[eSSU2V6Idx].reset ();
+				if ((*m_Addresses)[eSSU2V6Idx]->IsV4 () && (*m_Addresses)[eSSU2V4Idx])
+					(*m_Addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6;
+				(*m_Addresses)[eSSU2V6Idx].reset ();
 			}
 			UpdateSupportedTransports ();
 		}
@@ -885,18 +855,17 @@ namespace data
 	{
 		if (IsV4 ())
 		{
-			auto addresses = GetAddresses ();
-			if ((*addresses)[eNTCP2V4Idx])
+			if ((*m_Addresses)[eNTCP2V4Idx])
 			{
-				if ((*addresses)[eNTCP2V4Idx]->IsV6 () && (*addresses)[eNTCP2V6Idx])
-					(*addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4;
-				(*addresses)[eNTCP2V4Idx].reset ();
+				if ((*m_Addresses)[eNTCP2V4Idx]->IsV6 () && (*m_Addresses)[eNTCP2V6Idx])
+					(*m_Addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4;
+				(*m_Addresses)[eNTCP2V4Idx].reset ();
 			}
-			if ((*addresses)[eSSU2V4Idx])
+			if ((*m_Addresses)[eSSU2V4Idx])
 			{
-				if ((*addresses)[eSSU2V4Idx]->IsV6 () && (*addresses)[eSSU2V6Idx])
-					(*addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4;
-				(*addresses)[eSSU2V4Idx].reset ();
+				if ((*m_Addresses)[eSSU2V4Idx]->IsV6 () && (*m_Addresses)[eSSU2V6Idx])
+					(*m_Addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4;
+				(*m_Addresses)[eSSU2V4Idx].reset ();
 			}
 			UpdateSupportedTransports ();
 		}
@@ -917,7 +886,7 @@ namespace data
 		{
 			m_SupportedTransports &= ~eNTCP2V6Mesh;
 			m_ReachableTransports &= ~eNTCP2V6Mesh;
-			(*GetAddresses ())[eNTCP2V6MeshIdx].reset ();
+			(*m_Addresses)[eNTCP2V6MeshIdx].reset ();
 		}
 	}
 
@@ -946,12 +915,12 @@ namespace data
 		return nullptr;
 	}
 
-	RouterInfo::AddressesPtr RouterInfo::GetAddresses () const
+	boost::shared_ptr<RouterInfo::Addresses> RouterInfo::GetAddresses () const
 	{
-#ifdef __cpp_lib_atomic_shared_ptr
-		return m_Addresses;
-#else		
+#if (BOOST_VERSION >= 105300)
 		return boost::atomic_load (&m_Addresses);
+#else
+		return m_Addresses;
 #endif
 	}
 
@@ -959,10 +928,10 @@ namespace data
 	std::shared_ptr<const RouterInfo::Address> RouterInfo::GetAddress (Filter filter) const
 	{
 		// TODO: make it more generic using comparator
-#ifdef __cpp_lib_atomic_shared_ptr
-		AddressesPtr addresses = m_Addresses;
-#else		
+#if (BOOST_VERSION >= 105300)
 		auto addresses = boost::atomic_load (&m_Addresses);
+#else
+		auto addresses = m_Addresses;
 #endif
 		for (const auto& address : *addresses)
 			if (address && filter (address)) return address;
@@ -1019,28 +988,21 @@ namespace data
 
 	bool RouterInfo::IsEligibleFloodfill () const
 	{
-		// floodfill must have published ipv4 or reachable ipv4 and published ipv6
-		// >= 0.9.59 and not DSA
-		return m_Version >= NETDB_MIN_FLOODFILL_VERSION && (IsPublished (true) ||
-			(IsReachableBy (eNTCP2V4 | eSSU2V4) && IsPublished (false))) &&
+		// floodfill must have published ipv4, >= 0.9.38 and not DSA
+		return m_Version >= NETDB_MIN_FLOODFILL_VERSION && IsPublished (true) && 
 			GetIdentity ()->GetSigningKeyType () != SIGNING_KEY_TYPE_DSA_SHA1;
 	}
 
 	bool RouterInfo::IsPublished (bool v4) const
 	{
-		if (m_Caps & (eUnreachable | eHidden)) return false; // if router sets U or H we assume that all addresses are not published
-		return IsPublishedOn (v4 ? (eNTCP2V4 | eSSU2V4) : (eNTCP2V6 | eSSU2V6));
-	}	
-
-	bool RouterInfo::IsPublishedOn (CompatibleTransports transports) const
-	{
-		return m_PublishedTransports & transports;
-	}
-	
-	bool RouterInfo::IsNAT2NATOnly (const RouterInfo& other) const
-	{
-		return !(m_PublishedTransports & other.m_SupportedTransports) &&
-			!(other.m_PublishedTransports & m_SupportedTransports); 	
+		if (m_Caps & (eUnreachable | eHidden)) return false; // if router sets U or H we assume that all addreses are not published
+		auto addr = GetAddresses ();
+		if (v4)	
+			return ((*addr)[eNTCP2V4Idx] && ((*addr)[eNTCP2V4Idx])->published) ||
+				((*addr)[eSSU2V4Idx] && ((*addr)[eSSU2V4Idx])->published);
+		else
+			return ((*addr)[eNTCP2V6Idx] && ((*addr)[eNTCP2V6Idx])->published) ||
+				((*addr)[eSSU2V6Idx] && ((*addr)[eSSU2V6Idx])->published);
 	}	
 		
 	bool RouterInfo::IsSSU2PeerTesting (bool v4) const
@@ -1059,7 +1021,7 @@ namespace data
 
 	void RouterInfo::SetUnreachableAddressesTransportCaps (uint8_t transports)
 	{
-		for (auto& addr: *GetAddresses ())
+		for (auto& addr: *m_Addresses)
 		{
 			if (addr && !addr->published)
 			{
@@ -1073,7 +1035,7 @@ namespace data
 	{
 		m_SupportedTransports = 0;
 		m_ReachableTransports = 0;
-		for (const auto& addr: *GetAddresses ())
+		for (const auto& addr: *m_Addresses)
 		{
 			if (!addr) continue;
 			uint8_t transports = 0;
@@ -1125,20 +1087,13 @@ namespace data
 		
 	void RouterInfo::UpdateBuffer (const uint8_t * buf, size_t len)
 	{
-		m_IsBufferScheduledToDelete = false;
 		if (!m_Buffer)
 			m_Buffer = NewBuffer ();
 		if (len > m_Buffer->size ()) len = m_Buffer->size ();
 		memcpy (m_Buffer->data (), buf, len);
-		m_Buffer->SetBufferLen (len);
+		m_BufferLen = len;
 	}
 
-	std::shared_ptr<RouterInfo::Buffer> RouterInfo::CopyBuffer () const
-	{
-		if (!m_Buffer) return nullptr;
-		return netdb.NewRouterInfoBuffer (*m_Buffer);
-	}	
-		
 	std::shared_ptr<RouterInfo::Buffer> RouterInfo::NewBuffer () const
 	{
 		return netdb.NewRouterInfoBuffer ();
@@ -1149,7 +1104,7 @@ namespace data
 		return netdb.NewRouterInfoAddress ();
 	}
 
-	RouterInfo::AddressesPtr RouterInfo::NewAddresses () const
+	boost::shared_ptr<RouterInfo::Addresses> RouterInfo::NewAddresses () const
 	{
 		return netdb.NewRouterInfoAddresses ();
 	}
@@ -1184,19 +1139,6 @@ namespace data
 				return false;
 		}
 	}
-
-	std::string RouterInfo::GetTransportName (SupportedTransports tr)
-	{
-		switch (tr)
-		{
-			case eNTCP2V4: return "NTCP2V4";
-			case eNTCP2V6: return "NTCP2V6";
-			case eSSU2V4: return "SSU2V4";
-			case eSSU2V6: return "SSU2V6";
-			case eNTCP2V6Mesh: return "Mesh";
-			default: return "";
-		}
-	}	
 		
 	void LocalRouterInfo::CreateBuffer (const PrivateKeys& privateKeys)
 	{
@@ -1235,7 +1177,7 @@ namespace data
 				CAPS_FLAG_EXTRA_BANDWIDTH2 : // 'X'
 				CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P'
 			else
-				caps += CAPS_FLAG_HIGH_BANDWIDTH; // 'O'
+				caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O'
 			caps += CAPS_FLAG_FLOODFILL; // floodfill
 		}
 		else
@@ -1243,7 +1185,7 @@ namespace data
 			if (c & eExtraBandwidth)
 				caps += (c & eHighBandwidth) ? CAPS_FLAG_EXTRA_BANDWIDTH2 /* 'X' */ : CAPS_FLAG_EXTRA_BANDWIDTH1; /*'P' */
 			else
-				caps += (c & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth
+				caps += (c & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH3 /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth
 		}
 		if (c & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden
 		if (c & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable
@@ -1382,9 +1324,9 @@ namespace data
 						if (!introducer.iTag) continue;
 						if (introducer.iExp) // expiration is specified
 						{
-							WriteString ("iexp" + std::to_string(i), properties);
+							WriteString ("iexp" + boost::lexical_cast<std::string>(i), properties);
 							properties << '=';
-							WriteString (std::to_string(introducer.iExp), properties);
+							WriteString (boost::lexical_cast<std::string>(introducer.iExp), properties);
 							properties << ';';
 						}
 						i++;
@@ -1393,9 +1335,11 @@ namespace data
 					for (const auto& introducer: address.ssu->introducers)
 					{
 						if (!introducer.iTag) continue;
-						WriteString ("ih" + std::to_string(i), properties);
+						WriteString ("ih" + boost::lexical_cast<std::string>(i), properties);
 						properties << '=';
-						auto value = ByteStreamToBase64 (introducer.iH, 32);
+						char value[64];
+						size_t l = ByteStreamToBase64 (introducer.iH, 32, value, 64);
+						value[l] = 0;
 						WriteString (value, properties);
 						properties << ';';
 						i++;
@@ -1404,9 +1348,9 @@ namespace data
 					for (const auto& introducer: address.ssu->introducers)
 					{
 						if (!introducer.iTag) continue;
-						WriteString ("itag" + std::to_string(i), properties);
+						WriteString ("itag" + boost::lexical_cast<std::string>(i), properties);
 						properties << '=';
-						WriteString (std::to_string(introducer.iTag), properties);
+						WriteString (boost::lexical_cast<std::string>(introducer.iTag), properties);
 						properties << ';';
 						i++;
 					}
@@ -1420,7 +1364,7 @@ namespace data
 				{
 					WriteString ("mtu", properties);
 					properties << '=';
-					WriteString (std::to_string(address.ssu->mtu), properties);
+					WriteString (boost::lexical_cast<std::string>(address.ssu->mtu), properties);
 					properties << ';';
 				}
 			}
@@ -1428,7 +1372,7 @@ namespace data
 			{
 				WriteString ("port", properties);
 				properties << '=';
-				WriteString (std::to_string(address.port), properties);
+				WriteString (boost::lexical_cast<std::string>(address.port), properties);
 				properties << ';';
 			}
 			if (address.IsNTCP2 () || address.IsSSU2 ())
@@ -1463,11 +1407,9 @@ namespace data
 		s.write (properties.str ().c_str (), properties.str ().size ());
 	}
 
-	void LocalRouterInfo::SetProperty (std::string_view key, std::string_view value)
+	void LocalRouterInfo::SetProperty (const std::string& key, const std::string& value)
 	{
-		auto [it, inserted] = m_Properties.emplace (key, value);
-		if (!inserted)
-			it->second = value;
+		m_Properties[key] = value;
 	}
 
 	void LocalRouterInfo::DeleteProperty (const std::string& key)
@@ -1483,20 +1425,6 @@ namespace data
 		return "";
 	}
 
-	void LocalRouterInfo::UpdateFloodfillProperty (bool floodfill)
-	{
-		if (floodfill)
-		{	
-			UpdateCaps (GetCaps () | i2p::data::RouterInfo::eFloodfill);
-			SetFloodfill ();
-		}	
-		else
-		{	
-			UpdateCaps (GetCaps () & ~i2p::data::RouterInfo::eFloodfill);
-			ResetFloodfill ();
-		}	
-	}	
-		
 	void LocalRouterInfo::WriteString (const std::string& str, std::ostream& s) const
 	{
 		uint8_t len = str.size ();
@@ -1514,9 +1442,9 @@ namespace data
 		return std::make_shared<Address> ();
 	}
 
-	RouterInfo::AddressesPtr LocalRouterInfo::NewAddresses () const
+	boost::shared_ptr<RouterInfo::Addresses> LocalRouterInfo::NewAddresses () const
 	{
-		return RouterInfo::AddressesPtr(new RouterInfo::Addresses ());
+		return boost::make_shared<Addresses> ();
 	}
 
 	std::shared_ptr<IdentityEx> LocalRouterInfo::NewIdentity (const uint8_t * buf, size_t len) const
@@ -1558,23 +1486,5 @@ namespace data
 		}
 		return false;
 	}
-
-	bool LocalRouterInfo::UpdateSSU2Introducer (const IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp)
-	{
-		auto addresses = GetAddresses ();
-		if (!addresses) return false;
-		auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx];
-		if (addr)
-		{
-			for (auto& it: addr->ssu->introducers)
-				if (h == it.iH)
-				{
-					it.iTag = iTag;
-					it.iExp = iExp;
-					return true;
-				}	
-		}
-		return false;
-	}	
 }
 }
diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h
index cb3ae499..a95119ed 100644
--- a/libi2pd/RouterInfo.h
+++ b/libi2pd/RouterInfo.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -11,17 +11,12 @@
 
 #include <inttypes.h>
 #include <string>
-#include <string_view>
-#include <tuple>
 #include <map>
 #include <vector>
 #include <array>
 #include <iostream>
-#include <memory>
 #include <boost/asio.hpp>
-#ifndef __cpp_lib_atomic_shared_ptr
 #include <boost/shared_ptr.hpp>
-#endif
 #include "Identity.h"
 #include "Profiling.h"
 #include "Family.h"
@@ -44,9 +39,9 @@ namespace data
 	/* bandwidth flags */
 	const char CAPS_FLAG_LOW_BANDWIDTH1   = 'K'; /*   < 12 KBps */
 	const char CAPS_FLAG_LOW_BANDWIDTH2   = 'L'; /*  12-48 KBps */
-	const char CAPS_FLAG_LOW_BANDWIDTH3  = 'M'; /*  48-64 KBps */
-	const char CAPS_FLAG_LOW_BANDWIDTH4  = 'N'; /*  64-128 KBps */
-	const char CAPS_FLAG_HIGH_BANDWIDTH  = 'O'; /* 128-256 KBps */
+	const char CAPS_FLAG_HIGH_BANDWIDTH1  = 'M'; /*  48-64 KBps */
+	const char CAPS_FLAG_HIGH_BANDWIDTH2  = 'N'; /*  64-128 KBps */
+	const char CAPS_FLAG_HIGH_BANDWIDTH3  = 'O'; /* 128-256 KBps */
 	const char CAPS_FLAG_EXTRA_BANDWIDTH1 = 'P'; /* 256-2048 KBps */
 	const char CAPS_FLAG_EXTRA_BANDWIDTH2 = 'X'; /*   > 2048 KBps */
 	// bandwidth limits in kBps
@@ -193,25 +188,13 @@ namespace data
 
 					Buffer () = default;
 					Buffer (const uint8_t * buf, size_t len);
-					Buffer (const Buffer& other): Buffer (other.data (), other.m_BufferLen) {};
-
-					size_t GetBufferLen () const { return m_BufferLen; };
-					void SetBufferLen (size_t len) { m_BufferLen = len; };
-					
-				private:
-
-					size_t m_BufferLen = 0;
 			};
 
 			typedef std::array<std::shared_ptr<Address>, eNumTransports> Addresses;
-#ifdef __cpp_lib_atomic_shared_ptr
-			typedef std::shared_ptr<Addresses> AddressesPtr;
-#else
-			typedef boost::shared_ptr<Addresses> AddressesPtr;
-#endif			
+
 			RouterInfo (const std::string& fullPath);
-			RouterInfo (const RouterInfo& ) = delete;
-			RouterInfo& operator=(const RouterInfo& ) = delete;
+			RouterInfo (const RouterInfo& ) = default;
+			RouterInfo& operator=(const RouterInfo& ) = default;
 			RouterInfo (std::shared_ptr<Buffer>&& buf, size_t len);
 			RouterInfo (const uint8_t * buf, size_t len);
 			virtual ~RouterInfo ();
@@ -221,9 +204,9 @@ namespace data
 			std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); };
 			uint64_t GetTimestamp () const { return m_Timestamp; };
 			int GetVersion () const { return m_Version; };
-			virtual void SetProperty (std::string_view key, std::string_view value) {};
+			virtual void SetProperty (const std::string& key, const std::string& value) {};
 			virtual void ClearProperties () {};
-			AddressesPtr GetAddresses () const; // should be called for local RI only, otherwise must return shared_ptr
+			boost::shared_ptr<Addresses> GetAddresses () const; // should be called for local RI only, otherwise must return shared_ptr
 			std::shared_ptr<const Address> GetNTCP2V4Address () const;
 			std::shared_ptr<const Address> GetNTCP2V6Address () const;
 			std::shared_ptr<const Address> GetPublishedNTCP2V4Address () const;
@@ -244,9 +227,8 @@ namespace data
 			void SetUnreachableAddressesTransportCaps (uint8_t transports); // bitmask of AddressCaps
 			void UpdateSupportedTransports ();
 			void UpdateIntroducers (uint64_t ts); // ts in seconds
-			bool IsFloodfill () const { return m_IsFloodfill; };
-			void SetFloodfill () { m_IsFloodfill = true; };
-			void ResetFloodfill () { m_IsFloodfill = false; };
+			bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; };
+			void ResetFlooldFill () { m_Caps &= ~Caps::eFloodfill; };
 			bool IsECIES () const { return m_RouterIdentity->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; };
 			bool IsNTCP2 (bool v4only = true) const;
 			bool IsNTCP2V6 () const { return m_SupportedTransports & eNTCP2V6; };
@@ -265,22 +247,17 @@ namespace data
 			bool IsReachableFrom (const RouterInfo& other) const { return m_ReachableTransports & other.m_SupportedTransports; };
 			bool IsReachableBy (CompatibleTransports transports) const { return m_ReachableTransports & transports; };
 			CompatibleTransports GetCompatibleTransports (bool incoming) const { return incoming ? m_ReachableTransports : m_SupportedTransports; };
-			CompatibleTransports GetPublishedTransports () const { return m_PublishedTransports; };	
 			bool HasValidAddresses () const { return m_SupportedTransports; };
 			bool IsHidden () const { return m_Caps & eHidden; };
 			bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; };
 			bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; };
 			bool IsEligibleFloodfill () const;
-			bool IsDeclaredFloodfill () const { return m_Caps & RouterInfo::eFloodfill; };
 			bool IsPublished (bool v4) const;
-			bool IsPublishedOn (CompatibleTransports transports) const;
-			bool IsNAT2NATOnly (const RouterInfo& other) const; // only NAT-to-NAT connection is possible
 			bool IsSSU2PeerTesting (bool v4) const;
 			bool IsSSU2Introducer (bool v4) const;
 			bool IsHighCongestion (bool highBandwidth) const;
 
 			uint8_t GetCaps () const { return m_Caps; };
-			char GetBandwidthCap() const { return m_BandwidthCap; };
 			void SetCaps (uint8_t caps) { m_Caps = caps; };
 
 			Congestion GetCongestion () const { return m_Congestion; };
@@ -291,24 +268,17 @@ namespace data
 
 			const uint8_t * GetBuffer () const { return m_Buffer ? m_Buffer->data () : nullptr; };
 			const uint8_t * LoadBuffer (const std::string& fullPath); // load if necessary
-			size_t GetBufferLen () const { return m_Buffer ? m_Buffer->GetBufferLen () : 0; };
-			void DeleteBuffer () { m_Buffer = nullptr; m_IsBufferScheduledToDelete = false; };
-			std::shared_ptr<Buffer> GetSharedBuffer () const { return m_Buffer; };	
-			std::shared_ptr<Buffer> CopyBuffer () const;
-			void ScheduleBufferToDelete () { m_IsBufferScheduledToDelete = true; };
-			void CancelBufferToDelete () { m_IsBufferScheduledToDelete = false; };
-			bool IsBufferScheduledToDelete () const { return m_IsBufferScheduledToDelete; };
+			size_t GetBufferLen () const { return m_BufferLen; };
 
 			bool IsUpdated () const { return m_IsUpdated; };
 			void SetUpdated (bool updated) { m_IsUpdated = updated; };
 			bool SaveToFile (const std::string& fullPath);
-			static bool SaveToFile (const std::string& fullPath, std::shared_ptr<Buffer> buf);
-		
+
 			std::shared_ptr<RouterProfile> GetProfile () const;
 			void DropProfile () { m_Profile = nullptr; };
-			bool HasProfile () const { return (bool)m_Profile; }; 
 
 			bool Update (const uint8_t * buf, size_t len);
+			void DeleteBuffer () { m_Buffer = nullptr; };
 			bool IsNewer (const uint8_t * buf, size_t len) const;
 
 			/** return true if we are in a router family and the signature is valid */
@@ -325,7 +295,7 @@ namespace data
 			RouterInfo ();
 			uint8_t * GetBufferPointer (size_t offset = 0 ) { return m_Buffer->data () + offset; };
 			void UpdateBuffer (const uint8_t * buf, size_t len);
-			void SetBufferLen (size_t len) { if (m_Buffer) m_Buffer->SetBufferLen (len); };
+			void SetBufferLen (size_t len) { m_BufferLen = len; };
 			void RefreshTimestamp ();
 			CompatibleTransports GetReachableTransports () const { return m_ReachableTransports; };
 			void SetReachableTransports (CompatibleTransports transports) { m_ReachableTransports = transports; };
@@ -335,18 +305,17 @@ namespace data
 
 			bool LoadFile (const std::string& fullPath);
 			void ReadFromFile (const std::string& fullPath);
-			bool ReadFromBuffer (const uint8_t * buf, size_t len); // return false if malformed
+			void ReadFromStream (std::istream& s);
 			void ReadFromBuffer (bool verifySignature);
-			std::string_view ExtractString (const uint8_t * buf, size_t len) const;
-			std::tuple<std::string_view, std::string_view, size_t> ExtractParam (const uint8_t * buf, size_t len) const;
-			void ExtractCaps (std::string_view value);
-			uint8_t ExtractAddressCaps (std::string_view value) const;
+			size_t ReadString (char* str, size_t len, std::istream& s) const;
+			void ExtractCaps (const char * value);
+			uint8_t ExtractAddressCaps (const char * value) const;
 			void UpdateIntroducers (std::shared_ptr<Address> address, uint64_t ts); 
 			template<typename Filter>
 			std::shared_ptr<const Address> GetAddress (Filter filter) const;
 			virtual std::shared_ptr<Buffer> NewBuffer () const;
 			virtual std::shared_ptr<Address> NewAddress () const;
-			virtual AddressesPtr NewAddresses () const;
+			virtual boost::shared_ptr<Addresses> NewAddresses () const;
 			virtual std::shared_ptr<IdentityEx> NewIdentity (const uint8_t * buf, size_t len) const;
 
 		private:
@@ -354,23 +323,15 @@ namespace data
 			FamilyID m_FamilyID;
 			std::shared_ptr<const IdentityEx> m_RouterIdentity;
 			std::shared_ptr<Buffer> m_Buffer;
+			size_t m_BufferLen;
 			uint64_t m_Timestamp; // in milliseconds
-#ifdef __cpp_lib_atomic_shared_ptr
-			std::atomic<AddressesPtr> m_Addresses;
-#else		
-			AddressesPtr m_Addresses;
-#endif		
-			bool m_IsUpdated, m_IsUnreachable, m_IsFloodfill, m_IsBufferScheduledToDelete;
-			CompatibleTransports m_SupportedTransports, m_ReachableTransports, m_PublishedTransports;
+			boost::shared_ptr<Addresses> m_Addresses; // TODO: use std::shared_ptr and std::atomic_store for gcc >= 4.9
+			bool m_IsUpdated, m_IsUnreachable;
+			CompatibleTransports m_SupportedTransports, m_ReachableTransports;
 			uint8_t m_Caps;
-			char m_BandwidthCap;
 			int m_Version;
 			Congestion m_Congestion;
 			mutable std::shared_ptr<RouterProfile> m_Profile;
-
-		public:
-
-			static std::string GetTransportName (SupportedTransports tr);
 	};
 
 	class LocalRouterInfo: public RouterInfo
@@ -382,15 +343,13 @@ namespace data
 			void UpdateCaps (uint8_t caps);
 			bool UpdateCongestion (Congestion c); // returns true if updated
 
-			void SetProperty (std::string_view key, std::string_view value) override;
+			void SetProperty (const std::string& key, const std::string& value) override;
 			void DeleteProperty (const std::string& key);
 			std::string GetProperty (const std::string& key) const;
 			void ClearProperties () override { m_Properties.clear (); };
-			void UpdateFloodfillProperty (bool floodfill);
-			
+
 			bool AddSSU2Introducer (const Introducer& introducer, bool v4);
 			bool RemoveSSU2Introducer (const IdentHash& h, bool v4);
-			bool UpdateSSU2Introducer (const IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp);
 
 		private:
 
@@ -399,7 +358,7 @@ namespace data
 			void WriteString (const std::string& str, std::ostream& s) const;
 			std::shared_ptr<Buffer> NewBuffer () const override;
 			std::shared_ptr<Address> NewAddress () const override;
-			RouterInfo::AddressesPtr NewAddresses () const override;
+			boost::shared_ptr<Addresses> NewAddresses () const override;
 			std::shared_ptr<IdentityEx> NewIdentity (const uint8_t * buf, size_t len) const override;
 
 		private:
diff --git a/libi2pd/SSU2.cpp b/libi2pd/SSU2.cpp
index fc2355a5..6a8615d5 100644
--- a/libi2pd/SSU2.cpp
+++ b/libi2pd/SSU2.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2022-2025, The PurpleI2P Project
+* Copyright (c) 2022-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -25,7 +25,7 @@ namespace transport
 		m_TerminationTimer (GetService ()), m_CleanupTimer (GetService ()), m_ResendTimer (GetService ()),
 		m_IntroducersUpdateTimer (GetService ()), m_IntroducersUpdateTimerV6 (GetService ()),
 		m_IsPublished (true), m_IsSyncClockFromPeers (true), m_PendingTimeOffset (0),
-		m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL), m_IsThroughProxy (false)
+		m_IsThroughProxy (false)
 	{
 	}
 
@@ -81,7 +81,7 @@ namespace transport
 							found = true;
 							LogPrint (eLogDebug, "SSU2: Opening IPv4 socket at Start");
 							OpenSocket (boost::asio::ip::udp::endpoint (m_AddressV4, port));
-							boost::asio::post (m_ReceiveService.GetService (),
+							m_ReceiveService.GetService ().post(
 								[this]()
 								{
 									Receive (m_SocketV4);
@@ -93,8 +93,8 @@ namespace transport
 							found = true;
 							LogPrint (eLogDebug, "SSU2: Opening IPv6 socket at Start");
 							OpenSocket (boost::asio::ip::udp::endpoint (m_AddressV6, port));
-							boost::asio::post (m_ReceiveService.GetService (),
-								[this]()
+							m_ReceiveService.GetService ().post(
+							[this]()
 								{
 									Receive (m_SocketV6);
 								});
@@ -152,14 +152,8 @@ namespace transport
 		m_SessionsByRouterHash.clear ();
 		m_PendingOutgoingSessions.clear ();
 		m_Relays.clear ();
-		m_PeerTests.clear ();
 		m_Introducers.clear ();
 		m_IntroducersV6.clear ();
-		m_ConnectedRecently.clear ();
-		m_RequestedPeerTests.clear ();
-
-		m_PacketsPool.ReleaseMt (m_ReceivedPacketsQueue);
-		m_ReceivedPacketsQueue.clear ();
 	}
 
 	void SSU2Server::SetLocalAddress (const boost::asio::ip::address& localAddress)
@@ -216,65 +210,27 @@ namespace transport
 		return ep.port ();
 	}
 
-	bool SSU2Server::IsConnectedRecently (const boost::asio::ip::udp::endpoint& ep, bool max)
-	{
-		if (!ep.port () || ep.address ().is_unspecified ()) return false;
-		std::lock_guard<std::mutex> l(m_ConnectedRecentlyMutex);
-		auto it = m_ConnectedRecently.find (ep);
-		if (it != m_ConnectedRecently.end ())
-		{	
-			if (i2p::util::GetSecondsSinceEpoch () <= it->second + (max ? SSU2_MAX_HOLE_PUNCH_EXPIRATION : SSU2_MIN_HOLE_PUNCH_EXPIRATION))
-				return true;
-			else if (max)
-				m_ConnectedRecently.erase (it);
-		}	
-		return false;
-	}	
-
-	void SSU2Server::AddConnectedRecently (const boost::asio::ip::udp::endpoint& ep, uint64_t ts)
-	{
-		if (!ep.port () || ep.address ().is_unspecified () || 
-		    i2p::util::GetSecondsSinceEpoch () > ts + SSU2_MAX_HOLE_PUNCH_EXPIRATION) return;
-		std::lock_guard<std::mutex> l(m_ConnectedRecentlyMutex);
-		auto [it, added] = m_ConnectedRecently.try_emplace (ep, ts);
-		if (!added && ts > it->second)
-			it->second = ts; // renew timestamp of existing endpoint
-	}	
-		
-	void SSU2Server::AdjustTimeOffset (int64_t offset, std::shared_ptr<const i2p::data::IdentityEx> from)
+	void SSU2Server::AdjustTimeOffset (int64_t offset)
 	{
 		if (offset)
 		{	
 			if (m_PendingTimeOffset) // one more
 			{	
-				if (m_PendingTimeOffsetFrom && from && 
-					m_PendingTimeOffsetFrom->GetIdentHash ().GetLL()[0] != from->GetIdentHash ().GetLL()[0]) // from different routers
+				if (std::abs (m_PendingTimeOffset - offset) < SSU2_CLOCK_SKEW)
 				{	
-					if (std::abs (m_PendingTimeOffset - offset) < SSU2_CLOCK_SKEW)
-					{	
-						offset = (m_PendingTimeOffset + offset)/2; // average
-						LogPrint (eLogWarning, "SSU2: Clock adjusted by ", offset, " seconds");
-						i2p::util::AdjustTimeOffset (offset);
-					}	
-					else
-						LogPrint (eLogWarning, "SSU2: Time offsets are too different. Clock not adjusted");
-					m_PendingTimeOffset = 0;
-					m_PendingTimeOffsetFrom = nullptr;
-				}
+					offset = (m_PendingTimeOffset + offset)/2; // average
+					LogPrint (eLogWarning, "SSU2: Clock adjusted by ", offset, " seconds");
+					i2p::util::AdjustTimeOffset (offset);
+				}	
 				else
-					LogPrint (eLogWarning, "SSU2: Time offsets from same router. Clock not adjusted");
+					LogPrint (eLogWarning, "SSU2: Time offsets are too different. Clock not adjusted");
+				m_PendingTimeOffset = 0;
 			}
 			else
-			{	
 				m_PendingTimeOffset = offset; // first 
-				m_PendingTimeOffsetFrom = from;
-			}	
 		}	
 		else
-		{	
 			m_PendingTimeOffset = 0; // reset
-			m_PendingTimeOffsetFrom = nullptr;
-		}	
 	}	
 		
 	boost::asio::ip::udp::socket& SSU2Server::OpenSocket (const boost::asio::ip::udp::endpoint& localEndpoint)
@@ -287,49 +243,15 @@ namespace transport
 			socket.open (localEndpoint.protocol ());
 			if (localEndpoint.address ().is_v6 ())
 				socket.set_option (boost::asio::ip::v6_only (true));
-
-			uint64_t bufferSize = i2p::context.GetBandwidthLimit() * 1024 / 5; // max lag = 200ms
-			bufferSize = std::max(SSU2_SOCKET_MIN_BUFFER_SIZE, std::min(bufferSize, SSU2_SOCKET_MAX_BUFFER_SIZE));
-
-			boost::asio::socket_base::receive_buffer_size receiveBufferSizeSet (bufferSize);
-			boost::asio::socket_base::send_buffer_size sendBufferSizeSet (bufferSize);
-			socket.set_option (receiveBufferSizeSet);
-			socket.set_option (sendBufferSizeSet);
-			boost::asio::socket_base::receive_buffer_size receiveBufferSizeGet;
-			boost::asio::socket_base::send_buffer_size sendBufferSizeGet;
-			socket.get_option (receiveBufferSizeGet);
-			socket.get_option (sendBufferSizeGet);
-			if (receiveBufferSizeGet.value () != receiveBufferSizeSet.value () ||
-				sendBufferSizeGet.value () != sendBufferSizeSet.value ())
-			{
-				LogPrint (eLogWarning, "SSU2: Socket receive buffer size: requested = ",
-					receiveBufferSizeSet.value (), ", got = ", receiveBufferSizeGet.value ());
-				LogPrint (eLogWarning, "SSU2: Socket send buffer size: requested = ",
-					sendBufferSizeSet.value (), ", got = ", sendBufferSizeGet.value ());
-			}
-			else
-			{
-				LogPrint (eLogInfo, "SSU2: Socket receive buffer size: ", receiveBufferSizeGet.value ());
-				LogPrint (eLogInfo, "SSU2: Socket send buffer size: ", sendBufferSizeGet.value ());
-			}
-
-			socket.non_blocking (true);
-		}
-		catch (std::exception& ex )
-		{
-			LogPrint (eLogCritical, "SSU2: Failed to open socket on ", localEndpoint.address (), ": ", ex.what());
-			ThrowFatal ("Unable to start SSU2 transport on ", localEndpoint.address (), ": ", ex.what ());
-			return socket;
-		}
-		try
-		{
+			socket.set_option (boost::asio::socket_base::receive_buffer_size (SSU2_SOCKET_RECEIVE_BUFFER_SIZE));
+			socket.set_option (boost::asio::socket_base::send_buffer_size (SSU2_SOCKET_SEND_BUFFER_SIZE));
 			socket.bind (localEndpoint);
 			LogPrint (eLogInfo, "SSU2: Start listening on ", localEndpoint);
 		}
 		catch (std::exception& ex )
 		{
-			LogPrint (eLogWarning, "SSU2: Failed to bind to ", localEndpoint, ": ", ex.what(), ". Actual endpoint is ", socket.local_endpoint ());
-			// we can continue without binding being firewalled
+			LogPrint (eLogCritical, "SSU2: Failed to bind to ", localEndpoint, ": ", ex.what());
+			ThrowFatal ("Unable to start SSU2 transport on ", localEndpoint, ": ", ex.what ());
 		}
 		return socket;
 	}
@@ -361,32 +283,22 @@ namespace transport
 		// but better to find out which host were sent it and mark that router as unreachable
 		{
 			i2p::transport::transports.UpdateReceivedBytes (bytes_transferred);
-			if (bytes_transferred < SSU2_MIN_RECEIVED_PACKET_SIZE)
-			{
-				// drop too short packets
-				m_PacketsPool.ReleaseMt (packet);
-				Receive (socket);
-				return;
-			}	
 			packet->len = bytes_transferred;
-		
+
 			boost::system::error_code ec;
 			size_t moreBytes = socket.available (ec);
 			if (!ec && moreBytes)
 			{
-				std::list<Packet *> packets;
+				std::vector<Packet *> packets;
 				packets.push_back (packet);
-				while (moreBytes && packets.size () < SSU2_MAX_NUM_PACKETS_PER_BATCH)
-				{	
+				while (moreBytes && packets.size () < 32)
+				{
 					packet = m_PacketsPool.AcquireMt ();
 					packet->len = socket.receive_from (boost::asio::buffer (packet->buf, SSU2_MAX_PACKET_SIZE), packet->from, 0, ec);
 					if (!ec)
 					{
 						i2p::transport::transports.UpdateReceivedBytes (packet->len);
-						if (packet->len >= SSU2_MIN_RECEIVED_PACKET_SIZE)
-							packets.push_back (packet);
-						else // drop too short packets
-							m_PacketsPool.ReleaseMt (packet);
+						packets.push_back (packet);
 						moreBytes = socket.available(ec);
 						if (ec) break;
 					}
@@ -397,10 +309,10 @@ namespace transport
 						break;
 					}
 				}
-				InsertToReceivedPacketsQueue (packets);
+				GetService ().post (std::bind (&SSU2Server::HandleReceivedPackets, this, packets));
 			}
 			else
-				InsertToReceivedPacketsQueue (packet); 
+				GetService ().post (std::bind (&SSU2Server::HandleReceivedPacket, this, packet));
 			Receive (socket);
 		}
 		else
@@ -427,75 +339,40 @@ namespace transport
 		}
 	}
 
-	void SSU2Server::HandleReceivedPackets (std::list<Packet *>&& packets)
+	void SSU2Server::HandleReceivedPacket (Packet * packet)
+	{
+		if (packet)
+		{
+			if (m_IsThroughProxy)
+				ProcessNextPacketFromProxy (packet->buf, packet->len);
+			else
+				ProcessNextPacket (packet->buf, packet->len, packet->from);
+			m_PacketsPool.ReleaseMt (packet);
+			if (m_LastSession && m_LastSession->GetState () != eSSU2SessionStateTerminated)
+				m_LastSession->FlushData ();
+		}
+	}
+
+	void SSU2Server::HandleReceivedPackets (std::vector<Packet *> packets)
 	{
-		if (packets.empty ()) return;
 		if (m_IsThroughProxy)
-			for (auto it: packets)
-				ProcessNextPacketFromProxy (it->buf, it->len);
+			for (auto& packet: packets)
+				ProcessNextPacketFromProxy (packet->buf, packet->len);
 		else
-			for (auto it: packets)
-				ProcessNextPacket (it->buf, it->len, it->from);
+			for (auto& packet: packets)
+				ProcessNextPacket (packet->buf, packet->len, packet->from);
 		m_PacketsPool.ReleaseMt (packets);
 		if (m_LastSession && m_LastSession->GetState () != eSSU2SessionStateTerminated)
 			m_LastSession->FlushData ();
 	}
 
-	void SSU2Server::InsertToReceivedPacketsQueue (Packet * packet)
-	{
-		if (!packet) return;
-		bool empty = false;
-		{
-			std::lock_guard<std::mutex> l(m_ReceivedPacketsQueueMutex);
-			empty = m_ReceivedPacketsQueue.empty ();
-			m_ReceivedPacketsQueue.push_back (packet);
-		}
-		if (empty)
-			boost::asio::post (GetService (), [this]() { HandleReceivedPacketsQueue (); });
-	}	
-
-	void SSU2Server::InsertToReceivedPacketsQueue (std::list<Packet *>& packets)
-	{
-		if (packets.empty ()) return;
-		size_t queueSize = 0;
-		{
-			std::lock_guard<std::mutex> l(m_ReceivedPacketsQueueMutex);
-			queueSize = m_ReceivedPacketsQueue.size ();
-			if (queueSize < SSU2_MAX_RECEIVED_QUEUE_SIZE)
-				m_ReceivedPacketsQueue.splice (m_ReceivedPacketsQueue.end (), packets);
-			else
-			{
-				LogPrint (eLogError, "SSU2: Received queue size ", queueSize, " exceeds max size", SSU2_MAX_RECEIVED_QUEUE_SIZE);
-				m_PacketsPool.ReleaseMt (packets);
-				queueSize = 0; // invoke processing just in case
-			}		
-		}
-		if (!queueSize)
-			boost::asio::post (GetService (), [this]() { HandleReceivedPacketsQueue (); });
-	}	
-		
-	void SSU2Server::HandleReceivedPacketsQueue ()
-	{
-		std::list<Packet *> receivedPackets;
-		{
-			std::lock_guard<std::mutex> l(m_ReceivedPacketsQueueMutex);
-			m_ReceivedPacketsQueue.swap (receivedPackets);
-		}
-		HandleReceivedPackets (std::move (receivedPackets));
-	}	
-		
-	bool SSU2Server::AddSession (std::shared_ptr<SSU2Session> session)
+	void SSU2Server::AddSession (std::shared_ptr<SSU2Session> session)
 	{
 		if (session)
 		{
-			if (m_Sessions.emplace (session->GetConnID (), session).second)
-			{	
-				if (session->GetState () != eSSU2SessionStatePeerTest)
-					AddSessionByRouterHash (session);
-				return true;
-			}	
+			m_Sessions.emplace (session->GetConnID (), session);
+			AddSessionByRouterHash (session);
 		}
-		return false;
 	}
 
 	void SSU2Server::RemoveSession (uint64_t connID)
@@ -503,28 +380,15 @@ namespace transport
 		auto it = m_Sessions.find (connID);
 		if (it != m_Sessions.end ())
 		{
-			if (it->second->GetState () != eSSU2SessionStatePeerTest)
-			{	
-				auto ident = it->second->GetRemoteIdentity ();
-				if (ident)
-				{
-					std::lock_guard<std::mutex> l(m_SessionsByRouterHashMutex);
-					auto it1 = m_SessionsByRouterHash.find (ident->GetIdentHash ());
-					if (it1 != m_SessionsByRouterHash.end () && it->second == it1->second.lock ())
-						m_SessionsByRouterHash.erase (it1);
-				}	
-			}
+			auto ident = it->second->GetRemoteIdentity ();
+			if (ident)
+				m_SessionsByRouterHash.erase (ident->GetIdentHash ());
 			if (m_LastSession == it->second)
 				m_LastSession = nullptr;
 			m_Sessions.erase (it);
 		}
 	}
 
-	void SSU2Server::RequestRemoveSession (uint64_t connID)
-	{
-		boost::asio::post (GetService (), [connID, this]() { RemoveSession (connID); });
-	}	
-		
 	void SSU2Server::AddSessionByRouterHash (std::shared_ptr<SSU2Session> session)
 	{
 		if (session)
@@ -532,26 +396,16 @@ namespace transport
 			auto ident = session->GetRemoteIdentity ();
 			if (ident)
 			{
-				std::shared_ptr<SSU2Session> oldSession;
+				auto ret = m_SessionsByRouterHash.emplace (ident->GetIdentHash (), session);
+				if (!ret.second)
 				{
-					std::lock_guard<std::mutex> l(m_SessionsByRouterHashMutex);
-					auto ret = m_SessionsByRouterHash.emplace (ident->GetIdentHash (), session);
-					if (!ret.second)
-					{
-						oldSession = ret.first->second.lock ();
-						// update session
-						ret.first->second = session;
-					}	
-				}	
-				if (oldSession && oldSession != session)
-				{	
 					// session already exists
 					LogPrint (eLogWarning, "SSU2: Session to ", ident->GetIdentHash ().ToBase64 (), " already exists");
-					// move unsent msgs to new session
-					oldSession->MoveSendQueue (session);
 					// terminate existing
-					boost::asio::post (GetService (), std::bind (&SSU2Session::RequestTermination, oldSession, eSSU2TerminationReasonReplacedByNewSession));
-				}	
+					GetService ().post (std::bind (&SSU2Session::RequestTermination, ret.first->second, eSSU2TerminationReasonReplacedByNewSession));
+					// update session
+					ret.first->second = session;
+				}
 			}
 		}
 	}
@@ -559,30 +413,21 @@ namespace transport
 	bool SSU2Server::AddPendingOutgoingSession (std::shared_ptr<SSU2Session> session)
 	{
 		if (!session) return false;
-		std::lock_guard<std::mutex> l(m_PendingOutgoingSessionsMutex);
+		std::unique_lock<std::mutex> l(m_PendingOutgoingSessionsMutex);
 		return m_PendingOutgoingSessions.emplace (session->GetRemoteEndpoint (), session).second;
 	}
 
-	std::shared_ptr<SSU2Session> SSU2Server::FindSession (const i2p::data::IdentHash& ident)
+	std::shared_ptr<SSU2Session> SSU2Server::FindSession (const i2p::data::IdentHash& ident) const
 	{
-		std::lock_guard<std::mutex> l(m_SessionsByRouterHashMutex);
 		auto it = m_SessionsByRouterHash.find (ident);
 		if (it != m_SessionsByRouterHash.end ())
-		{	
-			if (!it->second.expired ())
-			{
-				auto s = it->second.lock ();
-				if (s && s->GetState () != eSSU2SessionStateTerminated)
-					return s;
-			}
-			m_SessionsByRouterHash.erase (it);
-		}	
+			return it->second;
 		return nullptr;
 	}
 
 	std::shared_ptr<SSU2Session> SSU2Server::FindPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) const
 	{
-		std::lock_guard<std::mutex> l(m_PendingOutgoingSessionsMutex);
+		std::unique_lock<std::mutex> l(m_PendingOutgoingSessionsMutex);
 		auto it = m_PendingOutgoingSessions.find (ep);
 		if (it != m_PendingOutgoingSessions.end ())
 			return it->second;
@@ -591,20 +436,22 @@ namespace transport
 
 	void SSU2Server::RemovePendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep)
 	{
-		std::lock_guard<std::mutex> l(m_PendingOutgoingSessionsMutex);
+		std::unique_lock<std::mutex> l(m_PendingOutgoingSessionsMutex);
 		m_PendingOutgoingSessions.erase (ep);
 	}
 
-	std::shared_ptr<SSU2Session> SSU2Server::GetRandomPeerTestSession (
-		i2p::data::RouterInfo::CompatibleTransports remoteTransports, const i2p::data::IdentHash& excluded)
+	std::shared_ptr<SSU2Session> SSU2Server::GetRandomSession (
+		i2p::data::RouterInfo::CompatibleTransports remoteTransports, const i2p::data::IdentHash& excluded) const
 	{
 		if (m_Sessions.empty ()) return nullptr;
-		int ind = m_Rng () % m_Sessions.size ();
+		uint16_t ind;
+		RAND_bytes ((uint8_t *)&ind, sizeof (ind));
+		ind %= m_Sessions.size ();
 		auto it = m_Sessions.begin ();
 		std::advance (it, ind);
 		while (it != m_Sessions.end ())
 		{
-			if (it->second->IsEstablished () && (it->second->GetRemotePeerTestTransports () & remoteTransports) &&
+			if ((it->second->GetRemoteTransports () & remoteTransports) &&
 			    it->second->GetRemoteIdentity ()->GetIdentHash () != excluded)
 				return it->second;
 			it++;
@@ -613,7 +460,7 @@ namespace transport
 		it = m_Sessions.begin ();
 		while (it != m_Sessions.end () && ind)
 		{
-			if (it->second->IsEstablished () && (it->second->GetRemotePeerTestTransports () & remoteTransports) &&
+			if ((it->second->GetRemoteTransports () & remoteTransports) &&
 			    it->second->GetRemoteIdentity ()->GetIdentHash () != excluded)
 				return it->second;
 			it++; ind--;
@@ -636,51 +483,14 @@ namespace transport
 		auto it = m_Relays.find (tag);
 		if (it != m_Relays.end ())
 		{
-			if (!it->second.expired ())
-			{	
-				auto s = it->second.lock ();
-				if (s && s->IsEstablished ())
-					return s;
-			}	
-			m_Relays.erase (it);
+			if (it->second->IsEstablished ())
+				return it->second;
+			else
+				m_Relays.erase (it);
 		}
 		return nullptr;
 	}
 
-	bool SSU2Server::AddPeerTest (uint32_t nonce, std::shared_ptr<SSU2Session> aliceSession, uint64_t ts)
-	{
-		return m_PeerTests.emplace (nonce, std::pair{ aliceSession, ts }).second;
-	}	
-
-	std::shared_ptr<SSU2Session> SSU2Server::GetPeerTest (uint32_t nonce)
-	{
-		auto it = m_PeerTests.find (nonce);
-		if (it != m_PeerTests.end ())
-		{
-			auto s = it->second.first.lock ();
-			m_PeerTests.erase (it);
-			return s;
-		}
-		return nullptr;
-	}	
-		
-	bool SSU2Server::AddRequestedPeerTest (uint32_t nonce, std::shared_ptr<SSU2PeerTestSession> session, uint64_t ts)
-	{
-		return m_RequestedPeerTests.emplace (nonce, std::pair{ session, ts }).second;
-	}
-		
-	std::shared_ptr<SSU2PeerTestSession> SSU2Server::GetRequestedPeerTest (uint32_t nonce)
-	{
-		auto it = m_RequestedPeerTests.find (nonce);
-		if (it != m_RequestedPeerTests.end ())
-		{
-			auto s = it->second.first.lock ();
-			m_RequestedPeerTests.erase (it);
-			return s;
-		}
-		return nullptr;
-	}	
-		
 	void SSU2Server::ProcessNextPacket (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint)
 	{
 		if (len < 24) return;
@@ -728,9 +538,6 @@ namespace transport
 					m_LastSession->SetRemoteEndpoint (senderEndpoint);
 					m_LastSession->ProcessPeerTest (buf, len);
 				break;
-				case eSSU2SessionStateHolePunch:
-					m_LastSession->ProcessFirstIncomingMessage (connID, buf, len); // SessionRequest
-				break;	
 				case eSSU2SessionStateClosing:
 					m_LastSession->ProcessData (buf, len, senderEndpoint); // we might receive termintaion block
 					if (m_LastSession && m_LastSession->GetState () == eSSU2SessionStateClosing)
@@ -753,13 +560,13 @@ namespace transport
 				if (it1->second->GetState () == eSSU2SessionStateSessionRequestSent &&
 					it1->second->ProcessSessionCreated (buf, len))
 				{
-					std::lock_guard<std::mutex> l(m_PendingOutgoingSessionsMutex);
+					std::unique_lock<std::mutex> l(m_PendingOutgoingSessionsMutex);
 					m_PendingOutgoingSessions.erase (it1); // we are done with that endpoint
 				}
 				else
 					it1->second->ProcessRetry (buf, len);
 			}
-			else if (!i2p::transport::transports.IsInReservedRange(senderEndpoint.address ()) && senderEndpoint.port ())
+			else if (!i2p::util::net::IsInReservedRange(senderEndpoint.address ()) && senderEndpoint.port ())
 			{
 				// assume new incoming session
 				auto session = std::make_shared<SSU2Session> (*this);
@@ -801,10 +608,7 @@ namespace transport
 		if (!ec)
 			i2p::transport::transports.UpdateSentBytes (headerLen + payloadLen);
 		else
-		{
-			LogPrint (ec == boost::asio::error::would_block ? eLogInfo : eLogError,
-				"SSU2: Send exception: ", ec.message (), " to ", to);
-		}
+			LogPrint (eLogError, "SSU2: Send exception: ", ec.message (), " to ", to);
 	}
 
 	void SSU2Server::Send (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen,
@@ -838,76 +642,59 @@ namespace transport
 		if (!ec)
 			i2p::transport::transports.UpdateSentBytes (headerLen + headerXLen + payloadLen);
 		else
-		{
-			LogPrint (ec == boost::asio::error::would_block ? eLogInfo : eLogError,
-				"SSU2: Send exception: ", ec.message (), " to ", to);
-		}
+			LogPrint (eLogError, "SSU2: Send exception: ", ec.message (), " to ", to);
 	}
 
-	bool SSU2Server::CheckPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep, bool peerTest)
-	{
-		auto s = FindPendingOutgoingSession (ep);
-		if (s)
-		{
-			if (peerTest)
-			{
-				// if peer test requested add it to the list for pending session
-				auto onEstablished = s->GetOnEstablished ();
-				if (onEstablished)
-					s->SetOnEstablished ([s, onEstablished]()
-						{
-							onEstablished ();
-							s->SendPeerTest ();
-						});
-				else
-					s->SetOnEstablished ([s]() { s->SendPeerTest (); });
-			}
-			return true;
-		}
-		return false;
-	}	
-		
 	bool SSU2Server::CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router,
 		std::shared_ptr<const i2p::data::RouterInfo::Address> address, bool peerTest)
 	{
 		if (router && address)
 		{
 			// check if no session
-			auto existingSession = FindSession (router->GetIdentHash ());
-			if (existingSession)
+			auto it = m_SessionsByRouterHash.find (router->GetIdentHash ());
+			if (it != m_SessionsByRouterHash.end ())
 			{
 				// session with router found, trying to send peer test if requested
-				if (peerTest && existingSession->IsEstablished ())
-					boost::asio::post (GetService (), [existingSession]() { existingSession->SendPeerTest (); });
+				if (peerTest && it->second->IsEstablished ())
+				{
+					auto session = it->second;
+					GetService ().post ([session]() { session->SendPeerTest (); });
+				}
 				return false;
 			}
 			// check is no pending session
 			bool isValidEndpoint = !address->host.is_unspecified () && address->port;
 			if (isValidEndpoint)
 			{
-				if (i2p::transport::transports.IsInReservedRange(address->host)) return false;
-				if (CheckPendingOutgoingSession (boost::asio::ip::udp::endpoint (address->host, address->port), peerTest)) return false;
+				if (i2p::util::net::IsInReservedRange(address->host)) return false;
+				auto s = FindPendingOutgoingSession (boost::asio::ip::udp::endpoint (address->host, address->port));
+				if (s)
+				{
+					if (peerTest)
+					{
+						// if peer test requested add it to the list for pending session
+						auto onEstablished = s->GetOnEstablished ();
+						if (onEstablished)
+							s->SetOnEstablished ([s, onEstablished]()
+								{
+									onEstablished ();
+									s->SendPeerTest ();
+								});
+						else
+							s->SetOnEstablished ([s]() { s->SendPeerTest (); });
+					}
+					return false;
+				}
 			}
 
 			auto session = std::make_shared<SSU2Session> (*this, router, address);
-			if (!isValidEndpoint && router->HasProfile () && router->GetProfile ()->HasLastEndpoint (address->IsV4 ()))
-			{
-				// router doesn't publish endpoint, but we connected before and hole punch might be alive
-				auto ep = router->GetProfile ()->GetLastEndpoint ();
-				if (IsConnectedRecently (ep, false))
-				{
-					if (CheckPendingOutgoingSession (ep, peerTest)) return false;
-					session->SetRemoteEndpoint (ep);
-					isValidEndpoint = true;
-				}	
-			}	
 			if (peerTest)
 				session->SetOnEstablished ([session]() {session->SendPeerTest (); });
 
-			if (isValidEndpoint) // we know endpoint
-				boost::asio::post (GetService (), [session]() { session->Connect (); });
-			else if (address->UsesIntroducer ()) // we don't know endpoint yet
-				boost::asio::post (GetService (), std::bind (&SSU2Server::ConnectThroughIntroducer, this, session));
+			if (address->UsesIntroducer ())
+				GetService ().post (std::bind (&SSU2Server::ConnectThroughIntroducer, this, session));
+			else if (isValidEndpoint) // we can't connect without endpoint
+				GetService ().post ([session]() { session->Connect (); });
 			else
 				return false;
 		}
@@ -922,119 +709,82 @@ namespace transport
 		auto address = session->GetAddress ();
 		if (!address) return;
 		session->WaitForIntroduction ();
-		auto ts = i2p::util::GetSecondsSinceEpoch ();
-		std::vector<int> indices; int i = 0;
 		// try to find existing session first
 		for (auto& it: address->ssu->introducers)
 		{
-			if (it.iTag && ts < it.iExp)
+			auto it1 = m_SessionsByRouterHash.find (it.iH);
+			if (it1 != m_SessionsByRouterHash.end ())
 			{
-				auto s = FindSession (it.iH);
-				if (s)
-				{
-					auto addr = s->GetAddress ();
-					if (addr && addr->IsIntroducer ())
-					{	
-						s->Introduce (session, it.iTag);
-						return;
-					}
-				}
-				else
-					indices.push_back(i);
-			}	
-			i++;
+				it1->second->Introduce (session, it.iTag);
+				return;
+			}
 		}
 		// we have to start a new session to an introducer
-		std::vector<i2p::data::IdentHash> newRouters;
+		auto ts = i2p::util::GetSecondsSinceEpoch ();
 		std::shared_ptr<i2p::data::RouterInfo> r;
-		std::shared_ptr<const i2p::data::RouterInfo::Address> addr;
 		uint32_t relayTag = 0;
-		if (!indices.empty ())
+		if (!address->ssu->introducers.empty ())
 		{
+			std::vector<int> indices;
+			for (int i = 0; i < (int)address->ssu->introducers.size (); i++) indices.push_back(i);
 			if (indices.size () > 1)
-				std::shuffle (indices.begin(), indices.end(), m_Rng);
+				std::shuffle (indices.begin(), indices.end(), std::mt19937(std::random_device()()));
 
-			for (auto ind: indices)
+			for (auto i: indices)
 			{
-				const auto& introducer = address->ssu->introducers[ind];
-				// introducer is not expired, because in indices
-				r = i2p::data::netdb.FindRouter (introducer.iH);
-				if (r)
-				{	
-					if (r->IsPublishedOn (i2p::context.GetRouterInfo ().GetCompatibleTransports (false) & // outgoing
-					    (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6)))                  
+				const auto& introducer = address->ssu->introducers[indices[i]];
+				if (introducer.iTag && ts < introducer.iExp)
+				{
+					r = i2p::data::netdb.FindRouter (introducer.iH);
+					if (r && r->IsReachableFrom (i2p::context.GetRouterInfo ()))
 					{
 						relayTag = introducer.iTag;
-						addr = address->IsV6 () ? r->GetSSU2V6Address () : r->GetSSU2V4Address ();
-						if (addr && addr->IsIntroducer () && !addr->host.is_unspecified () && addr->port &&
-							!i2p::transport::transports.IsInReservedRange(addr->host))
-							break;
-						else
-						{
-							// address is invalid or not intrudcer, try another SSU2 address if exists
-							if (address->IsV4 ())
-							{
-								if (i2p::context.SupportsV6 ())
-									addr = r->GetSSU2V6Address ();
-							}	
-							else
-							{
-								if (i2p::context.SupportsV4 ())
-									addr = r->GetSSU2V4Address ();
-							}	
-							if (addr && addr->IsIntroducer () && !addr->host.is_unspecified () && addr->port &&
-								!i2p::transport::transports.IsInReservedRange(addr->host))
-								break;
-							else
-							{	
-								// all addresses are invalid, try next introducer
-								relayTag = 0;
-								addr = nullptr;
-								r = nullptr;
-							}	
-						}	
+						if (relayTag) break;
 					}
-					else
-						r = nullptr;
-				}	
-				else if (!i2p::data::IsRouterBanned (introducer.iH))
-					newRouters.push_back (introducer.iH);
+				}
 			}
 		}
 		if (r)
 		{
-			if (relayTag && addr)
+			if (relayTag)
 			{
 				// introducer and tag found connect to it through SSU2
-				auto s = FindPendingOutgoingSession (boost::asio::ip::udp::endpoint (addr->host, addr->port));
-				if (!s)
+				auto addr = address->IsV6 () ? r->GetSSU2V6Address () : r->GetSSU2V4Address ();
+				if (addr)
 				{
-					s = std::make_shared<SSU2Session> (*this, r, addr);
-					s->SetOnEstablished ([session, s, relayTag]() { s->Introduce (session, relayTag); });
-					s->Connect ();
-				}
-				else
-				{
-					auto onEstablished = s->GetOnEstablished ();
-					if (onEstablished)
-						s->SetOnEstablished ([session, s, relayTag, onEstablished]()
-							{
-								onEstablished ();
-								s->Introduce (session, relayTag);
-							});
-					else
-						s->SetOnEstablished ([session, s, relayTag]() {s->Introduce (session, relayTag); });
+					bool isValidEndpoint = !addr->host.is_unspecified () && addr->port &&
+						!i2p::util::net::IsInReservedRange(addr->host);
+					if (isValidEndpoint)
+					{
+						auto s = FindPendingOutgoingSession (boost::asio::ip::udp::endpoint (addr->host, addr->port));
+						if (!s)
+						{
+							s = std::make_shared<SSU2Session> (*this, r, addr);
+							s->SetOnEstablished ([session, s, relayTag]() { s->Introduce (session, relayTag); });
+							s->Connect ();
+						}
+						else
+						{
+							auto onEstablished = s->GetOnEstablished ();
+							if (onEstablished)
+								s->SetOnEstablished ([session, s, relayTag, onEstablished]()
+									{
+										onEstablished ();
+										s->Introduce (session, relayTag);
+									});
+							else
+								s->SetOnEstablished ([session, s, relayTag]() {s->Introduce (session, relayTag); });
+						}
+					}
 				}
 			}
-			else
-				session->Done ();
 		}
 		else
 		{
 			// introducers not found, try to request them
-			for (auto& it: newRouters)
-				i2p::data::netdb.RequestDestination (it);
-			session->Done (); // don't wait for connect timeout
+			for (auto& it: address->ssu->introducers)
+				if (it.iTag && ts < it.iExp)
+					i2p::data::netdb.RequestDestination (it.iH);
 		}
 	}
 
@@ -1043,16 +793,14 @@ namespace transport
 		if (!router) return false;
 		auto addr = v4 ? router->GetSSU2V4Address () : router->GetSSU2V6Address ();
 		if (!addr) return false;
-		auto session = FindSession (router->GetIdentHash ());
-		if (session)
+		auto it = m_SessionsByRouterHash.find (router->GetIdentHash ());
+		if (it != m_SessionsByRouterHash.end ())
 		{
-			auto remoteAddr = session->GetAddress ();
-			if (!remoteAddr || !remoteAddr->IsPeerTesting () ||
-			    (v4 && !remoteAddr->IsV4 ()) || (!v4 && !remoteAddr->IsV6 ())) return false;   
-			if (session->IsEstablished ())
-				boost::asio::post (GetService (), [session]() { session->SendPeerTest (); });
+			auto s = it->second;
+			if (it->second->IsEstablished ())
+				GetService ().post ([s]() { s->SendPeerTest (); });
 			else
-				session->SetOnEstablished ([session]() { session->SendPeerTest (); });
+				s->SetOnEstablished ([s]() { s->SendPeerTest (); });
 			return true;
 		}
 		else
@@ -1062,8 +810,7 @@ namespace transport
 
 	void SSU2Server::ScheduleTermination ()
 	{
-		m_TerminationTimer.expires_from_now (boost::posix_time::seconds(
-			SSU2_TERMINATION_CHECK_TIMEOUT + m_Rng () % SSU2_TERMINATION_CHECK_TIMEOUT_VARIANCE));
+		m_TerminationTimer.expires_from_now (boost::posix_time::seconds(SSU2_TERMINATION_CHECK_TIMEOUT));
 		m_TerminationTimer.async_wait (std::bind (&SSU2Server::HandleTerminationTimer,
 			this, std::placeholders::_1));
 	}
@@ -1073,20 +820,17 @@ namespace transport
 		if (ecode != boost::asio::error::operation_aborted)
 		{
 			auto ts = i2p::util::GetSecondsSinceEpoch ();
-
+			for (auto it = m_PendingOutgoingSessions.begin (); it != m_PendingOutgoingSessions.end ();)
 			{
-				std::lock_guard<std::mutex> l(m_PendingOutgoingSessionsMutex);
-				for (auto it = m_PendingOutgoingSessions.begin (); it != m_PendingOutgoingSessions.end ();)
+				if (it->second->IsTerminationTimeoutExpired (ts))
 				{
-					if (it->second->IsTerminationTimeoutExpired (ts))
-					{
-						//it->second->Terminate ();
-						it = m_PendingOutgoingSessions.erase (it);
-					}
-					else
-						it++;
+					//it->second->Terminate ();
+					std::unique_lock<std::mutex> l(m_PendingOutgoingSessionsMutex);
+					it = m_PendingOutgoingSessions.erase (it);
 				}
-			}	
+				else
+					it++;
+			}
 
 			for (auto it: m_Sessions)
 			{
@@ -1104,6 +848,14 @@ namespace transport
 					it.second->CleanUp (ts);
 			}
 
+			for (auto it = m_SessionsByRouterHash.begin (); it != m_SessionsByRouterHash.begin ();)
+			{
+				if (it->second && it->second->GetState () == eSSU2SessionStateTerminated)
+					it = m_SessionsByRouterHash.erase (it);
+				else
+					it++;
+			}
+
 			ScheduleTermination ();
 		}
 	}
@@ -1122,23 +874,12 @@ namespace transport
 			auto ts = i2p::util::GetSecondsSinceEpoch ();
 			for (auto it = m_Relays.begin (); it != m_Relays.begin ();)
 			{
-				if (it->second.expired ())
+				if (it->second && it->second->GetState () == eSSU2SessionStateTerminated)
 					it = m_Relays.erase (it);
 				else
 					it++;
 			}
 
-			for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();)
-			{
-				if (ts > it->second.second + SSU2_PEER_TEST_EXPIRATION_TIMEOUT || it->second.first.expired ())
-				{
-					LogPrint (eLogInfo, "SSU2: Peer test nonce ", it->first, " was not responded in ", SSU2_PEER_TEST_EXPIRATION_TIMEOUT, " seconds or session invalid. Deleted");
-					it = m_PeerTests.erase (it);
-				}
-				else
-					it++;
-			}
-			
 			for (auto it = m_IncomingTokens.begin (); it != m_IncomingTokens.end (); )
 			{
 				if (ts > it->second.second)
@@ -1155,33 +896,6 @@ namespace transport
 					it++;
 			}
 
-			for (auto it = m_ConnectedRecently.begin (); it != m_ConnectedRecently.end (); )
-			{
-				if (ts > it->second + SSU2_MAX_HOLE_PUNCH_EXPIRATION)
-					it = m_ConnectedRecently.erase (it);
-				else
-					it++;
-			}	
-
-			for (auto it = m_RequestedPeerTests.begin (); it != m_RequestedPeerTests.end ();)
-			{
-				if (ts > it->second.second + SSU2_PEER_TEST_EXPIRATION_TIMEOUT)
-					it = m_RequestedPeerTests.erase (it);
-				else
-					it++;
-			}
-
-			{
-				std::lock_guard<std::mutex> l(m_SessionsByRouterHashMutex);
-				for (auto it = m_SessionsByRouterHash.begin (); it != m_SessionsByRouterHash.begin ();)
-				{
-					if (it->second.expired ())
-						it = m_SessionsByRouterHash.erase (it);
-					else
-						it++;
-				}
-			}	
-			
 			m_PacketsPool.CleanUpMt ();
 			m_SentPacketsPool.CleanUp ();
 			m_IncompleteMessagesPool.CleanUp ();
@@ -1192,9 +906,8 @@ namespace transport
 
 	void SSU2Server::ScheduleResend (bool more)
 	{
-		m_ResendTimer.expires_from_now (boost::posix_time::milliseconds (more ? 
-		    (SSU2_RESEND_CHECK_MORE_TIMEOUT + m_Rng () % SSU2_RESEND_CHECK_MORE_TIMEOUT_VARIANCE):
-			(SSU2_RESEND_CHECK_TIMEOUT + m_Rng () % SSU2_RESEND_CHECK_TIMEOUT_VARIANCE)));
+		m_ResendTimer.expires_from_now (boost::posix_time::milliseconds (more ? SSU2_RESEND_CHECK_MORE_TIMEOUT :
+			(SSU2_RESEND_CHECK_TIMEOUT + rand () % SSU2_RESEND_CHECK_TIMEOUT_VARIANCE)));
 		m_ResendTimer.async_wait (std::bind (&SSU2Server::HandleResendTimer,
 			this, std::placeholders::_1));
 	}
@@ -1207,8 +920,7 @@ namespace transport
 			auto ts = i2p::util::GetMillisecondsSinceEpoch ();
 			for (auto it: m_Sessions)
 			{
-				if (ts >= it.second->GetLastResendTime () + SSU2_RESEND_CHECK_TIMEOUT)
-					resentPacketsNum += it.second->Resend (ts);
+				resentPacketsNum += it.second->Resend (ts);
 				if (resentPacketsNum > SSU2_MAX_RESEND_PACKETS) break;
 			}
 			for (auto it: m_PendingOutgoingSessions)
@@ -1251,118 +963,111 @@ namespace transport
 		}
 		uint64_t token;
 		RAND_bytes ((uint8_t *)&token, 8);
-		if (!token) token = 1; // token can't be zero
-		m_IncomingTokens.try_emplace (ep, token, uint32_t(ts + SSU2_TOKEN_EXPIRATION_TIMEOUT));
+		m_IncomingTokens.emplace (ep, std::make_pair (token, uint32_t(ts + SSU2_TOKEN_EXPIRATION_TIMEOUT)));
 		return token;
 	}
 
 	std::pair<uint64_t, uint32_t> SSU2Server::NewIncomingToken (const boost::asio::ip::udp::endpoint& ep)
 	{
+		m_IncomingTokens.erase (ep); // drop previous
 		uint64_t token;
 		RAND_bytes ((uint8_t *)&token, 8);
-		if (!token) token = 1; // token can't be zero
-		uint32_t expires = i2p::util::GetSecondsSinceEpoch () + SSU2_NEXT_TOKEN_EXPIRATION_TIMEOUT;
-		auto [it, inserted] = m_IncomingTokens.try_emplace (ep, token, expires);
-		if (!inserted)
-			it->second = { token, expires }; // override
-		return it->second;
+		auto ret = std::make_pair (token, uint32_t(i2p::util::GetSecondsSinceEpoch () + SSU2_NEXT_TOKEN_EXPIRATION_TIMEOUT));
+		m_IncomingTokens.emplace (ep, ret);
+		return ret;
 	}
 
-	std::vector<std::shared_ptr<SSU2Session> > SSU2Server::FindIntroducers (int maxNumIntroducers,
-		bool v4, const std::unordered_set<i2p::data::IdentHash>& excluded)
+	std::list<std::shared_ptr<SSU2Session> > SSU2Server::FindIntroducers (int maxNumIntroducers,
+		bool v4, const std::set<i2p::data::IdentHash>& excluded) const
 	{
-		std::vector<std::shared_ptr<SSU2Session> > ret;
-		if (maxNumIntroducers <= 0 || m_Sessions.empty ()) return ret;
-		
-		std::vector<std::shared_ptr<SSU2Session> > eligible;
-		eligible.reserve (m_Sessions.size ()/2);
-		auto ts = i2p::util::GetSecondsSinceEpoch ();
+		std::list<std::shared_ptr<SSU2Session> > ret;
 		for (const auto& s : m_Sessions)
 		{
 			if (s.second->IsEstablished () && (s.second->GetRelayTag () && s.second->IsOutgoing ()) &&
-			    ts < s.second->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION/2 &&
 			    !excluded.count (s.second->GetRemoteIdentity ()->GetIdentHash ()) &&
 			    ((v4 && (s.second->GetRemoteTransports () & i2p::data::RouterInfo::eSSU2V4)) ||
 			    (!v4 && (s.second->GetRemoteTransports () & i2p::data::RouterInfo::eSSU2V6))))
-				eligible.push_back (s.second);
+				ret.push_back (s.second);
+		}
+		if ((int)ret.size () > maxNumIntroducers)
+		{
+			// shink ret randomly
+			int sz = ret.size () - maxNumIntroducers;
+			for (int i = 0; i < sz; i++)
+			{
+				auto ind = rand () % ret.size ();
+				auto it = ret.begin ();
+				std::advance (it, ind);
+				ret.erase (it);
+			}
 		}
-
-		if (eligible.size () <= (size_t)maxNumIntroducers)
-			return eligible;
-		else	
-			std::sample (eligible.begin(), eligible.end(), std::back_inserter(ret), maxNumIntroducers, m_Rng);
 		return ret;
 	}
 
 	void SSU2Server::UpdateIntroducers (bool v4)
 	{
 		uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
-		std::list<std::pair<i2p::data::IdentHash, uint32_t> > newList, impliedList;
+		std::list<i2p::data::IdentHash> newList, impliedList;
 		auto& introducers = v4 ? m_Introducers : m_IntroducersV6;
-		std::unordered_set<i2p::data::IdentHash> excluded;
-		for (const auto& [ident, tag] : introducers)
+		std::set<i2p::data::IdentHash> excluded;
+		for (const auto& it : introducers)
 		{
-			std::shared_ptr<SSU2Session> session = FindSession (ident);
-			if (session)
-				excluded.insert (ident);
-			if (session)
-			{	
-				if (session->IsEstablished () && session->GetRelayTag () && session->IsOutgoing () && // still session with introducer?
-					ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION)
+			std::shared_ptr<SSU2Session> session;
+			auto it1 = m_SessionsByRouterHash.find (it);
+			if (it1 != m_SessionsByRouterHash.end ())
+			{
+				session = it1->second;
+				excluded.insert (it);
+			}
+			if (session && session->IsEstablished () && session->GetRelayTag () && session->IsOutgoing ()) // still session with introducer?
+			{
+				if (ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION)
 				{	
 					session->SendKeepAlive ();
 					if (ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION)	
-					{	
-						newList.push_back ({ident, session->GetRelayTag ()});
-						if (tag != session->GetRelayTag ())
-						{
-							LogPrint (eLogDebug, "SSU2: Introducer session to  ", session->GetIdentHashBase64() , " was replaced. iTag ", tag, "->", session->GetRelayTag ());
-							i2p::context.UpdateSSU2Introducer (ident, v4, session->GetRelayTag (),
-								session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION);
-						}	
-					}
+						newList.push_back (it);
 					else	
 					{	
-						impliedList.push_back ({ident, session->GetRelayTag ()}); // keep in introducers list, but not publish
+						impliedList.push_back (it); // keep in introducers list, but not publish
 						session = nullptr;	
-					}
+					}		
 				}	
 				else
 					session = nullptr;
-			}	
-			
+			}
 			if (!session)
-				i2p::context.RemoveSSU2Introducer (ident, v4);
+				i2p::context.RemoveSSU2Introducer (it, v4);
 		}
-		int numOldSessions = 0;
 		if (newList.size () < SSU2_MAX_NUM_INTRODUCERS)
 		{
 			auto sessions = FindIntroducers (SSU2_MAX_NUM_INTRODUCERS - newList.size (), v4, excluded);
-			if (sessions.empty () && !impliedList.empty ())
+			if (sessions.empty () && !introducers.empty ())
 			{
+				// bump creation time for previous introducers if no new sessions found
 				LogPrint (eLogDebug, "SSU2: No new introducers found. Trying to reuse existing");
-				for (const auto& it : impliedList)
+				impliedList.clear ();
+				for (auto& it : introducers)
 				{
-					auto session = FindSession (it.first);
-					if (session)
+					auto it1 = m_SessionsByRouterHash.find (it);
+					if (it1 != m_SessionsByRouterHash.end ())
 					{
-						if (std::find_if (newList.begin (), newList.end (), 
-						    [&ident = it.first](const auto& s){ return ident == s.first; }) == newList.end ())
-						{	
-							sessions.push_back (session);
-							numOldSessions++;
-						}	
+						auto session = it1->second;
+						if (session->IsEstablished () && session->GetRelayTag () && session->IsOutgoing ())
+						{
+							session->SetCreationTime (session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION);
+							if (std::find (newList.begin (), newList.end (), it) == newList.end ())
+								sessions.push_back (session);
+						}
 					}
 				}
-				impliedList.clear ();
 			}
 
 			for (const auto& it : sessions)
 			{
-				uint32_t tag = it->GetRelayTag ();		
+				uint32_t tag = it->GetRelayTag ();
 				uint32_t exp = it->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION;
-				if (!tag && ts >= exp) 
-					continue; // don't publish expired introducer
+				if (!tag || ts + SSU2_TO_INTRODUCER_SESSION_DURATION/2 > exp)
+					continue; // don't pick too old session for introducer	
 				i2p::data::RouterInfo::Introducer introducer;
 				introducer.iTag = tag;
 				introducer.iH = it->GetRemoteIdentity ()->GetIdentHash ();
@@ -1372,28 +1077,16 @@ namespace transport
 				{
 					LogPrint (eLogDebug, "SSU2: Introducer added ", it->GetRelayTag (), " at ",
 						i2p::data::GetIdentHashAbbreviation (it->GetRemoteIdentity ()->GetIdentHash ()));
-					newList.push_back ({ it->GetRemoteIdentity ()->GetIdentHash (), tag });
-					it->SendKeepAlive ();
+					newList.push_back (it->GetRemoteIdentity ()->GetIdentHash ());
 					if (newList.size () >= SSU2_MAX_NUM_INTRODUCERS) break;
 				}
 			}
 		}
 		introducers = newList;
 
-		if (introducers.size () < SSU2_MAX_NUM_INTRODUCERS || numOldSessions)
+		if (introducers.size () < SSU2_MAX_NUM_INTRODUCERS)
 		{
-			// we need to create more sessions with relay tag
-			
-			// exclude all existing sessions
-			excluded.clear ();
-			{
-				std::lock_guard<std::mutex> l(m_SessionsByRouterHashMutex);
-				for (const auto& [ident, s] : m_SessionsByRouterHash)
-					excluded.insert (ident);
-			}	
-
-			// session about to expire are not counted
-			for (auto i = introducers.size (); i < SSU2_MAX_NUM_INTRODUCERS + numOldSessions; i++)
+			for (auto i = introducers.size (); i < SSU2_MAX_NUM_INTRODUCERS; i++)
 			{
 				auto introducer = i2p::data::netdb.GetRandomSSU2Introducer (v4, excluded);
 				if (introducer)
@@ -1420,7 +1113,7 @@ namespace transport
 		if (m_IsPublished)
 		{
 			m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(
-				SSU2_KEEP_ALIVE_INTERVAL + m_Rng () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE));
+				SSU2_KEEP_ALIVE_INTERVAL + rand () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE));
 			m_IntroducersUpdateTimer.async_wait (std::bind (&SSU2Server::HandleIntroducersUpdateTimer,
 				this, std::placeholders::_1, true));
 		}
@@ -1434,7 +1127,7 @@ namespace transport
 			i2p::context.ClearSSU2Introducers (true);
 			m_Introducers.clear ();
 			m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(
-				(SSU2_KEEP_ALIVE_INTERVAL + m_Rng () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE)/2));
+				(SSU2_KEEP_ALIVE_INTERVAL + rand () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE)/2));
 			m_IntroducersUpdateTimer.async_wait (std::bind (&SSU2Server::HandleIntroducersUpdateTimer,
 				this, std::placeholders::_1, true));
 		}
@@ -1445,7 +1138,7 @@ namespace transport
 		if (m_IsPublished)
 		{
 			m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(
-				SSU2_KEEP_ALIVE_INTERVAL + m_Rng () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE));
+				SSU2_KEEP_ALIVE_INTERVAL + rand () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE));
 			m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSU2Server::HandleIntroducersUpdateTimer,
 				this, std::placeholders::_1, false));
 		}
@@ -1459,7 +1152,7 @@ namespace transport
 			i2p::context.ClearSSU2Introducers (false);
 			m_IntroducersV6.clear ();
 			m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(
-				(SSU2_KEEP_ALIVE_INTERVAL + m_Rng () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE)/2));
+				(SSU2_KEEP_ALIVE_INTERVAL + rand () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE)/2));
 			m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSU2Server::HandleIntroducersUpdateTimer,
 				this, std::placeholders::_1, false));
 		}
@@ -1519,23 +1212,6 @@ namespace transport
 		}
 	}
 
-	bool SSU2Server::AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen,
-		const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len)
-	{
-		return m_Encryptor.Encrypt (msg, msgLen, ad, adLen, key, nonce, buf, len);
-	}
-
-	bool SSU2Server::AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen,
-		const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len)
-	{
-		return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len);
-	}
-
-	void SSU2Server::ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out)
-	{
-		m_ChaCha20 (msg, msgLen, key, nonce, out);
-	}	
-		
 	void SSU2Server::SendThroughProxy (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen,
 		const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to)
 	{
@@ -1784,7 +1460,7 @@ namespace transport
 	bool SSU2Server::SetProxy (const std::string& address, uint16_t port)
 	{
 		boost::system::error_code ecode;
-		auto addr = boost::asio::ip::make_address (address, ecode);
+		auto addr = boost::asio::ip::address::from_string (address, ecode);
 		if (!ecode && !addr.is_unspecified () && port)
 		{
 			m_IsThroughProxy = true;
diff --git a/libi2pd/SSU2.h b/libi2pd/SSU2.h
index a8598ce3..03e22245 100644
--- a/libi2pd/SSU2.h
+++ b/libi2pd/SSU2.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2022-2025, The PurpleI2P Project
+* Copyright (c) 2022-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -10,42 +10,28 @@
 #define SSU2_H__
 
 #include <unordered_map>
-#include <unordered_set>
-#include <vector>
-#include <list>
-#include <array>
 #include <mutex>
-#include <random>
 #include "util.h"
 #include "SSU2Session.h"
-#include "SSU2OutOfSession.h"
-#include "Socks5.h"
 
 namespace i2p
 {
 namespace transport
 {
-	const int SSU2_TERMINATION_CHECK_TIMEOUT = 23; // in seconds
-	const int SSU2_TERMINATION_CHECK_TIMEOUT_VARIANCE = 5; // in seconds
+	const int SSU2_TERMINATION_CHECK_TIMEOUT = 25; // in seconds
 	const int SSU2_CLEANUP_INTERVAL = 72; // in seconds
-	const int SSU2_RESEND_CHECK_TIMEOUT = 40; // in milliseconds
-	const int SSU2_RESEND_CHECK_TIMEOUT_VARIANCE = 10; // in milliseconds
-	const int SSU2_RESEND_CHECK_MORE_TIMEOUT = 4; // in milliseconds
-	const int SSU2_RESEND_CHECK_MORE_TIMEOUT_VARIANCE = 9; // in milliseconds
+	const int SSU2_RESEND_CHECK_TIMEOUT = 400; // in milliseconds
+	const int SSU2_RESEND_CHECK_TIMEOUT_VARIANCE = 100; // in milliseconds
+	const int SSU2_RESEND_CHECK_MORE_TIMEOUT = 10; // in milliseconds
 	const size_t SSU2_MAX_RESEND_PACKETS = 128; // packets to resend at the time
-	const uint64_t SSU2_SOCKET_MIN_BUFFER_SIZE = 128 * 1024;
-	const uint64_t SSU2_SOCKET_MAX_BUFFER_SIZE = 4 * 1024 * 1024;
+	const size_t SSU2_SOCKET_RECEIVE_BUFFER_SIZE = 0x1FFFF; // 128K
+	const size_t SSU2_SOCKET_SEND_BUFFER_SIZE = 0x1FFFF; // 128K
 	const size_t SSU2_MAX_NUM_INTRODUCERS = 3;
-	const size_t SSU2_MIN_RECEIVED_PACKET_SIZE = 40; // 16 byte short header + 8 byte minimum payload + 16 byte MAC
-	const size_t SSU2_MAX_RECEIVED_QUEUE_SIZE = 2500; // in packets
 	const int SSU2_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour
 	const int SSU2_TO_INTRODUCER_SESSION_EXPIRATION = 4800; // 80 minutes
 	const int SSU2_KEEP_ALIVE_INTERVAL = 15; // in seconds
 	const int SSU2_KEEP_ALIVE_INTERVAL_VARIANCE = 4; // in seconds
 	const int SSU2_PROXY_CONNECT_RETRY_TIMEOUT = 30; // in seconds
-	const int SSU2_MIN_HOLE_PUNCH_EXPIRATION = 30; // in seconds
-	const int SSU2_MAX_HOLE_PUNCH_EXPIRATION = 160; // in seconds
-	const size_t SSU2_MAX_NUM_PACKETS_PER_BATCH = 64;
 
 	class SSU2Server: private i2p::util::RunnableServiceWithWork
 	{
@@ -55,13 +41,13 @@ namespace transport
 			size_t len;
 			boost::asio::ip::udp::endpoint from;
 		};
-	
+
 		class ReceiveService: public i2p::util::RunnableService
 		{
 			public:
 
 				ReceiveService (const std::string& name): RunnableService (name) {};
-				auto& GetService () { return GetIOService (); };
+				boost::asio::io_service& GetService () { return GetIOService (); };
 				void Start () { StartIOService (); };
 				void Stop () { StopIOService (); };
 		};
@@ -73,45 +59,29 @@ namespace transport
 
 			void Start ();
 			void Stop ();
-			auto& GetService () { return GetIOService (); };
+			boost::asio::io_service& GetService () { return GetIOService (); };
 			void SetLocalAddress (const boost::asio::ip::address& localAddress);
 			bool SetProxy (const std::string& address, uint16_t port);
 			bool UsesProxy () const { return m_IsThroughProxy; };
 			bool IsSupported (const boost::asio::ip::address& addr) const;
 			uint16_t GetPort (bool v4) const;
-			bool IsConnectedRecently (const boost::asio::ip::udp::endpoint& ep, bool max = true);
-			void AddConnectedRecently (const boost::asio::ip::udp::endpoint& ep, uint64_t ts);
-			std::mt19937& GetRng () { return m_Rng; }
-			bool AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
-				const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); 
-			bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
-				const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); 
-			void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out);
-			bool IsMaxNumIntroducers (bool v4) const { return (v4 ? m_Introducers.size () : m_IntroducersV6.size ()) >= SSU2_MAX_NUM_INTRODUCERS; }
 			bool IsSyncClockFromPeers () const { return m_IsSyncClockFromPeers; };
-			void AdjustTimeOffset (int64_t offset, std::shared_ptr<const i2p::data::IdentityEx> from);
+			void AdjustTimeOffset (int64_t offset);
 
-			bool AddSession (std::shared_ptr<SSU2Session> session);
+			void AddSession (std::shared_ptr<SSU2Session> session);
 			void RemoveSession (uint64_t connID);
-			void RequestRemoveSession (uint64_t connID);
 			void AddSessionByRouterHash (std::shared_ptr<SSU2Session> session);
 			bool AddPendingOutgoingSession (std::shared_ptr<SSU2Session> session);
 			void RemovePendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep);
-			std::shared_ptr<SSU2Session> FindSession (const i2p::data::IdentHash& ident);
+			std::shared_ptr<SSU2Session> FindSession (const i2p::data::IdentHash& ident) const;
 			std::shared_ptr<SSU2Session> FindPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) const;
-			std::shared_ptr<SSU2Session> GetRandomPeerTestSession (i2p::data::RouterInfo::CompatibleTransports remoteTransports,
-				const i2p::data::IdentHash& excluded);
+			std::shared_ptr<SSU2Session> GetRandomSession (i2p::data::RouterInfo::CompatibleTransports remoteTransports,
+				const i2p::data::IdentHash& excluded) const;
 
 			void AddRelay (uint32_t tag, std::shared_ptr<SSU2Session> relay);
 			void RemoveRelay (uint32_t tag);
 			std::shared_ptr<SSU2Session> FindRelaySession (uint32_t tag);
 
-			bool AddPeerTest (uint32_t nonce, std::shared_ptr<SSU2Session> aliceSession, uint64_t ts); 
-			std::shared_ptr<SSU2Session> GetPeerTest (uint32_t nonce);	
-		
-			bool AddRequestedPeerTest (uint32_t nonce, std::shared_ptr<SSU2PeerTestSession> session, uint64_t ts);
-			std::shared_ptr<SSU2PeerTestSession> GetRequestedPeerTest (uint32_t nonce);		
-		
 			void Send (const uint8_t * header, size_t headerLen, const uint8_t * payload, size_t payloadLen,
 				const boost::asio::ip::udp::endpoint& to);
 			void Send (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen,
@@ -139,12 +109,10 @@ namespace transport
 			void Receive (boost::asio::ip::udp::socket& socket);
 			void HandleReceivedFrom (const boost::system::error_code& ecode, size_t bytes_transferred,
 				Packet * packet, boost::asio::ip::udp::socket& socket);
-			void HandleReceivedPackets (std::list<Packet *>&& packets);
+			void HandleReceivedPacket (Packet * packet);
+			void HandleReceivedPackets (std::vector<Packet *> packets);
 			void ProcessNextPacket (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint);
-			void InsertToReceivedPacketsQueue (Packet * packet);
-			void InsertToReceivedPacketsQueue (std::list<Packet *>& packets);
-			void HandleReceivedPacketsQueue ();
-		
+
 			void ScheduleTermination ();
 			void HandleTerminationTimer (const boost::system::error_code& ecode);
 
@@ -154,10 +122,9 @@ namespace transport
 			void ScheduleResend (bool more);
 			void HandleResendTimer (const boost::system::error_code& ecode);
 
-			bool CheckPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep, bool peerTest);
 			void ConnectThroughIntroducer (std::shared_ptr<SSU2Session> session);
-			std::vector<std::shared_ptr<SSU2Session> > FindIntroducers (int maxNumIntroducers,
-				bool v4, const std::unordered_set<i2p::data::IdentHash>& excluded);
+			std::list<std::shared_ptr<SSU2Session> > FindIntroducers (int maxNumIntroducers,
+				bool v4, const std::set<i2p::data::IdentHash>& excluded) const;
 			void UpdateIntroducers (bool v4);
 			void ScheduleIntroducersUpdateTimer ();
 			void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4);
@@ -180,14 +147,12 @@ namespace transport
 			boost::asio::ip::udp::socket m_SocketV4, m_SocketV6;
 			boost::asio::ip::address m_AddressV4, m_AddressV6;
 			std::unordered_map<uint64_t, std::shared_ptr<SSU2Session> > m_Sessions;
-			std::unordered_map<i2p::data::IdentHash, std::weak_ptr<SSU2Session> > m_SessionsByRouterHash;
-			mutable std::mutex m_SessionsByRouterHashMutex;
+			std::unordered_map<i2p::data::IdentHash, std::shared_ptr<SSU2Session> > m_SessionsByRouterHash;
 			std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<SSU2Session> > m_PendingOutgoingSessions;
 			mutable std::mutex m_PendingOutgoingSessionsMutex;
 			std::map<boost::asio::ip::udp::endpoint, std::pair<uint64_t, uint32_t> > m_IncomingTokens, m_OutgoingTokens; // remote endpoint -> (token, expires in seconds)
-			std::unordered_map<uint32_t, std::weak_ptr<SSU2Session> > m_Relays; // we are introducer, relay tag -> session
-			std::unordered_map<uint32_t, std::pair <std::weak_ptr<SSU2Session>, uint64_t > > m_PeerTests; // nonce->(Alice, timestamp). We are Bob
-			std::list<std::pair<i2p::data::IdentHash, uint32_t> > m_Introducers, m_IntroducersV6; // introducers we are connected to
+			std::map<uint32_t, std::shared_ptr<SSU2Session> > m_Relays; // we are introducer, relay tag -> session
+			std::list<i2p::data::IdentHash> m_Introducers, m_IntroducersV6; // introducers we are connected to
 			i2p::util::MemoryPoolMt<Packet> m_PacketsPool;
 			i2p::util::MemoryPool<SSU2SentPacket> m_SentPacketsPool;
 			i2p::util::MemoryPool<SSU2IncompleteMessage> m_IncompleteMessagesPool;
@@ -198,17 +163,7 @@ namespace transport
 			bool m_IsPublished; // if we maintain introducers
 			bool m_IsSyncClockFromPeers;
 			int64_t m_PendingTimeOffset; // during peer test
-			std::shared_ptr<const i2p::data::IdentityEx> m_PendingTimeOffsetFrom;
-			std::mt19937 m_Rng;
-			std::map<boost::asio::ip::udp::endpoint, uint64_t> m_ConnectedRecently; // endpoint -> last activity time in seconds
-			mutable std::mutex m_ConnectedRecentlyMutex;
-			std::unordered_map<uint32_t, std::pair <std::weak_ptr<SSU2PeerTestSession>, uint64_t > > m_RequestedPeerTests; // nonce->(Alice, timestamp) 
-			std::list<Packet *> m_ReceivedPacketsQueue;
-			mutable std::mutex m_ReceivedPacketsQueueMutex;
-			i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor;
-			i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor;
-			i2p::crypto::ChaCha20Context m_ChaCha20;
-		
+
 			// proxy
 			bool m_IsThroughProxy;
 			uint8_t m_UDPRequestHeader[SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE];
diff --git a/libi2pd/SSU2OutOfSession.cpp b/libi2pd/SSU2OutOfSession.cpp
deleted file mode 100644
index 3760e329..00000000
--- a/libi2pd/SSU2OutOfSession.cpp
+++ /dev/null
@@ -1,345 +0,0 @@
-/*
-* Copyright (c) 2024-2025, The PurpleI2P Project
-*
-* This file is part of Purple i2pd project and licensed under BSD3
-*
-* See full license text in LICENSE file at top of project tree
-*/
-
-#include "Log.h"
-#include "SSU2.h"
-#include "SSU2OutOfSession.h"
-
-namespace i2p
-{
-namespace transport
-{
-	SSU2PeerTestSession::SSU2PeerTestSession (SSU2Server& server, uint64_t sourceConnID, uint64_t destConnID): 
-		SSU2Session (server, nullptr, nullptr, false), 
-		m_MsgNumReceived (0), m_NumResends (0),m_IsConnectedRecently (false), m_IsStatusChanged (false),
-		m_PeerTestResendTimer (server.GetService ())
-	{
-		if (!sourceConnID) sourceConnID = ~destConnID;
-		if (!destConnID) destConnID = ~sourceConnID;
-		SetSourceConnID (sourceConnID);
-		SetDestConnID (destConnID);	
-		SetState (eSSU2SessionStatePeerTest);	
-		SetTerminationTimeout (SSU2_PEER_TEST_EXPIRATION_TIMEOUT);
-	}	
-
-	bool SSU2PeerTestSession::ProcessPeerTest (uint8_t * buf, size_t len)
-	{
-		// we are Alice or Charlie, msgs 5,6,7
-		Header header;
-		memcpy (header.buf, buf, 16);
-		header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 24));
-		header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 12));
-		if (header.h.type != eSSU2PeerTest)
-		{
-			LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2PeerTest);
-			return false;
-		}
-		if (len < 48)
-		{
-			LogPrint (eLogWarning, "SSU2: PeerTest message too short ", len);
-			return false;
-		}
-		uint8_t nonce[12] = {0};
-		uint64_t headerX[2]; // sourceConnID, token
-		GetServer ().ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX);
-		SetDestConnID (headerX[0]);
-		// decrypt and handle payload
-		uint8_t * payload = buf + 32;
-		CreateNonce (be32toh (header.h.packetNum), nonce);
-		uint8_t h[32];
-		memcpy (h, header.buf, 16);
-		memcpy (h + 16, &headerX, 16);
-		if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 48, h, 32,
-			i2p::context.GetSSU2IntroKey (), nonce, payload, len - 48, false))
-		{
-			LogPrint (eLogWarning, "SSU2: PeerTest AEAD verification failed ");
-			return false;
-		}
-		HandlePayload (payload, len - 48);
-		SetIsDataReceived (false);
-		return true;
-	}	
-
-	void SSU2PeerTestSession::HandleAddress (const uint8_t * buf, size_t len)
-	{
-		if (!ExtractEndpoint (buf, len, m_OurEndpoint))
-			LogPrint (eLogWarning, "SSU2: Can't handle address block from peer test message");
-	}	
-		
-	void SSU2PeerTestSession::HandlePeerTest (const uint8_t * buf, size_t len)
-	{
-		// msgs 5-7
-		if (len < 8) return;
-		uint8_t msg = buf[0];
-		if (msg <= m_MsgNumReceived)
-		{
-			LogPrint (eLogDebug, "SSU2: PeerTest msg num ", msg, " received after ", m_MsgNumReceived, ". Ignored");
-			return;
-		}	
-		size_t offset = 3; // points to signed data after msg + code + flag
-		uint32_t nonce = bufbe32toh (buf + offset + 1); // 1 - ver
-		switch (msg) // msg
-		{
-			case 5: // Alice from Charlie 1
-			{	
-				if (htobe64 (((uint64_t)nonce << 32) | nonce) == GetSourceConnID ())
-				{
-					m_PeerTestResendTimer.cancel (); // cancel delayed msg 6 if any
-					m_IsConnectedRecently = GetServer ().IsConnectedRecently (GetRemoteEndpoint ());
-					if (GetAddress ())
-					{
-						if (!m_IsConnectedRecently)
-							SetRouterStatus (eRouterStatusOK);
-						else if (m_IsStatusChanged && GetRouterStatus () == eRouterStatusFirewalled)
-							SetRouterStatus (eRouterStatusUnknown);
-						SendPeerTest (6, buf + offset, len - offset);
-					}
-				}
-				else
-					LogPrint (eLogWarning, "SSU2: Peer test 5 nonce mismatch ", nonce, " connID=", GetSourceConnID ());
-				break;
-			}
-			case 6: // Charlie from Alice
-			{	
-				m_PeerTestResendTimer.cancel (); // no more msg 5 resends
-				if (GetAddress ())
-					SendPeerTest (7, buf + offset, len - offset);
-				else
-					LogPrint (eLogWarning, "SSU2: Unknown address for peer test 6");
-				GetServer ().RequestRemoveSession (GetConnID ());
-				break;
-			}			
-			case 7: // Alice from Charlie 2
-			{	
-				m_PeerTestResendTimer.cancel (); // no more msg 6 resends
-				if (m_MsgNumReceived < 5 && m_OurEndpoint.port ()) // msg 5 was not received
-				{
-					if (m_OurEndpoint.address ().is_v4 ()) // ipv4
-					{
-						if (i2p::context.GetStatus () == eRouterStatusFirewalled)
-						{	
-						    if (m_OurEndpoint.port () != GetServer ().GetPort (true))
-								i2p::context.SetError (eRouterErrorSymmetricNAT);
-							else if (i2p::context.GetError () == eRouterErrorSymmetricNAT)
-								i2p::context.SetError (eRouterErrorNone);
-						}
-					}	
-					else
-					{
-						if (i2p::context.GetStatusV6 () == eRouterStatusFirewalled)
-						{	
-						    if (m_OurEndpoint.port () != GetServer ().GetPort (false))
-								i2p::context.SetErrorV6 (eRouterErrorSymmetricNAT);
-							else if (i2p::context.GetErrorV6 () == eRouterErrorSymmetricNAT)
-								i2p::context.SetErrorV6 (eRouterErrorNone);
-						}
-					}	
-				}	
-				GetServer ().RequestRemoveSession (GetConnID ());	
-				break;
-			}	
-			default:	
-				LogPrint (eLogWarning, "SSU2: PeerTest unexpected msg num ", msg);
-				return;
-		}	
-		m_MsgNumReceived = msg;	
-	}
-
-	void SSU2PeerTestSession::SendPeerTest (uint8_t msg)
-	{
-		auto addr = GetAddress ();
-		if (!addr) return;
-		Header header;
-		uint8_t h[32], payload[SSU2_MAX_PACKET_SIZE];
-		// fill packet
-		header.h.connID = GetDestConnID (); // dest id
-		RAND_bytes (header.buf + 8, 4); // random packet num
-		header.h.type = eSSU2PeerTest;
-		header.h.flags[0] = 2; // ver
-		header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID
-		header.h.flags[2] = 0; // flag
-		memcpy (h, header.buf, 16);
-		htobuf64 (h + 16, GetSourceConnID ()); // source id
-		// payload
-		payload[0] = eSSU2BlkDateTime;
-		htobe16buf (payload + 1, 4);
-		htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000);
-		size_t payloadSize = 7;
-		if (msg == 6 || msg == 7)
-			payloadSize += CreateAddressBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize, GetRemoteEndpoint ());
-		payloadSize += CreatePeerTestBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize,
-			msg, eSSU2PeerTestCodeAccept, nullptr, m_SignedData.data (), m_SignedData.size ());
-		payloadSize += CreatePaddingBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize);
-		// encrypt
-		uint8_t n[12];
-		CreateNonce (be32toh (header.h.packetNum), n);
-		i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, addr->i, n, payload, payloadSize + 16, true);
-		payloadSize += 16;
-		header.ll[0] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 24));
-		header.ll[1] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 12));
-		memset (n, 0, 12);
-		GetServer ().ChaCha20 (h + 16, 16, addr->i, n, h + 16);
-		// send
-		GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, GetRemoteEndpoint ());
-		UpdateNumSentBytes (payloadSize + 32);
-	}	
-
-	void SSU2PeerTestSession::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, bool delayed)
-	{
-		m_SignedData.assign (signedData, signedData + signedDataLen);
-		if (!delayed)
-			SendPeerTest (msg);
-		// schedule resend for msgs 5 or 6
-		if (msg == 5 || msg == 6)
-			ScheduleResend (msg);
-	}	
-		
-	void SSU2PeerTestSession::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, 
-		std::shared_ptr<const i2p::data::RouterInfo::Address> addr, bool delayed)
-	{
-		if (!addr) return;
-		SetAddress (addr);
-		SendPeerTest (msg, signedData, signedDataLen, delayed);	
-	}	
-
-	void SSU2PeerTestSession::Connect ()
-	{
-		LogPrint (eLogError, "SSU2: Can't connect peer test session");
-	}	
-
-	bool SSU2PeerTestSession::ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len)
-	{
-		LogPrint (eLogError, "SSU2: Can't handle incoming message in peer test session");
-		return false;
-	}	
-
-	void SSU2PeerTestSession::ScheduleResend (uint8_t msg)
-	{
-		if (m_NumResends < SSU2_PEER_TEST_MAX_NUM_RESENDS)
-		{
-			m_PeerTestResendTimer.expires_from_now (boost::posix_time::milliseconds(
-				SSU2_PEER_TEST_RESEND_INTERVAL + GetServer ().GetRng ()() % SSU2_PEER_TEST_RESEND_INTERVAL_VARIANCE));
-			std::weak_ptr<SSU2PeerTestSession> s(std::static_pointer_cast<SSU2PeerTestSession>(shared_from_this ()));
-			m_PeerTestResendTimer.async_wait ([s, msg](const boost::system::error_code& ecode)
-				{
-					if (ecode != boost::asio::error::operation_aborted)
-					{
-						auto s1 = s.lock ();
-						if (s1) 
-						{
-							if (msg > s1->m_MsgNumReceived) 
-							{	
-								s1->SendPeerTest (msg);
-								s1->m_NumResends++;
-								s1->ScheduleResend (msg);
-							}	
-						}	
-					}	
-				});
-		}	
-	}	
-
-	SSU2HolePunchSession::SSU2HolePunchSession (SSU2Server& server, uint32_t nonce,
-		const boost::asio::ip::udp::endpoint& remoteEndpoint,
-		std::shared_ptr<const i2p::data::RouterInfo::Address> addr):
-		SSU2Session (server), // we create full incoming session
-		m_NumResends (0), m_HolePunchResendTimer (server.GetService ())
-	{
-		// we are Charlie
-		uint64_t destConnID = htobe64 (((uint64_t)nonce << 32) | nonce); // dest id
-		uint64_t sourceConnID = ~destConnID;
-		SetSourceConnID (sourceConnID);
-		SetDestConnID (destConnID);	
-		SetState (eSSU2SessionStateHolePunch);
-		SetRemoteEndpoint (remoteEndpoint);
-		SetAddress (addr);
-		SetTerminationTimeout (SSU2_RELAY_NONCE_EXPIRATION_TIMEOUT);	
-	}	
-
-	void SSU2HolePunchSession::SendHolePunch ()
-	{
-		auto addr = GetAddress ();
-		if (!addr) return;
-		auto& ep = GetRemoteEndpoint ();
-		LogPrint (eLogDebug, "SSU2: Sending HolePunch to ", ep);
-		Header header;
-		uint8_t h[32], payload[SSU2_MAX_PACKET_SIZE];
-		// fill packet
-		header.h.connID = GetDestConnID (); // dest id
-		RAND_bytes (header.buf + 8, 4); // random packet num
-		header.h.type = eSSU2HolePunch;
-		header.h.flags[0] = 2; // ver
-		header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID
-		header.h.flags[2] = 0; // flag
-		memcpy (h, header.buf, 16);
-		htobuf64 (h + 16, GetSourceConnID ()); // source id
-		RAND_bytes (h + 24, 8); // header token, to be ignored by Alice
-		// payload
-		payload[0] = eSSU2BlkDateTime;
-		htobe16buf (payload + 1, 4);
-		htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000);
-		size_t payloadSize = 7;
-		payloadSize += CreateAddressBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize, ep);
-		// relay response block	
-		if (payloadSize + m_RelayResponseBlock.size () < GetMaxPayloadSize ())
-		{	
-			memcpy (payload + payloadSize, m_RelayResponseBlock.data (), m_RelayResponseBlock.size ());
-			payloadSize += m_RelayResponseBlock.size ();
-		}	
-		payloadSize += CreatePaddingBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize);
-		// encrypt
-		uint8_t n[12];
-		CreateNonce (be32toh (header.h.packetNum), n);
-		i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, addr->i, n, payload, payloadSize + 16, true);
-		payloadSize += 16;
-		header.ll[0] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 24));
-		header.ll[1] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 12));
-		memset (n, 0, 12);
-		GetServer ().ChaCha20 (h + 16, 16, addr->i, n, h + 16);
-		// send
-		GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, ep);
-		UpdateNumSentBytes (payloadSize + 32);
-	}	
-
-	void SSU2HolePunchSession::SendHolePunch (const uint8_t * relayResponseBlock, size_t relayResponseBlockLen)
-	{
-		m_RelayResponseBlock.assign (relayResponseBlock, relayResponseBlock + relayResponseBlockLen);
-		SendHolePunch ();
-		ScheduleResend ();
-	}	
-
-	void SSU2HolePunchSession::ScheduleResend ()
-	{
-		if (m_NumResends < SSU2_HOLE_PUNCH_MAX_NUM_RESENDS)
-		{
-			m_HolePunchResendTimer.expires_from_now (boost::posix_time::milliseconds(
-				SSU2_HOLE_PUNCH_RESEND_INTERVAL + GetServer ().GetRng ()() % SSU2_HOLE_PUNCH_RESEND_INTERVAL_VARIANCE));
-			std::weak_ptr<SSU2HolePunchSession> s(std::static_pointer_cast<SSU2HolePunchSession>(shared_from_this ()));
-			m_HolePunchResendTimer.async_wait ([s](const boost::system::error_code& ecode)
-				{
-					if (ecode != boost::asio::error::operation_aborted)
-					{
-						auto s1 = s.lock ();
-						if (s1 && s1->GetState () == eSSU2SessionStateHolePunch) 
-						{
-							s1->SendHolePunch ();
-							s1->m_NumResends++;
-							s1->ScheduleResend ();	
-						}	
-					}	
-				});
-		}	
-	}
-
-	bool SSU2HolePunchSession::ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len)
-	{
-		m_HolePunchResendTimer.cancel ();
-		return SSU2Session::ProcessFirstIncomingMessage (connID, buf, len);
-	}	
-}
-}
diff --git a/libi2pd/SSU2OutOfSession.h b/libi2pd/SSU2OutOfSession.h
deleted file mode 100644
index e8c55c3c..00000000
--- a/libi2pd/SSU2OutOfSession.h
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
-* Copyright (c) 2024, The PurpleI2P Project
-*
-* This file is part of Purple i2pd project and licensed under BSD3
-*
-* See full license text in LICENSE file at top of project tree
-*/
-
-#ifndef SSU2_OUT_OF_SESSION_H__
-#define SSU2_OUT_OF_SESSION_H__
-
-#include <vector>
-#include "SSU2Session.h"
-
-namespace i2p
-{
-namespace transport
-{
-	const int SSU2_PEER_TEST_RESEND_INTERVAL = 3000; // in milliseconds
-	const int SSU2_PEER_TEST_RESEND_INTERVAL_VARIANCE = 2000; // in milliseconds
-	const int SSU2_PEER_TEST_MAX_NUM_RESENDS = 3;
-	
-	class SSU2PeerTestSession: public SSU2Session // for PeerTest msgs 5,6,7
-	{
-		public:
-
-			SSU2PeerTestSession (SSU2Server& server, uint64_t sourceConnID, uint64_t destConnID);
-
-			uint8_t GetMsgNumReceived () const { return m_MsgNumReceived; }	
-			bool IsConnectedRecently () const { return m_IsConnectedRecently; }
-			void SetStatusChanged () { m_IsStatusChanged = true; }
-			
-			void SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, 
-				std::shared_ptr<const i2p::data::RouterInfo::Address> addr, bool delayed = false);
-			bool ProcessPeerTest (uint8_t * buf, size_t len) override;
-			void Connect () override; // outgoing
-			bool ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len) override; // incoming
-			
-		private:
-
-			void SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, bool delayed = false); // PeerTest message
-			void SendPeerTest (uint8_t msg); // send or resend m_SignedData
-			void HandlePeerTest (const uint8_t * buf, size_t len) override;
-			void HandleAddress (const uint8_t * buf, size_t len) override;
-
-			void ScheduleResend (uint8_t msg);
-			
-		private:
-
-			uint8_t m_MsgNumReceived, m_NumResends;
-			bool m_IsConnectedRecently, m_IsStatusChanged;
-			std::vector<uint8_t> m_SignedData; // for resends
-			boost::asio::deadline_timer m_PeerTestResendTimer;
-			boost::asio::ip::udp::endpoint m_OurEndpoint; // as seen by peer
-	};	
-
-	const int SSU2_HOLE_PUNCH_RESEND_INTERVAL = 1000; // in milliseconds
-	const int SSU2_HOLE_PUNCH_RESEND_INTERVAL_VARIANCE = 500; // in milliseconds
-	const int SSU2_HOLE_PUNCH_MAX_NUM_RESENDS = 3;
-	
-	class SSU2HolePunchSession: public SSU2Session // Charlie
-	{
-		public:
-
-			SSU2HolePunchSession (SSU2Server& server, uint32_t nonce, const boost::asio::ip::udp::endpoint& remoteEndpoint,
-				std::shared_ptr<const i2p::data::RouterInfo::Address> addr);
-
-			void SendHolePunch (const uint8_t * relayResponseBlock, size_t relayResponseBlockLen);
-
-			bool ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len) override; // SessionRequest
-			
-		private:
-			
-			void SendHolePunch ();
-			void ScheduleResend ();
-			
-		private:
-
-			int m_NumResends;
-			std::vector<uint8_t> m_RelayResponseBlock;
-			boost::asio::deadline_timer m_HolePunchResendTimer;
-	};	
-}
-}
-	
-#endif
diff --git a/libi2pd/SSU2Session.cpp b/libi2pd/SSU2Session.cpp
index cb10f848..e5261622 100644
--- a/libi2pd/SSU2Session.cpp
+++ b/libi2pd/SSU2Session.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2022-2025, The PurpleI2P Project
+* Copyright (c) 2022-2024, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -13,7 +13,6 @@
 #include "Gzip.h"
 #include "NetDb.hpp"
 #include "SSU2.h"
-#include "SSU2Session.h"
 
 namespace i2p
 {
@@ -80,40 +79,30 @@ namespace transport
 	}
 
 	SSU2Session::SSU2Session (SSU2Server& server, std::shared_ptr<const i2p::data::RouterInfo> in_RemoteRouter,
-		std::shared_ptr<const i2p::data::RouterInfo::Address> addr, bool noise):
+		std::shared_ptr<const i2p::data::RouterInfo::Address> addr):
 		TransportSession (in_RemoteRouter, SSU2_CONNECT_TIMEOUT),
-		m_Server (server), m_Address (addr), m_RemoteTransports (0), m_RemotePeerTestTransports (0),
-		m_RemoteVersion (0), m_DestConnID (0), m_SourceConnID (0), m_State (eSSU2SessionStateUnknown),
+		m_Server (server), m_Address (addr), m_RemoteTransports (0),
+		m_DestConnID (0), m_SourceConnID (0), m_State (eSSU2SessionStateUnknown),
 		m_SendPacketNum (0), m_ReceivePacketNum (0), m_LastDatetimeSentPacketNum (0),
-		m_IsDataReceived (false), m_RTT (SSU2_UNKNOWN_RTT),
-		m_MsgLocalExpirationTimeout (I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MAX),
-		m_MsgLocalSemiExpirationTimeout (I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MAX / 2),
-		m_WindowSize (SSU2_MIN_WINDOW_SIZE),
-		m_RTO (SSU2_INITIAL_RTO), m_RelayTag (0),m_ConnectTimer (server.GetService ()), 
-		m_TerminationReason (eSSU2TerminationReasonNormalClose),
-		m_MaxPayloadSize (SSU2_MIN_PACKET_SIZE - IPV6_HEADER_SIZE - UDP_HEADER_SIZE - 32), // min size
-		m_LastResendTime (0), m_LastResendAttemptTime (0), m_NumRanges (0)
+		m_IsDataReceived (false), m_WindowSize (SSU2_MIN_WINDOW_SIZE),
+		m_RTT (SSU2_RESEND_INTERVAL), m_RTO (SSU2_RESEND_INTERVAL*SSU2_kAPPA), m_RelayTag (0),
+		m_ConnectTimer (server.GetService ()), m_TerminationReason (eSSU2TerminationReasonNormalClose),
+		m_MaxPayloadSize (SSU2_MIN_PACKET_SIZE - IPV6_HEADER_SIZE - UDP_HEADER_SIZE - 32) // min size
 	{
-		if (noise)	
-			m_NoiseState.reset (new i2p::crypto::NoiseSymmetricState);
+		m_NoiseState.reset (new i2p::crypto::NoiseSymmetricState);
 		if (in_RemoteRouter && m_Address)
 		{
 			// outgoing
-			if (noise)
-				InitNoiseXKState1 (*m_NoiseState, m_Address->s);
+			InitNoiseXKState1 (*m_NoiseState, m_Address->s);
 			m_RemoteEndpoint = boost::asio::ip::udp::endpoint (m_Address->host, m_Address->port);
 			m_RemoteTransports = in_RemoteRouter->GetCompatibleTransports (false);
-			m_RemoteVersion = in_RemoteRouter->GetVersion ();
-			if (in_RemoteRouter->IsSSU2PeerTesting (true)) m_RemotePeerTestTransports |= i2p::data::RouterInfo::eSSU2V4;
-			if (in_RemoteRouter->IsSSU2PeerTesting (false)) m_RemotePeerTestTransports |= i2p::data::RouterInfo::eSSU2V6;
 			RAND_bytes ((uint8_t *)&m_DestConnID, 8);
 			RAND_bytes ((uint8_t *)&m_SourceConnID, 8);
 		}
 		else
 		{
 			// incoming
-			if (noise)
-				InitNoiseXKState1 (*m_NoiseState, i2p::context.GetSSU2StaticPublicKey ());
+			InitNoiseXKState1 (*m_NoiseState, i2p::context.GetSSU2StaticPublicKey ());
 		}
 	}
 
@@ -149,7 +138,7 @@ namespace transport
 
 	void SSU2Session::HandleConnectTimer (const boost::system::error_code& ecode)
 	{
-		if (!ecode && m_State != eSSU2SessionStateTerminated)
+		if (!ecode)
 		{
 			// timeout expired
 			if (m_State == eSSU2SessionStateIntroduced) // WaitForIntroducer
@@ -166,47 +155,40 @@ namespace transport
 		if (!session || !relayTag) return false;
 		// find local address to introduce
 		auto localAddress = session->FindLocalAddress ();
-		if (!localAddress || localAddress->host.is_unspecified () || !localAddress->port) 
-		{	
-			// can't introduce invalid endpoint
-			LogPrint (eLogWarning, "SSU2: Can't find local address to introduce");
-			return false; 
-		}	
+		if (!localAddress) return false;
 		// create nonce
 		uint32_t nonce;
 		RAND_bytes ((uint8_t *)&nonce, 4);
-		auto ts = i2p::util::GetMillisecondsSinceEpoch ();
+		auto ts = i2p::util::GetSecondsSinceEpoch ();
 		// payload
-		auto packet = m_Server.GetSentPacketsPool ().AcquireShared ();
-		uint8_t * payload = packet->payload;
+		uint8_t payload[SSU2_MAX_PACKET_SIZE];
+		size_t payloadSize = 0;
 		payload[0] = eSSU2BlkRelayRequest;
 		payload[3] = 0; // flag
 		htobe32buf (payload + 4, nonce);
 		htobe32buf (payload + 8, relayTag);
-		htobe32buf (payload + 12, ts/1000);
+		htobe32buf (payload + 12, ts);
 		payload[16] = 2; // ver
 		size_t asz = CreateEndpoint (payload + 18, m_MaxPayloadSize - 18, boost::asio::ip::udp::endpoint (localAddress->host, localAddress->port));
 		if (!asz) return false;
 		payload[17] = asz;
-		packet->payloadSize = asz + 18;
-		SignedData<128> s;
+		payloadSize += asz + 18;
+		SignedData s;
 		s.Insert ((const uint8_t *)"RelayRequestData", 16); // prologue
 		s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
 		s.Insert (session->GetRemoteIdentity ()->GetIdentHash (), 32); // chash
 		s.Insert (payload + 4, 14 + asz); // nonce, relay tag, timestamp, ver, asz and Alice's endpoint
-		s.Sign (i2p::context.GetPrivateKeys (), payload + packet->payloadSize);
-		packet->payloadSize += i2p::context.GetIdentity ()->GetSignatureLen ();
-		htobe16buf (payload + 1, packet->payloadSize - 3); // size
-		packet->payloadSize += CreatePaddingBlock (payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize);
+		s.Sign (i2p::context.GetPrivateKeys (), payload + payloadSize);
+		payloadSize += i2p::context.GetIdentity ()->GetSignatureLen ();
+		htobe16buf (payload + 1, payloadSize - 3); // size
+		payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
 		// send
-		m_RelaySessions.emplace (nonce, std::make_pair (session, ts/1000));
+		m_RelaySessions.emplace (nonce, std::make_pair (session, ts));
 		session->m_SourceConnID = htobe64 (((uint64_t)nonce << 32) | nonce);
 		session->m_DestConnID = ~session->m_SourceConnID;
 		m_Server.AddSession (session);
-		int32_t packetNum = SendData (packet->payload, packet->payloadSize);
-		packet->sendTime = ts;
-		m_SentPackets.emplace (packetNum, packet);
-		
+		SendData (payload, payloadSize);
+
 		return true;
 	}
 
@@ -220,29 +202,15 @@ namespace transport
 	{
 		if (m_State == eSSU2SessionStateIntroduced)
 		{
-			// we are Alice
-			//  keep ConnIDs used for introduction, because Charlie waits for SessionRequest from us
+			// create new connID
+			uint64_t oldConnID = GetConnID ();
+			RAND_bytes ((uint8_t *)&m_DestConnID, 8);
+			RAND_bytes ((uint8_t *)&m_SourceConnID, 8);
+			// connect
 			m_State = eSSU2SessionStateTokenReceived;
-			// move session to pending outgoing
-			if (m_Server.AddPendingOutgoingSession (shared_from_this ()))
-			{                                                             
-				m_Server.RemoveSession (GetConnID ());
-				// update endpoint in profile because we know it now 
-				auto identity = GetRemoteIdentity ();
-				if (identity)
-				{	
-					auto profile = i2p::data::GetRouterProfile (identity->GetIdentHash ());
-					if (profile) profile->SetLastEndpoint (m_RemoteEndpoint);
-				}	
-				// connect
-				LogPrint (eLogDebug, "SSU2: Connecting after introduction to ", GetIdentHashBase64());
-				Connect ();
-			}
-			else 
-			{
-				LogPrint (eLogError, "SSU2: Session ", GetConnID (), " is already pending");
-				m_Server.RequestRemoveSession (GetConnID ());
-			}	
+			m_Server.AddPendingOutgoingSession (shared_from_this ());
+			m_Server.RemoveSession (oldConnID);
+			Connect ();
 		}
 	}
 
@@ -253,9 +221,11 @@ namespace transport
 		RAND_bytes ((uint8_t *)&nonce, 4);
 		auto ts = i2p::util::GetMillisecondsSinceEpoch ();
 		// session for message 5
-		auto session = std::make_shared<SSU2PeerTestSession> (m_Server, 
-			htobe64 (((uint64_t)nonce << 32) | nonce), 0);
-		m_Server.AddRequestedPeerTest (nonce, session, ts/1000);
+		auto session = std::make_shared<SSU2Session> (m_Server);
+		session->SetState (eSSU2SessionStatePeerTest);
+		m_PeerTests.emplace (nonce, std::make_pair (session, ts/1000));
+		session->m_SourceConnID = htobe64 (((uint64_t)nonce << 32) | nonce);
+		session->m_DestConnID = ~session->m_SourceConnID;
 		m_Server.AddSession (session);
 		// peer test block
 		auto packet = m_Server.GetSentPacketsPool ().AcquireShared ();
@@ -276,7 +246,7 @@ namespace transport
 		{
 			uint8_t payload[20];
 			size_t payloadSize = CreatePaddingBlock (payload, 20, 8);
-			SendData (payload, payloadSize, SSU2_FLAG_IMMEDIATE_ACK_REQUESTED);
+			SendData (payload, payloadSize);
 		}
 	}
 
@@ -289,28 +259,28 @@ namespace transport
 			m_OnEstablished = nullptr;
 			if (m_RelayTag)
 				m_Server.RemoveRelay (m_RelayTag);
-			m_Server.AddConnectedRecently (m_RemoteEndpoint, GetLastActivityTimestamp ());
 			m_SentHandshakePacket.reset (nullptr);
 			m_SessionConfirmedFragment.reset (nullptr);
 			m_PathChallenge.reset (nullptr);
-			if (!m_IntermediateQueue.empty ())
-				m_SendQueue.splice (m_SendQueue.end (), m_IntermediateQueue);
-			for (auto& it: m_SendQueue)
-				it->Drop ();
 			m_SendQueue.clear ();
 			SetSendQueueSize (0);
 			m_SentPackets.clear ();
 			m_IncompleteMessages.clear ();
 			m_RelaySessions.clear ();
+			m_PeerTests.clear ();
 			m_ReceivedI2NPMsgIDs.clear ();
 			m_Server.RemoveSession (m_SourceConnID);
 			transports.PeerDisconnected (shared_from_this ());
 			auto remoteIdentity = GetRemoteIdentity ();
 			if (remoteIdentity)
+			{
 				LogPrint (eLogDebug, "SSU2: Session with ", GetRemoteEndpoint (),
-					" (", i2p::data::GetIdentHashAbbreviation (remoteIdentity->GetIdentHash ()), ") terminated");
+					" (", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()), ") terminated");
+			}
 			else
+			{
 				LogPrint (eLogDebug, "SSU2: Session with ", GetRemoteEndpoint (), " terminated");
+			}
 		}
 	}
 
@@ -320,10 +290,8 @@ namespace transport
 		{
 			m_TerminationReason = reason;
 			SendTermination ();
-			m_State = eSSU2SessionStateClosing;
 		}
-		else
-			Done ();
+		m_State = eSSU2SessionStateClosing;
 	}
 
 	void SSU2Session::Established ()
@@ -335,21 +303,19 @@ namespace transport
 		m_SentHandshakePacket.reset (nullptr);
 		m_ConnectTimer.cancel ();
 		SetTerminationTimeout (SSU2_TERMINATION_TIMEOUT);
-		SendQueue ();
 		transports.PeerConnected (shared_from_this ());
-		
-		LogPrint(eLogDebug, "SSU2: Session with ", GetRemoteEndpoint (),
-			" (", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()), ") established");
 		if (m_OnEstablished)
 		{
 			m_OnEstablished ();
 			m_OnEstablished = nullptr;
 		}
+		LogPrint(eLogDebug, "SSU2: Session with ", GetRemoteEndpoint (),
+			" (", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()), ") established");
 	}
 
 	void SSU2Session::Done ()
 	{
-		boost::asio::post (m_Server.GetService (), std::bind (&SSU2Session::Terminate, shared_from_this ()));
+		m_Server.GetService ().post (std::bind (&SSU2Session::Terminate, shared_from_this ()));
 	}
 
 	void SSU2Session::SendLocalRouterInfo (bool update)
@@ -357,11 +323,11 @@ namespace transport
 		if (update || !IsOutgoing ())
 		{
 			auto s = shared_from_this ();
-			boost::asio::post (m_Server.GetService (), [s]()
+			m_Server.GetService ().post ([s]()
 				{
 					if (!s->IsEstablished ()) return;
 					uint8_t payload[SSU2_MAX_PACKET_SIZE];
-					size_t payloadSize = s->CreateRouterInfoBlock (payload, s->m_MaxPayloadSize - 32, i2p::context.CopyRouterInfoBuffer ());
+					size_t payloadSize = s->CreateRouterInfoBlock (payload, s->m_MaxPayloadSize - 32, i2p::context.GetSharedRouterInfo ());
 					if (payloadSize)
 					{
 						if (payloadSize < s->m_MaxPayloadSize)
@@ -375,92 +341,37 @@ namespace transport
 
 	}
 
-	void SSU2Session::SendI2NPMessages (std::list<std::shared_ptr<I2NPMessage> >& msgs)
+	void SSU2Session::SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs)
 	{
-		if (m_State == eSSU2SessionStateTerminated || msgs.empty ()) 
-		{
-			msgs.clear ();
-			return;
-		}	
-		bool empty = false;
-		{
-			std::lock_guard<std::mutex> l(m_IntermediateQueueMutex);
-			empty = m_IntermediateQueue.empty ();
-			m_IntermediateQueue.splice (m_IntermediateQueue.end (), msgs);
-		}
-		if (empty)
-			boost::asio::post (m_Server.GetService (), std::bind (&SSU2Session::PostI2NPMessages, shared_from_this ()));
+		m_Server.GetService ().post (std::bind (&SSU2Session::PostI2NPMessages, shared_from_this (), msgs));
 	}
 
-	void SSU2Session::PostI2NPMessages ()
+	void SSU2Session::PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs)
 	{
 		if (m_State == eSSU2SessionStateTerminated) return;
-		std::list<std::shared_ptr<I2NPMessage> > msgs;
+		for (auto it: msgs)
+			m_SendQueue.push_back (std::move (it));
+		SendQueue ();
+
+		if (m_SendQueue.size () > 0) // windows is full
 		{
-			std::lock_guard<std::mutex> l(m_IntermediateQueueMutex);
-			m_IntermediateQueue.swap (msgs);		
-		}	
-		uint64_t mts = i2p::util::GetMonotonicMicroseconds ();
-		bool isSemiFull = false;
-		if (m_SendQueue.size ())
-		{
-			int64_t queueLag = (int64_t)mts - (int64_t)m_SendQueue.front ()->GetEnqueueTime ();
-			isSemiFull = queueLag > m_MsgLocalSemiExpirationTimeout;
-			if (isSemiFull)
+			if (m_SendQueue.size () <= SSU2_MAX_OUTGOING_QUEUE_SIZE)
+				Resend (i2p::util::GetMillisecondsSinceEpoch ());
+			else
 			{
-				LogPrint (eLogWarning, "SSU2: Outgoing messages queue to ",
-					i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()),
-					" is semi-full (size = ", m_SendQueue.size (), ", lag = ", queueLag / 1000, ", rtt = ", (int)m_RTT, ")");
+				LogPrint (eLogWarning, "SSU2: Outgoing messages queue size to ",
+					GetIdentHashBase64(), " exceeds ", SSU2_MAX_OUTGOING_QUEUE_SIZE);
+				RequestTermination (eSSU2TerminationReasonTimeout);
 			}
 		}
-		if (isSemiFull)
-		{	
-			for (auto it: msgs)
-			{
-				if (it->onDrop)
-					it->Drop (); // drop earlier because we can handle it
-				else
-				{
-					it->SetEnqueueTime (mts);
-					m_SendQueue.push_back (std::move (it));
-				}
-			}
-		}	
-		else
-		{
-			for (auto& it: msgs) it->SetEnqueueTime (mts);
-			m_SendQueue.splice (m_SendQueue.end (), msgs);
-		}	
-		if (IsEstablished ())
-		{	
-			SendQueue ();
-			if (m_SendQueue.size () > 0) // windows is full
-				Resend (i2p::util::GetMillisecondsSinceEpoch ());
-		}	
 		SetSendQueueSize (m_SendQueue.size ());
 	}
 
-	void SSU2Session::MoveSendQueue (std::shared_ptr<SSU2Session> other)
-	{
-		if (!other || m_SendQueue.empty ()) return;
-		std::list<std::shared_ptr<I2NPMessage> > msgs;
-		auto ts = i2p::util::GetMillisecondsSinceEpoch ();
-		for (auto it: m_SendQueue)
-			if (!it->IsExpired (ts))
-				msgs.push_back (it);
-			else
-				it->Drop ();
-		m_SendQueue.clear ();
-		if (!msgs.empty ())
-			other->SendI2NPMessages (msgs);
-	}	
-		
 	bool SSU2Session::SendQueue ()
 	{
-		if (!m_SendQueue.empty () && m_SentPackets.size () <= m_WindowSize && IsEstablished ())
+		if (!m_SendQueue.empty () && m_SentPackets.size () <= m_WindowSize)
 		{
 			auto ts = i2p::util::GetMillisecondsSinceEpoch ();
-			uint64_t mts = i2p::util::GetMonotonicMicroseconds ();
 			auto packet = m_Server.GetSentPacketsPool ().AcquireShared ();
 			size_t ackBlockSize = CreateAckBlock (packet->payload, m_MaxPayloadSize);
 			bool ackBlockSent = false;
@@ -468,10 +379,8 @@ namespace transport
 			while (!m_SendQueue.empty () && m_SentPackets.size () <= m_WindowSize)
 			{
 				auto msg = m_SendQueue.front ();
-				if (!msg || msg->IsExpired (ts) || msg->GetEnqueueTime() + m_MsgLocalExpirationTimeout < mts)
+				if (!msg)
 				{
-					// drop null or expired message
-					if (msg) msg->Drop ();
 					m_SendQueue.pop_front ();
 					continue;
 				}
@@ -564,7 +473,7 @@ namespace transport
 			else
 				extraSize -= packet->payloadSize;
 		}
-		size_t offset = extraSize > 0 ? (m_Server.GetRng ()() % extraSize) : 0;
+		size_t offset = extraSize > 0 ? (rand () % extraSize) : 0;
 		if (offset + packet->payloadSize >= m_MaxPayloadSize) offset = 0;
 		auto size = CreateFirstFragmentBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - offset - packet->payloadSize, msg);
 		if (!size) return false;
@@ -576,7 +485,7 @@ namespace transport
 		uint8_t fragmentNum = 0;
 		while (msg->offset < msg->len)
 		{
-			offset = extraSize > 0 ? (m_Server.GetRng ()() % extraSize) : 0;
+			offset = extraSize > 0 ? (rand () % extraSize) : 0;
 			packet = m_Server.GetSentPacketsPool ().AcquireShared ();
 			packet->payloadSize = CreateFollowOnFragmentBlock (packet->payload, m_MaxPayloadSize - offset, msg, fragmentNum, msgID);
 			extraSize -= offset;
@@ -596,8 +505,6 @@ namespace transport
 
 	size_t SSU2Session::Resend (uint64_t ts)
 	{
-		if (ts + SSU2_RESEND_ATTEMPT_MIN_INTERVAL < m_LastResendAttemptTime) return 0;
-		m_LastResendAttemptTime = ts;
 		// resend handshake packet
 		if (m_SentHandshakePacket && ts >= m_SentHandshakePacket->sendTime + SSU2_HANDSHAKE_RESEND_INTERVAL)
 		{
@@ -610,7 +517,7 @@ namespace transport
 		if (m_SentPackets.empty ()) return 0;
 		std::map<uint32_t, std::shared_ptr<SSU2SentPacket> > resentPackets;
 		for (auto it = m_SentPackets.begin (); it != m_SentPackets.end (); )
-			if (ts >= it->second->sendTime + (it->second->numResends + 1) * m_RTO)
+			if (ts >= it->second->sendTime + it->second->numResends*m_RTO)
 			{
 				if (it->second->numResends > SSU2_MAX_NUM_RESENDS)
 				{
@@ -623,8 +530,7 @@ namespace transport
 				}
 				else
 				{
-					uint32_t packetNum = SendData (it->second->payload, it->second->payloadSize, 
-						it->second->numResends > 1 ? SSU2_FLAG_IMMEDIATE_ACK_REQUESTED : 0);
+					uint32_t packetNum = SendData (it->second->payload, it->second->payloadSize);
 					it->second->numResends++;
 					it->second->sendTime = ts;
 					resentPackets.emplace (packetNum, it->second);
@@ -635,8 +541,11 @@ namespace transport
 				it++;
 		if (!resentPackets.empty ())
 		{
-			m_LastResendTime = ts;
+#if (__cplusplus >= 201703L) // C++ 17 or higher
 			m_SentPackets.merge (resentPackets);
+#else
+			m_SentPackets.insert (resentPackets.begin (), resentPackets.end ());
+#endif
 			m_WindowSize >>= 1; // /2
 			if (m_WindowSize < SSU2_MIN_WINDOW_SIZE) m_WindowSize = SSU2_MIN_WINDOW_SIZE;
 			return resentPackets.size ();
@@ -683,7 +592,7 @@ namespace transport
 				}
 				const uint8_t nonce[12] = {0};
 				uint64_t headerX[2];
-				m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX);
+				i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX);
 				LogPrint (eLogWarning, "SSU2: Unexpected PeerTest message SourceConnID=", connID, " DestConnID=", headerX[0]);
 				break;
 			}
@@ -727,14 +636,10 @@ namespace transport
 		size_t payloadSize = 7;
 		if (GetRouterStatus () == eRouterStatusFirewalled && m_Address->IsIntroducer ())
 		{
-			if (!m_Server.IsMaxNumIntroducers (m_RemoteEndpoint.address ().is_v4 ()) ||
-			    m_Server.GetRng ()() & 0x01) // request tag with probability 1/2 if we have enough introducers
-			{	
-				// relay tag request
-				payload[payloadSize] = eSSU2BlkRelayTagRequest;
-				memset (payload + payloadSize + 1, 0, 2); // size = 0
-				payloadSize += 3;
-			}	
+			// relay tag request
+			payload[payloadSize] = eSSU2BlkRelayTagRequest;
+			memset (payload + payloadSize + 1, 0, 2); // size = 0
+			payloadSize += 3;
 		}
 		payloadSize += CreatePaddingBlock (payload + payloadSize, 40 - payloadSize, 1);
 		// KDF for session request
@@ -749,7 +654,7 @@ namespace transport
 		payloadSize += 16;
 		header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24));
 		header.ll[1] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 12));
-		m_Server.ChaCha20 (headerX, 48, m_Address->i, nonce, headerX);
+		i2p::crypto::ChaCha20 (headerX, 48, m_Address->i, nonce, headerX);
 		m_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || encrypted payload from Session Request) for SessionCreated
 		m_SentHandshakePacket->payloadSize = payloadSize;
 		// send
@@ -776,7 +681,7 @@ namespace transport
 		}
 		const uint8_t nonce[12] = {0};
 		uint8_t headerX[48];
-		m_Server.ChaCha20 (buf + 16, 48, i2p::context.GetSSU2IntroKey (), nonce, headerX);
+		i2p::crypto::ChaCha20 (buf + 16, 48, i2p::context.GetSSU2IntroKey (), nonce, headerX);
 		memcpy (&m_DestConnID, headerX, 8);
 		uint64_t token;
 		memcpy (&token, headerX + 8, 8);
@@ -875,7 +780,7 @@ namespace transport
 		m_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || encrypted Noise payload from Session Created)
 		header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 24));
 		header.ll[1] ^= CreateHeaderMask (kh2, payload + (payloadSize - 12));
-		m_Server.ChaCha20 (headerX, 48, kh2, nonce, headerX);
+		i2p::crypto::ChaCha20 (headerX, 48, kh2, nonce, headerX);
 		m_State = eSSU2SessionStateSessionCreatedSent;
 		m_SentHandshakePacket->payloadSize = payloadSize;
 		// send
@@ -903,7 +808,7 @@ namespace transport
 		m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval;
 		const uint8_t nonce[12] = {0};
 		uint8_t headerX[48];
-		m_Server.ChaCha20 (buf + 16, 48, kh2, nonce, headerX);
+		i2p::crypto::ChaCha20 (buf + 16, 48, kh2, nonce, headerX);
 		// KDF for SessionCreated
 		m_NoiseState->MixHash ( { {header.buf, 16}, {headerX, 16} } ); // h = SHA256(h || header)
 		m_NoiseState->MixHash (headerX + 16, 32); // h = SHA256(h || bepk);
@@ -952,12 +857,12 @@ namespace transport
 		// payload
 		size_t maxPayloadSize = m_MaxPayloadSize - 48; // for part 2, 48 is part1
 		uint8_t * payload = m_SentHandshakePacket->payload;
-		size_t payloadSize = CreateRouterInfoBlock (payload, maxPayloadSize, i2p::context.CopyRouterInfoBuffer ());
+		size_t payloadSize = CreateRouterInfoBlock (payload, maxPayloadSize, i2p::context.GetSharedRouterInfo ());
 		if (!payloadSize)
 		{
 			// split by two fragments
 			maxPayloadSize += m_MaxPayloadSize;
-			payloadSize = CreateRouterInfoBlock (payload, maxPayloadSize, i2p::context.CopyRouterInfoBuffer ());
+			payloadSize = CreateRouterInfoBlock (payload, maxPayloadSize, i2p::context.GetSharedRouterInfo ());
 			header.h.flags[0] = 0x02; // frag 0, total fragments 2
 			// TODO: check if we need more fragments
 		}
@@ -985,7 +890,7 @@ namespace transport
 		{
 			if (payloadSize > m_MaxPayloadSize - 48)
 			{
-				payloadSize = m_MaxPayloadSize - 48 - (m_Server.GetRng ()() % 16);
+				payloadSize = m_MaxPayloadSize - 48 - (rand () % 16);
 				if (m_SentHandshakePacket->payloadSize - payloadSize < 24)
 					payloadSize -= 24;
 			}
@@ -1172,28 +1077,6 @@ namespace transport
 			LogPrint (eLogError, "SSU2: RouterInfo in SessionConfirmed is from future for ", (ri->GetTimestamp () - ts)/1000LL, " seconds");
 			return false;
 		}	
-		// update RouterInfo in netdb
-		auto ri1 = i2p::data::netdb.AddRouterInfo (ri->GetBuffer (), ri->GetBufferLen ()); // ri points to one from netdb now
-		if (!ri1)
-		{
-			LogPrint (eLogError, "SSU2: Couldn't update RouterInfo from SessionConfirmed in netdb");
-			return false;
-		}
-		
-		bool isOlder = false;
-		if (ri->GetTimestamp () + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri1->GetTimestamp ())
-		{	
-			// received RouterInfo is older than one in netdb
-			isOlder = true;
-			if (ri->HasProfile ())
-			{	
-				auto profile = i2p::data::GetRouterProfile (ri->GetIdentHash ()); // retrieve profile	
-				if (profile && profile->IsDuplicated ())
-					return false;
-			}	
-		}	
-		ri = ri1;
-		
 		m_Address = m_RemoteEndpoint.address ().is_v6 () ? ri->GetSSU2V6Address () : ri->GetSSU2V4Address ();
 		if (!m_Address || memcmp (S, m_Address->s, 32))
 		{
@@ -1204,37 +1087,21 @@ namespace transport
 		    (!m_RemoteEndpoint.address ().is_v6 () ||
 			 memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), m_Address->host.to_v6 ().to_bytes ().data (), 8))) // temporary address
 		{
-			if (isOlder) // older router?
-				i2p::data::UpdateRouterProfile (ri->GetIdentHash (),
-					[](std::shared_ptr<i2p::data::RouterProfile> profile)
-					{
-						if (profile) profile->Duplicated (); // mark router as duplicated in profile
-					});
-			else	
-				LogPrint (eLogInfo, "SSU2: Host mismatch between published address ", m_Address->host,
-					" and actual endpoint ", m_RemoteEndpoint.address (), " from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ()));
+			LogPrint (eLogError, "SSU2: Host mismatch between published address ", m_Address->host,
+				" and actual endpoint ", m_RemoteEndpoint.address (), " from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ()));
+			return false;
+		}
+		// update RouterInfo in netdb
+		ri = i2p::data::netdb.AddRouterInfo (ri->GetBuffer (), ri->GetBufferLen ()); // ri points to one from netdb now
+		if (!ri)
+		{
+			LogPrint (eLogError, "SSU2: Couldn't update RouterInfo from SessionConfirmed in netdb");
 			return false;
 		}
-		if (!m_Address->published)
-		{	
-			if (ri->HasProfile ())	
-				ri->GetProfile ()->SetLastEndpoint (m_RemoteEndpoint);
-			else
-				i2p::data::UpdateRouterProfile (ri->GetIdentHash (), 
-					[ep = m_RemoteEndpoint](std::shared_ptr<i2p::data::RouterProfile> profile)
-				    {
-						if (profile) profile->SetLastEndpoint (ep);
-					});	
-		}	
 		SetRemoteIdentity (ri->GetRouterIdentity ());
 		AdjustMaxPayloadSize ();
 		m_Server.AddSessionByRouterHash (shared_from_this ()); // we know remote router now
 		m_RemoteTransports = ri->GetCompatibleTransports (false);
-		m_RemotePeerTestTransports = 0;
-		if (ri->IsSSU2PeerTesting (true)) m_RemotePeerTestTransports |= i2p::data::RouterInfo::eSSU2V4;
-		if (ri->IsSSU2PeerTesting (false)) m_RemotePeerTestTransports |= i2p::data::RouterInfo::eSSU2V6;
-		m_RemoteVersion = ri->GetVersion ();
-		
 		// handle other blocks
 		HandlePayload (decryptedPayload.data () + riSize + 3, decryptedPayload.size () - riSize - 3);
 		Established ();
@@ -1283,7 +1150,7 @@ namespace transport
 		header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24));
 		header.ll[1] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 12));
 		memset (nonce, 0, 12);
-		m_Server.ChaCha20 (h + 16, 16, m_Address->i, nonce, h + 16);
+		i2p::crypto::ChaCha20 (h + 16, 16, m_Address->i, nonce, h + 16);
 		// send
 		if (m_Server.AddPendingOutgoingSession (shared_from_this ()))
 			m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint);
@@ -1305,7 +1172,7 @@ namespace transport
 		uint8_t nonce[12] = {0};
 		uint8_t h[32];
 		memcpy (h, header.buf, 16);
-		m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16);
+		i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16);
 		memcpy (&m_DestConnID, h + 16, 8);
 		// decrypt
 		CreateNonce (be32toh (header.h.packetNum), nonce);
@@ -1357,7 +1224,7 @@ namespace transport
 		header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 24));
 		header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 12));
 		memset (nonce, 0, 12);
-		m_Server.ChaCha20 (h + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16);
+		i2p::crypto::ChaCha20 (h + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16);
 		// send
 		m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint);
 	}
@@ -1381,7 +1248,7 @@ namespace transport
 		}
 		uint8_t nonce[12] = {0};
 		uint64_t headerX[2]; // sourceConnID, token
-		m_Server.ChaCha20 (buf + 16, 16, m_Address->i, nonce, (uint8_t *)headerX);
+		i2p::crypto::ChaCha20 (buf + 16, 16, m_Address->i, nonce, (uint8_t *)headerX);
 		uint64_t token = headerX[1];
 		if (token)
 			m_Server.UpdateOutgoingToken (m_RemoteEndpoint, token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT);
@@ -1409,7 +1276,47 @@ namespace transport
 		SendSessionRequest (token);
 		return true;
 	}
-		
+
+	void SSU2Session::SendHolePunch (uint32_t nonce, const boost::asio::ip::udp::endpoint& ep,
+		const uint8_t * introKey, uint64_t token)
+	{
+		// we are Charlie
+		LogPrint (eLogDebug, "SSU2: Sending HolePunch to ", ep);
+		Header header;
+		uint8_t h[32], payload[SSU2_MAX_PACKET_SIZE];
+		// fill packet
+		header.h.connID = htobe64 (((uint64_t)nonce << 32) | nonce); // dest id
+		RAND_bytes (header.buf + 8, 4); // random packet num
+		header.h.type = eSSU2HolePunch;
+		header.h.flags[0] = 2; // ver
+		header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID
+		header.h.flags[2] = 0; // flag
+		memcpy (h, header.buf, 16);
+		uint64_t c = ~header.h.connID;
+		memcpy (h + 16, &c, 8); // source id
+		RAND_bytes (h + 24, 8); // token
+		// payload
+		payload[0] = eSSU2BlkDateTime;
+		htobe16buf (payload + 1, 4);
+		htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000);
+		size_t payloadSize = 7;
+		payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, ep);
+		payloadSize += CreateRelayResponseBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize,
+			eSSU2RelayResponseCodeAccept, nonce, token, ep.address ().is_v4 ());
+		payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
+		// encrypt
+		uint8_t n[12];
+		CreateNonce (be32toh (header.h.packetNum), n);
+		i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, introKey, n, payload, payloadSize + 16, true);
+		payloadSize += 16;
+		header.ll[0] ^= CreateHeaderMask (introKey, payload + (payloadSize - 24));
+		header.ll[1] ^= CreateHeaderMask (introKey, payload + (payloadSize - 12));
+		memset (n, 0, 12);
+		i2p::crypto::ChaCha20 (h + 16, 16, introKey, n, h + 16);
+		// send
+		m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, ep);
+	}
+
 	bool SSU2Session::ProcessHolePunch (uint8_t * buf, size_t len)
 	{
 		// we are Alice
@@ -1430,7 +1337,7 @@ namespace transport
 		}
 		uint8_t nonce[12] = {0};
 		uint64_t headerX[2]; // sourceConnID, token
-		m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX);
+		i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX);
 		m_DestConnID = headerX[0];
 		// decrypt and handle payload
 		uint8_t * payload = buf + 32;
@@ -1445,17 +1352,83 @@ namespace transport
 			return false;
 		}
 		HandlePayload (payload, len - 48);
-		m_IsDataReceived = false;
 		// connect to Charlie
 		ConnectAfterIntroduction ();
 
 		return true;
 	}
 
+	void SSU2Session::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, const uint8_t * introKey)
+	{
+		Header header;
+		uint8_t h[32], payload[SSU2_MAX_PACKET_SIZE];
+		// fill packet
+		header.h.connID = m_DestConnID; // dest id
+		RAND_bytes (header.buf + 8, 4); // random packet num
+		header.h.type = eSSU2PeerTest;
+		header.h.flags[0] = 2; // ver
+		header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID
+		header.h.flags[2] = 0; // flag
+		memcpy (h, header.buf, 16);
+		memcpy (h + 16, &m_SourceConnID, 8); // source id
+		// payload
+		payload[0] = eSSU2BlkDateTime;
+		htobe16buf (payload + 1, 4);
+		htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000);
+		size_t payloadSize = 7;
+		if (msg == 6 || msg == 7)
+			payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, m_RemoteEndpoint);
+		payloadSize += CreatePeerTestBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize,
+			msg, eSSU2PeerTestCodeAccept, nullptr, signedData, signedDataLen);
+		payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
+		// encrypt
+		uint8_t n[12];
+		CreateNonce (be32toh (header.h.packetNum), n);
+		i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, introKey, n, payload, payloadSize + 16, true);
+		payloadSize += 16;
+		header.ll[0] ^= CreateHeaderMask (introKey, payload + (payloadSize - 24));
+		header.ll[1] ^= CreateHeaderMask (introKey, payload + (payloadSize - 12));
+		memset (n, 0, 12);
+		i2p::crypto::ChaCha20 (h + 16, 16, introKey, n, h + 16);
+		// send
+		m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint);
+	}
+
 	bool SSU2Session::ProcessPeerTest (uint8_t * buf, size_t len)
 	{
-		LogPrint (eLogWarning, "SSU2:  Unexpected peer test message for this session type");
-		return false;
+		// we are Alice or Charlie
+		Header header;
+		memcpy (header.buf, buf, 16);
+		header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 24));
+		header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 12));
+		if (header.h.type != eSSU2PeerTest)
+		{
+			LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2PeerTest);
+			return false;
+		}
+		if (len < 48)
+		{
+			LogPrint (eLogWarning, "SSU2: PeerTest message too short ", len);
+			return false;
+		}
+		uint8_t nonce[12] = {0};
+		uint64_t headerX[2]; // sourceConnID, token
+		i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX);
+		m_DestConnID = headerX[0];
+		// decrypt and handle payload
+		uint8_t * payload = buf + 32;
+		CreateNonce (be32toh (header.h.packetNum), nonce);
+		uint8_t h[32];
+		memcpy (h, header.buf, 16);
+		memcpy (h + 16, &headerX, 16);
+		if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 48, h, 32,
+			i2p::context.GetSSU2IntroKey (), nonce, payload, len - 48, false))
+		{
+			LogPrint (eLogWarning, "SSU2: PeerTest AEAD verification failed ");
+			return false;
+		}
+		HandlePayload (payload, len - 48);
+		return true;
 	}
 
 	uint32_t SSU2Session::SendData (const uint8_t * buf, size_t len, uint8_t flags)
@@ -1474,7 +1447,7 @@ namespace transport
 		uint8_t nonce[12];
 		CreateNonce (m_SendPacketNum, nonce);
 		uint8_t payload[SSU2_MAX_PACKET_SIZE];
-		m_Server.AEADChaCha20Poly1305Encrypt (buf, len, header.buf, 16, m_KeyDataSend, nonce, payload, SSU2_MAX_PACKET_SIZE);
+		i2p::crypto::AEADChaCha20Poly1305 (buf, len, header.buf, 16, m_KeyDataSend, nonce, payload, SSU2_MAX_PACKET_SIZE, true);
 		header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (len - 8));
 		header.ll[1] ^= CreateHeaderMask (m_KeyDataSend + 32, payload + (len + 4));
 		m_Server.Send (header.buf, 16, payload, len + 16, m_RemoteEndpoint);
@@ -1498,11 +1471,11 @@ namespace transport
 				ResendHandshakePacket (); // assume we receive
 			return;
 		}
-		if (from != m_RemoteEndpoint && !i2p::transport::transports.IsInReservedRange (from.address ()) &&
-		    (!m_PathChallenge || from != m_PathChallenge->second)) // path challenge was not sent to this endpoint yet 
+		if (from != m_RemoteEndpoint && !i2p::util::net::IsInReservedRange (from.address ()))
 		{
 			LogPrint (eLogInfo, "SSU2: Remote endpoint update ", m_RemoteEndpoint, "->", from);
-			SendPathChallenge (from);
+			m_RemoteEndpoint = from;
+			SendPathChallenge ();
 		}
 		if (len < 32)
 		{
@@ -1514,14 +1487,13 @@ namespace transport
 		uint32_t packetNum = be32toh (header.h.packetNum);
 		uint8_t nonce[12];
 		CreateNonce (packetNum, nonce);
-		if (!m_Server.AEADChaCha20Poly1305Decrypt (buf + 16, payloadSize, header.buf, 16,
-			m_KeyDataReceive, nonce, payload, payloadSize))
+		if (!i2p::crypto::AEADChaCha20Poly1305 (buf + 16, payloadSize, header.buf, 16,
+			m_KeyDataReceive, nonce, payload, payloadSize, false))
 		{
 			LogPrint (eLogWarning, "SSU2: Data AEAD verification failed ");
 			return;
 		}
 		UpdateNumReceivedBytes (len);
-		if (header.h.flags[0] & SSU2_FLAG_IMMEDIATE_ACK_REQUESTED) m_IsDataReceived = true;
 		if (!packetNum || UpdateReceivePacketNum (packetNum))
 			HandlePayload (payload, payloadSize);
 	}
@@ -1551,9 +1523,14 @@ namespace transport
 					LogPrint (eLogDebug, "SSU2: Options");
 				break;
 				case eSSU2BlkRouterInfo:
+				{
+					// not from SessionConfirmed, we must add it instantly to use in next block
 					LogPrint (eLogDebug, "SSU2: RouterInfo");
-					HandleRouterInfo (buf + offset, size);
-				break;
+					auto ri = ExtractRouterInfo (buf + offset, size);
+					if (ri)
+						i2p::data::netdb.AddRouterInfo (ri->GetBuffer (), ri->GetBufferLen ());	// TODO: add ri
+					break;
+				}
 				case eSSU2BlkI2NPMessage:
 				{
 					LogPrint (eLogDebug, "SSU2: I2NP message");
@@ -1597,17 +1574,14 @@ namespace transport
 				case eSSU2BlkRelayRequest:
 					LogPrint (eLogDebug, "SSU2: RelayRequest");
 					HandleRelayRequest (buf + offset, size);
-					m_IsDataReceived = true;
 				break;
 				case eSSU2BlkRelayResponse:
 					LogPrint (eLogDebug, "SSU2: RelayResponse");
 					HandleRelayResponse (buf + offset, size);
-					m_IsDataReceived = true;
 				break;
 				case eSSU2BlkRelayIntro:
 					LogPrint (eLogDebug, "SSU2: RelayIntro");
 					HandleRelayIntro (buf + offset, size);
-					m_IsDataReceived = true;
 				break;
 				case eSSU2BlkPeerTest:
 					LogPrint (eLogDebug, "SSU2: PeerTest msg=", (int)buf[offset], " code=", (int)buf[offset+1]);
@@ -1631,12 +1605,8 @@ namespace transport
 					LogPrint (eLogDebug, "SSU2: RelayTagRequest");
 					if (!m_RelayTag)
 					{
-						auto addr = FindLocalAddress ();
-						if (addr && addr->IsIntroducer ())
-						{	
-							RAND_bytes ((uint8_t *)&m_RelayTag, 4);
-							m_Server.AddRelay (m_RelayTag, shared_from_this ());
-						}	
+						RAND_bytes ((uint8_t *)&m_RelayTag, 4);
+						m_Server.AddRelay (m_RelayTag, shared_from_this ());
 					}
 				break;
 				case eSSU2BlkRelayTag:
@@ -1660,11 +1630,10 @@ namespace transport
 					LogPrint (eLogDebug, "SSU2: Path response");
 					if (m_PathChallenge)
 					{
-						if (buf64toh (buf + offset) == m_PathChallenge->first)
-						{
-							m_RemoteEndpoint = m_PathChallenge->second;
+						i2p::data::IdentHash hash;
+						SHA256 (buf + offset, size, hash);
+						if (hash == *m_PathChallenge)
 							m_PathChallenge.reset (nullptr);
-						}	
 					}
 					break;
 				}
@@ -1701,10 +1670,10 @@ namespace transport
 						if (std::abs (offset) > SSU2_CLOCK_THRESHOLD)
 						{	
 							LogPrint (eLogWarning, "SSU2: Time offset ", offset, " from ", m_RemoteEndpoint);
-							m_Server.AdjustTimeOffset (-offset, GetRemoteIdentity ());
+							m_Server.AdjustTimeOffset (-offset);
 						}	
 						else
-							m_Server.AdjustTimeOffset (0, nullptr);
+							m_Server.AdjustTimeOffset (0);
 					}
 					else if (std::abs (offset) > SSU2_CLOCK_SKEW)
 					{
@@ -1717,38 +1686,6 @@ namespace transport
 		};
 	}
 
-	void SSU2Session::HandleRouterInfo (const uint8_t * buf, size_t len)
-	{
-		if (len < 2) return;
-		// not from SessionConfirmed, we must add it instantly to use in next block
-		std::shared_ptr<const i2p::data::RouterInfo> newRi;
-		if (buf[0] & SSU2_ROUTER_INFO_FLAG_GZIP) // compressed?
-		{	
-			auto ri = ExtractRouterInfo (buf, len);
-			if (ri)
-				newRi = i2p::data::netdb.AddRouterInfo (ri->GetBuffer (), ri->GetBufferLen ());
-		}	
-		else // use buffer directly. TODO: handle frag
-			newRi = i2p::data::netdb.AddRouterInfo (buf + 2, len - 2);
-
-		if (newRi)
-		{
-			auto remoteIdentity = GetRemoteIdentity ();
-			if (remoteIdentity && remoteIdentity->GetIdentHash () == newRi->GetIdentHash ())
-			{
-				// peer's RouterInfo update
-				SetRemoteIdentity (newRi->GetIdentity ());
-				auto address = m_RemoteEndpoint.address ().is_v6 () ? newRi->GetSSU2V6Address () : newRi->GetSSU2V4Address ();
-				if (address)
-				{
-					m_Address = address;
-					if (IsOutgoing () && m_RelayTag && !address->IsIntroducer ())
-						m_RelayTag = 0; // not longer introducer
-				}	
-			}	
-		}		
-	}	
-		
 	void SSU2Session::HandleAck (const uint8_t * buf, size_t len)
 	{
 		if (m_State == eSSU2SessionStateSessionConfirmedSent)
@@ -1764,7 +1701,6 @@ namespace transport
 		HandleAckRange (firstPacketNum, ackThrough, i2p::util::GetMillisecondsSinceEpoch ()); // acnt
 		// ranges
 		len -= 5;
-		if (!len || m_SentPackets.empty ()) return; // don't handle ranges if nothing to acknowledge
 		const uint8_t * ranges = buf + 5;
 		while (len > 0 && firstPacketNum && ackThrough - firstPacketNum < SSU2_MAX_NUM_ACK_PACKETS)
 		{
@@ -1793,15 +1729,8 @@ namespace transport
 				if (ts > it1->second->sendTime)
 				{
 					auto rtt = ts - it1->second->sendTime;
-					if (m_RTT != SSU2_UNKNOWN_RTT)
-						m_RTT = SSU2_RTT_EWMA_ALPHA * rtt + (1.0 - SSU2_RTT_EWMA_ALPHA) * m_RTT;
-					else
-						m_RTT = rtt;
+					m_RTT = std::round ((m_RTT*m_SendPacketNum + rtt)/(m_SendPacketNum + 1.0));
 					m_RTO = m_RTT*SSU2_kAPPA;
-					m_MsgLocalExpirationTimeout = std::max (I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MIN,
-						std::min (I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MAX,
-						(unsigned int)(m_RTT * 1000 * I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_FACTOR)));
-					m_MsgLocalSemiExpirationTimeout = m_MsgLocalExpirationTimeout / 2;
 					if (m_RTO < SSU2_MIN_RTO) m_RTO = SSU2_MIN_RTO;
 					if (m_RTO > SSU2_MAX_RTO) m_RTO = SSU2_MAX_RTO;
 				}
@@ -1824,7 +1753,7 @@ namespace transport
 		if (ExtractEndpoint (buf, len, ep))
 		{
 			LogPrint (eLogInfo, "SSU2: Our external address is ", ep);
-			if (!i2p::transport::transports.IsInReservedRange (ep.address ()))
+			if (!i2p::util::net::IsInReservedRange (ep.address ()))
 			{
 				i2p::context.UpdateAddress (ep.address ());
 				// check our port
@@ -1966,99 +1895,77 @@ namespace transport
 	void SSU2Session::HandleRelayRequest (const uint8_t * buf, size_t len)
 	{
 		// we are Bob
-		if (len < 9) return;
-		auto mts = i2p::util::GetMillisecondsSinceEpoch ();
-		uint32_t nonce = bufbe32toh (buf + 1); // nonce
 		uint32_t relayTag = bufbe32toh (buf + 5); // relay tag
 		auto session = m_Server.FindRelaySession (relayTag);
 		if (!session)
 		{
 			LogPrint (eLogWarning, "SSU2: RelayRequest session with relay tag ", relayTag, " not found");
 			// send relay response back to Alice
-			auto packet = m_Server.GetSentPacketsPool ().AcquireShared ();
-			packet->payloadSize = CreateAckBlock (packet->payload, m_MaxPayloadSize);
-			packet->payloadSize += CreateRelayResponseBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize,
-				eSSU2RelayResponseCodeBobRelayTagNotFound, nonce, 0, false);
-			packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize);
-			uint32_t packetNum = SendData (packet->payload, packet->payloadSize);
-			if (m_RemoteVersion >= SSU2_MIN_RELAY_RESPONSE_RESEND_VERSION)
-			{	
-				// sometimes Alice doesn't ack this RelayResponse in older versions
-				packet->sendTime = mts;
-				m_SentPackets.emplace (packetNum, packet);
-			}	
+			uint8_t payload[SSU2_MAX_PACKET_SIZE];
+			size_t payloadSize = CreateRelayResponseBlock (payload, m_MaxPayloadSize,
+				eSSU2RelayResponseCodeBobRelayTagNotFound, bufbe32toh (buf + 1), 0, false);
+			payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
+			SendData (payload, payloadSize);
 			return;
 		}
-		if (session->m_RelaySessions.emplace (nonce, std::make_pair (shared_from_this (), mts/1000)).second)
-		{
-			// send relay intro to Charlie
-			auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); // Alice's RI
-			if (r && (r->IsUnreachable () || !i2p::data::netdb.PopulateRouterInfoBuffer (r))) r = nullptr;
-			if (!r) LogPrint (eLogWarning, "SSU2: RelayRequest Alice's router info not found");
+		session->m_RelaySessions.emplace (bufbe32toh (buf + 1), // nonce
+			std::make_pair (shared_from_this (), i2p::util::GetSecondsSinceEpoch ()) );
 
-			auto packet = m_Server.GetSentPacketsPool ().AcquireShared ();
-			packet->payloadSize = r ? CreateRouterInfoBlock (packet->payload, m_MaxPayloadSize - len - 32, r) : 0;
-			if (!packet->payloadSize && r)
-				session->SendFragmentedMessage (CreateDatabaseStoreMsg (r));
-			packet->payloadSize += CreateRelayIntroBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize, buf + 1, len - 1);
-			if (packet->payloadSize < m_MaxPayloadSize)
-				packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize);
-			uint32_t packetNum = session->SendData (packet->payload, packet->payloadSize);
-			packet->sendTime = mts;
-			// Charlie always responds with RelayResponse
-			session->m_SentPackets.emplace (packetNum, packet);
-		}
-		else
-			LogPrint (eLogInfo, "SSU2: Relay request nonce ", nonce, " already exists. Ignore");
+		// send relay intro to Charlie
+		auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); // Alice's RI
+		if (r && (r->IsUnreachable () || !i2p::data::netdb.PopulateRouterInfoBuffer (r))) r = nullptr;
+		if (!r) LogPrint (eLogWarning, "SSU2: RelayRequest Alice's router info not found");
+
+		uint8_t payload[SSU2_MAX_PACKET_SIZE];
+		size_t payloadSize = r ? CreateRouterInfoBlock (payload, m_MaxPayloadSize - len - 32, r) : 0;
+		if (!payloadSize && r)
+			session->SendFragmentedMessage (CreateDatabaseStoreMsg (r));
+		payloadSize += CreateRelayIntroBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, buf + 1, len -1);
+		if (payloadSize < m_MaxPayloadSize)
+			payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
+		session->SendData (payload, payloadSize);
 	}
 
 	void SSU2Session::HandleRelayIntro (const uint8_t * buf, size_t len, int attempts)
 	{
 		// we are Charlie
-		if (len < 47) return;
 		SSU2RelayResponseCode code = eSSU2RelayResponseCodeAccept;
-		boost::asio::ip::udp::endpoint ep;
-		std::shared_ptr<const i2p::data::RouterInfo::Address> addr;
+		uint64_t token = 0;
+		bool isV4 = false;
 		auto r = i2p::data::netdb.FindRouter (buf + 1); // Alice
 		if (r)
 		{
-			SignedData<128> s;
+			SignedData s;
 			s.Insert ((const uint8_t *)"RelayRequestData", 16); // prologue
 			s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
 			s.Insert (i2p::context.GetIdentHash (), 32); // chash
 			s.Insert (buf + 33, 14); // nonce, relay tag, timestamp, ver, asz
 			uint8_t asz = buf[46];
-			if (asz + 47 + r->GetIdentity ()->GetSignatureLen () > len) 
-			{	
-				LogPrint (eLogWarning, "SSU2: Malformed RelayIntro len=", len);
-				return; 
-			}	
 			s.Insert (buf + 47, asz); // Alice Port, Alice IP
 			if (s.Verify (r->GetIdentity (), buf + 47 + asz))
 			{
-				// obtain and check endpoint and address for HolePunch
+				// send HolePunch
+				boost::asio::ip::udp::endpoint ep;
 				if (ExtractEndpoint (buf + 47, asz, ep))
 				{
-					if (!ep.address ().is_unspecified () && ep.port ())
+					auto addr = ep.address ().is_v6 () ? r->GetSSU2V6Address () : r->GetSSU2V4Address ();
+					if (addr)
 					{
 						if (m_Server.IsSupported (ep.address ()))
 						{
-							addr = ep.address ().is_v6 () ? r->GetSSU2V6Address () : r->GetSSU2V4Address ();
-							if (!addr)
-							{
-								LogPrint (eLogWarning, "SSU2: RelayIntro address for endpoint not found");
-								code = eSSU2RelayResponseCodeCharlieAliceIsUnknown;
-							}	
+							token = m_Server.GetIncomingToken (ep);
+							isV4 = ep.address ().is_v4 ();
+							SendHolePunch (bufbe32toh (buf + 33), ep, addr->i, token);
 						}
 						else
 						{
 							LogPrint (eLogWarning, "SSU2: RelayIntro unsupported address");
 							code = eSSU2RelayResponseCodeCharlieUnsupportedAddress;
-						}	
+						}
 					}
 					else
 					{
-						LogPrint (eLogWarning, "SSU2: RelayIntro invalid endpoint");
+						LogPrint (eLogWarning, "SSU2: RelayIntro unknown address");
 						code = eSSU2RelayResponseCodeCharlieAliceIsUnknown;
 					}
 				}
@@ -2080,7 +1987,7 @@ namespace transport
 			auto vec = std::make_shared<std::vector<uint8_t> >(len);
 			memcpy (vec->data (), buf, len);
 			auto s = shared_from_this ();
-			boost::asio::post (m_Server.GetService (), [s, vec, attempts]()
+			m_Server.GetService ().post ([s, vec, attempts]()
 				{
 					LogPrint (eLogDebug, "SSU2: RelayIntro attempt ", attempts + 1);
 					s->HandleRelayIntro (vec->data (), vec->size (), attempts + 1);
@@ -2093,35 +2000,15 @@ namespace transport
 			code = eSSU2RelayResponseCodeCharlieAliceIsUnknown;
 		}
 		// send relay response to Bob
-		auto packet = m_Server.GetSentPacketsPool ().AcquireShared ();
-		uint32_t nonce = bufbe32toh (buf + 33);
-		packet->payloadSize = CreateRelayResponseBlock (packet->payload, m_MaxPayloadSize,
-			code, nonce, m_Server.GetIncomingToken (ep), ep.address ().is_v4 ());
-		if (code == eSSU2RelayResponseCodeAccept && addr)
-		{
-			// send HolePunch 
-			auto holePunchSession = std::make_shared<SSU2HolePunchSession>(m_Server, nonce, ep, addr); 
-			if (m_Server.AddSession (holePunchSession))
-				holePunchSession->SendHolePunch (packet->payload, packet->payloadSize); // relay response block
-			else
-			{
-				LogPrint (eLogInfo, "SSU2: Relay intro nonce ", nonce, " already exists. Ignore");
-				return;
-			}		
-		}	
-		packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize);
-		uint32_t packetNum = SendData (packet->payload, packet->payloadSize);
-		if (m_RemoteVersion >= SSU2_MIN_RELAY_RESPONSE_RESEND_VERSION)
-		{	
-			// sometimes Bob doesn't ack this RelayResponse in older versions
-			packet->sendTime = i2p::util::GetMillisecondsSinceEpoch ();
-			m_SentPackets.emplace (packetNum, packet);
-		}	
+		uint8_t payload[SSU2_MAX_PACKET_SIZE];
+		size_t payloadSize = CreateRelayResponseBlock (payload, m_MaxPayloadSize,
+			code, bufbe32toh (buf + 33), token, isV4);
+		payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
+		SendData (payload, payloadSize);
 	}
 
 	void SSU2Session::HandleRelayResponse (const uint8_t * buf, size_t len)
 	{
-		if (len < 6) return;
 		uint32_t nonce = bufbe32toh (buf + 2);
 		if (m_State == eSSU2SessionStateIntroduced)
 		{
@@ -2142,25 +2029,16 @@ namespace transport
 		auto it = m_RelaySessions.find (nonce);
 		if (it != m_RelaySessions.end ())
 		{
-			auto relaySession = it->second.first;
-			m_RelaySessions.erase (it);
-			if (relaySession && relaySession->IsEstablished ())
+			if (it->second.first && it->second.first->IsEstablished ())
 			{
 				// we are Bob, message from Charlie
-				auto packet = m_Server.GetSentPacketsPool ().AcquireShared ();
-				uint8_t * payload = packet->payload;
+				uint8_t payload[SSU2_MAX_PACKET_SIZE];
 				payload[0] = eSSU2BlkRelayResponse;
 				htobe16buf (payload + 1, len);
 				memcpy (payload + 3, buf, len); // forward to Alice as is
-				packet->payloadSize = len + 3;
-				packet->payloadSize += CreatePaddingBlock (payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize);
-				uint32_t packetNum = relaySession->SendData (packet->payload, packet->payloadSize);
-				if (m_RemoteVersion >= SSU2_MIN_RELAY_RESPONSE_RESEND_VERSION)
-				{	
-					// sometimes Alice doesn't ack this RelayResponse in older versions
-					packet->sendTime = i2p::util::GetMillisecondsSinceEpoch ();
-					relaySession->m_SentPackets.emplace (packetNum, packet);
-				}	
+				size_t payloadSize = len + 3;
+				payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
+				it->second.first->SendData (payload, payloadSize);
 			}
 			else
 			{
@@ -2168,31 +2046,25 @@ namespace transport
 				if (!buf[1]) // status code accepted?
 				{
 					// verify signature
-					uint8_t csz = (len >= 12) ? buf[11] : 0;
-					if (csz + 12 + relaySession->GetRemoteIdentity ()->GetSignatureLen () > len)
-					{
-						LogPrint (eLogWarning, "SSU2: Malformed RelayResponse len=", len);
-						relaySession->Done ();
-						return;
-					}	
-					SignedData<128> s;
+					uint8_t csz = buf[11];
+					SignedData s;
 					s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue
 					s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
 					s.Insert (buf + 2, 10 + csz); // nonce, timestamp, ver, csz and Charlie's endpoint
-					if (s.Verify (relaySession->GetRemoteIdentity (), buf + 12 + csz))
+					if (s.Verify (it->second.first->GetRemoteIdentity (), buf + 12 + csz))
 					{
-						if (relaySession->m_State == eSSU2SessionStateIntroduced) // HolePunch not received yet
+						if (it->second.first->m_State == eSSU2SessionStateIntroduced) // HolePunch not received yet
 						{
 							// update Charlie's endpoint
-							if (ExtractEndpoint (buf + 12, csz, relaySession->m_RemoteEndpoint))
+							if (ExtractEndpoint (buf + 12, csz, it->second.first->m_RemoteEndpoint))
 							{
 								// update token
 								uint64_t token;
 								memcpy (&token, buf + len - 8, 8);
-								m_Server.UpdateOutgoingToken (relaySession->m_RemoteEndpoint,
+								m_Server.UpdateOutgoingToken (it->second.first->m_RemoteEndpoint,
 									token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT);
 								// connect to Charlie, HolePunch will be ignored
-								relaySession->ConnectAfterIntroduction ();
+								it->second.first->ConnectAfterIntroduction ();
 							}
 							else
 								LogPrint (eLogWarning, "SSU2: RelayResponse can't extract endpoint");
@@ -2201,23 +2073,23 @@ namespace transport
 					else
 					{
 						LogPrint (eLogWarning, "SSU2: RelayResponse signature verification failed");
-						relaySession->Done ();
+						it->second.first->Done ();
 					}
 				}
 				else
 				{
-					LogPrint (eLogInfo, "SSU2: RelayResponse status code=", (int)buf[1], " nonce=", bufbe32toh (buf + 2));
-					relaySession->Done ();
+					LogPrint (eLogInfo, "SSU2: RelayResponse status code=", (int)buf[1]);
+					it->second.first->Done ();
 				}
 			}
+			m_RelaySessions.erase (it);
 		}
 		else
-			LogPrint (eLogDebug, "SSU2: RelayResponse unknown nonce ", bufbe32toh (buf + 2));
+			LogPrint (eLogWarning, "SSU2: RelayResponse unknown nonce ", bufbe32toh (buf + 2));
 	}
 
 	void SSU2Session::HandlePeerTest (const uint8_t * buf, size_t len)
 	{
-		// msgs 1-4	
 		if (len < 3) return;
 		uint8_t msg = buf[0];
 		size_t offset = 3; // points to signed data
@@ -2229,62 +2101,52 @@ namespace transport
 		{
 			case 1: // Bob from Alice
 			{
-				auto session = m_Server.GetRandomPeerTestSession ((buf[12] == 6) ? i2p::data::RouterInfo::eSSU2V4 : i2p::data::RouterInfo::eSSU2V6,
+				auto session = m_Server.GetRandomSession ((buf[12] == 6) ? i2p::data::RouterInfo::eSSU2V4 : i2p::data::RouterInfo::eSSU2V6,
 					GetRemoteIdentity ()->GetIdentHash ());
 				if (session) // session with Charlie
 				{
-					if (m_Server.AddPeerTest (nonce, shared_from_this (), ts/1000))
-					{	
-						auto packet = m_Server.GetSentPacketsPool ().AcquireShared ();
-						// Alice's RouterInfo
-						auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ());
-						if (r && (r->IsUnreachable () || !i2p::data::netdb.PopulateRouterInfoBuffer (r))) r = nullptr;
-						packet->payloadSize = r ? CreateRouterInfoBlock (packet->payload, m_MaxPayloadSize - len - 32, r) : 0;
-						if (!packet->payloadSize && r)
-							session->SendFragmentedMessage (CreateDatabaseStoreMsg (r));
-						if (packet->payloadSize + len + 48 > m_MaxPayloadSize)
-						{
-							// doesn't fit one message, send RouterInfo in separate message
-							uint32_t packetNum = session->SendData (packet->payload, packet->payloadSize, SSU2_FLAG_IMMEDIATE_ACK_REQUESTED);
-							packet->sendTime = ts;
-							session->m_SentPackets.emplace (packetNum, packet);
-							packet = m_Server.GetSentPacketsPool ().AcquireShared (); // new packet
-						}
-						// PeerTest to Charlie
-						packet->payloadSize += CreatePeerTestBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize, 2,
-							eSSU2PeerTestCodeAccept, GetRemoteIdentity ()->GetIdentHash (), buf + offset, len - offset);
-						packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize);
+					session->m_PeerTests.emplace (nonce, std::make_pair (shared_from_this (), i2p::util::GetSecondsSinceEpoch ()));
+					auto packet = m_Server.GetSentPacketsPool ().AcquireShared ();
+					// Alice's RouterInfo
+					auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ());
+					if (r && (r->IsUnreachable () || !i2p::data::netdb.PopulateRouterInfoBuffer (r))) r = nullptr;
+					packet->payloadSize = r ? CreateRouterInfoBlock (packet->payload, m_MaxPayloadSize - len - 32, r) : 0;
+					if (!packet->payloadSize && r)
+						session->SendFragmentedMessage (CreateDatabaseStoreMsg (r));
+					if (packet->payloadSize + len + 48 > m_MaxPayloadSize)
+					{
+						// doesn't fit one message, send RouterInfo in separate message
 						uint32_t packetNum = session->SendData (packet->payload, packet->payloadSize, SSU2_FLAG_IMMEDIATE_ACK_REQUESTED);
 						packet->sendTime = ts;
 						session->m_SentPackets.emplace (packetNum, packet);
+						packet = m_Server.GetSentPacketsPool ().AcquireShared (); // new packet
 					}
-					else
-						LogPrint (eLogInfo, "SSU2: Peer test 1 nonce ", nonce, " already exists. Ignored");
+					// PeerTest to Charlie
+					packet->payloadSize += CreatePeerTestBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize, 2,
+						eSSU2PeerTestCodeAccept, GetRemoteIdentity ()->GetIdentHash (), buf + offset, len - offset);
+					packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize);
+					uint32_t packetNum = session->SendData (packet->payload, packet->payloadSize, SSU2_FLAG_IMMEDIATE_ACK_REQUESTED);
+					packet->sendTime = ts;
+					session->m_SentPackets.emplace (packetNum, packet);
 				}
 				else
 				{
 					// Charlie not found, send error back to Alice
-					auto packet = m_Server.GetSentPacketsPool ().AcquireShared ();
-					uint8_t zeroHash[32] = {0};
-					packet->payloadSize = CreatePeerTestBlock (packet->payload, m_MaxPayloadSize, 4,
+					uint8_t payload[SSU2_MAX_PACKET_SIZE], zeroHash[32] = {0};
+					size_t payloadSize = CreatePeerTestBlock (payload, m_MaxPayloadSize, 4,
 						eSSU2PeerTestCodeBobNoCharlieAvailable, zeroHash, buf + offset, len - offset);
-					packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize);
-					uint32_t packetNum = SendData (packet->payload, packet->payloadSize);
-					packet->sendTime = ts;
-					m_SentPackets.emplace (packetNum, packet);
+					payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
+					SendData (payload, payloadSize);
 				}
 				break;
 			}
 			case 2: // Charlie from Bob
 			{
 				// sign with Charlie's key
-				if (len < offset + 9) return;
 				uint8_t asz = buf[offset + 9];
-				size_t l = asz + 10 + i2p::context.GetIdentity ()->GetSignatureLen ();
-				if (len < offset + l) return;
-				std::vector<uint8_t> newSignedData (l);
+				std::vector<uint8_t> newSignedData (asz + 10 + i2p::context.GetIdentity ()->GetSignatureLen ());
 				memcpy (newSignedData.data (), buf + offset, asz + 10);
-				SignedData<128> s;
+				SignedData s;
 				s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue
 				s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
 				s.Insert (buf + 3, 32); // ahash
@@ -2308,22 +2170,18 @@ namespace transport
 							{
 								boost::asio::ip::udp::endpoint ep;
 								std::shared_ptr<const i2p::data::RouterInfo::Address> addr;
-								if (ExtractEndpoint (buf + offset + 10, asz, ep) && !ep.address ().is_unspecified () && ep.port ())
+								if (ExtractEndpoint (buf + offset + 10, asz, ep))
 									addr = r->GetSSU2Address (ep.address ().is_v4 ());
-								if (addr && m_Server.IsSupported (ep.address ()) && 
-								    i2p::context.GetRouterInfo ().IsSSU2PeerTesting (ep.address ().is_v4 ()))
+								if (addr && m_Server.IsSupported (ep.address ()))
 								{
-									if (!m_Server.IsConnectedRecently (ep)) // no alive hole punch
-									{	
-										// send msg 5 to Alice
-										auto session = std::make_shared<SSU2PeerTestSession> (m_Server, 
-											0, htobe64 (((uint64_t)nonce << 32) | nonce));
-										session->m_RemoteEndpoint = ep; // might be different
-										m_Server.AddSession (session);
-										session->SendPeerTest (5, newSignedData.data (), newSignedData.size (), addr);
-									}
-									else
-										code = eSSU2PeerTestCodeCharlieAliceIsAlreadyConnected;
+									// send msg 5 to Alice
+									auto session = std::make_shared<SSU2Session> (m_Server, r, addr);
+									session->SetState (eSSU2SessionStatePeerTest);
+									session->m_RemoteEndpoint = ep; // might be different
+									session->m_DestConnID = htobe64 (((uint64_t)nonce << 32) | nonce);
+									session->m_SourceConnID = ~session->m_DestConnID;
+									m_Server.AddSession (session);
+									session->SendPeerTest (5, newSignedData.data (), newSignedData.size (), addr->i);
 								}
 								else
 									code = eSSU2PeerTestCodeCharlieUnsupportedAddress;
@@ -2340,107 +2198,83 @@ namespace transport
 				else
 					code = eSSU2PeerTestCodeCharlieAliceIsUnknown;
 				// send msg 3 back to Bob
-				auto packet = m_Server.GetSentPacketsPool ().AcquireShared ();
-				packet->payloadSize = CreatePeerTestBlock (packet->payload, m_MaxPayloadSize, 3,
+				uint8_t payload[SSU2_MAX_PACKET_SIZE];
+				size_t payloadSize = CreatePeerTestBlock (payload, m_MaxPayloadSize, 3,
 					code, nullptr, newSignedData.data (), newSignedData.size ());
-				packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize);
-				uint32_t packetNum = SendData (packet->payload, packet->payloadSize);
-				packet->sendTime = ts;
-				m_SentPackets.emplace (packetNum, packet);
+				payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
+				SendData (payload, payloadSize);
 				break;
 			}
 			case 3: // Bob from Charlie
 			{
-				auto aliceSession = m_Server.GetPeerTest (nonce);
-				if (aliceSession && aliceSession->IsEstablished ())
-				{	
-					auto packet = m_Server.GetSentPacketsPool ().AcquireShared ();
+				auto it = m_PeerTests.find (nonce);
+				if (it != m_PeerTests.end () && it->second.first)
+				{
+					uint8_t payload[SSU2_MAX_PACKET_SIZE];
 					// Charlie's RouterInfo
 					auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ());
 					if (r && (r->IsUnreachable () || !i2p::data::netdb.PopulateRouterInfoBuffer (r))) r = nullptr;
-					packet->payloadSize = r ? CreateRouterInfoBlock (packet->payload, m_MaxPayloadSize - len - 32, r) : 0;
-					if (!packet->payloadSize && r)
-						aliceSession->SendFragmentedMessage (CreateDatabaseStoreMsg (r));
-					if (packet->payloadSize + len + 16 > m_MaxPayloadSize)
+					size_t payloadSize = r ? CreateRouterInfoBlock (payload, m_MaxPayloadSize - len - 32, r) : 0;
+					if (!payloadSize && r)
+						it->second.first->SendFragmentedMessage (CreateDatabaseStoreMsg (r));
+					if (payloadSize + len + 16 > m_MaxPayloadSize)
 					{
 						// doesn't fit one message, send RouterInfo in separate message
-						uint32_t packetNum = aliceSession->SendData (packet->payload, packet->payloadSize);
-						packet->sendTime = ts;
-						aliceSession->m_SentPackets.emplace (packetNum, packet);
-						packet = m_Server.GetSentPacketsPool ().AcquireShared ();
+						it->second.first->SendData (payload, payloadSize);
+						payloadSize = 0;
 					}
 					// PeerTest to Alice
-					packet->payloadSize += CreatePeerTestBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize, 4,
+					payloadSize += CreatePeerTestBlock (payload + payloadSize, m_MaxPayloadSize, 4,
 						(SSU2PeerTestCode)buf[1], GetRemoteIdentity ()->GetIdentHash (), buf + offset, len - offset);
-					if (packet->payloadSize < m_MaxPayloadSize)
-						packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize);
-					uint32_t packetNum = aliceSession->SendData (packet->payload, packet->payloadSize);
-					packet->sendTime = ts;
-					aliceSession->m_SentPackets.emplace (packetNum, packet);
-				}	
+					if (payloadSize < m_MaxPayloadSize)
+						payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
+					it->second.first->SendData (payload, payloadSize);
+					m_PeerTests.erase (it);
+				}
 				else
-					LogPrint (eLogDebug, "SSU2: Unknown peer test 3 nonce ", nonce);
+					LogPrint (eLogWarning, "SSU2: Unknown peer test 3 nonce ", nonce);
 				break;
 			}
 			case 4: // Alice from Bob
 			{
-				auto session = m_Server.GetRequestedPeerTest (nonce);
-				if (session)
+				auto it = m_PeerTests.find (nonce);
+				if (it != m_PeerTests.end ())
 				{
 					if (buf[1] == eSSU2PeerTestCodeAccept)
 					{
 						if (GetRouterStatus () == eRouterStatusUnknown)
 							SetTestingState (true);
 						auto r = i2p::data::netdb.FindRouter (buf + 3); // find Charlie
-						if (r && len >= offset + 9)
+						if (r && it->second.first)
 						{
 							uint8_t asz = buf[offset + 9];
-							if (len < offset + asz + 10 + r->GetIdentity ()->GetSignatureLen ())
-							{
-								LogPrint (eLogWarning, "Malformed PeerTest 4 len=", len);
-								session->Done ();
-								return;
-							}	
-							SignedData<128> s;
+							SignedData s;
 							s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue
 							s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
 							s.Insert (i2p::context.GetIdentity ()->GetIdentHash (), 32); // ahash
 							s.Insert (buf + offset, asz + 10); // ver, nonce, ts, asz, Alice's endpoint
 							if (s.Verify (r->GetIdentity (), buf + offset + asz + 10))
 							{
-								session->SetRemoteIdentity (r->GetIdentity ());
+								it->second.first->SetRemoteIdentity (r->GetIdentity ());
 								auto addr = r->GetSSU2Address (m_Address->IsV4 ());
-								if (addr && addr->IsPeerTesting ())
+								if (addr)
 								{
-									if (session->GetMsgNumReceived () >= 5)
+									it->second.first->m_Address = addr;
+									if (it->second.first->m_State == eSSU2SessionStatePeerTestReceived)
 									{
-										// msg 5 already received and we know remote endpoint
-										if (session->GetMsgNumReceived () == 5)
-										{	
-											if (!session->IsConnectedRecently ())
-												SetRouterStatus (eRouterStatusOK);
-										 	// send msg 6 immeditely
-											session->SendPeerTest (6, buf + offset, len - offset, addr);
-										}
-										else
-											LogPrint (eLogWarning, "SSU2: PeerTest 4 received, but msg ", session->GetMsgNumReceived (), " already received");
+										// msg 5 already received. send msg 6
+										SetRouterStatus (eRouterStatusOK);
+										it->second.first->m_State = eSSU2SessionStatePeerTest;
+										it->second.first->SendPeerTest (6, buf + offset, len - offset, addr->i);
 									}
 									else
 									{
-										session->m_Address = addr;
 										if (GetTestingState ())
 										{
-											// schedule msg 6 with delay
-											if (!addr->host.is_unspecified () && addr->port)
-											{	
-												session->SetRemoteEndpoint (boost::asio::ip::udp::endpoint (addr->host, addr->port));
-												session->SendPeerTest (6, buf + offset, len - offset, addr, true);
-											}	
 											SetTestingState (false);
-											if (GetRouterStatus () != eRouterStatusFirewalled && addr->IsPeerTesting ())
+											if (GetRouterStatus () != eRouterStatusFirewalled)
 											{
 												SetRouterStatus (eRouterStatusFirewalled);
-												session->SetStatusChanged ();
 												if (m_Address->IsV4 ())
 													m_Server.RescheduleIntroducersUpdateTimer ();
 												else
@@ -2453,35 +2287,64 @@ namespace transport
 								}
 								else
 								{
-									LogPrint (eLogWarning, "SSU2: Peer test 4 address not found or not supported");
-									session->Done ();
+									LogPrint (eLogWarning, "SSU2: Peer test 4 address not found");
+									it->second.first->Done ();
 								}
 							}
 							else
 							{
 								LogPrint (eLogWarning, "SSU2: Peer test 4 signature verification failed");
-								session->Done ();
+								it->second.first->Done ();
 							}
 						}
 						else
 						{
 							LogPrint (eLogWarning, "SSU2: Peer test 4 router not found");
-							session->Done ();
+							if (it->second.first)
+								it->second.first->Done ();
 						}
 					}
 					else
 					{
 						LogPrint (eLogInfo, "SSU2: Peer test 4 error code ", (int)buf[1], " from ",
 							i2p::data::GetIdentHashAbbreviation (buf[1] < 64 ? GetRemoteIdentity ()->GetIdentHash () : i2p::data::IdentHash (buf + 3)));
-						if (GetTestingState () && GetRouterStatus () != eRouterStatusFirewalled)
+						if (GetTestingState ())
 							SetRouterStatus (eRouterStatusUnknown);
-						session->Done ();
+						it->second.first->Done ();
 					}
+					m_PeerTests.erase (it);
 				}
 				else
-					LogPrint (eLogDebug, "SSU2: Unknown peer test 4 nonce ", nonce);
+					LogPrint (eLogWarning, "SSU2: Unknown peer test 4 nonce ", nonce);
 				break;
 			}
+			case 5: // Alice from Charlie 1
+				if (htobe64 (((uint64_t)nonce << 32) | nonce) == m_SourceConnID)
+				{
+					if (m_Address)
+					{
+						SetRouterStatus (eRouterStatusOK);
+						SendPeerTest (6, buf + offset, len - offset, m_Address->i);
+					}
+					else
+						// we received msg 5 before msg 4
+						m_State = eSSU2SessionStatePeerTestReceived;
+				}
+				else
+					LogPrint (eLogWarning, "SSU2: Peer test 5 nonce mismatch ", nonce, " connID=", m_SourceConnID);
+			break;
+			case 6: // Charlie from Alice
+				if (m_Address)
+					SendPeerTest (7, buf + offset, len - offset, m_Address->i);
+				else
+					LogPrint (eLogWarning, "SSU2: Unknown address for peer test 6");
+				m_Server.RemoveSession (~htobe64 (((uint64_t)nonce << 32) | nonce));
+			break;
+			case 7: // Alice from Charlie 2
+				if (m_Address->IsV6 ())
+					i2p::context.SetStatusV6 (eRouterStatusOK); // set status OK for ipv6 even if from SSU2
+				m_Server.RemoveSession (htobe64 (((uint64_t)nonce << 32) | nonce));
+			break;
 			default:
 				LogPrint (eLogWarning, "SSU2: PeerTest unexpected msg num ", buf[0]);
 		}
@@ -2555,24 +2418,20 @@ namespace transport
 	{
 		if (m_Address)
 			return i2p::context.GetRouterInfo ().GetSSU2Address (m_Address->IsV4 ());
-		else if (!m_RemoteEndpoint.address ().is_unspecified ())
-			return i2p::context.GetRouterInfo ().GetSSU2Address (m_RemoteEndpoint.address ().is_v4 ());
 		return nullptr;
 	}
 
-	void SSU2Session::AdjustMaxPayloadSize (size_t maxMtu)
+	void SSU2Session::AdjustMaxPayloadSize ()
 	{
 		auto addr = FindLocalAddress ();
 		if (addr && addr->ssu)
 		{
 			int mtu = addr->ssu->mtu;
 			if (!mtu && addr->IsV4 ()) mtu = SSU2_MAX_PACKET_SIZE;
-			if (mtu > (int)maxMtu) mtu = maxMtu;
 			if (m_Address && m_Address->ssu && (!mtu || m_Address->ssu->mtu < mtu))
 				mtu = m_Address->ssu->mtu;
 			if (mtu)
 			{
-				if (mtu > (int)SSU2_MAX_PACKET_SIZE) mtu = SSU2_MAX_PACKET_SIZE;
 				if (mtu < (int)SSU2_MIN_PACKET_SIZE) mtu = SSU2_MIN_PACKET_SIZE;
 				m_MaxPayloadSize = mtu - (addr->IsV6 () ? IPV6_HEADER_SIZE: IPV4_HEADER_SIZE) - UDP_HEADER_SIZE - 32;
 				LogPrint (eLogDebug, "SSU2: Session MTU=", mtu, ", max payload size=", m_MaxPayloadSize);
@@ -2625,7 +2484,7 @@ namespace transport
 				i2p::context.SetTestingV6 (testing);
 		}
 		if (!testing)
-			m_Server.AdjustTimeOffset (0, nullptr); // reset time offset when testing is over
+			m_Server.AdjustTimeOffset (0); // reset time offset when testing is over
 	}
 
 	size_t SSU2Session::CreateAddressBlock (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep)
@@ -2640,48 +2499,41 @@ namespace transport
 
 	size_t SSU2Session::CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr<const i2p::data::RouterInfo> r)
 	{
-		if (!r || len < 5) return 0;
-		return CreateRouterInfoBlock (buf, len, r->GetSharedBuffer ());
-	}
-
-	size_t SSU2Session::CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr<const i2p::data::RouterInfo::Buffer> riBuffer)
-	{
-		if (!riBuffer || len < 5) return 0;
+		if (!r || !r->GetBuffer () || len < 5) return 0;
 		buf[0] = eSSU2BlkRouterInfo;
-		size_t size = riBuffer->GetBufferLen ();
+		size_t size = r->GetBufferLen ();
 		if (size + 5 < len)
 		{
-			memcpy (buf + 5, riBuffer->data (), size);
+			memcpy (buf + 5, r->GetBuffer (), size);
 			buf[3] = 0; // flag
 		}
 		else
 		{
 			i2p::data::GzipDeflator deflator;
 			deflator.SetCompressionLevel (9);
-			size = deflator.Deflate (riBuffer->data (), riBuffer->GetBufferLen (), buf + 5, len - 5);
+			size = deflator.Deflate (r->GetBuffer (), r->GetBufferLen (), buf + 5, len - 5);
 			if (!size) return 0; // doesn't fit
 			buf[3] = SSU2_ROUTER_INFO_FLAG_GZIP; // flag
 		}
 		htobe16buf (buf + 1, size + 2); // size
 		buf[4] = 1; // frag
 		return size + 5;
-	}	
-	
-		
+	}
+
 	size_t SSU2Session::CreateAckBlock (uint8_t * buf, size_t len)
 	{
 		if (len < 8) return 0;
+		int maxNumRanges = (len - 8) >> 1;
+		if (maxNumRanges > SSU2_MAX_NUM_ACK_RANGES) maxNumRanges = SSU2_MAX_NUM_ACK_RANGES;
 		buf[0] = eSSU2BlkAck;
 		uint32_t ackThrough = m_OutOfSequencePackets.empty () ? m_ReceivePacketNum : *m_OutOfSequencePackets.rbegin ();
 		htobe32buf (buf + 3, ackThrough); // Ack Through
 		uint16_t acnt = 0;
+		int numRanges = 0;
 		if (ackThrough)
 		{
 			if (m_OutOfSequencePackets.empty ())
-			{	
 				acnt = std::min ((int)ackThrough, SSU2_MAX_NUM_ACNT); // no gaps
-				m_NumRanges = 0;
-			}	
 			else
 			{
 				auto it = m_OutOfSequencePackets.rbegin (); it++; // prev packet num
@@ -2694,102 +2546,93 @@ namespace transport
 						it++;
 				}
 				// ranges
-				if (!m_NumRanges)
-				{	
-					int maxNumRanges = (len - 8) >> 1;
-					if (maxNumRanges > SSU2_MAX_NUM_ACK_RANGES) maxNumRanges = SSU2_MAX_NUM_ACK_RANGES;
-					int numRanges = 0;
-					uint32_t lastNum = ackThrough - acnt;
-					if (acnt > SSU2_MAX_NUM_ACNT)
+				uint32_t lastNum = ackThrough - acnt;
+				if (acnt > SSU2_MAX_NUM_ACNT)
+				{
+					auto d = std::div (acnt - SSU2_MAX_NUM_ACNT, SSU2_MAX_NUM_ACNT);
+					acnt = SSU2_MAX_NUM_ACNT;
+					if (d.quot > maxNumRanges)
 					{
-						auto d = std::div (acnt - SSU2_MAX_NUM_ACNT, SSU2_MAX_NUM_ACNT);
-						acnt = SSU2_MAX_NUM_ACNT;
-						if (d.quot > maxNumRanges)
-						{
-							d.quot = maxNumRanges;
-							d.rem = 0;
-						}
-						// Acks only ranges for acnt
-						for (int i = 0; i < d.quot; i++)
-						{
-							m_Ranges[numRanges*2] = 0; m_Ranges[numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // NACKs 0, Acks 255
-							numRanges++;
-						}
-						if (d.rem > 0)
-						{
-							m_Ranges[numRanges*2] = 0; m_Ranges[numRanges*2 + 1] = d.rem;
-							numRanges++;
-						}
+						d.quot = maxNumRanges;
+						d.rem = 0;
 					}
-					int numPackets = acnt + numRanges*SSU2_MAX_NUM_ACNT;
-					while (it != m_OutOfSequencePackets.rend () &&
-						numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS)
+					// Acks only ranges for acnt
+					for (int i = 0; i < d.quot; i++)
 					{
-						if (lastNum - (*it) > SSU2_MAX_NUM_ACNT)
-						{
-							// NACKs only ranges
-							if (lastNum > (*it) + SSU2_MAX_NUM_ACNT*(maxNumRanges - numRanges)) break; // too many NACKs
-							while (lastNum - (*it) > SSU2_MAX_NUM_ACNT)
-							{
-								m_Ranges[numRanges*2] = SSU2_MAX_NUM_ACNT; m_Ranges[numRanges*2 + 1] = 0; // NACKs 255, Acks 0
-								lastNum -= SSU2_MAX_NUM_ACNT;
-								numRanges++;
-								numPackets += SSU2_MAX_NUM_ACNT;
-							}
-						}
-						// NACKs and Acks ranges
-						m_Ranges[numRanges*2] = lastNum - (*it) - 1; // NACKs
-						numPackets += m_Ranges[numRanges*2];
-						lastNum = *it; it++;
-						int numAcks = 1;
-						while (it != m_OutOfSequencePackets.rend () && lastNum > 0 && *it == lastNum - 1)
-						{
-							numAcks++; lastNum--;
-							it++;
-						}
-						while (numAcks > SSU2_MAX_NUM_ACNT)
-						{
-							// Acks only ranges
-							m_Ranges[numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // Acks 255
-							numAcks -= SSU2_MAX_NUM_ACNT;
-							numRanges++;
-							numPackets += SSU2_MAX_NUM_ACNT;
-							m_Ranges[numRanges*2] = 0; // NACKs 0
-							if (numRanges >= maxNumRanges || numPackets >= SSU2_MAX_NUM_ACK_PACKETS) break;
-						}
-						if (numAcks > SSU2_MAX_NUM_ACNT) numAcks = SSU2_MAX_NUM_ACNT;
-						m_Ranges[numRanges*2 + 1] = (uint8_t)numAcks; // Acks
-						numPackets += numAcks;
+						buf[8 + numRanges*2] = 0; buf[8 + numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // NACKs 0, Acks 255
 						numRanges++;
 					}
-					if (it == m_OutOfSequencePackets.rend () &&
-						numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS)
+					if (d.rem > 0)
 					{
-						// add range between out-of-sequence and received
-						int nacks = *m_OutOfSequencePackets.begin () - m_ReceivePacketNum - 1;
-						if (nacks > 0)
+						buf[8 + numRanges*2] = 0; buf[8 + numRanges*2 + 1] = d.rem;
+						numRanges++;
+					}
+				}
+				int numPackets = acnt + numRanges*SSU2_MAX_NUM_ACNT;
+				while (it != m_OutOfSequencePackets.rend () &&
+					numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS)
+				{
+					if (lastNum - (*it) > SSU2_MAX_NUM_ACNT)
+					{
+						// NACKs only ranges
+						if (lastNum > (*it) + SSU2_MAX_NUM_ACNT*(maxNumRanges - numRanges)) break; // too many NACKs
+						while (lastNum - (*it) > SSU2_MAX_NUM_ACNT)
 						{
-							if (nacks > SSU2_MAX_NUM_ACNT) nacks = SSU2_MAX_NUM_ACNT;
-							m_Ranges[numRanges*2] = nacks;
-							m_Ranges[numRanges*2 + 1] = std::min ((int)m_ReceivePacketNum + 1, SSU2_MAX_NUM_ACNT);
+							buf[8 + numRanges*2] = SSU2_MAX_NUM_ACNT; buf[8 + numRanges*2 + 1] = 0; // NACKs 255, Acks 0
+							lastNum -= SSU2_MAX_NUM_ACNT;
 							numRanges++;
+							numPackets += SSU2_MAX_NUM_ACNT;
 						}
 					}
-					m_NumRanges = numRanges;
-				}	
-				if (m_NumRanges)
-					memcpy (buf + 8, m_Ranges, m_NumRanges*2);
+					// NACKs and Acks ranges
+					buf[8 + numRanges*2] = lastNum - (*it) - 1; // NACKs
+					numPackets += buf[8 + numRanges*2];
+					lastNum = *it; it++;
+					int numAcks = 1;
+					while (it != m_OutOfSequencePackets.rend () && lastNum > 0 && *it == lastNum - 1)
+					{
+						numAcks++; lastNum--;
+						it++;
+					}
+					while (numAcks > SSU2_MAX_NUM_ACNT)
+					{
+						// Acks only ranges
+						buf[8 + numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // Acks 255
+						numAcks -= SSU2_MAX_NUM_ACNT;
+						numRanges++;
+						numPackets += SSU2_MAX_NUM_ACNT;
+						buf[8 + numRanges*2] = 0; // NACKs 0
+						if (numRanges >= maxNumRanges || numPackets >= SSU2_MAX_NUM_ACK_PACKETS) break;
+					}
+					if (numAcks > SSU2_MAX_NUM_ACNT) numAcks = SSU2_MAX_NUM_ACNT;
+					buf[8 + numRanges*2 + 1] = (uint8_t)numAcks; // Acks
+					numPackets += numAcks;
+					numRanges++;
+				}
+				if (it == m_OutOfSequencePackets.rend () &&
+					numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS)
+				{
+					// add range between out-of-sequence and received
+					int nacks = *m_OutOfSequencePackets.begin () - m_ReceivePacketNum - 1;
+					if (nacks > 0)
+					{
+						if (nacks > SSU2_MAX_NUM_ACNT) nacks = SSU2_MAX_NUM_ACNT;
+						buf[8 + numRanges*2] = nacks;
+						buf[8 + numRanges*2 + 1] = std::min ((int)m_ReceivePacketNum + 1, SSU2_MAX_NUM_ACNT);
+						numRanges++;
+					}
+				}
 			}
 		}
 		buf[7] = (uint8_t)acnt; // acnt
-		htobe16buf (buf + 1, 5 + m_NumRanges*2);
-		return 8 + m_NumRanges*2;
+		htobe16buf (buf + 1, 5 + numRanges*2);
+		return 8 + numRanges*2;
 	}
 
 	size_t SSU2Session::CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize)
 	{
 		if (len < 3 || len < minSize) return 0;
-		size_t paddingSize = m_Server.GetRng ()() & 0x1F; // 0 - 31
+		size_t paddingSize = rand () & 0x0F; // 0 - 15
 		if (paddingSize + 3 > len) paddingSize = len - 3;
 		else if (paddingSize + 3 < minSize) paddingSize = minSize - 3;
 		buf[0] = eSSU2BlkPadding;
@@ -2891,7 +2734,7 @@ namespace transport
 			LogPrint (eLogError, "SSU2: Buffer for RelayResponse signature is too small ", len);
 			return 0;
 		}
-		SignedData<128> s;
+		SignedData s;
 		s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue
 		if (code == eSSU2RelayResponseCodeAccept || code >= 64) // Charlie
 			s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
@@ -2953,7 +2796,7 @@ namespace transport
 		size_t asz = CreateEndpoint (signedData + 10, 86, boost::asio::ip::udp::endpoint (localAddress->host, localAddress->port));
 		signedData[9] = asz;
 		// signature
-		SignedData<128> s;
+		SignedData s;
 		s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue
 		s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
 		s.Insert (signedData, 10 + asz); // ver, nonce, ts, asz, Alice's endpoint
@@ -2981,18 +2824,22 @@ namespace transport
 			i2p::data::GzipInflator inflator;
 			uint8_t uncompressed[i2p::data::MAX_RI_BUFFER_SIZE];
 			size_t uncompressedSize = inflator.Inflate (buf + 2, size - 2, uncompressed, i2p::data::MAX_RI_BUFFER_SIZE);
-			if (uncompressedSize && uncompressedSize <= i2p::data::MAX_RI_BUFFER_SIZE)
+			if (uncompressedSize && uncompressedSize < i2p::data::MAX_RI_BUFFER_SIZE)
 				ri = std::make_shared<i2p::data::RouterInfo>(uncompressed, uncompressedSize);
 			else
 				LogPrint (eLogInfo, "SSU2: RouterInfo decompression failed ", uncompressedSize);
 		}
-		else if (size <= i2p::data::MAX_RI_BUFFER_SIZE + 2)
-			ri = std::make_shared<i2p::data::RouterInfo>(buf + 2, size - 2);
 		else
-			LogPrint (eLogInfo, "SSU2: RouterInfo is too long ", size);
+			ri = std::make_shared<i2p::data::RouterInfo>(buf + 2, size - 2);
 		return ri;
 	}
 
+	void SSU2Session::CreateNonce (uint64_t seqn, uint8_t * nonce)
+	{
+		memset (nonce, 0, 4);
+		htole64buf (nonce + 4, seqn);
+	}
+
 	bool SSU2Session::UpdateReceivePacketNum (uint32_t packetNum)
 	{
 		if (packetNum <= m_ReceivePacketNum) return false; // duplicate
@@ -3017,17 +2864,11 @@ namespace transport
 					}
 					m_OutOfSequencePackets.erase (m_OutOfSequencePackets.begin (), it);
 				}
-				m_NumRanges = 0; // recalculate ranges when create next Ack
 			}
 			m_ReceivePacketNum = packetNum;
 		}
 		else
-		{
-			if (m_NumRanges && (m_OutOfSequencePackets.empty () ||
-				packetNum != (*m_OutOfSequencePackets.rbegin ()) + 1))
-				m_NumRanges = 0; // reset ranges if received packet is not next
 			m_OutOfSequencePackets.insert (packetNum);
-		}	
 		return true;
 	}
 
@@ -3058,67 +2899,38 @@ namespace transport
 
 	void SSU2Session::SendPathResponse (const uint8_t * data, size_t len)
 	{
-		uint8_t payload[SSU2_MAX_PACKET_SIZE];
-		size_t payloadSize = 0;
-		// datetime block
-		payload[0] = eSSU2BlkDateTime;
-		htobe16buf (payload + 1, 4);
-		htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000);
-		payloadSize += 7;
-		// address block 
-		payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize  - payloadSize, m_RemoteEndpoint);
-		// path response
-		if (payloadSize + len > m_MaxPayloadSize)
+		if (len > m_MaxPayloadSize - 3)
 		{
 			LogPrint (eLogWarning, "SSU2: Incorrect data size for path response ", len);
 			return;
-		}	
-		payload[payloadSize] = eSSU2BlkPathResponse;
-		htobe16buf (payload + payloadSize + 1, len);
-		memcpy (payload + payloadSize + 3, data, len);
-		payloadSize += len + 3;	
-		// ack block
+		}
+		uint8_t payload[SSU2_MAX_PACKET_SIZE];
+		payload[0] = eSSU2BlkPathResponse;
+		htobe16buf (payload + 1, len);
+		memcpy (payload + 3, data, len);
+		size_t payloadSize = len + 3;	
 		if (payloadSize < m_MaxPayloadSize)
-			payloadSize += CreateAckBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
-		// padding
-		if (payloadSize < m_MaxPayloadSize)
-			payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
+			payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, payloadSize < 8 ? 8 : 0);
 		SendData (payload, payloadSize);
 	}
 
-	void SSU2Session::SendPathChallenge (const boost::asio::ip::udp::endpoint& to)
+	void SSU2Session::SendPathChallenge ()
 	{
-		AdjustMaxPayloadSize (SSU2_MIN_PACKET_SIZE); // reduce to minimum 
-		m_WindowSize = SSU2_MIN_WINDOW_SIZE; // reduce window to minimum
-		
 		uint8_t payload[SSU2_MAX_PACKET_SIZE];
-		size_t payloadSize = 0;
-		// datetime block
-		payload[0] = eSSU2BlkDateTime;
-		htobe16buf (payload + 1, 4);
-		htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000);
-		payloadSize += 7;
-		// address block with new address
-		payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize  - payloadSize, to);
-		// path challenge block
-		payload[payloadSize] = eSSU2BlkPathChallenge;
-		uint64_t challenge;
-		RAND_bytes ((uint8_t *)&challenge, 8);		
-		htobe16buf (payload + payloadSize + 1, 8); // always 8 bytes
-		htobuf64 (payload + payloadSize + 3, challenge);
-		payloadSize += 11;
-		m_PathChallenge = std::make_unique<std::pair<uint64_t, boost::asio::ip::udp::endpoint> >(challenge, to);	
-		// ack block
-		if (payloadSize < m_MaxPayloadSize)
-			payloadSize += CreateAckBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
-		// padding block
-		if (payloadSize < m_MaxPayloadSize)
-			payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
-		// send to new endpoint
-		auto existing = m_RemoteEndpoint;
-		m_RemoteEndpoint = to; // send path challenge to new endpoint
-		SendData (payload, payloadSize);
-		m_RemoteEndpoint = existing; // restore endpoint back until path response received
+		payload[0] = eSSU2BlkPathChallenge;
+		size_t len = rand () % (m_MaxPayloadSize - 3);
+		htobe16buf (payload + 1, len);
+		if (len > 0)
+		{
+			RAND_bytes (payload + 3, len);
+			i2p::data::IdentHash * hash = new i2p::data::IdentHash ();
+			SHA256 (payload + 3, len, *hash);
+			m_PathChallenge.reset (hash);
+		}
+		len += 3;
+		if (len < m_MaxPayloadSize)
+			len += CreatePaddingBlock (payload + len, m_MaxPayloadSize - len, len < 8 ? 8 : 0);
+		SendData (payload, len);
 	}
 
 	void SSU2Session::CleanUp (uint64_t ts)
@@ -3181,12 +2993,22 @@ namespace transport
 		{
 			if (ts > it->second.second + SSU2_RELAY_NONCE_EXPIRATION_TIMEOUT)
 			{
-				LogPrint (eLogInfo, "SSU2: Relay nonce ", it->first, " was not responded in ", SSU2_RELAY_NONCE_EXPIRATION_TIMEOUT, " seconds, deleted");
+				LogPrint (eLogWarning, "SSU2: Relay nonce ", it->first, " was not responded in ", SSU2_RELAY_NONCE_EXPIRATION_TIMEOUT, " seconds, deleted");
 				it = m_RelaySessions.erase (it);
 			}
 			else
 				++it;
 		}
+		for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();)
+		{
+			if (ts > it->second.second + SSU2_PEER_TEST_EXPIRATION_TIMEOUT)
+			{
+				LogPrint (eLogWarning, "SSU2: Peer test nonce ", it->first, " was not responded in ", SSU2_PEER_TEST_EXPIRATION_TIMEOUT, " seconds, deleted");
+				it = m_PeerTests.erase (it);
+			}
+			else
+				++it;
+		}
 		if (m_PathChallenge)
 			RequestTermination (eSSU2TerminationReasonNormalClose);
 	}
@@ -3202,13 +3024,7 @@ namespace transport
 			m_Handler.Flush ();
 			m_IsDataReceived = false;
 		}
-		else if (!sent && !m_SentPackets.empty ()) // if only acks received, nothing sent and we still have something to resend
-			Resend (i2p::util::GetMillisecondsSinceEpoch ()); // than right time to resend
 	}
 
-	i2p::data::RouterInfo::SupportedTransports SSU2Session::GetTransportType () const
-	{
-		return m_RemoteEndpoint.address ().is_v4 () ? i2p::data::RouterInfo::eSSU2V4 : i2p::data::RouterInfo::eSSU2V6;
-	}	
 }
 }
diff --git a/libi2pd/SSU2Session.h b/libi2pd/SSU2Session.h
index ee26255f..14d76971 100644
--- a/libi2pd/SSU2Session.h
+++ b/libi2pd/SSU2Session.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2022-2025, The PurpleI2P Project
+* Copyright (c) 2022-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -15,7 +15,6 @@
 #include <set>
 #include <list>
 #include <boost/asio.hpp>
-#include "version.h"
 #include "Crypto.h"
 #include "RouterInfo.h"
 #include "RouterContext.h"
@@ -26,7 +25,7 @@ namespace i2p
 namespace transport
 {
 	const int SSU2_CONNECT_TIMEOUT = 5; // 5 seconds
-	const int SSU2_TERMINATION_TIMEOUT = 165; // in seconds
+	const int SSU2_TERMINATION_TIMEOUT = 330; // 5.5 minutes
 	const int SSU2_CLOCK_SKEW = 60; // in seconds
 	const int SSU2_CLOCK_THRESHOLD = 15; // in seconds, if more we should adjust
 	const int SSU2_TOKEN_EXPIRATION_TIMEOUT = 9; // for Retry message, in seconds
@@ -37,8 +36,8 @@ namespace transport
 	const size_t SSU2_MAX_PACKET_SIZE = 1500;
 	const size_t SSU2_MIN_PACKET_SIZE = 1280;
 	const int SSU2_HANDSHAKE_RESEND_INTERVAL = 1000; // in milliseconds
+	const int SSU2_RESEND_INTERVAL = 300; // in milliseconds
 	const int SSU2_MAX_NUM_RESENDS = 5;
-	const int SSU2_RESEND_ATTEMPT_MIN_INTERVAL = 3; // in milliseconds
 	const int SSU2_INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds
 	const int SSU2_MAX_NUM_RECEIVED_I2NP_MSGIDS = 5000; // how many msgID we store for duplicates check
 	const int SSU2_RECEIVED_I2NP_MSGIDS_CLEANUP_TIMEOUT = 10; // in seconds
@@ -46,17 +45,14 @@ namespace transport
 	const size_t SSU2_MIN_WINDOW_SIZE = 16; // in packets
 	const size_t SSU2_MAX_WINDOW_SIZE = 256; // in packets
 	const size_t SSU2_MIN_RTO = 100; // in milliseconds
-	const size_t SSU2_INITIAL_RTO = 540; // in milliseconds
 	const size_t SSU2_MAX_RTO = 2500; // in milliseconds
-	const double SSU2_UNKNOWN_RTT = -1;
-	const double SSU2_RTT_EWMA_ALPHA = 0.125;
 	const float SSU2_kAPPA = 1.8;
+	const size_t SSU2_MAX_OUTGOING_QUEUE_SIZE = 500; // in messages
 	const int SSU2_MAX_NUM_ACNT = 255; // acnt, acks or nacks
 	const int SSU2_MAX_NUM_ACK_PACKETS = 511; // ackthrough + acnt + 1 range
 	const int SSU2_MAX_NUM_ACK_RANGES = 32; // to send
 	const uint8_t SSU2_MAX_NUM_FRAGMENTS = 64;
 	const int SSU2_SEND_DATETIME_NUM_PACKETS = 256;
-	const int SSU2_MIN_RELAY_RESPONSE_RESEND_VERSION = MAKE_VERSION_NUMBER(0, 9, 64); // 0.9.64
 
 	// flags
 	const uint8_t SSU2_FLAG_IMMEDIATE_ACK_REQUESTED = 0x01;
@@ -114,8 +110,8 @@ namespace transport
 		eSSU2SessionStateTerminated,
 		eSSU2SessionStateFailed,
 		eSSU2SessionStateIntroduced,
-		eSSU2SessionStateHolePunch,
 		eSSU2SessionStatePeerTest,
+		eSSU2SessionStatePeerTestReceived, // 5 before 4
 		eSSU2SessionStateTokenRequestReceived
 	};
 
@@ -208,50 +204,45 @@ namespace transport
 	class SSU2Server;
 	class SSU2Session: public TransportSession, public std::enable_shared_from_this<SSU2Session>
 	{
-		protected:
-			
-			union Header
+		union Header
+		{
+			uint64_t ll[2];
+			uint8_t buf[16];
+			struct
 			{
-				uint64_t ll[2];
-				uint8_t buf[16];
-				struct
-				{
-					uint64_t connID;
-					uint32_t packetNum;
-					uint8_t type;
-					uint8_t flags[3];
-				} h;
-			};
+				uint64_t connID;
+				uint32_t packetNum;
+				uint8_t type;
+				uint8_t flags[3];
+			} h;
+		};
 
-		private:
-			
-			struct HandshakePacket
-			{
-				Header header;
-				uint8_t headerX[48]; // part1 for SessionConfirmed
-				uint8_t payload[SSU2_MAX_PACKET_SIZE*2];
-				size_t payloadSize = 0;
-				uint64_t sendTime = 0; // in milliseconds
-				bool isSecondFragment = false; // for SessionConfirmed
-			};
+		struct HandshakePacket
+		{
+			Header header;
+			uint8_t headerX[48]; // part1 for SessionConfirmed
+			uint8_t payload[SSU2_MAX_PACKET_SIZE*2];
+			size_t payloadSize = 0;
+			uint64_t sendTime = 0; // in milliseconds
+			bool isSecondFragment = false; // for SessionConfirmed
+		};
 
-			typedef std::function<void ()> OnEstablished;
+		typedef std::function<void ()> OnEstablished;
 
 		public:
 
 			SSU2Session (SSU2Server& server, std::shared_ptr<const i2p::data::RouterInfo> in_RemoteRouter = nullptr,
-				std::shared_ptr<const i2p::data::RouterInfo::Address> addr = nullptr, bool noise = true);
-			virtual ~SSU2Session ();
+				std::shared_ptr<const i2p::data::RouterInfo::Address> addr = nullptr);
+			~SSU2Session ();
 
 			void SetRemoteEndpoint (const boost::asio::ip::udp::endpoint& ep) { m_RemoteEndpoint = ep; };
 			const boost::asio::ip::udp::endpoint& GetRemoteEndpoint () const { return m_RemoteEndpoint; };
 			i2p::data::RouterInfo::CompatibleTransports GetRemoteTransports () const { return m_RemoteTransports; };
-			i2p::data::RouterInfo::CompatibleTransports GetRemotePeerTestTransports () const { return m_RemotePeerTestTransports; };
 			std::shared_ptr<const i2p::data::RouterInfo::Address> GetAddress () const { return m_Address; };
 			void SetOnEstablished (OnEstablished e) { m_OnEstablished = e; };
 			OnEstablished GetOnEstablished () const { return m_OnEstablished; };
 
-			virtual void Connect ();
+			void Connect ();
 			bool Introduce (std::shared_ptr<SSU2Session> session, uint32_t relayTag);
 			void WaitForIntroduction ();
 			void SendPeerTest (); // Alice, Data message
@@ -261,54 +252,29 @@ namespace transport
 			void FlushData ();
 			void Done () override;
 			void SendLocalRouterInfo (bool update) override;
-			void SendI2NPMessages (std::list<std::shared_ptr<I2NPMessage> >& msgs) override;
-			void MoveSendQueue (std::shared_ptr<SSU2Session> other);
+			void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs) override;
 			uint32_t GetRelayTag () const override { return m_RelayTag; };
-			size_t Resend (uint64_t ts); // return number of resent packets
-			uint64_t GetLastResendTime () const { return m_LastResendTime; };
+			size_t Resend (uint64_t ts); // return number or resent packets
 			bool IsEstablished () const override { return m_State == eSSU2SessionStateEstablished; };
-			i2p::data::RouterInfo::SupportedTransports GetTransportType () const override;
 			uint64_t GetConnID () const { return m_SourceConnID; };
 			SSU2SessionState GetState () const { return m_State; };
 			void SetState (SSU2SessionState state) { m_State = state; };
 
-			virtual bool ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len);
+			bool ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len);
 			bool ProcessSessionCreated (uint8_t * buf, size_t len);
 			bool ProcessSessionConfirmed (uint8_t * buf, size_t len);
 			bool ProcessRetry (uint8_t * buf, size_t len);
 			bool ProcessHolePunch (uint8_t * buf, size_t len);
-			virtual bool ProcessPeerTest (uint8_t * buf, size_t len);
+			bool ProcessPeerTest (uint8_t * buf, size_t len);
 			void ProcessData (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from);
 
-		protected:
-
-			SSU2Server& GetServer () { return m_Server; }
-			RouterStatus GetRouterStatus () const;
-			void SetRouterStatus (RouterStatus status) const;
-			size_t GetMaxPayloadSize () const { return m_MaxPayloadSize; }
-			void SetIsDataReceived (bool dataReceived) { m_IsDataReceived = dataReceived; };
-			
-			uint64_t GetSourceConnID () const { return m_SourceConnID; }
-			void SetSourceConnID (uint64_t sourceConnID) { m_SourceConnID = sourceConnID; }
-			uint64_t GetDestConnID () const { return m_DestConnID; }
-			void SetDestConnID (uint64_t destConnID) { m_DestConnID = destConnID; }
-
-			void SetAddress (std::shared_ptr<const i2p::data::RouterInfo::Address> addr) { m_Address = addr; }
-			void HandlePayload (const uint8_t * buf, size_t len);
-
-			size_t CreateAddressBlock (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep);
-			size_t CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize = 0);
-			size_t CreatePeerTestBlock (uint8_t * buf, size_t len, uint8_t msg, SSU2PeerTestCode code, const uint8_t * routerHash, const uint8_t * signedData, size_t signedDataLen);
-
-			bool ExtractEndpoint (const uint8_t * buf, size_t size, boost::asio::ip::udp::endpoint& ep);
-			
 		private:
 
 			void Terminate ();
 			void Established ();
 			void ScheduleConnectTimer ();
 			void HandleConnectTimer (const boost::system::error_code& ecode);
-			void PostI2NPMessages ();
+			void PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs);
 			bool SendQueue (); // returns true if ack block was sent
 			bool SendFragmentedMessage (std::shared_ptr<I2NPMessage> msg);
 			void ResendHandshakePacket ();
@@ -326,41 +292,48 @@ namespace transport
 			uint32_t SendData (const uint8_t * buf, size_t len, uint8_t flags = 0); // returns packet num
 			void SendQuickAck ();
 			void SendTermination ();
+			void SendHolePunch (uint32_t nonce, const boost::asio::ip::udp::endpoint& ep, const uint8_t * introKey, uint64_t token);
+			void SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, const uint8_t * introKey); // PeerTest message
 			void SendPathResponse (const uint8_t * data, size_t len);
-			void SendPathChallenge (const boost::asio::ip::udp::endpoint& to);
+			void SendPathChallenge ();
 
+			void HandlePayload (const uint8_t * buf, size_t len);
 			void HandleDateTime (const uint8_t * buf, size_t len);
-			void HandleRouterInfo (const uint8_t * buf, size_t len);
 			void HandleAck (const uint8_t * buf, size_t len);
 			void HandleAckRange (uint32_t firstPacketNum, uint32_t lastPacketNum, uint64_t ts);
-			virtual void HandleAddress (const uint8_t * buf, size_t len);
+			void HandleAddress (const uint8_t * buf, size_t len);
+			bool ExtractEndpoint (const uint8_t * buf, size_t size, boost::asio::ip::udp::endpoint& ep);
 			size_t CreateEndpoint (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep);
 			std::shared_ptr<const i2p::data::RouterInfo::Address> FindLocalAddress () const;
-			void AdjustMaxPayloadSize (size_t maxMtu = SSU2_MAX_PACKET_SIZE);
+			void AdjustMaxPayloadSize ();
+			RouterStatus GetRouterStatus () const;
+			void SetRouterStatus (RouterStatus status) const;
 			bool GetTestingState () const;
 			void SetTestingState(bool testing) const;
 			std::shared_ptr<const i2p::data::RouterInfo> ExtractRouterInfo (const uint8_t * buf, size_t size);
+			void CreateNonce (uint64_t seqn, uint8_t * nonce);
 			bool UpdateReceivePacketNum (uint32_t packetNum); // for Ack, returns false if duplicate
 			void HandleFirstFragment (const uint8_t * buf, size_t len);
 			void HandleFollowOnFragment (const uint8_t * buf, size_t len);
 			void HandleRelayRequest (const uint8_t * buf, size_t len);
 			void HandleRelayIntro (const uint8_t * buf, size_t len, int attempts = 0);
 			void HandleRelayResponse (const uint8_t * buf, size_t len);
-			virtual void HandlePeerTest (const uint8_t * buf, size_t len);
+			void HandlePeerTest (const uint8_t * buf, size_t len);
 			void HandleI2NPMsg (std::shared_ptr<I2NPMessage>&& msg);
 
+			size_t CreateAddressBlock (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep);
 			size_t CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr<const i2p::data::RouterInfo> r);
-			size_t CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr<const i2p::data::RouterInfo::Buffer> riBuffer);
 			size_t CreateAckBlock (uint8_t * buf, size_t len);
+			size_t CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize = 0);
 			size_t CreateI2NPBlock (uint8_t * buf, size_t len, std::shared_ptr<I2NPMessage>&& msg);
 			size_t CreateFirstFragmentBlock (uint8_t * buf, size_t len, std::shared_ptr<I2NPMessage> msg);
 			size_t CreateFollowOnFragmentBlock (uint8_t * buf, size_t len, std::shared_ptr<I2NPMessage> msg, uint8_t& fragmentNum, uint32_t msgID);
 			size_t CreateRelayIntroBlock (uint8_t * buf, size_t len, const uint8_t * introData, size_t introDataLen);
 			size_t CreateRelayResponseBlock (uint8_t * buf, size_t len, SSU2RelayResponseCode code, uint32_t nonce, uint64_t token, bool v4);
-
+			size_t CreatePeerTestBlock (uint8_t * buf, size_t len, uint8_t msg, SSU2PeerTestCode code, const uint8_t * routerHash, const uint8_t * signedData, size_t signedDataLen);
 			size_t CreatePeerTestBlock (uint8_t * buf, size_t len, uint32_t nonce); // Alice
 			size_t CreateTerminationBlock (uint8_t * buf, size_t len);
-			
+
 		private:
 
 			SSU2Server& m_Server;
@@ -370,8 +343,7 @@ namespace transport
 			std::unique_ptr<HandshakePacket> m_SentHandshakePacket; // SessionRequest, SessionCreated or SessionConfirmed
 			std::shared_ptr<const i2p::data::RouterInfo::Address> m_Address;
 			boost::asio::ip::udp::endpoint m_RemoteEndpoint;
-			i2p::data::RouterInfo::CompatibleTransports m_RemoteTransports, m_RemotePeerTestTransports; 
-			int m_RemoteVersion;
+			i2p::data::RouterInfo::CompatibleTransports m_RemoteTransports; // for peer tests
 			uint64_t m_DestConnID, m_SourceConnID;
 			SSU2SessionState m_State;
 			uint8_t m_KeyDataSend[64], m_KeyDataReceive[64];
@@ -379,40 +351,27 @@ namespace transport
 			std::set<uint32_t> m_OutOfSequencePackets; // packet nums > receive packet num
 			std::map<uint32_t, std::shared_ptr<SSU2SentPacket> > m_SentPackets; // packetNum -> packet
 			std::unordered_map<uint32_t, std::shared_ptr<SSU2IncompleteMessage> > m_IncompleteMessages; // msgID -> I2NP
-			std::unordered_map<uint32_t, std::pair <std::shared_ptr<SSU2Session>, uint64_t > > m_RelaySessions; // nonce->(Alice, timestamp) for Bob or nonce->(Charlie, timestamp) for Alice
+			std::map<uint32_t, std::pair <std::shared_ptr<SSU2Session>, uint64_t > > m_RelaySessions; // nonce->(Alice, timestamp) for Bob or nonce->(Charlie, timestamp) for Alice
+			std::map<uint32_t, std::pair <std::shared_ptr<SSU2Session>, uint64_t > > m_PeerTests; // same as for relay sessions
 			std::list<std::shared_ptr<I2NPMessage> > m_SendQueue;
 			i2p::I2NPMessagesHandler m_Handler;
-			std::list<std::shared_ptr<I2NPMessage> > m_IntermediateQueue; // from transports
-			mutable std::mutex m_IntermediateQueueMutex;
 			bool m_IsDataReceived;
-			double m_RTT;
-			int m_MsgLocalExpirationTimeout;
-			int m_MsgLocalSemiExpirationTimeout;
-			size_t m_WindowSize, m_RTO;
+			size_t m_WindowSize, m_RTT, m_RTO;
 			uint32_t m_RelayTag; // between Bob and Charlie
 			OnEstablished m_OnEstablished; // callback from Established
 			boost::asio::deadline_timer m_ConnectTimer;
 			SSU2TerminationReason m_TerminationReason;
 			size_t m_MaxPayloadSize;
-			std::unique_ptr<std::pair<uint64_t, boost::asio::ip::udp::endpoint> > m_PathChallenge;
+			std::unique_ptr<i2p::data::IdentHash> m_PathChallenge;
 			std::unordered_map<uint32_t, uint32_t> m_ReceivedI2NPMsgIDs; // msgID -> timestamp in seconds
-			uint64_t m_LastResendTime, m_LastResendAttemptTime; // in milliseconds
-			int m_NumRanges;
-			uint8_t m_Ranges[SSU2_MAX_NUM_ACK_RANGES*2]; // ranges sent with previous Ack if any
 	};
-	
+
 	inline uint64_t CreateHeaderMask (const uint8_t * kh, const uint8_t * nonce)
 	{
 		uint64_t data = 0;
 		i2p::crypto::ChaCha20 ((uint8_t *)&data, 8, kh, nonce, (uint8_t *)&data);
 		return data;
 	}
-
-	inline void CreateNonce (uint64_t seqn, uint8_t * nonce)
-	{
-		memset (nonce, 0, 4);
-		htole64buf (nonce + 4, seqn);
-	}
 }
 }
 
diff --git a/libi2pd/Signature.cpp b/libi2pd/Signature.cpp
index 3e4b451b..342b6d03 100644
--- a/libi2pd/Signature.cpp
+++ b/libi2pd/Signature.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -7,10 +7,6 @@
 */
 
 #include <memory>
-#include <openssl/evp.h>
-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
-#include <openssl/core_names.h>
-#endif
 #include "Log.h"
 #include "Signature.h"
 
@@ -18,163 +14,6 @@ namespace i2p
 {
 namespace crypto
 {
-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
-	DSAVerifier::DSAVerifier ():
-		m_PublicKey (nullptr)
-	{
-	}
-
-	DSAVerifier::~DSAVerifier ()
-	{
-		if (m_PublicKey)
-			EVP_PKEY_free (m_PublicKey);
-	}
-
-	void DSAVerifier::SetPublicKey (const uint8_t * signingKey)
-	{
-		if (m_PublicKey)
-			EVP_PKEY_free (m_PublicKey);
-		BIGNUM * pub = BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL);
-		m_PublicKey = CreateDSA (pub);
-		BN_free (pub);
-	}
-	
-	bool DSAVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
-	{
-		// calculate SHA1 digest
-		uint8_t digest[20], sign[48];
-		SHA1 (buf, len, digest);
-		// signature
-		DSA_SIG * sig = DSA_SIG_new();
-		DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL));
-		// to DER format
-		uint8_t * s = sign;
-		auto l = i2d_DSA_SIG (sig, &s);
-		DSA_SIG_free(sig);
-		// verify
-		auto ctx = EVP_PKEY_CTX_new (m_PublicKey, NULL);
-        EVP_PKEY_verify_init(ctx);
-        EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha1());
-        bool ret = EVP_PKEY_verify(ctx, sign, l, digest, 20);
-		EVP_PKEY_CTX_free(ctx);
-		return ret;
-	}
-
-	DSASigner::DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey)
-	{
-		BIGNUM * priv = BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL);
-		m_PrivateKey = CreateDSA (nullptr, priv);
-		BN_free (priv);
-	}
-
-	DSASigner::~DSASigner ()
-	{
-		if (m_PrivateKey)
-			EVP_PKEY_free (m_PrivateKey);
-	}
-
-	void DSASigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const
-	{
-		uint8_t digest[20], sign[48];
-		SHA1 (buf, len, digest);
-		auto ctx = EVP_PKEY_CTX_new (m_PrivateKey, NULL);
-        EVP_PKEY_sign_init(ctx);
-        EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha1());
-		size_t l = 48;
-        EVP_PKEY_sign(ctx, sign, &l, digest, 20);
-		const uint8_t * s1 = sign;
-    	DSA_SIG * sig = d2i_DSA_SIG (NULL, &s1, l);
-		const BIGNUM * r, * s;
-		DSA_SIG_get0 (sig, &r, &s);
-		bn2buf (r, signature, DSA_SIGNATURE_LENGTH/2);
-		bn2buf (s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2);
-		DSA_SIG_free(sig);
-		EVP_PKEY_CTX_free(ctx);
-	}
-	
-	void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
-	{
-		EVP_PKEY * paramskey = CreateDSA();
-		EVP_PKEY_CTX * ctx = EVP_PKEY_CTX_new_from_pkey(NULL, paramskey, NULL);
-		EVP_PKEY_keygen_init(ctx);
-		EVP_PKEY * pkey = nullptr;
-		EVP_PKEY_keygen(ctx, &pkey);
-		BIGNUM * pub = NULL, * priv = NULL;
-		EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, &pub);
-		bn2buf (pub, signingPublicKey, DSA_PUBLIC_KEY_LENGTH);
-		EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, &priv);
-		bn2buf (priv, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH);
-		BN_free (pub); BN_free (priv);
-		EVP_PKEY_free (pkey);
-		EVP_PKEY_free (paramskey);
-		EVP_PKEY_CTX_free (ctx);
-	}	
-#else	
-	
-	DSAVerifier::DSAVerifier ()
-	{
-		m_PublicKey = CreateDSA ();
-	}
-
-	DSAVerifier::~DSAVerifier ()
-	{
-		DSA_free (m_PublicKey);
-	}
-
-	void DSAVerifier::SetPublicKey (const uint8_t * signingKey)
-	{
-		DSA_set0_key (m_PublicKey, BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL), NULL);
-	}
-	
-	bool DSAVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
-	{
-		// calculate SHA1 digest
-		uint8_t digest[20];
-		SHA1 (buf, len, digest);
-		// signature
-		DSA_SIG * sig = DSA_SIG_new();
-		DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL));
-		// DSA verification
-		int ret = DSA_do_verify (digest, 20, sig, m_PublicKey);
-		DSA_SIG_free(sig);
-		return ret;
-	}
-
-	DSASigner::DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey)
-	{
-		m_PrivateKey = CreateDSA ();
-		DSA_set0_key (m_PrivateKey, BN_bin2bn (signingPublicKey, DSA_PUBLIC_KEY_LENGTH, NULL), BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL));
-	}
-
-	DSASigner::~DSASigner ()
-	{
-		DSA_free (m_PrivateKey);
-	}
-
-	void DSASigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const
-	{
-		uint8_t digest[20];
-		SHA1 (buf, len, digest);
-		DSA_SIG * sig = DSA_do_sign (digest, 20, m_PrivateKey);
-		const BIGNUM * r, * s;
-		DSA_SIG_get0 (sig, &r, &s);
-		bn2buf (r, signature, DSA_SIGNATURE_LENGTH/2);
-		bn2buf (s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2);
-		DSA_SIG_free(sig);
-	}
-
-	void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
-	{
-		DSA * dsa = CreateDSA ();
-		DSA_generate_key (dsa);
-		const BIGNUM * pub_key, * priv_key;
-		DSA_get0_key(dsa, &pub_key, &priv_key);
-		bn2buf (priv_key, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH);
-		bn2buf (pub_key, signingPublicKey, DSA_PUBLIC_KEY_LENGTH);
-		DSA_free (dsa);
-	}
-#endif	
-	
 #if OPENSSL_EDDSA
 	EDDSA25519Verifier::EDDSA25519Verifier ():
 		m_Pkey (nullptr)
@@ -310,178 +149,5 @@ namespace crypto
 			LogPrint (eLogError, "EdDSA signing key is not set");
 	}
 #endif
-
-#if (OPENSSL_VERSION_NUMBER >= 0x030000000)
-	static const OSSL_PARAM EDDSA25519phParams[] =
-	{
-		OSSL_PARAM_utf8_string ("instance", (char *)"Ed25519ph", 9),
-		OSSL_PARAM_END
-	};
-		
-	bool EDDSA25519phVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
-	{
-		auto pkey = GetPkey ();
-		if (pkey)
-		{	
-			uint8_t digest[64];
-			SHA512 (buf, len, digest);
-			EVP_MD_CTX * ctx = EVP_MD_CTX_create ();
-			EVP_DigestVerifyInit_ex (ctx, NULL, NULL, NULL, NULL, pkey, EDDSA25519phParams);
-			auto ret = EVP_DigestVerify (ctx, signature, 64, digest, 64);
-			EVP_MD_CTX_destroy (ctx);	
-			return ret;	
-		}	
-		else
-			LogPrint (eLogError, "EdDSA verification key is not set");
-		return false;
-	}
-
-	EDDSA25519phSigner::EDDSA25519phSigner (const uint8_t * signingPrivateKey): 
-		EDDSA25519Signer (signingPrivateKey)
-	{		
-	}
-		
-	void EDDSA25519phSigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const
-	{
-		auto pkey = GetPkey ();
-		if (pkey)
-		{	
-			uint8_t digest[64];
-			SHA512 (buf, len, digest);
-			EVP_MD_CTX * ctx = EVP_MD_CTX_create ();
-			size_t l = 64;
-			uint8_t sig[64];
-			EVP_DigestSignInit_ex (ctx, NULL, NULL, NULL, NULL, pkey, EDDSA25519phParams);
-			if (!EVP_DigestSign (ctx, sig, &l, digest, 64))
-				LogPrint (eLogError, "EdDSA signing failed");
-			memcpy (signature, sig, 64);
-			EVP_MD_CTX_destroy (ctx);
-		}
-		else
-			LogPrint (eLogError, "EdDSA signing key is not set");
-	}		
-#endif	
-		
-#if OPENSSL_PQ
-		
-	MLDSA44Verifier::MLDSA44Verifier ():
-		m_Pkey (nullptr)
-	{
-	}
-
-	MLDSA44Verifier::~MLDSA44Verifier ()
-	{
-		EVP_PKEY_free (m_Pkey);
-	}
-
-	void MLDSA44Verifier::SetPublicKey (const uint8_t * signingKey)
-	{
-		if (m_Pkey) 
-		{ 
-			EVP_PKEY_free (m_Pkey);
-			m_Pkey = nullptr;
-		}	
-		OSSL_PARAM params[] =
-		{
-			OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PUB_KEY, (uint8_t *)signingKey, GetPublicKeyLen ()),
-			OSSL_PARAM_END
-		};		
-		EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "ML-DSA-44", NULL);
-		if (ctx)
-		{
-			EVP_PKEY_fromdata_init (ctx);
-			EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, params);
-			EVP_PKEY_CTX_free (ctx);
-		}
-		else
-			LogPrint (eLogError, "MLDSA44 can't create PKEY context");
-	}
-
-	bool MLDSA44Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
-	{
-		bool ret = false;
-		if (m_Pkey)
-		{	
-			EVP_PKEY_CTX * vctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL);
-			if (vctx)
-			{
-				EVP_SIGNATURE * sig = EVP_SIGNATURE_fetch (NULL, "ML-DSA-44", NULL);
-				if (sig)
-				{
-					int encode = 1;
-					OSSL_PARAM params[] =
-					{
-						OSSL_PARAM_int(OSSL_SIGNATURE_PARAM_MESSAGE_ENCODING, &encode),
-						OSSL_PARAM_END
-					};		
-					EVP_PKEY_verify_message_init (vctx, sig, params);
-					ret = EVP_PKEY_verify (vctx, signature, GetSignatureLen (), buf, len);
-					EVP_SIGNATURE_free (sig);
-				}	
-				EVP_PKEY_CTX_free (vctx);
-			}	
-			else
-				LogPrint (eLogError, "MLDSA44 can't obtain context from PKEY");
-		}	
-		else
-			LogPrint (eLogError, "MLDSA44 verification key is not set");
-		return ret;
-	}
-
-	MLDSA44Signer::MLDSA44Signer (const uint8_t * signingPrivateKey):
-		m_Pkey (nullptr)
-	{
-		OSSL_PARAM params[] =
-		{
-			OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PRIV_KEY, (uint8_t *)signingPrivateKey, MLDSA44_PRIVATE_KEY_LENGTH),
-			OSSL_PARAM_END
-		};		
-		EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "ML-DSA-44", NULL);
-		if (ctx)
-		{
-			EVP_PKEY_fromdata_init (ctx);
-			EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PRIVATE_KEY, params);
-			EVP_PKEY_CTX_free (ctx);
-		}
-		else
-			LogPrint (eLogError, "MLDSA44 can't create PKEY context");
-	}
-
-	MLDSA44Signer::~MLDSA44Signer ()
-	{
-		if (m_Pkey) EVP_PKEY_free (m_Pkey);
-	}
-
-	void MLDSA44Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const
-	{
-		if (m_Pkey)
-		{	
-			EVP_PKEY_CTX * sctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL);
-			if (sctx)
-			{
-				EVP_SIGNATURE * sig = EVP_SIGNATURE_fetch (NULL, "ML-DSA-44", NULL);
-				if (sig)
-				{
-					int encode = 1;
-					OSSL_PARAM params[] =
-					{
-						OSSL_PARAM_int(OSSL_SIGNATURE_PARAM_MESSAGE_ENCODING, &encode),
-						OSSL_PARAM_END
-					};		
-					EVP_PKEY_sign_message_init (sctx, sig, params);
-					size_t siglen = MLDSA44_SIGNATURE_LENGTH;
-					EVP_PKEY_sign (sctx, signature, &siglen, buf, len);
-					EVP_SIGNATURE_free (sig);
-				}	
-				EVP_PKEY_CTX_free (sctx);
-			}	
-			else
-				LogPrint (eLogError, "MLDSA44 can't obtain context from PKEY");
-		}	
-		else
-			LogPrint (eLogError, "MLDSA44 signing key is not set");
-	}	
-		
-#endif		
 }
 }
diff --git a/libi2pd/Signature.h b/libi2pd/Signature.h
index 20c7e11b..8bd94357 100644
--- a/libi2pd/Signature.h
+++ b/libi2pd/Signature.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -43,55 +43,94 @@ namespace crypto
 			virtual void Sign (const uint8_t * buf, int len, uint8_t * signature) const = 0;
 	};
 
-	// DSA
 	const size_t DSA_PUBLIC_KEY_LENGTH = 128;
 	const size_t DSA_SIGNATURE_LENGTH = 40;
 	const size_t DSA_PRIVATE_KEY_LENGTH = DSA_SIGNATURE_LENGTH/2;
 	class DSAVerifier: public Verifier
 	{
 		public:
-			
-			DSAVerifier ();
-			~DSAVerifier ();
 
-			// implements Verifier
-			void SetPublicKey (const uint8_t * signingKey) override;
-			bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const override;
-			size_t GetPublicKeyLen () const override { return DSA_PUBLIC_KEY_LENGTH; };
-			size_t GetSignatureLen () const override { return DSA_SIGNATURE_LENGTH; };
-			
+			DSAVerifier ()
+			{
+				m_PublicKey = CreateDSA ();
+			}
+
+			void SetPublicKey (const uint8_t * signingKey)
+			{
+				DSA_set0_key (m_PublicKey, BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL), NULL);
+			}
+
+			~DSAVerifier ()
+			{
+				DSA_free (m_PublicKey);
+			}
+
+			bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
+			{
+				// calculate SHA1 digest
+				uint8_t digest[20];
+				SHA1 (buf, len, digest);
+				// signature
+				DSA_SIG * sig = DSA_SIG_new();
+				DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL));
+				// DSA verification
+				int ret = DSA_do_verify (digest, 20, sig, m_PublicKey);
+				DSA_SIG_free(sig);
+				return ret;
+			}
+
+			size_t GetPublicKeyLen () const { return DSA_PUBLIC_KEY_LENGTH; };
+			size_t GetSignatureLen () const { return DSA_SIGNATURE_LENGTH; };
+
 		private:
 
-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
-			EVP_PKEY * m_PublicKey;
-#else			
 			DSA * m_PublicKey;
-#endif			
 	};
 
 	class DSASigner: public Signer
 	{
 		public:
 
-			DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey);
+			DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey)
 			// openssl 1.1 always requires DSA public key even for signing
-			~DSASigner ();
+			{
+				m_PrivateKey = CreateDSA ();
+				DSA_set0_key (m_PrivateKey, BN_bin2bn (signingPublicKey, DSA_PUBLIC_KEY_LENGTH, NULL), BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL));
+			}
 
-			// implements Signer
-			void Sign (const uint8_t * buf, int len, uint8_t * signature) const override;
+			~DSASigner ()
+			{
+				DSA_free (m_PrivateKey);
+			}
+
+			void Sign (const uint8_t * buf, int len, uint8_t * signature) const
+			{
+				uint8_t digest[20];
+				SHA1 (buf, len, digest);
+				DSA_SIG * sig = DSA_do_sign (digest, 20, m_PrivateKey);
+				const BIGNUM * r, * s;
+				DSA_SIG_get0 (sig, &r, &s);
+				bn2buf (r, signature, DSA_SIGNATURE_LENGTH/2);
+				bn2buf (s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2);
+				DSA_SIG_free(sig);
+			}
 
 		private:
 
-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
-			EVP_PKEY * m_PrivateKey;
-#else
 			DSA * m_PrivateKey;
-#endif			
 	};
 
-	void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey);
+	inline void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
+	{
+		DSA * dsa = CreateDSA ();
+		DSA_generate_key (dsa);
+		const BIGNUM * pub_key, * priv_key;
+		DSA_get0_key(dsa, &pub_key, &priv_key);
+		bn2buf (priv_key, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH);
+		bn2buf (pub_key, signingPublicKey, DSA_PUBLIC_KEY_LENGTH);
+		DSA_free (dsa);
+	}
 
-	// ECDSA	
 	struct SHA256Hash
 	{
 		static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest)
@@ -122,6 +161,7 @@ namespace crypto
 		enum { hashLen = 64 };
 	};
 
+	// EcDSA
 	template<typename Hash, int curve, size_t keyLen>
 	class ECDSAVerifier: public Verifier
 	{
@@ -263,28 +303,14 @@ namespace crypto
 
 		private:
 
-#if OPENSSL_EDDSA	
-			
+#if OPENSSL_EDDSA
 			EVP_PKEY * m_Pkey;
-			
-		protected:
-
-			EVP_PKEY * GetPkey () const { return m_Pkey; };
 #else
 			EDDSAPoint m_PublicKey;
 			uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH];
 #endif
 	};
 
-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
-	class EDDSA25519phVerifier: public EDDSA25519Verifier
-	{
-		public:
-
-			bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const;
-	};
-#endif	
-	
 	class EDDSA25519SignerCompat: public Signer
 	{
 		public:
@@ -313,10 +339,6 @@ namespace crypto
 
 			void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
 
-		protected:
-
-			EVP_PKEY * GetPkey () const { return m_Pkey; };
-			
 		private:
 
 			EVP_PKEY * m_Pkey;
@@ -328,18 +350,6 @@ namespace crypto
 
 #endif
 
-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
-	class EDDSA25519phSigner: public EDDSA25519Signer
-	{
-		public:
-
-			EDDSA25519phSigner (const uint8_t * signingPrivateKey);
-		
-			void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
-	};
-	
-#endif	
-	
 	inline void CreateEDDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
 	{
 #if OPENSSL_EDDSA
@@ -520,57 +530,6 @@ namespace crypto
 		RedDSA25519Signer signer (signingPrivateKey);
 		memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH);
 	}
-	
-#if OPENSSL_PQ
-#include <openssl/core_names.h>
-	
-	// Post-Quantum
-	const size_t MLDSA44_PUBLIC_KEY_LENGTH = 1312;
-	const size_t MLDSA44_SIGNATURE_LENGTH = 2420;
-	const size_t MLDSA44_PRIVATE_KEY_LENGTH = 2560;
-	class MLDSA44Verifier: public Verifier
-	{
-		public:
-
-			MLDSA44Verifier ();
-			void SetPublicKey (const uint8_t * signingKey);
-			~MLDSA44Verifier ();
-
-			bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const;
-
-			size_t GetPublicKeyLen () const { return MLDSA44_PUBLIC_KEY_LENGTH; };
-			size_t GetSignatureLen () const { return MLDSA44_SIGNATURE_LENGTH; };
-			size_t GetPrivateKeyLen () const { return MLDSA44_PRIVATE_KEY_LENGTH; };
-
-		private:
-			
-			EVP_PKEY * m_Pkey;
-	};
-
-	class MLDSA44Signer: public Signer
-	{
-		public:
-
-			MLDSA44Signer (const uint8_t * signingPrivateKey);
-			~MLDSA44Signer ();
-
-			void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
-			
-		private:
-
-			EVP_PKEY * m_Pkey;
-	};
-	
-	inline void CreateMLDSA44RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
-	{
-		EVP_PKEY * pkey = EVP_PKEY_Q_keygen (NULL, NULL, "ML-DSA-44");
-		size_t len = MLDSA44_PUBLIC_KEY_LENGTH;
-        EVP_PKEY_get_octet_string_param (pkey, OSSL_PKEY_PARAM_PUB_KEY, signingPublicKey, MLDSA44_PUBLIC_KEY_LENGTH, &len);
-		len = MLDSA44_PRIVATE_KEY_LENGTH;
-		EVP_PKEY_get_octet_string_param (pkey, OSSL_PKEY_PARAM_PRIV_KEY, signingPrivateKey, MLDSA44_PRIVATE_KEY_LENGTH, &len);
-		EVP_PKEY_free (pkey);
-	}	
-#endif	
 }
 }
 
diff --git a/libi2pd/Socks5.h b/libi2pd/Socks5.h
deleted file mode 100644
index 8db8939b..00000000
--- a/libi2pd/Socks5.h
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
-* Copyright (c) 2024, The PurpleI2P Project
-*
-* This file is part of Purple i2pd project and licensed under BSD3
-*
-* See full license text in LICENSE file at top of project tree
-*
-*/
-
-#ifndef SOCKS5_H__
-#define SOCKS5_H__
-
-#include <string>
-#include <memory>
-#include <boost/asio.hpp>
-#include "I2PEndian.h"
-
-namespace i2p
-{
-namespace transport
-{
-	// SOCKS5 constants
-	const uint8_t SOCKS5_VER = 0x05;
-	const uint8_t SOCKS5_CMD_CONNECT = 0x01;
-	const uint8_t SOCKS5_CMD_UDP_ASSOCIATE = 0x03;
-	const uint8_t SOCKS5_ATYP_IPV4 = 0x01;
-	const uint8_t SOCKS5_ATYP_IPV6 = 0x04;
-	const uint8_t SOCKS5_ATYP_NAME = 0x03;
-	const size_t SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE = 10;
-	const size_t SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE = 22;
-
-	const uint8_t SOCKS5_REPLY_SUCCESS = 0x00;
-	const uint8_t SOCKS5_REPLY_SERVER_FAILURE = 0x01;
-	const uint8_t SOCKS5_REPLY_CONNECTION_NOT_ALLOWED = 0x02;
-	const uint8_t SOCKS5_REPLY_NETWORK_UNREACHABLE = 0x03;
-	const uint8_t SOCKS5_REPLY_HOST_UNREACHABLE = 0x04;
-	const uint8_t SOCKS5_REPLY_CONNECTION_REFUSED = 0x05;
-	const uint8_t SOCKS5_REPLY_TTL_EXPIRED = 0x06;
-	const uint8_t SOCKS5_REPLY_COMMAND_NOT_SUPPORTED = 0x07;
-	const uint8_t SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED = 0x08;
-	
-	// SOCKS5 handshake
-	template<typename Socket, typename Handler>
-	void Socks5ReadReply (Socket& s, Handler handler)
-	{
-		auto readbuff = std::make_shared<std::vector<int8_t> >(258); // max possible
-		boost::asio::async_read(s, boost::asio::buffer(readbuff->data (), 5), boost::asio::transfer_all(), // read 4 bytes of header + first byte of address
-			[readbuff, &s, handler](const boost::system::error_code& ec, std::size_t transferred)
-			{
-				if (!ec)
-				{    
-				    if ((*readbuff)[1] == SOCKS5_REPLY_SUCCESS)
-				    {
-						size_t len = 0;
-						switch ((*readbuff)[3]) // ATYP
-						{
-							case SOCKS5_ATYP_IPV4: len = 3; break; // address length 4 bytes
-							case SOCKS5_ATYP_IPV6: len = 15; break; // address length 16 bytes
-							case SOCKS5_ATYP_NAME: len += (*readbuff)[4]; break; // first byte of address is length
-							default: ;
-						}	
-						if (len)
-						{
-							len += 2; // port
-							boost::asio::async_read(s, boost::asio::buffer(readbuff->data (), len), boost::asio::transfer_all(),
-								[readbuff, handler](const boost::system::error_code& ec, std::size_t transferred)
-								{
-									if (!ec)
-										handler (boost::system::error_code ()); // success
-									else
-										handler (boost::asio::error::make_error_code (boost::asio::error::connection_aborted));
-								});		
-						}	
-						else
-							handler (boost::asio::error::make_error_code (boost::asio::error::fault)); // unknown address type 
-				    }
-				    else	
-						switch ((*readbuff)[1]) // REP
-						{
-							case SOCKS5_REPLY_SERVER_FAILURE:
-								handler (boost::asio::error::make_error_code (boost::asio::error::access_denied ));
-							break;	
-							case SOCKS5_REPLY_CONNECTION_NOT_ALLOWED:
-								handler (boost::asio::error::make_error_code (boost::asio::error::no_permission));
-							break;	
-							case SOCKS5_REPLY_HOST_UNREACHABLE:
-								handler (boost::asio::error::make_error_code (boost::asio::error::host_unreachable));
-							break;
-							case SOCKS5_REPLY_NETWORK_UNREACHABLE:
-								handler (boost::asio::error::make_error_code (boost::asio::error::network_unreachable));
-							break;
-							case SOCKS5_REPLY_CONNECTION_REFUSED:
-								handler (boost::asio::error::make_error_code (boost::asio::error::connection_refused));
-							break;
-							case SOCKS5_REPLY_TTL_EXPIRED:
-								handler (boost::asio::error::make_error_code (boost::asio::error::timed_out));
-							break;	
-							case SOCKS5_REPLY_COMMAND_NOT_SUPPORTED:
-								handler (boost::asio::error::make_error_code (boost::asio::error::operation_not_supported));
-							break;	
-							case SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED:
-								handler (boost::asio::error::make_error_code (boost::asio::error::no_protocol_option));
-							break;	
-							default:
-				        		handler (boost::asio::error::make_error_code (boost::asio::error::connection_aborted)); 
-						}
-				}
-				else
-				   handler (ec); 
-			});
-	}	
-	
-	template<typename Socket, typename Handler>
-	void Socks5Connect (Socket& s, Handler handler, std::shared_ptr<std::vector<uint8_t> > buff, uint16_t port)
-	{
-		if (buff && buff->size () >= 6)
-		{
-		    (*buff)[0] = SOCKS5_VER;
-			(*buff)[1] = SOCKS5_CMD_CONNECT;
-			(*buff)[2] = 0x00;
-			htobe16buf(buff->data () + buff->size () - 2, port);
-		    boost::asio::async_write(s, boost::asio::buffer(*buff), boost::asio::transfer_all(),
-				[buff, &s, handler](const boost::system::error_code& ec, std::size_t transferred)
-				{
-		            (void) transferred;
-					if (!ec)
-		           		Socks5ReadReply (s, handler);
-					else
-		                handler (ec);
-				});
-		}
-		else
-		    handler (boost::asio::error::make_error_code (boost::asio::error::no_buffer_space));
-	}
-
-	template<typename Socket, typename Handler>
-	void Socks5Connect (Socket& s, const boost::asio::ip::tcp::endpoint& ep, Handler handler)
-	{
-		std::shared_ptr<std::vector<uint8_t> > buff;
-		if(ep.address ().is_v4 ())
-		{
-		    buff = std::make_shared<std::vector<uint8_t> >(10);
-			(*buff)[3] = SOCKS5_ATYP_IPV4;
-			auto addrbytes = ep.address ().to_v4().to_bytes();
-			memcpy(buff->data () + 4, addrbytes.data(), 4);
-		}
-		else if (ep.address ().is_v6 ())
-		{
-		    buff = std::make_shared<std::vector<uint8_t> >(22);
-			(*buff)[3] = SOCKS5_ATYP_IPV6;
-			auto addrbytes = ep.address ().to_v6().to_bytes();
-			memcpy(buff->data () + 4, addrbytes.data(), 16);
-		}
-		if (buff)
-		    Socks5Connect (s, handler, buff, ep.port ());
-		else
-		    handler (boost::asio::error::make_error_code (boost::asio::error::fault));  
-	}
-
-	template<typename Socket, typename Handler>
-	void Socks5Connect (Socket& s, const std::pair<std::string, uint16_t>& ep, Handler handler)
-	{
-		auto& addr = ep.first;
-		if (addr.length () <= 255) 
-		{
-		    auto buff = std::make_shared<std::vector<uint8_t> >(addr.length () + 7);
-		    (*buff)[3] = SOCKS5_ATYP_NAME;
-		    (*buff)[4] = addr.length (); 
-		    memcpy (buff->data () + 5, addr.c_str (), addr.length ());
-		    Socks5Connect (s, handler, buff, ep.second);    
-		}
-		else
-		    handler (boost::asio::error::make_error_code (boost::asio::error::name_too_long));
-	}
-
-
-	template<typename Socket, typename Endpoint, typename Handler>
-	void Socks5Handshake (Socket& s, Endpoint ep, Handler handler)
-	{
-		static const uint8_t methodSelection[3] = { SOCKS5_VER, 0x01, 0x00 }; // 1 method, no auth
-		boost::asio::async_write(s, boost::asio::buffer(methodSelection, 3), boost::asio::transfer_all(),
-			[&s, ep, handler] (const boost::system::error_code& ec, std::size_t transferred)
-			{
-				(void) transferred;
-		        if (!ec)
-		        {
-		            auto readbuff = std::make_shared<std::vector<uint8_t> >(2);
-		            boost::asio::async_read(s, boost::asio::buffer(*readbuff), boost::asio::transfer_all(),
-		                [&s, ep, handler, readbuff] (const boost::system::error_code& ec, std::size_t transferred)
-			            {
-		                    if (!ec)
-		                    {
-		                        if (transferred == 2 && (*readbuff)[1] == 0x00) // no auth
-		                            Socks5Connect (s, ep, handler);
-		                        else
-		                            handler (boost::asio::error::make_error_code (boost::asio::error::invalid_argument)); 
-		                    }
-		                    else 
-		                        handler (ec);
-		                });
-		        }
-		        else
-		            handler (ec);
-			});
-	}
-	
-}
-}
-
-#endif
diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp
index 99da5fd2..629039ab 100644
--- a/libi2pd/Streaming.cpp
+++ b/libi2pd/Streaming.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -19,55 +19,39 @@ namespace i2p
 {
 namespace stream
 {
-	void SendBufferQueue::Add (std::shared_ptr<SendBuffer>&& buf)
+	void SendBufferQueue::Add (std::shared_ptr<SendBuffer> buf)
 	{
 		if (buf)
 		{
+			m_Buffers.push_back (buf);
 			m_Size += buf->len;
-			m_Buffers.push_back (std::move (buf));
 		}
 	}
 
 	size_t SendBufferQueue::Get (uint8_t * buf, size_t len)
 	{
-		if (!m_Size) return 0;
 		size_t offset = 0;
-		if (len >= m_Size)
+		while (!m_Buffers.empty () && offset < len)
 		{
-			for (auto& it: m_Buffers)
+			auto nextBuffer = m_Buffers.front ();
+			auto rem = nextBuffer->GetRemainingSize ();
+			if (offset + rem <= len)
 			{
-				auto rem = it->GetRemainingSize ();
-				memcpy (buf + offset, it->GetRemaningBuffer (), rem);
+				// whole buffer
+				memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), rem);
 				offset += rem;
+				m_Buffers.pop_front (); // delete it
 			}
-			m_Buffers.clear ();
-			m_Size = 0;
-			return offset;
-		}	
-		else
-		{	
-			while (!m_Buffers.empty () && offset < len)
+			else
 			{
-				auto nextBuffer = m_Buffers.front ();
-				auto rem = nextBuffer->GetRemainingSize ();
-				if (offset + rem <= len)
-				{
-					// whole buffer
-					memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), rem);
-					offset += rem;
-					m_Buffers.pop_front (); // delete it
-				}
-				else
-				{
-					// partially
-					rem = len - offset;
-					memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), rem);
-					nextBuffer->offset += rem;
-					offset = len; // break
-				}
+				// partially
+				rem = len - offset;
+				memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), rem);
+				nextBuffer->offset += rem;
+				offset = len; // break
 			}
-			m_Size -= offset;
-		}	
+		}
+		m_Size -= offset;
 		return offset;
 	}
 
@@ -75,66 +59,36 @@ namespace stream
 	{
 		if (!m_Buffers.empty ())
 		{
-			for (auto& it: m_Buffers)
+			for (auto it: m_Buffers)
 				it->Cancel ();
 			m_Buffers.clear ();
 			m_Size = 0;
 		}
 	}
 
-	Stream::Stream (boost::asio::io_context& service, StreamingDestination& local,
+	Stream::Stream (boost::asio::io_service& service, StreamingDestination& local,
 		std::shared_ptr<const i2p::data::LeaseSet> remote, int port): m_Service (service),
-		m_SendStreamID (0), m_SequenceNumber (0), m_DropWindowDelaySequenceNumber (0),
-		m_TunnelsChangeSequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_PreviousReceivedSequenceNumber (-1),
-		m_LastConfirmedReceivedSequenceNumber (0), // for limit inbound speed
-		m_Status (eStreamStatusNew), m_IsIncoming (false), m_IsAckSendScheduled (false), m_IsNAcked (false), m_IsFirstACK (false), 
-		m_IsResendNeeded (false), m_IsFirstRttSample (false), m_IsSendTime (true), m_IsWinDropped (false), m_IsClientChoked (false),
-		m_IsTimeOutResend (false), m_IsImmediateAckRequested (false), m_IsRemoteLeaseChangeInProgress (false), m_DoubleWinIncCounter (false), m_LocalDestination (local),
-		m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_SendTimer (m_Service), m_ResendTimer (m_Service),
+		m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1),
+		m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local),
+		m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service),
 		m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port),
-		m_RTT (INITIAL_RTT), m_SlowRTT (INITIAL_RTT), m_SlowRTT2 (INITIAL_RTT), m_WindowSize (INITIAL_WINDOW_SIZE), m_LastWindowDropSize  (0),
-		m_WindowDropTargetSize (0), m_WindowIncCounter (0), m_RTO (INITIAL_RTO),
-		m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_PrevRTTSample (INITIAL_RTT), m_WindowSizeTail (0), 
-		m_Jitter (0), m_MinPacingTime (0),
-		m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_LastACKRecieveTime (0), m_ACKRecieveInterval (local.GetOwner ()->GetStreamingAckDelay ()), m_RemoteLeaseChangeTime (0), m_LastWindowIncTime (0),
-		m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed
-		m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU)
+		m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO),
+		m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()),
+		m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0), m_MTU (STREAMING_MTU)
 	{
 		RAND_bytes ((uint8_t *)&m_RecvStreamID, 4);
 		m_RemoteIdentity = remote->GetIdentity ();
-		auto outboundSpeed = local.GetOwner ()->GetStreamingOutboundSpeed ();
-		if (outboundSpeed)
-			m_MinPacingTime = (1000000LL*STREAMING_MTU)/outboundSpeed;
-		
-		auto inboundSpeed = local.GetOwner ()->GetStreamingInboundSpeed (); // for limit inbound speed
-		if (inboundSpeed)
-			m_PacketACKInterval = (1000000LL*STREAMING_MTU)/inboundSpeed;
 	}
 
-	Stream::Stream (boost::asio::io_context& service, StreamingDestination& local):
-		m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_DropWindowDelaySequenceNumber (0),
-		m_TunnelsChangeSequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_PreviousReceivedSequenceNumber (-1),
-		m_LastConfirmedReceivedSequenceNumber (0), // for limit inbound speed
-		m_Status (eStreamStatusNew), m_IsIncoming (true), m_IsAckSendScheduled (false), m_IsNAcked (false), m_IsFirstACK (false),  
-		m_IsResendNeeded (false), m_IsFirstRttSample (false), m_IsSendTime (true), m_IsWinDropped (false), m_IsClientChoked (false),
-		m_IsTimeOutResend (false), m_IsImmediateAckRequested (false), m_IsRemoteLeaseChangeInProgress (false), m_DoubleWinIncCounter (false), m_LocalDestination (local),
-		m_ReceiveTimer (m_Service), m_SendTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service),
-		m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_RTT (INITIAL_RTT), m_SlowRTT (INITIAL_RTT), m_SlowRTT2 (INITIAL_RTT),
-		m_WindowSize (INITIAL_WINDOW_SIZE), m_LastWindowDropSize  (0), m_WindowDropTargetSize (0), m_WindowIncCounter (0), 
-		m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()),
-		m_PrevRTTSample (INITIAL_RTT), m_WindowSizeTail (0), m_Jitter (0), m_MinPacingTime (0),
-		m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_LastACKRecieveTime (0), m_ACKRecieveInterval (local.GetOwner ()->GetStreamingAckDelay ()), m_RemoteLeaseChangeTime (0), m_LastWindowIncTime (0),
-		m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed
-		m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU)
+	Stream::Stream (boost::asio::io_service& service, StreamingDestination& local):
+		m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1),
+		m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local),
+		m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service),
+		m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_WindowSize (MIN_WINDOW_SIZE),
+		m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()),
+		m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0), m_MTU (STREAMING_MTU)
 	{
 		RAND_bytes ((uint8_t *)&m_RecvStreamID, 4);
-		auto outboundSpeed = local.GetOwner ()->GetStreamingOutboundSpeed ();
-		if (outboundSpeed)
-			m_MinPacingTime = (1000000LL*STREAMING_MTU)/outboundSpeed;
-		
-		auto inboundSpeed = local.GetOwner ()->GetStreamingInboundSpeed (); // for limit inbound speed
-		if (inboundSpeed)
-			m_PacketACKInterval = (1000000LL*STREAMING_MTU)/inboundSpeed;
 	}
 
 	Stream::~Stream ()
@@ -149,7 +103,6 @@ namespace stream
 		m_AckSendTimer.cancel ();
 		m_ReceiveTimer.cancel ();
 		m_ResendTimer.cancel ();
-		m_SendTimer.cancel ();
 		//CleanUp (); /* Need to recheck - broke working on windows */
 		if (deleteFromDestination)
 			m_LocalDestination.DeleteStream (shared_from_this ());
@@ -164,8 +117,6 @@ namespace stream
 			m_ReceiveQueue.pop ();
 			m_LocalDestination.DeletePacket (packet);
 		}
-		
-		m_NACKedPackets.clear ();
 
 		for (auto it: m_SentPackets)
 			m_LocalDestination.DeletePacket (it);
@@ -178,11 +129,6 @@ namespace stream
 
 	void Stream::HandleNextPacket (Packet * packet)
 	{
-		if (m_Status == eStreamStatusTerminated)
-		{
-			m_LocalDestination.DeletePacket (packet);
-			return;
-		}	
 		m_NumReceivedBytes += packet->GetLength ();
 		if (!m_SendStreamID)
 		{	
@@ -200,29 +146,10 @@ namespace stream
 			ProcessAck (packet);
 
 		int32_t receivedSeqn = packet->GetSeqn ();
-		if (!receivedSeqn && m_LastReceivedSequenceNumber >= 0)
+		if (!receivedSeqn && !packet->GetFlags ())
 		{
-			uint16_t flags = packet->GetFlags ();
-			if (flags)
-				// plain ack with options
-				ProcessOptions (flags, packet);
-			else	
-				// plain ack
-				{
-					LogPrint (eLogDebug, "Streaming: Plain ACK received");
-					if (m_IsImmediateAckRequested)
-					{
-						auto ts = i2p::util::GetMillisecondsSinceEpoch ();
-						if (m_IsFirstRttSample)
-						{
-							m_RTT = ts - m_LastSendTime;
-							m_IsFirstRttSample = false;
-						}
-						else
-							m_RTT = (m_RTT + (ts - m_LastSendTime)) / 2;
-						m_IsImmediateAckRequested = false;
-					}
-				}
+			// plain ack
+			LogPrint (eLogDebug, "Streaming: Plain ACK received");
 			m_LocalDestination.DeletePacket (packet);
 			return;
 		}
@@ -232,8 +159,7 @@ namespace stream
 		{
 			// we have received next in sequence message
 			ProcessPacket (packet);
-			if (m_Status == eStreamStatusTerminated) return;
-			
+
 			// we should also try stored messages if any
 			for (auto it = m_SavedPackets.begin (); it != m_SavedPackets.end ();)
 			{
@@ -243,7 +169,6 @@ namespace stream
 					m_SavedPackets.erase (it++);
 
 					ProcessPacket (savedPacket);
-					if (m_Status == eStreamStatusTerminated) return;
 				}
 				else
 					break;
@@ -254,9 +179,13 @@ namespace stream
 			{
 				if (!m_IsAckSendScheduled)
 				{
+					m_IsAckSendScheduled = true;
 					auto ackTimeout = m_RTT/10;
 					if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay;
-					ScheduleAck (ackTimeout);
+					else if (ackTimeout < MIN_SEND_ACK_TIMEOUT) ackTimeout = MIN_SEND_ACK_TIMEOUT;
+					m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(ackTimeout));
+					m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer,
+						shared_from_this (), std::placeholders::_1));
 				}
 			}
 			else if (packet->IsSYN ())
@@ -269,21 +198,8 @@ namespace stream
 			{
 				// we have received duplicate
 				LogPrint (eLogWarning, "Streaming: Duplicate message ", receivedSeqn, " on sSID=", m_SendStreamID);
-				if (receivedSeqn <= m_PreviousReceivedSequenceNumber || receivedSeqn == m_LastReceivedSequenceNumber)
- 				{
- 					m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel);
- 					CancelRemoteLeaseChange ();
- 					UpdateCurrentRemoteLease ();
- 				}
- 				m_PreviousReceivedSequenceNumber = receivedSeqn;
+				SendQuickAck (); // resend ack for previous message again
 				m_LocalDestination.DeletePacket (packet); // packet dropped
-				if (!m_IsAckSendScheduled)
-				{
-					SendQuickAck (); // resend ack for previous message again
-					auto ackTimeout = m_RTT/10;
-					if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay;
-					ScheduleAck (ackTimeout);
-				}
 			}
 			else
 			{
@@ -292,18 +208,22 @@ namespace stream
 				SavePacket (packet);
 				if (m_LastReceivedSequenceNumber >= 0)
 				{
-					if (!m_IsAckSendScheduled)
-					{	
-						// send NACKs for missing messages 
-						SendQuickAck (); 
-						auto ackTimeout = m_RTT/10;
-						if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay;
-						ScheduleAck (ackTimeout);
-					}	
-				}	
+					// send NACKs for missing messages ASAP
+					if (m_IsAckSendScheduled)
+					{
+						m_IsAckSendScheduled = false;
+						m_AckSendTimer.cancel ();
+					}
+					SendQuickAck ();
+				}
 				else
+				{
 					// wait for SYN
-					ScheduleAck (SYN_TIMEOUT);
+					m_IsAckSendScheduled = true;
+					m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(SYN_TIMEOUT));
+					m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer,
+						shared_from_this (), std::placeholders::_1));
+				}
 			}
 		}
 	}
@@ -357,41 +277,18 @@ namespace stream
 	{
 		const uint8_t * optionData = packet->GetOptionData ();
 		size_t optionSize = packet->GetOptionSize ();
-		if (optionSize > packet->len)
-		{
-			LogPrint (eLogInfo, "Streaming: Invalid option size ", optionSize, " Discarded");
-			return false;
-		}	
-		if (!flags) return true;
-		bool immediateAckRequested = false;
 		if (flags & PACKET_FLAG_DELAY_REQUESTED)
 		{
-			uint16_t delayRequested = bufbe16toh (optionData);
-			if (!delayRequested) // 0 requests an immediate ack
-				immediateAckRequested = true;
-			else if (!m_IsAckSendScheduled)
+			if (!m_IsAckSendScheduled)
 			{
-				if (delayRequested < m_RTT)
+				uint16_t delayRequested = bufbe16toh (optionData);
+				if (delayRequested > 0 && delayRequested < m_RTT)
 				{
 					m_IsAckSendScheduled = true;
 					m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(delayRequested));
 					m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer,
 						shared_from_this (), std::placeholders::_1));
 				}
-				if (delayRequested >= DELAY_CHOKING)
-				{
-					if (!m_IsClientChoked)
-					{
-						LogPrint (eLogDebug, "Streaming: Client choked, set min. window size");
-						m_WindowDropTargetSize = MIN_WINDOW_SIZE;
-						m_LastWindowDropSize = 0;
-						m_WindowIncCounter = 0;
-						m_IsClientChoked = true;
-						m_IsWinDropped = false;
-						m_DropWindowDelaySequenceNumber = m_SequenceNumber;
-						UpdatePacingTime ();
-					}
-				}
 			}
 			optionData += 2;
 		}
@@ -450,45 +347,30 @@ namespace stream
 
 		if (flags & PACKET_FLAG_SIGNATURE_INCLUDED)
 		{
-			bool verified = false;
+			uint8_t signature[256];
 			auto signatureLen = m_TransientVerifier ? m_TransientVerifier->GetSignatureLen () : m_RemoteIdentity->GetSignatureLen ();
-			if (signatureLen > packet->GetLength ())
+			if(signatureLen <= sizeof(signature))
+			{
+				memcpy (signature, optionData, signatureLen);
+				memset (const_cast<uint8_t *>(optionData), 0, signatureLen);
+				bool verified = m_TransientVerifier ?
+					m_TransientVerifier->Verify (packet->GetBuffer (), packet->GetLength (), signature) :
+					m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature);
+				if (!verified)
+				{
+					LogPrint (eLogError, "Streaming: Signature verification failed, sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID);
+					Close ();
+					flags |= PACKET_FLAG_CLOSE;
+				}
+				memcpy (const_cast<uint8_t *>(optionData), signature, signatureLen);
+				optionData += signatureLen;
+			}
+			else
 			{
 				LogPrint (eLogError, "Streaming: Signature too big, ", signatureLen, " bytes");
 				return false;
-			}	
-			if(signatureLen <= 256)
-			{
-				// standard
-				uint8_t signature[256];
-				memcpy (signature, optionData, signatureLen);
-				memset (const_cast<uint8_t *>(optionData), 0, signatureLen);
-				verified = m_TransientVerifier ?
-					m_TransientVerifier->Verify (packet->GetBuffer (), packet->GetLength (), signature) :
-					m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature);
-				if (verified)
-					memcpy (const_cast<uint8_t *>(optionData), signature, signatureLen);
 			}
-			else
-			{
-				// post quantum
-				std::vector<uint8_t> signature(signatureLen);
-				memcpy (signature.data (), optionData, signatureLen);
-				memset (const_cast<uint8_t *>(optionData), 0, signatureLen);
-				verified = m_TransientVerifier ?
-					m_TransientVerifier->Verify (packet->GetBuffer (), packet->GetLength (), signature.data ()) :
-					m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature.data ());
-			}
-			if (verified)
-				optionData += signatureLen;
-			else
-			{
-				LogPrint (eLogError, "Streaming: Signature verification failed, sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID);
-				return false;
-			}	
 		}
-		if (immediateAckRequested)
-			SendQuickAck ();
 		return true;
 	}
 
@@ -519,16 +401,11 @@ namespace stream
 		bool acknowledged = false;
 		auto ts = i2p::util::GetMillisecondsSinceEpoch ();
 		uint32_t ackThrough = packet->GetAckThrough ();
-		m_NACKedPackets.clear ();
 		if (ackThrough > m_SequenceNumber)
 		{
 			LogPrint (eLogError, "Streaming: Unexpected ackThrough=", ackThrough, " > seqn=", m_SequenceNumber);
 			return;
 		}
-		int rttSample = INT_MAX;
-		int incCounter = 0;
-		m_IsNAcked = false;
-		m_IsResendNeeded = false;
 		int nackCount = packet->GetNACKCount ();
 		for (auto it = m_SentPackets.begin (); it != m_SentPackets.end ();)
 		{
@@ -541,8 +418,6 @@ namespace stream
 					for (int i = 0; i < nackCount; i++)
 						if (seqn == packet->GetNACK (i))
 						{
-							m_NACKedPackets.insert (*it);
-							m_IsNAcked = true;
 							nacked = true;
 							break;
 						}
@@ -554,134 +429,43 @@ namespace stream
 					}
 				}
 				auto sentPacket = *it;
-				int64_t rtt = (int64_t)ts - (int64_t)sentPacket->sendTime;
-				if (rtt < 0)
-					LogPrint (eLogError, "Streaming: Packet ", seqn, "sent from the future, sendTime=", sentPacket->sendTime);
-				if (!seqn)
+				uint64_t rtt = ts - sentPacket->sendTime;
+				if(ts < sentPacket->sendTime)
 				{
-					m_IsFirstRttSample = true;
-					rttSample = rtt < 0 ? 1 : rtt;
+					LogPrint(eLogError, "Streaming: Packet ", seqn, "sent from the future, sendTime=", sentPacket->sendTime);
+					rtt = 1;
 				}
-				else if (!sentPacket->resent && seqn > m_TunnelsChangeSequenceNumber && rtt >= 0)
-					rttSample = std::min (rttSample, (int)rtt);
+				m_RTT = std::round ((m_RTT*seqn + rtt)/(seqn + 1.0));
+				m_RTO = m_RTT*1.5; // TODO: implement it better
 				LogPrint (eLogDebug, "Streaming: Packet ", seqn, " acknowledged rtt=", rtt, " sentTime=", sentPacket->sendTime);
 				m_SentPackets.erase (it++);
 				m_LocalDestination.DeletePacket (sentPacket);
 				acknowledged = true;
-				if (m_WindowIncCounter < MAX_WINDOW_SIZE && !m_IsFirstACK && !m_IsWinDropped)
-					incCounter++;
+				if (m_WindowSize < WINDOW_SIZE)
+					m_WindowSize++; // slow start
+				else
+				{
+					// linear growth
+					if (ts > m_LastWindowSizeIncreaseTime + m_RTT)
+					{
+						m_WindowSize++;
+						if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE;
+						m_LastWindowSizeIncreaseTime = ts;
+					}
+				}
+				if (!seqn && m_RoutingSession) // first message confirmed
+					m_RoutingSession->SetSharedRoutingPath (
+						std::make_shared<i2p::garlic::GarlicRoutingPath> (
+							i2p::garlic::GarlicRoutingPath{m_CurrentOutboundTunnel, m_CurrentRemoteLease, m_RTT, 0, 0}));
 			}
 			else
 				break;
 		}
-		if (m_LastACKRecieveTime)
-		{
-			uint64_t interval = ts - m_LastACKRecieveTime;
-			if (m_ACKRecieveInterval)
-				m_ACKRecieveInterval = (m_ACKRecieveInterval + interval) / 2;
-			else
-				m_ACKRecieveInterval = interval;
-		}
-		m_LastACKRecieveTime = ts;
-		if (rttSample != INT_MAX)
-		{
-			if (m_IsFirstRttSample && !m_IsFirstACK)
-			{
-				m_RTT = rttSample;
-				m_SlowRTT = rttSample;
-				m_SlowRTT2 = rttSample;
-				m_PrevRTTSample = rttSample;
-				m_Jitter = rttSample / 10; // 10%
-				m_Jitter += 15; // for low-latency connections
-				m_IsFirstRttSample = false;
-			}
-			else
-				m_RTT = (m_PrevRTTSample + rttSample) / 2;
-			if (!m_IsWinDropped) 
-			{
-				m_SlowRTT = SLOWRTT_EWMA_ALPHA * m_RTT + (1.0 - SLOWRTT_EWMA_ALPHA) * m_SlowRTT;
-				m_SlowRTT2 = RTT_EWMA_ALPHA * m_RTT + (1.0 - RTT_EWMA_ALPHA) * m_SlowRTT2;
-				// calculate jitter
-				double jitter = 0;
-				if (rttSample > m_PrevRTTSample)
-					jitter = rttSample - m_PrevRTTSample;
-				else if (rttSample < m_PrevRTTSample)
-					jitter = m_PrevRTTSample - rttSample;
-				else
-					jitter = rttSample / 10; // 10%
-				jitter += 15; // for low-latency connections
-				m_Jitter = (0.05 * jitter) + (1.0 - 0.05) * m_Jitter;
-			}
-			if (rttSample > m_SlowRTT)
-			{
-				incCounter = 0;
-				m_DoubleWinIncCounter = 1;
-			}
-			else if (rttSample < m_SlowRTT)
-			{
-				if (m_DoubleWinIncCounter)
-				{
-					incCounter = incCounter * 2;
-					m_DoubleWinIncCounter = 0;
-				}
-			}
-			m_WindowIncCounter = m_WindowIncCounter + incCounter;
-			//
-			// delay-based CC
-			if ((m_SlowRTT2 > m_SlowRTT + m_Jitter && rttSample > m_SlowRTT2 && rttSample > m_PrevRTTSample) && !m_IsWinDropped && !m_IsClientChoked) // Drop window if RTT grows too fast, late detection
-			{
-				LogPrint (eLogDebug, "Streaming: Congestion detected, reduce window size");
-				ProcessWindowDrop ();
-			}
-			UpdatePacingTime ();
-			m_PrevRTTSample = rttSample;
-			
-			bool wasInitial = m_RTO == INITIAL_RTO;
-			m_RTO = std::max (MIN_RTO, (int)(m_RTT * 1.3 + m_Jitter + m_ACKRecieveInterval)); // TODO: implement it better
-			
-			if (wasInitial)
-				ScheduleResend ();
-		}
-		if (m_IsClientChoked && ackThrough >= m_DropWindowDelaySequenceNumber)
-			m_IsClientChoked = false;
-		if (m_IsWinDropped && ackThrough > m_DropWindowDelaySequenceNumber)
-		{
-			m_IsFirstRttSample = true;
-			m_IsWinDropped = false;
-		}
-		if (m_WindowDropTargetSize && int(m_SentPackets.size ()) <= m_WindowDropTargetSize)
-		{
-			m_WindowSize = m_WindowDropTargetSize;
-			m_WindowDropTargetSize = 0;
-		}
-		if (acknowledged || m_IsNAcked)
-		{
-			ScheduleResend ();
-		}
-		if (m_SendBuffer.IsEmpty () && m_SentPackets.size () > 0) // tail loss
-		{
-			m_IsResendNeeded = true;
-			m_RTO = std::max (MIN_RTO, (int)(m_RTT * 1.5 + m_Jitter + m_ACKRecieveInterval)); // to prevent spurious retransmit
-		}
-		if (m_SentPackets.empty () && m_SendBuffer.IsEmpty ())
-		{
+		if (m_SentPackets.empty ())
 			m_ResendTimer.cancel ();
-			m_SendTimer.cancel ();
-			m_LastACKRecieveTime = 0;
-			m_ACKRecieveInterval = m_AckDelay;
-		}
-		if (acknowledged && m_IsFirstACK)
-		{
-			if (m_RoutingSession)
-				m_RoutingSession->SetSharedRoutingPath (
-					std::make_shared<i2p::garlic::GarlicRoutingPath> (
-						i2p::garlic::GarlicRoutingPath{m_CurrentOutboundTunnel, m_CurrentRemoteLease, (int)m_RTT, 0}));
-			m_IsFirstACK = false;
-		}
 		if (acknowledged)
 		{
 			m_NumResendAttempts = 0;
-			m_IsTimeOutResend = false;
 			SendBuffer ();
 		}
 		if (m_Status == eStreamStatusClosed)
@@ -718,7 +502,7 @@ namespace stream
 		{
 			// make sure that AsycReceive complete
 			auto s = shared_from_this();
-			boost::asio::post (m_Service, [s]()
+			m_Service.post ([s]()
 		    {
 				s->m_ReceiveTimer.cancel ();
 			});
@@ -746,27 +530,19 @@ namespace stream
 		else if (handler)
 			handler(boost::system::error_code ());
 		auto s = shared_from_this ();
-		boost::asio::post (m_Service, [s, buffer = std::move(buffer)]() mutable
+		m_Service.post ([s, buffer]()
 			{
 				if (buffer)
-					s->m_SendBuffer.Add (std::move(buffer));
+					s->m_SendBuffer.Add (buffer);
 				s->SendBuffer ();
 			});	
 	}
 
 	void Stream::SendBuffer ()
 	{
-		if (m_RemoteLeaseSet) // don't scheudle send for first SYN for incoming stream
-			ScheduleSend ();
-		auto ts = i2p::util::GetMillisecondsSinceEpoch ();
 		int numMsgs = m_WindowSize - m_SentPackets.size ();
-		if (numMsgs <= 0 || !m_IsSendTime) // window is full
-		{
-			m_LastSendTime = ts;
-			return;
-		}
-		else if (numMsgs > m_NumPacketsToSend)
-			numMsgs = m_NumPacketsToSend;
+		if (numMsgs <= 0) return; // window is full
+
 		bool isNoAck = m_LastReceivedSequenceNumber < 0; // first packet
 		std::vector<Packet *> packets;
 		while ((m_Status == eStreamStatusNew) || (IsEstablished () && !m_SendBuffer.IsEmpty () && numMsgs > 0))
@@ -808,8 +584,8 @@ namespace stream
 				if (!m_RemoteLeaseSet) m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ());;
 				if (m_RemoteLeaseSet)
 				{
-					m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true, !m_IsIncoming);
-					m_MTU = (m_RoutingSession && m_RoutingSession->IsRatchets ()) ? STREAMING_MTU_RATCHETS : STREAMING_MTU;
+					m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true);
+					m_MTU = m_RoutingSession->IsRatchets () ? STREAMING_MTU_RATCHETS : STREAMING_MTU;
 				}
 				uint16_t flags = PACKET_FLAG_SYNCHRONIZE | PACKET_FLAG_FROM_INCLUDED |
 					PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED;
@@ -860,15 +636,13 @@ namespace stream
 				m_AckSendTimer.cancel ();
 			}
 			bool isEmpty = m_SentPackets.empty ();
-//			auto ts = i2p::util::GetMillisecondsSinceEpoch ();
+			auto ts = i2p::util::GetMillisecondsSinceEpoch ();
 			for (auto& it: packets)
 			{
 				it->sendTime = ts;
 				m_SentPackets.insert (it);
 			}
 			SendPackets (packets);
-			m_LastSendTime = ts;
-			m_IsSendTime = false;
 			if (m_Status == eStreamStatusClosing && m_SendBuffer.IsEmpty ())
 				SendClose ();
 			if (isEmpty)
@@ -879,53 +653,10 @@ namespace stream
 	void Stream::SendQuickAck ()
 	{
 		int32_t lastReceivedSeqn = m_LastReceivedSequenceNumber;
-		// for limit inbound speed
-		auto ts = i2p::util::GetMillisecondsSinceEpoch ();
-		int numPackets = 0;
-		bool lostPackets = false;
-		int64_t passedTime = m_PacketACKInterval * INITIAL_WINDOW_SIZE; // in microseconds // while m_LastACKSendTime == 0
-		if (m_LastACKSendTime)
-			passedTime = (ts - m_LastACKSendTime)*1000; // in microseconds
-		numPackets = (passedTime + m_PacketACKIntervalRem) / m_PacketACKInterval;
-		m_PacketACKIntervalRem = (passedTime + m_PacketACKIntervalRem) - (numPackets * m_PacketACKInterval);
-		if (m_LastConfirmedReceivedSequenceNumber + numPackets < m_LastReceivedSequenceNumber)
-		{
-			lastReceivedSeqn = m_LastConfirmedReceivedSequenceNumber + numPackets;
-			if (!m_IsAckSendScheduled)
-			{
-				auto ackTimeout = m_RTT/10;
-				if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay;
-				ScheduleAck (ackTimeout);
-			}
-		}
-		if (numPackets == 0) return;
-		// for limit inbound speed
 		if (!m_SavedPackets.empty ())
 		{
-			for (auto it: m_SavedPackets)
-			{
-				auto seqn = it->GetSeqn ();
-				// for limit inbound speed
-				if (m_LastConfirmedReceivedSequenceNumber + numPackets < int(seqn)) 
-				{
-					if (!m_IsAckSendScheduled)
-					{
-						auto ackTimeout = m_RTT/10;
-						if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay;
-						ScheduleAck (ackTimeout);
-					}
-					if (lostPackets)
-						break;
-					else
-						return;
-				}
-				// for limit inbound speed
-				if ((int)seqn > lastReceivedSeqn)
-				{
-					lastReceivedSeqn = seqn;
-					lostPackets = true;	// for limit inbound speed
-				}
-			}
+			int32_t seqn = (*m_SavedPackets.rbegin ())->GetSeqn ();
+			if (seqn > lastReceivedSeqn) lastReceivedSeqn = seqn;
 		}
 		if (lastReceivedSeqn < 0)
 		{
@@ -945,7 +676,6 @@ namespace stream
 		htobe32buf (packet + size, lastReceivedSeqn);
 		size += 4; // ack Through
 		uint8_t numNacks = 0;
-		bool choking = false;
 		if (lastReceivedSeqn > m_LastReceivedSequenceNumber)
 		{
 			// fill NACKs
@@ -954,16 +684,10 @@ namespace stream
 			for (auto it: m_SavedPackets)
 			{
 				auto seqn = it->GetSeqn ();
-				if (m_LastConfirmedReceivedSequenceNumber + numPackets < int(seqn)) // for limit inbound speed
-				{
-					htobe32buf (packet + 12, nextSeqn - 1);
-					break;
-				}
 				if (numNacks + (seqn - nextSeqn) >= 256)
 				{
 					LogPrint (eLogError, "Streaming: Number of NACKs exceeds 256. seqn=", seqn, " nextSeqn=", nextSeqn);
-					htobe32buf (packet + 12, nextSeqn - 1); // change ack Through back
-					choking = true;
+					htobe32buf (packet + 12, nextSeqn); // change ack Through
 					break;
 				}
 				for (uint32_t i = nextSeqn; i < seqn; i++)
@@ -985,32 +709,14 @@ namespace stream
 			size++; // NACK count
 		}
 		packet[size] = 0;
-		size++; // resend delay	
-		bool requestImmediateAck = false;
-		if (!choking)
-			requestImmediateAck = m_LastSendTime && ts > m_LastSendTime + REQUEST_IMMEDIATE_ACK_INTERVAL &&
-				ts > m_LastSendTime + REQUEST_IMMEDIATE_ACK_INTERVAL + m_LocalDestination.GetRandom () % REQUEST_IMMEDIATE_ACK_INTERVAL_VARIANCE;
-		htobe16buf (packet + size, (choking || requestImmediateAck) ? PACKET_FLAG_DELAY_REQUESTED : 0); // no flags set or delay requested
+		size++; // resend delay
+		htobuf16 (packet + size, 0); // no flags set
 		size += 2; // flags
-		if (choking || requestImmediateAck)
-		{
-			htobe16buf (packet + size, 2); // 2 bytes delay interval
-			htobe16buf (packet + size + 2, choking ? DELAY_CHOKING : 0); // set choking or immediate ack interval
-			size += 2;
-			if (requestImmediateAck) // ack request sent
-			{
-				m_LastSendTime = ts;
-				m_IsImmediateAckRequested = true;
-			}
-		}	
-		else	
-			htobuf16 (packet + size, 0); // no options
+		htobuf16 (packet + size, 0); // no options
 		size += 2; // options size
 		p.len = size;
 
 		SendPackets (std::vector<Packet *> { &p });
-		m_LastACKSendTime = ts; // for limit inbound speed
-		m_LastConfirmedReceivedSequenceNumber = lastReceivedSeqn; // for limit inbound speed
 		LogPrint (eLogDebug, "Streaming: Quick Ack sent. ", (int)numNacks, " NACKs");
 	}
 
@@ -1109,7 +815,7 @@ namespace stream
 		m_LocalDestination.GetOwner ()->Sign (packet, size, signature);
 
 		p->len = size;
-		boost::asio::post (m_Service, std::bind (&Stream::SendPacket, shared_from_this (), p));
+		m_Service.post (std::bind (&Stream::SendPacket, shared_from_this (), p));
 		LogPrint (eLogDebug, "Streaming: FIN sent, sSID=", m_SendStreamID);
 	}
 
@@ -1141,7 +847,6 @@ namespace stream
 				m_IsAckSendScheduled = false;
 				m_AckSendTimer.cancel ();
 			}
-			if (!packet->sendTime) packet->sendTime = i2p::util::GetMillisecondsSinceEpoch ();
 			SendPackets (std::vector<Packet *> { packet });
 			bool isEmpty = m_SentPackets.empty ();
 			m_SentPackets.insert (packet);
@@ -1157,7 +862,6 @@ namespace stream
 	{
 		if (!m_RemoteLeaseSet)
 		{
-			CancelRemoteLeaseChange ();
 			UpdateCurrentRemoteLease ();
 			if (!m_RemoteLeaseSet)
 			{
@@ -1166,15 +870,7 @@ namespace stream
 			}
 		}
 		if (!m_RoutingSession || m_RoutingSession->IsTerminated () || !m_RoutingSession->IsReadyToSend ()) // expired and detached or new session sent
-		{	
-			m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true, !m_IsIncoming || m_SequenceNumber > 1);
-			if (!m_RoutingSession)
-			{
-				LogPrint (eLogError, "Streaming: Can't obtain routing session, sSID=", m_SendStreamID);
-				Terminate ();
-				return;
-			}	
-		}	
+			m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true);
 		if (!m_CurrentOutboundTunnel && m_RoutingSession) // first message to send
 		{
 			// try to get shared path first
@@ -1184,59 +880,30 @@ namespace stream
 				m_CurrentOutboundTunnel = routingPath->outboundTunnel;
 				m_CurrentRemoteLease = routingPath->remoteLease;
 				m_RTT = routingPath->rtt;
+				m_RTO = m_RTT*1.5; // TODO: implement it better
 			}
 		}
 
 		auto ts = i2p::util::GetMillisecondsSinceEpoch ();
-		if (!m_CurrentRemoteLease || !m_CurrentRemoteLease->endDate) // excluded from LeaseSet
-		{
-			CancelRemoteLeaseChange ();
+		if (!m_CurrentRemoteLease || !m_CurrentRemoteLease->endDate || // excluded from LeaseSet
+			ts >= m_CurrentRemoteLease->endDate - i2p::data::LEASE_ENDDATE_THRESHOLD)
 			UpdateCurrentRemoteLease (true);
-		}
-		if (m_RemoteLeaseChangeTime && m_IsRemoteLeaseChangeInProgress && ts > m_RemoteLeaseChangeTime + INITIAL_RTO)
-		{
-			LogPrint (eLogDebug, "Streaming: RemoteLease changed, set initial window size");
-			CancelRemoteLeaseChange ();
-			m_CurrentRemoteLease = m_NextRemoteLease;
-			ResetWindowSize ();
-		}
-		auto currentRemoteLease = m_CurrentRemoteLease;
-		if (!m_IsRemoteLeaseChangeInProgress && m_RemoteLeaseSet && m_CurrentRemoteLease && ts >= m_CurrentRemoteLease->endDate - i2p::data::LEASE_ENDDATE_THRESHOLD)
-		{
-			auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (false);
-			if (leases.size ())
-			{
-				m_IsRemoteLeaseChangeInProgress = true;
-				UpdateCurrentRemoteLease (true);
-				m_NextRemoteLease = m_CurrentRemoteLease;
-			}
-			else
-				UpdateCurrentRemoteLease (true);
-		}
 		if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate + i2p::data::LEASE_ENDDATE_THRESHOLD)
 		{
-			bool freshTunnel = false;
 			if (!m_CurrentOutboundTunnel)
 			{
 				auto leaseRouter = i2p::data::netdb.FindRouter (m_CurrentRemoteLease->tunnelGateway);
 				m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (nullptr,
 					leaseRouter ? leaseRouter->GetCompatibleTransports (false) : (i2p::data::RouterInfo::CompatibleTransports)i2p::data::RouterInfo::eAllTransports);
-				freshTunnel = true;
 			}
 			else if (!m_CurrentOutboundTunnel->IsEstablished ())
-				std::tie(m_CurrentOutboundTunnel, freshTunnel) = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel);
+				m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel);
 			if (!m_CurrentOutboundTunnel)
 			{
 				LogPrint (eLogError, "Streaming: No outbound tunnels in the pool, sSID=", m_SendStreamID);
 				m_CurrentRemoteLease = nullptr;
 				return;
 			}
-			if (freshTunnel)
-			{
-				LogPrint (eLogDebug, "Streaming: OutboundTunnel changed, set initial window size");
-				ResetWindowSize ();
-//				m_TunnelsChangeSequenceNumber = m_SequenceNumber; // should be determined more precisely
-			}
 
 			std::vector<i2p::tunnel::TunnelMessageBlock> msgs;
 			for (const auto& it: packets)
@@ -1250,11 +917,6 @@ namespace stream
 						msg
 					});
 				m_NumSentBytes += it->GetLength ();
-				if (m_IsRemoteLeaseChangeInProgress && !m_RemoteLeaseChangeTime)
-				{
-					m_RemoteLeaseChangeTime = ts;
-					m_CurrentRemoteLease = currentRemoteLease; // change it back before new lease is confirmed
-				}
 			}
 			m_CurrentOutboundTunnel->SendTunnelDataMsgs (msgs);
 		}
@@ -1273,10 +935,10 @@ namespace stream
 			if (m_RoutingSession->IsLeaseSetNonConfirmed ())
 			{
 				auto ts = i2p::util::GetMillisecondsSinceEpoch ();
-				if (ts > m_RoutingSession->GetLeaseSetSubmissionTime () + i2p::garlic::LEASESET_CONFIRMATION_TIMEOUT)
+				if (ts > m_RoutingSession->GetLeaseSetSubmissionTime () + i2p::garlic::LEASET_CONFIRMATION_TIMEOUT)
 				{
 					// LeaseSet was not confirmed, should try other tunnels
-					LogPrint (eLogWarning, "Streaming: LeaseSet was not confirmed in ", i2p::garlic::LEASESET_CONFIRMATION_TIMEOUT, " milliseconds. Trying to resubmit");
+					LogPrint (eLogWarning, "Streaming: LeaseSet was not confirmed in ", i2p::garlic::LEASET_CONFIRMATION_TIMEOUT, " milliseconds. Trying to resubmit");
 					m_RoutingSession->SetSharedRoutingPath (nullptr);
 					m_CurrentOutboundTunnel = nullptr;
 					m_CurrentRemoteLease = nullptr;
@@ -1293,103 +955,6 @@ namespace stream
 			SendQuickAck ();
 	}
 
-	void Stream::ScheduleSend ()
-	{
-		if (m_Status != eStreamStatusTerminated)
-		{
-			m_SendTimer.cancel ();
-			m_SendTimer.expires_from_now (boost::posix_time::microseconds(
-				SEND_INTERVAL + m_LocalDestination.GetRandom () % SEND_INTERVAL_VARIANCE));
-			m_SendTimer.async_wait (std::bind (&Stream::HandleSendTimer,
-				shared_from_this (), std::placeholders::_1));
-		}
-	}
-
-	void Stream::HandleSendTimer (const boost::system::error_code& ecode)
-	{
-		if (ecode != boost::asio::error::operation_aborted)
-		{
-			auto ts = i2p::util::GetMillisecondsSinceEpoch ();
-			if (m_LastSendTime && ts*1000 > m_LastSendTime*1000 + m_PacingTime)
-			{
-				if (m_PacingTime)
-				{	
-					auto numPackets = std::lldiv (m_PacingTimeRem + ts*1000 - m_LastSendTime*1000, m_PacingTime);
-					m_NumPacketsToSend = numPackets.quot;
-					m_PacingTimeRem = numPackets.rem;
-				}	
-				else
-				{
-					LogPrint (eLogError, "Streaming: pacing time is zero");
-					m_NumPacketsToSend = 1; m_PacingTimeRem = 0;
-				}	
-				m_IsSendTime = true;
-				if (m_WindowIncCounter && (m_WindowSize < MAX_WINDOW_SIZE || m_WindowDropTargetSize) && !m_SendBuffer.IsEmpty () && m_PacingTime > m_MinPacingTime && m_RTT <= m_SlowRTT)
-				{
-					float winSize = m_WindowSize;
-					if (m_WindowDropTargetSize)
-						winSize = m_WindowDropTargetSize;
-					float maxWinSize = MAX_WINDOW_SIZE;
-					if (m_LastWindowIncTime)
-						maxWinSize = (ts - m_LastWindowIncTime) / (m_RTT / MAX_WINDOW_SIZE_INC_PER_RTT) + winSize;
-					for (int i = 0; i < m_NumPacketsToSend; i++)
-					{
-						if (m_WindowIncCounter)
-						{
-							if (m_WindowDropTargetSize)
-							{
-								if (m_LastWindowDropSize && (m_LastWindowDropSize >= m_WindowDropTargetSize))
-									m_WindowDropTargetSize += 1 - (1 / ((m_LastWindowDropSize + PREV_SPEED_KEEP_TIME_COEFF) / m_WindowDropTargetSize)); // some magic here
-								else if (m_LastWindowDropSize && (m_LastWindowDropSize < m_WindowDropTargetSize))
-									m_WindowDropTargetSize += (m_WindowDropTargetSize - (m_LastWindowDropSize - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowDropTargetSize; // some magic here
-								else
-									m_WindowDropTargetSize += (m_WindowDropTargetSize - (1 - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowDropTargetSize;
-								if (m_WindowDropTargetSize > MAX_WINDOW_SIZE) m_WindowDropTargetSize = MAX_WINDOW_SIZE;
-								m_WindowIncCounter--;
-								if (m_WindowDropTargetSize >= maxWinSize)
-								{
-									m_WindowDropTargetSize = maxWinSize;
-									break;
-								}
-							}
-							else
-							{
-								if (m_LastWindowDropSize && (m_LastWindowDropSize >= m_WindowSize))
-									m_WindowSize += 1 - (1 / ((m_LastWindowDropSize + PREV_SPEED_KEEP_TIME_COEFF) / m_WindowSize)); // some magic here
-								else if (m_LastWindowDropSize && (m_LastWindowDropSize < m_WindowSize))
-									m_WindowSize += (m_WindowSize - (m_LastWindowDropSize - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowSize; // some magic here
-								else
-									m_WindowSize += (m_WindowSize - (1 - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowSize;
-								if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE;
-								m_WindowIncCounter--;
-								if (m_WindowSize >= maxWinSize)
-								{
-									m_WindowSize = maxWinSize;
-									break;
-								}
-							}
-						}
-						else
-							break;
-					}
-					m_LastWindowIncTime = ts;
-					UpdatePacingTime ();
-				}
-				else if (m_WindowIncCounter && m_WindowSize == MAX_WINDOW_SIZE && !m_SendBuffer.IsEmpty () && m_PacingTime > m_MinPacingTime)
-				{
-					m_WindowSizeTail = m_WindowSizeTail + m_WindowIncCounter;
-					if (m_WindowSizeTail > MAX_WINDOW_SIZE) m_WindowSizeTail = MAX_WINDOW_SIZE;
-				}
-				if (m_IsNAcked || m_IsResendNeeded || m_IsClientChoked) // resend packets
-					ResendPacket ();
-				else if (m_WindowSize > int(m_SentPackets.size ())) // send packets
-					SendBuffer ();
-			}
-			else // pass
-				ScheduleSend ();
-		}
-	}
-
 	void Stream::ScheduleResend ()
 	{
 		if (m_Status != eStreamStatusTerminated)
@@ -1407,137 +972,63 @@ namespace stream
 	{
 		if (ecode != boost::asio::error::operation_aborted)
 		{
-			m_IsSendTime = true;
-			if (m_RTO > INITIAL_RTO) m_RTO = INITIAL_RTO;
-			m_SendTimer.cancel (); // if no ack's in RTO, disable fast retransmit
-			m_IsTimeOutResend = true;
-			m_IsNAcked = false;
-			m_IsResendNeeded = false;
-			m_NumPacketsToSend = 1;
-			ResendPacket (); // send one packet per RTO, waiting for ack
-		}
-	}
-	
-	void Stream::ResendPacket ()
-	{
-		// check for resend attempts
-		if (m_IsIncoming && m_SequenceNumber == 1 && m_NumResendAttempts > 0)
-		{
-			LogPrint (eLogWarning, "Streaming: SYNACK packet was not ACKed after ", m_NumResendAttempts, " attempts, terminate, rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID);
-			m_Status = eStreamStatusReset;
-			Close ();
-			return;
-		}
-		if (m_NumResendAttempts >= MAX_NUM_RESEND_ATTEMPTS)
-		{
-			LogPrint (eLogWarning, "Streaming: packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts, terminate, rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID);
-			m_Status = eStreamStatusReset;
-			Close ();
-			return;
-		}
-
-		// collect packets to resend
-		auto ts = i2p::util::GetMillisecondsSinceEpoch ();
-		std::vector<Packet *> packets;
-		if (m_IsNAcked)
-		{
-			for (auto it : m_NACKedPackets)
+			// check for resend attempts
+			if (m_NumResendAttempts >= MAX_NUM_RESEND_ATTEMPTS)
 			{
-				if (ts >= it->sendTime + m_RTO)
-				{
-					if (ts < it->sendTime + m_RTO*2)
-						it->resent = true;
-					else
-						it->resent = false;
-					it->sendTime = ts;
-					packets.push_back (it);
-					if ((int)packets.size () >= m_NumPacketsToSend) break;
-				}
+				LogPrint (eLogWarning, "Streaming: packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts, terminate, rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID);
+				m_Status = eStreamStatusReset;
+				Close ();
+				return;
 			}
-		}
-		else
-		{
+
+			// collect packets to resend
+			auto ts = i2p::util::GetMillisecondsSinceEpoch ();
+			std::vector<Packet *> packets;
 			for (auto it : m_SentPackets)
 			{
 				if (ts >= it->sendTime + m_RTO)
 				{
-					if (ts < it->sendTime + m_RTO*2)
-						it->resent = true;
-					else
-						it->resent = false;
 					it->sendTime = ts;
 					packets.push_back (it);
-					if ((int)packets.size () >= m_NumPacketsToSend) break;
 				}
 			}
-		}
-		
-		// select tunnels if necessary and send
-		if (packets.size () > 0 && m_IsSendTime)
-		{
-			if (m_IsNAcked) m_NumResendAttempts = 1;
-			else if (m_IsTimeOutResend) m_NumResendAttempts++;
-			if (m_NumResendAttempts == 1 && m_RTO != INITIAL_RTO)
+
+			// select tunnels if necessary and send
+			if (packets.size () > 0)
 			{
-				// loss-based CC
-				if (!m_IsWinDropped && LOSS_BASED_CONTROL_ENABLED && !m_IsClientChoked)
+				m_NumResendAttempts++;
+				m_RTO *= 2;
+				switch (m_NumResendAttempts)
 				{
-					LogPrint (eLogDebug, "Streaming: Packet loss, reduce window size");
-					ProcessWindowDrop ();
+					case 1: // congesion avoidance
+						m_WindowSize >>= 1; // /2
+						if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE;
+					break;
+					case 2:
+						m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change first time
+#if (__cplusplus >= 201703L) // C++ 17 or higher
+						[[fallthrough]];
+#endif
+						// no break here
+					case 4:
+						if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr);
+						UpdateCurrentRemoteLease (); // pick another lease
+						LogPrint (eLogWarning, "Streaming: Another remote lease has been selected for stream with rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID);
+					break;
+					case 3:
+						// pick another outbound tunnel
+						if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr);
+						m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel);
+						LogPrint (eLogWarning, "Streaming: Another outbound tunnel has been selected for stream with sSID=", m_SendStreamID);
+					break;
+					default: ;
 				}
+				SendPackets (packets);
 			}
-			else if (m_IsTimeOutResend)
-			{
-				m_IsTimeOutResend = false;
-				m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change
-				m_WindowDropTargetSize = INITIAL_WINDOW_SIZE;
-				m_LastWindowDropSize = 0;
-				m_WindowIncCounter = 0;
-				m_IsWinDropped = true;
-				m_IsFirstRttSample = true;
-				m_DropWindowDelaySequenceNumber = 0;
-				m_IsFirstACK = true;
-				m_LastACKRecieveTime = 0;
-				m_ACKRecieveInterval = m_AckDelay;
-				UpdatePacingTime ();
-				if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr);
-				if (m_NumResendAttempts & 1)
-				{
-					// pick another outbound tunnel
-					m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel);
-					LogPrint (eLogWarning, "Streaming: Resend #", m_NumResendAttempts,
-						", another outbound tunnel has been selected for stream with sSID=", m_SendStreamID);
-				}
-				else
-				{
-					CancelRemoteLeaseChange ();
-					UpdateCurrentRemoteLease (); // pick another lease
-					LogPrint (eLogWarning, "Streaming: Resend #", m_NumResendAttempts,
-						", another remote lease has been selected for stream with rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID);
-				}
-			}
-			SendPackets (packets);
-			m_LastSendTime = ts;
-			m_IsSendTime = false;
-			if (m_IsNAcked || m_IsResendNeeded || m_IsClientChoked) ScheduleSend ();
+			ScheduleResend ();
 		}
-		else if (!m_IsClientChoked)
-			SendBuffer ();
-		if (!m_IsNAcked && !m_IsResendNeeded) ScheduleResend ();
-		if (m_IsClientChoked) ScheduleSend ();
 	}
 
-	void Stream::ScheduleAck (int timeout)
-	{
-		if (m_IsAckSendScheduled)
-			m_AckSendTimer.cancel ();
-		m_IsAckSendScheduled = true;
-		if (timeout < MIN_SEND_ACK_TIMEOUT) timeout = MIN_SEND_ACK_TIMEOUT;
-		m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(timeout));
-		m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer,
-			shared_from_this (), std::placeholders::_1));
-	}	
-		
 	void Stream::HandleAckSendTimer (const boost::system::error_code& ecode)
 	{
 		if (m_IsAckSendScheduled)
@@ -1553,13 +1044,9 @@ namespace stream
 			{
 				if (m_RoutingSession && m_RoutingSession->IsLeaseSetNonConfirmed ())
 				{
-					auto ts = i2p::util::GetMillisecondsSinceEpoch ();
-					if (ts > m_RoutingSession->GetLeaseSetSubmissionTime () + i2p::garlic::LEASESET_CONFIRMATION_TIMEOUT)
-					{	
-						// seems something went wrong and we should re-select tunnels
-						m_CurrentOutboundTunnel = nullptr;
-						m_CurrentRemoteLease = nullptr;
-					}	
+					// seems something went wrong and we should re-select tunnels
+					m_CurrentOutboundTunnel = nullptr;
+					m_CurrentRemoteLease = nullptr;
 				}
 				SendQuickAck ();
 			}
@@ -1569,33 +1056,23 @@ namespace stream
 
 	void Stream::UpdateCurrentRemoteLease (bool expired)
 	{
-		bool isLeaseChanged = true;
 		if (!m_RemoteLeaseSet || m_RemoteLeaseSet->IsExpired ())
 		{
 			auto remoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ());
 			if (!remoteLeaseSet)
 			{
 				LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), m_RemoteLeaseSet ? " expired" : " not found");
-				if (!m_IsIncoming) // outgoing
-				{	
-					if (m_RemoteLeaseSet && m_RemoteLeaseSet->IsPublishedEncrypted ())
-					{
-						m_LocalDestination.GetOwner ()->RequestDestinationWithEncryptedLeaseSet (
-							std::make_shared<i2p::data::BlindedPublicKey>(m_RemoteIdentity));
-						return; // we keep m_RemoteLeaseSet for possible next request
-					}
-					else
-					{
-						m_RemoteLeaseSet = nullptr;
-						m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // try to request for a next attempt
-					}
-				}
-				else // incoming
+				if (m_RemoteLeaseSet && m_RemoteLeaseSet->IsPublishedEncrypted ())
 				{
-					// just close the socket without sending FIN or RST
-					m_Status = eStreamStatusClosed;
-					AsyncClose ();
-				}	
+					m_LocalDestination.GetOwner ()->RequestDestinationWithEncryptedLeaseSet (
+						std::make_shared<i2p::data::BlindedPublicKey>(m_RemoteIdentity));
+					return; // we keep m_RemoteLeaseSet for possible next request
+				}
+				else
+				{
+					m_RemoteLeaseSet = nullptr;
+					m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // try to request for a next attempt
+				}
 			}
 			else
 			{
@@ -1636,15 +1113,10 @@ namespace stream
 				}
 				if (!updated)
 				{
-					uint32_t i = m_LocalDestination.GetRandom () % leases.size ();
+					uint32_t i = rand () % leases.size ();
 					if (m_CurrentRemoteLease && leases[i]->tunnelID == m_CurrentRemoteLease->tunnelID)
-					{
 						// make sure we don't select previous
-						if (leases.size () > 1)
-							i = (i + 1) % leases.size (); // if so, pick next
-						else
-							isLeaseChanged = false;
-					}
+						i = (i + 1) % leases.size (); // if so, pick next
 					m_CurrentRemoteLease = leases[i];
 				}
 			}
@@ -1661,92 +1133,11 @@ namespace stream
 			LogPrint (eLogWarning, "Streaming: Remote LeaseSet not found");
 			m_CurrentRemoteLease = nullptr;
 		}
-		if (isLeaseChanged && !m_IsRemoteLeaseChangeInProgress)
-		{
-			LogPrint (eLogDebug, "Streaming: RemoteLease changed, set initial window size");
-			ResetWindowSize ();
-		}
-	}
-
-	void Stream::ResetRoutingPath ()
-	{
-		m_CurrentOutboundTunnel = nullptr;
-		m_CurrentRemoteLease = nullptr;
-		m_RTT = INITIAL_RTT;
-		m_RTO = INITIAL_RTO;
-		if (m_RoutingSession)
-			m_RoutingSession->SetSharedRoutingPath (nullptr); // TODO: count failures
-	}	
-
-	void Stream::UpdatePacingTime ()
-	{
-		if (m_WindowDropTargetSize)
-			m_PacingTime = std::round (m_RTT*1000/m_WindowDropTargetSize);
-		else
-			m_PacingTime = std::round (m_RTT*1000/m_WindowSize);
-		if (m_MinPacingTime && m_PacingTime < m_MinPacingTime)
-			m_PacingTime = m_MinPacingTime;
-	}	
-
-	void Stream::ProcessWindowDrop ()
-	{
-		if (m_WindowDropTargetSize)
-			m_WindowDropTargetSize = (m_WindowDropTargetSize / 2) * 0.75; // congestion window size and -25% to drain queue
-		else
-		{
-			if (m_WindowSize < m_LastWindowDropSize)
-			{
-				m_LastWindowDropSize = std::max ((m_WindowSize - MAX_WINDOW_SIZE_INC_PER_RTT), (m_WindowSize - (m_LastWindowDropSize - m_WindowSize)));
-				if (m_LastWindowDropSize < MIN_WINDOW_SIZE) m_LastWindowDropSize = MIN_WINDOW_SIZE;
-			}
-			else
-			{
-				m_LastWindowDropSize = std::max ((m_WindowSize - MAX_WINDOW_SIZE_INC_PER_RTT), ((m_LastWindowDropSize + m_WindowSize + m_WindowSizeTail) / 2));
-				if (m_LastWindowDropSize > MAX_WINDOW_SIZE) m_LastWindowDropSize = MAX_WINDOW_SIZE;
-			}
-			m_WindowDropTargetSize = m_LastWindowDropSize * 0.75; // -25% to drain queue
-		}
-		if (m_WindowDropTargetSize < MIN_WINDOW_SIZE)
-			m_WindowDropTargetSize = MIN_WINDOW_SIZE;
-		m_WindowIncCounter = 0; // disable window growth
-		m_DropWindowDelaySequenceNumber = m_SequenceNumber + int(m_WindowDropTargetSize);
-		m_IsFirstACK = true; // ignore first RTT sample
-		m_IsWinDropped = true; // don't drop window twice
-		m_WindowSizeTail = 0;
-		UpdatePacingTime ();
-	}
-
-	void Stream::ResetWindowSize ()
-	{
-		m_RTO = INITIAL_RTO;
-		if (!m_IsClientChoked)
-		{
-			if (m_WindowSize > INITIAL_WINDOW_SIZE)
-			{
-				m_WindowDropTargetSize = (float)INITIAL_WINDOW_SIZE;
-				m_IsWinDropped = true;
-			}
-			else
-				m_WindowSize = INITIAL_WINDOW_SIZE;
-		}
-		m_LastWindowDropSize = 0;
-		m_WindowIncCounter = 0;
-		m_IsFirstRttSample = true;
-		m_IsFirstACK = true;
-		m_WindowSizeTail = 0;
-		UpdatePacingTime ();
-	}
-
-	void Stream::CancelRemoteLeaseChange ()
-	{
-		m_RemoteLeaseChangeTime = 0;
-		m_IsRemoteLeaseChangeInProgress = false;
 	}
 
 	StreamingDestination::StreamingDestination (std::shared_ptr<i2p::client::ClientDestination> owner, uint16_t localPort, bool gzip):
 		m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip),
-		m_PendingIncomingTimer (m_Owner->GetService ()), 
-		m_LastCleanupTime (i2p::util::GetSecondsSinceEpoch ())
+		m_PendingIncomingTimer (m_Owner->GetService ())
 	{
 	}
 
@@ -1824,24 +1215,12 @@ namespace stream
 				{
 					// already pending
 					LogPrint(eLogWarning, "Streaming: Incoming streaming with rSID=", receiveStreamID, " already exists");
-					it1->second->ResetRoutingPath (); // Ack was not delivered, changing path
 					DeletePacket (packet); // drop it, because previous should be connected
 					return;
 				}
-				if (m_Owner->GetStreamingMaxConcurrentStreams () > 0 && (int)m_Streams.size () > m_Owner->GetStreamingMaxConcurrentStreams ())
-				{
-					LogPrint(eLogWarning, "Streaming: Number of streams exceeds ", m_Owner->GetStreamingMaxConcurrentStreams ());
-					DeletePacket (packet); 
-					return;
-				}	
 				auto incomingStream = CreateNewIncomingStream (receiveStreamID);
 				incomingStream->HandleNextPacket (packet); // SYN
-				if (!incomingStream->GetRemoteLeaseSet ())
-				{
-					LogPrint (eLogWarning, "Streaming: No remote LeaseSet for incoming stream. Terminated");
-					incomingStream->Terminate (); // can't send FIN anyway
-					return;
-				}	
+				auto ident = incomingStream->GetRemoteIdentity();
 
 				// handle saved packets if any
 				{
@@ -1943,16 +1322,13 @@ namespace stream
 		{
 			std::unique_lock<std::mutex> l(m_StreamsMutex);
 			m_Streams.erase (stream->GetRecvStreamID ());
-			if (stream->IsIncoming ())
-				m_IncomingStreams.erase (stream->GetSendStreamID ());
+			m_IncomingStreams.erase (stream->GetSendStreamID ());
 			if (m_LastStream == stream) m_LastStream = nullptr;
 		}
-		auto ts = i2p::util::GetSecondsSinceEpoch ();
-		if (m_Streams.empty () || ts > m_LastCleanupTime + STREAMING_DESTINATION_POOLS_CLEANUP_INTERVAL)
+		if (m_Streams.empty ())
 		{
 			m_PacketsPool.CleanUp ();
 			m_I2NPMsgsPool.CleanUp ();
-			m_LastCleanupTime = ts;
 		}
 	}
 
@@ -1962,7 +1338,7 @@ namespace stream
 		if (it == m_Streams.end ())
 			return false;
 		auto s = it->second;
-		boost::asio::post (m_Owner->GetService (), [this, s] ()
+		m_Owner->GetService ().post ([this, s] ()
 			{
 				s->Close (); // try to send FIN
 				s->Terminate (false);
@@ -1975,7 +1351,7 @@ namespace stream
 	{
 		m_Acceptor = acceptor; // we must set it immediately for IsAcceptorSet
 		auto s = shared_from_this ();
-		boost::asio::post (m_Owner->GetService (), [s](void)
+		m_Owner->GetService ().post([s](void)
 			{
 				// take care about incoming queue
 				for (auto& it: s->m_PendingIncomingStreams)
@@ -1994,7 +1370,7 @@ namespace stream
 
 	void StreamingDestination::AcceptOnce (const Acceptor& acceptor)
 	{
-		boost::asio::post (m_Owner->GetService (), [acceptor, this](void)
+		m_Owner->GetService ().post([acceptor, this](void)
 			{
 				if (!m_PendingIncomingStreams.empty ())
 				{
@@ -2088,15 +1464,5 @@ namespace stream
 		return msg;
 	}
 
-	uint32_t StreamingDestination::GetRandom ()
-	{
-		if (m_Owner)
-		{	
-			auto pool = m_Owner->GetTunnelPool ();
-			if (pool) 
-				return pool->GetRng ()();
-		}
-		return rand ();
-	}	
 }
 }
diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h
index 570fdd1d..1db59118 100644
--- a/libi2pd/Streaming.h
+++ b/libi2pd/Streaming.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -19,7 +19,6 @@
 #include <mutex>
 #include <boost/asio.hpp>
 #include "Base.h"
-#include "Gzip.h"
 #include "I2PEndian.h"
 #include "Identity.h"
 #include "LeaseSet.h"
@@ -51,47 +50,29 @@ namespace stream
 
 	const size_t STREAMING_MTU = 1730;
 	const size_t STREAMING_MTU_RATCHETS = 1812;
-#if OPENSSL_PQ	
-	const size_t MAX_PACKET_SIZE = 8192;
-#else
 	const size_t MAX_PACKET_SIZE = 4096;
-#endif	
 	const size_t COMPRESSION_THRESHOLD_SIZE = 66;
-	const int MAX_NUM_RESEND_ATTEMPTS = 10;
-	const int INITIAL_WINDOW_SIZE = 10;
-	const int MIN_WINDOW_SIZE = 3;
-	const int MAX_WINDOW_SIZE = 512;
-	const int MAX_WINDOW_SIZE_INC_PER_RTT = 16;
-	const double RTT_EWMA_ALPHA = 0.25;
-	const double SLOWRTT_EWMA_ALPHA = 0.05;
-	const double PREV_SPEED_KEEP_TIME_COEFF = 0.35; // 0.1 - 1 // how long will the window size stay around the previous drop level, less is longer
-	const int MIN_RTO = 20; // in milliseconds
-	const int INITIAL_RTT = 1500; // in milliseconds
+	const int MAX_NUM_RESEND_ATTEMPTS = 6;
+	const int WINDOW_SIZE = 6; // in messages
+	const int MIN_WINDOW_SIZE = 1;
+	const int MAX_WINDOW_SIZE = 128;
+	const int INITIAL_RTT = 8000; // in milliseconds
 	const int INITIAL_RTO = 9000; // in milliseconds
-	const int INITIAL_PACING_TIME = 1000 * INITIAL_RTT / INITIAL_WINDOW_SIZE; // in microseconds
 	const int MIN_SEND_ACK_TIMEOUT = 2; // in milliseconds
 	const int SYN_TIMEOUT = 200; // how long we wait for SYN after follow-on, in milliseconds
-	const size_t MAX_PENDING_INCOMING_BACKLOG = 1024;
+	const size_t MAX_PENDING_INCOMING_BACKLOG = 128;
 	const int PENDING_INCOMING_TIMEOUT = 10; // in seconds
 	const int MAX_RECEIVE_TIMEOUT = 20; // in seconds
-	const uint16_t DELAY_CHOKING = 60000; // in milliseconds
-	const uint64_t SEND_INTERVAL = 10000; // in microseconds
-	const uint64_t SEND_INTERVAL_VARIANCE = 2000; // in microseconds
-	const uint64_t REQUEST_IMMEDIATE_ACK_INTERVAL = 7500; // in milliseconds 
-	const uint64_t REQUEST_IMMEDIATE_ACK_INTERVAL_VARIANCE = 3200; // in milliseconds 	
-	const bool LOSS_BASED_CONTROL_ENABLED = 1; // 0/1
-	const uint64_t STREAMING_DESTINATION_POOLS_CLEANUP_INTERVAL = 646; // in seconds
-	
+
 	struct Packet
 	{
 		size_t len, offset;
 		uint8_t buf[MAX_PACKET_SIZE];
 		uint64_t sendTime;
-		bool resent;
 
-		Packet (): len (0), offset (0), sendTime (0), resent (false) {};
+		Packet (): len (0), offset (0), sendTime (0) {};
 		uint8_t * GetBuffer () { return buf + offset; };
-		size_t GetLength () const { return len > offset ? len - offset : 0; };
+		size_t GetLength () const { return len - offset; };
 
 		uint32_t GetSendStreamID () const { return bufbe32toh (buf); };
 		uint32_t GetReceiveStreamID () const { return bufbe32toh (buf + 4); };
@@ -154,7 +135,7 @@ namespace stream
 			SendBufferQueue (): m_Size (0) {};
 			~SendBufferQueue () { CleanUp (); };
 
-			void Add (std::shared_ptr<SendBuffer>&& buf);
+			void Add (std::shared_ptr<SendBuffer> buf);
 			size_t Get (uint8_t * buf, size_t len);
 			size_t GetSize () const { return m_Size; };
 			bool IsEmpty () const { return m_Buffers.empty (); };
@@ -181,9 +162,9 @@ namespace stream
 	{
 		public:
 
-			Stream (boost::asio::io_context& service, StreamingDestination& local,
+			Stream (boost::asio::io_service& service, StreamingDestination& local,
 				std::shared_ptr<const i2p::data::LeaseSet> remote, int port = 0); // outgoing
-			Stream (boost::asio::io_context& service, StreamingDestination& local); // incoming
+			Stream (boost::asio::io_service& service, StreamingDestination& local); // incoming
 
 			~Stream ();
 			uint32_t GetSendStreamID () const { return m_SendStreamID; };
@@ -192,10 +173,8 @@ namespace stream
 			std::shared_ptr<const i2p::data::IdentityEx> GetRemoteIdentity () const { return m_RemoteIdentity; };
 			bool IsOpen () const { return m_Status == eStreamStatusOpen; };
 			bool IsEstablished () const { return m_SendStreamID; };
-			bool IsIncoming () const { return m_IsIncoming; };
 			StreamStatus GetStatus () const { return m_Status; };
 			StreamingDestination& GetLocalDestination () { return m_LocalDestination; };
-			void ResetRoutingPath ();
 
 			void HandleNextPacket (Packet * packet);
 			void HandlePing (Packet * packet);
@@ -208,7 +187,7 @@ namespace stream
 			size_t ReadSome (uint8_t * buf, size_t len) { return ConcatenatePackets (buf, len); };
 			size_t Receive (uint8_t * buf, size_t len, int timeout);
 
-			void AsyncClose() { boost::asio::post(m_Service, std::bind(&Stream::Close, shared_from_this())); };
+			void AsyncClose() { m_Service.post(std::bind(&Stream::Close, shared_from_this())); };
 
 			/** only call close from destination thread, use Stream::AsyncClose for other threads */
 			void Close ();
@@ -244,69 +223,37 @@ namespace stream
 			void UpdateCurrentRemoteLease (bool expired = false);
 
 			template<typename Buffer, typename ReceiveHandler>
-			void HandleReceiveTimer (const boost::system::error_code& ecode, Buffer& buffer, ReceiveHandler handler, int remainingTimeout);
+			void HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler, int remainingTimeout);
 
-			void ScheduleSend ();
-			void HandleSendTimer (const boost::system::error_code& ecode);
 			void ScheduleResend ();
 			void HandleResendTimer (const boost::system::error_code& ecode);
-			void ResendPacket ();
-			void ScheduleAck (int timeout);
 			void HandleAckSendTimer (const boost::system::error_code& ecode);
 
-			void UpdatePacingTime ();
-			void ProcessWindowDrop ();
-			void ResetWindowSize ();
-			void CancelRemoteLeaseChange ();
-			
 		private:
 
-			boost::asio::io_context& m_Service;
+			boost::asio::io_service& m_Service;
 			uint32_t m_SendStreamID, m_RecvStreamID, m_SequenceNumber;
-			uint32_t m_DropWindowDelaySequenceNumber;
-			uint32_t m_TunnelsChangeSequenceNumber;
 			int32_t m_LastReceivedSequenceNumber;
-			int32_t m_PreviousReceivedSequenceNumber;
-			int32_t m_LastConfirmedReceivedSequenceNumber; // for limit inbound speed
 			StreamStatus m_Status;
-			bool m_IsIncoming;
 			bool m_IsAckSendScheduled;
-			bool m_IsNAcked;
-			bool m_IsFirstACK;
-			bool m_IsResendNeeded;
-			bool m_IsFirstRttSample;
-			bool m_IsSendTime;
-			bool m_IsWinDropped;
-			bool m_IsClientChoked;
-			bool m_IsTimeOutResend;
-			bool m_IsImmediateAckRequested;
-			bool m_IsRemoteLeaseChangeInProgress;
-			bool m_DoubleWinIncCounter;
 			StreamingDestination& m_LocalDestination;
 			std::shared_ptr<const i2p::data::IdentityEx> m_RemoteIdentity;
 			std::shared_ptr<const i2p::crypto::Verifier> m_TransientVerifier; // in case of offline key
 			std::shared_ptr<const i2p::data::LeaseSet> m_RemoteLeaseSet;
 			std::shared_ptr<i2p::garlic::GarlicRoutingSession> m_RoutingSession;
 			std::shared_ptr<const i2p::data::Lease> m_CurrentRemoteLease;
-			std::shared_ptr<const i2p::data::Lease> m_NextRemoteLease;
 			std::shared_ptr<i2p::tunnel::OutboundTunnel> m_CurrentOutboundTunnel;
 			std::queue<Packet *> m_ReceiveQueue;
 			std::set<Packet *, PacketCmp> m_SavedPackets;
 			std::set<Packet *, PacketCmp> m_SentPackets;
-			std::set<Packet *, PacketCmp> m_NACKedPackets;
-			boost::asio::deadline_timer m_ReceiveTimer, m_SendTimer, m_ResendTimer, m_AckSendTimer;
+			boost::asio::deadline_timer m_ReceiveTimer, m_ResendTimer, m_AckSendTimer;
 			size_t m_NumSentBytes, m_NumReceivedBytes;
 			uint16_t m_Port;
 
 			SendBufferQueue m_SendBuffer;
-			double m_RTT, m_SlowRTT, m_SlowRTT2;
-			float m_WindowSize, m_LastWindowDropSize, m_WindowDropTargetSize;
-			int m_WindowIncCounter, m_RTO, m_AckDelay, m_PrevRTTSample, m_WindowSizeTail;
-			double m_Jitter;
-			uint64_t m_MinPacingTime, m_PacingTime, m_PacingTimeRem, // microseconds
-				m_LastSendTime, m_LastACKRecieveTime, m_ACKRecieveInterval, m_RemoteLeaseChangeTime, m_LastWindowIncTime;	// milliseconds
-			uint64_t m_LastACKSendTime, m_PacketACKInterval, m_PacketACKIntervalRem; // for limit inbound speed
-			int m_NumResendAttempts, m_NumPacketsToSend;
+			int m_WindowSize, m_RTT, m_RTO, m_AckDelay;
+			uint64_t m_LastWindowSizeIncreaseTime;
+			int m_NumResendAttempts;
 			size_t m_MTU;
 	};
 
@@ -326,7 +273,6 @@ namespace stream
 			void SendPing (std::shared_ptr<const i2p::data::LeaseSet> remote);
 			void DeleteStream (std::shared_ptr<Stream> stream);
 			bool DeleteStream (uint32_t recvStreamID);
-			size_t GetNumStreams () const { return m_Streams.size (); };
 			void SetAcceptor (const Acceptor& acceptor);
 			void ResetAcceptor ();
 			bool IsAcceptorSet () const { return m_Acceptor != nullptr; };
@@ -343,7 +289,6 @@ namespace stream
 
 			Packet * NewPacket () { return m_PacketsPool.Acquire(); }
 			void DeletePacket (Packet * p) { return m_PacketsPool.Release(p); }
-			uint32_t GetRandom ();
 
 		private:
 
@@ -367,8 +312,7 @@ namespace stream
 
 			i2p::util::MemoryPool<Packet> m_PacketsPool;
 			i2p::util::MemoryPool<I2NPMessageBuffer<I2NP_MAX_SHORT_MESSAGE_SIZE> > m_I2NPMsgsPool;
-			uint64_t m_LastCleanupTime; // in seconds
-			
+
 		public:
 
 			i2p::data::GzipInflator m_Inflator;
@@ -384,7 +328,7 @@ namespace stream
 	void Stream::AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout)
 	{
 		auto s = shared_from_this();
-		boost::asio::post (m_Service, [s, buffer, handler, timeout](void)
+		m_Service.post ([s, buffer, handler, timeout](void)
 		{
 			if (!s->m_ReceiveQueue.empty () || s->m_Status == eStreamStatusReset)
 				s->HandleReceiveTimer (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), buffer, handler, 0);
@@ -403,9 +347,9 @@ namespace stream
 	}
 
 	template<typename Buffer, typename ReceiveHandler>
-	void Stream::HandleReceiveTimer (const boost::system::error_code& ecode, Buffer& buffer, ReceiveHandler handler, int remainingTimeout)
+	void Stream::HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler, int remainingTimeout)
 	{
-		size_t received = ConcatenatePackets ((uint8_t *)buffer.data (), buffer.size ());
+		size_t received = ConcatenatePackets (boost::asio::buffer_cast<uint8_t *>(buffer), boost::asio::buffer_size(buffer));
 		if (received > 0)
 			handler (boost::system::error_code (), received);
 		else if (ecode == boost::asio::error::operation_aborted)
diff --git a/libi2pd/Tag.h b/libi2pd/Tag.h
index 30b7708d..72f181a2 100644
--- a/libi2pd/Tag.h
+++ b/libi2pd/Tag.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -12,14 +12,10 @@
 #include <boost/static_assert.hpp>
 #include <string.h>
 #include <openssl/rand.h>
-#include <string>
-#include <string_view>
 #include "Base.h"
 
-namespace i2p 
-{
-namespace data 
-{
+namespace i2p {
+namespace data {
 	template<size_t sz>
 	class Tag
 	{
@@ -62,22 +58,26 @@ namespace data
 
 			std::string ToBase64 (size_t len = sz) const
 			{
-				return i2p::data::ByteStreamToBase64 (m_Buf, len);
+				char str[sz*2];
+				size_t l = i2p::data::ByteStreamToBase64 (m_Buf, len, str, sz*2);
+				return std::string (str, str + l);
 			}
 
 			std::string ToBase32 (size_t len = sz) const
 			{
-				return i2p::data::ByteStreamToBase32 (m_Buf, len);
+				char str[sz*2];
+				size_t l = i2p::data::ByteStreamToBase32 (m_Buf, len, str, sz*2);
+				return std::string (str, str + l);
 			}
 
-			size_t FromBase32 (std::string_view s)
+			size_t FromBase32 (const std::string& s)
 			{
-				return i2p::data::Base32ToByteStream (s, m_Buf, sz);
+				return i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz);
 			}
 
-			size_t FromBase64 (std::string_view s)
+			size_t FromBase64 (const std::string& s)
 			{
-				return i2p::data::Base64ToByteStream (s, m_Buf, sz);
+				return i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz);
 			}
 
 			uint8_t GetBit (int i) const
diff --git a/libi2pd/Timestamp.cpp b/libi2pd/Timestamp.cpp
index a22e9bde..99507398 100644
--- a/libi2pd/Timestamp.cpp
+++ b/libi2pd/Timestamp.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2022, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -60,16 +60,18 @@ namespace util
 	static void SyncTimeWithNTP (const std::string& address)
 	{
 		LogPrint (eLogInfo, "Timestamp: NTP request to ", address);
-		boost::asio::io_context service;
+		boost::asio::io_service service;
 		boost::system::error_code ec;
-		auto endpoints = boost::asio::ip::udp::resolver (service).resolve (address, "ntp", ec);
+		auto it = boost::asio::ip::udp::resolver (service).resolve (
+			boost::asio::ip::udp::resolver::query (address, "ntp"), ec);
 		if (!ec)
 		{
 			bool found = false;
+			boost::asio::ip::udp::resolver::iterator end;
 			boost::asio::ip::udp::endpoint ep;
-			for (const auto& it: endpoints)
+			while (it != end)
 			{
-				ep = it;
+				ep = *it;
 				if (!ep.address ().is_unspecified ())
 				{
 					if (ep.address ().is_v4 ())
@@ -86,6 +88,7 @@ namespace util
 					}
 				}
 				if (found) break;
+				it++;
 			}
 			if (!found)
 			{
@@ -151,7 +154,7 @@ namespace util
 		{
 			m_IsRunning = true;
 			LogPrint(eLogInfo, "Timestamp: NTP time sync starting");
-			boost::asio::post (m_Service, std::bind (&NTPTimeSync::Sync, this));
+			m_Service.post (std::bind (&NTPTimeSync::Sync, this));
 			m_Thread.reset (new std::thread (std::bind (&NTPTimeSync::Run, this)));
 		}
 		else
@@ -229,34 +232,11 @@ namespace util
 		return GetLocalHoursSinceEpoch () + g_TimeOffset/3600;
 	}
 
-	uint64_t GetMonotonicMicroseconds()
-	{
-		return std::chrono::duration_cast<std::chrono::microseconds>(
-			std::chrono::steady_clock::now().time_since_epoch()).count();
-	}
-
-	uint64_t GetMonotonicMilliseconds()
-	{
-		return std::chrono::duration_cast<std::chrono::milliseconds>(
-			std::chrono::steady_clock::now().time_since_epoch()).count();
-	}
-
-	uint64_t GetMonotonicSeconds ()
-	{
-		return std::chrono::duration_cast<std::chrono::seconds>(
-			std::chrono::steady_clock::now().time_since_epoch()).count();
-	}	
-	
 	void GetCurrentDate (char * date)
 	{
 		GetDateString (GetSecondsSinceEpoch (), date);
 	}
 
-	void GetNextDayDate (char * date)
-	{
-		GetDateString (GetSecondsSinceEpoch () + 24*60*60, date);
-	}	
-	
 	void GetDateString (uint64_t timestamp, char * date)
 	{
 		using clock = std::chrono::system_clock;
diff --git a/libi2pd/Timestamp.h b/libi2pd/Timestamp.h
index 00c60433..ff777257 100644
--- a/libi2pd/Timestamp.h
+++ b/libi2pd/Timestamp.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2022, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -24,12 +24,7 @@ namespace util
 	uint32_t GetMinutesSinceEpoch ();
 	uint32_t GetHoursSinceEpoch ();
 
-	uint64_t GetMonotonicMicroseconds ();
-	uint64_t GetMonotonicMilliseconds ();
-	uint64_t GetMonotonicSeconds ();
-	
-	void GetCurrentDate (char * date); // returns UTC date as YYYYMMDD string, 9 bytes
-	void GetNextDayDate (char * date); // returns next UTC day as YYYYMMDD string, 9 bytes
+	void GetCurrentDate (char * date); // returns date as YYYYMMDD string, 9 bytes
 	void GetDateString (uint64_t timestamp, char * date); // timestamp is seconds since epoch, returns date as YYYYMMDD string, 9 bytes
 	void AdjustTimeOffset (int64_t offset); // in seconds from current
 
@@ -52,7 +47,7 @@ namespace util
 
 			bool m_IsRunning;
 			std::unique_ptr<std::thread> m_Thread;
-			boost::asio::io_context m_Service;
+			boost::asio::io_service m_Service;
 			boost::asio::deadline_timer m_Timer;
 			int m_SyncInterval;
 			std::vector<std::string> m_NTPServersList;
diff --git a/libi2pd/TransitTunnel.cpp b/libi2pd/TransitTunnel.cpp
index b24c8ac5..6c2c52a7 100644
--- a/libi2pd/TransitTunnel.cpp
+++ b/libi2pd/TransitTunnel.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2022, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -8,14 +8,9 @@
 
 #include <string.h>
 #include "I2PEndian.h"
-#include "Crypto.h"
 #include "Log.h"
-#include "Identity.h"
-#include "RouterInfo.h"
 #include "RouterContext.h"
 #include "I2NPProtocol.h"
-#include "Garlic.h"
-#include "ECIESX25519AEADRatchetSession.h"
 #include "Tunnel.h"
 #include "Transports.h"
 #include "TransitTunnel.h"
@@ -43,21 +38,6 @@ namespace tunnel
 		i2p::transport::transports.UpdateTotalTransitTransmittedBytes (TUNNEL_DATA_MSG_SIZE);
 	}
 
-	std::string TransitTunnel::GetNextPeerName () const
-	{
-		return i2p::data::GetIdentHashAbbreviation (GetNextIdentHash ());
-	}	
-
-	void TransitTunnel::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
-	{
-		LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ());
-	}
-
-	void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg)
-	{
-		LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ());
-	}
-		
 	TransitTunnelParticipant::~TransitTunnelParticipant ()
 	{
 	}
@@ -79,100 +59,45 @@ namespace tunnel
 			auto num = m_TunnelDataMsgs.size ();
 			if (num > 1)
 				LogPrint (eLogDebug, "TransitTunnel: ", GetTunnelID (), "->", GetNextTunnelID (), " ", num);
-			if (!m_Sender) m_Sender = std::make_unique<TunnelTransportSender>();
-			m_Sender->SendMessagesTo (GetNextIdentHash (), m_TunnelDataMsgs); // send and clear
+			i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs);
+			m_TunnelDataMsgs.clear ();
 		}
 	}
 
-	std::string TransitTunnelParticipant::GetNextPeerName () const
+	void TransitTunnel::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
 	{
-		if (m_Sender)
-		{
-			auto transport = m_Sender->GetCurrentTransport ();
-			if (transport)
-				return TransitTunnel::GetNextPeerName () + "-" + 
-					i2p::data::RouterInfo::GetTransportName (transport->GetTransportType ());
-		}	
-		return TransitTunnel::GetNextPeerName ();
-	}	
-		
+		LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ());
+	}
+
+	void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg)
+	{
+		LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ());
+	}
+
 	void TransitTunnelGateway::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
 	{
 		TunnelMessageBlock block;
 		block.deliveryType = eDeliveryTypeLocal;
 		block.data = msg;
-		std::lock_guard<std::mutex> l(m_SendMutex);
+		std::unique_lock<std::mutex> l(m_SendMutex);
 		m_Gateway.PutTunnelDataMsg (block);
 	}
 
 	void TransitTunnelGateway::FlushTunnelDataMsgs ()
 	{
-		std::lock_guard<std::mutex> l(m_SendMutex);
+		std::unique_lock<std::mutex> l(m_SendMutex);
 		m_Gateway.SendBuffer ();
 	}
 
-	std::string TransitTunnelGateway::GetNextPeerName () const
-	{
-		const auto& sender = m_Gateway.GetSender ();
-		if (sender)
-		{
-			auto transport = sender->GetCurrentTransport ();
-			if (transport)
-				return TransitTunnel::GetNextPeerName () + "-" + 
-					i2p::data::RouterInfo::GetTransportName (transport->GetTransportType ());
-		}	
-		return TransitTunnel::GetNextPeerName ();
-	}	
-		
 	void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg)
 	{
 		auto newMsg = CreateEmptyTunnelDataMsg (true);
 		EncryptTunnelMsg (tunnelMsg, newMsg);
 
 		LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ());
-		std::lock_guard<std::mutex> l(m_HandleMutex);
-		if (!m_Endpoint) m_Endpoint = std::make_unique<TunnelEndpoint>(false); // transit endpoint is always outbound
-		m_Endpoint->HandleDecryptedTunnelDataMsg (newMsg);
+		m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg);
 	}
 
-	void TransitTunnelEndpoint::FlushTunnelDataMsgs ()
-	{
-		if (m_Endpoint)
-		{
-			std::lock_guard<std::mutex> l(m_HandleMutex);
-			m_Endpoint->FlushI2NPMsgs ();
-		}	
-	}	
-
-	void TransitTunnelEndpoint::Cleanup ()
-	{ 
-		if (m_Endpoint)
-		{	
-			std::lock_guard<std::mutex> l(m_HandleMutex);
-			m_Endpoint->Cleanup ();
-		}	
-	}	
-		
-	std::string TransitTunnelEndpoint::GetNextPeerName () const
-	{ 
-		if (!m_Endpoint) return "";
-		auto hash = m_Endpoint->GetCurrentHash ();
-		if (hash)
-		{	
-			const auto& sender = m_Endpoint->GetSender ();
-			if (sender)
-			{
-				auto transport = sender->GetCurrentTransport ();
-				if (transport)
-					return i2p::data::GetIdentHashAbbreviation (*hash) + "-" + 
-						i2p::data::RouterInfo::GetTransportName (transport->GetTransportType ());
-				else
-					return i2p::data::GetIdentHashAbbreviation (*hash);
-			}	
-		}
-		return "";
-	}	
-		
 	std::shared_ptr<TransitTunnel> CreateTransitTunnel (uint32_t receiveTunnelID,
 		const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID,
 		const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey,
@@ -194,440 +119,5 @@ namespace tunnel
 			return std::make_shared<TransitTunnelParticipant> (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey);
 		}
 	}
-
-	TransitTunnels::TransitTunnels ():
-		m_IsRunning (false), m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL)
-	{
-	}
-		
-	TransitTunnels::~TransitTunnels ()
-	{
-		Stop ();
-	}	
-		
-	void TransitTunnels::Start () 
-	{
-		m_IsRunning = true;
-		m_Thread.reset (new std::thread (std::bind (&TransitTunnels::Run, this)));
-	}
-		
-	void TransitTunnels::Stop ()
-	{
-		m_IsRunning = false;
-		m_TunnelBuildMsgQueue.WakeUp ();
-		if (m_Thread)
-		{
-			m_Thread->join ();
-			m_Thread = nullptr;
-		}
-		m_TransitTunnels.clear ();
-	}	
-
-	void TransitTunnels::Run () 
-	{
-		i2p::util::SetThreadName("TBM");
-		uint64_t lastTs = 0;
-		std::list<std::shared_ptr<I2NPMessage> > msgs;
-		while (m_IsRunning)
-		{
-			try
-			{
-				if (m_TunnelBuildMsgQueue.Wait (TRANSIT_TUNNELS_QUEUE_WAIT_INTERVAL, 0))
-				{
-					m_TunnelBuildMsgQueue.GetWholeQueue (msgs);
-					while (!msgs.empty ())
-					{
-						auto msg = msgs.front (); msgs.pop_front ();
-						if (!msg) continue;
-						uint8_t typeID = msg->GetTypeID ();
-						switch (typeID)
-						{
-							case eI2NPShortTunnelBuild:
-								HandleShortTransitTunnelBuildMsg (std::move (msg));
-							break;	
-							case eI2NPVariableTunnelBuild:
-								HandleVariableTransitTunnelBuildMsg (std::move (msg));
-							break;	
-							default:
-								LogPrint (eLogWarning, "TransitTunnel: Unexpected message type ", (int) typeID);
-						}
-						if (!m_IsRunning) break;
-					}	
-				}	
-				if (m_IsRunning)
-				{
-					uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
-					if (ts  >= lastTs + TUNNEL_MANAGE_INTERVAL || ts + TUNNEL_MANAGE_INTERVAL < lastTs)
-					{
-						ManageTransitTunnels (ts);
-						lastTs = ts;
-					}
-				}	
-			}
-			catch (std::exception& ex)
-			{
-				LogPrint (eLogError, "TransitTunnel: Runtime exception: ", ex.what ());
-			}
-		}
-	}
-
-	void TransitTunnels::PostTransitTunnelBuildMsg  (std::shared_ptr<I2NPMessage>&& msg)
-	{
-		if (msg) m_TunnelBuildMsgQueue.Put (msg);
-	}	
-		
-	void TransitTunnels::HandleShortTransitTunnelBuildMsg (std::shared_ptr<I2NPMessage>&& msg)
-	{
-		if (!msg) return;
-		uint8_t * buf = msg->GetPayload();
-		size_t len = msg->GetPayloadLength();
-		int num = buf[0];
-		LogPrint (eLogDebug, "TransitTunnel: ShortTunnelBuild ", num, " records");
-		if (num > i2p::tunnel::MAX_NUM_RECORDS)
-		{
-			LogPrint (eLogError, "TransitTunnel: Too many records in ShortTunnelBuild message ", num);
-			return;
-		}
-		if (len < num*SHORT_TUNNEL_BUILD_RECORD_SIZE + 1)
-		{
-			LogPrint (eLogError, "TransitTunnel: ShortTunnelBuild message of ", num, " records is too short ", len);
-			return;
-		}
-		const uint8_t * record = buf + 1;
-		for (int i = 0; i < num; i++)
-		{
-			if (!memcmp (record, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16))
-			{
-				LogPrint (eLogDebug, "TransitTunnel: Short request record ", i, " is ours");
-				uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE];
-				if (!i2p::context.DecryptTunnelShortRequestRecord (record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText))
-				{
-					LogPrint (eLogWarning, "TransitTunnel: Can't decrypt short request record ", i);
-					return;
-				}
-				if (clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE]) // not AES
-				{
-					LogPrint (eLogWarning, "TransitTunnel: Unknown layer encryption type ", clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE], " in short request record");
-					return;
-				}
-				auto& noiseState = i2p::context.GetCurrentNoiseState ();
-				uint8_t replyKey[32]; // AEAD/Chacha20/Poly1305
-				i2p::crypto::AESKey layerKey, ivKey; // AES
-				i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelReplyKey", noiseState.m_CK);
-				memcpy (replyKey, noiseState.m_CK + 32, 32);
-				i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelLayerKey", noiseState.m_CK);
-				memcpy (layerKey, noiseState.m_CK + 32, 32);
-				bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG;
-				if (isEndpoint)
-				{
-					i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "TunnelLayerIVKey", noiseState.m_CK);
-					memcpy (ivKey, noiseState.m_CK + 32, 32);
-				}
-				else
-				{	
-					if (!memcmp ((const uint8_t *)i2p::context.GetIdentHash (), clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // if next ident is now ours
-					{
-						LogPrint (eLogWarning, "TransitTunnel: Next ident is ours in short request record");
-						return;
-					}	
-					memcpy (ivKey, noiseState.m_CK , 32);
-				}	
-
-				// check if we accept this tunnel
-				std::shared_ptr<i2p::tunnel::TransitTunnel> transitTunnel;
-				uint8_t retCode = 0;
-				if (i2p::context.AcceptsTunnels ())
-				{
-					auto congestionLevel = i2p::context.GetCongestionLevel (false);
-					if (congestionLevel < CONGESTION_LEVEL_FULL)
-					{	
-						if (congestionLevel >= CONGESTION_LEVEL_MEDIUM)
-						{	
-							// random reject depending on congestion level
-							int level = m_Rng () % (CONGESTION_LEVEL_FULL - CONGESTION_LEVEL_MEDIUM) + CONGESTION_LEVEL_MEDIUM;
-							if (congestionLevel > level)
-								retCode = 30;
-						}	
-					}	
-					else
-						retCode = 30;
-				}	
-				else	
-					retCode = 30;
-				
-				if (!retCode)
-				{
-					i2p::data::IdentHash nextIdent(clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET);
-					bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG;
-					if (isEndpoint || !i2p::data::IsRouterDuplicated (nextIdent))
-					{	
-						// create new transit tunnel
-						transitTunnel = CreateTransitTunnel (
-							bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET),
-							nextIdent,
-							bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
-							layerKey, ivKey,
-							clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG,
-							isEndpoint);
-						if (!AddTransitTunnel (transitTunnel))
-							retCode = 30;
-					}
-					else
-						// decline tunnel going to duplicated router 
-						retCode = 30;
-				}
-
-				// encrypt reply
-				uint8_t nonce[12];
-				memset (nonce, 0, 12);
-				uint8_t * reply = buf + 1;
-				for (int j = 0; j < num; j++)
-				{
-					nonce[4] = j; // nonce is record #
-					if (j == i)
-					{
-						memset (reply + SHORT_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options
-						reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = retCode;
-						if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16,
-							noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt
-						{
-							LogPrint (eLogWarning, "TransitTunnel: Short reply AEAD encryption failed");
-							return;
-						}
-					}
-					else
-						i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, reply);
-					reply += SHORT_TUNNEL_BUILD_RECORD_SIZE;
-				}
-				// send reply
-				auto onDrop = [transitTunnel]()
-					{
-						if (transitTunnel)
-						{
-							LogPrint (eLogDebug, "TransitTunnel: Failed to send reply for transit tunnel ", transitTunnel->GetTunnelID ());
-							auto t = transitTunnel->GetCreationTime ();
-							if (t > i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT)
-								// make transit tunnel expired 
-								transitTunnel->SetCreationTime (t - i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT);
-						}	
-					};
-				if (isEndpoint)
-				{
-					auto replyMsg = NewI2NPShortMessage ();
-					replyMsg->Concat (buf, len);
-					replyMsg->FillI2NPMessageHeader (eI2NPShortTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET));
-					if (transitTunnel) replyMsg->onDrop = onDrop;
-					if (memcmp ((const uint8_t *)i2p::context.GetIdentHash (),
-						clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // reply IBGW is not local?
-					{
-						i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK);
-						uint64_t tag;
-						memcpy (&tag, noiseState.m_CK, 8);
-						// we send it to reply tunnel
-						i2p::transport::transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET,
-						CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
-							i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag)));
-					}
-					else
-					{
-						// IBGW is local
-						uint32_t tunnelID = bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET);
-						auto tunnel = i2p::tunnel::tunnels.GetTunnel (tunnelID);
-						if (tunnel)
-						{	
-							tunnel->SendTunnelDataMsg (replyMsg);
-							tunnel->FlushTunnelDataMsgs ();
-						}	
-						else
-							LogPrint (eLogWarning, "I2NP: Tunnel ", tunnelID, " not found for short tunnel build reply");
-					}
-				}
-				else
-				{
-					auto msg = CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len,
-							bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET));
-					if (transitTunnel) msg->onDrop = onDrop;
-					i2p::transport::transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, msg);
-				}	
-				return;
-			}
-			record += SHORT_TUNNEL_BUILD_RECORD_SIZE;
-		}
-	}	
-		
-	bool TransitTunnels::HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText)
-	{
-		for (int i = 0; i < num; i++)
-		{
-			uint8_t * record = records + i*TUNNEL_BUILD_RECORD_SIZE;
-			if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16))
-			{
-				LogPrint (eLogDebug, "TransitTunnel: Build request record ", i, " is ours");
-				if (!i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) 
-				{
-					LogPrint (eLogWarning, "TransitTunnel: Failed to decrypt tunnel build record");
-					return false;
-				}	
-				if (!memcmp ((const uint8_t *)i2p::context.GetIdentHash (), clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32) && // if next ident is now ours
-				    !(clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG)) // and not endpoint
-				{
-					LogPrint (eLogWarning, "TransitTunnel: Next ident is ours in tunnel build record");
-					return false;
-				}	
-				uint8_t retCode = 0;
-				// decide if we should accept tunnel
-				bool accept = i2p::context.AcceptsTunnels ();
-				if (accept)
-				{
-					auto congestionLevel = i2p::context.GetCongestionLevel (false);
-					if (congestionLevel >= CONGESTION_LEVEL_MEDIUM)
-					{	
-						if (congestionLevel < CONGESTION_LEVEL_FULL)
-						{
-							// random reject depending on congestion level
-							int level = m_Rng () % (CONGESTION_LEVEL_FULL - CONGESTION_LEVEL_MEDIUM) + CONGESTION_LEVEL_MEDIUM;
-							if (congestionLevel > level)
-								accept = false;
-						}	
-						else	
-							accept = false;
-					}	
-				}	
-					
-				if (accept)
-				{
-					i2p::data::IdentHash nextIdent(clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET);
-					bool isEndpoint = clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG;
-					if (isEndpoint || !i2p::data::IsRouterDuplicated (nextIdent))
-					{	
-						auto transitTunnel = CreateTransitTunnel (
-								bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET),
-								nextIdent,
-								bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
-								clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET,
-								clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET,
-								clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG,
-								isEndpoint);
-						if (!AddTransitTunnel (transitTunnel))
-							retCode = 30;
-					}	
-					else
-						// decline tunnel going to duplicated router 
-						retCode = 30;
-				}
-				else
-					retCode = 30; // always reject with bandwidth reason (30)
-
-				// replace record to reply
-				memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options
-				record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode;
-				// encrypt reply
-				i2p::crypto::CBCEncryption encryption;
-				for (int j = 0; j < num; j++)
-				{
-					uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE;
-					if (j == i)
-					{
-						uint8_t nonce[12];
-						memset (nonce, 0, 12);
-						auto& noiseState = i2p::context.GetCurrentNoiseState ();
-						if (!i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16,
-							noiseState.m_H, 32, noiseState.m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt
-						{
-							LogPrint (eLogWarning, "TransitTunnel: Reply AEAD encryption failed");
-							return false;
-						}
-					}
-					else
-					{
-						encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET);
-						encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, reply);
-					}
-				}
-				return true;
-			}
-		}
-		return false;
-	}
-
-	void TransitTunnels::HandleVariableTransitTunnelBuildMsg (std::shared_ptr<I2NPMessage>&& msg)
-	{
-		if (!msg) return;
-		uint8_t * buf = msg->GetPayload();
-		size_t len = msg->GetPayloadLength();
-		int num = buf[0];
-		LogPrint (eLogDebug, "TransitTunnel: VariableTunnelBuild ", num, " records");
-		if (num > i2p::tunnel::MAX_NUM_RECORDS)
-		{
-			LogPrint (eLogError, "TransitTunnle: Too many records in VaribleTunnelBuild message ", num);
-			return;
-		}
-		if (len < num*TUNNEL_BUILD_RECORD_SIZE + 1)
-		{
-			LogPrint (eLogError, "TransitTunnel: VaribleTunnelBuild message of ", num, " records is too short ", len);
-			return;
-		}
-		uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE];
-		if (HandleBuildRequestRecords (num, buf + 1, clearText))
-		{
-			if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel
-			{
-				// so we send it to reply tunnel
-				i2p::transport::transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
-					CreateTunnelGatewayMsg (bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
-						eI2NPVariableTunnelBuildReply, buf, len,
-						bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
-			}
-			else
-				i2p::transport::transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
-					CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len,
-						bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
-		}
-	}
-
-	bool TransitTunnels::AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel)
-	{
-		if (tunnels.AddTunnel (tunnel))
-			m_TransitTunnels.push_back (tunnel);
-		else
-		{
-			LogPrint (eLogError, "TransitTunnel: Tunnel with id ", tunnel->GetTunnelID (), " already exists");
-			return false;
-		}
-		return true;
-	}
-		
-	void TransitTunnels::ManageTransitTunnels (uint64_t ts)
-	{
-		for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();)
-		{
-			auto tunnel = *it;
-			if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT ||
-			    ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ())
-			{
-				LogPrint (eLogDebug, "TransitTunnel: Transit tunnel with id ", tunnel->GetTunnelID (), " expired");
-				tunnels.RemoveTunnel (tunnel->GetTunnelID ());
-				it = m_TransitTunnels.erase (it);
-			}
-			else
-			{
-				tunnel->Cleanup ();
-				it++;
-			}
-		}
-	}
-
-	int TransitTunnels::GetTransitTunnelsExpirationTimeout ()
-	{
-		int timeout = 0;
-		uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
-		// TODO: possible race condition with I2PControl
-		for (const auto& it : m_TransitTunnels)
-		{
-			int t = it->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts;
-			if (t > timeout) timeout = t;
-		}
-		return timeout;
-	}	
 }
 }
diff --git a/libi2pd/TransitTunnel.h b/libi2pd/TransitTunnel.h
index 34bcc79f..f83007a9 100644
--- a/libi2pd/TransitTunnel.h
+++ b/libi2pd/TransitTunnel.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -10,11 +10,10 @@
 #define TRANSIT_TUNNEL_H__
 
 #include <inttypes.h>
-#include <list>
+#include <vector>
 #include <mutex>
 #include <memory>
 #include "Crypto.h"
-#include "Queue.h"
 #include "I2NPProtocol.h"
 #include "TunnelEndpoint.h"
 #include "TunnelGateway.h"
@@ -33,13 +32,11 @@ namespace tunnel
 				const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey);
 
 			virtual size_t GetNumTransmittedBytes () const { return 0; };
-			virtual std::string GetNextPeerName () const;
 
 			// implements TunnelBase
 			void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg) override;
 			void HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg) override;
 			void EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out) override;
-		
 		private:
 
 			i2p::crypto::AESKey m_LayerKey, m_IVKey;
@@ -58,15 +55,13 @@ namespace tunnel
 			~TransitTunnelParticipant ();
 
 			size_t GetNumTransmittedBytes () const override { return m_NumTransmittedBytes; };
-			std::string GetNextPeerName () const override;
 			void HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg) override;
 			void FlushTunnelDataMsgs () override;
 
 		private:
 
 			size_t m_NumTransmittedBytes;
-			std::list<std::shared_ptr<i2p::I2NPMessage> > m_TunnelDataMsgs;
-			std::unique_ptr<TunnelTransportSender> m_Sender;
+			std::vector<std::shared_ptr<i2p::I2NPMessage> > m_TunnelDataMsgs;
 	};
 
 	class TransitTunnelGateway: public TransitTunnel
@@ -77,13 +72,12 @@ namespace tunnel
 				const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID,
 				const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey):
 				TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID,
-				layerKey, ivKey), m_Gateway(*this) {};
+				layerKey, ivKey), m_Gateway(this) {};
 
 			void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg) override;
 			void FlushTunnelDataMsgs () override;
 			size_t GetNumTransmittedBytes () const override { return m_Gateway.GetNumSentBytes (); };
-			std::string GetNextPeerName () const override;
-			
+
 		private:
 
 			std::mutex m_SendMutex;
@@ -97,68 +91,23 @@ namespace tunnel
 			TransitTunnelEndpoint (uint32_t receiveTunnelID,
 				const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID,
 				const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey):
-				TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey) {}; 
+				TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey),
+				m_Endpoint (false) {}; // transit endpoint is always outbound
+
+			void Cleanup () override { m_Endpoint.Cleanup (); }
 
-			void Cleanup () override;
-		
 			void HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg) override;
-			void FlushTunnelDataMsgs () override;
-			size_t GetNumTransmittedBytes () const override { return m_Endpoint ? m_Endpoint->GetNumReceivedBytes () : 0; }
-			std::string GetNextPeerName () const override;
-			
+			size_t GetNumTransmittedBytes () const override { return m_Endpoint.GetNumReceivedBytes (); }
+
 		private:
 
-			std::mutex m_HandleMutex;
-			std::unique_ptr<TunnelEndpoint> m_Endpoint;
+			TunnelEndpoint m_Endpoint;
 	};
 
 	std::shared_ptr<TransitTunnel> CreateTransitTunnel (uint32_t receiveTunnelID,
 		const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID,
 		const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey,
 		bool isGateway, bool isEndpoint);
-
-	
-	const int TRANSIT_TUNNELS_QUEUE_WAIT_INTERVAL = 10; // in seconds
-		
-	class TransitTunnels
-	{	
-		public:
-
-			TransitTunnels ();
-			~TransitTunnels ();
-			
-			void Start ();
-			void Stop ();
-			void PostTransitTunnelBuildMsg  (std::shared_ptr<I2NPMessage>&& msg);
-			
-			size_t GetNumTransitTunnels () const { return m_TransitTunnels.size (); }
-			int GetTransitTunnelsExpirationTimeout ();
-
-		private:
-
-			bool AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel);
-			void ManageTransitTunnels (uint64_t ts);
-
-			void HandleShortTransitTunnelBuildMsg (std::shared_ptr<I2NPMessage>&& msg);
-			void HandleVariableTransitTunnelBuildMsg (std::shared_ptr<I2NPMessage>&& msg);
-			bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText);
-
-			void Run ();
-			
-		private:
-
-			volatile bool m_IsRunning;
-			std::unique_ptr<std::thread> m_Thread;
-			std::list<std::shared_ptr<TransitTunnel> > m_TransitTunnels;
-			i2p::util::Queue<std::shared_ptr<I2NPMessage> > m_TunnelBuildMsgQueue;
-			std::mt19937 m_Rng;
-			
-		public:
-
-			// for HTTP only
-			const auto& GetTransitTunnels () const { return m_TransitTunnels; };
-			size_t GetTunnelBuildMsgQueueSize () const { return m_TunnelBuildMsgQueue.GetSize (); };
-	};
 }
 }
 
diff --git a/libi2pd/TransportSession.h b/libi2pd/TransportSession.h
index 2cff0b1f..f4668116 100644
--- a/libi2pd/TransportSession.h
+++ b/libi2pd/TransportSession.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -10,7 +10,7 @@
 #define TRANSPORT_SESSION_H__
 
 #include <inttypes.h>
-#include <string.h>
+#include <iostream>
 #include <memory>
 #include <vector>
 #include <mutex>
@@ -28,51 +28,45 @@ namespace transport
 	const size_t IPV6_HEADER_SIZE = 40;
 	const size_t UDP_HEADER_SIZE = 8;
 
-	template<size_t sz>
 	class SignedData
 	{
 		public:
 
-			SignedData (): m_Size(0) {}
+			SignedData () {}
 			SignedData (const SignedData& other)
 			{
-				m_Size = other.m_Size;
-				memcpy (m_Buf, other.m_Buf, m_Size);
+				m_Stream << other.m_Stream.rdbuf ();
 			}
 
 			void Reset ()
 			{
-				m_Size = 0;
+				m_Stream.str("");
 			}
 
-			size_t Insert (const uint8_t * buf, size_t len)
+			void Insert (const uint8_t * buf, size_t len)
 			{
-				if (m_Size + len > sz) len = sz - m_Size;
-				memcpy (m_Buf + m_Size, buf, len); 
-				m_Size += len;
-				return len;
+				m_Stream.write ((char *)buf, len);
 			}
 
 			template<typename T>
 			void Insert (T t)
 			{
-				Insert ((const uint8_t *)&t, sizeof (T));
+				m_Stream.write ((char *)&t, sizeof (T));
 			}
 
 			bool Verify (std::shared_ptr<const i2p::data::IdentityEx> ident, const uint8_t * signature) const
 			{
-				return ident->Verify (m_Buf, m_Size, signature);
+				return ident->Verify ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature);
 			}
 
 			void Sign (const i2p::data::PrivateKeys& keys, uint8_t * signature) const
 			{
-				keys.Sign (m_Buf, m_Size, signature);
+				keys.Sign ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature);
 			}
 
 		private:
 
-			uint8_t m_Buf[sz];
-			size_t m_Size;
+			std::stringstream m_Stream;
 	};
 
 	const int64_t TRANSPORT_SESSION_SLOWNESS_THRESHOLD = 500; // in milliseconds
@@ -150,14 +144,9 @@ namespace transport
 			void SetLastActivityTimestamp (uint64_t ts) { m_LastActivityTimestamp = ts; };
 			
 			virtual uint32_t GetRelayTag () const { return 0; };
-			virtual void SendLocalRouterInfo (bool update = false) 
-			{
-				std::list<std::shared_ptr<I2NPMessage> > msgs{ CreateDatabaseStoreMsg () };
-				SendI2NPMessages (msgs); 
-			};
-			virtual void SendI2NPMessages (std::list<std::shared_ptr<I2NPMessage> >& msgs) = 0;
+			virtual void SendLocalRouterInfo (bool update = false) { SendI2NPMessages ({ CreateDatabaseStoreMsg () }); };
+			virtual void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs) = 0;
 			virtual bool IsEstablished () const = 0;
-			virtual i2p::data::RouterInfo::SupportedTransports GetTransportType () const = 0;
 
 		private:
 
@@ -198,6 +187,15 @@ namespace transport
 			uint64_t m_LastActivityTimestamp, m_LastBandwidthUpdateTimestamp;	
 			uint32_t m_InBandwidth, m_OutBandwidth;
 	};
+
+	// SOCKS5 proxy
+	const uint8_t SOCKS5_VER = 0x05;
+	const uint8_t SOCKS5_CMD_CONNECT = 0x01;
+	const uint8_t SOCKS5_CMD_UDP_ASSOCIATE = 0x03;
+	const uint8_t SOCKS5_ATYP_IPV4 = 0x01;
+	const uint8_t SOCKS5_ATYP_IPV6 = 0x04;
+	const size_t SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE = 10;
+	const size_t SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE = 22;
 }
 }
 
diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp
index 98dbcd94..b6e2e261 100644
--- a/libi2pd/Transports.cpp
+++ b/libi2pd/Transports.cpp
@@ -1,12 +1,11 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
 * See full license text in LICENSE file at top of project tree
 */
 
-#include <boost/algorithm/string.hpp> // for boost::to_lower
 #include "Log.h"
 #include "Crypto.h"
 #include "RouterContext.h"
@@ -25,7 +24,7 @@ namespace transport
 {
 	template<typename Keys>
 	EphemeralKeysSupplier<Keys>::EphemeralKeysSupplier (int size):
-		m_QueueSize (size), m_IsRunning (false)
+		m_QueueSize (size), m_IsRunning (false), m_Thread (nullptr)
 	{
 	}
 
@@ -39,7 +38,7 @@ namespace transport
 	void EphemeralKeysSupplier<Keys>::Start ()
 	{
 		m_IsRunning = true;
-		m_Thread.reset (new std::thread (std::bind (&EphemeralKeysSupplier<Keys>::Run, this)));
+		m_Thread = new std::thread (std::bind (&EphemeralKeysSupplier<Keys>::Run, this));
 	}
 
 	template<typename Keys>
@@ -53,15 +52,9 @@ namespace transport
 		if (m_Thread)
 		{
 			m_Thread->join ();
-			m_Thread = nullptr;
+			delete m_Thread;
+			m_Thread = 0;
 		}
-		if (!m_Queue.empty ())
-		{
-			// clean up queue
-			std::queue<std::shared_ptr<Keys> > tmp;
-	   		std::swap (m_Queue, tmp);
-		}
-		m_KeysPool.CleanUpMt ();
 	}
 
 	template<typename Keys>
@@ -72,19 +65,18 @@ namespace transport
 		while (m_IsRunning)
 		{
 			int num, total = 0;
-			while ((num = m_QueueSize - (int)m_Queue.size ()) > 0 && total < m_QueueSize)
+			while ((num = m_QueueSize - (int)m_Queue.size ()) > 0 && total < 10)
 			{
 				CreateEphemeralKeys (num);
 				total += num;
 			}
-			if (total > m_QueueSize)
+			if (total >= 10)
 			{
 				LogPrint (eLogWarning, "Transports: ", total, " ephemeral keys generated at the time");
 				std::this_thread::sleep_for (std::chrono::seconds(1)); // take a break
 			}
 			else
 			{
-				m_KeysPool.CleanUpMt ();
 				std::unique_lock<std::mutex> l(m_AcquiredMutex);
 				if (!m_IsRunning) break;
 				m_Acquired.wait (l); // wait for element gets acquired
@@ -99,7 +91,7 @@ namespace transport
 		{
 			for (int i = 0; i < num; i++)
 			{
-				auto pair = m_KeysPool.AcquireSharedMt ();
+				auto pair = std::make_shared<Keys> ();
 				pair->GenerateKeys ();
 				std::unique_lock<std::mutex> l(m_AcquiredMutex);
 				m_Queue.push (pair);
@@ -121,7 +113,7 @@ namespace transport
 			}
 		}
 		// queue is empty, create new
-		auto pair = m_KeysPool.AcquireSharedMt ();
+		auto pair = std::make_shared<Keys> ();
 		pair->GenerateKeys ();
 		return pair;
 	}
@@ -131,37 +123,27 @@ namespace transport
 	{
 		if (pair)
 		{
-			std::unique_lock<std::mutex> l(m_AcquiredMutex);
+			std::unique_lock<std::mutex>l(m_AcquiredMutex);
 			if ((int)m_Queue.size () < 2*m_QueueSize)
 				m_Queue.push (pair);
 		}
 		else
-			LogPrint(eLogError, "Transports: Return null keys");
+			LogPrint(eLogError, "Transports: Return null DHKeys");
 	}
 
-	void Peer::UpdateParams (std::shared_ptr<const i2p::data::RouterInfo> router)
-	{
-		if (router)
-		{		
-			isHighBandwidth = router->IsHighBandwidth ();
-			isEligible =(bool)router->GetCompatibleTransports (true) && // reachable
-				router->GetCongestion () != i2p::data::RouterInfo::eRejectAll && // accepts tunnel
-				router->IsECIES () && router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION; // not too old
-		}	
-	}	
-		
 	Transports transports;
 
 	Transports::Transports ():
 		m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_CheckReserved(true), m_Thread (nullptr),
 		m_Service (nullptr), m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr),
 		m_UpdateBandwidthTimer (nullptr), m_SSU2Server (nullptr), m_NTCP2Server (nullptr),
-		m_X25519KeysPairSupplier (NUM_X25519_PRE_GENERATED_KEYS),
+		m_X25519KeysPairSupplier (15), // 15 pre-generated keys
 		m_TotalSentBytes (0), m_TotalReceivedBytes (0), m_TotalTransitTransmittedBytes (0),
 		m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth (0),
+		m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), m_LastTransitBandwidthUpdateBytes (0),
 		m_InBandwidth15s (0), m_OutBandwidth15s (0), m_TransitBandwidth15s (0),
-		m_InBandwidth5m (0), m_OutBandwidth5m (0), m_TransitBandwidth5m (0),
-		m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL)
+		m_LastInBandwidth15sUpdateBytes (0), m_LastOutBandwidth15sUpdateBytes (0), m_LastTransitBandwidth15sUpdateBytes (0),
+		m_LastBandwidth15sUpdateTime (0)
 	{
 	}
 
@@ -182,8 +164,8 @@ namespace transport
 	{
 		if (!m_Service)
 		{
-			m_Service = new boost::asio::io_context ();
-			m_Work = new boost::asio::executor_work_guard<boost::asio::io_context::executor_type> (m_Service->get_executor ());
+			m_Service = new boost::asio::io_service ();
+			m_Work = new boost::asio::io_service::work (*m_Service);
 			m_PeerCleanupTimer = new boost::asio::deadline_timer (*m_Service);
 			m_PeerTestTimer = new boost::asio::deadline_timer (*m_Service);
 			m_UpdateBandwidthTimer = new boost::asio::deadline_timer (*m_Service);
@@ -257,7 +239,7 @@ namespace transport
 			if (!address.empty ())
 			{
 				boost::system::error_code ec;
-				auto addr = boost::asio::ip::make_address (address, ec);
+				auto addr = boost::asio::ip::address::from_string (address, ec);
 				if (!ec)
 				{
 					if (m_NTCP2Server) m_NTCP2Server->SetLocalAddress (addr);
@@ -283,7 +265,7 @@ namespace transport
 			if (!address.empty ())
 			{
 				boost::system::error_code ec;
-				auto addr = boost::asio::ip::make_address (address, ec);
+				auto addr = boost::asio::ip::address::from_string (address, ec);
 				if (!ec)
 				{
 					if (m_NTCP2Server) m_NTCP2Server->SetLocalAddress (addr);
@@ -310,7 +292,7 @@ namespace transport
 			if (!address.empty ())
 			{
 				boost::system::error_code ec;
-				auto addr = boost::asio::ip::make_address (address, ec);
+				auto addr = boost::asio::ip::address::from_string (address, ec);
 				if (!ec && m_NTCP2Server && i2p::util::net::IsYggdrasilAddress (addr))
 					m_NTCP2Server->SetLocalAddress (addr);
 			}
@@ -324,22 +306,12 @@ namespace transport
 		m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(5 * SESSION_CREATION_TIMEOUT));
 		m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1));
 
-		uint64_t ts = i2p::util::GetMillisecondsSinceEpoch();
-		for (int i = 0; i < TRAFFIC_SAMPLE_COUNT; i++)
-		{
-			m_TrafficSamples[i].Timestamp = ts - (TRAFFIC_SAMPLE_COUNT - i - 1) * 1000;
-			m_TrafficSamples[i].TotalReceivedBytes = 0;
-			m_TrafficSamples[i].TotalSentBytes = 0;
-			m_TrafficSamples[i].TotalTransitTransmittedBytes = 0;
-		}
-		m_TrafficSamplePtr = TRAFFIC_SAMPLE_COUNT - 1;
-
 		m_UpdateBandwidthTimer->expires_from_now (boost::posix_time::seconds(1));
 		m_UpdateBandwidthTimer->async_wait (std::bind (&Transports::HandleUpdateBandwidthTimer, this, std::placeholders::_1));
 
 		if (m_IsNAT)
 		{
-			m_PeerTestTimer->expires_from_now (boost::posix_time::seconds(PEER_TEST_INTERVAL + m_Rng() % PEER_TEST_INTERVAL_VARIANCE));
+			m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL));
 			m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1));
 		}
 	}
@@ -348,6 +320,7 @@ namespace transport
 	{
 		if (m_PeerCleanupTimer) m_PeerCleanupTimer->cancel ();
 		if (m_PeerTestTimer) m_PeerTestTimer->cancel ();
+		m_Peers.clear ();
 
 		if (m_SSU2Server)
 		{
@@ -372,7 +345,6 @@ namespace transport
 			delete m_Thread;
 			m_Thread = nullptr;
 		}
-		m_Peers.clear ();
 	}
 
 	void Transports::Run ()
@@ -392,85 +364,65 @@ namespace transport
 		}
 	}
 
-	void Transports::UpdateBandwidthValues(int interval, uint32_t& in, uint32_t& out, uint32_t& transit)
-	{
-		TrafficSample& sample1 = m_TrafficSamples[m_TrafficSamplePtr];
-		TrafficSample& sample2 = m_TrafficSamples[(TRAFFIC_SAMPLE_COUNT + m_TrafficSamplePtr - interval) % TRAFFIC_SAMPLE_COUNT];
-		auto delta = (int64_t)sample1.Timestamp - (int64_t)sample2.Timestamp;
-		if (delta <= 0)
-		{
-			LogPrint (eLogError, "Transports: Backward clock jump detected, got ", delta, " instead of ", interval * 1000);
-			return;
-		}
-		in = (sample1.TotalReceivedBytes - sample2.TotalReceivedBytes) * 1000 / delta;
-		out = (sample1.TotalSentBytes - sample2.TotalSentBytes) * 1000 / delta;
-		transit = (sample1.TotalTransitTransmittedBytes - sample2.TotalTransitTransmittedBytes) * 1000 / delta;
-	}
-
 	void Transports::HandleUpdateBandwidthTimer (const boost::system::error_code& ecode)
 	{
 		if (ecode != boost::asio::error::operation_aborted)
 		{
-			m_TrafficSamplePtr++;
-			if (m_TrafficSamplePtr == TRAFFIC_SAMPLE_COUNT)
-				m_TrafficSamplePtr = 0;
+			uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
 
-			TrafficSample& sample = m_TrafficSamples[m_TrafficSamplePtr];
-			sample.Timestamp = i2p::util::GetMillisecondsSinceEpoch();
-			sample.TotalReceivedBytes = m_TotalReceivedBytes;
-			sample.TotalSentBytes = m_TotalSentBytes;
-			sample.TotalTransitTransmittedBytes = m_TotalTransitTransmittedBytes;
+			// updated every second
+			m_InBandwidth = m_TotalReceivedBytes - m_LastInBandwidthUpdateBytes;
+			m_OutBandwidth = m_TotalSentBytes - m_LastOutBandwidthUpdateBytes;
+			m_TransitBandwidth = m_TotalTransitTransmittedBytes - m_LastTransitBandwidthUpdateBytes;
 
-			UpdateBandwidthValues (1, m_InBandwidth, m_OutBandwidth, m_TransitBandwidth);
-			UpdateBandwidthValues (15, m_InBandwidth15s, m_OutBandwidth15s, m_TransitBandwidth15s);
-			UpdateBandwidthValues (300, m_InBandwidth5m, m_OutBandwidth5m, m_TransitBandwidth5m);
+			m_LastInBandwidthUpdateBytes = m_TotalReceivedBytes;
+			m_LastOutBandwidthUpdateBytes = m_TotalSentBytes;
+			m_LastTransitBandwidthUpdateBytes = m_TotalTransitTransmittedBytes;
+
+			// updated every 15 seconds
+			auto delta = ts - m_LastBandwidth15sUpdateTime;
+			if (delta > 15 * 1000)
+			{
+				m_InBandwidth15s = (m_TotalReceivedBytes - m_LastInBandwidth15sUpdateBytes) * 1000 / delta;
+				m_OutBandwidth15s = (m_TotalSentBytes - m_LastOutBandwidth15sUpdateBytes) * 1000 / delta;
+				m_TransitBandwidth15s = (m_TotalTransitTransmittedBytes - m_LastTransitBandwidth15sUpdateBytes) * 1000 / delta;
+
+				m_LastBandwidth15sUpdateTime = ts;
+				m_LastInBandwidth15sUpdateBytes = m_TotalReceivedBytes;
+				m_LastOutBandwidth15sUpdateBytes = m_TotalSentBytes;
+				m_LastTransitBandwidth15sUpdateBytes = m_TotalTransitTransmittedBytes;
+			}
 
 			m_UpdateBandwidthTimer->expires_from_now (boost::posix_time::seconds(1));
 			m_UpdateBandwidthTimer->async_wait (std::bind (&Transports::HandleUpdateBandwidthTimer, this, std::placeholders::_1));
 		}
 	}
 
-	int Transports::GetCongestionLevel (bool longTerm) const
+	bool Transports::IsBandwidthExceeded () const
 	{
-		auto bwLimit = i2p::context.GetBandwidthLimit () * 1024; // convert to bytes
-		auto tbwLimit = i2p::context.GetTransitBandwidthLimit () * 1024; // convert to bytes
-
-		if (tbwLimit == 0 || bwLimit == 0)
-			return CONGESTION_LEVEL_FULL;
-
-		uint32_t bw;
-		uint32_t tbw;
-		if (longTerm)
-		{
-			bw = std::max (m_InBandwidth5m, m_OutBandwidth5m);
-			tbw = m_TransitBandwidth5m;
-		}
-		else
-		{
-			bw = std::max (m_InBandwidth15s, m_OutBandwidth15s);
-			tbw = m_TransitBandwidth;
-		}
-		auto bwCongestionLevel = CONGESTION_LEVEL_FULL * bw / bwLimit;
-		auto tbwCongestionLevel = CONGESTION_LEVEL_FULL * tbw / tbwLimit;
-		return std::max (bwCongestionLevel, tbwCongestionLevel);
+		auto limit = i2p::context.GetBandwidthLimit() * 1024; // convert to bytes
+		auto bw = std::max (m_InBandwidth15s, m_OutBandwidth15s);
+		return bw > limit;
 	}
 
-	std::future<std::shared_ptr<TransportSession> > Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr<i2p::I2NPMessage> msg)
+	bool Transports::IsTransitBandwidthExceeded () const
+	{
+		auto limit = i2p::context.GetTransitBandwidthLimit() * 1024; // convert to bytes
+		return m_TransitBandwidth > limit;
+	}
+
+	void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr<i2p::I2NPMessage> msg)
 	{
 		if (m_IsOnline)
-			return SendMessages (ident, { msg });
-		return {}; // invalid future
+			SendMessages (ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > {msg });
 	}
 
-	std::future<std::shared_ptr<TransportSession> > Transports::SendMessages (const i2p::data::IdentHash& ident, std::list<std::shared_ptr<i2p::I2NPMessage> >&& msgs)
+	void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector<std::shared_ptr<i2p::I2NPMessage> >& msgs)
 	{
-		return boost::asio::post (*m_Service, boost::asio::use_future ([this, ident, msgs = std::move(msgs)] () mutable
-			{
-				return PostMessages (ident, msgs);
-			}));	
-	}	
-	
-	std::shared_ptr<TransportSession> Transports::PostMessages (const i2p::data::IdentHash& ident, std::list<std::shared_ptr<i2p::I2NPMessage> >& msgs)
+		m_Service->post (std::bind (&Transports::PostMessages, this, ident, msgs));
+	}
+
+	void Transports::PostMessages (i2p::data::IdentHash ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > msgs)
 	{
 		if (ident == i2p::context.GetRouterInfo ().GetIdentHash ())
 		{
@@ -478,105 +430,73 @@ namespace transport
 			for (auto& it: msgs)
 				m_LoopbackHandler.PutNextMessage (std::move (it));
 			m_LoopbackHandler.Flush ();
-			return nullptr;
+			return;
 		}
-		if(RoutesRestricted() && !IsRestrictedPeer(ident)) return nullptr;
-		std::shared_ptr<Peer> peer;
+		if(RoutesRestricted() && !IsRestrictedPeer(ident)) return;
+		auto it = m_Peers.find (ident);
+		if (it == m_Peers.end ())
 		{
-			std::lock_guard<std::mutex> l(m_PeersMutex);
-			auto it = m_Peers.find (ident);
-			if (it != m_Peers.end ())
-				peer = it->second;
-		}	
-		if (!peer)
-		{
-			// check if not banned
-			if (i2p::data::IsRouterBanned (ident)) return nullptr; // don't create peer to unreachable router
-			// try to connect
 			bool connected = false;
 			try
 			{
 				auto r = netdb.FindRouter (ident);
-				if (r && (r->IsUnreachable () || !r->IsReachableFrom (i2p::context.GetRouterInfo ()))) return nullptr; // router found but non-reachable
-
-				peer = std::make_shared<Peer>(r, i2p::util::GetSecondsSinceEpoch ());
-				{	
-					std::lock_guard<std::mutex> l(m_PeersMutex);
-					peer = m_Peers.emplace (ident, peer).first->second;
+				if (r && (r->IsUnreachable () || !r->IsReachableFrom (i2p::context.GetRouterInfo ()))) return; // router found but non-reachable
+				{
+					auto ts = i2p::util::GetSecondsSinceEpoch ();
+					std::unique_lock<std::mutex> l(m_PeersMutex);
+					it = m_Peers.insert (std::pair<i2p::data::IdentHash, Peer>(ident, {r, ts})).first;
 				}
-				if (peer)
-					connected = ConnectToPeer (ident, peer);
+				connected = ConnectToPeer (ident, it->second);
 			}
 			catch (std::exception& ex)
 			{
 				LogPrint (eLogError, "Transports: PostMessages exception:", ex.what ());
 			}
-			if (!connected) return nullptr;
+			if (!connected) return;
 		}
-		
-		if (!peer) return nullptr;
-		if (peer->IsConnected ())
-		{	
-			auto session = peer->sessions.front (); 
-			if (session) session->SendI2NPMessages (msgs);
-			return session;
-		}	
+		if (!it->second.sessions.empty ())
+			it->second.sessions.front ()->SendI2NPMessages (msgs);
 		else
 		{
-			auto sz = peer->delayedMessages.size (); 	
+			auto sz = it->second.delayedMessages.size (); 	
 			if (sz < MAX_NUM_DELAYED_MESSAGES)
 			{
 				if (sz < CHECK_PROFILE_NUM_DELAYED_MESSAGES && sz + msgs.size () >= CHECK_PROFILE_NUM_DELAYED_MESSAGES)
 				{
-					if (i2p::data::IsRouterBanned (ident))
+					auto profile = i2p::data::GetRouterProfile (ident);
+					if (profile && profile->IsUnreachable ())
 					{
-						LogPrint (eLogWarning, "Transports: Router ", ident.ToBase64 (), " is banned. Peer dropped");
-						std::lock_guard<std::mutex> l(m_PeersMutex);
-						m_Peers.erase (ident);
-						return nullptr;
+						LogPrint (eLogWarning, "Transports: Peer profile for ", ident.ToBase64 (), " reports unreachable. Dropped");
+						std::unique_lock<std::mutex> l(m_PeersMutex);
+						m_Peers.erase (it);
+						return;
 					}	
 				}	
-				if (sz > MAX_NUM_DELAYED_MESSAGES/2)
-				{	
-					for (auto& it1: msgs)
-						if (it1->onDrop)
-							it1->Drop (); // drop earlier because we can handle it
-						else
-							peer->delayedMessages.push_back (it1);
-				}	
-				else
-					peer->delayedMessages.splice (peer->delayedMessages.end (), msgs);
+				for (auto& it1: msgs)
+					it->second.delayedMessages.push_back (it1);
 			}
 			else
 			{
 				LogPrint (eLogWarning, "Transports: Delayed messages queue size to ",
 					ident.ToBase64 (), " exceeds ", MAX_NUM_DELAYED_MESSAGES);
-				std::lock_guard<std::mutex> l(m_PeersMutex);
-				m_Peers.erase (ident);
+				std::unique_lock<std::mutex> l(m_PeersMutex);
+				m_Peers.erase (it);
 			}
 		}
-		return nullptr;
 	}
 
-	bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr<Peer> peer)
+	bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer)
 	{
-		if (!peer->router) // reconnect
-		{	
-			auto r = netdb.FindRouter (ident); // try to get new one from netdb
-			if (r)
-			{	
-				peer->SetRouter (r); 
-				r->CancelBufferToDelete ();
-			}	
-		}	
-		if (peer->router) // we have RI already
+		if (!peer.router) // reconnect
+			peer.SetRouter (netdb.FindRouter (ident)); // try to get new one from netdb
+		if (peer.router) // we have RI already
 		{
-			if (peer->priority.empty ())
+			if (peer.priority.empty ())
 				SetPriority (peer);
-			while (peer->numAttempts < (int)peer->priority.size ())
+			while (peer.numAttempts < (int)peer.priority.size ())
 			{
-				auto tr = peer->priority[peer->numAttempts];
-				peer->numAttempts++;
+				auto tr = peer.priority[peer.numAttempts];
+				peer.numAttempts++;
 				switch (tr)
 				{
 					case i2p::data::RouterInfo::eNTCP2V4:
@@ -584,12 +504,12 @@ namespace transport
 					{
 						if (!m_NTCP2Server) continue;
 						std::shared_ptr<const RouterInfo::Address> address = (tr == i2p::data::RouterInfo::eNTCP2V6) ?
-							peer->router->GetPublishedNTCP2V6Address () : peer->router->GetPublishedNTCP2V4Address ();
-						if (address && IsInReservedRange(address->host))
+							peer.router->GetPublishedNTCP2V6Address () : peer.router->GetPublishedNTCP2V4Address ();
+						if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host))
 							address = nullptr;
 						if (address)
 						{
-							auto s = std::make_shared<NTCP2Session> (*m_NTCP2Server, peer->router, address);
+							auto s = std::make_shared<NTCP2Session> (*m_NTCP2Server, peer.router, address);
 							if( m_NTCP2Server->UsingProxy())
 								m_NTCP2Server->ConnectWithProxy(s);
 							else
@@ -603,12 +523,12 @@ namespace transport
 					{
 						if (!m_SSU2Server) continue;
 						std::shared_ptr<const RouterInfo::Address> address = (tr == i2p::data::RouterInfo::eSSU2V6) ?
-							peer->router->GetSSU2V6Address () : peer->router->GetSSU2V4Address ();
-						if (address && IsInReservedRange(address->host))
+							peer.router->GetSSU2V6Address () : peer.router->GetSSU2V4Address ();
+						if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host))
 							address = nullptr;
 						if (address && address->IsReachableSSU ())
 						{
-							if (m_SSU2Server->CreateSession (peer->router, address))
+							if (m_SSU2Server->CreateSession (peer.router, address))
 								return true;
 						}
 						break;
@@ -616,10 +536,10 @@ namespace transport
 					case i2p::data::RouterInfo::eNTCP2V6Mesh:
 					{
 						if (!m_NTCP2Server) continue;
-						auto address = peer->router->GetYggdrasilAddress ();
+						auto address = peer.router->GetYggdrasilAddress ();
 						if (address)
 						{
-							auto s = std::make_shared<NTCP2Session> (*m_NTCP2Server, peer->router, address);
+							auto s = std::make_shared<NTCP2Session> (*m_NTCP2Server, peer.router, address);
 							m_NTCP2Server->Connect (s);
 							return true;
 						}
@@ -631,18 +551,10 @@ namespace transport
 			}
 
 			LogPrint (eLogInfo, "Transports: No compatible addresses available");
-			if (!i2p::context.IsLimitedConnectivity () && peer->router->IsReachableFrom (i2p::context.GetRouterInfo ()))
+			if (peer.router->IsReachableFrom (i2p::context.GetRouterInfo ()))
 				i2p::data::netdb.SetUnreachable (ident, true); // we are here because all connection attempts failed but router claimed them
-			peer->Done ();
-			std::lock_guard<std::mutex> l(m_PeersMutex);
-			m_Peers.erase (ident);
-			return false;
-		}
-		else if (i2p::data::IsRouterBanned (ident))
-		{
-			LogPrint (eLogWarning, "Transports: Router ", ident.ToBase64 (), " is banned. Peer dropped");
-			peer->Done ();
-			std::lock_guard<std::mutex> l(m_PeersMutex);
+			peer.Done ();
+			std::unique_lock<std::mutex> l(m_PeersMutex);
 			m_Peers.erase (ident);
 			return false;
 		}
@@ -655,7 +567,7 @@ namespace transport
 		return true;
 	}
 
-	void Transports::SetPriority (std::shared_ptr<Peer> peer)
+	void Transports::SetPriority (Peer& peer) const
 	{
 		static const std::vector<i2p::data::RouterInfo::SupportedTransports>
 			ntcp2Priority =
@@ -674,107 +586,41 @@ namespace transport
 			i2p::data::RouterInfo::eNTCP2V4,
 			i2p::data::RouterInfo::eNTCP2V6Mesh
 		};
-		if (!peer || !peer->router) return;
+		if (!peer.router) return;
 		auto compatibleTransports = context.GetRouterInfo ().GetCompatibleTransports (false) &
-			peer->router->GetCompatibleTransports (true);
-		auto directTransports = compatibleTransports & peer->router->GetPublishedTransports ();
-		peer->numAttempts = 0;
-		peer->priority.clear ();
-		
-		std::shared_ptr<RouterProfile> profile;
-		if (peer->router->HasProfile ()) profile = peer->router->GetProfile (); // only if in memory
-		bool ssu2 = false; // NTCP2 by default
-		bool isReal = profile ? profile->IsReal () : true;
-		if (isReal) 
-		{	
-			ssu2 = m_Rng () & 1; // 1/2
-			if (ssu2 && !profile)
-			{	
-				profile = peer->router->GetProfile (); // load profile if necessary
-				isReal = profile->IsReal ();  
-				if (!isReal) ssu2 = false; // try NTCP2 if router is not confirmed real
-			}
-		}	
+			peer.router->GetCompatibleTransports (true);
+		peer.numAttempts = 0;
+		peer.priority.clear ();
+		bool ssu2 = peer.router->GetProfile ()->IsReal () ? (rand () & 1) : false; // try NTCP2 if router is not confirmed real
 		const auto& priority = ssu2 ? ssu2Priority : ntcp2Priority;
-		if (directTransports)
-		{	
-			// direct connections have higher priority
-			if (!isReal && (directTransports & (i2p::data::RouterInfo::eNTCP2V4 | i2p::data::RouterInfo::eNTCP2V6)))
-			{
-				// Non-confirmed router and a NTCP2 direct connection is presented
-				compatibleTransports &= ~directTransports; // exclude SSU2 direct connections
-				directTransports &= ~(i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6);
-			}	
-			for (auto transport: priority)
-				if (transport & directTransports)
-					peer->priority.push_back (transport);
-			compatibleTransports &= ~directTransports;
-		}	
-		if (compatibleTransports)
-		{	
-			// then remaining
-			for (auto transport: priority)
-				if (transport & compatibleTransports)
-					peer->priority.push_back (transport);
-		}	
-		if (peer->priority.empty ())
-		{
-			// try recently connected SSU2 if any
-			auto supportedTransports = context.GetRouterInfo ().GetCompatibleTransports (false) &
-				peer->router->GetCompatibleTransports (false);
-			if ((supportedTransports & (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6)) &&
-			    peer->router->HasProfile ())
-			{
-				auto ep = peer->router->GetProfile ()->GetLastEndpoint ();
-				if (!ep.address ().is_unspecified () && ep.port ())
-				{	
-					if (ep.address ().is_v4 ())
-					{	
-						if ((supportedTransports & i2p::data::RouterInfo::eSSU2V4) &&
-							m_SSU2Server->IsConnectedRecently (ep, false))
-							peer->priority.push_back (i2p::data::RouterInfo::eSSU2V4);
-					}	
-					else if (ep.address ().is_v6 ())
-					{
-						if ((supportedTransports & i2p::data::RouterInfo::eSSU2V6) &&
-							m_SSU2Server->IsConnectedRecently (ep))
-							peer->priority.push_back (i2p::data::RouterInfo::eSSU2V6);
-					}
-				}	
-			}	
-		}	
+		for (auto transport: priority)
+			if (transport & compatibleTransports)
+				peer.priority.push_back (transport);
 	}
 
 	void Transports::RequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, const i2p::data::IdentHash& ident)
 	{
-		boost::asio::post (*m_Service, std::bind (&Transports::HandleRequestComplete, this, r, ident));
+		m_Service->post (std::bind (&Transports::HandleRequestComplete, this, r, ident));
 	}
 
 	void Transports::HandleRequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, i2p::data::IdentHash ident)
 	{
-		std::shared_ptr<Peer> peer;
+		auto it = m_Peers.find (ident);
+		if (it != m_Peers.end ())
 		{
-			std::lock_guard<std::mutex> l(m_PeersMutex);
-			auto it = m_Peers.find (ident);
-			if (it != m_Peers.end ())
+			if (r)
 			{
-				if (r)
-					peer = it->second;
-				else
-					m_Peers.erase (it);
-			}	
+				LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, trying to connect");
+				it->second.SetRouter (r);
+				ConnectToPeer (ident, it->second);
+			}
+			else
+			{
+				LogPrint (eLogWarning, "Transports: RouterInfo not found, failed to send messages");
+				std::unique_lock<std::mutex> l(m_PeersMutex);
+				m_Peers.erase (it);
+			}
 		}
-				
-		if (peer && !peer->router && r)
-		{
-			LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, trying to connect");
-			peer->SetRouter (r);
-			if (!peer->IsConnected ())
-				ConnectToPeer (ident, peer);
-		}
-		else if (!r)
-			LogPrint (eLogInfo, "Transports: RouterInfo not found, failed to send messages");
-
 	}
 
 	void Transports::DetectExternalIP ()
@@ -797,7 +643,7 @@ namespace transport
 		if (ipv4 && i2p::context.SupportsV4 ())
 		{
 			LogPrint (eLogInfo, "Transports: Started peer test IPv4");
-			std::unordered_set<i2p::data::IdentHash> excluded;
+			std::set<i2p::data::IdentHash> excluded;
 			excluded.insert (i2p::context.GetIdentHash ()); // don't pick own router
 			int testDelay = 0;
 			for (int i = 0; i < 5; i++)
@@ -813,7 +659,7 @@ namespace transport
 					}	
 					else
 					{
-						testDelay += PEER_TEST_DELAY_INTERVAL + m_Rng() % PEER_TEST_DELAY_INTERVAL_VARIANCE;
+						testDelay += PEER_TEST_DELAY_INTERVAL + rand() % PEER_TEST_DELAY_INTERVAL_VARIANCE;
 						if (m_Service)
 						{	
 							auto delayTimer = std::make_shared<boost::asio::deadline_timer>(*m_Service);
@@ -835,7 +681,7 @@ namespace transport
 		if (ipv6 && i2p::context.SupportsV6 ())
 		{
 			LogPrint (eLogInfo, "Transports: Started peer test IPv6");
-			std::unordered_set<i2p::data::IdentHash> excluded;
+			std::set<i2p::data::IdentHash> excluded;
 			excluded.insert (i2p::context.GetIdentHash ()); // don't pick own router
 			int testDelay = 0;
 			for (int i = 0; i < 5; i++)
@@ -851,7 +697,7 @@ namespace transport
 					}	
 					else
 					{
-						testDelay += PEER_TEST_DELAY_INTERVAL + m_Rng() % PEER_TEST_DELAY_INTERVAL_VARIANCE;
+						testDelay += PEER_TEST_DELAY_INTERVAL + rand() % PEER_TEST_DELAY_INTERVAL_VARIANCE;
 						if (m_Service)
 						{	
 							auto delayTimer = std::make_shared<boost::asio::deadline_timer>(*m_Service);
@@ -884,7 +730,7 @@ namespace transport
 
 	void Transports::PeerConnected (std::shared_ptr<TransportSession> session)
 	{
-		boost::asio::post (*m_Service, [session, this]()
+		m_Service->post([session, this]()
 		{
 			auto remoteIdentity = session->GetRemoteIdentity ();
 			if (!remoteIdentity) return;
@@ -892,36 +738,31 @@ namespace transport
 			auto it = m_Peers.find (ident);
 			if (it != m_Peers.end ())
 			{
-				auto peer = it->second;
-				if (peer->numAttempts > 1)
+				if (it->second.numAttempts > 1)
 				{
 					// exclude failed transports
 					i2p::data::RouterInfo::CompatibleTransports transports = 0;
-					int numExcluded = peer->numAttempts - 1;
-					if (numExcluded > (int)peer->priority.size ()) numExcluded = peer->priority.size ();
+					int numExcluded = it->second.numAttempts - 1;
+					if (numExcluded > (int)it->second.priority.size ()) numExcluded = it->second.priority.size ();
 					for (int i = 0; i < numExcluded; i++)
-						transports |= peer->priority[i];
+						transports |= it->second.priority[i];
 					i2p::data::netdb.ExcludeReachableTransports (ident, transports);
 				}	
-				if (peer->router && peer->numAttempts)
+				if (it->second.router && it->second.numAttempts)
 				{	
-					auto transport = peer->priority[peer->numAttempts-1];
+					auto transport = it->second.priority[it->second.numAttempts-1];
 					if (transport == i2p::data::RouterInfo::eNTCP2V4 || 
 						transport == i2p::data::RouterInfo::eNTCP2V6 || transport == i2p::data::RouterInfo::eNTCP2V6Mesh)
-							i2p::data::UpdateRouterProfile (ident,
-								[](std::shared_ptr<i2p::data::RouterProfile> profile)
-								{
-									if (profile) profile->Connected (); // outgoing NTCP2 connection if always real
-								});
+						it->second.router->GetProfile ()->Connected (); // outgoing NTCP2 connection if always real
 					i2p::data::netdb.SetUnreachable (ident, false); // clear unreachable 
 				}		
-				peer->numAttempts = 0;
-				peer->router = nullptr; // we don't need RouterInfo after successive connect
+				it->second.numAttempts = 0;
+				it->second.router = nullptr; // we don't need RouterInfo after successive connect
 				bool sendDatabaseStore = true;
-				if (it->second->delayedMessages.size () > 0)
+				if (it->second.delayedMessages.size () > 0)
 				{
 					// check if first message is our DatabaseStore (publishing)
-					auto firstMsg = peer->delayedMessages.front ();
+					auto firstMsg = it->second.delayedMessages[0];
 					if (firstMsg && firstMsg->GetTypeID () == eI2NPDatabaseStore &&
 							i2p::data::IdentHash(firstMsg->GetPayload () + DATABASE_STORE_KEY_OFFSET) == i2p::context.GetIdentHash ())
 						sendDatabaseStore = false; // we have it in the list already
@@ -930,8 +771,9 @@ namespace transport
 					session->SendLocalRouterInfo ();
 				else
 					session->SetTerminationTimeout (10); // most likely it's publishing, no follow-up messages expected, set timeout to 10 seconds
-				peer->sessions.push_back (session);
-				session->SendI2NPMessages (peer->delayedMessages); // send and clear
+				it->second.sessions.push_back (session);
+				session->SendI2NPMessages (it->second.delayedMessages);
+				it->second.delayedMessages.clear ();
 			}
 			else // incoming connection or peer test
 			{
@@ -942,29 +784,21 @@ namespace transport
 					return;
 				}
 				if (!session->IsOutgoing ()) // incoming
-				{
-					std::list<std::shared_ptr<I2NPMessage> > msgs{ CreateDatabaseStoreMsg () };
-					session->SendI2NPMessages (msgs); // send DatabaseStore
-				}	
+					session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore
 				auto r = i2p::data::netdb.FindRouter (ident); // router should be in netdb after SessionConfirmed
-				i2p::data::UpdateRouterProfile (ident,
-					[](std::shared_ptr<i2p::data::RouterProfile> profile)
-					{
-						if (profile) profile->Connected ();
-					});
+				if (r) r->GetProfile ()->Connected ();
 				auto ts = i2p::util::GetSecondsSinceEpoch ();
-				auto peer = std::make_shared<Peer>(r, ts);
-				peer->sessions.push_back (session);
-				peer->router = nullptr;
-				std::lock_guard<std::mutex> l(m_PeersMutex);
-				m_Peers.emplace (ident, peer);
+				std::unique_lock<std::mutex> l(m_PeersMutex);
+				auto it = m_Peers.insert (std::make_pair (ident, Peer{ r, ts })).first;
+				it->second.sessions.push_back (session);
+				it->second.router = nullptr;
 			}
 		});
 	}
 
 	void Transports::PeerDisconnected (std::shared_ptr<TransportSession> session)
 	{
-		boost::asio::post (*m_Service, [session, this]()
+		m_Service->post([session, this]()
 		{
 			auto remoteIdentity = session->GetRemoteIdentity ();
 			if (!remoteIdentity) return;
@@ -972,26 +806,20 @@ namespace transport
 			auto it = m_Peers.find (ident);
 			if (it != m_Peers.end ())
 			{
-				auto peer = it->second;
-				bool wasConnected = peer->IsConnected ();
-				peer->sessions.remove (session);
-				if (!peer->IsConnected ())
+				auto before = it->second.sessions.size ();
+				it->second.sessions.remove (session);
+				if (it->second.sessions.empty ())
 				{
-					if (peer->delayedMessages.size () > 0)
+					if (it->second.delayedMessages.size () > 0)
 					{
-						if (wasConnected) // we had an active session before
-							peer->numAttempts = 0; // start over
-						ConnectToPeer (ident, peer);
+						if (before > 0) // we had an active session before
+							it->second.numAttempts = 0; // start over
+						ConnectToPeer (ident, it->second);
 					}
 					else
 					{
-						{
-							std::lock_guard<std::mutex> l(m_PeersMutex);
-							m_Peers.erase (it);
-						}
-						// delete buffer of just disconnected router 
-						auto r = i2p::data::netdb.FindRouter (ident);
-						if (r && !r->IsUpdated ()) r->ScheduleBufferToDelete ();
+						std::unique_lock<std::mutex> l(m_PeersMutex);
+						m_Peers.erase (it);
 					}
 				}
 			}
@@ -1000,13 +828,9 @@ namespace transport
 
 	bool Transports::IsConnected (const i2p::data::IdentHash& ident) const
 	{
-		std::lock_guard<std::mutex> l(m_PeersMutex);
-#if __cplusplus >= 202002L // C++20
-		return m_Peers.contains (ident);
-#else		
+		std::unique_lock<std::mutex> l(m_PeersMutex);
 		auto it = m_Peers.find (ident);
 		return it != m_Peers.end ();
-#endif		
 	}
 
 	void Transports::HandlePeerCleanupTimer (const boost::system::error_code& ecode)
@@ -1016,12 +840,12 @@ namespace transport
 			auto ts = i2p::util::GetSecondsSinceEpoch ();
 			for (auto it = m_Peers.begin (); it != m_Peers.end (); )
 			{
-				it->second->sessions.remove_if (
+				it->second.sessions.remove_if (
 					[](std::shared_ptr<TransportSession> session)->bool
 					{
 						return !session || !session->IsEstablished ();
 					});
- 				if (!it->second->IsConnected () && ts > it->second->creationTime + SESSION_CREATION_TIMEOUT)
+ 				if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT)
 				{
 					LogPrint (eLogWarning, "Transports: Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds");
 				/*	if (!it->second.router) 
@@ -1030,18 +854,18 @@ namespace transport
 						auto profile = i2p::data::GetRouterProfile (it->first);
 						if (profile) profile->Unreachable ();
 					}	*/
-					std::lock_guard<std::mutex> l(m_PeersMutex);
+					std::unique_lock<std::mutex> l(m_PeersMutex);
 					it = m_Peers.erase (it);
 				}
 				else
 				{
-					if (ts > it->second->nextRouterInfoUpdateTime)
+					if (ts > it->second.nextRouterInfoUpdateTime)
 					{
-						auto session = it->second->sessions.front ();
+						auto session = it->second.sessions.front ();
 						if (session)
 							session->SendLocalRouterInfo (true);
-						it->second->nextRouterInfoUpdateTime = ts + PEER_ROUTER_INFO_UPDATE_INTERVAL +
-							m_Rng() % PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE;
+						it->second.nextRouterInfoUpdateTime = ts + PEER_ROUTER_INFO_UPDATE_INTERVAL +
+							rand () % PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE;
 					}
 					++it;
 				}
@@ -1055,7 +879,7 @@ namespace transport
 			// if still testing or unknown, repeat peer test
 			if (ipv4Testing || ipv6Testing)
 				PeerTest (ipv4Testing, ipv6Testing);
-			m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(2 * SESSION_CREATION_TIMEOUT + m_Rng() % SESSION_CREATION_TIMEOUT));
+			m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(3 * SESSION_CREATION_TIMEOUT));
 			m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1));
 		}
 	}
@@ -1065,7 +889,7 @@ namespace transport
 		if (ecode != boost::asio::error::operation_aborted)
 		{
 			PeerTest ();
-			m_PeerTestTimer->expires_from_now (boost::posix_time::seconds(PEER_TEST_INTERVAL + m_Rng() % PEER_TEST_INTERVAL_VARIANCE));
+			m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL));
 			m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1));
 		}
 	}
@@ -1074,13 +898,12 @@ namespace transport
 	std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRandomPeer (Filter filter) const
 	{
 		if (m_Peers.empty()) return nullptr;
-		auto ts = i2p::util::GetSecondsSinceEpoch ();
 		bool found = false;
 		i2p::data::IdentHash ident;
 		{
 			uint16_t inds[3];
 			RAND_bytes ((uint8_t *)inds, sizeof (inds));
-			std::lock_guard<std::mutex> l(m_PeersMutex);
+			std::unique_lock<std::mutex> l(m_PeersMutex);
 			auto count = m_Peers.size ();
 			if(count == 0) return nullptr;
 			inds[0] %= count;
@@ -1115,11 +938,9 @@ namespace transport
 				it = it1;
 				while (it != it2 && it != m_Peers.end ())
 				{
-					if (ts > it->second->lastSelectionTime + PEER_SELECTION_MIN_INTERVAL &&
-					    filter (it->second))
+					if (filter (it->second))
 					{
 						ident = it->first;
-						it->second->lastSelectionTime = ts;
 						found = true;
 						break;
 					}
@@ -1131,11 +952,9 @@ namespace transport
 					it = m_Peers.begin ();
 					while (it != it1 && it != m_Peers.end ())
 					{
-						if (ts > it->second->lastSelectionTime + PEER_SELECTION_MIN_INTERVAL &&
-						    filter (it->second))
+						if (filter (it->second))
 						{
 							ident = it->first;
-							it->second->lastSelectionTime = ts;
 							found = true;
 							break;
 						}
@@ -1147,11 +966,9 @@ namespace transport
 						it = it2;
 						while (it != m_Peers.end ())
 						{
-							if (ts > it->second->lastSelectionTime + PEER_SELECTION_MIN_INTERVAL &&
-							    filter (it->second))
+							if (filter (it->second))
 							{
 								ident = it->first;
-								it->second->lastSelectionTime = ts;
 								found = true;
 								break;
 							}
@@ -1167,13 +984,13 @@ namespace transport
 	std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRandomPeer (bool isHighBandwidth) const
 	{
 		return GetRandomPeer (
-			[isHighBandwidth](std::shared_ptr<const Peer> peer)->bool
+			[isHighBandwidth](const Peer& peer)->bool
 			{
 				// connected, not overloaded and not slow
-				return !peer->router && peer->IsConnected () && peer->isEligible &&
-					peer->sessions.front ()->GetSendQueueSize () <= PEER_ROUTER_INFO_OVERLOAD_QUEUE_SIZE &&
-					!peer->sessions.front ()->IsSlow () && !peer->sessions.front ()->IsBandwidthExceeded (peer->isHighBandwidth) &&
-					(!isHighBandwidth || peer->isHighBandwidth);
+				return !peer.router && !peer.sessions.empty () && peer.isReachable &&
+					peer.sessions.front ()->GetSendQueueSize () <= PEER_ROUTER_INFO_OVERLOAD_QUEUE_SIZE &&
+					!peer.sessions.front ()->IsSlow () && !peer.sessions.front ()->IsBandwidthExceeded (peer.isHighBandwidth) &&
+					(!isHighBandwidth || peer.isHighBandwidth);
 			});
 	}
 
@@ -1190,29 +1007,22 @@ namespace transport
 		}
 	}
 
-	void Transports::RestrictRoutesToRouters(const std::set<i2p::data::IdentHash>& routers)
+	void Transports::RestrictRoutesToRouters(std::set<i2p::data::IdentHash> routers)
 	{
-		std::lock_guard<std::mutex> lock(m_TrustedRoutersMutex);
+		std::unique_lock<std::mutex> lock(m_TrustedRoutersMutex);
 		m_TrustedRouters.clear();
 		for (const auto & ri : routers )
-			m_TrustedRouters.insert(ri);
+			m_TrustedRouters.push_back(ri);
 	}
 
-	bool Transports::RoutesRestricted() const 
-	{
-		{
-			std::lock_guard<std::mutex> routerslock(m_TrustedRoutersMutex);
-			if (!m_TrustedRouters.empty ()) return true;
-		}
-		{
-			std::lock_guard<std::mutex> famlock(m_FamilyMutex);
-			if (!m_TrustedFamilies.empty ()) return true;
-		}
-		return false;
+	bool Transports::RoutesRestricted() const {
+		std::unique_lock<std::mutex> famlock(m_FamilyMutex);
+		std::unique_lock<std::mutex> routerslock(m_TrustedRoutersMutex);
+		return m_TrustedFamilies.size() > 0 || m_TrustedRouters.size() > 0;
 	}
 
 	/** XXX: if routes are not restricted this dies */
-	std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRestrictedPeer()
+	std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRestrictedPeer() const
 	{
 		{
 			std::lock_guard<std::mutex> l(m_FamilyMutex);
@@ -1221,7 +1031,7 @@ namespace transport
 			if(sz > 1)
 			{
 				auto it = m_TrustedFamilies.begin ();
-				std::advance(it, m_Rng() % sz);
+				std::advance(it, rand() % sz);
 				fam = *it;
 			}
 			else if (sz == 1)
@@ -1232,38 +1042,29 @@ namespace transport
 				return i2p::data::netdb.GetRandomRouterInFamily(fam);
 		}
 		{
-			std::lock_guard<std::mutex> l(m_TrustedRoutersMutex);
+			std::unique_lock<std::mutex> l(m_TrustedRoutersMutex);
 			auto sz = m_TrustedRouters.size();
 			if (sz)
 			{
+				if(sz == 1)
+					return i2p::data::netdb.FindRouter(m_TrustedRouters[0]);
 				auto it = m_TrustedRouters.begin();
-				if(sz > 1)
-					std::advance(it, m_Rng() % sz);
+				std::advance(it, rand() % sz);
 				return i2p::data::netdb.FindRouter(*it);
 			}
 		}
 		return nullptr;
 	}
 
-	bool Transports::IsTrustedRouter (const i2p::data::IdentHash& ih) const
+	bool Transports::IsRestrictedPeer(const i2p::data::IdentHash & ih) const
 	{
-		if (m_TrustedRouters.empty ()) return false;
-		std::lock_guard<std::mutex> l(m_TrustedRoutersMutex);
-#if __cplusplus >= 202002L // C++20
-		if (m_TrustedRouters.contains (ih))
-#else
-		if (m_TrustedRouters.count (ih) > 0)
-#endif		
-			return true;
-		return false;
-	}	
-		
-	bool Transports::IsRestrictedPeer(const i2p::data::IdentHash& ih) const
-	{
-		if (IsTrustedRouter (ih)) return true;
-		
 		{
-			std::lock_guard<std::mutex> l(m_FamilyMutex);
+			std::unique_lock<std::mutex> l(m_TrustedRoutersMutex);
+			for (const auto & r : m_TrustedRouters )
+				if ( r == ih ) return true;
+		}
+		{
+			std::unique_lock<std::mutex> l(m_FamilyMutex);
 			auto ri = i2p::data::netdb.FindRouter(ih);
 			for (const auto & fam : m_TrustedFamilies)
 				if(ri->IsFamily(fam)) return true;
@@ -1283,11 +1084,6 @@ namespace transport
 		}
 	}
 
-	bool Transports::IsInReservedRange (const boost::asio::ip::address& host) const 
-	{
-		return IsCheckReserved () && i2p::util::net::IsInReservedRange (host);
-	}	
-		
 	void InitAddressFromIface ()
 	{
 		bool ipv6; i2p::config::GetOption("ipv6", ipv6);
@@ -1326,7 +1122,7 @@ namespace transport
 			std::string yggaddress; i2p::config::GetOption ("meshnets.yggaddress", yggaddress);
 			if (!yggaddress.empty ())
 			{
-				yggaddr = boost::asio::ip::make_address (yggaddress).to_v6 ();
+				yggaddr = boost::asio::ip::address_v6::from_string (yggaddress);
 				if (yggaddr.is_unspecified () || !i2p::util::net::IsYggdrasilAddress (yggaddr) ||
 					!i2p::util::net::IsLocalAddress (yggaddr))
 				{
@@ -1371,7 +1167,7 @@ namespace transport
 				if (ipv6)
 				{
 					std::string ipv6Addr; i2p::config::GetOption("ntcp2.addressv6", ipv6Addr);
-					auto addr = boost::asio::ip::make_address (ipv6Addr).to_v6 ();
+					auto addr = boost::asio::ip::address_v6::from_string (ipv6Addr);
 					if (!addr.is_unspecified () && addr != boost::asio::ip::address_v6::any ())
 						i2p::context.UpdateNTCP2V6Address (addr); // set ipv6 address if configured
 				}
@@ -1399,6 +1195,7 @@ namespace transport
 			else
 				i2p::context.PublishSSU2Address (ssu2port, false, ipv4, ipv6); // unpublish
 		}
+
 	}
 }
 }
diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h
index fcd2cfc6..cb63ca91 100644
--- a/libi2pd/Transports.h
+++ b/libi2pd/Transports.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -11,17 +11,14 @@
 
 #include <thread>
 #include <mutex>
-#include <future>
 #include <condition_variable>
 #include <functional>
 #include <unordered_map>
-#include <unordered_set>
 #include <vector>
 #include <queue>
 #include <string>
 #include <memory>
 #include <atomic>
-#include <random>
 #include <boost/asio.hpp>
 #include "TransportSession.h"
 #include "SSU2.h"
@@ -29,7 +26,6 @@
 #include "RouterInfo.h"
 #include "I2NPProtocol.h"
 #include "Identity.h"
-#include "util.h"
 
 namespace i2p
 {
@@ -56,11 +52,10 @@ namespace transport
 		private:
 
 			const int m_QueueSize;
-			i2p::util::MemoryPoolMt<Keys> m_KeysPool;
 			std::queue<std::shared_ptr<Keys> > m_Queue;
 
 			bool m_IsRunning;
-			std::unique_ptr<std::thread> m_Thread;
+			std::thread * m_Thread;
 			std::condition_variable m_Acquired;
 			std::mutex m_AcquiredMutex;
 	};
@@ -69,63 +64,51 @@ namespace transport
 	const int PEER_ROUTER_INFO_UPDATE_INTERVAL = 31*60; // in seconds
 	const int PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE = 7*60; // in seconds
 	const size_t PEER_ROUTER_INFO_OVERLOAD_QUEUE_SIZE = 25;
-	const int PEER_SELECTION_MIN_INTERVAL = 20; // in seconds
 	struct Peer
 	{
 		int numAttempts;
 		std::shared_ptr<const i2p::data::RouterInfo> router;
 		std::list<std::shared_ptr<TransportSession> > sessions;
-		uint64_t creationTime, nextRouterInfoUpdateTime, lastSelectionTime;
-		std::list<std::shared_ptr<i2p::I2NPMessage> > delayedMessages;
+		uint64_t creationTime, nextRouterInfoUpdateTime;
+		std::vector<std::shared_ptr<i2p::I2NPMessage> > delayedMessages;
 		std::vector<i2p::data::RouterInfo::SupportedTransports> priority;
-		bool isHighBandwidth, isEligible;
+		bool isHighBandwidth, isReachable;
 
 		Peer (std::shared_ptr<const i2p::data::RouterInfo> r, uint64_t ts):
 			numAttempts (0), router (r), creationTime (ts),
 			nextRouterInfoUpdateTime (ts + PEER_ROUTER_INFO_UPDATE_INTERVAL),
-			lastSelectionTime (0), isHighBandwidth (false), isEligible (false) 
+			isHighBandwidth (false), isReachable (false)
 		{
-			UpdateParams (router);
+			if (router)
+			{		
+				isHighBandwidth = router->IsHighBandwidth ();
+				isReachable = (bool)router->GetCompatibleTransports (true);
+			}	
 		}
-			
+
 		void Done ()
 		{
 			for (auto& it: sessions)
 				it->Done ();
-			// drop not sent delayed messages
-			for (auto& it: delayedMessages)
-				it->Drop ();
 		}
 
 		void SetRouter (std::shared_ptr<const i2p::data::RouterInfo> r)
 		{
 			router = r;
-			UpdateParams (router);
+			if (router)
+			{	
+				isHighBandwidth = router->IsHighBandwidth ();
+				isReachable = (bool)router->GetCompatibleTransports (true);
+			}	
 		}
-
-		bool IsConnected () const { return !sessions.empty (); }
-		void UpdateParams (std::shared_ptr<const i2p::data::RouterInfo> router);
 	};
 
 	const uint64_t SESSION_CREATION_TIMEOUT = 15; // in seconds
-	const int PEER_TEST_INTERVAL = 68*60; // in seconds
-	const int PEER_TEST_INTERVAL_VARIANCE = 3*60; // in seconds
+	const int PEER_TEST_INTERVAL = 71; // in minutes
 	const int PEER_TEST_DELAY_INTERVAL = 20; // in milliseconds
 	const int PEER_TEST_DELAY_INTERVAL_VARIANCE = 30; // in milliseconds
 	const int MAX_NUM_DELAYED_MESSAGES = 150;
 	const int CHECK_PROFILE_NUM_DELAYED_MESSAGES = 15; // check profile after
-	const int NUM_X25519_PRE_GENERATED_KEYS = 25; // pre-generated x25519 keys pairs
-	
-	const int TRAFFIC_SAMPLE_COUNT = 301; // seconds
-
-	struct TrafficSample
-	{
-		uint64_t Timestamp;
-		uint64_t TotalReceivedBytes;
-		uint64_t TotalSentBytes;
-		uint64_t TotalTransitTransmittedBytes;
-	};
-
 	class Transports
 	{
 		public:
@@ -135,7 +118,6 @@ namespace transport
 
 			void Start (bool enableNTCP2=true, bool enableSSU2=true);
 			void Stop ();
-			bool IsRunning () const { return m_IsRunning; }
 
 			bool IsBoundSSU2() const { return m_SSU2Server != nullptr; }
 			bool IsBoundNTCP2() const { return m_NTCP2Server != nullptr; }
@@ -143,12 +125,12 @@ namespace transport
 			bool IsOnline() const { return m_IsOnline; };
 			void SetOnline (bool online);
 
-			auto& GetService () { return *m_Service; };
+			boost::asio::io_service& GetService () { return *m_Service; };
 			std::shared_ptr<i2p::crypto::X25519Keys> GetNextX25519KeysPair ();
 			void ReuseX25519KeysPair (std::shared_ptr<i2p::crypto::X25519Keys> pair);
 
-			std::future<std::shared_ptr<TransportSession> > SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr<i2p::I2NPMessage> msg);
-			std::future<std::shared_ptr<TransportSession> > SendMessages (const i2p::data::IdentHash& ident, std::list<std::shared_ptr<i2p::I2NPMessage> >&& msgs);
+			void SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr<i2p::I2NPMessage> msg);
+			void SendMessages (const i2p::data::IdentHash& ident, const std::vector<std::shared_ptr<i2p::I2NPMessage> >& msgs);
 
 			void PeerConnected (std::shared_ptr<TransportSession> session);
 			void PeerDisconnected (std::shared_ptr<TransportSession> session);
@@ -166,40 +148,38 @@ namespace transport
 			uint32_t GetInBandwidth15s () const { return m_InBandwidth15s; };
 			uint32_t GetOutBandwidth15s () const { return m_OutBandwidth15s; };
 			uint32_t GetTransitBandwidth15s () const { return m_TransitBandwidth15s; };
-			int GetCongestionLevel (bool longTerm) const;
+			bool IsBandwidthExceeded () const;
+			bool IsTransitBandwidthExceeded () const;
 			size_t GetNumPeers () const { return m_Peers.size (); };
 			std::shared_ptr<const i2p::data::RouterInfo> GetRandomPeer (bool isHighBandwidth) const;
 
 			/** get a trusted first hop for restricted routes */
-			std::shared_ptr<const i2p::data::RouterInfo> GetRestrictedPeer();
+			std::shared_ptr<const i2p::data::RouterInfo> GetRestrictedPeer() const;
 			/** do we want to use restricted routes? */
 			bool RoutesRestricted() const;
 			/** restrict routes to use only these router families for first hops */
 			void RestrictRoutesToFamilies(const std::set<std::string>& families);
 			/** restrict routes to use only these routers for first hops */
-			void RestrictRoutesToRouters(const std::set<i2p::data::IdentHash>& routers);
+			void RestrictRoutesToRouters(std::set<i2p::data::IdentHash> routers);
 
-			bool IsTrustedRouter (const i2p::data::IdentHash& ih) const;
-			bool IsRestrictedPeer(const i2p::data::IdentHash& ih) const;
+			bool IsRestrictedPeer(const i2p::data::IdentHash & ident) const;
 
 			void PeerTest (bool ipv4 = true, bool ipv6 = true);
 
 			void SetCheckReserved (bool check) { m_CheckReserved = check; };
-			bool IsCheckReserved () const { return m_CheckReserved; };
-			bool IsInReservedRange (const boost::asio::ip::address& host) const;
+			bool IsCheckReserved () { return m_CheckReserved; };
 
 		private:
 
 			void Run ();
 			void RequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, const i2p::data::IdentHash& ident);
 			void HandleRequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, i2p::data::IdentHash ident);
-			std::shared_ptr<TransportSession> PostMessages (const i2p::data::IdentHash& ident, std::list<std::shared_ptr<i2p::I2NPMessage> >& msgs);
-			bool ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr<Peer> peer);
-			void SetPriority (std::shared_ptr<Peer> peer);
+			void PostMessages (i2p::data::IdentHash ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > msgs);
+			bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer);
+			void SetPriority (Peer& peer) const;
 			void HandlePeerCleanupTimer (const boost::system::error_code& ecode);
 			void HandlePeerTestTimer (const boost::system::error_code& ecode);
 			void HandleUpdateBandwidthTimer (const boost::system::error_code& ecode);
-			void UpdateBandwidthValues (int interval, uint32_t& in, uint32_t& out, uint32_t& transit);
 
 			void DetectExternalIP ();
 
@@ -211,39 +191,37 @@ namespace transport
 			volatile bool m_IsOnline;
 			bool m_IsRunning, m_IsNAT, m_CheckReserved;
 			std::thread * m_Thread;
-			boost::asio::io_context * m_Service;
-			boost::asio::executor_work_guard<boost::asio::io_context::executor_type> * m_Work;
+			boost::asio::io_service * m_Service;
+			boost::asio::io_service::work * m_Work;
 			boost::asio::deadline_timer * m_PeerCleanupTimer, * m_PeerTestTimer, * m_UpdateBandwidthTimer;
 
 			SSU2Server * m_SSU2Server;
 			NTCP2Server * m_NTCP2Server;
 			mutable std::mutex m_PeersMutex;
-			std::unordered_map<i2p::data::IdentHash, std::shared_ptr<Peer> > m_Peers;
+			std::unordered_map<i2p::data::IdentHash, Peer> m_Peers;
 
 			X25519KeysPairSupplier m_X25519KeysPairSupplier;
 
 			std::atomic<uint64_t> m_TotalSentBytes, m_TotalReceivedBytes, m_TotalTransitTransmittedBytes;
 
-			TrafficSample m_TrafficSamples[TRAFFIC_SAMPLE_COUNT];
-			int m_TrafficSamplePtr;
-
 			// Bandwidth per second
 			uint32_t m_InBandwidth, m_OutBandwidth, m_TransitBandwidth;
-			// Bandwidth during last 15 seconds
+			uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes, m_LastTransitBandwidthUpdateBytes;
+
+			// Bandwidth every 15 seconds
 			uint32_t m_InBandwidth15s, m_OutBandwidth15s, m_TransitBandwidth15s;
-			// Bandwidth during last 5 minutes
-			uint32_t m_InBandwidth5m, m_OutBandwidth5m, m_TransitBandwidth5m;
+			uint64_t m_LastInBandwidth15sUpdateBytes, m_LastOutBandwidth15sUpdateBytes, m_LastTransitBandwidth15sUpdateBytes;
+			uint64_t m_LastBandwidth15sUpdateTime;
 
 			/** which router families to trust for first hops */
 			std::vector<i2p::data::FamilyID> m_TrustedFamilies;
 			mutable std::mutex m_FamilyMutex;
 
 			/** which routers for first hop to trust */
-			std::unordered_set<i2p::data::IdentHash> m_TrustedRouters;
+			std::vector<i2p::data::IdentHash> m_TrustedRouters;
 			mutable std::mutex m_TrustedRoutersMutex;
 
 			i2p::I2NPMessagesHandler m_LoopbackHandler;
-			std::mt19937 m_Rng;
 
 		public:
 
diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp
index 1b317121..6234ceb4 100644
--- a/libi2pd/Tunnel.cpp
+++ b/libi2pd/Tunnel.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -33,7 +33,7 @@ namespace tunnel
 		TunnelBase (config->GetTunnelID (), config->GetNextTunnelID (), config->GetNextIdentHash ()),
 		m_Config (config), m_IsShortBuildMessage (false), m_Pool (nullptr),
 		m_State (eTunnelStatePending), m_FarEndTransports (i2p::data::RouterInfo::eAllTransports),
-		m_IsRecreated (false), m_Latency (UNKNOWN_LATENCY)
+		m_IsRecreated (false), m_Latency (0)
 	{
 	}
 
@@ -52,7 +52,7 @@ namespace tunnel
 		// shuffle records
 		std::vector<int> recordIndicies;
 		for (int i = 0; i < numRecords; i++) recordIndicies.push_back(i);
-		std::shuffle (recordIndicies.begin(), recordIndicies.end(), m_Pool ? m_Pool->GetRng () : std::mt19937(std::random_device()()));
+		std::shuffle (recordIndicies.begin(), recordIndicies.end(), std::mt19937(std::random_device()()));
 
 		// create real records
 		uint8_t * records = msg->GetPayload () + 1;
@@ -90,20 +90,14 @@ namespace tunnel
 			hop = hop->prev;
 		}
 		msg->FillI2NPMessageHeader (m_Config->IsShort () ? eI2NPShortTunnelBuild : eI2NPVariableTunnelBuild);
-		auto s = shared_from_this ();
-		msg->onDrop = [s]()
-			{
-				LogPrint (eLogInfo, "I2NP: Tunnel ", s->GetTunnelID (), " request was not sent");
-				s->SetState (i2p::tunnel::eTunnelStateBuildFailed);		
-			};
-		
+
 		// send message
 		if (outboundTunnel)
 		{
 			if (m_Config->IsShort ())
 			{
 				auto ident = m_Config->GetFirstHop () ? m_Config->GetFirstHop ()->ident : nullptr;
-				if (ident && ident->GetIdentHash () != outboundTunnel->GetEndpointIdentHash ()) // don't encrypt if IBGW = OBEP
+				if (ident && ident->GetIdentHash () != outboundTunnel->GetNextIdentHash ()) // don't encrypt if IBGW = OBEP
 				{
 					auto msg1 = i2p::garlic::WrapECIESX25519MessageForRouter (msg, ident->GetEncryptionPublicKey ());
 					if (msg1) msg = msg1;
@@ -122,7 +116,7 @@ namespace tunnel
 				if (m_Pool && m_Pool->GetLocalDestination ())
 					m_Pool->GetLocalDestination ()->SubmitECIESx25519Key (key, tag);
 				else
-					i2p::context.SubmitECIESx25519Key (key, tag);
+					i2p::context.AddECIESx25519Key (key, tag);
 			}
 			i2p::transport::transports.SendMessage (GetNextIdentHash (), msg);
 		}
@@ -130,19 +124,8 @@ namespace tunnel
 
 	bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len)
 	{
-		int num = msg[0];
-		LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", num, " records.");
-		if (num > MAX_NUM_RECORDS)
-		{
-			LogPrint (eLogError, "Tunnel: Too many records in TunnelBuildResponse", num);
-			return false;
-		}		
-		if (len < num*m_Config->GetRecordSize () + 1)
-		{
-			LogPrint (eLogError, "Tunnel: TunnelBuildResponse of ", num, " records is too short ", len);
-			return false;
-		}
-		
+		LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records.");
+
 		TunnelHopConfig * hop = m_Config->GetLastHop ();
 		while (hop)
 		{
@@ -163,7 +146,7 @@ namespace tunnel
 			while (hop1)
 			{
 				auto idx = hop1->recordIndex;
-				if (idx >= 0 && idx < num)
+				if (idx >= 0 && idx < msg[0])
 					hop->DecryptRecord (msg + 1, idx);
 				else
 					LogPrint (eLogWarning, "Tunnel: Hop index ", idx, " is out of range");
@@ -179,12 +162,9 @@ namespace tunnel
 		{
 			uint8_t ret = hop->GetRetCode (msg + 1);
 			LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret);
-			if (hop->ident)
-				i2p::data::UpdateRouterProfile (hop->ident->GetIdentHash (),
-					[ret](std::shared_ptr<i2p::data::RouterProfile> profile)
-					{
-						if (profile) profile->TunnelBuildResponse (ret);
-					});
+			auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ());
+			if (profile)
+				profile->TunnelBuildResponse (ret);
 			if (ret)
 				// if any of participants declined the tunnel is not established
 				established = false;
@@ -212,10 +192,10 @@ namespace tunnel
 		return established;
 	}
 
-	bool Tunnel::LatencyFitsRange(int lowerbound, int upperbound) const
+	bool Tunnel::LatencyFitsRange(uint64_t lower, uint64_t upper) const
 	{
 		auto latency = GetMeanLatency();
-		return latency >= lowerbound && latency <= upperbound;
+		return latency >= lower && latency <= upper;
 	}
 
 	void Tunnel::EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out)
@@ -264,38 +244,12 @@ namespace tunnel
 
 	void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr<I2NPMessage>&& msg)
 	{
-		if (!IsEstablished () && GetState () != eTunnelStateExpiring) 
-		{	
-			// incoming messages means a tunnel is alive
-			SetState (eTunnelStateEstablished); 
-			auto pool = GetTunnelPool ();
-			if (pool)
-			{
-				// update LeaseSet
-				auto dest = pool->GetLocalDestination ();
-				if (dest) dest->SetLeaseSetUpdated (true);
-			}	
-		}	
+		if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive
 		EncryptTunnelMsg (msg, msg);
-		msg->from = GetSharedFromThis ();
+		msg->from = shared_from_this ();
 		m_Endpoint.HandleDecryptedTunnelDataMsg (msg);
 	}
 
-	bool InboundTunnel::Recreate ()
-	{
-		if (!IsRecreated ())
-		{
-			auto pool = GetTunnelPool ();
-			if (pool)
-			{
-				SetRecreated (true);
-				pool->RecreateInboundTunnel (std::static_pointer_cast<InboundTunnel>(shared_from_this ()));
-				return true;
-			}	
-		}
-		return false;
-	}	
-		
 	ZeroHopsInboundTunnel::ZeroHopsInboundTunnel ():
 		InboundTunnel (std::make_shared<ZeroHopsTunnelConfig> ()),
 		m_NumReceivedBytes (0)
@@ -307,7 +261,7 @@ namespace tunnel
 		if (msg)
 		{
 			m_NumReceivedBytes += msg->GetLength ();
-			msg->from = GetSharedFromThis ();
+			msg->from = shared_from_this ();
 			HandleI2NPMessage (msg);
 		}
 	}
@@ -315,28 +269,22 @@ namespace tunnel
 	void OutboundTunnel::SendTunnelDataMsgTo (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr<i2p::I2NPMessage> msg)
 	{
 		TunnelMessageBlock block;
-		block.tunnelID = 0; // Initialize tunnelID to a default value
-	
 		if (gwHash)
 		{
 			block.hash = gwHash;
 			if (gwTunnel)
 			{
 				block.deliveryType = eDeliveryTypeTunnel;
-				block.tunnelID = gwTunnel; // Set tunnelID only if gwTunnel is non-zero
+				block.tunnelID = gwTunnel;
 			}
 			else
-			{
 				block.deliveryType = eDeliveryTypeRouter;
-			}
 		}
 		else
-		{
 			block.deliveryType = eDeliveryTypeLocal;
-		}
-	
 		block.data = msg;
-		SendTunnelDataMsgs({block});
+
+		SendTunnelDataMsgs ({block});
 	}
 
 	void OutboundTunnel::SendTunnelDataMsgs (const std::vector<TunnelMessageBlock>& msgs)
@@ -352,21 +300,6 @@ namespace tunnel
 		LogPrint (eLogError, "Tunnel: Incoming message for outbound tunnel ", GetTunnelID ());
 	}
 
-	bool OutboundTunnel::Recreate ()
-	{
-		if (!IsRecreated ())
-		{
-			auto pool = GetTunnelPool ();
-			if (pool)
-			{
-				SetRecreated (true);
-				pool->RecreateOutboundTunnel (std::static_pointer_cast<OutboundTunnel>(shared_from_this ()));
-				return true;
-			}	
-		}
-		return false;
-	}
-		
 	ZeroHopsOutboundTunnel::ZeroHopsOutboundTunnel ():
 		OutboundTunnel (std::make_shared<ZeroHopsTunnelConfig> ()),
 		m_NumSentBytes (0)
@@ -400,8 +333,7 @@ namespace tunnel
 
 	Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr), m_MaxNumTransitTunnels (DEFAULT_MAX_NUM_TRANSIT_TUNNELS),
 		m_TotalNumSuccesiveTunnelCreations (0), m_TotalNumFailedTunnelCreations (0), // for normal average
-		m_TunnelCreationSuccessRate (TCSR_START_VALUE), m_TunnelCreationAttemptsNum(0),
-		m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL)
+		m_TunnelCreationSuccessRate (TCSR_START_VALUE), m_TunnelCreationAttemptsNum(0)
 	{
 	}
 
@@ -412,26 +344,12 @@ namespace tunnel
 
 	std::shared_ptr<TunnelBase> Tunnels::GetTunnel (uint32_t tunnelID)
 	{
-		std::lock_guard<std::mutex> l(m_TunnelsMutex);
 		auto it = m_Tunnels.find(tunnelID);
 		if (it != m_Tunnels.end ())
 			return it->second;
 		return nullptr;
 	}
 
-	bool Tunnels::AddTunnel (std::shared_ptr<TunnelBase> tunnel)
-	{
-		if (!tunnel) return false;
-		std::lock_guard<std::mutex> l(m_TunnelsMutex);
-		return m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second;
-	}
-		
-	void Tunnels::RemoveTunnel (uint32_t tunnelID)
-	{
-		std::lock_guard<std::mutex> l(m_TunnelsMutex);
-		m_Tunnels.erase (tunnelID);
-	}	
-		
 	std::shared_ptr<InboundTunnel> Tunnels::GetPendingInboundTunnel (uint32_t replyMsgID)
 	{
 		return GetPendingTunnel (replyMsgID, m_PendingInboundTunnels);
@@ -473,7 +391,7 @@ namespace tunnel
 	std::shared_ptr<OutboundTunnel> Tunnels::GetNextOutboundTunnel ()
 	{
 		if (m_OutboundTunnels.empty ()) return nullptr;
-		uint32_t ind = m_Rng () % m_OutboundTunnels.size (), i = 0;
+		uint32_t ind = rand () % m_OutboundTunnels.size (), i = 0;
 		std::shared_ptr<OutboundTunnel> tunnel;
 		for (const auto& it: m_OutboundTunnels)
 		{
@@ -487,12 +405,10 @@ namespace tunnel
 		return tunnel;
 	}
 
-	std::shared_ptr<TunnelPool> Tunnels::CreateTunnelPool (int numInboundHops, 
-	    int numOutboundHops, int numInboundTunnels, int numOutboundTunnels, 
-	    int inboundVariance, int outboundVariance, bool isHighBandwidth)
+	std::shared_ptr<TunnelPool> Tunnels::CreateTunnelPool (int numInboundHops, int numOutboundHops,
+		int numInboundTunnels, int numOutboundTunnels, int inboundVariance, int outboundVariance)
 	{
-		auto pool = std::make_shared<TunnelPool> (numInboundHops, numOutboundHops, 
-			numInboundTunnels, numOutboundTunnels, inboundVariance, outboundVariance, isHighBandwidth);
+		auto pool = std::make_shared<TunnelPool> (numInboundHops, numOutboundHops, numInboundTunnels, numOutboundTunnels, inboundVariance, outboundVariance);
 		std::unique_lock<std::mutex> l(m_PoolsMutex);
 		m_Pools.push_back (pool);
 		return pool;
@@ -519,16 +435,26 @@ namespace tunnel
 		}
 	}
 
+	bool Tunnels::AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel)
+	{
+		if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second)
+			m_TransitTunnels.push_back (tunnel);
+		else
+		{
+			LogPrint (eLogError, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " already exists");
+			return false;
+		}
+		return true;
+	}
+
 	void Tunnels::Start ()
 	{
 		m_IsRunning = true;
 		m_Thread = new std::thread (std::bind (&Tunnels::Run, this));
-		m_TransitTunnels.Start ();
 	}
 
 	void Tunnels::Stop ()
 	{
-		m_TransitTunnels.Stop ();
 		m_IsRunning = false;
 		m_Queue.WakeUp ();
 		if (m_Thread)
@@ -545,21 +471,18 @@ namespace tunnel
 		std::this_thread::sleep_for (std::chrono::seconds(1)); // wait for other parts are ready
 
 		uint64_t lastTs = 0, lastPoolsTs = 0, lastMemoryPoolTs = 0;
-		std::list<std::shared_ptr<I2NPMessage> > msgs;
 		while (m_IsRunning)
 		{
 			try
 			{
-				if (m_Queue.Wait (1,0)) // 1 sec
+				auto msg = m_Queue.GetNextWithTimeout (1000); // 1 sec
+				if (msg)
 				{
-					m_Queue.GetWholeQueue (msgs);
 					int numMsgs = 0;
 					uint32_t prevTunnelID = 0, tunnelID = 0;
 					std::shared_ptr<TunnelBase> prevTunnel;
-					while (!msgs.empty ())
+					do
 					{
-						auto msg = msgs.front (); msgs.pop_front ();
-						if (!msg) continue;
 						std::shared_ptr<TunnelBase> tunnel;
 						uint8_t typeID = msg->GetTypeID ();
 						switch (typeID)
@@ -587,38 +510,29 @@ namespace tunnel
 
 								break;
 							}
-							case eI2NPShortTunnelBuild:
-								HandleShortTunnelBuildMsg (msg);
-							break;	
 							case eI2NPVariableTunnelBuild:
-								HandleVariableTunnelBuildMsg (msg);
-							break;	
-							case eI2NPShortTunnelBuildReply:
-								HandleTunnelBuildReplyMsg (msg, true);
-							break;
 							case eI2NPVariableTunnelBuildReply:
-								HandleTunnelBuildReplyMsg (msg, false);
-							break;	
+							case eI2NPShortTunnelBuild:
+							case eI2NPShortTunnelBuildReply:
 							case eI2NPTunnelBuild:
 							case eI2NPTunnelBuildReply:
-								LogPrint (eLogWarning, "Tunnel: TunnelBuild is too old for ECIES router");
-							break;	
+								HandleTunnelBuildI2NPMessage (msg);
+							break;
 							default:
 								LogPrint (eLogWarning, "Tunnel: Unexpected message type ", (int) typeID);
 						}
 
-						prevTunnelID = tunnelID;
-						prevTunnel = tunnel;
-						numMsgs++;	
-						
-						if (msgs.empty ())
-						{	
-							if (numMsgs < MAX_TUNNEL_MSGS_BATCH_SIZE && !m_Queue.IsEmpty ())
-								m_Queue.GetWholeQueue (msgs); // try more
-							else if (tunnel)
-								tunnel->FlushTunnelDataMsgs (); // otherwise flush last
-						}	
+						msg = (numMsgs <= MAX_TUNNEL_MSGS_BATCH_SIZE) ? m_Queue.Get () : nullptr;
+						if (msg)
+						{
+							prevTunnelID = tunnelID;
+							prevTunnel = tunnel;
+							numMsgs++;
+						}
+						else if (tunnel)
+							tunnel->FlushTunnelDataMsgs ();
 					}
+					while (msg);
 				}
 
 				if (i2p::transport::transports.IsOnline())
@@ -672,95 +586,26 @@ namespace tunnel
 		auto typeID = msg->GetTypeID ();
 		LogPrint (eLogDebug, "Tunnel: Gateway of ", (int) len, " bytes for tunnel ", tunnel->GetTunnelID (), ", msg type ", (int)typeID);
 
+		if (typeID == eI2NPDatabaseSearchReply)
+			// DatabaseSearchReply with new routers
+			i2p::data::netdb.PostI2NPMsg (CopyI2NPMessage (msg));
+		else if (IsRouterInfoMsg (msg))
+		{
+			// transit DatabaseStore might contain new/updated RI
+			auto m = CopyI2NPMessage (msg);
+			if (bufbe32toh (m->GetPayload () + DATABASE_STORE_REPLY_TOKEN_OFFSET))
+				memset (m->GetPayload () + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0xFF, 4); // fake replyToken meaning no reply
+			i2p::data::netdb.PostI2NPMsg (m);
+		}
 		tunnel->SendTunnelDataMsg (msg);
 	}
 
-	void Tunnels::HandleShortTunnelBuildMsg (std::shared_ptr<I2NPMessage> msg)
-	{
-		if (!msg) return;
-		auto tunnel = GetPendingInboundTunnel (msg->GetMsgID()); // replyMsgID
-		if (tunnel)
-		{
-			// endpoint of inbound tunnel
-			LogPrint (eLogDebug, "Tunnel: ShortTunnelBuild reply for tunnel ", tunnel->GetTunnelID ());
-			if (tunnel->HandleTunnelBuildResponse (msg->GetPayload(), msg->GetPayloadLength()))
-			{
-				LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been created");
-				tunnel->SetState (eTunnelStateEstablished);
-				AddInboundTunnel (tunnel);
-			}
-			else
-			{
-				LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined");
-				tunnel->SetState (eTunnelStateBuildFailed);
-			}
-			return;
-		}
-		else
-			m_TransitTunnels.PostTransitTunnelBuildMsg (std::move (msg));
-	}	
-
-	void Tunnels::HandleVariableTunnelBuildMsg (std::shared_ptr<I2NPMessage> msg)
-	{
-		auto tunnel = GetPendingInboundTunnel (msg->GetMsgID()); // replyMsgID
-		if (tunnel)
-		{
-			// endpoint of inbound tunnel
-			LogPrint (eLogDebug, "Tunnel: VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ());
-			if (tunnel->HandleTunnelBuildResponse (msg->GetPayload(), msg->GetPayloadLength()))
-			{
-				LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been created");
-				tunnel->SetState (eTunnelStateEstablished);
-				AddInboundTunnel (tunnel);
-			}
-			else
-			{
-				LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined");
-				tunnel->SetState (eTunnelStateBuildFailed);
-			}
-		}
-		else
-			m_TransitTunnels.PostTransitTunnelBuildMsg (std::move (msg));
-	}	
-
-	void Tunnels::HandleTunnelBuildReplyMsg (std::shared_ptr<I2NPMessage> msg, bool isShort)
-	{
-		auto tunnel = GetPendingOutboundTunnel (msg->GetMsgID()); // replyMsgID
-		if (tunnel)
-		{
-			// reply for outbound tunnel
-			LogPrint (eLogDebug, "Tunnel: TunnelBuildReply for tunnel ", tunnel->GetTunnelID ());
-			if (tunnel->HandleTunnelBuildResponse (msg->GetPayload(), msg->GetPayloadLength()))
-			{
-				LogPrint (eLogInfo, "Tunnel: Outbound tunnel ", tunnel->GetTunnelID (), " has been created");
-				tunnel->SetState (eTunnelStateEstablished);
-				AddOutboundTunnel (tunnel);
-			}
-			else
-			{
-				LogPrint (eLogInfo, "Tunnel: Outbound tunnel ", tunnel->GetTunnelID (), " has been declined");
-				tunnel->SetState (eTunnelStateBuildFailed);
-			}
-		}
-		else
-			LogPrint (eLogWarning, "Tunnel: Pending tunnel for message ", msg->GetMsgID(), " not found");
-
-	}	
-		
 	void Tunnels::ManageTunnels (uint64_t ts)
 	{
 		ManagePendingTunnels (ts);
-		std::vector<std::shared_ptr<Tunnel> > tunnelsToRecreate;
-		ManageInboundTunnels (ts, tunnelsToRecreate);
-		ManageOutboundTunnels (ts, tunnelsToRecreate);
-		// rec-create in random order
-		if (!tunnelsToRecreate.empty ())
-		{	
-			if (tunnelsToRecreate.size () > 1)
-				std::shuffle (tunnelsToRecreate.begin(), tunnelsToRecreate.end(), m_Rng);
-			for (auto& it: tunnelsToRecreate)
-				it->Recreate ();
-		}	
+		ManageInboundTunnels (ts);
+		ManageOutboundTunnels (ts);
+		ManageTransitTunnels (ts);
 	}
 
 	void Tunnels::ManagePendingTunnels (uint64_t ts)
@@ -791,11 +636,11 @@ namespace tunnel
 							while (hop)
 							{
 								if (hop->ident)
-									i2p::data::UpdateRouterProfile (hop->ident->GetIdentHash (),
-										[](std::shared_ptr<i2p::data::RouterProfile> profile)
-				    					{
-											if (profile) profile->TunnelNonReplied ();
-										});
+								{
+									auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ());
+									if (profile)
+										profile->TunnelNonReplied ();
+								}
 								hop = hop->next;
 							}
 						}
@@ -823,15 +668,14 @@ namespace tunnel
 		}
 	}
 
-	void Tunnels::ManageOutboundTunnels (uint64_t ts, std::vector<std::shared_ptr<Tunnel> >& toRecreate)
+	void Tunnels::ManageOutboundTunnels (uint64_t ts)
 	{
 		for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();)
 		{
 			auto tunnel = *it;
-			if (tunnel->IsFailed () || ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT ||
-			    ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ())
+			if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
 			{
-				LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired or failed");
+				LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired");
 				auto pool = tunnel->GetTunnelPool ();
 				if (pool)
 					pool->TunnelExpired (tunnel);
@@ -847,7 +691,10 @@ namespace tunnel
 						auto pool = tunnel->GetTunnelPool ();
 						// let it die if the tunnel pool has been reconfigured and this is old
 						if (pool && tunnel->GetNumHops() == pool->GetNumOutboundHops())
-							toRecreate.push_back (tunnel);
+						{
+							tunnel->SetRecreated (true);
+							pool->RecreateOutboundTunnel (tunnel);
+						}
 					}
 					if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
 						tunnel->SetState (eTunnelStateExpiring);
@@ -862,7 +709,7 @@ namespace tunnel
 			auto inboundTunnel = GetNextInboundTunnel ();
 			auto router = i2p::transport::transports.RoutesRestricted() ?
 				i2p::transport::transports.GetRestrictedPeer() :
-				i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true, false); // reachable by us
+				i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true); // reachable by us
 			if (!inboundTunnel || !router) return;
 			LogPrint (eLogDebug, "Tunnel: Creating one hop outbound tunnel");
 			CreateTunnel<OutboundTunnel> (
@@ -872,19 +719,19 @@ namespace tunnel
 		}
 	}
 
-	void Tunnels::ManageInboundTunnels (uint64_t ts, std::vector<std::shared_ptr<Tunnel> >& toRecreate)
+	void Tunnels::ManageInboundTunnels (uint64_t ts)
 	{
 		for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();)
 		{
 			auto tunnel = *it;
-			if (tunnel->IsFailed () || ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT ||
+			if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT ||
 			    ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ())
 			{
-				LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired or failed");
+				LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired");
 				auto pool = tunnel->GetTunnelPool ();
 				if (pool)
 					pool->TunnelExpired (tunnel);
-				RemoveTunnel (tunnel->GetTunnelID ());
+				m_Tunnels.erase (tunnel->GetTunnelID ());
 				it = m_InboundTunnels.erase (it);
 			}
 			else
@@ -896,7 +743,10 @@ namespace tunnel
 						auto pool = tunnel->GetTunnelPool ();
 						// let it die if the tunnel pool was reconfigured and has different number of hops
 						if (pool && tunnel->GetNumHops() == pool->GetNumInboundHops())
-							toRecreate.push_back (tunnel);
+						{
+							tunnel->SetRecreated (true);
+							pool->RecreateInboundTunnel (tunnel);
+						}
 					}
 
 					if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
@@ -919,7 +769,7 @@ namespace tunnel
 				int obLen; i2p::config::GetOption("exploratory.outbound.length", obLen);
 				int ibNum; i2p::config::GetOption("exploratory.inbound.quantity", ibNum);
 				int obNum; i2p::config::GetOption("exploratory.outbound.quantity", obNum);
-				m_ExploratoryPool = CreateTunnelPool (ibLen, obLen, ibNum, obNum, 0, 0, false);
+				m_ExploratoryPool = CreateTunnelPool (ibLen, obLen, ibNum, obNum, 0, 0);
 				m_ExploratoryPool->SetLocalDestination (i2p::context.GetSharedDestination ());
 			}
 			return;
@@ -931,7 +781,7 @@ namespace tunnel
 			auto router = i2p::transport::transports.RoutesRestricted() ?
 				i2p::transport::transports.GetRestrictedPeer() :
 				// should be reachable by us because we send build request directly
-				i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true, false);
+				i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true);
 			if (!router) {
 				LogPrint (eLogWarning, "Tunnel: Can't find any router, skip creating tunnel");
 				return;
@@ -943,6 +793,26 @@ namespace tunnel
 		}
 	}
 
+	void Tunnels::ManageTransitTunnels (uint64_t ts)
+	{
+		for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();)
+		{
+			auto tunnel = *it;
+			if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT ||
+			    ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ())
+			{
+				LogPrint (eLogDebug, "Tunnel: Transit tunnel with id ", tunnel->GetTunnelID (), " expired");
+				m_Tunnels.erase (tunnel->GetTunnelID ());
+				it = m_TransitTunnels.erase (it);
+			}
+			else
+			{
+				tunnel->Cleanup ();
+				it++;
+			}
+		}
+	}
+
 	void Tunnels::ManageTunnelPools (uint64_t ts)
 	{
 		std::unique_lock<std::mutex> l(m_PoolsMutex);
@@ -958,7 +828,7 @@ namespace tunnel
 		if (msg) m_Queue.Put (msg);
 	}
 
-	void Tunnels::PostTunnelData (std::list<std::shared_ptr<I2NPMessage> >& msgs)
+	void Tunnels::PostTunnelData (const std::vector<std::shared_ptr<I2NPMessage> >& msgs)
 	{
 		m_Queue.Put (msgs);
 	}
@@ -1016,7 +886,7 @@ namespace tunnel
 
 	void Tunnels::AddInboundTunnel (std::shared_ptr<InboundTunnel> newTunnel)
 	{
-		if (AddTunnel (newTunnel))
+		if (m_Tunnels.emplace (newTunnel->GetTunnelID (), newTunnel).second)
 		{
 			m_InboundTunnels.push_back (newTunnel);
 			auto pool = newTunnel->GetTunnelPool ();
@@ -1046,7 +916,7 @@ namespace tunnel
 		inboundTunnel->SetTunnelPool (pool);
 		inboundTunnel->SetState (eTunnelStateEstablished);
 		m_InboundTunnels.push_back (inboundTunnel);
-		AddTunnel (inboundTunnel);
+		m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel;
 		return inboundTunnel;
 	}
 
@@ -1080,12 +950,21 @@ namespace tunnel
 
 	int Tunnels::GetTransitTunnelsExpirationTimeout ()
 	{
-		return m_TransitTunnels.GetTransitTunnelsExpirationTimeout ();
+		int timeout = 0;
+		uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
+		// TODO: possible race condition with I2PControl
+		for (const auto& it : m_TransitTunnels)
+		{
+			int t = it->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts;
+			if (t > timeout) timeout = t;
+		}
+		return timeout;
 	}
 
 	size_t Tunnels::CountTransitTunnels() const
 	{
-		return m_TransitTunnels.GetNumTransitTunnels ();
+		// TODO: locking
+		return m_TransitTunnels.size();
 	}
 
 	size_t Tunnels::CountInboundTunnels() const
@@ -1100,7 +979,7 @@ namespace tunnel
 		return m_OutboundTunnels.size();
 	}
 
-	void Tunnels::SetMaxNumTransitTunnels (uint32_t maxNumTransitTunnels)
+	void Tunnels::SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels)
 	{
 		if (maxNumTransitTunnels > 0 && m_MaxNumTransitTunnels != maxNumTransitTunnels)
 		{
diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h
index 5d21cd8b..e6e3c3a5 100644
--- a/libi2pd/Tunnel.h
+++ b/libi2pd/Tunnel.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -18,7 +18,6 @@
 #include <thread>
 #include <mutex>
 #include <memory>
-#include <random>
 #include "util.h"
 #include "Queue.h"
 #include "Crypto.h"
@@ -40,8 +39,7 @@ namespace tunnel
 	const int TUNNEL_CREATION_TIMEOUT = 30; // 30 seconds
 	const int STANDARD_NUM_RECORDS = 4; // in VariableTunnelBuild message
 	const int MAX_NUM_RECORDS = 8;
-	const int UNKNOWN_LATENCY = -1;
-	const int HIGH_LATENCY_PER_HOP = 250000; // in microseconds
+	const int HIGH_LATENCY_PER_HOP = 250; // in milliseconds
 	const int MAX_TUNNEL_MSGS_BATCH_SIZE = 100; // handle messages without interrupt
 	const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 5000;
 	const int TUNNEL_MANAGE_INTERVAL = 15; // in seconds
@@ -67,8 +65,7 @@ namespace tunnel
 
 	class OutboundTunnel;
 	class InboundTunnel;
-	class Tunnel: public TunnelBase,
-		 public std::enable_shared_from_this<Tunnel>
+	class Tunnel: public TunnelBase
 	{
 		struct TunnelHop
 		{
@@ -93,13 +90,12 @@ namespace tunnel
 			i2p::data::RouterInfo::CompatibleTransports GetFarEndTransports () const { return m_FarEndTransports; };
 			TunnelState GetState () const { return m_State; };
 			void SetState (TunnelState state);
-			bool IsEstablished () const { return m_State == eTunnelStateEstablished || m_State == eTunnelStateTestFailed; };
+			bool IsEstablished () const { return m_State == eTunnelStateEstablished; };
 			bool IsFailed () const { return m_State == eTunnelStateFailed; };
 			bool IsRecreated () const { return m_IsRecreated; };
 			void SetRecreated (bool recreated) { m_IsRecreated = recreated; };
 			int GetNumHops () const { return m_Hops.size (); };
 			virtual bool IsInbound() const = 0;
-			virtual bool Recreate () = 0;
 
 			std::shared_ptr<TunnelPool> GetTunnelPool () const { return m_Pool; };
 			void SetTunnelPool (std::shared_ptr<TunnelPool> pool) { m_Pool = pool; };
@@ -111,14 +107,14 @@ namespace tunnel
 			void EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out) override;
 
 			/** @brief add latency sample */
-			void AddLatencySample(const int us) { m_Latency = LatencyIsKnown() ? (m_Latency + us) >> 1 : us; }
+			void AddLatencySample(const uint64_t ms) { m_Latency = (m_Latency + ms) >> 1; }
 			/** @brief get this tunnel's estimated latency */
-			int GetMeanLatency() const { return (m_Latency + 500) / 1000; }
+			uint64_t GetMeanLatency() const { return m_Latency; }
 			/** @brief return true if this tunnel's latency fits in range [lowerbound, upperbound] */
-			bool LatencyFitsRange(int lowerbound, int upperbound) const;
+			bool LatencyFitsRange(uint64_t lowerbound, uint64_t upperbound) const;
 
-			bool LatencyIsKnown() const { return m_Latency != UNKNOWN_LATENCY; }
-			bool IsSlow () const { return LatencyIsKnown() && m_Latency > HIGH_LATENCY_PER_HOP*GetNumHops (); }
+			bool LatencyIsKnown() const { return m_Latency > 0; }
+			bool IsSlow () const { return LatencyIsKnown() && (int)m_Latency > HIGH_LATENCY_PER_HOP*GetNumHops (); }
 
 			/** visit all hops we currently store */
 			void VisitTunnelHops(TunnelHopVisitor v);
@@ -132,7 +128,7 @@ namespace tunnel
 			TunnelState m_State;
 			i2p::data::RouterInfo::CompatibleTransports m_FarEndTransports;
 			bool m_IsRecreated; // if tunnel is replaced by new, or new tunnel requested to replace
-			int m_Latency; // in microseconds
+			uint64_t m_Latency; // in milliseconds
 	};
 
 	class OutboundTunnel: public Tunnel
@@ -140,7 +136,7 @@ namespace tunnel
 		public:
 
 			OutboundTunnel (std::shared_ptr<const TunnelConfig> config):
-				Tunnel (config), m_Gateway (*this), m_EndpointIdentHash (config->GetLastIdentHash ()) {};
+				Tunnel (config), m_Gateway (this), m_EndpointIdentHash (config->GetLastIdentHash ()) {};
 
 			void SendTunnelDataMsgTo (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr<i2p::I2NPMessage> msg);
 			virtual void SendTunnelDataMsgs (const std::vector<TunnelMessageBlock>& msgs); // multiple messages
@@ -151,7 +147,6 @@ namespace tunnel
 			void HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg) override;
 
 			bool IsInbound() const override { return false; }
-			bool Recreate () override;
 
 		private:
 
@@ -160,7 +155,7 @@ namespace tunnel
 			i2p::data::IdentHash m_EndpointIdentHash;
 	};
 
-	class InboundTunnel: public Tunnel
+	class InboundTunnel: public Tunnel, public std::enable_shared_from_this<InboundTunnel>
 	{
 		public:
 
@@ -168,18 +163,10 @@ namespace tunnel
 			void HandleTunnelDataMsg (std::shared_ptr<I2NPMessage>&& msg) override;
 			virtual size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); };
 			bool IsInbound() const override { return true; }
-			bool Recreate () override;
 
 			// override TunnelBase
 			void Cleanup () override { m_Endpoint.Cleanup (); };
 
-		protected:
-
-			std::shared_ptr<InboundTunnel> GetSharedFromThis () 
-			{
-				return std::static_pointer_cast<InboundTunnel>(shared_from_this ());
-			}
-			
 		private:
 
 			TunnelEndpoint m_Endpoint;
@@ -226,29 +213,27 @@ namespace tunnel
 			std::shared_ptr<OutboundTunnel> GetNextOutboundTunnel ();
 			std::shared_ptr<TunnelPool> GetExploratoryPool () const { return m_ExploratoryPool; };
 			std::shared_ptr<TunnelBase> GetTunnel (uint32_t tunnelID);
-			bool AddTunnel (std::shared_ptr<TunnelBase> tunnel);
-			void RemoveTunnel (uint32_t tunnelID);
 			int GetTransitTunnelsExpirationTimeout ();
+			bool AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel);
 			void AddOutboundTunnel (std::shared_ptr<OutboundTunnel> newTunnel);
 			void AddInboundTunnel (std::shared_ptr<InboundTunnel> newTunnel);
 			std::shared_ptr<InboundTunnel> CreateInboundTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<TunnelPool> pool, std::shared_ptr<OutboundTunnel> outboundTunnel);
 			std::shared_ptr<OutboundTunnel> CreateOutboundTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<TunnelPool> pool);
 			void PostTunnelData (std::shared_ptr<I2NPMessage> msg);
-			void PostTunnelData (std::list<std::shared_ptr<I2NPMessage> >& msgs); // and cleanup msgs
+			void PostTunnelData (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
 			void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<InboundTunnel> tunnel);
 			void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> tunnel);
-			std::shared_ptr<TunnelPool> CreateTunnelPool (int numInboundHops, 
-			    int numOuboundHops, int numInboundTunnels, int numOutboundTunnels, 
-			    int inboundVariance, int outboundVariance,  bool isHighBandwidth);
+			std::shared_ptr<TunnelPool> CreateTunnelPool (int numInboundHops, int numOuboundHops,
+				int numInboundTunnels, int numOutboundTunnels, int inboundVariance, int outboundVariance);
 			void DeleteTunnelPool (std::shared_ptr<TunnelPool> pool);
 			void StopTunnelPool (std::shared_ptr<TunnelPool> pool);
 
 			std::shared_ptr<I2NPMessage> NewI2NPTunnelMessage (bool endpoint);
 
-			void SetMaxNumTransitTunnels (uint32_t maxNumTransitTunnels);
-			uint32_t GetMaxNumTransitTunnels () const { return m_MaxNumTransitTunnels; };
-			int GetCongestionLevel() const { return m_MaxNumTransitTunnels ? CONGESTION_LEVEL_FULL * m_TransitTunnels.GetNumTransitTunnels () / m_MaxNumTransitTunnels : CONGESTION_LEVEL_FULL; }
-			
+			void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels);
+			uint16_t GetMaxNumTransitTunnels () const { return m_MaxNumTransitTunnels; };
+			bool IsTooManyTransitTunnels () const { return m_TransitTunnels.size () >= m_MaxNumTransitTunnels; };
+
 		private:
 
 			template<class TTunnel>
@@ -259,14 +244,12 @@ namespace tunnel
 			std::shared_ptr<TTunnel> GetPendingTunnel (uint32_t replyMsgID, const std::map<uint32_t, std::shared_ptr<TTunnel> >& pendingTunnels);
 
 			void HandleTunnelGatewayMsg (std::shared_ptr<TunnelBase> tunnel, std::shared_ptr<I2NPMessage> msg);
-			void HandleShortTunnelBuildMsg (std::shared_ptr<I2NPMessage> msg);
-			void HandleVariableTunnelBuildMsg (std::shared_ptr<I2NPMessage> msg);
-			void HandleTunnelBuildReplyMsg (std::shared_ptr<I2NPMessage> msg, bool isShort);
-			
+
 			void Run ();
 			void ManageTunnels (uint64_t ts);
-			void ManageOutboundTunnels (uint64_t ts, std::vector<std::shared_ptr<Tunnel> >& toRecreate);
-			void ManageInboundTunnels (uint64_t ts, std::vector<std::shared_ptr<Tunnel> >& toRecreate);
+			void ManageOutboundTunnels (uint64_t ts);
+			void ManageInboundTunnels (uint64_t ts);
+			void ManageTransitTunnels (uint64_t ts);
 			void ManagePendingTunnels (uint64_t ts);
 			template<class PendingTunnels>
 			void ManagePendingTunnels (PendingTunnels& pendingTunnels, uint64_t ts);
@@ -297,39 +280,36 @@ namespace tunnel
 
 			bool m_IsRunning;
 			std::thread * m_Thread;
-			i2p::util::MemoryPoolMt<I2NPMessageBuffer<I2NP_TUNNEL_ENPOINT_MESSAGE_SIZE> > m_I2NPTunnelEndpointMessagesMemoryPool;
-			i2p::util::MemoryPoolMt<I2NPMessageBuffer<I2NP_TUNNEL_MESSAGE_SIZE> > m_I2NPTunnelMessagesMemoryPool;
 			std::map<uint32_t, std::shared_ptr<InboundTunnel> > m_PendingInboundTunnels; // by replyMsgID
 			std::map<uint32_t, std::shared_ptr<OutboundTunnel> > m_PendingOutboundTunnels; // by replyMsgID
 			std::list<std::shared_ptr<InboundTunnel> > m_InboundTunnels;
 			std::list<std::shared_ptr<OutboundTunnel> > m_OutboundTunnels;
-			mutable std::mutex m_TunnelsMutex;
+			std::list<std::shared_ptr<TransitTunnel> > m_TransitTunnels;
 			std::unordered_map<uint32_t, std::shared_ptr<TunnelBase> > m_Tunnels; // tunnelID->tunnel known by this id
-			mutable std::mutex m_PoolsMutex;
+			std::mutex m_PoolsMutex;
 			std::list<std::shared_ptr<TunnelPool>> m_Pools;
 			std::shared_ptr<TunnelPool> m_ExploratoryPool;
 			i2p::util::Queue<std::shared_ptr<I2NPMessage> > m_Queue;
-			uint32_t m_MaxNumTransitTunnels;
+			i2p::util::MemoryPoolMt<I2NPMessageBuffer<I2NP_TUNNEL_ENPOINT_MESSAGE_SIZE> > m_I2NPTunnelEndpointMessagesMemoryPool;
+			i2p::util::MemoryPoolMt<I2NPMessageBuffer<I2NP_TUNNEL_MESSAGE_SIZE> > m_I2NPTunnelMessagesMemoryPool;
+			uint16_t m_MaxNumTransitTunnels;
 			// count of tunnels for total TCSR algorithm
 			int m_TotalNumSuccesiveTunnelCreations, m_TotalNumFailedTunnelCreations;
 			double m_TunnelCreationSuccessRate;
 			int m_TunnelCreationAttemptsNum;
-			std::mt19937 m_Rng;
-			TransitTunnels m_TransitTunnels;
-			
+
 		public:
 
 			// for HTTP only
 			const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; };
 			const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; };
-			const auto& GetTransitTunnels () const { return m_TransitTunnels.GetTransitTunnels (); };
+			const decltype(m_TransitTunnels)& GetTransitTunnels () const { return m_TransitTunnels; };
 
 			size_t CountTransitTunnels() const;
 			size_t CountInboundTunnels() const;
 			size_t CountOutboundTunnels() const;
 
-			size_t GetQueueSize () const { return m_Queue.GetSize (); };
-			size_t GetTBMQueueSize () const { return m_TransitTunnels.GetTunnelBuildMsgQueueSize (); };
+			int GetQueueSize () { return m_Queue.GetSize (); };
 			int GetTunnelCreationSuccessRate () const { return std::round(m_TunnelCreationSuccessRate * 100); } // in percents
 			double GetPreciseTunnelCreationSuccessRate () const { return m_TunnelCreationSuccessRate * 100; } // in percents
 			int GetTotalTunnelCreationSuccessRate () const // in percents
diff --git a/libi2pd/TunnelBase.cpp b/libi2pd/TunnelBase.cpp
deleted file mode 100644
index b5a4a0b3..00000000
--- a/libi2pd/TunnelBase.cpp
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
-* Copyright (c) 2024, The PurpleI2P Project
-*
-* This file is part of Purple i2pd project and licensed under BSD3
-*
-* See full license text in LICENSE file at top of project tree
-*
-*/
-
-#include "Transports.h"
-#include "TunnelBase.h"
-
-namespace i2p
-{
-namespace tunnel
-{
-	void TunnelTransportSender::SendMessagesTo (const i2p::data::IdentHash& to,
-		std::list<std::shared_ptr<I2NPMessage> >&& msgs)
-	{
-		if (msgs.empty ()) return;
-		auto currentTransport = m_CurrentTransport.lock ();
-		if (!currentTransport)
-		{
-			// try to obtain transport from pending request or send thought transport is not complete
-			if (m_PendingTransport.valid ()) // pending request?
-			{
-				if (m_PendingTransport.wait_for(std::chrono::seconds(0)) == std::future_status::ready) 
-				{	
-					// pending request complete
-					currentTransport = m_PendingTransport.get (); // take transports used in pending request
-					if (currentTransport)
-					{	
-						if (currentTransport->IsEstablished ()) 
-							m_CurrentTransport = currentTransport;
-						else
-							currentTransport = nullptr;
-					}
-				}	
-				else // still pending
-				{	
-					// send through transports, but don't update pending transport
-					i2p::transport::transports.SendMessages (to, std::move (msgs));
-					return;
-				}	
-			}
-		}
-		if (currentTransport) // session is good
-			// send to session directly
-			currentTransport->SendI2NPMessages (msgs);
-		else // no session yet
-			// send through transports
-			m_PendingTransport = i2p::transport::transports.SendMessages (to, std::move (msgs));
-
-	}
-
-	void TunnelTransportSender::SendMessagesTo (const i2p::data::IdentHash& to,
-		std::list<std::shared_ptr<I2NPMessage> >& msgs)
-	{
-		std::list<std::shared_ptr<i2p::I2NPMessage> > msgs1;
-		msgs.swap (msgs1);
-		SendMessagesTo (to, std::move (msgs1));
-	}	
-
-	void TunnelTransportSender::Reset ()
-	{
-		m_CurrentTransport.reset ();
-		if (m_PendingTransport.valid ())
-			m_PendingTransport = std::future<std::shared_ptr<i2p::transport::TransportSession> >();
-	}	
-}
-}
diff --git a/libi2pd/TunnelBase.h b/libi2pd/TunnelBase.h
index 39d6e780..d58ec2d7 100644
--- a/libi2pd/TunnelBase.h
+++ b/libi2pd/TunnelBase.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2022, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -11,19 +11,12 @@
 
 #include <inttypes.h>
 #include <memory>
-#include <future>
-#include <list>
 #include "Timestamp.h"
 #include "I2NPProtocol.h"
 #include "Identity.h"
 
 namespace i2p
 {
-namespace transport
-{
-	class TransportSession;
-}	
-	
 namespace tunnel
 {
 	const size_t TUNNEL_DATA_MSG_SIZE = 1028;
@@ -83,25 +76,6 @@ namespace tunnel
 				return t1 < t2;
 		}
 	};
-
-	class TunnelTransportSender final
-	{
-		public: 
-
-			TunnelTransportSender () = default;
-			~TunnelTransportSender () = default;
-
-			void SendMessagesTo (const i2p::data::IdentHash& to, std::list<std::shared_ptr<I2NPMessage> >&& msgs);
-			void SendMessagesTo (const i2p::data::IdentHash& to, std::list<std::shared_ptr<I2NPMessage> >& msgs); // send and clear
-
-			std::shared_ptr<const i2p::transport::TransportSession> GetCurrentTransport () const { return m_CurrentTransport.lock (); }
-			void Reset ();
-			
-		private:
-			
-			std::weak_ptr<i2p::transport::TransportSession> m_CurrentTransport;
-			std::future<std::shared_ptr<i2p::transport::TransportSession> > m_PendingTransport;
-	};	
 }
 }
 
diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp
index fe0e8573..e19b515d 100644
--- a/libi2pd/TunnelConfig.cpp
+++ b/libi2pd/TunnelConfig.cpp
@@ -79,7 +79,8 @@ namespace tunnel
 		uint8_t * record = records + index*TUNNEL_BUILD_RECORD_SIZE;
 		i2p::crypto::CBCDecryption decryption;
 		decryption.SetKey (replyKey);
-		decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, replyIV, record);
+		decryption.SetIV (replyIV);
+		decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record);
 	}
 
 	void ECIESTunnelHopConfig::EncryptECIES (const uint8_t * plainText, size_t len, uint8_t * encrypted)
diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h
index 718a6fdb..9dcf2c02 100644
--- a/libi2pd/TunnelConfig.h
+++ b/libi2pd/TunnelConfig.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2021, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -181,8 +181,6 @@ namespace tunnel
 				return peers;
 			}
 
-			size_t GetRecordSize () const { return m_IsShort ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; };
-			
 		protected:
 
 			// this constructor can't be called from outside
diff --git a/libi2pd/TunnelEndpoint.cpp b/libi2pd/TunnelEndpoint.cpp
index 66b7effa..3dc0dc07 100644
--- a/libi2pd/TunnelEndpoint.cpp
+++ b/libi2pd/TunnelEndpoint.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -21,7 +21,10 @@ namespace i2p
 {
 namespace tunnel
 {
-	
+	TunnelEndpoint::~TunnelEndpoint ()
+	{
+	}
+
 	void TunnelEndpoint::HandleDecryptedTunnelDataMsg (std::shared_ptr<I2NPMessage> msg)
 	{
 		m_NumReceivedBytes += TUNNEL_DATA_MSG_SIZE;
@@ -258,8 +261,9 @@ namespace tunnel
 	void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum,
 		bool isLastFragment, const uint8_t * fragment, size_t size)
 	{
-		if (!m_OutOfSequenceFragments.try_emplace ((uint64_t)msgID << 32 | fragmentNum, 
-			isLastFragment, i2p::util::GetMillisecondsSinceEpoch (), fragment, size).second)
+		std::unique_ptr<Fragment> f(new Fragment (isLastFragment, i2p::util::GetMillisecondsSinceEpoch (), size));
+		memcpy (f->data.data (), fragment, size);
+		if (!m_OutOfSequenceFragments.emplace ((uint64_t)msgID << 32 | fragmentNum, std::move (f)).second)
 			LogPrint (eLogInfo, "TunnelMessage: Duplicate out-of-sequence fragment ", fragmentNum, " of message ", msgID);
 	}
 
@@ -289,7 +293,7 @@ namespace tunnel
 		if (it != m_OutOfSequenceFragments.end ())
 		{
 			LogPrint (eLogDebug, "TunnelMessage: Out-of-sequence fragment ", (int)msg.nextFragmentNum, " of message ", msgID, " found");
-			size_t size = it->second.data.size ();
+			size_t size = it->second->data.size ();
 			if (msg.data->len + size > msg.data->maxLen)
 			{
 				LogPrint (eLogWarning, "TunnelMessage: Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough");
@@ -297,9 +301,9 @@ namespace tunnel
 				*newMsg = *(msg.data);
 				msg.data = newMsg;
 			}
-			if (msg.data->Concat (it->second.data.data (), size) < size) // concatenate out-of-sync fragment	
+			if (msg.data->Concat (it->second->data.data (), size) < size) // concatenate out-of-sync fragment	
 				LogPrint (eLogError, "TunnelMessage: Tunnel endpoint I2NP buffer overflow ", msg.data->maxLen);
-			if (it->second.isLastFragment)
+			if (it->second->isLastFragment)
 				// message complete
 				msg.nextFragmentNum = 0;
 			else
@@ -327,13 +331,13 @@ namespace tunnel
 			break;
 			case eDeliveryTypeTunnel:
 				if (!m_IsInbound) // outbound transit tunnel
-					SendMessageTo (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data));
+					i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data));
 				else
 					LogPrint (eLogError, "TunnelMessage: Delivery type 'tunnel' arrived from an inbound tunnel, dropped");
 			break;
 			case eDeliveryTypeRouter:
 				if (!m_IsInbound) // outbound transit tunnel
-					i2p::transport::transports.SendMessage (msg.hash, msg.data); // send right away, because most likely it's single message
+					i2p::transport::transports.SendMessage (msg.hash, msg.data);
 				else // we shouldn't send this message. possible leakage
 					LogPrint (eLogError, "TunnelMessage: Delivery type 'router' arrived from an inbound tunnel, dropped");
 			break;
@@ -348,7 +352,7 @@ namespace tunnel
 		// out-of-sequence fragments
 		for (auto it = m_OutOfSequenceFragments.begin (); it != m_OutOfSequenceFragments.end ();)
 		{
-			if (ts > it->second.receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT)
+			if (ts > it->second->receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT)
 				it = m_OutOfSequenceFragments.erase (it);
 			else
 				++it;
@@ -362,35 +366,5 @@ namespace tunnel
 				++it;
 		}
 	}
-
-	void TunnelEndpoint::SendMessageTo (const i2p::data::IdentHash& to, std::shared_ptr<i2p::I2NPMessage> msg)
-	{	
-		if (msg)
-		{	
-			if (!m_Sender && m_I2NPMsgs.empty ()) // first message
-				m_CurrentHash = to;
-			else if (m_CurrentHash != to) // new target router
-			{
-				FlushI2NPMsgs (); //  flush message to previous
-				if (m_Sender) m_Sender->Reset (); // reset sender
-				m_CurrentHash = to; // set new target router
-			}	// otherwise add msg to the list for current target router
-			m_I2NPMsgs.push_back (msg);
-		}	
-	}
-	
-	void TunnelEndpoint::FlushI2NPMsgs ()
-	{
-		if (!m_I2NPMsgs.empty ())
-		{
-			if (!m_Sender) m_Sender = std::make_unique<TunnelTransportSender>();
-			m_Sender->SendMessagesTo (m_CurrentHash, m_I2NPMsgs); // send and clear
-		}	
-	}	
-
-	const i2p::data::IdentHash * TunnelEndpoint::GetCurrentHash () const
-	{
-		return (m_Sender || !m_I2NPMsgs.empty ()) ? &m_CurrentHash : nullptr;
-	}	
 }
 }
diff --git a/libi2pd/TunnelEndpoint.h b/libi2pd/TunnelEndpoint.h
index 1e81c445..17590a5f 100644
--- a/libi2pd/TunnelEndpoint.h
+++ b/libi2pd/TunnelEndpoint.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2021, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -11,10 +11,8 @@
 
 #include <inttypes.h>
 #include <vector>
-#include <list>
 #include <string>
 #include <unordered_map>
-#include <memory>
 #include "I2NPProtocol.h"
 #include "TunnelBase.h"
 
@@ -22,7 +20,7 @@ namespace i2p
 {
 namespace tunnel
 {
-	class TunnelEndpoint final
+	class TunnelEndpoint
 	{
 		struct TunnelMessageBlockEx: public TunnelMessageBlock
 		{
@@ -32,8 +30,7 @@ namespace tunnel
 
 		struct Fragment
 		{
-			Fragment (bool last, uint64_t t, const uint8_t * buf, size_t size): 
-				isLastFragment (last), receiveTime (t), data (size) { memcpy (data.data(), buf, size); };
+			Fragment (bool last, uint64_t t, size_t size): isLastFragment (last), receiveTime (t), data (size) {};
 			bool isLastFragment;
 			uint64_t receiveTime; // milliseconds since epoch
 			std::vector<uint8_t> data;
@@ -42,23 +39,18 @@ namespace tunnel
 		public:
 
 			TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0), m_CurrentMsgID (0) {};
-			~TunnelEndpoint () = default;
+			~TunnelEndpoint ();
 			size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
 			void Cleanup ();
 
 			void HandleDecryptedTunnelDataMsg (std::shared_ptr<I2NPMessage> msg);
-			void FlushI2NPMsgs (); 
 
-			const i2p::data::IdentHash * GetCurrentHash () const; // return null if not available
-			const std::unique_ptr<TunnelTransportSender>& GetSender () const { return m_Sender; };
-		
 		private:
 
 			void HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, uint8_t fragmentNum, const uint8_t * fragment, size_t size);
 			bool ConcatFollowOnFragment (TunnelMessageBlockEx& msg, const uint8_t * fragment, size_t size) const; // true if success
 			void HandleCurrenMessageFollowOnFragment (const uint8_t * fragment, size_t size, bool isLastFragment);
 			void HandleNextMessage (const TunnelMessageBlock& msg);
-			void SendMessageTo (const i2p::data::IdentHash& to, std::shared_ptr<i2p::I2NPMessage> msg);
 
 			void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, const uint8_t * fragment, size_t size);
 			bool ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg); // true if something added
@@ -68,15 +60,11 @@ namespace tunnel
 		private:
 
 			std::unordered_map<uint32_t, TunnelMessageBlockEx> m_IncompleteMessages;
-			std::unordered_map<uint64_t, Fragment> m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment
+			std::unordered_map<uint64_t, std::unique_ptr<Fragment> > m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment
 			bool m_IsInbound;
 			size_t m_NumReceivedBytes;
 			TunnelMessageBlockEx m_CurrentMessage;
 			uint32_t m_CurrentMsgID;
-			// I2NP messages to send
-			std::list<std::shared_ptr<i2p::I2NPMessage> > m_I2NPMsgs; // to send
-			i2p::data::IdentHash m_CurrentHash; // send msgs to
-			std::unique_ptr<TunnelTransportSender> m_Sender;
 	};
 }
 }
diff --git a/libi2pd/TunnelGateway.cpp b/libi2pd/TunnelGateway.cpp
index 9e27d207..12e7652f 100644
--- a/libi2pd/TunnelGateway.cpp
+++ b/libi2pd/TunnelGateway.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2021, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -35,13 +35,6 @@ namespace tunnel
 		if (!m_CurrentTunnelDataMsg)
 		{
 			CreateCurrentTunnelDataMessage ();
-			if (block.data && block.data->onDrop)
-			{	
-				// onDrop is called for the first fragment in tunnel message
-				// that's usually true for short TBMs or lookups 
-				m_CurrentTunnelDataMsg->onDrop = block.data->onDrop;
-				block.data->onDrop = nullptr;
-			}	
 			messageCreated = true;
 		}
 
@@ -162,6 +155,7 @@ namespace tunnel
 
 	void TunnelGatewayBuffer::CreateCurrentTunnelDataMessage ()
 	{
+		m_CurrentTunnelDataMsg = nullptr;
 		m_CurrentTunnelDataMsg = NewI2NPTunnelMessage (true); // tunnel endpoint is at least of two tunnel messages size
 		// we reserve space for padding
 		m_CurrentTunnelDataMsg->offset += TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE;
@@ -220,24 +214,20 @@ namespace tunnel
 
 	void TunnelGateway::SendBuffer ()
 	{
-		// create list or tunnel messages
 		m_Buffer.CompleteCurrentTunnelDataMessage ();
-		std::list<std::shared_ptr<I2NPMessage> > newTunnelMsgs;
+		std::vector<std::shared_ptr<I2NPMessage> > newTunnelMsgs;
 		const auto& tunnelDataMsgs = m_Buffer.GetTunnelDataMsgs ();
 		for (auto& tunnelMsg : tunnelDataMsgs)
 		{
 			auto newMsg = CreateEmptyTunnelDataMsg (false);
-			m_Tunnel.EncryptTunnelMsg (tunnelMsg, newMsg);
-			htobe32buf (newMsg->GetPayload (), m_Tunnel.GetNextTunnelID ());
+			m_Tunnel->EncryptTunnelMsg (tunnelMsg, newMsg);
+			htobe32buf (newMsg->GetPayload (), m_Tunnel->GetNextTunnelID ());
 			newMsg->FillI2NPMessageHeader (eI2NPTunnelData);
-			if (tunnelMsg->onDrop) newMsg->onDrop = tunnelMsg->onDrop;
 			newTunnelMsgs.push_back (newMsg);
 			m_NumSentBytes += TUNNEL_DATA_MSG_SIZE;
 		}
 		m_Buffer.ClearTunnelDataMsgs ();
-		// send 
-		if (!m_Sender) m_Sender = std::make_unique<TunnelTransportSender>();
-		m_Sender->SendMessagesTo (m_Tunnel.GetNextIdentHash (), std::move (newTunnelMsgs));		
+		i2p::transport::transports.SendMessages (m_Tunnel->GetNextIdentHash (), newTunnelMsgs);
 	}
 }
 }
diff --git a/libi2pd/TunnelGateway.h b/libi2pd/TunnelGateway.h
index 75f27581..741bbe84 100644
--- a/libi2pd/TunnelGateway.h
+++ b/libi2pd/TunnelGateway.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2021, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -45,20 +45,18 @@ namespace tunnel
 	{
 		public:
 
-			TunnelGateway (TunnelBase& tunnel):
+			TunnelGateway (TunnelBase * tunnel):
 				m_Tunnel (tunnel), m_NumSentBytes (0) {};
 			void SendTunnelDataMsg (const TunnelMessageBlock& block);
 			void PutTunnelDataMsg (const TunnelMessageBlock& block);
 			void SendBuffer ();
 			size_t GetNumSentBytes () const { return m_NumSentBytes; };
-			const std::unique_ptr<TunnelTransportSender>& GetSender () const { return m_Sender; };
 
 		private:
 
-			TunnelBase& m_Tunnel;
+			TunnelBase * m_Tunnel;
 			TunnelGatewayBuffer m_Buffer;
 			size_t m_NumSentBytes;
-			std::unique_ptr<TunnelTransportSender> m_Sender;
 	};
 }
 }
diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp
index 26367aa6..23cc53e3 100644
--- a/libi2pd/TunnelPool.cpp
+++ b/libi2pd/TunnelPool.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -7,13 +7,13 @@
 */
 
 #include <algorithm>
+#include <random>
 #include "I2PEndian.h"
 #include "Crypto.h"
 #include "Tunnel.h"
 #include "NetDb.hpp"
 #include "Timestamp.h"
 #include "Garlic.h"
-#include "ECIESX25519AEADRatchetSession.h"
 #include "Transports.h"
 #include "Log.h"
 #include "Tunnel.h"
@@ -41,12 +41,11 @@ namespace tunnel
 	}
 
 	TunnelPool::TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels,
-		int numOutboundTunnels, int inboundVariance, int outboundVariance, bool isHighBandwidth):
+		int numOutboundTunnels, int inboundVariance, int outboundVariance):
 		m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops),
 		m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels),
 		m_InboundVariance (inboundVariance), m_OutboundVariance (outboundVariance),
-		m_IsActive (true), m_IsHighBandwidth (isHighBandwidth), m_CustomPeerSelector(nullptr), 
-		m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL)
+		m_IsActive (true), m_CustomPeerSelector(nullptr)
 	{
 		if (m_NumInboundTunnels > TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY)
 			m_NumInboundTunnels = TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY;
@@ -60,7 +59,7 @@ namespace tunnel
 			m_InboundVariance = (m_NumInboundHops < STANDARD_NUM_RECORDS) ? STANDARD_NUM_RECORDS - m_NumInboundHops : 0;
 		if (m_OutboundVariance > 0 && m_NumOutboundHops + m_OutboundVariance > STANDARD_NUM_RECORDS)
 			m_OutboundVariance = (m_NumOutboundHops < STANDARD_NUM_RECORDS) ? STANDARD_NUM_RECORDS - m_NumOutboundHops : 0;
-		m_NextManageTime = i2p::util::GetSecondsSinceEpoch () + m_Rng () % TUNNEL_POOL_MANAGE_INTERVAL;
+		m_NextManageTime = i2p::util::GetSecondsSinceEpoch () + rand () % TUNNEL_POOL_MANAGE_INTERVAL;
 	}
 
 	TunnelPool::~TunnelPool ()
@@ -103,10 +102,7 @@ namespace tunnel
 				it->SetTunnelPool (nullptr);
 			m_OutboundTunnels.clear ();
 		}
-		{
-			std::unique_lock<std::mutex> l(m_TestsMutex);
-			m_Tests.clear ();
-		}	
+		m_Tests.clear ();
 	}
 
 	bool TunnelPool::Reconfigure(int inHops, int outHops, int inQuant, int outQuant)
@@ -141,7 +137,7 @@ namespace tunnel
 			m_InboundTunnels.insert (createdTunnel);
 		}
 		if (m_LocalDestination)
-			m_LocalDestination->SetLeaseSetUpdated (true);
+			m_LocalDestination->SetLeaseSetUpdated ();
 	}
 
 	void TunnelPool::TunnelExpired (std::shared_ptr<InboundTunnel> expiredTunnel)
@@ -149,11 +145,8 @@ namespace tunnel
 		if (expiredTunnel)
 		{
 			expiredTunnel->SetTunnelPool (nullptr);
-			{
-				std::unique_lock<std::mutex> l(m_TestsMutex);
-				for (auto& it: m_Tests)
-					if (it.second.second == expiredTunnel) it.second.second = nullptr;
-			}	
+			for (auto& it: m_Tests)
+				if (it.second.second == expiredTunnel) it.second.second = nullptr;
 
 			std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
 			m_InboundTunnels.erase (expiredTunnel);
@@ -174,11 +167,8 @@ namespace tunnel
 		if (expiredTunnel)
 		{
 			expiredTunnel->SetTunnelPool (nullptr);
-			{
-				std::unique_lock<std::mutex> l(m_TestsMutex);
-				for (auto& it: m_Tests)
-					if (it.second.first == expiredTunnel) it.second.first = nullptr;
-			}	
+			for (auto& it: m_Tests)
+				if (it.second.first == expiredTunnel) it.second.first = nullptr;
 
 			std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
 			m_OutboundTunnels.erase (expiredTunnel);
@@ -211,14 +201,14 @@ namespace tunnel
 	}
 
 	std::shared_ptr<OutboundTunnel> TunnelPool::GetNextOutboundTunnel (std::shared_ptr<OutboundTunnel> excluded,
-		i2p::data::RouterInfo::CompatibleTransports compatible)
+		i2p::data::RouterInfo::CompatibleTransports compatible) const
 	{
 		std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
 		return GetNextTunnel (m_OutboundTunnels, excluded, compatible);
 	}
 
 	std::shared_ptr<InboundTunnel> TunnelPool::GetNextInboundTunnel (std::shared_ptr<InboundTunnel> excluded,
-		i2p::data::RouterInfo::CompatibleTransports compatible)
+		i2p::data::RouterInfo::CompatibleTransports compatible) const
 	{
 		std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
 		return GetNextTunnel (m_InboundTunnels, excluded, compatible);
@@ -226,10 +216,10 @@ namespace tunnel
 
 	template<class TTunnels>
 	typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels,
-		typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible)
+		typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible) const
 	{
 		if (tunnels.empty ()) return nullptr;
-		uint32_t ind = m_Rng () % (tunnels.size ()/2 + 1), i = 0;
+		uint32_t ind = rand () % (tunnels.size ()/2 + 1), i = 0;
 		bool skipped = false;
 		typename TTunnels::value_type tunnel = nullptr;
 		for (const auto& it: tunnels)
@@ -249,7 +239,7 @@ namespace tunnel
 		}
 		if (!tunnel && skipped)
 		{
-			ind = m_Rng () % (tunnels.size ()/2 + 1), i = 0;
+			ind = rand () % (tunnels.size ()/2 + 1), i = 0;
 			for (const auto& it: tunnels)
 			{
 				if (it->IsEstablished () && it != excluded)
@@ -264,11 +254,10 @@ namespace tunnel
 		return tunnel;
 	}
 
-	std::pair<std::shared_ptr<OutboundTunnel>, bool> TunnelPool::GetNewOutboundTunnel (std::shared_ptr<OutboundTunnel> old)
+	std::shared_ptr<OutboundTunnel> TunnelPool::GetNewOutboundTunnel (std::shared_ptr<OutboundTunnel> old) const
 	{
-		if (old && old->IsEstablished ()) return std::make_pair(old, false);
+		if (old && old->IsEstablished ()) return old;
 		std::shared_ptr<OutboundTunnel> tunnel;
-		bool freshTunnel = false;
 		if (old)
 		{
 			std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
@@ -281,11 +270,8 @@ namespace tunnel
 		}
 
 		if (!tunnel)
-		{	
 			tunnel = GetNextOutboundTunnel ();
-			freshTunnel = true;
-		}	
-		return std::make_pair(tunnel, freshTunnel);
+		return tunnel;
 	}
 
 	void TunnelPool::CreateTunnels ()
@@ -315,7 +301,7 @@ namespace tunnel
 		{
 			for (auto it: m_OutboundTunnels)
 			{
-				// try to create inbound tunnel through the same path as successive outbound
+				// try to create inbound tunnel through the same path as succesive outbound
 				CreatePairedInboundTunnel (it);
 				num++;
 				if (num >= m_NumInboundTunnels) break;
@@ -330,7 +316,7 @@ namespace tunnel
 		}
 
 		if (num < m_NumInboundTunnels && m_NumInboundHops <= 0 && m_LocalDestination) // zero hops IB
-			m_LocalDestination->SetLeaseSetUpdated (true); // update LeaseSet immediately
+			m_LocalDestination->SetLeaseSetUpdated (); // update LeaseSet immediately
 	}
 
 	void TunnelPool::TestTunnels ()
@@ -351,15 +337,9 @@ namespace tunnel
 				{
 					it.second.first->SetState (eTunnelStateFailed);
 					std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
-					if (m_OutboundTunnels.size () > 1) // don't fail last tunnel
-						m_OutboundTunnels.erase (it.second.first);
-					else
-					{	
-						it.second.first->SetState (eTunnelStateTestFailed);
-						CreateOutboundTunnel (); // create new tunnel immediately because last one failed
-					}		
+					m_OutboundTunnels.erase (it.second.first);
 				}
-				else if (it.second.first->GetState () != eTunnelStateExpiring)
+				else
 					it.second.first->SetState (eTunnelStateTestFailed);
 			}
 			if (it.second.second)
@@ -368,100 +348,48 @@ namespace tunnel
 				{
 					it.second.second->SetState (eTunnelStateFailed);
 					{
-						bool failed = false;
-						{
-							std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
-							if (m_InboundTunnels.size () > 1) // don't fail last tunnel
-							{	
-								m_InboundTunnels.erase (it.second.second);
-								failed = true;	
-							}	
-							else
-							{
-								it.second.second->SetState (eTunnelStateTestFailed);
-								CreateInboundTunnel (); // create new tunnel immediately because last one failed
-							}	
-						}
-						if (failed && m_LocalDestination)
-							m_LocalDestination->SetLeaseSetUpdated (true);
+						std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
+						m_InboundTunnels.erase (it.second.second);
 					}
 					if (m_LocalDestination)
-						m_LocalDestination->SetLeaseSetUpdated (true);
+						m_LocalDestination->SetLeaseSetUpdated ();
 				}
-				else if (it.second.second->GetState () != eTunnelStateExpiring)
+				else
 					it.second.second->SetState (eTunnelStateTestFailed);
 			}
 		}
 
 		// new tests
-		if (!m_LocalDestination) return; 
-		std::vector<std::pair<std::shared_ptr<OutboundTunnel>, std::shared_ptr<InboundTunnel> > > newTests;
-		std::vector<std::shared_ptr<OutboundTunnel> > outboundTunnels;
+		std::unique_lock<std::mutex> l1(m_OutboundTunnelsMutex);
+		auto it1 = m_OutboundTunnels.begin ();
+		std::unique_lock<std::mutex> l2(m_InboundTunnelsMutex);
+		auto it2 = m_InboundTunnels.begin ();
+		while (it1 != m_OutboundTunnels.end () && it2 != m_InboundTunnels.end ())
 		{
-			std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
-			for (auto& it: m_OutboundTunnels)
-				if (it->IsEstablished ())
-					outboundTunnels.push_back (it);
-		}
-		std::shuffle (outboundTunnels.begin(), outboundTunnels.end(), m_Rng);
-		std::vector<std::shared_ptr<InboundTunnel> > inboundTunnels;
-		{
-			std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
-			for (auto& it: m_InboundTunnels)
-				if (it->IsEstablished ())
-					inboundTunnels.push_back (it);
-		}
-		std::shuffle (inboundTunnels.begin(), inboundTunnels.end(), m_Rng);
-		auto it1 = outboundTunnels.begin ();
-		auto it2 = inboundTunnels.begin ();
-		while (it1 != outboundTunnels.end () && it2 != inboundTunnels.end ())
-		{
-			newTests.push_back(std::make_pair (*it1, *it2));
-			++it1; ++it2;
-		}
-		bool isECIES = m_LocalDestination->SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD);
-		for (auto& it: newTests)
-		{
-			uint32_t msgID;
-			RAND_bytes ((uint8_t *)&msgID, 4);
+			bool failed = false;
+			if ((*it1)->IsFailed ())
 			{
-				std::unique_lock<std::mutex> l(m_TestsMutex);
-				m_Tests[msgID] = it;
+				failed = true;
+				++it1;
 			}
-			auto msg = CreateTunnelTestMsg (msgID);
-			auto outbound = it.first;
-			auto s = shared_from_this ();
-			msg->onDrop = [msgID, outbound, s]()
+			if ((*it2)->IsFailed ())
+			{
+				failed = true;
+				++it2;
+			}
+			if (!failed)
+			{
+				uint32_t msgID;
+				RAND_bytes ((uint8_t *)&msgID, 4);
 				{
-					// if test msg dropped locally it's outbound tunnel to blame
-					outbound->SetState (eTunnelStateFailed);
-					{
-						std::unique_lock<std::mutex> l(s->m_TestsMutex);
-						s->m_Tests.erase (msgID);
-					}
-					{
-						std::unique_lock<std::mutex> l(s->m_OutboundTunnelsMutex);
-						s->m_OutboundTunnels.erase (outbound);
-					}
-				};
-			// encrypt
-			if (isECIES)
-			{
-				uint8_t key[32]; RAND_bytes (key, 32);
-				uint64_t tag; RAND_bytes ((uint8_t *)&tag, 8); 
-				m_LocalDestination->SubmitECIESx25519Key (key, tag);
-				msg = i2p::garlic::WrapECIESX25519Message (msg, key, tag);
+					std::unique_lock<std::mutex> l(m_TestsMutex);
+					m_Tests[msgID] = std::make_pair (*it1, *it2);
+				}
+				(*it1)->SendTunnelDataMsgTo ((*it2)->GetNextIdentHash (), (*it2)->GetNextTunnelID (),
+					CreateDeliveryStatusMsg (msgID));
+				++it1; ++it2;
 			}
-			else
-			{
-				uint8_t key[32], tag[32];
-				RAND_bytes (key, 32); RAND_bytes (tag, 32);
-				m_LocalDestination->SubmitSessionKey (key, tag);
-				i2p::garlic::ElGamalAESSession garlic (key, tag);
-				msg = garlic.WrapSingleMessage (msg);
-			}	
-			outbound->SendTunnelDataMsgTo (it.second->GetNextIdentHash (), it.second->GetNextTunnelID (), msg);
-		}	
+		}
 	}
 
 	void TunnelPool::ManageTunnels (uint64_t ts)
@@ -470,7 +398,7 @@ namespace tunnel
 		{
 			CreateTunnels ();
 			TestTunnels ();
-			m_NextManageTime = ts + TUNNEL_POOL_MANAGE_INTERVAL + (m_Rng () % TUNNEL_POOL_MANAGE_INTERVAL)/2;
+			m_NextManageTime = ts + TUNNEL_POOL_MANAGE_INTERVAL + (rand () % TUNNEL_POOL_MANAGE_INTERVAL)/2;
 		}
 	}
 
@@ -483,25 +411,12 @@ namespace tunnel
 	}
 
 	void TunnelPool::ProcessDeliveryStatus (std::shared_ptr<I2NPMessage> msg)
-	{
-		if (m_LocalDestination)
-			m_LocalDestination->ProcessDeliveryStatusMessage (msg);
-		else
-			LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, dropped");
-	}
-
-	void TunnelPool::ProcessTunnelTest (std::shared_ptr<I2NPMessage> msg)
 	{
 		const uint8_t * buf = msg->GetPayload ();
 		uint32_t msgID = bufbe32toh (buf);
 		buf += 4;
 		uint64_t timestamp = bufbe64toh (buf);
 
-		ProcessTunnelTest (msgID, timestamp);
-	}
-
-	bool TunnelPool::ProcessTunnelTest (uint32_t msgID, uint64_t timestamp)
-	{
 		decltype(m_Tests)::mapped_type test;
 		bool found = false;
 		{
@@ -516,37 +431,42 @@ namespace tunnel
 		}
 		if (found)
 		{
-			int dlt = (uint64_t)i2p::util::GetMonotonicMicroseconds () - (int64_t)timestamp;
-			LogPrint (eLogDebug, "Tunnels: Test of ", msgID, " successful. ", dlt, " microseconds");
-			if (dlt < 0) dlt = 0; // should not happen
+			uint64_t dlt = i2p::util::GetMillisecondsSinceEpoch () - timestamp;
+			LogPrint (eLogDebug, "Tunnels: Test of ", msgID, " successful. ", dlt, " milliseconds");
 			int numHops = 0;
 			if (test.first) numHops += test.first->GetNumHops ();
 			if (test.second) numHops += test.second->GetNumHops ();
 			// restore from test failed state if any
 			if (test.first)
 			{
-				if (test.first->GetState () != eTunnelStateExpiring)
+				if (test.first->GetState () == eTunnelStateTestFailed)
 					test.first->SetState (eTunnelStateEstablished);
 				// update latency
-				int latency = 0;
+				uint64_t latency = 0;
 				if (numHops) latency = dlt*test.first->GetNumHops ()/numHops;
 				if (!latency) latency = dlt/2;
-				test.first->AddLatencySample (latency);
+				test.first->AddLatencySample(latency);
 			}
 			if (test.second)
 			{
-				if (test.second->GetState () != eTunnelStateExpiring)
+				if (test.second->GetState () == eTunnelStateTestFailed)
 					test.second->SetState (eTunnelStateEstablished);
 				// update latency
-				int latency = 0;
+				uint64_t latency = 0;
 				if (numHops) latency = dlt*test.second->GetNumHops ()/numHops;
 				if (!latency) latency = dlt/2;
-				test.second->AddLatencySample (latency);
+				test.second->AddLatencySample(latency);
 			}
 		}
-		return found;
-	}	
-		
+		else
+		{
+			if (m_LocalDestination)
+				m_LocalDestination->ProcessDeliveryStatusMessage (msg);
+			else
+				LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, dropped");
+		}
+	}
+
 	bool TunnelPool::IsExploratory () const
 	{
 		return i2p::tunnel::tunnels.GetExploratoryPool () == shared_from_this ();
@@ -555,25 +475,11 @@ namespace tunnel
 	std::shared_ptr<const i2p::data::RouterInfo> TunnelPool::SelectNextHop (std::shared_ptr<const i2p::data::RouterInfo> prevHop, 
 		bool reverse, bool endpoint) const
 	{
-		bool tryClient = !IsExploratory () && !i2p::context.IsLimitedConnectivity ();
-		std::shared_ptr<const i2p::data::RouterInfo> hop;
-		for (int i = 0; i < TUNNEL_POOL_MAX_HOP_SELECTION_ATTEMPTS; i++)
-		{
-			hop = tryClient ?
-				(m_IsHighBandwidth ?
-				 	i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop, reverse, endpoint) : 
-				 	i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint, true)):
-				i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint, false);
-			if (hop)
-			{
-				if (!hop->HasProfile () || !hop->GetProfile ()->IsBad ())
-					break;
-			}
-			else if (tryClient)
-				tryClient = false;
-			else
-				return nullptr;
-		}
+		auto hop = IsExploratory () ? i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint):
+			i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop, reverse, endpoint);
+
+		if (!hop || hop->GetProfile ()->IsBad ())
+			hop = i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint);
 		return hop;
 	}
 
@@ -593,8 +499,8 @@ namespace tunnel
 		else if (i2p::transport::transports.GetNumPeers () > 100 ||
 			(inbound && i2p::transport::transports.GetNumPeers () > 25))
 		{
-			auto r = i2p::transport::transports.GetRandomPeer (m_IsHighBandwidth && !i2p::context.IsLimitedConnectivity ());
-			if (r && r->IsECIES () && (!r->HasProfile () || !r->GetProfile ()->IsBad ()) &&
+			auto r = i2p::transport::transports.GetRandomPeer (!IsExploratory ());
+			if (r && r->IsECIES () && !r->GetProfile ()->IsBad () &&
 				(numHops > 1 || (r->IsV4 () && (!inbound || r->IsPublished (true))))) // first inbound must be published ipv4
 			{
 				prevHop = r;
@@ -635,7 +541,7 @@ namespace tunnel
 			numHops = m_NumInboundHops;
 			if (m_InboundVariance)
 			{
-				int offset = m_Rng () % (std::abs (m_InboundVariance) + 1);
+				int offset = rand () % (std::abs (m_InboundVariance) + 1);
 				if (m_InboundVariance < 0) offset = -offset;
 				numHops += offset;
 			}
@@ -645,7 +551,7 @@ namespace tunnel
 			numHops = m_NumOutboundHops;
 			if (m_OutboundVariance)
 			{
-				int offset = m_Rng () % (std::abs (m_OutboundVariance) + 1);
+				int offset = rand () % (std::abs (m_OutboundVariance) + 1);
 				if (m_OutboundVariance < 0) offset = -offset;
 				numHops += offset;
 			}
@@ -865,7 +771,7 @@ namespace tunnel
 	{
 		std::shared_ptr<InboundTunnel> tun = nullptr;
 		std::unique_lock<std::mutex> lock(m_InboundTunnelsMutex);
-		int min = 1000000;
+		uint64_t min = 1000000;
 		for (const auto & itr : m_InboundTunnels) {
 			if(!itr->LatencyIsKnown()) continue;
 			auto l = itr->GetMeanLatency();
@@ -881,7 +787,7 @@ namespace tunnel
 	{
 		std::shared_ptr<OutboundTunnel> tun = nullptr;
 		std::unique_lock<std::mutex> lock(m_OutboundTunnelsMutex);
-		int min = 1000000;
+		uint64_t min = 1000000;
 		for (const auto & itr : m_OutboundTunnels) {
 			if(!itr->LatencyIsKnown()) continue;
 			auto l = itr->GetMeanLatency();
diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h
index 0ebfd1ac..c1fd19cd 100644
--- a/libi2pd/TunnelPool.h
+++ b/libi2pd/TunnelPool.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -15,7 +15,6 @@
 #include <utility>
 #include <mutex>
 #include <memory>
-#include <random>
 #include "Identity.h"
 #include "LeaseSet.h"
 #include "RouterInfo.h"
@@ -31,8 +30,7 @@ namespace tunnel
 	const int TUNNEL_POOL_MANAGE_INTERVAL = 10; // in seconds
 	const int TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY = 16;
 	const int TUNNEL_POOL_MAX_OUTBOUND_TUNNELS_QUANTITY = 16;
-	const int TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS = 3;
-	const int TUNNEL_POOL_MAX_HOP_SELECTION_ATTEMPTS = 3;
+	const int TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS = 2;
 
 	class Tunnel;
 	class InboundTunnel;
@@ -62,7 +60,7 @@ namespace tunnel
 		public:
 
 			TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels,
-				int numOutboundTunnels, int inboundVariance, int outboundVariance, bool isHighBandwidth);
+				int numOutboundTunnels, int inboundVariance, int outboundVariance);
 			~TunnelPool ();
 
 			std::shared_ptr<i2p::garlic::GarlicDestination> GetLocalDestination () const { return m_LocalDestination; };
@@ -78,15 +76,13 @@ namespace tunnel
 			void RecreateOutboundTunnel (std::shared_ptr<OutboundTunnel> tunnel);
 			std::vector<std::shared_ptr<InboundTunnel> > GetInboundTunnels (int num) const;
 			std::shared_ptr<OutboundTunnel> GetNextOutboundTunnel (std::shared_ptr<OutboundTunnel> excluded = nullptr,
-				i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports);
+				i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports) const;
 			std::shared_ptr<InboundTunnel> GetNextInboundTunnel (std::shared_ptr<InboundTunnel> excluded = nullptr,
-				i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports);
-			std::pair<std::shared_ptr<OutboundTunnel>, bool> GetNewOutboundTunnel (std::shared_ptr<OutboundTunnel> old);
+				i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports) const;
+			std::shared_ptr<OutboundTunnel> GetNewOutboundTunnel (std::shared_ptr<OutboundTunnel> old) const;
 			void ManageTunnels (uint64_t ts);
 			void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
 			void ProcessDeliveryStatus (std::shared_ptr<I2NPMessage> msg);
-			void ProcessTunnelTest (std::shared_ptr<I2NPMessage> msg);
-			bool ProcessTunnelTest (uint32_t msgID, uint64_t timestamp);
 
 			bool IsExploratory () const;
 			bool IsActive () const { return m_IsActive; };
@@ -106,7 +102,7 @@ namespace tunnel
 			bool HasCustomPeerSelector();
 
 			/** @brief make this tunnel pool yield tunnels that fit latency range [min, max] */
-			void RequireLatency(int min, int max) { m_MinLatency = min; m_MaxLatency = max; }
+			void RequireLatency(uint64_t min, uint64_t max) { m_MinLatency = min; m_MaxLatency = max; }
 
 			/** @brief return true if this tunnel pool has a latency requirement */
 			bool HasLatencyRequirement() const { return m_MinLatency > 0 && m_MaxLatency > 0; }
@@ -119,8 +115,6 @@ namespace tunnel
 			std::shared_ptr<const i2p::data::RouterInfo> SelectNextHop (std::shared_ptr<const i2p::data::RouterInfo> prevHop, bool reverse, bool endpoint) const;
 			bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop);
 
-			std::mt19937& GetRng () { return m_Rng; }
-			
 		private:
 
 			void TestTunnels ();
@@ -129,7 +123,7 @@ namespace tunnel
 			void CreatePairedInboundTunnel (std::shared_ptr<OutboundTunnel> outboundTunnel);
 			template<class TTunnels>
 			typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels,
-				typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible);
+				typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible) const;
 			bool SelectPeers (Path& path, bool isInbound);
 			bool SelectExplicitPeers (Path& path, bool isInbound);
 			bool ValidatePeers (std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers) const;
@@ -146,16 +140,14 @@ namespace tunnel
 			std::set<std::shared_ptr<OutboundTunnel>, TunnelCreationTimeCmp> m_OutboundTunnels;
 			mutable std::mutex m_TestsMutex;
 			std::map<uint32_t, std::pair<std::shared_ptr<OutboundTunnel>, std::shared_ptr<InboundTunnel> > > m_Tests;
-			bool m_IsActive, m_IsHighBandwidth;
+			bool m_IsActive;
 			uint64_t m_NextManageTime; // in seconds
 			std::mutex m_CustomPeerSelectorMutex;
 			ITunnelPeerSelector * m_CustomPeerSelector;
 
-			int m_MinLatency = 0; // if > 0 this tunnel pool will try building tunnels with minimum latency by ms
-			int m_MaxLatency = 0; // if > 0 this tunnel pool will try building tunnels with maximum latency by ms
+			uint64_t m_MinLatency = 0; // if > 0 this tunnel pool will try building tunnels with minimum latency by ms
+			uint64_t m_MaxLatency = 0; // if > 0 this tunnel pool will try building tunnels with maximum latency by ms
 
-			std::mt19937 m_Rng;
-			
 		public:
 
 			// for HTTP only
diff --git a/libi2pd/api.cpp b/libi2pd/api.cpp
index 7dc11157..ad24519f 100644
--- a/libi2pd/api.cpp
+++ b/libi2pd/api.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -37,14 +37,13 @@ namespace api
 		i2p::fs::Init();
 
 		bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation);
-		i2p::crypto::InitCrypto (precomputation);
+		bool aesni; i2p::config::GetOption("cpuext.aesni", aesni);
+		bool forceCpuExt; i2p::config::GetOption("cpuext.force", forceCpuExt);
+		i2p::crypto::InitCrypto (precomputation, aesni, forceCpuExt);
 
 		int netID; i2p::config::GetOption("netid", netID);
 		i2p::context.SetNetID (netID);
 
-		bool checkReserved; i2p::config::GetOption("reservedrange", checkReserved);
-		i2p::transport::transports.SetCheckReserved(checkReserved);
-
 		i2p::context.Init ();
 	}
 
diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp
index 925cf629..d1ed9992 100644
--- a/libi2pd/util.cpp
+++ b/libi2pd/util.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -8,7 +8,6 @@
 
 #include <cstdlib>
 #include <string>
-#include <array>
 #include <unordered_set>
 #include <boost/asio.hpp>
 
@@ -124,8 +123,8 @@ const char *inet_ntop_xp(int af, const void *src, char *dst, socklen_t size)
 #endif
 #endif
 
-#define address_pair_v4(a,b) std::pair{ boost::asio::ip::make_address (a).to_v4 ().to_uint (), boost::asio::ip::make_address(b).to_v4 ().to_uint () }
-#define address_pair_v6(a,b) std::pair{ boost::asio::ip::make_address (a).to_v6 ().to_bytes (), boost::asio::ip::make_address(b).to_v6 ().to_bytes () }
+#define address_pair_v4(a,b) { boost::asio::ip::address_v4::from_string (a).to_ulong (), boost::asio::ip::address_v4::from_string (b).to_ulong () }
+#define address_pair_v6(a,b) { boost::asio::ip::address_v6::from_string (a).to_bytes (), boost::asio::ip::address_v6::from_string (b).to_bytes () }
 
 namespace i2p
 {
@@ -172,14 +171,6 @@ namespace util
 		}
 	}
 
-	void RunnableService::SetName (std::string_view name)
-	{
-		if (name.length() < 16)
-			m_Name = name;
-		else
-			m_Name = name.substr(0,15);
-	}
-
 	void SetThreadName (const char *name) {
 #if defined(__APPLE__)
 # if (!defined(MAC_OS_X_VERSION_10_6) || \
@@ -455,9 +446,9 @@ namespace net
 #ifdef _WIN32
 		LogPrint(eLogError, "NetIface: Cannot get address by interface name, not implemented on WIN32");
 		if (ipv6)
-			return boost::asio::ip::make_address("::1");
+			return boost::asio::ip::address::from_string("::1");
 		else
-			return boost::asio::ip::make_address("127.0.0.1");
+			return boost::asio::ip::address::from_string("127.0.0.1");
 #else
 		int af = (ipv6 ? AF_INET6 : AF_INET);
 		ifaddrs *addrs;
@@ -479,7 +470,7 @@ namespace net
 							inet_ntop(af, &((sockaddr_in6 *)cur->ifa_addr)->sin6_addr, addr, INET6_ADDRSTRLEN);
 						freeifaddrs(addrs);
 						std::string cur_ifaddr(addr);
-						return boost::asio::ip::make_address(cur_ifaddr);
+						return boost::asio::ip::address::from_string(cur_ifaddr);
 					}
 				}
 			}
@@ -499,7 +490,7 @@ namespace net
 			fallback = "127.0.0.1";
 			LogPrint(eLogWarning, "NetIface: Cannot find IPv4 address for interface ", ifname);
 		}
-		return boost::asio::ip::make_address(fallback);
+		return boost::asio::ip::address::from_string(fallback);
 #endif
 	}
 
@@ -648,8 +639,7 @@ namespace net
 		if (host.is_unspecified ()) return false;
 		if (host.is_v4())
 		{
-			static const std::array<std::pair<uint32_t, uint32_t>, 14> reservedIPv4Ranges 
-			{
+			static const std::vector< std::pair<uint32_t, uint32_t> > reservedIPv4Ranges {
 				address_pair_v4("0.0.0.0",      "0.255.255.255"),
 				address_pair_v4("10.0.0.0",     "10.255.255.255"),
 				address_pair_v4("100.64.0.0",   "100.127.255.255"),
@@ -666,7 +656,7 @@ namespace net
 				address_pair_v4("224.0.0.0",    "255.255.255.255")
 			};
 
-			uint32_t ipv4_address = host.to_v4 ().to_uint ();
+			uint32_t ipv4_address = host.to_v4 ().to_ulong ();
 			for (const auto& it : reservedIPv4Ranges) {
 				if (ipv4_address >= it.first && ipv4_address <= it.second)
 					return true;
@@ -674,8 +664,7 @@ namespace net
 		}
 		if (host.is_v6())
 		{
-			static const std::array<std::pair<boost::asio::ip::address_v6::bytes_type, boost::asio::ip::address_v6::bytes_type>, 7> reservedIPv6Ranges 
-			{
+			static const std::vector< std::pair<boost::asio::ip::address_v6::bytes_type, boost::asio::ip::address_v6::bytes_type> > reservedIPv6Ranges {
 				address_pair_v6("64:ff9b::",  "64:ff9b:ffff:ffff:ffff:ffff:ffff:ffff"),  // NAT64
 				address_pair_v6("2001:db8::", "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"),
 				address_pair_v6("fc00::",     "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"),
diff --git a/libi2pd/util.h b/libi2pd/util.h
index 7bd35e67..e2037212 100644
--- a/libi2pd/util.h
+++ b/libi2pd/util.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2022, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -131,14 +131,6 @@ namespace util
 				this->Release (t);
 			}
 
-			void ReleaseMt (T * * arr, size_t num)
-			{
-				if (!arr || !num) return;
-				std::lock_guard<std::mutex> l(m_Mutex);
-				for (size_t i = 0; i < num; i++)
-					this->Release (arr[i]);
-			}
-
 			template<template<typename, typename...>class C, typename... R>
 			void ReleaseMt(const C<T *, R...>& c)
 			{
@@ -177,14 +169,12 @@ namespace util
 			RunnableService (const std::string& name): m_Name (name), m_IsRunning (false) {}
 			virtual ~RunnableService () {}
 
-			auto& GetIOService () { return m_Service; }
+			boost::asio::io_service& GetIOService () { return m_Service; }
 			bool IsRunning () const { return m_IsRunning; };
 
 			void StartIOService ();
 			void StopIOService ();
 
-			void SetName (std::string_view name);
-
 		private:
 
 			void Run ();
@@ -194,7 +184,7 @@ namespace util
 			std::string m_Name;
 			volatile bool m_IsRunning;
 			std::unique_ptr<std::thread> m_Thread;
-			boost::asio::io_context m_Service;
+			boost::asio::io_service m_Service;
 	};
 
 	class RunnableServiceWithWork: public RunnableService
@@ -202,11 +192,11 @@ namespace util
 		protected:
 
 			RunnableServiceWithWork (const std::string& name):
-				RunnableService (name), m_Work (GetIOService ().get_executor ()) {}
+				RunnableService (name), m_Work (GetIOService ()) {}
 
 		private:
 
-			boost::asio::executor_work_guard<boost::asio::io_context::executor_type> m_Work;
+			boost::asio::io_service::work m_Work;
 	};
 
 	void SetThreadName (const char *name);
diff --git a/libi2pd/version.h b/libi2pd/version.h
index 1e63ae08..7b9e20c8 100644
--- a/libi2pd/version.h
+++ b/libi2pd/version.h
@@ -18,8 +18,8 @@
 #define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c)
 
 #define I2PD_VERSION_MAJOR 2
-#define I2PD_VERSION_MINOR 56
-#define I2PD_VERSION_MICRO 0
+#define I2PD_VERSION_MINOR 50
+#define I2PD_VERSION_MICRO 2
 #define I2PD_VERSION_PATCH 0
 #ifdef GITVER
 	#define I2PD_VERSION XSTRINGIZE(GITVER)
@@ -33,7 +33,7 @@
 
 #define I2P_VERSION_MAJOR 0
 #define I2P_VERSION_MINOR 9
-#define I2P_VERSION_MICRO 65
+#define I2P_VERSION_MICRO 61
 #define I2P_VERSION_PATCH 0
 #define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO)
 #define I2P_VERSION_NUMBER MAKE_VERSION_NUMBER(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO)
diff --git a/libi2pd_client/AddressBook.cpp b/libi2pd_client/AddressBook.cpp
index 8333e87d..8f2117a7 100644
--- a/libi2pd_client/AddressBook.cpp
+++ b/libi2pd_client/AddressBook.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -9,15 +9,15 @@
 #include <string.h>
 #include <inttypes.h>
 #include <string>
-#include <unordered_map>
+#include <map>
 #include <fstream>
 #include <chrono>
 #include <condition_variable>
 #include <openssl/rand.h>
 #include <boost/algorithm/string.hpp>
+#include <boost/filesystem.hpp>
 #include "Base.h"
 #include "util.h"
-#include "Timestamp.h"
 #include "Identity.h"
 #include "FS.h"
 #include "Log.h"
@@ -27,14 +27,6 @@
 #include "AddressBook.h"
 #include "Config.h"
 
-#if STD_FILESYSTEM
-#include <filesystem>
-namespace fs_lib = std::filesystem;
-#else
-#include <boost/filesystem.hpp>
-namespace fs_lib = boost::filesystem;
-#endif
-
 namespace i2p
 {
 namespace client
@@ -50,23 +42,22 @@ namespace client
 				if (m_IsPersist)
 					i2p::config::GetOption("addressbook.hostsfile", m_HostsFile);
 			}
-			std::shared_ptr<const i2p::data::IdentityEx> GetAddress (const i2p::data::IdentHash& ident) override;
-			void AddAddress (std::shared_ptr<const i2p::data::IdentityEx> address) override;
-			void RemoveAddress (const i2p::data::IdentHash& ident) override;
-			void CleanUpCache () override;
+			std::shared_ptr<const i2p::data::IdentityEx> GetAddress (const i2p::data::IdentHash& ident) const;
+			void AddAddress (std::shared_ptr<const i2p::data::IdentityEx> address);
+			void RemoveAddress (const i2p::data::IdentHash& ident);
 
-			bool Init () override;
-			int Load (Addresses& addresses) override;
-			int LoadLocal (Addresses& addresses) override;
-			int Save (const Addresses& addresses) override;
+			bool Init ();
+			int Load (std::map<std::string, std::shared_ptr<Address> > & addresses);
+			int LoadLocal (std::map<std::string, std::shared_ptr<Address> >& addresses);
+			int Save (const std::map<std::string, std::shared_ptr<Address> >& addresses);
+
+			void SaveEtag (const i2p::data::IdentHash& subsciption, const std::string& etag, const std::string& lastModified);
+			bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified);
+			void ResetEtags ();
 
-			void SaveEtag (const i2p::data::IdentHash& subsciption, const std::string& etag, const std::string& lastModified) override;
-			bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) override;
-			void ResetEtags () override;
-		
 		private:
 
-			int LoadFromFile (const std::string& filename, Addresses& addresses); // returns -1 if can't open file, otherwise number of records
+			int LoadFromFile (const std::string& filename, std::map<std::string, std::shared_ptr<Address> >& addresses); // returns -1 if can't open file, otherwise number of records
 
 		private:
 
@@ -74,8 +65,6 @@ namespace client
 			std::string etagsPath, indexPath, localPath;
 			bool m_IsPersist;
 			std::string m_HostsFile; // file to dump hosts.txt, empty if not used
-			std::unordered_map<i2p::data::IdentHash, std::pair<std::vector<uint8_t>, uint64_t> > m_FullAddressCache; // ident hash -> (full ident buffer, last access timestamp) 
-			std::mutex m_FullAddressCacheMutex;
 	};
 
 	bool AddressBookFilesystemStorage::Init()
@@ -96,19 +85,8 @@ namespace client
 		return false;
 	}
 
-	std::shared_ptr<const i2p::data::IdentityEx> AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident)
+	std::shared_ptr<const i2p::data::IdentityEx> AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) const
 	{
-		auto ts = i2p::util::GetMonotonicSeconds ();
-		{
-			std::lock_guard<std::mutex> l(m_FullAddressCacheMutex);
-			auto it = m_FullAddressCache.find (ident);
-			if (it != m_FullAddressCache.end ())
-			{
-				it->second.second = ts;
-				return std::make_shared<i2p::data::IdentityEx>(it->second.first.data (), it->second.first.size ());
-			}	
-		}
-	
 		if (!m_IsPersist)
 		{
 			LogPrint(eLogDebug, "Addressbook: Persistence is disabled");
@@ -116,72 +94,48 @@ namespace client
 		}
 		std::string filename = storage.Path(ident.ToBase32());
 		std::ifstream f(filename, std::ifstream::binary);
-		if (!f.is_open ()) 
-		{
+		if (!f.is_open ()) {
 			LogPrint(eLogDebug, "Addressbook: Requested, but not found: ", filename);
 			return nullptr;
 		}
 
 		f.seekg (0,std::ios::end);
 		size_t len = f.tellg ();
-		if (len < i2p::data::DEFAULT_IDENTITY_SIZE) 
-		{
+		if (len < i2p::data::DEFAULT_IDENTITY_SIZE) {
 			LogPrint (eLogError, "Addressbook: File ", filename, " is too short: ", len);
 			return nullptr;
 		}
 		f.seekg(0, std::ios::beg);
-		std::vector<uint8_t> buf(len);
-		f.read((char *)buf.data (), len);
-		if (!f)
-		{
-			LogPrint (eLogError, "Addressbook: Couldn't read ", filename);
-			return nullptr;
-		}	
-		{
-			std::lock_guard<std::mutex> l(m_FullAddressCacheMutex);
-			m_FullAddressCache.try_emplace (ident, buf, ts);
-		}	
-		return std::make_shared<i2p::data::IdentityEx>(buf.data (), len);
+		uint8_t * buf = new uint8_t[len];
+		f.read((char *)buf, len);
+		auto address = std::make_shared<i2p::data::IdentityEx>(buf, len);
+		delete[] buf;
+		return address;
 	}
 
 	void AddressBookFilesystemStorage::AddAddress (std::shared_ptr<const i2p::data::IdentityEx> address)
 	{
-		if (!address) return;
-		size_t len = address->GetFullLen ();
-		std::vector<uint8_t> buf;
-		if (!len) return; // invalid address
-		{
-			std::lock_guard<std::mutex> l(m_FullAddressCacheMutex);
-			auto [it, inserted] = m_FullAddressCache.try_emplace (address->GetIdentHash(), len, i2p::util::GetMonotonicSeconds ());
-			if (inserted)
-				address->ToBuffer (it->second.first.data (), len);
-			if (m_IsPersist)
-				buf = it->second.first;
+		if (!m_IsPersist) return;
+		std::string path = storage.Path( address->GetIdentHash().ToBase32() );
+		std::ofstream f (path, std::ofstream::binary | std::ofstream::out);
+		if (!f.is_open ())	{
+			LogPrint (eLogError, "Addressbook: Can't open file ", path);
+			return;
 		}
-		if (m_IsPersist && !buf.empty ())
-		{
-			std::string path = storage.Path(address->GetIdentHash().ToBase32());
-			std::ofstream f (path, std::ofstream::binary | std::ofstream::out);
-			if (!f.is_open ())	
-			{
-				LogPrint (eLogError, "Addressbook: Can't open file ", path);
-				return;
-			}
-			f.write ((const char *)buf.data (), len);
-		}	
+		size_t len = address->GetFullLen ();
+		uint8_t * buf = new uint8_t[len];
+		address->ToBuffer (buf, len);
+		f.write ((char *)buf, len);
+		delete[] buf;
 	}
 
 	void AddressBookFilesystemStorage::RemoveAddress (const i2p::data::IdentHash& ident)
 	{
-		{
-			std::lock_guard<std::mutex> l(m_FullAddressCacheMutex);
-			m_FullAddressCache.erase (ident);
-		}
 		if (!m_IsPersist) return;
 		storage.Remove( ident.ToBase32() );
 	}
 
-	int AddressBookFilesystemStorage::LoadFromFile (const std::string& filename, Addresses& addresses)
+	int AddressBookFilesystemStorage::LoadFromFile (const std::string& filename, std::map<std::string, std::shared_ptr<Address> >& addresses)
 	{
 		int num = 0;
 		std::ifstream f (filename, std::ifstream::in); // in text mode
@@ -207,7 +161,7 @@ namespace client
 		return num;
 	}
 
-	int AddressBookFilesystemStorage::Load (Addresses& addresses)
+	int AddressBookFilesystemStorage::Load (std::map<std::string, std::shared_ptr<Address> >& addresses)
 	{
 		int num = LoadFromFile (indexPath, addresses);
 		if (num < 0)
@@ -221,7 +175,7 @@ namespace client
 		return num;
 	}
 
-	int AddressBookFilesystemStorage::LoadLocal (Addresses& addresses)
+	int AddressBookFilesystemStorage::LoadLocal (std::map<std::string, std::shared_ptr<Address> >& addresses)
 	{
 		int num = LoadFromFile (localPath, addresses);
 		if (num < 0) return 0;
@@ -229,7 +183,7 @@ namespace client
 		return num;
 	}
 
-	int AddressBookFilesystemStorage::Save (const Addresses& addresses)
+	int AddressBookFilesystemStorage::Save (const std::map<std::string, std::shared_ptr<Address> >& addresses)
 	{
 		if (addresses.empty())
 		{
@@ -312,30 +266,17 @@ namespace client
 	void AddressBookFilesystemStorage::ResetEtags ()
 	{
 		LogPrint (eLogError, "Addressbook: Resetting eTags");
-		for (fs_lib::directory_iterator it (etagsPath); it != fs_lib::directory_iterator (); ++it)
+		for (boost::filesystem::directory_iterator it (etagsPath); it != boost::filesystem::directory_iterator (); ++it)
 		{
-			if (!fs_lib::is_regular_file (it->status ()))
+			if (!boost::filesystem::is_regular_file (it->status ()))
 				continue;
-			fs_lib::remove (it->path ());
+			boost::filesystem::remove (it->path ());
 		}
 	}
 
-	void AddressBookFilesystemStorage::CleanUpCache ()
-	{
-		auto ts = i2p::util::GetMonotonicSeconds ();
-		std::lock_guard<std::mutex> l(m_FullAddressCacheMutex);
-		for (auto it = m_FullAddressCache.begin (); it != m_FullAddressCache.end ();)
-		{
-			if (ts > it->second.second + ADDRESS_CACHE_EXPIRATION_TIMEOUT)
-				it = m_FullAddressCache.erase (it);
-			else
-				it++;
-		}	
-	}	
-	
 //---------------------------------------------------------------------
 
-	Address::Address (std::string_view b32):
+	Address::Address (const std::string& b32):
 		addressType (eAddressInvalid)
 	{
 		if (b32.length () <= B33_ADDRESS_THRESHOLD)
@@ -357,7 +298,7 @@ namespace client
 		identHash = hash;
 	}
 
-	AddressBook::AddressBook (): m_Storage(nullptr), m_IsLoaded (false),
+	AddressBook::AddressBook (): m_Storage(nullptr), m_IsLoaded (false), m_IsDownloading (false),
 		m_NumRetries (0), m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr),
 		m_IsEnabled (true)
 	{
@@ -379,7 +320,6 @@ namespace client
 			LoadHosts (); /* try storage, then hosts.txt, then download */
 			StartSubscriptions ();
 			StartLookups ();
-			ScheduleCacheUpdate ();
 		}
 	}
 
@@ -394,36 +334,23 @@ namespace client
 		StopSubscriptions ();
 		if (m_SubscriptionsUpdateTimer)
 		{
-			m_SubscriptionsUpdateTimer->cancel ();
+			delete m_SubscriptionsUpdateTimer;
 			m_SubscriptionsUpdateTimer = nullptr;
 		}
-		if (m_AddressCacheUpdateTimer)
+		if (m_IsDownloading)
 		{
-			m_AddressCacheUpdateTimer->cancel ();
-			m_AddressCacheUpdateTimer = nullptr;
-		}	
-		bool isDownloading = m_Downloading.valid ();
-		if (isDownloading)
-		{
-			if (m_Downloading.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
-				isDownloading = false;
-			else	
-			{	
-				LogPrint (eLogInfo, "Addressbook: Subscriptions are downloading, abort");
-				for (int i = 0; i < 30; i++)
+			LogPrint (eLogInfo, "Addressbook: Subscriptions are downloading, abort");
+			for (int i = 0; i < 30; i++)
+			{
+				if (!m_IsDownloading)
 				{
-					if (m_Downloading.wait_for(std::chrono::seconds(1)) == std::future_status::ready) // wait for 1 seconds
-					{
-						isDownloading = false;
-						LogPrint (eLogInfo, "Addressbook: Subscriptions download complete");
-						break;
-					}
+					LogPrint (eLogInfo, "Addressbook: Subscriptions download complete");
+					break;
 				}
-			}	
-			if (!isDownloading)
-				m_Downloading.get ();
-			else
-				LogPrint (eLogError, "Addressbook: Subscription download timeout");
+				std::this_thread::sleep_for (std::chrono::seconds (1)); // wait for 1 seconds
+			}
+			LogPrint (eLogError, "Addressbook: Subscription download timeout");
+			m_IsDownloading = false;
 		}
 		if (m_Storage)
 		{
@@ -435,7 +362,7 @@ namespace client
 		m_Subscriptions.clear ();
 	}
 
-	std::shared_ptr<const Address> AddressBook::GetAddress (std::string_view address)
+	std::shared_ptr<const Address> AddressBook::GetAddress (const std::string& address)
 	{
 		auto pos = address.find(".b32.i2p");
 		if (pos != std::string::npos)
@@ -443,18 +370,17 @@ namespace client
 			auto addr = std::make_shared<const Address>(address.substr (0, pos));
 			return addr->IsValid () ? addr : nullptr;
 		}
-		else 
-#if __cplusplus >= 202002L // C++20
-		if (address.ends_with (".i2p"))
-#else
-		if (address.find (".i2p") != std::string::npos)
-#endif			
+		else
 		{
-			if (!m_IsEnabled) return nullptr;
-			auto addr = FindAddress (address);
-			if (!addr)
-				LookupAddress (address); // TODO:
-			return addr;
+			pos = address.find (".i2p");
+			if (pos != std::string::npos)
+			{
+				if (!m_IsEnabled) return nullptr;
+				auto addr = FindAddress (address);
+				if (!addr)
+					LookupAddress (address); // TODO:
+				return addr;
+			}
 		}
 		// if not .b32 we assume full base64 address
 		i2p::data::IdentityEx dest;
@@ -463,7 +389,7 @@ namespace client
 		return std::make_shared<const Address>(dest.GetIdentHash ());
 	}
 
-	std::shared_ptr<const Address> AddressBook::FindAddress (std::string_view address)
+	std::shared_ptr<const Address> AddressBook::FindAddress (const std::string& address)
 	{
 		auto it = m_Addresses.find (address);
 		if (it != m_Addresses.end ())
@@ -508,7 +434,7 @@ namespace client
 			auto ident = std::make_shared<i2p::data::IdentityEx>();
 			if (ident->FromBase64 (jump))
 			{
-				if (m_Storage) m_Storage->AddAddress (ident);
+				m_Storage->AddAddress (ident);
 				m_Addresses[address] = std::make_shared<Address>(ident->GetIdentHash ());
 				LogPrint (eLogInfo, "Addressbook: Added ", address," -> ", ToAddress(ident->GetIdentHash ()));
 			}
@@ -519,19 +445,18 @@ namespace client
 
 	void AddressBook::InsertFullAddress (std::shared_ptr<const i2p::data::IdentityEx> address)
 	{
-		if (m_Storage) m_Storage->AddAddress (address);
+		m_Storage->AddAddress (address);
 	}
 
 	std::shared_ptr<const i2p::data::IdentityEx> AddressBook::GetFullAddress (const std::string& address)
 	{
 		auto addr = GetAddress (address);
 		if (!addr || !addr->IsIdentHash ()) return nullptr;
-		return m_Storage ? m_Storage->GetAddress (addr->identHash) : nullptr;
+		return m_Storage->GetAddress (addr->identHash);
 	}
 
 	void AddressBook::LoadHosts ()
 	{
-		if (!m_Storage) return;
 		if (m_Storage->Load (m_Addresses) > 0)
 		{
 			m_IsLoaded = true;
@@ -567,35 +492,29 @@ namespace client
 
 			if (pos != std::string::npos)
 			{
-				std::string_view name = std::string_view(s).substr(0, pos++);
-				std::string_view addr = std::string_view(s).substr(pos);
+				std::string name = s.substr(0, pos++);
+				std::string addr = s.substr(pos);
 
 				size_t pos = addr.find('#');
-				if (pos != addr.npos)
+				if (pos != std::string::npos)
 					addr = addr.substr(0, pos); // remove comments
-#if __cplusplus >= 202002L // C++20
-				if (name.ends_with (".b32.i2p"))
-#else					
-				if (name.find(".b32.i2p") != name.npos)
-#endif					
+
+				pos = name.find(".b32.i2p");
+				if (pos != std::string::npos)
 				{
 					LogPrint (eLogError, "Addressbook: Skipped adding of b32 address: ", name);
 					continue;
 				}
 
-#if __cplusplus >= 202002L // C++20
-				if (!name.ends_with (".i2p"))
-#else	
-				if (name.find(".i2p") == name.npos)
-#endif					
+				pos = name.find(".i2p");
+				if (pos == std::string::npos)
 				{
 					LogPrint (eLogError, "Addressbook: Malformed domain: ", name);
 					continue;
 				}
 
 				auto ident = std::make_shared<i2p::data::IdentityEx> ();
-				if (!ident->FromBase64(addr)) 
-				{
+				if (!ident->FromBase64(addr)) {
 					LogPrint (eLogError, "Addressbook: Malformed address ", addr, " for ", name);
 					incomplete = f.eof ();
 					continue;
@@ -608,18 +527,15 @@ namespace client
 						ident->GetSigningKeyType () != i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) // don't replace by DSA
 					{
 						it->second->identHash = ident->GetIdentHash ();
-						if (m_Storage)
-						{	
-							m_Storage->AddAddress (ident);
-							m_Storage->RemoveAddress (it->second->identHash);
-						}	
+						m_Storage->AddAddress (ident);
+						m_Storage->RemoveAddress (it->second->identHash);
 						LogPrint (eLogInfo, "Addressbook: Updated host: ", name);
 					}
 				}
 				else
 				{
 					m_Addresses.emplace (name, std::make_shared<Address>(ident->GetIdentHash ()));
-					if (m_Storage) m_Storage->AddAddress (ident);
+					m_Storage->AddAddress (ident);
 					if (is_update)
 						LogPrint (eLogInfo, "Addressbook: Added new host: ", name);
 				}
@@ -631,7 +547,7 @@ namespace client
 		if (numAddresses > 0)
 		{
 			if (!incomplete) m_IsLoaded = true;
-			if (m_Storage) m_Storage->Save (m_Addresses);
+			m_Storage->Save (m_Addresses);
 		}
 		return !incomplete;
 	}
@@ -655,15 +571,16 @@ namespace client
 			}
 			else
 			{
-				LogPrint (eLogInfo, "Addressbook: Loading subscriptions from config");
+				LogPrint (eLogInfo, "Addressbook: Loading subscriptions from config file");
 				// using config file items
 				std::string subscriptionURLs; i2p::config::GetOption("addressbook.subscriptions", subscriptionURLs);
 				std::vector<std::string> subsList;
 				boost::split(subsList, subscriptionURLs, boost::is_any_of(","), boost::token_compress_on);
 
 				for (const auto& s: subsList)
-					if (!s.empty ())
-						m_Subscriptions.push_back (std::make_shared<AddressBookSubscription> (*this, s));
+				{
+					m_Subscriptions.push_back (std::make_shared<AddressBookSubscription> (*this, s));
+				}
 				LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded");
 			}
 		}
@@ -674,7 +591,7 @@ namespace client
 	void AddressBook::LoadLocal ()
 	{
 		if (!m_Storage) return;
-		AddressBookStorage::Addresses localAddresses;
+		std::map<std::string, std::shared_ptr<Address>> localAddresses;
 		m_Storage->LoadLocal (localAddresses);
 		for (const auto& it: localAddresses)
 		{
@@ -717,6 +634,7 @@ namespace client
 
 	void AddressBook::DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified)
 	{
+		m_IsDownloading = false;
 		m_NumRetries++;
 		int nextUpdateTimeout = m_NumRetries*CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT;
 		if (m_NumRetries > CONTINIOUS_SUBSCRIPTION_MAX_NUM_RETRIES || nextUpdateTimeout > CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT)
@@ -747,7 +665,7 @@ namespace client
 		auto dest = i2p::client::context.GetSharedLocalDestination ();
 		if (dest)
 		{
-			m_SubscriptionsUpdateTimer = std::make_unique<boost::asio::deadline_timer>(dest->GetService ());
+			m_SubscriptionsUpdateTimer = new boost::asio::deadline_timer (dest->GetService ());
 			m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT));
 			m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer,
 				this, std::placeholders::_1));
@@ -771,13 +689,7 @@ namespace client
 				LogPrint(eLogWarning, "Addressbook: Missing local destination, skip subscription update");
 				return;
 			}
-			bool isDownloading = m_Downloading.valid ();
-			if (isDownloading && m_Downloading.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // still active?
-			{
-				m_Downloading.get ();
-				isDownloading = false;
-			}	
-			if (!isDownloading && dest->IsReady ())
+			if (!m_IsDownloading && dest->IsReady ())
 			{
 				if (!m_IsLoaded)
 				{
@@ -786,15 +698,17 @@ namespace client
 					std::string defaultSubURL; i2p::config::GetOption("addressbook.defaulturl", defaultSubURL);
 					if (!m_DefaultSubscription)
 						m_DefaultSubscription = std::make_shared<AddressBookSubscription>(*this, defaultSubURL);
-					m_Downloading = std::async (std::launch::async,
-						std::bind (&AddressBookSubscription::CheckUpdates, m_DefaultSubscription));
+					m_IsDownloading = true;
+					std::thread load_hosts(std::bind (&AddressBookSubscription::CheckUpdates, m_DefaultSubscription));
+					load_hosts.detach(); // TODO: use join
 				}
 				else if (!m_Subscriptions.empty ())
 				{
 					// pick random subscription
 					auto ind = rand () % m_Subscriptions.size();
-					m_Downloading = std::async (std::launch::async,
-						std::bind (&AddressBookSubscription::CheckUpdates, m_Subscriptions[ind]));
+					m_IsDownloading = true;
+					std::thread load_hosts(std::bind (&AddressBookSubscription::CheckUpdates, m_Subscriptions[ind]));
+					load_hosts.detach(); // TODO: use join
 				}
 			}
 			else
@@ -831,7 +745,7 @@ namespace client
 		}
 	}
 
-	void AddressBook::LookupAddress (std::string_view address)
+	void AddressBook::LookupAddress (const std::string& address)
 	{
 		std::shared_ptr<const Address> addr;
 		auto dot = address.find ('.');
@@ -861,7 +775,7 @@ namespace client
 				memset (buf, 0, 4);
 				htobe32buf (buf + 4, nonce);
 				buf[8] = address.length ();
-				memcpy (buf + 9, address.data (), address.length ());
+				memcpy (buf + 9, address.c_str (), address.length ());
 				datagram->SendDatagramTo (buf, len, addr->identHash, ADDRESS_RESPONSE_DATAGRAM_PORT, ADDRESS_RESOLVER_DATAGRAM_PORT);
 				delete[] buf;
 			}
@@ -898,30 +812,7 @@ namespace client
 		}
 	}
 
-	void AddressBook::ScheduleCacheUpdate ()
-	{
-		if (!m_AddressCacheUpdateTimer)
-		{
-			auto dest = i2p::client::context.GetSharedLocalDestination ();
-			if(dest)
-				m_AddressCacheUpdateTimer = std::make_unique<boost::asio::deadline_timer>(dest->GetService ());
-		}	
-		if (m_AddressCacheUpdateTimer)
-		{	
-			m_AddressCacheUpdateTimer->expires_from_now (boost::posix_time::seconds(ADDRESS_CACHE_UPDATE_INTERVAL ));
-			m_AddressCacheUpdateTimer->async_wait (
-				[this](const boost::system::error_code& ecode) 
-				{
-					if (ecode != boost::asio::error::operation_aborted)
-					{
-						if (m_Storage) m_Storage->CleanUpCache ();
-						ScheduleCacheUpdate ();
-					}	
-				});
-		}	
-	}	
-		
-	AddressBookSubscription::AddressBookSubscription (AddressBook& book, std::string_view link):
+	AddressBookSubscription::AddressBookSubscription (AddressBook& book, const std::string& link):
 		m_Book (book), m_Link (link)
 	{
 	}
diff --git a/libi2pd_client/AddressBook.h b/libi2pd_client/AddressBook.h
index 8b32aa93..9b2c7e7e 100644
--- a/libi2pd_client/AddressBook.h
+++ b/libi2pd_client/AddressBook.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2022, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -11,12 +11,10 @@
 
 #include <string.h>
 #include <string>
-#include <string_view>
 #include <map>
 #include <vector>
 #include <iostream>
 #include <mutex>
-#include <future>
 #include <memory>
 #include <boost/asio.hpp>
 #include "Base.h"
@@ -34,9 +32,7 @@ namespace client
 	const int CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT = 720; // in minutes (12 hours)
 	const int CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT = 5; // in minutes
 	const int CONTINIOUS_SUBSCRIPTION_MAX_NUM_RETRIES = 10; // then update timeout
-	const int SUBSCRIPTION_REQUEST_TIMEOUT = 120; //in seconds
-	const int ADDRESS_CACHE_EXPIRATION_TIMEOUT = 710; // in seconds
-	const int ADDRESS_CACHE_UPDATE_INTERVAL = 76; // in seconds
+	const int SUBSCRIPTION_REQUEST_TIMEOUT = 120; //in second
 
 	const uint16_t ADDRESS_RESOLVER_DATAGRAM_PORT = 53;
 	const uint16_t ADDRESS_RESPONSE_DATAGRAM_PORT = 54;
@@ -49,7 +45,7 @@ namespace client
 		i2p::data::IdentHash identHash;
 		std::shared_ptr<i2p::data::BlindedPublicKey> blindedPublicKey;
 
-		Address (std::string_view b32);
+		Address (const std::string& b32);
 		Address (const i2p::data::IdentHash& hash);
 		bool IsIdentHash () const { return addressType == eAddressIndentHash; };
 		bool IsValid () const { return addressType != eAddressInvalid; };
@@ -61,18 +57,15 @@ namespace client
 	{
 		public:
 
-			typedef std::map<std::string, std::shared_ptr<Address>, std::less<> > Addresses;
-			
 			virtual ~AddressBookStorage () {};
-			virtual std::shared_ptr<const i2p::data::IdentityEx> GetAddress (const i2p::data::IdentHash& ident) = 0;
+			virtual std::shared_ptr<const i2p::data::IdentityEx> GetAddress (const i2p::data::IdentHash& ident) const = 0;
 			virtual void AddAddress (std::shared_ptr<const i2p::data::IdentityEx> address) = 0;
 			virtual void RemoveAddress (const i2p::data::IdentHash& ident) = 0;
-			virtual void CleanUpCache () = 0;
 
 			virtual bool Init () = 0;
-			virtual int Load (Addresses& addresses) = 0;
-			virtual int LoadLocal (Addresses& addresses) = 0;
-			virtual int Save (const Addresses& addresses) = 0;
+			virtual int Load (std::map<std::string, std::shared_ptr<Address> >& addresses) = 0;
+			virtual int LoadLocal (std::map<std::string, std::shared_ptr<Address> >& addresses) = 0;
+			virtual int Save (const std::map<std::string, std::shared_ptr<Address> >& addresses) = 0;
 
 			virtual void SaveEtag (const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) = 0;
 			virtual bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) = 0;
@@ -84,16 +77,16 @@ namespace client
 	class AddressBook
 	{
 		public:
-			
+
 			AddressBook ();
 			~AddressBook ();
 			void Start ();
 			void StartResolvers ();
 			void Stop ();
-			std::shared_ptr<const Address> GetAddress (std::string_view address);
+			std::shared_ptr<const Address> GetAddress (const std::string& address);
 			std::shared_ptr<const i2p::data::IdentityEx> GetFullAddress (const std::string& address);
-			std::shared_ptr<const Address> FindAddress (std::string_view address);
-			void LookupAddress (std::string_view address);
+			std::shared_ptr<const Address> FindAddress (const std::string& address);
+			void LookupAddress (const std::string& address);
 			void InsertAddress (const std::string& address, const std::string& jump); // for jump links
 			void InsertFullAddress (std::shared_ptr<const i2p::data::IdentityEx> address);
 
@@ -106,8 +99,7 @@ namespace client
 			std::string ToAddress(std::shared_ptr<const i2p::data::IdentityEx> ident) { return ToAddress(ident->GetIdentHash ()); }
 
 			bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified);
-			bool IsEnabled () const { return m_IsEnabled; }
-			
+
 		private:
 
 			void StartSubscriptions ();
@@ -123,22 +115,19 @@ namespace client
 			void StopLookups ();
 			void HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
 
-			void ScheduleCacheUpdate ();
-			
 		private:
 
 			std::mutex m_AddressBookMutex;
-			AddressBookStorage::Addresses m_Addresses;
+			std::map<std::string, std::shared_ptr<Address> > m_Addresses;
 			std::map<i2p::data::IdentHash, std::shared_ptr<AddressResolver> > m_Resolvers; // local destination->resolver
 			std::mutex m_LookupsMutex;
 			std::map<uint32_t, std::string> m_Lookups; // nonce -> address
 			AddressBookStorage * m_Storage;
-			volatile bool m_IsLoaded;
-			std::future<void> m_Downloading;
+			volatile bool m_IsLoaded, m_IsDownloading;
 			int m_NumRetries;
 			std::vector<std::shared_ptr<AddressBookSubscription> > m_Subscriptions;
 			std::shared_ptr<AddressBookSubscription> m_DefaultSubscription; // in case if we don't know any addresses yet
-			std::unique_ptr<boost::asio::deadline_timer> m_SubscriptionsUpdateTimer, m_AddressCacheUpdateTimer;
+			boost::asio::deadline_timer * m_SubscriptionsUpdateTimer;
 			bool m_IsEnabled;
 	};
 
@@ -146,7 +135,7 @@ namespace client
 	{
 		public:
 
-			AddressBookSubscription (AddressBook& book, std::string_view link);
+			AddressBookSubscription (AddressBook& book, const std::string& link);
 			void CheckUpdates ();
 
 		private:
diff --git a/libi2pd_client/BOB.cpp b/libi2pd_client/BOB.cpp
index 8d94e94b..23c3b72f 100644
--- a/libi2pd_client/BOB.cpp
+++ b/libi2pd_client/BOB.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -16,24 +16,6 @@ namespace i2p
 {
 namespace client
 {
-	void BOBI2PTunnelIncomingConnection::Established ()
-	{
-		if (m_IsQuiet)
-			StreamReceive ();
-		else
-		{
-			// send destination first like received from I2P
-			std::string dest = GetStream ()->GetRemoteIdentity ()->ToBase64 ();
-			dest += "\n";
-			if (dest.size() <= I2P_TUNNEL_CONNECTION_BUFFER_SIZE)
-				memcpy (GetStreamBuffer (), dest.c_str (), dest.size ());
-			else
-				memset (GetStreamBuffer (), 0, I2P_TUNNEL_CONNECTION_BUFFER_SIZE);
-			HandleStreamReceive (boost::system::error_code (), dest.size ());
-		}
-		Receive ();
-	}	
-	
 	BOBI2PInboundTunnel::BOBI2PInboundTunnel (const boost::asio::ip::tcp::endpoint& ep, std::shared_ptr<ClientDestination> localDestination):
 		BOBI2PTunnel (localDestination), m_Acceptor (localDestination->GetService (), ep)
 	{
@@ -147,7 +129,7 @@ namespace client
 
 	BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& outhost, uint16_t port,
 		std::shared_ptr<ClientDestination> localDestination, bool quiet): BOBI2PTunnel (localDestination),
-		m_Endpoint (boost::asio::ip::make_address (outhost), port), m_IsQuiet (quiet)
+		m_Endpoint (boost::asio::ip::address::from_string (outhost), port), m_IsQuiet (quiet)
 	{
 	}
 
@@ -174,7 +156,7 @@ namespace client
 	{
 		if (stream)
 		{
-			auto conn = std::make_shared<BOBI2PTunnelIncomingConnection> (this, stream, m_Endpoint, m_IsQuiet);
+			auto conn = std::make_shared<I2PTunnelConnection> (this, stream, m_Endpoint, m_IsQuiet);
 			AddHandler (conn);
 			conn->Connect ();
 		}
@@ -238,7 +220,7 @@ namespace client
 			if (!inhost.empty ())
 			{
 				boost::system::error_code ec;
-				auto addr = boost::asio::ip::make_address (inhost, ec);
+				auto addr = boost::asio::ip::address::from_string (inhost, ec);
 				if (!ec)
 					ep.address (addr);
 				else
@@ -443,7 +425,7 @@ namespace client
 		{
 			// TODO: FIXME: temporary validation, until hostname support is added
 			boost::system::error_code ec;
-			boost::asio::ip::make_address(m_InHost, ec);
+			boost::asio::ip::address::from_string(m_InHost, ec);
 			if (ec)
 			{
 				SendReplyError("inhost must be a valid IPv4 address.");
@@ -454,7 +436,7 @@ namespace client
 		{
 			// TODO: FIXME: temporary validation, until hostname support is added
 			boost::system::error_code ec;
-			boost::asio::ip::make_address(m_OutHost, ec);
+			boost::asio::ip::address::from_string(m_OutHost, ec);
 			if (ec)
 			{
 				SendReplyError("outhost must be a IPv4 address.");
@@ -846,7 +828,7 @@ namespace client
 
 	BOBCommandChannel::BOBCommandChannel (const std::string& address, uint16_t port):
 		RunnableService ("BOB"),
-		m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), port))
+		m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port))
 	{
 		// command -> handler
 		m_CommandHandlers[BOB_COMMAND_ZAP] = &BOBCommandSession::ZapCommandHandler;
diff --git a/libi2pd_client/BOB.h b/libi2pd_client/BOB.h
index f5aefd0a..1f5fda5f 100644
--- a/libi2pd_client/BOB.h
+++ b/libi2pd_client/BOB.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -71,23 +71,6 @@ namespace client
 	const char BOB_HELP_STATUS[] = "status <NICKNAME> - Display status of a nicknamed tunnel.";
 	const char BOB_HELP_HELP [] = "help <COMMAND> - Get help on a command.";
 
-	class BOBI2PTunnelIncomingConnection: public I2PTunnelConnection
-	{
-		public:
-
-			BOBI2PTunnelIncomingConnection (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
-				const boost::asio::ip::tcp::endpoint& target, bool quiet):
-				I2PTunnelConnection (owner, stream, target), m_IsQuiet (quiet) {};
-
-		protected:
-
-			void Established () override;
-		
-		private:
-
-			bool m_IsQuiet; // don't send destination
-	};
-	
 	class BOBI2PTunnel: public I2PService
 	{
 		public:
@@ -271,7 +254,7 @@ namespace client
 			void Start ();
 			void Stop ();
 
-			auto& GetService () { return GetIOService (); };
+			boost::asio::io_service& GetService () { return GetIOService (); };
 			void AddDestination (const std::string& name, std::shared_ptr<BOBDestination> dest);
 			void DeleteDestination (const std::string& name);
 			std::shared_ptr<BOBDestination> FindDestination (const std::string& name);
diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp
index d26e33ab..d8c0bd2d 100644
--- a/libi2pd_client/ClientContext.cpp
+++ b/libi2pd_client/ClientContext.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -16,7 +16,6 @@
 #include "Identity.h"
 #include "util.h"
 #include "ClientContext.h"
-#include "HTTPProxy.h"
 #include "SOCKS.h"
 #include "MatchedDestination.h"
 
@@ -265,15 +264,11 @@ namespace client
 		}
 	}
 
-	bool ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, std::string_view filename,
+	bool ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename,
 		i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType)
 	{
-#if __cplusplus >= 202002L // C++20
-		if (filename.starts_with ("transient"))
-#else		
-		std::string_view transient("transient");
+		static const std::string transient("transient");
 		if (!filename.compare (0, transient.length (), transient)) // starts with transient
-#endif			
 		{
 			keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true);
 			LogPrint (eLogInfo, "Clients: New transient keys address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created");
@@ -301,7 +296,7 @@ namespace client
 		}
 		else
 		{
-			LogPrint (eLogInfo, "Clients: Can't open file ", fullPath, " Creating new one with signature type ", sigType, " crypto type ", cryptoType);
+			LogPrint (eLogCritical, "Clients: Can't open file ", fullPath, " Creating new one with signature type ", sigType, " crypto type ", cryptoType);
 			keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true);
 			std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out);
 			size_t len = keys.GetFullLen ();
@@ -349,7 +344,7 @@ namespace client
 	}
 
 	std::shared_ptr<ClientDestination> ClientContext::CreateNewLocalDestination (
-		boost::asio::io_context& service, bool isPublic,
+		boost::asio::io_service& service, bool isPublic,
 		i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType,
 		const std::map<std::string, std::string> * params)
 	{
@@ -403,7 +398,7 @@ namespace client
 		return localDestination;
 	}
 
-	std::shared_ptr<ClientDestination> ClientContext::CreateNewLocalDestination (boost::asio::io_context& service,
+	std::shared_ptr<ClientDestination> ClientContext::CreateNewLocalDestination (boost::asio::io_service& service,
 		const i2p::data::PrivateKeys& keys, bool isPublic, const std::map<std::string, std::string> * params)
 	{
 		auto it = m_Destinations.find (keys.GetPublic ()->GetIdentHash ());
@@ -420,10 +415,13 @@ namespace client
 
 	void ClientContext::CreateNewSharedLocalDestination ()
 	{
-		std::map<std::string, std::string> params;
-		ReadI2CPOptionsFromConfig ("shareddest.", params);
-		params[I2CP_PARAM_OUTBOUND_NICKNAME] = "SharedDest";
-		
+		std::map<std::string, std::string> params
+		{
+			{ I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, "3" },
+			{ I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, "3" },
+			{ I2CP_PARAM_LEASESET_TYPE, "3" },
+			{ I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4" }
+		};
 		m_SharedLocalDestination = CreateNewLocalDestination (false, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519,
 			i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, &params); // non-public, EDDSA
 		m_SharedLocalDestination->Acquire ();
@@ -472,13 +470,9 @@ namespace client
 		options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MIN_TUNNEL_LATENCY, DEFAULT_MIN_TUNNEL_LATENCY);
 		options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MAX_TUNNEL_LATENCY, DEFAULT_MAX_TUNNEL_LATENCY);
 		options[I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY] = GetI2CPOption(section, I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY, DEFAULT_INITIAL_ACK_DELAY);
-		options[I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED] = GetI2CPOption(section, I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED, DEFAULT_MAX_OUTBOUND_SPEED);
-		options[I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED] = GetI2CPOption(section, I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED, DEFAULT_MAX_INBOUND_SPEED);
-		options[I2CP_PARAM_STREAMING_MAX_CONCURRENT_STREAMS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_MAX_CONCURRENT_STREAMS, DEFAULT_MAX_CONCURRENT_STREAMS);
 		options[I2CP_PARAM_STREAMING_ANSWER_PINGS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_ANSWER_PINGS, isServer ? DEFAULT_ANSWER_PINGS : false);
-		options[I2CP_PARAM_STREAMING_PROFILE] = GetI2CPOption(section, I2CP_PARAM_STREAMING_PROFILE, DEFAULT_STREAMING_PROFILE);
 		options[I2CP_PARAM_LEASESET_TYPE] = GetI2CPOption(section, I2CP_PARAM_LEASESET_TYPE, DEFAULT_LEASESET_TYPE);
-		std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, isServer ? "4" : "0,4");
+		std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4");
 		if (encType.length () > 0) options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = encType;
 		std::string privKey = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_PRIV_KEY, "");
 		if (privKey.length () > 0) options[I2CP_PARAM_LEASESET_PRIV_KEY] = privKey;
@@ -522,8 +516,6 @@ namespace client
 			options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = value;
 		if (i2p::config::GetOption(prefix + I2CP_PARAM_LEASESET_PRIV_KEY, value) && !value.empty ())
 			options[I2CP_PARAM_LEASESET_PRIV_KEY] = value;
-		if (i2p::config::GetOption(prefix + I2CP_PARAM_STREAMING_PROFILE, value))
-			options[I2CP_PARAM_STREAMING_PROFILE] = value;
 	}
 
 	void ClientContext::ReadTunnels ()
@@ -547,11 +539,7 @@ namespace client
 			{
 				for (auto& it: files)
 				{
-#if __cplusplus >= 202002L // C++20
-					if (!it.ends_with (".conf")) continue;
-#else					
 					if (it.substr(it.size() - 5) != ".conf") continue; // skip files which not ends with ".conf"
-#endif					
 					LogPrint(eLogDebug, "Clients: Tunnels extra config file: ", it);
 					ReadTunnels (it, numClientTunnels, numServerTunnels);
 				}
@@ -601,15 +589,8 @@ namespace client
 					std::map<std::string, std::string> options;
 					ReadI2CPOptions (section, false, options);
 
-					// Set I2CP name if not set
-					auto itopt = options.find (I2CP_PARAM_OUTBOUND_NICKNAME);
-					if (itopt == options.end ())
-						options[I2CP_PARAM_OUTBOUND_NICKNAME] = name;
-
 					std::shared_ptr<ClientDestination> localDestination = nullptr;
-					if (keys == "shareddest")
-						localDestination = m_SharedLocalDestination;
-					else if (keys.length () > 0)
+					if (keys.length () > 0)
 					{
 						auto it = destinations.find (keys);
 						if (it != destinations.end ())
@@ -636,7 +617,7 @@ namespace client
 					if (type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) {
 						// udp client
 						// TODO: hostnames
-						boost::asio::ip::udp::endpoint end (boost::asio::ip::make_address(address), port);
+						boost::asio::ip::udp::endpoint end (boost::asio::ip::address::from_string(address), port);
 						if (!localDestination)
 							localDestination = m_SharedLocalDestination;
 
@@ -679,9 +660,7 @@ namespace client
 							// http proxy
 							std::string outproxy = section.second.get("outproxy", "");
 							bool addresshelper = section.second.get("addresshelper", true);
-							bool senduseragent = section.second.get("senduseragent", false);
-							auto tun = std::make_shared<i2p::proxy::HTTPProxy>(name, address, port,
-								outproxy, addresshelper, senduseragent, localDestination);
+							auto tun = std::make_shared<i2p::proxy::HTTPProxy>(name, address, port, outproxy, addresshelper, localDestination);
 							clientTunnel = tun;
 							clientEndpoint = tun->GetLocalEndpoint ();
 						}
@@ -762,42 +741,32 @@ namespace client
 					std::map<std::string, std::string> options;
 					ReadI2CPOptions (section, true, options);
 
-					// Set I2CP name if not set
-					auto itopt = options.find (I2CP_PARAM_INBOUND_NICKNAME);
-					if (itopt == options.end ())
-						options[I2CP_PARAM_INBOUND_NICKNAME] = name;
-
 					std::shared_ptr<ClientDestination> localDestination = nullptr;
-					if (keys == "shareddest")
-						localDestination = m_SharedLocalDestination;
+					auto it = destinations.find (keys);
+					if (it != destinations.end ())
+					{
+						localDestination = it->second;
+						localDestination->SetPublic (true);
+					}
 					else
-					{	
-						auto it = destinations.find (keys);
-						if (it != destinations.end ())
+					{
+						i2p::data::PrivateKeys k;
+						if(!LoadPrivateKeys (k, keys, sigType, cryptoType))
+							continue;
+						localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ());
+						if (!localDestination)
 						{
-							localDestination = it->second;
-							localDestination->SetPublic (true);
+							localDestination = CreateNewLocalDestination (k, true, &options);
+							destinations[keys] = localDestination;
 						}
 						else
-						{
-							i2p::data::PrivateKeys k;
-							if(!LoadPrivateKeys (k, keys, sigType, cryptoType))
-								continue;
-							localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ());
-							if (!localDestination)
-							{
-								localDestination = CreateNewLocalDestination (k, true, &options);
-								destinations[keys] = localDestination;
-							}
-							else
-								localDestination->SetPublic (true);
-						}
-					}	
+							localDestination->SetPublic (true);
+					}
 					if (type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER)
 					{
 						// udp server tunnel
 						// TODO: hostnames
-						boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::make_address(host), port);
+						boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(host), port);
 						if (address.empty ())
 						{
 							if (!endpoint.address ().is_unspecified () && endpoint.address ().is_v6 ())
@@ -805,8 +774,8 @@ namespace client
 							else
 								address = "127.0.0.1";
 						}
-						auto localAddress = boost::asio::ip::make_address(address);
-						auto serverTunnel = std::make_shared<I2PUDPServerTunnel>(name, localDestination, localAddress, endpoint, inPort, gzip);
+						auto localAddress = boost::asio::ip::address::from_string(address);
+						auto serverTunnel = std::make_shared<I2PUDPServerTunnel>(name, localDestination, localAddress, endpoint, port, gzip);
 						if(!isUniqueLocal)
 						{
 							LogPrint(eLogInfo, "Clients: Disabling loopback address mapping");
@@ -886,7 +855,7 @@ namespace client
 
 				}
 				else
-					LogPrint (eLogError, "Clients: Unknown section type = ", type, " of ", name, " in ", tunConf);
+					LogPrint (eLogWarning, "Clients: Unknown section type = ", type, " of ", name, " in ", tunConf);
 			}
 			catch (std::exception& ex)
 			{
@@ -907,24 +876,17 @@ namespace client
 			uint16_t    httpProxyPort;         i2p::config::GetOption("httpproxy.port",          httpProxyPort);
 			std::string httpOutProxyURL;       i2p::config::GetOption("httpproxy.outproxy",      httpOutProxyURL);
 			bool        httpAddresshelper;     i2p::config::GetOption("httpproxy.addresshelper", httpAddresshelper);
-			bool        httpSendUserAgent;     i2p::config::GetOption("httpproxy.senduseragent", httpSendUserAgent);
 			if (httpAddresshelper)
 				i2p::config::GetOption("addressbook.enabled", httpAddresshelper); // addresshelper is not supported without address book
 			i2p::data::SigningKeyType sigType; i2p::config::GetOption("httpproxy.signaturetype", sigType);
 			LogPrint(eLogInfo, "Clients: Starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort);
-			if (httpProxyKeys == "shareddest")
-			{
-				localDestination = m_SharedLocalDestination;
-				localDestination->Acquire ();
-			}
-			else if (httpProxyKeys.length () > 0)
+			if (httpProxyKeys.length () > 0)
 			{
 				i2p::data::PrivateKeys keys;
 				if(LoadPrivateKeys (keys, httpProxyKeys, sigType))
 				{
 					std::map<std::string, std::string> params;
 					ReadI2CPOptionsFromConfig ("httpproxy.", params);
-					params[I2CP_PARAM_OUTBOUND_NICKNAME] = "HTTPProxy";
 					localDestination = CreateNewLocalDestination (keys, false, &params);
 					if (localDestination) localDestination->Acquire ();
 				}
@@ -933,8 +895,7 @@ namespace client
 			}
 			try
 			{
-				m_HttpProxy = new i2p::proxy::HTTPProxy("HTTP Proxy", httpProxyAddr, httpProxyPort,
-					httpOutProxyURL, httpAddresshelper, httpSendUserAgent, localDestination);
+				m_HttpProxy = new i2p::proxy::HTTPProxy("HTTP Proxy", httpProxyAddr, httpProxyPort, httpOutProxyURL, httpAddresshelper, localDestination);
 				m_HttpProxy->Start();
 			}
 			catch (std::exception& e)
@@ -961,12 +922,7 @@ namespace client
 			uint16_t    socksOutProxyPort;     i2p::config::GetOption("socksproxy.outproxyport",     socksOutProxyPort);
 			i2p::data::SigningKeyType sigType; i2p::config::GetOption("socksproxy.signaturetype",    sigType);
 			LogPrint(eLogInfo, "Clients: Starting SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort);
-			if (socksProxyKeys == "shareddest")
-			{
-				localDestination = m_SharedLocalDestination;
-				localDestination->Acquire ();
-			}
-			else if (httpProxyKeys == socksProxyKeys && m_HttpProxy)
+			if (httpProxyKeys == socksProxyKeys && m_HttpProxy)
 			{
 				localDestination = m_HttpProxy->GetLocalDestination ();
 				localDestination->Acquire ();
@@ -978,7 +934,6 @@ namespace client
 				{
 					std::map<std::string, std::string> params;
 					ReadI2CPOptionsFromConfig ("socksproxy.", params);
-					params[I2CP_PARAM_OUTBOUND_NICKNAME] = "SOCKSProxy";
 					localDestination = CreateNewLocalDestination (keys, false, &params);
 					if (localDestination) localDestination->Acquire ();
 				}
diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h
index 3f7eaf9a..4e969a6b 100644
--- a/libi2pd_client/ClientContext.h
+++ b/libi2pd_client/ClientContext.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2022, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -15,6 +15,8 @@
 #include <boost/asio.hpp>
 #include "Destination.h"
 #include "I2PService.h"
+#include "HTTPProxy.h"
+#include "SOCKS.h"
 #include "I2PTunnel.h"
 #include "UDPTunnel.h"
 #include "SAM.h"
@@ -79,20 +81,20 @@ namespace client
 				i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519,
 				i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL,
 				const std::map<std::string, std::string> * params = nullptr); // used by SAM only
-			std::shared_ptr<ClientDestination> CreateNewLocalDestination (boost::asio::io_context& service,
+			std::shared_ptr<ClientDestination> CreateNewLocalDestination (boost::asio::io_service& service,
 				bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519,
 				i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL,
 				const std::map<std::string, std::string> * params = nullptr); // same as previous but on external io_service
 			std::shared_ptr<ClientDestination> CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true,
 				const std::map<std::string, std::string> * params = nullptr);
-			std::shared_ptr<ClientDestination> CreateNewLocalDestination (boost::asio::io_context& service,
+			std::shared_ptr<ClientDestination> CreateNewLocalDestination (boost::asio::io_service& service,
 				const i2p::data::PrivateKeys& keys, bool isPublic = true,
 				const std::map<std::string, std::string> * params = nullptr); // same as previous but on external io_service
 			std::shared_ptr<ClientDestination> CreateNewMatchedTunnelDestination(const i2p::data::PrivateKeys &keys,
 				const std::string & name, const std::map<std::string, std::string> * params = nullptr);
 			void DeleteLocalDestination (std::shared_ptr<ClientDestination> destination);
 			std::shared_ptr<ClientDestination> FindLocalDestination (const i2p::data::IdentHash& destination) const;
-			bool LoadPrivateKeys (i2p::data::PrivateKeys& keys, std::string_view filename,
+			bool LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename,
 				i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519,
 				i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL);
 
@@ -139,7 +141,8 @@ namespace client
 
 			AddressBook m_AddressBook;
 
-			I2PService * m_HttpProxy, * m_SocksProxy;
+			i2p::proxy::HTTPProxy * m_HttpProxy;
+			i2p::proxy::SOCKSProxy * m_SocksProxy;
 			std::map<boost::asio::ip::tcp::endpoint, std::shared_ptr<I2PService> > m_ClientTunnels; // local endpoint -> tunnel
 			std::map<std::pair<i2p::data::IdentHash, int>, std::shared_ptr<I2PServerTunnel> > m_ServerTunnels; // <destination,port> -> tunnel
 
@@ -164,8 +167,8 @@ namespace client
 			const decltype(m_ServerTunnels)& GetServerTunnels () const { return m_ServerTunnels; };
 			const decltype(m_ClientForwards)& GetClientForwards () const { return m_ClientForwards; }
 			const decltype(m_ServerForwards)& GetServerForwards () const { return m_ServerForwards; }
-			const I2PService * GetHttpProxy () const { return m_HttpProxy; }
-			const I2PService * GetSocksProxy () const { return m_SocksProxy; }
+			const i2p::proxy::HTTPProxy * GetHttpProxy () const { return m_HttpProxy; }
+			const i2p::proxy::SOCKSProxy * GetSocksProxy () const { return m_SocksProxy; }
 	};
 
 	extern ClientContext context;
diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp
index 4c2771b5..a0fc8f07 100644
--- a/libi2pd_client/HTTPProxy.cpp
+++ b/libi2pd_client/HTTPProxy.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -9,7 +9,6 @@
 #include <cstring>
 #include <cassert>
 #include <string>
-#include <string_view>
 #include <atomic>
 #include <memory>
 #include <set>
@@ -30,7 +29,6 @@
 #include "Config.h"
 #include "HTTP.h"
 #include "I18N.h"
-#include "Socks5.h"
 
 namespace i2p {
 namespace proxy {
@@ -60,8 +58,7 @@ namespace proxy {
 		"</head>\r\n"
 	;
 
-	static bool str_rmatch(std::string_view str, const char *suffix) 
-	{
+	bool str_rmatch(std::string & str, const char *suffix) {
 		auto pos = str.rfind (suffix);
 		if (pos == std::string::npos)
 			return false; /* not found */
@@ -79,26 +76,29 @@ namespace proxy {
 			void Terminate();
 			void AsyncSockRead();
 			static bool ExtractAddressHelper(i2p::http::URL& url, std::string& jump, bool& confirm);
-			static bool VerifyAddressHelper (std::string_view jump);
-			void SanitizeHTTPRequest(i2p::http::HTTPReq& req);
+			static bool VerifyAddressHelper (const std::string& jump);
+			static void SanitizeHTTPRequest(i2p::http::HTTPReq& req);
 			void SentHTTPFailed(const boost::system::error_code & ecode);
 			void HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream);
 			/* error helpers */
-			void GenericProxyError(std::string_view title, std::string_view description);
-			void GenericProxyInfo(std::string_view title, std::string_view description);
-			void HostNotFound(std::string_view host);
-			void SendProxyError(std::string_view content);
-			void SendRedirect(const std::string& address);
+			void GenericProxyError(const std::string& title, const std::string& description);
+			void GenericProxyInfo(const std::string& title, const std::string& description);
+			void HostNotFound(std::string& host);
+			void SendProxyError(std::string& content);
+			void SendRedirect(std::string& address);
 
 			void ForwardToUpstreamProxy();
 			void HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec);
 			void HandleUpstreamSocksProxyConnect(const boost::system::error_code & ec);
-			void HTTPConnect(std::string_view host, uint16_t port);
+			void HTTPConnect(const std::string & host, uint16_t port);
 			void HandleHTTPConnectStreamRequestComplete(std::shared_ptr<i2p::stream::Stream> stream);
 
+			void HandleSocksProxySendHandshake(const boost::system::error_code & ec, std::size_t bytes_transfered);
+			void HandleSocksProxyReply(const boost::system::error_code & ec, std::size_t bytes_transfered);
+
 			typedef std::function<void(boost::asio::ip::tcp::endpoint)> ProxyResolvedHandler;
 
-			void HandleUpstreamProxyResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::results_type endpoints, ProxyResolvedHandler handler);
+			void HandleUpstreamProxyResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr, ProxyResolvedHandler handler);
 
 			void SocksProxySuccess();
 			void HandoverToUpstreamProxy();
@@ -110,9 +110,10 @@ namespace proxy {
 			std::shared_ptr<boost::asio::ip::tcp::socket> m_proxysock;
 			boost::asio::ip::tcp::resolver m_proxy_resolver;
 			std::string m_OutproxyUrl, m_Response;
-			bool m_Addresshelper, m_SendUserAgent;
+			bool m_Addresshelper;
 			i2p::http::URL m_ProxyURL;
 			i2p::http::URL m_RequestURL;
+			uint8_t m_socks_buf[255+8]; // for socks request/response
 			int m_req_len;
 			i2p::http::URL m_ClientRequestURL;
 			i2p::http::HTTPReq m_ClientRequest;
@@ -126,8 +127,7 @@ namespace proxy {
 				m_proxysock(std::make_shared<boost::asio::ip::tcp::socket>(parent->GetService())),
 				m_proxy_resolver(parent->GetService()),
 				m_OutproxyUrl(parent->GetOutproxyURL()),
-				m_Addresshelper(parent->GetHelperSupport()),
-				m_SendUserAgent (parent->GetSendUserAgent ()) {}
+				m_Addresshelper(parent->GetHelperSupport()) {}
 			~HTTPReqHandler() { Terminate(); }
 			void Handle () { AsyncSockRead(); } /* overload */
 	};
@@ -162,24 +162,23 @@ namespace proxy {
 		Done(shared_from_this());
 	}
 
-	void HTTPReqHandler::GenericProxyError(std::string_view title, std::string_view description) 
-	{
+	void HTTPReqHandler::GenericProxyError(const std::string& title, const std::string& description) {
 		std::stringstream ss;
 		ss << "<h1>" << tr("Proxy error") << ": " << title << "</h1>\r\n";
 		ss << "<p>" << description << "</p>\r\n";
-		SendProxyError(ss.str ());
+		std::string content = ss.str();
+		SendProxyError(content);
 	}
 
-	void HTTPReqHandler::GenericProxyInfo(std::string_view title, std::string_view description) 
-	{
+	void HTTPReqHandler::GenericProxyInfo(const std::string& title, const std::string& description) {
 		std::stringstream ss;
 		ss << "<h1>" << tr("Proxy info") << ": " << title << "</h1>\r\n";
 		ss << "<p>" << description << "</p>\r\n";
-		SendProxyError(ss.str ());
+		std::string content = ss.str();
+		SendProxyError(content);
 	}
 
-	void HTTPReqHandler::HostNotFound(std::string_view host) 
-	{
+	void HTTPReqHandler::HostNotFound(std::string& host) {
 		std::stringstream ss;
 		ss << "<h1>" << tr("Proxy error: Host not found") << "</h1>\r\n"
 		   << "<p>" << tr("Remote host not found in router's addressbook") << "</p>\r\n"
@@ -192,10 +191,11 @@ namespace proxy {
 				ss << "  <li><a href=\"" << js->second << host << "\">" << js->first << "</a></li>\r\n";
 		}
 		ss << "</ul>\r\n";
-		SendProxyError(ss.str ());
+		std::string content = ss.str();
+		SendProxyError(content);
 	}
 
-	void HTTPReqHandler::SendProxyError(std::string_view content)
+	void HTTPReqHandler::SendProxyError(std::string& content)
 	{
 		i2p::http::HTTPRes res;
 		res.code = 500;
@@ -211,7 +211,7 @@ namespace proxy {
 			std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1));
 	}
 
-	void HTTPReqHandler::SendRedirect(const std::string& address)
+	void HTTPReqHandler::SendRedirect(std::string& address)
 	{
 		i2p::http::HTTPRes res;
 		res.code = 302;
@@ -275,7 +275,7 @@ namespace proxy {
 		return true;
 	}
 
-	bool HTTPReqHandler::VerifyAddressHelper (std::string_view jump)
+	bool HTTPReqHandler::VerifyAddressHelper (const std::string& jump)
 	{
 		auto pos = jump.find(".b32.i2p");
 		if (pos != std::string::npos)
@@ -315,8 +315,7 @@ namespace proxy {
 		req.RemoveHeader("X-Forwarded");
 		req.RemoveHeader("Proxy-"); // Proxy-*
 		/* replace headers */
-		if (!m_SendUserAgent)
-			req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)");
+		req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)");
 
 		/**
 		 * i2pd PR #1816:
@@ -377,7 +376,7 @@ namespace proxy {
 		std::string jump;
 		if (ExtractAddressHelper(m_RequestURL, jump, m_Confirm))
 		{
-			if (!m_Addresshelper || !i2p::client::context.GetAddressBook ().IsEnabled ())
+			if (!m_Addresshelper)
 			{
 				LogPrint(eLogWarning, "HTTPProxy: Addresshelper request rejected");
 				GenericProxyError(tr("Invalid request"), tr("Addresshelper is not supported"));
@@ -445,7 +444,7 @@ namespace proxy {
 		bool useConnect = false;
 		if(m_ClientRequest.method == "CONNECT")
 		{
-			const std::string& uri = m_ClientRequest.uri;
+			std::string uri(m_ClientRequest.uri);
 			auto pos = uri.find(":");
 			if(pos == std::string::npos || pos == uri.size() - 1)
 			{
@@ -472,7 +471,7 @@ namespace proxy {
 			if (dest_host != "")
 			{
 				/* absolute url, replace 'Host' header */
-				std::string h (dest_host);
+				std::string h = dest_host;
 				if (dest_port != 0 && dest_port != 80)
 					h += ":" + std::to_string(dest_port);
 				m_ClientRequest.UpdateHeader("Host", h);
@@ -512,7 +511,7 @@ namespace proxy {
 					GenericProxyError(tr("Outproxy failure"), tr("Bad outproxy settings"));
 			} else {
 				LogPrint (eLogWarning, "HTTPProxy: Outproxy failure for ", dest_host, ": no outproxy enabled");
-				std::stringstream ss; ss << tr("Host %s is not inside I2P network, but outproxy is not enabled", dest_host.c_str ());
+				std::stringstream ss; ss << tr("Host %s is not inside I2P network, but outproxy is not enabled", dest_host.c_str());
 				GenericProxyError(tr("Outproxy failure"), ss.str());
 			}
 			return true;
@@ -552,9 +551,9 @@ namespace proxy {
 		std::string origURI = m_ClientRequest.uri; // TODO: what do we need to change uri for?
 		m_ClientRequest.uri = m_ClientRequestURL.to_string();
 
-		/* update User-Agent to ESR version of Firefox, same as Tor Browser below version 13, for non-HTTPS connections */
-		if(m_ClientRequest.method != "CONNECT" && !m_SendUserAgent)
-			m_ClientRequest.UpdateHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; rv:109.0) Gecko/20100101 Firefox/115.0");
+		/* update User-Agent to ESR version of Firefox, same as Tor Browser below version 8, for non-HTTPS connections */
+		if(m_ClientRequest.method != "CONNECT")
+			m_ClientRequest.UpdateHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; rv:60.0) Gecko/20100101 Firefox/60.0");
 
 		m_ClientRequest.write(m_ClientRequestBuffer);
 		m_ClientRequestBuffer << m_recv_buf.substr(m_req_len);
@@ -583,22 +582,20 @@ namespace proxy {
 			}
 			else
 			{
-				m_proxy_resolver.async_resolve(m_ProxyURL.host, std::to_string(m_ProxyURL.port), std::bind(&HTTPReqHandler::HandleUpstreamProxyResolved, this, 
-				    std::placeholders::_1, std::placeholders::_2, [&](boost::asio::ip::tcp::endpoint ep) 
-				    {
-					 	m_proxysock->async_connect(ep, std::bind(&HTTPReqHandler::HandleUpstreamHTTPProxyConnect, this, std::placeholders::_1));
-					}));
+				boost::asio::ip::tcp::resolver::query q(m_ProxyURL.host, std::to_string(m_ProxyURL.port));
+				m_proxy_resolver.async_resolve(q, std::bind(&HTTPReqHandler::HandleUpstreamProxyResolved, this, std::placeholders::_1, std::placeholders::_2, [&](boost::asio::ip::tcp::endpoint ep) {
+					m_proxysock->async_connect(ep, std::bind(&HTTPReqHandler::HandleUpstreamHTTPProxyConnect, this, std::placeholders::_1));
+				}));
 			}
 		}
 		else if (m_ProxyURL.schema == "socks")
 		{
 			/* handle upstream socks proxy */
 			if (!m_ProxyURL.port) m_ProxyURL.port = 9050; // default to tor default if not specified
-			m_proxy_resolver.async_resolve(m_ProxyURL.host, std::to_string(m_ProxyURL.port), std::bind(&HTTPReqHandler::HandleUpstreamProxyResolved, this, 
-			    std::placeholders::_1, std::placeholders::_2, [&](boost::asio::ip::tcp::endpoint ep) 
-			    {
-					m_proxysock->async_connect(ep, std::bind(&HTTPReqHandler::HandleUpstreamSocksProxyConnect, this, std::placeholders::_1));
-				}));
+			boost::asio::ip::tcp::resolver::query q(m_ProxyURL.host, std::to_string(m_ProxyURL.port));
+			m_proxy_resolver.async_resolve(q, std::bind(&HTTPReqHandler::HandleUpstreamProxyResolved, this, std::placeholders::_1, std::placeholders::_2, [&](boost::asio::ip::tcp::endpoint ep) {
+				m_proxysock->async_connect(ep, std::bind(&HTTPReqHandler::HandleUpstreamSocksProxyConnect, this, std::placeholders::_1));
+			}));
 		}
 		else
 		{
@@ -607,44 +604,57 @@ namespace proxy {
 		}
 	}
 
-	void HTTPReqHandler::HandleUpstreamProxyResolved(const boost::system::error_code & ec, boost::asio::ip::tcp::resolver::results_type endpoints, ProxyResolvedHandler handler)
+	void HTTPReqHandler::HandleUpstreamProxyResolved(const boost::system::error_code & ec, boost::asio::ip::tcp::resolver::iterator it, ProxyResolvedHandler handler)
 	{
 		if(ec) GenericProxyError(tr("Cannot resolve upstream proxy"), ec.message());
-		else handler(*endpoints.begin ());
+		else handler(*it);
 	}
 
 	void HTTPReqHandler::HandleUpstreamSocksProxyConnect(const boost::system::error_code & ec)
 	{
-		if(!ec) 
-		{
-			if(m_RequestURL.host.size() > 255) 
-			{
+		if(!ec) {
+			if(m_RequestURL.host.size() > 255) {
 				GenericProxyError(tr("Hostname is too long"), m_RequestURL.host);
 				return;
 			}
 			uint16_t port = m_RequestURL.port;
 			if(!port) port = 80;
 			LogPrint(eLogDebug, "HTTPProxy: Connected to SOCKS upstream");
+
 			std::string host = m_RequestURL.host;
-			auto s = shared_from_this ();
-			i2p::transport::Socks5Handshake (*m_proxysock, std::make_pair(host, port),
-				[s](const boost::system::error_code& ec)
-			    {
-					if (!ec)
-						s->SocksProxySuccess();
-					else
-						s->GenericProxyError(tr("SOCKS proxy error"), ec.message ());	
-				});
-			
-		} 
-		else 
-			GenericProxyError(tr("Cannot connect to upstream SOCKS proxy"), ec.message());
+			std::size_t reqsize = 0;
+			m_socks_buf[0] = '\x04';
+			m_socks_buf[1] = 1;
+			htobe16buf(m_socks_buf+2, port);
+			m_socks_buf[4] = 0;
+			m_socks_buf[5] = 0;
+			m_socks_buf[6] = 0;
+			m_socks_buf[7] = 1;
+			// user id
+			m_socks_buf[8] = 'i';
+			m_socks_buf[9] = '2';
+			m_socks_buf[10] = 'p';
+			m_socks_buf[11] = 'd';
+			m_socks_buf[12] = 0;
+			reqsize += 13;
+			memcpy(m_socks_buf+ reqsize, host.c_str(), host.size());
+			reqsize += host.size();
+			m_socks_buf[++reqsize] = 0;
+			boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_socks_buf, reqsize), boost::asio::transfer_all(), std::bind(&HTTPReqHandler::HandleSocksProxySendHandshake, this, std::placeholders::_1, std::placeholders::_2));
+		} else GenericProxyError(tr("Cannot connect to upstream SOCKS proxy"), ec.message());
+	}
+
+	void HTTPReqHandler::HandleSocksProxySendHandshake(const boost::system::error_code & ec, std::size_t bytes_transferred)
+	{
+		LogPrint(eLogDebug, "HTTPProxy: Upstream SOCKS handshake sent");
+		if(ec) GenericProxyError(tr("Cannot negotiate with SOCKS proxy"), ec.message());
+		else m_proxysock->async_read_some(boost::asio::buffer(m_socks_buf, 8), std::bind(&HTTPReqHandler::HandleSocksProxyReply, this, std::placeholders::_1, std::placeholders::_2));
 	}
 
 	void HTTPReqHandler::HandoverToUpstreamProxy()
 	{
 		LogPrint(eLogDebug, "HTTPProxy: Handover to SOCKS proxy");
-		auto connection = CreateSocketsPipe (GetOwner(), m_proxysock, m_sock);
+		auto connection = std::make_shared<i2p::client::TCPIPPipe>(GetOwner(), m_proxysock, m_sock);
 		m_sock = nullptr;
 		m_proxysock = nullptr;
 		GetOwner()->AddHandler(connection);
@@ -652,10 +662,11 @@ namespace proxy {
 		Terminate();
 	}
 
-	void HTTPReqHandler::HTTPConnect(std::string_view host, uint16_t port)
+	void HTTPReqHandler::HTTPConnect(const std::string & host, uint16_t port)
 	{
 		LogPrint(eLogDebug, "HTTPProxy: CONNECT ",host, ":", port);
-		if(str_rmatch(host, ".i2p"))
+		std::string hostname(host);
+		if(str_rmatch(hostname, ".i2p"))
 			GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleHTTPConnectStreamRequestComplete,
 				shared_from_this(), std::placeholders::_1), host, port);
 		else
@@ -703,6 +714,24 @@ namespace proxy {
 		}
 	}
 
+	void HTTPReqHandler::HandleSocksProxyReply(const boost::system::error_code & ec, std::size_t bytes_transferred)
+	{
+		if(!ec)
+		{
+			if(m_socks_buf[1] == 90) {
+				// success
+				SocksProxySuccess();
+			} else {
+				std::stringstream ss;
+				ss << "error code: ";
+				ss << (int) m_socks_buf[1];
+				std::string msg = ss.str();
+				GenericProxyError(tr("SOCKS proxy error"), msg);
+			}
+		}
+		else GenericProxyError(tr("No reply from SOCKS proxy"), ec.message());
+	}
+
 	void HTTPReqHandler::HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec)
 	{
 		if(!ec) {
@@ -753,10 +782,9 @@ namespace proxy {
 		Done (shared_from_this());
 	}
 
-	HTTPProxy::HTTPProxy(const std::string& name, const std::string& address, uint16_t port, 
-	    const std::string & outproxy, bool addresshelper, bool senduseragent, std::shared_ptr<i2p::client::ClientDestination> localDestination):
+	HTTPProxy::HTTPProxy(const std::string& name, const std::string& address, uint16_t port, const std::string & outproxy, bool addresshelper, std::shared_ptr<i2p::client::ClientDestination> localDestination):
 		TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()),
-		m_Name (name), m_OutproxyUrl (outproxy), m_Addresshelper (addresshelper), m_SendUserAgent (senduseragent)
+		m_Name (name), m_OutproxyUrl (outproxy), m_Addresshelper (addresshelper)
 	{
 	}
 
diff --git a/libi2pd_client/HTTPProxy.h b/libi2pd_client/HTTPProxy.h
index 507a87e2..d819a53c 100644
--- a/libi2pd_client/HTTPProxy.h
+++ b/libi2pd_client/HTTPProxy.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -15,15 +15,13 @@ namespace proxy {
 	{
 		public:
 
-			HTTPProxy(const std::string& name, const std::string& address, uint16_t port, const std::string & outproxy, 
-				bool addresshelper, bool senduseragent, std::shared_ptr<i2p::client::ClientDestination> localDestination);
+			HTTPProxy(const std::string& name, const std::string& address, uint16_t port, const std::string & outproxy, bool addresshelper, std::shared_ptr<i2p::client::ClientDestination> localDestination);
 			HTTPProxy(const std::string& name, const std::string& address, uint16_t port, std::shared_ptr<i2p::client::ClientDestination> localDestination = nullptr) :
-				HTTPProxy(name, address, port, "", true, false, localDestination) {} ;
+				HTTPProxy(name, address, port, "", true, localDestination) {} ;
 			~HTTPProxy() {};
 
 			std::string GetOutproxyURL() const { return m_OutproxyUrl; }
-			bool GetHelperSupport() const { return m_Addresshelper; }
-			bool GetSendUserAgent () const { return m_SendUserAgent; }
+			bool GetHelperSupport() { return m_Addresshelper; }
 
 		protected:
 
@@ -35,7 +33,7 @@ namespace proxy {
 
 			std::string m_Name;
 			std::string m_OutproxyUrl;
-			bool m_Addresshelper, m_SendUserAgent;
+			bool m_Addresshelper;
 	};
 } // http
 } // i2p
diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp
index 11278e7a..2c53e766 100644
--- a/libi2pd_client/I2CP.cpp
+++ b/libi2pd_client/I2CP.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -16,7 +16,6 @@
 #include "ClientContext.h"
 #include "Transports.h"
 #include "Signature.h"
-#include "Config.h"
 #include "I2CP.h"
 
 namespace i2p
@@ -24,22 +23,19 @@ namespace i2p
 namespace client
 {
 
-	I2CPDestination::I2CPDestination (boost::asio::io_context& service, std::shared_ptr<I2CPSession> owner,
-		std::shared_ptr<const i2p::data::IdentityEx> identity, bool isPublic, bool isSameThread,
-	    const std::map<std::string, std::string>& params):
+	I2CPDestination::I2CPDestination (boost::asio::io_service& service, std::shared_ptr<I2CPSession> owner,
+		std::shared_ptr<const i2p::data::IdentityEx> identity, bool isPublic, const std::map<std::string, std::string>& params):
 		LeaseSetDestination (service, isPublic, &params),
 		m_Owner (owner), m_Identity (identity), m_EncryptionKeyType (m_Identity->GetCryptoKeyType ()),
-		m_IsCreatingLeaseSet (false), m_IsSameThread (isSameThread), 
-		m_LeaseSetCreationTimer (service), m_ReadinessCheckTimer (service)
+		m_IsCreatingLeaseSet (false), m_LeaseSetCreationTimer (service)
 	{
 	}
 
 	void I2CPDestination::Stop ()
 	{
-		m_LeaseSetCreationTimer.cancel ();
-		m_ReadinessCheckTimer.cancel ();
 		LeaseSetDestination::Stop ();
 		m_Owner = nullptr;
+		m_LeaseSetCreationTimer.cancel ();
 	}
 
 	void I2CPDestination::SetEncryptionPrivateKey (const uint8_t * key)
@@ -79,13 +75,7 @@ namespace client
 		return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? (bool)m_ECIESx25519Decryptor : m_EncryptionKeyType == keyType;
 	}
 
-	i2p::data::CryptoKeyType I2CPDestination::GetPreferredCryptoType () const
-	{
-		if (m_ECIESx25519Decryptor)
-			return i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD;
-		return i2p::data::CRYPTO_KEY_TYPE_ELGAMAL;
-	}	
-		
+
 	void I2CPDestination::HandleDataMessage (const uint8_t * buf, size_t len)
 	{
 		uint32_t length = bufbe32toh (buf);
@@ -96,7 +86,7 @@ namespace client
 
 	void I2CPDestination::CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels)
 	{
-		boost::asio::post (GetService (), std::bind (&I2CPDestination::PostCreateNewLeaseSet, GetSharedFromThis (), tunnels));
+		GetService ().post (std::bind (&I2CPDestination::PostCreateNewLeaseSet, this, tunnels));
 	}
 
 	void I2CPDestination::PostCreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels)
@@ -106,33 +96,19 @@ namespace client
 			LogPrint (eLogInfo, "I2CP: LeaseSet is being created");
 			return;
 		}
-		m_ReadinessCheckTimer.cancel ();
-		auto pool = GetTunnelPool ();
-		if (!pool || pool->GetOutboundTunnels ().empty ())
-		{
-			// try again later
-			m_ReadinessCheckTimer.expires_from_now (boost::posix_time::seconds(I2CP_DESTINATION_READINESS_CHECK_INTERVAL));
-			m_ReadinessCheckTimer.async_wait(
-				[s=GetSharedFromThis (), tunnels=std::move(tunnels)](const boost::system::error_code& ecode)
-			    {
-					if (ecode != boost::asio::error::operation_aborted)
-						s->PostCreateNewLeaseSet (tunnels);
-				});	
-			return;
-		}	
 		uint8_t priv[256] = {0};
 		i2p::data::LocalLeaseSet ls (m_Identity, priv, tunnels); // we don't care about encryption key, we need leases only
 		m_LeaseSetExpirationTime = ls.GetExpirationTime ();
 		uint8_t * leases = ls.GetLeases ();
-		int numLeases = leases[-1];
-		if (m_Owner && numLeases)
+		leases[-1] = tunnels.size ();
+		if (m_Owner)
 		{
 			uint16_t sessionID = m_Owner->GetSessionID ();
 			if (sessionID != 0xFFFF)
 			{
 				m_IsCreatingLeaseSet = true;
 				htobe16buf (leases - 3, sessionID);
-				size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*numLeases;
+				size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*tunnels.size ();
 				m_Owner->SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l);
 				m_LeaseSetCreationTimer.expires_from_now (boost::posix_time::seconds (I2CP_LEASESET_CREATION_TIMEOUT));
 				auto s = GetSharedFromThis ();
@@ -146,8 +122,6 @@ namespace client
 				});
 			}
 		}
-		else
-			LogPrint (eLogError, "I2CP: Can't request LeaseSet");
 	}
 
 	void I2CPDestination::LeaseSetCreated (const uint8_t * buf, size_t len)
@@ -178,32 +152,20 @@ namespace client
 		memcpy (buf + 4, payload, len);
 		msg->len += len + 4;
 		msg->FillI2NPMessageHeader (eI2NPData);
+		auto s = GetSharedFromThis ();
 		auto remote = FindLeaseSet (ident);
 		if (remote)
 		{
-			if (m_IsSameThread)
-			{
-				// send right a way
-				bool sent = SendMsg (msg, remote);
-				if (m_Owner)
-					m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure);
-			}	
-			else
-			{	
-				// send in destination's thread
-				auto s = GetSharedFromThis ();
-				boost::asio::post (GetService (),
-					[s, msg, remote, nonce]()
-					{
-						bool sent = s->SendMsg (msg, remote);
-						if (s->m_Owner)
-							s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure);
-					});
-			}	
+			GetService ().post (
+				[s, msg, remote, nonce]()
+				{
+					bool sent = s->SendMsg (msg, remote);
+					if (s->m_Owner)
+						s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure);
+				});
 		}
 		else
 		{
-			auto s = GetSharedFromThis ();
 			RequestDestination (ident,
 				[s, msg, nonce](std::shared_ptr<i2p::data::LeaseSet> ls)
 				{
@@ -227,7 +189,6 @@ namespace client
 			LogPrint (eLogError, "I2CP: Failed to create remote session");
 			return false;
 		}
-		auto garlic = remoteSession->WrapSingleMessage (msg); // shared routing path mitgh be dropped here
 		auto path = remoteSession->GetSharedRoutingPath ();
 		std::shared_ptr<i2p::tunnel::OutboundTunnel> outboundTunnel;
 		std::shared_ptr<const i2p::data::Lease> remoteLease;
@@ -241,43 +202,35 @@ namespace client
 			else
 				remoteSession->SetSharedRoutingPath (nullptr);
 		}
-		if (!outboundTunnel || !remoteLease)
+		else
 		{
 			auto leases = remote->GetNonExpiredLeases (false); // without threshold
 			if (leases.empty ())
 				leases = remote->GetNonExpiredLeases (true); // with threshold
 			if (!leases.empty ())
 			{
-				auto pool = GetTunnelPool ();
-				remoteLease = leases[(pool ? pool->GetRng ()() : rand ()) % leases.size ()];
+				remoteLease = leases[rand () % leases.size ()];
 				auto leaseRouter = i2p::data::netdb.FindRouter (remoteLease->tunnelGateway);
 				outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (nullptr,
 					leaseRouter ? leaseRouter->GetCompatibleTransports (false) : (i2p::data::RouterInfo::CompatibleTransports)i2p::data::RouterInfo::eAllTransports);
 			}
 			if (remoteLease && outboundTunnel)
 				remoteSession->SetSharedRoutingPath (std::make_shared<i2p::garlic::GarlicRoutingPath> (
-					i2p::garlic::GarlicRoutingPath{outboundTunnel, remoteLease, 10000, 0})); // 10 secs RTT
+					i2p::garlic::GarlicRoutingPath{outboundTunnel, remoteLease, 10000, 0, 0})); // 10 secs RTT
 			else
 				remoteSession->SetSharedRoutingPath (nullptr);
 		}
-		m_Owner->AddRoutingSession (remote->GetIdentity ()->GetStandardIdentity ().signingKey + 96, remoteSession); // last 32 bytes
-		return SendMsg (garlic, outboundTunnel, remoteLease);
-	}
-
-	bool I2CPDestination::SendMsg (std::shared_ptr<I2NPMessage> garlic, 
-	    std::shared_ptr<i2p::tunnel::OutboundTunnel> outboundTunnel, std::shared_ptr<const i2p::data::Lease> remoteLease)
-	{
 		if (remoteLease && outboundTunnel)
 		{
-			outboundTunnel->SendTunnelDataMsgs (
-			    {
-					i2p::tunnel::TunnelMessageBlock
-					{
-						i2p::tunnel::eDeliveryTypeTunnel,
-						remoteLease->tunnelGateway, remoteLease->tunnelID,
-						garlic
-					}
+			std::vector<i2p::tunnel::TunnelMessageBlock> msgs;
+			auto garlic = remoteSession->WrapSingleMessage (msg);
+			msgs.push_back (i2p::tunnel::TunnelMessageBlock
+				{
+					i2p::tunnel::eDeliveryTypeTunnel,
+					remoteLease->tunnelGateway, remoteLease->tunnelID,
+					garlic
 				});
+			outboundTunnel->SendTunnelDataMsgs (msgs);
 			return true;
 		}
 		else
@@ -287,55 +240,13 @@ namespace client
 			else
 				LogPrint (eLogWarning, "I2CP: Failed to send message. No outbound tunnels");
 			return false;
-		}		
-	}	
-
-	bool I2CPDestination::SendMsg (const uint8_t * payload, size_t len, 
-		std::shared_ptr<i2p::garlic::GarlicRoutingSession> remoteSession, uint32_t nonce)
-	{
-		if (!remoteSession) return false;
-		auto path = remoteSession->GetSharedRoutingPath ();
-		if (!path) return false;
-		// get tunnels
-		std::shared_ptr<i2p::tunnel::OutboundTunnel> outboundTunnel;
-		std::shared_ptr<const i2p::data::Lease> remoteLease;
-		if (!remoteSession->CleanupUnconfirmedTags ()) // no stuck tags
-		{
-			outboundTunnel = path->outboundTunnel;
-			remoteLease = path->remoteLease;
 		}
-		else
-		{	
-			remoteSession->SetSharedRoutingPath (nullptr);
-			return false;
-		}	
-		// create Data message
-		auto msg = m_I2NPMsgsPool.AcquireSharedMt ();
-		uint8_t * buf = msg->GetPayload ();
-		htobe32buf (buf, len);
-		memcpy (buf + 4, payload, len);
-		msg->len += len + 4;
-		msg->FillI2NPMessageHeader (eI2NPData);
-		// wrap in gralic
-		auto garlic = remoteSession->WrapSingleMessage (msg);
-		// send
-		bool sent = SendMsg (garlic, outboundTunnel, remoteLease);
-		m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure);
-		if (!sent)
-			remoteSession->SetSharedRoutingPath (nullptr);
-		return sent;
 	}
 
-	void I2CPDestination::CleanupDestination ()
-	{
-		m_I2NPMsgsPool.CleanUpMt ();
-		if (m_Owner) m_Owner->CleanupRoutingSessions ();
-	}	
-		
 	RunnableI2CPDestination::RunnableI2CPDestination (std::shared_ptr<I2CPSession> owner,
 		std::shared_ptr<const i2p::data::IdentityEx> identity, bool isPublic, const std::map<std::string, std::string>& params):
 		RunnableService ("I2CP"),
-		I2CPDestination (GetIOService (), owner, identity, isPublic, false, params)
+		I2CPDestination (GetIOService (), owner, identity, isPublic, params)
 	{
 	}
 
@@ -364,8 +275,8 @@ namespace client
 	}
 
 	I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket):
-		m_Owner (owner), m_Socket (socket), m_SessionID (0xFFFF), m_MessageID (0), 
-		m_IsSendAccepted (true), m_IsSending (false)
+		m_Owner (owner), m_Socket (socket), m_SessionID (0xFFFF),
+		m_MessageID (0), m_IsSendAccepted (true), m_IsSending (false)
 	{
 	}
 
@@ -376,11 +287,6 @@ namespace client
 
 	void I2CPSession::Start ()
 	{
-		if (m_Socket)
-		{	
-			m_Socket->set_option (boost::asio::socket_base::receive_buffer_size (I2CP_MAX_MESSAGE_LENGTH));
-			m_Socket->set_option (boost::asio::socket_base::send_buffer_size (I2CP_MAX_MESSAGE_LENGTH));
-		}	
 		ReadProtocolByte ();
 	}
 
@@ -427,27 +333,7 @@ namespace client
 			if (m_PayloadLen > 0)
 			{
 				if (m_PayloadLen <= I2CP_MAX_MESSAGE_LENGTH)
-				{
-					if (!m_Socket) return;
-					boost::system::error_code ec;
-					size_t moreBytes = m_Socket->available(ec);
-					if (!ec)
-					{	
-						if (moreBytes >= m_PayloadLen)
-						{
-							// read and process payload immediately if available
-							moreBytes = boost::asio::read (*m_Socket, boost::asio::buffer(m_Payload, m_PayloadLen), boost::asio::transfer_all (), ec);
-							HandleReceivedPayload (ec, moreBytes);
-						}	
-						else	
-							ReceivePayload ();
-					}	
-					else
-					{
-						LogPrint (eLogWarning, "I2CP: Socket error: ", ec.message ());
-						Terminate ();
-					}	
-				}	
+					ReceivePayload ();
 				else
 				{
 					LogPrint (eLogError, "I2CP: Unexpected payload length ", m_PayloadLen);
@@ -533,7 +419,7 @@ namespace client
 		if (sendBuf)
 		{
 			if (m_SendQueue.GetSize () < I2CP_MAX_SEND_QUEUE_SIZE)
-				m_SendQueue.Add (std::move(sendBuf));
+				m_SendQueue.Add (sendBuf);
 			else
 			{
 				LogPrint (eLogWarning, "I2CP: Send queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE);
@@ -577,30 +463,30 @@ namespace client
 			m_IsSending = false;
 	}
 
-	std::string_view I2CPSession::ExtractString (const uint8_t * buf, size_t len) const
+	std::string I2CPSession::ExtractString (const uint8_t * buf, size_t len)
 	{
 		uint8_t l = buf[0];
 		if (l > len) l = len;
-		return { (const char *)(buf + 1), l };
+		return std::string ((const char *)(buf + 1), l);
 	}
 
-	size_t I2CPSession::PutString (uint8_t * buf, size_t len, std::string_view str)
+	size_t I2CPSession::PutString (uint8_t * buf, size_t len, const std::string& str)
 	{
 		auto l = str.length ();
 		if (l + 1 >= len) l = len - 1;
 		if (l > 255) l = 255; // 1 byte max
 		buf[0] = l;
-		memcpy (buf + 1, str.data (), l);
+		memcpy (buf + 1, str.c_str (), l);
 		return l + 1;
 	}
 
-	void I2CPSession::ExtractMapping (const uint8_t * buf, size_t len, std::map<std::string, std::string>& mapping) const
+	void I2CPSession::ExtractMapping (const uint8_t * buf, size_t len, std::map<std::string, std::string>& mapping)
 	// TODO: move to Base.cpp
 	{
 		size_t offset = 0;
 		while (offset < len)
 		{
-			auto param = ExtractString (buf + offset, len - offset);
+			std::string param = ExtractString (buf + offset, len - offset);
 			offset += param.length () + 1;
 			if (buf[offset] != '=')
 			{
@@ -609,7 +495,7 @@ namespace client
 			}
 			offset++;
 
-			auto value = ExtractString (buf + offset, len - offset);
+			std::string value = ExtractString (buf + offset, len - offset);
 			offset += value.length () + 1;
 			if (buf[offset] != ';')
 			{
@@ -617,7 +503,7 @@ namespace client
 				break;
 			}
 			offset++;
-			mapping.emplace (param, value);
+			mapping.insert (std::make_pair (param, value));
 		}
 	}
 
@@ -672,13 +558,13 @@ namespace client
 			if (!m_Destination)
 			{
 				m_Destination = m_Owner.IsSingleThread () ?
-					std::make_shared<I2CPDestination>(m_Owner.GetService (), shared_from_this (), identity, true, true, params):
+					std::make_shared<I2CPDestination>(m_Owner.GetService (), shared_from_this (), identity, true, params):
 					std::make_shared<RunnableI2CPDestination>(shared_from_this (), identity, true, params);
 				if (m_Owner.InsertSession (shared_from_this ()))
 				{
+					SendSessionStatusMessage (eI2CPSessionStatusCreated); // created
 					LogPrint (eLogDebug, "I2CP: Session ", m_SessionID, " created");
 					m_Destination->Start ();
-					SendSessionStatusMessage (eI2CPSessionStatusCreated); // created
 				}
 				else
 				{
@@ -698,7 +584,7 @@ namespace client
 			SendSessionStatusMessage (eI2CPSessionStatusInvalid); // invalid
 		}
 	}
-	
+
 	void I2CPSession::DestroySessionMessageHandler (const uint8_t * buf, size_t len)
 	{
 		SendSessionStatusMessage (eI2CPSessionStatusDestroyed); // destroy
@@ -783,26 +669,6 @@ namespace client
 		SendI2CPMessage (I2CP_MESSAGE_STATUS_MESSAGE, buf, 15);
 	}
 
-	void I2CPSession::AddRoutingSession (const i2p::data::IdentHash& signingKey, std::shared_ptr<i2p::garlic::GarlicRoutingSession> remoteSession)
-	{
-		if (!remoteSession) return;
-		remoteSession->SetAckRequestInterval (I2CP_SESSION_ACK_REQUEST_INTERVAL);
-		std::lock_guard<std::mutex> l(m_RoutingSessionsMutex);
-		m_RoutingSessions[signingKey] = remoteSession;
-	}
-
-	void I2CPSession::CleanupRoutingSessions ()
-	{
-		std::lock_guard<std::mutex> l(m_RoutingSessionsMutex);
-		for (auto it = m_RoutingSessions.begin (); it != m_RoutingSessions.end ();)
-		{
-			if (it->second->IsTerminated ())
-				it = m_RoutingSessions.erase (it);
-			else	
-				it++;
-		}	
-	}	
-		
 	void I2CPSession::CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len)
 	{
 		uint16_t sessionID = bufbe16toh (buf);
@@ -873,44 +739,19 @@ namespace client
 			size_t offset = 2;
 			if (m_Destination)
 			{
-				const uint8_t * ident = buf + offset;
-				size_t identSize = i2p::data::GetIdentityBufferLen (ident, len - offset);
-				if (identSize)
+				i2p::data::IdentityEx identity;
+				size_t identsize = identity.FromBuffer (buf + offset, len - offset);
+				if (identsize)
 				{
-					offset += identSize;
+					offset += identsize;
 					uint32_t payloadLen = bufbe32toh (buf + offset);
 					if (payloadLen + offset <= len)
 					{
 						offset += 4;
 						uint32_t nonce = bufbe32toh (buf + offset + payloadLen);
-						if (m_Destination->IsReady ())
-						{	
-							if (m_IsSendAccepted)
-								SendMessageStatusMessage (nonce, eI2CPMessageStatusAccepted); // accepted
-							std::shared_ptr<i2p::garlic::GarlicRoutingSession> remoteSession;
-							{
-								std::lock_guard<std::mutex> l(m_RoutingSessionsMutex);
-								auto it = m_RoutingSessions.find (ident + i2p::data::DEFAULT_IDENTITY_SIZE - 35); // 32 bytes signing key
-								if (it != m_RoutingSessions.end ())
-								{		
-									if (!it->second->IsTerminated ())
-										remoteSession = it->second;
-									else
-										m_RoutingSessions.erase (it);
-								}
-							}
-							if (!remoteSession || !m_Destination->SendMsg (buf + offset, payloadLen, remoteSession, nonce))
-							{	
-								i2p::data::IdentHash identHash;
-								SHA256(ident, identSize, identHash); // calculate ident hash, because we don't need full identity
-								m_Destination->SendMsgTo (buf + offset, payloadLen, identHash, nonce);
-							}	
-						}
-						else
-						{
-							LogPrint(eLogInfo, "I2CP: Destination is not ready");
-							SendMessageStatusMessage (nonce, eI2CPMessageStatusNoLocalTunnels);
-						}	
+						if (m_IsSendAccepted)
+							SendMessageStatusMessage (nonce, eI2CPMessageStatusAccepted); // accepted
+						m_Destination->SendMsgTo (buf + offset, payloadLen, identity.GetIdentHash (), nonce);
 					}
 					else
 						LogPrint(eLogError, "I2CP: Cannot send message, too big");
@@ -1050,12 +891,8 @@ namespace client
 	{
 		uint8_t limits[64];
 		memset (limits, 0, 64);
-		uint32_t limit; i2p::config::GetOption("i2cp.inboundlimit", limit);
-		if (!limit) limit = i2p::context.GetBandwidthLimit ();
-		htobe32buf (limits, limit); // inbound
-		i2p::config::GetOption("i2cp.outboundlimit", limit);
-		if (!limit) limit = i2p::context.GetBandwidthLimit ();
-		htobe32buf (limits + 4, limit); // outbound
+		htobe32buf (limits, i2p::transport::transports.GetInBandwidth ()); // inbound
+		htobe32buf (limits + 4, i2p::transport::transports.GetOutBandwidth ()); // outbound
 		SendI2CPMessage (I2CP_BANDWIDTH_LIMITS_MESSAGE, limits, 64);
 	}
 
@@ -1079,7 +916,7 @@ namespace client
 		if (sendBuf)
 		{
 			if (m_SendQueue.GetSize () < I2CP_MAX_SEND_QUEUE_SIZE)
-				m_SendQueue.Add (std::move(sendBuf));
+				m_SendQueue.Add (sendBuf);
 			else
 			{
 				LogPrint (eLogWarning, "I2CP: Send queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE);
@@ -1102,7 +939,7 @@ namespace client
 	I2CPServer::I2CPServer (const std::string& interface, uint16_t port, bool isSingleThread):
 		RunnableService ("I2CP"), m_IsSingleThread (isSingleThread),
 		m_Acceptor (GetIOService (),
-		boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(interface), port))
+		boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(interface), port))
 	{
 		memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers));
 		m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler;
@@ -1133,12 +970,12 @@ namespace client
 	void I2CPServer::Stop ()
 	{
 		m_Acceptor.cancel ();
-		
-		decltype(m_Sessions) sessions;
-		m_Sessions.swap (sessions);
-		for (auto& it: sessions)
-			it.second->Stop ();
-		
+		{
+			auto sessions = m_Sessions;
+			for (auto& it: sessions)
+				it.second->Stop ();
+		}
+		m_Sessions.clear ();
 		StopIOService ();
 	}
 
diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h
index 37c14dbb..f0081ef6 100644
--- a/libi2pd_client/I2CP.h
+++ b/libi2pd_client/I2CP.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -11,17 +11,13 @@
 
 #include <inttypes.h>
 #include <string>
-#include <string_view>
 #include <memory>
-#include <mutex>
 #include <thread>
 #include <map>
-#include <unordered_map>
 #include <boost/asio.hpp>
 #include "util.h"
 #include "Destination.h"
 #include "Streaming.h"
-#include "CryptoKey.h"
 
 namespace i2p
 {
@@ -32,8 +28,6 @@ namespace client
 	const size_t I2CP_MAX_MESSAGE_LENGTH = 65535;
 	const size_t I2CP_MAX_SEND_QUEUE_SIZE = 1024*1024; // in bytes, 1M
 	const int I2CP_LEASESET_CREATION_TIMEOUT = 10; // in seconds
-	const int I2CP_DESTINATION_READINESS_CHECK_INTERVAL = 5; // in seconds
-	const int I2CP_SESSION_ACK_REQUEST_INTERVAL = 12100; // in milliseconds
 
 	const size_t I2CP_HEADER_LENGTH_OFFSET = 0;
 	const size_t I2CP_HEADER_TYPE_OFFSET = I2CP_HEADER_LENGTH_OFFSET + 4;
@@ -64,7 +58,6 @@ namespace client
 		eI2CPMessageStatusAccepted = 1,
 		eI2CPMessageStatusGuaranteedSuccess = 4,
 		eI2CPMessageStatusGuaranteedFailure = 5,
-		eI2CPMessageStatusNoLocalTunnels = 16,
 		eI2CPMessageStatusNoLeaseSet = 21
 	};
 
@@ -85,12 +78,11 @@ namespace client
 	{
 		public:
 
-			I2CPDestination (boost::asio::io_context& service, std::shared_ptr<I2CPSession> owner,
-				std::shared_ptr<const i2p::data::IdentityEx> identity, bool isPublic, bool isSameThread, 
-			    const std::map<std::string, std::string>& params);
+			I2CPDestination (boost::asio::io_service& service, std::shared_ptr<I2CPSession> owner,
+				std::shared_ptr<const i2p::data::IdentityEx> identity, bool isPublic, const std::map<std::string, std::string>& params);
 			~I2CPDestination () {};
 
-			void Stop () override;
+			void Stop ();
 
 			void SetEncryptionPrivateKey (const uint8_t * key);
 			void SetEncryptionType (i2p::data::CryptoKeyType keyType) { m_EncryptionKeyType = keyType; };
@@ -98,36 +90,25 @@ namespace client
 			void LeaseSetCreated (const uint8_t * buf, size_t len); // called from I2CPSession
 			void LeaseSet2Created (uint8_t storeType, const uint8_t * buf, size_t len); // called from I2CPSession
 			void SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce); // called from I2CPSession
-			bool SendMsg (const uint8_t * payload, size_t len, std::shared_ptr<i2p::garlic::GarlicRoutingSession> remoteSession, uint32_t nonce);
-			
+
 			// implements LocalDestination
-			bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const override;
-			bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const override;
-			const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const override; // for 4 only
-			std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const override { return m_Identity; };
+			bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const;
+			bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const;
+			const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const; // for 4 only
+			std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const { return m_Identity; };
 
 		protected:
 
-			// GarlicDestination
-			i2p::data::CryptoKeyType GetRatchetsHighestCryptoType () const override 
-			{
-				return m_ECIESx25519Decryptor ? i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD : 0;
-			}
-			// LeaseSetDestination
-			void CleanupDestination () override;
-			i2p::data::CryptoKeyType GetPreferredCryptoType () const override;
 			// I2CP
-			void HandleDataMessage (const uint8_t * buf, size_t len) override;
-			void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels) override;
-			
+			void HandleDataMessage (const uint8_t * buf, size_t len);
+			void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels);
+
 		private:
 
 			std::shared_ptr<I2CPDestination> GetSharedFromThis ()
 			{ return std::static_pointer_cast<I2CPDestination>(shared_from_this ()); }
 			bool SendMsg (std::shared_ptr<I2NPMessage> msg, std::shared_ptr<const i2p::data::LeaseSet> remote);
-			bool SendMsg (std::shared_ptr<I2NPMessage> garlic, 
-				std::shared_ptr<i2p::tunnel::OutboundTunnel> outboundTunnel, std::shared_ptr<const i2p::data::Lease> remoteLease);
-			
+
 			void PostCreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels);
 
 		private:
@@ -139,8 +120,8 @@ namespace client
 			std::shared_ptr<i2p::crypto::ECIESX25519AEADRatchetDecryptor> m_ECIESx25519Decryptor;
 			uint8_t m_ECIESx25519PrivateKey[32];
 			uint64_t m_LeaseSetExpirationTime;
-			bool m_IsCreatingLeaseSet, m_IsSameThread;
-			boost::asio::deadline_timer m_LeaseSetCreationTimer, m_ReadinessCheckTimer;
+			bool m_IsCreatingLeaseSet;
+			boost::asio::deadline_timer m_LeaseSetCreationTimer;
 			i2p::util::MemoryPoolMt<I2NPMessageBuffer<I2NP_MAX_MESSAGE_SIZE> > m_I2NPMsgsPool;
 	};
 
@@ -174,8 +155,6 @@ namespace client
 			void SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len);
 			void SendMessagePayloadMessage (const uint8_t * payload, size_t len);
 			void SendMessageStatusMessage (uint32_t nonce, I2CPMessageStatus status);
-			void AddRoutingSession (const i2p::data::IdentHash& signingKey, std::shared_ptr<i2p::garlic::GarlicRoutingSession> remoteSession);
-			void CleanupRoutingSessions ();
 
 			// message handlers
 			void GetDateMessageHandler (const uint8_t * buf, size_t len);
@@ -202,12 +181,12 @@ namespace client
 
 			void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
 
-			std::string_view ExtractString (const uint8_t * buf, size_t len) const;
-			size_t PutString (uint8_t * buf, size_t len, std::string_view str);
-			void ExtractMapping (const uint8_t * buf, size_t len, std::map<std::string, std::string>& mapping) const;
+			std::string ExtractString (const uint8_t * buf, size_t len);
+			size_t PutString (uint8_t * buf, size_t len, const std::string& str);
+			void ExtractMapping (const uint8_t * buf, size_t len, std::map<std::string, std::string>& mapping);
 			void SendSessionStatusMessage (I2CPSessionStatus status);
 			void SendHostReplyMessage (uint32_t requestID, std::shared_ptr<const i2p::data::IdentityEx> identity);
-			
+
 		private:
 
 			I2CPServer& m_Owner;
@@ -216,8 +195,6 @@ namespace client
 			size_t m_PayloadLen;
 
 			std::shared_ptr<I2CPDestination> m_Destination;
-			std::mutex m_RoutingSessionsMutex;
-			std::unordered_map<i2p::data::IdentHash, std::shared_ptr<i2p::garlic::GarlicRoutingSession> > m_RoutingSessions; // signing key->session
 			uint16_t m_SessionID;
 			uint32_t m_MessageID;
 			bool m_IsSendAccepted;
@@ -238,7 +215,7 @@ namespace client
 
 			void Start ();
 			void Stop ();
-			auto& GetService () { return GetIOService (); };
+			boost::asio::io_service& GetService () { return GetIOService (); };
 			bool IsSingleThread () const { return m_IsSingleThread; };
 
 			bool InsertSession (std::shared_ptr<I2CPSession> session);
@@ -247,6 +224,8 @@ namespace client
 
 		private:
 
+			void Run ();
+
 			void Accept ();
 
 			void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<boost::asio::ip::tcp::socket> socket);
diff --git a/libi2pd_client/I2PService.cpp b/libi2pd_client/I2PService.cpp
index 4ec2648a..c30b7c9f 100644
--- a/libi2pd_client/I2PService.cpp
+++ b/libi2pd_client/I2PService.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -107,7 +107,7 @@ namespace client
 			m_ReadyTimerTriggered = false;
 	}
 
-	void I2PService::CreateStream (StreamRequestComplete streamRequestComplete, std::string_view dest, uint16_t port) {
+	void I2PService::CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, uint16_t port) {
 		assert(streamRequestComplete);
 		auto address = i2p::client::context.GetAddressBook ().GetAddress (dest);
 		if (address)
@@ -147,5 +147,196 @@ namespace client
 				m_LocalDestination->CreateStream (streamRequestComplete, address->blindedPublicKey, port);
 		}
 	}
+
+	TCPIPPipe::TCPIPPipe(I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> upstream, std::shared_ptr<boost::asio::ip::tcp::socket> downstream) : I2PServiceHandler(owner), m_up(upstream), m_down(downstream)
+	{
+		boost::asio::socket_base::receive_buffer_size option(TCP_IP_PIPE_BUFFER_SIZE);
+		upstream->set_option(option);
+		downstream->set_option(option);
+	}
+
+	TCPIPPipe::~TCPIPPipe()
+	{
+		Terminate();
+	}
+
+	void TCPIPPipe::Start()
+	{
+		AsyncReceiveUpstream();
+		AsyncReceiveDownstream();
+	}
+
+	void TCPIPPipe::Terminate()
+	{
+		if(Kill()) return;
+		if (m_up)
+		{
+			if (m_up->is_open())
+				m_up->close();
+			m_up = nullptr;
+		}
+		if (m_down)
+		{
+			if (m_down->is_open())
+				m_down->close();
+			m_down = nullptr;
+		}
+		Done(shared_from_this());
+	}
+
+	void TCPIPPipe::AsyncReceiveUpstream()
+	{
+		if (m_up)
+		{
+			m_up->async_read_some(boost::asio::buffer(m_upstream_to_down_buf, TCP_IP_PIPE_BUFFER_SIZE),
+				std::bind(&TCPIPPipe::HandleUpstreamReceived, shared_from_this(),
+					std::placeholders::_1, std::placeholders::_2));
+		}
+		else
+			LogPrint(eLogError, "TCPIPPipe: Upstream receive: No socket");
+	}
+
+	void TCPIPPipe::AsyncReceiveDownstream()
+	{
+		if (m_down) {
+			m_down->async_read_some(boost::asio::buffer(m_downstream_to_up_buf, TCP_IP_PIPE_BUFFER_SIZE),
+				std::bind(&TCPIPPipe::HandleDownstreamReceived, shared_from_this(),
+					std::placeholders::_1, std::placeholders::_2));
+		}
+		else
+			LogPrint(eLogError, "TCPIPPipe: Downstream receive: No socket");
+	}
+
+	void TCPIPPipe::UpstreamWrite(size_t len)
+	{
+		if (m_up)
+		{
+			LogPrint(eLogDebug, "TCPIPPipe: Upstream: ", (int) len, " bytes written");
+			boost::asio::async_write(*m_up, boost::asio::buffer(m_upstream_buf, len),
+				boost::asio::transfer_all(),
+				std::bind(&TCPIPPipe::HandleUpstreamWrite,
+					shared_from_this(),
+					std::placeholders::_1));
+		}
+		else
+			LogPrint(eLogError, "TCPIPPipe: Upstream write: no socket");
+	}
+
+	void TCPIPPipe::DownstreamWrite(size_t len)
+	{
+		if (m_down)
+		{
+			LogPrint(eLogDebug, "TCPIPPipe: Downstream: ", (int) len, " bytes written");
+			boost::asio::async_write(*m_down, boost::asio::buffer(m_downstream_buf, len),
+				boost::asio::transfer_all(),
+				std::bind(&TCPIPPipe::HandleDownstreamWrite,
+					shared_from_this(),
+					std::placeholders::_1));
+		}
+		else
+			LogPrint(eLogError, "TCPIPPipe: Downstream write: No socket");
+	}
+
+
+	void TCPIPPipe::HandleDownstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered)
+	{
+		LogPrint(eLogDebug, "TCPIPPipe: Downstream: ", (int) bytes_transfered, " bytes received");
+		if (ecode)
+		{
+			LogPrint(eLogError, "TCPIPPipe: Downstream read error:" , ecode.message());
+			if (ecode != boost::asio::error::operation_aborted)
+				Terminate();
+		} else {
+			if (bytes_transfered > 0 )
+				memcpy(m_upstream_buf, m_downstream_to_up_buf, bytes_transfered);
+			UpstreamWrite(bytes_transfered);
+		}
+	}
+
+	void TCPIPPipe::HandleDownstreamWrite(const boost::system::error_code & ecode) {
+		if (ecode)
+		{
+			LogPrint(eLogError, "TCPIPPipe: Downstream write error:" , ecode.message());
+			if (ecode != boost::asio::error::operation_aborted)
+				Terminate();
+		}
+		else
+			AsyncReceiveUpstream();
+	}
+
+	void TCPIPPipe::HandleUpstreamWrite(const boost::system::error_code & ecode) {
+		if (ecode)
+		{
+			LogPrint(eLogError, "TCPIPPipe: Upstream write error:" , ecode.message());
+			if (ecode != boost::asio::error::operation_aborted)
+				Terminate();
+		}
+		else
+			AsyncReceiveDownstream();
+	}
+
+	void TCPIPPipe::HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered)
+	{
+		LogPrint(eLogDebug, "TCPIPPipe: Upstream ", (int)bytes_transfered, " bytes received");
+		if (ecode)
+		{
+			LogPrint(eLogError, "TCPIPPipe: Upstream read error:" , ecode.message());
+			if (ecode != boost::asio::error::operation_aborted)
+				Terminate();
+		} else {
+			if (bytes_transfered > 0 )
+				memcpy(m_downstream_buf, m_upstream_to_down_buf, bytes_transfered);
+			DownstreamWrite(bytes_transfered);
+		}
+	}
+
+	void TCPIPAcceptor::Start ()
+	{
+		m_Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService (), m_LocalEndpoint));
+		// update the local end point in case port has been set zero and got updated now
+		m_LocalEndpoint = m_Acceptor->local_endpoint();
+		m_Acceptor->listen ();
+		Accept ();
+	}
+
+	void TCPIPAcceptor::Stop ()
+	{
+		if (m_Acceptor)
+		{
+			m_Acceptor->close();
+			m_Acceptor.reset (nullptr);
+		}
+		m_Timer.cancel ();
+		ClearHandlers();
+	}
+
+	void TCPIPAcceptor::Accept ()
+	{
+		auto newSocket = std::make_shared<boost::asio::ip::tcp::socket> (GetService ());
+		m_Acceptor->async_accept (*newSocket, std::bind (&TCPIPAcceptor::HandleAccept, this,
+			std::placeholders::_1, newSocket));
+	}
+
+	void TCPIPAcceptor::HandleAccept (const boost::system::error_code& ecode, std::shared_ptr<boost::asio::ip::tcp::socket> socket)
+	{
+		if (!ecode)
+		{
+			LogPrint(eLogDebug, "I2PService: ", GetName(), " accepted");
+			auto handler = CreateHandler(socket);
+			if (handler)
+			{
+				AddHandler(handler);
+				handler->Handle();
+			}
+			else
+				socket->close();
+			Accept();
+		}
+		else
+		{
+			if (ecode != boost::asio::error::operation_aborted)
+				LogPrint (eLogError, "I2PService: ", GetName(), " closing socket on accept because: ", ecode.message ());
+		}
+	}
 }
 }
diff --git a/libi2pd_client/I2PService.h b/libi2pd_client/I2PService.h
index d19c28e2..4f67e19c 100644
--- a/libi2pd_client/I2PService.h
+++ b/libi2pd_client/I2PService.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -59,9 +59,9 @@ namespace client
 				if (dest) dest->Acquire ();
 				m_LocalDestination = dest;
 			}
-			void CreateStream (StreamRequestComplete streamRequestComplete, std::string_view dest, uint16_t port = 0);
+			void CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, uint16_t port = 0);
 			void CreateStream(StreamRequestComplete complete, std::shared_ptr<const Address> address, uint16_t port);
-			auto& GetService () { return m_LocalDestination->GetService (); }
+			inline boost::asio::io_service& GetService () { return m_LocalDestination->GetService (); }
 
 			virtual void Start () = 0;
 			virtual void Stop () = 0;
@@ -99,7 +99,6 @@ namespace client
 			virtual ~I2PServiceHandler() { }
 			//If you override this make sure you call it from the children
 			virtual void Handle() {}; //Start handling the socket
-			virtual void Start () {}; 
 
 			void Terminate () { Kill (); };
 
@@ -120,171 +119,72 @@ namespace client
 			std::atomic<bool> m_Dead; //To avoid cleaning up multiple times
 	};
 
-	const size_t SOCKETS_PIPE_BUFFER_SIZE = 8192 * 8;
+	const size_t TCP_IP_PIPE_BUFFER_SIZE = 8192 * 8;
 
-	// bidirectional pipe for 2 stream sockets
-	template<typename SocketUpstream, typename SocketDownstream>
-	class SocketsPipe: public I2PServiceHandler, 
-		public std::enable_shared_from_this<SocketsPipe<SocketUpstream, SocketDownstream> >
+	// bidirectional pipe for 2 tcp/ip sockets
+	class TCPIPPipe: public I2PServiceHandler, public std::enable_shared_from_this<TCPIPPipe>
 	{
 		public:
 
-			SocketsPipe(I2PService * owner, std::shared_ptr<SocketUpstream> upstream, std::shared_ptr<SocketDownstream> downstream):
-				I2PServiceHandler(owner), m_up(upstream), m_down(downstream)
-			{
-				boost::asio::socket_base::receive_buffer_size option(SOCKETS_PIPE_BUFFER_SIZE);
-				upstream->set_option(option);
-				downstream->set_option(option);
-			}	
-			~SocketsPipe() { Terminate(); }
-			
-			void Start() override
-			{
-				Transfer (m_up, m_down, m_upstream_to_down_buf, SOCKETS_PIPE_BUFFER_SIZE); // receive from upstream
-				Transfer (m_down, m_up, m_downstream_to_up_buf, SOCKETS_PIPE_BUFFER_SIZE); // receive from upstream
-			}	
-
-		private:
-
-			void Terminate()
-			{
-				if(Kill()) return;
-				if (m_up)
-				{
-					if (m_up->is_open())
-						m_up->close();
-					m_up = nullptr;
-				}
-				if (m_down)
-				{
-					if (m_down->is_open())
-						m_down->close();
-					m_down = nullptr;
-				}
-				Done(SocketsPipe<SocketUpstream, SocketDownstream>::shared_from_this());
-			}
-			
-			template<typename From, typename To>
-			void Transfer (std::shared_ptr<From> from, std::shared_ptr<To> to, uint8_t * buf, size_t len)
-			{
-				if (!from || !to || !buf) return;
-				auto s = SocketsPipe<SocketUpstream, SocketDownstream>::shared_from_this ();
-				from->async_read_some(boost::asio::buffer(buf, len),
-					[from, to, s, buf, len](const boost::system::error_code& ecode, std::size_t transferred)
-				    {
-						if (ecode == boost::asio::error::operation_aborted) return;
-						if (!ecode)
-						{
-							boost::asio::async_write(*to, boost::asio::buffer(buf, transferred), boost::asio::transfer_all(),
-								[from, to, s, buf, len](const boost::system::error_code& ecode, std::size_t transferred)
-				    			{
-									(void) transferred;
-									if (ecode == boost::asio::error::operation_aborted) return;
-									if (!ecode)
-										s->Transfer (from, to, buf, len);
-									else
-									{
-										LogPrint(eLogWarning, "SocketsPipe: Write error:" , ecode.message());
-										s->Terminate();
-									}	
-								});	
-						}	
-						else
-						{
-							LogPrint(eLogWarning, "SocketsPipe: Read error:" , ecode.message());
-							s->Terminate();
-						}	
-					});	
-			}
-			
-		private:
-
-			uint8_t m_upstream_to_down_buf[SOCKETS_PIPE_BUFFER_SIZE], m_downstream_to_up_buf[SOCKETS_PIPE_BUFFER_SIZE];			
-			std::shared_ptr<SocketUpstream> m_up;
-			std::shared_ptr<SocketDownstream> m_down;
-	};
-
-	template<typename SocketUpstream, typename SocketDownstream>
-	std::shared_ptr<I2PServiceHandler> CreateSocketsPipe (I2PService * owner, std::shared_ptr<SocketUpstream> upstream, std::shared_ptr<SocketDownstream> downstream)
-	{
-		return std::make_shared<SocketsPipe<SocketUpstream, SocketDownstream> >(owner, upstream, downstream);
-	}	
-	
-	//This is a service that listens for connections on the IP network or local socket and interacts with I2P
-	template<typename Protocol>
-	class ServiceAcceptor: public I2PService
-	{
-		public:
-
-			ServiceAcceptor (const typename Protocol::endpoint& localEndpoint, std::shared_ptr<ClientDestination> localDestination = nullptr) :
-				I2PService(localDestination), m_LocalEndpoint (localEndpoint) {}
-			
-			virtual ~ServiceAcceptor () { Stop(); }
-			void Start () override
-			{
-				m_Acceptor.reset (new typename Protocol::acceptor (GetService (), m_LocalEndpoint));
-				// update the local end point in case port has been set zero and got updated now
-				m_LocalEndpoint = m_Acceptor->local_endpoint();
-				m_Acceptor->listen ();
-				Accept ();
-			}	
-			void Stop () override
-			{	
-				if (m_Acceptor)
-				{
-					m_Acceptor->close();
-					m_Acceptor.reset (nullptr);
-				}
-				ClearHandlers();
-			}
-			const typename Protocol::endpoint& GetLocalEndpoint () const { return m_LocalEndpoint; };
-
-			const char* GetName() override { return "Generic TCP/IP accepting daemon"; }
+			TCPIPPipe(I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> upstream, std::shared_ptr<boost::asio::ip::tcp::socket> downstream);
+			~TCPIPPipe();
+			void Start();
 
 		protected:
 
-			virtual std::shared_ptr<I2PServiceHandler> CreateHandler(std::shared_ptr<typename Protocol::socket> socket) = 0;
+			void Terminate();
+			void AsyncReceiveUpstream();
+			void AsyncReceiveDownstream();
+			void HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transferred);
+			void HandleDownstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transferred);
+			void HandleUpstreamWrite(const boost::system::error_code & ecode);
+			void HandleDownstreamWrite(const boost::system::error_code & ecode);
+			void UpstreamWrite(size_t len);
+			void DownstreamWrite(size_t len);
 
 		private:
 
-			void Accept()
-			{
-				auto newSocket = std::make_shared<typename Protocol::socket> (GetService ());
-				m_Acceptor->async_accept (*newSocket,
-					[newSocket, this](const boost::system::error_code& ecode)
-				    {
-						if (ecode == boost::asio::error::operation_aborted) return;
-						if (!ecode)
-						{
-							LogPrint(eLogDebug, "ServiceAcceptor: ", GetName(), " accepted");
-							auto handler = CreateHandler(newSocket);
-							if (handler)
-							{
-								AddHandler(handler);
-								handler->Handle();
-							}
-							else
-								newSocket->close();
-							Accept();
-						}	
-						else
-							LogPrint (eLogError, "ServiceAcceptor: ", GetName(), " closing socket on accept because: ", ecode.message ());
-					});	
-			}	
-			
-		private:
-			
-			typename Protocol::endpoint m_LocalEndpoint;
-			std::unique_ptr<typename Protocol::acceptor> m_Acceptor;
+			uint8_t m_upstream_to_down_buf[TCP_IP_PIPE_BUFFER_SIZE], m_downstream_to_up_buf[TCP_IP_PIPE_BUFFER_SIZE];
+			uint8_t m_upstream_buf[TCP_IP_PIPE_BUFFER_SIZE], m_downstream_buf[TCP_IP_PIPE_BUFFER_SIZE];
+			std::shared_ptr<boost::asio::ip::tcp::socket> m_up, m_down;
 	};
 
-	class TCPIPAcceptor: public ServiceAcceptor<boost::asio::ip::tcp>
+	/* TODO: support IPv6 too */
+	//This is a service that listens for connections on the IP network and interacts with I2P
+	class TCPIPAcceptor: public I2PService
 	{
 		public:
 
 			TCPIPAcceptor (const std::string& address, uint16_t port, std::shared_ptr<ClientDestination> localDestination = nullptr) :
-				ServiceAcceptor (boost::asio::ip::tcp::endpoint (boost::asio::ip::make_address(address), port), localDestination) {}
-	};	
+				I2PService(localDestination),
+				m_LocalEndpoint (boost::asio::ip::address::from_string(address), port),
+				m_Timer (GetService ()) {}
+			TCPIPAcceptor (const std::string& address, uint16_t port, i2p::data::SigningKeyType kt) :
+				I2PService(kt),
+				m_LocalEndpoint (boost::asio::ip::address::from_string(address), port),
+				m_Timer (GetService ()) {}
+			virtual ~TCPIPAcceptor () { TCPIPAcceptor::Stop(); }
+			//If you override this make sure you call it from the children
+			void Start ();
+			//If you override this make sure you call it from the children
+			void Stop ();
+
+			const boost::asio::ip::tcp::endpoint& GetLocalEndpoint () const { return m_LocalEndpoint; };
+
+			virtual const char* GetName() { return "Generic TCP/IP accepting daemon"; }
+
+		protected:
+
+			virtual std::shared_ptr<I2PServiceHandler> CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket) = 0;
+
+		private:
+
+			void Accept();
+			void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<boost::asio::ip::tcp::socket> socket);
+			boost::asio::ip::tcp::endpoint m_LocalEndpoint;
+			std::unique_ptr<boost::asio::ip::tcp::acceptor> m_Acceptor;
+			boost::asio::deadline_timer m_Timer;
+	};
 }
 }
 
diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp
index d6436c78..ad4e14b8 100644
--- a/libi2pd_client/I2PTunnel.cpp
+++ b/libi2pd_client/I2PTunnel.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -32,7 +32,8 @@ namespace client
 
 	I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
 		std::shared_ptr<const i2p::data::LeaseSet> leaseSet, uint16_t port):
-		I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ())
+		I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()),
+		m_IsQuiet (true)
 	{
 		m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port);
 	}
@@ -40,13 +41,14 @@ namespace client
 	I2PTunnelConnection::I2PTunnelConnection (I2PService * owner,
 		std::shared_ptr<boost::asio::ip::tcp::socket> socket, std::shared_ptr<i2p::stream::Stream> stream):
 		I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream),
-		m_RemoteEndpoint (socket->remote_endpoint ())
+		m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true)
 	{
 	}
 
 	I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
-		const boost::asio::ip::tcp::endpoint& target,std::shared_ptr<boost::asio::ssl::context> sslCtx):
-		I2PServiceHandler(owner), m_Stream (stream), m_RemoteEndpoint (target)
+		const boost::asio::ip::tcp::endpoint& target, bool quiet,
+	    std::shared_ptr<boost::asio::ssl::context> sslCtx):
+		I2PServiceHandler(owner), m_Stream (stream), m_RemoteEndpoint (target), m_IsQuiet (quiet)
 	{
 		m_Socket = std::make_shared<boost::asio::ip::tcp::socket> (owner->GetService ());
 		if (sslCtx)
@@ -290,7 +292,18 @@ namespace client
 
 	void I2PTunnelConnection::Established ()
 	{
-		StreamReceive ();
+		if (m_IsQuiet)
+			StreamReceive ();
+		else
+		{
+			// send destination first like received from I2P
+			std::string dest = m_Stream->GetRemoteIdentity ()->ToBase64 ();
+			dest += "\n";
+			if(sizeof(m_StreamBuffer) >= dest.size()) {
+				memcpy (m_StreamBuffer, dest.c_str (), dest.size ());
+			}
+			HandleStreamReceive (boost::system::error_code (), dest.size ());
+		}
 		Receive ();
 	}
 
@@ -362,10 +375,10 @@ namespace client
 	}
 
 	I2PServerTunnelConnectionHTTP::I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
-		const boost::asio::ip::tcp::endpoint& target, const std::string& host, const std::string& XI2P,
+		const boost::asio::ip::tcp::endpoint& target, const std::string& host,
 	    std::shared_ptr<boost::asio::ssl::context> sslCtx):
-		I2PTunnelConnection (owner, stream, target, sslCtx), m_Host (host), m_XI2P (XI2P),
-		m_HeaderSent (false), m_ResponseHeaderSent (false)
+		I2PTunnelConnection (owner, stream, target, true, sslCtx), m_Host (host),
+		m_HeaderSent (false), m_ResponseHeaderSent (false), m_From (stream->GetRemoteIdentity ())
 	{
 		if (sslCtx)
 			SSL_set_tlsext_host_name(GetSSL ()->native_handle(), host.c_str ());
@@ -391,7 +404,7 @@ namespace client
 					else
 					{
 						// strip up some headers
-						static const std::array<std::string_view, 2> excluded // list of excluded headers
+						static const std::vector<std::string> excluded // list of excluded headers
 						{
 							"Keep-Alive:", "X-I2P"
 						};
@@ -435,12 +448,17 @@ namespace client
 				if (!connection)
 					m_OutHeader << "Connection: close\r\n";
 				// add X-I2P fields
-				m_OutHeader << m_XI2P;
-				// end of header
-				m_OutHeader << "\r\n"; 
-				
+				if (m_From)
+				{
+					m_OutHeader << X_I2P_DEST_B32 << ": " << context.GetAddressBook ().ToAddress(m_From->GetIdentHash ()) << "\r\n";
+					m_OutHeader << X_I2P_DEST_HASH << ": " << m_From->GetIdentHash ().ToBase64 () << "\r\n";
+					m_OutHeader << X_I2P_DEST_B64 << ": " << m_From->ToBase64 () << "\r\n";
+				}
+
+				m_OutHeader << "\r\n"; // end of header
 				m_OutHeader << m_InHeader.str ().substr (m_InHeader.tellg ()); // data right after header
 				m_InHeader.str ("");
+				m_From = nullptr;
 				m_HeaderSent = true;
 				I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ());
 			}
@@ -474,7 +492,7 @@ namespace client
 					if (line == "\r") endOfHeader = true;
 					else
 					{
-						static const std::array<std::string_view, 5> excluded // list of excluded headers
+						static const std::vector<std::string> excluded // list of excluded headers
 						{
 							"Server:", "Date:", "X-Runtime:", "X-Powered-By:", "Proxy"
 						};
@@ -515,7 +533,7 @@ namespace client
 	I2PTunnelConnectionIRC::I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
 		const boost::asio::ip::tcp::endpoint& target, const std::string& webircpass,
 	    std::shared_ptr<boost::asio::ssl::context> sslCtx):
-		I2PTunnelConnection (owner, stream, target, sslCtx), m_From (stream->GetRemoteIdentity ()),
+		I2PTunnelConnection (owner, stream, target, true, sslCtx), m_From (stream->GetRemoteIdentity ()),
 		m_NeedsWebIrc (webircpass.length() ? true : false), m_WebircPass (webircpass)
 	{
 	}
@@ -699,7 +717,7 @@ namespace client
 	{
 		m_Endpoint.port (m_Port);
 		boost::system::error_code ec;
-		auto addr = boost::asio::ip::make_address (m_Address, ec);
+		auto addr = boost::asio::ip::address::from_string (m_Address, ec);
 		if (!ec)
 		{
 			m_Endpoint.address (addr);
@@ -708,7 +726,7 @@ namespace client
 		else
 		{
 			auto resolver = std::make_shared<boost::asio::ip::tcp::resolver>(GetService ());
-			resolver->async_resolve (m_Address, "",
+			resolver->async_resolve (boost::asio::ip::tcp::resolver::query (m_Address, ""),
 				std::bind (&I2PServerTunnel::HandleResolve, this,
 					std::placeholders::_1, std::placeholders::_2, resolver));
 		}
@@ -725,7 +743,7 @@ namespace client
 		ClearHandlers ();
 	}
 
-	void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::results_type endpoints,
+	void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
 		std::shared_ptr<boost::asio::ip::tcp::resolver> resolver)
 	{
 		if (!ecode)
@@ -734,9 +752,10 @@ namespace client
 			boost::asio::ip::tcp::endpoint ep;
 			if (m_LocalAddress)
 			{
-				for (const auto& it: endpoints)
+				boost::asio::ip::tcp::resolver::iterator end;
+				while (it != end)
 				{
-					ep = it;
+					ep = *it;
 					if (!ep.address ().is_unspecified ())
 					{
 						if (ep.address ().is_v4 ())
@@ -755,26 +774,27 @@ namespace client
 						}
 					}
 					if (found) break;
+					it++;
 				}
 			}
 			else
 			{
 				found = true;
-				ep = *endpoints.begin (); // first available
+				ep = *it; // first available
 			}
 			if (!found)
 			{
-				LogPrint (eLogError, "I2PTunnel: Unable to resolve ", m_Address, " to compatible address");
+				LogPrint (eLogError, "I2PTunnel: Unable to resolve to compatible address");
 				return;
 			}
 
 			auto addr = ep.address ();
-			LogPrint (eLogInfo, "I2PTunnel: Server tunnel ", (*endpoints.begin ()).host_name (), " has been resolved to ", addr);
+			LogPrint (eLogInfo, "I2PTunnel: Server tunnel ", (*it).host_name (), " has been resolved to ", addr);
 			m_Endpoint.address (addr);
 			Accept ();
 		}
 		else
-			LogPrint (eLogError, "I2PTunnel: Unable to resolve server tunnel address ", m_Address, ": ", ecode.message ());
+			LogPrint (eLogError, "I2PTunnel: Unable to resolve server tunnel address: ", ecode.message ());
 	}
 
 	void I2PServerTunnel::SetAccessList (const std::set<i2p::data::IdentHash>& accessList)
@@ -786,7 +806,7 @@ namespace client
 	void I2PServerTunnel::SetLocalAddress (const std::string& localAddress)
 	{
 		boost::system::error_code ec;
-		auto addr = boost::asio::ip::make_address(localAddress, ec);
+		auto addr = boost::asio::ip::address::from_string(localAddress, ec);
 		if (!ec)
 			m_LocalAddress.reset (new boost::asio::ip::address (addr));
 		else
@@ -844,7 +864,7 @@ namespace client
 
 	std::shared_ptr<I2PTunnelConnection> I2PServerTunnel::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream)
 	{
-		return std::make_shared<I2PTunnelConnection> (this, stream, GetEndpoint (), m_SSLCtx);
+		return std::make_shared<I2PTunnelConnection> (this, stream, GetEndpoint (), true, m_SSLCtx);
 
 	}
 
@@ -858,17 +878,7 @@ namespace client
 
 	std::shared_ptr<I2PTunnelConnection> I2PServerTunnelHTTP::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream)
 	{
-		if (m_XI2P.empty () || stream->GetRemoteIdentity () != m_From.lock ())
-		{
-			auto from = stream->GetRemoteIdentity ();
-			m_From = from;
-			std::stringstream ss;
-			ss << X_I2P_DEST_B32 << ": " << context.GetAddressBook ().ToAddress(from->GetIdentHash ()) << "\r\n";
-			ss << X_I2P_DEST_HASH << ": " << from->GetIdentHash ().ToBase64 () << "\r\n";
-			ss << X_I2P_DEST_B64 << ": " << from->ToBase64 () << "\r\n";
-			m_XI2P = ss.str ();
-		}	
-		return std::make_shared<I2PServerTunnelConnectionHTTP> (this, stream, GetEndpoint (), m_Host, m_XI2P, GetSSLCtx ());
+		return std::make_shared<I2PServerTunnelConnectionHTTP> (this, stream, GetEndpoint (), m_Host, GetSSLCtx ());
 	}
 
 	I2PServerTunnelIRC::I2PServerTunnelIRC (const std::string& name, const std::string& address,
diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h
index 7d4c3400..b94eb9e4 100644
--- a/libi2pd_client/I2PTunnel.h
+++ b/libi2pd_client/I2PTunnel.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -27,13 +27,13 @@ namespace i2p
 {
 namespace client
 {
-	const size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 16384;
+	const size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 65536;
 	const int I2P_TUNNEL_CONNECTION_MAX_IDLE = 3600; // in seconds
 	const int I2P_TUNNEL_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds
 	// for HTTP tunnels
-	constexpr char X_I2P_DEST_HASH[] = "X-I2P-DestHash"; // hash in base64
-	constexpr char X_I2P_DEST_B64[] = "X-I2P-DestB64"; // full address in base64
-	constexpr char X_I2P_DEST_B32[] = "X-I2P-DestB32"; // .b32.i2p address
+	const char X_I2P_DEST_HASH[] = "X-I2P-DestHash"; // hash in base64
+	const char X_I2P_DEST_B64[] = "X-I2P-DestB64"; // full address in base64
+	const char X_I2P_DEST_B32[] = "X-I2P-DestB32"; // .b32.i2p address
 	const int I2P_TUNNEL_HTTP_MAX_HEADER_SIZE = 8192;
 
 	class I2PTunnelConnection: public I2PServiceHandler, public std::enable_shared_from_this<I2PTunnelConnection>
@@ -45,7 +45,7 @@ namespace client
 			I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
 				std::shared_ptr<i2p::stream::Stream> stream); // to I2P using simplified API
 			I2PTunnelConnection (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
-				const boost::asio::ip::tcp::endpoint& target,
+				const boost::asio::ip::tcp::endpoint& target, bool quiet = true,
 			    std::shared_ptr<boost::asio::ssl::context> sslCtx = nullptr); // from I2P
 			~I2PTunnelConnection ();
 			void I2PConnect (const uint8_t * msg = nullptr, size_t len = 0);
@@ -54,27 +54,25 @@ namespace client
 
 		protected:
 
-			virtual void Established ();
 			void Terminate ();
 
 			void Receive ();
 			void StreamReceive ();
-			void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
 			virtual void Write (const uint8_t * buf, size_t len); // can be overloaded
 			virtual void WriteToStream (const uint8_t * buf, size_t len); // can be overloaded
 
 			std::shared_ptr<boost::asio::ip::tcp::socket> GetSocket () const { return m_Socket; };
-			std::shared_ptr<i2p::stream::Stream> GetStream () const { return m_Stream; };
 			std::shared_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket&> > GetSSL () const { return m_SSL; };
-			uint8_t * GetStreamBuffer () { return m_StreamBuffer; };
-		
+
 		private:
 
 			void HandleConnect (const boost::system::error_code& ecode);
 			void HandleHandshake (const boost::system::error_code& ecode);
+			void Established ();
 			void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
 			void HandleWrite (const boost::system::error_code& ecode);
-			
+			void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
+
 		private:
 
 			uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE];
@@ -82,6 +80,7 @@ namespace client
 			std::shared_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket&> > m_SSL;
 			std::shared_ptr<i2p::stream::Stream> m_Stream;
 			boost::asio::ip::tcp::endpoint m_RemoteEndpoint;
+			bool m_IsQuiet; // don't send destination
 	};
 
 	class I2PClientTunnelConnectionHTTP: public I2PTunnelConnection
@@ -95,7 +94,7 @@ namespace client
 
 		protected:
 
-			void Write (const uint8_t * buf, size_t len) override;
+			void Write (const uint8_t * buf, size_t len);
 
 		private:
 
@@ -108,19 +107,20 @@ namespace client
 		public:
 
 			I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
-				const boost::asio::ip::tcp::endpoint& target, const std::string& host, const std::string& XI2P,
+				const boost::asio::ip::tcp::endpoint& target, const std::string& host,
 			    std::shared_ptr<boost::asio::ssl::context> sslCtx = nullptr);
 
 		protected:
 
-			void Write (const uint8_t * buf, size_t len) override;
-			void WriteToStream (const uint8_t * buf, size_t len) override;
+			void Write (const uint8_t * buf, size_t len);
+			void WriteToStream (const uint8_t * buf, size_t len);
 
 		private:
 
-			std::string m_Host, m_XI2P;
+			std::string m_Host;
 			std::stringstream m_InHeader, m_OutHeader;
 			bool m_HeaderSent, m_ResponseHeaderSent;
+			std::shared_ptr<const i2p::data::IdentityEx> m_From;
 	};
 
 	class I2PTunnelConnectionIRC: public I2PTunnelConnection
@@ -133,7 +133,7 @@ namespace client
 
 		protected:
 
-			void Write (const uint8_t * buf, size_t len) override;
+			void Write (const uint8_t * buf, size_t len);
 
 		private:
 
@@ -208,7 +208,7 @@ namespace client
 
 		private:
 
-			void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::results_type endpoints,
+			void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
 				std::shared_ptr<boost::asio::ip::tcp::resolver> resolver);
 
 			void Accept ();
@@ -242,8 +242,7 @@ namespace client
 
 		private:
 
-			std::string m_Host, m_XI2P;
-			std::weak_ptr<const i2p::data::IdentityEx> m_From;
+			std::string m_Host;
 	};
 
 	class I2PServerTunnelIRC: public I2PServerTunnel
diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp
index 2c0f8d92..ffdeb5ac 100644
--- a/libi2pd_client/SAM.cpp
+++ b/libi2pd_client/SAM.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -43,21 +43,23 @@ namespace client
 			m_Stream->AsyncClose ();
 			m_Stream = nullptr;
 		}
+		auto Session = m_Owner.FindSession(m_ID);
 		switch (m_SocketType)
 		{
 			case eSAMSocketTypeSession:
 				m_Owner.CloseSession (m_ID);
 			break;
 			case eSAMSocketTypeStream:
-			break;
+			{
+				break;
+			}
 			case eSAMSocketTypeAcceptor:
 			case eSAMSocketTypeForward:
 			{
-				auto session = m_Owner.FindSession(m_ID);
-				if (session)
+				if (Session)
 				{
-					if (m_IsAccepting && session->GetLocalDestination ())
-						session->GetLocalDestination ()->StopAcceptingStreams ();
+					if (m_IsAccepting && Session->GetLocalDestination ())
+						Session->GetLocalDestination ()->StopAcceptingStreams ();
 				}
 				break;
 			}
@@ -369,7 +371,7 @@ namespace client
 			// udp forward selected
 			boost::system::error_code e;
 			// TODO: support hostnames in udp forward
-			auto addr = boost::asio::ip::make_address(params[SAM_PARAM_HOST], e);
+			auto addr = boost::asio::ip::address::from_string(params[SAM_PARAM_HOST], e);
 			if (e)
 			{
 				// not an ip address
@@ -413,17 +415,12 @@ namespace client
 			{
 				session->UDPEndpoint = forward;
 				auto dest = session->GetLocalDestination ()->CreateDatagramDestination ();
-				auto port = forward ? std::stoi(params[SAM_PARAM_PORT]) : 0;
 				if (type == eSAMSessionTypeDatagram)
 					dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (),
-						std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5),
-						port
-					);
+						std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
 				else // raw
 					dest->SetRawReceiver (std::bind (&SAMSocket::HandleI2PRawDatagramReceive, shared_from_this (),
-						std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4),
-						port
-					);
+						std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
 			}
 
 			if (session->GetLocalDestination ()->IsReady ())
@@ -468,11 +465,15 @@ namespace client
 		auto session = m_Owner.FindSession(m_ID);
 		if (session)
 		{
-			std::string priv = session->GetLocalDestination ()->GetPrivateKeys ().ToBase64 ();
+			uint8_t buf[1024];
+			char priv[1024];
+			size_t l = session->GetLocalDestination ()->GetPrivateKeys ().ToBuffer (buf, 1024);
+			size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, priv, 1024);
+			priv[l1] = 0;
 #ifdef _MSC_VER
-			size_t l2 = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv.c_str ());
+			size_t l2 = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv);
 #else
-			size_t l2 = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv.c_str ());
+			size_t l2 = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv);
 #endif
 			SendMessageReply (m_Buffer, l2, false);
 		}
@@ -522,20 +523,15 @@ namespace client
 			{
 				if (addr->IsIdentHash ())
 				{
-					if (session->GetLocalDestination ()->GetIdentHash () != addr->identHash)
-					{
-						auto leaseSet = session->GetLocalDestination ()->FindLeaseSet(addr->identHash);
-						if (leaseSet)
-							Connect(leaseSet, session);
-						else
-						{
-							session->GetLocalDestination ()->RequestDestination(addr->identHash,
-								std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete,
-								shared_from_this(), std::placeholders::_1));
-						}
-					}
+					auto leaseSet = session->GetLocalDestination ()->FindLeaseSet(addr->identHash);
+					if (leaseSet)
+						Connect(leaseSet, session);
 					else
-						SendStreamCantReachPeer ("Can't connect to myself");
+					{
+						session->GetLocalDestination ()->RequestDestination(addr->identHash,
+							std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete,
+							shared_from_this(), std::placeholders::_1));
+					}
 				}
 				else // B33
 					session->GetLocalDestination ()->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey,
@@ -554,22 +550,17 @@ namespace client
 		if (!session) session = m_Owner.FindSession(m_ID);
 		if (session)
 		{
-			if (session->GetLocalDestination ()->SupportsEncryptionType (remote->GetEncryptionType ()))
+			m_SocketType = eSAMSocketTypeStream;
+			m_Stream = session->GetLocalDestination ()->CreateStream (remote);
+			if (m_Stream)
 			{
-				m_SocketType = eSAMSocketTypeStream;
-				m_Stream = session->GetLocalDestination ()->CreateStream (remote);
-				if (m_Stream)
-				{
-					m_Stream->Send ((uint8_t *)m_Buffer, m_BufferOffset); // connect and send
-					m_BufferOffset = 0;
-					I2PReceive ();
-					SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false);
-				}
-				else
-					SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true);
+				m_Stream->Send ((uint8_t *)m_Buffer, m_BufferOffset); // connect and send
+				m_BufferOffset = 0;
+				I2PReceive ();
+				SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false);
 			}
 			else
-				SendStreamCantReachPeer ("Incompatible crypto");
+				SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true);
 		}
 		else
 			SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true);
@@ -582,7 +573,7 @@ namespace client
 		else
 		{
 			LogPrint (eLogError, "SAM: Destination to connect not found");
-			SendStreamCantReachPeer ("LeaseSet not found");
+			SendMessageReply (SAM_STREAM_STATUS_CANT_REACH_PEER, strlen(SAM_STREAM_STATUS_CANT_REACH_PEER), true);
 		}
 	}
 
@@ -611,27 +602,27 @@ namespace client
 				session->GetLocalDestination ()->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, shared_from_this (), std::placeholders::_1));
 			}
 			else
-			{
+			{	
 				auto ts = i2p::util::GetSecondsSinceEpoch ();
 				while (!session->acceptQueue.empty () && session->acceptQueue.front ().second + SAM_SESSION_MAX_ACCEPT_INTERVAL > ts)
-				{
+				{	
 					auto socket = session->acceptQueue.front ().first;
 					session->acceptQueue.pop_front ();
 					if (socket)
-						boost::asio::post (m_Owner.GetService (), std::bind(&SAMSocket::TerminateClose, socket));
-				}
+						m_Owner.GetService ().post (std::bind(&SAMSocket::TerminateClose, socket));
+				}	
 				if (session->acceptQueue.size () < SAM_SESSION_MAX_ACCEPT_QUEUE_SIZE)
 				{
 					// already accepting, queue up
 					SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false);
 					session->acceptQueue.push_back (std::make_pair(shared_from_this(), ts));
-				}
-				else
-				{
+				}	
+				else 
+				{	
 					LogPrint (eLogInfo, "SAM: Session ", m_ID, " accept queue is full ", session->acceptQueue.size ());
 					SendStreamI2PError ("Already accepting");
-				}
-			}
+				}	
+			}	
 		}
 		else
 			SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true);
@@ -866,33 +857,28 @@ namespace client
 			SendSessionI2PError ("Wrong session type");
 	}
 
-	void SAMSocket::SendReplyWithMessage (const char * reply, const std::string & msg)
-	{
-#ifdef _MSC_VER
-		size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, reply, msg.c_str());
-#else
-		size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, reply, msg.c_str());
-#endif
-		SendMessageReply (m_Buffer, len, true);
-	}
-
 	void SAMSocket::SendSessionI2PError(const std::string & msg)
 	{
 		LogPrint (eLogError, "SAM: Session I2P error: ", msg);
-		SendReplyWithMessage (SAM_SESSION_STATUS_I2P_ERROR, msg);
+#ifdef _MSC_VER
+		size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_STATUS_I2P_ERROR, msg.c_str());
+#else
+		size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_STATUS_I2P_ERROR, msg.c_str());
+#endif
+		SendMessageReply (m_Buffer, len, true);
 	}
 
 	void SAMSocket::SendStreamI2PError(const std::string & msg)
 	{
 		LogPrint (eLogError, "SAM: Stream I2P error: ", msg);
-		SendReplyWithMessage (SAM_STREAM_STATUS_I2P_ERROR, msg);
+#ifdef _MSC_VER
+		size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_STREAM_STATUS_I2P_ERROR, msg.c_str());
+#else
+		size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_STREAM_STATUS_I2P_ERROR, msg.c_str());
+#endif
+		SendMessageReply (m_Buffer, len, true);
 	}
-
-	void SAMSocket::SendStreamCantReachPeer(const std::string & msg)
-	{
-		SendReplyWithMessage (SAM_STREAM_STATUS_CANT_REACH_PEER, msg);
-	}
-
+		
 	void SAMSocket::HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, std::string name)
 	{
 		if (leaseSet)
@@ -1040,13 +1026,13 @@ namespace client
 				else
 				{
 					auto s = shared_from_this ();
-					boost::asio::post (m_Owner.GetService (), [s] { s->Terminate ("stream read error"); });
+					m_Owner.GetService ().post ([s] { s->Terminate ("stream read error"); });
 				}
 			}
 			else
 			{
 				auto s = shared_from_this ();
-				boost::asio::post (m_Owner.GetService (), [s] { s->Terminate ("stream read error (op aborted)"); });
+				m_Owner.GetService ().post ([s] { s->Terminate ("stream read error (op aborted)"); });
 			}
 		}
 		else
@@ -1092,14 +1078,14 @@ namespace client
 				// pending acceptors
 				auto ts = i2p::util::GetSecondsSinceEpoch ();
 				while (!session->acceptQueue.empty () && session->acceptQueue.front ().second + SAM_SESSION_MAX_ACCEPT_INTERVAL > ts)
-				{
+				{	
 					auto socket = session->acceptQueue.front ().first;
 					session->acceptQueue.pop_front ();
 					if (socket)
-						boost::asio::post (m_Owner.GetService (), std::bind(&SAMSocket::TerminateClose, socket));
-				}
+						m_Owner.GetService ().post (std::bind(&SAMSocket::TerminateClose, socket));
+				}	
 				if (!session->acceptQueue.empty ())
-				{
+				{	
 					auto socket = session->acceptQueue.front ().first;
 					session->acceptQueue.pop_front ();
 					if (socket && socket->GetSocketType () == eSAMSocketTypeAcceptor)
@@ -1107,22 +1093,21 @@ namespace client
 						socket->m_IsAccepting = true;
 						session->GetLocalDestination ()->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, socket, std::placeholders::_1));
 					}
-				}
+				}	
 			}
 			if (!m_IsSilent)
-			{			
-				if (m_SocketType != eSAMSocketTypeTerminated)
-				{
-					// get remote peer address
-					auto ident = std::make_shared<std::string>(stream->GetRemoteIdentity()->ToBase64 ()); // we need to keep it until sent
-					ident->push_back ('\n');
-					// send remote peer address back to client like received from stream
-					boost::asio::async_write (m_Socket, boost::asio::buffer (ident->data (), ident->size ()), boost::asio::transfer_all(),
-						[ident, s = shared_from_this ()](const boost::system::error_code& ecode, size_t bytes_transferred)
-						{
-							s->HandleWriteI2PData (ecode, bytes_transferred);
-						});
-				}	
+			{
+				// get remote peer address
+				auto ident_ptr = stream->GetRemoteIdentity();
+				const size_t ident_len = ident_ptr->GetFullLen();
+				uint8_t* ident = new uint8_t[ident_len];
+
+				// send remote peer address as base64
+				const size_t l = ident_ptr->ToBuffer (ident, ident_len);
+				const size_t l1 = i2p::data::ByteStreamToBase64 (ident, l, (char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE);
+				delete[] ident;
+				m_StreamBuffer[l1] = '\n';
+				HandleI2PReceive (boost::system::error_code (), l1 +1); // we send identity like it has been received from stream
 			}
 			else
 				I2PReceive ();
@@ -1231,7 +1216,7 @@ namespace client
 
 	void SAMSocket::HandleStreamSend(const boost::system::error_code & ec)
 	{
-		boost::asio::post (m_Owner.GetService (), std::bind( !ec ? &SAMSocket::Receive : &SAMSocket::TerminateClose, shared_from_this()));
+		m_Owner.GetService ().post (std::bind( !ec ? &SAMSocket::Receive : &SAMSocket::TerminateClose, shared_from_this()));
 	}
 
 	SAMSession::SAMSession (SAMBridge & parent, const std::string & id, SAMSessionType type):
@@ -1305,8 +1290,8 @@ namespace client
 
 	SAMBridge::SAMBridge (const std::string& address, uint16_t portTCP, uint16_t portUDP, bool singleThread):
 		RunnableService ("SAM"), m_IsSingleThread (singleThread),
-		m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), portTCP)),
-		m_DatagramEndpoint (boost::asio::ip::make_address(address), (!portUDP) ? portTCP-1 : portUDP), m_DatagramSocket (GetIOService (), m_DatagramEndpoint),
+		m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), portTCP)),
+		m_DatagramEndpoint (boost::asio::ip::address::from_string(address), (!portUDP) ? portTCP-1 : portUDP), m_DatagramSocket (GetIOService (), m_DatagramEndpoint),
 		m_SignatureTypes
 		{
 			{"DSA_SHA1", i2p::data::SIGNING_KEY_TYPE_DSA_SHA1},
@@ -1345,14 +1330,12 @@ namespace client
 			LogPrint (eLogError, "SAM: Runtime exception: ", ex.what ());
 		}
 
-		decltype(m_Sessions) sessions;
 		{
 			std::unique_lock<std::mutex> l(m_SessionsMutex);
-			m_Sessions.swap (sessions);
-		}	
-		for (auto& it: sessions)
-			it.second->Close ();
-		
+			for (auto& it: m_Sessions)
+				it.second->Close ();
+			m_Sessions.clear ();
+		}
 		StopIOService ();
 	}
 
@@ -1477,39 +1460,17 @@ namespace client
 			session->StopLocalDestination ();
 			session->Close ();
 			if (m_IsSingleThread)
-				ScheduleSessionCleanupTimer (session); // let all session's streams close
+			{
+				auto timer = std::make_shared<boost::asio::deadline_timer>(GetService ());
+				timer->expires_from_now (boost::posix_time::seconds(5)); // postpone destination clean for 5 seconds
+				timer->async_wait ([timer, session](const boost::system::error_code& ecode)
+				{
+					// session's destructor is called here
+				});
+			}
 		}
 	}
 
-	void SAMBridge::ScheduleSessionCleanupTimer (std::shared_ptr<SAMSession> session)
-	{
-		auto timer = std::make_shared<boost::asio::deadline_timer>(GetService ());
-		timer->expires_from_now (boost::posix_time::seconds(5)); // postpone destination clean for 5 seconds
-		timer->async_wait (std::bind (&SAMBridge::HandleSessionCleanupTimer, this, std::placeholders::_1, session, timer));
-	}	
-		
-	void SAMBridge::HandleSessionCleanupTimer (const boost::system::error_code& ecode,
-		std::shared_ptr<SAMSession> session, std::shared_ptr<boost::asio::deadline_timer> timer)
-	{
-		if (ecode != boost::asio::error::operation_aborted && session)
-		{
-			auto dest = session->GetLocalDestination ();
-			if (dest)
-			{
-				auto streamingDest = dest->GetStreamingDestination ();
-				auto numStreams = streamingDest->GetNumStreams ();
-				if (numStreams > 0)
-				{
-					LogPrint (eLogInfo, "SAM: Session ", session->Name, " still has ", numStreams, " streams");
-					ScheduleSessionCleanupTimer (session);
-				}	
-				else
-					LogPrint (eLogDebug, "SAM: Session ", session->Name, " terminated");
-			}	
-		}	
-		// session's destructor is called here unless rescheduled
-	}	
-		
 	std::shared_ptr<SAMSession> SAMBridge::FindSession (const std::string& id) const
 	{
 		std::unique_lock<std::mutex> l(m_SessionsMutex);
diff --git a/libi2pd_client/SAM.h b/libi2pd_client/SAM.h
index 1886324a..34af5a62 100644
--- a/libi2pd_client/SAM.h
+++ b/libi2pd_client/SAM.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2025, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -51,7 +51,7 @@ namespace client
 	const char SAM_STREAM_STATUS_OK[] = "STREAM STATUS RESULT=OK\n";
 	const char SAM_STREAM_STATUS_INVALID_ID[] = "STREAM STATUS RESULT=INVALID_ID\n";
 	const char SAM_STREAM_STATUS_INVALID_KEY[] = "STREAM STATUS RESULT=INVALID_KEY\n";
-	const char SAM_STREAM_STATUS_CANT_REACH_PEER[] = "STREAM STATUS RESULT=CANT_REACH_PEER MESSAGE=\"%s\"\n";
+	const char SAM_STREAM_STATUS_CANT_REACH_PEER[] = "STREAM STATUS RESULT=CANT_REACH_PEER\n";
 	const char SAM_STREAM_STATUS_I2P_ERROR[] = "STREAM STATUS RESULT=I2P_ERROR MESSAGE=\"%s\"\n";
 	const char SAM_STREAM_ACCEPT[] = "STREAM ACCEPT";
 	const char SAM_STREAM_FORWARD[] = "STREAM FORWARD";
@@ -144,10 +144,8 @@ namespace client
 			void ProcessNamingLookup (char * buf, size_t len);
 			void ProcessSessionAdd (char * buf, size_t len);
 			void ProcessSessionRemove (char * buf, size_t len);
-			void SendReplyWithMessage (const char * reply, const std::string & msg);
 			void SendSessionI2PError(const std::string & msg);
 			void SendStreamI2PError(const std::string & msg);	
-			void SendStreamCantReachPeer(const std::string & msg);
 			size_t ProcessDatagramSend (char * buf, size_t len, const char * data); // from SAM 1.0
 			void ExtractParams (char * buf, std::map<std::string, std::string>& params);
 
@@ -246,7 +244,7 @@ namespace client
 			void Start ();
 			void Stop ();
 
-			auto& GetService () { return GetIOService (); };
+			boost::asio::io_service& GetService () { return GetIOService (); };
 			std::shared_ptr<SAMSession> CreateSession (const std::string& id, SAMSessionType type, const std::string& destination, // empty string means transient
 				const std::map<std::string, std::string> * params);
 			bool AddSession (std::shared_ptr<SAMSession> session);
@@ -271,10 +269,6 @@ namespace client
 			void ReceiveDatagram ();
 			void HandleReceivedDatagram (const boost::system::error_code& ecode, std::size_t bytes_transferred);
 
-			void ScheduleSessionCleanupTimer (std::shared_ptr<SAMSession> session);
-			void HandleSessionCleanupTimer (const boost::system::error_code& ecode,
-				std::shared_ptr<SAMSession> session, std::shared_ptr<boost::asio::deadline_timer> timer);	                                
-			
 		private:
 
 			bool m_IsSingleThread;
diff --git a/libi2pd_client/SOCKS.cpp b/libi2pd_client/SOCKS.cpp
index 27df33c8..547a2da2 100644
--- a/libi2pd_client/SOCKS.cpp
+++ b/libi2pd_client/SOCKS.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2023, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -19,7 +19,6 @@
 #include "I2PTunnel.h"
 #include "I2PService.h"
 #include "util.h"
-#include "Socks5.h"
 
 namespace i2p
 {
@@ -28,6 +27,10 @@ namespace proxy
 	static const size_t socks_buffer_size = 8192;
 	static const size_t max_socks_hostname_size = 255; // Limit for socks5 and bad idea to traverse
 
+	//static const size_t SOCKS_FORWARDER_BUFFER_SIZE = 8192;
+
+	static const size_t SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE = 8;
+
 	struct SOCKSDnsAddress
 	{
 		uint8_t size;
@@ -126,8 +129,10 @@ namespace proxy
 			void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered);
 			void Terminate();
 			void AsyncSockRead();
-			boost::asio::const_buffer GenerateSOCKS4Response(errTypes error, uint32_t ip, uint16_t port);
-			boost::asio::const_buffer GenerateSOCKS5Response(errTypes error, addrTypes type, const address &addr, uint16_t port);
+			boost::asio::const_buffers_1 GenerateSOCKS5SelectAuth(authMethods method);
+			boost::asio::const_buffers_1 GenerateSOCKS4Response(errTypes error, uint32_t ip, uint16_t port);
+			boost::asio::const_buffers_1 GenerateSOCKS5Response(errTypes error, addrTypes type, const address &addr, uint16_t port);
+			boost::asio::const_buffers_1 GenerateUpstreamRequest();
 			bool Socks5ChooseAuth();
 			void Socks5UserPasswdResponse ();
 			void SocksRequestFailed(errTypes error);
@@ -138,26 +143,27 @@ namespace proxy
 			void HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream);
 			void ForwardSOCKS();
 
-			template<typename Socket>
-			void SocksUpstreamSuccess(std::shared_ptr<Socket>& upstreamSock);
+			void SocksUpstreamSuccess();
 			void AsyncUpstreamSockRead();
-			template<typename Socket>
-			void SendUpstreamRequest(std::shared_ptr<Socket>& upstreamSock);
+			void SendUpstreamRequest();
+			void HandleUpstreamData(uint8_t * buff, std::size_t len);
+			void HandleUpstreamSockSend(const boost::system::error_code & ecode, std::size_t bytes_transfered);
+			void HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered);
 			void HandleUpstreamConnected(const boost::system::error_code & ecode,
-				const boost::asio::ip::tcp::endpoint& ep);
+			boost::asio::ip::tcp::resolver::iterator itr);
 			void HandleUpstreamResolved(const boost::system::error_code & ecode,
-				boost::asio::ip::tcp::resolver::results_type endpoints);
+				boost::asio::ip::tcp::resolver::iterator itr);
 
 			boost::asio::ip::tcp::resolver m_proxy_resolver;
 			uint8_t m_sock_buff[socks_buffer_size];
 			std::shared_ptr<boost::asio::ip::tcp::socket> m_sock, m_upstreamSock;
-#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
-			std::shared_ptr<boost::asio::local::stream_protocol::socket> m_upstreamLocalSock;
-#endif			
 			std::shared_ptr<i2p::stream::Stream> m_stream;
 			uint8_t *m_remaining_data; //Data left to be sent
 			uint8_t *m_remaining_upstream_data; //upstream data left to be forwarded
 			uint8_t m_response[7+max_socks_hostname_size];
+			uint8_t m_upstream_response[SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE];
+			uint8_t m_upstream_request[14+max_socks_hostname_size];
+			std::size_t m_upstream_response_len;
 			address m_address; //Address
 			std::size_t m_remaining_data_len; //Size of the data left to be sent
 			uint32_t m_4aip; //Used in 4a requests
@@ -216,14 +222,6 @@ namespace proxy
 			m_upstreamSock->close();
 			m_upstreamSock = nullptr;
 		}
-#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
-		if (m_upstreamLocalSock)
-		{
-			LogPrint(eLogDebug, "SOCKS: Closing upstream local socket");
-			m_upstreamLocalSock->close();
-			m_upstreamLocalSock = nullptr;
-		}
-#endif		
 		if (m_stream)
 		{
 			LogPrint(eLogDebug, "SOCKS: Closing stream");
@@ -232,17 +230,17 @@ namespace proxy
 		Done(shared_from_this());
 	}
 
-	boost::asio::const_buffer SOCKSHandler::GenerateSOCKS4Response(SOCKSHandler::errTypes error, uint32_t ip, uint16_t port)
+	boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS4Response(SOCKSHandler::errTypes error, uint32_t ip, uint16_t port)
 	{
 		assert(error >= SOCKS4_OK);
 		m_response[0] = '\x00';           // version
 		m_response[1] = error;            // response code
 		htobe16buf(m_response + 2, port); // port
 		htobe32buf(m_response + 4, ip);   // IP
-		return boost::asio::const_buffer (m_response,8);
+		return boost::asio::const_buffers_1(m_response,8);
 	}
 
-	boost::asio::const_buffer SOCKSHandler::GenerateSOCKS5Response(SOCKSHandler::errTypes error, SOCKSHandler::addrTypes type, const SOCKSHandler::address &addr, uint16_t port)
+	boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS5Response(SOCKSHandler::errTypes error, SOCKSHandler::addrTypes type, const SOCKSHandler::address &addr, uint16_t port)
 	{
 		size_t size = 6;        // header + port
 		assert(error <= SOCKS5_ADDR_UNSUP);
@@ -279,14 +277,45 @@ namespace proxy
 				}
 				break;
 		}
-		return boost::asio::const_buffer (m_response, size);
+		return boost::asio::const_buffers_1(m_response, size);
+	}
+
+	boost::asio::const_buffers_1 SOCKSHandler::GenerateUpstreamRequest()
+	{
+		size_t upstreamRequestSize = 0;
+		// TODO: negotiate with upstream
+		// SOCKS 4a
+		m_upstream_request[0] = '\x04'; //version
+		m_upstream_request[1] = m_cmd;
+		htobe16buf(m_upstream_request + 2, m_port);
+		m_upstream_request[4] = 0;
+		m_upstream_request[5] = 0;
+		m_upstream_request[6] = 0;
+		m_upstream_request[7] = 1;
+		// user id
+		m_upstream_request[8] = 'i';
+		m_upstream_request[9] = '2';
+		m_upstream_request[10] = 'p';
+		m_upstream_request[11] = 'd';
+		m_upstream_request[12] = 0;
+		upstreamRequestSize += 13;
+		if (m_address.dns.size <= max_socks_hostname_size - ( upstreamRequestSize + 1) ) {
+			// bounds check okay
+			memcpy(m_upstream_request + upstreamRequestSize, m_address.dns.value, m_address.dns.size);
+			upstreamRequestSize += m_address.dns.size;
+			// null terminate
+			m_upstream_request[++upstreamRequestSize] = 0;
+		} else {
+			LogPrint(eLogError, "SOCKS: BUG!!! m_addr.dns.sizs > max_socks_hostname - ( upstreamRequestSize + 1 ) )");
+		}
+		return boost::asio::const_buffers_1(m_upstream_request, upstreamRequestSize);
 	}
 
 	bool SOCKSHandler::Socks5ChooseAuth()
 	{
 		m_response[0] = '\x05'; // Version
 		m_response[1] = m_authchosen; // Response code
-		boost::asio::const_buffer response(m_response, 2);
+		boost::asio::const_buffers_1 response(m_response, 2);
 		if (m_authchosen == AUTH_UNACCEPTABLE)
 		{
 			LogPrint(eLogWarning, "SOCKS: v5 authentication negotiation failed");
@@ -306,14 +335,14 @@ namespace proxy
 		m_response[0] = 1; // Version of the subnegotiation
 		m_response[1] = 0; // Response code
 		LogPrint(eLogDebug, "SOCKS: v5 user/password response");
-		boost::asio::async_write(*m_sock, boost::asio::const_buffer(m_response, 2), 
+		boost::asio::async_write(*m_sock, boost::asio::const_buffers_1(m_response, 2), 
 			std::bind(&SOCKSHandler::SentSocksResponse, shared_from_this(), std::placeholders::_1));
 	}	
 	
 	/* All hope is lost beyond this point */
 	void SOCKSHandler::SocksRequestFailed(SOCKSHandler::errTypes error)
 	{
-		boost::asio::const_buffer response(nullptr,0);
+		boost::asio::const_buffers_1 response(nullptr,0);
 		assert(error != SOCKS4_OK && error != SOCKS5_OK);
 		switch (m_socksv)
 		{
@@ -333,7 +362,7 @@ namespace proxy
 
 	void SOCKSHandler::SocksRequestSuccess()
 	{
-		boost::asio::const_buffer response(nullptr,0);
+		boost::asio::const_buffers_1 response(nullptr,0);
 		// TODO: this should depend on things like the command type and callbacks may change
 		switch (m_socksv)
 		{
@@ -443,7 +472,9 @@ namespace proxy
 						break;
 						case CMD_UDP:
 							if (m_socksv == SOCKS5) break;
+#if (__cplusplus >= 201703L) // C++ 17 or higher
 							[[fallthrough]];
+#endif
 						default:
 							LogPrint(eLogError, "SOCKS: Invalid command: ", ((int)*sock_buff));
 							SocksRequestFailed(SOCKS5_GEN_FAIL);
@@ -687,47 +718,42 @@ namespace proxy
 	void SOCKSHandler::ForwardSOCKS()
 	{
 		LogPrint(eLogInfo, "SOCKS: Forwarding to upstream");
-		if (m_UpstreamProxyPort) // TCP
-		{	
-			EnterState(UPSTREAM_RESOLVE);
-			m_proxy_resolver.async_resolve(m_UpstreamProxyAddress, std::to_string(m_UpstreamProxyPort), 
-				std::bind(&SOCKSHandler::HandleUpstreamResolved, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
-		}	
-		else  if (!m_UpstreamProxyAddress.empty ())// local
-		{
-#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)	
-			EnterState(UPSTREAM_CONNECT);
-			m_upstreamLocalSock = std::make_shared<boost::asio::local::stream_protocol::socket>(GetOwner()->GetService());
-			auto s = shared_from_this ();
-			m_upstreamLocalSock->async_connect(m_UpstreamProxyAddress,
-			    [s](const boost::system::error_code& ecode)
-			    {
-					if (ecode) 
-					{
-						LogPrint(eLogWarning, "SOCKS: Could not connect to local upstream proxy: ", ecode.message());
-						s->SocksRequestFailed(SOCKS5_NET_UNREACH);
-						return;
-					}
-					LogPrint(eLogInfo, "SOCKS: Connected to local upstream proxy");
-					s->SendUpstreamRequest(s->m_upstreamLocalSock);
-				});
-#else
-			LogPrint(eLogError, "SOCKS: Local sockets for upstream proxy not supported");
-			SocksRequestFailed(SOCKS5_ADDR_UNSUP);
-#endif
-		}
-		else
-		{
-			LogPrint(eLogError, "SOCKS: Incorrect upstream proxy address");
-			SocksRequestFailed(SOCKS5_ADDR_UNSUP);
-		}		
+		EnterState(UPSTREAM_RESOLVE);
+		boost::asio::ip::tcp::resolver::query q(m_UpstreamProxyAddress, std::to_string(m_UpstreamProxyPort));
+		m_proxy_resolver.async_resolve(q, std::bind(&SOCKSHandler::HandleUpstreamResolved, shared_from_this(),
+			std::placeholders::_1, std::placeholders::_2));
 	}
 
-	template<typename Socket>
-	void SOCKSHandler::SocksUpstreamSuccess(std::shared_ptr<Socket>& upstreamSock)
+	void SOCKSHandler::AsyncUpstreamSockRead()
+	{
+		LogPrint(eLogDebug, "SOCKS: Async upstream sock read");
+		if (m_upstreamSock) {
+			m_upstreamSock->async_read_some(boost::asio::buffer(m_upstream_response, SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE),
+				std::bind(&SOCKSHandler::HandleUpstreamSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
+		} else {
+			LogPrint(eLogError, "SOCKS: No upstream socket for read");
+			SocksRequestFailed(SOCKS5_GEN_FAIL);
+		}
+	}
+
+	void SOCKSHandler::HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered)
+	{
+		if (ecode) {
+			if (m_state == UPSTREAM_HANDSHAKE ) {
+				// we are trying to handshake but it failed
+				SocksRequestFailed(SOCKS5_NET_UNREACH);
+			} else {
+				LogPrint(eLogError, "SOCKS: Bad state when reading from upstream: ", (int) m_state);
+			}
+			return;
+		}
+		HandleUpstreamData(m_upstream_response, bytes_transfered);
+	}
+
+	void SOCKSHandler::SocksUpstreamSuccess()
 	{
 		LogPrint(eLogInfo, "SOCKS: Upstream success");
-		boost::asio::const_buffer response(nullptr, 0);
+		boost::asio::const_buffers_1 response(nullptr, 0);
 		switch (m_socksv)
 		{
 			case SOCKS4:
@@ -741,40 +767,58 @@ namespace proxy
 			break;
 		}
 		m_sock->send(response);
-		auto forwarder = CreateSocketsPipe (GetOwner(), m_sock, upstreamSock);
-		upstreamSock = nullptr;
+		auto forwarder = std::make_shared<i2p::client::TCPIPPipe>(GetOwner(), m_sock, m_upstreamSock);
+		m_upstreamSock = nullptr;
 		m_sock = nullptr;
 		GetOwner()->AddHandler(forwarder);
 		forwarder->Start();
 		Terminate();
+
 	}
 
-	template<typename Socket>
-	void SOCKSHandler::SendUpstreamRequest(std::shared_ptr<Socket>& upstreamSock)
+	void SOCKSHandler::HandleUpstreamData(uint8_t * dataptr, std::size_t len)
+	{
+		if (m_state == UPSTREAM_HANDSHAKE) {
+			m_upstream_response_len += len;
+			// handle handshake data
+			if (m_upstream_response_len < SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE) {
+				// too small, continue reading
+				AsyncUpstreamSockRead();
+			} else if (len == SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE) {
+				// just right
+				uint8_t resp = m_upstream_response[1];
+				if (resp == SOCKS4_OK) {
+					// we have connected !
+					SocksUpstreamSuccess();
+				} else {
+					// upstream failure
+					LogPrint(eLogError, "SOCKS: Upstream proxy failure: ", (int) resp);
+					// TODO: runtime error?
+					SocksRequestFailed(SOCKS5_GEN_FAIL);
+				}
+			} else {
+				// too big
+				SocksRequestFailed(SOCKS5_GEN_FAIL);
+			}
+		} else {
+			// invalid state
+			LogPrint(eLogError, "SOCKS: Invalid state reading from upstream: ", (int) m_state);
+		}
+	}
+
+	void SOCKSHandler::SendUpstreamRequest()
 	{
 		LogPrint(eLogInfo, "SOCKS: Negotiating with upstream proxy");
 		EnterState(UPSTREAM_HANDSHAKE);
-		if (upstreamSock) 
-		{
-			auto s = shared_from_this ();
-			i2p::transport::Socks5Handshake (*upstreamSock, std::make_pair(m_address.dns.ToString (), m_port),
-			    [s, &upstreamSock](const boost::system::error_code& ec)
-			    {
-					if (!ec) 
-						s->SocksUpstreamSuccess(upstreamSock);
-					else
-					{	
-						s->SocksRequestFailed(SOCKS5_NET_UNREACH);
-						LogPrint(eLogError, "SOCKS: Upstream proxy failure: ", ec.message ());
-					}
-				});	
-		} 
-		else 
+		if (m_upstreamSock) {
+			boost::asio::write(*m_upstreamSock, GenerateUpstreamRequest());
+			AsyncUpstreamSockRead();
+		} else {
 			LogPrint(eLogError, "SOCKS: No upstream socket to send handshake to");
+		}
 	}
 
-	void SOCKSHandler::HandleUpstreamConnected(const boost::system::error_code & ecode, 
-		const boost::asio::ip::tcp::endpoint&  ep)
+	void SOCKSHandler::HandleUpstreamConnected(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr)
 	{
 		if (ecode) {
 			LogPrint(eLogWarning, "SOCKS: Could not connect to upstream proxy: ", ecode.message());
@@ -782,11 +826,10 @@ namespace proxy
 			return;
 		}
 		LogPrint(eLogInfo, "SOCKS: Connected to upstream proxy");
-		SendUpstreamRequest(m_upstreamSock);
+		SendUpstreamRequest();
 	}
 
-	void SOCKSHandler::HandleUpstreamResolved(const boost::system::error_code & ecode, 
-		boost::asio::ip::tcp::resolver::results_type endpoints)
+	void SOCKSHandler::HandleUpstreamResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr)
 	{
 		if (ecode) {
 			// error resolving
@@ -798,7 +841,7 @@ namespace proxy
 		EnterState(UPSTREAM_CONNECT);
 		auto & service = GetOwner()->GetService();
 		m_upstreamSock = std::make_shared<boost::asio::ip::tcp::socket>(service);
-		boost::asio::async_connect(*m_upstreamSock, endpoints,
+		boost::asio::async_connect(*m_upstreamSock, itr,
 			std::bind(&SOCKSHandler::HandleUpstreamConnected,
 			shared_from_this(), std::placeholders::_1, std::placeholders::_2));
 	}
diff --git a/libi2pd_client/UDPTunnel.cpp b/libi2pd_client/UDPTunnel.cpp
index b173fc0f..1e4b3d7c 100644
--- a/libi2pd_client/UDPTunnel.cpp
+++ b/libi2pd_client/UDPTunnel.cpp
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2022, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -19,22 +19,16 @@ namespace client
 	void I2PUDPServerTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
 	{
 		if (!m_LastSession || m_LastSession->Identity.GetLL()[0] != from.GetIdentHash ().GetLL()[0] || fromPort != m_LastSession->RemotePort)
+		{
+			std::lock_guard<std::mutex> lock(m_SessionsMutex);
 			m_LastSession = ObtainUDPSession(from, toPort, fromPort);
+		}
 		m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint);
 		m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch();
 	}
 
-	void I2PUDPServerTunnel::HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
+	void I2PUDPServerTunnel::HandleRecvFromI2PRaw (uint16_t, uint16_t, const uint8_t * buf, size_t len)
 	{
-		if (m_LastSession && (fromPort != m_LastSession->RemotePort || toPort != m_LastSession->LocalPort))
-		{
-			std::lock_guard<std::mutex> lock(m_SessionsMutex);
-			auto it = m_Sessions.find (GetSessionIndex (fromPort, toPort));
-			if (it != m_Sessions.end ())
-				m_LastSession = it->second;
-			else
-				m_LastSession = nullptr;
-		}
 		if (m_LastSession)
 		{
 			m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint);
@@ -47,12 +41,11 @@ namespace client
 		std::lock_guard<std::mutex> lock(m_SessionsMutex);
 		uint64_t now = i2p::util::GetMillisecondsSinceEpoch();
 		auto itr = m_Sessions.begin();
-		while(itr != m_Sessions.end())
-		{
-			if(now - itr->second->LastActivity >= delta )
+		while(itr != m_Sessions.end()) {
+			if(now - (*itr)->LastActivity >= delta )
 				itr = m_Sessions.erase(itr);
 			else
-				itr++;
+				++itr;
 		}
 	}
 
@@ -73,25 +66,15 @@ namespace client
 	UDPSessionPtr I2PUDPServerTunnel::ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort)
 	{
 		auto ih = from.GetIdentHash();
-		auto idx = GetSessionIndex (remotePort, localPort);
+		for (auto & s : m_Sessions )
 		{
-			std::lock_guard<std::mutex> lock(m_SessionsMutex);
-			auto it = m_Sessions.find (idx);
-			if (it != m_Sessions.end ())
+			if (s->Identity.GetLL()[0] == ih.GetLL()[0] && remotePort == s->RemotePort)
 			{
-				if (it->second->Identity.GetLL()[0] == ih.GetLL()[0])
-				{
-					LogPrint(eLogDebug, "UDPServer: Found session ", it->second->IPSocket.local_endpoint(), " ", ih.ToBase32());
-					return it->second;
-				}
-				else
-				{
-					LogPrint(eLogWarning, "UDPServer: Session with from ", remotePort, " and to ", localPort, " ports already exists. But from different address. Removed");
-					m_Sessions.erase (it);
-				}
+				/** found existing session */
+				LogPrint(eLogDebug, "UDPServer: Found session ", s->IPSocket.local_endpoint(), " ", ih.ToBase32());
+				return s;
 			}
 		}
-
 		boost::asio::ip::address addr;
 		/** create new udp session */
 		if(m_IsUniqueLocal && m_LocalAddress.is_loopback())
@@ -101,12 +84,10 @@ namespace client
 		}
 		else
 			addr = m_LocalAddress;
-
-		auto s = std::make_shared<UDPSession>(boost::asio::ip::udp::endpoint(addr, 0),
-			m_LocalDest, m_RemoteEndpoint, ih, localPort, remotePort);
-		std::lock_guard<std::mutex> lock(m_SessionsMutex);
-		m_Sessions.emplace (idx, s);
-		return s;
+		boost::asio::ip::udp::endpoint ep(addr, 0);
+		m_Sessions.push_back(std::make_shared<UDPSession>(ep, m_LocalDest, m_RemoteEndpoint, ih, localPort, remotePort));
+		auto & back = m_Sessions.back();
+		return back;
 	}
 
 	UDPSession::UDPSession(boost::asio::ip::udp::endpoint localEndpoint,
@@ -163,9 +144,9 @@ namespace client
 	}
 
 	I2PUDPServerTunnel::I2PUDPServerTunnel (const std::string & name, std::shared_ptr<i2p::client::ClientDestination> localDestination,
-		const boost::asio::ip::address& localAddress, const boost::asio::ip::udp::endpoint& forwardTo, uint16_t inPort, bool gzip) :
+		const boost::asio::ip::address& localAddress, const boost::asio::ip::udp::endpoint& forwardTo, uint16_t port, bool gzip) :
 		m_IsUniqueLocal (true), m_Name (name), m_LocalAddress (localAddress),
-		m_RemoteEndpoint (forwardTo), m_LocalDest (localDestination), m_inPort(inPort), m_Gzip (gzip)
+		m_RemoteEndpoint (forwardTo), m_LocalDest (localDestination), m_Gzip (gzip)
 	{
 	}
 
@@ -179,23 +160,14 @@ namespace client
 		m_LocalDest->Start ();
 
 		auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip);
-		dgram->SetReceiver (
-			std::bind (&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5),
-			m_inPort
-		);
-		dgram->SetRawReceiver (
-			std::bind (&I2PUDPServerTunnel::HandleRecvFromI2PRaw, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4),
-			m_inPort
-		);
+		dgram->SetReceiver (std::bind (&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
+		dgram->SetRawReceiver (std::bind (&I2PUDPServerTunnel::HandleRecvFromI2PRaw, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
 	}
 
 	void I2PUDPServerTunnel::Stop ()
 	{
 		auto dgram = m_LocalDest->GetDatagramDestination ();
-		if (dgram) {
-			dgram->ResetReceiver (m_inPort);
-			dgram->ResetRawReceiver (m_inPort);
-		}
+		if (dgram) dgram->ResetReceiver ();
 	}
 
 	std::vector<std::shared_ptr<DatagramSessionInfo> > I2PUDPServerTunnel::GetSessions ()
@@ -203,9 +175,8 @@ namespace client
 		std::vector<std::shared_ptr<DatagramSessionInfo> > sessions;
 		std::lock_guard<std::mutex> lock (m_SessionsMutex);
 
-        for (const auto &it: m_Sessions)
+		for (UDPSessionPtr s: m_Sessions)
 		{
-			auto s = it.second;
 			if (!s->m_Destination) continue;
 			auto info = s->m_Destination->GetInfoForRemote (s->Identity);
 			if (!info) continue;
@@ -249,13 +220,9 @@ namespace client
 		dgram->SetReceiver (std::bind (&I2PUDPClientTunnel::HandleRecvFromI2P, this,
 			std::placeholders::_1, std::placeholders::_2,
 			std::placeholders::_3, std::placeholders::_4,
-			std::placeholders::_5),
-			RemotePort
-		);
+			std::placeholders::_5));
 		dgram->SetRawReceiver (std::bind (&I2PUDPClientTunnel::HandleRecvFromI2PRaw, this,
-			std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4),
-			RemotePort
-		);
+			std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
 
 		m_LocalDest->Start ();
 		if (m_ResolveThread == nullptr)
@@ -266,10 +233,7 @@ namespace client
 	void I2PUDPClientTunnel::Stop ()
 	{
 		auto dgram = m_LocalDest->GetDatagramDestination ();
-		if (dgram) {
-			dgram->ResetReceiver (RemotePort);
-			dgram->ResetRawReceiver (RemotePort);
-		}
+		if (dgram) dgram->ResetReceiver ();
 		m_cancel_resolve = true;
 
 		m_Sessions.clear();
diff --git a/libi2pd_client/UDPTunnel.h b/libi2pd_client/UDPTunnel.h
index 5650124c..862ce216 100644
--- a/libi2pd_client/UDPTunnel.h
+++ b/libi2pd_client/UDPTunnel.h
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2013-2024, The PurpleI2P Project
+* Copyright (c) 2013-2022, The PurpleI2P Project
 *
 * This file is part of Purple i2pd project and licensed under BSD3
 *
@@ -72,7 +72,7 @@ namespace client
 		boost::asio::ip::udp::endpoint LocalEndpoint;
 		/** client's udp endpoint */
 		boost::asio::ip::udp::endpoint RemoteEndpoint;
-		/** how long has this conversation been idle in ms */
+		/** how long has this converstation been idle in ms */
 		uint64_t idle;
 	};
 
@@ -104,7 +104,6 @@ namespace client
 			void HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
 			void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
 			UDPSessionPtr ObtainUDPSession (const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort);
-			uint32_t GetSessionIndex (uint16_t fromPort, uint16_t toPort) const { return ((uint32_t)fromPort << 16) + toPort; }
 
 		private:
 
@@ -113,10 +112,9 @@ namespace client
 			boost::asio::ip::address m_LocalAddress;
 			boost::asio::ip::udp::endpoint m_RemoteEndpoint;
 			std::mutex m_SessionsMutex;
-			std::unordered_map<uint32_t, UDPSessionPtr> m_Sessions; // (from port, to port)->session
+			std::vector<UDPSessionPtr> m_Sessions;
 			std::shared_ptr<i2p::client::ClientDestination> m_LocalDest;
 			UDPSessionPtr m_LastSession;
-			uint16_t m_inPort;
 			bool m_Gzip;
 
 		public:
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index fb03d434..de319f5d 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -49,6 +49,10 @@ set(test-gost-sig_SRCS
   test-gost-sig.cpp
 )
 
+set(test-x25519_SRCS
+  test-x25519.cpp
+)
+
 set(test-aeadchacha20poly1305_SRCS
   test-aeadchacha20poly1305.cpp
 )
@@ -65,10 +69,6 @@ set(test-eddsa_SRCS
   test-eddsa.cpp
 )
 
-set(test-aes_SRCS
-  test-aes.cpp
-)
-
 add_executable(test-http-merge_chunked ${test-http-merge_chunked_SRCS})
 add_executable(test-http-req ${test-http-req_SRCS})
 add_executable(test-http-res ${test-http-res_SRCS})
@@ -77,11 +77,11 @@ add_executable(test-http-url ${test-http-url_SRCS})
 add_executable(test-base-64 ${test-base-64_SRCS})
 add_executable(test-gost ${test-gost_SRCS})
 add_executable(test-gost-sig ${test-gost-sig_SRCS})
+add_executable(test-x25519 ${test-x25519_SRCS})
 add_executable(test-aeadchacha20poly1305 ${test-aeadchacha20poly1305_SRCS})
 add_executable(test-blinding ${test-blinding_SRCS})
 add_executable(test-elligator ${test-elligator_SRCS})
 add_executable(test-eddsa ${test-eddsa_SRCS})
-add_executable(test-aes ${test-aes_SRCS})
 
 set(LIBS
   libi2pd
@@ -102,11 +102,11 @@ target_link_libraries(test-http-url ${LIBS})
 target_link_libraries(test-base-64 ${LIBS})
 target_link_libraries(test-gost ${LIBS})
 target_link_libraries(test-gost-sig ${LIBS})
+target_link_libraries(test-x25519 ${LIBS})
 target_link_libraries(test-aeadchacha20poly1305 ${LIBS})
 target_link_libraries(test-blinding ${LIBS})
 target_link_libraries(test-elligator ${LIBS})
 target_link_libraries(test-eddsa ${LIBS})
-target_link_libraries(test-aes ${LIBS})
 
 add_test(test-http-merge_chunked ${TEST_PATH}/test-http-merge_chunked)
 add_test(test-http-req ${TEST_PATH}/test-http-req)
@@ -116,8 +116,8 @@ add_test(test-http-url ${TEST_PATH}/test-http-url)
 add_test(test-base-64 ${TEST_PATH}/test-base-64)
 add_test(test-gost ${TEST_PATH}/test-gost)
 add_test(test-gost-sig ${TEST_PATH}/test-gost-sig)
+add_test(test-x25519 ${TEST_PATH}/test-x25519)
 add_test(test-aeadchacha20poly1305 ${TEST_PATH}/test-aeadchacha20poly1305)
 add_test(test-blinding ${TEST_PATH}/test-blinding)
 add_test(test-elligator ${TEST_PATH}/test-elligator)
 add_test(test-eddsa ${TEST_PATH}/test-eddsa)
-add_test(test-aes ${TEST_PATH}/test-aes)
diff --git a/tests/Makefile b/tests/Makefile
index b020427d..7c44e467 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -1,14 +1,14 @@
 SYS := $(shell $(CXX) -dumpmachine)
 
-CXXFLAGS += -Wall -Wno-unused-parameter -Wextra -pedantic -O0 -g -std=c++17 -D_GLIBCXX_USE_NANOSLEEP=1 -DOPENSSL_SUPPRESS_DEPRECATED -pthread -Wl,--unresolved-symbols=ignore-in-object-files
+CXXFLAGS += -Wall -Wno-unused-parameter -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -DOPENSSL_SUPPRESS_DEPRECATED -pthread -Wl,--unresolved-symbols=ignore-in-object-files
 INCFLAGS += -I../libi2pd
 
 LIBI2PD = ../libi2pd.a
 
 TESTS = \
 	test-http-merge_chunked test-http-req test-http-res test-http-url test-http-url_decode \
-	test-gost test-gost-sig test-base-64 test-aeadchacha20poly1305 test-blinding \
-	test-elligator test-eddsa test-aes
+	test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 test-blinding \
+	test-elligator test-eddsa 
 
 ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstring cygwin, $(SYS)))
 	CXXFLAGS += -DWIN32_LEAN_AND_MEAN
@@ -18,7 +18,7 @@ ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstrin
 endif
 
 LDLIBS = \
-	-lboost_system$(BOOST_SUFFIX) \
+	-lboost_filesystem$(BOOST_SUFFIX) \
 	-lboost_program_options$(BOOST_SUFFIX) \
 	-lssl \
 	-lcrypto \
@@ -44,6 +44,9 @@ test-gost: test-gost.cpp $(LIBI2PD)
 test-gost-sig: test-gost-sig.cpp $(LIBI2PD)
 	$(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
 
+test-x25519: test-x25519.cpp $(LIBI2PD)
+	$(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
+
 test-aeadchacha20poly1305: test-aeadchacha20poly1305.cpp $(LIBI2PD)
 	 $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
 
@@ -56,9 +59,6 @@ test-elligator: test-elligator.cpp $(LIBI2PD)
 test-eddsa: test-eddsa.cpp $(LIBI2PD)
 	$(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
 
-test-aes: test-aes.cpp $(LIBI2PD)
-	$(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
-
 run: $(TESTS)
 	@for TEST in $(TESTS); do echo Running $$TEST; ./$$TEST ; done
 
diff --git a/tests/test-aeadchacha20poly1305.cpp b/tests/test-aeadchacha20poly1305.cpp
index 2ba6a253..64a0f358 100644
--- a/tests/test-aeadchacha20poly1305.cpp
+++ b/tests/test-aeadchacha20poly1305.cpp
@@ -43,20 +43,18 @@ uint8_t encrypted[114] =
 int main ()
 {
 	uint8_t buf[114+16];
-    i2p::crypto::AEADChaCha20Poly1305Encryptor encryptor;
 	// test encryption
-	encryptor.Encrypt ((uint8_t *)text, 114, ad, 12, key, nonce, buf, 114 + 16);
+	i2p::crypto::AEADChaCha20Poly1305 ((uint8_t *)text, 114, ad, 12, key, nonce, buf, 114 + 16, true);
 	assert (memcmp (buf, encrypted, 114) == 0);
 	assert (memcmp (buf + 114, tag, 16) == 0);
 	// test decryption
 	uint8_t buf1[114];
-    i2p::crypto::AEADChaCha20Poly1305Decryptor decryptor;
-	assert (decryptor.Decrypt (buf, 114, ad, 12, key, nonce, buf1, 114));
+	assert (i2p::crypto::AEADChaCha20Poly1305 (buf, 114, ad, 12, key, nonce, buf1, 114, false));
 	assert (memcmp (buf1, text, 114) == 0);
 	// test encryption of multiple buffers
 	memcpy (buf, text, 114);
 	std::vector<std::pair<uint8_t*, std::size_t> > bufs{ std::make_pair (buf, 20), std::make_pair (buf + 20, 10), std::make_pair (buf + 30, 70), std::make_pair (buf + 100, 14) };
-	encryptor.Encrypt (bufs, key, nonce, buf + 114);
-	decryptor.Decrypt (buf, 114, nullptr, 0, key, nonce, buf1, 114);
+	i2p::crypto::AEADChaCha20Poly1305Encrypt (bufs, key, nonce, buf + 114);
+	i2p::crypto::AEADChaCha20Poly1305 (buf, 114, nullptr, 0, key, nonce, buf1, 114, false);
 	assert (memcmp (buf1, text, 114) == 0);
 }
diff --git a/tests/test-aes.cpp b/tests/test-aes.cpp
deleted file mode 100644
index 15f4de1e..00000000
--- a/tests/test-aes.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-#include <cassert>
-#include <inttypes.h>
-#include <string.h>
-
-#include "Crypto.h"
-
-uint8_t ecb_key1[32] =
-{
-    0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81,
-    0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4
-};
-
-uint8_t ecb_plain1[16] =
-{
-    0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a
-};
-
-uint8_t ecb_cipher1[16] = 
-{
-    0xf3, 0xee, 0xd1, 0xbd, 0xb5, 0xd2, 0xa0, 0x3c, 0x06, 0x4b, 0x5a, 0x7e, 0x3d, 0xb1, 0x81, 0xf8
-};
-
-uint8_t cbc_key1[32] =
-{
-    0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81,
-    0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4
-};
-
-uint8_t cbc_iv1[16] =
-{
-    0xF5, 0x8C, 0x4C, 0x04, 0xD6, 0xE5, 0xF1, 0xBA, 0x77, 0x9E, 0xAB, 0xFB, 0x5F, 0x7B, 0xFB, 0xD6
-};
-
-uint8_t cbc_plain1[16] =
-{
-    0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51
-};
-
-uint8_t cbc_cipher1[16] = 
-{
-    0x9c, 0xfc, 0x4e, 0x96, 0x7e, 0xdb, 0x80, 0x8d, 0x67, 0x9f, 0x77, 0x7b, 0xc6, 0x70, 0x2c, 0x7d
-};
-
-int main ()
-{
-    // ECB encrypt test1
-    i2p::crypto::ECBEncryption ecbencryption;
-    ecbencryption.SetKey (ecb_key1);
-    uint8_t out[16];
-    ecbencryption.Encrypt (ecb_plain1, out);
-    assert (memcmp (ecb_cipher1, out, 16) == 0);
-
-    // ECB decrypt test1
-    i2p::crypto::ECBDecryption ecbdecryption;
-    ecbdecryption.SetKey (ecb_key1);
-    ecbdecryption.Decrypt (ecb_cipher1, out);
-    assert (memcmp (ecb_plain1, out, 16) == 0);
-    // CBC encrypt test
-    i2p::crypto::CBCEncryption cbcencryption;
-    cbcencryption.SetKey (cbc_key1);
-    cbcencryption.Encrypt (cbc_plain1, 16, cbc_iv1, out);
-    assert (memcmp (cbc_cipher1, out, 16) == 0);
-    // CBC decrypt test
-    i2p::crypto::CBCDecryption cbcdecryption;
-    cbcdecryption.SetKey (cbc_key1);
-    cbcdecryption.Decrypt (cbc_cipher1, 16, cbc_iv1, out);
-    assert (memcmp (cbc_plain1, out, 16) == 0);
-}
-
diff --git a/tests/test-x25519.cpp b/tests/test-x25519.cpp
new file mode 100644
index 00000000..a1f3f424
--- /dev/null
+++ b/tests/test-x25519.cpp
@@ -0,0 +1,38 @@
+#include <cassert>
+#include <inttypes.h>
+#include <string.h>
+
+#include "Ed25519.h"
+
+const uint8_t k[32] =
+{
+    0xa5, 0x46, 0xe3, 0x6b, 0xf0, 0x52, 0x7c, 0x9d, 0x3b, 0x16, 0x15,
+    0x4b, 0x82, 0x46, 0x5e, 0xdd, 0x62, 0x14, 0x4c, 0x0a, 0xc1, 0xfc,
+    0x5a, 0x18, 0x50, 0x6a, 0x22, 0x44, 0xba, 0x44, 0x9a, 0xc4
+};
+
+const uint8_t u[32] =
+{
+    0xe6, 0xdb, 0x68, 0x67, 0x58, 0x30, 0x30, 0xdb, 0x35, 0x94, 0xc1,
+    0xa4, 0x24, 0xb1, 0x5f, 0x7c, 0x72, 0x66, 0x24, 0xec, 0x26, 0xb3,
+    0x35, 0x3b, 0x10, 0xa9, 0x03, 0xa6, 0xd0, 0xab, 0x1c, 0x4c
+};
+
+uint8_t p[32] =
+{
+    0xc3, 0xda, 0x55, 0x37, 0x9d, 0xe9, 0xc6, 0x90, 0x8e, 0x94, 0xea,
+    0x4d, 0xf2, 0x8d, 0x08, 0x4f, 0x32, 0xec, 0xcf, 0x03, 0x49, 0x1c,
+    0x71, 0xf7, 0x54, 0xb4, 0x07, 0x55, 0x77, 0xa2, 0x85, 0x52
+};
+
+int main ()
+{
+#if !OPENSSL_X25519
+// we test it for openssl < 1.1.0
+    uint8_t buf[32];
+    BN_CTX * ctx = BN_CTX_new ();
+    i2p::crypto::GetEd25519 ()->ScalarMul (u, k, buf, ctx);
+    BN_CTX_free (ctx);
+    assert(memcmp (buf, p, 32) == 0);
+#endif
+}