141 lines
9.9 KiB
C
141 lines
9.9 KiB
C
/* db_sync — репликация подписанных записей SQLite по прямым ETCP-соединениям.
|
||
* Каждый DB_SYNC_INSTANCE связывает таблицу с group_id. Различия находятся по
|
||
* общему Merkle-дереву; изменения рассылаются PUSH. Постоянный ключ — message_id,
|
||
* порядок отображения — (timestamp, message_id). Подпись защищает весь конверт и data.
|
||
* Ядро создаёт модуль, чат включает его и регистрирует таблицы каналов.
|
||
* Обычно используется общая SQLite ядра; remove отключает таблицу от обмена,
|
||
* сохраняя данные. Все вызовы и callbacks выполняются в потоке uasync. */
|
||
#ifndef DB_SYNC_H
|
||
#define DB_SYNC_H
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
#include <stdint.h>
|
||
#include <stddef.h>
|
||
|
||
struct UTUN_INSTANCE;
|
||
struct DB_SYNC_INSTANCE;
|
||
|
||
// etcp service ID: ETCP_ID_DB_SYNC (etcp_bind, см. etcp_api.h)
|
||
|
||
// Message types
|
||
#define DB_MSG_PUSH 0x05
|
||
#define DB_MSG_ACK_PUSH 0x06
|
||
|
||
/* Максимальная длина подписанного тела. Транспортный буфер также учитывает identity и подпись. */
|
||
#define DB_SYNC_MESSAGE_MAX 4000
|
||
|
||
// PUSH/MESSAGES: [id:32][ts:8BE][author:8BE][nonce:16][ver:8BE][edited_at:8BE][dlen:4BE][data][sig:64].
|
||
// ACK_PUSH: [id:32][ver:8BE]. Подтверждает конкретную версию; повторы идемпотентны.
|
||
|
||
struct db_message_revision {
|
||
uint8_t nonce[16]; /* Случайность создания, неизменна во всех версиях. */
|
||
uint64_t ver; /* 1..INT64_MAX; принимается только более высокая версия. */
|
||
uint64_t edited_at; /* Unix ms последней правки; 0 для ver=1. */
|
||
};
|
||
|
||
/* Подписать канонический конверт, включая канал; 0/-1. Не обращается к БД, nonce задаёт вызывающий. */
|
||
int db_sync_sign_message(uint64_t group, uint64_t timestamp, uint64_t author,
|
||
const struct db_message_revision* revision, const char* data, size_t len,
|
||
const uint8_t private_key[32], uint8_t signature[64]);
|
||
|
||
// Интервалы служебных таймеров в секундах.
|
||
#define DB_SYNC_PEER_CHECK_INTERVAL 5
|
||
#define DB_SYNC_TTL_INTERVAL 3600
|
||
|
||
// Record flags
|
||
#define DB_REC_FLAG_WAS_SENT 0x01
|
||
|
||
// Ed25519 signature size
|
||
#define DB_SIG_SIZE 64
|
||
|
||
// Создать контекст и учесть db_sync_enabled из конфига. 0/-1.
|
||
int db_sync_init(struct UTUN_INSTANCE* inst);
|
||
// Включить ранее созданный модуль независимо от флага конфига. Повтор допустим; 0/-1.
|
||
int db_sync_enable(struct UTUN_INSTANCE* inst);
|
||
// Подключить MESSAGES после создания общего Merkle-движка; запланировать проверку своей истории. 0/-1.
|
||
int db_sync_register_model(struct UTUN_INSTANCE* inst);
|
||
// Отменить таймеры/обмен, снять обработчики; общую SQLite не закрывает.
|
||
void db_sync_destroy(struct UTUN_INSTANCE* inst);
|
||
|
||
// Зарегистрировать таблицу существующей группы без сетевого обмена; первоначально gated. NULL при ошибке.
|
||
struct DB_SYNC_INSTANCE* db_sync_instance_add(struct UTUN_INSTANCE* inst, const char* table_name, uint64_t group_id);
|
||
// Освободить экземпляр синхронизации; сама таблица остаётся в БД.
|
||
void db_sync_instance_remove(struct DB_SYNC_INSTANCE* si);
|
||
// Заимствованный указатель по group_id; действителен до remove/destroy, NULL если нет.
|
||
struct DB_SYNC_INSTANCE* db_sync_instance_find(struct UTUN_INSTANCE* inst, uint64_t group_id);
|
||
// group_id инстанса (для chat-таблиц равен числовому channel_id). Возвращает 0 при si==NULL.
|
||
uint64_t db_sync_instance_group_id(struct DB_SYNC_INSTANCE* si);
|
||
|
||
// Data operations (per-instance)
|
||
// db_sync_insert_signed: sig MUST be non-NULL, 64 bytes.
|
||
// sig создаётся db_sync_sign_message; revision обязателен, автор — локальный узел.
|
||
// ts — call db_sync_next_timestamp(si) before signing to reserve monotonically increasing timestamp.
|
||
// Возвращает 0 (вставлено), 1 (повтор), -1 (операционная ошибка/ключ ещё неизвестен), -2 (неверные данные).
|
||
// Корректная сохранённая копия переживает неверный входящий повтор. Удаляем только строку,
|
||
// чьи собственные байты имеют битый формат либо подпись не совпала при завершённой проверке.
|
||
int db_sync_insert_signed(struct DB_SYNC_INSTANCE* si, const char* json_data, size_t len,
|
||
const uint8_t* sig, size_t sig_len, uint64_t ts,
|
||
const char* local_attrs, const struct db_message_revision* revision);
|
||
uint32_t db_sync_count(struct DB_SYNC_INSTANCE* si);
|
||
// Последний известный/зарезервированный timestamp в миллисекундах Unix; 0 для NULL.
|
||
uint64_t db_sync_get_last_timestamp(struct DB_SYNC_INSTANCE* si);
|
||
// Зарезервировать следующий timestamp перед подписью; растёт даже в пределах одной ms.
|
||
uint64_t db_sync_next_timestamp(struct DB_SYNC_INSTANCE* si);
|
||
|
||
// Обход по (timestamp, message_id), начиная с offset; limit=0 — без ограничения.
|
||
// Возвращает число записей или -1. Данные callback заимствованы на время вызова.
|
||
typedef void (*db_sync_select_cb)(void* arg, uint64_t id, uint64_t timestamp,
|
||
const char* data, size_t data_len, uint64_t author,
|
||
const uint8_t* author_sig, size_t sig_len,
|
||
int delivered_peers, const char* delivery_chain);
|
||
int db_sync_select(struct DB_SYNC_INSTANCE* si, uint32_t offset, uint32_t limit,
|
||
db_sync_select_cb cb, void* arg);
|
||
|
||
// Callback после вставки/обновления. updated=1 заменяет строку без нового unread/звука.
|
||
// author_node_id — исходный автор записи, не обязательно переславший её пир.
|
||
// initial_sync = 1 для страницы MESSAGES, 0 для live-PUSH и локальной вставки.
|
||
// Вызов после commit: для страниц отложен до выхода из обработчика Merkle.
|
||
typedef void (*db_sync_insert_cb)(struct DB_SYNC_INSTANCE* si,
|
||
const uint8_t message_id[32],
|
||
uint64_t record_timestamp,
|
||
const char* json_data, size_t len,
|
||
uint64_t author_node_id, int initial_sync, int updated, void* arg);
|
||
void db_sync_set_insert_cb(struct DB_SYNC_INSTANCE* si, db_sync_insert_cb cb, void* arg);
|
||
|
||
// Диагностика: синхронно перепроверить/очистить сохранённые записи и восстановить индекс.
|
||
// 0 — готов, 1 — ожидается ключ автора, -1 — операционная ошибка. В обычной работе обход порционный.
|
||
int db_sync_verify(struct DB_SYNC_INSTANCE* si);
|
||
// Прочитать полный корень: 0 — успех, 1 — preflight ещё не завершён, -1 — ошибка. root[32] обязателен.
|
||
int db_sync_read_root(struct DB_SYNC_INSTANCE* si, uint8_t root[32]);
|
||
|
||
// Пауза обмена с выбранным пиром для диагностических сценариев; reinitiate снимает паузу.
|
||
void db_sync_peer_set_paused(struct DB_SYNC_INSTANCE* si, uint64_t node_id, int paused);
|
||
|
||
// Per-(chat,peer) «play sound on message» flag: sound_off + sleeping peer → throttled.
|
||
void db_sync_set_peer_sound(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t peer_node_id, int sound_on);
|
||
|
||
// До успешного member_sync блокирует входящий и исходящий обмен. После снятия
|
||
// блокировки запускает историю только через READY-пиров этой группы.
|
||
void db_sync_instance_set_gated(struct DB_SYNC_INSTANCE* si, int gated);
|
||
|
||
// Force re-initiate sync to a specific peer (for testing)
|
||
void db_sync_reinitiate(struct DB_SYNC_INSTANCE* si, uint64_t node_id);
|
||
|
||
// После sleep/sound переоценить работу истории; после изменения MEMBERS также повторить ожидание общих ключей.
|
||
void db_sync_peer_policy_changed(struct UTUN_INSTANCE* inst, uint64_t peer);
|
||
void db_sync_members_changed(struct UTUN_INSTANCE* inst, const char* ns);
|
||
|
||
// Глобальная подписка на подтверждённый полный Merkle-корень истории; callback может удалить подписку/канал.
|
||
typedef void (*db_sync_done_fn)(struct DB_SYNC_INSTANCE* si, uint64_t peer_node_id, void* arg);
|
||
// 0 — подписан/та же пара уже подписана,-1 — отказ без изменения списка.
|
||
int db_sync_add_done_cbk(struct UTUN_INSTANCE* inst, db_sync_done_fn fn, void* arg);
|
||
void db_sync_remove_done_cbk(struct UTUN_INSTANCE* inst, db_sync_done_fn fn, void* arg);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
#endif // DB_SYNC_H
|