diff --git a/src/chat/chat_core.c b/src/chat/chat_core.c index 6d9c153e..c42f43ad 100644 --- a/src/chat/chat_core.c +++ b/src/chat/chat_core.c @@ -397,8 +397,9 @@ int chat_core_get_messages_json(struct UTUN_INSTANCE* inst, const char* ch_id, i if (!data) data = ""; if (!la) la = ""; const char* ct = "text"; + char ctbuf[32] = {0}; const char* ctpos = strstr(data, "\"ct\":\""); - if (ctpos) { ctpos += 6; char ctbuf[32] = {0}; int ci = 0; while (*ctpos && *ctpos != '"' && ci < 31) ctbuf[ci++] = *ctpos++; ct = ctbuf; } + if (ctpos) { ctpos += 6; int ci = 0; while (*ctpos && *ctpos != '"' && ci < 31) ctbuf[ci++] = *ctpos++; ct = ctbuf; } char esc_data[4096], esc_aname[256], esc_la[256]; json_escape(data, esc_data, sizeof(esc_data)); json_escape(author_name ? author_name : "", esc_aname, sizeof(esc_aname)); diff --git a/src/chat/db_sync_arch.md b/src/chat/db_sync_arch.md index 6f0cf48d..16a80a44 100644 --- a/src/chat/db_sync_arch.md +++ b/src/chat/db_sync_arch.md @@ -36,7 +36,8 @@ **DB_SYNC_INSTANCE** — одна таблица синхронизации: - `table_name` — имя SQLite-таблицы (например `msg_GENERAL`) -- `hash` — instance_hash = первые 8 байт SHA256(name || id_be) для маршрутизации сообщений +- `group_id` — идентификатор группы для маршрутизации сообщений +- `sync_gated` — ожидание успешной синхронизации участников; блокирует входящий и исходящий обмен - `next_id` — автоинкрементный ID записей - `last_timestamp_ms` — последний использованный timestamp (монотонный) - `peers[]` — динамический массив SI_PEER (состояние синхронизации с каждым пиром) @@ -50,8 +51,8 @@ - `verified_pos` — последняя подтверждённая позиция (совпадение хешей) - `last_peer_count` — последнее известное количество записей у пира - `sync_state` — 0=idle, 1=syncing, 2=synced -- `sync_start_tb` — время начала синхронизации (для детекции таймаутов) -- `retry_count` — счётчик ретраев (зарезервировано, не используется) +- `sync_start_tb` — время начала/последнего прогресса обмена; устанавливается в обеих ролях +- `retry_count` — число повторов после таймаута ### 1.2 Структура данных (SQLite) @@ -128,7 +129,7 @@ chain_hash[0] = SHA256(0x00..00 [32] || id_0 || ts_0 || author_0 || sig_0) │ └──────────────┘ └───────────────┘ │ │ └─────────────────────────────────────────────┘ - CONN DOWN / ERROR (DB_ERR_NOT_FOUND, DB_ERR_DISABLED) + потеря READY группы / ERROR (NOT_FOUND, DISABLED, NOT_READY) ``` ### 2.2 Диаграмма протокола синхронизации @@ -137,7 +138,7 @@ chain_hash[0] = SHA256(0x00..00 [32] || id_0 || ts_0 || author_0 || sig_0) MASTER (node_id > peer) SLAVE (node_id < peer) ─────────────────────── ────────────────────── -conn UP → initiate sync +участники синхронизированы + READY группы → initiate sync │ ├─ INIT_SYNC ──────────────────────────► │ [my_count:4] │ flush recalc @@ -183,31 +184,29 @@ conn UP → initiate sync │ db_sync_init(inst) │ │ ├─ открывает SQLite (shared или свой chats.db) │ │ ├─ etcp_bind(inst, 0x20, recv_cb) │ -│ ├─ etcp_add_conn_status_cbk(conn_status) │ -│ └─ запускает peer_check_timer (5s) │ +│ └─ таймер повторов запускается при появлении работы │ │ │ │ ... позже, при создании канала ... │ │ │ -│ db_sync_instance_add(inst, "msg_GENERAL", hash, 1) │ +│ db_sync_instance_add(inst, "msg_GENERAL", group_id) │ │ ├─ CREATE TABLE IF NOT EXISTS... │ │ ├─ загружает next_id = MAX(id)+1 │ │ ├─ db_verify_chain() — проверка целостности цепных хешей │ │ ├─ запускает TTL-таймер (1 час) │ -│ └─ для всех активных соединений: si_peer_add() + initiate_sync() │ +│ └─ подписка на READY группы; gated=1, сетевого обмена нет │ ├────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─── DB_SYNC_INSTANCE активен ───────────────────────────────────────────┐│ │ │ ││ -│ │ conn UP ──► on_conn_up() ││ -│ │ └─ для всех enabled инстансов: ││ -│ │ si_peer_add() → initiate_sync() ││ +│ │ member_sync OK + READY группы → initiate_sync() ││ +│ │ └─ пир должен быть участником именно этой группы ││ │ │ ││ -│ │ conn DOWN ──► on_conn_down() ││ -│ │ └─ сброс sync_state для всех инстансов ││ +│ │ потеря READY (DOWN/REINIT/LEAVE/ошибка отправки снимка) ││ +│ │ └─ сброс состояния обмена только этой групповой сессии ││ │ │ ││ │ │ recv_cb() — приём сообщений ││ -│ │ ├─ извлекает instance_hash, type, payload ││ -│ │ ├─ lazy-register если инстанс не найден ││ +│ │ ├─ извлекает group_id, type, payload ││ +│ │ ├─ проверяет регистрацию, членство, gated и READY ││ │ │ └─ диспетчеризация по type ││ │ │ ││ │ │ peer_check_timer (каждые 5с) ││ @@ -222,6 +221,7 @@ conn UP → initiate sync │ └─────────────────────────────────────────────────────────────────────────┘│ │ │ │ db_sync_instance_remove(si) │ +│ ├─ снимает подписку на готовность группы │ │ ├─ отменяет таймеры │ │ ├─ освобождает peers[] │ │ └─ сдвигает массив instances[] (таблица БД не удаляется) │ @@ -245,13 +245,13 @@ conn UP → initiate sync ``` ┌─────────────────┬──────────────┬──────────────────────────────┐ -│ svc_id (1 byte) │ hash_be (8) │ payload (variable) │ -│ ETCP_RT_ID=0x20 │ instance_hash│ зависит от type │ +│ svc_id (1 byte) │ group_id (8) │ payload (variable) │ +│ ETCP_RT_ID=0x20 │ big-endian │ зависит от type │ └─────────────────┴──────────────┴──────────────────────────────┘ ``` -**instance_hash** = первые 8 байт `SHA256(table_name || id_be)` в big-endian. -Это позволяет приёмной стороне найти `DB_SYNC_INSTANCE` по хешу. +**group_id** — идентификатор группы в big-endian. Приёмная сторона находит +зарегистрированную таблицу этой группы; входящий пакет не создаёт таблицы или callbacks. ### 3.2 Типы сообщений @@ -263,7 +263,7 @@ conn UP → initiate sync | `DB_MSG_PUSH` | 0x05 | →Peers | Рассылка новой записи всем synced-пирам | | `DB_MSG_ACK_PUSH` | 0x06 | →Author | Подтверждение получения PUSH | | `DB_MSG_SYNC_DONE` | 0x07 | ↔ | Завершение синхронизации | -| `DB_MSG_ERROR` | 0x08 | ↔ | Ошибка (NOT_FOUND, DISABLED) | +| `DB_MSG_ERROR` | 0x08 | ↔ | Ошибка (NOT_FOUND, DISABLED, NOT_READY) | | `DB_MSG_REQUEST_SYNC` | 0x09 | Slave→Master | Запрос на инициацию синхронизации | ### 3.3 DB_MSG_INIT_SYNC (0x01) @@ -416,10 +416,18 @@ tp=10 → entry[0]: pos=9 (tp - 1) Коды ошибок: | #define | Value | Значение | |---------|-------|----------| -| `DB_ERR_NOT_FOUND` | 0x01 | Инстанс с таким hash не найден | +| `DB_ERR_NOT_FOUND` | 0x01 | Таблица этой группы не зарегистрирована | | `DB_ERR_DISABLED` | 0x02 | Инстанс существует, но disabled | +| `DB_ERR_NOT_READY` | 0x03 | Участники или групповая сессия ещё не готовы | -При получении ERROR сбрасывается `sync_state` пира в 0. +При получении ERROR сбрасываются `sync_state` и ожидание ответа; планируется повтор. +Ошибка отправки также возвращает обмен в IDLE. Обе роли ждут прогресс не более 15 секунд; +таймер повторов работает с периодом 5 секунд, пока есть несинхронизированные READY-пиры. +Появление нового READY запускает обмен независимо от того, работал ли таймер. + +При JOIN запись connection-узла из JOIN_INFO_RESP применяется через `member_sync_apply_record()`: +подписи проверяются, модель и Merkle-индекс обновляются в одной транзакции. JOIN_READY +подтверждает вступление, но история разблокируется только после успешного member_sync. ### 3.10 DB_MSG_REQUEST_SYNC (0x09)