SHA256
198 lines
9.6 KiB
Markdown
198 lines
9.6 KiB
Markdown
# Технические вставки внутри plaintext DM v1
|
||
|
||
## Статус документа
|
||
|
||
Этот файл описывает внутренний формат технических вставок, которые находятся внутри уже расшифрованного plaintext DM.
|
||
|
||
Важно:
|
||
|
||
- это не отдельный серверный DM-envelope;
|
||
- это часть plaintext контентного DM после E2EE-расшифровки;
|
||
- сервер не обязан знать этот формат;
|
||
- клиент может использовать эти вставки для специального UI-рендера.
|
||
|
||
## 1. Общий принцип
|
||
|
||
Обычное сообщение по-прежнему остаётся обычным текстом.
|
||
|
||
Если в начале plaintext стоит один или несколько специальных блоков формата:
|
||
|
||
```text
|
||
<S:...>
|
||
```
|
||
|
||
то клиент трактует их как технические вставки.
|
||
|
||
Техническими считаются только блоки, которые:
|
||
|
||
- стоят строго в начале plaintext;
|
||
- начинаются с префикса `<S:` или legacy-префикса `<SHiNE:`;
|
||
- заканчиваются первым символом `>`.
|
||
|
||
Если текст не начинается с `<S:` или `<SHiNE:`, никакие технические вставки не ищутся.
|
||
|
||
## 2. Правило скрытия
|
||
|
||
Все корректно распознанные блоки `<S:...>` или `<SHiNE:...>` в начале plaintext:
|
||
|
||
- не показываются пользователю как сырой текст;
|
||
- используются клиентом для UI-логики;
|
||
- неизвестные будущие типы тоже скрываются, если они распознаны как корректный SHiNE-блок.
|
||
|
||
Если блок битый и не закрыт символом `>`, он не считается техническим и сообщение показывается как обычный текст целиком.
|
||
|
||
## 3. Защита от случайного пользовательского ввода
|
||
|
||
Перед отправкой обычного текстового сообщения клиент обязан проверить:
|
||
|
||
- если пользовательский текст начинается с `<S:` или `<SHiNE:`
|
||
|
||
то клиент автоматически превращает начало в:
|
||
|
||
```text
|
||
< SHiNE:
|
||
```
|
||
|
||
Такой текст уже не считается техническим блоком и должен отображаться как обычное сообщение.
|
||
|
||
## 4. Формат блока
|
||
|
||
Общий вид:
|
||
|
||
```text
|
||
<S:kind;key=value;key=value;...>
|
||
```
|
||
|
||
Правила:
|
||
|
||
- `kind` — ASCII-идентификатор типа вставки;
|
||
- параметры отделяются `;`;
|
||
- ключ и значение отделяются `=`;
|
||
- значения обычных v1-вставок не экранируются; для `file` поля `url`, `name`, `mime` кодируются через percent-encoding (`encodeURIComponent`), чтобы `;`, `=` и `>` внутри метаданных не ломали блок;
|
||
- канонический новый префикс: `<S:`;
|
||
- legacy-префикс `<SHiNE:` продолжает поддерживаться при чтении.
|
||
|
||
## 5. Тип `reply`
|
||
|
||
Формат:
|
||
|
||
```text
|
||
<S:reply;v=1;id=user1|user2|1720612345678|77>Текст ответа
|
||
```
|
||
|
||
Где поле `id` — это логический идентификатор сообщения:
|
||
|
||
```text
|
||
fromLogin|toLogin|timeMs|nonce
|
||
```
|
||
|
||
Правила:
|
||
|
||
- этот блок должен стоять в начале plaintext;
|
||
- после него может идти обычный текст ответа;
|
||
- официальный UI формирует такой блок при отправке ответа через пункт `Ответить` в меню сообщения;
|
||
- если клиент не находит сообщение, на которое ссылается `reply`, он просто не показывает reply-preview;
|
||
- в таком случае само сообщение отображается как обычный текст без блока `<S:reply...>`.
|
||
|
||
## 6. Тип `call`
|
||
|
||
### Успешный звонок
|
||
|
||
```text
|
||
<S:call;v=1;status=completed;duration=367>
|
||
```
|
||
|
||
Где:
|
||
|
||
- `duration` — длительность разговора в секундах.
|
||
|
||
### Неуспешный звонок
|
||
|
||
```text
|
||
<S:call;v=1;status=failed;reason=offline>
|
||
```
|
||
|
||
Допустимые причины в v1:
|
||
|
||
- `offline`
|
||
- `no_answer`
|
||
- `connect_failed`
|
||
- `busy`
|
||
- `declined`
|
||
|
||
Правила:
|
||
|
||
- call-блок в v1 должен содержать только техническую запись звонка;
|
||
- пользовательский текст после такого блока в штатной логике не предполагается;
|
||
- UI может рисовать такие сообщения отдельным специальным стилем.
|
||
- официальный UI не отправляет call-summary, если от старта исходящего звонка до его завершения прошло меньше `5` секунд.
|
||
|
||
## 7. Тип `file`
|
||
|
||
Формат v1:
|
||
|
||
```text
|
||
<S:file;v=1;id=BASE58_SHA256;url=ENCODED_URL;key=BASE64URL_AES_KEY;iv=BASE64URL_IV;name=ENCODED_NAME;mime=ENCODED_MIME;size=123;encsize=139>📎 example.pdf
|
||
```
|
||
|
||
Поля:
|
||
|
||
- `id` — `Base58(SHA-256(ciphertext))`;
|
||
- `url` — абсолютный URL ciphertext на access-сервере отправителя, percent-encoded;
|
||
- `key` — случайный 32-byte AES-256 key в Base64URL;
|
||
- `iv` — случайный 12-byte AES-GCM IV в Base64URL;
|
||
- `name` — исходное безопасно нормализованное имя файла, percent-encoded;
|
||
- `mime` — исходный MIME, percent-encoded;
|
||
- `size` — размер plaintext в байтах;
|
||
- `encsize` — размер ciphertext в байтах.
|
||
|
||
`key`, `iv` и метаданные безопасно находятся здесь только потому, что весь plaintext DM затем шифруется существующим E2EE-механизмом. Они не передаются файловому HTTP endpoint отдельно.
|
||
|
||
Текст `📎 example.pdf` после блока служит fallback для старого клиента. Новый официальный UI вместо него рисует карточку файла и кнопку «Скачать».
|
||
|
||
Перед локальной AES-GCM-расшифровкой клиент обязан повторно вычислить SHA-256 скачанного ciphertext и сравнить Base58 с `id`.
|
||
|
||
## 8. Поведение официального UI
|
||
|
||
Официальный UI SHiNE в v1:
|
||
|
||
- скрывает все корректные `<S:...>` и legacy `<SHiNE:...>` блоки в начале plaintext;
|
||
- для `call` строит специальный человекочитаемый текст:
|
||
- `Звонок: M:SS`
|
||
- `Звонок: H:MM:SS`
|
||
- `Звонил, но недозвонился: ...`
|
||
- для `reply` скрывает сам блок и показывает только текст ответа;
|
||
- для `file` скрывает fallback-текст и показывает карточку вложения с локальным download/decrypt;
|
||
- если исходное reply-сообщение не найдено, reply-preview не показывается.
|
||
|
||
## 9. Совместимость
|
||
|
||
Так как это часть plaintext, а не часть серверного envelope:
|
||
|
||
- сервер не обязан понимать этот формат;
|
||
- будущие клиенты могут добавлять новые `kind`;
|
||
- клиенты, которые распознают SHiNE-вставки, должны скрывать неизвестные блоки целиком, если они стоят в начале и корректно закрыты.
|
||
|
||
## 10. Расширение `file` v2
|
||
|
||
Клиент обязан продолжать читать `v=1`. Новые chunked-вложения отправляются так:
|
||
|
||
```text
|
||
<S:file;v=2;id=ROOT_MANIFEST_ID;url=ENCODED_URL;key=BASE64URL_AES_KEY;ivp=BASE64URL_4BYTE_PREFIX;name=ENCODED_NAME;mime=ENCODED_MIME;size=123;encsize=456;chunk=1048576;chunks=7;th=TORRENT_V2_INFOHASH;pr=TORRENT_V2_PIECES_ROOT;kind=file;dur=0>
|
||
```
|
||
|
||
Дополнительные поля v2:
|
||
|
||
- `id` / `url` указывают не на весь файл, а на зашифрованный root manifest;
|
||
- `ivp` — случайный 4-byte IV prefix, из которого детерминированно строятся уникальные IV chunks/pages/root;
|
||
- `chunk` — plaintext chunk size, сейчас `1048576`;
|
||
- `chunks` — число частей;
|
||
- `th` — BitTorrent v2 SHA-256 infohash;
|
||
- `pr` — BitTorrent v2 pieces root;
|
||
- `kind=file|voice`;
|
||
- `dur` — длительность voice в миллисекундах, для обычного файла `0`.
|
||
|
||
В одном plaintext DM разрешено несколько последовательных `<S:file...>` блоков. Официальный UI собирает их в один список вложений и скрывает fallback-текст.
|
||
|
||
Подробный формат chunk encryption, manifest pages и BitTorrent v2 hashing: `Файлы_DM_v2.md`.
|