/*
  $Source: /local/data/cvs/yellowbank/postgres/src/y_octet_t/y_octet_t.c,v $
  $Revision: 1.3 $
  $State: Exp $
  $Date: 2007/01/22 20:47:51 $
  $Author: yrp001 $
  $Locker:  $

  Copyright (c) 2006
  Ronald Peterson
  (Y) Yellowbank
  All rights reserved.  Applicable BSD license terms can be found in
  the associated LICENSE file.

  Fixed size binary string types.  Goal is to eliminate overhead of
  generic bytea type for standard length binary string objects, such
  as UUID's (aka GUID's), hash digests, and so on.
*/

/* PostgreSQL includes */
#include "postgres.h"
#include "fmgr.h"
#include "lib/stringinfo.h"
#include "libpq/pqformat.h"
// #include "utils/datetime.h"

/* external requirements */
#include <stdio.h>
#include <string.h>

#include "y_octet_t.h"

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

static char *hex2bin_palloc( const char *, int, int );
static char *hexc2bin_palloc( const char *, int );
static char *hext2bin_palloc( const text *, int );
static char *bin2hex_palloc( const char *, int );

////////////////////////////////////////////////////////////////////////
// Convert hex string to binary string.
//
// Eat whitespace, colons, and dashes.
// e.g. valid string: ' abc def 1234-5678:9  '
//
// Number of hex characters must equal 'size' * 2.
//
static
char
*hex2bin_palloc( const char *hexstr, int hexlen, int binsize ) {
   char *binstr, *binend;
   char c, bits;
   int i, hexcount, binndx, rightside;
   char *hexend;

   hexend = (char *)(hexstr + hexlen - 1);
   binstr = palloc( binsize );
   bzero( binstr, binsize );
   binend = binstr + binsize - 1;
   binndx = 0;
   hexcount = 0;

   for( i = 0; i < hexlen; i++ ) {
      c = *(hexend - i);

      // eat padding characters
      if( c == ' ' ||
          c == '\t' ||
          c == '-' ||
          c == ':' ) {
         continue;
      }
      if( 'a' <= c && c <= 'f' ) {
         c = (c - 'a') + 10;
      } else if( 'A' <= c && c <= 'F' ) {
         c = (c - 'A') + 10;
      } else if( '0' <= c && c <= '9' ) {
         c = c - '0';
      } else {
         ereport(ERROR,
                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                  errmsg("input string contains invalid characters")));
      }

      // rightmost hex digit? (0 or 1)
      rightside = hexcount % 2;

      if( ++hexcount > binsize * 2 ) {
         ereport(ERROR,
                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                  errmsg("input string too long")));
      }

      // push bits left for every other hex digit
      bits = (c & 0x0F) << (rightside * 4);

      if( rightside ) {
         bits = bits | *(binend - binndx);
      }

      // Fill binstr from right to left.
      *(binend - binndx) = bits;

      binndx += rightside;
   }

   if( hexcount != (binsize * 2) ) {
      ereport(WARNING,
              (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
               errmsg("input string too short")));
   }
   return( binstr );
}

static
char
*hexc2bin_palloc( const char *hexstr, int binsize ) {
   int hexlen;
   char *binstr;
   hexlen = strlen( hexstr );
   binstr = hex2bin_palloc( hexstr, hexlen, binsize );
   return( binstr );
}

static
char
*hext2bin_palloc( const text *hexstr, int binsize ) {
   int hexlen;
   char *binstr;
   hexlen = VARSIZE(hexstr) - VARHDRSZ;
   binstr = hex2bin_palloc( VARDATA(hexstr), hexlen, binsize );
   return( binstr );
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
// will allocate twice binsize + 1 (for terminating NULL)
static
char
*bin2hex_palloc( const char* binstr, int binsize ) {
   char *hexstr;
   int i, hexsize;
   char c, r, l, h;

   hexsize = (binsize * 2) + 1;
   hexstr = palloc( hexsize );
   for( i = 0; i < binsize; i++ ) {
      c = *(binstr + i);
      l = (c >> 4) & 0x0F;
      r = (c & 0x0F);
      h = (int)l < 10 ? '0' : 'a' - 10;
      hexstr[i*2] = l + h;
      h = (int)r < 10 ? '0' : 'a' - 10;
      hexstr[i*2+1] = r + h;
   }
   memset( hexstr + (binsize * 2), '\0', 1 );

   return( hexstr );
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
// text in/out
PG_FUNCTION_INFO_V1( y_octet_16_in );
Datum
y_octet_16_in( PG_FUNCTION_ARGS )
{
   char *hexstr;
   char *binstr;
   if( PG_ARGISNULL(0) ) {
      PG_RETURN_NULL();
   }
   hexstr = PG_GETARG_CSTRING(0);
   binstr = hexc2bin_palloc( hexstr, 16 );
   PG_RETURN_POINTER( binstr );
}

PG_FUNCTION_INFO_V1( y_octet_16_out );
Datum
y_octet_16_out( PG_FUNCTION_ARGS )
{
   char *binstr;
   char *hexstr;
   if( PG_ARGISNULL(0) ) {
      PG_RETURN_NULL();
   }
   binstr = PG_GETARG_POINTER(0);
   hexstr = bin2hex_palloc( binstr, 16 );
   PG_RETURN_CSTRING( hexstr );
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
// binary in/out
PG_FUNCTION_INFO_V1( y_octet_16_recv );
Datum
y_octet_16_recv( PG_FUNCTION_ARGS )
{
   StringInfo buf;
   char *binstr;

   if( PG_ARGISNULL(0) ) {
      PG_RETURN_NULL();
   }
   buf = (StringInfo)PG_GETARG_POINTER(0);

   binstr = palloc( 16 );
   binstr = (char *)pq_getmsgbytes( buf, 16 );

   PG_RETURN_POINTER( binstr );
}

PG_FUNCTION_INFO_V1( y_octet_16_send );
Datum
y_octet_16_send( PG_FUNCTION_ARGS )
{
   char *binstr;
   StringInfoData buf;

   if( PG_ARGISNULL(0) ) {
      PG_RETURN_NULL();
   }
   binstr = (char *)PG_GETARG_POINTER(0);

   pq_begintypsend( &buf );
   pq_copymsgbytes( &buf, binstr, 16 );

   PG_RETURN_BYTEA_P( pq_endtypsend( &buf ) );
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
static
int
y_octet_16_cmp_internal( const char *a, const char *b ) {
   int i;
   for( i = 0; i < 16; i++ ) {
      if( a[i] < b[i] ) {
         return( -1 );
      } else if( a[i] > b[i] ) {
         return( 1 );
      }
   }
   return( 0 );
}

PG_FUNCTION_INFO_V1(y_octet_16_lt);
Datum
y_octet_16_lt(PG_FUNCTION_ARGS)
{
   char *a;
   char *b;
   a = (char *)PG_GETARG_POINTER(0);
   b = (char *)PG_GETARG_POINTER(1);

   PG_RETURN_BOOL(y_octet_16_cmp_internal(a, b) < 0);
}

PG_FUNCTION_INFO_V1(y_octet_16_lteq);
Datum
y_octet_16_lteq(PG_FUNCTION_ARGS)
{
   char *a;
   char *b;
   a = (char *)PG_GETARG_POINTER(0);
   b = (char *)PG_GETARG_POINTER(1);

   PG_RETURN_BOOL(y_octet_16_cmp_internal(a, b) <= 0);
}

PG_FUNCTION_INFO_V1(y_octet_16_gt);
Datum
y_octet_16_gt(PG_FUNCTION_ARGS)
{
   char *a;
   char *b;
   a = (char *)PG_GETARG_POINTER(0);
   b = (char *)PG_GETARG_POINTER(1);

   PG_RETURN_BOOL(y_octet_16_cmp_internal(a, b) > 0);
}

PG_FUNCTION_INFO_V1(y_octet_16_gteq);
Datum
y_octet_16_gteq(PG_FUNCTION_ARGS)
{
   char *a;
   char *b;
   a = (char *)PG_GETARG_POINTER(0);
   b = (char *)PG_GETARG_POINTER(1);

   PG_RETURN_BOOL(y_octet_16_cmp_internal(a, b) >= 0);
}

PG_FUNCTION_INFO_V1(y_octet_16_eq);
Datum
y_octet_16_eq(PG_FUNCTION_ARGS)
{
   char *a;
   char *b;
   a = (char *)PG_GETARG_POINTER(0);
   b = (char *)PG_GETARG_POINTER(1);

   PG_RETURN_BOOL(y_octet_16_cmp_internal(a, b) == 0);
}

PG_FUNCTION_INFO_V1(y_octet_16_ne);
Datum
y_octet_16_ne(PG_FUNCTION_ARGS)
{
   char *a;
   char *b;
   a = (char *)PG_GETARG_POINTER(0);
   b = (char *)PG_GETARG_POINTER(1);

   PG_RETURN_BOOL(y_octet_16_cmp_internal(a, b) != 0);
}

PG_FUNCTION_INFO_V1(y_octet_16_cmp);
Datum
y_octet_16_cmp(PG_FUNCTION_ARGS)
{
   char *a;
   char *b;
   a = (char *)PG_GETARG_POINTER(0);
   b = (char *)PG_GETARG_POINTER(1);

   PG_RETURN_INT32( y_octet_16_cmp_internal( a, b ) );
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
PG_FUNCTION_INFO_V1(text_to_y_octet_16);
Datum
text_to_y_octet_16(PG_FUNCTION_ARGS)
{
   text *txtstr;
   char *octstr;

   if( PG_ARGISNULL(0) ) {
      PG_RETURN_NULL();
   }
   txtstr = PG_GETARG_TEXT_P(0);
   
   octstr = hext2bin_palloc( txtstr, 16 );
   
   PG_RETURN_POINTER( octstr );
}

