/*
 *------------------------------------------------------------------
 * Copyright (c) 2020 Cisco and/or its affiliates.
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *------------------------------------------------------------------
 */

#include <stdlib.h>
#include <sys/types.h>
#include <inttypes.h>
#include <string.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <getopt.h>

#include <libmemif.h>
#include <common.h>

#define APP_NAME "loopback_example"
#define IF0_NAME "lo0"
#define IF1_NAME "lo1"

memif_connection_t intf0, intf1;
int is_reverse;
int epfd;

int
packet_generator (memif_connection_t *c, uint16_t num_pkts)
{
  int i, bi = 0;
  memif_buffer_t *mb;

  for (i = 0; (i < num_pkts) && (bi < c->tx_buf_num); i++)
    {
      mb = &c->tx_bufs[bi++];
      memset (mb->data, 1, mb->len);
    }

  return 0;
}

/* informs user about connected status. private_ctx is used by user to identify
 * connection */
int
on_connect (memif_conn_handle_t conn, void *private_ctx)
{
  INFO ("memif connected!");
  int err;

  memif_connection_t *c = (memif_connection_t *) private_ctx;

  c->is_connected = 1;
  alloc_memif_buffers (c);

  err = memif_refill_queue (conn, 0, -1, 0);
  if (err != MEMIF_ERR_SUCCESS)
    {
      INFO ("memif_refill_queue: %s", memif_strerror (err));
      return err;
    }

  print_memif_details (c);

  /* Once both interfaces are connected send a test packet, master -> slave.
   * Slave will use zero-copy method to reply the same pakcet back.
   * (Configured by assigning responder_zero_copy as on_interrupt callback.)
   */
  if ((intf0.is_connected == 1) && (intf1.is_connected == 1))
    {
      send_packets (is_reverse ? &intf1 : &intf0, 0, packet_generator, 1,
		    2048);
    }

  return 0;
}

/* informs user about disconnected status. private_ctx is used by user to
 * identify connection */
int
on_disconnect (memif_conn_handle_t conn, void *private_ctx)
{
  INFO ("memif disconnected!");

  memif_connection_t *c = (memif_connection_t *) private_ctx;

  c->is_connected = 0;
  free_memif_buffers (c);

  /* stop event polling thread */
  int err = memif_cancel_poll_event (memif_get_socket_handle (conn));
  if (err != MEMIF_ERR_SUCCESS)
    INFO ("We are doomed...");

  return 0;
}

int
verify_packet (memif_conn_handle_t conn, void *private_ctx, uint16_t qid)
{
  memif_connection_t *c = (memif_connection_t *) private_ctx;
  int err;
  void *want;

  err = memif_rx_burst (conn, qid, c->rx_bufs, MAX_MEMIF_BUFS, &c->rx_buf_num);
  if (err != MEMIF_ERR_SUCCESS)
    {
      INFO ("meif_rx_burst: %s", memif_strerror (err));
      return err;
    }

  want = malloc (c->rx_bufs[0].len);
  if (want == NULL)
    {
      INFO ("Out of memory");
      goto done;
    }

  memset (want, 1, c->rx_bufs[0].len);

  err = memcmp (c->rx_bufs[0].data, want, c->rx_bufs[0].len);
  if (err != 0)
    {
      INFO ("Received malformed data. ret: %d", err);
    }
  else
    {
      INFO ("Received correct data.");
    }

done:
  err = memif_refill_queue (conn, qid, c->rx_buf_num, 0);
  if (err != MEMIF_ERR_SUCCESS)
    INFO ("memif_refill_queue: %s", memif_strerror (err));

  /* stop polling and exit the program */
  INFO ("Stopping the program");
  err = memif_cancel_poll_event (memif_get_socket_handle (conn));
  if (err != MEMIF_ERR_SUCCESS)
    INFO ("We are doomed...");

  return err;
}

int
create_memif_interface (memif_socket_handle_t memif_socket,
			const char *if_name, int id, uint8_t is_master,
			memif_connection_t *ctx)
{
  memif_conn_args_t memif_conn_args = { 0 };
  int err;

  memif_conn_args.socket = memif_socket;
  memif_conn_args.interface_id = id;
  strncpy (memif_conn_args.interface_name, if_name,
	   sizeof (memif_conn_args.interface_name));
  memif_conn_args.is_master = is_master;

  err = memif_create (&ctx->conn, &memif_conn_args, on_connect, on_disconnect,
		      is_master ? verify_packet : responder_zero_copy,
		      (void *) ctx);
  if (err != MEMIF_ERR_SUCCESS)
    {
      INFO ("memif_create_socket: %s", memif_strerror (err));
      return err;
    }

  return 0;
}

void
print_help ()
{
  printf ("LIBMEMIF EXAMPLE APP: %s", APP_NAME);
#ifdef ICMP_DBG
  printf (" (debug)");
#endif
  printf ("\n");
  printf ("==============================\n");
  printf ("libmemif version: %s", LIBMEMIF_VERSION);
#ifdef MEMIF_DBG
  printf (" (debug)");
#endif
  printf ("\n");

  printf ("memif version: %s\n", memif_get_version_str ());
  printf ("==============================\n");
  printf ("In this example, two memif endpoints are connected to create a "
	  "loopback.\n");
  printf ("Once connected, a test packet is sent out the memif master "
	  "interface to\n");
  printf (
    "the memif slave interface, which replies with the same packet in a\n");
  printf ("zero-copy way.\n");
  printf (
    "In reverse mode, the packet is sent from the slave interface and is\n");
  printf ("looped back by the master interface.\n");
  printf ("==============================\n");
  printf ("Usage: loopback [OPTIONS]\n\n");
  printf ("Options:\n");
  printf ("\t-r\tReverse mode, verification packet is sent by slave.\n");
  printf ("\t-?\tShow help and exit.\n");
  printf ("\t-v\tShow libmemif and memif version information and exit.\n");
}

int
main (int argc, char *argv[])
{
  memif_socket_args_t memif_socket_args = { 0 };
  memif_socket_handle_t memif_socket;
  int opt, err, ret = 0;
  is_reverse = 0;

  while ((opt = getopt (argc, argv, "r?v")) != -1)
    {
      switch (opt)
	{
	case 'r':
	  is_reverse = 1;
	  break;
	case '?':
	  print_help ();
	  return 0;
	case 'v':
	  print_version ();
	  return 0;
	}
    }

  /** Create memif socket
   *
   * Interfaces are internally stored in a database referenced by memif socket.
   */
  /* Abstract socket supported */
  memif_socket_args.path[0] = '@';
  strncpy (memif_socket_args.path + 1, APP_NAME, strlen (APP_NAME));
  /* Set application name */
  strncpy (memif_socket_args.app_name, APP_NAME, strlen (APP_NAME));

  err = memif_create_socket (&memif_socket, &memif_socket_args, NULL);
  if (err != MEMIF_ERR_SUCCESS)
    {
      INFO ("memif_create_socket: %s", memif_strerror (err));
      goto error;
    }

  /** Create memif interfaces
   *
   * Both interaces are assigned the same socket and same id to create a
   * loopback.
   */

  /* prepare the private data */
  memset (&intf0, 0, sizeof (intf0));
  memset (&intf1, 0, sizeof (intf1));
  if (is_reverse)
    {
      intf0.packet_handler = basic_packet_handler;
    }
  else
    {
      intf1.packet_handler = basic_packet_handler;
    }

  err =
    create_memif_interface (memif_socket, IF0_NAME, 0, /* master */ 1, &intf0);
  if (err != 0)
    {
      goto error;
    }

  err =
    create_memif_interface (memif_socket, IF1_NAME, 0, /* slave */ 0, &intf1);
  if (err != 0)
    {
      goto error;
    }

  do
    {
      err = memif_poll_event (memif_socket, -1);
    }
  while (err == MEMIF_ERR_SUCCESS);

  return 0;

error:
  ret = -1;
done:
  free_memif_buffers (&intf0);
  free_memif_buffers (&intf1);
  memif_delete (&intf0.conn);
  memif_delete (&intf1.conn);
  memif_delete_socket (&memif_socket);
  return ret;
}