Test real group avatar block delivery, permissions and recovery
This commit is contained in:
1 parent
4062ecdfb1
commit
46f4ce7091
2 files changed
+136
No files matched your search
@@ -74,6 +74,7 @@ check_PROGRAMS = \
|
||||
test_services \
|
||||
test_member_files \
|
||||
test_member_files_network \
|
||||
test_group_avatar \
|
||||
test_daemon_worker \
|
||||
test_group_exchange \
|
||||
test_group_recovery \
|
||||
@@ -726,6 +727,9 @@ test_member_files_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
test_member_files_network_SOURCES = test_member_files_network.c
|
||||
test_member_files_network_CFLAGS = $(test_services_CFLAGS)
|
||||
test_member_files_network_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
test_group_avatar_SOURCES = test_group_avatar.c
|
||||
test_group_avatar_CFLAGS = $(test_services_CFLAGS)
|
||||
test_group_avatar_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
test_daemon_worker_SOURCES = test_daemon_worker.c
|
||||
test_daemon_worker_CFLAGS = $(test_services_CFLAGS) -DUTUN_CLI_PATH='"$(abs_top_builddir)/src/utun"'
|
||||
test_daemon_worker_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/* Настоящие ядра/UDP/router: подпись группы, четыре блока, backpressure и restart. */
|
||||
#include "../src/chat/group_avatar.c"
|
||||
#include "../src/chat/member_sync.h"
|
||||
#include "test_utils.h"
|
||||
#include <assert.h>
|
||||
|
||||
#define GROUP 6767676767ULL
|
||||
#define CHANNEL "6767676767"
|
||||
static struct UTUN_INSTANCE* nodes[2];
|
||||
static struct UASYNC* ua;
|
||||
static unsigned received_blocks;
|
||||
static uint64_t old_version;
|
||||
static int capture_blocks;
|
||||
static uint8_t group_private[32], group_public[32], group_x[32];
|
||||
|
||||
static void hex(const uint8_t* input, char* out) { for (unsigned i = 0; i < 32; i++) snprintf(out + i * 2, 3, "%02x", input[i]); }
|
||||
static int hold(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t peer, const uint8_t* data, size_t size) {
|
||||
(void)inst; (void)group; (void)peer; (void)data; (void)size; return 0;
|
||||
}
|
||||
static void capture(int level, const char* category, const char* message) {
|
||||
(void)level; (void)category;
|
||||
if (!capture_blocks || !strstr(message, "group avatar: block received")) return;
|
||||
received_blocks++;
|
||||
struct group_avatar_record current; assert(!group_avatar_get(nodes[1], GROUP, ¤t));
|
||||
assert(current.version == old_version); // Даже последний блок ещё не опубликован до проверки всей записи.
|
||||
}
|
||||
static void put_member(unsigned destination, unsigned member) {
|
||||
uint8_t join[64], tree[64], message[512];
|
||||
int n = member_sync_build_join_msg(group_x, group_public, nodes[member]->node_id,
|
||||
nodes[member]->my_keys.public_key, 123, message, sizeof(message)); assert(n > 0);
|
||||
assert(sc_ed25519_sign(nodes[member]->my_ed25519_privkey, message, n, join) == SC_OK);
|
||||
assert(member_sync_sign_pubkey(group_private, nodes[member]->my_keys.public_key, tree) == 0);
|
||||
assert(member_sync_put(nodes[destination], CHANNEL, nodes[member]->node_id, nodes[member]->my_keys.public_key,
|
||||
nodes[member]->my_ed25519_pubkey, join, 123, NULL, 0, "{}", NULL, NULL, 0, 0, tree) >= 0);
|
||||
}
|
||||
static int wait_record(unsigned node, uint64_t version, const uint8_t hash[32]) {
|
||||
uint64_t deadline = get_time_tb() + 150000;
|
||||
while (get_time_tb() < deadline) {
|
||||
struct group_avatar_record r;
|
||||
if (!group_avatar_get(nodes[node], GROUP, &r) && r.version == version && !memcmp(r.hash, hash, 32)) return 1;
|
||||
uasync_poll(ua, 2);
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "test group avatar: sync timeout node=%u version=%llu", node, (unsigned long long)version); return 0;
|
||||
}
|
||||
static void set_key(unsigned node, const uint8_t* key) {
|
||||
sqlite3_stmt* st; assert(sqlite3_prepare_v2(nodes[node]->topo_sqlite_db,
|
||||
"UPDATE channels SET ed25519_privkey=? WHERE channel_id=?", -1, &st, NULL) == SQLITE_OK);
|
||||
if (key) sqlite3_bind_blob(st, 1, key, 32, SQLITE_STATIC); else sqlite3_bind_null(st, 1);
|
||||
sqlite3_bind_text(st, 2, CHANNEL, -1, SQLITE_STATIC); assert(sqlite3_step(st) == SQLITE_DONE); sqlite3_finalize(st);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); debug_set_category_level(DEBUG_CATEGORY_CHAT, DEBUG_LEVEL_DEBUG);
|
||||
utun_instance_set_tun_init_enabled(0); ua = uasync_create(); assert(ua);
|
||||
struct SC_MYKEYS keys[2], channel;
|
||||
assert(sc_generate_keypair(&channel) == SC_OK); memcpy(group_x, channel.public_key, 32);
|
||||
uint8_t seed[64]; SHA512(channel.private_key, 32, seed); memcpy(group_private, seed, 32);
|
||||
assert(sc_derive_ed25519_pubkey(channel.private_key, group_public) == 0);
|
||||
for (unsigned i = 0; i < 2; i++) assert(sc_generate_keypair(&keys[i]) == SC_OK);
|
||||
int port = 19000 + (int)(getpid() % 1000);
|
||||
char directory[512] = "/tmp/utun-group-avatar-XXXXXX"; assert(!test_mkdtemp(directory));
|
||||
for (unsigned i = 0; i < 2; i++) {
|
||||
char pub[65], priv[65], peer[65], config[2048]; hex(keys[i].public_key, pub); hex(keys[i].private_key, priv); hex(keys[1-i].public_key, peer);
|
||||
snprintf(config, sizeof(config), "[global]\nmy_private_key=%s\nmy_public_key=%s\ndb_path=%s/node%u\n"
|
||||
"[server:s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n"
|
||||
"[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\nmember_files_enabled=0\n"
|
||||
"[client:peer]\npeer_public_key=%s\nlink=s1:127.0.0.1:%d\n", priv, pub, directory, i, port + i, peer, port + 1-i);
|
||||
nodes[i] = utun_instance_create_from_str(ua, config); assert(nodes[i] && !utun_instance_init(nodes[i]));
|
||||
}
|
||||
uint8_t signature[64] = {0};
|
||||
struct TOPO_PEER_REQUEST* connections[2] = {0};
|
||||
for (unsigned i = 0; i < 2; i++) {
|
||||
assert(!topo_node_sqlite_channel_put(nodes[i]->topo_sqlite_db, CHANNEL, "avatar", nodes[0]->node_id,
|
||||
group_x, NULL, group_public, i ? NULL : group_private, signature));
|
||||
chat_core_ensure_channel_ready(nodes[i], CHANNEL);
|
||||
put_member(i, 0); put_member(i, 1);
|
||||
assert(!topo_group_peer_open(topo_groups_find(nodes[i]->topo_groups, GROUP), nodes[1-i]->node_id, &connections[i]));
|
||||
}
|
||||
uint64_t deadline = get_time_tb() + 150000;
|
||||
while ((topo_group_peer_phase(connections[0]) != TOPO_PEER_READY || topo_group_peer_phase(connections[1]) != TOPO_PEER_READY) &&
|
||||
get_time_tb() < deadline) uasync_poll(ua, 2);
|
||||
assert(topo_group_peer_phase(connections[0]) == TOPO_PEER_READY && topo_group_peer_phase(connections[1]) == TOPO_PEER_READY);
|
||||
uint8_t image[GROUP_AVATAR_MAX + 1]; for (unsigned i = 0; i < sizeof(image); i++) image[i] = (uint8_t)(i * 73 + 19);
|
||||
assert(group_avatar_set(nodes[0], GROUP, image, sizeof(image)) < 0);
|
||||
assert(group_avatar_set(nodes[1], GROUP, image, 1) < 0);
|
||||
assert(!group_avatar_set(nodes[0], GROUP, image, GROUP_AVATAR_MAX));
|
||||
struct group_avatar_record first; assert(!group_avatar_get(nodes[0], GROUP, &first));
|
||||
capture_blocks = 1; debug_set_log_hook(capture);
|
||||
assert(wait_record(1, first.version, first.hash)); capture_blocks = 0; debug_set_log_hook(NULL);
|
||||
assert(received_blocks == 4);
|
||||
struct group_avatar_record copy; assert(!group_avatar_get(nodes[1], GROUP, ©)); assert(!memcmp(copy.data, image, GROUP_AVATAR_MAX));
|
||||
copy.signature[0] ^= 1; assert(group_avatar_apply(nodes[1], GROUP, ©) < 0);
|
||||
copy = first; copy.data[0] ^= 1; assert(group_avatar_apply(nodes[1], GROUP, ©) < 0);
|
||||
assert(group_avatar_apply(nodes[1], GROUP + 1, &first) < 0);
|
||||
|
||||
// Повторное описание группы сохраняет ключ владельца, аватарку и UI-состояние.
|
||||
assert(!topo_node_sqlite_channel_put(nodes[0]->topo_sqlite_db, CHANNEL, "avatar", nodes[0]->node_id,
|
||||
group_x, NULL, group_public, NULL, signature));
|
||||
assert(!group_avatar_get(nodes[0], GROUP, ©) && copy.version == first.version);
|
||||
uint8_t private_key[32]; assert(!topo_node_sqlite_channel_get_priv(nodes[0]->topo_sqlite_db, CHANNEL, private_key));
|
||||
|
||||
etcp_router_set_receive_ready(nodes[1], ETCP_RT_ID_GROUP_AVATAR, hold);
|
||||
image[0] ^= 1; assert(!group_avatar_set(nodes[0], GROUP, image, 4097));
|
||||
struct group_avatar_record next; assert(!group_avatar_get(nodes[0], GROUP, &next));
|
||||
deadline = get_time_tb() + 20000; while (get_time_tb() < deadline) uasync_poll(ua, 2);
|
||||
assert(!group_avatar_get(nodes[1], GROUP, ©) && copy.version == first.version);
|
||||
etcp_router_conn_restart(nodes[0], GROUP, nodes[1]->node_id, ETCP_RT_ID_GROUP_AVATAR);
|
||||
etcp_router_set_receive_ready(nodes[1], ETCP_RT_ID_GROUP_AVATAR, ga_receive_ready);
|
||||
etcp_router_resume_receive(nodes[1], GROUP, nodes[0]->node_id, ETCP_RT_ID_GROUP_AVATAR);
|
||||
assert(wait_record(1, next.version, next.hash)); assert(group_avatar_apply(nodes[1], GROUP, &first) == 0);
|
||||
|
||||
// Держатель ключа, который не является создателем группы, тоже может обновить картинку.
|
||||
set_key(1, group_private); assert(!group_avatar_set(nodes[1], GROUP, image, 1)); set_key(1, NULL);
|
||||
assert(!group_avatar_get(nodes[1], GROUP, &next)); assert(wait_record(0, next.version, next.hash));
|
||||
copy = next; copy.data[0]++;
|
||||
SHA256(copy.data, copy.size, copy.hash); uint8_t signed_bytes[GROUP_AVATAR_MAX + 64];
|
||||
assert(sc_ed25519_sign(group_private, signed_bytes, ga_message(GROUP, ©, signed_bytes), copy.signature) == SC_OK);
|
||||
int accepted = ga_compare(copy.version, copy.hash, next.version, next.hash) > 0;
|
||||
assert(group_avatar_apply(nodes[0], GROUP, ©) == accepted);
|
||||
assert(!group_avatar_set(nodes[0], GROUP, NULL, 0)); assert(!group_avatar_get(nodes[0], GROUP, &next));
|
||||
assert(wait_record(1, next.version, next.hash)); assert(!group_avatar_get(nodes[1], GROUP, ©) && !copy.size);
|
||||
assert(group_avatar_apply(nodes[1], GROUP, &first) == 0);
|
||||
|
||||
topo_group_peer_close(connections[0]); topo_group_peer_close(connections[1]);
|
||||
utun_instance_destroy(nodes[1]); utun_instance_destroy(nodes[0]);
|
||||
char config[1024]; snprintf(config, sizeof(config), "[global]\ndb_path=%s/node0\n[chatserver]\nmember_files_enabled=0\n", directory);
|
||||
nodes[0] = utun_instance_create_from_str(ua, config); assert(nodes[0]);
|
||||
assert(!utun_core_start(nodes[0]) && !chat_service_start(nodes[0]));
|
||||
assert(!group_avatar_get(nodes[0], GROUP, ©) && copy.version == next.version && !copy.size);
|
||||
utun_instance_destroy(nodes[0]); uasync_destroy(ua, 1);
|
||||
puts("group avatar: PASS limits, signatures, four 4KiB blocks, atomicity, restart, key holders and tombstones"); return 0;
|
||||
}
|
||||
Reference in new issue
Block a user