You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

10 KiB

База данных децентрализованного чата (chatgui)

Чат децентрализованный — нет центрального сервера. Каждый узел хранит локальную SQLite БД (chats.db, WAL mode). Синхронизация сообщений между узлами — через протокол chat_sync поверх etcp_router (svc_id 0x30). Данные о других узлах поступают из NODEINFO через conn_mgr.

Вся работа с БД — в GUI-потоке (класс DbManager). Протокол синхронизации работает в uasync-потоке (chat_sync.c) и общается с БД асинхронно через gui_bridge.

Обзор таблиц

Таблица Назначение
local_identity Наш собственный узел (id, ключи)
nodes Все известные узлы (node_id, ключи, last_seen)
node_addresses Адреса узлов (family, protocol, address, port, rtt, is_nat)
accounts Профили узлов (display_name, avatar)
channels Метаданные каналов/сетей (ключи, подпись, позиции скролла)
msg_<ch> Per-channel таблица сообщений
peers_<ch> Per-channel таблица участников (подписи входа, создателя, comment)
ui_state Локальное состояние UI (key-value)

Таблицы

local_identity — наш собственный узел

CREATE TABLE IF NOT EXISTS local_identity (
    id              INTEGER PRIMARY KEY CHECK (id = 1),
    node_id         INTEGER NOT NULL UNIQUE,
    name            TEXT NOT NULL,
    x25519_pubkey   BLOB NOT NULL,
    x25519_privkey  BLOB,
    ed25519_pubkey  BLOB,
    created_at      INTEGER DEFAULT (unixepoch()),
    updated_at      INTEGER DEFAULT (unixepoch())
);

nodes — все известные узлы

CREATE TABLE IF NOT EXISTS nodes (
    node_id          INTEGER PRIMARY KEY,
    name             TEXT,
    x25519_pubkey    BLOB NOT NULL,
    ed25519_pubkey   BLOB,
    last_seen_at     INTEGER,
    created_at       INTEGER DEFAULT (unixepoch())
);
Поле Описание
node_id ID узла в сети uTun
name Никнейм
x25519_pubkey Публичный ключ для key exchange (32 байта)
ed25519_pubkey Публичный ключ для подписей (32 байта), может быть NULL
last_seen_at Unix-время последней активности

Статус online определяется через conn_mgr, а не полем в БД.

node_addresses — адреса узлов

CREATE TABLE IF NOT EXISTS node_addresses (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    node_id     INTEGER NOT NULL REFERENCES nodes(node_id) ON DELETE CASCADE,
    family      INTEGER NOT NULL CHECK(family IN (4, 6)),
    protocol    INTEGER NOT NULL DEFAULT 1,
    address     BLOB NOT NULL,
    port        INTEGER NOT NULL CHECK(port > 0 AND port <= 65535),
    rtt         INTEGER,
    is_nat      INTEGER DEFAULT 0,
    created_at  INTEGER DEFAULT (unixepoch())
);
CREATE INDEX IF NOT EXISTS idx_na_node ON node_addresses(node_id);
Поле Описание
family 4=IPv4, 6=IPv6
protocol Битовая маска: 1=UDP (NODE_PROTO_UDP), 2=TCP (NODE_PROTO_TCP), 3=UDP+TCP
address BLOB: 4 байта для IPv4, 16 байт для IPv6
port Порт
rtt RTT в миллисекундах, NULL если не измерен
is_nat 1 = узел за NAT

accounts — профили узлов

CREATE TABLE IF NOT EXISTS accounts (
    node_id         INTEGER PRIMARY KEY REFERENCES nodes(node_id),
    display_name    TEXT NOT NULL,
    avatar_color    TEXT DEFAULT '#4A90E2',
    avatar_letter   TEXT NOT NULL,
    is_contact      INTEGER DEFAULT 1,
    created_at      INTEGER DEFAULT (unixepoch())
);

channels — метаданные каналов

CREATE TABLE IF NOT EXISTS channels (
    channel_id       TEXT PRIMARY KEY,
    name             TEXT NOT NULL,
    owner_node_id    INTEGER,
    is_dm            INTEGER DEFAULT 0,
    x25519_pubkey    BLOB NOT NULL,
    x25519_privkey   BLOB,
    ed25519_pubkey   BLOB NOT NULL,
    ed25519_privkey  BLOB,
    signature        BLOB NOT NULL,
    last_read_msg_id INTEGER,
    last_pos_msg_id  INTEGER,
    created_at       INTEGER DEFAULT (unixepoch())
);
Поле Описание
channel_id Уникальный ID канала (напр. "ch:general")
name Отображаемое имя (напр. "#general")
owner_node_id ID создателя канала
is_dm 1 = личная переписка (direct message)
x25519_pubkey Публичный ключ канала (32 байта)
x25519_privkey Приватный ключ канала, NULL если локальный узел не админ
ed25519_pubkey Публичный ключ подписи канала (32 байта)
ed25519_privkey Приватный ключ подписи, NULL если локальный узел не админ
signature Ed25519 подпись всех полей записи приватным ключом канала
last_read_msg_id ID последнего прочитанного сообщения (локальное)
last_pos_msg_id ID сообщения на позиции скролла (для восстановления при открытии)

msg_<ch> — per-channel таблица сообщений

Для каждого канала создаётся отдельная таблица. Имя: msg_ + sanitized channel_id (не-буквоцифры заменяются на _).

CREATE TABLE IF NOT EXISTS "msg_<ch>" (
    id           INTEGER PRIMARY KEY AUTOINCREMENT,
    node_id      INTEGER NOT NULL,
    content_type TEXT NOT NULL,
    data         BLOB NOT NULL,
    timestamp    INTEGER NOT NULL,
    datahash     INTEGER NOT NULL,
    chain_hash   BLOB NOT NULL,
    signature    BLOB,
    is_outgoing  INTEGER DEFAULT 0,
    is_read      INTEGER DEFAULT 0,
    sync_flags   INTEGER DEFAULT 0,
    UNIQUE(timestamp, datahash)
);
CREATE INDEX IF NOT EXISTS "idx_msg_<ch>_ts" ON "msg_<ch>"(timestamp, datahash);
Поле Описание
node_id Автор сообщения
content_type MIME-тип: "text/plain", "image/png", ...
data Содержимое сообщения (BLOB)
timestamp Время отправки в микросекундах (первая часть ключа sync)
datahash Первые 8 байт SHA256(data), вторая часть ключа sync
chain_hash SHA256 цепочки (32 байта) — как в blockchain, связывает записи по порядку
signature Ed25519 подпись автора (64 байта), пока заглушка (64 нуля)
is_outgoing 1 = отправлено нами
is_read 1 = прочитано
sync_flags Битовая маска: 0x01 = WAS_SENT (запись подтверждена пирами)

Дедупликация: уникальный индекс (timestamp, datahash) предотвращает повторную вставку одного и того же сообщения при получении через разные пути.

Chain hash: chain_hash = SHA256(prev_chain_hash || timestamp || datahash). Обеспечивает проверку целостности порядка сообщений при синхронизации.

peers_<ch> — per-channel таблица участников

CREATE TABLE IF NOT EXISTS "peers_<ch>" (
    node_id      INTEGER NOT NULL,
    join_sig     BLOB NOT NULL,
    creator_sig  BLOB,
    comment      TEXT,
    joined_at    INTEGER DEFAULT (unixepoch()),
    PRIMARY KEY (node_id)
);
Поле Описание
join_sig Ed25519 подпись присоединяющегося: `sign(sk_user, channel_id
creator_sig Ed25519 подпись создателя канала: `sign(sk_creator, channel_id
comment Комментарий/заметка об участнике

ui_state — локальное состояние UI

CREATE TABLE IF NOT EXISTS ui_state (
    key   TEXT PRIMARY KEY,
    value TEXT
);

Key-value хранилище для произвольных настроек UI (позиции окон, выбранный канал, ...).


Формат binary-записей sync (gui_bridge / chat_sync)

Записи передаются между потоками и по сети в компактном бинарном формате:

Insert record (для GUI_OP_INSERT_RECORD / PUSH wire)

[timestamp:8 LE][datahash:8 LE][node_id:8 LE][ct_len:1][content_type:ct_len][data_len:4 LE][data:data_len][chain_hash:32]

NodeInfo (GUI_OP_LOAD_NODEINFO)

[x25519_pubkey:32][ed25519_pubkey:32][addr_count:1]([family:1][proto:1][addr_len:1][addr:4|16][port:2 LE][rtt:2 LE])...

SEND_DATA cursor record (GUI_OP_CURSOR_NEXT)

[ts:8 LE][dh:8 LE][node_id:8 LE][ct_len:1][ct:var][data_len:4 LE][data:var]

Порядок синхронизации (chat_sync)

  1. Peer online → для каждого общего канала: INIT_SYNC(my_count, last_chain_hash)
  2. INIT_RESP → сравнение chain_hash на позиции min(counts)-1. Если match → synced. Иначе sparse hashes (-1, -2, -4, -8, ...) для поиска точки расхождения.
  3. SEND_DATA → обмен недостающими записями пакетами до 32 штук
  4. SYNC_DONE → финальная сверка count + last_chain_hash
  5. PUSH → реал-тайм доставка новых сообщений (→ ACK_PUSH)
  6. TTL → раз в час удаление своих неподтверждённых записей старше 24ч

Все операции с БД асинхронные: chat_sync (uasync) → gui_bridge_call() → GUI-поток (SQL) → uasync_post() → callback в chat_sync.