/* Python memory buffer interface for reading inferior memory.

   Copyright (C) 2009-2022 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 "defs.h"
#include "python-internal.h"

struct membuf_object {
  PyObject_HEAD

  /* Pointer to the raw data, and array of gdb_bytes.  */
  void *buffer;

  /* The address from where the data was read, held for mbpy_str.  */
  CORE_ADDR addr;

  /* The number of octets in BUFFER.  */
  CORE_ADDR length;
};

extern PyTypeObject membuf_object_type
    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("membuf_object");

/* Wrap BUFFER, ADDRESS, and LENGTH into a gdb.Membuf object.  ADDRESS is
   the address within the inferior that the contents of BUFFER were read,
   and LENGTH is the number of octets in BUFFER.  */

PyObject *
gdbpy_buffer_to_membuf (gdb::unique_xmalloc_ptr<gdb_byte> buffer,
			CORE_ADDR address,
			ULONGEST length)
{
  gdbpy_ref<membuf_object> membuf_obj (PyObject_New (membuf_object,
						     &membuf_object_type));
  if (membuf_obj == nullptr)
    return nullptr;

  membuf_obj->buffer = buffer.release ();
  membuf_obj->addr = address;
  membuf_obj->length = length;

  PyObject *result;
#ifdef IS_PY3K
  result = PyMemoryView_FromObject ((PyObject *) membuf_obj.get ());
#else
  result = PyBuffer_FromReadWriteObject ((PyObject *) membuf_obj.get (), 0,
					 Py_END_OF_BUFFER);
#endif

  return result;
}

/* Destructor for gdb.Membuf objects.  */

static void
mbpy_dealloc (PyObject *self)
{
  xfree (((membuf_object *) self)->buffer);
  Py_TYPE (self)->tp_free (self);
}

/* Return a description of the gdb.Membuf object.  */

static PyObject *
mbpy_str (PyObject *self)
{
  membuf_object *membuf_obj = (membuf_object *) self;

  return PyString_FromFormat (_("Memory buffer for address %s, \
which is %s bytes long."),
			      paddress (python_gdbarch, membuf_obj->addr),
			      pulongest (membuf_obj->length));
}

#ifdef IS_PY3K

static int
get_buffer (PyObject *self, Py_buffer *buf, int flags)
{
  membuf_object *membuf_obj = (membuf_object *) self;
  int ret;

  ret = PyBuffer_FillInfo (buf, self, membuf_obj->buffer,
			   membuf_obj->length, 0,
			   PyBUF_CONTIG);

  /* Despite the documentation saying this field is a "const char *",
     in Python 3.4 at least, it's really a "char *".  */
  buf->format = (char *) "c";

  return ret;
}

#else

static Py_ssize_t
get_read_buffer (PyObject *self, Py_ssize_t segment, void **ptrptr)
{
  membuf_object *membuf_obj = (membuf_object *) self;

  if (segment)
    {
      PyErr_SetString (PyExc_SystemError,
		       _("The memory buffer supports only one segment."));
      return -1;
    }

  *ptrptr = membuf_obj->buffer;

  return membuf_obj->length;
}

static Py_ssize_t
get_write_buffer (PyObject *self, Py_ssize_t segment, void **ptrptr)
{
  return get_read_buffer (self, segment, ptrptr);
}

static Py_ssize_t
get_seg_count (PyObject *self, Py_ssize_t *lenp)
{
  if (lenp)
    *lenp = ((membuf_object *) self)->length;

  return 1;
}

static Py_ssize_t
get_char_buffer (PyObject *self, Py_ssize_t segment, char **ptrptr)
{
  void *ptr = nullptr;
  Py_ssize_t ret;

  ret = get_read_buffer (self, segment, &ptr);
  *ptrptr = (char *) ptr;

  return ret;
}

#endif	/* IS_PY3K */

/* General Python initialization callback.  */

int
gdbpy_initialize_membuf (void)
{
  membuf_object_type.tp_new = PyType_GenericNew;
  if (PyType_Ready (&membuf_object_type) < 0)
    return -1;

  return gdb_pymodule_addobject (gdb_module, "Membuf",
				 (PyObject *) &membuf_object_type);
}

#ifdef IS_PY3K

static PyBufferProcs buffer_procs =
{
  get_buffer
};

#else

static PyBufferProcs buffer_procs = {
  get_read_buffer,
  get_write_buffer,
  get_seg_count,
  get_char_buffer
};

#endif	/* IS_PY3K */

PyTypeObject membuf_object_type = {
  PyVarObject_HEAD_INIT (nullptr, 0)
  "gdb.Membuf",			  /*tp_name*/
  sizeof (membuf_object),	  /*tp_basicsize*/
  0,				  /*tp_itemsize*/
  mbpy_dealloc,			  /*tp_dealloc*/
  0,				  /*tp_print*/
  0,				  /*tp_getattr*/
  0,				  /*tp_setattr*/
  0,				  /*tp_compare*/
  0,				  /*tp_repr*/
  0,				  /*tp_as_number*/
  0,				  /*tp_as_sequence*/
  0,				  /*tp_as_mapping*/
  0,				  /*tp_hash */
  0,				  /*tp_call*/
  mbpy_str,			  /*tp_str*/
  0,				  /*tp_getattro*/
  0,				  /*tp_setattro*/
  &buffer_procs,		  /*tp_as_buffer*/
  Py_TPFLAGS_DEFAULT,		  /*tp_flags*/
  "GDB memory buffer object", 	  /*tp_doc*/
  0,				  /* tp_traverse */
  0,				  /* tp_clear */
  0,				  /* tp_richcompare */
  0,				  /* tp_weaklistoffset */
  0,				  /* tp_iter */
  0,				  /* tp_iternext */
  0,				  /* tp_methods */
  0,				  /* tp_members */
  0,				  /* tp_getset */
  0,				  /* tp_base */
  0,				  /* tp_dict */
  0,				  /* tp_descr_get */
  0,				  /* tp_descr_set */
  0,				  /* tp_dictoffset */
  0,				  /* tp_init */
  0,				  /* tp_alloc */
};
