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.
 
 
 
 
 
 

201 lines
9.2 KiB

Архитектура чата (группового)
База данных: 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 не добавлены — можно добавить позже при необходимости.