/* vim: et ts=2 sw=2 tw=80: */
/*
 * This file is part of the Nice GLib ICE library.
 *
 * (C) 2010, 2014 Collabora Ltd.
 *  Contact: Philip Withnall
 *
 * 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.
 *
 * Contributors:
 *   Philip Withnall, Collabora Ltd.
 *   Youness Alaoui, Collabora Ltd.
 *
 * 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 <locale.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <math.h>

#include "pseudotcp.h"


/**
 * A fuzzing test for the pseudotcp socket. This connects two sockets in a
 * loopback arrangement, with the packet output from one being fed to the other,
 * and vice-versa. Fuzzing happens on the packet interface between the two,
 * mutating the packets slightly and seeing what happens.
 *
 * The input data to the left-most socket is read from a file. The output data
 * from the loopback is written to another file, although this probably isn’t
 * very useful. If no files are provided, a small amount of dummy data is sent
 * through the sockets instead. This almost certainly won’t catch any bugs, and
 * is just present to allow this test to be run as part of `make check` so it
 * doesn’t bit rot.
 *
 * A good command to generate an input file is:
 *     dd if=/dev/urandom of=rand count=10000 ibs=1024
 *
 * None of the data is validated, and the test results are effectively the 1-bit
 * value of ‘did it crash?’. In particular, the output file is not validated,
 * and the TCP packets emitted by both sockets are not checked for validity.
 *
 * It is suggested that this test is run under GDB and Valgrind. Any crashes or
 * errors which are detected can be reproduced by providing the same input file
 * and seed (using the --seed option). The seed is printed out at the beginning
 * of each test run.
 */


PseudoTcpSocket *left;
PseudoTcpSocket *right;
GMainLoop *main_loop = NULL;
GRand *prng = NULL;
gint retval = 0;
FILE *in = NULL;
FILE *out = NULL;
int total_read = 0;
int total_wrote = 0;
guint left_clock = 0;
guint right_clock = 0;
gboolean left_closed = FALSE;
gboolean right_closed = FALSE;
gboolean reading_done = FALSE;

/* Number of bytes of payload each socket has received so far. */
guint32 left_stream_pos = 0;
guint32 right_stream_pos = 0;

/* Configuration options. */
gint64 seed = 0;
guint32 fuzz_start_pos = 1;  /* bytes into stream payload; after the SYN-ACKs */
guint n_changes_lambda = 1;  /* lambda parameter for a Poisson distribution
                              * controlling the number of mutations made to each
                              * packet */


static void
adjust_clock (PseudoTcpSocket *sock);


static void
write_to_sock (PseudoTcpSocket *sock)
{
  gchar buf[1024];
  gsize len;
  gint wlen;
  guint total = 0;

  while (TRUE) {
    len = fread (buf, 1, sizeof(buf), in);
    if (len == 0) {
      g_debug ("Done reading data from file");
      g_assert (feof (in));
      reading_done = TRUE;
      pseudo_tcp_socket_close (sock, FALSE);
      break;
    } else {
      wlen = pseudo_tcp_socket_send (sock, buf, len);
      g_debug ("Sending %" G_GSIZE_FORMAT " bytes : %d", len, wlen);
      total += wlen;
      total_read += wlen;
      if (wlen < (gint) len) {
        g_debug ("seeking  %" G_GSIZE_FORMAT " from %lu", wlen - len, ftell (in));
        fseek (in, wlen - len, SEEK_CUR);
        g_assert (!feof (in));
        g_debug ("Socket queue full after %d bytes written", total);
        break;
      }
    }
  }
  adjust_clock (sock);
}

static void
opened (PseudoTcpSocket *sock, gpointer data)
{
  g_debug ("Socket %p Opened", sock);
  if (sock == left) {
    if (in)
      write_to_sock (sock);
    else {
      pseudo_tcp_socket_send (sock, "abcdefghijklmnopqrstuvwxyz", 26);
      reading_done = TRUE;
      pseudo_tcp_socket_close (sock, FALSE);
    }
  }
}

static void
readable (PseudoTcpSocket *sock, gpointer data)
{
  gchar buf[1024];
  gint len;
  g_debug ("Socket %p Readable", sock);

  do {
    len = pseudo_tcp_socket_recv (sock, buf, sizeof(buf));

    if (len > 0) {
      g_debug ("Read %d bytes", len);
      if (out) {
        if (fwrite (buf, len, 1, out) == 0)
          g_debug ("Error writing to output file");
        else {
          total_wrote += len;

          g_assert_cmpint (total_wrote, <=, total_read);
          g_debug ("Written %d bytes, need %d bytes", total_wrote, total_read);
          if (total_wrote == total_read && feof (in)) {
            g_assert (reading_done);
            pseudo_tcp_socket_close (sock, FALSE);
          }
        }
      } else {
        pseudo_tcp_socket_close (sock, FALSE);
      }
    } else if (len == 0) {
      pseudo_tcp_socket_close (sock, FALSE);
    }
  } while (len > 0);

  if (len == -1 &&
      pseudo_tcp_socket_get_error (sock) != EWOULDBLOCK) {
    g_printerr ("Error reading from socket %p: %s.\n",
        sock, g_strerror (pseudo_tcp_socket_get_error (sock)));

    retval = -1;
    g_main_loop_quit (main_loop);
    return;
  }
}

static void
writable (PseudoTcpSocket *sock, gpointer data)
{
  g_debug ("Socket %p Writable", sock);
  if (in && sock == left)
    write_to_sock (sock);
}

static void
closed (PseudoTcpSocket *sock, guint32 err, gpointer data)
{
  /* Don’t treat this as an error, since we’re throwing rubbish into the
   * socket and can hardly expect it to complete successfully. */
  g_debug ("Socket %p Closed: %s", sock, g_strerror (err));
  retval = 0;
  g_main_loop_quit (main_loop);
}

struct notify_data {
  PseudoTcpSocket *sock;
  guint32 len;
  guint32 stream_pos;
  guint8 buffer[];
};

/**
 * random_int_poisson:
 * @lambda: Lambda parameter for the distribution function, which must be
 * non-zero
 *
 * Generate a random variable from a Poisson distribution with parameter
 * @lambda. This consumes one %gdouble’s worth of randomness from the global
 * @prng.
 *
 * This is implemented using the inverse transform of the Poisson CDF, and is
 * guaranteed to return in time linearly proportional to @lambda.
 *
 * Returns: Poisson-distributed pseudo-random variable
 */
static guint32
random_int_poisson (guint lambda)
{
  gdouble U;
  guint32 i;
  gdouble p, F;

  g_return_val_if_fail (lambda > 0, 0);

  /*
   * Reference: http://www.cs.bgu.ac.il/~mps042/invtransnote.htm,
   * §Simulating a Poisson random variable.
   */
  U = g_rand_double (prng);  /* step 1 */
  i = 0;
  p = exp (0.0 - (gdouble) lambda);
  F = p;  /* step 2 */

  while (U >= F) {  /* step 3 */
    p = (lambda * p) / (i + 1);
    F += p;
    i += 1;  /* step 4 and 5 */
  }

  return i;
}

static guint32
fuzz_packet (guint8 *buf, guint32 len, guint32 stream_pos)
{
  guint32 i;
  guint n_changes;
#define HEADER_LENGTH 24 /* bytes; or thereabouts (include some options) */

  /* Do we want to fuzz this packet? */
  if (stream_pos < fuzz_start_pos) {
    return len;
  }

  /* Get fuzzing. Only bother fuzzing the header; fuzzing the payload is
   * pointless. Weight the number of changes towards having only a few changes,
   * since that makes them less likely to be summarily rejected. */
  n_changes = random_int_poisson (n_changes_lambda);
  g_debug ("Making %u changes for bytes at stream position %u:",
      n_changes, stream_pos);

  for (i = 0; i < n_changes; i++) {
    guint32 pos = g_rand_int_range (prng, 0, MIN (len, HEADER_LENGTH));
    g_debug (" • Changing byte %u.", stream_pos + pos);
    buf[pos] = g_rand_int_range (prng, 0, G_MAXUINT8 + 1);
  }

  return len;
}

static gboolean
notify_packet (gpointer user_data)
{
  struct notify_data *data = (struct notify_data*) user_data;

  /* Fuzz the packet. */
  data->len = fuzz_packet (data->buffer, data->len, data->stream_pos);

  pseudo_tcp_socket_notify_packet (data->sock,
      (gchar *) data->buffer, data->len);
  adjust_clock (data->sock);

  g_free (data);
  return FALSE;
}

static PseudoTcpWriteResult
write_packet (PseudoTcpSocket *sock, const gchar *buffer, guint32 len,
    gpointer user_data)
{
  struct notify_data *data;
  PseudoTcpState state;
  g_object_get (sock, "state", &state, NULL);

  data = g_malloc (sizeof(struct notify_data) + len);

  g_debug ("Socket %p(%d) Writing : %d bytes", sock, state, len);

  memcpy (data->buffer, buffer, len);
  data->len = len;

  if (sock == left) {
    data->stream_pos = left_stream_pos;
    left_stream_pos += len;
    data->sock = right;
  } else {
    data->stream_pos = right_stream_pos;
    right_stream_pos += len;
    data->sock = left;
  }

  g_idle_add (notify_packet, data);

  return WR_SUCCESS;
}


static gboolean notify_clock (gpointer data)
{
  PseudoTcpSocket *sock = (PseudoTcpSocket *)data;
  //g_debug ("Socket %p: Notifying clock", sock);
  pseudo_tcp_socket_notify_clock (sock);
  adjust_clock (sock);
  return FALSE;
}

static void adjust_clock (PseudoTcpSocket *sock)
{
  guint64 timeout = 0;

  if (pseudo_tcp_socket_get_next_clock (sock, &timeout)) {
    timeout -= g_get_monotonic_time () / 1000;
    g_debug ("Socket %p: Adjusting clock to %" G_GUINT64_FORMAT " ms", sock, timeout);
    if (sock == left) {
      if (left_clock != 0)
         g_source_remove (left_clock);
      left_clock = g_timeout_add (timeout, notify_clock, sock);
    } else {
      if (right_clock != 0)
         g_source_remove (right_clock);
      right_clock = g_timeout_add (timeout, notify_clock, sock);
    }
  } else {
    g_debug ("Socket %p should be destroyed, it's done", sock);
    if (sock == left)
      left_closed = TRUE;
    else
      right_closed = TRUE;
    if (left_closed && right_closed)
      g_main_loop_quit (main_loop);
  }
}

static GOptionEntry entries[] = {
  { "seed", 's', 0, G_OPTION_ARG_INT64, &seed, "PRNG seed", "N" },
  { "fuzz-start-position", 'p', 0, G_OPTION_ARG_INT, &fuzz_start_pos,
    "Number of bytes into the stream to start fuzzing after", "B" },
  { "fuzz-n-changes-lambda", 'l', 0, G_OPTION_ARG_INT, &n_changes_lambda,
    "Lambda value for the Poisson distribution controlling the number of "
    "changes made to each packet", "λ" },
  { NULL }
};

int main (int argc, char *argv[])
{
  PseudoTcpCallbacks cbs = {
    NULL, opened, readable, writable, closed, write_packet
  };
  GOptionContext *context;
  GError *error = NULL;

  setlocale (LC_ALL, "");

  /* Configuration. */
  context = g_option_context_new ("— fuzz-test the pseudotcp socket");
  g_option_context_add_main_entries (context, entries, NULL);

  if (!g_option_context_parse (context, &argc, &argv, &error)) {
    g_printerr ("Option parsing failed: %s\n", error->message);
    goto context_error;
  }

  if (n_changes_lambda == 0) {
    g_printerr ("Option parsing failed: %s\n",
        "Lambda values must be positive.");
    goto context_error;
  }

  g_option_context_free (context);

  /* Tweak the configuration. */
  if (seed == 0) {
    seed = g_get_real_time ();
  }

  /* Open the input and output files */
  if (argc >= 3) {
    in = fopen (argv[1], "r");
    out = fopen (argv[2], "w");
  }

  /* Set up the main loop and sockets. */
  main_loop = g_main_loop_new (NULL, FALSE);

  g_print ("Using seed: %" G_GINT64_FORMAT ", start position: %u, λ: %u\n",
      seed, fuzz_start_pos, n_changes_lambda);
  prng = g_rand_new_with_seed (seed);

  pseudo_tcp_set_debug_level (PSEUDO_TCP_DEBUG_VERBOSE);

  left = pseudo_tcp_socket_new (0, &cbs);
  right = pseudo_tcp_socket_new (0, &cbs);
  g_debug ("Left: %p. Right: %p", left, right);

  pseudo_tcp_socket_notify_mtu (left, 1496);
  pseudo_tcp_socket_notify_mtu (right, 1496);

  pseudo_tcp_socket_connect (left);
  adjust_clock (left);
  adjust_clock (right);

  /* Run the main loop. */
  g_main_loop_run (main_loop);
  g_main_loop_unref (main_loop);

  g_object_unref (left);
  g_object_unref (right);

  g_rand_free (prng);

  if (in != NULL)
    fclose (in);
  if (out != NULL)
    fclose (out);

  return retval;

context_error:
  g_printerr ("\n%s\n", g_option_context_get_help (context, TRUE, NULL));
  g_option_context_free (context);

  return 1;
}
