From f2d2e6be8ffd114956d91255edab95c2ee5563eb Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 6 Oct 2026 12:40:07 +0200 Subject: [PATCH] Test avatar immutability, hash validation and quota accounting --- src/chat/chat_event.h | 4 ++-- tests/test_member_files.c | 29 +++++++++++++++++++++++++++- tools/chatgui/transport/gui_bridge.h | 8 ++++---- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/chat/chat_event.h b/src/chat/chat_event.h index c6d7792f..acf01311 100644 --- a/src/chat/chat_event.h +++ b/src/chat/chat_event.h @@ -43,8 +43,8 @@ extern "C" { #define CHAT_EVT_CONN_LIST 20 /* [count:2][entry:46B]* — список ETCP-соединений */ #define CHAT_EVT_CONN_METRICS 21 /* текст — полный дамп метрик выбранного соединения */ #define CHAT_EVT_MEMBER_DETAIL 22 /* [node_id:8][flags:1][own_tcp_active:2][links...][socks...] — live-снапшот для панели деталей участника */ -#define CHAT_EVT_MEMBER_LIST 23 /* [ch_id_len:1][ch_id:var][count:2][member:79B]* — полный список мемберов канала */ -#define CHAT_EVT_MEMBER_UPDATED 24 /* [ch_id_len:1][ch_id:var][count:1][member:79B] — обновление одного мембера */ +#define CHAT_EVT_MEMBER_LIST 23 /* [ch_id_len:1][ch_id:var][count:2][member:CHAT_MEMBER_DISPLAY_SIZE]* */ +#define CHAT_EVT_MEMBER_UPDATED 24 /* [ch_id_len:1][ch_id:var][count:1][member:CHAT_MEMBER_DISPLAY_SIZE] */ #define CHAT_EVT_MEMBER_REMOVED 25 /* [ch_id_len:1][ch_id:var][node_id:8] — мембер удалён из канала */ #define CHAT_EVT_BGP_INFO 26 /* текст: "rtt:XXms hop:X via bgp: [XXXX] name1 [XXXX] name2" */ #define CHAT_EVT_CONNECTING_NODES 27 /* [ch_id_len:1][ch_id:var][count:2][node_id:8]* */ diff --git a/tests/test_member_files.c b/tests/test_member_files.c index d10fbf14..258117f7 100644 --- a/tests/test_member_files.c +++ b/tests/test_member_files.c @@ -35,6 +35,32 @@ static void protocol_test(void) { wire[MF_WIRE_HEADER] = 0; EXPECT(mf_decode(wire, (size_t)n, &r) != 0, "embedded NUL"); } +static void avatar_storage_test(const char* directory) { + char root[1200]; snprintf(root, sizeof(root), "%s/avatar-store", directory); + struct mf_storage* s = mf_storage_create(root, MF_AVATAR_MAX_BYTES, 200000); + EXPECT(s && mf_storage_initialize(s) == MF_OK, "avatar storage initialized"); if (!s) return; + memset(&q, 0, sizeof(q)); q.owner = 77; snprintf(q.path, sizeof(q.path), "avatars"); + run(s, 55, 77, MF_MKDIR, MF_OK); + const uint8_t contents[] = {1, 2, 3, 4}; SHA256(contents, sizeof(contents), q.hash); + char hash[65]; for (unsigned i = 0; i < 32; i++) snprintf(hash + i * 2, 3, "%02x", q.hash[i]); + snprintf(q.path, sizeof(q.path), "avatars/%s.png", hash); q.size = sizeof(contents); memset(q.upload, 1, 16); + run(s, 55, 77, MF_BEGIN_WRITE, MF_OK); + memcpy(q.data, contents, sizeof(contents)); q.data_len = sizeof(contents); run(s, 55, 77, MF_WRITE, MF_OK); + q.data_len = 0; run(s, 55, 77, MF_COMMIT, MF_OK); + uint8_t version[16]; memcpy(version, r.version, 16); + memset(q.upload, 2, 16); run(s, 55, 77, MF_BEGIN_WRITE, MF_EXISTS); + memcpy(q.version, version, 16); run(s, 55, 77, MF_BEGIN_WRITE, MF_EXISTS); + memset(q.version, 0, 16); q.hash[0] ^= 1; run(s, 55, 77, MF_BEGIN_WRITE, MF_HASH_MISMATCH); q.hash[0] ^= 1; + q.size = MF_AVATAR_MAX_BYTES + 1; run(s, 55, 77, MF_BEGIN_WRITE, MF_INVALID_RANGE); + q.size = 0; run(s, 55, 77, MF_BEGIN_WRITE, MF_INVALID_RANGE); + memcpy(q.version, version, 16); snprintf(q.target, sizeof(q.target), "renamed.png"); run(s, 55, 77, MF_RENAME, MF_INVALID_PATH); + memset(&q, 0, sizeof(q)); q.owner = 77; run(s, 55, 88, MF_QUOTA, MF_OK); + EXPECT(mf_get64(r.data) == sizeof(contents) && mf_get64(r.data + 16) == 0 && + mf_get64(r.data + 24) == 1 && mf_get64(r.data + 32) == 1, "avatars counted once in UID quota"); + EXPECT(!mf_avatar_file_valid("../avatar.png") && !mf_avatar_file_valid("avatar.png"), "noncanonical avatar names rejected"); + mf_storage_destroy(s); +} + static void storage_test(const char* directory) { struct mf_storage* s = mf_storage_create(directory, 40000, 200000); EXPECT(s != NULL, "create storage"); if (!s) return; @@ -346,7 +372,8 @@ int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_MEDIA, DEBUG_LEVEL_DEBUG); char directory[1024] = "/tmp/utun_member_files_XXXXXX"; if (test_mkdtemp(directory)) return 1; - protocol_test(); storage_test(directory); quota_test(directory); count_test(directory); quota_scan_failure_test(directory); worker_test(); + protocol_test(); storage_test(directory); avatar_storage_test(directory); quota_test(directory); + count_test(directory); quota_scan_failure_test(directory); worker_test(); #ifndef _WIN32 large_offset_test(directory); #endif diff --git a/tools/chatgui/transport/gui_bridge.h b/tools/chatgui/transport/gui_bridge.h index 02c91fa8..b7b28633 100644 --- a/tools/chatgui/transport/gui_bridge.h +++ b/tools/chatgui/transport/gui_bridge.h @@ -36,8 +36,8 @@ struct UTUN_INSTANCE; #define GUI_EVT_CONN_LIST 20 /* data: [count:2][entry:46B]* — binary conn list */ #define GUI_EVT_CONN_METRICS 21 /* data: text dump of selected connection metrics */ #define GUI_EVT_MEMBER_DETAIL 22 /* data: [node_id:8][flags:1][own_tcp_active:2][links...][socks...] — live snapshot for member detail panel */ -#define GUI_EVT_MEMBER_LIST 23 /* data: [ch_id_len:1][ch_id:var][count:2][member:79B]* — полный список мемберов канала */ -#define GUI_EVT_MEMBER_UPDATED 24 /* data: [ch_id_len:1][ch_id:var][count:1][member:79B] — обновление одного мембера */ +#define GUI_EVT_MEMBER_LIST 23 /* data: [ch_id_len:1][ch_id:var][count:2][member:CHAT_MEMBER_DISPLAY_SIZE]* */ +#define GUI_EVT_MEMBER_UPDATED 24 /* data: [ch_id_len:1][ch_id:var][count:1][member:CHAT_MEMBER_DISPLAY_SIZE] */ #define GUI_EVT_MEMBER_REMOVED 25 /* data: [ch_id_len:1][ch_id:var][node_id:8] — мембер удалён из канала */ #define GUI_EVT_BGP_INFO 26 /* data: text — BGP hop summary for member detail */ #define GUI_EVT_ADMIN_KEY_RECEIVED 28 /* data: [ch_id_len:1][ch_id:var][from_node_id:8] — получен канальный ключ */ @@ -152,12 +152,12 @@ typedef void (*gui_member_detail_fn)(const uint8_t* data, int len); void gui_bridge_set_member_detail_cb(gui_member_detail_fn cb); /* Callback для получения полного списка мемберов канала (вызывается из GUI-потока) - data: [ch_id_len:1][ch_id:var][count:2][member:79B]* */ + data: [ch_id_len:1][ch_id:var][count:2][member:CHAT_MEMBER_DISPLAY_SIZE]* */ typedef void (*gui_member_list_fn)(const uint8_t* data, int len); void gui_bridge_set_member_list_cb(gui_member_list_fn cb); /* Callback для обновления одного мембера (вызывается из GUI-потока) - data: [ch_id_len:1][ch_id:var][count:1][member:79B] */ + data: [ch_id_len:1][ch_id:var][count:1][member:CHAT_MEMBER_DISPLAY_SIZE] */ typedef void (*gui_member_updated_fn)(const uint8_t* data, int len); void gui_bridge_set_member_updated_cb(gui_member_updated_fn cb);