From b79d87baf8b40a6d86e0864f269ed50e4ed4199f Mon Sep 17 00:00:00 2001
From: Aleksandr Lysikov <lysikov@inbox.ru>
Date: Mon, 8 Apr 2019 12:23:43 +0500
Subject: [PATCH] added support for binary data

---
 dap_chain_global_db.c        |  78 ++++++++++++-------
 dap_chain_global_db.h        |  12 ++-
 dap_chain_global_db_hist.c   |  44 ++++++++++-
 dap_chain_global_db_pvt.c    | 141 ++++++++++++++++++++---------------
 dap_chain_global_db_pvt.h    |   9 ++-
 dap_chain_global_db_remote.c |  35 +++++++++
 dap_chain_global_db_remote.h |  10 +++
 7 files changed, 234 insertions(+), 95 deletions(-)
 create mode 100644 dap_chain_global_db_remote.c
 create mode 100644 dap_chain_global_db_remote.h

diff --git a/dap_chain_global_db.c b/dap_chain_global_db.c
index 83710a1..8d365ed 100644
--- a/dap_chain_global_db.c
+++ b/dap_chain_global_db.c
@@ -2,8 +2,11 @@
 #include <stdio.h>
 #include <stdint.h>
 #include <pthread.h>
+#include <time.h>
+
 #include "dap_hash.h"
 #include "dap_chain_common.h"
+#include "dap_strfuncs.h"
 #include "dap_chain_global_db_pvt.h"
 #include "dap_chain_global_db_hist.h"
 #include "dap_chain_global_db.h"
@@ -16,7 +19,7 @@ static pthread_mutex_t ldb_mutex = PTHREAD_MUTEX_INITIALIZER;
  */
 static bool is_local_group(const char *a_group)
 {
-    if(!strcmp(a_group, GROUP_ALIAS))
+    if(!strcmp(a_group, GROUP_ALIAS) || !strcmp(a_group, GROUP_REMOTE_NODE))
         return true;
     return false;
 }
@@ -79,8 +82,8 @@ void dap_chain_global_db_deinit(void)
 /**
  * Get entry from base
  *
+ * return dap_store_obj_t*
  */
-
 void* dap_chain_global_db_obj_get(const char *a_key, const char *a_group)
 {
     int count = 0;
@@ -90,46 +93,52 @@ void* dap_chain_global_db_obj_get(const char *a_key, const char *a_group)
     char *query = DAP_NEW_Z_SIZE(char, query_len + 1); //char query[32 + strlen(a_key)];
     snprintf(query, query_len + 1, "(&(cn=%s)(objectClass=%s))", a_key, a_group); // objectClass != ou
     pthread_mutex_lock(&ldb_mutex);
-    pdap_store_obj_t store_data = dap_db_read_data(query, &count, a_group);
+    dap_store_obj_t *store_data = dap_db_read_data(query, &count, a_group);
     pthread_mutex_unlock(&ldb_mutex);
     assert(count <= 1);
     DAP_DELETE(query);
     return store_data;
 }
 
-char * dap_chain_global_db_gr_get(const char *a_key, const char *a_group)
+uint8_t * dap_chain_global_db_gr_get(const char *a_key, size_t *a_data_out, const char *a_group)
 {
-    char *str = NULL;
-    int count = 0;
+    uint8_t *l_ret_value = NULL;
+    int l_count = 0;
     if(!a_key)
         return NULL;
-    size_t query_len = snprintf(NULL, 0, "(&(cn=%s)(objectClass=%s))", a_key, a_group);
-    char *query = DAP_NEW_Z_SIZE(char, query_len + 1); //char query[32 + strlen(a_key)];
-    snprintf(query, query_len + 1, "(&(cn=%s)(objectClass=%s))", a_key, a_group); // objectClass != ou
+    size_t l_query_len = snprintf(NULL, 0, "(&(cn=%s)(objectClass=%s))", a_key, a_group);
+    char *l_query = DAP_NEW_Z_SIZE(char, l_query_len + 1); //char query[32 + strlen(a_key)];
+    snprintf(l_query, l_query_len + 1, "(&(cn=%s)(objectClass=%s))", a_key, a_group); // objectClass != ou
     pthread_mutex_lock(&ldb_mutex);
-    pdap_store_obj_t store_data = dap_db_read_data(query, &count, a_group);
+    pdap_store_obj_t store_data = dap_db_read_data(l_query, &l_count, a_group);
     pthread_mutex_unlock(&ldb_mutex);
-    if(count == 1 && store_data && !strcmp(store_data->key, a_key))
-        str = (store_data->value) ? strdup(store_data->value) : NULL;
-    dab_db_free_pdap_store_obj_t(store_data, count);
-    DAP_DELETE(query);
-    return str;
+    if(l_count == 1 && store_data && !strcmp(store_data->key, a_key)) {
+        l_ret_value = (store_data->value) ? DAP_NEW_SIZE(uint8_t, store_data->value_len) : NULL; //ret_value = (store_data->value) ? strdup(store_data->value) : NULL;
+        memcpy(l_ret_value, store_data->value, store_data->value_len);
+        if(a_data_out)
+            *a_data_out = store_data->value_len;
+    }
+    dab_db_free_pdap_store_obj_t(store_data, l_count);
+    DAP_DELETE(l_query);
+    return l_ret_value;
 }
 
-char * dap_chain_global_db_get(const char *a_key)
+uint8_t * dap_chain_global_db_get(const char *a_key, size_t *a_data_out)
 {
-    return dap_chain_global_db_gr_get(a_key, GROUP_NAME_DEFAULT);
+    return dap_chain_global_db_gr_get(a_key, a_data_out, GROUP_NAME_DEFAULT);
 }
 
 /**
  * Set one entry to base
  */
-bool dap_chain_global_db_gr_set(const char *a_key, const char *a_value, const char *a_group)
+bool dap_chain_global_db_gr_set(const char *a_key, const uint8_t *a_value, size_t a_value_len, const char *a_group)
 {
     pdap_store_obj_t store_data = DAP_NEW_Z_SIZE(dap_store_obj_t, sizeof(struct dap_store_obj));
     store_data->key = (char*) a_key;
     store_data->value = (char*) a_value;
+    store_data->value_len = (a_value_len == (size_t) -1) ? strlen(a_value) : a_value_len;
     store_data->group = (char*) a_group;
+    store_data->timestamp = time(NULL);
     pthread_mutex_lock(&ldb_mutex);
     int res = dap_db_add(store_data, 1);
     if(!res && !is_local_group(a_group))
@@ -141,9 +150,9 @@ bool dap_chain_global_db_gr_set(const char *a_key, const char *a_value, const ch
     return false;
 }
 
-bool dap_chain_global_db_set(const char *a_key, const char *a_value)
+bool dap_chain_global_db_set(const char *a_key, const uint8_t *a_value, size_t a_value_len)
 {
-    return dap_chain_global_db_gr_set(a_key, a_value, GROUP_NAME_DEFAULT);
+    return dap_chain_global_db_gr_set(a_key, a_value, a_value_len, GROUP_NAME_DEFAULT);
 }
 /**
  * Delete entry from base
@@ -189,7 +198,7 @@ dap_global_db_obj_t** dap_chain_global_db_gr_load(const char *a_group, size_t *a
     pthread_mutex_unlock(&ldb_mutex);
     DAP_DELETE(query);
     // Serialization data
-    dap_store_obj_pkt_t *pkt = dap_store_packet_multiple(store_obj, count);
+    dap_store_obj_pkt_t *pkt = dap_store_packet_multiple(store_obj, 0, count);
     dab_db_free_pdap_store_obj_t(store_obj, count);
     if(pkt)
     {
@@ -228,10 +237,26 @@ dap_global_db_obj_t** dap_chain_global_db_load(size_t *a_data_size_out)
  */
 bool dap_chain_global_db_obj_save(void* a_store_data, size_t a_objs_count)
 {
-    dap_store_obj_t* l_store_data = (dap_store_obj_t*)a_store_data;
-    if(l_store_data && a_objs_count>0)
-    {
+    dap_store_obj_t* l_store_data = (dap_store_obj_t*) a_store_data;
+    if(l_store_data && a_objs_count > 0) {
         const char *l_group = l_store_data[0].group;
+        // read data
+        for(int i = 0; i < a_objs_count; i++) {
+            dap_store_obj_t* l_obj = l_store_data + i;
+            int l_count = 0;
+            char *l_query = dap_strdup_printf("(&(cn=%s)(objectClass=%s))", l_obj->key, l_obj->group);
+            pthread_mutex_lock(&ldb_mutex);
+            dap_store_obj_t *l_read_store_data = dap_db_read_data(l_query, &l_count, l_group);
+            pthread_mutex_unlock(&ldb_mutex);
+            // don't save obj if (present timestamp) > (new timestamp)
+            if(l_read_store_data && l_count == 1 && l_read_store_data->timestamp > l_obj->timestamp){
+                // mark to not save
+                l_obj->timestamp = (time_t)-1;
+            }
+            DAP_DELETE(l_query);
+        }
+
+        // save data
         pthread_mutex_lock(&ldb_mutex);
         int res = dap_db_add(l_store_data, a_objs_count);
         if(!res && !is_local_group(l_group))
@@ -252,12 +277,15 @@ bool dap_chain_global_db_gr_save(dap_global_db_obj_t* a_objs, size_t a_objs_coun
     //pdap_store_obj_t store_data = dap_store_unpacket(pkt, &count);
     //DAP_DELETE(pkt);
     dap_store_obj_t *store_data = DAP_NEW_Z_SIZE(dap_store_obj_t, a_objs_count * sizeof(struct dap_store_obj));
+    time_t l_timestamp = time(NULL);
     for(size_t q = 0; q < a_objs_count; ++q) {
         dap_store_obj_t *store_data_cur = store_data + q;
         dap_global_db_obj_t *a_obj_cur = a_objs + q;
         store_data_cur->key = a_obj_cur->key;
-        store_data_cur->value = a_obj_cur->value;
         store_data_cur->group = (char*) a_group;
+        store_data_cur->value = a_obj_cur->value;
+        store_data_cur->value_len = a_obj_cur->value_len;
+        store_data_cur->timestamp = l_timestamp;
     }
     if(store_data)
     {
diff --git a/dap_chain_global_db.h b/dap_chain_global_db.h
index ef3ab4e..cfcd953 100644
--- a/dap_chain_global_db.h
+++ b/dap_chain_global_db.h
@@ -10,12 +10,14 @@
 #define GROUP_NODE "addrs_leased"
 #define GROUP_ALIAS "aliases_leased"
 #define GROUP_DATUM "datums"
+#define GROUP_REMOTE_NODE "remote_node"
 
 #define GROUP_NAME_DEFAULT GROUP_DATUM
 
 typedef struct dap_global_db_obj {
     char *key;
     char *value;
+    size_t value_len;
 }DAP_ALIGN_PACKED dap_global_db_obj_t, *pdap_global_db_obj_t;
 
 /**
@@ -40,14 +42,14 @@ void dap_chain_global_db_deinit();
  * Get entry from base
  */
 void* dap_chain_global_db_obj_get(const char *a_key, const char *a_group);
-char * dap_chain_global_db_gr_get(const char *a_key, const char *a_group);
-char* dap_chain_global_db_get(const char *a_key);
+uint8_t * dap_chain_global_db_gr_get(const char *a_key, size_t *a_data_out, const char *a_group);
+uint8_t * dap_chain_global_db_get(const char *a_key, size_t *a_data_out);
 
 /**
  * Set one entry to base
  */
-bool dap_chain_global_db_gr_set(const char *a_key, const char *a_value, const char *a_group);
-bool dap_chain_global_db_set(const char *a_key, const char *a_value);
+bool dap_chain_global_db_gr_set(const char *a_key, const uint8_t *a_value, size_t a_value_len, const char *a_group);
+bool dap_chain_global_db_set(const char *a_key, const uint8_t *a_value, size_t a_value_len);
 
 /**
  * Delete entry from base
@@ -85,6 +87,8 @@ char* dap_chain_global_db_hash_fast(const uint8_t *data, size_t data_size);
 uint8_t* dap_db_log_pack(dap_global_db_obj_t *a_obj, int *a_data_size_out);
 // Parse data from dap_db_log_pack()
 void* dap_db_log_unpack(uint8_t *a_data, int a_data_size, int *a_store_obj_count);
+// Get timestamp from dap_db_log_pack()
+time_t dap_db_log_unpack_get_timestamp(uint8_t *a_data, int a_data_size);
 
 // Get last timestamp in log
 char *dap_db_log_get_last_timestamp(void);
diff --git a/dap_chain_global_db_hist.c b/dap_chain_global_db_hist.c
index 9ef5a98..795c97e 100644
--- a/dap_chain_global_db_hist.c
+++ b/dap_chain_global_db_hist.c
@@ -1,5 +1,6 @@
 #include <string.h>
 #include <stdlib.h>
+#include <time.h>
 
 #include <dap_common.h>
 #include <dap_strfuncs.h>
@@ -50,6 +51,8 @@ uint8_t* dap_db_log_pack(dap_global_db_obj_t *a_obj, int *a_data_size_out)
     dap_global_db_hist_t l_rec;
     if(dap_db_history_unpack_hist(a_obj->value, &l_rec) == -1)
         return NULL;
+    time_t l_timestamp = strtoll(a_obj->key, NULL, 10);
+
     // parse global_db records in a history record
     char **l_keys = dap_strsplit(l_rec.keys, HIST_KEY_SEPARATOR, -1);
     int l_count = dap_str_countv(l_keys);
@@ -64,7 +67,7 @@ uint8_t* dap_db_log_pack(dap_global_db_obj_t *a_obj, int *a_data_size_out)
         i++;
     };
     // serialize data
-    dap_store_obj_pkt_t *l_data_out = dap_store_packet_multiple(l_store_obj, l_count);
+    dap_store_obj_pkt_t *l_data_out = dap_store_packet_multiple(l_store_obj, l_timestamp, l_count);
 
     dab_db_free_pdap_store_obj_t(l_store_obj, l_count);
     dap_strfreev(l_keys);
@@ -93,6 +96,17 @@ void* dap_db_log_unpack(uint8_t *a_data, int a_data_size, int *a_store_obj_count
     return l_obj;
 }
 
+/**
+ * Get timestamp from dap_db_log_pack()
+ */
+time_t dap_db_log_unpack_get_timestamp(uint8_t *a_data, int a_data_size)
+{
+    dap_store_obj_pkt_t *l_pkt = (dap_store_obj_pkt_t*) a_data;
+    if(!l_pkt || l_pkt->data_size != (a_data_size - sizeof(dap_store_obj_pkt_t)))
+        return 0;
+    return l_pkt->timestamp;
+}
+
 /**
  * Add data to the history log
  */
@@ -103,16 +117,19 @@ bool dap_db_history_add(char a_type, pdap_store_obj_t a_store_obj, int a_dap_sto
     dap_global_db_hist_t l_rec;
     l_rec.keys_count = a_dap_store_count;
     l_rec.type = a_type;
-    // TODO Make for keys_count>1
+    // group name should be always the same
     if(l_rec.keys_count >= 1)
         l_rec.group = a_store_obj->group;
     if(l_rec.keys_count == 1)
         l_rec.keys = a_store_obj->key;
     else {
         // make keys vector
-        char **l_keys = DAP_NEW_SIZE(char*, sizeof(char*) * (a_dap_store_count + 1));
+        char **l_keys = DAP_NEW_Z_SIZE(char*, sizeof(char*) * (a_dap_store_count + 1));
         int i;
         for(i = 0; i < a_dap_store_count; i++) {
+            // if it is marked, the data has not been saved
+            if(a_store_obj[i].timestamp == (time_t) -1)
+                continue;
             l_keys[i] = a_store_obj[i].key;
         }
         l_keys[i] = NULL;
@@ -127,7 +144,9 @@ bool dap_db_history_add(char a_type, pdap_store_obj_t a_store_obj, int a_dap_sto
     // value - keys of added/deleted data
     l_store_data.key = dap_db_history_timestamp();
     l_store_data.value = (char*) l_str;
+    l_store_data.value_len = l_str_len;
     l_store_data.group = GROUP_HISTORY;
+    l_store_data.timestamp = time(NULL);
     int l_res = dap_db_add(&l_store_data, 1);
     if(l_rec.keys_count > 1)
         DAP_DELETE(l_rec.keys);
@@ -168,6 +187,14 @@ char *dap_db_log_get_last_timestamp(void)
     return l_ret_str;
 }
 
+static int compare_items(const void * l_a, const void * l_b)
+{
+    dap_global_db_obj_t *l_item_a = (dap_global_db_obj_t*) l_a;
+    dap_global_db_obj_t *l_item_b = (dap_global_db_obj_t*) l_b;
+    int l_ret = strcmp(l_item_a->key, l_item_b->key);
+    return l_ret;
+}
+
 /**
  * Get log diff as list
  */
@@ -186,6 +213,17 @@ dap_list_t* dap_db_log_get_list(time_t first_timestamp)
             l_list = dap_list_append(l_list, l_item);
         }
     }
+    // sort list by key (time str)
+    dap_list_sort(l_list, (DapCompareFunc) compare_items);
+
+    /*/ dbg - sort result
+     l_data_size_out = dap_list_length(l_list);
+     for(size_t i = 0; i < l_data_size_out; i++) {
+     dap_list_t *l_list_tmp = dap_list_nth(l_list, i);
+     dap_global_db_obj_t *l_item = l_list_tmp->data;
+     printf("2 %d %s\n", i, l_item->key);
+     }*/
+
     DAP_DELETE(l_first_key);
     dap_chain_global_db_objs_delete(l_objs);
     return l_list;
diff --git a/dap_chain_global_db_pvt.c b/dap_chain_global_db_pvt.c
index 7a6cac6..d65269d 100644
--- a/dap_chain_global_db_pvt.c
+++ b/dap_chain_global_db_pvt.c
@@ -1,6 +1,8 @@
 #include <string.h>
 #include <stdio.h>
 #include <assert.h>
+#include <time.h>
+
 //#include "talloc.h"
 #include "dap_chain_global_db_pvt.h"
 
@@ -194,7 +196,15 @@ pdap_store_obj_t dap_db_read_data(const char *query, int *count, const char *gro
         store_data[q].group = strdup(ldb_msg_find_attr_as_string(data_message->msgs[q], "objectClass", "")); //strdup(group);
         store_data[q].type = 1;
         store_data[q].key = strdup(ldb_msg_find_attr_as_string(data_message->msgs[q], "cn", ""));
-        store_data[q].value = strdup(ldb_msg_find_attr_as_string(data_message->msgs[q], "time", ""));
+        store_data[q].timestamp = ldb_msg_find_attr_as_uint64(data_message->msgs[q], "time", 5);
+        const struct ldb_val *l_val = ldb_msg_find_ldb_val(data_message->msgs[q], "time");
+
+        l_val = ldb_msg_find_ldb_val(data_message->msgs[q], "val");
+        if(l_val) {
+            store_data[q].value_len = l_val->length;
+            store_data[q].value = DAP_NEW_SIZE(uint8_t, l_val->length);
+            memcpy(store_data[q].value, l_val->data, l_val->length);
+        }
         log_it(L_INFO, "Record %s read successfully", ldb_dn_get_linearized(data_message->msgs[q]->dn));
     }
     talloc_free(data_message);
@@ -272,7 +282,7 @@ pdap_store_obj_t dap_db_read_file_data(const char *path, const char *group)
  */
 int dap_db_add(pdap_store_obj_t a_store_obj, int a_store_count)
 {
-    int ret = 0;
+    int l_ret = 0;
     if(a_store_obj == NULL) {
         log_it(L_ERROR, "Invalid Dap store objects passed");
         return -1;
@@ -281,15 +291,20 @@ int dap_db_add(pdap_store_obj_t a_store_obj, int a_store_count)
         log_it(L_ERROR, "Couldn't connect to database");
         return -2;
     }
-    log_it(L_INFO, "We're about to put %d records into database", a_store_count);
-    struct ldb_message *msg;
+    //log_it(L_INFO, "We're about to put %d records into database", a_store_count);
+    struct ldb_message *l_msg;
     int q;
     if(a_store_count == 0) {
         a_store_count = 1;
     }
     for(q = 0; q < a_store_count; q++) {
         // level 3: leased address, single whitelist entity
-        msg = ldb_msg_new(ldb);
+
+        // if it is marked, don't save
+        if(a_store_obj[q].timestamp == (time_t) -1)
+            continue;
+
+        l_msg = ldb_msg_new(ldb);
         char dn[256];
         memset(dn, '\0', 256);
         strcat(dn, "cn=");
@@ -298,17 +313,28 @@ int dap_db_add(pdap_store_obj_t a_store_obj, int a_store_count)
         strcat(dn, ",ou=");
         strcat(dn, a_store_obj[q].group);
         strcat(dn, ",dc=kelvin_nodes");
-        msg->dn = ldb_dn_new(mem_ctx, ldb, dn);
-        ldb_msg_add_string(msg, "cn", a_store_obj[q].key);
-        ldb_msg_add_string(msg, "objectClass", a_store_obj[q].group);
-        ldb_msg_add_string(msg, "section", "kelvin_nodes");
-        ldb_msg_add_string(msg, "description", "Approved Kelvin node");
-        ldb_msg_add_string(msg, "time", a_store_obj[q].value);
-        ret += dap_db_add_msg(msg); // accumulation error codes
-        talloc_free(msg->dn);
-        talloc_free(msg);
+        l_msg->dn = ldb_dn_new(mem_ctx, ldb, dn);
+        int l_res = ldb_msg_add_string(l_msg, "cn", a_store_obj[q].key);
+        ldb_msg_add_string(l_msg, "objectClass", a_store_obj[q].group);
+        ldb_msg_add_string(l_msg, "section", "kelvin_nodes");
+        ldb_msg_add_string(l_msg, "description", "Approved Kelvin node");
+
+        struct ldb_val l_val;
+        struct ldb_message_element *return_el;
+        l_val.data = (uint8_t*) &a_store_obj[q].timestamp;
+        l_val.length = sizeof(time_t);
+        l_res = ldb_msg_add_value(l_msg, "time", &l_val, &return_el);
+        ldb_msg_remove_element(l_msg, return_el);
+
+        l_val.data = a_store_obj[q].value;
+        l_val.length = a_store_obj[q].value_len;
+        l_res = ldb_msg_add_steal_value(l_msg, "val", &l_val);
+
+        l_ret += dap_db_add_msg(l_msg); // accumulation error codes
+        talloc_free(l_msg->dn);
+        talloc_free(l_msg);
     }
-    return ret;
+    return l_ret;
 }
 
 /*
@@ -327,7 +353,7 @@ int dap_db_delete(pdap_store_obj_t store_obj, int a_store_count)
         log_it(L_ERROR, "Couldn't connect to database");
         return -2;
     }
-    log_it(L_INFO, "We're delete %d records from database", a_store_count);
+    //log_it(L_INFO, "We're delete %d records from database", a_store_count);
     struct ldb_message *msg;
     int q;
     if(a_store_count == 0) {
@@ -376,60 +402,55 @@ static size_t dap_db_get_size_pdap_store_obj_t(pdap_store_obj_t store_obj)
 
 /**
  * serialization
- * @param store_obj_count count of structures store_obj
- * @param size_out[out] size of output structure
+ * @param a_store_obj_count count of structures store_obj
+ * @param a_timestamp create data time
+ * @param a_size_out[out] size of output structure
  * @return NULL in case of an error
  */
-dap_store_obj_pkt_t *dap_store_packet_multiple(pdap_store_obj_t store_obj, int store_obj_count)
+dap_store_obj_pkt_t *dap_store_packet_multiple(pdap_store_obj_t a_store_obj, time_t a_timestamp, int a_store_obj_count)
 {
-    if(!store_obj || store_obj_count < 1)
+    if(!a_store_obj || a_store_obj_count < 1)
         return NULL;
-    size_t data_size_out = sizeof(uint32_t); // size of output data
-    int q;
+    size_t l_data_size_out = sizeof(uint32_t); // size of output data
+    int l_q;
     // calculate output structure size
-    for(q = 0; q < store_obj_count; ++q)
-        data_size_out += dap_db_get_size_pdap_store_obj_t(&store_obj[q]);
-
-    dap_store_obj_pkt_t *pkt = DAP_NEW_Z_SIZE(dap_store_obj_pkt_t, sizeof(dap_store_obj_pkt_t) + data_size_out);
-    /*    pkt->grp_size = strlen(store_obj[0].group) + 1;
-     pkt->name_size = strlen(store_obj[0].key) + 1; // useless here since it can differ from one store_obj to another
-     pkt->sec_size = strlen(store_obj[0].section) + 1;
-     pkt->type = store_obj[0].type;
-     memcpy(pkt->data, &store_obj[0].section, pkt->sec_size);
-     memcpy(pkt->data + pkt->sec_size, &store_obj[0].group, pkt->grp_size);
-     uint64_t offset = pkt->sec_size + pkt->grp_size;*/
-    pkt->data_size = data_size_out;
-    uint64_t offset = 0;
-    uint32_t count = (uint32_t) store_obj_count;
-    memcpy(pkt->data + offset, &count, sizeof(uint32_t));
-    offset += sizeof(uint32_t);
-    for(q = 0; q < store_obj_count; ++q) {
-        dap_store_obj_t obj = store_obj[q];
+    for(l_q = 0; l_q < a_store_obj_count; ++l_q)
+        l_data_size_out += dap_db_get_size_pdap_store_obj_t(&a_store_obj[l_q]);
+
+    dap_store_obj_pkt_t *l_pkt = DAP_NEW_Z_SIZE(dap_store_obj_pkt_t, sizeof(dap_store_obj_pkt_t) + l_data_size_out);
+    l_pkt->data_size = l_data_size_out;
+    l_pkt->timestamp = a_timestamp;
+    uint64_t l_offset = 0;
+    uint32_t l_count = (uint32_t) a_store_obj_count;
+    memcpy(l_pkt->data + l_offset, &l_count, sizeof(uint32_t));
+    l_offset += sizeof(uint32_t);
+    for(l_q = 0; l_q < a_store_obj_count; ++l_q) {
+        dap_store_obj_t obj = a_store_obj[l_q];
         uint16_t section_size = (uint16_t) strlen(obj.section);
         uint16_t group_size = (uint16_t) strlen(obj.group);
         uint16_t key_size = (uint16_t) strlen(obj.key);
         uint16_t value_size = (uint16_t) strlen(obj.value);
-        memcpy(pkt->data + offset, &obj.type, sizeof(int));
-        offset += sizeof(int);
-        memcpy(pkt->data + offset, &section_size, sizeof(uint16_t));
-        offset += sizeof(uint16_t);
-        memcpy(pkt->data + offset, obj.section, section_size);
-        offset += section_size;
-        memcpy(pkt->data + offset, &group_size, sizeof(uint16_t));
-        offset += sizeof(uint16_t);
-        memcpy(pkt->data + offset, obj.group, group_size);
-        offset += group_size;
-        memcpy(pkt->data + offset, &key_size, sizeof(uint16_t));
-        offset += sizeof(uint16_t);
-        memcpy(pkt->data + offset, obj.key, key_size);
-        offset += key_size;
-        memcpy(pkt->data + offset, &value_size, sizeof(uint16_t));
-        offset += sizeof(uint16_t);
-        memcpy(pkt->data + offset, obj.value, value_size);
-        offset += value_size;
+        memcpy(l_pkt->data + l_offset, &obj.type, sizeof(int));
+        l_offset += sizeof(int);
+        memcpy(l_pkt->data + l_offset, &section_size, sizeof(uint16_t));
+        l_offset += sizeof(uint16_t);
+        memcpy(l_pkt->data + l_offset, obj.section, section_size);
+        l_offset += section_size;
+        memcpy(l_pkt->data + l_offset, &group_size, sizeof(uint16_t));
+        l_offset += sizeof(uint16_t);
+        memcpy(l_pkt->data + l_offset, obj.group, group_size);
+        l_offset += group_size;
+        memcpy(l_pkt->data + l_offset, &key_size, sizeof(uint16_t));
+        l_offset += sizeof(uint16_t);
+        memcpy(l_pkt->data + l_offset, obj.key, key_size);
+        l_offset += key_size;
+        memcpy(l_pkt->data + l_offset, &value_size, sizeof(uint16_t));
+        l_offset += sizeof(uint16_t);
+        memcpy(l_pkt->data + l_offset, obj.value, value_size);
+        l_offset += value_size;
     }
-    assert(data_size_out == offset);
-    return pkt;
+    assert(l_data_size_out == l_offset);
+    return l_pkt;
 }
 /**
  * deserialization
diff --git a/dap_chain_global_db_pvt.h b/dap_chain_global_db_pvt.h
index f9c9952..655e361 100644
--- a/dap_chain_global_db_pvt.h
+++ b/dap_chain_global_db_pvt.h
@@ -5,11 +5,13 @@
 #include "ldb.h"
 
 typedef struct dap_store_obj {
+    time_t timestamp;
+    uint8_t type;
     char *section;
     char *group;
     char *key;
-    uint8_t type;
-    char *value;
+    uint8_t *value;
+    size_t value_len;
 }DAP_ALIGN_PACKED dap_store_obj_t, *pdap_store_obj_t;
 
 typedef struct dap_store_obj_pkt {
@@ -17,6 +19,7 @@ typedef struct dap_store_obj_pkt {
      uint8_t sec_size;
      uint8_t grp_size;
      uint8_t name_size;*/
+    time_t timestamp;
     size_t data_size;
     uint8_t data[];
 }__attribute__((packed)) dap_store_obj_pkt_t;
@@ -30,7 +33,7 @@ int dap_db_delete(pdap_store_obj_t a_store_obj, int a_store_count);
 pdap_store_obj_t dap_db_read_data(const char *a_query, int *a_count, const char *a_group);
 pdap_store_obj_t dap_db_read_file_data(const char *a_path, const char *a_group); // state of emergency only, if LDB database is inaccessible
 dap_store_obj_pkt_t *dap_store_packet_single(pdap_store_obj_t a_store_obj);
-dap_store_obj_pkt_t *dap_store_packet_multiple(pdap_store_obj_t a_store_obj, int a_store_obj_count);
+dap_store_obj_pkt_t *dap_store_packet_multiple(pdap_store_obj_t a_store_obj, time_t a_timestamp, int a_store_obj_count);
 dap_store_obj_t *dap_store_unpacket(dap_store_obj_pkt_t *a_pkt, int *a_store_obj_count);
 
 void dab_db_free_pdap_store_obj_t(pdap_store_obj_t a_store_data, int a_count);
diff --git a/dap_chain_global_db_remote.c b/dap_chain_global_db_remote.c
new file mode 100644
index 0000000..69b64ec
--- /dev/null
+++ b/dap_chain_global_db_remote.c
@@ -0,0 +1,35 @@
+#include <string.h>
+#include <stdlib.h>
+
+//#include <dap_common.h>
+#include <dap_strfuncs.h>
+#include "dap_chain_global_db.h"
+#include "dap_chain_global_db_remote.h"
+
+/**
+ * Set last timestamp for remote node
+ */
+bool dap_db_log_set_last_timestamp_remote(uint64_t a_node_addr, time_t a_timestamp)
+{
+    dap_global_db_obj_t l_objs;
+    l_objs.key = dap_strdup_printf("%lld", a_node_addr);
+    l_objs.value = dap_strdup_printf("%lld", a_timestamp);
+    bool l_ret = dap_chain_global_db_gr_save(&l_objs, 1, GROUP_REMOTE_NODE);
+    DAP_DELETE(l_objs.key);
+    DAP_DELETE(l_objs.value);
+    return l_ret;
+}
+
+/**
+ * Get last timestamp for remote node
+ */
+time_t dap_db_log_get_last_timestamp_remote(uint64_t a_node_addr)
+{
+    char *l_node_addr_str = dap_strdup_printf("%lld", a_node_addr);
+    size_t l_node_addr_str_len = 0;
+    char *l_timestamp_str = dap_chain_global_db_gr_get((const char*) l_node_addr_str, &l_node_addr_str_len, GROUP_REMOTE_NODE);
+    time_t l_ret_timestamp = (l_timestamp_str) ? strtoll(l_timestamp_str, NULL,10) : 0;
+    DAP_DELETE(l_node_addr_str);
+    DAP_DELETE(l_timestamp_str);
+    return l_ret_timestamp;
+}
diff --git a/dap_chain_global_db_remote.h b/dap_chain_global_db_remote.h
new file mode 100644
index 0000000..292b871
--- /dev/null
+++ b/dap_chain_global_db_remote.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <stdbool.h>
+#include <time.h>
+
+// Set last timestamp for remote node
+bool dap_db_log_set_last_timestamp_remote(uint64_t a_node_addr, time_t a_timestamp);
+// Get last timestamp for remote node
+time_t dap_db_log_get_last_timestamp_remote(uint64_t a_node_addr);
+
-- 
GitLab