diff --git a/src/media_async/attachment.c b/src/media_async/attachment.c index 489f79ff..a4f0f1a2 100644 --- a/src/media_async/attachment.c +++ b/src/media_async/attachment.c @@ -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;) { diff --git a/src/media_async/attachment.h b/src/media_async/attachment.h index 6ea14974..1f9d019b 100644 --- a/src/media_async/attachment.h +++ b/src/media_async/attachment.h @@ -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 @@ -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 diff --git a/src/media_async/voice_file.h b/src/media_async/voice_file.h index c99d3265..64a1aca7 100644 --- a/src/media_async/voice_file.h +++ b/src/media_async/voice_file.h @@ -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"