docs(media): specify attachment metadata and voice file contracts

This commit is contained in:
evgeny committed 2026-10-24 17:26:11 +02:00
1 parent c1717d4047
commit 28bbd62d74
3 files changed
+21 -10

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@
#include "../../lib/debug_config.h"
#include "attachment.h"
/* Имя должно быть корректным UTF-8 basename без управляющих символов. */
/* Корректный UTF-8 basename без ASCII C0/DEL и разделителей пути. */
static int valid_name(const char* name, size_t n) {
if (!n || n > 255 || (n == 1 && name[0] == '.') || (n == 2 && !memcmp(name, "..", 2))) return 0;
for (size_t i = 0; i < n;) {
+13 -6
View File
@@ -1,8 +1,9 @@
/* attachment — общие метаданные file/voice/video, без хранения файла, адресата и транспорта.
* validate/decode возвращают 0 / -1, encode — число записанных байтов / -1.
* Формат: kind:1, name_len:2, duration_ms:4, width:2, height:2, UTF-8 basename, waveform:100 только для voice.
* Числа big-endian; имя 1..255 байт без разделителей пути, лишние wire-байты запрещены.
* Caller владеет struct/буферами; DM шифрует эти метаданные, GUI получает разобранные поля. */
* Числа big-endian; имя — корректный UTF-8 basename длиной 1..255 байт, без '/'/'\\', ASCII C0/DEL и '.'/'..'.
* Лишние wire-байты и NUL внутри переданного имени запрещены. Caller владеет struct/буферами;
* вход и выход не пересекаются. DM шифрует эти метаданные, GUI получает разобранные поля. */
#ifndef ATTACHMENT_H
#define ATTACHMENT_H
#include <stdint.h>
@@ -16,15 +17,21 @@ enum attachment_kind { ATTACHMENT_FILE = 1, ATTACHMENT_VOICE, ATTACHMENT_VIDEO }
#define ATTACHMENT_WIRE_MAX (11 + 255 + ATTACHMENT_WAVE_SIZE)
#define ATTACHMENT_DURATION_MAX_MS 86400000u /* Общий максимум длительности voice/video: 24ч. */
struct attachment_info {
enum attachment_kind kind;
char name[256];
uint32_t duration_ms;
uint16_t width, height;
enum attachment_kind kind; /* Только FILE/VOICE/VIDEO. */
char name[256]; /* UTF-8 basename с завершающим NUL. */
uint32_t duration_ms; /* VOICE/VIDEO: 1..ATTACHMENT_DURATION_MAX_MS; FILE: 0. */
uint16_t width, height; /* VIDEO: 1..16384; FILE/VOICE: 0. */
char waveform[ATTACHMENT_WAVE_SIZE + 1]; /* 100 уровней из A-Za-z0-9+/; только voice. */
};
/* Проверить все поля и их согласованность с kind; info не меняется, NULL даёт -1. */
int attachment_validate(const struct attachment_info* info);
/* Записать полные проверенные метаданные в out ёмкостью capacity; ошибка не меняет out.
* Вернуть точную длину wire или -1. out обязателен, ATTACHMENT_WIRE_MAX достаточно для любого kind. */
int attachment_encode(const struct attachment_info* info, uint8_t* out, size_t capacity);
/* Разобрать ровно length байт и проверить метаданные; data/info обязательны. 0/-1.
* При ошибке info может быть изменена: результат используется только при возврате 0. */
int attachment_decode(const uint8_t* data, size_t length, struct attachment_info* info);
/* Заимствованная постоянная строка: VOICE=audio/opus, VIDEO=video/mp4, иначе application/octet-stream. Не валидатор kind. */
const char* attachment_content_type(enum attachment_kind kind);
#ifdef __cplusplus
+7 -3
View File
@@ -1,10 +1,14 @@
/* voice_file — кодирование PCM в общий контейнер Opus голосовых сообщений Qt/Android. Вызывать только в worker.
* PCM принадлежит вызывающему; encode заполняет duration/waveform по реально
* записанным кадрам. При ошибке неполный файл удаляется; возврат 0 / -1.
* PCM принадлежит вызывающему; encode заполняет duration/waveform по реально записанным кадрам. Возврат 0 / -1.
* path — собственный файл вызывающего: fopen(wb) перезаписывает прежнее содержимое;
* после ошибки записи/close файл удаляется (отказ удаления логируется). fsync/атомарной публикации здесь нет.
* count — число interleaved int16 отсчётов; rate — 8/12/16/24/48 kHz, channels — 1/2, preset — 0..2.
* Пишутся полные кадры по 20мс (хвост отбрасывается), минимум 500мс, максимум ATTACHMENT_DURATION_MAX_MS.
* Недопустимая длительность отвергается до создания encoder, открытия path и изменения info.
* name задаёт caller; encode устанавливает kind=VOICE и обнуляет width/height. */
* name задаёт caller; encode устанавливает kind=VOICE и обнуляет width/height.
* info используется только при rc=0: при поздней ошибке close часть полей уже может быть изменена.
* Формат native-endian: magic:uint32=0x5355504f, rate:uint32, channels:uint16, samples_per_channel:uint16;
* затем (packet_len:uint16, Opus bytes) для каждого кадра и нулевая длина в конце. */
#ifndef VOICE_FILE_H
#define VOICE_FILE_H
#include "attachment.h"