/* dm_mailbox_media — durable задания суперузла: размещение на M, доставка * метаданных получателю и освобождение назначений по DELETE_ACK/EXPIRED либо подтверждённому TTL. * Жизненный цикл принадлежит dm_mailbox, control принимает dm_media. Все API в uasync. * Координатор хранится в dm_media_jobs/dm_media_holders SQLite ядра; таймер — только триггер повторов. * Таймер 1с обрабатывает до 8 задач, retry_at через 5с; выбранный M сохраняется до STORE. * Выбор M: storage=yes в общей группе A/B, достижимый узел по node_id, кроме A/B и прежних назначений. * S может одновременно быть M: local control проходит тот же обработчик без router-соединения к себе. * SQLite и группы принадлежат ядру; instance должен пережить owner. Входные тела заимствованы до возврата. * Из C++ этот внутренний C API включать внутри extern "C". */ #ifndef DM_MAILBOX_MEDIA_H #define DM_MAILBOX_MEDIA_H #include #include struct UTUN_INSTANCE; struct dm_mb_media; /* Создать таблицы/owner/таймер; inst с живыми SQLite/ua обязателен. Owner либо NULL при ошибке. * Повторы задач после restart начинаются сразу. Единственным owner управляет dm_mailbox. */ struct dm_mb_media* dm_mailbox_media_create(struct UTUN_INSTANCE* inst); /* Отменить таймер и освободить owner, сохранив задания/назначения в БД. NULL допустим. */ void dm_mailbox_media_destroy(struct dm_mb_media* media); /* Вызвать в транзакции PUT после проверки роли S, получателя и подписи полного msg/len. * 0=не file либо зарегистрирован точный SHA256 полного body для UUID, -1=ошибка/конфликт. Не отправляет STORE. * media обязателен для file; commit/rollback принадлежит вызывающему. */ int dm_mailbox_media_register(struct dm_mb_media* media, uint64_t group, uint64_t recipient, const uint8_t* msg, size_t len); /* После проверки router-подписи и размера команды в dm_media: group/source — доверенные поля router. * body заимствован на время вызова; RECEIPT=152, STORED=24, DELETE_ACK/EXPIRED=16, REJECT=17 байт. * RECEIPT имеет формат dm_media.h; STORED=[UUID:16][expires:8], ACK/EXPIRED=[UUID:16], REJECT=[UUID:16][reason:1]. * STORED/REJECT/ACK относятся к сохранённому назначению M; RECEIPT проверяется отдельно подписью B. * EXPIRED/REJECT атомарно освобождают назначение и сбрасывают текущий holder; ошибка откатывает оба изменения. * Таймер завершает stored-назначение по expires даже при offline B/потерянном EXPIRED, также после restart. * После квитанции повторяется DELETE до 16 неосвобождённым M за проход cleanup. * Тело удаляется только при отсутствии неосвобождённых назначений; ошибка сканирования откладывает очистку. * Для неподтверждённого STORE повтор запрашивает результат из custody/tombstone M; квитанция B не отменяется. */ void dm_mailbox_media_recv(struct dm_mb_media* media, uint64_t group, uint64_t source, uint8_t cmd, const uint8_t* body, size_t len); /* Проверить 152-байтовую квитанцию и попытаться отправить доступным S общей группы. * За один вызов выбирается до 64 подходящих S на группу, включая локальный S; A/B исключаются. * Это попытка отправки; durable повтор обеспечивает квитанция в dm_files/dm_media_jobs, а не сам вызов. */ void dm_mailbox_media_receipt(struct UTUN_INSTANCE* inst, const uint8_t* receipt); #endif