From c81cc8b9cae8e17cfa87d05d2cd21e91eba48942 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 22 Oct 2026 19:33:12 +0200 Subject: [PATCH] Document media attempt validation and reserve before tracking outgoing writes --- src/chat/chat_msg.c | 9 ++++----- src/media_delivery/media_download.h | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/chat/chat_msg.c b/src/chat/chat_msg.c index f7c47073..46d528e1 100644 --- a/src/chat/chat_msg.c +++ b/src/chat/chat_msg.c @@ -426,11 +426,6 @@ void chat_core_submit_media_message(struct UTUN_INSTANCE* inst, struct chat_msg_ return; } - /* build media_base from db_path: dirname(db_path) e.g. /path/to/data */ - if (media_index_track_preparation(cc->db, req->channel_id, destination, media_base)) { - chat_event_submitted(inst, req->request_id, 0); return; - } - /* copy fields for async callback (req is freed by trampoline immediately) */ struct media_submit_ctx* mctx = u_calloc(1, sizeof(*mctx)); if (!mctx) { @@ -439,6 +434,10 @@ void chat_core_submit_media_message(struct UTUN_INSTANCE* inst, struct chat_msg_ } mctx->destination = media_delivery_reserve(inst, destination); if (!mctx->destination) { u_free(mctx); chat_event_submitted(inst, req->request_id, 0); return; } + if (media_index_track_preparation(cc->db, req->channel_id, destination, media_base)) { + media_delivery_release(inst, mctx->destination); u_free(mctx); + chat_event_submitted(inst, req->request_id, 0); return; + } mctx->inst = inst; mctx->request_id = req->request_id; mctx->next = cc->media_submissions; cc->media_submissions = mctx; diff --git a/src/media_delivery/media_download.h b/src/media_delivery/media_download.h index c313658b..0cb8635f 100644 --- a/src/media_delivery/media_download.h +++ b/src/media_delivery/media_download.h @@ -5,6 +5,9 @@ * QUERY: одна пачка до 32 блоков в полёте, отдельный UUID/дедлайн, источники только своей группы и автор. * До четырёх кандидатов на блок; до десяти используемых узлов, слоты переиспользуются после освобождения. * Повторы ограничены источниками и бюджетом провалов блока; OVERLOADED соблюдает retry_after. + * Попытка имеет UUID и отдельный temp; принимается только непрерывный префикс от текущего узла своей группы. + * Прогресс обновляется после успешного close; DONE проверяет размер и подпись по сохранённому ключу автора. + * Проверенный temp независимо удерживают relay-выдачи. Итоговый файл публикуется после полного SHA-256. * done сообщает результат, а не гарантирует доставку другим участникам. */ #ifndef MEDIA_DOWNLOAD_H #define MEDIA_DOWNLOAD_H @@ -72,7 +75,6 @@ struct media_download_peer { /* ── внутреннее состояние загрузки (доступно для тестов) ── */ struct media_download { - struct ll_entry ll; struct ll_entry* entry; /* Владеющая запись; после удаления из индекса живёт до последнего refs. */ unsigned refs; /* Активная загрузка и обработчики, вызывающие пользовательские callbacks. */ struct media_destination* destination; // Исключительное право записи dest_path до terminal. @@ -112,11 +114,13 @@ struct media_download { struct UTUN_INSTANCE* inst; }; -/* Запустить непустой файл с корректной геометрией и массивами block_ids/block_sigs. +/* Запустить непустой файл с корректной геометрией и массивами block_ids/block_sigs; блок до MEDIA_BLOCK_SIZE_MAX. + * author_node_id должен иметь ровно 32 байта Ed25519-ключа в nodes; значение первого байта не используется как признак наличия. * dest_path <1024, media_base <512 байт (UTF-8); назначение принадлежит общей базе БД, смена базы отклоняется. * Проверки идут до создания каталогов. 0 — принят: done_cb ровно один раз, в том числе синхронно при отсутствии суперузлов; * -1 — отказ без done_cb, включая уже активный media_id. Аргументы callbacks заимствованы до done_cb. - * Успех включает атомарную регистрацию блоков собранного файла. Ошибка регистрации не удаляет собранный файл; + * Успех включает атомарную регистрацию блоков опубликованного файла в SQLite; общей транзакции ФС/БД нет. + * Ошибка регистрации не удаляет опубликованный файл; * путь остаётся в media_preparations для удаления канала. Остальные поля result копируются до возврата. * progress_cb может отменить загрузку и запустить новую с тем же media_id; done_cb старой загрузки * вызывается после возврата из progress_cb, когда её последний обработчик освободил контекст. */ @@ -127,6 +131,8 @@ int media_download_start(struct UTUN_INSTANCE* inst, uint64_t group_id, void (*done_cb)(void* arg, int err), void* done_arg, void (*progress_cb)(void* arg, int blocks_done, int num_blocks), void* progress_arg); +/* Отменить всю загрузку media_id: block_id/chunk не используются. Отдельный CANCEL отправляется для каждой текущей попытки. + * 0 — отменена, -1 — не найдена. done_cb(-2) после последнего удерживающего обработчика; повторной отмены/done нет. */ int media_download_cancel(struct UTUN_INSTANCE* inst, const uint8_t* media_id, const uint8_t* block_id, uint32_t chunk); @@ -136,6 +142,9 @@ int media_download_cancel(struct UTUN_INSTANCE* inst, * Может завершить загрузку и вызвать done_cb; после возврата прежний dl может быть освобождён. */ void media_download_handle_query_resp(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t from_node, const uint8_t* data, size_t len); +/* group_id/from_node берутся из router. Длина/состояние/блок/chunk/UUID проверяются до записи и прогресса. + * Повреждение текущей попытки меняет держателя, локальная ошибка завершает загрузку. + * Чужие и устаревшие пакеты игнорируются; обработчики могут завершить загрузку синхронно. */ void media_download_handle_chunk(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t from_node, const uint8_t* data, size_t len); void media_download_handle_done(struct UTUN_INSTANCE* inst,