diff --git a/dap-sdk/core/CMakeLists.txt b/dap-sdk/core/CMakeLists.txt
index 31c0eea678deae9981cbe572f79b2145150b5bc3..c7d865b88b1e14f2e06fc7a0e61a20353338904e 100755
--- a/dap-sdk/core/CMakeLists.txt
+++ b/dap-sdk/core/CMakeLists.txt
@@ -100,7 +100,7 @@ endif()
 
 if(ANDROID)
     add_subdirectory(src/android)
-    target_link_libraries(${PROJECT_NAME} dap_core_android)
+    target_link_libraries(${PROJECT_NAME} dap_core_android rt)
 endif()
 
 if (WIN32)
diff --git a/dap-sdk/core/include/dap_strfuncs.h b/dap-sdk/core/include/dap_strfuncs.h
index ea2f6c20bc7b6566b4158121402b3ea414c9a352..c581294cb9df00d18df510e4425c3e4f8787414e 100755
--- a/dap-sdk/core/include/dap_strfuncs.h
+++ b/dap-sdk/core/include/dap_strfuncs.h
@@ -108,7 +108,7 @@ char *dap_itoa128(char *a_str, int128_t a_value, int a_base);
 #ifdef HAVE_STRNDUP
 #define strndup(s, l) _strndup(s, l)
 #endif
-char *_strndup(char *str, unsigned long len);
+char *_strndup(const char *str, unsigned long len);
 #endif
 
 #define DAP_USEC_PER_SEC 1000000
diff --git a/dap-sdk/core/src/dap_common.c b/dap-sdk/core/src/dap_common.c
index c2c8cf55f7108cf6e177d04c244346745897f06a..3803cfee430abe230ac629a1da667c775695e799 100755
--- a/dap-sdk/core/src/dap_common.c
+++ b/dap-sdk/core/src/dap_common.c
@@ -827,7 +827,7 @@ static int s_timer_find(void *a_timer)
 static void CALLBACK s_win_callback(PVOID a_arg, BOOLEAN a_always_true)
 {
     UNUSED(a_always_true);
-    s_timers[(int)a_arg].callback(s_timers[(int)a_arg].param);
+    s_timers[(size_t)a_arg].callback(s_timers[(size_t)a_arg].param);
 }
 #elif defined __MACH__
 static void s_bsd_callback(int a_arg)
@@ -852,12 +852,12 @@ void *dap_interval_timer_create(unsigned int a_msec, dap_timer_callback_t a_call
     if (s_timers_count == DAP_INTERVAL_TIMERS_MAX) {
         return NULL;
     }
-#ifdef _WIN32
+#ifdef WIN32
     if (s_timers_count == 0) {
         InitializeCriticalSection(&s_timers_lock);
     }
     HANDLE l_timer;
-    if (!CreateTimerQueueTimer(&l_timer, NULL, (WAITORTIMERCALLBACK)s_win_callback, (PVOID)s_timers_count, a_msec, a_msec, 0)) {
+    if (!CreateTimerQueueTimer(&l_timer, NULL, (WAITORTIMERCALLBACK)s_win_callback, (PVOID)(size_t)s_timers_count, a_msec, a_msec, 0)) {
         return NULL;
     }
     EnterCriticalSection(&s_timers_lock);
@@ -888,6 +888,8 @@ void *dap_interval_timer_create(unsigned int a_msec, dap_timer_callback_t a_call
     l_period.it_interval.tv_nsec = l_period.it_value.tv_nsec = (a_msec % 1000) * 1000000;
     timer_settime(l_timer, 0, &l_period, NULL);
     pthread_mutex_lock(&s_timers_lock);
+#else
+    //DARWIN
 #endif
     s_timers[s_timers_count].timer = (void *)l_timer;
     s_timers[s_timers_count].callback = a_callback;
@@ -895,8 +897,10 @@ void *dap_interval_timer_create(unsigned int a_msec, dap_timer_callback_t a_call
     s_timers_count++;
 #ifdef _WIN32
     LeaveCriticalSection(&s_timers_lock);
-#else
+#elif DAP_OS_UNIX
     pthread_mutex_unlock(&s_timers_lock);
+#else
+    //DARWIN
 #endif
     return (void *)l_timer;
 }
@@ -913,8 +917,10 @@ int dap_interval_timer_delete(void *a_timer)
     }
 #ifdef _WIN32
     EnterCriticalSection(&s_timers_lock);
-#else
+#elif DAP_OS_UNIX
     pthread_mutex_lock(&s_timers_lock);
+#else
+    //DARWIN
 #endif
     int l_timer_idx = s_timer_find(a_timer);
     if (l_timer_idx == -1) {
@@ -930,7 +936,8 @@ int dap_interval_timer_delete(void *a_timer)
         DeleteCriticalSection(&s_timers_lock);
     }
     return !DeleteTimerQueueTimer(NULL, (HANDLE)a_timer, NULL);
-#else
+#elif DAP_OS_UNIX
+    pthread_mutex_unlock(&s_timers_lock);
     if (s_timers_count == 0) {
         pthread_mutex_destroy(&s_timers_lock);
     }
@@ -939,6 +946,8 @@ int dap_interval_timer_delete(void *a_timer)
     return 0;
 #else
     return timer_delete((timer_t)a_timer);
+#else
+   //DARWIN
 #endif
 #endif
 }
diff --git a/dap-sdk/core/src/dap_strfuncs.c b/dap-sdk/core/src/dap_strfuncs.c
index 99180b36fc207d2a3d345a69d344d711815dc6e1..2685947f96e330bdc41bb865cfac7434e6e2d326 100755
--- a/dap-sdk/core/src/dap_strfuncs.c
+++ b/dap-sdk/core/src/dap_strfuncs.c
@@ -855,7 +855,7 @@ char *strptime( char *buff, const char *fmt, struct tm *tm ) {
   return buff + len;
 }
 
-char *_strndup(char *str, unsigned long len) {
+char *_strndup(const char *str, unsigned long len) {
     char *buf = (char*)memchr(str, '\0', len);
     if (buf)
         len = buf - str;
diff --git a/dap-sdk/crypto/CMakeLists.txt b/dap-sdk/crypto/CMakeLists.txt
index 51423bd00b470a69dadef7c609044279744e2b42..0d5a8a979c32373f627da157cae3e8ade9afa8ff 100755
--- a/dap-sdk/crypto/CMakeLists.txt
+++ b/dap-sdk/crypto/CMakeLists.txt
@@ -92,7 +92,7 @@ endif()
 
 add_library(${PROJECT_NAME} STATIC ${CRYPTO_SRCS} ${XKCP_SRCS} ${XKCP_SRCS2} ${CRYPTO_HEADERS} )
 
-target_include_directories(dap_crypto PRIVATE src/rand src/iaes src/oaes src/sha3 src/msrln src/defeo_scheme src/sig_bliss src/sig_tesla src/sig_picnic src/sig_dilithium src include)
+target_include_directories(dap_crypto PRIVATE src/seed src/rand src/iaes src/oaes src/sha3 src/msrln src/defeo_scheme src/sig_bliss src/sig_tesla src/sig_picnic src/sig_dilithium src include)
 target_include_directories(dap_crypto INTERFACE src/ src/sha3 include/)
 
 target_include_directories(dap_crypto PUBLIC
diff --git a/dap-sdk/crypto/src/GOST/28147_14.c b/dap-sdk/crypto/src/GOST/28147_14.c
index 69133fa7771e4083c7b526f2b06627af4723660d..7b49540a848f09aeacb7d0cbe04236c429b9fd2c 100644
--- a/dap-sdk/crypto/src/GOST/28147_14.c
+++ b/dap-sdk/crypto/src/GOST/28147_14.c
@@ -403,7 +403,7 @@ int DLL_IMPORT  ExpandKey(unsigned char* masterKey, unsigned char* keys, printou
      return 0;
 }
 
-int DLL_IMPORT  Encrypt_14(unsigned char* plainText, unsigned char* chipherText, unsigned char* keys, printout_byte_array print, printout_uint_array print_uint)
+int DLL_IMPORT  Encrypt_14(const unsigned char* plainText, unsigned char* chipherText, unsigned char* keys, printout_byte_array print, printout_uint_array print_uint)
 {
      unsigned char xTemp[16];
      unsigned char yTemp[16];
@@ -442,7 +442,7 @@ int DLL_IMPORT  Encrypt_14(unsigned char* plainText, unsigned char* chipherText,
      return 0;
 }
 
-int DLL_IMPORT  Decrypt_14(unsigned char* chipherText, unsigned char* plainText, unsigned char* keys, printout_byte_array print, printout_uint_array print_uint)
+int DLL_IMPORT  Decrypt_14(const unsigned char* chipherText, unsigned char* plainText, unsigned char* keys, printout_byte_array print, printout_uint_array print_uint)
 {
      unsigned char xTemp[16];
      unsigned char yTemp[16];
@@ -478,4 +478,4 @@ int DLL_IMPORT  Decrypt_14(unsigned char* chipherText, unsigned char* plainText,
      }
 
      return 0;
-}
\ No newline at end of file
+}
diff --git a/dap-sdk/crypto/src/GOST/28147_14.h b/dap-sdk/crypto/src/GOST/28147_14.h
index 80ac954416dea6970a459373d8d61e691f94512d..2bc201a9d42e8da24a29076197b3da02d10d402d 100644
--- a/dap-sdk/crypto/src/GOST/28147_14.h
+++ b/dap-sdk/crypto/src/GOST/28147_14.h
@@ -151,7 +151,7 @@ int DLL_IMPORT ExpandKey(unsigned char* masterKey, unsigned char* keys, printout
  * @return 0 если все преобразование прошло успешно
  * @return -1 если произошла ошибка
  */
-int DLL_IMPORT Encrypt_14(unsigned char* plainText, unsigned char* chipherText, unsigned char* keys, printout_byte_array print, printout_uint_array print_uint);
+int DLL_IMPORT Encrypt_14(const unsigned char* plainText, unsigned char* chipherText, unsigned char* keys, printout_byte_array print, printout_uint_array print_uint);
 
 /** @brief Выполение расшифрования блока
  * 
@@ -163,7 +163,7 @@ int DLL_IMPORT Encrypt_14(unsigned char* plainText, unsigned char* chipherText,
  * @return 0 если все преобразование прошло успешно
  * @return -1 если произошла ошибка
  */
-int DLL_IMPORT Decrypt_14(unsigned char* chipherText, unsigned char* plainText, unsigned char* keys, printout_byte_array print, printout_uint_array print_uint);
+int DLL_IMPORT Decrypt_14(const unsigned char* chipherText, unsigned char* plainText, unsigned char* keys, printout_byte_array print, printout_uint_array print_uint);
 
 #ifdef __cplusplus
 }
diff --git a/dap-sdk/crypto/src/GOST/28147_89.c b/dap-sdk/crypto/src/GOST/28147_89.c
index 0f45378cac7b356a8b8247a52407aec8705ea979..60eda929366dc55ebc41eea0d290883e7685c40b 100644
--- a/dap-sdk/crypto/src/GOST/28147_89.c
+++ b/dap-sdk/crypto/src/GOST/28147_89.c
@@ -11,7 +11,7 @@
  * @param[in] input массив из 4 байт
  * @return int32 число
  */
-static unsigned int uint8ToUint32(unsigned char* input)
+static unsigned int uint8ToUint32(const unsigned char* input)
 {
      unsigned int r = ( (input[3]) | (input[2]<<8) | (input[1]<<16) | (input[0]<<24));
      return r;
@@ -143,7 +143,7 @@ void DLL_IMPORT RoundShtrih(unsigned int* a1, unsigned int* a0, unsigned int k,
      }
 }
 
-int DLL_IMPORT CryptBlock(unsigned char* input, unsigned char* output, unsigned char* key, unsigned char* keyIndex, printout_uint_array print)
+int DLL_IMPORT CryptBlock(const unsigned char* input, unsigned char* output, unsigned char* key, unsigned char* keyIndex, printout_uint_array print)
 {
      unsigned int a1 = uint8ToUint32(input);
      unsigned int a0 = uint8ToUint32(input + 4);
@@ -175,17 +175,17 @@ int DLL_IMPORT CryptBlock(unsigned char* input, unsigned char* output, unsigned
      return 0;
 }
 
-int DLL_IMPORT EncryptBlock(unsigned char* input, unsigned char* output, unsigned char* key, printout_uint_array print)
+int DLL_IMPORT EncryptBlock(const unsigned char* input, unsigned char* output, unsigned char* key, printout_uint_array print)
 {
      return CryptBlock(input, output, key, kEncRoundKey, print);
 }
 
-int DLL_IMPORT DecryptBlock(unsigned char* input, unsigned char* output, unsigned char* key, printout_uint_array print)
+int DLL_IMPORT DecryptBlock(const unsigned char* input, unsigned char* output, unsigned char* key, printout_uint_array print)
 {
      return CryptBlock(input, output, key, kDecRoundKey, print);
 }
 
-int DLL_IMPORT Encrypt_89(unsigned char* input, unsigned char* output, unsigned char* key, printout_byte_array print, printout_uint_array print_uint)
+int DLL_IMPORT Encrypt_89(const unsigned char* input, unsigned char* output, unsigned char* key, printout_byte_array print, printout_uint_array print_uint)
 {
      if( !input || !output || !key )
      {
@@ -206,7 +206,7 @@ int DLL_IMPORT Encrypt_89(unsigned char* input, unsigned char* output, unsigned
      return 0;
 }
 
-int DLL_IMPORT Decrypt_89(unsigned char* input, unsigned char* output, unsigned char* key, printout_byte_array print, printout_uint_array print_uint)
+int DLL_IMPORT Decrypt_89(const unsigned char* input, unsigned char* output, unsigned char* key, printout_byte_array print, printout_uint_array print_uint)
 {
      if( !input || !output || !key )
      {
diff --git a/dap-sdk/crypto/src/GOST/28147_89.h b/dap-sdk/crypto/src/GOST/28147_89.h
index a0f78eb8a7ae1be046866357be601aafddd1c2e0..5a80f03a45c4f0553dfa2f5fb6f8a61443f90b41 100644
--- a/dap-sdk/crypto/src/GOST/28147_89.h
+++ b/dap-sdk/crypto/src/GOST/28147_89.h
@@ -59,7 +59,7 @@ void DLL_IMPORT RoundShtrih(unsigned int *a1, unsigned int *a0, unsigned int k,
  * @return 0 если все преобразование прошло успешно
  * @return -1 если произошла ошибка
  */
-int DLL_IMPORT CryptBlock(unsigned char* input, unsigned char* output, unsigned char* key, unsigned char* keySequence, printout_uint_array print);
+int DLL_IMPORT CryptBlock(const unsigned char* input, unsigned char* output, unsigned char* key, unsigned char* keySequence, printout_uint_array print);
 
 /** @brief Шифруем блок данных
  *
@@ -70,7 +70,7 @@ int DLL_IMPORT CryptBlock(unsigned char* input, unsigned char* output, unsigned
  * @return 0 если все преобразование прошло успешно
  * @return -1 если произошла ошибка
  */
-int DLL_IMPORT EncryptBlock(unsigned char* input, unsigned char* output, unsigned char* key, printout_uint_array print);
+int DLL_IMPORT EncryptBlock(const unsigned char* input, unsigned char* output, unsigned char* key, printout_uint_array print);
 
 /** @brief Расшифровываем блок данных
  *
@@ -81,7 +81,7 @@ int DLL_IMPORT EncryptBlock(unsigned char* input, unsigned char* output, unsigne
  * @return 0 если все преобразование прошло успешно
  * @return -1 если произошла ошибка
  */
-int DLL_IMPORT DecryptBlock(unsigned char* input, unsigned char* output, unsigned char* key, printout_uint_array print);
+int DLL_IMPORT DecryptBlock(const unsigned char* input, unsigned char* output, unsigned char* key, printout_uint_array print);
 
 /** @brief Шифруем блок данных
  *
@@ -93,7 +93,7 @@ int DLL_IMPORT DecryptBlock(unsigned char* input, unsigned char* output, unsigne
  * @return 0 если все преобразование прошло успешно
  * @return -1 если произошла ошибка
  */
-int DLL_IMPORT Encrypt_89(unsigned char* input, unsigned char* output, unsigned char* key, printout_byte_array print, printout_uint_array print_uint);
+int DLL_IMPORT Encrypt_89(const unsigned char* input, unsigned char* output, unsigned char* key, printout_byte_array print, printout_uint_array print_uint);
 
 /** @brief Расшифровываем блок данных
  *
@@ -105,7 +105,7 @@ int DLL_IMPORT Encrypt_89(unsigned char* input, unsigned char* output, unsigned
  * @return 0 если все преобразование прошло успешно
  * @return -1 если произошла ошибка
  */
-int DLL_IMPORT Decrypt_89(unsigned char* input, unsigned char* output, unsigned char* key, printout_byte_array print, printout_uint_array print_uint);
+int DLL_IMPORT Decrypt_89(const unsigned char* input, unsigned char* output, unsigned char* key, printout_byte_array print, printout_uint_array print_uint);
 
 #ifdef __cplusplus
 }
diff --git a/dap-sdk/crypto/src/GOST/GOST.pri b/dap-sdk/crypto/src/GOST/GOST.pri
index 176c5281c593784d1dbd5853b8aef35a2d1ba64c..2f44dc32c02c18288edaffcd53567d5c4205326d 100644
--- a/dap-sdk/crypto/src/GOST/GOST.pri
+++ b/dap-sdk/crypto/src/GOST/GOST.pri
@@ -6,11 +6,9 @@ HEADERS += $$PWD/28147_14.h \
            $$PWD/dll_import.h \
            $$PWD/print_data.h \
            $$PWD/table.h \
-           $$PWD/test_data.inc \
            $$PWD/block_cipher.h
 
 SOURCES += $$PWD/28147_14.c \
            $$PWD/28147_89.c \
            $$PWD/block_cipher.c \
            $$PWD/print_data.c \
-           $$PWD/testgost.c
diff --git a/dap-sdk/crypto/src/GOST/block_cipher.c b/dap-sdk/crypto/src/GOST/block_cipher.c
index e6b9d16fd0a2faed222b152a08edf7b559cf4db2..93175427f0e22cb99207cc8cf1fec9060090b65b 100644
--- a/dap-sdk/crypto/src/GOST/block_cipher.c
+++ b/dap-sdk/crypto/src/GOST/block_cipher.c
@@ -32,10 +32,10 @@ const unsigned char kAlg14 = 1;
 const unsigned char kAlg89 = 2;
 
 /** @brief указатель на функцию шифрования */
-typedef int (DLL_IMPORT *pEncrypt)(unsigned char* plainText, unsigned char* chipherText, unsigned char* keys, printout_byte_array print, printout_uint_array print_uint);
+typedef int (DLL_IMPORT *pEncrypt)(const unsigned char* plainText, unsigned char* chipherText, unsigned char* keys, printout_byte_array print, printout_uint_array print_uint);
 
 /** @brief указатель на функцию расшифрования */
-typedef int (DLL_IMPORT *pDecrypt)(unsigned char* chipherText, unsigned char* plainText, unsigned char* keys, printout_byte_array print, printout_uint_array print_uint);
+typedef int (DLL_IMPORT *pDecrypt)(const unsigned char* chipherText, unsigned char* plainText, unsigned char* keys, printout_byte_array print, printout_uint_array print_uint);
 
 /** @brief Функция самотестирования режима ECB */
 static int SelfTestGost14Ecb();
@@ -260,7 +260,7 @@ void DLL_IMPORT free_ecb(void* ctx)
      }
 }
 
-static int init_cbc_14_impl(unsigned char *key, void* ctx, unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
+static int init_cbc_14_impl(unsigned char *key, void* ctx, const unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
 {
      Context_cbc* context;
      INFOTECS_ASSERT(sizeof(Context_cbc)<=kCbc14ContextLen);
@@ -297,14 +297,14 @@ static int init_cbc_14_impl(unsigned char *key, void* ctx, unsigned char *iv, si
      return 0;
 }
 
-int DLL_IMPORT init_cbc_14(unsigned char *key, void* ctx, unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
+int DLL_IMPORT init_cbc_14(unsigned char *key, void* ctx, const unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
 {
      if(SelfTestGost14Cbc())
           return -1;
      return init_cbc_14_impl(key, ctx, iv, ivLength, print, print_uint);
 }
 
-static int init_cbc_89_impl(unsigned char *key, void* ctx, unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
+static int init_cbc_89_impl(unsigned char *key, void* ctx, const unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
 {
      Context_cbc* context;
      INFOTECS_ASSERT(sizeof(Context_cbc)<=kCbc89ContextLen);
@@ -341,7 +341,7 @@ static int init_cbc_89_impl(unsigned char *key, void* ctx, unsigned char *iv, si
      return 0;
 }
 
-int DLL_IMPORT init_cbc_89(unsigned char *key, void* ctx, unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
+int DLL_IMPORT init_cbc_89(unsigned char *key, void* ctx, const unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
 {
      if(SelfTestGost89Cbc())
           return -1;
@@ -386,7 +386,7 @@ void DLL_IMPORT free_cbc(void* ctx)
      }
 }
 
-static int init_ctr_14_impl(unsigned char* key, unsigned char *iv, size_t s, void *ctx, printout_byte_array print, printout_uint_array print_uint)
+static int init_ctr_14_impl(unsigned char* key, const unsigned char *iv, size_t s, void *ctx, printout_byte_array print, printout_uint_array print_uint)
 {
      Context_ctr* context;
      INFOTECS_ASSERT(sizeof(Context_ctr)<=kCtr14ContextLen);
@@ -422,14 +422,14 @@ static int init_ctr_14_impl(unsigned char* key, unsigned char *iv, size_t s, voi
      return 0;
 }
 
-int DLL_IMPORT init_ctr_14(unsigned char* key, unsigned char *iv, size_t s, void *ctx, printout_byte_array print, printout_uint_array print_uint)
+int DLL_IMPORT init_ctr_14(unsigned char* key, const unsigned char *iv, size_t s, void *ctx, printout_byte_array print, printout_uint_array print_uint)
 {
      if(SelfTestGost14Ctr())
           return -1;
      return init_ctr_14_impl(key, iv, s, ctx, print, print_uint);
 }
 
-static int init_ctr_89_impl(unsigned char* key, unsigned char *iv, size_t s, void *ctx, printout_byte_array print, printout_uint_array print_uint)
+static int init_ctr_89_impl(unsigned char* key, const unsigned char *iv, size_t s, void *ctx, printout_byte_array print, printout_uint_array print_uint)
 {
      Context_ctr* context;
      INFOTECS_ASSERT(sizeof(Context_ctr)<=kCtr89ContextLen);
@@ -464,7 +464,7 @@ static int init_ctr_89_impl(unsigned char* key, unsigned char *iv, size_t s, voi
      return 0;
 }
 
-int DLL_IMPORT init_ctr_89(unsigned char* key, unsigned char *iv, size_t s, void *ctx, printout_byte_array print, printout_uint_array print_uint)
+int DLL_IMPORT init_ctr_89(unsigned char* key, const unsigned char *iv, size_t s, void *ctx, printout_byte_array print, printout_uint_array print_uint)
 {
      if(SelfTestGost89Ctr())
           return -1;
@@ -499,7 +499,7 @@ void DLL_IMPORT free_ctr(void* ctx)
      }
 }
 
-static int init_ofb_14_impl(unsigned char *key, void *ctx, size_t s, unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
+static int init_ofb_14_impl(unsigned char *key, void *ctx, size_t s, const unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
 {
      Context_ofb* context;
      INFOTECS_ASSERT(sizeof(Context_ofb)<=kOfb14ContextLen);
@@ -546,7 +546,7 @@ int DLL_IMPORT init_ofb_14(unsigned char *key, void *ctx, size_t s, const unsign
      return init_ofb_14_impl(key, ctx, s, iv, ivLength, print, print_uint);
 }
 
-static int init_ofb_89_impl(unsigned char *key, void *ctx, size_t s, unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
+static int init_ofb_89_impl(unsigned char *key, void *ctx, size_t s, const unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
 {
      Context_ofb* context;
      INFOTECS_ASSERT(sizeof(Context_ofb)<=kOfb89ContextLen);
@@ -584,7 +584,7 @@ static int init_ofb_89_impl(unsigned char *key, void *ctx, size_t s, unsigned ch
      return 0;
 }
 
-int DLL_IMPORT init_ofb_89(unsigned char *key, void *ctx, size_t s, unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
+int DLL_IMPORT init_ofb_89(unsigned char *key, void *ctx, size_t s, const unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
 {
      if(SelfTestGost89Ofb())
           return -1;
@@ -626,7 +626,7 @@ void DLL_IMPORT free_ofb(void* ctx)
 
 }
 
-static int init_cfb_14_impl(unsigned char *key, void *ctx, size_t s, unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
+static int init_cfb_14_impl(unsigned char *key, void *ctx, size_t s, const unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
 {
      Context_cfb* context;
      INFOTECS_ASSERT(sizeof(Context_cfb)<=kCfb14ContextLen);
@@ -665,14 +665,14 @@ static int init_cfb_14_impl(unsigned char *key, void *ctx, size_t s, unsigned ch
      return 0;
 }
 
-int DLL_IMPORT init_cfb_14(unsigned char *key, void *ctx, size_t s, unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
+int DLL_IMPORT init_cfb_14(unsigned char *key, void *ctx, size_t s, const unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
 {
      if(SelfTestGost14Cfb())
           return -1;
      return init_cfb_14_impl(key, ctx, s, iv, ivLength, print, print_uint);
 }
 
-static int init_cfb_89_impl(unsigned char *key, void *ctx, size_t s, unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
+static int init_cfb_89_impl(unsigned char *key, void *ctx, size_t s, const unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
 {
      Context_cfb* context;
      INFOTECS_ASSERT(sizeof(Context_cfb)<=kCfb89ContextLen);
@@ -710,7 +710,7 @@ static int init_cfb_89_impl(unsigned char *key, void *ctx, size_t s, unsigned ch
      return 0; 
 }
 
-int DLL_IMPORT init_cfb_89(unsigned char *key, void *ctx, size_t s, unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
+int DLL_IMPORT init_cfb_89(unsigned char *key, void *ctx, size_t s, const unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint)
 {
      if(SelfTestGost89Cfb())
           return -1;
@@ -1047,7 +1047,7 @@ int DLL_IMPORT decrypt_ecb(void *ctx, const unsigned char *indata, unsigned char
      return 0;
 }
 
-static void PackBlock(unsigned char* a, size_t aLen, unsigned char* b, unsigned char* r, size_t rLen)
+static void PackBlock(unsigned char* a, size_t aLen, const unsigned char* b, unsigned char* r, size_t rLen)
 {
      memcpy(r, a, aLen);
      memcpy(r + aLen, b, rLen - aLen);
diff --git a/dap-sdk/crypto/src/GOST/block_cipher.h b/dap-sdk/crypto/src/GOST/block_cipher.h
index 4a6315ee1540c59ea0fc4fa651836721161e9212..00f60cc5643dbdbc8b6db944c1d11b407ef1ae55 100644
--- a/dap-sdk/crypto/src/GOST/block_cipher.h
+++ b/dap-sdk/crypto/src/GOST/block_cipher.h
@@ -105,7 +105,7 @@ void DLL_IMPORT free_ecb(void* ctx);
  * @return 0 если все преобразование прошло успешно
  * @return -1 если произошла ошибка
  */
-int DLL_IMPORT  init_cbc_14(unsigned char *key, void* ctx, unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint);
+int DLL_IMPORT  init_cbc_14(unsigned char *key, void* ctx, const unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint);
 
 /** @brief Инициализация контекста шифрования в режиме CBC для алгоритма 28147-89
  *
@@ -118,7 +118,7 @@ int DLL_IMPORT  init_cbc_14(unsigned char *key, void* ctx, unsigned char *iv, si
  * @return 0 если все преобразование прошло успешно
  * @return -1 если произошла ошибка
  */
-int DLL_IMPORT init_cbc_89(unsigned char *key, void* ctx, unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint);
+int DLL_IMPORT init_cbc_89(unsigned char *key, void* ctx, const unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint);
 
 /** @brief Удаление контекста cbc
  *
@@ -139,7 +139,7 @@ void DLL_IMPORT free_cbc(void* ctx);
  * @return 0 если все преобразование прошло успешно
  * @return -1 если произошла ошибка
  */
-int DLL_IMPORT init_ctr_14(unsigned char* key, unsigned char *iv, size_t length, void *ctx, printout_byte_array print, printout_uint_array print_uint);
+int DLL_IMPORT init_ctr_14(unsigned char* key, const unsigned char *iv, size_t length, void *ctx, printout_byte_array print, printout_uint_array print_uint);
 
 /** @brief Инициализация контекста шифрования в режиме CTR для алгоритма 28147-89
  *
@@ -152,7 +152,7 @@ int DLL_IMPORT init_ctr_14(unsigned char* key, unsigned char *iv, size_t length,
  * @return 0 если все преобразование прошло успешно
  * @return -1 если произошла ошибка
  */
-int DLL_IMPORT init_ctr_89(unsigned char* key, unsigned char *iv, size_t length, void *ctx, printout_byte_array print, printout_uint_array print_uint);
+int DLL_IMPORT init_ctr_89(unsigned char* key, const unsigned char *iv, size_t length, void *ctx, printout_byte_array print, printout_uint_array print_uint);
 
 /** @brief Удаление контекста ctr
  *
@@ -188,7 +188,7 @@ int DLL_IMPORT init_ofb_14(unsigned char *key, void *ctx, size_t s, const unsign
  * @return 0 если все преобразование прошло успешно
  * @return -1 если произошла ошибка
  */
-int DLL_IMPORT init_ofb_89(unsigned char *key, void *ctx, size_t s, unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint);
+int DLL_IMPORT init_ofb_89(unsigned char *key, void *ctx, size_t s, const unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint);
 
 /** @brief Удаление контекста ofb
  *
@@ -210,7 +210,7 @@ void DLL_IMPORT free_ofb(void* ctx);
  * @return 0 если все преобразование прошло успешно
  * @return -1 если произошла ошибка
  */
-int DLL_IMPORT init_cfb_14(unsigned char *key, void *ctx, size_t s, unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint);
+int DLL_IMPORT init_cfb_14(unsigned char *key, void *ctx, size_t s, const unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint);
 
 /** @brief Инициализация контекста шифрования в режиме CFB для алгоритма 28147-89
  *
@@ -224,7 +224,7 @@ int DLL_IMPORT init_cfb_14(unsigned char *key, void *ctx, size_t s, unsigned cha
  * @return 0 если все преобразование прошло успешно
  * @return -1 если произошла ошибка
  */
-int DLL_IMPORT init_cfb_89(unsigned char *key, void *ctx, size_t s, unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint);
+int DLL_IMPORT init_cfb_89(unsigned char *key, void *ctx, size_t s, const unsigned char *iv, size_t ivLength, printout_byte_array print, printout_uint_array print_uint);
 
 /** @brief Удаление контекста cfb
  *
diff --git a/dap-sdk/crypto/src/GOST/callback_print.h b/dap-sdk/crypto/src/GOST/callback_print.h
index 01e387f0db946f85d4eec859e0f26229362aa236..5d76323e3e3e74587ac7187190786d900bbaa22d 100644
--- a/dap-sdk/crypto/src/GOST/callback_print.h
+++ b/dap-sdk/crypto/src/GOST/callback_print.h
@@ -10,9 +10,9 @@
 #include "dll_import.h"
 
 /** @brief callback для вывода массива byte */
-typedef void (DLL_IMPORT *printout_byte_array)(const char* text, unsigned char* value, unsigned int valueSize);
+typedef void (DLL_IMPORT *printout_byte_array)(const char* text, const unsigned char* value, unsigned int valueSize);
 
 /** @brief callback для вывода массива unsigned int32 */
-typedef void (DLL_IMPORT *printout_uint_array)(const char* text, unsigned int* value, unsigned int valueSize);
+typedef void (DLL_IMPORT *printout_uint_array)(const char* text, const unsigned int* value, unsigned int valueSize);
 
 #endif
diff --git a/dap-sdk/crypto/src/GOST/test_data.inc b/dap-sdk/crypto/src/GOST/test_data.inc
deleted file mode 100644
index 94d893eb745e313b9bc06c72d25edfdf75b2b76d..0000000000000000000000000000000000000000
--- a/dap-sdk/crypto/src/GOST/test_data.inc
+++ /dev/null
@@ -1,313 +0,0 @@
-/*----------------------------------------------------------------------
- * тестовые значения для алгоритма кузнечик из стандарта
- */
-
-/* тестовые значения преобразования S */
-unsigned char kSData[5][kBlockLen14] = 
-{
-     {0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x00},
-     {0xb6, 0x6c, 0xd8, 0x88, 0x7d, 0x38, 0xe8, 0xd7, 0x77, 0x65, 0xae, 0xea, 0x0c, 0x9a, 0x7e, 0xfc},
-     {0x55, 0x9d, 0x8d, 0xd7, 0xbd, 0x06, 0xcb, 0xfe, 0x7e, 0x7b, 0x26, 0x25, 0x23, 0x28, 0x0d, 0x39},
-     {0x0c, 0x33, 0x22, 0xfe, 0xd5, 0x31, 0xe4, 0x63, 0x0d, 0x80, 0xef, 0x5c, 0x5a, 0x81, 0xc5, 0x0b},
-     {0x23, 0xae, 0x65, 0x63, 0x3f, 0x84, 0x2d, 0x29, 0xc5, 0xdf, 0x52, 0x9c, 0x13, 0xf5, 0xac, 0xda}
-};
-
-/* тестовые значения преобразования R */
-unsigned char kRData[5][kBlockLen14] = 
-{
-     {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00},
-     {0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
-     {0xa5, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
-     {0x64, 0xa5, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
-     {0x0d, 0x64, 0xa5, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
-};
-
-/* тестовые значения преобразования L */
-unsigned char kLData[5][kBlockLen14] = 
-{
-     {0x64, 0xa5, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
-     {0xd4, 0x56, 0x58, 0x4d, 0xd0, 0xe3, 0xe8, 0x4c, 0xc3, 0x16, 0x6e, 0x4b, 0x7f, 0xa2, 0x89, 0x0d},
-     {0x79, 0xd2, 0x62, 0x21, 0xb8, 0x7b, 0x58, 0x4c, 0xd4, 0x2f, 0xbc, 0x4f, 0xfe, 0xa5, 0xde, 0x9a},
-     {0x0e, 0x93, 0x69, 0x1a, 0x0c, 0xfc, 0x60, 0x40, 0x8b, 0x7b, 0x68, 0xf6, 0x6b, 0x51, 0x3c, 0x13},
-     {0xe6, 0xa8, 0x09, 0x4f, 0xee, 0x0a, 0xa2, 0x04, 0xfd, 0x97, 0xbc, 0xb0, 0xb4, 0x4b, 0x85, 0x80}
-};
-
-/* тестовые значения развернутых ключей */
-static const unsigned char kKData[10][kBlockLen14] =
-{
-     {0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77},
-     {0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
-     {0xdb, 0x31, 0x48, 0x53, 0x15, 0x69, 0x43, 0x43, 0x22, 0x8d, 0x6a, 0xef, 0x8c, 0xc7, 0x8c, 0x44},
-     {0x3d, 0x45, 0x53, 0xd8, 0xe9, 0xcf, 0xec, 0x68, 0x15, 0xeb, 0xad, 0xc4, 0x0a, 0x9f, 0xfd, 0x04},
-     {0x57, 0x64, 0x64, 0x68, 0xc4, 0x4a, 0x5e, 0x28, 0xd3, 0xe5, 0x92, 0x46, 0xf4, 0x29, 0xf1, 0xac},
-     {0xbd, 0x07, 0x94, 0x35, 0x16, 0x5c, 0x64, 0x32, 0xb5, 0x32, 0xe8, 0x28, 0x34, 0xda, 0x58, 0x1b},
-     {0x51, 0xe6, 0x40, 0x75, 0x7e, 0x87, 0x45, 0xde, 0x70, 0x57, 0x27, 0x26, 0x5a, 0x00, 0x98, 0xb1},
-     {0x5a, 0x79, 0x25, 0x01, 0x7b, 0x9f, 0xdd, 0x3e, 0xd7, 0x2a, 0x91, 0xa2, 0x22, 0x86, 0xf9, 0x84},
-     {0xbb, 0x44, 0xe2, 0x53, 0x78, 0xc7, 0x31, 0x23, 0xa5, 0xf3, 0x2f, 0x73, 0xcd, 0xb6, 0xe5, 0x17},
-     {0x72, 0xe9, 0xdd, 0x74, 0x16, 0xbc, 0xf4, 0x5b, 0x75, 0x5d, 0xba, 0xa8, 0x8e, 0x4a, 0x40, 0x43},
-};
-
-/* тестовые значения мастер-ключа */
-unsigned char kMasterKeyData[32] = 
-{
-     0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,0x77,
-     0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef
-}; 
-
-/* значение открытого текста */
-unsigned char kPlainTextData[] = 
-{
-     0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88
-}; 
-
-
-/* значение шифротекста */
-unsigned char kChipherTextData[] = 
-{
-     0x7f, 0x67, 0x9d, 0x90, 0xbe, 0xbc, 0x24, 0x30, 0x5a, 0x46, 0x8d, 0x42, 0xb9, 0xd4, 0xed, 0xcd
-}; 
-
-/* тестовые значения обратного преобразования L */
-unsigned char kReverseLData[2][kBlockLen14] = 
-{
-     {0x0d, 0x8e, 0x40, 0xe4, 0xa8, 0x00, 0xd0, 0x6b, 0x2f, 0x1b, 0x37, 0xea, 0x37, 0x9e, 0xad, 0x8e},
-     {0x8a, 0x6b, 0x93, 0x0a, 0x52, 0x21, 0x1b, 0x45, 0xc5, 0xba, 0xa4, 0x3f, 0xf8, 0xb9, 0x13, 0x19},
-};
-
-/* тестовые значения обратного преобразования LSX */
-unsigned char kReverseLSXData[10][kBlockLen14] = 
-{
-     {0x8a, 0x6b, 0x93, 0x0a, 0x52, 0x21, 0x1b, 0x45, 0xc5, 0xba, 0xa4, 0x3f, 0xf8, 0xb9, 0x13, 0x19},
-     {0x76, 0xca, 0x14, 0x9e, 0xef, 0x27, 0xdl, 0xbl, 0x0d, 0x17, 0xe3, 0xd5, 0xd6, 0x8e, 0x5a, 0x72},
-     {0x5d, 0x9b, 0x06, 0xd4, 0x1b, 0x9d, 0x1d, 0x2d, 0x04, 0xdf, 0x77, 0x55, 0x36, 0x3e, 0x94, 0xa9},
-     {0x79, 0x48, 0x71, 0x92, 0xaa, 0x45, 0x70, 0x9c, 0x11, 0x55, 0x59, 0xd6, 0xe9, 0x28, 0x0f, 0x6e},
-     {0xae, 0x50, 0x69, 0x24, 0xc8, 0xce, 0x33, 0x1b, 0xb9, 0x18, 0xfc, 0x5b, 0xdf, 0xbl, 0x95, 0xfa},
-     {0xbb, 0xff, 0xbf, 0xc8, 0x93, 0x9e, 0xaa, 0xff, 0xaf, 0xb8, 0xe2, 0x27, 0x69, 0xe3, 0x23, 0xaa},
-     {0x3c, 0xc2, 0xf0, 0x7c, 0xc0, 0x7a, 0x8b, 0xec, 0x0f, 0x3e, 0xa0, 0xed, 0x2a, 0xe3, 0x3e, 0x4a},
-     {0xf3, 0x6f, 0x01, 0x29, 0x1d, 0x0b, 0x96, 0xd5, 0x91, 0xe2, 0x28, 0xb7, 0x2d, 0x01, 0x1c, 0x36},
-     {0x1c, 0x4b, 0x0c, 0x1e, 0x95, 0x01, 0x82, 0xbl, 0xce, 0x69, 0x6a, 0xf5, 0xc0, 0xbf, 0xc5, 0xdf},
-     {0x99, 0xbb, 0x99, 0xff, 0x99, 0xbb, 0x99, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
-};
-
-unsigned char kGost14SV[8] =
-{
-     0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef
-};
-
-unsigned char kGost14PlainText[64] = 
-{
-     0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
-     0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xee, 0xff, 0x0a,
-     0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xee, 0xff, 0x0a, 0x00,
-     0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xee, 0xff, 0x0a, 0x00, 0x11
-};
-
-unsigned char kGost14EcbEncText[64] = 
-{
-     0x7f, 0x67, 0x9d, 0x90, 0xbe, 0xbc, 0x24, 0x30, 0x5a, 0x46, 0x8d, 0x42, 0xb9, 0xd4, 0xed, 0xcd,
-     0xb4, 0x29, 0x91, 0x2c, 0x6e, 0x00, 0x32, 0xf9, 0x28, 0x54, 0x52, 0xd7, 0x67, 0x18, 0xd0, 0x8b,
-     0xf0, 0xca, 0x33, 0x54, 0x9d, 0x24, 0x7c, 0xee, 0xf3, 0xf5, 0xa5, 0x31, 0x3b, 0xd4, 0xb1, 0x57,
-     0xd0, 0xb0, 0x9c, 0xcd, 0xe8, 0x30, 0xb9, 0xeb, 0x3a, 0x02, 0xc4, 0xc5, 0xaa, 0x8a, 0xda, 0x98
-};
-
-
-unsigned char kGost14CtrSV[16] =
-{
-     0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xce, 0xf0, 0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf0, 0x01, 0x12
-}; 
-
-unsigned char kGost14CtrC[64] = 
-{
-     0xf1, 0x95, 0xd8, 0xbe, 0xc1, 0x0e, 0xd1, 0xdb, 0xd5, 0x7b, 0x5f, 0xa2, 0x40, 0xbd, 0xa1, 0xb8,
-     0x85, 0xee, 0xe7, 0x33, 0xf6, 0xa1, 0x3e, 0x5d, 0xf3, 0x3c, 0xe4, 0xb3, 0x3c, 0x45, 0xde, 0xe4,
-     0xa5, 0xea, 0xe8, 0x8b, 0xe6, 0x35, 0x6e, 0xd3, 0xd5, 0xe8, 0x77, 0xf1, 0x35, 0x64, 0xa3, 0xa5,
-     0xcb, 0x91, 0xfa, 0xb1, 0xf2, 0x0c, 0xba, 0xb6, 0xd1, 0xc6, 0xd1, 0x58, 0x20, 0xbd, 0xba, 0x73
-};
-
-unsigned char kGost14OfbSV[32] =
-{
-     0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xce, 0xf0, 0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf0, 0x01, 0x12,
-     0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0x90, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19
-};
-
-unsigned char kGost14OfbC[64] = 
-{
-     0x81, 0x80, 0x0a, 0x59, 0xb1, 0x84, 0x2b, 0x24, 0xff, 0x1f, 0x79, 0x5e, 0x89, 0x7a, 0xbd, 0x95,
-     0xed, 0x5b, 0x47, 0xa7, 0x04, 0x8c, 0xfa, 0xb4, 0x8f, 0xb5, 0x21, 0x36, 0x9d, 0x93, 0x26, 0xbf,
-     0x66, 0xa2, 0x57, 0xac, 0x3c, 0xa0, 0xb8, 0xb1, 0xc8, 0x0f, 0xe7, 0xfc, 0x10, 0x28, 0x8a, 0x13,
-     0x20, 0x3e, 0xbb, 0xc0, 0x66, 0x13, 0x86, 0x60, 0xa0, 0x29, 0x22, 0x43, 0xf6, 0x90, 0x31, 0x50
-};
-
-unsigned char kGost14CbcSV[32] =
-{
-     0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xce, 0xf0, 0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf0, 0x01, 0x12,
-     0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0x90, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19
-};
-
-unsigned char kGost14CbcC[64] = 
-{
-    0x68, 0x99, 0x72, 0xd4, 0xa0, 0x85, 0xfa, 0x4d, 0x90, 0xe5, 0x2e, 0x3d, 0x6d, 0x7d, 0xcc, 0x27,
-    0x28, 0x26, 0xe6, 0x61, 0xb4, 0x78, 0xec, 0xa6, 0xaf, 0x1e, 0x8e, 0x44, 0x8d, 0x5e, 0xa5, 0xac,
-    0xfe, 0x7b, 0xab, 0xf1, 0xe9, 0x19, 0x99, 0xe8, 0x56, 0x40, 0xe8, 0xb0, 0xf4, 0x9d, 0x90, 0xd0,
-    0x16, 0x76, 0x88, 0x06, 0x5a, 0x89, 0x5c, 0x63, 0x1a, 0x2d, 0x9a, 0x15, 0x60, 0xb6, 0x39, 0x70
-};
-
-unsigned char kGost14CfbSV[32] =
-{
-     0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xce, 0xf0, 0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf0, 0x01, 0x12,
-     0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0x90, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19
-};
-
-unsigned char kGost14CfbC[64] = 
-{
-     0x81, 0x80, 0x0a, 0x59, 0xb1, 0x84, 0x2b, 0x24, 0xff, 0x1f, 0x79, 0x5e, 0x89, 0x7a, 0xbd, 0x95,
-     0xed, 0x5b, 0x47, 0xa7, 0x04, 0x8c, 0xfa, 0xb4, 0x8f, 0xb5, 0x21, 0x36, 0x9d, 0x93, 0x26, 0xbf,
-     0x79, 0xf2, 0xa8, 0xeb, 0x5c, 0xc6, 0x8d, 0x38, 0x84, 0x2d, 0x26, 0x4e, 0x97, 0xa2, 0x38, 0xb5,
-     0x4f, 0xfe, 0xbe, 0xcd, 0x4e, 0x92, 0x2d, 0xe6, 0xc7, 0x5b, 0xd9, 0xdd, 0x44, 0xfb, 0xf4, 0xd1
-};
-
-unsigned char kGost14ImitS[8] = 
-{
-     0x33, 0x6f, 0x4d, 0x29, 0x60, 0x59, 0xfb, 0xe3
-};
-
-
- 
-
-/* -------------------------------------------------------- */
-
-unsigned char kMasterKeyGost89[] = 
-{
-     0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
-     0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00,
-     0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
-     0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
-};
-
-/* тестовые значения из стандарты для алгоритма 89го года */
-unsigned char kGost89StandartPlainText[] = 
-{
-    0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10
-};
-
-unsigned char kGost89StandartEncrText[] = 
-{
-     0x4e, 0xe9, 0x01, 0xe5, 0xc2, 0xd8, 0xca, 0x3d
-};
-
-/* тестовые значения для преобразования t, описанного в стандарте */
-unsigned int kTData[5] = 
-{
-    0xfdb97531, 0x2a196f34, 0xebd9f03a, 0xb039bb3d, 0x68695433
-};
-
-/* тестовые значения для преобразования g, описанного в стандарте */
-unsigned int kgData[4][3] = 
-{
-    {0x87654321, 0xfedcba98, 0xfdcbc20c},
-    {0xfdcbc20c, 0x87654321, 0x7e791a4b},
-    {0x7e791a4b, 0xfdcbc20c, 0xc76549ec},
-    {0xc76549ec, 0x7e791a4b, 0x9791c849}
-};
-
-unsigned char kGost89PlaintText[32] = 
-{
-     0x92, 0xde, 0xf0, 0x6b, 0x3c, 0x13, 0x0a, 0x59,
-     0xdb, 0x54, 0xc7, 0x04, 0xf8, 0x18, 0x9d, 0x20,
-     0x4a, 0x98, 0xfb, 0x2e, 0x67, 0xa8, 0x02, 0x4c,
-     0x89, 0x12, 0x40, 0x9b, 0x17, 0xb5, 0x7e, 0x41,
-};
-
-unsigned char kGost89EcbC[32] = 
-{
-     0x2b, 0x07, 0x3f, 0x04, 0x94, 0xf3, 0x72, 0xa0,
-     0xde, 0x70, 0xe7, 0x15, 0xd3, 0x55, 0x6e, 0x48,
-     0x11, 0xd8, 0xd9, 0xe9, 0xea, 0xcf, 0xbc, 0x1e,
-     0x7c, 0x68, 0x26, 0x09, 0x96, 0xc6, 0x7e, 0xfb
-};
-
-unsigned char kGost89CtrSV[4] =
-{
-     0x12, 0x34, 0x56, 0x78
-};
-
-unsigned char kGost89CtrC[32] = 
-{
-     0x4e, 0x98, 0x11, 0x0c, 0x97, 0xb7, 0xb9, 0x3c,
-     0x3e, 0x25, 0x0d, 0x93, 0xd6, 0xe8, 0x5d, 0x69,
-     0x13, 0x6d, 0x86, 0x88, 0x07, 0xb2, 0xdb, 0xef,
-     0x56, 0x8e, 0xb6, 0x80, 0xab, 0x52, 0xa1, 0x2d
-};
-
-unsigned char kGost89OfbSV[16] =
-{
-     0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef,
-     0x23, 0x45, 0x67, 0x89, 0x0a, 0xbc, 0xde, 0xf1
-};
-
-unsigned char kGost89OfbC[32] = 
-{
-     0xdb, 0x37, 0xe0, 0xe2, 0x66, 0x90, 0x3c, 0x83,
-     0x0d, 0x46, 0x64, 0x4c, 0x1f, 0x9a, 0x08, 0x9c,
-     0xa0, 0xf8, 0x30, 0x62, 0x43, 0x0e, 0x32, 0x7e,
-     0xc8, 0x24, 0xef, 0xb8, 0xbd, 0x4f, 0xdb, 0x05
-};
-
-
-unsigned char kGost89CbcSV[24] = 
-{
-     0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef,  
-     0x23, 0x45, 0x67, 0x89, 0x0a, 0xbc, 0xde, 0xf1,  
-     0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x12,  
-};
-
-unsigned char kGost89CbcC[32] = 
-{
-     0x96, 0xd1, 0xb0, 0x5e, 0xea, 0x68, 0x39, 0x19,
-     0xaf, 0xf7, 0x61, 0x29, 0xab, 0xb9, 0x37, 0xb9,
-     0x50, 0x58, 0xb4, 0xa1, 0xc4, 0xbc, 0x00, 0x19,
-     0x20, 0xb7, 0x8b, 0x1a, 0x7c, 0xd7, 0xe6, 0x67,
-};
-
-unsigned char kGost89CfbSV[16] = 
-{
-     0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef,  
-     0x23, 0x45, 0x67, 0x89, 0x0a, 0xbc, 0xde, 0xf1
-};
-
-unsigned char kGost89CfbC[32] = 
-{
-     0xdb, 0x37, 0xe0, 0xe2, 0x66, 0x90, 0x3c, 0x83,
-     0x0d, 0x46, 0x64, 0x4c, 0x1f, 0x9a, 0x08, 0x9c,
-     0x24, 0xbd, 0xd2, 0x03, 0x53, 0x15, 0xd3, 0x8b,
-     0xbc, 0xc0, 0x32, 0x14, 0x21, 0x07, 0x55, 0x05
-};
-
-unsigned char kGost89ImitS[8] = 
-{
-     0x15, 0x4e, 0x72, 0x10, 0x20, 0x30, 0xc5, 0xbb
-};
-
-/* -----------------------------------------------------  */
-
-unsigned char kPaddingText[16] = 
-{
-     2, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
-};
-
-unsigned char kPaddingText2[32] = 
-{
-     3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
-     0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
-};
-
-unsigned char paddingBufferText[16] = 
-{
-     2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
-};
-
-unsigned char paddingBufferText2[32] = 
-{
-     3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
-     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
-};
diff --git a/dap-sdk/crypto/src/GOST/testgost.c b/dap-sdk/crypto/src/GOST/testgost.c
deleted file mode 100644
index 289b71abc9ec45ea62796e553ee17293f2c0a9da..0000000000000000000000000000000000000000
--- a/dap-sdk/crypto/src/GOST/testgost.c
+++ /dev/null
@@ -1,906 +0,0 @@
-/** @file 
- * @brief Реализация функций тестирования алгоритмов "кузнечик" и 28147-89. А также режимов работы блочных шифров
- *
- * @copyright InfoTeCS. All rights reserved.
- */
-
-#include <memory.h>
-
-#include "28147_14.h"
-#include "block_cipher.h"
-#include "28147_89.h"
-#include "test_data.inc"
-#include "print_data.h"
-
-/** @brief размер тестовых данных для алгоритма "кузнечик" */
-#define textLen14 sizeof(kGost14PlainText)
-
-/** @brief размер тестовых данных для алгоритма "28147-89" */
-#define textLen89 sizeof(kGost89PlaintText)
- 
-/** @brief тестирование преобразования S из алгоритма "кузнечик" */
-int testS()
-{
-     unsigned char tmp[kBlockLen14];
-     unsigned int i;
-
-     PrintLabel("Test S function start.");
-
-     for(i = 0; i < 4; ++i)
-     {
-          funcS(kSData[i], tmp, 0);
-
-          PrintBlockLeft("Test ", i+1);
-          PrintBlock("Input Value: ", kSData[i], kBlockLen14, kBlockLen14);
-          PrintEmptyLine();
-          PrintBlock("Output Value: ", tmp, kBlockLen14, kBlockLen14);
-          PrintEmptyLine();
-          PrintBlock("Control Value: ", kSData[i+1], kBlockLen14, kBlockLen14);
-          PrintEmptyLine();
-
-          if(memcmp(tmp, kSData[i+1], kBlockLen14))
-               return -1;
-     }
-     
-     return 0;
-}
-
-/** @brief тестирование преобразования R из алгоритма "кузнечик" */
-int testR()
-{
-     unsigned char tmp[kBlockLen14];
-     int i;
-     
-     PrintLabel("Test R function start.");
-
-     for(i =0; i < 4; ++i)
-     {
-          if(funcR(kRData[i], tmp, 0))
-               return -1;
-          
-          PrintBlockLeft("Test ", i+1);
-          PrintBlock("Input Value: ", kRData[i], kBlockLen14, kBlockLen14);
-          PrintEmptyLine();
-          PrintBlock("Output Value: ", tmp, kBlockLen14, kBlockLen14);
-          PrintEmptyLine();
-          PrintBlock("Control Value: ", kRData[i+1], kBlockLen14, kBlockLen14);
-          PrintEmptyLine();
-
-          if(memcmp(tmp, kRData[i+1], kBlockLen14))
-               return -1;
-     }
-
-     return 0;
-}
-
-/** @brief тестирование преобразования L из алгоритма "кузнечик" */
-int testL()
-{
-     unsigned char tmp[kBlockLen14];
-     int i;
-
-     PrintLabel("Test L function start.");
-
-     for(i =0; i < 4; ++i)
-     {
-          if(funcL(kLData[i], tmp, 0))
-               return -1;
-
-          PrintBlockLeft("Test ", i+1);
-          PrintBlock("Input Value: ", kLData[i], kBlockLen14, kBlockLen14);
-          PrintEmptyLine();
-          PrintBlock("Output Value: ", tmp, kBlockLen14, kBlockLen14);
-          PrintEmptyLine();
-          PrintBlock("Control Value: ", kLData[i+1], kBlockLen14, kBlockLen14);
-          PrintEmptyLine();
-          
-          if( memcmp(tmp, kLData[i+1], kBlockLen14))
-               return -1;
-     }
-     return 0;
-}
-
-/** @brief тестирование разворачивания ключа из алгоритма "кузнечик" */
-int testExpandKey()
-{
-     const size_t keyLen = sizeof(kMasterKeyData)/sizeof(kMasterKeyData[0]);
-     unsigned char keys[160];
-     int i;
-
-     PrintLabel("Test Expand Key function start.");
-
-     if(ExpandKey(kMasterKeyData, keys, 0))
-          return -1;
-
-     PrintBlock("Master Key: ", kMasterKeyData, keyLen, kBlockLen14);
-     PrintEmptyLine();
-          
-     for(i = 0; i < 10; ++i)
-     {
-          PrintBlock("Output Key: ", keys+i*kBlockLen14, kBlockLen14, kBlockLen14);
-          PrintBlock("Control Key: ", (unsigned char*)kKData[i], kBlockLen14, kBlockLen14);
-          PrintEmptyLine();
-
-          if( memcmp(keys+i*kBlockLen14, kKData[i], kBlockLen14))
-               return -1;
-     }
-
-     return 0;
-}
-
-/** @brief тестирование шифрования по алгоритму "кузнечик" */
-int testEncrypt()
-{
-     const size_t keyLen = sizeof(kMasterKeyData)/sizeof(kMasterKeyData[0]);
-
-     unsigned char ctx[kEcb14ContextLen];
-     unsigned char output[kBlockLen14];
-
-     PrintLabel("Test Encrypt start.");
-
-     if(init_ecb_14(kMasterKeyData, ctx, 0, 0))
-          return -1;
-
-     if(encrypt_ecb(ctx, kPlainTextData, output, kBlockLen14))
-          return -1;
-
-     PrintBlock("Master Key: ", kMasterKeyData, keyLen, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Input Value: ", kPlainTextData, kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", output, kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Control value: ", kChipherTextData, kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-
-     if( memcmp(output, kChipherTextData, kBlockLen14))
-          return -1;
-
-     return 0;
-}
-
-/** @brief тестирование расшифрования по алгоритму "кузнечик" */
-int testDecrypt()
-{
-     const size_t keyLen = sizeof(kMasterKeyData)/sizeof(kMasterKeyData[0]);
-
-     unsigned char ctx[kEcb14ContextLen];
-     unsigned char output[kBlockLen14];
-
-     PrintLabel("Test Decrypt start.");
-
-     if(init_ecb_14(kMasterKeyData, ctx, 0, 0))
-          return -1;
-
-     if(decrypt_ecb(ctx, kChipherTextData, output, kBlockLen14))
-          return -1;
-
-     PrintBlock("Master Key: ", kMasterKeyData, keyLen, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Input Value: ", kChipherTextData, kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", output, kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Control value: ", kPlainTextData, kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-
-     if( memcmp(output, kPlainTextData, kBlockLen14)  )
-          return -1;
-
-     free_ecb(ctx);
-
-     return 0;
-}
-
-/** @brief тестирование шифроавание в режиме ecb по алгоритму "кузнечик" */
-int gost14_ECB_test()
-{
-     unsigned char ctx[kEcb14ContextLen];
-     unsigned char output[textLen14];
-     unsigned char outputE[textLen14];
-
-     PrintLabel("Ecb mode 28147-14 test start.");
-
-     if(init_ecb_14(kMasterKeyData, ctx, print_array, print_uint_array))
-          return -1;
-
-     if(encrypt_ecb(ctx, kGost14PlainText, output, textLen14))
-          return -1;
-
-     PrintBlock("Master Key: ", kMasterKeyData, 32, kBlockLen14);
-     PrintEmptyLine();
-     PrintLineLeft("Test Encrypt.");
-     PrintEmptyLine();
-     PrintBlock("Input Value: ", kGost14PlainText, kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", output, kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Control value: ", kGost14EcbEncText, kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-
-     if( memcmp(output, kGost14EcbEncText, textLen14))
-          return -1;
-
-     if(init_ecb_14(kMasterKeyData, ctx, print_array, print_uint_array))
-          return -1;
-
-     if(decrypt_ecb(ctx, output, outputE, textLen14))
-          return -1;
-
-     PrintLineLeft("Test Decrypt.");
-     PrintEmptyLine();
-     PrintBlock("Input Value: ", output, kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", outputE, kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Control value: ", kGost14PlainText, kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-
-     if( memcmp(outputE, kGost14PlainText, textLen14))
-          return -1;
-
-     free_ecb(ctx);
-
-     return 0;
-}
-
-/** @brief тестирование режима ctr алгоритма "кузнечик" */
-int gost14_CTR_test()
-{
-     const size_t svLen = sizeof(kGost14CtrSV);
-
-     unsigned char outText[textLen14];
-     unsigned char ctx[kCtr14ContextLen];
-
-     PrintLabel("CTR mode 28147-14 test start.");
-     
-     if(init_ctr_14(kMasterKeyData, kGost14CtrSV, svLen, ctx, print_array, print_uint_array))
-          return -1;
-
-     if(crypt_ctr(ctx,  kGost14PlainText, outText, textLen14))
-          return -1;
-
-     free_ctr(ctx);
-   
-     PrintBlock("Master Key: ", kMasterKeyData, 32, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("SV: ", kGost14CtrSV, kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Input Value: ", kGost14PlainText, textLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", outText, textLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Control value: ", kGost14CtrC, textLen14, kBlockLen14);
-     PrintEmptyLine();
-
-     return memcmp(outText, kGost14CtrC, textLen14);
-}
-
-/** @brief тестирование режима ofb алгоритма "кузнечик" */
-int gost14_OFB_test()
-{
-     const size_t svLen = sizeof(kGost14OfbSV);
-
-     unsigned char outText[textLen14];
-     unsigned char ctx[kOfb14ContextLen];
-
-     PrintLabel("OFB mode 28147-14 test start.");
-
-     if(init_ofb_14(kMasterKeyData, ctx, kBlockLen14, kGost14OfbSV, svLen, print_array, print_uint_array))
-          return -1;
-
-     if(crypt_ofb(ctx, kGost14PlainText, outText, textLen14))
-          return -1;
-
-     free_ofb(ctx);
-
-     PrintBlock("Master Key: ", kMasterKeyData, 32, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("SV: ", kGost14OfbSV, kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Input Value: ", kGost14PlainText, textLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", outText, textLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Control value: ", kGost14OfbC, textLen14, kBlockLen14);
-     PrintEmptyLine();
-
-     return memcmp(outText, kGost14OfbC, textLen14);
-}
-
-/** @brief тестирование режима cbc алгоритма "кузнечик" */
-int gost14_CBC_test()
-{
-     const size_t svLen = sizeof(kGost14CbcSV);
-
-     unsigned char outText[textLen14];
-     unsigned char outTextDec[textLen14];
-     unsigned char ctx[kCbc14ContextLen];
-
-     PrintLabel("CBC mode 28147-14 test start.");
-     
-     if(init_cbc_14(kMasterKeyData, ctx, kGost14CbcSV, svLen, print_array, print_uint_array))
-          return -1;
-
-     if(encrypt_cbc(ctx, kGost14PlainText, outText, textLen14))
-          return -1;
-
-     free_cbc(ctx);
-
-     PrintBlock("Master Key: ", kMasterKeyData, 32, kBlockLen14);
-     PrintEmptyLine();
-     PrintLineLeft("Test Encrypt.");
-     PrintEmptyLine();
-     PrintBlock("SV: ", kGost14CbcSV, svLen, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Input Value: ", kGost14PlainText, textLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", outText, textLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Control value: ",  kGost14CbcC, textLen14, kBlockLen14);
-     PrintEmptyLine();
-
-     if(init_cbc_14(kMasterKeyData, ctx, kGost14CbcSV, svLen, print_array, print_uint_array))
-          return -1;
-
-     if(decrypt_cbc(ctx, outText, outTextDec, textLen14))
-          return -1;
-
-     free_cbc(ctx);
-
-     PrintLineLeft("Test Decrypt.");
-     PrintEmptyLine();
-     PrintBlock("Input Value: ", outText, textLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", outTextDec, textLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Control value: ", kGost14PlainText, textLen14, kBlockLen14);
-     PrintEmptyLine();
-
-     if(memcmp(outTextDec, kGost14PlainText, textLen14))
-          return -1;
-
-     return memcmp(outText, kGost14CbcC, textLen14);
-}
-
-/** @brief тестирование режима cfb алгоритма "кузнечик" */
-int gost14_CFB_test()
-{
-     const size_t svLen = sizeof(kGost14CfbSV);
-
-     unsigned char outText[textLen14];
-     unsigned char outTextDec[textLen14];
-     unsigned char ctx[kCfb14ContextLen];
-
-     PrintLabel("CFB mode 28147-14 test start.");
-
-     if(init_cfb_14(kMasterKeyData, ctx, kBlockLen14, kGost14CfbSV, svLen, print_array, print_uint_array))
-          return -1;
-
-     if(encrypt_cfb(ctx, kGost14PlainText, outText, textLen89))
-          return -1;
-
-     if(memcmp(outText, kGost14CfbC, textLen89))
-          return -1;
-
-     PrintBlock("Master Key: ", kMasterKeyData, 32, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("SV: ", kGost14CfbSV, svLen, kBlockLen14);
-     PrintEmptyLine();
-     PrintLineLeft("Test Encrypt.");
-     PrintEmptyLine();
-     PrintBlock("Input Value: ", kGost14PlainText, textLen89, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", outText, textLen89, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Control value: ",  kGost14CfbC, textLen89, kBlockLen14);
-     PrintEmptyLine();
-
-     free_cfb(ctx);
-
-     if(init_cfb_14(kMasterKeyData, ctx, 16, kGost14CfbSV, svLen, print_array, print_uint_array))
-          return -1;
-
-     if(decrypt_cfb(ctx, outText, outTextDec, textLen89))
-          return -1;
-
-     PrintLineLeft("Test Decrypt.");
-     PrintEmptyLine();
-     PrintBlock("Input Value: ", outText, textLen89, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", outTextDec, textLen89, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Control value: ",  kGost14PlainText, textLen89, kBlockLen14);
-     PrintEmptyLine();
-
-     if(memcmp(outTextDec, kGost14PlainText, textLen89))
-          return -1;
-
-     free_cfb(ctx);
-
-     return 0;
-}
-
-/** @brief тестирование режима имитовставки алгоритма "кузнечик" */
-int gost14_imita_test()
-{
-     const size_t imitLen = sizeof(kGost14ImitS);
-     unsigned char outText[16];
-     unsigned char ctx[kImit14ContextLen];
-
-     PrintLabel("Imita mode 28147-14 test start.");
-
-     if(init_imit_14(kMasterKeyData, kBlockLen14, ctx, print_array, print_uint_array))
-          return -1;
-
-     if(imit(ctx, kGost14PlainText, textLen14))
-          return 0;
-
-     done_imit(ctx, outText);
-
-     PrintBlock("Input Value: ", kGost14PlainText, textLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", outText, imitLen, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Control value: ",  kGost14ImitS, imitLen, kBlockLen14);
-     PrintEmptyLine();
-
-     free_imit(ctx);
-
-     return memcmp(outText, kGost14ImitS, imitLen);
-}
-
-/** @brief тестирование режима ecb алгоритма 28147-89 */
-int gost89_ECB_test()
-{
-     unsigned char ctx[kEcb89ContextLen];
-     unsigned char output[textLen89];
-     unsigned char outputDec[textLen89];
-
-     PrintLabel("Ecb mode 28147-89 test start.");
-
-     if(init_ecb_89(kMasterKeyGost89, ctx, print_array, print_uint_array))
-          return -1;
-
-     if(encrypt_ecb(ctx, kGost89PlaintText, output, textLen89))
-          return -1;
-
-     PrintBlock("Master Key: ", kMasterKeyGost89, 32, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Input Value: ", kGost89PlaintText, textLen89, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", output, textLen89, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Control value: ", kGost89EcbC, textLen89, kBlockLen89);
-     PrintEmptyLine();
-
-     if(memcmp(output, kGost89EcbC, textLen89))
-          return -1;
-
-     free_ecb(ctx);
-
-     if(init_ecb_89(kMasterKeyGost89, ctx, print_array, print_uint_array))
-          return -1;
-
-     if(decrypt_ecb(ctx, output, outputDec, textLen89))
-          return -1;
-
-     if(memcmp(outputDec, kGost89PlaintText, textLen89))
-          return -1;
-
-     free_ecb(ctx);
-
-     return 0;
-}
-
-/** @brief тестирование режима ctr алгоритма 28147-89 */
-int gost89_CTR_test()
-{
-     const size_t SvLen = sizeof(kGost89CtrSV);
-
-     unsigned char outText[textLen89];
-     unsigned char ctx[kCtr89ContextLen];
-
-     PrintLabel("Ctr mode 28147-89 test start.");
-     
-     if(init_ctr_89(kMasterKeyGost89, kGost89CtrSV, kBlockLen89, ctx, print_array, print_uint_array))
-          return -1;
-
-     if(crypt_ctr(ctx, kGost89PlaintText, outText, textLen89))
-          return -1;
-
-     free_ctr(ctx);
-
-     PrintBlock("Master Key: ", kMasterKeyGost89, 32, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("SV: ", kGost89CtrSV, SvLen, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Input Value: ", kGost89PlaintText, textLen89, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", outText, textLen89, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Control value: ", kGost89CtrC, textLen89, kBlockLen89);
-     PrintEmptyLine();
-    
-     return memcmp(outText, kGost89CtrC, textLen89);
-}
-
-/** @brief тестирование режима ofb алгоритма 28147-89 */
-int gost89_OFB_test()
-{
-     const size_t SvLen = sizeof(kGost89OfbSV);
-
-     unsigned char outText[textLen89];
-     unsigned char ctx[kOfb89ContextLen];
-
-     PrintLabel("Ofb mode 28147-89 test start.");
-
-     if(init_ofb_89(kMasterKeyGost89, ctx, kBlockLen89, kGost89OfbSV, SvLen, print_array, print_uint_array))
-          return -1;
-
-     if(crypt_ofb(ctx, kGost89PlaintText, outText, textLen89))
-          return -1;
-
-     PrintBlock("Master Key: ", kMasterKeyGost89, 32, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("SV: ", kGost89OfbSV, SvLen, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Input Value: ", kGost89PlaintText, textLen89, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", outText, textLen89, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Control value: ", kGost89OfbC, textLen89, kBlockLen89);
-     PrintEmptyLine();
-
-     free_ofb(ctx);
-
-     return memcmp(outText,  kGost89OfbC, textLen89);
-}
-
-/** @brief тестирование режима cbc алгоритма 28147-89 */
-int gost89_CBC_test()
-{
-     const size_t SvLen = sizeof(kGost89CbcSV);
-
-     unsigned char outText[textLen89];
-     unsigned char outTextDec[textLen89];
-     unsigned char ctx[kCbc89ContextLen];
-
-     PrintLabel("Cbc mode 28147-89 test start.");
-
-     if(init_cbc_89(kMasterKeyGost89, ctx, kGost89CbcSV, SvLen, print_array, print_uint_array))
-          return -1;
-
-     if(encrypt_cbc(ctx, kGost89PlaintText, outText, textLen89))
-          return 0;
-
-     free_cbc(ctx);
-
-     PrintBlock("Master Key: ", kMasterKeyGost89, 32, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("SV: ", kGost89CbcSV, SvLen, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Input Value: ", kGost89PlaintText, textLen89, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", outText, textLen89, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Control value: ", kGost89CbcC, textLen89, kBlockLen89);
-     PrintEmptyLine();
-
-     if(init_cbc_89(kMasterKeyGost89, ctx, kGost89CbcSV, SvLen, print_array, print_uint_array))
-          return -1;
-
-     if(decrypt_cbc(ctx, outText, outTextDec, textLen89))
-          return -1;
-
-     free_cbc(ctx);
-
-     if(memcmp(outTextDec, kGost89PlaintText, textLen89))
-          return -1;
-
-     return memcmp(outText, kGost89CbcC, textLen89);
-}
-
-/** @brief Тестирование криптографического преобразования алгоритма 28147-89 */
-int standart_89_encr_test()
-{
-     const size_t textLen = sizeof(kGost89StandartPlainText);
-     
-     unsigned char ctx[kEcb89ContextLen];
-     unsigned char output[sizeof(kGost89StandartPlainText)];
-     unsigned char outputE[sizeof(kGost89StandartPlainText)];
-
-     PrintLabel("Standart 28147-89 encryption test start.");
-
-     if(init_ecb_89(kMasterKeyGost89, ctx, print_array, print_uint_array))
-          return -1;
-
-     if(encrypt_ecb(ctx, kGost89StandartPlainText, output, textLen))
-          return -1;
-
-     PrintBlock("Master Key: ", kMasterKeyGost89, 32, kBlockLen89);
-     PrintEmptyLine();
-     
-     PrintBlock("Input Value: ", kGost89StandartPlainText, textLen, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", output, textLen, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Control value: ", kGost89StandartEncrText, textLen, kBlockLen89);
-     PrintEmptyLine();
-
-     if(memcmp(output, kGost89StandartEncrText, textLen))
-          return -1;
-
-     free_ecb(ctx);
-
-     if(init_ecb_89(kMasterKeyGost89, ctx, print_array, print_uint_array))
-          return -1;
-
-     if(decrypt_ecb(ctx, output, outputE, textLen))
-          return -1;
-
-     if(memcmp(outputE, kGost89StandartPlainText, textLen))
-          return -1;
-
-     free_ecb(ctx);
-
-     return 0;
-}
-
-/** @brief тестирование режима cfb алгоритма 28147-89 */
-int gost89_CFB_test()
-{
-     const size_t SvLen = sizeof(kGost89CfbSV);
-     
-     unsigned char outText[textLen89];
-     unsigned char outTextDec[textLen89];
-     unsigned char ctx[kCfb89ContextLen];
-
-     PrintLabel("Cfb mode 28147-89 test start.");
-
-     if(init_cfb_89(kMasterKeyGost89, ctx, kBlockLen89, kGost89CfbSV, SvLen, print_array, print_uint_array))
-          return -1;
-
-     if(encrypt_cfb(ctx, kGost89PlaintText, outText, textLen89))
-          return -1;
-
-     PrintBlock("Master Key: ", kMasterKeyGost89, 32, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("SV: ", kGost89CfbSV, SvLen, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Input Value: ", kGost89PlaintText, textLen89, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", outText, textLen89, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Control value: ", kGost89CfbC, textLen89, kBlockLen89);
-     PrintEmptyLine();
-
-     if(memcmp(outText, kGost89CfbC, textLen89))
-          return -1;
-
-     free_cfb(ctx);
-
-     if(init_cfb_89(kMasterKeyGost89, ctx, kBlockLen89, kGost89CfbSV, SvLen, print_array, print_uint_array))
-          return -1;
-
-     if(decrypt_cfb(ctx, outText, outTextDec, textLen89))
-          return -1;
-
-     if(memcmp(outTextDec, kGost89PlaintText, textLen89))
-          return -1;
-
-     free_cfb(ctx);
-
-     return 0;
-}
-#include<stdio.h>
-/** @brief тестирование режима имтовставки алгоритма 28147-89 */
-int gost89_imita_test()
-{
-     const size_t imitLen = sizeof(kGost89ImitS);
-
-     unsigned char outText[sizeof(kGost89ImitS)];
-     unsigned char ctx[kImit89ContextLen];
-
-     PrintLabel("Imita mode 28147-89 test start.");
-
-     if(init_imit_89(kMasterKeyGost89, kBlockLen89, ctx, print_array, print_uint_array))
-          return -1;
-
-     if(imit(ctx, kGost89PlaintText, textLen89))
-          return -1;
-
-     done_imit(ctx, outText);
-
-     free_imit(ctx);
-
-     PrintBlock("Master Key: ", kMasterKeyGost89, 32, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Input Value: ", kGost89PlaintText, textLen89, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", outText, imitLen, kBlockLen89);
-     PrintEmptyLine();
-     PrintBlock("Control value: ", kGost89ImitS, imitLen, kBlockLen89);
-     PrintEmptyLine();
-
-     return memcmp(outText, kGost89ImitS, imitLen);
-}
-
-/** @brief тестирование дополнения сообщения */
-int testPadding()
-{
-     const size_t len = sizeof(kPaddingText)/sizeof(kPaddingText[0]);
-     const size_t len2 = sizeof(kPaddingText2)/sizeof(kPaddingText2[0]);
-
-     padd(paddingBufferText, 1, kBlockLen14);
-     padd(paddingBufferText2, kBlockLen14, 2*kBlockLen14);
-
-     PrintLineLeft("Test 1");
-     PrintBlock("Input Value: ", paddingBufferText, 1, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", paddingBufferText, kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Control value: ", kPaddingText, len, kBlockLen14);
-     PrintEmptyLine();
-
-//     if(memcmp(paddingBufferText, kPaddingText, len))
-//          return -1;
-
-     PrintLineLeft("Test 2");
-     PrintBlock("Input Value: ", paddingBufferText2, kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", paddingBufferText2, 2*kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Control value: ", kPaddingText2, len2, kBlockLen14);
-     PrintEmptyLine();
-     fflush(stdout);
-
-//     if(memcmp(paddingBufferText2, kPaddingText2, len2))
-//          return -1;
-     
-     return 0;
-}
-
-/** @brief тестирование снятия дополнения сообщения */
-int testCut()
-{
-     size_t rLen, rLen2;
-     padd(paddingBufferText, 1, kBlockLen14);
-     padd(paddingBufferText2, kBlockLen14, 2*kBlockLen14);
-
-     rLen = unpadd(paddingBufferText, kBlockLen14);
-     rLen2 = unpadd(paddingBufferText2, 2*kBlockLen14);
-
-     PrintLabel("Cut padding test start.");
-
-     PrintLineLeft("Test 1");
-     PrintBlock("Input Value: ", paddingBufferText, kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", paddingBufferText, rLen, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Control value: ", kPaddingText, 1, kBlockLen14);
-     PrintEmptyLine();
-
-     PrintLineLeft("Test 2");
-     PrintBlock("Input Value: ", paddingBufferText2, 2*kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Output Value: ", paddingBufferText2, rLen2, kBlockLen14);
-     PrintEmptyLine();
-     PrintBlock("Control value: ", kPaddingText2, kBlockLen14, kBlockLen14);
-     PrintEmptyLine();
-
-     if(rLen !=1 )
-          return -1;
-
-     if(rLen2 != kBlockLen14)
-          return -1;
-     
-     return 0;
-}
-
-/** @brief Тестирование преобразования t алгоритма 28147-89 */
-int testFuncT()
-{
-     int i;
-     PrintLabel("Test 28147-89 function T start.");
-
-     for(i = 0; i < 4; ++i)
-     {
-          unsigned int ans;
-          ans = funcT(kTData[i], 0);
-
-          PrintBlockLeft("Test ", i+1);
-          PrintBlockInt("Input value", kTData[i]);
-          PrintBlockInt("Ouput value", ans);
-          PrintBlockInt("Control value", kTData[i+1]);
-          PrintEmptyLine();
-
-          if(ans != kTData[i+1])
-               return -1;
-     }
-
-     return 0;
-}
-
-/** @brief Тестирование преобразования g алгоритма 28147-89 */
-int testG()
-{
-     int i;
-     PrintLabel("Test 28147-89 function G start.");
-
-     for(i = 0; i < 4; ++i)
-     {
-          unsigned int ans;
-          ans = funcG(kgData[i][0], kgData[i][1], 0);
-
-          PrintBlockLeft("Test ", i+1);
-          PrintBlockInt("Input value", kgData[i][0]);
-          PrintBlockInt("Key value", kgData[i][1]);
-          PrintBlockInt("Ouput value", ans);
-          PrintBlockInt("Control value", kgData[i][2]);
-          PrintEmptyLine();
-          
-          if( ans!= kgData[i][2])
-               return -1;
-     }
-
-     return 0;
-}
-
-/** @brief точка входа  */
-int main_gost_test()
-{
-     int testRes = 0;
-     PrintLine("TEST start.");
-     PrintEmptyLine();
-     
-     PrintLine("TEST 28147-14 standart start.");
-     testRes |= PrintTest("S function test end", testS());
-     testRes |= PrintTest("R function test.", testR());
-     testRes |= PrintTest("L function test.", testL());
-     testRes |= PrintTest("Expand Key 28147-14 test.", testExpandKey());
-     testRes |= PrintTest("Encrypt test.", testEncrypt());
-     testRes |= PrintTest("Decrypt test.", testDecrypt());
-     PrintEmptyLine();
-
-     PrintLine("TEST 28147-89 standart test.");
-     testRes |= PrintTest("28147-89 T function test.", testFuncT());
-     testRes |= PrintTest("28147-89 G function test.", testG());
-     testRes |= PrintTest("Encrypt test.", standart_89_encr_test());
-     PrintEmptyLine();
-
-     PrintLine("TEST 28147-14 mode test.");
-     testRes |= PrintTest("Ecb mode 28147-14 test.", gost14_ECB_test());
-     testRes |= PrintTest("CTR mode 28147-14 test.", gost14_CTR_test());
-     testRes |= PrintTest("OFB mode 28147-14 test.", gost14_OFB_test());
-     testRes |= PrintTest("CBC mode 28147-14 test.", gost14_CBC_test());
-     testRes |= PrintTest("CFB mode 28147-14 test.", gost14_CFB_test());
-     testRes |= PrintTest("Imita mode 28147-14 test.", gost14_imita_test());
-     PrintEmptyLine();
-
-     PrintLine("TEST 28147-89 mode test.");
-     testRes |= PrintTest("Ecb mode 28147-89 test.", gost89_ECB_test());
-     testRes |= PrintTest("CTR mode 28147-89 test.", gost89_CTR_test());
-     testRes |= PrintTest("OFB mode 28147-89 test.", gost89_OFB_test());
-     testRes |= PrintTest("CBC mode 28147-89 test.", gost89_CBC_test());
-     testRes |= PrintTest("CFB mode 28147-89 test.", gost89_CFB_test());
-     testRes |= PrintTest("Imita mode 28147-89 test.", gost89_imita_test());
-     PrintEmptyLine();
-
-     PrintLine("TEST padding test.");
-
-     testRes |= PrintTest("Add padding test.", testPadding());
-     testRes |= PrintTest("Cut padding test.", testCut());
-     PrintEmptyLine();
-
-     if ( testRes )
-     {
-          PrintLine("FAILED TESTS EXIST!!!!!.");
-     }
-     else
-     {
-          PrintLine("ALL TEST OK.");
-     }
-     
-     return testRes;
-}
diff --git a/dap-sdk/crypto/src/rand/dap_rand.c b/dap-sdk/crypto/src/rand/dap_rand.c
index 7a2c48e3a6c787e858c3fbde3d87f406c387f974..4e102fd6621a6f20d858866b89e504ceb746ae8d 100755
--- a/dap-sdk/crypto/src/rand/dap_rand.c
+++ b/dap-sdk/crypto/src/rand/dap_rand.c
@@ -29,7 +29,7 @@ uint32_t random_uint32_t(const uint32_t MAX_NUMBER)
 int randombase64(void*random_array, unsigned int size)
 {
     int off = size - (size/4)*3;
-    int odd_signs = size - ((size/4)*4);
+    unsigned int odd_signs = size - ((size/4)*4);
     if(odd_signs < size)
     {
         randombytes(random_array + off, (size/4)*3);
@@ -39,8 +39,8 @@ int randombase64(void*random_array, unsigned int size)
     {
         uint8_t tmpv[7];
         randombytes(tmpv+4,3);
-        dap_enc_base64_encode(tmpv + 4, 3,tmpv,DAP_ENC_DATA_TYPE_B64);
-        for(int i = 0; i < odd_signs; ++i)
+        dap_enc_base64_encode(tmpv + 4, 3,(char*)tmpv,DAP_ENC_DATA_TYPE_B64);
+        for(unsigned int i = 0; i < odd_signs; ++i)
         {
             ((uint8_t*)random_array)[size - odd_signs + i] = tmpv[i];
         }
diff --git a/dap-sdk/crypto/src/ringct20/poly.c b/dap-sdk/crypto/src/ringct20/poly.c
index ed350c619ddbff01cbcc30892d2bdb58ec3aa6ec..f00896fa7674fafe494c2ca1cc05e617ec3f66e3 100644
--- a/dap-sdk/crypto/src/ringct20/poly.c
+++ b/dap-sdk/crypto/src/ringct20/poly.c
@@ -427,7 +427,7 @@ void poly_serial(poly_ringct20 *r)
 		r->coeffs[i] = coeff_freeze(r->coeffs[i]);
 	}
 }
-void poly_cofcopy(poly_ringct20 *des, poly_ringct20 *sour)
+void poly_cofcopy(poly_ringct20 *des, const poly_ringct20 *sour)
 {
 	size_t i;
 	for ( i = 0; i < NEWHOPE_RINGCT20_N; i++)
@@ -436,18 +436,16 @@ void poly_cofcopy(poly_ringct20 *des, poly_ringct20 *sour)
 	}
 }
 
-void poly_copy(poly_ringct20 *des, poly_ringct20 *sou, size_t mLen)
+void poly_copy(poly_ringct20 *des, const poly_ringct20 *sou, const int mLen)
 {
-	size_t i;
-	for ( i = 0; i < mLen; i++)
+    for (int i = 0; i < mLen; i++)
 	{
 		poly_cofcopy(des + i, sou + i);
 	}
 }
 int poly_equal(const poly_ringct20 *a, const poly_ringct20 *b)
 {
-	size_t i;
-	for ( i = 0; i < NEWHOPE_RINGCT20_N; i++)
+    for (int i = 0; i < NEWHOPE_RINGCT20_N; i++)
 	{
 		if (a->coeffs[i] != b->coeffs[i])
 		{
@@ -459,9 +457,8 @@ int poly_equal(const poly_ringct20 *a, const poly_ringct20 *b)
 
 void poly_constmul(poly_ringct20 *r, const poly_ringct20 *a, uint16_t cof)
 {
-	size_t i;
 	uint32_t tmp = 0;
-	for (i = 0; i < NEWHOPE_RINGCT20_N; i++)
+    for (int i = 0; i < NEWHOPE_RINGCT20_N; i++)
 	{
 		tmp = cof * a->coeffs[i];
 		r->coeffs[i] = tmp%NEWHOPE_RINGCT20_2Q;
diff --git a/dap-sdk/crypto/src/ringct20/poly.h b/dap-sdk/crypto/src/ringct20/poly.h
index 78894205666f6bf34f20ef088bb7444ee302ab91..c05658f14f508352453036aa109e10e2e5dd02ac 100644
--- a/dap-sdk/crypto/src/ringct20/poly.h
+++ b/dap-sdk/crypto/src/ringct20/poly.h
@@ -45,7 +45,7 @@ int poly_equal(const poly_ringct20 *a, const poly_ringct20 *b);
 //
 void poly_constmul(poly_ringct20 *r, const poly_ringct20 *a, uint16_t cof);
 void poly_serial(poly_ringct20 *r);
-void poly_cofcopy(poly_ringct20 *des, poly_ringct20 *sour);
-void poly_copy(poly_ringct20 *des, poly_ringct20 *sour, size_t mLen);
+void poly_cofcopy(poly_ringct20 *des, const poly_ringct20 *sour);
+void poly_copy(poly_ringct20 *des, const poly_ringct20 *sour, const int mLen);
 void poly_shift(poly_ringct20 *des, const poly_ringct20 *r, int iNumber);
 #endif
diff --git a/dap-sdk/crypto/src/ringct20/ring.c b/dap-sdk/crypto/src/ringct20/ring.c
index ca36f6dfdb059169ced2222e99fa6b462a33ddeb..e85b12ebecfad7d9770ee71d415a988a1035cef9 100644
--- a/dap-sdk/crypto/src/ringct20/ring.c
+++ b/dap-sdk/crypto/src/ringct20/ring.c
@@ -5,10 +5,10 @@
 
 #define Keccak_HashInitialize_SHA3_KDF(hashInstance, out_bytes)        Keccak_HashInitialize(hashInstance, 1088,  512, out_bytes*8, 0x06)
 
-void LRCT_SampleKey(poly_ringct20 *r, size_t mLen)
+void LRCT_SampleKey(poly_ringct20 *r, int mLen)
 {
 	uint8_t seed[NEWHOPE_RINGCT20_SYMBYTES] = { 0 };
-	size_t i;
+    int i;
 	for ( i = 0; i < mLen; i++)
 	{
 #ifndef NEW_SAMPLE_KEY
@@ -57,11 +57,11 @@ void LRCT_SampleKey(poly_ringct20 *r, size_t mLen)
 	}
 
 }
-void LRCT_Setup(poly_ringct20 *A, poly_ringct20 *H, size_t mLen)
+void LRCT_Setup(poly_ringct20 *A, poly_ringct20 *H, int mLen)
 {
 
 	uint8_t seed[NEWHOPE_RINGCT20_SYMBYTES] = { 0 };
-	size_t i = 0;
+    int i = 0;
 
 	for ( i = 0; i < mLen; i++)
 	{
@@ -74,16 +74,18 @@ void LRCT_Setup(poly_ringct20 *A, poly_ringct20 *H, size_t mLen)
 	}
 }
 
-void LRCT_KeyGen(poly_ringct20 *a, poly_ringct20 *A, poly_ringct20 *S, size_t mLen)
+void LRCT_KeyGen(poly_ringct20 *a, poly_ringct20 *A, poly_ringct20 *S, int mLen)
 {
 	LRCT_MatrixMulPoly(a, A, S,  mLen);
 	poly_serial(a);
 }
 
-void LRCT_SigGen(poly_ringct20 *c1, poly_ringct20 **t, poly_ringct20 *h, poly_ringct20 *A, poly_ringct20 *H, poly_ringct20 *S, poly_ringct20 *u, size_t mLen, poly_ringct20 *L, uint8_t w, uint8_t pai, const unsigned char *msg, int msgLen)
+void LRCT_SigGen(poly_ringct20 *c1, poly_ringct20 **t, poly_ringct20 *h,
+                 poly_ringct20 *A, poly_ringct20 *H, poly_ringct20 *S, poly_ringct20 *u,
+                 int  mLen, poly_ringct20 *L, int w, int pai, const unsigned char *msg, int msgLen)
 {
 	//H2q
-	size_t i, j, k;
+    int i, j, k;
 	poly_ringct20 *H2q = (poly_ringct20 *)malloc((mLen + 1) * sizeof(poly_ringct20));
 	poly_ringct20 *S2q = (poly_ringct20 *)malloc((mLen + 1) * sizeof(poly_ringct20));
 	poly_ringct20 *A2qp = (poly_ringct20 *)malloc((mLen + 1) * sizeof(poly_ringct20));
@@ -93,7 +95,7 @@ void LRCT_SigGen(poly_ringct20 *c1, poly_ringct20 **t, poly_ringct20 *h, poly_ri
     //SHA256_CTX ctx;
     Keccak_HashInstance ctx;
 
-	unsigned char bHash[32] = { 0 };
+//	unsigned char bHash[32] = { 0 };
 	unsigned char bpoly[NEWHOPE_RINGCT20_POLYBYTES] = { 0 };
 	unsigned char bt[NEWHOPE_RINGCT20_POLYBYTES] = { 0 };
 	uint8_t coin = 0;
@@ -232,16 +234,16 @@ void LRCT_SigGen(poly_ringct20 *c1, poly_ringct20 **t, poly_ringct20 *h, poly_ri
 	free(A2qp);
 	free(tmp2q);
 }
-int LRCT_SigVer(const poly_ringct20 *c1, poly_ringct20 **t, poly_ringct20 *A, poly_ringct20 *H, size_t mLen, poly_ringct20 *h, poly_ringct20 *L,
-    uint8_t w, const unsigned char *msg, int msgLen)
+int LRCT_SigVer(const poly_ringct20 *c1, poly_ringct20 **t, poly_ringct20 *A, poly_ringct20 *H, int mLen, poly_ringct20 *h, poly_ringct20 *L,
+    int w, const unsigned char *msg, int msgLen)
 {
-	size_t i,k;
+    int i,k;
 	poly_ringct20 *H2q = (poly_ringct20 *)malloc((mLen + 1) * sizeof(poly_ringct20));
 	poly_ringct20 *A2qp = (poly_ringct20 *)malloc((mLen + 1) * sizeof(poly_ringct20));
 	poly_ringct20 c, tmp, tmp1;
     //SHA256_CTX ctx;
     Keccak_HashInstance ctx;
-	unsigned char bHash[32] = { 0 };
+//	unsigned char bHash[32] = { 0 };
 	unsigned char bpoly[NEWHOPE_RINGCT20_POLYBYTES] = { 0 };
 	for (i = 0; i < (mLen + 1); i++)
 	{
@@ -307,14 +309,14 @@ int LRCT_SigVer(const poly_ringct20 *c1, poly_ringct20 **t, poly_ringct20 *A, po
 	return 0;
 }
 
-void LRCT_Mint(IW *iw, poly_ringct20 *ck, poly_ringct20 *a, poly_ringct20 *A, size_t mLen, unsigned char* bMessage, size_t msglen)
+void LRCT_Mint(IW *iw, poly_ringct20 *ck, poly_ringct20 *a, poly_ringct20 *A, int mLen, unsigned char* bMessage, size_t msglen)
 {
 	LRCT_SampleKey(ck, mLen);
 	LRCT_nttCom(&(iw->cn), A, ck, mLen, bMessage, msglen);
 	poly_cofcopy(&(iw->a), a);
 }
 void LRCT_Spend(IW *iwOA, poly_ringct20 *ckOA, poly_ringct20 *c1, poly_ringct20 **t, poly_ringct20 *h, poly_ringct20 *L, unsigned char* bSignMess, size_t sigMsgLen, IW *iws, size_t iwsLen,
-	int PaiInd, poly_ringct20 *skPai, poly_ringct20 *ckPai, unsigned char* bVal, size_t bvalLen, poly_ringct20 *OA, poly_ringct20 *A, poly_ringct20 *H, size_t mLen)
+    int PaiInd, poly_ringct20 *skPai, poly_ringct20 *ckPai, unsigned char* bVal, size_t bvalLen, poly_ringct20 *OA, poly_ringct20 *A, poly_ringct20 *H, int mLen)
 {
 
 	poly_ringct20 *u = (poly_ringct20 *)malloc((mLen+1)*sizeof(poly_ringct20));
@@ -336,18 +338,18 @@ void LRCT_Spend(IW *iwOA, poly_ringct20 *ckOA, poly_ringct20 *c1, poly_ringct20
 	free(u);
 	free(S);
 }
-int LRCT_Verify(poly_ringct20 *c1, poly_ringct20 **t, poly_ringct20 *h, poly_ringct20* A, poly_ringct20 *H, size_t mLen,
-	unsigned char* bSignMess, size_t sigMsgLen, poly_ringct20 *L, size_t iwsLen)
+int LRCT_Verify(poly_ringct20 *c1, poly_ringct20 **t, poly_ringct20 *h, poly_ringct20* A, poly_ringct20 *H, int mLen,
+    unsigned char* bSignMess, size_t sigMsgLen, poly_ringct20 *L, int iwsLen)
 {
 	int result = 0;
 	result = LRCT_SigVer(c1, t, A, H, mLen, h, L, iwsLen, bSignMess, sigMsgLen);
 	return result;
 }
 /////multiple
-void MIMO_LRCT_Setup(poly_ringct20 *A, poly_ringct20 *H, size_t mLen)
+void MIMO_LRCT_Setup(poly_ringct20 *A, poly_ringct20 *H, int mLen)
 {
 	uint8_t seed[NEWHOPE_RINGCT20_SYMBYTES] = { 0 };
-	size_t i = 0;
+    int i = 0;
 
 	for (i = 0; i < mLen; i++)
 	{
@@ -359,23 +361,23 @@ void MIMO_LRCT_Setup(poly_ringct20 *A, poly_ringct20 *H, size_t mLen)
 		poly_serial(H + i);
 	}
 }
-void MIMO_LRCT_KeyGen(poly_ringct20 *a, poly_ringct20 *A, poly_ringct20 *S, size_t mLen)
+void MIMO_LRCT_KeyGen(poly_ringct20 *a, poly_ringct20 *A, poly_ringct20 *S, int mLen)
 {
 	LRCT_MatrixMulPoly(a, A, S, mLen);
 	poly_serial(a);
 }
-void MIMO_LRCT_Mint(IW *iw, poly_ringct20 *ck, poly_ringct20 *a, poly_ringct20 *A, size_t mLen, unsigned char* bMessage, size_t msglen)
+void MIMO_LRCT_Mint(IW *iw, poly_ringct20 *ck, poly_ringct20 *a, poly_ringct20 *A, int mLen, unsigned char* bMessage, size_t msglen)
 {
 	LRCT_SampleKey(ck, mLen);
 	LRCT_nttCom(&(iw->cn), A, ck, mLen, bMessage, msglen);
 	poly_cofcopy(&(iw->a), a);
 }
-void MIMO_LRCT_Hash(int *pTable, poly_ringct20 *cn, poly_ringct20 *a, poly_ringct20 *ia, int beta)
+void MIMO_LRCT_Hash(/*int *pTable, */poly_ringct20 *cn, poly_ringct20 *a, poly_ringct20 *ia, int beta)
 {
     //SHA256_CTX ctx;
     Keccak_HashInstance ctx;
 
-	unsigned char bHash[32] = { 0 };
+//	unsigned char bHash[32] = { 0 };
 	unsigned char bpoly[NEWHOPE_RINGCT20_POLYBYTES] = { 0 };
     unsigned char bt[NEWHOPE_RINGCT20_POLYCOMPRESSEDBYTES] = { 0 };
 	int i;
@@ -402,8 +404,8 @@ void MIMO_LRCT_Hash(int *pTable, poly_ringct20 *cn, poly_ringct20 *a, poly_ringc
 }
 ////
 
-void ZKP_OR(poly_ringct20 *ck, int bit, int betaLen)
-{}
+//void ZKP_OR(poly_ringct20 *ck, int bit, int betaLen)
+//{}
 //////
 void MIMO_LRCT_SigGen(poly_ringct20 *c1, poly_ringct20 *tList, poly_ringct20 *hList, poly_ringct20 *SList, int NLen,
 	poly_ringct20 *A, poly_ringct20 *H, int mLen,  poly_ringct20 *LList, int wLen, uint8_t pai, unsigned char *msg, int msgLen)
@@ -419,7 +421,7 @@ void MIMO_LRCT_SigGen(poly_ringct20 *c1, poly_ringct20 *tList, poly_ringct20 *hL
 
 	poly_ringct20 tmp, tmp1, ctmp;
 	poly_ringct20 c, cpai;
-	unsigned char bHash[32] = { 0 };
+//	unsigned char bHash[32] = { 0 };
 	unsigned char bpoly[NEWHOPE_RINGCT20_POLYBYTES] = { 0 };
 	unsigned char bt[NEWHOPE_RINGCT20_POLYBYTES] = { 0 };
 	uint8_t coin = 0;
@@ -571,17 +573,17 @@ void MIMO_LRCT_SigGen(poly_ringct20 *c1, poly_ringct20 *tList, poly_ringct20 *hL
 
 
 }
-int MIMO_LRCT_SigVer(poly_ringct20 *c1, poly_ringct20 *tList, poly_ringct20 *hList, int NLen, poly_ringct20 *A, poly_ringct20 *H,
-	size_t mLen, poly_ringct20 *LList, int wLen, unsigned char *msg, int msgLen)
+int MIMO_LRCT_SigVer(poly_ringct20 *c1, poly_ringct20 *tList, poly_ringct20 *hList, int NLen, poly_ringct20 *A,/* poly_ringct20 *H,*/
+    int mLen, poly_ringct20 *LList, int wLen, unsigned char *msg, int msgLen)
 {
-	size_t i,j, k;
+    int i,j, k;
 	poly_ringct20 *H2q = (poly_ringct20 *)malloc(NLen*(mLen + 1) * sizeof(poly_ringct20));
 	poly_ringct20 *A2qp = (poly_ringct20 *)malloc((mLen + 1) * sizeof(poly_ringct20));
 	poly_ringct20 ctmp,tmp, tmp1;
     //SHA256_CTX ctx;
     Keccak_HashInstance ctx;
 
-	unsigned char bHash[32] = { 0 };
+//	unsigned char bHash[32] = { 0 };
 	unsigned char bpoly[NEWHOPE_RINGCT20_POLYBYTES] = { 0 };
 	/////////
 	poly_cofcopy(&ctmp, c1);
@@ -639,11 +641,10 @@ int MIMO_LRCT_SigVer(poly_ringct20 *c1, poly_ringct20 *tList, poly_ringct20 *hLi
 
 
 
-void LRCT_Lift(poly_ringct20 *LA, poly_ringct20 *A, poly_ringct20 *a, size_t mLen)
+void LRCT_Lift(poly_ringct20 *LA, poly_ringct20 *A, poly_ringct20 *a, int mLen)
 {
-	size_t i = 0;
-	size_t j = 0;
-	int16_t tmp = 0;
+    int i = 0;
+    int j = 0;
 	for ( i = 0; i < mLen; i++)
 	{
 		for ( j = 0; j < NEWHOPE_RINGCT20_N; j++)
@@ -657,10 +658,10 @@ void LRCT_Lift(poly_ringct20 *LA, poly_ringct20 *A, poly_ringct20 *a, size_t mLe
 	}
 }
 
-void LRCT_Com(poly_ringct20 *r, poly_ringct20 *A, poly_ringct20 *sk, size_t mLen, unsigned char *bMessage, size_t msglen)
+void LRCT_Com(poly_ringct20 *r, poly_ringct20 *A, poly_ringct20 *sk, int mLen, unsigned char *bMessage, size_t msglen)
 {
 	poly_ringct20 tmp;
-	size_t j;
+    size_t j;
 
 	LRCT_MatrixMulPoly(&tmp, A, sk, mLen);
 	poly_cofcopy(r, &tmp);
@@ -678,7 +679,7 @@ void LRCT_Com(poly_ringct20 *r, poly_ringct20 *A, poly_ringct20 *sk, size_t mLen
 	}
 
 }
-void LRCT_nttCom(poly_ringct20 *r, poly_ringct20 *A, poly_ringct20 *sk, size_t mLen, unsigned char *bMessage, size_t msglen)
+void LRCT_nttCom(poly_ringct20 *r, poly_ringct20 *A, poly_ringct20 *sk, int mLen, unsigned char *bMessage, size_t msglen)
 {
 	poly_ringct20 tmp, pMess;
 	size_t j;
@@ -704,9 +705,9 @@ void LRCT_nttCom(poly_ringct20 *r, poly_ringct20 *A, poly_ringct20 *sk, size_t m
 
 
 //N*M mul M*1  
-void LRCT_MatrixMulPoly(poly_ringct20 *r, poly_ringct20 *A, poly_ringct20 *s, size_t mLen)
+void LRCT_MatrixMulPoly(poly_ringct20 *r, poly_ringct20 *A, poly_ringct20 *s, int mLen)
 {
-	size_t i;
+    int i;
 	poly_ringct20 tmp, tmpA, tmps;
 	poly_init(r);
 	for ( i = 0; i < mLen; i++)
@@ -721,35 +722,32 @@ void LRCT_MatrixMulPoly(poly_ringct20 *r, poly_ringct20 *A, poly_ringct20 *s, si
 	//poly_invntt(r);
 }
 //M*N  mul N*1
-void LRCT_PolyMultMatrix(poly_ringct20 *r, poly_ringct20 *p, poly_ringct20 *A, size_t mLen)
+void LRCT_PolyMultMatrix(poly_ringct20 *r, poly_ringct20 *p, poly_ringct20 *A, int mLen)
 {
-	size_t i;
-	for ( i = 0; i < mLen; i++)
+    for (int i = 0; i < mLen; i++)
 	{
 		poly_mul_pointwise(r+i, A+i, p);
 	}
 }
 
-void LRCT_MatrixAddMatrix(poly_ringct20 *R, poly_ringct20 *A, poly_ringct20 *B, size_t mLen)
+void LRCT_MatrixAddMatrix(poly_ringct20 *R, poly_ringct20 *A, poly_ringct20 *B, int mLen)
 {
-	size_t i;
-	for ( i = 0; i < mLen; i++)
+    for (int i = 0; i < mLen; i++)
 	{
 		poly_add_ringct20(R + i, A + i, B + i);
 	}
 }
-void LRCT_MatrixSubMatrix(poly_ringct20 *R, poly_ringct20 *A, poly_ringct20 *B, size_t mLen)
+void LRCT_MatrixSubMatrix(poly_ringct20 *R, poly_ringct20 *A, poly_ringct20 *B, int mLen)
 {
-	size_t i;
-	for (i = 0; i < mLen; i++)
+    for (int i = 0; i < mLen; i++)
 	{
 		poly_sub_ringct20(R + i, A + i, B + i);
 	}
 }
 
-void LRCT_ConstMulMatrix(poly_ringct20 *r, const poly_ringct20 *A, uint16_t cof, size_t mLen)
+void LRCT_ConstMulMatrix(poly_ringct20 *r, const poly_ringct20 *A, uint16_t cof, int mLen)
 {
-	size_t i, j;
+    int i, j;
 	for (i = 0; i < mLen; i++)
 	{
 		for ( j = 0; j < NEWHOPE_RINGCT20_N; j++)
@@ -760,18 +758,17 @@ void LRCT_ConstMulMatrix(poly_ringct20 *r, const poly_ringct20 *A, uint16_t cof,
 	}
 }
 ///
-void LRCT_MatrixShift(poly_ringct20 *desCK, poly_ringct20* rCK, size_t mLen, int iNumber)
+void LRCT_MatrixShift(poly_ringct20 *desCK, poly_ringct20* rCK, int mLen, int iNumber)
 {
-	size_t i;
-	for ( i = 0; i < mLen; i++)
+    for (int i = 0; i < mLen; i++)
 	{
 		poly_shift(desCK + i, rCK + i, iNumber);
 	}
 }
 
-void LRCT_GetCK0(poly_ringct20 *CK0, poly_ringct20 * CK, size_t mLen, poly_ringct20* CKi, int messBitLen)
+void LRCT_GetCK0(poly_ringct20 *CK0, poly_ringct20 * CK, int mLen, poly_ringct20* CKi, int messBitLen)
 {
-	size_t i;
+    int i;
 	poly_ringct20 *tmp = (poly_ringct20 *)malloc((mLen) * sizeof(poly_ringct20));
 	poly_ringct20 *desCK = (poly_ringct20 *)malloc((mLen) * sizeof(poly_ringct20));
 	for (i = 0; i < (mLen); i++)
diff --git a/dap-sdk/crypto/src/ringct20/ring.h b/dap-sdk/crypto/src/ringct20/ring.h
index 63c94d7dce4b112ad4bfcb4eaa2dbf87ac7429ef..1d23baec753fd7f84dd0b13bb9a8d87d9d15d536 100644
--- a/dap-sdk/crypto/src/ringct20/ring.h
+++ b/dap-sdk/crypto/src/ringct20/ring.h
@@ -15,22 +15,22 @@ typedef struct {
     poly_ringct20 cn;
 } IW;
 
-void LRCT_Setup(poly_ringct20 *A, poly_ringct20 *H, size_t mLen);
-void LRCT_KeyGen(poly_ringct20 *a, poly_ringct20 *A, poly_ringct20 *S, size_t mLen);
+void LRCT_Setup(poly_ringct20 *A, poly_ringct20 *H, int mLen);
+void LRCT_KeyGen(poly_ringct20 *a, poly_ringct20 *A, poly_ringct20 *S, int mLen);
 void LRCT_SigGen(poly_ringct20 *c1, poly_ringct20 **t, poly_ringct20 *h, poly_ringct20 *A, poly_ringct20 *H,
-                 poly_ringct20 *S, poly_ringct20 *u, size_t mLen, poly_ringct20 *L, uint8_t w,
-                 uint8_t pai, const unsigned char *msg, int msgLen);
-int LRCT_SigVer(const poly_ringct20 *c1, poly_ringct20 **t, poly_ringct20 *A, poly_ringct20 *H, size_t mLen, poly_ringct20 *h, poly_ringct20 *L,
-                uint8_t w, const unsigned char *msg, int msgLen);
+                 poly_ringct20 *S, poly_ringct20 *u, int mLen, poly_ringct20 *L, int w,
+                 int pai, const unsigned char *msg, int msgLen);
+int LRCT_SigVer(const poly_ringct20 *c1, poly_ringct20 **t, poly_ringct20 *A, poly_ringct20 *H, int mLen, poly_ringct20 *h, poly_ringct20 *L,
+                int w, const unsigned char *msg, int msgLen);
 /////Single output trading scheme
 /*
 plan description:
 */
-void LRCT_Mint(IW *iw, poly_ringct20 *ck, poly_ringct20 *a, poly_ringct20 *A, size_t mLen, unsigned char* bMessage, size_t msglen);
+void LRCT_Mint(IW *iw, poly_ringct20 *ck, poly_ringct20 *a, poly_ringct20 *A, int mLen, unsigned char* bMessage, size_t msglen);
 void LRCT_Spend(IW *iwOA, poly_ringct20 *ckOA, poly_ringct20 *c1, poly_ringct20 **t, poly_ringct20 *h, poly_ringct20 *L, unsigned char* bSignMess, size_t sigMsgLen, IW *iws, size_t iwsLen,
-                int PaiInd, poly_ringct20 *skPai, poly_ringct20 *ckPai, unsigned char* bVal, size_t bvalLen, poly_ringct20 *OA, poly_ringct20 *A, poly_ringct20 *H, size_t mLen);
-int LRCT_Verify(poly_ringct20 *c1, poly_ringct20 **t, poly_ringct20 *h, poly_ringct20* A, poly_ringct20 *H, size_t mLen,
-    unsigned char* bSignMess, size_t sigMsgLen, poly_ringct20 *L, size_t iwsLen);
+                int PaiInd, poly_ringct20 *skPai, poly_ringct20 *ckPai, unsigned char* bVal, size_t bvalLen, poly_ringct20 *OA, poly_ringct20 *A, poly_ringct20 *H, int mLen);
+int LRCT_Verify(poly_ringct20 *c1, poly_ringct20 **t, poly_ringct20 *h, poly_ringct20* A, poly_ringct20 *H, int mLen,
+    unsigned char* bSignMess, size_t sigMsgLen, poly_ringct20 *L, int iwsLen);
 
 //
 /*
@@ -41,13 +41,13 @@ MIMO SCheme
 Function declaration: system initialization, generating public parameters
 Output: public matrix A, H, row number mLen
 */
-void MIMO_LRCT_Setup(poly_ringct20 *A, poly_ringct20 *H, size_t mLen);
+void MIMO_LRCT_Setup(poly_ringct20 *A, poly_ringct20 *H, int mLen);
 /*
 Function declaration: key generation.
 Input: matrix A, private key S, matrix row number mLen
 Output: user public key
 */
-void MIMO_LRCT_KeyGen(poly_ringct20 *a, poly_ringct20 *A, poly_ringct20 *S, size_t mLen);
+void MIMO_LRCT_KeyGen(poly_ringct20 *a, poly_ringct20 *A, poly_ringct20 *S, int mLen);
 /*
 Function declaration: signature generation
 Input: private key list SList, length NLen, public parameters A, H, matrix width mLen, public key list LList, length wLen, trader position pai, signature message msg, message length msgLen
@@ -59,58 +59,58 @@ void MIMO_LRCT_SigGen(poly_ringct20 *c1, poly_ringct20 *tList, poly_ringct20 *hL
 Function declaration: signature verification
 Input: signature (c1, t-list tList, h-list: hList, number of transactions, public parameters A, H, matrix width mLen, public key list LList, list length wLen, signature message, message length)
 */
-int MIMO_LRCT_SigVer(poly_ringct20 *c1, poly_ringct20 *tList, poly_ringct20 *hList, int NLen, poly_ringct20 *A, poly_ringct20 *H,
-    size_t mLen, poly_ringct20 *LList, int wLen, unsigned char *msg, int msgLen);
+int MIMO_LRCT_SigVer(poly_ringct20 *c1, poly_ringct20 *tList, poly_ringct20 *hList, int NLen, poly_ringct20 *A, /*poly_ringct20 *H,*/
+    int mLen, poly_ringct20 *LList, int wLen, unsigned char *msg, int msgLen);
 /*
 
 */
-void MIMO_LRCT_Mint(IW *iw, poly_ringct20 *ck, poly_ringct20 *a, poly_ringct20 *A, size_t mLen, unsigned char* bMessage, size_t msglen);
+void MIMO_LRCT_Mint(IW *iw, poly_ringct20 *ck, poly_ringct20 *a, poly_ringct20 *A, int mLen, unsigned char* bMessage, size_t msglen);
 /// 
-void MIMO_LRCT_Hash(int *pTable, poly_ringct20 *cn, poly_ringct20 *a, poly_ringct20 *ia, int beta);
+void MIMO_LRCT_Hash(/*int *pTable, */poly_ringct20 *cn, poly_ringct20 *a, poly_ringct20 *ia, int beta);
 
 //////
 void ZKP_OR(poly_ringct20 *ck , int bit, int betaLen);
 ////////////
 
-void LRCT_Lift(poly_ringct20 *LA, poly_ringct20 *A, poly_ringct20 *a, size_t mLen);
+void LRCT_Lift(poly_ringct20 *LA, poly_ringct20 *A, poly_ringct20 *a, int mLen);
 /*
 Function declaration: promise message m, r = A * sk + m
 Input: public matrix A, private key sk, matrix row number mLen, acknowledge message m, message length bMessage
 Output: Commitment r (polynomial N * 1)
 */
-void LRCT_Com(poly_ringct20 *r, poly_ringct20 *A, poly_ringct20 *sk, size_t mLen, unsigned char *bMessage, size_t msglen);
-void LRCT_nttCom(poly_ringct20 *r, poly_ringct20 *A, poly_ringct20 *sk, size_t mLen, unsigned char *bMessage, size_t msglen);
+void LRCT_Com(poly_ringct20 *r, poly_ringct20 *A, poly_ringct20 *sk, int mLen, unsigned char *bMessage, size_t msglen);
+void LRCT_nttCom(poly_ringct20 *r, poly_ringct20 *A, poly_ringct20 *sk, int mLen, unsigned char *bMessage, size_t msglen);
 
 /*
 Function declaration: matrix A (N * M) * matrix s (M * 1)
 Input: matrix A, matrix s, matrix rows mLen
 Output: matrix r = A * s (N * 1)
 */
-void LRCT_MatrixMulPoly(poly_ringct20 *r, poly_ringct20 *A, poly_ringct20 *s, size_t mLen);
+void LRCT_MatrixMulPoly(poly_ringct20 *r, poly_ringct20 *A, poly_ringct20 *s, int mLen);
 /*
 Function declaration: constant * matrix
 Input: constant cof, matrix A, number of matrix rows (mLen)
 Output: matrix r
 */
-void LRCT_ConstMulMatrix(poly_ringct20 *r, const poly_ringct20 *A, uint16_t cof, size_t mLen);
+void LRCT_ConstMulMatrix(poly_ringct20 *r, const poly_ringct20 *A, uint16_t cof, int mLen);
 /*
 Function declaration: matrix A (M rows and N columns) * polynomial p (N rows and 1 column)
 Input: polynomial p, matrix A, matrix rows mLen
 Output: polynomial r (M rows and 1 column)
 */
-void LRCT_PolyMultMatrix(poly_ringct20 *r, poly_ringct20 *p, poly_ringct20 *A,  size_t mLen);
+void LRCT_PolyMultMatrix(poly_ringct20 *r, poly_ringct20 *p, poly_ringct20 *A,  int mLen);
 /*
 Function declaration: matrix addition (A + B)
 Input: matrix A, matrix B, matrix size mLen
 Output: Matrix R = (A + B)
 */
-void LRCT_MatrixAddMatrix(poly_ringct20 *R, poly_ringct20 *A, poly_ringct20 *B, size_t mLen);
+void LRCT_MatrixAddMatrix(poly_ringct20 *R, poly_ringct20 *A, poly_ringct20 *B, int mLen);
 /*
 Function declaration: Matrix subtraction (A-B)
 Input: matrix A, matrix B, matrix size mLen
 Output: Matrix R = (A-B)
 */
-void LRCT_MatrixSubMatrix(poly_ringct20 *R, poly_ringct20 *A, poly_ringct20 *B, size_t mLen);
+void LRCT_MatrixSubMatrix(poly_ringct20 *R, poly_ringct20 *A, poly_ringct20 *B, int mLen);
 
 ///////////////////*
 /*
@@ -119,10 +119,10 @@ Input: Key length mLen
 Output: polynomial matrix r (n * mLen)
 */
 /////////////////
-void LRCT_SampleKey(poly_ringct20 *r, size_t mLen);
+void LRCT_SampleKey(poly_ringct20 *r, int mLen);
 
-void LRCT_MatrixShift(poly_ringct20 *desCK, poly_ringct20* rCK, size_t mLen, int iNumber);
+void LRCT_MatrixShift(poly_ringct20 *desCK, poly_ringct20* rCK, int mLen, int iNumber);
 
-void LRCT_GetCK0(poly_ringct20 *CK0, poly_ringct20 * CK, size_t mLen, poly_ringct20* CKi, int messBitLen);
+void LRCT_GetCK0(poly_ringct20 *CK0, poly_ringct20 * CK, int mLen, poly_ringct20* CKi, int messBitLen);
 #endif
 
diff --git a/dap-sdk/net/client/dap_client_http.c b/dap-sdk/net/client/dap_client_http.c
index e5f1ae6bb283e48bb3d7c53d509820bb3caf8cfc..b6dde60076c03551b65b453b773240ca2a368c37 100644
--- a/dap-sdk/net/client/dap_client_http.c
+++ b/dap-sdk/net/client/dap_client_http.c
@@ -33,6 +33,7 @@
 //#include <bits/socket_type.h>
 #endif
 #include <unistd.h>
+#include <errno.h>
 
 #include "dap_common.h"
 #include "dap_strfuncs.h"
@@ -258,6 +259,52 @@ static void s_http_delete(dap_events_socket_t *a_es, void *arg)
     a_es->_inheritor = NULL;
 }
 
+/**
+ * @brief resolve_host
+ * @param a_host hostname
+ * @param ai_family AF_INET  for ipv4 or AF_INET6 for ipv6
+ * @param a_addr_out out addr (struct in_addr or struct in6_addr)
+ * @param return 0 of OK, <0 Error
+ */
+int resolve_host(const char *a_host, int ai_family, struct sockaddr *a_addr_out)
+{
+    struct addrinfo l_hints, *l_res;
+    void *l_cur_addr = NULL;
+
+    memset(&l_hints, 0, sizeof(l_hints));
+    l_hints.ai_family = PF_UNSPEC;
+    l_hints.ai_socktype = SOCK_STREAM;
+    l_hints.ai_flags |= AI_CANONNAME;
+
+    int errcode = getaddrinfo(a_host, NULL, &l_hints, &l_res);
+    if(errcode != 0)
+            {
+        return -2;
+    }
+    while(l_res)
+    {
+        if(ai_family == l_res->ai_family)
+            switch (l_res->ai_family)
+            {
+            case AF_INET:
+                l_cur_addr = &((struct sockaddr_in *) l_res->ai_addr)->sin_addr;
+                memcpy(a_addr_out, l_cur_addr, sizeof(struct in_addr));
+                break;
+            case AF_INET6:
+                l_cur_addr = &((struct sockaddr_in6 *) l_res->ai_addr)->sin6_addr;
+                memcpy(a_addr_out, l_cur_addr, sizeof(struct in6_addr));
+                break;
+            }
+        if(l_cur_addr) {
+            freeaddrinfo(l_res);
+            return 0;
+        }
+        l_res = l_res->ai_next;
+    }
+    freeaddrinfo(l_res);
+    return -1;
+}
+
 /**
  * @brief dap_client_http_request_custom
  * @param a_uplink_addr
@@ -309,35 +356,37 @@ void* dap_client_http_request_custom(const char *a_uplink_addr, uint16_t a_uplin
     //l_client_http_internal->socket = l_socket;
     l_client_http_internal->obj = a_obj;
 
-    // add to dap_worker
-    dap_events_socket_create_after(l_ev_socket);
-
-    // connect
     struct sockaddr_in l_remote_addr;
     memset(&l_remote_addr, 0, sizeof(l_remote_addr));
+    // get struct in_addr from ip_str
+    inet_pton(AF_INET, a_uplink_addr, &(l_remote_addr.sin_addr));
+    //Resolve addr if
+    if(!l_remote_addr.sin_addr.s_addr) {
+        if(resolve_host(a_uplink_addr, AF_INET, &l_remote_addr.sin_addr) < 0) {
+            log_it(L_ERROR, "Wrong remote address '%s:%u'", a_uplink_addr, a_uplink_port);
+            dap_events_socket_kill_socket(l_ev_socket);
+            return NULL;
+        }
+    }
+    // connect
     l_remote_addr.sin_family = AF_INET;
     l_remote_addr.sin_port = htons(a_uplink_port);
-    if(inet_pton(AF_INET, a_uplink_addr, &(l_remote_addr.sin_addr)) < 0) {
-        log_it(L_ERROR, "Wrong remote address '%s:%u'", a_uplink_addr, a_uplink_port);
-        //close(l_ev_socket->socket);
-        dap_events_socket_kill_socket(l_ev_socket);
-        return NULL;
+    int l_err = 0;
+    if((l_err = connect(l_socket, (struct sockaddr *) &l_remote_addr, sizeof(struct sockaddr_in))) != -1) {
+        //s_set_sock_nonblock(a_client_pvt->stream_socket, false);
+        log_it(L_INFO, "Remote address connected (%s:%u) with sock_id %d", a_uplink_addr, a_uplink_port, l_socket);
+        // add to dap_worker
+        dap_events_socket_create_after(l_ev_socket);
     }
     else {
-        int l_err = 0;
-        if((l_err = connect(l_socket, (struct sockaddr *) &l_remote_addr, sizeof(struct sockaddr_in))) != -1) {
-            //s_set_sock_nonblock(a_client_pvt->stream_socket, false);
-            log_it(L_INFO, "Remote address connected (%s:%u) with sock_id %d", a_uplink_addr, a_uplink_port, l_socket);
-        }
-        else {
-            log_it(L_ERROR, "Remote address can't connected (%s:%u) with sock_id %d", a_uplink_addr, a_uplink_port, l_socket);
-            //l_ev_socket->no_close = false;
-            dap_events_socket_kill_socket(l_ev_socket);
-            //shutdown(l_ev_socket->socket, SHUT_RDWR);
-            //dap_events_socket_remove_and_delete(l_ev_socket, true);
-            //l_ev_socket->socket = 0;
-            return NULL;
-        }
+        log_it(L_ERROR, "Remote address can't connected (%s:%u) with sock_id %d err=%d", a_uplink_addr, a_uplink_port,
+                l_socket, errno);
+        //l_ev_socket->no_close = false;
+        dap_events_socket_kill_socket(l_ev_socket);
+        //shutdown(l_ev_socket->socket, SHUT_RDWR);
+        //dap_events_socket_remove_and_delete(l_ev_socket, true);
+        //l_ev_socket->socket = 0;
+        return NULL ;
     }
 
     //dap_client_pvt_t * l_client_pvt = (dap_client_pvt_t*) a_obj;
@@ -372,8 +421,17 @@ void* dap_client_http_request_custom(const char *a_uplink_addr, uint16_t a_uplin
     // adding string for GET request
     char *l_get_str = NULL;
     if(!dap_strcmp(a_method, "GET")) {
-        l_get_str = dap_strdup_printf("?%s", a_request);
+        char l_buf[1024];
+        dap_snprintf(l_buf, sizeof(l_buf), "User-Agent: Mozilla\r\n");
+        if(a_cookie) {
+            dap_snprintf(l_buf, sizeof(l_buf), "Cookie: %s\r\n", a_cookie);
+            l_request_headers = dap_string_append(l_request_headers, l_buf);
+        }
+
+        if(a_request)
+            l_get_str = dap_strdup_printf("?%s", a_request);
     }
+
     // send header
     dap_events_socket_write_f(l_ev_socket, "%s /%s%s HTTP/1.1\r\n"
             "Host: %s\r\n"
diff --git a/modules/net/srv/CMakeLists.txt b/modules/net/srv/CMakeLists.txt
index 2e61dfc24495455bd746aa45f2a7f8a66c6b6fbb..3dddc56908a9ab7453cf77ae3d77d16c15b57940 100644
--- a/modules/net/srv/CMakeLists.txt
+++ b/modules/net/srv/CMakeLists.txt
@@ -1,9 +1,9 @@
 cmake_minimum_required(VERSION 2.8)
 project (dap_chain_net_srv)
 
-file(GLOB DAP_CHAIN_NET_SRV_SRCS *.c)
+file(GLOB DAP_CHAIN_NET_SRV_SRCS *.c libmaxminddb/*.c)
 
-file(GLOB DAP_CHAIN_NET_SRV_HEADERS include/*.h)
+file(GLOB DAP_CHAIN_NET_SRV_HEADERS include/*.h libmaxminddb/*.h)
 
 add_library(${PROJECT_NAME} STATIC ${DAP_CHAIN_NET_SRV_SRCS} ${DAP_CHAIN_NET_SRV_HEADERS})
 
diff --git a/modules/net/srv/dap_chain_net_srv_countries.h b/modules/net/srv/dap_chain_net_srv_countries.h
new file mode 100644
index 0000000000000000000000000000000000000000..06d47dd512aef5a463565213847cfab94cb0d0ae
--- /dev/null
+++ b/modules/net/srv/dap_chain_net_srv_countries.h
@@ -0,0 +1,258 @@
+/*
+ * CountryCodes
+ * ISO, ISO3, Country, Continent
+ */
+const char *s_server_countries[] = {
+	"AD", "AND", "Andorra", "EU",
+	"AE", "ARE", "United Arab Emirates", "AS",
+	"AF", "AFG", "Afghanistan", "AS",
+	"AG", "ATG", "Antigua and Barbuda", "NA",
+	"AI", "AIA", "Anguilla", "NA",
+	"AL", "ALB", "Albania", "EU",
+	"AM", "ARM", "Armenia", "AS",
+	"AO", "AGO", "Angola", "AF",
+	"AQ", "ATA", "Antarctica", "AN",
+	"AR", "ARG", "Argentina", "SA",
+	"AS", "ASM", "American Samoa", "OC",
+	"AT", "AUT", "Austria", "EU",
+	"AU", "AUS", "Australia", "OC",
+	"AW", "ABW", "Aruba", "NA",
+	"AX", "ALA", "Aland Islands", "EU",
+	"AZ", "AZE", "Azerbaijan", "AS",
+	"BA", "BIH", "Bosnia and Herzegovina", "EU",
+	"BB", "BRB", "Barbados", "NA",
+	"BD", "BGD", "Bangladesh", "AS",
+	"BE", "BEL", "Belgium", "EU",
+	"BF", "BFA", "Burkina Faso", "AF",
+	"BG", "BGR", "Bulgaria", "EU",
+	"BH", "BHR", "Bahrain", "AS",
+	"BI", "BDI", "Burundi", "AF",
+	"BJ", "BEN", "Benin", "AF",
+	"BL", "BLM", "Saint Barthelemy", "NA",
+	"BM", "BMU", "Bermuda", "NA",
+	"BN", "BRN", "Brunei", "AS",
+	"BO", "BOL", "Bolivia", "SA",
+	"BQ", "BES", "Bonaire", "NA",
+	"BR", "BRA", "Brazil", "SA",
+	"BS", "BHS", "Bahamas", "NA",
+	"BT", "BTN", "Bhutan", "AS",
+	"BV", "BVT", "Bouvet Island", "AN",
+	"BW", "BWA", "Botswana", "AF",
+	"BY", "BLR", "Belarus", "EU",
+	"BZ", "BLZ", "Belize", "NA",
+	"CA", "CAN", "Canada", "NA",
+	"CC", "CCK", "Cocos Islands", "AS",
+	"CD", "COD", "Democratic Republic of the Congo", "AF",
+	"CF", "CAF", "Central African Republic", "AF",
+	"CG", "COG", "Republic of the Congo", "AF",
+	"CH", "CHE", "Switzerland", "EU",
+	"CI", "CIV", "Ivory Coast", "AF",
+	"CK", "COK", "Cook Islands", "OC",
+	"CL", "CHL", "Chile", "SA",
+	"CM", "CMR", "Cameroon", "AF",
+	"CN", "CHN", "China", "AS",
+	"CO", "COL", "Colombia", "SA",
+	"CR", "CRI", "Costa Rica", "NA",
+	"CU", "CUB", "Cuba", "NA",
+	"CV", "CPV", "Cabo Verde", "AF",
+	"CW", "CUW", "Curacao", "NA",
+	"CX", "CXR", "Christmas Island", "OC",
+	"CY", "CYP", "Cyprus", "EU",
+	"CZ", "CZE", "Czechia", "EU",
+	"DE", "DEU", "Germany", "EU",
+	"DJ", "DJI", "Djibouti", "AF",
+	"DK", "DNK", "Denmark", "EU",
+	"DM", "DMA", "Dominica", "NA",
+	"DO", "DOM", "Dominican Republic", "NA",
+	"DZ", "DZA", "Algeria", "AF",
+	"EC", "ECU", "Ecuador", "SA",
+	"EE", "EST", "Estonia", "EU",
+	"EG", "EGY", "Egypt", "AF",
+	"EH", "ESH", "Western Sahara", "AF",
+	"ER", "ERI", "Eritrea", "AF",
+	"ES", "ESP", "Spain", "EU",
+	"ET", "ETH", "Ethiopia", "AF",
+	"FI", "FIN", "Finland", "EU",
+	"FJ", "FJI", "Fiji", "OC",
+	"FK", "FLK", "Falkland Islands", "SA",
+	"FM", "FSM", "Micronesia", "OC",
+	"FO", "FRO", "Faroe Islands", "EU",
+	"FR", "FRA", "France", "EU",
+	"GA", "GAB", "Gabon", "AF",
+	"GB", "GBR", "United Kingdom", "EU",
+	"GD", "GRD", "Grenada", "NA",
+	"GE", "GEO", "Georgia", "AS",
+	"GF", "GUF", "French Guiana", "SA",
+	"GG", "GGY", "Guernsey", "EU",
+	"GH", "GHA", "Ghana", "AF",
+	"GI", "GIB", "Gibraltar", "EU",
+	"GL", "GRL", "Greenland", "NA",
+	"GM", "GMB", "Gambia", "AF",
+	"GN", "GIN", "Guinea", "AF",
+	"GP", "GLP", "Guadeloupe", "NA",
+	"GQ", "GNQ", "Equatorial Guinea", "AF",
+	"GR", "GRC", "Greece", "EU",
+	"GS", "SGS", "South Georgia and the South Sandwich Islands", "AN",
+	"GT", "GTM", "Guatemala", "NA",
+	"GU", "GUM", "Guam", "OC",
+	"GW", "GNB", "Guinea-Bissau", "AF",
+	"GY", "GUY", "Guyana", "SA",
+	"HK", "HKG", "Hong Kong", "AS",
+	"HM", "HMD", "Heard Island and McDonald Islands", "AN",
+	"HN", "HND", "Honduras", "NA",
+	"HR", "HRV", "Croatia", "EU",
+	"HT", "HTI", "Haiti", "NA",
+	"HU", "HUN", "Hungary", "EU",
+	"ID", "IDN", "Indonesia", "AS",
+	"IE", "IRL", "Ireland", "EU",
+	"IL", "ISR", "Israel", "AS",
+	"IM", "IMN", "Isle of Man", "EU",
+	"IN", "IND", "India", "AS",
+	"IO", "IOT", "British Indian Ocean Territory", "AS",
+	"IQ", "IRQ", "Iraq", "AS",
+	"IR", "IRN", "Iran", "AS",
+	"IS", "ISL", "Iceland", "EU",
+	"IT", "ITA", "Italy", "EU",
+	"JE", "JEY", "Jersey", "EU",
+	"JM", "JAM", "Jamaica", "NA",
+	"JO", "JOR", "Jordan", "AS",
+	"JP", "JPN", "Japan", "AS",
+	"KE", "KEN", "Kenya", "AF",
+	"KG", "KGZ", "Kyrgyzstan", "AS",
+	"KH", "KHM", "Cambodia", "AS",
+	"KI", "KIR", "Kiribati", "OC",
+	"KM", "COM", "Comoros", "AF",
+	"KN", "KNA", "Saint Kitts and Nevis", "NA",
+	"KP", "PRK", "North Korea", "AS",
+	"KR", "KOR", "South Korea", "AS",
+	"XK", "XKX", "Kosovo", "EU",
+	"KW", "KWT", "Kuwait", "AS",
+	"KY", "CYM", "Cayman Islands", "NA",
+	"KZ", "KAZ", "Kazakhstan", "AS",
+	"LA", "LAO", "Laos", "AS",
+	"LB", "LBN", "Lebanon", "AS",
+	"LC", "LCA", "Saint Lucia", "NA",
+	"LI", "LIE", "Liechtenstein", "EU",
+	"LK", "LKA", "Sri Lanka", "AS",
+	"LR", "LBR", "Liberia", "AF",
+	"LS", "LSO", "Lesotho", "AF",
+	"LT", "LTU", "Lithuania", "EU",
+	"LU", "LUX", "Luxembourg", "EU",
+	"LV", "LVA", "Latvia", "EU",
+	"LY", "LBY", "Libya", "AF",
+	"MA", "MAR", "Morocco", "AF",
+	"MC", "MCO", "Monaco", "EU",
+	"MD", "MDA", "Moldova", "EU",
+	"ME", "MNE", "Montenegro", "EU",
+	"MF", "MAF", "Saint Martin", "NA",
+	"MG", "MDG", "Madagascar", "AF",
+	"MH", "MHL", "Marshall Islands", "OC",
+	"MK", "MKD", "North Macedonia", "EU",
+	"ML", "MLI", "Mali", "AF",
+	"MM", "MMR", "Myanmar", "AS",
+	"MN", "MNG", "Mongolia", "AS",
+	"MO", "MAC", "Macao", "AS",
+	"MP", "MNP", "Northern Mariana Islands", "OC",
+	"MQ", "MTQ", "Martinique", "NA",
+	"MR", "MRT", "Mauritania", "AF",
+	"MS", "MSR", "Montserrat", "NA",
+	"MT", "MLT", "Malta", "EU",
+	"MU", "MUS", "Mauritius", "AF",
+	"MV", "MDV", "Maldives", "AS",
+	"MW", "MWI", "Malawi", "AF",
+	"MX", "MEX", "Mexico", "NA",
+	"MY", "MYS", "Malaysia", "AS",
+	"MZ", "MOZ", "Mozambique", "AF",
+	"NA", "NAM", "Namibia", "AF",
+	"NC", "NCL", "New Caledonia", "OC",
+	"NE", "NER", "Niger", "AF",
+	"NF", "NFK", "Norfolk Island", "OC",
+	"NG", "NGA", "Nigeria", "AF",
+	"NI", "NIC", "Nicaragua", "NA",
+	"NL", "NLD", "Netherlands", "EU",
+	"NO", "NOR", "Norway", "EU",
+	"NP", "NPL", "Nepal", "AS",
+	"NR", "NRU", "Nauru", "OC",
+	"NU", "NIU", "Niue", "OC",
+	"NZ", "NZL", "New Zealand", "OC",
+	"OM", "OMN", "Oman", "AS",
+	"PA", "PAN", "Panama", "NA",
+	"PE", "PER", "Peru", "SA",
+	"PF", "PYF", "French Polynesia", "OC",
+	"PG", "PNG", "Papua New Guinea", "OC",
+	"PH", "PHL", "Philippines", "AS",
+	"PK", "PAK", "Pakistan", "AS",
+	"PL", "POL", "Poland", "EU",
+	"PM", "SPM", "Saint Pierre and Miquelon", "NA",
+	"PN", "PCN", "Pitcairn", "OC",
+	"PR", "PRI", "Puerto Rico", "NA",
+	"PS", "PSE", "Palestinian Territory", "AS",
+	"PT", "PRT", "Portugal", "EU",
+	"PW", "PLW", "Palau", "OC",
+	"PY", "PRY", "Paraguay", "SA",
+	"QA", "QAT", "Qatar", "AS",
+	"RE", "REU", "Reunion", "AF",
+	"RO", "ROU", "Romania", "EU",
+	"RS", "SRB", "Serbia", "EU",
+	"RU", "RUS", "Russia", "EU",
+	"RW", "RWA", "Rwanda", "AF",
+	"SA", "SAU", "Saudi Arabia", "AS",
+	"SB", "SLB", "Solomon Islands", "OC",
+	"SC", "SYC", "Seychelles", "AF",
+	"SD", "SDN", "Sudan", "AF",
+	"SS", "SSD", "South Sudan", "AF",
+	"SE", "SWE", "Sweden", "EU",
+	"SG", "SGP", "Singapore", "AS",
+	"SH", "SHN", "Saint Helena", "AF",
+	"SI", "SVN", "Slovenia", "EU",
+	"SJ", "SJM", "Svalbard and Jan Mayen", "EU",
+	"SK", "SVK", "Slovakia", "EU",
+	"SL", "SLE", "Sierra Leone", "AF",
+	"SM", "SMR", "San Marino", "EU",
+	"SN", "SEN", "Senegal", "AF",
+	"SO", "SOM", "Somalia", "AF",
+	"SR", "SUR", "Suriname", "SA",
+	"ST", "STP", "Sao Tome and Principe", "AF",
+	"SV", "SLV", "El Salvador", "NA",
+	"SX", "SXM", "Sint Maarten", "NA",
+	"SY", "SYR", "Syria", "AS",
+	"SZ", "SWZ", "Eswatini", "AF",
+	"TC", "TCA", "Turks and Caicos Islands", "NA",
+	"TD", "TCD", "Chad", "AF",
+	"TF", "ATF", "French Southern Territories", "AN",
+	"TG", "TGO", "Togo", "AF",
+	"TH", "THA", "Thailand", "AS",
+	"TJ", "TJK", "Tajikistan", "AS",
+	"TK", "TKL", "Tokelau", "OC",
+	"TL", "TLS", "Timor Leste", "OC",
+	"TM", "TKM", "Turkmenistan", "AS",
+	"TN", "TUN", "Tunisia", "AF",
+	"TO", "TON", "Tonga", "OC",
+	"TR", "TUR", "Turkey", "AS",
+	"TT", "TTO", "Trinidad and Tobago", "NA",
+	"TV", "TUV", "Tuvalu", "OC",
+	"TW", "TWN", "Taiwan", "AS",
+	"TZ", "TZA", "Tanzania", "AF",
+	"UA", "UKR", "Ukraine", "EU",
+	"UG", "UGA", "Uganda", "AF",
+	"UM", "UMI", "United States Minor Outlying Islands", "OC",
+	"US", "USA", "United States", "NA",
+	"UY", "URY", "Uruguay", "SA",
+	"UZ", "UZB", "Uzbekistan", "AS",
+	"VA", "VAT", "Vatican", "EU",
+	"VC", "VCT", "Saint Vincent and the Grenadines", "NA",
+	"VE", "VEN", "Venezuela", "SA",
+	"VG", "VGB", "British Virgin Islands", "NA",
+	"VI", "VIR", "U.S. Virgin Islands", "NA",
+	"VN", "VNM", "Vietnam", "AS",
+	"VU", "VUT", "Vanuatu", "OC",
+	"WF", "WLF", "Wallis and Futuna", "OC",
+	"WS", "WSM", "Samoa", "OC",
+	"YE", "YEM", "Yemen", "AS",
+	"YT", "MYT", "Mayotte", "AF",
+	"ZA", "ZAF", "South Africa", "AF",
+	"ZM", "ZMB", "Zambia", "AF",
+	"ZW", "ZWE", "Zimbabwe", "AF",
+	"CS", "SCG", "Serbia and Montenegro", "EU",
+	"AN", "ANT", "Netherlands Antilles", "NA"
+};
diff --git a/modules/net/srv/dap_chain_net_srv_geoip.c b/modules/net/srv/dap_chain_net_srv_geoip.c
new file mode 100644
index 0000000000000000000000000000000000000000..a0f1a31b0b300f7c9d755fda8a7057b377f1c7f2
--- /dev/null
+++ b/modules/net/srv/dap_chain_net_srv_geoip.c
@@ -0,0 +1,254 @@
+/*
+ * Authors:
+ * Aleksandr Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+ * DeM Labs Open source community https://github.com/demlabsinc
+ * Copyright  (c) 2020
+ * All rights reserved.
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+ DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DAP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stddef.h>
+
+#include "dap_common.h"
+#include "dap_strfuncs.h"
+#include "dap_file_utils.h"
+#include "dap_enc_key.h"
+#include "dap_enc_base64.h"
+#include "dap_client_http.h"
+#include "dap_chain_net_srv_geoip.h"
+#include "libmaxminddb/maxminddb.h"
+
+#define LOG_TAG "chain_net_srv_geoip"
+#define LOCALE_DEFAULT  "en"
+
+/**
+ * @brief m_request_response
+ * @param a_response
+ * @param a_response_size
+ * @param a_obj
+ */
+static void m_request_getip_response(void * a_response, size_t a_response_size, void * a_obj)
+{
+    char *l_addr = (char *) a_obj;
+    //printf("m_request_getip_response %s\n", a_response);
+}
+
+static void m_request_getip_request_error(int a_err_code, void *a_obj)
+{
+    char *l_addr = (char *) a_obj;
+    //printf("m_request_getip_request_error %s\n", l_addr);
+}
+
+geoip_info_t *chain_net_geoip_get_ip_info_by_web(const char *a_ip_str)
+{
+    // https://geoip.maxmind.com/geoip/v2.1/insights/<ip>
+	// https://geoip.maxmind.com/geoip/v2.1/city/<ip>
+    char *l_path = dap_strdup_printf("/geoip/v2.1/insights/%s", a_ip_str);
+    //104.16.38.47:443
+    // geoip.maxmind.com
+    char l_out[40];
+    //Account/User ID        288651
+    //License key
+    // https://dev.maxmind.com/geoip/geoip2/web-services/
+    const char *user_id = "288651";
+    const char *license_key = "1JGvRmd3Ux1kcBkb";
+    char *l_auth = dap_strdup_printf("%s:%s", user_id, license_key);
+    size_t l_out_len = dap_enc_base64_encode(l_auth, strlen(l_auth), l_out, DAP_ENC_DATA_TYPE_B64);
+    char * l_custom = l_out_len > 0 ? dap_strdup_printf("Authorization: Basic %s", l_out) : NULL;
+    size_t l_custom_count = 1;
+    // todo just need to finish up https request
+    dap_client_http_request_custom("geoip.maxmind.com", 443, "GET", "application/json", l_path, NULL,
+            0, NULL, m_request_getip_response, m_request_getip_request_error, NULL, &l_custom, l_custom_count);
+    return NULL;
+}
+
+/*
+ * Get value from mmdb by 2 strings
+ */
+static int mmdb_get_value_double2(MMDB_lookup_result_s *a_result, const char *a_one, const char *a_two, double *a_out_double)
+{
+	if (!a_out_double || !a_result || !a_result->found_entry)
+		return -1;
+	MMDB_entry_data_s entry_data;
+	int l_status = MMDB_get_value(&a_result->entry, &entry_data, a_one, a_two, NULL);
+	if (MMDB_SUCCESS != l_status) {
+		log_it(L_DEBUG, "False get_value [%s->%s] with errcode=%d", a_one, a_two, l_status);
+		return -2;
+	}
+	if (entry_data.has_data) {
+		if (a_out_double && entry_data.type == MMDB_DATA_TYPE_DOUBLE) {
+			//memcpy(a_out_double, &entry_data.double_value, entry_data.data_size);
+			*a_out_double = entry_data.double_value;
+		} else
+			log_it(L_DEBUG,
+					"error value [%s->%s] has size=%d(>0) type=%d(%d)",
+					a_one, a_two, entry_data.data_size,
+					entry_data.type, MMDB_DATA_TYPE_DOUBLE);
+	}
+	else
+		return -3;
+	return 0;
+}
+
+/*
+ * Get value from mmdb by 2 strings
+ */
+static int mmdb_get_value_str2(MMDB_lookup_result_s *a_result, const char *a_one, const char *a_two, char *a_out_str, size_t a_out_str_size)
+{
+	if (!a_out_str || !a_result || !a_result->found_entry)
+		return -1;
+	MMDB_entry_data_s entry_data;
+	int l_status = MMDB_get_value(&a_result->entry, &entry_data, a_one, a_two, NULL);
+	if (MMDB_SUCCESS != l_status) {
+		log_it(L_DEBUG, "False get_value [%s->%s] with errcode=%d", a_one, a_two, l_status);
+		return -2;
+	}
+	if (entry_data.has_data) {
+		if (entry_data.data_size > 0 && entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
+			size_t l_size = min(a_out_str_size-1, entry_data.data_size);
+			strncpy(a_out_str, entry_data.utf8_string, l_size);
+			a_out_str[l_size] = 0;
+		} else
+			log_it(L_DEBUG,
+					"error value [%s->%s] has size=%d(>0) type=%d(%d)",
+					a_one, a_two, entry_data.data_size,
+					entry_data.type, MMDB_DATA_TYPE_UTF8_STRING);
+	}
+	else
+		return -3;
+	return 0;
+}
+
+/*
+ * Get value from mmdb by 3 strings
+ */
+static int mmdb_get_value_str3(MMDB_lookup_result_s *a_result, const char *a_one, const char *a_two, const char *a_three, char *a_out_str, size_t a_out_str_size)
+{
+	if (!a_out_str || !a_result || !a_result->found_entry)
+		return -1;
+	MMDB_entry_data_s entry_data;
+	int l_status = MMDB_get_value(&a_result->entry, &entry_data, a_one, a_two, a_three, NULL);
+	if (MMDB_SUCCESS != l_status) {
+		log_it(L_DEBUG, "False get_value [%s->%s->%s] with errcode=%d", a_one, a_two, a_three, l_status);
+		return -2;
+	}
+	if (entry_data.has_data) {
+		if (entry_data.data_size > 0 && entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
+			size_t l_size = min(a_out_str_size-1, entry_data.data_size);
+			strncpy(a_out_str, entry_data.utf8_string, l_size);
+			a_out_str[l_size] = 0;
+		} else
+			log_it(L_DEBUG,
+					"error value [%s->%s->%s] has size=%d(>0) type=%d(%d)",
+					a_one, a_two, a_three, entry_data.data_size,
+					entry_data.type, MMDB_DATA_TYPE_UTF8_STRING);
+	}
+	else
+		return -3;
+	return 0;
+}
+
+geoip_info_t *chain_net_geoip_get_ip_info_by_local_db(const char *a_ip_str, const char *a_locale)
+{
+	// https://geoip.maxmind.com/geoip/v2.1/city/178.7.88.55
+	// https://maxmind.github.io/libmaxminddb/
+    char *l_file_db_name = dap_strdup_printf("%s/share/geoip/GeoLite2-City.mmdb", g_sys_dir_path);
+    if(!dap_file_test(l_file_db_name)) {
+        DAP_DELETE(l_file_db_name);
+        return NULL ;
+    }
+    MMDB_s mmdb;
+    int l_status = MMDB_open(l_file_db_name, MMDB_MODE_MMAP, &mmdb);
+    if(MMDB_SUCCESS != l_status) {
+        log_it(L_WARNING, "geoip file %s opened with errcode=%d", l_file_db_name, l_status);
+        return NULL ;
+    }
+    DAP_DELETE(l_file_db_name);
+
+	geoip_info_t *l_ret = DAP_NEW_Z(geoip_info_t);
+
+	int gai_error, mmdb_error;
+	MMDB_lookup_result_s result = MMDB_lookup_string(&mmdb, a_ip_str, &gai_error, &mmdb_error);
+	if (0 != gai_error || MMDB_SUCCESS != mmdb_error) {
+		log_it(L_WARNING, "no lookup ip=%s with errcode=%d", a_ip_str, l_status);
+	}
+
+	// continent
+	if (mmdb_get_value_str3(&result, "continent", "names", a_locale, l_ret->continent, sizeof(l_ret->continent))) {
+		if (mmdb_get_value_str3(&result, "continent", "names", LOCALE_DEFAULT, l_ret->continent, sizeof(l_ret->continent))) {
+			MMDB_close(&mmdb);
+			DAP_FREE(l_ret);
+			return NULL;
+		}
+	}
+	// country
+	if (mmdb_get_value_str3(&result, "country", "names", a_locale, l_ret->country_name, sizeof(l_ret->country_name))) {
+		if (mmdb_get_value_str3(&result, "country", "names", LOCALE_DEFAULT, l_ret->country_name, sizeof(l_ret->country_name))) {
+			MMDB_close(&mmdb);
+			DAP_FREE(l_ret);
+			return NULL;
+		}
+	}
+	// all the country names http://download.geonames.org/export/dump/countryInfo.txt
+	if (mmdb_get_value_str2(&result, "country", "iso_code", l_ret->country_code, sizeof(l_ret->country_code))) {
+		MMDB_close(&mmdb);
+		DAP_FREE(l_ret);
+		return NULL;
+	}
+	// city
+	/*if (mmdb_get_value_str3(&result, "city", "names", a_locale, l_ret->city_name, sizeof(l_ret->city_name))) {
+		if (mmdb_get_value_str3(&result, "city", "names", LOCALE_DEFAULT, l_ret->city_name, sizeof(l_ret->city_name))) {
+			MMDB_close(&mmdb);
+			DAP_FREE(l_ret);
+			return NULL;
+		}
+	}*/
+
+	//location
+	if (mmdb_get_value_double2(&result, "location", "latitude", &l_ret->latitude)) {
+		MMDB_close(&mmdb);
+		DAP_FREE(l_ret);
+		return NULL;
+	}
+	if (mmdb_get_value_double2(&result, "location", "longitude", &l_ret->longitude)) {
+		MMDB_close(&mmdb);
+		DAP_FREE(l_ret);
+		return NULL;
+	}
+
+	// IP
+	/*if (mmdb_get_value_str2(&result, "traits", "ip_address", l_ret->ip_str, sizeof(l_ret->ip_str))) {
+		MMDB_close(&mmdb);
+		DAP_FREE(l_ret);
+		return NULL;
+	}*/
+	int a = sizeof(l_ret->ip_str);
+	size_t l_size = min(dap_strlen(a_ip_str), sizeof(l_ret->ip_str));
+	l_ret->ip_str[l_size] = 0;
+	strncpy(l_ret->ip_str, a_ip_str, l_size);
+
+	MMDB_close(&mmdb);
+	return l_ret;
+}
+
+geoip_info_t *chain_net_geoip_get_ip_info(const char *a_ip_str)
+{
+    return chain_net_geoip_get_ip_info_by_local_db(a_ip_str, "en");
+    //return chain_net_geoip_get_ip_info_by_web(a_ip_str);
+}
diff --git a/modules/net/srv/dap_chain_net_srv_geoip.h b/modules/net/srv/dap_chain_net_srv_geoip.h
new file mode 100644
index 0000000000000000000000000000000000000000..714dbc62f243ff722285a3633aaaf04324787352
--- /dev/null
+++ b/modules/net/srv/dap_chain_net_srv_geoip.h
@@ -0,0 +1,37 @@
+/*
+ * Authors:
+ * Aleksandr Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+ * DeM Labs Open source community https://github.com/demlabsinc
+ * Copyright  (c) 2020
+ * All rights reserved.
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+ DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DAP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+typedef struct geoip_info {
+
+    char ip_str[20];
+    char continent[60];
+    char country_name[64];
+    char country_code[3];// iso_code, all the country names http://download.geonames.org/export/dump/countryInfo.txt
+    char city_name[64];
+    double latitude;
+    double longitude;
+
+} geoip_info_t;
+
+geoip_info_t *chain_net_geoip_get_ip_info(const char *a_ip_str);
diff --git a/modules/net/srv/dap_chain_net_srv_order.c b/modules/net/srv/dap_chain_net_srv_order.c
index 104424e56a1842fd67c42f38aa0cd2ed3c938951..81c490d689347d6ec19f76c970a27a76edd3f3b6 100644
--- a/modules/net/srv/dap_chain_net_srv_order.c
+++ b/modules/net/srv/dap_chain_net_srv_order.c
@@ -22,13 +22,28 @@
  along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <stdio.h>
+#include <strings.h>
+
 #include "dap_chain_net_srv_order.h"
 
 #include "dap_hash.h"
 #include "dap_chain_global_db.h"
+#include "dap_chain_net_srv_countries.h"
+//#include "dap_chain_net_srv_geoip.h"
 
 #define LOG_TAG "dap_chain_net_srv_order"
 
+/*
+Continent codes :
+AF : Africa			geonameId=6255146
+AS : Asia			geonameId=6255147
+EU : Europe			geonameId=6255148
+NA : North America		geonameId=6255149
+OC : Oceania			geonameId=6255151
+SA : South America		geonameId=6255150
+AN : Antarctica			geonameId=6255152
+ */
 char *s_server_continents[]={
         "None",
         "Africa",
@@ -36,7 +51,10 @@ char *s_server_continents[]={
         "North America",
         "South America",
         "Southeast Asia",
-        "Near East",
+		"Asia",
+        //"Near East",
+		"Oceania",
+		"Antarctica"
  };
 
 /**
@@ -46,6 +64,7 @@ char *s_server_continents[]={
 int dap_chain_net_srv_order_init(void)
 {
 
+	//geoip_info_t *l_ipinfo = chain_net_geoip_get_ip_info("8.8.8.8");
     return 0;
 }
 
@@ -112,8 +131,7 @@ bool dap_chain_net_srv_order_get_continent_region(dap_chain_net_srv_order_t *a_o
            memcpy(a_continent_num, a_order->ext + 1, sizeof(uint8_t));
         else
            a_continent_num = 0;
-    }else
-        a_continent_num = 0;
+    }
     if(a_region) {
         size_t l_size = a_order->ext_size - sizeof(uint8_t) - 1;
         if(l_size > 0) {
@@ -126,6 +144,26 @@ bool dap_chain_net_srv_order_get_continent_region(dap_chain_net_srv_order_t *a_o
     return true;
 }
 
+/**
+ * @brief dap_chain_net_srv_order_get_country_code
+ * @param a_order
+ */
+const char* dap_chain_net_srv_order_get_country_code(dap_chain_net_srv_order_t *a_order)
+{
+	char *l_region = NULL;
+	if (!dap_chain_net_srv_order_get_continent_region(a_order, NULL, &l_region))
+		return NULL;
+	int l_countries = sizeof(s_server_countries)/sizeof(char*);
+	for (int i = 0; i < l_countries; i+=4) {
+		if(l_region && (!strcasecmp(l_region, s_server_countries[i+1]) || !strcasecmp(l_region, s_server_countries[i+2]))){
+			const char *l_country_code = s_server_countries[i];
+			DAP_DELETE(l_region);
+			return l_country_code;
+		}
+	}
+	DAP_DELETE(l_region);
+	return NULL;
+}
 
 /**
  * @brief dap_chain_net_srv_order_continents_count
diff --git a/modules/net/srv/include/dap_chain_net_srv_order.h b/modules/net/srv/include/dap_chain_net_srv_order.h
index 3aa88fd18145045a68c158a1edd97bd760588f22..d99377fcbf8935a2601800a3915bee50b0d8a8c5 100644
--- a/modules/net/srv/include/dap_chain_net_srv_order.h
+++ b/modules/net/srv/include/dap_chain_net_srv_order.h
@@ -57,6 +57,7 @@ size_t dap_chain_net_srv_order_get_size(dap_chain_net_srv_order_t *a_order);
 bool dap_chain_net_srv_order_set_continent_region(dap_chain_net_srv_order_t **a_order, uint8_t a_continent_num, const char *a_region);
 bool dap_chain_net_srv_order_get_continent_region(dap_chain_net_srv_order_t *a_order, uint8_t *a_continent_num, char **a_region);
 
+const char* dap_chain_net_srv_order_get_country_code(dap_chain_net_srv_order_t *a_order);
 size_t dap_chain_net_srv_order_continents_count(void);
 const char* dap_chain_net_srv_order_continent_to_str(int8_t a_num);
 int8_t dap_chain_net_srv_order_continent_to_num(const char *l_continent_str);
diff --git a/modules/net/srv/libmaxminddb/data-pool.c b/modules/net/srv/libmaxminddb/data-pool.c
new file mode 100644
index 0000000000000000000000000000000000000000..48521b64dec3df796ca876011ee4d6c65d2a4efb
--- /dev/null
+++ b/modules/net/srv/libmaxminddb/data-pool.c
@@ -0,0 +1,180 @@
+#include "data-pool.h"
+#include "maxminddb.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+static bool can_multiply(size_t const, size_t const, size_t const);
+
+// Allocate an MMDB_data_pool_s. It initially has space for size
+// MMDB_entry_data_list_s structs.
+MMDB_data_pool_s *data_pool_new(size_t const size)
+{
+    MMDB_data_pool_s *const pool = calloc(1, sizeof(MMDB_data_pool_s));
+    if (!pool) {
+        return NULL;
+    }
+
+    if (size == 0 ||
+        !can_multiply(SIZE_MAX, size, sizeof(MMDB_entry_data_list_s))) {
+        data_pool_destroy(pool);
+        return NULL;
+    }
+    pool->size = size;
+    pool->blocks[0] = calloc(pool->size, sizeof(MMDB_entry_data_list_s));
+    if (!pool->blocks[0]) {
+        data_pool_destroy(pool);
+        return NULL;
+    }
+    pool->blocks[0]->pool = pool;
+
+    pool->sizes[0] = size;
+
+    pool->block = pool->blocks[0];
+
+    return pool;
+}
+
+// Determine if we can multiply m*n. We can do this if the result will be below
+// the given max. max will typically be SIZE_MAX.
+//
+// We want to know if we'll wrap around.
+static bool can_multiply(size_t const max, size_t const m, size_t const n)
+{
+    if (m == 0) {
+        return false;
+    }
+
+    return n <= max / m;
+}
+
+// Clean up the data pool.
+void data_pool_destroy(MMDB_data_pool_s *const pool)
+{
+    if (!pool) {
+        return;
+    }
+
+    for (size_t i = 0; i <= pool->index; i++) {
+        free(pool->blocks[i]);
+    }
+
+    free(pool);
+}
+
+// Claim a new struct from the pool. Doing this may cause the pool's size to
+// grow.
+MMDB_entry_data_list_s *data_pool_alloc(MMDB_data_pool_s *const pool)
+{
+    if (!pool) {
+        return NULL;
+    }
+
+    if (pool->used < pool->size) {
+        MMDB_entry_data_list_s *const element = pool->block + pool->used;
+        pool->used++;
+        return element;
+    }
+
+    // Take it from a new block of memory.
+
+    size_t const new_index = pool->index + 1;
+    if (new_index == DATA_POOL_NUM_BLOCKS) {
+        // See the comment about not growing this on DATA_POOL_NUM_BLOCKS.
+        return NULL;
+    }
+
+    if (!can_multiply(SIZE_MAX, pool->size, 2)) {
+        return NULL;
+    }
+    size_t const new_size = pool->size * 2;
+
+    if (!can_multiply(SIZE_MAX, new_size, sizeof(MMDB_entry_data_list_s))) {
+        return NULL;
+    }
+    pool->blocks[new_index] = calloc(new_size, sizeof(MMDB_entry_data_list_s));
+    if (!pool->blocks[new_index]) {
+        return NULL;
+    }
+
+    // We don't need to set this, but it's useful for introspection in tests.
+    pool->blocks[new_index]->pool = pool;
+
+    pool->index = new_index;
+    pool->block = pool->blocks[pool->index];
+
+    pool->size = new_size;
+    pool->sizes[pool->index] = pool->size;
+
+    MMDB_entry_data_list_s *const element = pool->block;
+    pool->used = 1;
+    return element;
+}
+
+// Turn the structs in the array-like pool into a linked list.
+//
+// Before calling this function, the list isn't linked up.
+MMDB_entry_data_list_s *data_pool_to_list(MMDB_data_pool_s *const pool)
+{
+    if (!pool) {
+        return NULL;
+    }
+
+    if (pool->index == 0 && pool->used == 0) {
+        return NULL;
+    }
+
+    for (size_t i = 0; i <= pool->index; i++) {
+        MMDB_entry_data_list_s *const block = pool->blocks[i];
+
+        size_t size = pool->sizes[i];
+        if (i == pool->index) {
+            size = pool->used;
+        }
+
+        for (size_t j = 0; j < size - 1; j++) {
+            MMDB_entry_data_list_s *const cur = block + j;
+            cur->next = block + j + 1;
+        }
+
+        if (i < pool->index) {
+            MMDB_entry_data_list_s *const last = block + size - 1;
+            last->next = pool->blocks[i + 1];
+        }
+    }
+
+    return pool->blocks[0];
+}
+
+#ifdef TEST_DATA_POOL
+
+#include <libtap/tap.h>
+#include <maxminddb_test_helper.h>
+
+static void test_can_multiply(void);
+
+int main(void)
+{
+    plan(NO_PLAN);
+    test_can_multiply();
+    done_testing();
+}
+
+static void test_can_multiply(void)
+{
+    {
+        ok(can_multiply(SIZE_MAX, 1, SIZE_MAX), "1*SIZE_MAX is ok");
+    }
+
+    {
+        ok(!can_multiply(SIZE_MAX, 2, SIZE_MAX), "2*SIZE_MAX is not ok");
+    }
+
+    {
+        ok(can_multiply(SIZE_MAX, 10240, sizeof(MMDB_entry_data_list_s)),
+           "1024 entry_data_list_s's are okay");
+    }
+}
+
+#endif
diff --git a/modules/net/srv/libmaxminddb/data-pool.h b/modules/net/srv/libmaxminddb/data-pool.h
new file mode 100644
index 0000000000000000000000000000000000000000..25d09923e0cc2c301576037e4ba65548ce42b4fb
--- /dev/null
+++ b/modules/net/srv/libmaxminddb/data-pool.h
@@ -0,0 +1,52 @@
+#ifndef DATA_POOL_H
+#define DATA_POOL_H
+
+#include "maxminddb.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+
+// This should be large enough that we never need to grow the array of pointers
+// to blocks. 32 is enough. Even starting out of with size 1 (1 struct), the
+// 32nd element alone will provide 2**32 structs as we exponentially increase
+// the number in each block. Being confident that we do not have to grow the
+// array lets us avoid writing code to do that. That code would be risky as it
+// would rarely be hit and likely not be well tested.
+#define DATA_POOL_NUM_BLOCKS 32
+
+// A pool of memory for MMDB_entry_data_list_s structs. This is so we can
+// allocate multiple up front rather than one at a time for performance
+// reasons.
+//
+// The order you add elements to it (by calling data_pool_alloc()) ends up as
+// the order of the list.
+//
+// The memory only grows. There is no support for releasing an element you take
+// back to the pool.
+typedef struct MMDB_data_pool_s {
+    // Index of the current block we're allocating out of.
+    size_t index;
+
+    // The size of the current block, counting by structs.
+    size_t size;
+
+    // How many used in the current block, counting by structs.
+    size_t used;
+
+    // The current block we're allocating out of.
+    MMDB_entry_data_list_s *block;
+
+    // The size of each block.
+    size_t sizes[DATA_POOL_NUM_BLOCKS];
+
+    // An array of pointers to blocks of memory holding space for list
+    // elements.
+    MMDB_entry_data_list_s *blocks[DATA_POOL_NUM_BLOCKS];
+} MMDB_data_pool_s;
+
+MMDB_data_pool_s *data_pool_new(size_t const);
+void data_pool_destroy(MMDB_data_pool_s *const);
+MMDB_entry_data_list_s *data_pool_alloc(MMDB_data_pool_s *const);
+MMDB_entry_data_list_s *data_pool_to_list(MMDB_data_pool_s *const);
+
+#endif
diff --git a/modules/net/srv/libmaxminddb/maxminddb-compat-util.h b/modules/net/srv/libmaxminddb/maxminddb-compat-util.h
new file mode 100644
index 0000000000000000000000000000000000000000..e3f0320f29b9180305ec3b8484fc6a00432011d9
--- /dev/null
+++ b/modules/net/srv/libmaxminddb/maxminddb-compat-util.h
@@ -0,0 +1,167 @@
+#include <stdlib.h>
+#include <string.h>
+
+/* *INDENT-OFF* */
+
+/* The memmem, strdup, and strndup functions were all copied from the
+ * FreeBSD source, along with the relevant copyright notice.
+ *
+ * It'd be nicer to simply use the functions available on the system if they
+ * exist, but there doesn't seem to be a good way to detect them without also
+ * defining things like _GNU_SOURCE, which we want to avoid, because then we
+ * end up _accidentally_ using GNU features without noticing, which then
+ * breaks on systems like OSX.
+ *
+ * C is fun! */
+
+/* Applies to memmem implementation */
+/*-
+ * Copyright (c) 2005 Pascal Gloor <pascal.gloor@spale.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+static void *
+mmdb_memmem(const void *l, size_t l_len, const void *s, size_t s_len)
+{
+	register char *cur, *last;
+	const char *cl = (const char *)l;
+	const char *cs = (const char *)s;
+
+	/* we need something to compare */
+	if (l_len == 0 || s_len == 0)
+		return NULL;
+
+	/* "s" must be smaller or equal to "l" */
+	if (l_len < s_len)
+		return NULL;
+
+	/* special case where s_len == 1 */
+	if (s_len == 1)
+		return memchr(l, (int)*cs, l_len);
+
+	/* the last position where its possible to find "s" in "l" */
+	last = (char *)cl + l_len - s_len;
+
+	for (cur = (char *)cl; cur <= last; cur++)
+		if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
+			return cur;
+
+	return NULL;
+}
+
+/* Applies to strnlen implementation */
+/*-
+ * Copyright (c) 2009 David Schultz <das@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+static size_t
+mmdb_strnlen(const char *s, size_t maxlen)
+{
+	size_t len;
+
+	for (len = 0; len < maxlen; len++, s++) {
+		if (!*s)
+			break;
+	}
+	return (len);
+}
+
+/* Applies to strdup and strndup implementation */
+/*
+ * Copyright (c) 1988, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+static char *
+mmdb_strdup(const char *str)
+{
+	size_t len;
+	char *copy;
+
+	len = strlen(str) + 1;
+	if ((copy = malloc(len)) == NULL)
+		return (NULL);
+	memcpy(copy, str, len);
+	return (copy);
+}
+
+static char *
+mmdb_strndup(const char *str, size_t n)
+{
+	size_t len;
+	char *copy;
+
+	len = mmdb_strnlen(str, n);
+	if ((copy = malloc(len + 1)) == NULL)
+		return (NULL);
+	memcpy(copy, str, len);
+	copy[len] = '\0';
+	return (copy);
+}
+/* *INDENT-ON* */
diff --git a/modules/net/srv/libmaxminddb/maxminddb.c b/modules/net/srv/libmaxminddb/maxminddb.c
new file mode 100644
index 0000000000000000000000000000000000000000..e7c9d3b921d2b3a431b0017c92fb5a2f26aed396
--- /dev/null
+++ b/modules/net/srv/libmaxminddb/maxminddb.c
@@ -0,0 +1,2154 @@
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "data-pool.h"
+#include "maxminddb.h"
+#include "maxminddb-compat-util.h"
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+#ifndef UNICODE
+#define UNICODE
+#endif
+#include <windows.h>
+#include <ws2ipdef.h>
+#else
+#include <arpa/inet.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#endif
+
+#define MMDB_DATA_SECTION_SEPARATOR (16)
+#define MAXIMUM_DATA_STRUCTURE_DEPTH (512)
+
+#ifdef MMDB_DEBUG
+#define LOCAL
+#define DEBUG_MSG(msg) fprintf(stderr, msg "\n")
+#define DEBUG_MSGF(fmt, ...) fprintf(stderr, fmt "\n", __VA_ARGS__)
+#define DEBUG_BINARY(fmt, byte)                                 \
+    do {                                                        \
+        char *binary = byte_to_binary(byte);                    \
+        if (NULL == binary) {                                   \
+            fprintf(stderr, "Malloc failed in DEBUG_BINARY\n"); \
+            abort();                                            \
+        }                                                       \
+        fprintf(stderr, fmt "\n", binary);                      \
+        free(binary);                                           \
+    } while (0)
+#define DEBUG_NL fprintf(stderr, "\n")
+#else
+#define LOCAL static
+#define DEBUG_MSG(...)
+#define DEBUG_MSGF(...)
+#define DEBUG_BINARY(...)
+#define DEBUG_NL
+#endif
+
+#ifdef MMDB_DEBUG
+char *byte_to_binary(uint8_t byte)
+{
+    char *bits = malloc(sizeof(char) * 9);
+    if (NULL == bits) {
+        return bits;
+    }
+
+    for (uint8_t i = 0; i < 8; i++) {
+        bits[i] = byte & (128 >> i) ? '1' : '0';
+    }
+    bits[8] = '\0';
+
+    return bits;
+}
+
+char *type_num_to_name(uint8_t num)
+{
+    switch (num) {
+    case 0:
+        return "extended";
+    case 1:
+        return "pointer";
+    case 2:
+        return "utf8_string";
+    case 3:
+        return "double";
+    case 4:
+        return "bytes";
+    case 5:
+        return "uint16";
+    case 6:
+        return "uint32";
+    case 7:
+        return "map";
+    case 8:
+        return "int32";
+    case 9:
+        return "uint64";
+    case 10:
+        return "uint128";
+    case 11:
+        return "array";
+    case 12:
+        return "container";
+    case 13:
+        return "end_marker";
+    case 14:
+        return "boolean";
+    case 15:
+        return "float";
+    default:
+        return "unknown type";
+    }
+}
+#endif
+
+/* None of the values we check on the lhs are bigger than uint32_t, so on
+ * platforms where SIZE_MAX is a 64-bit integer, this would be a no-op, and it
+ * makes the compiler complain if we do the check anyway. */
+#if SIZE_MAX == UINT32_MAX
+#define MAYBE_CHECK_SIZE_OVERFLOW(lhs, rhs, error) \
+    if ((lhs) > (rhs)) {                           \
+        return error;                              \
+    }
+#else
+#define MAYBE_CHECK_SIZE_OVERFLOW(...)
+#endif
+
+typedef struct record_info_s {
+    uint16_t record_length;
+    uint32_t (*left_record_getter)(const uint8_t *);
+    uint32_t (*right_record_getter)(const uint8_t *);
+    uint8_t right_record_offset;
+} record_info_s;
+
+#define METADATA_MARKER "\xab\xcd\xefMaxMind.com"
+/* This is 128kb */
+#define METADATA_BLOCK_MAX_SIZE 131072
+
+// 64 leads us to allocating 4 KiB on a 64bit system.
+#define MMDB_POOL_INIT_SIZE 64
+
+LOCAL int map_file(MMDB_s *const mmdb);
+LOCAL const uint8_t *find_metadata(const uint8_t *file_content,
+                                   ssize_t file_size, uint32_t *metadata_size);
+LOCAL int read_metadata(MMDB_s *mmdb);
+LOCAL MMDB_s make_fake_metadata_db(const MMDB_s *const mmdb);
+LOCAL int value_for_key_as_uint16(MMDB_entry_s *start, char *key,
+                                  uint16_t *value);
+LOCAL int value_for_key_as_uint32(MMDB_entry_s *start, char *key,
+                                  uint32_t *value);
+LOCAL int value_for_key_as_uint64(MMDB_entry_s *start, char *key,
+                                  uint64_t *value);
+LOCAL int value_for_key_as_string(MMDB_entry_s *start, char *key,
+                                  char const **value);
+LOCAL int populate_languages_metadata(MMDB_s *mmdb, MMDB_s *metadata_db,
+                                      MMDB_entry_s *metadata_start);
+LOCAL int populate_description_metadata(MMDB_s *mmdb, MMDB_s *metadata_db,
+                                        MMDB_entry_s *metadata_start);
+LOCAL int resolve_any_address(const char *ipstr, struct addrinfo **addresses);
+LOCAL int find_address_in_search_tree(const MMDB_s *const mmdb,
+                                      uint8_t *address,
+                                      sa_family_t address_family,
+                                      MMDB_lookup_result_s *result);
+LOCAL record_info_s record_info_for_database(const MMDB_s *const mmdb);
+LOCAL int find_ipv4_start_node(MMDB_s *const mmdb);
+LOCAL uint8_t record_type(const MMDB_s *const mmdb, uint64_t record);
+LOCAL uint32_t get_left_28_bit_record(const uint8_t *record);
+LOCAL uint32_t get_right_28_bit_record(const uint8_t *record);
+LOCAL uint32_t data_section_offset_for_record(const MMDB_s *const mmdb,
+                                              uint64_t record);
+LOCAL int path_length(va_list va_path);
+LOCAL int lookup_path_in_array(const char *path_elem, const MMDB_s *const mmdb,
+                               MMDB_entry_data_s *entry_data);
+LOCAL int lookup_path_in_map(const char *path_elem, const MMDB_s *const mmdb,
+                             MMDB_entry_data_s *entry_data);
+LOCAL int skip_map_or_array(const MMDB_s *const mmdb,
+                            MMDB_entry_data_s *entry_data);
+LOCAL int decode_one_follow(const MMDB_s *const mmdb, uint32_t offset,
+                            MMDB_entry_data_s *entry_data);
+LOCAL int decode_one(const MMDB_s *const mmdb, uint32_t offset,
+                     MMDB_entry_data_s *entry_data);
+LOCAL int get_ext_type(int raw_ext_type);
+LOCAL uint32_t get_ptr_from(uint8_t ctrl, uint8_t const *const ptr,
+                            int ptr_size);
+LOCAL int get_entry_data_list(const MMDB_s *const mmdb,
+                              uint32_t offset,
+                              MMDB_entry_data_list_s *const entry_data_list,
+                              MMDB_data_pool_s *const pool,
+                              int depth);
+LOCAL float get_ieee754_float(const uint8_t *restrict p);
+LOCAL double get_ieee754_double(const uint8_t *restrict p);
+LOCAL uint32_t get_uint32(const uint8_t *p);
+LOCAL uint32_t get_uint24(const uint8_t *p);
+LOCAL uint32_t get_uint16(const uint8_t *p);
+LOCAL uint64_t get_uintX(const uint8_t *p, int length);
+LOCAL int32_t get_sintX(const uint8_t *p, int length);
+LOCAL void free_mmdb_struct(MMDB_s *const mmdb);
+LOCAL void free_languages_metadata(MMDB_s *mmdb);
+LOCAL void free_descriptions_metadata(MMDB_s *mmdb);
+LOCAL MMDB_entry_data_list_s *dump_entry_data_list(
+    FILE *stream, MMDB_entry_data_list_s *entry_data_list, int indent,
+    int *status);
+LOCAL void print_indentation(FILE *stream, int i);
+LOCAL char *bytes_to_hex(uint8_t *bytes, uint32_t size);
+
+#define CHECKED_DECODE_ONE(mmdb, offset, entry_data)                        \
+    do {                                                                    \
+        int status = decode_one(mmdb, offset, entry_data);                  \
+        if (MMDB_SUCCESS != status) {                                       \
+            DEBUG_MSGF("CHECKED_DECODE_ONE failed."                         \
+                       " status = %d (%s)", status, MMDB_strerror(status)); \
+            return status;                                                  \
+        }                                                                   \
+    } while (0)
+
+#define CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, entry_data)                 \
+    do {                                                                    \
+        int status = decode_one_follow(mmdb, offset, entry_data);           \
+        if (MMDB_SUCCESS != status) {                                       \
+            DEBUG_MSGF("CHECKED_DECODE_ONE_FOLLOW failed."                  \
+                       " status = %d (%s)", status, MMDB_strerror(status)); \
+            return status;                                                  \
+        }                                                                   \
+    } while (0)
+
+#define FREE_AND_SET_NULL(p) { free((void *)(p)); (p) = NULL; }
+
+int MMDB_open(const char *const filename, uint32_t flags, MMDB_s *const mmdb)
+{
+    int status = MMDB_SUCCESS;
+
+    mmdb->file_content = NULL;
+    mmdb->data_section = NULL;
+    mmdb->metadata.database_type = NULL;
+    mmdb->metadata.languages.count = 0;
+    mmdb->metadata.languages.names = NULL;
+    mmdb->metadata.description.count = 0;
+
+    mmdb->filename = mmdb_strdup(filename);
+    if (NULL == mmdb->filename) {
+        status = MMDB_OUT_OF_MEMORY_ERROR;
+        goto cleanup;
+    }
+
+    if ((flags & MMDB_MODE_MASK) == 0) {
+        flags |= MMDB_MODE_MMAP;
+    }
+    mmdb->flags = flags;
+
+    if (MMDB_SUCCESS != (status = map_file(mmdb))) {
+        goto cleanup;
+    }
+
+#ifdef _WIN32
+    WSADATA wsa;
+    WSAStartup(MAKEWORD(2, 2), &wsa);
+#endif
+
+    uint32_t metadata_size = 0;
+    const uint8_t *metadata = find_metadata(mmdb->file_content, mmdb->file_size,
+                                            &metadata_size);
+    if (NULL == metadata) {
+        status = MMDB_INVALID_METADATA_ERROR;
+        goto cleanup;
+    }
+
+    mmdb->metadata_section = metadata;
+    mmdb->metadata_section_size = metadata_size;
+
+    status = read_metadata(mmdb);
+    if (MMDB_SUCCESS != status) {
+        goto cleanup;
+    }
+
+    if (mmdb->metadata.binary_format_major_version != 2) {
+        status = MMDB_UNKNOWN_DATABASE_FORMAT_ERROR;
+        goto cleanup;
+    }
+
+    uint32_t search_tree_size = mmdb->metadata.node_count *
+                                mmdb->full_record_byte_size;
+
+    mmdb->data_section = mmdb->file_content + search_tree_size
+                         + MMDB_DATA_SECTION_SEPARATOR;
+    if (search_tree_size + MMDB_DATA_SECTION_SEPARATOR >
+        (uint32_t)mmdb->file_size) {
+        status = MMDB_INVALID_METADATA_ERROR;
+        goto cleanup;
+    }
+    mmdb->data_section_size = (uint32_t)mmdb->file_size - search_tree_size -
+                              MMDB_DATA_SECTION_SEPARATOR;
+
+    // Although it is likely not possible to construct a database with valid
+    // valid metadata, as parsed above, and a data_section_size less than 3,
+    // we do this check as later we assume it is at least three when doing
+    // bound checks.
+    if (mmdb->data_section_size < 3) {
+        status = MMDB_INVALID_DATA_ERROR;
+        goto cleanup;
+    }
+
+    mmdb->metadata_section = metadata;
+    mmdb->ipv4_start_node.node_value = 0;
+    mmdb->ipv4_start_node.netmask = 0;
+
+    // We do this immediately as otherwise there is a race to set
+    // ipv4_start_node.node_value and ipv4_start_node.netmask.
+    if (mmdb->metadata.ip_version == 6) {
+        status = find_ipv4_start_node(mmdb);
+        if (status != MMDB_SUCCESS) {
+            goto cleanup;
+        }
+    }
+
+ cleanup:
+    if (MMDB_SUCCESS != status) {
+        int saved_errno = errno;
+        free_mmdb_struct(mmdb);
+        errno = saved_errno;
+    }
+    return status;
+}
+
+#ifdef _WIN32
+
+LOCAL LPWSTR utf8_to_utf16(const char *utf8_str)
+{
+    int wide_chars = MultiByteToWideChar(CP_UTF8, 0, utf8_str, -1, NULL, 0);
+    wchar_t *utf16_str = (wchar_t *)malloc(wide_chars * sizeof(wchar_t));
+
+    if (MultiByteToWideChar(CP_UTF8, 0, utf8_str, -1, utf16_str,
+                            wide_chars) < 1) {
+        free(utf16_str);
+        return NULL;
+    }
+
+    return utf16_str;
+}
+
+LOCAL int map_file(MMDB_s *const mmdb)
+{
+    DWORD size;
+    int status = MMDB_SUCCESS;
+    HANDLE mmh = NULL;
+    HANDLE fd = INVALID_HANDLE_VALUE;
+    LPWSTR utf16_filename = utf8_to_utf16(mmdb->filename);
+    if (!utf16_filename) {
+        status = MMDB_FILE_OPEN_ERROR;
+        goto cleanup;
+    }
+    fd = CreateFile(utf16_filename, GENERIC_READ, FILE_SHARE_READ, NULL,
+                    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+    if (fd == INVALID_HANDLE_VALUE) {
+        status = MMDB_FILE_OPEN_ERROR;
+        goto cleanup;
+    }
+    size = GetFileSize(fd, NULL);
+    if (size == INVALID_FILE_SIZE) {
+        status = MMDB_FILE_OPEN_ERROR;
+        goto cleanup;
+    }
+    mmh = CreateFileMapping(fd, NULL, PAGE_READONLY, 0, size, NULL);
+    /* Microsoft documentation for CreateFileMapping indicates this returns
+        NULL not INVALID_HANDLE_VALUE on error */
+    if (NULL == mmh) {
+        status = MMDB_IO_ERROR;
+        goto cleanup;
+    }
+    uint8_t *file_content =
+        (uint8_t *)MapViewOfFile(mmh, FILE_MAP_READ, 0, 0, 0);
+    if (file_content == NULL) {
+        status = MMDB_IO_ERROR;
+        goto cleanup;
+    }
+
+    mmdb->file_size = size;
+    mmdb->file_content = file_content;
+
+ cleanup:;
+    int saved_errno = errno;
+    if (INVALID_HANDLE_VALUE != fd) {
+        CloseHandle(fd);
+    }
+    if (NULL != mmh) {
+        CloseHandle(mmh);
+    }
+    errno = saved_errno;
+    free(utf16_filename);
+
+    return status;
+}
+
+#else // _WIN32
+
+LOCAL int map_file(MMDB_s *const mmdb)
+{
+    ssize_t size;
+    int status = MMDB_SUCCESS;
+
+    int flags = O_RDONLY;
+#ifdef O_CLOEXEC
+    flags |= O_CLOEXEC;
+#endif
+    int fd = open(mmdb->filename, flags);
+    struct stat s;
+    if (fd < 0 || fstat(fd, &s)) {
+        status = MMDB_FILE_OPEN_ERROR;
+        goto cleanup;
+    }
+
+    size = s.st_size;
+    if (size < 0 || size != s.st_size) {
+        status = MMDB_OUT_OF_MEMORY_ERROR;
+        goto cleanup;
+    }
+
+    uint8_t *file_content =
+        (uint8_t *)mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+    if (MAP_FAILED == file_content) {
+        if (ENOMEM == errno) {
+            status = MMDB_OUT_OF_MEMORY_ERROR;
+        } else {
+            status = MMDB_IO_ERROR;
+        }
+        goto cleanup;
+    }
+
+    mmdb->file_size = size;
+    mmdb->file_content = file_content;
+
+ cleanup:;
+    int saved_errno = errno;
+    if (fd >= 0) {
+        close(fd);
+    }
+    errno = saved_errno;
+
+    return status;
+}
+
+#endif // _WIN32
+
+LOCAL const uint8_t *find_metadata(const uint8_t *file_content,
+                                   ssize_t file_size, uint32_t *metadata_size)
+{
+    const ssize_t marker_len = sizeof(METADATA_MARKER) - 1;
+    ssize_t max_size = file_size >
+                       METADATA_BLOCK_MAX_SIZE ? METADATA_BLOCK_MAX_SIZE :
+                       file_size;
+
+    uint8_t *search_area = (uint8_t *)(file_content + (file_size - max_size));
+    uint8_t *start = search_area;
+    uint8_t *tmp;
+    do {
+        tmp = mmdb_memmem(search_area, max_size,
+                          METADATA_MARKER, marker_len);
+
+        if (NULL != tmp) {
+            max_size -= tmp - search_area;
+            search_area = tmp;
+
+            /* Continue searching just after the marker we just read, in case
+             * there are multiple markers in the same file. This would be odd
+             * but is certainly not impossible. */
+            max_size -= marker_len;
+            search_area += marker_len;
+        }
+    } while (NULL != tmp);
+
+    if (search_area == start) {
+        return NULL;
+    }
+
+    *metadata_size = (uint32_t)max_size;
+
+    return search_area;
+}
+
+LOCAL int read_metadata(MMDB_s *mmdb)
+{
+    /* We need to create a fake MMDB_s struct in order to decode values from
+       the metadata. The metadata is basically just like the data section, so we
+       want to use the same functions we use for the data section to get metadata
+       values. */
+    MMDB_s metadata_db = make_fake_metadata_db(mmdb);
+
+    MMDB_entry_s metadata_start = {
+        .mmdb   = &metadata_db,
+        .offset = 0
+    };
+
+    int status =
+        value_for_key_as_uint32(&metadata_start, "node_count",
+                                &mmdb->metadata.node_count);
+    if (MMDB_SUCCESS != status) {
+        return status;
+    }
+    if (!mmdb->metadata.node_count) {
+        DEBUG_MSG("could not find node_count value in metadata");
+        return MMDB_INVALID_METADATA_ERROR;
+    }
+
+    status = value_for_key_as_uint16(&metadata_start, "record_size",
+                                     &mmdb->metadata.record_size);
+    if (MMDB_SUCCESS != status) {
+        return status;
+    }
+    if (!mmdb->metadata.record_size) {
+        DEBUG_MSG("could not find record_size value in metadata");
+        return MMDB_INVALID_METADATA_ERROR;
+    }
+
+    if (mmdb->metadata.record_size != 24 && mmdb->metadata.record_size != 28
+        && mmdb->metadata.record_size != 32) {
+        DEBUG_MSGF("bad record size in metadata: %i",
+                   mmdb->metadata.record_size);
+        return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR;
+    }
+
+    status = value_for_key_as_uint16(&metadata_start, "ip_version",
+                                     &mmdb->metadata.ip_version);
+    if (MMDB_SUCCESS != status) {
+        return status;
+    }
+    if (!mmdb->metadata.ip_version) {
+        DEBUG_MSG("could not find ip_version value in metadata");
+        return MMDB_INVALID_METADATA_ERROR;
+    }
+    if (!(mmdb->metadata.ip_version == 4 || mmdb->metadata.ip_version == 6)) {
+        DEBUG_MSGF("ip_version value in metadata is not 4 or 6 - it was %i",
+                   mmdb->metadata.ip_version);
+        return MMDB_INVALID_METADATA_ERROR;
+    }
+
+    status = value_for_key_as_string(&metadata_start, "database_type",
+                                     &mmdb->metadata.database_type);
+    if (MMDB_SUCCESS != status) {
+        DEBUG_MSG("error finding database_type value in metadata");
+        return status;
+    }
+
+    status =
+        populate_languages_metadata(mmdb, &metadata_db, &metadata_start);
+    if (MMDB_SUCCESS != status) {
+        DEBUG_MSG("could not populate languages from metadata");
+        return status;
+    }
+
+    status = value_for_key_as_uint16(
+        &metadata_start, "binary_format_major_version",
+        &mmdb->metadata.binary_format_major_version);
+    if (MMDB_SUCCESS != status) {
+        return status;
+    }
+    if (!mmdb->metadata.binary_format_major_version) {
+        DEBUG_MSG(
+            "could not find binary_format_major_version value in metadata");
+        return MMDB_INVALID_METADATA_ERROR;
+    }
+
+    status = value_for_key_as_uint16(
+        &metadata_start, "binary_format_minor_version",
+        &mmdb->metadata.binary_format_minor_version);
+    if (MMDB_SUCCESS != status) {
+        return status;
+    }
+
+    status = value_for_key_as_uint64(&metadata_start, "build_epoch",
+                                     &mmdb->metadata.build_epoch);
+    if (MMDB_SUCCESS != status) {
+        return status;
+    }
+    if (!mmdb->metadata.build_epoch) {
+        DEBUG_MSG("could not find build_epoch value in metadata");
+        return MMDB_INVALID_METADATA_ERROR;
+    }
+
+    status = populate_description_metadata(mmdb, &metadata_db, &metadata_start);
+    if (MMDB_SUCCESS != status) {
+        DEBUG_MSG("could not populate description from metadata");
+        return status;
+    }
+
+    mmdb->full_record_byte_size = mmdb->metadata.record_size * 2 / 8U;
+
+    mmdb->depth = mmdb->metadata.ip_version == 4 ? 32 : 128;
+
+    return MMDB_SUCCESS;
+}
+
+LOCAL MMDB_s make_fake_metadata_db(const MMDB_s *const mmdb)
+{
+    MMDB_s fake_metadata_db = {
+        .data_section      = mmdb->metadata_section,
+        .data_section_size = mmdb->metadata_section_size
+    };
+
+    return fake_metadata_db;
+}
+
+LOCAL int value_for_key_as_uint16(MMDB_entry_s *start, char *key,
+                                  uint16_t *value)
+{
+    MMDB_entry_data_s entry_data;
+    const char *path[] = { key, NULL };
+    int status = MMDB_aget_value(start, &entry_data, path);
+    if (MMDB_SUCCESS != status) {
+        return status;
+    }
+    if (MMDB_DATA_TYPE_UINT16 != entry_data.type) {
+        DEBUG_MSGF("expect uint16 for %s but received %s", key,
+                   type_num_to_name(
+                       entry_data.type));
+        return MMDB_INVALID_METADATA_ERROR;
+    }
+    *value = entry_data.uint16;
+    return MMDB_SUCCESS;
+}
+
+LOCAL int value_for_key_as_uint32(MMDB_entry_s *start, char *key,
+                                  uint32_t *value)
+{
+    MMDB_entry_data_s entry_data;
+    const char *path[] = { key, NULL };
+    int status = MMDB_aget_value(start, &entry_data, path);
+    if (MMDB_SUCCESS != status) {
+        return status;
+    }
+    if (MMDB_DATA_TYPE_UINT32 != entry_data.type) {
+        DEBUG_MSGF("expect uint32 for %s but received %s", key,
+                   type_num_to_name(
+                       entry_data.type));
+        return MMDB_INVALID_METADATA_ERROR;
+    }
+    *value = entry_data.uint32;
+    return MMDB_SUCCESS;
+}
+
+LOCAL int value_for_key_as_uint64(MMDB_entry_s *start, char *key,
+                                  uint64_t *value)
+{
+    MMDB_entry_data_s entry_data;
+    const char *path[] = { key, NULL };
+    int status = MMDB_aget_value(start, &entry_data, path);
+    if (MMDB_SUCCESS != status) {
+        return status;
+    }
+    if (MMDB_DATA_TYPE_UINT64 != entry_data.type) {
+        DEBUG_MSGF("expect uint64 for %s but received %s", key,
+                   type_num_to_name(
+                       entry_data.type));
+        return MMDB_INVALID_METADATA_ERROR;
+    }
+    *value = entry_data.uint64;
+    return MMDB_SUCCESS;
+}
+
+LOCAL int value_for_key_as_string(MMDB_entry_s *start, char *key,
+                                  char const **value)
+{
+    MMDB_entry_data_s entry_data;
+    const char *path[] = { key, NULL };
+    int status = MMDB_aget_value(start, &entry_data, path);
+    if (MMDB_SUCCESS != status) {
+        return status;
+    }
+    if (MMDB_DATA_TYPE_UTF8_STRING != entry_data.type) {
+        DEBUG_MSGF("expect string for %s but received %s", key,
+                   type_num_to_name(
+                       entry_data.type));
+        return MMDB_INVALID_METADATA_ERROR;
+    }
+    *value = mmdb_strndup((char *)entry_data.utf8_string, entry_data.data_size);
+    if (NULL == *value) {
+        return MMDB_OUT_OF_MEMORY_ERROR;
+    }
+    return MMDB_SUCCESS;
+}
+
+LOCAL int populate_languages_metadata(MMDB_s *mmdb, MMDB_s *metadata_db,
+                                      MMDB_entry_s *metadata_start)
+{
+    MMDB_entry_data_s entry_data;
+
+    const char *path[] = { "languages", NULL };
+    int status = MMDB_aget_value(metadata_start, &entry_data, path);
+    if (MMDB_SUCCESS != status) {
+        return status;
+    }
+    if (MMDB_DATA_TYPE_ARRAY != entry_data.type) {
+        return MMDB_INVALID_METADATA_ERROR;
+    }
+
+    MMDB_entry_s array_start = {
+        .mmdb   = metadata_db,
+        .offset = entry_data.offset
+    };
+
+    MMDB_entry_data_list_s *member;
+    status = MMDB_get_entry_data_list(&array_start, &member);
+    if (MMDB_SUCCESS != status) {
+        return status;
+    }
+
+    MMDB_entry_data_list_s *first_member = member;
+
+    uint32_t array_size = member->entry_data.data_size;
+    MAYBE_CHECK_SIZE_OVERFLOW(array_size, SIZE_MAX / sizeof(char *),
+                              MMDB_INVALID_METADATA_ERROR);
+
+    mmdb->metadata.languages.count = 0;
+    mmdb->metadata.languages.names = malloc(array_size * sizeof(char *));
+    if (NULL == mmdb->metadata.languages.names) {
+        return MMDB_OUT_OF_MEMORY_ERROR;
+    }
+
+    for (uint32_t i = 0; i < array_size; i++) {
+        member = member->next;
+        if (MMDB_DATA_TYPE_UTF8_STRING != member->entry_data.type) {
+            return MMDB_INVALID_METADATA_ERROR;
+        }
+
+        mmdb->metadata.languages.names[i] =
+            mmdb_strndup((char *)member->entry_data.utf8_string,
+                         member->entry_data.data_size);
+
+        if (NULL == mmdb->metadata.languages.names[i]) {
+            return MMDB_OUT_OF_MEMORY_ERROR;
+        }
+        // We assign this as we go so that if we fail a malloc and need to
+        // free it, the count is right.
+        mmdb->metadata.languages.count = i + 1;
+    }
+
+    MMDB_free_entry_data_list(first_member);
+
+    return MMDB_SUCCESS;
+}
+
+LOCAL int populate_description_metadata(MMDB_s *mmdb, MMDB_s *metadata_db,
+                                        MMDB_entry_s *metadata_start)
+{
+    MMDB_entry_data_s entry_data;
+
+    const char *path[] = { "description", NULL };
+    int status = MMDB_aget_value(metadata_start, &entry_data, path);
+    if (MMDB_SUCCESS != status) {
+        return status;
+    }
+
+    if (MMDB_DATA_TYPE_MAP != entry_data.type) {
+        DEBUG_MSGF("Unexpected entry_data type: %d", entry_data.type);
+        return MMDB_INVALID_METADATA_ERROR;
+    }
+
+    MMDB_entry_s map_start = {
+        .mmdb   = metadata_db,
+        .offset = entry_data.offset
+    };
+
+    MMDB_entry_data_list_s *member;
+    status = MMDB_get_entry_data_list(&map_start, &member);
+    if (MMDB_SUCCESS != status) {
+        DEBUG_MSGF(
+            "MMDB_get_entry_data_list failed while populating description."
+            " status = %d (%s)", status, MMDB_strerror(status));
+        return status;
+    }
+
+    MMDB_entry_data_list_s *first_member = member;
+
+    uint32_t map_size = member->entry_data.data_size;
+    mmdb->metadata.description.count = 0;
+    if (0 == map_size) {
+        mmdb->metadata.description.descriptions = NULL;
+        goto cleanup;
+    }
+    MAYBE_CHECK_SIZE_OVERFLOW(map_size, SIZE_MAX / sizeof(MMDB_description_s *),
+                              MMDB_INVALID_METADATA_ERROR);
+
+    mmdb->metadata.description.descriptions =
+        malloc(map_size * sizeof(MMDB_description_s *));
+    if (NULL == mmdb->metadata.description.descriptions) {
+        status = MMDB_OUT_OF_MEMORY_ERROR;
+        goto cleanup;
+    }
+
+    for (uint32_t i = 0; i < map_size; i++) {
+        mmdb->metadata.description.descriptions[i] =
+            malloc(sizeof(MMDB_description_s));
+        if (NULL == mmdb->metadata.description.descriptions[i]) {
+            status = MMDB_OUT_OF_MEMORY_ERROR;
+            goto cleanup;
+        }
+
+        mmdb->metadata.description.count = i + 1;
+        mmdb->metadata.description.descriptions[i]->language = NULL;
+        mmdb->metadata.description.descriptions[i]->description = NULL;
+
+        member = member->next;
+
+        if (MMDB_DATA_TYPE_UTF8_STRING != member->entry_data.type) {
+            status = MMDB_INVALID_METADATA_ERROR;
+            goto cleanup;
+        }
+
+        mmdb->metadata.description.descriptions[i]->language =
+            mmdb_strndup((char *)member->entry_data.utf8_string,
+                         member->entry_data.data_size);
+
+        if (NULL == mmdb->metadata.description.descriptions[i]->language) {
+            status = MMDB_OUT_OF_MEMORY_ERROR;
+            goto cleanup;
+        }
+
+        member = member->next;
+
+        if (MMDB_DATA_TYPE_UTF8_STRING != member->entry_data.type) {
+            status = MMDB_INVALID_METADATA_ERROR;
+            goto cleanup;
+        }
+
+        mmdb->metadata.description.descriptions[i]->description =
+            mmdb_strndup((char *)member->entry_data.utf8_string,
+                         member->entry_data.data_size);
+
+        if (NULL == mmdb->metadata.description.descriptions[i]->description) {
+            status = MMDB_OUT_OF_MEMORY_ERROR;
+            goto cleanup;
+        }
+    }
+
+ cleanup:
+    MMDB_free_entry_data_list(first_member);
+
+    return status;
+}
+
+MMDB_lookup_result_s MMDB_lookup_string(const MMDB_s *const mmdb,
+                                        const char *const ipstr,
+                                        int *const gai_error,
+                                        int *const mmdb_error)
+{
+    MMDB_lookup_result_s result = {
+        .found_entry = false,
+        .netmask     = 0,
+        .entry       = {
+            .mmdb    = mmdb,
+            .offset  = 0
+        }
+    };
+
+    struct addrinfo *addresses = NULL;
+    *gai_error = resolve_any_address(ipstr, &addresses);
+
+    if (!*gai_error) {
+        result = MMDB_lookup_sockaddr(mmdb, addresses->ai_addr, mmdb_error);
+    }
+
+    if (NULL != addresses) {
+        freeaddrinfo(addresses);
+    }
+
+    return result;
+}
+
+LOCAL int resolve_any_address(const char *ipstr, struct addrinfo **addresses)
+{
+    struct addrinfo hints = {
+        .ai_family   = AF_UNSPEC,
+        .ai_flags    = AI_NUMERICHOST,
+        // We set ai_socktype so that we only get one result back
+        .ai_socktype = SOCK_STREAM
+    };
+
+    int gai_status = getaddrinfo(ipstr, NULL, &hints, addresses);
+    if (gai_status) {
+        return gai_status;
+    }
+
+    return 0;
+}
+
+MMDB_lookup_result_s MMDB_lookup_sockaddr(
+    const MMDB_s *const mmdb,
+    const struct sockaddr *const sockaddr,
+    int *const mmdb_error)
+{
+    MMDB_lookup_result_s result = {
+        .found_entry = false,
+        .netmask     = 0,
+        .entry       = {
+            .mmdb    = mmdb,
+            .offset  = 0
+        }
+    };
+
+    uint8_t mapped_address[16], *address;
+    if (mmdb->metadata.ip_version == 4) {
+        if (sockaddr->sa_family == AF_INET6) {
+            *mmdb_error = MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR;
+            return result;
+        }
+        address = (uint8_t *)&((struct sockaddr_in *)sockaddr)->sin_addr.s_addr;
+    } else {
+        if (sockaddr->sa_family == AF_INET6) {
+            address =
+                (uint8_t *)&((struct sockaddr_in6 *)sockaddr)->sin6_addr.
+                s6_addr;
+        } else {
+            address = mapped_address;
+            memset(address, 0, 12);
+            memcpy(address + 12,
+                   &((struct sockaddr_in *)sockaddr)->sin_addr.s_addr, 4);
+        }
+    }
+
+    *mmdb_error =
+        find_address_in_search_tree(mmdb, address, sockaddr->sa_family,
+                                    &result);
+
+    return result;
+}
+
+LOCAL int find_address_in_search_tree(const MMDB_s *const mmdb,
+                                      uint8_t *address,
+                                      sa_family_t address_family,
+                                      MMDB_lookup_result_s *result)
+{
+    record_info_s record_info = record_info_for_database(mmdb);
+    if (0 == record_info.right_record_offset) {
+        return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR;
+    }
+
+    uint32_t value = 0;
+    uint16_t current_bit = 0;
+    if (mmdb->metadata.ip_version == 6 && address_family == AF_INET) {
+        value = mmdb->ipv4_start_node.node_value;
+        current_bit = mmdb->ipv4_start_node.netmask;
+    }
+
+    uint32_t node_count = mmdb->metadata.node_count;
+    const uint8_t *search_tree = mmdb->file_content;
+    const uint8_t *record_pointer;
+    for (; current_bit < mmdb->depth && value < node_count; current_bit++) {
+        uint8_t bit = 1U &
+                      (address[current_bit >> 3] >> (7 - (current_bit % 8)));
+
+        record_pointer = &search_tree[value * record_info.record_length];
+        if (record_pointer + record_info.record_length > mmdb->data_section) {
+            return MMDB_CORRUPT_SEARCH_TREE_ERROR;
+        }
+        if (bit) {
+            record_pointer += record_info.right_record_offset;
+            value = record_info.right_record_getter(record_pointer);
+        } else {
+            value = record_info.left_record_getter(record_pointer);
+        }
+    }
+
+    result->netmask = current_bit;
+
+    if (value >= node_count + mmdb->data_section_size) {
+        // The pointer points off the end of the database.
+        return MMDB_CORRUPT_SEARCH_TREE_ERROR;
+    }
+
+    if (value == node_count) {
+        // record is empty
+        result->found_entry = false;
+        return MMDB_SUCCESS;
+    }
+    result->found_entry = true;
+    result->entry.offset = data_section_offset_for_record(mmdb, value);
+
+    return MMDB_SUCCESS;
+}
+
+LOCAL record_info_s record_info_for_database(const MMDB_s *const mmdb)
+{
+    record_info_s record_info = {
+        .record_length       = mmdb->full_record_byte_size,
+        .right_record_offset = 0
+    };
+
+    if (record_info.record_length == 6) {
+        record_info.left_record_getter = &get_uint24;
+        record_info.right_record_getter = &get_uint24;
+        record_info.right_record_offset = 3;
+    } else if (record_info.record_length == 7) {
+        record_info.left_record_getter = &get_left_28_bit_record;
+        record_info.right_record_getter = &get_right_28_bit_record;
+        record_info.right_record_offset = 3;
+    } else if (record_info.record_length == 8) {
+        record_info.left_record_getter = &get_uint32;
+        record_info.right_record_getter = &get_uint32;
+        record_info.right_record_offset = 4;
+    } else {
+        assert(false);
+    }
+
+    return record_info;
+}
+
+LOCAL int find_ipv4_start_node(MMDB_s *const mmdb)
+{
+    /* In a pathological case of a database with a single node search tree,
+     * this check will be true even after we've found the IPv4 start node, but
+     * that doesn't seem worth trying to fix. */
+    if (mmdb->ipv4_start_node.node_value != 0) {
+        return MMDB_SUCCESS;
+    }
+
+    record_info_s record_info = record_info_for_database(mmdb);
+
+    const uint8_t *search_tree = mmdb->file_content;
+    uint32_t node_value = 0;
+    const uint8_t *record_pointer;
+    uint16_t netmask;
+    uint32_t node_count = mmdb->metadata.node_count;
+
+    for (netmask = 0; netmask < 96 && node_value < node_count; netmask++) {
+        record_pointer = &search_tree[node_value * record_info.record_length];
+        if (record_pointer + record_info.record_length > mmdb->data_section) {
+            return MMDB_CORRUPT_SEARCH_TREE_ERROR;
+        }
+        node_value = record_info.left_record_getter(record_pointer);
+    }
+
+    mmdb->ipv4_start_node.node_value = node_value;
+    mmdb->ipv4_start_node.netmask = netmask;
+
+    return MMDB_SUCCESS;
+}
+
+LOCAL uint8_t record_type(const MMDB_s *const mmdb, uint64_t record)
+{
+    uint32_t node_count = mmdb->metadata.node_count;
+
+    /* Ideally we'd check to make sure that a record never points to a
+     * previously seen value, but that's more complicated. For now, we can
+     * at least check that we don't end up at the top of the tree again. */
+    if (record == 0) {
+        DEBUG_MSG("record has a value of 0");
+        return MMDB_RECORD_TYPE_INVALID;
+    }
+
+    if (record < node_count) {
+        return MMDB_RECORD_TYPE_SEARCH_NODE;
+    }
+
+    if (record == node_count) {
+        return MMDB_RECORD_TYPE_EMPTY;
+    }
+
+    if (record - node_count < mmdb->data_section_size) {
+        return MMDB_RECORD_TYPE_DATA;
+    }
+
+    DEBUG_MSG("record has a value that points outside of the database");
+    return MMDB_RECORD_TYPE_INVALID;
+}
+
+LOCAL uint32_t get_left_28_bit_record(const uint8_t *record)
+{
+    return record[0] * 65536 + record[1] * 256 + record[2] +
+           ((record[3] & 0xf0) << 20);
+}
+
+LOCAL uint32_t get_right_28_bit_record(const uint8_t *record)
+{
+    uint32_t value = get_uint32(record);
+    return value & 0xfffffff;
+}
+
+int MMDB_read_node(const MMDB_s *const mmdb, uint32_t node_number,
+                   MMDB_search_node_s *const node)
+{
+    record_info_s record_info = record_info_for_database(mmdb);
+    if (0 == record_info.right_record_offset) {
+        return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR;
+    }
+
+    if (node_number > mmdb->metadata.node_count) {
+        return MMDB_INVALID_NODE_NUMBER_ERROR;
+    }
+
+    const uint8_t *search_tree = mmdb->file_content;
+    const uint8_t *record_pointer =
+        &search_tree[node_number * record_info.record_length];
+    node->left_record = record_info.left_record_getter(record_pointer);
+    record_pointer += record_info.right_record_offset;
+    node->right_record = record_info.right_record_getter(record_pointer);
+
+    node->left_record_type = record_type(mmdb, node->left_record);
+    node->right_record_type = record_type(mmdb, node->right_record);
+
+    // Note that offset will be invalid if the record type is not
+    // MMDB_RECORD_TYPE_DATA, but that's ok. Any use of the record entry
+    // for other data types is a programming error.
+    node->left_record_entry = (struct MMDB_entry_s) {
+        .mmdb = mmdb,
+        .offset = data_section_offset_for_record(mmdb, node->left_record),
+    };
+    node->right_record_entry = (struct MMDB_entry_s) {
+        .mmdb = mmdb,
+        .offset = data_section_offset_for_record(mmdb, node->right_record),
+    };
+
+    return MMDB_SUCCESS;
+}
+
+LOCAL uint32_t data_section_offset_for_record(const MMDB_s *const mmdb,
+                                              uint64_t record)
+{
+    return (uint32_t)record - mmdb->metadata.node_count -
+           MMDB_DATA_SECTION_SEPARATOR;
+}
+
+int MMDB_get_value(MMDB_entry_s *const start,
+                   MMDB_entry_data_s *const entry_data,
+                   ...)
+{
+    va_list path;
+    va_start(path, entry_data);
+    int status = MMDB_vget_value(start, entry_data, path);
+    va_end(path);
+    return status;
+}
+
+int MMDB_vget_value(MMDB_entry_s *const start,
+                    MMDB_entry_data_s *const entry_data,
+                    va_list va_path)
+{
+    int length = path_length(va_path);
+    const char *path_elem;
+    int i = 0;
+
+    MAYBE_CHECK_SIZE_OVERFLOW(length, SIZE_MAX / sizeof(const char *) - 1,
+                              MMDB_INVALID_METADATA_ERROR);
+
+    const char **path = malloc((length + 1) * sizeof(const char *));
+    if (NULL == path) {
+        return MMDB_OUT_OF_MEMORY_ERROR;
+    }
+
+    while (NULL != (path_elem = va_arg(va_path, char *))) {
+        path[i] = path_elem;
+        i++;
+    }
+    path[i] = NULL;
+
+    int status = MMDB_aget_value(start, entry_data, path);
+
+    free((char **)path);
+
+    return status;
+}
+
+LOCAL int path_length(va_list va_path)
+{
+    int i = 0;
+    const char *ignore;
+    va_list path_copy;
+    va_copy(path_copy, va_path);
+
+    while (NULL != (ignore = va_arg(path_copy, char *))) {
+        i++;
+    }
+
+    va_end(path_copy);
+
+    return i;
+}
+
+int MMDB_aget_value(MMDB_entry_s *const start,
+                    MMDB_entry_data_s *const entry_data,
+                    const char *const *const path)
+{
+    const MMDB_s *const mmdb = start->mmdb;
+    uint32_t offset = start->offset;
+
+    memset(entry_data, 0, sizeof(MMDB_entry_data_s));
+    DEBUG_NL;
+    DEBUG_MSG("looking up value by path");
+
+    CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, entry_data);
+
+    DEBUG_NL;
+    DEBUG_MSGF("top level element is a %s", type_num_to_name(entry_data->type));
+
+    /* Can this happen? It'd probably represent a pathological case under
+     * normal use, but there's nothing preventing someone from passing an
+     * invalid MMDB_entry_s struct to this function */
+    if (!entry_data->has_data) {
+        return MMDB_INVALID_LOOKUP_PATH_ERROR;
+    }
+
+    const char *path_elem;
+    int i = 0;
+    while (NULL != (path_elem = path[i++])) {
+        DEBUG_NL;
+        DEBUG_MSGF("path elem = %s", path_elem);
+
+        /* XXX - it'd be good to find a quicker way to skip through these
+           entries that doesn't involve decoding them
+           completely. Basically we need to just use the size from the
+           control byte to advance our pointer rather than calling
+           decode_one(). */
+        if (entry_data->type == MMDB_DATA_TYPE_ARRAY) {
+            int status = lookup_path_in_array(path_elem, mmdb, entry_data);
+            if (MMDB_SUCCESS != status) {
+                memset(entry_data, 0, sizeof(MMDB_entry_data_s));
+                return status;
+            }
+        } else if (entry_data->type == MMDB_DATA_TYPE_MAP) {
+            int status = lookup_path_in_map(path_elem, mmdb, entry_data);
+            if (MMDB_SUCCESS != status) {
+                memset(entry_data, 0, sizeof(MMDB_entry_data_s));
+                return status;
+            }
+        } else {
+            /* Once we make the code traverse maps & arrays without calling
+             * decode_one() we can get rid of this. */
+            memset(entry_data, 0, sizeof(MMDB_entry_data_s));
+            return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR;
+        }
+    }
+
+    return MMDB_SUCCESS;
+}
+
+LOCAL int lookup_path_in_array(const char *path_elem,
+                               const MMDB_s *const mmdb,
+                               MMDB_entry_data_s *entry_data)
+{
+    uint32_t size = entry_data->data_size;
+    char *first_invalid;
+
+    int saved_errno = errno;
+    errno = 0;
+    int array_index = strtol(path_elem, &first_invalid, 10);
+    if (ERANGE == errno) {
+        errno = saved_errno;
+        return MMDB_INVALID_LOOKUP_PATH_ERROR;
+    }
+    errno = saved_errno;
+
+    if (array_index < 0) {
+        array_index += size;
+
+        if (array_index < 0) {
+            return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR;
+        }
+    }
+
+    if (*first_invalid || (uint32_t)array_index >= size) {
+        return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR;
+    }
+
+    for (int i = 0; i < array_index; i++) {
+        /* We don't want to follow a pointer here. If the next element is a
+         * pointer we simply skip it and keep going */
+        CHECKED_DECODE_ONE(mmdb, entry_data->offset_to_next, entry_data);
+        int status = skip_map_or_array(mmdb, entry_data);
+        if (MMDB_SUCCESS != status) {
+            return status;
+        }
+    }
+
+    MMDB_entry_data_s value;
+    CHECKED_DECODE_ONE_FOLLOW(mmdb, entry_data->offset_to_next, &value);
+    memcpy(entry_data, &value, sizeof(MMDB_entry_data_s));
+
+    return MMDB_SUCCESS;
+}
+
+LOCAL int lookup_path_in_map(const char *path_elem,
+                             const MMDB_s *const mmdb,
+                             MMDB_entry_data_s *entry_data)
+{
+    uint32_t size = entry_data->data_size;
+    uint32_t offset = entry_data->offset_to_next;
+    size_t path_elem_len = strlen(path_elem);
+
+    while (size-- > 0) {
+        MMDB_entry_data_s key, value;
+        CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, &key);
+
+        uint32_t offset_to_value = key.offset_to_next;
+
+        if (MMDB_DATA_TYPE_UTF8_STRING != key.type) {
+            return MMDB_INVALID_DATA_ERROR;
+        }
+
+        if (key.data_size == path_elem_len &&
+            !memcmp(path_elem, key.utf8_string, path_elem_len)) {
+
+            DEBUG_MSG("found key matching path elem");
+
+            CHECKED_DECODE_ONE_FOLLOW(mmdb, offset_to_value, &value);
+            memcpy(entry_data, &value, sizeof(MMDB_entry_data_s));
+            return MMDB_SUCCESS;
+        } else {
+            /* We don't want to follow a pointer here. If the next element is
+             * a pointer we simply skip it and keep going */
+            CHECKED_DECODE_ONE(mmdb, offset_to_value, &value);
+            int status = skip_map_or_array(mmdb, &value);
+            if (MMDB_SUCCESS != status) {
+                return status;
+            }
+            offset = value.offset_to_next;
+        }
+    }
+
+    memset(entry_data, 0, sizeof(MMDB_entry_data_s));
+    return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR;
+}
+
+LOCAL int skip_map_or_array(const MMDB_s *const mmdb,
+                            MMDB_entry_data_s *entry_data)
+{
+    if (entry_data->type == MMDB_DATA_TYPE_MAP) {
+        uint32_t size = entry_data->data_size;
+        while (size-- > 0) {
+            CHECKED_DECODE_ONE(mmdb, entry_data->offset_to_next, entry_data);   // key
+            CHECKED_DECODE_ONE(mmdb, entry_data->offset_to_next, entry_data);   // value
+            int status = skip_map_or_array(mmdb, entry_data);
+            if (MMDB_SUCCESS != status) {
+                return status;
+            }
+        }
+    } else if (entry_data->type == MMDB_DATA_TYPE_ARRAY) {
+        uint32_t size = entry_data->data_size;
+        while (size-- > 0) {
+            CHECKED_DECODE_ONE(mmdb, entry_data->offset_to_next, entry_data);   // value
+            int status = skip_map_or_array(mmdb, entry_data);
+            if (MMDB_SUCCESS != status) {
+                return status;
+            }
+        }
+    }
+
+    return MMDB_SUCCESS;
+}
+
+LOCAL int decode_one_follow(const MMDB_s *const mmdb, uint32_t offset,
+                            MMDB_entry_data_s *entry_data)
+{
+    CHECKED_DECODE_ONE(mmdb, offset, entry_data);
+    if (entry_data->type == MMDB_DATA_TYPE_POINTER) {
+        uint32_t next = entry_data->offset_to_next;
+        CHECKED_DECODE_ONE(mmdb, entry_data->pointer, entry_data);
+        /* Pointers to pointers are illegal under the spec */
+        if (entry_data->type == MMDB_DATA_TYPE_POINTER) {
+            DEBUG_MSG("pointer points to another pointer");
+            return MMDB_INVALID_DATA_ERROR;
+        }
+
+        /* The pointer could point to any part of the data section but the
+         * next entry for this particular offset may be the one after the
+         * pointer, not the one after whatever the pointer points to. This
+         * depends on whether the pointer points to something that is a simple
+         * value or a compound value. For a compound value, the next one is
+         * the one after the pointer result, not the one after the pointer. */
+        if (entry_data->type != MMDB_DATA_TYPE_MAP
+            && entry_data->type != MMDB_DATA_TYPE_ARRAY) {
+
+            entry_data->offset_to_next = next;
+        }
+    }
+
+    return MMDB_SUCCESS;
+}
+
+#if !MMDB_UINT128_IS_BYTE_ARRAY
+LOCAL mmdb_uint128_t get_uint128(const uint8_t *p, int length)
+{
+    mmdb_uint128_t value = 0;
+    while (length-- > 0) {
+        value <<= 8;
+        value += *p++;
+    }
+    return value;
+}
+#endif
+
+LOCAL int decode_one(const MMDB_s *const mmdb, uint32_t offset,
+                     MMDB_entry_data_s *entry_data)
+{
+    const uint8_t *mem = mmdb->data_section;
+
+    // We subtract rather than add as it possible that offset + 1
+    // could overflow for a corrupt database while an underflow
+    // from data_section_size - 1 should not be possible.
+    if (offset > mmdb->data_section_size - 1) {
+        DEBUG_MSGF("Offset (%d) past data section (%d)", offset,
+                   mmdb->data_section_size);
+        return MMDB_INVALID_DATA_ERROR;
+    }
+
+    entry_data->offset = offset;
+    entry_data->has_data = true;
+
+    DEBUG_NL;
+    DEBUG_MSGF("Offset: %i", offset);
+
+    uint8_t ctrl = mem[offset++];
+    DEBUG_BINARY("Control byte: %s", ctrl);
+
+    int type = (ctrl >> 5) & 7;
+    DEBUG_MSGF("Type: %i (%s)", type, type_num_to_name(type));
+
+    if (type == MMDB_DATA_TYPE_EXTENDED) {
+        // Subtracting 1 to avoid possible overflow on offset + 1
+        if (offset > mmdb->data_section_size - 1) {
+            DEBUG_MSGF("Extended type offset (%d) past data section (%d)",
+                       offset,
+                       mmdb->data_section_size);
+            return MMDB_INVALID_DATA_ERROR;
+        }
+        type = get_ext_type(mem[offset++]);
+        DEBUG_MSGF("Extended type: %i (%s)", type, type_num_to_name(type));
+    }
+
+    entry_data->type = type;
+
+    if (type == MMDB_DATA_TYPE_POINTER) {
+        uint8_t psize = ((ctrl >> 3) & 3) + 1;
+        DEBUG_MSGF("Pointer size: %i", psize);
+
+        // We check that the offset does not extend past the end of the
+        // database and that the subtraction of psize did not underflow.
+        if (offset > mmdb->data_section_size - psize ||
+            mmdb->data_section_size < psize) {
+            DEBUG_MSGF("Pointer offset (%d) past data section (%d)", offset +
+                       psize,
+                       mmdb->data_section_size);
+            return MMDB_INVALID_DATA_ERROR;
+        }
+        entry_data->pointer = get_ptr_from(ctrl, &mem[offset], psize);
+        DEBUG_MSGF("Pointer to: %i", entry_data->pointer);
+
+        entry_data->data_size = psize;
+        entry_data->offset_to_next = offset + psize;
+        return MMDB_SUCCESS;
+    }
+
+    uint32_t size = ctrl & 31;
+    switch (size) {
+    case 29:
+        // We subtract when checking offset to avoid possible overflow
+        if (offset > mmdb->data_section_size - 1) {
+            DEBUG_MSGF("String end (%d, case 29) past data section (%d)",
+                       offset,
+                       mmdb->data_section_size);
+            return MMDB_INVALID_DATA_ERROR;
+        }
+        size = 29 + mem[offset++];
+        break;
+    case 30:
+        // We subtract when checking offset to avoid possible overflow
+        if (offset > mmdb->data_section_size - 2) {
+            DEBUG_MSGF("String end (%d, case 30) past data section (%d)",
+                       offset,
+                       mmdb->data_section_size);
+            return MMDB_INVALID_DATA_ERROR;
+        }
+        size = 285 + get_uint16(&mem[offset]);
+        offset += 2;
+        break;
+    case 31:
+        // We subtract when checking offset to avoid possible overflow
+        if (offset > mmdb->data_section_size - 3) {
+            DEBUG_MSGF("String end (%d, case 31) past data section (%d)",
+                       offset,
+                       mmdb->data_section_size);
+            return MMDB_INVALID_DATA_ERROR;
+        }
+        size = 65821 + get_uint24(&mem[offset]);
+        offset += 3;
+    default:
+        break;
+    }
+
+    DEBUG_MSGF("Size: %i", size);
+
+    if (type == MMDB_DATA_TYPE_MAP || type == MMDB_DATA_TYPE_ARRAY) {
+        entry_data->data_size = size;
+        entry_data->offset_to_next = offset;
+        return MMDB_SUCCESS;
+    }
+
+    if (type == MMDB_DATA_TYPE_BOOLEAN) {
+        entry_data->boolean = size ? true : false;
+        entry_data->data_size = 0;
+        entry_data->offset_to_next = offset;
+        DEBUG_MSGF("boolean value: %s", entry_data->boolean ? "true" : "false");
+        return MMDB_SUCCESS;
+    }
+
+    // Check that the data doesn't extend past the end of the memory
+    // buffer and that the calculation in doing this did not underflow.
+    if (offset > mmdb->data_section_size - size ||
+        mmdb->data_section_size < size) {
+        DEBUG_MSGF("Data end (%d) past data section (%d)", offset + size,
+                   mmdb->data_section_size);
+        return MMDB_INVALID_DATA_ERROR;
+    }
+
+    if (type == MMDB_DATA_TYPE_UINT16) {
+        if (size > 2) {
+            DEBUG_MSGF("uint16 of size %d", size);
+            return MMDB_INVALID_DATA_ERROR;
+        }
+        entry_data->uint16 = (uint16_t)get_uintX(&mem[offset], size);
+        DEBUG_MSGF("uint16 value: %u", entry_data->uint16);
+    } else if (type == MMDB_DATA_TYPE_UINT32) {
+        if (size > 4) {
+            DEBUG_MSGF("uint32 of size %d", size);
+            return MMDB_INVALID_DATA_ERROR;
+        }
+        entry_data->uint32 = (uint32_t)get_uintX(&mem[offset], size);
+        DEBUG_MSGF("uint32 value: %u", entry_data->uint32);
+    } else if (type == MMDB_DATA_TYPE_INT32) {
+        if (size > 4) {
+            DEBUG_MSGF("int32 of size %d", size);
+            return MMDB_INVALID_DATA_ERROR;
+        }
+        entry_data->int32 = get_sintX(&mem[offset], size);
+        DEBUG_MSGF("int32 value: %i", entry_data->int32);
+    } else if (type == MMDB_DATA_TYPE_UINT64) {
+        if (size > 8) {
+            DEBUG_MSGF("uint64 of size %d", size);
+            return MMDB_INVALID_DATA_ERROR;
+        }
+        entry_data->uint64 = get_uintX(&mem[offset], size);
+        DEBUG_MSGF("uint64 value: %" PRIu64, entry_data->uint64);
+    } else if (type == MMDB_DATA_TYPE_UINT128) {
+        if (size > 16) {
+            DEBUG_MSGF("uint128 of size %d", size);
+            return MMDB_INVALID_DATA_ERROR;
+        }
+#if MMDB_UINT128_IS_BYTE_ARRAY
+        memset(entry_data->uint128, 0, 16);
+        if (size > 0) {
+            memcpy(entry_data->uint128 + 16 - size, &mem[offset], size);
+        }
+#else
+        entry_data->uint128 = get_uint128(&mem[offset], size);
+#endif
+    } else if (type == MMDB_DATA_TYPE_FLOAT) {
+        if (size != 4) {
+            DEBUG_MSGF("float of size %d", size);
+            return MMDB_INVALID_DATA_ERROR;
+        }
+        size = 4;
+        entry_data->float_value = get_ieee754_float(&mem[offset]);
+        DEBUG_MSGF("float value: %f", entry_data->float_value);
+    } else if (type == MMDB_DATA_TYPE_DOUBLE) {
+        if (size != 8) {
+            DEBUG_MSGF("double of size %d", size);
+            return MMDB_INVALID_DATA_ERROR;
+        }
+        size = 8;
+        entry_data->double_value = get_ieee754_double(&mem[offset]);
+        DEBUG_MSGF("double value: %f", entry_data->double_value);
+    } else if (type == MMDB_DATA_TYPE_UTF8_STRING) {
+        entry_data->utf8_string = size == 0 ? "" : (char *)&mem[offset];
+        entry_data->data_size = size;
+#ifdef MMDB_DEBUG
+        char *string = mmdb_strndup(entry_data->utf8_string,
+                                    size > 50 ? 50 : size);
+        if (NULL == string) {
+            abort();
+        }
+        DEBUG_MSGF("string value: %s", string);
+        free(string);
+#endif
+    } else if (type == MMDB_DATA_TYPE_BYTES) {
+        entry_data->bytes = &mem[offset];
+        entry_data->data_size = size;
+    }
+
+    entry_data->offset_to_next = offset + size;
+
+    return MMDB_SUCCESS;
+}
+
+LOCAL int get_ext_type(int raw_ext_type)
+{
+    return 7 + raw_ext_type;
+}
+
+LOCAL uint32_t get_ptr_from(uint8_t ctrl, uint8_t const *const ptr,
+                            int ptr_size)
+{
+    uint32_t new_offset;
+    switch (ptr_size) {
+    case 1:
+        new_offset = ( (ctrl & 7) << 8) + ptr[0];
+        break;
+    case 2:
+        new_offset = 2048 + ( (ctrl & 7) << 16 ) + ( ptr[0] << 8) + ptr[1];
+        break;
+    case 3:
+        new_offset = 2048 + 524288 + ( (ctrl & 7) << 24 ) + get_uint24(ptr);
+        break;
+    case 4:
+    default:
+        new_offset = get_uint32(ptr);
+        break;
+    }
+    return new_offset;
+}
+
+int MMDB_get_metadata_as_entry_data_list(
+    const MMDB_s *const mmdb, MMDB_entry_data_list_s **const entry_data_list)
+{
+    MMDB_s metadata_db = make_fake_metadata_db(mmdb);
+
+    MMDB_entry_s metadata_start = {
+        .mmdb   = &metadata_db,
+        .offset = 0
+    };
+
+    return MMDB_get_entry_data_list(&metadata_start, entry_data_list);
+}
+
+int MMDB_get_entry_data_list(
+    MMDB_entry_s *start, MMDB_entry_data_list_s **const entry_data_list)
+{
+    MMDB_data_pool_s *const pool = data_pool_new(MMDB_POOL_INIT_SIZE);
+    if (!pool) {
+        return MMDB_OUT_OF_MEMORY_ERROR;
+    }
+
+    MMDB_entry_data_list_s *const list = data_pool_alloc(pool);
+    if (!list) {
+        data_pool_destroy(pool);
+        return MMDB_OUT_OF_MEMORY_ERROR;
+    }
+
+    int const status = get_entry_data_list(start->mmdb, start->offset, list,
+                                           pool, 0);
+
+    *entry_data_list = data_pool_to_list(pool);
+    if (!*entry_data_list) {
+        data_pool_destroy(pool);
+        return MMDB_OUT_OF_MEMORY_ERROR;
+    }
+
+    return status;
+}
+
+LOCAL int get_entry_data_list(const MMDB_s *const mmdb,
+                              uint32_t offset,
+                              MMDB_entry_data_list_s *const entry_data_list,
+                              MMDB_data_pool_s *const pool,
+                              int depth)
+{
+    if (depth >= MAXIMUM_DATA_STRUCTURE_DEPTH) {
+        DEBUG_MSG("reached the maximum data structure depth");
+        return MMDB_INVALID_DATA_ERROR;
+    }
+    depth++;
+    CHECKED_DECODE_ONE(mmdb, offset, &entry_data_list->entry_data);
+
+    switch (entry_data_list->entry_data.type) {
+    case MMDB_DATA_TYPE_POINTER:
+        {
+            uint32_t next_offset = entry_data_list->entry_data.offset_to_next;
+            uint32_t last_offset;
+            CHECKED_DECODE_ONE(mmdb, last_offset =
+                                   entry_data_list->entry_data.pointer,
+                               &entry_data_list->entry_data);
+
+            /* Pointers to pointers are illegal under the spec */
+            if (entry_data_list->entry_data.type == MMDB_DATA_TYPE_POINTER) {
+                DEBUG_MSG("pointer points to another pointer");
+                return MMDB_INVALID_DATA_ERROR;
+            }
+
+            if (entry_data_list->entry_data.type == MMDB_DATA_TYPE_ARRAY
+                || entry_data_list->entry_data.type == MMDB_DATA_TYPE_MAP) {
+
+                int status =
+                    get_entry_data_list(mmdb, last_offset, entry_data_list,
+                                        pool, depth);
+                if (MMDB_SUCCESS != status) {
+                    DEBUG_MSG("get_entry_data_list on pointer failed.");
+                    return status;
+                }
+            }
+            entry_data_list->entry_data.offset_to_next = next_offset;
+        }
+        break;
+    case MMDB_DATA_TYPE_ARRAY:
+        {
+            uint32_t array_size = entry_data_list->entry_data.data_size;
+            uint32_t array_offset = entry_data_list->entry_data.offset_to_next;
+            while (array_size-- > 0) {
+                MMDB_entry_data_list_s *entry_data_list_to =
+                    data_pool_alloc(pool);
+                if (!entry_data_list_to) {
+                    return MMDB_OUT_OF_MEMORY_ERROR;
+                }
+
+                int status =
+                    get_entry_data_list(mmdb, array_offset, entry_data_list_to,
+                                        pool, depth);
+                if (MMDB_SUCCESS != status) {
+                    DEBUG_MSG("get_entry_data_list on array element failed.");
+                    return status;
+                }
+
+                array_offset = entry_data_list_to->entry_data.offset_to_next;
+            }
+            entry_data_list->entry_data.offset_to_next = array_offset;
+
+        }
+        break;
+    case MMDB_DATA_TYPE_MAP:
+        {
+            uint32_t size = entry_data_list->entry_data.data_size;
+
+            offset = entry_data_list->entry_data.offset_to_next;
+            while (size-- > 0) {
+                MMDB_entry_data_list_s *list_key = data_pool_alloc(pool);
+                if (!list_key) {
+                    return MMDB_OUT_OF_MEMORY_ERROR;
+                }
+
+                int status =
+                    get_entry_data_list(mmdb, offset, list_key, pool, depth);
+                if (MMDB_SUCCESS != status) {
+                    DEBUG_MSG("get_entry_data_list on map key failed.");
+                    return status;
+                }
+
+                offset = list_key->entry_data.offset_to_next;
+
+                MMDB_entry_data_list_s *list_value = data_pool_alloc(pool);
+                if (!list_value) {
+                    return MMDB_OUT_OF_MEMORY_ERROR;
+                }
+
+                status = get_entry_data_list(mmdb, offset, list_value, pool,
+                                             depth);
+                if (MMDB_SUCCESS != status) {
+                    DEBUG_MSG("get_entry_data_list on map element failed.");
+                    return status;
+                }
+                offset = list_value->entry_data.offset_to_next;
+            }
+            entry_data_list->entry_data.offset_to_next = offset;
+        }
+        break;
+    default:
+        break;
+    }
+
+    return MMDB_SUCCESS;
+}
+
+LOCAL float get_ieee754_float(const uint8_t *restrict p)
+{
+    volatile float f;
+    uint8_t *q = (void *)&f;
+/* Windows builds don't use autoconf but we can assume they're all
+ * little-endian. */
+#if MMDB_LITTLE_ENDIAN || _WIN32
+    q[3] = p[0];
+    q[2] = p[1];
+    q[1] = p[2];
+    q[0] = p[3];
+#else
+    memcpy(q, p, 4);
+#endif
+    return f;
+}
+
+LOCAL double get_ieee754_double(const uint8_t *restrict p)
+{
+    volatile double d;
+    uint8_t *q = (void *)&d;
+#if MMDB_LITTLE_ENDIAN || _WIN32
+    q[7] = p[0];
+    q[6] = p[1];
+    q[5] = p[2];
+    q[4] = p[3];
+    q[3] = p[4];
+    q[2] = p[5];
+    q[1] = p[6];
+    q[0] = p[7];
+#else
+    memcpy(q, p, 8);
+#endif
+
+    return d;
+}
+
+LOCAL uint32_t get_uint32(const uint8_t *p)
+{
+    return p[0] * 16777216U + p[1] * 65536 + p[2] * 256 + p[3];
+}
+
+LOCAL uint32_t get_uint24(const uint8_t *p)
+{
+    return p[0] * 65536U + p[1] * 256 + p[2];
+}
+
+LOCAL uint32_t get_uint16(const uint8_t *p)
+{
+    return p[0] * 256U + p[1];
+}
+
+LOCAL uint64_t get_uintX(const uint8_t *p, int length)
+{
+    uint64_t value = 0;
+    while (length-- > 0) {
+        value <<= 8;
+        value += *p++;
+    }
+    return value;
+}
+
+LOCAL int32_t get_sintX(const uint8_t *p, int length)
+{
+    return (int32_t)get_uintX(p, length);
+}
+
+void MMDB_free_entry_data_list(MMDB_entry_data_list_s *const entry_data_list)
+{
+    if (entry_data_list == NULL) {
+        return;
+    }
+    data_pool_destroy(entry_data_list->pool);
+}
+
+void MMDB_close(MMDB_s *const mmdb)
+{
+    free_mmdb_struct(mmdb);
+}
+
+LOCAL void free_mmdb_struct(MMDB_s *const mmdb)
+{
+    if (!mmdb) {
+        return;
+    }
+
+    if (NULL != mmdb->filename) {
+        FREE_AND_SET_NULL(mmdb->filename);
+    }
+    if (NULL != mmdb->file_content) {
+#ifdef _WIN32
+        UnmapViewOfFile(mmdb->file_content);
+        /* Winsock is only initialized if open was successful so we only have
+         * to cleanup then. */
+        WSACleanup();
+#else
+        munmap((void *)mmdb->file_content, mmdb->file_size);
+#endif
+    }
+
+    if (NULL != mmdb->metadata.database_type) {
+        FREE_AND_SET_NULL(mmdb->metadata.database_type);
+    }
+
+    free_languages_metadata(mmdb);
+    free_descriptions_metadata(mmdb);
+}
+
+LOCAL void free_languages_metadata(MMDB_s *mmdb)
+{
+    if (!mmdb->metadata.languages.names) {
+        return;
+    }
+
+    for (size_t i = 0; i < mmdb->metadata.languages.count; i++) {
+        FREE_AND_SET_NULL(mmdb->metadata.languages.names[i]);
+    }
+    FREE_AND_SET_NULL(mmdb->metadata.languages.names);
+}
+
+LOCAL void free_descriptions_metadata(MMDB_s *mmdb)
+{
+    if (!mmdb->metadata.description.count) {
+        return;
+    }
+
+    for (size_t i = 0; i < mmdb->metadata.description.count; i++) {
+        if (NULL != mmdb->metadata.description.descriptions[i]) {
+            if (NULL !=
+                mmdb->metadata.description.descriptions[i]->language) {
+                FREE_AND_SET_NULL(
+                    mmdb->metadata.description.descriptions[i]->language);
+            }
+
+            if (NULL !=
+                mmdb->metadata.description.descriptions[i]->description) {
+                FREE_AND_SET_NULL(
+                    mmdb->metadata.description.descriptions[i]->description);
+            }
+            FREE_AND_SET_NULL(mmdb->metadata.description.descriptions[i]);
+        }
+    }
+
+    FREE_AND_SET_NULL(mmdb->metadata.description.descriptions);
+}
+
+const char *MMDB_lib_version(void)
+{
+    return PACKAGE_VERSION;
+}
+
+int MMDB_dump_entry_data_list(FILE *const stream,
+                              MMDB_entry_data_list_s *const entry_data_list,
+                              int indent)
+{
+    int status;
+    dump_entry_data_list(stream, entry_data_list, indent, &status);
+    return status;
+}
+
+LOCAL MMDB_entry_data_list_s *dump_entry_data_list(
+    FILE *stream, MMDB_entry_data_list_s *entry_data_list, int indent,
+    int *status)
+{
+    switch (entry_data_list->entry_data.type) {
+    case MMDB_DATA_TYPE_MAP:
+        {
+            uint32_t size = entry_data_list->entry_data.data_size;
+
+            print_indentation(stream, indent);
+            fprintf(stream, "{\n");
+            indent += 2;
+
+            for (entry_data_list = entry_data_list->next;
+                 size && entry_data_list; size--) {
+
+                if (MMDB_DATA_TYPE_UTF8_STRING !=
+                    entry_data_list->entry_data.type) {
+                    *status = MMDB_INVALID_DATA_ERROR;
+                    return NULL;
+                }
+                char *key =
+                    mmdb_strndup(
+                        (char *)entry_data_list->entry_data.utf8_string,
+                        entry_data_list->entry_data.data_size);
+                if (NULL == key) {
+                    *status = MMDB_OUT_OF_MEMORY_ERROR;
+                    return NULL;
+                }
+
+                print_indentation(stream, indent);
+                fprintf(stream, "\"%s\": \n", key);
+                free(key);
+
+                entry_data_list = entry_data_list->next;
+                entry_data_list =
+                    dump_entry_data_list(stream, entry_data_list, indent + 2,
+                                         status);
+
+                if (MMDB_SUCCESS != *status) {
+                    return NULL;
+                }
+            }
+
+            indent -= 2;
+            print_indentation(stream, indent);
+            fprintf(stream, "}\n");
+        }
+        break;
+    case MMDB_DATA_TYPE_ARRAY:
+        {
+            uint32_t size = entry_data_list->entry_data.data_size;
+
+            print_indentation(stream, indent);
+            fprintf(stream, "[\n");
+            indent += 2;
+
+            for (entry_data_list = entry_data_list->next;
+                 size && entry_data_list; size--) {
+                entry_data_list =
+                    dump_entry_data_list(stream, entry_data_list, indent,
+                                         status);
+                if (MMDB_SUCCESS != *status) {
+                    return NULL;
+                }
+            }
+
+            indent -= 2;
+            print_indentation(stream, indent);
+            fprintf(stream, "]\n");
+        }
+        break;
+    case MMDB_DATA_TYPE_UTF8_STRING:
+        {
+            char *string =
+                mmdb_strndup((char *)entry_data_list->entry_data.utf8_string,
+                             entry_data_list->entry_data.data_size);
+            if (NULL == string) {
+                *status = MMDB_OUT_OF_MEMORY_ERROR;
+                return NULL;
+            }
+            print_indentation(stream, indent);
+            fprintf(stream, "\"%s\" <utf8_string>\n", string);
+            free(string);
+            entry_data_list = entry_data_list->next;
+        }
+        break;
+    case MMDB_DATA_TYPE_BYTES:
+        {
+            char *hex_string =
+                bytes_to_hex((uint8_t *)entry_data_list->entry_data.bytes,
+                             entry_data_list->entry_data.data_size);
+            if (NULL == hex_string) {
+                *status = MMDB_OUT_OF_MEMORY_ERROR;
+                return NULL;
+            }
+
+            print_indentation(stream, indent);
+            fprintf(stream, "%s <bytes>\n", hex_string);
+            free(hex_string);
+
+            entry_data_list = entry_data_list->next;
+        }
+        break;
+    case MMDB_DATA_TYPE_DOUBLE:
+        print_indentation(stream, indent);
+        fprintf(stream, "%f <double>\n",
+                entry_data_list->entry_data.double_value);
+        entry_data_list = entry_data_list->next;
+        break;
+    case MMDB_DATA_TYPE_FLOAT:
+        print_indentation(stream, indent);
+        fprintf(stream, "%f <float>\n",
+                entry_data_list->entry_data.float_value);
+        entry_data_list = entry_data_list->next;
+        break;
+    case MMDB_DATA_TYPE_UINT16:
+        print_indentation(stream, indent);
+        fprintf(stream, "%u <uint16>\n", entry_data_list->entry_data.uint16);
+        entry_data_list = entry_data_list->next;
+        break;
+    case MMDB_DATA_TYPE_UINT32:
+        print_indentation(stream, indent);
+        fprintf(stream, "%u <uint32>\n", entry_data_list->entry_data.uint32);
+        entry_data_list = entry_data_list->next;
+        break;
+    case MMDB_DATA_TYPE_BOOLEAN:
+        print_indentation(stream, indent);
+        fprintf(stream, "%s <boolean>\n",
+                entry_data_list->entry_data.boolean ? "true" : "false");
+        entry_data_list = entry_data_list->next;
+        break;
+    case MMDB_DATA_TYPE_UINT64:
+        print_indentation(stream, indent);
+        fprintf(stream, "%" PRIu64 " <uint64>\n",
+                entry_data_list->entry_data.uint64);
+        entry_data_list = entry_data_list->next;
+        break;
+    case MMDB_DATA_TYPE_UINT128:
+        print_indentation(stream, indent);
+#if MMDB_UINT128_IS_BYTE_ARRAY
+        char *hex_string =
+            bytes_to_hex((uint8_t *)entry_data_list->entry_data.uint128, 16);
+        if (NULL == hex_string) {
+            *status = MMDB_OUT_OF_MEMORY_ERROR;
+            return NULL;
+        }
+        fprintf(stream, "0x%s <uint128>\n", hex_string);
+        free(hex_string);
+#else
+        uint64_t high = entry_data_list->entry_data.uint128 >> 64;
+        uint64_t low = (uint64_t)entry_data_list->entry_data.uint128;
+        fprintf(stream, "0x%016" PRIX64 "%016" PRIX64 " <uint128>\n", high,
+                low);
+#endif
+        entry_data_list = entry_data_list->next;
+        break;
+    case MMDB_DATA_TYPE_INT32:
+        print_indentation(stream, indent);
+        fprintf(stream, "%d <int32>\n", entry_data_list->entry_data.int32);
+        entry_data_list = entry_data_list->next;
+        break;
+    default:
+        *status = MMDB_INVALID_DATA_ERROR;
+        return NULL;
+    }
+
+    *status = MMDB_SUCCESS;
+    return entry_data_list;
+}
+
+LOCAL void print_indentation(FILE *stream, int i)
+{
+    char buffer[1024];
+    int size = i >= 1024 ? 1023 : i;
+    memset(buffer, 32, size);
+    buffer[size] = '\0';
+    fputs(buffer, stream);
+}
+
+LOCAL char *bytes_to_hex(uint8_t *bytes, uint32_t size)
+{
+    char *hex_string;
+    MAYBE_CHECK_SIZE_OVERFLOW(size, SIZE_MAX / 2 - 1, NULL);
+
+    hex_string = malloc((size * 2) + 1);
+    if (NULL == hex_string) {
+        return NULL;
+    }
+
+    for (uint32_t i = 0; i < size; i++) {
+        sprintf(hex_string + (2 * i), "%02X", bytes[i]);
+    }
+
+    return hex_string;
+}
+
+const char *MMDB_strerror(int error_code)
+{
+    switch (error_code) {
+    case MMDB_SUCCESS:
+        return "Success (not an error)";
+    case MMDB_FILE_OPEN_ERROR:
+        return "Error opening the specified MaxMind DB file";
+    case MMDB_CORRUPT_SEARCH_TREE_ERROR:
+        return "The MaxMind DB file's search tree is corrupt";
+    case MMDB_INVALID_METADATA_ERROR:
+        return "The MaxMind DB file contains invalid metadata";
+    case MMDB_IO_ERROR:
+        return "An attempt to read data from the MaxMind DB file failed";
+    case MMDB_OUT_OF_MEMORY_ERROR:
+        return "A memory allocation call failed";
+    case MMDB_UNKNOWN_DATABASE_FORMAT_ERROR:
+        return
+            "The MaxMind DB file is in a format this library can't handle (unknown record size or binary format version)";
+    case MMDB_INVALID_DATA_ERROR:
+        return
+            "The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)";
+    case MMDB_INVALID_LOOKUP_PATH_ERROR:
+        return
+            "The lookup path contained an invalid value (like a negative integer for an array index)";
+    case MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR:
+        return
+            "The lookup path does not match the data (key that doesn't exist, array index bigger than the array, expected array or map where none exists)";
+    case MMDB_INVALID_NODE_NUMBER_ERROR:
+        return
+            "The MMDB_read_node function was called with a node number that does not exist in the search tree";
+    case MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR:
+        return
+            "You attempted to look up an IPv6 address in an IPv4-only database";
+    default:
+        return "Unknown error code";
+    }
+}
diff --git a/modules/net/srv/libmaxminddb/maxminddb.h b/modules/net/srv/libmaxminddb/maxminddb.h
new file mode 100644
index 0000000000000000000000000000000000000000..f4b9bd24e48584ab6431775ec497e763bc4442f1
--- /dev/null
+++ b/modules/net/srv/libmaxminddb/maxminddb.h
@@ -0,0 +1,255 @@
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef MAXMINDDB_H
+#define MAXMINDDB_H
+
+/* Request POSIX.1-2008. However, we want to remain compatible with
+ * POSIX.1-2001 (since we have been historically and see no reason to drop
+ * compatibility). By requesting POSIX.1-2008, we can conditionally use
+ * features provided by that standard if the implementation provides it. We can
+ * check for what the implementation provides by checking the _POSIX_VERSION
+ * macro after including unistd.h. If a feature is in POSIX.1-2008 but not
+ * POSIX.1-2001, check that macro before using the feature (or check for the
+ * feature directly if possible). */
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+
+#include "maxminddb_config.h"
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#ifdef _WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+/* libmaxminddb package version from configure */
+#define PACKAGE_VERSION "1.4.2"
+
+typedef ADDRESS_FAMILY sa_family_t;
+
+#if defined(_MSC_VER)
+/* MSVC doesn't define signed size_t, copy it from configure */
+#define ssize_t SSIZE_T
+
+/* MSVC doesn't support restricted pointers */
+#define restrict
+#endif
+#else
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#endif
+
+#define MMDB_DATA_TYPE_EXTENDED (0)
+#define MMDB_DATA_TYPE_POINTER (1)
+#define MMDB_DATA_TYPE_UTF8_STRING (2)
+#define MMDB_DATA_TYPE_DOUBLE (3)
+#define MMDB_DATA_TYPE_BYTES (4)
+#define MMDB_DATA_TYPE_UINT16 (5)
+#define MMDB_DATA_TYPE_UINT32 (6)
+#define MMDB_DATA_TYPE_MAP (7)
+#define MMDB_DATA_TYPE_INT32 (8)
+#define MMDB_DATA_TYPE_UINT64 (9)
+#define MMDB_DATA_TYPE_UINT128 (10)
+#define MMDB_DATA_TYPE_ARRAY (11)
+#define MMDB_DATA_TYPE_CONTAINER (12)
+#define MMDB_DATA_TYPE_END_MARKER (13)
+#define MMDB_DATA_TYPE_BOOLEAN (14)
+#define MMDB_DATA_TYPE_FLOAT (15)
+
+#define MMDB_RECORD_TYPE_SEARCH_NODE (0)
+#define MMDB_RECORD_TYPE_EMPTY (1)
+#define MMDB_RECORD_TYPE_DATA (2)
+#define MMDB_RECORD_TYPE_INVALID (3)
+
+/* flags for open */
+#define MMDB_MODE_MMAP (1)
+#define MMDB_MODE_MASK (7)
+
+/* error codes */
+#define MMDB_SUCCESS (0)
+#define MMDB_FILE_OPEN_ERROR (1)
+#define MMDB_CORRUPT_SEARCH_TREE_ERROR (2)
+#define MMDB_INVALID_METADATA_ERROR (3)
+#define MMDB_IO_ERROR (4)
+#define MMDB_OUT_OF_MEMORY_ERROR (5)
+#define MMDB_UNKNOWN_DATABASE_FORMAT_ERROR (6)
+#define MMDB_INVALID_DATA_ERROR (7)
+#define MMDB_INVALID_LOOKUP_PATH_ERROR (8)
+#define MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR (9)
+#define MMDB_INVALID_NODE_NUMBER_ERROR (10)
+#define MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR (11)
+
+#if !(MMDB_UINT128_IS_BYTE_ARRAY)
+#if MMDB_UINT128_USING_MODE
+typedef unsigned int mmdb_uint128_t __attribute__ ((__mode__(TI)));
+#else
+typedef unsigned __int128 mmdb_uint128_t;
+#endif
+#endif
+
+/* This is a pointer into the data section for a given IP address lookup */
+typedef struct MMDB_entry_s {
+    const struct MMDB_s *mmdb;
+    uint32_t offset;
+} MMDB_entry_s;
+
+typedef struct MMDB_lookup_result_s {
+    bool found_entry;
+    MMDB_entry_s entry;
+    uint16_t netmask;
+} MMDB_lookup_result_s;
+
+typedef struct MMDB_entry_data_s {
+    bool has_data;
+    union {
+        uint32_t pointer;
+        const char *utf8_string;
+        double double_value;
+        const uint8_t *bytes;
+        uint16_t uint16;
+        uint32_t uint32;
+        int32_t int32;
+        uint64_t uint64;
+#if MMDB_UINT128_IS_BYTE_ARRAY
+        uint8_t uint128[16];
+#else
+        mmdb_uint128_t uint128;
+#endif
+        bool boolean;
+        float float_value;
+    };
+    /* This is a 0 if a given entry cannot be found. This can only happen
+     * when a call to MMDB_(v)get_value() asks for hash keys or array
+     * indices that don't exist. */
+    uint32_t offset;
+    /* This is the next entry in the data section, but it's really only
+     * relevant for entries that part of a larger map or array
+     * struct. There's no good reason for an end user to look at this
+     * directly. */
+    uint32_t offset_to_next;
+    /* This is only valid for strings, utf8_strings or binary data */
+    uint32_t data_size;
+    /* This is an MMDB_DATA_TYPE_* constant */
+    uint32_t type;
+} MMDB_entry_data_s;
+
+/* This is the return type when someone asks for all the entry data in a map or array */
+typedef struct MMDB_entry_data_list_s {
+    MMDB_entry_data_s entry_data;
+    struct MMDB_entry_data_list_s *next;
+    void *pool;
+} MMDB_entry_data_list_s;
+
+typedef struct MMDB_description_s {
+    const char *language;
+    const char *description;
+} MMDB_description_s;
+
+/* WARNING: do not add new fields to this struct without bumping the SONAME.
+ * The struct is allocated by the users of this library and increasing the
+ * size will cause existing users to allocate too little space when the shared
+ * library is upgraded */
+typedef struct MMDB_metadata_s {
+    uint32_t node_count;
+    uint16_t record_size;
+    uint16_t ip_version;
+    const char *database_type;
+    struct {
+        size_t count;
+        const char **names;
+    } languages;
+    uint16_t binary_format_major_version;
+    uint16_t binary_format_minor_version;
+    uint64_t build_epoch;
+    struct {
+        size_t count;
+        MMDB_description_s **descriptions;
+    } description;
+    /* See above warning before adding fields */
+} MMDB_metadata_s;
+
+/* WARNING: do not add new fields to this struct without bumping the SONAME.
+ * The struct is allocated by the users of this library and increasing the
+ * size will cause existing users to allocate too little space when the shared
+ * library is upgraded */
+typedef struct MMDB_ipv4_start_node_s {
+    uint16_t netmask;
+    uint32_t node_value;
+    /* See above warning before adding fields */
+} MMDB_ipv4_start_node_s;
+
+/* WARNING: do not add new fields to this struct without bumping the SONAME.
+ * The struct is allocated by the users of this library and increasing the
+ * size will cause existing users to allocate too little space when the shared
+ * library is upgraded */
+typedef struct MMDB_s {
+    uint32_t flags;
+    const char *filename;
+    ssize_t file_size;
+    const uint8_t *file_content;
+    const uint8_t *data_section;
+    uint32_t data_section_size;
+    const uint8_t *metadata_section;
+    uint32_t metadata_section_size;
+    uint16_t full_record_byte_size;
+    uint16_t depth;
+    MMDB_ipv4_start_node_s ipv4_start_node;
+    MMDB_metadata_s metadata;
+    /* See above warning before adding fields */
+} MMDB_s;
+
+typedef struct MMDB_search_node_s {
+    uint64_t left_record;
+    uint64_t right_record;
+    uint8_t left_record_type;
+    uint8_t right_record_type;
+    MMDB_entry_s left_record_entry;
+    MMDB_entry_s right_record_entry;
+} MMDB_search_node_s;
+
+extern int MMDB_open(const char *const filename, uint32_t flags,
+                     MMDB_s *const mmdb);
+extern MMDB_lookup_result_s MMDB_lookup_string(const MMDB_s *const mmdb,
+                                               const char *const ipstr,
+                                               int *const gai_error,
+                                               int *const mmdb_error);
+extern MMDB_lookup_result_s MMDB_lookup_sockaddr(
+    const MMDB_s *const mmdb,
+    const struct sockaddr *const sockaddr,
+    int *const mmdb_error);
+extern int MMDB_read_node(const MMDB_s *const mmdb,
+                          uint32_t node_number,
+                          MMDB_search_node_s *const node);
+extern int MMDB_get_value(MMDB_entry_s *const start,
+                          MMDB_entry_data_s *const entry_data,
+                          ...);
+extern int MMDB_vget_value(MMDB_entry_s *const start,
+                           MMDB_entry_data_s *const entry_data,
+                           va_list va_path);
+extern int MMDB_aget_value(MMDB_entry_s *const start,
+                           MMDB_entry_data_s *const entry_data,
+                           const char *const *const path);
+extern int MMDB_get_metadata_as_entry_data_list(
+    const MMDB_s *const mmdb, MMDB_entry_data_list_s **const entry_data_list);
+extern int MMDB_get_entry_data_list(
+    MMDB_entry_s *start, MMDB_entry_data_list_s **const entry_data_list);
+extern void MMDB_free_entry_data_list(
+    MMDB_entry_data_list_s *const entry_data_list);
+extern void MMDB_close(MMDB_s *const mmdb);
+extern const char *MMDB_lib_version(void);
+extern int MMDB_dump_entry_data_list(FILE *const stream,
+                                     MMDB_entry_data_list_s *const entry_data_list,
+                                     int indent);
+extern const char *MMDB_strerror(int error_code);
+
+#endif                          /* MAXMINDDB_H */
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/modules/net/srv/libmaxminddb/maxminddb_config.h b/modules/net/srv/libmaxminddb/maxminddb_config.h
new file mode 100644
index 0000000000000000000000000000000000000000..0b824ee8272b4a4cc2eebdf0a507249f8f93414c
--- /dev/null
+++ b/modules/net/srv/libmaxminddb/maxminddb_config.h
@@ -0,0 +1,18 @@
+#ifndef MAXMINDDB_CONFIG_H
+#define MAXMINDDB_CONFIG_H
+
+/* libmaxminddb package version from configure */
+#define PACKAGE_VERSION "1.4.2"
+
+#ifndef MMDB_UINT128_USING_MODE
+/* Define as 1 if we we use unsigned int __atribute__ ((__mode__(TI))) for uint128 values */
+#define MMDB_UINT128_USING_MODE 0
+#endif
+
+#ifndef MMDB_UINT128_IS_BYTE_ARRAY
+/* Define as 1 if we don't have an unsigned __int128 type */
+#define MMDB_UINT128_IS_BYTE_ARRAY 1
+#endif
+
+#endif                          /* MAXMINDDB_CONFIG_H */
+
diff --git a/modules/net/srv/libmaxminddb/mmdblookup.c b/modules/net/srv/libmaxminddb/mmdblookup.c
new file mode 100644
index 0000000000000000000000000000000000000000..4a3403c67e0f3466ea31d6a7ca856cd938d42758
--- /dev/null
+++ b/modules/net/srv/libmaxminddb/mmdblookup.c
@@ -0,0 +1,762 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "maxminddb.h"
+#include <errno.h>
+#include <getopt.h>
+#ifndef _WIN32
+#include <pthread.h>
+#endif
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifdef _WIN32
+#ifndef UNICODE
+#define UNICODE
+#endif
+#include <malloc.h>
+#else
+#include <libgen.h>
+#include <unistd.h>
+#endif
+
+#define LOCAL static
+
+LOCAL void usage(char *program, int exit_code, const char *error);
+LOCAL const char **get_options(
+    int argc,
+    char **argv,
+    char **mmdb_file,
+    char **ip_address,
+    int *verbose,
+    int *iterations,
+    int *lookup_path_length,
+    int *const thread_count,
+    char **const ip_file);
+LOCAL MMDB_s open_or_die(const char *fname);
+LOCAL void dump_meta(MMDB_s *mmdb);
+LOCAL bool lookup_from_file(MMDB_s *const mmdb,
+                            char const *const ip_file,
+                            bool const dump);
+LOCAL int lookup_and_print(MMDB_s *mmdb, const char *ip_address,
+                           const char **lookup_path,
+                           int lookup_path_length,
+                           bool verbose);
+LOCAL int benchmark(MMDB_s *mmdb, int iterations);
+LOCAL MMDB_lookup_result_s lookup_or_die(MMDB_s *mmdb, const char *ipstr);
+LOCAL void random_ipv4(char *ip);
+
+#ifndef _WIN32
+// These aren't with the automatically generated prototypes as we'd lose the
+// enclosing macros.
+static bool start_threaded_benchmark(
+    MMDB_s *const mmdb,
+    int const thread_count,
+    int const iterations);
+static long double get_time(void);
+static void *thread(void *arg);
+#endif
+
+#ifdef _WIN32
+int wmain(int argc, wchar_t **wargv)
+{
+    // Convert our argument list from UTF-16 to UTF-8.
+    char **argv = (char **)malloc(argc * sizeof(char *));
+    if (!argv) {
+        fprintf(stderr, "malloc(): %s\n", strerror(errno));
+        exit(1);
+    }
+    for (int i = 0; i < argc; i++) {
+        int utf8_width;
+        char *utf8_string;
+        utf8_width = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, NULL, 0,
+                                         NULL, NULL);
+        if (utf8_width < 1) {
+            fprintf(stderr, "WideCharToMultiByte() failed: %d\n",
+                    GetLastError());
+            exit(1);
+        }
+        utf8_string = malloc(utf8_width);
+        if (!utf8_string) {
+            fprintf(stderr, "malloc(): %s\n", strerror(errno));
+            exit(1);
+        }
+        if (WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, utf8_string,
+                                utf8_width, NULL, NULL) < 1) {
+            fprintf(stderr, "WideCharToMultiByte() failed: %d\n",
+                    GetLastError());
+            exit(1);
+        }
+        argv[i] = utf8_string;
+    }
+#else // _WIN32
+int main(int argc, char **argv)
+{
+#endif // _WIN32
+    char *mmdb_file = NULL;
+    char *ip_address = NULL;
+    int verbose = 0;
+    int iterations = 0;
+    int lookup_path_length = 0;
+    int thread_count = 0;
+    char *ip_file = NULL;
+
+    const char **lookup_path =
+        get_options(argc, argv, &mmdb_file, &ip_address, &verbose, &iterations,
+                    &lookup_path_length, &thread_count, &ip_file);
+
+    MMDB_s mmdb = open_or_die(mmdb_file);
+
+    if (verbose) {
+        dump_meta(&mmdb);
+    }
+
+    // The benchmarking and lookup from file modes are hidden features mainly
+    // intended for development right now. This means there are several flags
+    // that exist but are intentionally not mentioned in the usage or man page.
+
+    // The lookup from file mode may be useful to expose publicly in the usage,
+    // but we should have it respect the lookup_path functionality if we do so.
+    if (ip_file) {
+        free((void *)lookup_path);
+        if (!lookup_from_file(&mmdb, ip_file, verbose == 1)) {
+            MMDB_close(&mmdb);
+            return 1;
+        }
+        MMDB_close(&mmdb);
+        return 0;
+    }
+
+    if (0 == iterations) {
+        exit(lookup_and_print(&mmdb, ip_address, lookup_path,
+                              lookup_path_length, verbose));
+    }
+
+    free((void *)lookup_path);
+
+    srand( (int)time(NULL) );
+
+#ifndef _WIN32
+    if (thread_count > 0) {
+        if (!start_threaded_benchmark(&mmdb, thread_count, iterations)) {
+            MMDB_close(&mmdb);
+            exit(1);
+        }
+        MMDB_close(&mmdb);
+        exit(0);
+    }
+#endif
+
+    exit(benchmark(&mmdb, iterations));
+}
+
+LOCAL void usage(char *program, int exit_code, const char *error)
+{
+    if (NULL != error) {
+        fprintf(stderr, "\n  *ERROR: %s\n", error);
+    }
+
+    char *usage = "\n"
+                  "  %s --file /path/to/file.mmdb --ip 1.2.3.4 [path to lookup]\n"
+                  "\n"
+                  "  This application accepts the following options:\n"
+                  "\n"
+                  "      --file (-f)     The path to the MMDB file. Required.\n"
+                  "\n"
+                  "      --ip (-i)       The IP address to look up. Required.\n"
+                  "\n"
+                  "      --verbose (-v)  Turns on verbose output. Specifically, this causes this\n"
+                  "                      application to output the database metadata.\n"
+                  "\n"
+                  "      --version       Print the program's version number and exit.\n"
+                  "\n"
+                  "      --help (-h -?)  Show usage information.\n"
+                  "\n"
+                  "  If an IP's data entry resolves to a map or array, you can provide\n"
+                  "  a lookup path to only show part of that data.\n"
+                  "\n"
+                  "  For example, given a JSON structure like this:\n"
+                  "\n"
+                  "    {\n"
+                  "        \"names\": {\n"
+                  "             \"en\": \"Germany\",\n"
+                  "             \"de\": \"Deutschland\"\n"
+                  "        },\n"
+                  "        \"cities\": [ \"Berlin\", \"Frankfurt\" ]\n"
+                  "    }\n"
+                  "\n"
+                  "  You could look up just the English name by calling mmdblookup with a lookup path of:\n"
+                  "\n"
+                  "    mmdblookup --file ... --ip ... names en\n"
+                  "\n"
+                  "  Or you could look up the second city in the list with:\n"
+                  "\n"
+                  "    mmdblookup --file ... --ip ... cities 1\n"
+                  "\n"
+                  "  Array numbering begins with zero (0).\n"
+                  "\n"
+                  "  If you do not provide a path to lookup, all of the information for a given IP\n"
+                  "  will be shown.\n"
+                  "\n";
+
+    fprintf(stdout, usage, program);
+    exit(exit_code);
+}
+
+LOCAL const char **get_options(
+    int argc,
+    char **argv,
+    char **mmdb_file,
+    char **ip_address,
+    int *verbose,
+    int *iterations,
+    int *lookup_path_length,
+    int *const thread_count,
+    char **const ip_file)
+{
+    static int help = 0;
+    static int version = 0;
+
+    while (1) {
+        static struct option options[] = {
+            { "file",      required_argument, 0, 'f' },
+            { "ip",        required_argument, 0, 'i' },
+            { "verbose",   no_argument,       0, 'v' },
+            { "version",   no_argument,       0, 'n' },
+            { "benchmark", required_argument, 0, 'b' },
+#ifndef _WIN32
+            { "threads",   required_argument, 0, 't' },
+#endif
+            { "ip-file",   required_argument, 0, 'I' },
+            { "help",      no_argument,       0, 'h' },
+            { "?",         no_argument,       0, 1   },
+            { 0,           0,                 0, 0   }
+        };
+
+        int opt_index;
+#ifdef _WIN32
+        char const * const optstring = "f:i:b:I:vnh?";
+#else
+        char const * const optstring = "f:i:b:t:I:vnh?";
+#endif
+        int opt_char = getopt_long(argc, argv, optstring, options,
+                                   &opt_index);
+
+        if (-1 == opt_char) {
+            break;
+        }
+
+        if ('f' == opt_char) {
+            *mmdb_file = optarg;
+        } else if ('i' == opt_char) {
+            *ip_address = optarg;
+        } else if ('v' == opt_char) {
+            *verbose = 1;
+        } else if ('n' == opt_char) {
+            version = 1;
+        } else if ('b' == opt_char) {
+            *iterations = strtol(optarg, NULL, 10);
+        } else if ('h' == opt_char || '?' == opt_char) {
+            help = 1;
+        } else if (opt_char == 't') {
+            *thread_count = strtol(optarg, NULL, 10);
+        } else if (opt_char == 'I') {
+            *ip_file = optarg;
+        }
+    }
+
+#ifdef _WIN32
+    char *program = alloca(strlen(argv[0]));
+    _splitpath(argv[0], NULL, NULL, program, NULL);
+    _splitpath(argv[0], NULL, NULL, NULL, program + strlen(program));
+#else
+    char *program = basename(argv[0]);
+#endif
+
+    if (help) {
+        usage(program, 0, NULL);
+    }
+
+    if (version) {
+        fprintf(stdout, "\n  %s version %s\n\n", program, PACKAGE_VERSION);
+        exit(0);
+    }
+
+    if (NULL == *mmdb_file) {
+        usage(program, 1, "You must provide a filename with --file");
+    }
+
+    if (*ip_address == NULL && *iterations == 0 && !*ip_file) {
+        usage(program, 1, "You must provide an IP address with --ip");
+    }
+
+    const char **lookup_path =
+        malloc(sizeof(const char *) * ((argc - optind) + 1));
+    int i;
+    for (i = 0; i < argc - optind; i++) {
+        lookup_path[i] = argv[i + optind];
+        (*lookup_path_length)++;
+    }
+    lookup_path[i] = NULL;
+
+    return lookup_path;
+}
+
+LOCAL MMDB_s open_or_die(const char *fname)
+{
+    MMDB_s mmdb;
+    int status = MMDB_open(fname, MMDB_MODE_MMAP, &mmdb);
+
+    if (MMDB_SUCCESS != status) {
+        fprintf(stderr, "\n  Can't open %s - %s\n", fname,
+                MMDB_strerror(status));
+
+        if (MMDB_IO_ERROR == status) {
+            fprintf(stderr, "    IO error: %s\n", strerror(errno));
+        }
+
+        fprintf(stderr, "\n");
+
+        exit(2);
+    }
+
+    return mmdb;
+}
+
+LOCAL void dump_meta(MMDB_s *mmdb)
+{
+    const char *meta_dump = "\n"
+                            "  Database metadata\n"
+                            "    Node count:    %i\n"
+                            "    Record size:   %i bits\n"
+                            "    IP version:    IPv%i\n"
+                            "    Binary format: %i.%i\n"
+                            "    Build epoch:   %llu (%s)\n"
+                            "    Type:          %s\n"
+                            "    Languages:     ";
+
+    char date[40];
+    const time_t epoch = (const time_t)mmdb->metadata.build_epoch;
+    strftime(date, 40, "%F %T UTC", gmtime(&epoch));
+
+    fprintf(stdout, meta_dump,
+            mmdb->metadata.node_count,
+            mmdb->metadata.record_size,
+            mmdb->metadata.ip_version,
+            mmdb->metadata.binary_format_major_version,
+            mmdb->metadata.binary_format_minor_version,
+            mmdb->metadata.build_epoch,
+            date,
+            mmdb->metadata.database_type);
+
+    for (size_t i = 0; i < mmdb->metadata.languages.count; i++) {
+        fprintf(stdout, "%s", mmdb->metadata.languages.names[i]);
+        if (i < mmdb->metadata.languages.count - 1) {
+            fprintf(stdout, " ");
+        }
+    }
+    fprintf(stdout, "\n");
+
+    fprintf(stdout, "    Description:\n");
+    for (size_t i = 0; i < mmdb->metadata.description.count; i++) {
+        fprintf(stdout, "      %s:   %s\n",
+                mmdb->metadata.description.descriptions[i]->language,
+                mmdb->metadata.description.descriptions[i]->description);
+    }
+    fprintf(stdout, "\n");
+}
+
+// The input file should have one IP per line.
+//
+// We look up each IP.
+//
+// If dump is true, we dump the data for each IP to stderr. This is useful for
+// comparison in that you can dump out the data for the IPs before and after
+// making changes. It goes to stderr rather than stdout so that the report does
+// not get included in what you will compare (since it will almost always be
+// different).
+//
+// In addition to being useful for comparisons, this function provides a way to
+// have a more deterministic set of lookups for benchmarking.
+LOCAL bool lookup_from_file(MMDB_s *const mmdb,
+                            char const *const ip_file,
+                            bool const dump)
+{
+    FILE *const fh = fopen(ip_file, "r");
+    if (!fh) {
+        fprintf(stderr, "fopen(): %s: %s\n", ip_file, strerror(errno));
+        return false;
+    }
+
+    clock_t const clock_start = clock();
+    char buf[1024] = { 0 };
+    // I'd normally use uint64_t, but support for it is optional in C99.
+    unsigned long long i = 0;
+    while (1) {
+        if (fgets(buf, sizeof(buf), fh) == NULL) {
+            if (!feof(fh)) {
+                fprintf(stderr, "fgets(): %s\n", strerror(errno));
+                fclose(fh);
+                return false;
+            }
+            if (fclose(fh) != 0) {
+                fprintf(stderr, "fclose(): %s\n", strerror(errno));
+                return false;
+            }
+            break;
+        }
+
+        char *ptr = buf;
+        while (*ptr != '\0') {
+            if (*ptr == '\n') {
+                *ptr = '\0';
+                break;
+            }
+            ptr++;
+        }
+        if (strlen(buf) == 0) {
+            continue;
+        }
+
+        i++;
+
+        MMDB_lookup_result_s result = lookup_or_die(mmdb, buf);
+        if (!result.found_entry) {
+            continue;
+        }
+
+        MMDB_entry_data_list_s *entry_data_list = NULL;
+        int const status = MMDB_get_entry_data_list(&result.entry,
+                                                    &entry_data_list);
+        if (status != MMDB_SUCCESS) {
+            fprintf(stderr, "MMDB_get_entry_data_list(): %s\n",
+                    MMDB_strerror(status));
+            fclose(fh);
+            MMDB_free_entry_data_list(entry_data_list);
+            return false;
+        }
+
+        if (!entry_data_list) {
+            fprintf(stderr, "entry_data_list is NULL\n");
+            fclose(fh);
+            return false;
+        }
+
+        if (dump) {
+            fprintf(stdout, "%s:\n", buf);
+            int const status = MMDB_dump_entry_data_list(stderr,
+                                                         entry_data_list, 0);
+            if (status != MMDB_SUCCESS) {
+                fprintf(stderr, "MMDB_dump_entry_data_list(): %s\n",
+                        MMDB_strerror(status));
+                fclose(fh);
+                MMDB_free_entry_data_list(entry_data_list);
+                return false;
+            }
+        }
+
+        MMDB_free_entry_data_list(entry_data_list);
+    }
+
+    clock_t const clock_diff = clock() - clock_start;
+    double const seconds = (double)clock_diff / CLOCKS_PER_SEC;
+
+    fprintf(
+        stdout,
+        "Looked up %llu addresses in %.2f seconds. %.2f lookups per second.\n",
+        i, seconds, i / seconds);
+
+    return true;
+}
+
+LOCAL int lookup_and_print(MMDB_s *mmdb, const char *ip_address,
+                           const char **lookup_path,
+                           int lookup_path_length,
+                           bool verbose)
+{
+
+    MMDB_lookup_result_s result = lookup_or_die(mmdb, ip_address);
+    MMDB_entry_data_list_s *entry_data_list = NULL;
+
+    int exit_code = 0;
+
+    if (verbose) {
+        fprintf(
+            stdout,
+            "\n  Record prefix length: %d\n",
+            result.netmask
+            );
+    }
+
+    if (result.found_entry) {
+        int status;
+        if (lookup_path_length) {
+            MMDB_entry_data_s entry_data;
+            status = MMDB_aget_value(&result.entry, &entry_data,
+                                     lookup_path);
+            if (MMDB_SUCCESS == status) {
+                if (entry_data.offset) {
+                    MMDB_entry_s entry =
+                    { .mmdb = mmdb, .offset = entry_data.offset };
+                    status = MMDB_get_entry_data_list(&entry,
+                                                      &entry_data_list);
+                } else {
+                    fprintf(
+                        stdout,
+                        "\n  No data was found at the lookup path you provided\n\n");
+                }
+            }
+        } else {
+            status = MMDB_get_entry_data_list(&result.entry,
+                                              &entry_data_list);
+        }
+
+        if (MMDB_SUCCESS != status) {
+            fprintf(stderr, "Got an error looking up the entry data - %s\n",
+                    MMDB_strerror(status));
+            exit_code = 5;
+            goto end;
+        }
+
+        if (NULL != entry_data_list) {
+            fprintf(stdout, "\n");
+            MMDB_dump_entry_data_list(stdout, entry_data_list, 2);
+            fprintf(stdout, "\n");
+        }
+    } else {
+        fprintf(stderr,
+                "\n  Could not find an entry for this IP address (%s)\n\n",
+                ip_address);
+        exit_code = 6;
+    }
+
+ end:
+    MMDB_free_entry_data_list(entry_data_list);
+    MMDB_close(mmdb);
+    free((void *)lookup_path);
+
+    return exit_code;
+}
+
+LOCAL int benchmark(MMDB_s *mmdb, int iterations)
+{
+    char ip_address[16];
+    int exit_code = 0;
+
+    clock_t time = clock();
+
+    for (int i = 0; i < iterations; i++) {
+        random_ipv4(ip_address);
+
+        MMDB_lookup_result_s result = lookup_or_die(mmdb, ip_address);
+        MMDB_entry_data_list_s *entry_data_list = NULL;
+
+        if (result.found_entry) {
+
+            int status = MMDB_get_entry_data_list(&result.entry,
+                                                  &entry_data_list);
+
+            if (MMDB_SUCCESS != status) {
+                fprintf(stderr, "Got an error looking up the entry data - %s\n",
+                        MMDB_strerror(status));
+                exit_code = 5;
+                MMDB_free_entry_data_list(entry_data_list);
+                goto end;
+            }
+        }
+
+        MMDB_free_entry_data_list(entry_data_list);
+    }
+
+    time = clock() - time;
+    double seconds = ((double)time / CLOCKS_PER_SEC);
+    fprintf(
+        stdout,
+        "\n  Looked up %i addresses in %.2f seconds. %.2f lookups per second.\n\n",
+        iterations, seconds, iterations / seconds);
+
+ end:
+    MMDB_close(mmdb);
+
+    return exit_code;
+}
+
+LOCAL MMDB_lookup_result_s lookup_or_die(MMDB_s *mmdb, const char *ipstr)
+{
+    int gai_error, mmdb_error;
+    MMDB_lookup_result_s result =
+        MMDB_lookup_string(mmdb, ipstr, &gai_error, &mmdb_error);
+
+    if (0 != gai_error) {
+        fprintf(stderr,
+                "\n  Error from call to getaddrinfo for %s - %s\n\n",
+                ipstr,
+#ifdef _WIN32
+                gai_strerrorA(gai_error)
+#else
+                gai_strerror(gai_error)
+#endif
+                );
+        exit(3);
+    }
+
+    if (MMDB_SUCCESS != mmdb_error) {
+        fprintf(stderr, "\n  Got an error from the maxminddb library: %s\n\n",
+                MMDB_strerror(mmdb_error));
+        exit(4);
+    }
+
+    return result;
+}
+
+LOCAL void random_ipv4(char *ip)
+{
+    // rand() is perfectly fine for this use case
+    // coverity[dont_call]
+    int ip_int = rand();
+    uint8_t *bytes = (uint8_t *)&ip_int;
+
+    snprintf(ip, 16, "%u.%u.%u.%u",
+             *bytes, *(bytes + 1), *(bytes + 2), *(bytes + 3));
+}
+
+#ifndef _WIN32
+struct thread_info {
+    pthread_t id;
+    int num;
+    MMDB_s *mmdb;
+    int iterations;
+};
+
+static bool start_threaded_benchmark(
+    MMDB_s *const mmdb,
+    int const thread_count,
+    int const iterations)
+{
+    struct thread_info *const tinfo = calloc(thread_count,
+                                             sizeof(struct thread_info));
+    if (!tinfo) {
+        fprintf(stderr, "calloc(): %s\n", strerror(errno));
+        return false;
+    }
+
+    // Using clock() isn't appropriate for multiple threads. It's CPU time, not
+    // wall time.
+    long double const start_time = get_time();
+    if (start_time == -1) {
+        free(tinfo);
+        return false;
+    }
+
+    for (int i = 0; i < thread_count; i++) {
+        tinfo[i].num = i;
+        tinfo[i].mmdb = mmdb;
+        tinfo[i].iterations = iterations;
+
+        if (pthread_create(&tinfo[i].id, NULL, &thread, &tinfo[i]) != 0) {
+            fprintf(stderr, "pthread_create() failed\n");
+            free(tinfo);
+            return false;
+        }
+    }
+
+    for (int i = 0; i < thread_count; i++) {
+        if (pthread_join(tinfo[i].id, NULL) != 0) {
+            fprintf(stderr, "pthread_join() failed\n");
+            free(tinfo);
+            return false;
+        }
+    }
+
+    free(tinfo);
+
+    long double const end_time = get_time();
+    if (end_time == -1) {
+        return false;
+    }
+
+    long double const elapsed = end_time - start_time;
+    unsigned long long const total_ips = iterations * thread_count;
+    long double rate = total_ips;
+    if (elapsed != 0) {
+        rate = total_ips / elapsed;
+    }
+
+    fprintf(
+        stdout,
+        "Looked up %llu addresses using %d threads in %.2Lf seconds. %.2Lf lookups per second.\n",
+        total_ips, thread_count, elapsed, rate);
+
+    return true;
+}
+
+static long double get_time(void)
+{
+    // clock_gettime() is not present on OSX until 10.12.
+#ifdef HAVE_CLOCK_GETTIME
+    struct timespec tp = {
+        .tv_sec  = 0,
+        .tv_nsec = 0,
+    };
+    clockid_t clk_id = CLOCK_REALTIME;
+#ifdef _POSIX_MONOTONIC_CLOCK
+    clk_id = CLOCK_MONOTONIC;
+#endif
+    if (clock_gettime(clk_id, &tp) != 0) {
+        fprintf(stderr, "clock_gettime(): %s\n", strerror(errno));
+        return -1;
+    }
+    return tp.tv_sec + ((float)tp.tv_nsec / 1e9);
+#else
+    time_t t = time(NULL);
+    if (t == (time_t)-1) {
+        fprintf(stderr, "time(): %s\n", strerror(errno));
+        return -1;
+    }
+    return (long double)t;
+#endif
+}
+
+static void *thread(void *arg)
+{
+    const struct thread_info *const tinfo = arg;
+    if (!tinfo) {
+        fprintf(stderr, "thread(): %s\n", strerror(EINVAL));
+        return NULL;
+    }
+
+    char ip_address[16] = { 0 };
+
+    for (int i = 0; i < tinfo->iterations; i++) {
+        memset(ip_address, 0, 16);
+        random_ipv4(ip_address);
+
+        MMDB_lookup_result_s result = lookup_or_die(tinfo->mmdb, ip_address);
+        if (!result.found_entry) {
+            continue;
+        }
+
+        MMDB_entry_data_list_s *entry_data_list = NULL;
+        int const status = MMDB_get_entry_data_list(&result.entry,
+                                                    &entry_data_list);
+        if (status != MMDB_SUCCESS) {
+            fprintf(stderr, "MMDB_get_entry_data_list(): %s\n",
+                    MMDB_strerror(status));
+            MMDB_free_entry_data_list(entry_data_list);
+            return NULL;
+        }
+
+        if (!entry_data_list) {
+            fprintf(stderr, "entry_data_list is NULL\n");
+            return NULL;
+        }
+
+        MMDB_free_entry_data_list(entry_data_list);
+    }
+
+    return NULL;
+}
+#endif
diff --git a/modules/service/vpn/dap_chain_net_srv_vpn_cdb_server_list.c b/modules/service/vpn/dap_chain_net_srv_vpn_cdb_server_list.c
index 571383aba425ba26a6097826dca39a1b6f73f2c1..e3c807118e9ea489ea2f5c3322bc8247b76f6778 100644
--- a/modules/service/vpn/dap_chain_net_srv_vpn_cdb_server_list.c
+++ b/modules/service/vpn/dap_chain_net_srv_vpn_cdb_server_list.c
@@ -27,6 +27,7 @@
 #include <time.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <stdint.h>
 #include <string.h>
 #include <unistd.h>
 
@@ -43,6 +44,7 @@
 #include "dap_chain_net_srv.h"
 #include "dap_chain_net_srv_vpn.h"
 #include "dap_chain_net_srv_order.h"
+#include "dap_chain_net_srv_geoip.h"
 
 #include "dap_http.h"
 #include "dap_http_simple.h"
@@ -154,6 +156,9 @@ static void s_http_simple_proc(dap_http_simple_t *a_http_simple, void *a_arg)
     dap_string_t *l_reply_str = dap_string_new("[\n");
 
 
+    char *l_client_ip = a_http_simple->http->client->s_ip;//"77.222.110.44"
+    geoip_info_t *l_geoip_info = chain_net_geoip_get_ip_info(l_client_ip);
+
     log_it(L_DEBUG, "Have %zd chain networks for cdb lists", s_cdb_net_count );
 
     for ( size_t i = 0; i < s_cdb_net_count ; i++ ) {
@@ -225,21 +230,47 @@ static void s_http_simple_proc(dap_http_simple_t *a_http_simple, void *a_arg)
                 }
             }
 
-            // random node
-            int l_count = 0;
-            while(l_orders_num > 0) {
-                // first random node
-                size_t k = rand() % l_orders_num;
-                dap_chain_net_srv_order_t *l_order = l_orders_pos[k];
-                if(!order_info_print(l_reply_str, l_net, l_order, "Auto", -1)){
-                    dap_string_append_printf(l_reply_str, ",\n");
-                    break;
-                }
-                if (l_count>20)
-                    break;
-                l_count++;
+            int8_t l_client_continent = l_geoip_info ? dap_chain_net_srv_order_continent_to_num(l_geoip_info->continent) : 0;
+            // random node on client's continent
+			if (l_client_continent) {
+				int l_count = 0;
+				while (l_orders_num > 0) {
+					size_t k = rand() % l_continents_numbers[l_client_continent];
+					dap_chain_net_srv_order_t *l_order = l_orders_pos[k];
+					const char *country_code = dap_chain_net_srv_order_get_country_code(l_order);
+					if (country_code) {
+						// only for other countries
+						if (dap_strcmp(l_geoip_info->country_code, country_code)){
+							if (!order_info_print(l_reply_str, l_net, l_order, "Auto", -1)) {
+								dap_string_append_printf(l_reply_str, ",\n");
+								break;
+							}
+						}
+					}
+					if (l_count > 20)
+						break;
+					l_count++;
+				}
+
+			}
+			// random node for the whole world
+			else {
+				int l_count = 0;
+				while(l_orders_num > 0) {
+					// first random node
+					size_t k = rand() % l_orders_num;
+					dap_chain_net_srv_order_t *l_order = l_orders_pos[k];
+					if(!order_info_print(l_reply_str, l_net, l_order, "Auto", -1)){
+						dap_string_append_printf(l_reply_str, ",\n");
+						break;
+					}
+					if (l_count>20)
+						break;
+					l_count++;
+				}
             }
             // random nodes for continents
+            int l_count = 0;
             for(size_t l_c = 0; l_c < l_continents_count; l_c++) {
                 while(l_continents_numbers[l_c] > 0) {
                     // random node for continent
@@ -290,6 +321,7 @@ static void s_http_simple_proc(dap_http_simple_t *a_http_simple, void *a_arg)
             }
         }
     }
+    DAP_DELETE(l_geoip_info);
     dap_string_append_printf( l_reply_str, "]\n\n");
     dap_http_simple_reply( a_http_simple, l_reply_str->str, l_reply_str->len );
     dap_string_free(l_reply_str, true);