/*
 * pngxio.c - libpng extension: I/O state query.
 *
 * Copyright (C) 2003-2017 Cosmin Truta.
 * This software is distributed under the same licensing and warranty terms
 * as libpng.
 *
 * NOTE:
 * The functionality provided in this module has "graduated", and is now
 * part of libpng.  The original code, retrofitted as a back-port, has
 * limitations: it is thread-unsafe, and it only allows one png_ptr object
 * for reading and one for writing.
 *
 * CAUTION:
 * libpng-1.4.5 is the earliest version whose I/O state implementation
 * can be used reliably.
 */

#include "pngxutil.h"

#define PNGX_INTERNAL
#include "pngxpriv.h"


#if PNG_LIBPNG_VER < 10405


static png_structp _pngxio_read_ptr;
static png_structp _pngxio_write_ptr;

static png_rw_ptr _pngxio_read_fn;
static png_rw_ptr _pngxio_write_fn;

static int _pngxio_read_io_state;
static int _pngxio_write_io_state;

static png_byte _pngxio_read_crt_chunk_hdr[9];
static png_byte _pngxio_write_crt_chunk_hdr[9];

static unsigned int _pngxio_read_crt_chunk_hdr_len;
static unsigned int _pngxio_write_crt_chunk_hdr_len;

static png_uint_32 _pngxio_read_crt_len;
static png_uint_32 _pngxio_write_crt_len;

static const char *_pngxio_errmsg_invalid_argument =
   "[PNGXIO internal] invalid argument";


/* Update io_state and call the user-supplied read/write functions. */
void /* PRIVATE */
pngxio_read_write(png_structp png_ptr, png_bytep data, size_t length)
{
   png_rw_ptr io_data_fn;
   int *io_state_ptr;
   int io_state_op;
   png_byte *crt_chunk_hdr;
   unsigned int *crt_chunk_hdr_len_ptr;
   png_uint_32 *crt_len_ptr;

   if (png_ptr == _pngxio_read_ptr)
   {
      io_data_fn = _pngxio_read_fn;
      io_state_ptr = &_pngxio_read_io_state;
      io_state_op = PNGX_IO_READING;
      crt_chunk_hdr = _pngxio_read_crt_chunk_hdr;
      crt_chunk_hdr_len_ptr = &_pngxio_read_crt_chunk_hdr_len;
      crt_len_ptr = &_pngxio_read_crt_len;
   }
   else if (png_ptr == _pngxio_write_ptr)
   {
      io_data_fn = _pngxio_write_fn;
      io_state_ptr = &_pngxio_write_io_state;
      io_state_op = PNGX_IO_WRITING;
      crt_chunk_hdr = _pngxio_write_crt_chunk_hdr;
      crt_chunk_hdr_len_ptr = &_pngxio_write_crt_chunk_hdr_len;
      crt_len_ptr = &_pngxio_write_crt_len;
   }
   else
   {
      png_error(png_ptr, _pngxio_errmsg_invalid_argument);
      /* NOTREACHED */
      return;
   }

   switch (*io_state_ptr & PNGX_IO_MASK_LOC)
   {
   case PNGX_IO_SIGNATURE:
      /* libpng must serialize the signature in a single I/O session. */
      PNGX_ASSERT(length <= 8);
      io_data_fn(png_ptr, data, length);
      *io_state_ptr = io_state_op | PNGX_IO_CHUNK_HDR;
      *crt_chunk_hdr_len_ptr = 0;
      return;
   case PNGX_IO_CHUNK_HDR:
      /* libpng must serialize the chunk header in a single I/O session.
       * (This was done in libpng-1.2.30, but regressed in libpng-1.4.0,
       * so we cannot rely on it here.)
       */
      PNGX_ASSERT(length == 4 || length == 8);
      PNGX_ASSERT(length + *crt_chunk_hdr_len_ptr <= 8);
      if (io_state_op == PNGX_IO_READING)
      {
         if (*crt_chunk_hdr_len_ptr == 0)
            io_data_fn(png_ptr, crt_chunk_hdr, 8);
         memcpy(data, crt_chunk_hdr + *crt_chunk_hdr_len_ptr, length);
         *crt_chunk_hdr_len_ptr += length;
         if (*crt_chunk_hdr_len_ptr < 8)
            return;
         *crt_len_ptr = png_get_uint_32(crt_chunk_hdr);
         /* memcpy(png_ptr->chunk_name, crt_chunk_hdr + 4, 4); */
      }
      else  /* io_state_op == PNGX_IO_WRITING */
      {
         memcpy(crt_chunk_hdr + *crt_chunk_hdr_len_ptr, data, length);
         *crt_chunk_hdr_len_ptr += length;
         if (*crt_chunk_hdr_len_ptr < 8)
            return;
         *crt_len_ptr = png_get_uint_32(crt_chunk_hdr);
         /* memcpy(png_ptr->chunk_name, crt_chunk_hdr + 4, 4); */
         io_data_fn(png_ptr, crt_chunk_hdr, 8);
      }
      *crt_chunk_hdr_len_ptr = 0;
      *io_state_ptr = io_state_op | PNGX_IO_CHUNK_DATA;
      return;
   case PNGX_IO_CHUNK_DATA:
      /* libpng may serialize the chunk data in multiple I/O sessions. */
      if (length == 0)
         return;
      if (*crt_len_ptr > 0)
      {
         PNGX_ASSERT(length <= *crt_len_ptr);
         io_data_fn(png_ptr, data, length);
         *crt_len_ptr -= length;
         return;
      }
      *io_state_ptr = io_state_op | PNGX_IO_CHUNK_CRC;
      /* FALLTHROUGH */
   case PNGX_IO_CHUNK_CRC:
      /* libpng must serialize the chunk CRC in a single I/O session. */
      PNGX_ASSERT(length == 4);
      io_data_fn(png_ptr, data, 4);
      *io_state_ptr = io_state_op | PNGX_IO_CHUNK_HDR;
      return;
   }
}

/* Get png_ptr->io_state. */
png_uint_32 PNGAPI
pngx_get_io_state(png_structp png_ptr)
{
   png_uint_32 io_state;

   if (png_ptr == _pngxio_read_ptr)
      io_state = _pngxio_read_io_state;
   else if (png_ptr == _pngxio_write_ptr)
      io_state = _pngxio_write_io_state;
   else
   {
      png_error(png_ptr, _pngxio_errmsg_invalid_argument);
      /* NOTREACHED */
      io_state = PNGX_IO_NONE;
   }

   return io_state;
}

/* Get png_ptr->chunk_name. */
png_bytep PNGAPI
pngx_get_io_chunk_name(png_structp png_ptr)
{
   png_bytep chunk_name;

   if (png_ptr == _pngxio_read_ptr)
      chunk_name = _pngxio_read_crt_chunk_hdr + 4;
   else if (png_ptr == _pngxio_write_ptr)
      chunk_name = _pngxio_write_crt_chunk_hdr + 4;
   else
   {
      png_error(png_ptr, _pngxio_errmsg_invalid_argument);
      /* NOTREACHED */
      chunk_name = NULL;
   }

   return chunk_name;
}

/* Wrap png_set_read_fn. */
void PNGAPI
pngx_set_read_fn(png_structp png_ptr, png_voidp io_ptr,
   png_rw_ptr read_data_fn)
{
   _pngxio_read_ptr = png_ptr;
   _pngxio_write_ptr = NULL;
   _pngxio_read_fn = read_data_fn;
   png_set_read_fn(png_ptr, io_ptr, pngxio_read_write);
   _pngxio_read_io_state = PNGX_IO_READING | PNGX_IO_SIGNATURE;
}

/* Wrap png_set_write_fn. */
void PNGAPI
pngx_set_write_fn(png_structp png_ptr, png_voidp io_ptr,
   png_rw_ptr write_data_fn, png_flush_ptr output_flush_fn)
{
   _pngxio_write_ptr = png_ptr;
   _pngxio_read_ptr = NULL;
   _pngxio_write_fn = write_data_fn;
   png_set_write_fn(png_ptr, io_ptr, pngxio_read_write, output_flush_fn);
   _pngxio_write_io_state = PNGX_IO_WRITING | PNGX_IO_SIGNATURE;
}

/* Wrap png_write_sig. */
void PNGAPI
pngx_write_sig(png_structp png_ptr)
{
#if PNG_LIBPNG_VER >= 10400
   png_write_sig(png_ptr);
#else
   /* png_write_sig is not exported from the earlier libpng versions. */
   static png_byte png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10};
   pngxio_read_write(png_ptr, png_signature, 8);
   /* TODO: Take png_ptr->sig_bytes into account. */
#endif
}


#endif /* PNG_LIBPNG_VER < 10405 */
