/* Test-driver for the remote-virtual-component simulator framework
   for GDB, the GNU Debugger.

   Copyright 2006-2026 Free Software Foundation, Inc.

   This file is part of GDB.

   This program 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.

   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */

/* Avoid any problems whatsoever building this program if we're not
   also building hardware support.  */

#if !WITH_HW
int
main (int argc, char *argv[])
{
  return 2;
}
#else

/* This must come before any other includes.  */
#include "defs.h"

#include "getopt.h"
#include "libiberty.h"

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#include <sys/time.h>

#include <errno.h>

/* Not guarded in dv-sockser.c, so why here.  */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/select.h>
#include <sys/socket.h>

enum rv_command {
  RV_READ_CMD = 0,
  RV_WRITE_CMD = 1,
  RV_IRQ_CMD = 2,
  RV_MEM_RD_CMD = 3,
  RV_MEM_WR_CMD = 4,
  RV_MBOX_HANDLE_CMD = 5,
  RV_MBOX_PUT_CMD = 6,
  RV_WATCHDOG_CMD = 7
};

enum opts { OPT_PORT = 1, OPT_TIMEOUT, OPT_VERBOSE };

struct option longopts[] =
  {
    {"port", required_argument, NULL, OPT_PORT},
    {"timeout", required_argument, NULL, OPT_TIMEOUT},
    {"verbose", no_argument, NULL, OPT_VERBOSE},
    {NULL, 0, NULL, 0}
  };

int port = 10000;
time_t timeout = 30000;
char *progname = "(unknown)";
int verbose = 0;

/* Required forward-declarations.  */
static void handle_input_file (int, char *);

/* Set up a "server" listening to the port in PORT for a raw TCP
   connection.  Return a file descriptor for the connection or -1 on
   error.  */

static int setupsocket (void)
{
  int s;
  socklen_t len;
  int reuse = 1;
  struct sockaddr_in sa_in;
  struct sockaddr_in from;

  len = sizeof (from);
  memset (&from, 0, len);
  memset (&sa_in, 0, sizeof (sa_in));

  s = socket (AF_INET, SOCK_STREAM, 0);
  if (s == -1)
    return -1;

  if (setsockopt (s, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof reuse) != 0)
    return -1;

  sa_in.sin_port = htons (port);
  sa_in.sin_family = AF_INET;

  if (bind (s, (struct sockaddr *) & sa_in, sizeof sa_in) < 0)
    return -1;

  if (listen (s, 1) < 0)
    return -1;

  return accept (s, (struct sockaddr *) &from, &len);
}

/* Basic host-to-little-endian 32-bit value.  Could use the BFD
   machinery, but let's avoid it for this only dependency.  */

static void
h2le32 (unsigned char *dest, unsigned int val)
{
  dest[0] = val & 255;
  dest[1] = (val >> 8) & 255;
  dest[2] = (val >> 16) & 255;
  dest[3] = (val >> 24) & 255;
}

/* Send a blob of data.  */

static void
send_output (int fd, unsigned char *buf, int nbytes)
{
  while (nbytes > 0)
    {
      ssize_t written = write (fd, buf, nbytes);
      if (written < 0)
	{
	  fprintf (stderr, "%s: write to socket failed: %s\n",
		  progname, strerror (errno));
	  exit (2);
	}
      nbytes -= written;
    }
}

/* Receive a blob of data, NBYTES large.  Compare to the first NCOMP
   bytes of BUF; if not a match, write error message to stderr and
   exit (2).  Else put it in buf.  */

static void
expect_input (int fd, unsigned char *buf, int nbytes, int ncomp)
{
  unsigned char byt;
  int i;

  for (i = 0; i < nbytes; i++)
    {
      int r;

      do
	{
	  errno = 0;
	  r = read (fd, &byt, 1);
	}
      while (r <= 0 && (r == 0 || errno == EAGAIN));

      if (r != 1)
	{
	  fprintf (stderr, "%s: read from socket failed: %s",
		  progname, strerror (errno));
	  exit (2);
	}

      if (i < ncomp && byt != buf[i])
	{
	  int j;
	  fprintf (stderr, "%s: unexpected input,\n ", progname);
	  if (i == 0)
	    fprintf (stderr, "nothing,");
	  else
	    for (j = 0; j < i; j++)
	      fprintf (stderr, "%02x", buf[j]);
	  fprintf (stderr, "\nthen %02x instead of %02x\n", byt, buf[i]);
	  exit (2);
	}
      else
	buf[i] = byt;
    }
}

/* Handle everything about a nil-terminated line of input.
   Call exit (2) on error with error text on stderr.  */

static void
handle_input (int fd, char *buf, char *fname, int lineno)
{
  int nbytes = 0;
  int n = -1;
  char *s = buf + 2;
  unsigned int data;
  static unsigned char bytes[1024];
  int i;

  memset (bytes, 0, sizeof bytes);
  lineno++;

  if (buf[1] != ',')
    goto syntax_error;

  switch (buf[0])
    {
      /* Comment characters and empty lines.  */
    case 0: case '!': case '#':
      break;

      /* Include another file.  */
    case '@':
      handle_input_file (fd, s);
      break;

      /* Raw input (to be expected).  */
    case 'i':
      do
	{
	  n = -1;
	  sscanf (s, "%02x%n", &data, &n);
	  s += n;
	  if (n > 0)
	    bytes[nbytes++] = data;
	}
      while (n > 0);
      expect_input (fd, bytes, nbytes, nbytes);
      if (verbose)
	{
	  printf ("i,");
	  for (i = 0; i < nbytes; i++)
	    printf ("%02x", bytes[i]);
	  printf ("\n");
	}
      break;

      /* Raw output (to be written).  */
    case 'o':
      do
	{
	  n = -1;
	  sscanf (s, "%02x%n", &data, &n);
	  if (n > 0)
	    {
	      s += n;
	      bytes[nbytes++] = data;
	    }
	}
      while (n > 0);
      if (*s != 0)
	goto syntax_error;
      send_output (fd, bytes, nbytes);
      if (verbose)
	{
	  printf ("o,");
	  for (i = 0; i < nbytes; i++)
	    printf ("%02x", bytes[i]);
	  printf ("\n");
	}
      break;

      /* Read a register.  */
    case 'r':
      {
	unsigned int addr;
	sscanf (s, "%x,%x%n", &addr, &data, &n);
	if (n < 0 || s[n] != 0)
	  goto syntax_error;
	bytes[0] = 11;
	bytes[1] = 0;
	bytes[2] = RV_READ_CMD;
	h2le32 (bytes + 3, addr);
	expect_input (fd, bytes, 11, 7);
	h2le32 (bytes + 7, data);
	send_output (fd, bytes, 11);
	if (verbose)
	  printf ("r,%x,%x\n", addr, data);
      }
      break;

      /* Write a register.  */
    case 'w':
      {
	unsigned int addr;
	sscanf (s, "%x,%x%n", &addr, &data, &n);
	if (n < 0 || s[n] != 0)
	  goto syntax_error;
	bytes[0] = 11;
	bytes[1] = 0;
	bytes[2] = RV_WRITE_CMD;
	h2le32 (bytes + 3, addr);
	h2le32 (bytes + 7, data);
	expect_input (fd, bytes, 11, 11);
	send_output (fd, bytes, 11);
	if (verbose)
	  printf ("w,%x,%x\n", addr, data);
      }
      break;

      /* Wait for some milliseconds.  */
    case 't':
      {
	int del = 0;
	struct timeval to;
	sscanf (s, "%d%n", &del, &n);
	if (n < 0 || s[n] != 0 || del == 0)
	  goto syntax_error;

	to.tv_sec = del / 1000;
	to.tv_usec = (del % 1000) * 1000;

	if (select (0, NULL, NULL, NULL, &to) != 0)
	  {
	    fprintf (stderr, "%s: problem waiting for %d ms:\n %s\n",
		     progname, del, strerror (errno));
	    exit (2);
	  }
	if (verbose)
	  printf ("t,%d\n", del);
      }
      break;

      /* Expect a watchdog command.  */
    case 'W':
      if (*s != 0)
	goto syntax_error;
      bytes[0] = 3;
      bytes[1] = 0;
      bytes[2] = RV_WATCHDOG_CMD;
      expect_input (fd, bytes, 3, 3);
      if (verbose)
	printf ("W\n");
      break;

      /* Send an IRQ notification.  */
    case 'I':
      sscanf (s, "%x%n", &data, &n);
      if (n < 0 || s[n] != 0)
	goto syntax_error;
      bytes[0] = 7;
      bytes[1] = 0;
      bytes[2] = RV_IRQ_CMD;
      h2le32 (bytes + 3, data);
      send_output (fd, bytes, 7);
      if (verbose)
	printf ("I,%x\n", data);
      break;

      /* DMA store (to CPU).  */
    case 's':
      {
	unsigned int addr;
	sscanf (s, "%x,%n", &addr, &n);

	if (n < 0 || s[n] == 0)
	  goto syntax_error;
	s += n;
	do
	  {
	    n = -1;
	    sscanf (s, "%02x%n", &data, &n);
	    if (n > 0)
	      {
		s += n;
		bytes[11 + nbytes++] = data;
	      }
	  }
	while (n > 0);

	if (*s != 0)
	  goto syntax_error;
	h2le32 (bytes, nbytes + 11);
	bytes[2] = RV_MEM_WR_CMD;
	h2le32 (bytes + 3, addr);
	h2le32 (bytes + 7, nbytes);
	send_output (fd, bytes, nbytes + 11);
	if (verbose)
	  {
	    printf ("s,%x,", addr);
	    for (i = 0; i < nbytes; i++)
	      printf ("%02x", bytes[i]);
	    printf ("\n");
	  }
      }
      break;

      /* DMA load (from CPU).  */
    case 'l':
      {
	unsigned int addr;
	sscanf (s, "%x,%n", &addr, &n);

	if (n < 0 || s[n] == 0)
	  goto syntax_error;
	s += n;
	do
	  {
	    n = -1;
	    sscanf (s, "%02x%n", &data, &n);
	    if (n > 0)
	      {
		s += n;
		bytes[11 + nbytes++] = data;
	      }
	  }
	while (n > 0);

	if (*s != 0)
	  goto syntax_error;
	h2le32 (bytes, nbytes + 11);
	bytes[0] = 11;
	bytes[1] = 0;
	bytes[2] = RV_MEM_RD_CMD;
	h2le32 (bytes + 3, addr);
	h2le32 (bytes + 7, nbytes);
	send_output (fd, bytes, 11);
	bytes[0] = (nbytes + 11) & 255;
	bytes[1] = ((nbytes + 11) >> 8) & 255;
	expect_input (fd, bytes, nbytes + 11, nbytes + 11);
	if (verbose)
	  {
	    printf ("l,%x,", addr);
	    for (i = 0; i < nbytes; i++)
	      printf ("%02x", bytes[i]);
	    printf ("\n");
	  }
      }
      break;

    syntax_error:
    default:
      fprintf (stderr, "%s: invalid command line in %s:%d:\n %s",
	       progname, fname, lineno, strerror (errno));
      exit (2);
    }
}

/* Loop over the contents of FNAME, using handle_input to parse each line.
   Errors to stderr, exit (2).  */

static void
handle_input_file (int fd, char *fname)
{
  static char buf[2048] = {0};
  int lineno = 0;
  FILE *f = fopen (fname, "r");

  if (f == NULL)
    {
      fprintf (stderr, "%s: problem opening %s: %s\n",
	       progname, fname, strerror (errno));
      exit (2);
    }

  /* Let's cut the buffer short, so we always get a newline.  */
  while (fgets (buf, sizeof (buf) - 1, f) != NULL)
    {
      buf[strlen (buf) - 1] = 0;
      lineno++;
      handle_input (fd, buf, fname, lineno);
    }

  fclose (f);
}

int
main (int argc, char *argv[])
{
  int optc;
  int fd;
  int i;

  progname = argv[0];
  while ((optc = getopt_long (argc, argv, "", longopts, NULL)) != -1)
    switch (optc)
      {
      case OPT_PORT:
	port = atoi (optarg);
	break;

      case OPT_TIMEOUT:
	timeout = (time_t) atoi (optarg);
	break;

      case OPT_VERBOSE:
	verbose = 1;
	break;
      }

  fd = setupsocket ();
  if (fd == -1)
    {
      fprintf (stderr, "%s: problem setting up the connection: %s\n",
	       progname, strerror (errno));
      exit (2);
    }

  for (i = optind; i < argc; i++)
    handle_input_file (fd, argv[i]);

  /* FIXME: option-controlled test for remaining input?  */
  close (fd);
  return 1;
}
#endif
