/* -*-Mode: C;-*-
 * $Id: base64.c 1.1 Sun, 28 Jan 2007 10:02:26 -0800 jmacd $
 *
 * Copyright (C) 1998, 1999, Josh MacDonald.
 * All Rights Reserved.
 *
 * Author: Josh MacDonald <jmacd@CS.Berkeley.EDU>
 */

#include "edsio.h"

/* BASE64 Encoding
 */

static const unsigned char base64_table[64] = {
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
  'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a',
  'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
  'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2',
  '3', '4', '5', '6', '7', '8', '9', '+', '/'
};

static gint16 base64_inverse_table[128];

static void
init_inverse_table (void)
{
  static int i = 0;
  static int j = 0;

  for (; j < 128; j += 1)
    base64_inverse_table[j] = -1;

  for (; i < 64; i += 1)
    base64_inverse_table[base64_table[i]] = i;

  base64_inverse_table['='] = 0;
}

GByteArray*
edsio_base64_encode_region (const guint8* data, guint len)
{
  GByteArray* out = g_byte_array_new ();
  guint real_len;

  g_byte_array_set_size (out, (len+2)*4/3);

  real_len = out->len;

  if (! edsio_base64_encode_region_into (data, len, out->data, &real_len))
    {
      g_byte_array_free (out, TRUE);
      return NULL;
    }

  g_byte_array_set_size (out, real_len);

  return out;
}

gboolean
edsio_base64_encode_region_into (const guint8* data, guint len, guint8* out, guint *out_len)
{
  gint i;
  guint32 word = 0, count = 0;

  if ((*out_len) < (len + 2) * 4/3)
    {
      edsio_generate_void_event (EC_EdsioInvalidBase64Encoding);
      return FALSE;
    }

  *out_len = 0;

  for (i = 0; i < len; i += 1)
    {
      word |= data[i] << (8*(2-(count++)));

      if (count == 3)
	{
	  out[(*out_len)++] = base64_table[(word>>18) & 0x3f];
	  out[(*out_len)++] = base64_table[(word>>12) & 0x3f];
	  out[(*out_len)++] = base64_table[(word>> 6) & 0x3f];
	  out[(*out_len)++] = base64_table[(word    ) & 0x3f];

	  count = 0;
	  word = 0;
	}
    }

  if (count > 0)
    {
      out[(*out_len)++] = base64_table[(word>>18) & 0x3f];
      out[(*out_len)++] = base64_table[(word>>12) & 0x3f];
      out[(*out_len)++] = (count > 1) ? base64_table[(word>>6) & 0x3f] : '=';
      out[(*out_len)++] = '=';
    }

  return TRUE;
}

GByteArray*
edsio_base64_decode_region (const guint8* data, guint data_len)
{
  GByteArray* it = g_byte_array_new ();
  guint real_len;

  g_byte_array_set_size (it, data_len*3/4);

  real_len = it->len;

  if (! edsio_base64_decode_region_into (data, data_len, it->data, &real_len))
    {
      g_byte_array_free (it, TRUE);
      return NULL;
    }

  g_byte_array_set_size (it, real_len);

  return it;
}

gboolean
edsio_base64_decode_region_into (const guint8* data, guint len, guint8* out, guint *out_len)
{
  guint32 pos = 0;
  gboolean found_end = FALSE;
  gint found_end_at = 0;

  init_inverse_table ();

  if ((*out_len) < (len*3/4))
    {
      edsio_generate_void_event (EC_EdsioOutputBufferShort);
      return FALSE;
    }

  (*out_len) = 0;

  while (pos < len)
    {
      gint i, x;
      gint word = 0, junk = 0;

      if (len - pos < 4)
	{
	  edsio_generate_void_event (EC_EdsioInvalidBase64Encoding);
	  return FALSE;
	}

      for (i = 0; i < 4; i += 1)
	{
	  x = data[pos++];

	  if (x > 127 || base64_inverse_table[x] < 0)
	    {
	      edsio_generate_void_event (EC_EdsioInvalidBase64Encoding);
	      return FALSE;
	    }

	  if (x == '=')
	    {
	      if (! found_end)
		found_end_at = i;

	      found_end = TRUE;
	    }
	  else
	    {
	      if (found_end)
		{
		  edsio_generate_void_event (EC_EdsioInvalidBase64Encoding);
		  return FALSE;
		}

	      word |= base64_inverse_table[x] << (6*(3-i));
	    }
	}

      if (found_end)
	{
	  if (found_end_at < 2)
	    {
	      edsio_generate_void_event (EC_EdsioInvalidBase64Encoding);
	      return FALSE;
	    }

	  if (found_end_at == 2)
	    junk = 2;
	  else if (found_end_at == 3)
	    junk = 1;
	}
      else
	junk = 0;

      out[(*out_len)++] = (word >> 16) & 0xff;

      if (junk < 2)
	out[(*out_len)++] = (word >>  8) & 0xff;

      if (junk < 1)
	out[(*out_len)++] = (word >>  0) & 0xff;
    }

  return TRUE;
}

/* Base64 sink
 */
typedef struct _Base64Sink Base64Sink;

static gboolean base64_sink_close (SerialSink* sink);
static gboolean base64_sink_write (SerialSink* sink, const guint8 *ptr, guint32 len);
static void     base64_sink_free (SerialSink* sink);
static gboolean base64_sink_quantum (SerialSink* sink);

struct _Base64Sink
{
  SerialSink sink;

  SerialSink* out;

  guint32 word;
  guint32 count;
};

SerialSink*
serializeio_base64_sink   (SerialSink* out)
{
  Base64Sink* it = g_new0 (Base64Sink, 1);
  SerialSink* sink = (SerialSink*) it;

  serializeio_sink_init (sink,
			 NULL,
			 base64_sink_close,
			 base64_sink_write,
			 base64_sink_free,
			 base64_sink_quantum);

  it->out = out;

  return sink;
}

gboolean
base64_sink_write (SerialSink* fsink, const guint8 *ptr, guint32 len)
{
  guint32 i;

  Base64Sink* sink = (Base64Sink*) fsink;

  for (i = 0; i < len; )
    {
      if (sink->count == 3)
	{
	  guint8 out[4];

	  out[0] = base64_table[(sink->word>>18) & 0x3f];
	  out[1] = base64_table[(sink->word>>12) & 0x3f];
	  out[2] = base64_table[(sink->word>> 6) & 0x3f];
	  out[3] = base64_table[(sink->word    ) & 0x3f];

#if 0
	  g_print ("%02x %02x %02x -> %c%c%c%c (3)\n",
		   (sink->word>>16) & 0xff,
		   (sink->word>>8) & 0xff,
		   (sink->word>>0) & 0xff,
		   out[0],
		   out[1],
		   out[2],
		   out[3]);
#endif

	  if (! sink->out->sink_write (sink->out, out, 4))
	    return FALSE;

	  sink->count = 0;
	  sink->word = 0;
	}

      while (sink->count < 3 && i < len)
	sink->word |= ptr[i++] << (8*(2-(sink->count++)));
    }

  return TRUE;
}

gboolean
base64_sink_close (SerialSink* fsink)
{
  Base64Sink* sink = (Base64Sink*) fsink;

  if (sink->count == 3)
    {
      guint8 out[4];

      out[0] = base64_table[(sink->word>>18) & 0x3f];
      out[1] = base64_table[(sink->word>>12) & 0x3f];
      out[2] = base64_table[(sink->word>> 6) & 0x3f];
      out[3] = base64_table[(sink->word    ) & 0x3f];

#if 0
      g_print ("%02x %02x %02x -> %c%c%c%c (3)\n",
	       (sink->word>>16) & 0xff,
	       (sink->word>>8) & 0xff,
	       (sink->word>>0) & 0xff,
	       out[0],
	       out[1],
	       out[2],
	       out[3]);
#endif

      if (! sink->out->sink_write (sink->out, out, 4))
	return FALSE;

      sink->count = 0;
      sink->word = 0;
    }

  if (sink->count > 0)
    {
      guint8 out[4];

      out[0] = base64_table[(sink->word>>18) & 0x3f];
      out[1] = base64_table[(sink->word>>12) & 0x3f];
      out[2] = (sink->count > 1) ? base64_table[(sink->word>>6) & 0x3f] : '=';
      out[3] = '=';

#if 0
      g_print ("%02x %02x %02x -> %c%c%c%c (%d)\n",
	       (sink->word>>16) & 0xff,
	       (sink->word>>8) & 0xff,
	       (sink->word>>0) & 0xff,
	       out[0],
	       out[1],
	       out[2],
	       out[3],
	       sink->count);
#endif

      if (! sink->out->sink_write (sink->out, out, 4))
	return FALSE;

      sink->count = 0;
      sink->word = 0;
    }

  return sink->out->sink_close (sink->out);
}


void
base64_sink_free (SerialSink* fsink)
{
  Base64Sink* sink = (Base64Sink*) fsink;

  sink->out->sink_free (sink->out);

  g_free (sink);
}

gboolean
base64_sink_quantum (SerialSink* fsink)
{
  Base64Sink* sink = (Base64Sink*) fsink;

  if (sink->out->sink_quantum)
    return sink->out->sink_quantum (sink->out);

  return TRUE;
}

/* Base64 source
 */

typedef struct _Base64Source Base64Source;

struct _Base64Source {
  SerialSource source;

  SerialSource *in;

  guint32 avail;
  guint32 count;
  gboolean found_end;
  gint found_end_at;
  guint8 buf[3];
};

static gboolean     base64_source_close        (SerialSource* source);
static gboolean     base64_source_read         (SerialSource* source, guint8 *ptr, guint32 len);
static void         base64_source_free         (SerialSource* source);

SerialSource*
serializeio_base64_source (SerialSource* in0)
{
  Base64Source* it = g_new0 (Base64Source, 1);
  SerialSource* source = (SerialSource*) it;

  serializeio_source_init (source,
			   NULL,
			   base64_source_close,
			   base64_source_read,
			   base64_source_free,
			   NULL,
			   NULL);

  it->in = in0;

  return source;
}

gboolean
base64_source_close (SerialSource* fsource)
{
  Base64Source* source = (Base64Source*) fsource;

  if (! source->in->source_close (source->in))
    return FALSE;

  return TRUE;
}

gboolean
base64_source_read (SerialSource* fsource, guint8 *ptr, guint32 len)
{
  guint32 pos;
  Base64Source* source = (Base64Source*) fsource;

  init_inverse_table ();

  for (pos = 0; pos < len; )
    {
      if (source->count == 0)
	{
	  guint8 buf[4];
	  guint32 i, word = 0, junk;

	  if (source->found_end)
	    {
	      edsio_generate_void_event (EC_EdsioInvalidBase64Encoding);
	      return FALSE;
	    }

	  if (! source->in->source_read (source->in, buf, 4))
	    return FALSE;

	  for (i = 0; i < 4; i += 1)
	    {
	      gint x = buf[i];

	      if (x > 127 || base64_inverse_table[x] < 0)
		{
		  edsio_generate_void_event (EC_EdsioInvalidBase64Encoding);
		  return FALSE;
		}

	      if (x == '=')
		{
		  if (! source->found_end)
		    source->found_end_at = i;

		  source->found_end = TRUE;
		}
	      else
		{
		  if (source->found_end)
		    {
		      edsio_generate_void_event (EC_EdsioInvalidBase64Encoding);
		      return FALSE;
		    }

		  word |= base64_inverse_table[x] << (6*(3-i));
		}
	    }

	  if (source->found_end)
	    {
	      if (source->found_end_at == 2)
		junk = 2;
	      else if (source->found_end_at == 3)
		junk = 1;
	      else
		{
		  edsio_generate_void_event (EC_EdsioInvalidBase64Encoding);
		  return FALSE;
		}
	    }
	  else
	    junk = 0;

	  source->avail = source->count = 3 - junk;

	  source->buf[0] = (word >> 16) & 0xff;
	  source->buf[1] = (word >>  8) & 0xff;
	  source->buf[2] = (word >>  0) & 0xff;

#if 0
	  g_print ("%c%c%c%c -> %02x %02x %02x (%d)\n",
		   buf[0],
		   buf[1],
		   buf[2],
		   buf[3],
		   (word>>16) & 0xff,
		   (word>>8) & 0xff,
		   (word>>0) & 0xff,
		   3-junk);
#endif
	}

      ptr[pos++] = source->buf[source->avail-(source->count--)];
    }

  return TRUE;
}

void
base64_source_free (SerialSource* fsource)
{
  Base64Source* source = (Base64Source*) fsource;

  source->in->source_free (source->in);

  g_free (source);
}
