/* Low-level RSP routines for GDB, the GNU debugger.

   Copyright (C) 1988-2018 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/>.  */

#include "common-defs.h"
#include "rsp-low.h"

/* See rsp-low.h.  */

int
fromhex (int a)
{
  if (a >= '0' && a <= '9')
    return a - '0';
  else if (a >= 'a' && a <= 'f')
    return a - 'a' + 10;
  else if (a >= 'A' && a <= 'F')
    return a - 'A' + 10;
  else
    error (_("Reply contains invalid hex digit %d"), a);
}

/* See rsp-low.h.  */

int
tohex (int nib)
{
  if (nib < 10)
    return '0' + nib;
  else
    return 'a' + nib - 10;
}

/* Encode 64 bits in 16 chars of hex.  */

static const char hexchars[] = "0123456789abcdef";

static int
ishex (int ch, int *val)
{
  if ((ch >= 'a') && (ch <= 'f'))
    {
      *val = ch - 'a' + 10;
      return 1;
    }
  if ((ch >= 'A') && (ch <= 'F'))
    {
      *val = ch - 'A' + 10;
      return 1;
    }
  if ((ch >= '0') && (ch <= '9'))
    {
      *val = ch - '0';
      return 1;
    }
  return 0;
}

/* See rsp-low.h.  */

char *
pack_nibble (char *buf, int nibble)
{
  *buf++ = hexchars[(nibble & 0x0f)];
  return buf;
}

/* See rsp-low.h.  */

char *
pack_hex_byte (char *pkt, int byte)
{
  *pkt++ = hexchars[(byte >> 4) & 0xf];
  *pkt++ = hexchars[(byte & 0xf)];
  return pkt;
}

/* See rsp-low.h.  */

const char *
unpack_varlen_hex (const char *buff,	/* packet to parse */
		   ULONGEST *result)
{
  int nibble;
  ULONGEST retval = 0;

  while (ishex (*buff, &nibble))
    {
      buff++;
      retval = retval << 4;
      retval |= nibble & 0x0f;
    }
  *result = retval;
  return buff;
}

/* See rsp-low.h.  */

int
hex2bin (const char *hex, gdb_byte *bin, int count)
{
  int i;

  for (i = 0; i < count; i++)
    {
      if (hex[0] == 0 || hex[1] == 0)
	{
	  /* Hex string is short, or of uneven length.
	     Return the count that has been converted so far.  */
	  return i;
	}
      *bin++ = fromhex (hex[0]) * 16 + fromhex (hex[1]);
      hex += 2;
    }
  return i;
}

/* See rsp-low.h.  */

gdb::byte_vector
hex2bin (const char *hex)
{
  size_t bin_len = strlen (hex) / 2;
  gdb::byte_vector bin (bin_len);

  hex2bin (hex, bin.data (), bin_len);

  return bin;
}

/* See rsp-low.h.  */

std::string
hex2str (const char *hex)
{
  std::string ret;
  size_t len = strlen (hex);

  ret.reserve (len / 2);
  for (size_t i = 0; i < len; ++i)
    {
      if (hex[0] == '\0' || hex[1] == '\0')
	{
	  /* Hex string is short, or of uneven length.  Return what we
	     have so far.  */
	  return ret;
	}
      ret += fromhex (hex[0]) * 16 + fromhex (hex[1]);
      hex += 2;
    }

  return ret;
}

/* See rsp-low.h.  */

int
bin2hex (const gdb_byte *bin, char *hex, int count)
{
  int i;

  for (i = 0; i < count; i++)
    {
      *hex++ = tohex ((*bin >> 4) & 0xf);
      *hex++ = tohex (*bin++ & 0xf);
    }
  *hex = 0;
  return i;
}

/* See rsp-low.h.  */

std::string
bin2hex (const gdb_byte *bin, int count)
{
  std::string ret;

  ret.reserve (count * 2);
  for (int i = 0; i < count; ++i)
    {
      ret += tohex ((*bin >> 4) & 0xf);
      ret += tohex (*bin++ & 0xf);
    }

  return ret;
}

/* Return whether byte B needs escaping when sent as part of binary data.  */

static int
needs_escaping (gdb_byte b)
{
  return b == '$' || b == '#' || b == '}' || b == '*';
}

/* See rsp-low.h.  */

int
remote_escape_output (const gdb_byte *buffer, int len_units, int unit_size,
		      gdb_byte *out_buf, int *out_len_units,
		      int out_maxlen_bytes)
{
  int input_unit_index, output_byte_index = 0, byte_index_in_unit;
  int number_escape_bytes_needed;

  /* Try to copy integral addressable memory units until
     (1) we run out of space or
     (2) we copied all of them.  */
  for (input_unit_index = 0;
       input_unit_index < len_units;
       input_unit_index++)
    {
      /* Find out how many escape bytes we need for this unit.  */
      number_escape_bytes_needed = 0;
      for (byte_index_in_unit = 0;
	   byte_index_in_unit < unit_size;
	   byte_index_in_unit++)
	{
	  int idx = input_unit_index * unit_size + byte_index_in_unit;
	  gdb_byte b = buffer[idx];
	  if (needs_escaping (b))
	    number_escape_bytes_needed++;
	}

      /* Check if we have room to fit this escaped unit.  */
      if (output_byte_index + unit_size + number_escape_bytes_needed >
	    out_maxlen_bytes)
	  break;

      /* Copy the unit byte per byte, adding escapes.  */
      for (byte_index_in_unit = 0;
	   byte_index_in_unit < unit_size;
	   byte_index_in_unit++)
	{
	  int idx = input_unit_index * unit_size + byte_index_in_unit;
	  gdb_byte b = buffer[idx];
	  if (needs_escaping (b))
	    {
	      out_buf[output_byte_index++] = '}';
	      out_buf[output_byte_index++] = b ^ 0x20;
	    }
	  else
	    out_buf[output_byte_index++] = b;
	}
    }

  *out_len_units = input_unit_index;
  return output_byte_index;
}

/* See rsp-low.h.  */

int
remote_unescape_input (const gdb_byte *buffer, int len,
		       gdb_byte *out_buf, int out_maxlen)
{
  int input_index, output_index;
  int escaped;

  output_index = 0;
  escaped = 0;
  for (input_index = 0; input_index < len; input_index++)
    {
      gdb_byte b = buffer[input_index];

      if (output_index + 1 > out_maxlen)
	error (_("Received too much data from the target."));

      if (escaped)
	{
	  out_buf[output_index++] = b ^ 0x20;
	  escaped = 0;
	}
      else if (b == '}')
	escaped = 1;
      else
	out_buf[output_index++] = b;
    }

  if (escaped)
    error (_("Unmatched escape character in target response."));

  return output_index;
}

