/*
     This file is part of GNUnet.
     Copyright (C) 2006, 2009, 2013 GNUnet e.V.

     GNUnet is free software: you can redistribute it and/or modify it
     under the terms of the GNU Affero General Public License as published
     by the Free Software Foundation, either version 3 of the License,
     or (at your option) any later version.

     GNUnet 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
     Affero General Public License for more details.

     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

     SPDX-License-Identifier: AGPL3.0-or-later
 */
/**
 * @file util/bio.c
 * @brief functions for buffering IO
 * @author Christian Grothoff
 */

#include "platform.h"
#include "gnunet_util_lib.h"

#define LOG(kind, ...) GNUNET_log_from (kind, "util-bio", __VA_ARGS__)

#ifndef PATH_MAX
/**
 * Assumed maximum path length (for source file names).
 */
#define PATH_MAX 4096
#endif


/**
 * Size for I/O buffers.
 */
#define BIO_BUFFER_SIZE 65536


/**
 * Enum used internally to know how buffering is handled.
 *
 * The idea is that by using an enum, BIO can be extended to support other
 * kinds of "backend" for buffering (or just formatted I/O.)
 */
enum IOType
{
  /**
   * The handle uses a file to read/write data.
   */
  IO_FILE = 0,

  /**
   * The data is stored entirely in memory.
   */
  IO_BUFFER,
};


/**
 * Handle for buffered reading.
 */
struct GNUNET_BIO_ReadHandle
{
  /**
   * The "backend" type.
   */
  enum IOType type;

  /**
   * Handle to a file on disk, if @e type is #IO_FILE.
   */
  struct GNUNET_DISK_FileHandle *fd;

  /**
   * Error message, NULL if there were no errors.
   */
  char *emsg;

  /**
   * I/O buffer.  Do @b not free!
   */
  char *buffer;

  /**
   * Number of bytes available in @e buffer.
   */
  size_t have;

  /**
   * Total size of @e buffer.
   */
  size_t size;

  /**
   * Current read offset in @e buffer.
   */
  off_t pos;
};


/**
 * Open a file for reading.
 *
 * @param fn file name to be opened
 * @return IO handle on success, NULL on error
 */
struct GNUNET_BIO_ReadHandle *
GNUNET_BIO_read_open_file (const char *fn)
{
  struct GNUNET_DISK_FileHandle *fd;
  struct GNUNET_BIO_ReadHandle *h;

  fd = GNUNET_DISK_file_open (fn, GNUNET_DISK_OPEN_READ, GNUNET_DISK_PERM_NONE);
  if (NULL == fd)
    return NULL;
  h = GNUNET_malloc (sizeof(struct GNUNET_BIO_ReadHandle) + BIO_BUFFER_SIZE);
  h->type = IO_FILE;
  h->buffer = (char *) &h[1];
  h->size = BIO_BUFFER_SIZE;
  h->fd = fd;
  return h;
}


/**
 * Create a handle from an existing allocated buffer.
 *
 * @param buffer the buffer to use as source
 * @param size the total size in bytes of the buffer
 * @return IO handle on success, NULL on error
 */
struct GNUNET_BIO_ReadHandle *
GNUNET_BIO_read_open_buffer (void *buffer, size_t size)
{
  struct GNUNET_BIO_ReadHandle *h;

  h = GNUNET_new (struct GNUNET_BIO_ReadHandle);
  h->type = IO_BUFFER;
  h->buffer = buffer;
  h->size = size;
  return h;
}


/**
 * Close an open handle.  Reports if any errors reading
 * from the file were encountered.
 *
 * @param h file handle
 * @param emsg set to the (allocated) error message
 *             if the handle has an error message, the return
 *             value is #GNUNET_SYSERR
 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
 */
int
GNUNET_BIO_read_close (struct GNUNET_BIO_ReadHandle *h, char **emsg)
{
  int err;

  err = (NULL == h->emsg) ? GNUNET_OK : GNUNET_SYSERR;
  if (NULL != emsg)
    *emsg = h->emsg;
  else
    GNUNET_free (h->emsg);
  switch (h->type)
  {
  case IO_FILE:
    GNUNET_DISK_file_close (h->fd);
    break;
  case IO_BUFFER:
    break;
  default:
    break;
  }
  GNUNET_free (h);
  return err;
}

void
GNUNET_BIO_read_set_error (struct GNUNET_BIO_ReadHandle *h, const char* emsg)
{
  GNUNET_assert (NULL == h->emsg);
  h->emsg = GNUNET_strdup (emsg);
}


/**
 * Function used internally to read the contents of a file into a buffer.
 *
 * @param h the IO handle to read from
 * @param what describes what is being read (for error message creation)
 * @param result the buffer to write the data to
 * @param len the number of bytes to read
 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
 */
static int
read_from_file (struct GNUNET_BIO_ReadHandle *h,
                const char *what,
                char *result,
                size_t len)
{
  size_t pos = 0;
  size_t min;
  ssize_t ret;

  do
  {
    min = h->have - h->pos;
    if (0 < min)
    {
      if (len - pos < min)
        min = len - pos;
      GNUNET_memcpy (&result[pos], &h->buffer[h->pos], min);
      h->pos += min;
      pos += min;
    }
    if (len == pos)
      return GNUNET_OK;
    GNUNET_assert (((off_t) h->have) == h->pos);
    ret = GNUNET_DISK_file_read (h->fd, h->buffer, h->size);
    if (-1 == ret)
    {
      GNUNET_asprintf (&h->emsg,
                       _ ("Error reading `%s' from file: %s"),
                       what,
                       strerror (errno));
      return GNUNET_SYSERR;
    }
    if (0 == ret)
    {
      GNUNET_asprintf (&h->emsg,
                       _ ("Error reading `%s' from file: %s"),
                       what,
                       _ ("End of file"));
      return GNUNET_SYSERR;
    }
    h->pos = 0;
    h->have = ret;
  }
  while (pos < len);
  return GNUNET_OK;
}


/**
 * Function used internally to read the content of a buffer into a buffer.
 *
 * @param h the IO handle to read from
 * @param what describes what is being read (for error message creation)
 * @param result the buffer to write the result to
 * @param len the number of bytes to read
 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
 */
static int
read_from_buffer (struct GNUNET_BIO_ReadHandle *h,
                  const char *what,
                  char *result,
                  size_t len)
{
  if ((h->size < len) || (h->size - h->pos < len))
  {
    GNUNET_asprintf (&h->emsg,
                     _ ("Error while reading `%s' from buffer: %s"),
                     what,
                     _ ("Not enough data left"));
    return GNUNET_SYSERR;
  }
  GNUNET_memcpy (result, h->buffer + h->pos, len);
  h->pos += len;
  return GNUNET_OK;
}


/**
 * Read some contents into a buffer.
 *
 * @param h the IO handle to read from
 * @param what describes what is being read (for error message creation)
 * @param result the buffer to write the result to
 * @param len the number of bytes to read
 * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
 */
int
GNUNET_BIO_read (struct GNUNET_BIO_ReadHandle *h,
                 const char *what,
                 void *result,
                 size_t len)
{
  char *dst = result;

  if (NULL != h->emsg)
    return GNUNET_SYSERR;

  if (0 == len)
    return GNUNET_OK;

  switch (h->type)
  {
  case IO_FILE:
    return read_from_file (h, what, dst, len);
  case IO_BUFFER:
    return read_from_buffer (h, what, dst, len);
  default:
    GNUNET_asprintf (&h->emsg,
                     _ ("Invalid handle type while reading `%s'"),
                     what);
    return GNUNET_SYSERR;
  }
}


/**
 * Read 0-terminated string.
 *
 * @param h the IO handle to read from
 * @param what describes what is being read (for error message creation)
 * @param result where to store the pointer to the (allocated) string
 *        (note that *result could be set to NULL as well)
 * @param max_length maximum allowed length for the string
 * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
 */
int
GNUNET_BIO_read_string (struct GNUNET_BIO_ReadHandle *h,
                        const char *what,
                        char **result,
                        size_t max_length)
{
  char *buf;
  uint32_t big;

  if (GNUNET_OK != GNUNET_BIO_read_int32 (h,
                                          _ ("string length"),
                                          (int32_t *) &big))
  {
    char *tmp = h->emsg;
    if (NULL != tmp)
      GNUNET_asprintf (&h->emsg,
                       _ ("%s (while reading `%s')"),
                       tmp,
                       what);
    else
      GNUNET_asprintf (&h->emsg,
                       _ ("Error reading length of string `%s'"),
                       what);
    GNUNET_free (tmp);
    return GNUNET_SYSERR;
  }
  if (0 == big)
  {
    *result = NULL;
    return GNUNET_OK;
  }
  if (big > max_length)
  {
    GNUNET_asprintf (&h->emsg,
                     _ ("String `%s' longer than allowed (%u > %lu)"),
                     what,
                     big,
                     (unsigned long) max_length);
    return GNUNET_SYSERR;
  }
  buf = GNUNET_malloc (big);
  *result = buf;
  buf[--big] = '\0';
  if (0 == big)
    return GNUNET_OK;
  if (GNUNET_OK != GNUNET_BIO_read (h, what, buf, big))
  {
    GNUNET_free (buf);
    *result = NULL;
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}



/**
 * Read a float.
 *
 * @param h the IO handle to read from
 * @param what describes what is being read (for error message creation)
 * @param f address of float to read
 */
int
GNUNET_BIO_read_float (struct GNUNET_BIO_ReadHandle *h,
                       const char *what,
                       float *f)
{
  int32_t *i = (int32_t *) f;
  return GNUNET_BIO_read_int32 (h, what, i);
}


/**
 * Read a double.
 *
 * @param h the IO handle to read from
 * @param what describes what is being read (for error message creation)
 * @param f address of double to read
 */
int
GNUNET_BIO_read_double (struct GNUNET_BIO_ReadHandle *h,
                        const char *what,
                        double *f)
{
  int64_t *i = (int64_t *) f;
  return GNUNET_BIO_read_int64 (h, what, i);
}


/**
 * Read an (u)int32_t.
 *
 * @param h the IO handle to read from
 * @param what describes what is being read (for error message creation)
 * @param i where to store the data
 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
 */
int
GNUNET_BIO_read_int32 (struct GNUNET_BIO_ReadHandle *h,
                       const char *what,
                       int32_t *i)
{
  int32_t big;

  if (GNUNET_OK != GNUNET_BIO_read (h, what, &big, sizeof(int32_t)))
    return GNUNET_SYSERR;
  *i = ntohl (big);
  return GNUNET_OK;
}


/**
 * Read an (u)int64_t.
 *
 * @param h the IO handle to read from
 * @param what describes what is being read (for error message creation)
 * @param i where to store the data
 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
 */
int
GNUNET_BIO_read_int64 (struct GNUNET_BIO_ReadHandle *h,
                       const char *what,
                       int64_t *i)
{
  int64_t big;

  if (GNUNET_OK != GNUNET_BIO_read (h, what, &big, sizeof(int64_t)))
    return GNUNET_SYSERR;
  *i = GNUNET_ntohll (big);
  return GNUNET_OK;
}


/**
 * Handle for buffered writing.
 */
struct GNUNET_BIO_WriteHandle
{
  /**
   * The "backend" type.
   */
  enum IOType type;

  /**
   * Handle to a file on disk, if @e type is #IO_FILE.
   */
  struct GNUNET_DISK_FileHandle *fd;

  /**
   * Error message, NULL if there were no errors.
   */
  char *emsg;

  /**
   * I/O buffer.
   * This field is a void * because it is used to hold pointers to allocated
   * structures or arrays and will be casted to the appropriate type.
   */
  void *buffer;

  /**
   * Number of bytes available in @e buffer.
   */
  size_t have;

  /**
   * Total size of @e buffer.
   */
  size_t size;
};


/**
 * Open a file for writing.
 *
 * @param fn name of the file to be opened
 * @return IO handle on success, NULL on error
 */
struct GNUNET_BIO_WriteHandle *
GNUNET_BIO_write_open_file (const char *fn)
{
  struct GNUNET_DISK_FileHandle *fd;
  struct GNUNET_BIO_WriteHandle *h;

  fd =
    GNUNET_DISK_file_open (fn,
                           GNUNET_DISK_OPEN_WRITE
                           | GNUNET_DISK_OPEN_TRUNCATE
                           | GNUNET_DISK_OPEN_CREATE,
                           GNUNET_DISK_PERM_USER_READ
                           | GNUNET_DISK_PERM_USER_WRITE);
  if (NULL == fd)
    return NULL;
  h = GNUNET_malloc (sizeof(struct GNUNET_BIO_WriteHandle) + BIO_BUFFER_SIZE);
  h->buffer = &h[1];
  h->size = BIO_BUFFER_SIZE;
  h->fd = fd;
  return h;
}


/**
 * Create a handle backed by an in-memory buffer.
 *
 * @return IO handle on success, NULL on error
 */
struct GNUNET_BIO_WriteHandle *
GNUNET_BIO_write_open_buffer (void)
{
  struct GNUNET_BIO_WriteHandle *h;

  h = GNUNET_new (struct GNUNET_BIO_WriteHandle);
  h->type = IO_BUFFER;
  h->buffer = (void *) GNUNET_malloc (sizeof (struct GNUNET_Buffer));
  return h;
}


/**
 * Close an IO handle.
 * If the handle was using a file, the file will be closed.
 *
 * @param h file handle
 * @param emsg set to the (allocated) error message
 *        if the handle has an error message, the return value is #GNUNET_SYSERR
 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
 */
int
GNUNET_BIO_write_close (struct GNUNET_BIO_WriteHandle *h, char **emsg)
{
  int err;

  err = (NULL == h->emsg) ? GNUNET_OK : GNUNET_SYSERR;
  if (NULL != emsg)
    *emsg = h->emsg;
  else
    GNUNET_free (h->emsg);
  switch (h->type)
  {
  case IO_FILE:
    if (NULL == h->fd)
      return GNUNET_SYSERR;
    if (GNUNET_OK != GNUNET_BIO_flush (h))
    {
      if (NULL != emsg)
        *emsg = h->emsg;
      else
        GNUNET_free (h->emsg);
      err = GNUNET_SYSERR;
    }
    else
    {
      GNUNET_DISK_file_close (h->fd);
    }
    break;
  case IO_BUFFER:
    GNUNET_buffer_clear ((struct GNUNET_Buffer *) h->buffer);
    GNUNET_free (h->buffer);
    break;
  }
  GNUNET_free (h);
  return err;
}


/**
 * Force a file-based buffered writer to flush its buffer.
 * If the handle does not use a file, this function returns #GNUNET_OK
 * without doing anything.
 *
 * @param h the IO handle
 * @return #GNUNET_OK upon success.  Upon failure #GNUNET_SYSERR is returned
 *         and the file is closed
 */
int
GNUNET_BIO_flush (struct GNUNET_BIO_WriteHandle *h)
{
  ssize_t ret;

  if (IO_FILE != h->type)
    return GNUNET_OK;

  ret = GNUNET_DISK_file_write (h->fd, h->buffer, h->have);
  if (ret != (ssize_t) h->have)
  {
    GNUNET_DISK_file_close (h->fd);
    h->fd = NULL;
    GNUNET_free (h->emsg);
    GNUNET_asprintf (&h->emsg, _ ("Unable to flush buffer to file"));
    return GNUNET_SYSERR;
  }
  h->have = 0;
  return GNUNET_OK;
}


/**
 * Get the IO handle's contents.
 * If the handle doesn't use an in-memory buffer, this function returns
 * #GNUNET_SYSERR.
 *
 * @param h the IO handle
 * @param emsg set to the (allocated) error message
 *        if the handle has an error message the return value is #GNUNET_SYSERR
 * @param contents where to store the pointer to the handle's contents
 * @param size where to store the size of @e contents
 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
 */
int
GNUNET_BIO_get_buffer_contents (struct GNUNET_BIO_WriteHandle *h,
                                char **emsg,
                                void **contents,
                                size_t *size)
{
  if (IO_BUFFER != h->type)
    return GNUNET_SYSERR;
  if ((NULL == contents) || (NULL == size))
    return GNUNET_SYSERR;
  int ret = (NULL != h->emsg) ? GNUNET_SYSERR : GNUNET_OK;
  if (NULL != emsg)
    *emsg = h->emsg;
  else
    GNUNET_free (h->emsg);
  *contents = GNUNET_buffer_reap ((struct GNUNET_Buffer *) h->buffer, size);
  return ret;
}


/**
 * Function used internally to write the contents of a buffer into a file.
 *
 * @param h the IO handle to write to
 * @param what describes what is being written (for error message creation)
 * @param source the buffer to write
 * @param len the number of bytes to write
 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
 */
static int
write_to_file (struct GNUNET_BIO_WriteHandle *h,
               const char *what,
               const char *source,
               size_t len)
{
  size_t min;
  size_t pos = 0;
  char *buffer = (char *) h->buffer;

  if (NULL == h->fd)
  {
    GNUNET_asprintf (&h->emsg,
                     _ ("Error while writing `%s' to file: %s"),
                     what,
                     _ ("No associated file"));
    return GNUNET_SYSERR;
  }

  do
  {
    min = h->size - h->have;
    if (len - pos < min)
      min = len - pos;
    GNUNET_memcpy (&buffer[h->have], &source[pos], min);
    pos += min;
    h->have += min;
    if (len == pos)
      return GNUNET_OK;
    GNUNET_assert (h->have == h->size);
    if (GNUNET_OK != GNUNET_BIO_flush (h))
    {
      char *tmp = h->emsg;
      GNUNET_asprintf (&h->emsg,
                       _ ("Error while writing `%s' to file: %s"),
                       what,
                       tmp);
      GNUNET_free (tmp);
      return GNUNET_SYSERR;
    }
  }
  while (pos < len);
  GNUNET_break (0);
  return GNUNET_OK;
}


/**
 * Function used internally to write the contents of a buffer to another buffer.
 *
 * @param h the IO handle to write to
 * @param what describes what is being written (for error message creation)
 * @param source the buffer to write
 * @param len the number of bytes to write
 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
 */
static int
write_to_buffer (struct GNUNET_BIO_WriteHandle *h,
                 const char *what,
                 const char *source,
                 size_t len)
{
  GNUNET_buffer_write ((struct GNUNET_Buffer *) h->buffer, source, len);
  h->have += len;
  return GNUNET_OK;
}


/**
 * Write a buffer to a handle.
 *
 * @param h the IO handle to write to
 * @param what what is being written (for error message creation)
 * @param buffer the data to write
 * @param n number of bytes to write
 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
 */
int
GNUNET_BIO_write (struct GNUNET_BIO_WriteHandle *h,
                  const char *what,
                  const void *buffer,
                  size_t n)
{
  const char *src = buffer;

  if (NULL != h->emsg)
    return GNUNET_SYSERR;

  if (0 == n)
    return GNUNET_OK;

  switch (h->type)
  {
  case IO_FILE:
    return write_to_file (h, what, src, n);
  case IO_BUFFER:
    return write_to_buffer (h, what, src, n);
  default:
    GNUNET_asprintf (&h->emsg,
                     _ ("Invalid handle type while writing `%s'"),
                     what);
    return GNUNET_SYSERR;
  }
}


/**
 * Write a 0-terminated string.
 *
 * @param h the IO handle to write to
 * @param what what is being written (for error message creation)
 * @param s string to write (can be NULL)
 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
 */
int
GNUNET_BIO_write_string (struct GNUNET_BIO_WriteHandle *h,
                         const char *what,
                         const char *s)
{
  uint32_t slen;

  slen = (uint32_t) ((s == NULL) ? 0 : strlen (s) + 1);
  if (GNUNET_OK != GNUNET_BIO_write_int32 (h, _ ("string length"), slen))
    return GNUNET_SYSERR;
  if (0 != slen)
    return GNUNET_BIO_write (h, what, s, slen - 1);
  return GNUNET_OK;
}



/**
 * Write a float.
 *
 * @param h the IO handle to write to
 * @param what what is being written (for error message creation)
 * @param f float to write
 */
int
GNUNET_BIO_write_float (struct GNUNET_BIO_WriteHandle *h,
                        const char *what,
                        float f)
{
  int32_t i = f;
  return GNUNET_BIO_write_int32 (h, what, i);
}


/**
 * Write a double.
 *
 * @param h the IO handle to write to
 * @param what what is being written (for error message creation)
 * @param f double to write
 */
int
GNUNET_BIO_write_double (struct GNUNET_BIO_WriteHandle *h,
                         const char *what,
                         double f)
{
  int64_t i = f;
  return GNUNET_BIO_write_int64 (h, what, i);
}


/**
 * Write an (u)int32_t.
 *
 * @param h the IO handle to write to
 * @param what what is being written (for error message creation)
 * @param i 32-bit integer to write
 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
 */
int
GNUNET_BIO_write_int32 (struct GNUNET_BIO_WriteHandle *h,
                        const char *what,
                        int32_t i)
{
  int32_t big;

  big = htonl (i);
  return GNUNET_BIO_write (h, what, &big, sizeof(int32_t));
}


/**
 * Write an (u)int64_t.
 *
 * @param h the IO handle to write to
 * @param what what is being written (for error message creation)
 * @param i 64-bit integer to write
 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
 */
int
GNUNET_BIO_write_int64 (struct GNUNET_BIO_WriteHandle *h,
                        const char *what,
                        int64_t i)
{
  int64_t big;

  big = GNUNET_htonll (i);
  return GNUNET_BIO_write (h, what, &big, sizeof(int64_t));
}


/**
 * Function used internally to read some bytes from within a read spec.
 *
 * @param cls ignored, always NULL
 * @param h the IO handle to read from
 * @param what what is being read (for error message creation)
 * @param target where to store the data
 * @param target_size how many bytes to read
 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
 */
static int
read_spec_handler_object (void *cls,
                          struct GNUNET_BIO_ReadHandle *h,
                          const char *what,
                          void *target,
                          size_t target_size)
{
  return GNUNET_BIO_read (h, what, target, target_size);
}


/**
 * Create the specification to read a certain amount of bytes.
 *
 * @param what describes what is being read (for error message creation)
 * @param result the buffer to write the result to
 * @param len the number of bytes to read
 * @return the read spec
 */
struct GNUNET_BIO_ReadSpec
GNUNET_BIO_read_spec_object (const char *what,
                             void *result,
                             size_t len)
{
  struct GNUNET_BIO_ReadSpec rs = {
    .rh = &read_spec_handler_object,
    .cls = NULL,
    .what = what,
    .target = result,
    .size = len,
  };

  return rs;
}


/**
 * Function used internally to read a string from within a read spec.
 *
 * @param cls ignored, always NULL
 * @param h the IO handle to read from
 * @param what what is being read (for error message creation)
 * @param target where to store the data
 * @param target_size how many bytes to read
 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
 */
static int
read_spec_handler_string (void *cls,
                          struct GNUNET_BIO_ReadHandle *h,
                          const char *what,
                          void *target,
                          size_t target_size)
{
  char **result = target;
  return GNUNET_BIO_read_string (h, what, result, target_size);
}


/**
 * Create the specification to read a 0-terminated string.
 *
 * @param what describes what is being read (for error message creation)
 * @param result where to store the pointer to the (allocated) string
 *        (note that *result could be set to NULL as well)
 * @param max_length maximum allowed length for the string
 * @return the read spec
 */
struct GNUNET_BIO_ReadSpec
GNUNET_BIO_read_spec_string (const char *what,
                             char **result,
                             size_t max_length)
{
  struct GNUNET_BIO_ReadSpec rs = {
    .rh = &read_spec_handler_string,
    .cls = NULL,
    .target = result,
    .size = max_length,
  };

  return rs;
}


/**
 * Function used internally to read an (u)int32_t from within a read spec.
 *
 * @param cls ignored, always NULL
 * @param h the IO handle to read from
 * @param what what is being read (for error message creation)
 * @param target where to store the data
 * @param target_size ignored
 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
 */
static int
read_spec_handler_int32 (void *cls,
                         struct GNUNET_BIO_ReadHandle *h,
                         const char *what,
                         void *target,
                         size_t target_size)
{
  int32_t *result = target;
  return GNUNET_BIO_read_int32 (h, what, result);
}


/**
 * Create the specification to read an (u)int32_t.
 *
 * @param what describes what is being read (for error message creation)
 * @param i where to store the data
 * @return the read spec
 */
struct GNUNET_BIO_ReadSpec
GNUNET_BIO_read_spec_int32 (const char *what,
                            int32_t *i)
{
  struct GNUNET_BIO_ReadSpec rs = {
    .rh = &read_spec_handler_int32,
    .cls = NULL,
    .target = i,
    .size = 0,
  };

  return rs;
}


/**
 * Function used internally to read an (u)int64_t from within a read spec.
 *
 * @param cls ignored, always NULL
 * @param h the IO handle to read from
 * @param what what is being read (for error message creation)
 * @param target where to store the data
 * @param target_size ignored
 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
 */
static int
read_spec_handler_int64 (void *cls,
                         struct GNUNET_BIO_ReadHandle *h,
                         const char *what,
                         void *target,
                         size_t target_size)
{
  int64_t *result = target;
  return GNUNET_BIO_read_int64 (h, what, result);
}


/**
 * Create the specification to read an (u)int64_t.
 *
 * @param what describes what is being read (for error message creation)
 * @param i where to store the data
 * @return the read spec
 */
struct GNUNET_BIO_ReadSpec
GNUNET_BIO_read_spec_int64 (const char *what,
                            int64_t *i)
{
  struct GNUNET_BIO_ReadSpec rs = {
    .rh = &read_spec_handler_int64,
    .cls = NULL,
    .target = i,
    .size = 0,
  };

  return rs;
}


/**
 * Create the specification to read a float.
 *
 * @param what describes what is being read (for error message creation)
 * @param f address of float to read
 */
struct GNUNET_BIO_ReadSpec
GNUNET_BIO_read_spec_float (const char *what, float *f)
{
  struct GNUNET_BIO_ReadSpec rs = {
    .rh = &read_spec_handler_int32,
    .cls = NULL,
    .target = (int32_t *) f,
    .size = 0,
  };

  return rs;
}


/**
 * Create the specification to read a double.
 *
 * @param what describes what is being read (for error message creation)
 * @param f address of double to read
 */
struct GNUNET_BIO_ReadSpec
GNUNET_BIO_read_spec_double (const char *what, double *f)
{
  struct GNUNET_BIO_ReadSpec rs = {
    .rh = &read_spec_handler_int64,
    .cls = NULL,
    .target = (int64_t *) f,
    .size = 0,
  };

  return rs;
}


/**
 * Execute the read specifications in order.
 *
 * @param h the IO handle to read from
 * @param rs array of read specs
 *        the last element must be #GNUNET_BIO_read_spec_end
 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
 */
int
GNUNET_BIO_read_spec_commit (struct GNUNET_BIO_ReadHandle *h,
                             struct GNUNET_BIO_ReadSpec *rs)
{
  int ret = GNUNET_OK;

  for (size_t i = 0; NULL!=rs[i].rh; ++i)
  {
    ret = rs[i].rh (rs[i].cls, h, rs[i].what, rs[i].target, rs[i].size);
    if (GNUNET_OK != ret)
      return ret;
  }

  return ret;
}


/**
 * Function used internally to write some bytes from within a write spec.
 *
 * @param cls ignored, always NULL
 * @param h the IO handle to write to
 * @param what what is being written (for error message creation)
 * @param source the data to write
 * @param source_size how many bytes to write
 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
 */
static int
write_spec_handler_object (void *cls,
                           struct GNUNET_BIO_WriteHandle *h,
                           const char *what,
                           void *source,
                           size_t source_size)
{
  return GNUNET_BIO_write (h, what, source, source_size);
}


/**
 * Create the specification to read some bytes.
 *
 * @param what describes what is being written (for error message creation)
 * @param source the data to write
 * @param size how many bytes should be written
 * @return the write spec
 */
struct GNUNET_BIO_WriteSpec
GNUNET_BIO_write_spec_object (const char *what,
                              void *source,
                              size_t size)
{
  struct GNUNET_BIO_WriteSpec ws = {
    .wh = &write_spec_handler_object,
    .cls = NULL,
    .what = what,
    .source = source,
    .source_size = size,
  };

  return ws;
}


/**
 * Function used internally to write a 0-terminated string from within a write
 * spec.
 *
 * @param cls ignored, always NULL
 * @param h the IO handle to write to
 * @param what what is being written (for error message creation)
 * @param source the data to write
 * @param source_size ignored
 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
 */
static int
write_spec_handler_string (void *cls,
                           struct GNUNET_BIO_WriteHandle *h,
                           const char *what,
                           void *source,
                           size_t source_size)
{
  const char *s = source;
  return GNUNET_BIO_write_string (h, what, s);
}


/**
 * Create the specification to write a 0-terminated string.
 *
 * @param what describes what is being read (for error message creation)
 * @param s string to write (can be NULL)
 * @return the read spec
 */
struct GNUNET_BIO_WriteSpec
GNUNET_BIO_write_spec_string (const char *what,
                              const char *s)
{
  struct GNUNET_BIO_WriteSpec ws = {
    .wh = &write_spec_handler_string,
    .cls = NULL,
    .what = what,
    .source = (void *) s,
    .source_size = 0,
  };

  return ws;
}


/**
 * Function used internally to write an (u)int32_t from within a write spec.
 *
 * @param cls ignored, always NULL
 * @param h the IO handle to write to
 * @param what what is being written (for error message creation)
 * @param source the data to write
 * @param source_size ignored
 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
 */
static int
write_spec_handler_int32 (void *cls,
                          struct GNUNET_BIO_WriteHandle *h,
                          const char *what,
                          void *source,
                          size_t source_size)
{
  int32_t i = *(int32_t *) source;
  return GNUNET_BIO_write_int32 (h, what, i);
}


/**
 * Create the specification to write an (u)int32_t.
 *
 * @param what describes what is being written (for error message creation)
 * @param i pointer to a 32-bit integer
 * @return the write spec
 */
struct GNUNET_BIO_WriteSpec
GNUNET_BIO_write_spec_int32 (const char *what,
                             int32_t *i)
{
  struct GNUNET_BIO_WriteSpec ws = {
    .wh = &write_spec_handler_int32,
    .cls = NULL,
    .what = what,
    .source = i,
    .source_size = 0,
  };

  return ws;
}


/**
 * Function used internally to write an (u)int64_t from within a write spec.
 *
 * @param cls ignored, always NULL
 * @param h the IO handle to write to
 * @param what what is being written (for error message creation)
 * @param source the data to write
 * @param source_size ignored
 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
 */
static int
write_spec_handler_int64 (void *cls,
                          struct GNUNET_BIO_WriteHandle *h,
                          const char *what,
                          void *source,
                          size_t source_size)
{
  int64_t i = *(int64_t *) source;
  return GNUNET_BIO_write_int64 (h, what, i);
}


/**
 * Create the specification to write an (u)int64_t.
 *
 * @param what describes what is being written (for error message creation)
 * @param i pointer to a 64-bit integer
 * @return the write spec
 */
struct GNUNET_BIO_WriteSpec
GNUNET_BIO_write_spec_int64 (const char *what,
                             int64_t *i)
{
  struct GNUNET_BIO_WriteSpec ws = {
    .wh = &write_spec_handler_int64,
    .cls = NULL,
    .what = what,
    .source = i,
    .source_size = 0,
  };

  return ws;
}


/**
 * Create the specification to write a float.
 *
 * @param what describes what is being written (for error message creation)
 * @param f pointer to a float
 * @return the write spec
 */
struct GNUNET_BIO_WriteSpec
GNUNET_BIO_write_spec_float (const char *what, float *f)
{
  struct GNUNET_BIO_WriteSpec ws = {
    .wh = &write_spec_handler_int32,
    .cls = NULL,
    .what = what,
    .source = (int32_t *) f,
    .source_size = 0,
  };

  return ws;
}


/**
 * Create the specification to write an double.
 *
 * @param what describes what is being written (for error message creation)
 * @param f pointer to a double
 * @return the write spec
 */
struct GNUNET_BIO_WriteSpec
GNUNET_BIO_write_spec_double (const char *what, double *f)
{
  struct GNUNET_BIO_WriteSpec ws = {
    .wh = &write_spec_handler_int64,
    .cls = NULL,
    .what = what,
    .source = (int64_t *) f,
    .source_size = 0,
  };

  return ws;
}


/**
 * Execute the write specifications in order.
 *
 * @param h the IO handle to write to
 * @param ws array of write specs
 *        the last element must be #GNUNET_BIO_write_spec_end
 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
 */
int
GNUNET_BIO_write_spec_commit (struct GNUNET_BIO_WriteHandle *h,
                              struct GNUNET_BIO_WriteSpec *ws)
{
  int ret = GNUNET_OK;

  for (size_t i = 0; NULL!=ws[i].wh; ++i)
  {
    ret = ws[i].wh (ws[i].cls, h, ws[i].what, ws[i].source, ws[i].source_size);
    if (GNUNET_OK != ret)
      return ret;
  }

  /* If it's a file-based handle, the flush makes sure that the data in the
     buffer is actually written to the disk. */
  if (IO_FILE == h->type)
    ret = GNUNET_BIO_flush (h);

  return ret;
}


/* end of bio.c */
