Document implemented DM guarantees and remaining media integration
This commit is contained in:
1 parent
1740bec94f
commit
43c89ca1ed
2 files changed
+115
-66
No files matched your search
+114
-66
@@ -1,93 +1,141 @@
|
||||
# DM — прямой p2p чат между двумя пользователями группы
|
||||
# DM: доставка сообщений и подготовка медиа
|
||||
|
||||
## 1. Цель
|
||||
Личные беседы принадлежат `src/dm/`. Общие CHAT-группы используются для знакомства,
|
||||
проверки ролей и маршрутизации; группа не становится идентичностью беседы.
|
||||
|
||||
Возможность начать личный чат с любым пользователем доступных групп.
|
||||
Пока target offline сообщения временно хранятся на storage-узлах.
|
||||
## Реализовано
|
||||
|
||||
## 2. Ключевые решения
|
||||
- История и постоянная исходящая очередь сообщений в общей SQLite ядра.
|
||||
- Доставка через живой маршрут router, включая BGP-транзит.
|
||||
- При отсутствии маршрута — подписанный PUT доступному суперузлу общей группы сторон.
|
||||
- Приём после проверки подписи и расшифровки, подтверждение только после commit.
|
||||
- Доставка с суперузла после возвращения получателя, независимо от отправителя.
|
||||
- Точное удаление промежуточного сообщения и сохранение квитанции для поздних повторов.
|
||||
- Шифрование медиа порциями в worker; сетевой жизненный цикл медиа пока не подключён.
|
||||
|
||||
- Отдельная DM-подсистема (НЕ канал: без TOPO_GROUP/member_sync/merkle).
|
||||
- Идентичность детерминированная: одна ветка на пару, согласование без handshake.
|
||||
- E2E-шифрование содержимого сразу.
|
||||
- Offline-хранение: dm_mailbox на storage-узлах с флагом dm_storage.
|
||||
- Доставка: fallback — сперва проверка наличия узла в BGP-группе; доступен →
|
||||
прямой send; нет связи → storage.
|
||||
## Идентичность и шифрование
|
||||
|
||||
## 3. Идентичность и ключи (детерминированные)
|
||||
```text
|
||||
conv_id = SHA256("utun_dm_v1" || min(node_a,node_b) || max(node_a,node_b))[..63]
|
||||
content_key = SHA256(X25519(my_private, target_public) || "utun_dm_content")
|
||||
msg_nonce = SHA256(conv_id || author || seq)[..13]
|
||||
media_nonce = SHA256("utun_dm_media" || author || media_id || part)[..13]
|
||||
```
|
||||
|
||||
conv_id = SHA256("utun_dm_v1" || min(node_a,node_b) || max(node_a,node_b))[..63]
|
||||
shared = X25519(my_x25519_priv, peer_x25519_pub)
|
||||
content_key = SHA256(shared || "utun_dm_content")
|
||||
Сообщения и медиа используют один content_key target, без ключей отдельных файлов.
|
||||
AES-256-CCM: nonce 13 байт, tag 16 байт. Максимальная порция plaintext — 65535 байт;
|
||||
порция медиа — 32768 байт. UUID медиа новый для каждой подготовки. Повтор доставки
|
||||
использует неизменяемый ciphertext. Повторная подготовка после ошибки требует нового UUID.
|
||||
|
||||
Обе стороны выводят одинаковые значения из своего privkey и pubkey пира
|
||||
(известен из общей группы). Смена x25519 у пира = новая ветка.
|
||||
Каноническое тело сообщения:
|
||||
|
||||
## 4. Модель данных (SQLite, общий chats.db)
|
||||
```text
|
||||
[conv:8][seq:8][ts:8][author:8][ct_len:1][ct][enc_len:2][ciphertext+tag][author_sig:64]
|
||||
```
|
||||
|
||||
dm_conversations(conv_id TEXT PK, peer_node_id, peer_x25519 BLOB, peer_ed25519 BLOB,
|
||||
peer_name TEXT, last_out_seq INT, last_in_seq INT, created_at INT, last_ts INT)
|
||||
dm_messages(conv_id TEXT, dir INT, seq INT, ts INT, author INT, ct TEXT,
|
||||
data BLOB, sig BLOB, PRIMARY KEY(conv_id,dir,seq))
|
||||
dm_mail(recipient INT, sender INT, conv_id TEXT, seq INT, ts INT, data BLOB, sig BLOB,
|
||||
ttl INT, PRIMARY KEY(recipient,sender,seq))
|
||||
Автор подписывает все поля до sig. conv связывает автора с получателем.
|
||||
Размер plaintext сообщения ограничен 1024 байтами. seq — положительное int64.
|
||||
|
||||
Два независимых направленных потока (A→B, B→A) с монотонным seq.
|
||||
Без chain-хеша: для 2 пиров достаточно seq + catch-up по диапазону.
|
||||
Медиа ciphertext — последовательность порций с отдельным tag каждой порции.
|
||||
Последняя порция короче; пустой файл содержит один tag. Размер plaintext и SHA256
|
||||
ciphertext должны входить в подписанный manifest. Потоковая расшифровка проверяет все
|
||||
tags, точный размер и хеш; владелец публикует временный файл только после успеха
|
||||
и собственного durable сохранения.
|
||||
|
||||
## 5. Доставка
|
||||
## Владение и данные
|
||||
|
||||
1. Проверка доступности: peer присутствует в BGP-группе (общая группа).
|
||||
2. Доступен → прямой send (сервис ETCP_RT_ID_DM = 0x34), ждём DM_ACK{conv_id,seq}.
|
||||
Нет ACK за таймаут → fallback в mailbox.
|
||||
3. Нет связи → MAIL_PUT на 1..N storage-узлов.
|
||||
4. Catch-up при connect: DM_HELLO{conv_id,last_out_seq,last_in_seq} → пир досылает
|
||||
(in_seq+1 .. out_seq).
|
||||
5. Dedup: вставка только при seq > last_in_seq.
|
||||
- `dm_core`: беседы, история, outbox и подтверждение получателя.
|
||||
- `dm_mailbox`: промежуточные сообщения суперузла и квитанции.
|
||||
- `dm_crypto`: вывод ключей, nonce, CCM и файловые примитивы без БД/сети.
|
||||
|
||||
## 6. E2E-шифрование
|
||||
```text
|
||||
dm_conversations(conv_id, peer_node_id, peer_x25519, peer_ed25519, peer_name,
|
||||
group_id, last_out_seq, last_in_seq, created_at, last_ts)
|
||||
dm_messages(conv_id, dir, seq, ts, author, ct, data, sig)
|
||||
dm_outbox(conv_id, seq, body, retry_at)
|
||||
dm_pending_messages(recipient, sender, seq, msg, group_id, retry_at)
|
||||
dm_mail_receipts(recipient, sender, seq, receipt, group_id)
|
||||
```
|
||||
|
||||
data_enc = nonce(13B, счётчик по seq) || AES-CCM_ct || tag
|
||||
Запись исходящего сообщения, увеличение seq и запись неизменяемого body в outbox —
|
||||
одна транзакция. При ошибке откатывается всё, сообщение не выходит в сеть.
|
||||
Приём, создание/обновление беседы и история — также одна транзакция.
|
||||
|
||||
secure_channel уже умеет AES-CCM. Storage/relay видят только шифротекст.
|
||||
Dedup — точный ключ (conv,dir,seq) с проверкой сохранённой подписи. last_in_seq —
|
||||
статистика максимального номера, не курсор подтверждения: seq=99 после seq=100
|
||||
сохраняется. Тело с другой подписью для уже сохранённого seq отклоняется.
|
||||
|
||||
## 7. Storage-узлы (dm_storage)
|
||||
У каждой локальной очереди таймер 1с; порция обработки до 32 задач, повтор задачи
|
||||
через 5с. retry_at предотвращает голодание хвоста очереди. Перед отправкой проверяется
|
||||
backpressure router. Потеря пути сохраняет durable задачу; остановка отменяет таймеры,
|
||||
но сохраняет БД. При старте очередь продолжает работу.
|
||||
|
||||
- Новый флаг dm_storage=yes в adm_tags (блок B владельца), распространяется
|
||||
существующим member_sync без нового синка.
|
||||
- DM читает adm_tags мемберов общих групп (peers_<ch>) и кэширует storage-узлы.
|
||||
- Протокол mailbox (сервис ETCP_RT_ID_DM_MAILBOX = 0x35):
|
||||
## Подтверждения и протокол
|
||||
|
||||
MAIL_PUT {recipient,sender,conv_id,seq,ts,data,sig}
|
||||
MAIL_PULL {recipient, per-sender cursor}
|
||||
MAIL_ACK {up_to_seq}
|
||||
Сервис `ETCP_RT_ID_DM=0x34`: MSG=1, RECEIPT=2.
|
||||
|
||||
TTL 7 дней + лимит объёма, dedup по (recipient,sender,seq).
|
||||
```text
|
||||
["DMACK001":8][conv:8][seq:8][author:8][recipient:8][SHA256(message):32][recipient_sig:64]
|
||||
```
|
||||
|
||||
## 8. Старт и anti-spam
|
||||
Квитанцию создаёт только получатель после commit. Подпись охватывает первые 72 байта.
|
||||
Отправитель удаляет outbox только после проверки подписи и точного хеша тела.
|
||||
История беседы сохраняется. Подтверждение транспорта router не заменяет квитанцию.
|
||||
|
||||
- dm_start(peer): создать беседу (вывести conv_id/ключи), обеспечить прямое
|
||||
соединение (переиспользовать chat_sync_connect_node/node_conn_direct).
|
||||
- Пир автопринимает на первом DM_HELLO/сообщении, только если отправитель в
|
||||
общей группе.
|
||||
Сервис `ETCP_RT_ID_DM_MAILBOX=0x35`:
|
||||
|
||||
## 9. Файлы и интеграция
|
||||
```text
|
||||
PUT=1 [recipient:8][message]
|
||||
DELIVER=2 [message]
|
||||
RECEIPT=3 [receipt:136]
|
||||
STORED=4 [conv:8][seq:8]
|
||||
```
|
||||
|
||||
| Файл | Назначение |
|
||||
|------|-----------|
|
||||
| src/dm/dm_core.c/h | беседы, send/recv, таблицы, bind 0x34, conn-события, fallback |
|
||||
| src/dm/dm_crypto.c/h | derive + AES-CCM |
|
||||
| src/dm/dm_mailbox.c/h | storage-узел, push/pull/ack, bind 0x35 |
|
||||
Все пакеты идут с подписью router; unsigned control отклоняется.
|
||||
PUT принимается только от автора, при роли supernode=yes у локального узла
|
||||
в общей группе автора и получателя. Роль берётся из подписанного adm_tags;
|
||||
node_type=4 — производная сетевой классификации, для авторизации не используется.
|
||||
storage=yes само по себе не даёт права хранить сообщения.
|
||||
|
||||
Плюс:
|
||||
STORED подтверждает durable запись суперузлом, outbox до квитанции получателя остаётся
|
||||
у автора. Суперузел повторяет DELIVER при появлении живого маршрута. RECEIPT проверяется
|
||||
по точному сохранённому телу; запись квитанции и удаление копии — одна транзакция.
|
||||
Квитанция пересылается автору. Поздний PUT возвращает её и не возрождает задачу.
|
||||
|
||||
- src/chat/chat_member.c — бит CHAT_MEMBER_FLAG_DM_STORAGE + парсинг dm_storage.
|
||||
- utun_instance.c — dm_core_init/destroy.
|
||||
- chat_core — API dm_start/send/list + события CHAT_EVT_DM_*.
|
||||
- headless CLI — dm list/send/messages.
|
||||
- src/Makefile.am — новые источники.
|
||||
dm_mail_receipts содержит только служебные квитанции; доставленное содержимое
|
||||
с промежуточного узла удаляется. Ранее использовавшийся dm_mail/PULL/HELLO и
|
||||
диапазонные ACK не поддерживаются: это devel, совместимость не сохраняется.
|
||||
TTL текстовых сообщений не введён; недельный TTL из pm.txt относится к медиа.
|
||||
|
||||
## 10. Сервисы ETCP
|
||||
## Следующая интеграция медиа по pm.txt
|
||||
|
||||
ETCP_RT_ID_DM = 0x34
|
||||
ETCP_RT_ID_DM_MAILBOX = 0x35
|
||||
1. Общая передача байтов: явные peer/group/file/size, backpressure и CM handle
|
||||
на всю активную операцию. Владение handle сохраняется после DOWN/TIMEOUT.
|
||||
Передавать через собственный svc router; conn_mgr_send использует управляющий сервис CM.
|
||||
2. Отдельный owner dm_media: подготовка ciphertext, manifest, отправка/скачивание,
|
||||
временные файлы, TTL и лимит хранения. Каталоги dm_media/ и dm_pending/ вне
|
||||
группового обхода очистки orphan-файлов; групповые объявления HAVE_BLOCK не используются.
|
||||
3. Суперузел сначала durable регистрирует сообщение и media job, затем выбирает M.
|
||||
M скачивает ciphertext у A и сообщает S подтверждённое хранение, место и expiry.
|
||||
4. Текстовая квитанция и квитанция медиа независимы. Получатель подтверждает медиа
|
||||
после проверки, публикации файла и commit. S хранит задачу удаления у M до DELETE_ACK;
|
||||
поздний STORED не возрождает завершённую доставку.
|
||||
5. TTL медиа по умолчанию 7 дней, конфиг на M. Новая custody отклоняется при
|
||||
недостатке квоты; уже подтверждённые копии не вытесняются молча раньше срока.
|
||||
6. Stop снимает новые задачи, ждёт media_async с живыми owner-контекстами, затем
|
||||
закрывает операции/handles до уничтожения групп.
|
||||
|
||||
## Проверки
|
||||
|
||||
`test_dm`: симметрия ключа target, CCM round-trip, пустое тело, пределы длины,
|
||||
неверный ключ/tag, разделение nonce; потоковые файлы на границах 32 КиБ,
|
||||
неверные author/UUID/hash, обрезание и лишние байты.
|
||||
|
||||
`test_dm_e2e`: реальные A–C–B соединения и BGP; нет физического A–B линка.
|
||||
Проверяет out-of-order, dedup/conflict, подписи, SQL rollback при приёме и enqueue,
|
||||
ACK чужого тела, отказ mailbox без роли, точное удаление и поздний PUT.
|
||||
Первый сценарий перезапускает источник с pending outbox; второй перезапускает
|
||||
суперузел и доставляет первое сообщение при выключенном источнике.
|
||||
|
||||
Сетевые тесты CM-передачи медиа, media custody/TTL/quota и независимых квитанций
|
||||
нужно добавить вместе с сетевой интеграцией медиа.
|
||||
Reference in new issue
Block a user