/*
 * This file is part of the Nice GLib ICE library.
 *
 * (C) 2018 Jakub Adam <jakub.adam@ktknet.cz>
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Nice GLib ICE library.
 *
 * The Initial Developers of the Original Code are Collabora Ltd and Nokia
 * Corporation. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms of the
 * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
 * case the provisions of LGPL are applicable instead of those above. If you
 * wish to allow use of your version of this file only under the terms of the
 * LGPL and not to allow others to use your version of this file under the
 * MPL, indicate your decision by deleting the provisions above and replace
 * them with the notice and other provisions required by the LGPL. If you do
 * not delete the provisions above, a recipient may use your version of this
 * file under either the MPL or the LGPL.
 */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <gio/gnetworking.h>

#include "agent-priv.h"
#include "socket.h"

static GRand *randg;

static GSList *
generate_test_messages(void)
{
  guint i;
  GSList *result = NULL;

  for (i = 0; i != 100; ++i) {
    GInputVector *msg_data = g_new (GInputVector, 1);
    gsize msg_size = g_rand_int_range (randg, 0, G_MAXUINT16);
    gsize j;

    msg_data->size = msg_size + sizeof (guint16);
    msg_data->buffer = g_malloc (msg_data->size);
    *(guint16 *)(msg_data->buffer) = htons (msg_size);

    for (j = 2; j != msg_data->size; ++j) {
      ((guint8 *)msg_data->buffer)[j] = g_rand_int(randg);
    }

    result = g_slist_append(result, msg_data);
  }

  return result;
}

typedef struct {
  GSList *msg_data;
  GSList *current_msg;
  gsize offset;
  guint8 send_buffer[G_MAXUINT16 + sizeof (guint16)];
} TestSocketPriv;

static gint
test_socket_recv_messages (NiceSocket *sock, NiceInputMessage *recv_messages,
    guint n_recv_messages) {
  TestSocketPriv *priv = sock->priv;
  guint i;

  for (i = 0; priv->current_msg && i != n_recv_messages; ++i) {
    gsize msg_size = g_rand_int_range (randg, 0, G_MAXUINT16) + sizeof (guint16);
    gsize j;

    j = sizeof (guint16);
    while (priv->current_msg && j < msg_size) {
      GInputVector *msg = priv->current_msg->data;
      gsize cpylen = MIN (msg->size - priv->offset, msg_size - j);
      memcpy (priv->send_buffer + j, (guint8 *)msg->buffer + priv->offset,
          cpylen);
      priv->offset += cpylen;
      j += cpylen;

      if (priv->offset == msg->size) {
        priv->current_msg = priv->current_msg->next;
        priv->offset = 0;
      }
    }

    msg_size = j;
    *(guint16 *)(priv->send_buffer) = htons (msg_size - sizeof (guint16));

    memcpy_buffer_to_input_message (&recv_messages[i], priv->send_buffer, msg_size);
    nice_address_set_from_string (recv_messages[i].from, "127.0.0.1");
  }

  return i;
}

static gboolean
test_socket_is_reliable (NiceSocket *sock) {
  return TRUE;
}

static void
test_socket_close (NiceSocket *sock) {
  g_free (sock->priv);
}

static NiceSocket *
test_socket_new (GSList *msg_data)
{
  NiceSocket *sock = g_slice_new0 (NiceSocket);
  TestSocketPriv *priv = g_new0 (TestSocketPriv, 1);
  priv->msg_data = msg_data;
  priv->current_msg = msg_data;
  priv->offset = 0;

  sock->type = NICE_SOCKET_TYPE_UDP_TURN_OVER_TCP;
  sock->recv_messages = test_socket_recv_messages;
  sock->is_reliable = test_socket_is_reliable;
  sock->close = test_socket_close;
  sock->priv = (void *) priv;

  return sock;
}

#define N_RECV_MESSAGES 7

static void
tcp_turn_fragmentation (void)
{
  /* Generate some RFC4571-framed test messages. A dummy base socket will split
   * them randomly into TCP-TURN messages. Test that tcp-turn socket can
   * correctly extract and reassemble the original test data out of the TURN
   * messages. */
  GSList *test_messages = generate_test_messages ();
  NiceAddress addr;
  NiceSocket *turnsock;
  NiceSocket *testsock;

  NiceInputMessage recv_messages[N_RECV_MESSAGES];
  GInputVector recv_vectors[N_RECV_MESSAGES];
  NiceAddress recv_addr[N_RECV_MESSAGES];
  guint8 recv_buffers[N_RECV_MESSAGES][G_MAXUINT16 + sizeof (guint16)];

  gint n_messages;
  guint i;
  GSList *li;

  for (i = 0; i != N_RECV_MESSAGES; ++i) {
    recv_messages[i].buffers = &recv_vectors[i];
    recv_messages[i].from = &recv_addr[i];
    recv_messages[i].n_buffers = 1;
    recv_messages[i].length = 0;
    recv_vectors[i].buffer = &recv_buffers[i];
    recv_vectors[i].size = sizeof (recv_buffers[i]);
  }

  nice_address_set_from_string (&addr, "127.0.0.1");

  testsock = test_socket_new (test_messages);

  turnsock = nice_udp_turn_socket_new (NULL, &addr,
      testsock, &addr, "", "",
      NICE_TURN_SOCKET_COMPATIBILITY_OC2007);

  li = test_messages;
  while (li) {
    n_messages = nice_socket_recv_messages (turnsock, recv_messages,
        N_RECV_MESSAGES);

    for (i = 0; i != (guint)n_messages; ++i) {
      NiceInputMessage *message = &recv_messages[i];
      GInputVector *vec = li->data;
      if (message->length == 0) {
        continue;
      }
      g_assert_cmpint (message->length, ==, vec->size);
      g_assert_cmpmem (message->buffers->buffer, message->length,
          vec->buffer,  message->length);

      li = li->next;
    }
  }

  for (li = test_messages; li; li = li->next) {
    GInputVector *v = li->data;
    g_free (v->buffer);
    g_free (v);
  }
  g_slist_free (test_messages);

  nice_socket_free (turnsock);
  nice_socket_free (testsock);
}

int
main (int argc, char *argv[])
{
  GMainLoop *mainloop;

  g_networking_init ();

  randg = g_rand_new();
  g_test_init (&argc, &argv, NULL);

  mainloop = g_main_loop_new (NULL, TRUE);

  g_test_add_func ("/udp-turn/tcp-fragmentation", tcp_turn_fragmentation);

  g_test_run ();

  g_rand_free(randg);

  g_main_loop_unref (mainloop);

  return 0;
}
