10 changed files with 487 additions and 24 deletions
@ -0,0 +1,192 @@
|
||||
// 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; |
||||
} |
||||
Loading…
Reference in new issue