/*
 * GTK VNC Widget
 *
 * Copyright (C) 2006  Anthony Liguori <anthony@codemonkey.ws>
 * Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.0 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
 */

#include <config.h>

#include <string.h>
#include <stdlib.h>
#include <gnutls/gnutls.h>
#include <gnutls/crypto.h>

#include "vncconnection.h"
#include "vncbaseframebuffer.h"
#include "testutils.h"
#include "dh.h"

static gboolean debug;
static gboolean allowfail;

struct GVncTest {
    GMutex portlock;
    GCond portcond;
    GMutex clock;
    GCond cond;
    int port;
    VncConnection *conn;
    GMainLoop *loop;
    gboolean connected;
    gboolean quit;
    char *error;
    VncCursor *cursor;

    guint8 *pixels;

    int auth_type;
    gboolean (*auth_func)(GInputStream *, GOutputStream *);
    void (*test_func)(GInputStream *, GOutputStream *);
};


static void server_send_bytes(GOutputStream *os, const guint8 *str, gsize len)
{
    g_assert(g_output_stream_write_all(os, str, len, NULL, NULL, NULL) || allowfail);
}

static void server_send_u8(GOutputStream *os, guint8 v)
{
    g_assert(g_output_stream_write_all(os, &v, 1, NULL, NULL, NULL) || allowfail);
}

static void server_send_u16(GOutputStream *os, guint16 v)
{
    v = GUINT16_TO_BE(v);
    g_assert(g_output_stream_write_all(os, &v, 2, NULL, NULL, NULL) || allowfail);
}

static void server_send_u32(GOutputStream *os, guint32 v)
{
    v = GUINT32_TO_BE(v);
    g_assert(g_output_stream_write_all(os, &v, 4, NULL, NULL, NULL) || allowfail);
}

static void server_send_s32(GOutputStream *os, gint32 v)
{
    v = GINT32_TO_BE(v);
    g_assert(g_output_stream_write_all(os, &v, 4, NULL, NULL, NULL) || allowfail);
}

static void server_recv_bytes(GInputStream *is, guint8 *str, gsize len)
{
    g_assert(g_input_stream_read_all(is, str, len, NULL, NULL, NULL));
}

static void server_recv_u8(GInputStream *is, guint8 v)
{
    guint8 e;
    g_assert(g_input_stream_read_all(is, &e, 1, NULL, NULL, NULL));
    g_assert_cmpint(e, ==, v);
}

static void server_recv_u16(GInputStream *is, guint16 v)
{
    guint16 e;
    g_assert(g_input_stream_read_all(is, &e, 2, NULL, NULL, NULL));
    e = GINT16_FROM_BE(e);
    g_assert_cmpint(e, ==, v);
}

static void server_recv_s32(GInputStream *is, gint32 v)
{
    gint32 e;
    g_assert(g_input_stream_read_all(is, &e, 4, NULL, NULL, NULL));
    e = GINT32_FROM_BE(e);
    g_assert_cmpint(e, ==, v);
}


static gpointer server_thread(gpointer opaque)
{
    struct GVncTest *data = opaque;
    GSocketListener *server;
    GSocketConnection *client;
    GIOStream *ios;
    GInputStream *is;
    GOutputStream *os;

    server = g_socket_listener_new();

    g_mutex_lock(&data->portlock);
    data->port = g_socket_listener_add_any_inet_port(server, NULL, NULL);
    g_mutex_unlock(&data->portlock);
    g_cond_signal(&data->portcond);

    client = g_socket_listener_accept(server, NULL, NULL, NULL);

    ios = G_IO_STREAM(client);
    is = g_io_stream_get_input_stream(ios);
    os = g_io_stream_get_output_stream(ios);

    guint8 greeting[] = {
        'R', 'F', 'B', ' ',
        '0', '0', '3', '.',
        '0', '0', '8', '\n',
    };

    /* Greeting */
    server_send_bytes(os, greeting, G_N_ELEMENTS(greeting));
    server_recv_bytes(is, greeting, G_N_ELEMENTS(greeting));

    if (data->auth_func(is, os)) {
        /* shared flag */
        server_recv_u8(is, 0);

        if (data->test_func) {
            data->test_func(is, os);
        } else {
            VNC_DEBUG("No server test func");
        }
    } else {
        VNC_DEBUG("Sent auth failure, skipping test func");
    }

    g_mutex_lock(&data->clock);
    while (!data->quit) {
        VNC_DEBUG("Waiting for quit");
        g_cond_wait(&data->cond, &data->clock);
    }

    g_object_unref(client);
    g_object_unref(server);

    return NULL;
}

static void client_signal_desktop_resize(VncConnection *conn,
                                         int width, int height,
                                         gpointer opaque)
{
    struct GVncTest *test = opaque;
    const VncPixelFormat *remoteFormat;
    VncPixelFormat localFormat = {
        .bits_per_pixel = 32,
        .depth = 32,
        .byte_order = G_BYTE_ORDER,
        .true_color_flag = TRUE,
        .red_max = 255,
        .green_max = 255,
        .blue_max = 255,
        .red_shift = 0,
        .green_shift = 8,
        .blue_shift = 16,
    };
    VncBaseFramebuffer *fb;


    VNC_DEBUG("Resize %dx%d", width, height);
    remoteFormat = vnc_connection_get_pixel_format(conn);

    /* We'll fix our local copy as rgb888 */
    test->pixels = g_new0(guint8, width * height * 4);

    fb = vnc_base_framebuffer_new(test->pixels, width, height, width * 4,
                                  &localFormat, remoteFormat);

    vnc_connection_set_framebuffer(conn, VNC_FRAMEBUFFER(fb));

    g_object_unref(fb);
}

static void client_signal_cursor_changed(VncConnection *conn G_GNUC_UNUSED,
                                         VncCursor *cursor,
                                         gpointer opaque)
{
    struct GVncTest *test = opaque;

    g_clear_object(&test->cursor);
    test->cursor = g_object_ref(cursor);
    vnc_connection_shutdown(conn);
}

static void client_signal_initialized(VncConnection *conn,
                                      gpointer opaque)
{
    struct GVncTest *test = opaque;
    gint32 encodings[] = {  VNC_CONNECTION_ENCODING_ZRLE,
                            VNC_CONNECTION_ENCODING_HEXTILE,
                            VNC_CONNECTION_ENCODING_RRE,
                            VNC_CONNECTION_ENCODING_COPY_RECT,
                            VNC_CONNECTION_ENCODING_RAW,
                            VNC_CONNECTION_ENCODING_XCURSOR,
                            VNC_CONNECTION_ENCODING_RICH_CURSOR,
                            VNC_CONNECTION_ENCODING_ALPHA_CURSOR};
    gint32 *encodingsp;
    int n_encodings;

    client_signal_desktop_resize(conn,
                                 vnc_connection_get_width(conn),
                                 vnc_connection_get_height(conn),
                                 test);

    encodingsp = encodings;
    n_encodings = G_N_ELEMENTS(encodings);

    VNC_DEBUG("Sending %d encodings", n_encodings);
    if (!vnc_connection_set_encodings(conn, n_encodings, encodingsp))
        goto error;

    VNC_DEBUG("Requesting first framebuffer update");
    if (!vnc_connection_framebuffer_update_request(test->conn,
                                                   0, 0, 0,
                                                   vnc_connection_get_width(test->conn),
                                                   vnc_connection_get_height(test->conn)))
        vnc_connection_shutdown(test->conn);

    test->connected = TRUE;
    return;

 error:
    vnc_connection_shutdown(conn);
}

static void client_signal_initialize_quit(VncConnection *conn G_GNUC_UNUSED,
                                          gpointer opaque)
{
    struct GVncTest *test = opaque;
    VNC_DEBUG("Initialize seen, shutting down");
    g_main_loop_quit(test->loop);
}

static void client_signal_auth_failure(VncConnection *conn G_GNUC_UNUSED,
                                       const char *reason,
                                       gpointer opaque)
{
    struct GVncTest *test = opaque;
    VNC_DEBUG("Auth failure %s, shutting down", reason);
    g_main_loop_quit(test->loop);
}

static void client_signal_auth_choose_type(VncConnection *conn,
                                           GValueArray *types G_GNUC_UNUSED,
                                           gpointer opaque G_GNUC_UNUSED)
{
    struct GVncTest *test = opaque;
    vnc_connection_set_auth_type(conn, test->auth_type);
}

static void client_signal_auth_credential(VncConnection *conn,
                                          GValueArray *credList,
                                          gpointer opaque G_GNUC_UNUSED)
{
    size_t i;

    for (i = 0 ; i < credList->n_values ; i++) {
        GValue *cred = g_value_array_get_nth(credList, i);
        switch (g_value_get_enum(cred)) {
        case VNC_CONNECTION_CREDENTIAL_USERNAME:
            vnc_connection_set_credential(
                conn, VNC_CONNECTION_CREDENTIAL_USERNAME, "AzureDiamond");
            break;
        case VNC_CONNECTION_CREDENTIAL_PASSWORD:
            vnc_connection_set_credential(
                conn, VNC_CONNECTION_CREDENTIAL_PASSWORD, "Hunter2");
            break;
        case VNC_CONNECTION_CREDENTIAL_CLIENTNAME:
            vnc_connection_set_credential(
                conn, VNC_CONNECTION_CREDENTIAL_CLIENTNAME, "vncconnectiontest");
            break;
        default:
            break;
        }
    }
}


static void client_signal_disconnected(VncConnection *conn G_GNUC_UNUSED,
                                       gpointer opaque)
{
    struct GVncTest *test = opaque;
    g_main_loop_quit(test->loop);
}

static void client_signal_error(VncConnection *conn G_GNUC_UNUSED,
                                const char *str,
                                gpointer opaque)
{
    struct GVncTest *test = opaque;
    test->error = g_strdup(str);
}

static void server_send_auth_result_pass(GOutputStream *os)
{
    server_send_u32(os, 0);
}

static void server_send_auth_result_fail(GOutputStream *os)
{
    const char *msg = "Bad Wolf";
    server_send_u32(os, 1);
    server_send_u32(os, strlen(msg));
    server_send_bytes(os, (guint8*)msg, strlen(msg));
}

static gboolean server_process_auth_none(GInputStream *is,
                                         GOutputStream *os)
{
    /* N auth */
    server_send_u8(os, 1);
    /* auth == none */
    server_send_u8(os, VNC_CONNECTION_AUTH_NONE);
    server_recv_u8(is, VNC_CONNECTION_AUTH_NONE);

    server_send_auth_result_pass(os);

    return TRUE;
}

static gboolean server_process_auth_vnc(GInputStream *is,
                                        GOutputStream *os,
                                        gboolean fail)
{
    guint8 challenge[16] = {
        '0', '1', '2', '3', '4', '5', '6', '7',
        '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
    };
    guint8 result[16] = {};
    guint8 valid[16] = {
        0x93, 0x7d, 0xfe, 0xb0, 0x0c, 0xdf, 0x3d, 0x93,
        0x73, 0x16, 0x96, 0x52, 0x3c, 0xaf, 0x0b, 0x54
    };

    /* N auth */
    server_send_u8(os, 1);
    /* auth == none */
    server_send_u8(os, VNC_CONNECTION_AUTH_VNC);
    server_recv_u8(is, VNC_CONNECTION_AUTH_VNC);

    server_send_bytes(os, challenge, sizeof(challenge));
    server_recv_bytes(is, result, sizeof(result));

    test_assert_cmpbytes(result, sizeof(result), valid, sizeof(valid));

    if (fail) {
        server_send_auth_result_fail(os);
        return FALSE;
    } else {
        server_send_auth_result_pass(os);
        return TRUE;
    }
}

static gboolean server_process_auth_vnc_pass(GInputStream *is,
                                             GOutputStream *os)
{
    return server_process_auth_vnc(is, os, FALSE);
}

static gboolean server_process_auth_vnc_fail(GInputStream *is,
                                             GOutputStream *os)
{
    return server_process_auth_vnc(is, os, TRUE);
}

static void
vnc_munge_des_key(const unsigned char *key, unsigned char *newkey)
{
    size_t i;
    for (i = 0; i < 8; i++) {
        unsigned char r = key[i];
        r = (r & 0xf0) >> 4 | (r & 0x0f) << 4;
        r = (r & 0xcc) >> 2 | (r & 0x33) << 2;
        r = (r & 0xaa) >> 1 | (r & 0x55) << 1;
        newkey[i] = r;
    }
}

static gboolean
vnc_dec_des_ecb(unsigned char *where, const int length, GBytes *key)
{
    gnutls_cipher_hd_t c;
    int i, j;
    int err;
    const unsigned char *rawkey = g_bytes_get_data(key, NULL);
    unsigned char newkey[8];
    char *iv = NULL;

    g_assert(g_bytes_get_size(key) == sizeof(newkey));

    vnc_munge_des_key(rawkey, newkey);

    iv = g_new0(char, 8);
    for (i = length - 8; i >= 0; i -= 8) {
        gnutls_datum_t gkey = { newkey, sizeof(newkey) };

        err = gnutls_cipher_init(&c, GNUTLS_CIPHER_DES_CBC, &gkey, NULL);
        if (err != 0) {
            VNC_DEBUG("gnutls_cipher_open(DES) error: %s", gnutls_strerror(err));
            goto error;
        }

        gnutls_cipher_set_iv(c, iv, 8);

        err = gnutls_cipher_decrypt2(c, where + i, 8, where + i, 8);
        gnutls_cipher_deinit(c);

        if (err != 0) {
            VNC_DEBUG("gnutls_cipher_decrypt2 error: %s", gnutls_strerror(err));
            goto error;
        }

        if (i == 0) {
            for (j = 0; j< 8; j++)
                where[j] ^= rawkey[j];
        } else {
            for (j = 0; j < 8; j++)
                where[i + j] ^= where[i + j - 8];
        }
    }

    g_free(iv);
    return TRUE;

 error:
    g_free(iv);
    return FALSE;
}

static gboolean server_process_auth_mslogon(GInputStream *is,
                                            GOutputStream *os,
                                            gboolean fail)
{
    GBytes *mod = bytes_from_hex("a655e5905de52b53");
    GBytes *gen = bytes_from_hex("a835819f929dee92");
    GBytes *itr = NULL;
    guchar pubdata[8];
    GBytes *pub = NULL;
    GBytes *key = NULL;
    struct vnc_dh *dh = NULL;
    guint8 gotusername[256];
    guint8 gotpassword[64];
    guint8 wantusername[256] = {};
    guint8 wantpassword[64] = {};

    /* N auth */
    server_send_u8(os, 1);

    server_send_u8(os, VNC_CONNECTION_AUTH_MSLOGONII);
    server_recv_u8(is, VNC_CONNECTION_AUTH_MSLOGONII);

    dh = vnc_dh_new(gen, mod);
    itr = vnc_dh_gen_secret(dh, 8);

    server_send_bytes(os, g_bytes_get_data(gen, NULL),
                      g_bytes_get_size(mod));
    server_send_bytes(os, g_bytes_get_data(mod, NULL),
                      g_bytes_get_size(mod));
    server_send_bytes(os, g_bytes_get_data(itr, NULL),
                      g_bytes_get_size(itr));

    server_recv_bytes(is, pubdata, sizeof(pubdata));

    pub = g_bytes_new(pubdata, sizeof(pubdata));

    key = vnc_dh_gen_key(dh, pub, 8);

    server_recv_bytes(is, gotusername, sizeof(gotusername));
    server_recv_bytes(is, gotpassword, sizeof(gotpassword));

    g_assert(vnc_dec_des_ecb(gotusername, sizeof(gotusername), key));
    g_assert(vnc_dec_des_ecb(gotpassword, sizeof(gotpassword), key));

    memcpy(wantusername, "AzureDiamond", 12);
    memcpy(wantpassword, "Hunter2", 7);
    test_assert_cmpbytes(wantusername, sizeof(wantusername),
                         gotusername, sizeof(gotusername));
    test_assert_cmpbytes(wantpassword, sizeof(wantpassword),
                         gotpassword, sizeof(gotpassword));


    g_bytes_unref(mod);
    g_bytes_unref(gen);
    g_bytes_unref(itr);
    g_bytes_unref(pub);
    g_bytes_unref(key);

    vnc_dh_free(dh);

    if (fail) {
        server_send_auth_result_fail(os);
        return FALSE;
    } else {
        server_send_auth_result_pass(os);
        return TRUE;
    }
}

static gboolean server_process_auth_mslogon_pass(GInputStream *is,
                                                 GOutputStream *os)
{
    return server_process_auth_mslogon(is, os, FALSE);
}

static gboolean server_process_auth_mslogon_fail(GInputStream *is,
                                                 GOutputStream *os)
{
    return server_process_auth_mslogon(is, os, TRUE);
}

static gboolean server_process_auth_ard(GInputStream *is,
                                        GOutputStream *os,
                                        gboolean fail)
{
    GBytes *mod = bytes_from_hex("c30353225ca6a485fc3510ec172e3a03"
                                 "3c9727c583148e7f04f5616dd4bd68f2"
                                 "76d6cc55a1e63b5d3f7620023a797b89"
                                 "01cd87e7d94a6ab00825eb1e15448c5f"
                                 "9f4a64fab46d809460d49703daa76051"
                                 "fae562dbd0fe8879833d1ac94437fe89"
                                 "8986ab57dc75bfd081a9c6772ccccce3"
                                 "f458be1d4674871dc385407a2d586747");
    GBytes *gen = bytes_from_hex("0500");
    GBytes *itr = NULL;
    guchar pubdata[128];
    GBytes *pub = NULL;
    GBytes *key = NULL;
    struct vnc_dh *dh = NULL;
    guint8 userpass[128];
    gnutls_hash_hd_t md5 = NULL;
    gnutls_cipher_hd_t aes = NULL;
    gchar *iv = NULL;
    unsigned char shared[16];
    int err;
    size_t i;

    /* N auth */
    server_send_u8(os, 1);

    server_send_u8(os, VNC_CONNECTION_AUTH_ARD);
    server_recv_u8(is, VNC_CONNECTION_AUTH_ARD);

    dh = vnc_dh_new(gen, mod);
    itr = vnc_dh_gen_secret(dh, 128);

    server_send_bytes(os, g_bytes_get_data(gen, NULL),
                      g_bytes_get_size(gen));
    server_send_u16(os, 128);
    server_send_bytes(os, g_bytes_get_data(mod, NULL),
                      g_bytes_get_size(mod));
    server_send_bytes(os, g_bytes_get_data(itr, NULL),
                      g_bytes_get_size(itr));

    server_recv_bytes(is, userpass, sizeof(userpass));
    server_recv_bytes(is, pubdata, sizeof(pubdata));

    pub = g_bytes_new(pubdata, sizeof(pubdata));

    key = vnc_dh_gen_key(dh, pub, 128);

    err = gnutls_hash_init(&md5, GNUTLS_DIG_MD5);
    if (err != 0) {
        VNC_DEBUG("gnutls_hash_init(MD5) error: %s", gnutls_strerror(err));
        g_assert_not_reached();
    }
    err = gnutls_hash(md5, g_bytes_get_data(key, NULL), 128);
    if (err != 0) {
        VNC_DEBUG("gnutls_hash error: %s", gnutls_strerror(err));
        g_assert_not_reached();
    }

    gnutls_hash_output(md5, shared);
    gnutls_hash_deinit(md5, NULL);

    /* gnutls only provides CBC mode, so we fake ECB mode
     * by working 16 bytes at a time
     */
    iv = g_new0(char, 16);
    for (i = 0; i < sizeof(userpass); i += 16) {
        gnutls_datum_t aeskey = { shared, sizeof(shared) };
        err = gnutls_cipher_init(&aes, GNUTLS_CIPHER_AES_128_CBC, &aeskey, NULL);
        if (err != 0) {
            VNC_DEBUG("gnutls_cipher_init error: %s", gnutls_strerror(err));
            g_assert_not_reached();
        }

        gnutls_cipher_set_iv(aes, iv, 16);

        err = gnutls_cipher_decrypt(aes, userpass + i, 16);
        if (err != 0) {
            VNC_DEBUG("gnutls_cipher_decrypt error: %s", gnutls_strerror(err));
            g_assert_not_reached();
        }

        gnutls_cipher_deinit(aes);
        aes = NULL;
    }
    g_free(iv);

    g_assert_cmpstr((const char *)userpass, ==, "AzureDiamond");
    g_assert_cmpstr((const char *)userpass + 64, ==, "Hunter2");

    g_bytes_unref(mod);
    g_bytes_unref(gen);
    g_bytes_unref(itr);
    g_bytes_unref(pub);
    g_bytes_unref(key);

    vnc_dh_free(dh);

    if (fail) {
        server_send_auth_result_fail(os);
        return FALSE;
    } else {
        server_send_auth_result_pass(os);
        return TRUE;
    }
}

static gboolean server_process_auth_ard_pass(GInputStream *is,
                                             GOutputStream *os)
{
    return server_process_auth_ard(is, os, FALSE);
}

static gboolean server_process_auth_ard_fail(GInputStream *is,
                                             GOutputStream *os)
{
    return server_process_auth_ard(is, os, TRUE);
}

static gboolean server_process_auth_bogus_first_ok(GInputStream *is G_GNUC_UNUSED,
                                                   GOutputStream *os)
{
    /* N auth */
    server_send_u8(os, 3);

    /* Mix of ok & bogus auth types */
    server_send_u8(os, VNC_CONNECTION_AUTH_NONE);
    server_send_u8(os, 43);
    server_send_u8(os, 42);
    server_recv_u8(is, VNC_CONNECTION_AUTH_NONE);

    server_send_auth_result_pass(os);

    return TRUE;
}

static gboolean server_process_auth_bogus_middle_ok(GInputStream *is G_GNUC_UNUSED,
                                                    GOutputStream *os)
{
    /* N auth */
    server_send_u8(os, 5);

    /* Mix of ok & bogus auth types */
    server_send_u8(os, 42);
    server_send_u8(os, VNC_CONNECTION_AUTH_NONE);
    server_send_u8(os, 43);
    server_send_u8(os, VNC_CONNECTION_AUTH_MSLOGONII);
    server_send_u8(os, 44);
    server_recv_u8(is, VNC_CONNECTION_AUTH_NONE);

    server_send_auth_result_pass(os);

    return TRUE;
}

static gboolean server_process_auth_bogus_last_ok(GInputStream *is G_GNUC_UNUSED,
                                                   GOutputStream *os)
{
    /* N auth */
    server_send_u8(os, 3);

    /* Mix of ok & bogus auth types */
    server_send_u8(os, 42);
    server_send_u8(os, 43);
    server_send_u8(os, VNC_CONNECTION_AUTH_NONE);
    server_recv_u8(is, VNC_CONNECTION_AUTH_NONE);

    server_send_auth_result_pass(os);

    return TRUE;
}

static gboolean server_process_auth_bogus_one_bad(GInputStream *is G_GNUC_UNUSED,
                                                  GOutputStream *os)
{
    /* N auth */
    server_send_u8(os, 1);

    /* A bogus auth type */
    server_send_u8(os, 42);

    return FALSE;
}

static gboolean server_process_auth_bogus_many_bad(GInputStream *is G_GNUC_UNUSED,
                                                   GOutputStream *os)
{
    /* N auth */
    server_send_u8(os, 3);

    /* Several bogus auth types */
    server_send_u8(os, 42);
    server_send_u8(os, 43);
    server_send_u8(os, 44);

    return FALSE;
}

static gboolean server_process_auth_illegal(GInputStream *is,
                                            GOutputStream *os)
{
    /* N auth */
    server_send_u8(os, 1);

    server_send_u8(os, VNC_CONNECTION_AUTH_VNC);
    server_recv_u8(is, VNC_CONNECTION_AUTH_NONE);

    server_send_auth_result_fail(os);

    return FALSE;
}

static void server_send_fb_dimensions(GOutputStream *os,
                                      guint16 w, guint16 h)
{
    server_send_u16(os, w);
    server_send_u16(os, h);
}

static void server_send_pixel_format(GOutputStream *os,
                                     guint8 bpp,
                                     guint8 depth,
                                     guint8 endian,
                                     guint8 true_color,
                                     guint16 red_max,
                                     guint16 green_max,
                                     guint16 blue_max,
                                     guint8 red_shift,
                                     guint8 green_shift,
                                     guint8 blue_shift)
{
    guint8 pad[3] = {0};

    server_send_u8(os, bpp);
    server_send_u8(os, depth);
    server_send_u8(os, endian);
    server_send_u8(os, true_color);

    server_send_u16(os, red_max);
    server_send_u16(os, green_max);
    server_send_u16(os, blue_max);
    server_send_u8(os, red_shift);
    server_send_u8(os, green_shift);
    server_send_u8(os, blue_shift);

    server_send_bytes(os, pad, G_N_ELEMENTS(pad));
}

static void server_send_desktop_name(GOutputStream *os,
                                     const char *name)
{
    server_send_u32(os, strlen(name));
    server_send_bytes(os, (guint8*)name, strlen(name));
}

static void server_send_common_init(GInputStream *is G_GNUC_UNUSED,
                                    GOutputStream *os,
                                    gboolean true_color)
{
    server_send_fb_dimensions(os, 100, 100);

    server_send_pixel_format(
        os,
        /* BPP, depth, endian, true color */
        32, 8, 1, true_color ? 1 : 0,
        /* RGB max */
        255, 255, 255,
        /* RGB shift */
        0, 8, 16);

    server_send_desktop_name(os, "Test");
}

static void server_send_common_init_true_colour(GInputStream *is G_GNUC_UNUSED,
                                                GOutputStream *os)
{
    server_send_common_init(is, os, TRUE);
}

static void server_send_common_init_cmap(GInputStream *is G_GNUC_UNUSED,
                                         GOutputStream *os)
{
    server_send_common_init(is, os, FALSE);
}

static void server_send_exploit_rre_bounds(GInputStream *is, GOutputStream *os)
{
    server_send_common_init_true_colour(is, os);

    /* Message type & pad */
    server_send_u8(os, 0);
    server_send_u8(os, 0);

    /* num rect */
    server_send_u16(os, 1);
    /* x, y, w, h */
    server_send_u16(os, 90);
    server_send_u16(os, 90);
    server_send_u16(os, 10);
    server_send_u16(os, 10);

    server_send_s32(os, VNC_CONNECTION_ENCODING_RRE);

    /* num rect */
    server_send_u32(os, 1);

    /* bg pix, fg pix */
    server_send_u32(os, 0x41414141);
    server_send_u32(os, 0x42424242);

    /* x, y, w, h */
    allowfail = TRUE;
    server_send_u16(os, 10);
    server_send_u16(os, 10000);
    server_send_u16(os, 1);
    server_send_u16(os, 1);
    allowfail = FALSE;
}


static void server_send_exploit_hextile_bounds(GInputStream *is, GOutputStream *os)
{
    server_send_common_init_true_colour(is, os);

    /* Message type & pad */
    server_send_u8(os, 0);
    server_send_u8(os, 0);

    /* num rect */
    server_send_u16(os, 1);
    /* x, y, w, h */
    server_send_u16(os, 90);
    server_send_u16(os, 90);
    server_send_u16(os, 10);
    server_send_u16(os, 10);

    server_send_s32(os, VNC_CONNECTION_ENCODING_HEXTILE);

    /* tile type */
    server_send_u8(os, 0x18);

    /* num rect */
    server_send_u8(os, 1);

    /* fg pix */
    server_send_u32(os, 0x12345678);

    /* x, y */
    allowfail = TRUE;
    server_send_u8(os, 0xff);
    server_send_u8(os, 0xff);
    allowfail = FALSE;
}


static void server_send_exploit_copyrect_bounds(GInputStream *is, GOutputStream *os)
{
    server_send_common_init_true_colour(is, os);

    /* Message type & pad */
    server_send_u8(os, 0);
    server_send_u8(os, 0);

    /* num rect */
    server_send_u16(os, 1);
    /* x, y, w, h */
    server_send_u16(os, 90);
    server_send_u16(os, 90);
    server_send_u16(os, 10);
    server_send_u16(os, 10);

    server_send_s32(os, VNC_CONNECTION_ENCODING_COPY_RECT);

    /* src x, y */
    allowfail = TRUE;
    server_send_u16(os, 91);
    server_send_u16(os, 91);
    allowfail = FALSE;
}


static void server_send_exploit_unexpected_cmap(GInputStream *is, GOutputStream *os)
{
    server_send_common_init_true_colour(is, os);

    /* n-encodings */
    server_recv_u8(is, 2);
    /* pad */
    server_recv_u8(is, 0);
    /* num encodings */
    server_recv_u16(is, 8);

    /* encodings */
    server_recv_s32(is, VNC_CONNECTION_ENCODING_ZRLE);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_HEXTILE);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_RRE);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_COPY_RECT);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_RAW);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_XCURSOR);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_RICH_CURSOR);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_ALPHA_CURSOR);

    /* update request */
    server_recv_u8(is, 3);
    /* ! incremental */
    server_recv_u8(is, 0);

    /* x, y, w, h */
    server_recv_u16(is, 0);
    server_recv_u16(is, 0);
    server_recv_u16(is, 100);
    server_recv_u16(is, 100);

    /* set color map */
    server_send_u8(os, 1);
    /* pad */
    server_send_u8(os, 0);

    allowfail = TRUE;
    /* first color, ncolors */
    server_send_u16(os, 0);
    server_send_u16(os, 1);

    /* r,g,b */
    server_send_u16(os, 128);
    server_send_u16(os, 128);
    server_send_u16(os, 128);
    allowfail = FALSE;
}


static void server_send_exploit_cmap_overflow(GInputStream *is, GOutputStream *os)
{
    server_send_common_init_cmap(is, os);

    /* n-encodings */
    server_recv_u8(is, 2);
    /* pad */
    server_recv_u8(is, 0);
    /* num encodings */
    server_recv_u16(is, 8);

    /* encodings */
    server_recv_s32(is, VNC_CONNECTION_ENCODING_ZRLE);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_HEXTILE);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_RRE);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_COPY_RECT);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_RAW);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_XCURSOR);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_RICH_CURSOR);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_ALPHA_CURSOR);

    /* update request */
    server_recv_u8(is, 3);
    /* ! incremental */
    server_recv_u8(is, 0);

    /* x, y, w, h */
    server_recv_u16(is, 0);
    server_recv_u16(is, 0);
    server_recv_u16(is, 100);
    server_recv_u16(is, 100);

    /* set color map */
    server_send_u8(os, 1);
    /* pad */
    server_send_u8(os, 0);
    /* first color, ncolors */
    server_send_u16(os, 65535);
    server_send_u16(os, 2);

    allowfail = TRUE;
    /* r,g,b */
    for (int i = 0 ; i < 2; i++) {
        server_send_u16(os, i);
        server_send_u16(os, i);
        server_send_u16(os, i);
    }
    allowfail = FALSE;
}

static void server_send_common_init_cursor(GInputStream *is, GOutputStream *os)
{
    server_send_common_init_true_colour(is, os);

    /* set encodings message */
    server_recv_u8(is, 2);
    /* pad */
    server_recv_u8(is, 0);
    /* num encodings */
    server_recv_u16(is, 8);

    /* encodings */
    server_recv_s32(is, VNC_CONNECTION_ENCODING_ZRLE);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_HEXTILE);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_RRE);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_COPY_RECT);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_RAW);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_XCURSOR);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_RICH_CURSOR);
    server_recv_s32(is, VNC_CONNECTION_ENCODING_ALPHA_CURSOR);

    /* update request */
    server_recv_u8(is, 3);
    /* ! incremental */
    server_recv_u8(is, 0);

    /* x, y, w, h */
    server_recv_u16(is, 0);
    server_recv_u16(is, 0);
    server_recv_u16(is, 100);
    server_recv_u16(is, 100);

    /* Message type & pad */
    server_send_u8(os, 0);
    server_send_u8(os, 0);
    /* num rect */
    server_send_u16(os, 1);
}

static void server_send_xcursor(GInputStream *is, GOutputStream *os,
                                int hotx, int hoty)
{
    /* Plus sign with outline. */
    guint8 data[7][5] = {
        { 0, 0, 0, 0, 0 },
        { 0, 0, 1, 0, 0 },
        { 0, 0, 1, 0, 0 },
        { 1, 1, 1, 1, 1 },
        { 0, 0, 1, 0, 0 },
        { 0, 0, 1, 0, 0 },
        { 0, 0, 0, 0, 0 },
    };
    guint8 mask[7][5] = {
        { 0, 0, 0, 0, 0 },
        { 0, 1, 1, 1, 0 },
        { 1, 1, 1, 1, 1 },
        { 1, 1, 1, 1, 1 },
        { 1, 1, 1, 1, 1 },
        { 0, 1, 1, 1, 0 },
        { 0, 0, 0, 0, 0 },
    };
    int x, y;

    server_send_common_init_cursor(is, os);

    server_send_u16(os, hotx);
    server_send_u16(os, hoty);
    server_send_u16(os, 5);
    server_send_u16(os, 7);

    server_send_s32(os, VNC_CONNECTION_ENCODING_XCURSOR);
    /* fg color */
    server_send_u8(os, 0x3);
    server_send_u8(os, 0x2);
    server_send_u8(os, 0x1);
    /* bg color */
    server_send_u8(os, 0xe3);
    server_send_u8(os, 0xe2);
    server_send_u8(os, 0xe1);

    for (y = 0; y < 7; y++) {
        guint8 val = 0;
        for (x = 0; x < 5; x++) {
            if (data[y][x])
                val |= (1 << (7 - x));
        }
        server_send_u8(os, val);
    }
    for (y = 0; y < 7; y++) {
        guint8 val = 0;
        for (x = 0; x < 5; x++) {
            if (mask[y][x])
                val |= (1 << (7 - x));
        }
        server_send_u8(os, val);
    }

}

static void server_send_xcursor_valid(GInputStream *is, GOutputStream *os)
{
    server_send_xcursor(is, os, 2, 3);
}

static void server_send_xcursor_badhot(GInputStream *is, GOutputStream *os)
{
    server_send_xcursor(is, os, 5, 7); /* out of bounds hotx/y */
}

#if G_BYTE_ORDER != G_BIG_ENDIAN
static void server_send_richcursor(GInputStream *is, GOutputStream *os,
                                   int hotx, int hoty)
{
    /* Plus sign with outline. */
#define RGB(r, g, b) (r << 16) | (g << 8) | (b);
    gint32 tran = RGB(0, 0, 0);
    gint32 dark = RGB(0x1, 0x2, 0x3);
    gint32 pale = RGB(0xe1, 0xe2, 0xe3);
#undef RGB
    gint32 data[7][5] = {
        { tran, tran, tran, tran, tran, },
        { tran, pale, dark, pale, tran, },
        { pale, pale, dark, pale, pale, },
        { dark, dark, dark, dark, dark, },
        { pale, pale, dark, pale, pale, },
        { tran, pale, dark, pale, tran, },
        { tran, tran, tran, tran, tran, },
    };
    guint8 mask[7][5] = {
        { 0, 0, 0, 0, 0 },
        { 0, 1, 1, 1, 0 },
        { 1, 1, 1, 1, 1 },
        { 1, 1, 1, 1, 1 },
        { 1, 1, 1, 1, 1 },
        { 0, 1, 1, 1, 0 },
        { 0, 0, 0, 0, 0 },
    };
    int x, y;

    server_send_common_init_cursor(is, os);

    server_send_u16(os, hotx);
    server_send_u16(os, hoty);
    server_send_u16(os, 5);
    server_send_u16(os, 7);

    server_send_s32(os, VNC_CONNECTION_ENCODING_RICH_CURSOR);

    for (y = 0; y < 7; y++) {
        for (x = 0; x < 5; x++) {
            guint32 val = data[y][x];
            server_send_u32(os, val);
        }
    }
    for (y = 0; y < 7; y++) {
        guint8 val = 0;
        for (x = 0; x < 5; x++) {
            if (mask[y][x])
                val |= (1 << (7 - x));
        }
        server_send_u8(os, val);
    }

}

static void server_send_richcursor_valid(GInputStream *is, GOutputStream *os)
{
    server_send_richcursor(is, os, 2, 3);
}

static void server_send_richcursor_badhot(GInputStream *is, GOutputStream *os)
{
    server_send_richcursor(is, os, 5, 7); /* out of bounds hotx/y */
}


static void server_send_alphacursor(GInputStream *is, GOutputStream *os,
                                    int hotx, int hoty)
{
#define RGBA(r, g, b, a) (r << 24) | (g << 16) | (b << 8) | (a << 0);
    /* Plus sign with outline. */
    gint32 tran = RGBA(0, 0, 0, 0);
    gint32 dark = RGBA(0x1, 0x2, 0x3, 0xff);
    gint32 pale = RGBA(0xe1, 0xe2, 0xe3, 0xff);
#undef RGBA
    gint32 data[7][5] = {
        { tran, tran, tran, tran, tran, },
        { tran, pale, dark, pale, tran, },
        { pale, pale, dark, pale, pale, },
        { dark, dark, dark, dark, dark, },
        { pale, pale, dark, pale, pale, },
        { tran, pale, dark, pale, tran, },
        { tran, tran, tran, tran, tran, },
    };
    int x, y;

    server_send_common_init_cursor(is, os);

    server_send_u16(os, hotx);
    server_send_u16(os, hoty);
    server_send_u16(os, 5);
    server_send_u16(os, 7);

    server_send_s32(os, VNC_CONNECTION_ENCODING_ALPHA_CURSOR);
    server_send_s32(os, VNC_CONNECTION_ENCODING_RAW);

    for (y = 0; y < 7; y++) {
        for (x = 0; x < 5; x++) {
            guint32 val = data[y][x];
            server_send_u32(os, val);
        }
    }
}

static void server_send_alphacursor_valid(GInputStream *is, GOutputStream *os)
{
    server_send_alphacursor(is, os, 2, 3);
}

static void server_send_alphacursor_badhot(GInputStream *is, GOutputStream *os)
{
    server_send_alphacursor(is, os, 5, 7); /* out of bounds hotx/y */
}
#endif

static void client_validation_generic_pass(struct GVncTest *test)
{
    if (test->error) {
        VNC_DEBUG("Saw error message %s", test->error);
        g_free(test->error);
    }
    g_assert(!test->error);
}

static void client_validation_generic_fail(struct GVncTest *test)
{
    if (test->error) {
        VNC_DEBUG("Saw error message %s", test->error);
        g_free(test->error);
    }
    g_assert(test->error);
}

static void test_runner(
    int auth_type,
    gboolean (auth_func)(GInputStream *, GOutputStream *),
    void (*test_func)(GInputStream *, GOutputStream *),
    void (*validation_func)(struct GVncTest *),
    gboolean init_only)
{
    struct GVncTest *test;
    char *port;
    GThread *th;

    test = g_new0(struct GVncTest, 1);
    test->auth_func = auth_func;
    test->auth_type = auth_type;
    test->test_func = test_func;

    g_mutex_init(&test->portlock);
    g_cond_init(&test->portcond);
    g_mutex_init(&test->clock);
    g_cond_init(&test->cond);

    th = g_thread_new("vnc-server", server_thread, test);

    g_mutex_lock(&test->portlock);
    while (test->port == 0)
        g_cond_wait(&test->portcond, &test->portlock);
    port = g_strdup_printf("%d", test->port);
    g_mutex_unlock(&test->portlock);

    test->conn = vnc_connection_new();

    g_signal_connect(test->conn, "vnc-auth-choose-type",
                     G_CALLBACK(client_signal_auth_choose_type), test);
    g_signal_connect(test->conn, "vnc-auth-credential",
                     G_CALLBACK(client_signal_auth_credential), test);
    g_signal_connect(test->conn, "vnc-auth-failure",
                     G_CALLBACK(client_signal_auth_failure), test);
    g_signal_connect(test->conn, "vnc-error",
                     G_CALLBACK(client_signal_error), test);

    if (init_only) {
        g_signal_connect(test->conn, "vnc-initialized",
                         G_CALLBACK(client_signal_initialize_quit), test);
    } else {
        g_signal_connect(test->conn, "vnc-initialized",
                         G_CALLBACK(client_signal_initialized), test);
        g_signal_connect(test->conn, "vnc-disconnected",
                         G_CALLBACK(client_signal_disconnected), test);
        g_signal_connect(test->conn, "vnc-desktop-resize",
                         G_CALLBACK(client_signal_desktop_resize), test);
        g_signal_connect(test->conn, "vnc-cursor-changed",
                         G_CALLBACK(client_signal_cursor_changed), test);
    }
    vnc_connection_open_host(test->conn, "127.0.0.1", port);

    test->loop = g_main_loop_new(g_main_context_default(), FALSE);

    g_main_loop_run(test->loop);

    g_mutex_lock(&test->clock);
    VNC_DEBUG("Client loop done, telling server to quit");
    test->quit = TRUE;
    g_mutex_unlock(&test->clock);
    g_cond_signal(&test->cond);

    g_thread_join(th);

    vnc_connection_shutdown(test->conn);
    g_object_unref(test->conn);
    g_free(test->pixels);
    g_main_loop_unref(test->loop);

    validation_func(test);

    g_clear_object(&test->cursor);
    g_free(port);
    g_free(test);
}

static void test_validation(
    void (*test_func)(GInputStream *, GOutputStream *))
{
    test_runner(VNC_CONNECTION_AUTH_NONE,
                server_process_auth_none,
                test_func,
                client_validation_generic_fail,
                FALSE);
}

static void test_auth_pass(
    int auth_type,
    gboolean (*auth_func)(GInputStream *, GOutputStream *))
{
    test_runner(auth_type,
                auth_func,
                server_send_common_init_true_colour,
                client_validation_generic_pass,
                TRUE);
}

static void test_auth_fail(
    int auth_type,
    gboolean (*auth_func)(GInputStream *, GOutputStream *))
{
    test_runner(auth_type,
                auth_func,
                server_send_common_init_true_colour,
                client_validation_generic_fail,
                TRUE);
}

static void test_encodings(
    void (*test_func)(GInputStream *, GOutputStream *),
    void (*validation_func)(struct GVncTest *))
{
    test_runner(VNC_CONNECTION_AUTH_NONE,
                server_process_auth_none,
                test_func,
                validation_func,
                FALSE);
}

static void test_validation_rre(void)
{
    test_validation(server_send_exploit_rre_bounds);
}

static void test_validation_hextile(void)
{
    test_validation(server_send_exploit_hextile_bounds);
}

static void test_validation_copyrect(void)
{
    test_validation(server_send_exploit_copyrect_bounds);
}

static void test_validation_unexpected_cmap(void)
{
    test_validation(server_send_exploit_unexpected_cmap);
}

static void test_validation_overflow_cmap(void)
{
    test_validation(server_send_exploit_cmap_overflow);
}

static void test_auth_vnc_pass(void)
{
    test_auth_pass(VNC_CONNECTION_AUTH_VNC,
                   server_process_auth_vnc_pass);
}

static void test_auth_vnc_fail(void)
{
    test_auth_fail(VNC_CONNECTION_AUTH_VNC,
                   server_process_auth_vnc_fail);
}

static void test_auth_mslogon_pass(void)
{
    test_auth_pass(VNC_CONNECTION_AUTH_MSLOGONII,
                   server_process_auth_mslogon_pass);
}

static void test_auth_mslogon_fail(void)
{
    test_auth_fail(VNC_CONNECTION_AUTH_MSLOGONII,
                   server_process_auth_mslogon_fail);
}

static void test_auth_ard_pass(void)
{
    test_auth_pass(VNC_CONNECTION_AUTH_ARD,
                   server_process_auth_ard_pass);
}

static void test_auth_ard_fail(void)
{
    test_auth_fail(VNC_CONNECTION_AUTH_ARD,
                   server_process_auth_ard_fail);
}

/* Server sending an unknown auth */
static void test_auth_bogus_first_ok(void)
{
    test_auth_pass(VNC_CONNECTION_AUTH_NONE,
                   server_process_auth_bogus_first_ok);
}

static void test_auth_bogus_middle_ok(void)
{
    test_auth_pass(VNC_CONNECTION_AUTH_NONE,
                   server_process_auth_bogus_middle_ok);
}

static void test_auth_bogus_last_ok(void)
{
    test_auth_pass(VNC_CONNECTION_AUTH_NONE,
                   server_process_auth_bogus_last_ok);
}

static void test_auth_bogus_one_bad(void)
{
    test_auth_fail(VNC_CONNECTION_AUTH_NONE,
                   server_process_auth_bogus_one_bad);
}

static void test_auth_bogus_many_bad(void)
{
    test_auth_fail(VNC_CONNECTION_AUTH_NONE,
                   server_process_auth_bogus_many_bad);
}


/* Choosing an auth not in the list */
static void test_auth_illegal(void)
{
    test_auth_fail(VNC_CONNECTION_AUTH_NONE,
                   server_process_auth_illegal);
}

static void client_check_cursor(struct GVncTest *test,
                                int hotx, int hoty)
{
#define RGBA(r, g, b, a)  (r << 0) | (g << 8) | (b << 16) | (a << 24);
    gint32 tran = RGBA(0, 0, 0, 0);
    gint32 dark = RGBA(0x1, 0x2, 0x3, 0xff);
    gint32 pale = RGBA(0xe1, 0xe2, 0xe3, 0xff);
#undef RGBA
    gint32 want[7][5] = {
        { tran, tran, tran, tran, tran, },
        { tran, pale, dark, pale, tran, },
        { pale, pale, dark, pale, pale, },
        { dark, dark, dark, dark, dark, },
        { pale, pale, dark, pale, pale, },
        { tran, pale, dark, pale, tran, },
        { tran, tran, tran, tran, tran, },
    };
    int x, y;
    guint32 *got;
    gboolean failed = FALSE;
    gboolean cdebug = getenv("GTK_VNC_DEBUG") != NULL;
    client_validation_generic_pass(test);

    g_assert(test->cursor);

    g_assert_cmpint(5, ==, vnc_cursor_get_width(test->cursor));
    g_assert_cmpint(7, ==, vnc_cursor_get_height(test->cursor));
    g_assert_cmpint(hotx, ==, vnc_cursor_get_hotx(test->cursor));
    g_assert_cmpint(hoty, ==, vnc_cursor_get_hoty(test->cursor));

    got = (guint32 *)vnc_cursor_get_data(test->cursor);

    for (y = 0; y < 7; y++) {
        for (x = 0; x < 5; x++) {
            guint32 a = want[y][x];
            guint32 b = got[(y * 5) + x];

            if (cdebug)
                g_printerr("%08x==%08x ", a, b);
            if (a != b)
                failed = TRUE;
        }
        if (cdebug)
            g_printerr("\n");
    }
    g_assert(!failed);
}

static void client_check_cursor_valid(struct GVncTest *test)
{
    client_check_cursor(test, 2, 3);
}

static void client_check_cursor_badhot(struct GVncTest *test)
{
    client_check_cursor(test, 4, 6);
}

static void test_xcursor_valid(void)
{
    test_encodings(server_send_xcursor_valid,
                   client_check_cursor_valid);
}

static void test_xcursor_badhot(void)
{
    test_encodings(server_send_xcursor_badhot,
                   client_check_cursor_badhot);
}

#if G_BYTE_ORDER != G_BIG_ENDIAN
static void test_richcursor_valid(void)
{
    test_encodings(server_send_richcursor_valid,
                   client_check_cursor_valid);
}

static void test_richcursor_badhot(void)
{
    test_encodings(server_send_richcursor_badhot,
                   client_check_cursor_badhot);
}

static void test_alphacursor_valid(void)
{
    test_encodings(server_send_alphacursor_valid,
                   client_check_cursor_valid);
}

static void test_alphacursor_badhot(void)
{
    test_encodings(server_send_alphacursor_badhot,
                   client_check_cursor_badhot);
}
#endif

int main(int argc, char **argv) {
    g_test_init(&argc, &argv, NULL);

    if (getenv("GTK_VNC_DEBUG")) {
        debug = TRUE;
        vnc_util_set_debug(TRUE);
    }

    g_test_add_func("/conn/validation/rre", test_validation_rre);
    g_test_add_func("/conn/validation/copyrect", test_validation_copyrect);
    g_test_add_func("/conn/validation/hextile", test_validation_hextile);
    g_test_add_func("/conn/validation/unexpectedcmap", test_validation_unexpected_cmap);
    g_test_add_func("/conn/validation/overflowcmap", test_validation_overflow_cmap);
    g_test_add_func("/conn/auth/vnc/pass", test_auth_vnc_pass);
    g_test_add_func("/conn/auth/vnc/fail", test_auth_vnc_fail);
    g_test_add_func("/conn/auth/mslogon/pass", test_auth_mslogon_pass);
    g_test_add_func("/conn/auth/mslogon/fail", test_auth_mslogon_fail);
    g_test_add_func("/conn/auth/ard/pass", test_auth_ard_pass);
    g_test_add_func("/conn/auth/ard/fail", test_auth_ard_fail);
    g_test_add_func("/conn/auth/bogus/first-ok", test_auth_bogus_first_ok);
    g_test_add_func("/conn/auth/bogus/middle-ok", test_auth_bogus_middle_ok);
    g_test_add_func("/conn/auth/bogus/last-ok", test_auth_bogus_last_ok);
    g_test_add_func("/conn/auth/bogus/one-bad", test_auth_bogus_one_bad);
    g_test_add_func("/conn/auth/bogus/many-bad", test_auth_bogus_many_bad);
    g_test_add_func("/conn/auth/illegal", test_auth_illegal);
    g_test_add_func("/conn/xcursor/valid", test_xcursor_valid);
    g_test_add_func("/conn/xcursor/badhot", test_xcursor_badhot);

    /*
     * XXX the tests fail on big endian systems.
     *
     * Need to investigate whether this is a test suite flaw or
     * a genuine decoding flaw.
     */
#if G_BYTE_ORDER != G_BIG_ENDIAN
    g_test_add_func("/conn/richcursor/valid", test_richcursor_valid);
    g_test_add_func("/conn/richcursor/badhot", test_richcursor_badhot);
    g_test_add_func("/conn/alphacursor/valid", test_alphacursor_valid);
    g_test_add_func("/conn/alphacursor/badhot", test_alphacursor_badhot);
#endif

    return g_test_run();
}
