/* topo_node_sqlite — хранение подписанных снимков узлов, адресов, каналов и блоков мемберов в общей SQLite. * Модель/проверка подписей/registry — topo_node и member_sync; этот модуль реализует SQL-операции в uasync-потоке. * Только snapshot_put/load проверяют подпись; остальные записи требуют проверенных вызывающим данных. * snapshot_put сохраняет более свежий timestamp; node_load предпочитает snapshot. * db/groups заимствованы. Возвращённый TOPO_NODE без registry ref: передать registry либо уничтожить через topo_node_destroy. * Массивы out_ids/out_ch_ids/out_peers принадлежат вызывающему и освобождаются u_free. */ #ifndef TOPO_NODE_SQLITE_H #define TOPO_NODE_SQLITE_H #include #include #include #include #include "topo_node.h" #define PEERS_JOIN_SIG_SIZE 64 #define PEERS_JOIN_TS_SIZE 8 #define PEERS_UPDATE_SIG_SIZE 64 /* flags byte in wire format */ #define PEERS_FLAG_HAS_JOIN 0x01 /* Создать/обновить nodes, node_addresses, node_snapshots, channels; 0/-1. db открывает и закрывает вызывающий. */ int topo_node_sqlite_init(sqlite3* db); /* Проверить и сохранить полный подписанный NODEINFO без групповых полей; старый/равный timestamp даёт 0 без замены. */ int topo_node_sqlite_snapshot_put(sqlite3* db, struct TOPO_NODE* ni); /* Максимальный сохранённый timestamp (Unix microseconds), в том числе после инвалидирования record; 0 — нет/ошибка. */ uint64_t topo_node_sqlite_snapshot_timestamp(sqlite3* db, uint64_t node_id); /* Незарегистрированный проверенный снимок, включая снимок без адресов; NULL — нет/невалиден/ошибка. * groups и groups->instance->topo_groups должны обозначать один контейнер с пулами. */ struct TOPO_NODE* topo_node_sqlite_snapshot_load(sqlite3* db, struct TOPO_GROUPS* groups, uint64_t node_id); /* Персистентная запись адресов узла в node_addresses (источник истины — BGP/topo_group). * DELETE + INSERT из ni->v4_addrs/v6_addrs под savepoint, включая пустой набор; 0/-1, отказ откатывает замену. * mapping/inbound/source берутся из SOCKMETA; проверку ni выполняет вызывающий. RTT при замене не сохраняется. */ int topo_node_sqlite_addrs_put(sqlite3* db, uint64_t node_id, struct TOPO_NODE* ni); /* Есть ли строка хотя бы в одной peers_*: включая source=1 и deleted=1; 1/0, при ошибке также 0. */ int topo_node_sqlite_member_exists(sqlite3* db, uint64_t node_id); /* Числовые ненулевые channel_id таблиц peers_*, содержащих строку узла; включает placeholders/deleted. * 0/-1; пустой результат NULL/0, при ошибке частичный массив не выдаётся. */ int topo_node_sqlite_get_member_channels(sqlite3* db, uint64_t node_id, uint64_t** out_ch_ids, int* out_count); /* Создать peers_ и сохранить канал; 0/-1. Ключи — по 32, signature — 64 байта, privkey необязательны. * При обновлении публичные ключи обязаны совпасть; имя/владелец/подпись заменяются, NULL privkey сохраняет прежний. * Подпись/пары ключей не проверяет. Табличный суффикс: первые 63 символа, вне [A-Za-z0-9_] заменены на '_'. */ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id, const char* name, uint64_t owner_node_id, const uint8_t* x25519_pub, const uint8_t* x25519_priv, const uint8_t* ed25519_pub, const uint8_t* ed25519_priv, const uint8_t* signature); /* Добавить radio_muted=0 в существующую peers_, если поля ещё нет; 0/-1, идемпотентно. */ int topo_node_sqlite_member_local_init(sqlite3* db, const char* channel_id); /* Полное локальное удаление канала: DROP peers_/msg_, DELETE channels, * Удаляются feedback_ и merkle_tree_hash всех datasets канала (если таблица есть). * 0/-1; savepoint откатывает в том числе DROP TABLE, внешняя транзакция сохраняется. */ int topo_node_sqlite_channel_delete(sqlite3* db, const char* channel_id); /* Вставить source=1 с нулевой join_sig; существующую строку не менять. 0/-1; ключи по 32 байта. * Плейсхолдер не участвует в Merkle-хеше; канал/peers_ должны уже существовать. */ int topo_node_sqlite_member_placeholder_put(sqlite3* db, const char* channel_id, uint64_t node_id, const uint8_t* x25519_pubkey, const uint8_t* ed25519_pubkey); /* Обновляет только блок мембера (keys/join/update_sig/update_ts/userinfo), не трогая блок владельца. * signed_by/signature — дерево приглашений: кто подписал pubkey узла (0 = корень, ключом группы). * 0/-1; подписи по 64, ключи по 32 байта. NULL join_sig даёт нулевую подпись, userinfo=NULL даёт ''. * Сбрасывает source=0; свежесть/подписи проверяет вызывающий, owner/local-поля сохраняются. */ int topo_node_sqlite_member_block_put(sqlite3* db, const char* channel_id, uint64_t node_id, const uint8_t* join_sig, uint64_t join_ts, const uint8_t* update_sig, uint64_t update_ts, const uint8_t* x25519_pubkey, const uint8_t* ed25519_pubkey, const char* userinfo, const uint8_t* creator_sig, uint64_t signed_by, const uint8_t* signature); /* Прочитать дерево приглашений мембера (signed_by + signature). 0=ок, -1=нет записи/полей. */ int topo_node_sqlite_member_get_sign(sqlite3* db, const char* channel_id, uint64_t node_id, uint64_t* signed_by_out, uint8_t signature_out[64]); /* Есть ли строка в peers_, включая placeholders/deleted. 1/0; ошибка также даёт 0. */ int topo_node_sqlite_member_in_channel(sqlite3* db, const char* channel_id, uint64_t node_id); /* Обновить owner-блок и deleted из adm_tags; 0/-1, отсутствующая строка даёт 0 без вставки. * adm_tags/adm_tags_sig могут быть NULL; ненулевая подпись — 64 байта, проверяется вызывающим. */ int topo_node_sqlite_member_owner_put(sqlite3* db, const char* channel_id, uint64_t node_id, const char* adm_tags, const uint8_t* adm_tags_sig, int storage); /* Прочитать ровно 64 байта join_sig и join_ts; 0/-1, выходы обязательны и при неуспехе не меняются. */ int topo_node_sqlite_member_get_join(sqlite3* db, const char* channel_id, uint64_t node_id, uint8_t* join_sig_out, uint64_t* join_ts_out); /* Физически удалить строку участника; 0/-1. Отсутствующая строка даёт 0; tombstone/Merkle не создаёт. */ int topo_node_sqlite_member_del(sqlite3* db, const char* channel_id, uint64_t node_id); /* Записать проверенные вызывающим ключи/имя в nodes; 0/-1. Пустое имя сохраняет прежнее при UPDATE. * Ключи заменяются независимо от update_ts; update_ts хранит максимум, created_at сохраняется, если не NULL. * Новая строка online=1, при UPDATE online сохраняется. Подпись и свежесть здесь не проверяются. */ int topo_node_sqlite_node_update_verified(sqlite3* db, uint64_t node_id, const char* name, const uint8_t* x25519, const uint8_t* ed25519, uint64_t update_ts, time_t created_at); /* Метаданные канала; 0/-1. Все выходы необязательны; name_sz включает NUL, ключи по 32, подпись 64 байта. */ int topo_node_sqlite_channel_get(sqlite3* db, const char* channel_id, char* name_out, int name_sz, uint64_t* owner_node_id, uint8_t* x25519_pub, uint8_t* ed25519_pub, uint8_t* signature); /* Прочитать первые 32 байта Ed25519 privkey; 0/-1, отсутствующий/короткий ключ даёт -1. */ int topo_node_sqlite_channel_get_priv(sqlite3* db, const char* channel_id, uint8_t* ed25519_priv_out); /* Прочитать оба канальных privkey (x25519 + ed25519). Возвращает 0 если оба есть. */ int topo_node_sqlite_channel_get_privs(sqlite3* db, const char* channel_id, uint8_t* x25519_priv_out, uint8_t* ed25519_priv_out); /* Записать оба канальных privkey по 32 байта (импорт прав админа); 0/-1, без проверки пары/подписи. * Отсутствующая строка даёт 0 без вставки; наличие канала проверяет вызывающий. */ int topo_node_sqlite_channel_set_privs(sqlite3* db, const char* channel_id, const uint8_t* x25519_priv, const uint8_t* ed25519_priv); /* Установить online=0/1 существующей строки; 0/-1. Отсутствующая строка даёт 0 без вставки. */ int topo_node_sqlite_node_set_online(sqlite3* db, uint64_t node_id, int online); /* online из nodes; 0, если записи нет или запрос не выполнен. */ int topo_node_sqlite_node_get_online(sqlite3* db, uint64_t node_id); /* Скопировать ровно 32 байта Ed25519 из nodes: 0 — найден, -1 — отсутствует/другая длина/ошибка SQLite. * Значение байтов не определяет наличие ключа; криптографическую пригодность проверяет операция verify. */ int topo_node_sqlite_get_ed25519_pubkey(sqlite3* db, uint64_t node_id, uint8_t pubkey_out[32]); /* Атомарно пересчитать node_type/storage/deleted во всех peers_* по адресам и adm_tags; 0/-1. * node_type: 0 нет адресов, 1 прямой/локальный/strict, 2 NAT/contact, 4 supernode=yes. * storage=yes действует только при наличии ADDR_TYPE_DIRECT, deleted берётся из adm_tags.deleted=yes. */ int topo_node_sqlite_nodeinfo_updated(sqlite3* db, uint64_t node_id); /* Обновить node_addresses.rtt и все peers_*.node_RTT, единицы 0.1 ms; ошибки логируются, обновление неатомарно. */ void topo_node_sqlite_update_rtt(sqlite3* db, uint64_t node_id, uint16_t rtt); /** * @brief Загружает TOPO_NODE из SQLite (ref_count=0). * @param groups нужен для выделения адресов из memory_pool * @return TOPO_NODE* или NULL, если нет пригодной записи/адресов bootstrap либо при ошибке * * Сначала загружает и проверяет подписанный snapshot; при его отсутствии собирает сведения nodes/node_addresses. * Snapshot может быть без адресов. Bootstrap требует адресов и получает timestamp=0, профиль UNKNOWN. * При отказе SQL/памяти частичная запись освобождается. groups->instance->topo_groups должен совпадать с groups. * Вызывающий передаёт владение через topo_node_registry_store() либо вызывает topo_node_destroy(). */ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* groups, uint64_t node_id); /* Записать connected=0/1 существующего участника; 0/-1, без вставки отсутствующей строки. */ int topo_node_sqlite_set_connected(sqlite3* db, const char* channel_id, uint64_t node_id, int connected); /* Кандидат CHAT-подключения: роль канальная, тип устройства из подписанного snapshot. * UINT8_MAX означает отсутствие сведений о типе; адреса не ограничивают выборку. */ struct topo_connect_peer { uint64_t node_id; uint8_t client_type, supernode; }; /* Все неудалённые участники, включая self; сначала connected, затем RTT/node_id. * 0/-1; массив принадлежит вызывающему и освобождается u_free(). */ int topo_node_sqlite_get_connect_peers(sqlite3* db, const char* channel_id, struct topo_connect_peer** out, size_t* out_count); /* Нижеследующие выборки: 0/-1, пусто/ошибка — NULL/0, частичный массив не выдаётся; освобождение u_free. * connected/supernode ограничены участниками канала, self не исключают, deleted=1 исключают. * public/local — глобальные адреса с записью nodes, исключены self и deleted=1 в указанном канале; * наличие членства в канале не требуется. public: addr_type 1/2, local: 0/3; фильтра port!=0 нет. */ int topo_node_sqlite_get_connected_peers(sqlite3* db, const char* channel_id, uint64_t** out_ids, int* out_count); int topo_node_sqlite_get_public_peers(sqlite3* db, const char* channel_id, uint64_t** out_ids, int* out_count, uint64_t self_node_id); int topo_node_sqlite_get_local_peers(sqlite3* db, const char* channel_id, uint64_t** out_ids, int* out_count, uint64_t self_node_id); int topo_node_sqlite_get_supernode_peers(sqlite3* db, const char* channel_id, uint64_t** out_ids, int* out_count); /* node_type узла в канале (peers_.node_type); -1 если нет записи/ошибка. */ int topo_node_sqlite_get_node_type(sqlite3* db, const char* channel_id, uint64_t node_id); #endif