Files
utun2/tests/test_invite_codec.c
T

102 lines
6.5 KiB
C

/* Контракт кодека: полные roundtrip, точные буферы и атомарные выходы при отказе. */
#include "chat/invite_link.h"
#include "transport_layer/secure_channel.h"
#include "../lib/debug_config.h"
#include <openssl/evp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define CHECK(x) do { if (!(x)) { fprintf(stderr, "codec line=%d: %s\n", __LINE__, #x); exit(1); } } while (0)
static unsigned cases;
static void rejected(const char* link, size_t len) {
struct InviteData out, before; memset(&out, 0xa5, sizeof(out)); before = out;
char error[3] = {1, 1, 1};
CHECK(invite_link_decode(link, len, &out, error, sizeof(error)) < 0);
CHECK(!memcmp(&out, &before, sizeof(out)) && !error[2]); cases++;
}
static void rejected_raw(const uint8_t* raw, size_t size) {
char link[INVITE_LINK_MAX]; memcpy(link, "utun://", 7);
int len = EVP_EncodeBlock((unsigned char*)link + 7, raw, (int)size);
CHECK(len > 0); rejected(link, (size_t)len + 7);
}
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR);
struct InviteData input = { .channelId = 42, .join_key = UINT64_MAX };
struct SC_MYKEYS keys; CHECK(sc_generate_keypair(&keys) == SC_OK);
memcpy(input.pubkey, keys.public_key, 32);
/* Полный нулевой ключ запрещён; нулевой префикс допустим. */
input.pubkey[0] = 0;
char password[128]; memset(password, 'x', sizeof(password) - 1); password[127] = 0;
for (unsigned count = 1; count <= INVITE_ADDR_MAX; count++) for (unsigned mode = 0; mode < 5; mode++) {
input.addrCount = count; memset(&input.reality, 0, sizeof(input.reality));
for (unsigned i = 0; i < count; i++) {
struct InviteAddr* a = &input.addrs[i]; memset(a, 0, sizeof(*a));
a->family = mode == 0 ? 4 : (mode == 1 || mode == 4) ? 6 : (i & 1 ? 4 : 6);
a->port = (uint16_t)(1 + i * 1000); a->socketId = (uint8_t)(255 - i);
a->proto = mode >= 3 ? 6 : i & 1 ? 1 : 2;
for (unsigned j = 0; j < (a->family == 6 ? 16u : 4u); j++) a->address[j] = (uint8_t)(i + j + 1);
}
if (mode >= 3) {
input.reality.has = 1; input.reality.server_pubkey[0] = 7;
memset(input.reality.server_name, 'a', 63); input.reality.server_name[63] = 0;
}
const char* pass = (mode & 1 || mode == 4) ? password : NULL;
char link[INVITE_LINK_MAX], exact[INVITE_LINK_MAX], small[INVITE_LINK_MAX];
CHECK(invite_link_encode(&input, pass, link, sizeof(link)) > 0);
size_t length = strlen(link);
if (mode == 4 && count == 32) CHECK(length + 1 == INVITE_LINK_MAX);
CHECK(length < INVITE_LINK_MAX && invite_link_encode(&input, pass, exact, length + 1) > 0);
CHECK(!strcmp(exact, link)); memset(small, 0xa5, sizeof(small));
CHECK(invite_link_encode(&input, pass, small, length) < 0);
for (unsigned i = 0; i < sizeof(small); i++) CHECK((unsigned char)small[i] == 0xa5);
struct InviteData out; CHECK(!invite_link_decode(link, length, &out, NULL, 0));
CHECK(out.channelId == input.channelId && out.join_key == input.join_key && out.addrCount == count);
CHECK(out.nodeId == sc_derive_node_id_from_pubkey(input.pubkey) && !memcmp(out.pubkey, input.pubkey, 32));
CHECK(!strcmp(out.password, pass ? pass : "") && !memcmp(out.addrs, input.addrs, count * sizeof(input.addrs[0])));
CHECK(!memcmp(&out.reality, &input.reality, sizeof(out.reality)));
uint8_t a[1024], b[1024]; int bytes = invite_serialize_addrs(&out, a, sizeof(a));
CHECK(bytes > 0); memset(b, 0xa5, sizeof(b));
CHECK(invite_serialize_addrs(&out, b, (size_t)bytes - 1) < 0);
for (unsigned i = 0; i < sizeof(b); i++) CHECK(b[i] == 0xa5);
CHECK(invite_serialize_addrs(&out, b, (size_t)bytes) == bytes && !memcmp(a, b, (size_t)bytes));
/* Отказ при каждом усечении, кроме границ полных самостоятельных блоков. */
for (size_t cut = 0; cut < length; cut++) {
struct InviteData partial;
if (invite_link_decode(link, cut, &partial, NULL, 0) == 0) {
CHECK(partial.addrCount > 0 && partial.addrCount < count); continue;
}
rejected(link, cut);
}
cases++;
}
for (unsigned i = 0; i < 256; i++) {
char link[INVITE_LINK_MAX]; CHECK(invite_link_encode(&input, NULL, link, sizeof(link)) > 0);
if (!((i >= 'A' && i <= 'Z') || (i >= 'a' && i <= 'z') || (i >= '0' && i <= '9') || i == '+' || i == '/')) {
link[12] = (char)i; rejected(link, strlen(link + 13) + 13);
}
}
char link[INVITE_LINK_MAX]; CHECK(invite_link_encode(&input, NULL, link, sizeof(link)) > 0);
uint8_t raw[1200]; int raw_len = EVP_DecodeBlock(raw, (const unsigned char*)link + 7, (int)strlen(link) - 7);
raw_len -= link[strlen(link) - 1] == '='; raw_len -= link[strlen(link) - 2] == '=';
uint8_t saved[1200]; memcpy(saved, raw, (size_t)raw_len);
raw[0] = 2; rejected_raw(raw, (size_t)raw_len); memcpy(raw, saved, (size_t)raw_len);
raw[18] = 2; rejected_raw(raw, (size_t)raw_len); memcpy(raw, saved, (size_t)raw_len);
raw[62] = 64; rejected_raw(raw, (size_t)raw_len); memcpy(raw, saved, (size_t)raw_len);
raw[63] = 0; rejected_raw(raw, (size_t)raw_len); memcpy(raw, saved, (size_t)raw_len);
raw[126] |= 0x80; rejected_raw(raw, (size_t)raw_len); memcpy(raw, saved, (size_t)raw_len);
size_t second = 126 + 33 + 4 * 20; raw[second + 1] ^= 1;
rejected_raw(raw, (size_t)raw_len); memcpy(raw, saved, (size_t)raw_len);
rejected_raw(raw, 19); /* обязательный узел отсутствует */
memcpy(raw, saved, (size_t)raw_len); raw[1] = 128; rejected_raw(raw, (size_t)raw_len);
struct InviteData bad = input; bad.addrCount = 0; CHECK(invite_link_validate(&bad) < 0);
bad = input; bad.channelId = UINT64_MAX; CHECK(invite_link_validate(&bad) < 0);
bad = input; bad.addrCount = 33; CHECK(invite_link_validate(&bad) < 0);
bad = input; memset(bad.pubkey, 0, 32); CHECK(invite_link_validate(&bad) < 0);
bad = input; bad.addrs[0].family = 5; CHECK(invite_link_validate(&bad) < 0);
bad = input; bad.addrs[0].port = 0; CHECK(invite_link_validate(&bad) < 0);
bad = input; bad.addrs[0].proto = 3; CHECK(invite_link_validate(&bad) < 0);
bad = input; bad.reality.has = 0; CHECK(invite_link_validate(&bad) < 0);
rejected("utun://", 7); rejected(NULL, 1);
printf("invite codec: %u contract cases PASS\n", cases); return 0;
}