You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

187 lines
10 KiB

/* Три настоящих ядра A/B/M: подписанный storage, wire-запросы, загрузка A,
* скачивание B, запрет чужой записи, удалённого участника и restart сервиса. */
#include "../src/member_files/member_files.h"
#include "../src/utun_instance.h"
#include "../src/chat/chat_core.h"
#include "../src/chat/chat_event.h"
#include "../src/chat/member_sync.h"
#include "../src/routing_layer/topo_node_sqlite.h"
#include "../src/routing_layer/etcp_router.h"
#include "../src/transport_layer/etcp.h"
#include "../src/transport_layer/secure_channel.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "test_utils.h"
#include <openssl/sha.h>
#include <stdio.h>
#include <string.h>
#define GROUP 5858585858585858ULL
#define CHANNEL "5858585858585858"
#define BYTES (3 * MF_BLOCK_SIZE + 37)
enum { AUTHOR, READER, SERVER, NODE_COUNT };
struct network_test {
struct UASYNC* ua;
struct UTUN_INSTANCE* nodes[NODE_COUNT];
struct SC_MYKEYS keys[NODE_COUNT];
uint64_t ids[NODE_COUNT];
uint8_t ed[NODE_COUNT][32], channel_x[32], channel_ed[32], channel_private[32];
char dir[512], source[768], destination[768];
struct mf_message response;
unsigned phase, ticks, progress;
int answered, finished, transfer_status, failed;
void* timer;
};
static struct network_test* active;
static void fail(struct network_test* t, const char* why) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files network: FAIL phase=%u reason=%s", t->phase, why);
t->failed = 1; uasync_stop(t->ua);
}
static void hex(const uint8_t* input, char* out) { for (unsigned i = 0; i < 32; i++) snprintf(out + 2 * i, 3, "%02x", input[i]); }
static void event(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int len) {
(void)inst;
struct network_test* t = active;
if (type != CHAT_EVT_MEMBER_FILES_TRANSFER || len != 51) return;
t->progress++;
if (data[50] == 2) { t->finished = 1; t->transfer_status = data[48] | ((int)data[49] << 8); }
}
static void response(void* arg, const struct mf_message* r) {
struct network_test* t = arg; t->response = *r; t->answered = 1;
}
static int put_member(struct network_test* t, int destination, int member, int deleted) {
uint8_t msg[512], join[64], tags_sig[64], tree[64];
uint64_t ts = 100000;
int n = member_sync_build_join_msg(t->channel_x, t->channel_ed, t->ids[member], t->keys[member].public_key, ts, msg, sizeof(msg));
if (n < 0 || sc_ed25519_sign(t->nodes[member]->my_ed25519_privkey, msg, n, join) != SC_OK) return -1;
const char* tags = deleted ? "{\"deleted\":\"yes\",\"ver\":\"2\"}" :
member == SERVER ? "{\"storage\":\"yes\",\"ver\":\"1\"}" : "{\"ver\":\"1\"}";
n = member_sync_build_owner_msg(t->ids[member], tags, msg, sizeof(msg));
if (n < 0 || sc_ed25519_sign(t->channel_private, msg, n, tags_sig) != SC_OK ||
member_sync_sign_pubkey(t->channel_private, t->keys[member].public_key, tree)) return -1;
char name[64]; snprintf(name, sizeof(name), "{\"name\":\"node%d\"}", member);
return member_sync_put(t->nodes[destination], CHANNEL, t->ids[member], t->keys[member].public_key, t->ed[member],
join, ts, NULL, 0, name, tags, tags_sig, member == SERVER, 0, tree) < 0 ? -1 : 0;
}
static int start(struct network_test* t, int index, int port) {
char pub[65], priv[65], server[65], path[768], db[768];
hex(t->keys[index].public_key, pub); hex(t->keys[index].private_key, priv); hex(t->keys[SERVER].public_key, server);
snprintf(path, sizeof(path), "%s/node%d.conf", t->dir, index); snprintf(db, sizeof(db), "%s/db%d", t->dir, index);
FILE* f = fopen(path, "w"); if (!f) return -1;
fprintf(f, "[global]\nmy_private_key=%s\nmy_public_key=%s\ndb_path=%s\n"
"[server:s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n"
"[chatserver]\nstorage_autoload=0\nmember_files_quota_mb=8\n", priv, pub, db, port + index);
if (index != SERVER) fprintf(f, "[client:to_m]\npeer_public_key=%s\nlink=s1:127.0.0.1:%d\n", server, port + SERVER);
fclose(f);
t->nodes[index] = utun_instance_create(t->ua, path);
if (!t->nodes[index] || utun_instance_init(t->nodes[index])) return -1;
memcpy(t->ed[index], t->nodes[index]->my_ed25519_pubkey, 32); t->ids[index] = t->nodes[index]->node_id;
chat_event_set_handler(t->nodes[index], event); return 0;
}
static int setup(struct network_test* t) {
struct SC_MYKEYS keys; sc_generate_keypair(&keys); memcpy(t->channel_x, keys.public_key, 32);
uint8_t seed[SHA512_DIGEST_LENGTH]; SHA512(keys.private_key, 32, seed); memcpy(t->channel_private, seed, 32);
if (sc_derive_ed25519_pubkey(keys.private_key, t->channel_ed)) return -1;
uint8_t signature[64], message[256]; size_t at = sizeof(CHANNEL);
memcpy(message, CHANNEL, at); const char name[] = "member-files";
memcpy(message + at, name, sizeof(name)); at += sizeof(name); memcpy(message + at, &t->ids[AUTHOR], 8); at += 8;
memcpy(message + at, t->channel_x, 32); at += 32; memcpy(message + at, t->channel_ed, 32); at += 32;
if (sc_ed25519_sign(t->channel_private, message, at, signature)) return -1;
for (unsigned i = 0; i < NODE_COUNT; i++) {
if (topo_node_sqlite_channel_put(t->nodes[i]->topo_sqlite_db, CHANNEL, "member-files", t->ids[AUTHOR],
t->channel_x, NULL, t->channel_ed, i == AUTHOR ? t->channel_private : NULL, signature)) return -1;
chat_core_ensure_channel_ready(t->nodes[i], CHANNEL);
for (unsigned j = 0; j < NODE_COUNT; j++) if (put_member(t, i, j, 0)) return -1;
}
snprintf(t->source, sizeof(t->source), "%s/source.bin", t->dir);
snprintf(t->destination, sizeof(t->destination), "%s/download.bin", t->dir);
FILE* file = fopen(t->source, "wb"); if (!file) return -1;
for (unsigned i = 0; i < BYTES; i++) if (fputc((i * 73 + 19) & 255, file) == EOF) { fclose(file); return -1; }
return fclose(file);
}
static void transfer(struct network_test* t, int upload, uint64_t id) {
struct mf_transfer_request* q = u_calloc(1, sizeof(*q));
if (!q) { fail(t, "transfer allocation"); return; }
q->inst = t->nodes[upload ? AUTHOR : READER]; q->group = GROUP; q->server = t->ids[SERVER]; q->owner = t->ids[AUTHOR];
q->id = id; q->upload = upload;
snprintf(q->remote, sizeof(q->remote), "file.bin"); snprintf(q->local, sizeof(q->local), "%s", upload ? t->source : t->destination);
t->finished = 0; member_files_gui_transfer(q);
}
static void request(struct network_test* t, uint8_t command, uint64_t owner) {
struct mf_message q = { .command = command, .owner = owner };
if (owner) snprintf(q.path, sizeof(q.path), "file.bin");
t->answered = 0;
if (!member_files_request(t->nodes[READER], GROUP, t->ids[SERVER], &q, response, t)) fail(t, "request start");
}
static int downloaded(struct network_test* t) {
FILE* file = fopen(t->destination, "rb"); if (!file) return 0;
int ok = 1;
for (unsigned i = 0; i < BYTES; i++) if (fgetc(file) != (int)((i * 73 + 19) & 255)) { ok = 0; break; }
if (fgetc(file) != EOF) ok = 0;
fclose(file); return ok;
}
static void tick(void* arg) {
struct network_test* t = arg; t->timer = NULL;
if (++t->ticks > 6000) { fail(t, "60s deadline"); return; }
if (t->phase == 0) {
int a = etcp_router_get_path(t->nodes[AUTHOR], GROUP, t->ids[SERVER], ETCP_RT_ID_MEMBER_FILES).next_hop_node_id != 0;
int b = etcp_router_get_path(t->nodes[READER], GROUP, t->ids[SERVER], ETCP_RT_ID_MEMBER_FILES).next_hop_node_id != 0;
if (a && b) { t->phase = 1; transfer(t, 1, 1); }
} else if (t->phase == 1 && t->finished) {
if (t->transfer_status) { fail(t, mf_status_text(t->transfer_status)); return; }
t->phase = 2; request(t, MF_STAT, t->ids[AUTHOR]);
} else if (t->phase == 2 && t->answered) {
if (t->response.status || t->response.size != BYTES) { fail(t, "STAT after upload"); return; }
t->phase = 3; request(t, MF_BEGIN_WRITE, t->ids[AUTHOR]);
} else if (t->phase == 3 && t->answered) {
if (t->response.status != MF_ACCESS_DENIED) { fail(t, "foreign write accepted"); return; }
t->phase = 4; transfer(t, 0, 2);
} else if (t->phase == 4 && t->finished) {
if (t->transfer_status || !downloaded(t)) { fail(t, "download bytes"); return; }
t->phase = 5; request(t, MF_LIST, 0);
} else if (t->phase == 5 && t->answered) {
if (t->response.status || !t->response.data_len) { fail(t, "virtual group root"); return; }
t->phase = 6; t->answered = 0;
struct mf_message q = { .command = MF_STAT, .owner = t->ids[AUTHOR] };
snprintf(q.path, sizeof(q.path), "file.bin");
if (!member_files_request(t->nodes[SERVER], GROUP, t->ids[SERVER], &q, response, t)) { fail(t, "local server request"); return; }
} else if (t->phase == 6 && t->answered) {
if (t->response.status || t->response.size != BYTES) { fail(t, "local server STAT"); return; }
if (put_member(t, SERVER, READER, 1)) { fail(t, "signed deletion"); return; }
t->phase = 7; request(t, MF_STAT, t->ids[AUTHOR]);
} else if (t->phase == 7 && t->answered) {
if (t->response.status != MF_ACCESS_DENIED) { fail(t, "deleted reader accepted"); return; }
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "member_files network: PASS progress=%u ticks=%u", t->progress, t->ticks);
uasync_stop(t->ua); return;
}
t->timer = uasync_set_timeout(t->ua, 100, t, tick, "member_files_test");
}
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); debug_set_category_level(DEBUG_CATEGORY_MEDIA, DEBUG_LEVEL_DEBUG);
debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_TRACE);
utun_instance_set_tun_init_enabled(0);
struct network_test t = {0}; active = &t;
snprintf(t.dir, sizeof(t.dir), "/tmp/utun_member_files_net_XXXXXX");
if (test_mkdtemp(t.dir)) return 1;
t.ua = uasync_create();
for (unsigned i = 0; i < NODE_COUNT; i++) sc_generate_keypair(&t.keys[i]);
int port = 39000 + (int)(get_time_tb() % 8000);
for (unsigned i = 0; i < NODE_COUNT; i++) if (start(&t, i, port)) { t.failed = 1; goto stop; }
if (setup(&t)) { t.failed = 1; goto stop; }
t.timer = uasync_set_timeout(t.ua, 100, &t, tick, "member_files_test"); uasync_mainloop(t.ua);
stop:
if (t.timer) uasync_cancel_timeout(t.ua, t.timer);
for (unsigned i = 0; i < NODE_COUNT; i++) if (t.nodes[i]) utun_instance_destroy(t.nodes[i]);
uasync_destroy(t.ua, 0);
printf("member_files network: %s phase=%u\n", t.failed ? "FAIL" : "PASS", t.phase); return t.failed;
}