diff --git a/src/chat/chat_attachment.c b/src/chat/chat_attachment.c index 793afe1b..b20f4b96 100644 --- a/src/chat/chat_attachment.c +++ b/src/chat/chat_attachment.c @@ -426,6 +426,8 @@ static int ak_apply_result(struct UTUN_INSTANCE* inst, const char* channel, uint int incoming = sqlite3_column_int(st, 0), state = sqlite3_column_int(st, 1), queued = sqlite3_column_int(st, 2); stage = "read finalize"; rc = sqlite3_finalize(st); st = NULL; if (rc != SQLITE_OK) goto fail; + DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "attachment keys: result before transaction writes channel=%s peer=%llu command=%u incoming=%d state=%d queued=%d", + channel, (unsigned long long)peer, cmd, incoming, state, queued); if ((incoming != 0 && incoming != 1) || state < 0 || state > 4 || (cmd == AK_RESULT_ACK ? !incoming || (state != 2 && state != 3 && state != 4) : incoming || state > 3) || ((cmd == AK_GRANT || cmd == AK_DENY) && ((state == 2 && cmd != AK_GRANT) || (state == 3 && cmd != AK_DENY)))) { @@ -454,7 +456,10 @@ static int ak_apply_result(struct UTUN_INSTANCE* inst, const char* channel, uint changed = sqlite3_changes(db); stage = "key finalize"; rc = sqlite3_finalize(st); st = NULL; if (rc != SQLITE_OK) goto fail; - if (changed != 1) { stage = "key row count"; rc = SQLITE_CONSTRAINT; goto fail; } + if (changed != 1) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment keys: result key rows=%d expected=1 channel=%s", changed, channel); + stage = "key row count"; rc = SQLITE_CONSTRAINT; goto fail; + } } int next = cmd == AK_REQUEST_ACK ? (state == 0 ? 1 : state) : cmd == AK_RESULT_ACK ? (state == 3 ? 3 : 4) : cmd == AK_GRANT ? 2 : 3; stage = "state prepare"; @@ -473,7 +478,10 @@ static int ak_apply_result(struct UTUN_INSTANCE* inst, const char* channel, uint changed = sqlite3_changes(db); stage = "state finalize"; rc = sqlite3_finalize(st); st = NULL; if (rc != SQLITE_OK) goto fail; - if (changed != 1) { stage = "state row count"; rc = SQLITE_CONSTRAINT; goto fail; } + if (changed != 1) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment keys: result state rows=%d expected=1 channel=%s", changed, channel); + stage = "state row count"; rc = SQLITE_CONSTRAINT; goto fail; + } int command = cmd == AK_RESULT_ACK ? (state == 3 ? AK_DENY : AK_GRANT) : AK_REQUEST; stage = "outbox prepare"; rc = sqlite3_prepare_v2(db, "DELETE FROM attachment_key_outbox WHERE channel=? AND message_id=? AND peer=? AND command=?", -1, &st, NULL); @@ -492,7 +500,11 @@ static int ak_apply_result(struct UTUN_INSTANCE* inst, const char* channel, uint stage = "outbox finalize"; rc = sqlite3_finalize(st); st = NULL; if (rc != SQLITE_OK) goto fail; /* REQUEST_ACK мог удалить REQUEST раньше GRANT; дубликат результата также не требует строки outbox. */ - if (changed != (queued == command)) { stage = "outbox row count"; rc = SQLITE_CONSTRAINT; goto fail; } + if (changed != (queued == command)) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment keys: result outbox rows=%d expected=%d channel=%s command=%d", + changed, queued == command, channel, command); + stage = "outbox row count"; rc = SQLITE_CONSTRAINT; goto fail; + } stage = "commit"; rc = sqlite3_exec(db, "COMMIT", NULL, NULL, NULL); if (rc != SQLITE_OK) goto fail; *next_out = next; diff --git a/tests/test_attachment_results.c b/tests/test_attachment_results.c index 5c461880..eaf4adfa 100644 --- a/tests/test_attachment_results.c +++ b/tests/test_attachment_results.c @@ -41,10 +41,10 @@ static struct fixture* active; static enum operation operation(const char* sql) { if (!sql) return NO_OPERATION; - if (!strncmp(sql, "SELECT r.incoming,r.state,o.command", 34)) return READ; - if (!strncmp(sql, "UPDATE attachment_keys SET key=?,error=0 WHERE", 45)) return KEY; - if (!strncmp(sql, "UPDATE attachment_key_requests SET state=? WHERE", 47)) return STATE; - if (!strncmp(sql, "DELETE FROM attachment_key_outbox WHERE", 38)) return OUTBOX; + static const char* prefixes[] = {NULL, "SELECT r.incoming,r.state,o.command", "UPDATE attachment_keys SET key=?,error=0 WHERE", + "UPDATE attachment_key_requests SET state=? WHERE", "DELETE FROM attachment_key_outbox WHERE"}; + for (enum operation op = READ; op <= OUTBOX; op++) + if (!strncmp(sql, prefixes[op], strlen(prefixes[op]))) return op; return NO_OPERATION; } @@ -56,7 +56,7 @@ static int take(enum failure point, enum operation op, int index) { extern int __real_sqlite3_exec(sqlite3*, const char*, int (*)(void*, int, char**, char**), void*, char**); int __wrap_sqlite3_exec(sqlite3* db, const char* sql, int (*cb)(void*, int, char**, char**), void* arg, char** error) { - if (active && db == active->inst->topo_sqlite_db) { + if (active && active->inst && db == active->inst->topo_sqlite_db) { if (!strcmp(sql, "BEGIN IMMEDIATE") && take(BEGIN, NO_OPERATION, 0)) return SQLITE_BUSY; if (!strcmp(sql, "COMMIT") && take(COMMIT, NO_OPERATION, 0)) return SQLITE_BUSY; if (!strcmp(sql, "ROLLBACK") && fail_rollback) { @@ -147,7 +147,10 @@ static struct snapshot snapshot(struct fixture* f) { /* Reader проверяет обе целевые строки и не затронутую соседнюю запись без чтения секретов в лог. */ static void equal(struct fixture* f, struct snapshot expected) { - struct snapshot current = snapshot(f); assert(!memcmp(¤t, &expected, sizeof(current))); + struct snapshot current = snapshot(f); + assert(current.key_bytes == expected.key_bytes && current.error == expected.error && current.incoming == expected.incoming); + assert(current.state == expected.state && current.queued == expected.queued); + assert(!current.key_bytes || !memcmp(current.key, expected.key, 32)); sqlite3_stmt* st = NULL; assert(sqlite3_prepare_v2(f->reader, "SELECT key,error FROM attachment_keys WHERE channel='900'", -1, &st, NULL) == SQLITE_OK); assert(sqlite3_step(st) == SQLITE_ROW && sqlite3_column_bytes(st, 0) == 32 && sqlite3_column_int(st, 1) == 7); @@ -213,9 +216,10 @@ static void reset(struct fixture* f, int command) { f->room = 1; memset(f->key, 0, sizeof(f->key)); f->key[0] = 55; int author = command == RESULT_ACK, state = author ? 2 : command == REQUEST_ACK ? 0 : 1; sqlite3_stmt* st = NULL; - assert(sqlite3_prepare_v2(f->inst->topo_sqlite_db, "UPDATE attachment_keys SET key=?,error=1 WHERE channel=?", -1, &st, NULL) == SQLITE_OK); + assert(sqlite3_prepare_v2(f->inst->topo_sqlite_db, "UPDATE attachment_keys SET key=?,error=1,author=? WHERE channel=?", -1, &st, NULL) == SQLITE_OK); assert((author ? sqlite3_bind_blob(st, 1, f->key, 32, SQLITE_STATIC) : sqlite3_bind_null(st, 1)) == SQLITE_OK); - assert(sqlite3_bind_text(st, 2, f->channel, -1, SQLITE_STATIC) == SQLITE_OK && sqlite3_step(st) == SQLITE_DONE); + assert(sqlite3_bind_int64(st, 2, (sqlite3_int64)(author ? f->inst->node_id : 123)) == SQLITE_OK); + assert(sqlite3_bind_text(st, 3, f->channel, -1, SQLITE_STATIC) == SQLITE_OK && sqlite3_step(st) == SQLITE_DONE); assert(sqlite3_finalize(st) == SQLITE_OK); char query[512]; snprintf(query, sizeof(query), "UPDATE attachment_key_requests SET incoming=%d,state=%d WHERE channel='%s';"