From 864a250eea952542cbe94aac976ebe70ade36fc6 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 6 Oct 2026 20:02:41 +0200 Subject: [PATCH] Preserve input cursor when member page outputs alias it --- src/chat/chat_feedback.h | 47 ++++++++++++++++++++++++++++++++++++++++ src/chat/member_sync.c | 2 +- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/chat/chat_feedback.h diff --git a/src/chat/chat_feedback.h b/src/chat/chat_feedback.h new file mode 100644 index 00000000..a9f83831 --- /dev/null +++ b/src/chat/chat_feedback.h @@ -0,0 +1,47 @@ +/* Подписанные состояния реакций и ответов, отдельная таблица feedback_. + * Каждый (message_id, node_id, kind) хранит последнее полное состояние с ver. + * Пустое data — сохраняемая отмена. Слой хранит и реплицирует подписанные байты; + * интерпретацию ответа относительно опроса выполняет вызывающий модуль. + * Все операции — в uasync-потоке. init вызывается после member_sync_init; + * общий Merkle-движок освобождается member_sync_destroy. */ +#ifndef CHAT_FEEDBACK_H +#define CHAT_FEEDBACK_H + +#include +#include + +struct UTUN_INSTANCE; + +#define CHAT_FEEDBACK_REACTION 1 +#define CHAT_FEEDBACK_ANSWER 2 +#define CHAT_FEEDBACK_DATA_MAX 2048 +#define CHAT_MESSAGE_ID_SIZE 32 + +struct chat_feedback_record { + uint8_t message_id[CHAT_MESSAGE_ID_SIZE]; + uint64_t node_id; + uint64_t ver; /* 1..INT64_MAX; отдельная версия для каждого kind */ + uint8_t kind; + uint16_t data_len; + uint8_t data[CHAT_FEEDBACK_DATA_MAX]; + uint8_t signature[64]; +}; + +/* Производный ID неизменяемого сообщения; 0/-1. Числа хешируются как BE64. */ +int chat_message_id(uint64_t channel_id, uint64_t timestamp, const uint8_t signature[64], uint8_t id[32]); +/* Регистрирует FEEDBACK и восстанавливает производный индекс из подписанных записей; 0/-1. */ +int chat_feedback_init(struct UTUN_INSTANCE* inst); +/* Подготовить таблицу канала; 0/-1. Не создаёт членство или сетевую группу. */ +int chat_feedback_ensure_channel(struct UTUN_INSTANCE* inst, const char* channel); +/* Прочитать состояние: 1 найдено, 0 отсутствует, -1 ошибка. */ +int chat_feedback_get(struct UTUN_INSTANCE* inst, const char* channel, const uint8_t message_id[32], + uint64_t node_id, uint8_t kind, struct chat_feedback_record* out); +/* Проверить и применить подписанную запись: 1 изменилось, 0 повтор/старая, -1 ошибка. + * Одинаковый ver разрешается сравнением data; подпись проверяется до сравнения. */ +int chat_feedback_apply(struct UTUN_INSTANCE* inst, const char* channel, const struct chat_feedback_record* record); +/* Подписать локальное полное состояние с ver+1; NULL/0 отменяет. 1/0/-1 как apply. + * out_ver необязателен. Семантику data проверяет вызывающий модуль до этого вызова. */ +int chat_feedback_set(struct UTUN_INSTANCE* inst, const char* channel, const uint8_t message_id[32], + uint8_t kind, const uint8_t* data, size_t len, uint64_t* out_ver); + +#endif diff --git a/src/chat/member_sync.c b/src/chat/member_sync.c index 4ad14333..eccc82f2 100644 --- a/src/chat/member_sync.c +++ b/src/chat/member_sync.c @@ -209,12 +209,12 @@ static int _member_get_page(void* ctx, const char* ns, uint64_t prefix, int afte struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)ctx; sqlite3* db = _db(inst); if (!db || !buf || !len) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: get_items — invalid args db=%p buf=%p len=%p", MS_ID, (void*)db, (void*)buf, (void*)len); return -1; } const uint8_t level = MT_MAX_LEVEL; - memset(next, 0, sizeof(*next)); *more = 0; uint64_t after_id = member_read64(after->bytes); static const uint8_t zero_tail[MT_KEY_SIZE - 8]; if (after_valid && memcmp(after->bytes + 8, zero_tail, sizeof(zero_tail))) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: invalid member cursor ns=%s", MS_ID, ns); return -1; } + memset(next, 0, sizeof(*next)); *more = 0; DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: get_items ns=%s L%d/P%016llx", MS_ID, ns, level, (unsigned long long)prefix); char peers_tbl[128]; _peers_table(ns, peers_tbl, sizeof(peers_tbl));