НЕ ПРОВЕРЕНО: откат DM-вложений, оставлены ревизии и удаление

This commit is contained in:
AidarKC
2026-06-18 12:24:14 +04:00
parent 92fd315505
commit a95bd245cf
23 changed files with 309 additions and 1267 deletions
+1 -1
View File
@@ -61,6 +61,6 @@
## Важные замечания
- `ReceiveOutcomingMessage` сейчас зарегистрирован как алиас того же handler/request-класса, что и `SendMessagePair`.
- HTTP endpoints для DM-файлов (`HEAD/GET /f/<hashB64url>` и `POST /upload`) не являются WebSocket `op`, поэтому в таблицу выше не входят; они описаны в `12_Direct_Messages_Push_Calls_API.md`.
- Отдельных HTTP endpoints для DM-файлов сейчас нет.
- Классы `Net_MarkChannelMessagesSeen_*` существуют в коде, но операция `MarkChannelMessagesSeen` не зарегистрирована в `JsonHandlerRegistry`, поэтому в публичный список API не входит.
- HTTP debug endpoints из `src/main/java/server/debug/` не входят в этот индекс WebSocket `op`; они описаны отдельно в `13_HTTP_Debug_API.md`.
@@ -1,8 +1,10 @@
# API для разработчиков: DM, файлы, push и сигналы звонков
# API для разработчиков: DM, push и сигналы звонков
Документ описывает публичные операции и endpoints, связанные с личными сообщениями, файлами для DM, WebPush и сигналами звонков.
Документ описывает публичные операции, связанные с личными сообщениями, WebPush и сигналами звонков.
Подробная логика DM и бинарного формата: `Dev_Docs/Personal_Messages/README.md`.
Подробная логика DM и бинарного формата:
- `Dev_Docs/Personal_Messages/README.md`
## 1. `UpsertPushToken`
@@ -70,7 +72,7 @@
- `incomingBlobB64` — блок `type=1` или `type=3`
- `outgoingBlobB64` — блок `type=2` или `type=4`
Для контентных сообщений `type=1/2` внутри base64 лежит новый бинарный формат `SHiNE_DM`.
Для контентных сообщений `type=1/2` внутри base64 лежит бинарный формат `SHiNE_DM`.
### Запрос
@@ -108,12 +110,31 @@
- `400 / BAD_FIELDS` — пустой `incomingBlobB64` или `outgoingBlobB64`
- `400 / BAD_BLOCK_FORMAT` — base64 или бинарный контейнер повреждён
- `400 / BAD_CONTENT_FORMAT` — для контентного сообщения пришёл не `SHiNE_DM`
- `400 / TOO_MANY_ATTACHMENTS` — больше 12 вложений
- `400 / ATTACHMENT_NOT_FOUND` — сообщение ссылается на blob, которого нет на сервере
- `400 / ATTACHMENTS_DISABLED` — в `SHiNE_DM` пришёл `attachmentsCount != 0`
- `404 / USER_NOT_FOUND` — один из логинов не найден
- `460 / BAD_SIGNATURE` — подпись блока не прошла проверку
## 4. `AckSessionDelivery`
## 4. `ReceiveIncomingMessage`
Принимает только один входящий signed DM-блок.
### Назначение
Используется там, где нужно принять только incoming-вариант сообщения.
### Запрос
```json
{
"op": "ReceiveIncomingMessage",
"requestId": "dm-in-001",
"payload": {
"incomingBlobB64": "BASE64_INCOMING_SIGNED_BLOCK"
}
}
```
## 5. `AckSessionDelivery`
Требует авторизации. Подтверждает доставку в текущую сессию.
@@ -129,7 +150,7 @@
}
```
## 5. Событие `SignedMessageArrived`
## 6. Событие `SignedMessageArrived`
Сервер присылает его по WebSocket в активные сессии адресата.
@@ -152,71 +173,18 @@
Если это новая ревизия того же письма, `messageKey` остаётся тем же, а `revisionTimeMs` меняется внутри бинарного блока.
## 6. HTTP `HEAD /f/<hashB64url>`
Проверка, есть ли ciphertext-файл на сервере.
### Ответы
- `200` — файл существует
- `404` — файла нет
## 7. HTTP `GET /f/<hashB64url>`
Отдаёт ciphertext-файл.
### Особенности
- `Content-Type: application/octet-stream`
- файл сейчас доступен публично
- имя файла на диске и в URL — `base64url(SHA-256(ciphertext))`
## 8. HTTP `POST /upload?hash=<hashB64url>&size=<bytes>`
Загружает ciphertext-файл для будущего DM.
### Тело запроса
Raw bytes ciphertext-файла.
### Поведение сервера
- пересчитывает `SHA-256`
- сверяет размер
- сохраняет blob в папку `f/`, если его ещё не было
- если blob уже есть, не перезаписывает его
- создаёт или обновляет запись в `dm_files`
### Успешный ответ
```json
{
"ok": true,
"hash": "base64url_sha256",
"size": 245120,
"alreadyExists": false
}
```
### Ошибки
- `400 / bad hash`
- `400 / bad size`
- `400 / SIZE_MISMATCH`
- `400 / HASH_MISMATCH`
- `400 / UPLOAD_TOO_LARGE`
- `500 / upload_failed`
## 9. `CallInviteBroadcast`
## 7. `CallInviteBroadcast`
Требует авторизации. Шлёт приглашение к звонку в активные сессии `toLogin`.
## 10. `CallSignalToSession`
## 8. `CallSignalToSession`
Требует авторизации. Шлёт сигнал звонка в конкретную сессию.
## 11. Замечания
## 9. Замечания
- Для нового DM-файла сценарий такой: `HEAD /f/<hash>` → при `404` `POST /upload` → затем `SendMessagePair`.
- Сервер хранит только последнюю версию контентного сообщения по `messageKey`.
- Удаление сообщения реализуется новой ревизией с пустым телом и нулём вложений.
- read-receipt `type=3/4` пока остаются в legacy-формате `SHiNE_dm2`
- контентные DM `type=1/2` используют `SHiNE_DM`
- сервер хранит только последнюю версию контентного сообщения по `messageKey`
- удаление сообщения реализуется новой ревизией с пустым телом и `attachmentsCount = 0`
- HTTP endpoints для DM-файлов сейчас отсутствуют
@@ -1,19 +1,18 @@
# DM-вложения, upload и ревизии сообщений
# Ревизии и удаление личных сообщений
- краткое описание фичи:
Добавлен новый формат контентных DM `SHiNE_DM`, HTTP upload/download ciphertext-файлов, серверный `upsert` последней версии сообщения и UI-скачивание/расшифровка вложений.
Добавлен новый формат контентных DM `SHiNE_DM` без вложений, серверный `upsert` последней версии сообщения, редактирование через `revisionTimeMs` и удаление пустой ревизией.
- что проверять:
1. Отправка обычного текста без вложений.
2. Отправка сообщения с 1-2 вложениями.
3. Повторная отправка сообщения с уже загруженным файлом без повторной записи blob.
4. Скачивание вложения из UI и корректная расшифровка файла.
5. Доставка backlog после переподключения сессии.
6. Обновление существующего сообщения той же парой `timeMs+nonce` и большим `revisionTimeMs`.
7. Удаление сообщения пустой ревизией (`attachments=0`, `encryptedBodyLen=0`) и исчезновение из UI.
2. Повторная отправка того же логического сообщения с тем же `timeMs + nonce`, но большим `revisionTimeMs`.
3. Обновление текста у уже существующего сообщения в UI без появления нового пузыря.
4. Игнорирование более старой ревизии на сервере.
5. Удаление сообщения пустой ревизией (`attachmentsCount = 0`, `encryptedBodyLen = 0`) и исчезновение из UI.
6. Доставка backlog после переподключения сессии для последней версии сообщения.
- ожидаемый результат:
Сообщения `type=1/2` приходят в формате `SHiNE_DM`, файлы доступны по `/f/<hash>`, UI показывает вложения кнопками скачивания, сервер хранит только последнюю ревизию по `messageKey`, а пустая ревизия убирает сообщение из интерфейса.
Контентные сообщения `type=1/2` приходят в формате `SHiNE_DM`, сервер хранит только последнюю ревизию по `messageKey`, более старая ревизия не перетирает новую, а пустая ревизия убирает сообщение из интерфейса.
- статус:
pending
+119 -283
View File
@@ -1,52 +1,62 @@
# Личные сообщения (DM): как это устроено
# Личные сообщения (DM)
## Коротко
## Текущее состояние
Личные сообщения в SHiNE теперь работают в двух слоях:
Сейчас в проекте реализованы:
- контентные сообщения `type=1/2` идут в новом бинарном формате `SHiNE_DM`;
- read-receipt `type=3/4` пока остаются на legacy-формате `SHiNE_dm2`, чтобы не ломать текущую механику подтверждения прочтения.
- новый формат контентных личных сообщений `SHiNE_DM`;
- ревизии сообщений через `revisionTimeMs`;
- редактирование сообщения через повторную отправку той же логической пары;
- удаление сообщения через пустую ревизию;
- `upsert` последней версии сообщения на сервере.
Одно логическое сообщение по-прежнему отправляется парой блоков:
Сейчас в проекте **не реализованы**:
- `type=1` — входящее сообщение для получателя;
- вложения в DM;
- upload/download файлов для DM;
- UI-кнопка прикрепления файла;
- серверное хранение файловых связей для DM.
Черновик будущих вложений вынесен отдельно:
- `Dev_Docs/Personal_Messages/Черновик_будущих_DM_вложений.md`
## Общая схема
Личное сообщение по-прежнему отправляется парой signed-блоков:
- `type=1` — входящий блок для получателя;
- `type=2` — исходящая копия для отправителя.
Ключ сообщения остаётся прежним:
Read-receipt пока остаются в legacy-формате:
- `baseKey = from|to|timeMs|nonce`
- `type=3` — входящее подтверждение прочтения;
- `type=4` — исходящая копия подтверждения.
Ключи сообщения:
- `baseKey = fromLogin|toLogin|timeMs|nonce`
- `messageKey = baseKey|messageType`
Теперь `timeMs + nonce` задаются один раз на всё логическое сообщение и не меняются при редактировании.
Для новой версии того же письма используется то же `messageKey`, но большее `revisionTimeMs`.
Сервер хранит только последнюю версию записи по этому `messageKey` через `upsert`.
Логический идентификатор письма задаётся парой:
---
- `timeMs`
- `nonce`
## 1) Общая схема потока
Эти поля не меняются при редактировании или удалении. Меняется только:
1. Клиент при необходимости сначала шифрует вложенные файлы локально.
2. Клиент считает `SHA-256(ciphertext)` и размер ciphertext.
3. Клиент проверяет наличие blob через `HEAD /f/<hashB64url>`.
4. Если файла нет, клиент загружает ciphertext через `POST /upload?hash=<hashB64url>&size=<bytes>`.
5. После загрузки всех файлов клиент строит пару signed DM-блоков `type=1/2`.
6. Сервер валидирует подписи, формат, наличие blob-файлов и делает `upsert` пары.
7. В одной транзакции сервер:
- удаляет старые файловые связи этого сообщения;
- корректирует `ref_count`;
- записывает новую версию `signed_messages_v2`;
- создаёт новые файловые связи;
- сбрасывает pending-доставку по сессиям.
8. Сервер рассылает обновлённые блоки в активные сессии через `SignedMessageArrived`.
9. Клиент обновляет существующий пузырь по `messageKey`; если тело и вложения пустые — сообщение удаляется из UI.
- `revisionTimeMs`
- содержимое `encryptedBody`
---
Сервер хранит только последнюю версию записи для каждого `messageKey`.
## 2) Формат signed DM-блока для контентных сообщений (`SHiNE_DM`)
## Формат контентного DM: `SHiNE_DM`
Префикс: `SHiNE_DM` (ASCII).
Префикс бинарного блока:
Далее поля, big-endian:
- `SHiNE_DM`
Поля идут в big-endian порядке:
1. `formatVersionMajor` (`u8`) = `1`
2. `formatVersionMinor` (`u8`) = `0`
@@ -56,27 +66,24 @@
6. `nonce` (`u32`)
7. `messageType` (`u16`) — только `1` или `2`
8. `revisionTimeMs` (`u64`)
9. `attachmentsCount` (`u8`)`0..12`
10. `attachments[]`:
- `encFileHashSHA256` (`32 bytes`)
- `encFileSize` (`u64`)
11. `encryptedBodyLen` (`u32`) — сервер сейчас ограничивает до `16384`
12. `encryptedBody` (`bytes`)
13. `signature` (`64 bytes`, Ed25519)
9. `attachmentsCount` (`u8`)
10. `encryptedBodyLen` (`u32`)
11. `encryptedBody` (`bytes`)
12. `signature` (`64 bytes`, Ed25519)
### Важные правила
### Ограничения
- `messageType` не входит в ID логического письма, он только различает сторону пары.
- ID логического письма = `fromLogin + toLogin + timeMs + nonce`.
- У оригинала `revisionTimeMs = 0`.
- Для редактирования и удаления `timeMs`/`nonce` не меняются, меняется только `revisionTimeMs`.
- Чем больше `revisionTimeMs`, тем новее версия.
- `attachmentsCount` сейчас всегда должен быть `0`
- `encryptedBodyLen` сейчас ограничен сервером до `16384` байт
- `revisionTimeMs` не может быть отрицательным
---
Если приходит `attachmentsCount != 0`, сервер отклоняет такой DM как:
## 3) Legacy read-receipt (`SHiNE_dm2`)
- `ATTACHMENTS_DISABLED`
Пока только блоки `type=3/4` остаются в старом формате `SHiNE_dm2`:
## Legacy read-receipt: `SHiNE_dm2`
Подтверждения прочтения `type=3/4` пока используют старый контейнер `SHiNE_dm2`:
1. `toLoginLen` (`u8`) + `toLogin`
2. `fromLoginLen` (`u8`) + `fromLogin`
@@ -87,278 +94,107 @@
7. `payloadBytes`
8. `signature`
Это временный совместимый слой. Контентные сообщения `1/2` в `SHiNE_dm2` больше не считаются актуальным форматом.
## Редактирование
---
Редактирование делается новой отправкой той же логической пары сообщения:
## 4) Внешний контейнер вложений
- `timeMs` и `nonce` остаются теми же;
- `messageType` остаётся `1/2`;
- `revisionTimeMs` становится больше;
- `encryptedBody` содержит новую версию текста.
Внешняя часть сообщения содержит только технические ссылки на blob-файлы:
Если на сервер приходит более старая ревизия, она игнорируется.
- `attachmentsCount`
- список `encFileHashSHA256 + encFileSize`
Если приходит та же ревизия и тот же бинарный блок, сервер тоже её не применяет повторно.
Во внешней части **нет**:
## Удаление
- имени файла;
- MIME;
- пароля/ключа;
- nonce/iv;
- `origHash`.
Удаление личного сообщения делается как новая ревизия того же сообщения:
Это позволяет серверу хранить и отдавать blob, не зная человеческих метаданных вложения.
- `timeMs` и `nonce` остаются прежними;
- `revisionTimeMs` увеличивается;
- `attachmentsCount = 0`;
- `encryptedBodyLen = 0`;
- `encryptedBody` пустой.
---
В UI такое сообщение не показывается.
## 5) Внутреннее содержимое `encryptedBody`
На сервере это не отдельный тип сообщения, а просто последняя пустая ревизия того же `messageKey`.
Сейчас `encryptedBody` содержит текстовый контейнер сообщения, который UI интерпретирует как текст + встроенные метки файлов.
## Поведение сервера
Формат маркера:
Для контентных DM сервер:
```text
<<file:file-format(1.0):type|fileName|origSize|origHashB64u|encHashB64u|encSize|keyB64u|nonceB64u>>
```
1. принимает пару signed-блоков `type=1/2`;
2. валидирует формат, подпись и совпадение ключевых полей пары;
3. проверяет, что для обеих сторон пары совпадают:
- `fromLogin`
- `toLogin`
- `timeMs`
- `nonce`
- `revisionTimeMs`
- `encryptedBody`
4. делает `upsert` последней версии в `signed_messages_v2`;
5. сбрасывает pending-доставку по сессиям для новой ревизии;
6. рассылает актуальную версию адресатам через `SignedMessageArrived`.
Где:
История старых ревизий сейчас не хранится отдельно: в таблице остаётся только последняя версия по каждому `messageKey`.
- `type` = `photo` / `video` / `audio` / `file`
- `fileName` — настоящее имя файла, без символов `|`, `:`, `>`, переводов строки
- `origSize` — размер исходного файла
- `origHashB64u``SHA-256` исходного файла в `base64url`
- `encHashB64u``SHA-256` ciphertext-файла в `base64url`
- `encSize` — размер ciphertext-файла
- `keyB64u` — симметричный ключ расшифровки файла
- `nonceB64u` — nonce/iv для расшифровки файла
## Хранение в БД
UI:
Основная таблица:
- показывает обычный текст без маркеров;
- заменяет маркеры карточками скачивания;
- скачивает ciphertext по `/f/<encHashB64u>`;
- локально расшифровывает файл и отдаёт пользователю оригинал.
- `signed_messages_v2`
---
Для контентных DM в ней используются:
## 6) RPC и события
### `SendMessagePair` / `ReceiveOutcomingMessage`
Запрос не меняется: сервер по-прежнему принимает пару `incomingBlobB64` + `outgoingBlobB64`.
```json
{
"op": "SendMessagePair",
"requestId": "req-1",
"payload": {
"incomingBlobB64": "<base64 signed block type 1 or 3>",
"outgoingBlobB64": "<base64 signed block type 2 or 4>"
}
}
```
Успешный ответ:
```json
{
"op": "SendMessagePair",
"requestId": "req-1",
"status": 200,
"ok": true,
"payload": {
"baseKey": "from|to|time|nonce",
"incomingKey": "from|to|time|nonce|1",
"outgoingKey": "from|to|time|nonce|2",
"deliveredWsSessions": 2,
"deliveredWebPushSessions": 1
}
}
```
### `SignedMessageArrived`
Событие в сессию всё ещё содержит:
- `messageKey`, `baseKey`
- `fromLogin`, `toLogin`, `targetLogin`
- `messageType`, `timeMs`, `nonce`
- `blobB64`
- `backlog`
Новая версия того же письма приходит с тем же `messageKey`, но с более новым `revisionTimeMs` внутри бинарного блока.
### `AckSessionDelivery`
Формат не меняется.
---
## 7) HTTP endpoints для файлов
### `HEAD /f/<hashB64url>`
Проверяет наличие ciphertext-файла.
- `200` — файл есть;
- `404` — файла нет.
### `GET /f/<hashB64url>`
Отдаёт ciphertext-файл как `application/octet-stream`.
Сейчас доступ публичный, без проверки логина.
### `POST /upload?hash=<hashB64url>&size=<bytes>`
Принимает raw bytes ciphertext-файла.
Сервер:
- пересчитывает `SHA-256`;
- проверяет размер;
- сохраняет blob в папку `f/` под именем `<hashB64url>`;
- если файл уже существует, повторно не пишет его на диск;
- регистрирует строку в `dm_files`.
---
## 8) Хранение на сервере (SQLite)
### `signed_messages_v2`
Основная таблица текущих DM:
- `message_key` (PK)
- `message_key`
- `base_key`
- `target_login`
- `from_login`, `to_login`
- `time_ms`, `nonce`
- `from_login`
- `to_login`
- `time_ms`
- `nonce`
- `message_type`
- `revision_time_ms`
- `raw_block`
- `created_at_ms`
- `source_api`, `origin_session_id`
- `receipt_ref_base_key`, `receipt_ref_type`
Для контентных сообщений сервер делает `upsert` по `message_key`, поэтому в таблице всегда лежит только последняя версия конкретной стороны пары.
Отдельных таблиц файлов для DM сейчас нет.
### `signed_message_session_delivery`
## События и доставка
Хранит pending/ack по сессиям:
Запрос на отправку по WebSocket остаётся прежним:
- `(message_key, session_id)` — PK
- `delivered`
- `delivered_at_ms`
- `created_at_ms`
- `SendMessagePair`
- `ReceiveOutcomingMessage` как алиас
При новой ревизии того же сообщения сервер сбрасывает доставку этого `message_key` обратно в `delivered=0`.
Клиент отправляет:
### `dm_files`
- `incomingBlobB64`
- `outgoingBlobB64`
- `file_hash_sha256` (`BLOB`, PK)
- `file_size`
- `ref_count`
Событие в активные сессии:
### `dm_message_file_links`
- `SignedMessageArrived`
- `message_key`
- `login`
- `file_hash_sha256`
Если пришла новая ревизия того же сообщения, `messageKey` остаётся прежним, а внутри `blobB64` будет более новый `revisionTimeMs`.
По этой таблице сервер понимает, какие файловые ссылки нужно снять при редактировании/удалении сообщения.
Подтверждение доставки в сессию:
`ref_count` считается по числу логических message-side ссылок:
- `AckSessionDelivery`
- у одного письма с вложением обычно две ссылки:
- получатель (`type=1`)
- отправитель (`type=2`)
## Правила UI
Файлы с `ref_count = 0` на диске не удаляются автоматически.
UI сейчас работает так:
---
- показывает только текст `encryptedBody`;
- умеет обновлять уже существующее сообщение по тому же `messageKey`;
- не показывает удалённые сообщения;
- не показывает и не принимает вложения.
## 9) Доставка, редактирование и удаление
## Что обязательно помнить
### Новое сообщение
- `revisionTimeMs = 0`
- создаётся пара `1/2`
- сервер делает upsert, создаёт файловые связи и доставляет событие
### Редактирование
- используется тот же `timeMs + nonce`
- отправляется новая пара `1/2`
- `revisionTimeMs` больше
- может измениться и `encryptedBody`, и список вложений
### Удаление
- тот же `timeMs + nonce`
- новая ревизия с большим `revisionTimeMs`
- `attachmentsCount = 0`
- `encryptedBodyLen = 0`
UI такое сообщение полностью убирает из чата.
---
## 10) Логика UI-клиента
### Хранилище сообщений
- in-memory: `state.chats[chatId]`
- IndexedDB: `shine-ui-messages-v1`, store `messages`, key = `messageKey`
Так как `messageKey` теперь стабилен для всех ревизий одного message-side, клиент делает не append, а update той же записи.
### Поведение UI
- входящая новая ревизия с тем же `messageKey` обновляет существующий пузырь;
- пустая ревизия (`attachments=0`, `encryptedBodyLen=0`) удаляет пузырь из IndexedDB и из in-memory;
- вложения показываются кнопками скачивания;
- ciphertext скачивается с HTTP, затем расшифровывается локально в браузере.
---
## 11) Межсерверная синхронизация
Межсерверный relay DM пока не реализован.
Когда он появится, серверы должны будут:
- синхронизировать только актуальную ревизию `message_key`;
- применять правило "больший `revisionTimeMs` побеждает";
- одинаково пересчитывать файловые связи и `ref_count`.
---
## 12) Инварианты
1. Пара `1/2` должна применяться атомарно.
2. `baseKey/messageKey` формат не меняется.
3. Для одного `messageKey` в `signed_messages_v2` хранится только последняя версия.
4. Все изменения DM и файловых связей применяются одной транзакцией.
5. Для контентных сообщений обязательна предварительная загрузка blob-файлов.
6. Для одного сообщения разрешено не больше `12` вложений.
7. UI не показывает удалённые сообщения.
---
## 13) Ключевые файлы реализации
- UI:
- `shine-UI/js/services/auth-service.js`
- `shine-UI/js/services/crypto-utils.js`
- `shine-UI/js/state.js`
- `shine-UI/js/app.js`
- `shine-UI/js/pages/chat-view.js`
- Server:
- `SHiNE-server/shine-server-net-protocol/.../messages/SignedMessageBlock.java`
- `SHiNE-server/shine-server-net-protocol/.../messages/SignedMessagesCore.java`
- `SHiNE-server/shine-server-net-protocol/.../messages/Net_SendMessagePair_Handler.java`
- `SHiNE-server/shine-server-net-protocol/.../messages/SignedMessagesRealtime.java`
- `SHiNE-server/shine-server-net-protocol/.../messages/DmFileStorage.java`
- `SHiNE-server/shine-server-db/.../dao/SignedMessagesV2DAO.java`
- `SHiNE-server/src/main/java/server/files/DmFilesServlet.java`
- `SHiNE-server/src/main/java/server/files/DmUploadServlet.java`
- вложения в DM сейчас отключены на уровне протокола и UI;
- любые старые описания `/f/...`, `/upload` и файловых таблиц для DM больше не актуальны;
- если позже вложения вернутся, их формат и серверная логика могут быть другими.
@@ -0,0 +1,73 @@
# Черновик будущих вложений в DM
## Важно
Этот документ описывает только ранний черновик идеи.
Сейчас в проекте **нет** поддержки вложений в личных сообщениях:
- в реализованном формате `SHiNE_DM` поле `attachmentsCount` пока всегда должно быть `0`;
- UI не показывает кнопку прикрепления файлов;
- сервер не принимает upload файлов для DM;
- сервер не раздаёт специальные DM-файлы по отдельным endpoints;
- сервер не хранит отдельные файловые связи для личных сообщений.
Этот документ нужен только для того, чтобы рядом с актуальной документацией было явно видно:
- какие идеи обсуждались;
- что это **не реализовано**;
- что формат, хранение и способ загрузки потом могут сильно измениться.
## Что обсуждалось
Рассматривался такой общий подход:
- у контентного DM есть внешний список вложений;
- во внешнем формате лежат только технические данные;
- человекочитаемые данные о файле живут внутри зашифрованного тела сообщения;
- один и тот же blob-файл теоретически мог бы переиспользоваться в нескольких сообщениях.
Черновой вариант внешнего списка:
- `attachmentsCount`
- далее для каждого вложения:
- `encFileHashSHA256` (`32 bytes`)
- `encFileSize` (`u64`)
Черновой вариант внутреннего маркера в тексте:
```text
<<file:file-format(1.0):type|fileName|origSize|origHashB64u|encHashB64u|encSize|keyB64u|nonceB64u>>
```
Где обсуждались поля:
- `type`
- `fileName`
- `origSize`
- `origHashB64u`
- `encHashB64u`
- `encSize`
- `keyB64u`
- `nonceB64u`
## Что может измениться
В будущем могут измениться любые части идеи:
- сам бинарный формат;
- способ привязки файлов к сообщению;
- момент загрузки файла относительно отправки сообщения;
- серверное хранение blob-файлов;
- права доступа к скачиванию;
- способ рендера вложения в UI.
Именно поэтому этот файл не надо воспринимать как актуальную спецификацию.
## Источник истины на сейчас
Актуальное состояние личных сообщений описано только в:
- `Dev_Docs/Personal_Messages/README.md`
Если между этим черновиком и основным README есть расхождение, верным считается `README.md`.