/*
 * SPDX-FileCopyrightText: 2024-2026 Sébastien Helleu <flashcode@flashtux.org>
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 * This file is part of WeeChat, the extensible chat client.
 *
 * WeeChat is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * WeeChat is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WeeChat.  If not, see <http://www.gnu.org/licenses/>.
 */

/* Test websocket functions */

#include "CppUTest/TestHarness.h"

#include "tests.h"

extern "C"
{
#include <string.h>
#include <zlib.h>
#include "src/core/core-config-file.h"
#include "src/core/core-hashtable.h"
#include "src/plugins/relay/relay-config.h"
#include "src/plugins/relay/relay-http.h"
#include "src/plugins/relay/relay-websocket.h"

extern void relay_websocket_deflate_free_stream_deflate (struct t_relay_websocket_deflate *ws_deflate);
extern void relay_websocket_deflate_free_stream_inflate (struct t_relay_websocket_deflate *ws_deflate);
extern char *relay_websocket_deflate (const void *data, size_t size, z_stream *strm,
                                      size_t *size_compressed);
extern char *relay_websocket_inflate (const void *data, size_t size, z_stream *strm,
                                      size_t *size_decompressed);
}

TEST_GROUP(RelayWebsocket)
{
};

/*
 * Test functions:
 *   relay_websocket_deflate_alloc
 *   relay_websocket_deflate_init_stream_deflate
 *   relay_websocket_deflate_free_stream_deflate
 *   relay_websocket_deflate_init_stream_inflate
 *   relay_websocket_deflate_free_stream_inflate
 *   relay_websocket_deflate_free
 */

TEST(RelayWebsocket, DeflateAllocFree)
{
    struct t_relay_websocket_deflate *ws_deflate;

    ws_deflate = relay_websocket_deflate_alloc ();
    LONGS_EQUAL(0, ws_deflate->enabled);
    LONGS_EQUAL(0, ws_deflate->server_context_takeover);
    LONGS_EQUAL(0, ws_deflate->client_context_takeover);
    LONGS_EQUAL(0, ws_deflate->window_bits_deflate);
    LONGS_EQUAL(0, ws_deflate->window_bits_inflate);
    LONGS_EQUAL(0, ws_deflate->server_max_window_bits_recv);
    LONGS_EQUAL(0, ws_deflate->client_max_window_bits_recv);
    POINTERS_EQUAL(NULL, ws_deflate->strm_deflate);
    POINTERS_EQUAL(NULL, ws_deflate->strm_inflate);

    ws_deflate->window_bits_deflate = 15;
    ws_deflate->window_bits_inflate = 15;

    ws_deflate->strm_deflate = (z_stream *)calloc (1, sizeof (*ws_deflate->strm_deflate));
    CHECK(ws_deflate->strm_deflate);
    relay_websocket_deflate_init_stream_deflate (ws_deflate);
    relay_websocket_deflate_free_stream_deflate (ws_deflate);
    POINTERS_EQUAL(NULL, ws_deflate->strm_deflate);

    ws_deflate->strm_inflate = (z_stream *)calloc (1, sizeof (*ws_deflate->strm_inflate));
    CHECK(ws_deflate->strm_inflate);
    relay_websocket_deflate_init_stream_inflate (ws_deflate);
    relay_websocket_deflate_free_stream_inflate (ws_deflate);
    POINTERS_EQUAL(NULL, ws_deflate->strm_inflate);

    relay_websocket_deflate_free (ws_deflate);

    /* test free of NULL websocket deflate */
    relay_websocket_deflate_free (NULL);
}

/*
 * Test functions:
 *   relay_websocket_is_valid_http_get
 */

TEST(RelayWebsocket, IsValidHttpGet)
{
    LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_WEECHAT, NULL));
    LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_WEECHAT, ""));
    LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_WEECHAT, "xxx"));
    LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_WEECHAT, "GET /api\r\n"));
    LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_WEECHAT, "GET /api test\r\n"));
    LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_WEECHAT, "GET /api HTTP/1.1\r\n"));
    LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_API, "GET /weechat\r\n"));
    LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_API, "GET /weechat test\r\n"));
    LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_API, "GET /weechat HTTP/1.1\r\n"));

    LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_WEECHAT, "GET /weechat test\r\n"));
    LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_API, "GET /api test\r\n"));

    LONGS_EQUAL(1, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_WEECHAT, "GET /weechat\r\n"));
    LONGS_EQUAL(1, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_WEECHAT, "GET /weechat HTTP/1.1\r\n"));
    LONGS_EQUAL(1, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_API, "GET /api\r\n"));
    LONGS_EQUAL(1, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_API, "GET /api HTTP/1.1\r\n"));
}

/*
 * Test functions:
 *   relay_websocket_client_handshake_valid
 *   relay_websocket_build_handshake
 */

TEST(RelayWebsocket, ClientHandshakeValid)
{
    struct t_relay_http_request *request;
    char *str;

    LONGS_EQUAL(-1, relay_websocket_client_handshake_valid (NULL));

    request = relay_http_request_alloc ();
    CHECK(request);

    LONGS_EQUAL(-1, relay_websocket_client_handshake_valid (NULL));

    LONGS_EQUAL(-1, relay_websocket_client_handshake_valid (request));
    hashtable_set (request->headers, "upgrade", NULL);
    LONGS_EQUAL(-1, relay_websocket_client_handshake_valid (request));
    hashtable_set (request->headers, "upgrade", "test");
    LONGS_EQUAL(-1, relay_websocket_client_handshake_valid (request));
    hashtable_set (request->headers, "upgrade", "websocket");
    LONGS_EQUAL(-1, relay_websocket_client_handshake_valid (request));
    hashtable_set (request->headers, "sec-websocket-key", NULL);
    LONGS_EQUAL(-1, relay_websocket_client_handshake_valid (request));
    hashtable_set (request->headers, "sec-websocket-key", "CI1sXhf/u2o34BfWK7NeIg==");
    LONGS_EQUAL(0, relay_websocket_client_handshake_valid (request));

    STRCMP_EQUAL(NULL, relay_websocket_build_handshake (NULL));

    WEE_TEST_STR(
        "HTTP/1.1 101 Switching Protocols\r\n"
        "Upgrade: websocket\r\n"
        "Connection: Upgrade\r\n"
        "Sec-WebSocket-Accept: fhLJYtv//ugX2vQXpifQgByRZ5Y=\r\n"
        "\r\n",
        relay_websocket_build_handshake (request));

    config_file_option_set (relay_config_network_websocket_allowed_origins, "example.com", 1);
    LONGS_EQUAL(-2, relay_websocket_client_handshake_valid (request));
    hashtable_set (request->headers, "origin", NULL);
    LONGS_EQUAL(-2, relay_websocket_client_handshake_valid (request));
    hashtable_set (request->headers, "origin", "weechat.org");
    LONGS_EQUAL(-2, relay_websocket_client_handshake_valid (request));
    hashtable_set (request->headers, "origin", "example.com");
    LONGS_EQUAL(0, relay_websocket_client_handshake_valid (request));
    config_file_option_reset (relay_config_network_websocket_allowed_origins, 1);

    relay_websocket_deflate_reinit (request->ws_deflate);
    relay_websocket_parse_extensions ("permessage-deflate", request->ws_deflate, 1);
    LONGS_EQUAL(1, request->ws_deflate->enabled);
    LONGS_EQUAL(1, request->ws_deflate->server_context_takeover);
    LONGS_EQUAL(1, request->ws_deflate->client_context_takeover);
    LONGS_EQUAL(15, request->ws_deflate->window_bits_deflate);
    LONGS_EQUAL(15, request->ws_deflate->window_bits_inflate);
    LONGS_EQUAL(0, request->ws_deflate->server_max_window_bits_recv);
    LONGS_EQUAL(0, request->ws_deflate->client_max_window_bits_recv);
    WEE_TEST_STR(
        "HTTP/1.1 101 Switching Protocols\r\n"
        "Upgrade: websocket\r\n"
        "Connection: Upgrade\r\n"
        "Sec-WebSocket-Accept: fhLJYtv//ugX2vQXpifQgByRZ5Y=\r\n"
        "Sec-WebSocket-Extensions: permessage-deflate\r\n"
        "\r\n",
        relay_websocket_build_handshake (request));

    relay_websocket_deflate_reinit (request->ws_deflate);
    relay_websocket_parse_extensions (
        "permessage-deflate; client_max_window_bits",
        request->ws_deflate,
        1);
    LONGS_EQUAL(1, request->ws_deflate->enabled);
    LONGS_EQUAL(1, request->ws_deflate->server_context_takeover);
    LONGS_EQUAL(1, request->ws_deflate->client_context_takeover);
    LONGS_EQUAL(15, request->ws_deflate->window_bits_deflate);
    LONGS_EQUAL(15, request->ws_deflate->window_bits_inflate);
    LONGS_EQUAL(0, request->ws_deflate->server_max_window_bits_recv);
    LONGS_EQUAL(1, request->ws_deflate->client_max_window_bits_recv);
    WEE_TEST_STR(
        "HTTP/1.1 101 Switching Protocols\r\n"
        "Upgrade: websocket\r\n"
        "Connection: Upgrade\r\n"
        "Sec-WebSocket-Accept: fhLJYtv//ugX2vQXpifQgByRZ5Y=\r\n"
        "Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits=15\r\n"
        "\r\n",
        relay_websocket_build_handshake (request));

    relay_websocket_deflate_reinit (request->ws_deflate);
    relay_websocket_parse_extensions (
        "permessage-deflate; client_max_window_bits = 12; server_no_context_takeover",
        request->ws_deflate,
        1);
    LONGS_EQUAL(1, request->ws_deflate->enabled);
    LONGS_EQUAL(0, request->ws_deflate->server_context_takeover);
    LONGS_EQUAL(1, request->ws_deflate->client_context_takeover);
    LONGS_EQUAL(15, request->ws_deflate->window_bits_deflate);
    LONGS_EQUAL(12, request->ws_deflate->window_bits_inflate);
    LONGS_EQUAL(0, request->ws_deflate->server_max_window_bits_recv);
    LONGS_EQUAL(1, request->ws_deflate->client_max_window_bits_recv);
    WEE_TEST_STR(
        "HTTP/1.1 101 Switching Protocols\r\n"
        "Upgrade: websocket\r\n"
        "Connection: Upgrade\r\n"
        "Sec-WebSocket-Accept: fhLJYtv//ugX2vQXpifQgByRZ5Y=\r\n"
        "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_max_window_bits=12\r\n"
        "\r\n",
        relay_websocket_build_handshake (request));

    relay_websocket_deflate_reinit (request->ws_deflate);
    relay_websocket_parse_extensions (
        "permessage-deflate; client_max_window_bits = 12; server_max_window_bits=8; client_no_context_takeover; server_no_context_takeover",
        request->ws_deflate,
        1);
    LONGS_EQUAL(1, request->ws_deflate->enabled);
    LONGS_EQUAL(0, request->ws_deflate->server_context_takeover);
    LONGS_EQUAL(0, request->ws_deflate->client_context_takeover);
    LONGS_EQUAL(8, request->ws_deflate->window_bits_deflate);
    LONGS_EQUAL(12, request->ws_deflate->window_bits_inflate);
    LONGS_EQUAL(1, request->ws_deflate->server_max_window_bits_recv);
    LONGS_EQUAL(1, request->ws_deflate->client_max_window_bits_recv);
    WEE_TEST_STR(
        "HTTP/1.1 101 Switching Protocols\r\n"
        "Upgrade: websocket\r\n"
        "Connection: Upgrade\r\n"
        "Sec-WebSocket-Accept: fhLJYtv//ugX2vQXpifQgByRZ5Y=\r\n"
        "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover; server_max_window_bits=8; client_max_window_bits=12\r\n"
        "\r\n",
        relay_websocket_build_handshake (request));

    relay_websocket_deflate_reinit (request->ws_deflate);
    hashtable_set (request->headers, "sec-websocket-protocol",
                   WEBSOCKET_SUB_PROTOCOL_API_WEECHAT
                   ", base64url.bearer.authorization.weechat.cGxhaW46c2VjcmV0X3Bhc3N3b3Jk");
    WEE_TEST_STR(
        "HTTP/1.1 101 Switching Protocols\r\n"
        "Upgrade: websocket\r\n"
        "Connection: Upgrade\r\n"
        "Sec-WebSocket-Accept: fhLJYtv//ugX2vQXpifQgByRZ5Y=\r\n"
        "Sec-WebSocket-Protocol: " WEBSOCKET_SUB_PROTOCOL_API_WEECHAT "\r\n"
        "\r\n",
        relay_websocket_build_handshake (request));

    relay_http_request_free (request);
}

/*
 * Test functions:
 *   relay_websocket_parse_extensions
 */

TEST(RelayWebsocket, ParseExtensions)
{
    struct t_relay_websocket_deflate ws_deflate;

    relay_websocket_parse_extensions (NULL, NULL, 1);
    relay_websocket_parse_extensions ("test", NULL, 1);
    relay_websocket_parse_extensions (NULL, &ws_deflate, 1);

    memset (&ws_deflate, 0, sizeof (ws_deflate));
    relay_websocket_parse_extensions ("test", &ws_deflate, 1);
    LONGS_EQUAL(0, ws_deflate.enabled);

    memset (&ws_deflate, 0, sizeof (ws_deflate));
    relay_websocket_parse_extensions ("permessage-deflate", &ws_deflate, 0);
    LONGS_EQUAL(0, ws_deflate.enabled);
    LONGS_EQUAL(0, ws_deflate.server_context_takeover);
    LONGS_EQUAL(0, ws_deflate.client_context_takeover);
    LONGS_EQUAL(0, ws_deflate.window_bits_deflate);
    LONGS_EQUAL(0, ws_deflate.window_bits_inflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);

    memset (&ws_deflate, 0, sizeof (ws_deflate));
    relay_websocket_parse_extensions ("permessage-deflate", &ws_deflate, 1);
    LONGS_EQUAL(1, ws_deflate.enabled);
    LONGS_EQUAL(1, ws_deflate.server_context_takeover);
    LONGS_EQUAL(1, ws_deflate.client_context_takeover);
    LONGS_EQUAL(15, ws_deflate.window_bits_deflate);
    LONGS_EQUAL(15, ws_deflate.window_bits_inflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);

    memset (&ws_deflate, 0, sizeof (ws_deflate));
    relay_websocket_parse_extensions ("permessage-deflate; client_max_window_bits",
                                      &ws_deflate,
                                      1);
    LONGS_EQUAL(1, ws_deflate.enabled);
    LONGS_EQUAL(1, ws_deflate.server_context_takeover);
    LONGS_EQUAL(1, ws_deflate.client_context_takeover);
    LONGS_EQUAL(15, ws_deflate.window_bits_deflate);
    LONGS_EQUAL(15, ws_deflate.window_bits_inflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);

    /* client_max_window_bits < 8 (min value) */
    memset (&ws_deflate, 0, sizeof (ws_deflate));
    relay_websocket_parse_extensions (
        "permessage-deflate; client_max_window_bits=4",
        &ws_deflate,
        1);
    LONGS_EQUAL(1, ws_deflate.enabled);
    LONGS_EQUAL(1, ws_deflate.server_context_takeover);
    LONGS_EQUAL(1, ws_deflate.client_context_takeover);
    LONGS_EQUAL(15, ws_deflate.window_bits_deflate);
    LONGS_EQUAL(8, ws_deflate.window_bits_inflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);

    /* client_max_window_bits > 15 (max value) */
    memset (&ws_deflate, 0, sizeof (ws_deflate));
    relay_websocket_parse_extensions (
        "permessage-deflate; client_max_window_bits=30",
        &ws_deflate,
        1);
    LONGS_EQUAL(1, ws_deflate.enabled);
    LONGS_EQUAL(1, ws_deflate.server_context_takeover);
    LONGS_EQUAL(1, ws_deflate.client_context_takeover);
    LONGS_EQUAL(15, ws_deflate.window_bits_deflate);
    LONGS_EQUAL(15, ws_deflate.window_bits_inflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);

    /* invalid value for client_max_window_bits */
    memset (&ws_deflate, 0, sizeof (ws_deflate));
    relay_websocket_parse_extensions (
        "permessage-deflate; client_max_window_bits=test",
        &ws_deflate,
        1);
    LONGS_EQUAL(1, ws_deflate.enabled);
    LONGS_EQUAL(1, ws_deflate.server_context_takeover);
    LONGS_EQUAL(1, ws_deflate.client_context_takeover);
    LONGS_EQUAL(15, ws_deflate.window_bits_deflate);
    LONGS_EQUAL(15, ws_deflate.window_bits_inflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);

    memset (&ws_deflate, 0, sizeof (ws_deflate));
    relay_websocket_parse_extensions (
        "permessage-deflate; client_max_window_bits=9",
        &ws_deflate,
        1);
    LONGS_EQUAL(1, ws_deflate.enabled);
    LONGS_EQUAL(1, ws_deflate.server_context_takeover);
    LONGS_EQUAL(1, ws_deflate.client_context_takeover);
    LONGS_EQUAL(15, ws_deflate.window_bits_deflate);
    LONGS_EQUAL(9, ws_deflate.window_bits_inflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);

    memset (&ws_deflate, 0, sizeof (ws_deflate));
    relay_websocket_parse_extensions (
        "permessage-deflate; client_max_window_bits=9; server_max_window_bits=10",
        &ws_deflate,
        1);
    LONGS_EQUAL(1, ws_deflate.enabled);
    LONGS_EQUAL(1, ws_deflate.server_context_takeover);
    LONGS_EQUAL(1, ws_deflate.client_context_takeover);
    LONGS_EQUAL(10, ws_deflate.window_bits_deflate);
    LONGS_EQUAL(9, ws_deflate.window_bits_inflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);

    memset (&ws_deflate, 0, sizeof (ws_deflate));
    relay_websocket_parse_extensions (
        "permessage-deflate; client_max_window_bits=9; server_max_window_bits=10; "
        "server_no_context_takeover",
        &ws_deflate,
        1);
    LONGS_EQUAL(1, ws_deflate.enabled);
    LONGS_EQUAL(0, ws_deflate.server_context_takeover);
    LONGS_EQUAL(1, ws_deflate.client_context_takeover);
    LONGS_EQUAL(10, ws_deflate.window_bits_deflate);
    LONGS_EQUAL(9, ws_deflate.window_bits_inflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);

    memset (&ws_deflate, 0, sizeof (ws_deflate));
    relay_websocket_parse_extensions (
        "permessage-deflate; client_max_window_bits=9; server_max_window_bits=10; "
        "server_no_context_takeover; client_no_context_takeover",
        &ws_deflate,
        1);
    LONGS_EQUAL(1, ws_deflate.enabled);
    LONGS_EQUAL(0, ws_deflate.server_context_takeover);
    LONGS_EQUAL(0, ws_deflate.client_context_takeover);
    LONGS_EQUAL(10, ws_deflate.window_bits_deflate);
    LONGS_EQUAL(9, ws_deflate.window_bits_inflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
    POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);
}

/*
 * Test functions:
 *   relay_websocket_deflate
 *   relay_websocket_inflate
 */

TEST(RelayWebsocket, Inflate)
{
    struct t_relay_websocket_deflate *ws_deflate;
    char payload[256], *payload_comp, *payload_decomp;
    size_t size_comp, size_decomp;
    int i;

    ws_deflate = relay_websocket_deflate_alloc ();
    CHECK(ws_deflate);

    ws_deflate->window_bits_deflate = 15;
    ws_deflate->window_bits_inflate = 15;

    ws_deflate->strm_deflate = (z_stream *)calloc (1, sizeof (*ws_deflate->strm_deflate));
    CHECK(ws_deflate->strm_deflate);
    LONGS_EQUAL(1, relay_websocket_deflate_init_stream_deflate (ws_deflate));

    ws_deflate->strm_inflate = (z_stream *)calloc (1, sizeof (*ws_deflate->strm_inflate));
    CHECK(ws_deflate->strm_inflate);
    LONGS_EQUAL(1, relay_websocket_deflate_init_stream_inflate (ws_deflate));

    for (i = 0; i < (int)sizeof (payload); i++)
    {
        payload[i] = i % 64;
    }

    POINTERS_EQUAL(NULL, relay_websocket_deflate (NULL, 0, NULL, NULL));
    POINTERS_EQUAL(NULL, relay_websocket_deflate (payload, 0, NULL, NULL));
    POINTERS_EQUAL(NULL, relay_websocket_deflate (payload, sizeof (payload),
                                                  NULL, NULL));
    POINTERS_EQUAL(NULL, relay_websocket_deflate (payload, sizeof (payload),
                                                  ws_deflate->strm_deflate, NULL));

    payload_comp = (char *)relay_websocket_deflate (payload, sizeof (payload),
                                                    ws_deflate->strm_deflate, &size_comp);
    CHECK(payload_comp);
    CHECK((size_comp > 0) && (size_comp < (int)sizeof (payload)));

    payload_decomp = (char *)relay_websocket_inflate (payload_comp, size_comp,
                                                      ws_deflate->strm_inflate, &size_decomp);
    CHECK(payload_decomp);
    LONGS_EQUAL(sizeof (payload), size_decomp);
    MEMCMP_EQUAL(payload, payload_decomp, sizeof (payload));

    free (payload_decomp);
    free (payload_comp);

    relay_websocket_deflate_free (ws_deflate);

    /*
     * protection against "deflate bomb": a small compressed frame that
     * decompresses to more than WEBSOCKET_INFLATE_MAX_SIZE must be rejected
     * (relay_websocket_inflate returns NULL)
     */
    ws_deflate = relay_websocket_deflate_alloc ();
    CHECK(ws_deflate);
    ws_deflate->window_bits_deflate = 15;
    ws_deflate->window_bits_inflate = 15;
    ws_deflate->strm_deflate = (z_stream *)calloc (1, sizeof (*ws_deflate->strm_deflate));
    CHECK(ws_deflate->strm_deflate);
    LONGS_EQUAL(1, relay_websocket_deflate_init_stream_deflate (ws_deflate));
    ws_deflate->strm_inflate = (z_stream *)calloc (1, sizeof (*ws_deflate->strm_inflate));
    CHECK(ws_deflate->strm_inflate);
    LONGS_EQUAL(1, relay_websocket_deflate_init_stream_inflate (ws_deflate));

    /* highly compressible payload that decompresses past the maximum size */
    size_t bomb_size = WEBSOCKET_INFLATE_MAX_SIZE + (1024 * 1024);
    char *bomb = (char *)calloc (1, bomb_size);
    CHECK(bomb);

    payload_comp = (char *)relay_websocket_deflate (bomb, bomb_size,
                                                    ws_deflate->strm_deflate, &size_comp);
    CHECK(payload_comp);
    CHECK(size_comp < bomb_size);

    payload_decomp = (char *)relay_websocket_inflate (payload_comp, size_comp,
                                                      ws_deflate->strm_inflate, &size_decomp);
    POINTERS_EQUAL(NULL, payload_decomp);

    free (payload_comp);
    free (bomb);

    relay_websocket_deflate_free (ws_deflate);
}

/*
 * Test functions:
 *   relay_websocket_decode_frame
 */

TEST(RelayWebsocket, DecodeFrame)
{
    struct t_relay_websocket_frame *frames;
    char *partial_ws_frame;
    int num_frames, partial_ws_frame_size;
    /* small unmasked binary frame with payload "hello" */
    unsigned char frame_ok[7] = { 0x82, 0x05, 'h', 'e', 'l', 'l', 'o' };
    /* masked frame announcing a 1 GB payload (64-bit length field) */
    unsigned char frame_too_big[10] = {
        0x82, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
    };

    /* a valid small frame is decoded */
    frames = NULL;
    num_frames = 0;
    partial_ws_frame = NULL;
    partial_ws_frame_size = 0;
    LONGS_EQUAL(1, relay_websocket_decode_frame (
                    frame_ok, sizeof (frame_ok), 0, NULL,
                    &frames, &num_frames, &partial_ws_frame,
                    &partial_ws_frame_size));
    LONGS_EQUAL(1, num_frames);
    CHECK(frames);
    LONGS_EQUAL(5, frames[0].payload_size);
    MEMCMP_EQUAL("hello", frames[0].payload, 5);
    free (frames[0].payload);
    free (frames);
    free (partial_ws_frame);

    /* a frame announcing an oversized payload is rejected (return 0) */
    frames = NULL;
    num_frames = 0;
    partial_ws_frame = NULL;
    partial_ws_frame_size = 0;
    LONGS_EQUAL(0, relay_websocket_decode_frame (
                    frame_too_big, sizeof (frame_too_big), 1, NULL,
                    &frames, &num_frames, &partial_ws_frame,
                    &partial_ws_frame_size));
    free (frames);
    free (partial_ws_frame);
}

/*
 * Test functions:
 *   relay_websocket_encode_frame
 */

TEST(RelayWebsocket, EncodeFrame)
{
    /* TODO: write tests */
}

/*
 * Test functions:
 *   relay_websocket_deflate_print_log
 */

TEST(RelayWebsocket, DeflatePrintLog)
{
    /* TODO: write tests */
}
