Browse Source

ntp: use NTP-corrected time everywhere instead of unixepoch()/gettimeofday

- db_sync_next_timestamp(): ntp_time_get_us() via si->db_sync->inst
- topo_node_sqlite_node_put: +time_t now_sec param, replace unixepoch()
- topo_node_sqlite_node_update_verified: +time_t created_at param
- topo_node_sqlite_member_put: joined_at = join_ts (already NTP-corrected)
- All INSERTs (nodes, local_identity, accounts, peers):
  replace DEFAULT(unixepoch()) with explicit ntp_time_get_seconds()
- ON CONFLICT DO UPDATE: created_at = COALESCE(nodes.created_at, excluded.created_at)
- Updated callers: topo_group.c, chat_sync.c, chat_conn_mgr.c, chat_core.c
topo_upd
Evgeny 2 months ago
parent
commit
df809f251c
  1. 12
      src/db_sync.c
  2. 2
      src/routing_layer/topo_group.c
  3. 35
      src/routing_layer/topo_node_sqlite.c
  4. 5
      src/routing_layer/topo_node_sqlite.h
  5. 6
      tools/chatgui/transport/chat_conn_mgr.c
  6. 17
      tools/chatgui/transport/chat_core.c
  7. 26
      tools/chatgui/transport/chat_sync.c

12
src/db_sync.c

@ -1587,9 +1587,15 @@ uint64_t db_sync_get_last_timestamp(struct DB_SYNC_INSTANCE* si)
uint64_t db_sync_next_timestamp(struct DB_SYNC_INSTANCE* si)
{
if (!si) return 0;
struct timeval tv;
utun_gettimeofday(&tv, NULL);
uint64_t nu = (uint64_t)tv.tv_sec * 1000ULL + (uint64_t)tv.tv_usec / 1000ULL;
int64_t now_us;
if (si->db_sync && si->db_sync->inst && si->db_sync->inst->ntp.synced)
now_us = ntp_time_get_us(si->db_sync->inst);
else {
struct timeval tv;
utun_gettimeofday(&tv, NULL);
now_us = (int64_t)tv.tv_sec * 1000000LL + tv.tv_usec;
}
uint64_t nu = (uint64_t)(now_us / 1000LL);
if (nu <= si->last_timestamp_ms) nu = si->last_timestamp_ms + 1;
si->last_timestamp_ms = nu;
return nu;

2
src/routing_layer/topo_group.c

@ -581,7 +581,7 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from
if (node_id != group->instance->node_id) {
sqlite3* sdb = group->instance->topo_sqlite_db;
if (sdb) {
topo_node_sqlite_node_put(sdb, nodeinfo1);
topo_node_sqlite_node_put(sdb, nodeinfo1, ntp_time_get_seconds(group->instance));
if (group->group_type == TOPO_GROUP_TYPE_CHAT && group->channel_id[0]) {
topo_node_sqlite_member_put(sdb, group->channel_id, node_id, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "SQLite member_put: ch=%s node=%016llx", group->channel_id, (unsigned long long)node_id);

35
src/routing_layer/topo_node_sqlite.c

@ -118,7 +118,7 @@ static uint8_t collect_socket_ids(struct TOPO_NODE* ni, uint8_t* sock_ids) {
return count;
}
int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_NODEQ* nq) {
int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_NODEQ* nq, time_t now_sec) {
if (!db || !nq || !nq->node) return -1;
struct TOPO_NODE* ni = nq->node;
@ -127,15 +127,17 @@ int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_NODEQ* nq) {
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db,
"INSERT INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, last_seen_at)"
" VALUES(?,?,?,?,unixepoch())"
" VALUES(?,?,?,?,?)"
" ON CONFLICT(node_id) DO UPDATE SET"
" name=excluded.name, x25519_pubkey=excluded.x25519_pubkey,"
" ed25519_pubkey=excluded.ed25519_pubkey, last_seen_at=excluded.last_seen_at",
" ed25519_pubkey=excluded.ed25519_pubkey, last_seen_at=excluded.last_seen_at,\n"
" created_at=COALESCE(nodes.created_at, excluded.created_at)",
-1, &stmt, NULL) == SQLITE_OK) {
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)ni->node_id);
sqlite3_bind_text(stmt, 2, ni->node_name ? ni->node_name : "", -1, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 3, ni->public_key, 32, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 4, ni->ed25519_public_key, 32, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 5, (sqlite3_int64)now_sec);
sqlite3_step(stmt); sqlite3_finalize(stmt);
}
@ -317,8 +319,8 @@ int topo_node_sqlite_member_put(sqlite3* db, const char* channel_id, uint64_t no
char sql[256];
snprintf(sql, sizeof(sql),
"INSERT OR REPLACE INTO \"%s\"(node_id, x25519_pubkey, ed25519_pubkey,"
" join_sig, join_ts, update_sig, update_ts, userinfo, creator_sig)"
" VALUES(?,?,?,?,?,?,?,?,?)", peers_tbl);
" join_sig, join_ts, joined_at, update_sig, update_ts, userinfo, creator_sig)"
" VALUES(?,?,?,?,?,?,?,?,?,?)", peers_tbl);
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
@ -327,28 +329,30 @@ int topo_node_sqlite_member_put(sqlite3* db, const char* channel_id, uint64_t no
if (join_sig) sqlite3_bind_blob(stmt, 4, join_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC);
else { static const unsigned char zsig[PEERS_JOIN_SIG_SIZE] = {0}; sqlite3_bind_blob(stmt, 4, zsig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC); }
sqlite3_bind_int64(stmt, 5, (sqlite3_int64)join_ts);
if (update_sig) sqlite3_bind_blob(stmt, 6, update_sig, PEERS_UPDATE_SIG_SIZE, SQLITE_STATIC);
else sqlite3_bind_null(stmt, 6);
sqlite3_bind_int64(stmt, 7, (sqlite3_int64)update_ts);
sqlite3_bind_text(stmt, 8, userinfo ? userinfo : "", -1, SQLITE_STATIC);
if (creator_sig) sqlite3_bind_blob(stmt, 9, creator_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC);
else sqlite3_bind_null(stmt, 9);
sqlite3_bind_int64(stmt, 6, (sqlite3_int64)join_ts);
if (update_sig) sqlite3_bind_blob(stmt, 7, update_sig, PEERS_UPDATE_SIG_SIZE, SQLITE_STATIC);
else sqlite3_bind_null(stmt, 7);
sqlite3_bind_int64(stmt, 8, (sqlite3_int64)update_ts);
sqlite3_bind_text(stmt, 9, userinfo ? userinfo : "", -1, SQLITE_STATIC);
if (creator_sig) sqlite3_bind_blob(stmt, 10, creator_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC);
else sqlite3_bind_null(stmt, 10);
int rc = sqlite3_step(stmt); sqlite3_finalize(stmt);
return rc == SQLITE_DONE ? 0 : -1;
}
int topo_node_sqlite_node_update_verified(sqlite3* db, uint64_t node_id,
const char* name, const uint8_t* x25519, const uint8_t* ed25519, uint64_t update_ts) {
const char* name, const uint8_t* x25519, const uint8_t* ed25519, uint64_t update_ts, time_t created_at) {
if (!db || !x25519 || !ed25519) return -1;
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db,
"INSERT INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, update_ts)"
" VALUES(?,?,?,?,?)"
"INSERT INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, created_at, update_ts)"
" VALUES(?,?,?,?,?,?)"
" ON CONFLICT(node_id) DO UPDATE SET"
" name=CASE WHEN excluded.name IS NOT NULL AND excluded.name != ''"
" THEN excluded.name ELSE nodes.name END,"
" x25519_pubkey=excluded.x25519_pubkey,"
" ed25519_pubkey=excluded.ed25519_pubkey,"
" created_at=COALESCE(nodes.created_at, excluded.created_at),"
" update_ts=CASE WHEN nodes.update_ts IS NULL OR excluded.update_ts > nodes.update_ts"
" THEN excluded.update_ts ELSE nodes.update_ts END",
-1, &stmt, NULL) != SQLITE_OK) return -1;
@ -356,7 +360,8 @@ int topo_node_sqlite_node_update_verified(sqlite3* db, uint64_t node_id,
sqlite3_bind_text(stmt, 2, name && name[0] ? name : "", -1, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 3, x25519, 32, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 4, ed25519, 32, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 5, (sqlite3_int64)update_ts);
sqlite3_bind_int64(stmt, 5, (sqlite3_int64)created_at);
sqlite3_bind_int64(stmt, 6, (sqlite3_int64)update_ts);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
return 0;

5
src/routing_layer/topo_node_sqlite.h

@ -4,6 +4,7 @@
#include <sqlite3.h>
#include <stdint.h>
#include <stddef.h>
#include <time.h>
struct TOPO_NODEQ;
@ -16,7 +17,7 @@ struct TOPO_NODEQ;
int topo_node_sqlite_init(sqlite3* db);
int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_NODEQ* nq);
int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_NODEQ* nq, time_t now_sec);
int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id,
const char* name, uint64_t owner_node_id,
@ -36,7 +37,7 @@ int topo_node_sqlite_member_get_join(sqlite3* db, const char* channel_id, uint64
int topo_node_sqlite_member_del(sqlite3* db, const char* channel_id, uint64_t node_id);
int topo_node_sqlite_node_update_verified(sqlite3* db, uint64_t node_id,
const char* name, const uint8_t* x25519, const uint8_t* ed25519, uint64_t update_ts);
const char* name, const uint8_t* x25519, const uint8_t* ed25519, uint64_t update_ts, time_t created_at);
int topo_node_sqlite_channel_get(sqlite3* db, const char* channel_id,
char* name_out, int name_sz, uint64_t* owner_node_id,

6
tools/chatgui/transport/chat_conn_mgr.c

@ -598,11 +598,13 @@ void chat_conn_mgr_connect_from_invite(struct chat_conn_mgr* mgr, struct chat_in
/* save pubkey */
sqlite3_stmt* st = NULL;
if (sqlite3_prepare_v2(mgr->db, "INSERT INTO nodes(node_id,x25519_pubkey) VALUES(?,?)"
" ON CONFLICT(node_id) DO UPDATE SET x25519_pubkey=excluded.x25519_pubkey",
if (sqlite3_prepare_v2(mgr->db, "INSERT INTO nodes(node_id,x25519_pubkey,created_at) VALUES(?,?,?)"
" ON CONFLICT(node_id) DO UPDATE SET x25519_pubkey=excluded.x25519_pubkey,"
" created_at=COALESCE(nodes.created_at, excluded.created_at)",
-1, &st, NULL) == SQLITE_OK) {
sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id);
sqlite3_bind_blob(st, 2, inv->pubkey, 32, SQLITE_STATIC);
sqlite3_bind_int64(st, 3, (sqlite3_int64)ntp_time_get_seconds(mgr->inst));
sqlite3_step(st); sqlite3_finalize(st);
}

17
tools/chatgui/transport/chat_core.c

@ -112,6 +112,7 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) {
g_cc.inst = inst;
g_cc.my_node_id = inst->node_id;
sqlite3_int64 now_sec = (sqlite3_int64)ntp_time_get_seconds(inst);
topo_node_sqlite_init(g_cc.db);
@ -120,24 +121,27 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) {
const char* my_name = inst->name[0] ? inst->name : "Me";
sqlite3_stmt* st = NULL;
sqlite3_prepare_v2(g_cc.db,
"INSERT OR REPLACE INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, online)"
" VALUES(?,?,?,?,1)", -1, &st, NULL);
"INSERT OR REPLACE INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, created_at, online)"
" VALUES(?,?,?,?,?,1)", -1, &st, NULL);
if (st) {
sqlite3_bind_int64(st, 1, (sqlite3_int64)g_cc.my_node_id);
sqlite3_bind_text(st, 2, my_name, -1, SQLITE_STATIC);
sqlite3_bind_blob(st, 3, inst->my_keys.public_key, 32, SQLITE_STATIC);
sqlite3_bind_blob(st, 4, inst->my_ed25519_pubkey, 32, SQLITE_STATIC);
sqlite3_bind_int64(st, 5, now_sec);
sqlite3_step(st); sqlite3_finalize(st);
}
st = NULL;
sqlite3_prepare_v2(g_cc.db,
"INSERT OR REPLACE INTO local_identity(id,node_id,name,x25519_pubkey,ed25519_pubkey)"
" VALUES(1,?,?,?,?)", -1, &st, NULL);
"INSERT OR REPLACE INTO local_identity(id,node_id,name,x25519_pubkey,ed25519_pubkey,created_at,updated_at)"
" VALUES(1,?,?,?,?,?,?)", -1, &st, NULL);
if (st) {
sqlite3_bind_int64(st, 1, (sqlite3_int64)g_cc.my_node_id);
sqlite3_bind_text(st, 2, my_name, -1, SQLITE_STATIC);
sqlite3_bind_blob(st, 3, inst->my_keys.public_key, 32, SQLITE_STATIC);
sqlite3_bind_blob(st, 4, inst->my_ed25519_pubkey, 32, SQLITE_STATIC);
sqlite3_bind_int64(st, 5, now_sec);
sqlite3_bind_int64(st, 6, now_sec);
sqlite3_step(st); sqlite3_finalize(st);
}
}
@ -189,13 +193,14 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) {
for (int i = 0; i < 5; i++) {
sqlite3_stmt* ins = NULL;
sqlite3_prepare_v2(g_cc.db,
"INSERT OR IGNORE INTO accounts(node_id,display_name,avatar_color,avatar_letter)"
" VALUES(?,?,?,?)", -1, &ins, NULL);
"INSERT OR IGNORE INTO accounts(node_id,display_name,avatar_color,avatar_letter,created_at)"
" VALUES(?,?,?,?,?)", -1, &ins, NULL);
if (ins) {
sqlite3_bind_int64(ins, 1, accs[i].id);
sqlite3_bind_text(ins, 2, accs[i].name, -1, SQLITE_STATIC);
sqlite3_bind_text(ins, 3, accs[i].color, -1, SQLITE_STATIC);
sqlite3_bind_text(ins, 4, accs[i].letter, -1, SQLITE_STATIC);
sqlite3_bind_int64(ins, 5, now_sec);
sqlite3_step(ins); sqlite3_finalize(ins);
}
}

26
tools/chatgui/transport/chat_sync.c

@ -1052,14 +1052,16 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer,
if (vdb && inv_userinfo[0]) {
sqlite3_stmt* ns = NULL;
sqlite3_prepare_v2(vdb,
"INSERT INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey) VALUES(?,?,?,?)"
"INSERT INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey,created_at) VALUES(?,?,?,?,?)"
" ON CONFLICT(node_id) DO UPDATE SET"
" name=excluded.name, x25519_pubkey=excluded.x25519_pubkey, ed25519_pubkey=excluded.ed25519_pubkey",
" name=excluded.name, x25519_pubkey=excluded.x25519_pubkey, ed25519_pubkey=excluded.ed25519_pubkey,"
" created_at=COALESCE(nodes.created_at, excluded.created_at)",
-1, &ns, NULL);
if (ns) { sqlite3_bind_int64(ns, 1, (sqlite3_int64)peer);
sqlite3_bind_text(ns, 2, inv_userinfo, -1, SQLITE_STATIC);
sqlite3_bind_blob(ns, 3, inv_x25519, 32, SQLITE_STATIC);
sqlite3_bind_blob(ns, 4, inv_ed, 32, SQLITE_STATIC);
sqlite3_bind_int64(ns, 5, (sqlite3_int64)ntp_time_get_seconds(cs->inst));
sqlite3_step(ns); sqlite3_finalize(ns); }
}
/* save inviter's address from ETCP connection */
@ -1094,7 +1096,7 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer,
int rc = topo_node_sqlite_member_put(vdb, ch_id, peer, inviter_join_sig, inviter_join_ts, inviter_update_sig, inviter_update_ts, inv_x25519, inv_ed, inv_userinfo, inviter_join_sig);
if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put(inviter) FAILED ch=%s peer=%016llx rc=%d", CS_ID, ch_id, (unsigned long long)peer, rc);
char inv_name[128]; json_flat_get(inv_userinfo, "name", inv_name, sizeof(inv_name));
topo_node_sqlite_node_update_verified(vdb, peer, inv_name, inv_x25519, inv_ed, inviter_join_ts);
topo_node_sqlite_node_update_verified(vdb, peer, inv_name, inv_x25519, inv_ed, inviter_join_ts, ntp_time_get_seconds(cs->inst));
}
/* generate our own join_sig */
@ -1206,20 +1208,22 @@ static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer,
int rc = topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, joiner_userinfo, NULL);
if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put(joiner) FAILED ch=%s node=0x%016llx rc=%d", CS_ID, ch_id, (unsigned long long)node_id, rc);
{ char j_name[128]; json_flat_get(joiner_userinfo, "name", j_name, sizeof(j_name));
topo_node_sqlite_node_update_verified(db, node_id, j_name, x25519, ed_pub, join_ts); }
topo_node_sqlite_node_update_verified(db, node_id, j_name, x25519, ed_pub, join_ts, ntp_time_get_seconds(cs->inst)); }
chat_conn_mgr_add_node(chat_conn_mgr_get(), node_id);
/* save joiner node_info to local DB */
if (db && joiner_userinfo[0]) {
sqlite3_stmt* ns = NULL;
sqlite3_prepare_v2(db,
"INSERT INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey) VALUES(?,?,?,?)"
"INSERT INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey,created_at) VALUES(?,?,?,?,?)"
" ON CONFLICT(node_id) DO UPDATE SET"
" name=excluded.name, x25519_pubkey=excluded.x25519_pubkey, ed25519_pubkey=excluded.ed25519_pubkey",
" name=excluded.name, x25519_pubkey=excluded.x25519_pubkey, ed25519_pubkey=excluded.ed25519_pubkey,"
" created_at=COALESCE(nodes.created_at, excluded.created_at)",
-1, &ns, NULL);
if (ns) { sqlite3_bind_int64(ns, 1, (sqlite3_int64)node_id);
sqlite3_bind_text(ns, 2, joiner_userinfo, -1, SQLITE_STATIC);
sqlite3_bind_blob(ns, 3, x25519, 32, SQLITE_STATIC);
sqlite3_bind_blob(ns, 4, ed_pub, 32, SQLITE_STATIC);
sqlite3_bind_int64(ns, 5, (sqlite3_int64)ntp_time_get_seconds(cs->inst));
sqlite3_step(ns); sqlite3_finalize(ns); }
}
@ -1378,7 +1382,7 @@ static void cs_handle_welcome(struct chat_sync* cs, uint64_t peer,
int rc = topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, peer_userinfo, NULL);
if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put(welcome) FAILED ch=%s node=0x%016llx rc=%d", CS_ID, ch_id, (unsigned long long)node_id, rc);
{ char pn[256]; json_flat_get(peer_userinfo, "name", pn, sizeof(pn));
topo_node_sqlite_node_update_verified(db, node_id, pn, x25519, ed_pub, join_ts); }
topo_node_sqlite_node_update_verified(db, node_id, pn, x25519, ed_pub, join_ts, ntp_time_get_seconds(cs->inst)); }
chat_conn_mgr_add_node(chat_conn_mgr_get(), node_id);
@ -1482,19 +1486,21 @@ static void cs_handle_peer_upsert(struct chat_sync* cs, uint64_t peer,
int rc = topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, peer_userinfo, NULL);
if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put(peer_upsert) FAILED ch=%s node=0x%016llx rc=%d", CS_ID, ch_id, (unsigned long long)node_id, rc);
{ char pn2[128]; json_flat_get(peer_userinfo, "name", pn2, sizeof(pn2));
topo_node_sqlite_node_update_verified(db, node_id, pn2, x25519, ed_pub, join_ts); }
topo_node_sqlite_node_update_verified(db, node_id, pn2, x25519, ed_pub, join_ts, ntp_time_get_seconds(cs->inst)); }
chat_conn_mgr_add_node(chat_conn_mgr_get(), node_id);
if (db && peer_userinfo[0]) {
sqlite3_stmt* ns = NULL;
sqlite3_prepare_v2(db,
"INSERT INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey) VALUES(?,?,?,?)"
"INSERT INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey,created_at) VALUES(?,?,?,?,?)"
" ON CONFLICT(node_id) DO UPDATE SET"
" name=excluded.name, x25519_pubkey=excluded.x25519_pubkey, ed25519_pubkey=excluded.ed25519_pubkey",
" name=excluded.name, x25519_pubkey=excluded.x25519_pubkey, ed25519_pubkey=excluded.ed25519_pubkey,"
" created_at=COALESCE(nodes.created_at, excluded.created_at)",
-1, &ns, NULL);
if (ns) { sqlite3_bind_int64(ns, 1, (sqlite3_int64)node_id);
sqlite3_bind_text(ns, 2, peer_userinfo, -1, SQLITE_STATIC);
sqlite3_bind_blob(ns, 3, x25519, 32, SQLITE_STATIC);
sqlite3_bind_blob(ns, 4, ed_pub, 32, SQLITE_STATIC);
sqlite3_bind_int64(ns, 5, (sqlite3_int64)ntp_time_get_seconds(cs->inst));
sqlite3_step(ns); sqlite3_finalize(ns); }
}

Loading…
Cancel
Save