/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include <netinet/in.h>
#include <stdbool.h>
#include <string.h>
#include <sys/un.h>

#include "journald-server.h"
#include "log.h"
#include "path-util.h"
#include "socket-util.h"
#include "sparse-endian.h"
#include "tests.h"

#define _COMPRESS_PARSE_CHECK(str, enab, thresh, varname)               \
        do {                                                            \
                JournalCompressOptions varname = {true, 111};           \
                config_parse_compress("", "", 0, "", 0, "", 0, str,     \
                                      &varname, NULL);                  \
                assert_se((enab) == varname.enabled);                   \
                if (varname.enabled)                                    \
                        assert_se((thresh) == varname.threshold_bytes); \
        } while (0)

#define COMPRESS_PARSE_CHECK(str, enabled, threshold)                   \
        _COMPRESS_PARSE_CHECK(str, enabled, threshold, conf##__COUNTER__)

TEST(config_compress) {
        COMPRESS_PARSE_CHECK("yes", true, 111);
        COMPRESS_PARSE_CHECK("no", false, 111);
        COMPRESS_PARSE_CHECK("y", true, 111);
        COMPRESS_PARSE_CHECK("n", false, 111);
        COMPRESS_PARSE_CHECK("true", true, 111);
        COMPRESS_PARSE_CHECK("false", false, 111);
        COMPRESS_PARSE_CHECK("t", true, 111);
        COMPRESS_PARSE_CHECK("f", false, 111);
        COMPRESS_PARSE_CHECK("on", true, 111);
        COMPRESS_PARSE_CHECK("off", false, 111);

        /* Weird size/bool overlapping case. We preserve backward compatibility instead of assuming these are byte
         * counts. */
        COMPRESS_PARSE_CHECK("1", true, 111);
        COMPRESS_PARSE_CHECK("0", false, 111);

        /* IEC sizing */
        COMPRESS_PARSE_CHECK("1B", true, 1);
        COMPRESS_PARSE_CHECK("1K", true, 1024);
        COMPRESS_PARSE_CHECK("1M", true, 1024 * 1024);
        COMPRESS_PARSE_CHECK("1G", true, 1024 * 1024 * 1024);

        /* Invalid Case */
        COMPRESS_PARSE_CHECK("-1", true, 111);
        COMPRESS_PARSE_CHECK("blah blah", true, 111);
        COMPRESS_PARSE_CHECK("", true, UINT64_MAX);
}

#define _FORWARD_TO_SOCKET_PARSE_CHECK_FAILS(str, addr, varname)             \
        do {                                                                 \
                SocketAddress varname = {};                                  \
                config_parse_forward_to_socket("", "", 0, "", 0, "", 0, str, \
                                               &varname, NULL);              \
                assert_se(socket_address_verify(&varname, true) < 0);        \
        } while (0)

#define FORWARD_TO_SOCKET_PARSE_CHECK_FAILS(str) \
        _FORWARD_TO_SOCKET_PARSE_CHECK_FAILS(str, addr, conf##__COUNTER__)

#define _FORWARD_TO_SOCKET_PARSE_CHECK(str, addr, varname)                   \
        do {                                                                 \
                SocketAddress varname = {};                                  \
                config_parse_forward_to_socket("", "", 0, "", 0, "", 0, str, \
                                               &varname, NULL);              \
                buf = mfree(buf);                                            \
                buf2 = mfree(buf2);                                          \
                socket_address_print(&varname, &buf);                        \
                socket_address_print(&addr, &buf2);                          \
                log_info("\"%s\" parsed as \"%s\", should be \"%s\"", str, buf, buf2); \
                log_info("socket_address_verify(&addr, false) = %d", socket_address_verify(&addr, false)); \
                log_info("socket_address_verify(&varname, false) = %d", socket_address_verify(&varname, false)); \
                log_info("socket_address_family(&addr) = %d", socket_address_family(&addr)); \
                log_info("socket_address_family(&varname) = %d", socket_address_family(&varname)); \
                log_info("addr.size = %u", addr.size);                       \
                log_info("varname.size = %u", varname.size);                 \
                assert_se(socket_address_equal(&varname, &addr));            \
        } while (0)

#define FORWARD_TO_SOCKET_PARSE_CHECK(str, addr)                     \
        _FORWARD_TO_SOCKET_PARSE_CHECK(str, addr, conf##__COUNTER__)

TEST(config_forward_to_socket) {
        SocketAddress addr;
        _cleanup_free_ char *buf = NULL, *buf2 = NULL;

        /* Valid AF_UNIX */
        addr = (SocketAddress) {
                .sockaddr.un = (struct sockaddr_un) {
                        .sun_family = AF_UNIX,
                        .sun_path = "/run/host/journal/socket",
                },
                .size = offsetof(struct sockaddr_un, sun_path) + strlen("/run/host/journal/socket") + 1,
        };
        FORWARD_TO_SOCKET_PARSE_CHECK("/run/host/journal/socket", addr);

        addr.size -= 1;
        memcpy(addr.sockaddr.un.sun_path, "\0run/host/journal/socket", sizeof("\0run/host/journal/socket"));
        FORWARD_TO_SOCKET_PARSE_CHECK("@run/host/journal/socket", addr);

        /* Valid AF_INET */
        addr = (SocketAddress) {
                .sockaddr.in = (struct sockaddr_in) {
                        .sin_family = AF_INET,
                        .sin_addr = { htobe32(0xC0A80001) },
                        .sin_port = htobe16(1234),
                },
                .size = sizeof(struct sockaddr_in),
        };
        FORWARD_TO_SOCKET_PARSE_CHECK("192.168.0.1:1234", addr);

        /* Valid AF_INET6 */
        addr = (SocketAddress) {
                .sockaddr.in6 = (struct sockaddr_in6) {
                        .sin6_family = AF_INET6,
                        .sin6_addr = (struct in6_addr) {
                                .s6_addr16 = {
                                        htobe16(0x2001),
                                        htobe16(0xdb8),
                                        htobe16(0x4006),
                                        htobe16(0x812),
                                        0, 0, 0,
                                        htobe16(0x200e)
                                }
                        },
                        .sin6_port = htobe16(8080),
                },
                .size = sizeof(struct sockaddr_in6),
        };
        FORWARD_TO_SOCKET_PARSE_CHECK("[2001:db8:4006:812::200e]:8080", addr);

        /* Valid AF_VSOCK */
        addr = (SocketAddress) {
                .sockaddr.vm = (struct sockaddr_vm) {
                        .svm_family = AF_VSOCK,
                        .svm_cid = 123456,
                        .svm_port = 654321,
                },
                .size = sizeof(struct sockaddr_vm),
        };
        FORWARD_TO_SOCKET_PARSE_CHECK("vsock:123456:654321", addr);

        /* Invalid IPv4 */
        FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("256.123.45.12:1235");
        FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("252.123.45.12:123500");
        FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("252.123.45.12:0");
        FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("252.123.45.12:-1");
        FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("-1.123.45.12:22");

        /* Invalid IPv6 */
        FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("[2001:db8:4006:812::200e]:80800");
        FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("[1ffff:db8:4006:812::200e]:8080");
        FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("[-1:db8:4006:812::200e]:8080");
        FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("[2001:db8:4006:812::200e]:-1");

        /* Invalid UNIX */
        FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("a/b/c");

        /* Invalid VSock */
        FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("vsock:4294967296:1234");
        FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("vsock:1234:4294967296");
        FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("vsock:abcd:1234");
        FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("vsock:1234:abcd");
        FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("vsock:1234");

        /* Invalid Case */
        FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("");
        FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("ahh yes sockets, mmh");
}

DEFINE_TEST_MAIN(LOG_INFO);
