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.
192 lines
8.4 KiB
192 lines
8.4 KiB
// test_reality_config.c — автогенерация reality private_key и short_ids в конфиге |
|
// |
|
// Проверяет config_ensure_keys_and_node_id(): |
|
// 1. [reality] enabled=1 без private_key/short_ids → генерируются (privkey + 4 id). |
|
// 2. Пустая строка short_ids= заменяется на 4 id (без пустых строк и дублей). |
|
// 3. [reality] enabled=0 → ничего не генерируется. |
|
// 4. Без секции [reality] → ничего не генерируется. |
|
// 5. Идемпотентность: повторный вызов не добавляет дублей. |
|
#include "config_parser.h" |
|
#include "config_updater.h" |
|
#include "../lib/debug_config.h" |
|
#include "test_utils.h" |
|
#include <stdio.h> |
|
#include <string.h> |
|
|
|
static int test_failed = 0; |
|
|
|
#define CHECK(expr, msg) do { \ |
|
if (!(expr)) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "FAIL: %s", msg); test_failed = 1; } \ |
|
else { DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "PASS: %s", msg); } \ |
|
} while (0) |
|
|
|
static int write_file(const char *path, const char *content) { |
|
FILE *f = fopen(path, "w"); |
|
if (!f) return -1; |
|
fputs(content, f); |
|
fclose(f); |
|
return 0; |
|
} |
|
|
|
static int read_file(const char *path, char *buf, size_t cap) { |
|
FILE *f = fopen(path, "rb"); |
|
if (!f) return -1; |
|
size_t n = fread(buf, 1, cap - 1, f); |
|
buf[n] = '\0'; |
|
fclose(f); |
|
return (int)n; |
|
} |
|
|
|
// Возвращает: 0 — в секции [section] нет пустых строк, option встречается ровно want раз. |
|
// 1 — секции нет, 2 — есть пустая строка, 3 — неверное число вхождений option. |
|
static int check_section(const char *content, const char *section, const char *option, int want) { |
|
char hdr[64]; snprintf(hdr, sizeof(hdr), "[%s]", section); |
|
const char *sec = strstr(content, hdr); |
|
if (!sec) return 1; |
|
sec += strlen(hdr); |
|
while (*sec == '\r' || *sec == '\n') sec++; |
|
const char *end = strchr(sec, '['); |
|
if (!end) end = content + strlen(content); |
|
|
|
int found = 0, empty = 0; |
|
const char *line = sec; |
|
while (line < end) { |
|
const char *nl = strchr(line, '\n'); |
|
const char *line_end = (nl && nl < end) ? nl : end; |
|
size_t len = (size_t)(line_end - line); |
|
while (len && (line[0] == ' ' || line[0] == '\t' || line[0] == '\r')) { line++; len--; } |
|
if (len == 0) empty++; |
|
if (option && strncmp(line, option, strlen(option)) == 0 && line[strlen(option)] == '=') found++; |
|
line = nl ? nl + 1 : end; |
|
} |
|
if (empty) return 2; |
|
if (found != want) return 3; |
|
return 0; |
|
} |
|
|
|
int main(void) { |
|
debug_config_init(); |
|
debug_set_level(DEBUG_LEVEL_INFO); |
|
|
|
char tdir[] = "/tmp/utun_reality_cfg_XXXXXX"; |
|
if (test_mkdtemp(tdir) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "mkdtemp failed"); return 1; } |
|
char path[512]; |
|
snprintf(path, sizeof(path), "%s/r.conf", tdir); |
|
|
|
// ── Сценарий 1: enabled=1 без ключей → генерация ── |
|
{ |
|
const char *cfg = |
|
"[global]\nmy_node_name=test\n" |
|
"[reality]\n" |
|
"enabled=1\n" |
|
"server_name=www.microsoft.com\n" |
|
"dest=www.microsoft.com:443\n"; |
|
CHECK(write_file(path, cfg) == 0, "write config (scenario 1)"); |
|
CHECK(config_ensure_keys_and_node_id(path) == 0, "ensure keys (scenario 1)"); |
|
struct utun_config *uc = parse_config(path); |
|
CHECK(uc != NULL, "parse after generate (scenario 1)"); |
|
if (uc) { |
|
CHECK(uc->global.reality.enabled == 1, "enabled=1"); |
|
CHECK(uc->global.reality.has_private_key == 1, "private_key generated"); |
|
CHECK(uc->global.reality.short_id_count == 4, "4 short_ids generated"); |
|
free_config(uc); |
|
} |
|
char content[4096]; |
|
CHECK(read_file(path, content, sizeof(content)) > 0, "read file (scenario 1)"); |
|
CHECK(check_section(content, "reality", NULL, 0) == 0, "no empty lines in [reality]"); |
|
CHECK(check_section(content, "reality", "private_key", 1) == 0, "exactly one private_key"); |
|
CHECK(check_section(content, "reality", "short_ids", 1) == 0, "exactly one short_ids"); |
|
} |
|
|
|
// ── Сценарий 2: пустая строка short_ids= заменяется ── |
|
{ |
|
const char *cfg = |
|
"[global]\nmy_node_name=test\n" |
|
"[reality]\n" |
|
"enabled=1\n" |
|
"short_ids=\n" |
|
"server_name=www.microsoft.com\n"; |
|
CHECK(write_file(path, cfg) == 0, "write config (scenario 2)"); |
|
CHECK(config_ensure_keys_and_node_id(path) == 0, "ensure keys (scenario 2)"); |
|
struct utun_config *uc = parse_config(path); |
|
CHECK(uc != NULL, "parse after generate (scenario 2)"); |
|
if (uc) { |
|
CHECK(uc->global.reality.short_id_count == 4, "4 short_ids generated (replace)"); |
|
CHECK(uc->global.reality.has_private_key == 1, "private_key generated (scenario 2)"); |
|
free_config(uc); |
|
} |
|
char content[4096]; |
|
CHECK(read_file(path, content, sizeof(content)) > 0, "read file (scenario 2)"); |
|
CHECK(check_section(content, "reality", NULL, 0) == 0, "no empty lines (scenario 2)"); |
|
CHECK(check_section(content, "reality", "short_ids", 1) == 0, "short_ids replaced, no dup (scenario 2)"); |
|
} |
|
|
|
// ── Сценарий 3: enabled=0 → без генерации ── |
|
{ |
|
const char *cfg = |
|
"[global]\nmy_node_name=test\n" |
|
"[reality]\n" |
|
"enabled=0\n" |
|
"server_name=www.microsoft.com\n"; |
|
CHECK(write_file(path, cfg) == 0, "write config (scenario 3)"); |
|
CHECK(config_ensure_keys_and_node_id(path) == 0, "ensure keys (scenario 3)"); |
|
struct utun_config *uc = parse_config(path); |
|
CHECK(uc != NULL, "parse (scenario 3)"); |
|
if (uc) { |
|
CHECK(uc->global.reality.has_private_key == 0, "no private_key (disabled)"); |
|
CHECK(uc->global.reality.short_id_count == 0, "no short_ids (disabled)"); |
|
free_config(uc); |
|
} |
|
} |
|
|
|
// ── Сценарий 4: без секции [reality] → без генерации ── |
|
{ |
|
const char *cfg = "[global]\nmy_node_name=test\n"; |
|
CHECK(write_file(path, cfg) == 0, "write config (scenario 4)"); |
|
CHECK(config_ensure_keys_and_node_id(path) == 0, "ensure keys (scenario 4)"); |
|
struct utun_config *uc = parse_config(path); |
|
CHECK(uc != NULL, "parse (scenario 4)"); |
|
if (uc) { |
|
CHECK(uc->global.reality.enabled == 0, "reality disabled (no section)"); |
|
CHECK(uc->global.reality.has_private_key == 0, "no private_key (no section)"); |
|
CHECK(uc->global.reality.short_id_count == 0, "no short_ids (no section)"); |
|
free_config(uc); |
|
} |
|
} |
|
|
|
// ── Сценарий 5: идемпотентность ── |
|
{ |
|
const char *cfg = |
|
"[global]\nmy_node_name=test\n" |
|
"[reality]\n" |
|
"enabled=1\n" |
|
"server_name=www.microsoft.com\n"; |
|
CHECK(write_file(path, cfg) == 0, "write config (scenario 5)"); |
|
CHECK(config_ensure_keys_and_node_id(path) == 0, "ensure #1 (scenario 5)"); |
|
struct utun_config *uc1 = parse_config(path); |
|
CHECK(uc1 != NULL, "parse #1 (scenario 5)"); |
|
CHECK(config_ensure_keys_and_node_id(path) == 0, "ensure #2 (scenario 5)"); |
|
struct utun_config *uc2 = parse_config(path); |
|
CHECK(uc2 != NULL, "parse #2 (scenario 5)"); |
|
if (uc1 && uc2) { |
|
CHECK(uc1->global.reality.short_id_count == 4 && uc2->global.reality.short_id_count == 4, "ids stable"); |
|
CHECK(memcmp(uc1->global.reality.private_key, uc2->global.reality.private_key, REALITY_AUTH_KEY_SIZE) == 0, "privkey stable"); |
|
free_config(uc1); |
|
free_config(uc2); |
|
} |
|
char content[4096]; |
|
CHECK(read_file(path, content, sizeof(content)) > 0, "read file (scenario 5)"); |
|
CHECK(check_section(content, "reality", "private_key", 1) == 0, "no dup private_key (scenario 5)"); |
|
CHECK(check_section(content, "reality", "short_ids", 1) == 0, "no dup short_ids (scenario 5)"); |
|
} |
|
|
|
test_unlink(path); |
|
test_rmdir(tdir); |
|
|
|
if (test_failed) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "=== Reality Config Test: FAILED ==="); |
|
return 1; |
|
} |
|
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "=== Reality Config Test: PASSED ==="); |
|
return 0; |
|
}
|
|
|