Ground-Zerro / HydraRoute Public
Code Issues Pull requests Actions Releases View on GitHub ↗
5.1 KB c
#include "../include/watchlist.h"
#include "../include/watchlist_api.h"
#include "../include/util.h"
#include <assert.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#define SOCK_PATH "build/check_wlapi.sock"
#define BULK_KEYS 20000

static char big[1 << 21];

static int dial(void) {
    struct sockaddr_un addr = { .sun_family = AF_UNIX };
    snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", SOCK_PATH);
    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
    assert(fd >= 0);
    assert(connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == 0);
    return fd;
}

static void say(int fd, const char *s) {
    assert(send(fd, s, strlen(s), MSG_NOSIGNAL) == (ssize_t)strlen(s));
}

static size_t drain(int fd, char *buf, size_t cap) {
    size_t len = 0;
    ssize_t n;
    while ((n = recv(fd, buf + len, cap - 1 - len, 0)) > 0) len += (size_t)n;
    buf[len] = '\0';
    close(fd);
    return len;
}

static void read_line(int fd, char *buf, size_t cap) {
    size_t len = 0;
    struct pollfd p = { .fd = fd, .events = POLLIN };
    while (len == 0 || buf[len - 1] != '\n') {
        assert(poll(&p, 1, 1000) == 1);
        ssize_t n = recv(fd, buf + len, cap - 1 - len, 0);
        assert(n > 0);
        len += (size_t)n;
    }
    buf[len] = '\0';
}

static void expect_reply(int fd, const char *req, const char *reply) {
    char buf[512];
    say(fd, req);
    read_line(fd, buf, sizeof(buf));
    assert(strcmp(buf, reply) == 0);
}

static domain_hashtable_t *load(void) {
    domain_hashtable_t *ht = ht_create();
    ht_insert(ht, "ru", 2, "RU");
    ht_insert(ht, "google.ru", 9, "HydraRoute");
    ht_insert(ht, "telegram.org", 12, "HydraRoute");
    for (int i = 0; i < BULK_KEYS; i++) {
        char key[32];
        int n = snprintf(key, sizeof(key), "k%05d.example", i);
        ht_insert(ht, key, (size_t)n, "HydraRoute");
    }
    const char order[][64] = { "RU" };
    char names[MAX_TARGETS][64];
    int count = get_unique_names(ht, names, MAX_TARGETS);
    sort_policies(names, count, order, 1);
    ht_rank_targets(ht, (const char (*)[64])names, count);
    return ht;
}

static void check_pipeline(void) {
    int fd = dial();
    char name[300];
    memset(name, 'a', 254);
    name[254] = '\0';
    say(fd, "MATCH Mail.Google.RU.\nMATCH example.org\nMATCH telegram.org\nMATCH ru\r\n"
            "MATCH \nBOGUS\nMATCH ");
    say(fd, name);
    say(fd, "\n");
    shutdown(fd, SHUT_WR);
    char buf[1024];
    drain(fd, buf, sizeof(buf));
    assert(strcmp(buf, "OK RU 0 ru\nNO\nOK HydraRoute 1 telegram.org\nOK RU 0 ru\n"
                       "ERR name\nERR command\nERR name\n") == 0);
}

static void check_dump_does_not_block_match(void) {
    int dumper = dial();
    say(dumper, "DUMP\n");
    usleep(100000);

    int fd = dial();
    expect_reply(fd, "MATCH k00042.example\n", "OK HydraRoute 1 k00042.example\n");
    close(fd);

    shutdown(dumper, SHUT_WR);
    size_t len = drain(dumper, big, sizeof(big));
    assert(len > 0 && big[len - 1] == '\n');
    assert(strncmp(big, "T 0 RU\nT 1 HydraRoute\nK ", 24) == 0);
    assert(strstr(big, "\nK ru 0\n") && strstr(big, "\nK google.ru 1\n"));

    int keys = 0;
    for (char *line = big; *line; line = strchr(line, '\n') + 1) {
        if (line[0] == 'K') keys++;
    }
    assert(keys == BULK_KEYS + 3);
    char end[32];
    snprintf(end, sizeof(end), "\nEND %d\n", keys);
    assert(strcmp(big + len - strlen(end), end) == 0);
}

static void check_client_limit(void) {
    int fds[4];
    for (int i = 0; i < 4; i++) {
        fds[i] = dial();
        expect_reply(fds[i], "MATCH ru\n", "OK RU 0 ru\n");
    }
    char buf[64];
    drain(dial(), buf, sizeof(buf));
    assert(strcmp(buf, "ERR busy\n") == 0);
    for (int i = 0; i < 4; i++) close(fds[i]);

    for (int attempt = 0;; attempt++) {
        assert(attempt < 100);
        int fd = dial();
        say(fd, "MATCH ru\n");
        shutdown(fd, SHUT_WR);
        drain(fd, buf, sizeof(buf));
        if (strcmp(buf, "OK RU 0 ru\n") == 0) break;
        usleep(10000);
    }
}

static void check_cli_exit_codes(void) {
    fflush(stdout);
    int saved_out = dup(STDOUT_FILENO), saved_err = dup(STDERR_FILENO);
    int null_fd = open("/dev/null", O_WRONLY);
    assert(null_fd >= 0);
    dup2(null_fd, STDOUT_FILENO);
    dup2(null_fd, STDERR_FILENO);
    close(null_fd);
    assert(wlapi_request(SOCK_PATH, "MATCH", "www.Google.RU") == 0);
    assert(wlapi_request(SOCK_PATH, "MATCH", "example.org") == 1);
    assert(wlapi_request(SOCK_PATH, "BOGUS", NULL) == 2);
    assert(wlapi_request(SOCK_PATH, "DUMP", NULL) == 0);
    assert(wlapi_request("build/absent.sock", "DUMP", NULL) == 2);
    fflush(stdout);
    dup2(saved_out, STDOUT_FILENO);
    dup2(saved_err, STDERR_FILENO);
    close(saved_out);
    close(saved_err);
}

int main(void) {
    domain_hashtable_t *ht = load();
    assert(wlapi_start(SOCK_PATH, ht) == 0);

    check_pipeline();
    check_dump_does_not_block_match();
    check_client_limit();
    check_cli_exit_codes();

    wlapi_stop();
    assert(access(SOCK_PATH, F_OK) != 0);
    ht_destroy(ht);
    puts("check_wlapi: OK");
    return 0;
}