Index attachment manifests atomically and classify invalid identities
This commit is contained in:
1 parent
fe75a493ef
commit
45d5aaa7a1
2 files changed
+80
-24
No files matched your search
+73
-21
@@ -430,8 +430,8 @@ fail:
|
||||
/* Запись поступает только из проверенной истории; размер и hash относятся к ciphertext. */
|
||||
int chat_attachment_observe(struct UTUN_INSTANCE* inst, const char* channel, const uint8_t message[32],
|
||||
uint64_t author, const char* json, size_t length) {
|
||||
if (!CC(inst) || !json || !ak_channel(channel) || length > 65536) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment keys: invalid observed message"); return -1;
|
||||
if (!inst || !CC(inst) || CC(inst)->attachment_closing || !json || !message || !author || !ak_channel(channel) || length > 65536) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment keys: invalid observed message/state"); return -2;
|
||||
}
|
||||
char* copy = u_malloc(length + 1);
|
||||
if (!copy) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment keys: message allocation failed"); return -1; }
|
||||
@@ -440,29 +440,81 @@ int chat_attachment_observe(struct UTUN_INSTANCE* inst, const char* channel, con
|
||||
char* body = strstr(copy, "\"d\":\""); char* end = body ? strchr(body + 5, '"') : NULL;
|
||||
struct media_index_result result = {0}; struct ak_file job = { .inst = inst };
|
||||
snprintf(job.channel, sizeof(job.channel), "%s", channel);
|
||||
int rc = -1;
|
||||
int result_code = -2, rc = SQLITE_MISMATCH; sqlite3_stmt* st = NULL;
|
||||
const char* stage = "manifest";
|
||||
if (!end) goto done;
|
||||
body += 5; *end = 0;
|
||||
if (chat_attachment_identity(body, (size_t)(end - body), job.id) || chat_msg_parse_media_body(body, &result) || ak_paths(&job)) goto done;
|
||||
sqlite3_stmt* st = ak_prepare(inst, "INSERT INTO attachment_keys(channel,crypto_id,message_id,author,cipher_hash,cipher_path,plain_path)"
|
||||
" VALUES(?,?,?,?,?,?,?) ON CONFLICT(channel,crypto_id) DO NOTHING");
|
||||
if (!st) goto done;
|
||||
sqlite3_bind_text(st, 1, channel, -1, SQLITE_STATIC); sqlite3_bind_blob(st, 2, job.id, 16, SQLITE_STATIC);
|
||||
sqlite3_bind_blob(st, 3, message, 32, SQLITE_STATIC); sqlite3_bind_int64(st, 4, (sqlite3_int64)author);
|
||||
sqlite3_bind_blob(st, 5, result.content_hash, 32, SQLITE_STATIC); sqlite3_bind_text(st, 6, job.cipher, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(st, 7, job.plain, -1, SQLITE_STATIC); rc = ak_step(inst, st);
|
||||
if (!rc) {
|
||||
st = ak_prepare(inst, "SELECT 1 FROM attachment_keys WHERE channel=? AND crypto_id=? AND message_id=? AND author=? AND cipher_hash=?");
|
||||
if (!st) { rc = -1; goto done; }
|
||||
sqlite3_bind_text(st, 1, channel, -1, SQLITE_STATIC); sqlite3_bind_blob(st, 2, job.id, 16, SQLITE_STATIC);
|
||||
sqlite3_bind_blob(st, 3, message, 32, SQLITE_STATIC); sqlite3_bind_int64(st, 4, (sqlite3_int64)author);
|
||||
sqlite3_bind_blob(st, 5, result.content_hash, 32, SQLITE_STATIC);
|
||||
rc = sqlite3_step(st) == SQLITE_ROW ? 0 : -1; sqlite3_finalize(st);
|
||||
if (chat_attachment_identity(body, (size_t)(end - body), job.id) || chat_msg_parse_media_body(body, &result)) goto done;
|
||||
result_code = -1;
|
||||
if (ak_paths(&job)) goto done;
|
||||
sqlite3* db = CC(inst)->db;
|
||||
if (!sqlite3_get_autocommit(db)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment keys: observation rejected inside external transaction channel=%s", channel); goto done;
|
||||
}
|
||||
stage = "begin observation"; rc = sqlite3_exec(db, "BEGIN IMMEDIATE /* attachment observation */", NULL, NULL, NULL);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
stage = "prepare identity";
|
||||
rc = sqlite3_prepare_v2(db, "SELECT message_id,author,cipher_hash FROM attachment_keys WHERE channel=? AND crypto_id=?", -1, &st, NULL);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
stage = "bind channel"; rc = sqlite3_bind_text(st, 1, channel, -1, SQLITE_STATIC);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
stage = "bind identity"; rc = sqlite3_bind_blob(st, 2, job.id, 16, SQLITE_STATIC);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
stage = "read identity"; rc = sqlite3_step(st);
|
||||
if (rc != SQLITE_ROW && rc != SQLITE_DONE) goto fail;
|
||||
int exists = rc == SQLITE_ROW, matches = !exists;
|
||||
if (exists && sqlite3_column_bytes(st, 0) == 32 && sqlite3_column_bytes(st, 2) == 32 &&
|
||||
sqlite3_column_blob(st, 0) && sqlite3_column_blob(st, 2)) {
|
||||
matches = (uint64_t)sqlite3_column_int64(st, 1) == author && !memcmp(sqlite3_column_blob(st, 0), message, 32) &&
|
||||
!memcmp(sqlite3_column_blob(st, 2), result.content_hash, 32);
|
||||
}
|
||||
stage = "finalize identity"; rc = sqlite3_finalize(st); st = NULL;
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
if (!matches) { result_code = -2; stage = "colliding identity"; rc = SQLITE_CONSTRAINT; goto fail; }
|
||||
if (!exists) {
|
||||
stage = "prepare observation";
|
||||
rc = sqlite3_prepare_v2(db, "INSERT INTO attachment_keys(channel,crypto_id,message_id,author,cipher_hash,cipher_path,plain_path)"
|
||||
" VALUES(?,?,?,?,?,?,?)", -1, &st, NULL);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
stage = "bind channel"; rc = sqlite3_bind_text(st, 1, channel, -1, SQLITE_STATIC);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
stage = "bind identity"; rc = sqlite3_bind_blob(st, 2, job.id, 16, SQLITE_STATIC);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
stage = "bind message"; rc = sqlite3_bind_blob(st, 3, message, 32, SQLITE_STATIC);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
stage = "bind author"; rc = sqlite3_bind_int64(st, 4, (sqlite3_int64)author);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
stage = "bind hash"; rc = sqlite3_bind_blob(st, 5, result.content_hash, 32, SQLITE_STATIC);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
stage = "bind cipher path"; rc = sqlite3_bind_text(st, 6, job.cipher, -1, SQLITE_STATIC);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
stage = "bind plain path"; rc = sqlite3_bind_text(st, 7, job.plain, -1, SQLITE_STATIC);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
stage = "insert observation"; rc = sqlite3_step(st);
|
||||
if (rc != SQLITE_DONE) goto fail;
|
||||
int changed = sqlite3_changes(db);
|
||||
stage = "finalize observation"; rc = sqlite3_finalize(st); st = NULL;
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
if (changed != 1) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment keys: observation rows=%d expected=1 channel=%s", changed, channel);
|
||||
stage = "row count"; rc = SQLITE_CONSTRAINT; goto fail;
|
||||
}
|
||||
}
|
||||
stage = "commit observation"; rc = sqlite3_exec(db, "COMMIT /* attachment observation */", NULL, NULL, NULL);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
if (!exists) DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "attachment keys: manifest indexed channel=%s author=%llu message=%02x%02x%02x%02x",
|
||||
channel, (unsigned long long)author, message[0], message[1], message[2], message[3]);
|
||||
result_code = 0; goto done;
|
||||
fail:
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment keys: observation channel=%s stage=%s rc=%d (%s) db=%s",
|
||||
channel, stage, rc, sqlite3_errstr(rc), sqlite3_errmsg(db));
|
||||
int final_rc = sqlite3_finalize(st);
|
||||
if (final_rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment keys: observation cleanup finalize rc=%d", final_rc);
|
||||
ak_rollback(inst, "observation", channel);
|
||||
done:
|
||||
if (rc) DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "attachment keys: rejected manifest/colliding identity channel=%s author=%llu", channel,
|
||||
(unsigned long long)author);
|
||||
media_index_result_free(&result); u_free(copy); return rc;
|
||||
if (result_code) DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "attachment keys: manifest not indexed channel=%s author=%llu result=%d stage=%s",
|
||||
channel, (unsigned long long)author, result_code, stage);
|
||||
media_index_result_free(&result); u_free(copy); return result_code;
|
||||
}
|
||||
|
||||
/* Кодограммы привязаны к immutable сообщению, UUID и полному hash контейнера. */
|
||||
|
||||
@@ -49,14 +49,18 @@ void chat_attachment_destroy(struct UTUN_INSTANCE* inst);
|
||||
int chat_attachment_send(struct UTUN_INSTANCE* inst, const char* channel, const char* source,
|
||||
const struct attachment_info* info, const char* type, const uint8_t reply[32], uint64_t request);
|
||||
|
||||
// Прочитать UUID из канонического base64-префикса тела медиа; 0/-1. Подпись проверяет вызывающий.
|
||||
// Прочитать UUID из канонического base64-префикса тела медиа; 0/-1, id меняется только при успехе. Подпись проверяет вызывающий.
|
||||
int chat_attachment_identity(const char* body, size_t length, uint8_t id[16]);
|
||||
|
||||
// Связать авторский ключ с ID сообщения и SHA256 контейнера до публикации; 0/-1. Массивы копируются в БД.
|
||||
/* Собственной транзакцией связать авторский ключ с ID сообщения и SHA256 контейнера до публикации; 0/-1.
|
||||
* Массивы копируются в БД. Повтор той же пары допустим, её изменение запрещено; отказ сохраняет прежние поля.
|
||||
* Внешняя транзакция отклоняется без изменений; все SQL-этапы и commit проверяются. */
|
||||
int chat_attachment_bind(struct UTUN_INSTANCE* inst, const char* channel, const uint8_t id[16],
|
||||
const uint8_t message[32], const uint8_t hash[32]);
|
||||
|
||||
// Учесть проверенное db_sync сообщение и сохранить метаданные контейнера; 0/-1. Обычные сообщения игнорируются.
|
||||
/* Учесть проверенное db_sync сообщение собственной транзакцией, не меняя ключ/состояние существующей идентичности.
|
||||
* 0 — записано/точный дубликат/обычное сообщение, -1 — SQL/OOM-отказ для повтора, -2 — неверный manifest/коллизия.
|
||||
* Внешняя транзакция запрещена. Подпись проверяет вызывающий; JSON заимствован до возврата. */
|
||||
int chat_attachment_observe(struct UTUN_INSTANCE* inst, const char* channel, const uint8_t message[32],
|
||||
uint64_t author, const char* json, size_t length);
|
||||
|
||||
|
||||
Reference in new issue
Block a user