- etcp_router: SVC_ROUTE_HDR (+8B), ETCP_ROUTER_CONN/TRANSIT_QUEUE хеши расширены под group_id - Все caller'ы (proxy, routing, conn_mgr, nat, chatgui, tests) обновлены - topo_group: глобальный реестр TOPO_NODE по node_id, ref_count=0 при создании - BGP-обмен активируется для всех групп (а не только дефолтной) - Протокол: group_id добавлен в WITHDRAW/TABLE_REQ/TABLE_COMPLETE/ERR_GROUP_MISMATCH - Per-connection коллбэки заменены на instance-level conn_status - msg_transport полностью удалён из проекта 57/57 тестов
202 lines
9.2 KiB
Plaintext
202 lines
9.2 KiB
Plaintext
Архитектура чата (группового)
|
|
|
|
База данных: SQLite3 (WAL mode), chats.db
|
|
|
|
================================================================================
|
|
Схема БД (10 таблиц)
|
|
================================================================================
|
|
|
|
-- 1. local_identity — текущий локальный узел (одна строка, приватный ключ здесь)
|
|
-- приватный ключ получаем от utun через MSG_RSP_PRIVKEY (только localhost)
|
|
CREATE TABLE local_identity (
|
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
node_id INTEGER NOT NULL UNIQUE,
|
|
name TEXT NOT NULL,
|
|
x25519_pubkey BLOB NOT NULL, -- 32 bytes
|
|
x25519_privkey BLOB, -- 32 bytes (только на этом устройстве)
|
|
ed25519_pubkey BLOB, -- 32 bytes (derive из x25519 privkey)
|
|
created_at INTEGER DEFAULT (unixepoch()),
|
|
updated_at INTEGER DEFAULT (unixepoch())
|
|
);
|
|
|
|
|
|
-- 2. my_nodes — другие наши ноды (другие устройства, нет приватного ключа)
|
|
CREATE TABLE my_nodes (
|
|
node_id INTEGER PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
x25519_pubkey BLOB NOT NULL, -- 32 bytes
|
|
ed25519_pubkey BLOB, -- 32 bytes
|
|
created_at INTEGER DEFAULT (unixepoch()),
|
|
updated_at INTEGER DEFAULT (unixepoch())
|
|
);
|
|
|
|
|
|
-- 3. nodes — известные удалённые узлы (получаем через utun)
|
|
CREATE TABLE nodes (
|
|
node_id INTEGER PRIMARY KEY,
|
|
name TEXT,
|
|
x25519_pubkey BLOB NOT NULL, -- 32 bytes
|
|
ed25519_pubkey BLOB, -- 32 bytes (для проверки подписи пира)
|
|
is_online INTEGER DEFAULT 0,
|
|
last_seen_at INTEGER DEFAULT (unixepoch()),
|
|
created_at INTEGER DEFAULT (unixepoch()),
|
|
updated_at INTEGER DEFAULT (unixepoch())
|
|
);
|
|
|
|
|
|
-- 3a. accounts — UI-информация об узлах для отображения сообщений
|
|
CREATE TABLE accounts (
|
|
node_id INTEGER PRIMARY KEY REFERENCES nodes(node_id),
|
|
display_name TEXT NOT NULL, -- "Alice"
|
|
avatar_color TEXT DEFAULT '#4A90E2', -- hex RGB
|
|
avatar_letter TEXT NOT NULL, -- "A"
|
|
is_contact INTEGER DEFAULT 1, -- показывать в боковой панели
|
|
created_at INTEGER DEFAULT (unixepoch())
|
|
);
|
|
|
|
|
|
-- 4. channels — каналы (DM и групповые)
|
|
CREATE TABLE channels (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
channel_id TEXT NOT NULL UNIQUE, -- "dm:nodeA_nodeB" (A<B) или "group:hash"
|
|
name TEXT NOT NULL,
|
|
owner_node_id INTEGER, -- создатель (NULL для DM)
|
|
is_dm INTEGER DEFAULT 0,
|
|
last_message TEXT,
|
|
last_msg_at INTEGER, -- unix timestamp ms
|
|
created_at INTEGER DEFAULT (unixepoch())
|
|
);
|
|
CREATE UNIQUE INDEX idx_channel_cid ON channels(channel_id);
|
|
|
|
|
|
-- 5. channel_members — участники канала
|
|
CREATE TABLE channel_members (
|
|
channel_id TEXT NOT NULL REFERENCES channels(channel_id) ON DELETE CASCADE,
|
|
node_id INTEGER NOT NULL,
|
|
joined_at INTEGER DEFAULT (unixepoch()),
|
|
PRIMARY KEY (channel_id, node_id)
|
|
);
|
|
CREATE INDEX idx_cm_node ON channel_members(node_id);
|
|
|
|
|
|
-- 6. messages — сообщения чата
|
|
-- signature = Ed25519 отправителя над (channel_id, author_node_id, content, timestamp)
|
|
CREATE TABLE messages (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
channel_id TEXT NOT NULL REFERENCES channels(channel_id) ON DELETE CASCADE,
|
|
author_node_id INTEGER NOT NULL,
|
|
content TEXT NOT NULL CHECK(length(content) <= 4096),
|
|
content_type TEXT DEFAULT 'text/plain',
|
|
timestamp INTEGER NOT NULL, -- unix milliseconds
|
|
signature BLOB NOT NULL CHECK(length(signature) == 64),
|
|
is_outgoing INTEGER DEFAULT 0,
|
|
is_read INTEGER DEFAULT 0,
|
|
created_at INTEGER DEFAULT (unixepoch())
|
|
);
|
|
CREATE INDEX idx_msg_channel_time ON messages(channel_id, timestamp);
|
|
CREATE INDEX idx_msg_author ON messages(author_node_id);
|
|
-- дедупликация: один автор не может отправить два сообщения в один ms в один канал
|
|
CREATE UNIQUE INDEX idx_msg_dedup ON messages(channel_id, author_node_id, timestamp);
|
|
|
|
|
|
-- 7. reactions — реакции на сообщения
|
|
-- signature = Ed25519 отправителя над (message_id, node_id, emoji)
|
|
CREATE TABLE reactions (
|
|
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
node_id INTEGER NOT NULL,
|
|
emoji TEXT NOT NULL, -- unicode codepoint или :shortcode:
|
|
signature BLOB NOT NULL CHECK(length(signature) == 64),
|
|
created_at INTEGER DEFAULT (unixepoch()),
|
|
PRIMARY KEY (message_id, node_id, emoji)
|
|
);
|
|
|
|
|
|
-- 8. attachments — вложения к сообщениям
|
|
-- signature = Ed25519 отправителя над (message_id, filename, content_type, size, sha256)
|
|
CREATE TABLE attachments (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
filename TEXT NOT NULL,
|
|
content_type TEXT NOT NULL, -- MIME type
|
|
size INTEGER NOT NULL, -- bytes
|
|
sha256 BLOB NOT NULL, -- 32 bytes
|
|
file_path TEXT, -- локальный путь к сохранённому файлу
|
|
signature BLOB NOT NULL CHECK(length(signature) == 64),
|
|
is_upload INTEGER DEFAULT 0, -- 0=скачано, 1=наше
|
|
created_at INTEGER DEFAULT (unixepoch())
|
|
);
|
|
CREATE INDEX idx_att_msg ON attachments(message_id);
|
|
|
|
|
|
-- 9. ui_state — сохранение состояния UI (последний канал, позиция скролла и т.д.)
|
|
CREATE TABLE ui_state (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT
|
|
);================================================================================
|
|
Основные запросы
|
|
================================================================================
|
|
|
|
-- Текущая нода (приватный ключ — только на этом устройстве)
|
|
SELECT node_id, name, x25519_pubkey, x25519_privkey, ed25519_pubkey
|
|
FROM local_identity WHERE id = 1;
|
|
|
|
-- Другие наши ноды (без приватных ключей)
|
|
SELECT node_id, name, x25519_pubkey, ed25519_pubkey FROM my_nodes;
|
|
|
|
-- Список каналов с последним сообщением
|
|
SELECT c.channel_id, c.name, c.is_dm, c.last_message, c.last_msg_at
|
|
FROM channels c
|
|
JOIN channel_members cm ON c.channel_id = cm.channel_id
|
|
WHERE cm.node_id = (SELECT node_id FROM local_identity WHERE id = 1)
|
|
ORDER BY c.last_msg_at DESC;
|
|
|
|
-- Сообщения канала (последние 50)
|
|
SELECT id, author_node_id, content, content_type, timestamp,
|
|
signature, is_outgoing, is_read
|
|
FROM messages
|
|
WHERE channel_id = ?
|
|
ORDER BY timestamp DESC
|
|
LIMIT 50;
|
|
|
|
-- Кто в канале
|
|
SELECT cm.node_id, n.name
|
|
FROM channel_members cm LEFT JOIN nodes n ON cm.node_id = n.node_id
|
|
WHERE cm.channel_id = ?;
|
|
|
|
-- DM между двумя узлами
|
|
-- channel_id = "dm:" || min(a,b) || "_" || max(a,b)
|
|
|
|
-- Реакции сообщения
|
|
SELECT node_id, emoji, signature FROM reactions WHERE message_id = ? ORDER BY created_at;
|
|
|
|
-- Вложения сообщения
|
|
SELECT id, filename, content_type, size, sha256, file_path
|
|
FROM attachments WHERE message_id = ?;
|
|
|
|
|
|
================================================================================
|
|
Примечания
|
|
================================================================================
|
|
|
|
- local_identity — одна строка (id=1), содержит x25519_privkey.
|
|
Приватный ключ получаем от utun через MSG_RSP_PRIVKEY (доступен только с localhost).
|
|
|
|
- my_nodes — другие наши устройства, только pubkey-информация (нет приватных ключей).
|
|
|
|
- Адреса и подсети (node_addresses, node_subnets) — зона ответственности utun.
|
|
Чат не занимается сетевым уровнем.
|
|
|
|
- Все timestamp: INTEGER (unix epoch). messages.timestamp — миллисекунды,
|
|
created_at/updated_at/joined_at — секунды.
|
|
|
|
- signature везде NOT NULL Ed25519 (64 bytes) — подпись автора контента:
|
|
messages : sign(edPriv, channel_id || author_node_id || content || timestamp)
|
|
reactions : sign(edPriv, message_id || node_id || emoji)
|
|
attachments: sign(edPriv, message_id || filename || content_type || size || sha256)
|
|
|
|
- channel_id для DM: "dm:nodeA_nodeB" где A < B (канонический порядок).
|
|
|
|
- channel_id для групп: "group:" + SHA256(owner_node_id || name || timestamp).
|
|
|
|
- deleted_at / soft delete не добавлены — можно добавить позже при необходимости.
|