#include "../test.h"

#define TEST(name) TEST_CASE("deflate-selection: " name, "[deflate-selection]")

ConnectRequestSP creq () {
    return ConnectRequest::Builder().uri("ws://crazypanda.ru").ws_key("dGhlIHNhbXBsZSBub25jZQ==").build();
}

TEST("default config") {
    ServerParser p;
    Parser::Config cfg;
    cfg.deflate.reset();
    p.configure(cfg);
    CHECK(!p.deflate_config());
    cfg.deflate = Parser::DeflateConfig();
    cfg.deflate->client_no_context_takeover = 1;
    cfg.deflate->mem_level = 5;
    p.configure(cfg);
    CHECK(p.deflate_config());
    CHECK(p.deflate_config()->compression_level == -1); // default settings
    CHECK(p.deflate_config()->mem_level == 5); // supplied settings
}

TEST("permessage-deflate extension in request") {
    ClientParser client;
    SECTION("no deflate enabled => no extension") {
        client.no_deflate();
        auto str = client.connect_request(creq());
        CHECK_THAT(str, !ContainsSubstring("permessage-deflate"));
    }

    SECTION("deflate enabled => extension is present") {
        auto str = client.connect_request(creq());
        CHECK_THAT(str, ContainsSubstring("permessage-deflate"));
    }

    SECTION("deflate enabled(custom params) => extension is present") {
        Parser::Config cfg;
        cfg.deflate->client_no_context_takeover = 1;
        cfg.deflate->server_no_context_takeover = 1;
        cfg.deflate->server_max_window_bits     = 13;
        cfg.deflate->client_max_window_bits     = 14;
        client.configure(cfg);
        auto str = client.connect_request(creq());
        CHECK_THAT(str, ContainsSubstring("permessage-deflate"));
        CHECK_THAT(str, ContainsSubstring("client_no_context_takeover"));
        CHECK_THAT(str, ContainsSubstring("server_no_context_takeover"));
        CHECK_THAT(str, ContainsSubstring("server_max_window_bits=13"));
        CHECK_THAT(str, ContainsSubstring("client_max_window_bits=14"));
    }
}

TEST("permessage-deflate extension in server reply") {
    ClientParser client;
    ServerParser server;

    SECTION("no deflate enabled => no extension") {
        client.no_deflate();
        auto req = server.accept(client.connect_request(creq()));
        auto res_str = server.accept_response();
        CHECK_THAT(res_str, !ContainsSubstring("permessage-deflate"));
    }

    SECTION("client deflate: on, server deflate: off => extension: off") {
        server.no_deflate();
        auto req_str = client.connect_request(creq());
        auto req = server.accept(req_str);
        auto res_str = server.accept_response();
        CHECK_THAT(req_str, ContainsSubstring("permessage-deflate"));
        CHECK_THAT(res_str, !ContainsSubstring("permessage-deflate"));
        CHECK_FALSE(client.is_deflate_active());
        CHECK_FALSE(server.is_deflate_active());
    }

    SECTION("client deflate: on, server deflate: on => extension: on") {
        auto req_str = client.connect_request(creq());
        auto req = server.accept(req_str);
        auto res_str = server.accept_response();
        client.connect(res_str);
        CHECK_THAT(req_str, ContainsSubstring("permessage-deflate"));
        CHECK_THAT(res_str, ContainsSubstring("permessage-deflate"));
        CHECK(server.is_deflate_active());
        CHECK(client.is_deflate_active());
        CHECK(client.established());
    }

    SECTION("client deflate: on (empty client_max_window_bits), server deflate: on => extension: on") {
        auto req_str = client.connect_request(creq());
        regex_replace(req_str, "(client_max_window_bits)=15", "$1");
        auto req = server.accept(req_str);
        auto res_str = server.accept_response();
        client.connect(res_str);
        CHECK_THAT(req_str, ContainsSubstring("permessage-deflate"));
        CHECK_THAT(res_str, ContainsSubstring("permessage-deflate"));
        CHECK_THAT(res_str, ContainsSubstring("client_max_window_bits=15"));
        CHECK(server.is_deflate_active());
        CHECK(client.is_deflate_active());
        CHECK(client.established());
    }

    SECTION("client deflate: on (wrong params), server deflate: on => extension: off") {
        ConnectRequestSP conn_req;

        SECTION("too small window") {
            Parser::Config cfg;
            cfg.deflate->client_max_window_bits = 3;
            client.configure(cfg);
            conn_req = creq();
        }

        SECTION("too big window") {
            Parser::Config cfg;
            cfg.deflate->client_max_window_bits = 30;
            client.configure(cfg);
            conn_req = creq();
        }

        SECTION("unknown parameter") {
            conn_req = ConnectRequest::Builder()
                .uri("ws://crazypanda.ru")
                .ws_key("dGhlIHNhbXBsZSBub25jZQ==")
                .ws_extensions({{"permessage-deflate", {{"hacker", "huyaker"}}}})
                .build();
        }

        SECTION("incorrect parameter") {
            conn_req = ConnectRequest::Builder()
                .uri("ws://crazypanda.ru")
                .ws_key("dGhlIHNhbXBsZSBub25jZQ==")
                .ws_extensions({{"permessage-deflate", {{"client_max_window_bits", "kak tebe takoe, Ilon Mask?"}}}})
                .build();
        }

        auto req_str = client.connect_request(conn_req);
        auto req = server.accept(req_str);
        auto res_str = server.accept_response();
        client.connect(res_str);
        CHECK_THAT(req_str, ContainsSubstring("permessage-deflate"));
        CHECK_THAT(res_str, !ContainsSubstring("permessage-deflate"));
        CHECK_FALSE(client.is_deflate_active());
        CHECK_FALSE(server.is_deflate_active());
        CHECK(client.established());
    }

    SECTION("client deflate: on, server deflate: on (wrong params) => no connection") {
        client.connect_request(creq());
        string res_str;

        SECTION("offected other windows size") {
            res_str = "HTTP/1.1 101 Switching Protocols\r\n"
                      "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
                      "Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=13; client_max_window_bits=15\r\n"
                      "Connection: Upgrade\r\n"
                      "Server: Panda-WebSocket\r\n"
                      "Upgrade: websocket\r\n"
                      "\r\n";
        }
        SECTION("offected garbage windows size") {
            res_str = "HTTP/1.1 101 Switching Protocols\r\n"
                      "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
                      "Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=zzzz; client_max_window_bits=15\r\n"
                      "Connection: Upgrade\r\n"
                      "Server: Panda-WebSocket\r\n"
                      "Upgrade: websocket\r\n"
                      "\r\n";
        }
        SECTION("offected garbage extension parameter") {
            res_str = "HTTP/1.1 101 Switching Protocols\r\n"
                      "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
                      "Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15; hello=world\r\n"
                      "Connection: Upgrade\r\n"
                      "Server: Panda-WebSocket\r\n"
                      "Upgrade: websocket\r\n"
                      "\r\n";
        }

        auto conn_res = client.connect(res_str);
        CHECK_FALSE(client.established());
        CHECK_FALSE(client.is_deflate_active());
        CHECK(conn_res->error() == ErrorCode(errc::deflate_negotiation_failed));
    }
}

TEST("configuration getters") {
    ClientParser client;

    Parser::Config cfg;
    cfg.max_frame_size     = 7;
    cfg.max_message_size   = 8;
    cfg.max_handshake_size = 9;
    cfg.deflate->client_no_context_takeover = 1;
    cfg.deflate->server_no_context_takeover = 1;
    cfg.deflate->server_max_window_bits     = 13;
    cfg.deflate->client_max_window_bits     = 14;
    cfg.deflate->mem_level                  = 3;
    cfg.deflate->compression_level          = 2;
    cfg.deflate->strategy                   = 1;
    cfg.deflate->compression_threshold      = 1000;
    client.configure(cfg);

    CHECK(client.max_frame_size() == 7);
    CHECK(client.max_message_size() == 8);
    CHECK(client.max_handshake_size() == 9);
    CHECK(client.deflate_config()->client_no_context_takeover == 1);
    CHECK(client.deflate_config()->server_no_context_takeover == 1);
    CHECK(client.deflate_config()->server_max_window_bits == 13);
    CHECK(client.deflate_config()->client_max_window_bits == 14);
    CHECK(client.deflate_config()->mem_level == 3);
    CHECK(client.deflate_config()->compression_level == 2);
    CHECK(client.deflate_config()->strategy == 1);
    CHECK(client.deflate_config()->compression_threshold == 1000);

    client.no_deflate();
    CHECK_FALSE(client.deflate_config());
}

TEST("server ignores permessage-deflate option with window_bits=8, and uses no compression") {
    Parser::Config cfg;
    cfg.deflate->client_max_window_bits = 9;

    ClientParser client(cfg);
    ServerParser server;

    auto req_str = client.connect_request(creq());

    SECTION("8-bit request is rejected") {
        regex_replace(req_str, "window_bits=9", "window_bits=8");
        auto req = server.accept(req_str);
        auto res_str = server.accept_response();
        client.connect(res_str);
        CHECK_THAT(req_str, ContainsSubstring("permessage-deflate"));
        CHECK_THAT(res_str, !ContainsSubstring("permessage-deflate"));
        CHECK_FALSE(server.is_deflate_active());
        CHECK_FALSE(client.is_deflate_active());
        CHECK(client.established());
    }

    SECTION("8-bit response is rejected") {
        auto req = server.accept(req_str);
        auto res_str = server.accept_response();
        CHECK_THAT(req_str, ContainsSubstring("permessage-deflate"));
        CHECK_THAT(res_str, ContainsSubstring("permessage-deflate"));
        CHECK(server.is_deflate_active());

        regex_replace(res_str, "window_bits=9", "window_bits=8");
        auto conn_res = client.connect(res_str);
        CHECK_FALSE(client.established());
        CHECK_FALSE(client.is_deflate_active());
        CHECK(conn_res->error() == ErrorCode(errc::deflate_negotiation_failed));
    }

    SECTION("9-bit window is OK") {
        server.configure(cfg);
        auto req = server.accept(req_str);
        auto res_str = server.accept_response();
        client.connect(res_str);
        CHECK(client.established());
        CHECK(client.is_deflate_active());
        CHECK(server.is_deflate_active());
        CHECK(client.effective_deflate_config()->client_max_window_bits == 9);
        CHECK(client.effective_deflate_config()->server_max_window_bits == 15);
        CHECK(server.effective_deflate_config()->client_max_window_bits == 9);
        CHECK(server.effective_deflate_config()->server_max_window_bits == 15);
    }
}

TEST("SRV-1229 bufix") {
    string req_str = "GET / HTTP/1.1\r\n"
                     "User-Agent: Panda-WebSocket\r\n"
                     "Upgrade: websocket\r\n"
                     "Connection: Upgrade\r\n"
                     "Host: crazypanda.ru\r\n"
                     "Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=13; client_max_window_bits\r\n"
                     "Sec-WebSocket-Version: 13\r\n"
                     "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
                     "\r\n";
    ServerParser server;
    auto req = server.accept(req_str);
    auto res_str = server.accept_response();
    CHECK_THAT(res_str, ContainsSubstring("permessage-deflate"));
    CHECK_THAT(res_str, ContainsSubstring("client_max_window_bits=15"));
}