From 5013fc22d8993301f0b5fb104283b6abc1a18dbe Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 3 Nov 2026 12:54:43 +0100 Subject: [PATCH] Commit verified file receipts separately from message delivery --- src/Makefile.am | 4 ++++ src/dm/dm_media.c | 58 +++++++++++++---------------------------------- 2 files changed, 20 insertions(+), 42 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 6df20066e..9fe74d032 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -217,6 +217,8 @@ utun_CORE_SOURCES = \ dm/dm_media.c \ dm/dm_media.h \ dm/dm_media_priv.h \ + dm/dm_media_receipt.c \ + dm/dm_media_receipt.h \ dm/dm_mailbox_media.c \ dm/dm_mailbox_media.h \ call/call.c \ @@ -424,6 +426,8 @@ libutun_a_SOURCES = \ dm/dm_media.c \ dm/dm_media.h \ dm/dm_media_priv.h \ + dm/dm_media_receipt.c \ + dm/dm_media_receipt.h \ dm/dm_mailbox_media.c \ dm/dm_mailbox_media.h \ call/call.c \ diff --git a/src/dm/dm_media.c b/src/dm/dm_media.c index a6d6565ba..808353068 100644 --- a/src/dm/dm_media.c +++ b/src/dm/dm_media.c @@ -12,6 +12,7 @@ #include "../../lib/debug_config.h" #include "../../lib/json_flat.h" #include "dm_media_priv.h" +#include "dm_media_receipt.h" #include "dm_history.h" #include "dm_crypto.h" #include "dm_mailbox.h" @@ -347,35 +348,6 @@ void dm_media_notify(struct UTUN_INSTANCE* inst, uint64_t conv) { chat_event_post(inst, CHAT_EVT_DM_MSG_RECEIVED, evt, n + 9); } -/* Квитанция подписывает UUID и хеш всего сообщения с descriptor. */ -int dm_media_verify_receipt(struct UTUN_INSTANCE* inst, const uint8_t* r, const uint8_t* msg, size_t len) { - uint64_t conv, author, recipient, seq; - if (!inst || !r || memcmp(r, "DMFILE01", 8)) goto invalid; - memcpy(&conv, r + 8, 8); memcpy(&seq, r + 16, 8); - memcpy(&author, r + 24, 8); memcpy(&recipient, r + 32, 8); - if (!author || !recipient || author == recipient || !seq || seq > INT64_MAX || - conv != dm_derive_conv_id(author, recipient)) goto invalid; - sqlite3_stmt* st = dm_media_prepare(inst, "SELECT ed25519_pubkey FROM nodes WHERE node_id=?"); - if (!st) return -1; - sqlite3_bind_int64(st, 1, (sqlite3_int64)recipient); - uint8_t key[32]; - int found = sqlite3_step(st) == SQLITE_ROW && sqlite3_column_bytes(st, 0) == 32; - if (found) memcpy(key, sqlite3_column_blob(st, 0), sizeof(key)); - sqlite3_finalize(st); - if (!found || sc_ed25519_verify(key, r, 88, r + 88) != SC_OK) goto invalid; - if (msg) { - const uint8_t* desc = dm_message_media(msg, len); - uint8_t hash[32]; - if (!desc || memcmp(r + 8, msg, 16) || memcmp(r + 24, msg + 24, 8) || memcmp(r + 40, desc, 16)) goto invalid; - SHA256(msg, len, hash); - if (memcmp(hash, r + 56, 32)) goto invalid; - } - return 0; -invalid: - DEBUG_WARN(DEBUG_CATEGORY_DM, "dm_media: rejected media receipt body_bytes=%zu", len); - return -1; -} - /* Транзакция dm_core связывает историю, очередь и состояние файла. */ int dm_media_track(struct UTUN_INSTANCE* inst, int dir, const uint8_t* msg, size_t len) { const uint8_t* desc = dm_message_media(msg, len); @@ -421,7 +393,7 @@ int dm_media_track(struct UTUN_INSTANCE* inst, int dir, const uint8_t* msg, size int exact = rc == SQLITE_ROW && sqlite3_column_bytes(st, 0) == (int)len && !memcmp(sqlite3_column_blob(st, 0), msg, len); sqlite3_finalize(st); if (!exact) DEBUG_ERROR(DEBUG_CATEGORY_DM, "dm_media: conflicting UUID/body conv=%s seq=%llu", id, (unsigned long long)seq); - return exact ? 0 : -1; + return exact ? dm_media_targets_seed(inst, msg, len, recipient) : -1; } /* Квитанция endpoint удаляет только media pending, история/собственный файл остаются. */ @@ -449,13 +421,14 @@ int dm_media_accept_receipt(struct UTUN_INSTANCE* inst, const uint8_t* r) { if (exact) { DEBUG_DEBUG(DEBUG_CATEGORY_DM, "DM files: late receipt for deleted file conv=%s", id); return 0; } DEBUG_WARN(DEBUG_CATEGORY_DM, "DM files: receipt for unknown/conflicting deleted file conv=%s", id); return -1; } - if (rc != SQLITE_ROW || dm_media_verify_receipt(inst, r, sqlite3_column_blob(st, 0), (size_t)sqlite3_column_bytes(st, 0))) { + if (rc != SQLITE_ROW || dm_media_receipt_save(inst, r, sqlite3_column_blob(st, 0), (size_t)sqlite3_column_bytes(st, 0))) { DEBUG_WARN(DEBUG_CATEGORY_DM, "dm_media: receipt does not match outgoing artifact rc=%d", rc); sqlite3_finalize(st); return -1; } int already = sqlite3_column_bytes(st, 1) == DM_MEDIA_RECEIPT_SIZE; sqlite3_finalize(st); + if (dm_media_receipt_kind(r) == 1) return 0; st = dm_media_prepare(inst, "UPDATE dm_files SET receipt=? WHERE id=? AND dir=1"); if (!st) return -1; sqlite3_bind_blob(st, 1, r, DM_MEDIA_RECEIPT_SIZE, SQLITE_STATIC); sqlite3_bind_blob(st, 2, r + 40, 16, SQLITE_STATIC); @@ -572,6 +545,11 @@ static void dm_file_work(void* arg) { job->error = media_encrypt_stream(job->key, job->author, job->id, in, out, &job->size, job->hash); } else if (job->work == DM_FILE_RECEIVE) { job->error = media_decrypt_stream(job->key, job->author, job->id, job->size, job->hash, in, out); + if (!job->error) { + FILE* durable = fopen(job->cipher, "rb+"); + if (!durable) { DEBUG_ERROR(DEBUG_CATEGORY_DM, "dm_media: received ciphertext sync open failed"); job->error = -1; } + else { job->error = dm_file_sync(durable); if (fclose(durable)) job->error = -1; } + } } else { SHA256_CTX hash; SHA256_Init(&hash); @@ -658,22 +636,18 @@ static void dm_file_done(void* arg, int error) { goto fail; } uint8_t receipt[DM_MEDIA_RECEIPT_SIZE]; - if (!custody) { - memcpy(receipt, "DMFILE01", 8); memcpy(receipt + 8, body, 16); - memcpy(receipt + 24, body + 24, 8); memcpy(receipt + 32, &inst->node_id, 8); - memcpy(receipt + 40, job->id, 16); SHA256(body, len, receipt + 56); - if (sc_ed25519_sign(inst->my_ed25519_privkey, receipt, 88, receipt + 88) != SC_OK) { - DEBUG_ERROR(DEBUG_CATEGORY_DM, "dm_media: media receipt signing failed"); - goto fail; - } - } + if (!custody && dm_media_receipt_make(inst, body, len, 0, receipt)) goto fail; + if (dm_media_exec(inst, "BEGIN IMMEDIATE")) goto fail; st = dm_media_prepare(inst, custody ? "UPDATE dm_custody SET ready=1 WHERE id=? AND ready=0" : "UPDATE dm_files SET ready=1,downloading=0,receipt=? WHERE id=? AND ready<=0"); - if (!st) goto fail; + if (!st) { dm_media_exec(inst, "ROLLBACK"); goto fail; } int idx = 1; if (!custody) sqlite3_bind_blob(st, idx++, receipt, sizeof(receipt), SQLITE_STATIC); sqlite3_bind_blob(st, idx, job->id, 16, SQLITE_STATIC); - if (dm_media_step(inst, st) || sqlite3_changes(inst->topo_sqlite_db) != 1) goto fail; + if (dm_media_step(inst, st) || sqlite3_changes(inst->topo_sqlite_db) != 1 || + (!custody && dm_media_receipt_save(inst, receipt, body, len)) || dm_media_exec(inst, "COMMIT")) { + dm_media_exec(inst, "ROLLBACK"); goto fail; + } if (!custody && ready < 0) DEBUG_INFO(DEBUG_CATEGORY_DM, "dm_media: recovered expired file conv=%s generation=%llu bytes=%llu", job->conv, (unsigned long long)job->generation, (unsigned long long)job->size);