diff --git a/src/dm/dm_media.c b/src/dm/dm_media.c index ffe2a1508..9ded229ac 100644 --- a/src/dm/dm_media.c +++ b/src/dm/dm_media.c @@ -1002,24 +1002,35 @@ static void dm_custody_remove(struct dm_media_state* media, const uint8_t id[16] media_async_submit(media->inst->media_async, media->inst->ua, dm_file_work, job, dm_file_done, job); } -/* Подпись B позволяет любому авторизованному S завершить удаление точного артефакта. */ +/* Все факты уже проверены и сохранены. Терминальное состояние предшествует отмене I/O. */ +static int dm_custody_complete(struct UTUN_INSTANCE* inst, const uint8_t id[16], int policy) { + int done = dm_media_retention_done(inst, id, policy); + if (done <= 0) return done; + sqlite3_stmt* st = dm_media_prepare(inst, policy ? + "UPDATE dm_custody SET ready=-1,retry_at=0,receipt=(SELECT taken FROM dm_file_targets" + " WHERE id=? AND side=1 AND taken IS NOT NULL LIMIT 1) WHERE id=? AND ready>=0" : + "UPDATE dm_custody SET ready=-1,retry_at=0,receipt=(SELECT received FROM dm_file_targets" + " WHERE id=? AND side=1 AND received IS NOT NULL LIMIT 1) WHERE id=? AND ready>=0"); + if (!st) return -1; + sqlite3_bind_blob(st, 1, id, 16, SQLITE_STATIC); sqlite3_bind_blob(st, 2, id, 16, SQLITE_STATIC); + if (dm_media_step(inst, st)) return -1; + if (sqlite3_changes(inst->topo_sqlite_db)) DEBUG_INFO(DEBUG_CATEGORY_DM, "DM custody: retention complete delete_on_open=%d", policy); + return 1; +} + +/* DELETE переносит факт; удаление определяет сохранённая политика M. */ static void dm_custody_delete(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t source, const uint8_t* r) { if (dm_media_verify_receipt(inst, r, NULL, 0)) return; - uint64_t author, recipient; - memcpy(&author, r + 24, 8); memcpy(&recipient, r + 32, 8); /* Для существующей копии точная подпись B разрешает удаление даже после отзыва роли M/S. */ - sqlite3_stmt* st = dm_media_prepare(inst, "SELECT body FROM dm_custody WHERE id=?"); + sqlite3_stmt* st = dm_media_prepare(inst, "SELECT body,delete_on_open FROM dm_custody WHERE id=?"); if (!st) return; sqlite3_bind_blob(st, 1, r + 40, 16, SQLITE_STATIC); int rc = sqlite3_step(st); if (rc == SQLITE_ROW) { - int valid = !dm_media_verify_receipt(inst, r, sqlite3_column_blob(st, 0), (size_t)sqlite3_column_bytes(st, 0)); + int policy = sqlite3_column_int(st, 1); + int valid = !dm_media_receipt_save(inst, r, sqlite3_column_blob(st, 0), (size_t)sqlite3_column_bytes(st, 0)); sqlite3_finalize(st); - if (!valid) return; - st = dm_media_prepare(inst, "UPDATE dm_custody SET ready=-1,receipt=?,retry_at=0 WHERE id=?"); - if (!st) return; - sqlite3_bind_blob(st, 1, r, DM_MEDIA_RECEIPT_SIZE, SQLITE_STATIC); sqlite3_bind_blob(st, 2, r + 40, 16, SQLITE_STATIC); - if (dm_media_step(inst, st)) return; + if (!valid || dm_custody_complete(inst, r + 40, policy) != 1) return; file_transfer_cancel_object(inst->file_transfer, r + 40); dm_custody_remove(inst->dm_media, r + 40); return; @@ -1030,20 +1041,11 @@ static void dm_custody_delete(struct UTUN_INSTANCE* inst, uint64_t group, uint64 if (!st) return; sqlite3_bind_blob(st, 1, r + 40, 16, SQLITE_STATIC); rc = sqlite3_step(st); - int exact = rc == SQLITE_DONE || (rc == SQLITE_ROW && sqlite3_column_bytes(st, 0) == 32 && - !memcmp(sqlite3_column_blob(st, 0), r + 56, 32)); + int exact = rc == SQLITE_ROW && sqlite3_column_bytes(st, 0) == 32 && !memcmp(sqlite3_column_blob(st, 0), r + 56, 32); sqlite3_finalize(st); - if (!exact) { DEBUG_WARN(DEBUG_CATEGORY_DM, "dm_media: delete conflicts with retired artifact"); return; } - if (rc == SQLITE_DONE && (!dm_media_scope(inst, group, source, "supernode", author, recipient) || - !dm_media_scope(inst, group, inst->node_id, "storage", author, recipient))) { - DEBUG_WARN(DEBUG_CATEGORY_DM, "dm_media: unknown DELETE without custody grant source=%llu", (unsigned long long)source); return; - } - st = dm_media_prepare(inst, "INSERT INTO dm_custody_done(id,body_hash,receipt) VALUES(?,?,?)" - " ON CONFLICT(id) DO UPDATE SET receipt=excluded.receipt"); - if (!st) return; - sqlite3_bind_blob(st, 1, r + 40, 16, SQLITE_STATIC); sqlite3_bind_blob(st, 2, r + 56, 32, SQLITE_STATIC); - sqlite3_bind_blob(st, 3, r, DM_MEDIA_RECEIPT_SIZE, SQLITE_STATIC); - if (!dm_media_step(inst, st)) dm_media_send(inst, group, source, DM_MEDIA_DELETE_ACK, r + 40, 16); + if (exact) dm_media_send(inst, group, source, DM_MEDIA_DELETE_ACK, r + 40, 16); + else if (rc == SQLITE_DONE) DEBUG_DEBUG(DEBUG_CATEGORY_DM, "DM custody: fact before STORE; S retains it for retry"); + else DEBUG_WARN(DEBUG_CATEGORY_DM, "dm_media: delete conflicts with retired artifact rc=%d", rc); } /* OFFER не меняет идентичность беседы. Чужой holder допустим только от S. */ @@ -1092,7 +1094,7 @@ static void dm_media_control(struct UTUN_INSTANCE* inst, uint64_t group, uint64_ if (author == inst->node_id) { dm_media_accept_receipt(inst, p); } else dm_mailbox_media_recv(dm_mailbox_get_media(inst), group, source, cmd, p, len); - } else if ((cmd == DM_MEDIA_STORED && len == 24) || (cmd == DM_MEDIA_DELETE_ACK && len == 16) || + } else if (((cmd == DM_MEDIA_STORED || cmd == DM_MEDIA_RESERVED) && len == 25) || (cmd == DM_MEDIA_DELETE_ACK && len == 16) || (cmd == DM_MEDIA_EXPIRED && len == 16) || (cmd == DM_MEDIA_REJECT && len == 17)) { dm_mailbox_media_recv(dm_mailbox_get_media(inst), group, source, cmd, p, len); if (cmd == DM_MEDIA_EXPIRED) { @@ -1184,7 +1186,7 @@ static void dm_files_tick(struct dm_media_state* media, uint64_t now) { static void dm_custody_tick(struct dm_media_state* media, uint64_t now) { struct UTUN_INSTANCE* inst = media->inst; for (int i = 0; i < 8; i++) { - sqlite3_stmt* st = dm_media_prepare(inst, "SELECT id,body,author,supernode,group_id,expires,ready FROM dm_custody" + sqlite3_stmt* st = dm_media_prepare(inst, "SELECT id,body,author,supernode,group_id,expires,ready,delete_on_open FROM dm_custody" " WHERE retry_at<=? ORDER BY retry_at LIMIT 1"); if (!st) return; sqlite3_bind_int64(st, 1, (sqlite3_int64)now); @@ -1200,7 +1202,9 @@ static void dm_custody_tick(struct dm_media_state* media, uint64_t now) { uint64_t author = (uint64_t)sqlite3_column_int64(st, 2), super = (uint64_t)sqlite3_column_int64(st, 3); uint64_t group = (uint64_t)sqlite3_column_int64(st, 4), expires = (uint64_t)sqlite3_column_int64(st, 5); int ready = sqlite3_column_int(st, 6); + int policy = sqlite3_column_int(st, 7); sqlite3_finalize(st); + if (ready >= 0 && dm_custody_complete(inst, id, policy) == 1) ready = -1; st = dm_media_prepare(inst, "UPDATE dm_custody SET retry_at=?,ready=CASE WHEN expires<=? THEN -1 ELSE ready END WHERE id=?"); if (!st) return; sqlite3_bind_int64(st, 1, (sqlite3_int64)(now + 5)); sqlite3_bind_int64(st, 2, (sqlite3_int64)now); @@ -1214,8 +1218,7 @@ static void dm_custody_tick(struct dm_media_state* media, uint64_t now) { uint64_t gid = group; if (desc && dm_route_group(inst, author, group, &gid)) dm_file_pull(media, 1, gid, author, desc, author); } else { - uint8_t stored[24]; memcpy(stored, id, 16); memcpy(stored + 16, &expires, 8); - dm_media_send(inst, group, super, DM_MEDIA_STORED, stored, sizeof(stored)); + dm_custody_status(inst, group, super, id); } } }