/* WIDE AREA INFORMATION SERVER SOFTWARE:
   No guarantees or restrictions.  See the readme file for the full standard
   disclaimer.	
*/

/* Copyright (c) CNIDR (see ../COPYRIGHT) */

#ifndef lint
static char *RCSid = "$Header: /usr/users/freewais/FreeWAIS-0.1/ir/zutil.c,v 1.1 1993/02/16 15:05:35 freewais Exp $";
#endif

/* Change log:
 * $Log: zutil.c,v $
 * Revision 1.1  1993/02/16  15:05:35  freewais
 * Initial revision
 *
 * Revision 1.6  92/03/26  18:27:16  jonathan
 * Fixed function declaration.
 * 
 * Revision 1.5  92/03/07  19:41:14  jonathan
 * ANSIfied argument list.
 * 
 * Revision 1.4  92/02/12  14:01:37  jonathan
 * Added "$Log" so RCS will put the log message in the header
 * 
 *
   3.26.90	Harry Morris, morris@think.com
   3.30.90  Harry Morris - Changed any->bits to any->bytes
   4.11.90  HWM - fixed include file names, changed 
   				- writeCompressedIntegerWithPadding() to
                  writeCompressedIntWithPadding()
                - generalized conditional includes (see c-dialect.h)
   3.7.91   Jonny Goldman.  Replaced "short" in makeBitMap with "int" line 632.
*/

#define _C_utils_Z39_50_

#include "zutil.h"

#include <math.h>
#include <string.h>

char* readErrorPosition = NULL; /* pos where buf stoped making sense */

/*----------------------------------------------------------------------*/
/* A note on error handling 
   read - these are low level routines, they do not check the type tags
   which (sometimes) preceed the data (this is done by the higher
				       level functions which call these functions).  There is no 
				       attempt made to check that the reading does not exceed the read
				       buffer.  Such cases should be very rare and usually will be 
				       caught by the calling functions. (note - it is unlikely that 
				       a series of low level reads will go far off the edge without
				       triggering a type error.  However, it is possible for a single
				       bad read in an array function (eg. readAny) to attempt to read a 
				       large ammount, possibly causing a segmentation violation or out
				       of memory condition.
				       */
/*----------------------------------------------------------------------*/

diagnosticRecord* 
makeDiag(surrogate,code,addInfo)
     boolean surrogate;
     char* code;
     char* addInfo;
{
  diagnosticRecord* diag = 
    (diagnosticRecord*)s_malloc((size_t)sizeof(diagnosticRecord));
  
  diag->SURROGATE = surrogate;
  memcpy(diag->DIAG,code,DIAGNOSTIC_CODE_SIZE);
  diag->ADDINFO = s_strdup(addInfo); 

  return(diag);
}

/*----------------------------------------------------------------------*/

void 
freeDiag(diag)
     diagnosticRecord* diag;
{ 
  if (diag != NULL)
    { if (diag->ADDINFO != NULL)
	s_free(diag->ADDINFO);
	s_free(diag);
      }
}

/*----------------------------------------------------------------------*/

#define END_OF_RECORD	0x1D

char* 
writeDiag(diag,buffer,len)
     diagnosticRecord* diag;
     char* buffer;
     long* len;
     /* diagnostics (as per Appendix D) have a very weird format - this changes
	in SR-1
	*/
{
  char* buf = buffer;
  long  length;
  
  if (diag == NULL)		/* handle unspecified optional args */
    return(buf);

  buf = writeTag(DT_DatabaseDiagnosticRecords,buf,len);
  CHECK_FOR_SPACE_LEFT(0L,len);
  
  length = 3; 
  if (diag->ADDINFO != NULL)
    length += strlen(diag->ADDINFO);
    
  if (length >= 0xFFFF )	/* make sure the length is reasonable */
    { length = 0xFFFF - 1;
      diag->ADDINFO[0xFFFF - 3 - 1] = '\0';
    }
   
  buf = writeBinaryInteger(length,2L,buf,len);

  CHECK_FOR_SPACE_LEFT(1L,len);
  buf[0] = diag->DIAG[0]; 
  buf++;
  
  CHECK_FOR_SPACE_LEFT(1L,len);
  buf[0] = diag->DIAG[1];
  buf++;
  
  if (length > 3)
    { CHECK_FOR_SPACE_LEFT(3L,len);
      memcpy(buf,diag->ADDINFO,(size_t)length - 3);
      buf += length - 3;
    }
   
  CHECK_FOR_SPACE_LEFT(1L,len);
  buf[0] = diag->SURROGATE;
  buf++;
  
  CHECK_FOR_SPACE_LEFT(1L,len);
  buf[0] = END_OF_RECORD;
  buf++;

  return(buf);
}

/*----------------------------------------------------------------------*/

char* 
readDiag(diag,buffer)
     diagnosticRecord** diag;
     char* buffer;
{
  char* buf = buffer;
  diagnosticRecord* d 
    = (diagnosticRecord*)s_malloc((size_t)sizeof(diagnosticRecord));
  data_tag tag;
  long len;
  
  buf = readTag(&tag,buf);
  
  buf = readBinaryInteger(&len,2L,buf);
  
  d->DIAG[0] = buf[0];
  d->DIAG[1] = buf[1];
  d->DIAG[2] = '\0';
    
  if (len > 3)
    { d->ADDINFO = (char*)s_malloc((size_t)(len - 3 + 1));
      memcpy(d->ADDINFO,(char*)(buf + 2),(size_t)(len - 3));
      d->ADDINFO[len - 3] = '\0';
    }
  else
    d->ADDINFO = NULL;
    
  d->SURROGATE = buf[len - 1];
  
  *diag = d;

  return(buf + len + 1);
}

/*----------------------------------------------------------------------*/

#define continueBit	0x80
#define dataMask	0x7F
#define dataBits	7

char*
writeCompressedInteger(num,buf,len)
     unsigned long num;
     char* buf;
     long* len;
     /* write a binary integer in the format described on p. 40.
	this might be sped up 
	*/
{
  char byte;
  long i;
  unsigned long size;
  
  size = writtenCompressedIntSize(num);
  CHECK_FOR_SPACE_LEFT(size,len);
  
  for (i = size - 1; i >= 0; i--)
    { byte = num & dataMask;
      if (i != (size-1))	/* turn on continue bit */
	byte = (char)(byte | continueBit);
      buf[i] = byte;
      num = num >> dataBits;	/* don't and here */
    }
   
  return(buf + size);
} 

/*----------------------------------------------------------------------*/

char*
readCompressedInteger(num,buf)
     unsigned long *num;
     char* buf;
     /* read a binary integer in the format described on p. 40.
	this might be sped up 
	*/
{
  long i = 0;
  unsigned char byte;

  *num = 0;
  
  do 
    { byte = buf[i++];
      *num = *num << dataBits;
      *num += (byte & dataMask);
    }
  while (byte & continueBit);

  return(buf + i);
} 

/*----------------------------------------------------------------------*/

#define pad	128 /* high bit is set */

char*
writeCompressedIntWithPadding(num,size,buffer,len)
     unsigned long num;
     unsigned long size;
     char* buffer;
     long* len;
     /* Like writeCompressedInteger, except writes padding (128) to make
	sure that size bytes are used.  This can be read correctly by 
	readCompressedInteger()
	*/
{
  char* buf = buffer;
  unsigned long needed,padding;
  long i;
    
  CHECK_FOR_SPACE_LEFT(size,len);
  
  needed = writtenCompressedIntSize(num);
  padding = size - needed;
  i = padding - 1;

  for (i = padding - 1;i >= 0;i--)
    { buf[i] = pad;
    }
  
  buf = writeCompressedInteger(num,buf + padding,len);
  
  return(buf);
} 

/*----------------------------------------------------------------------*/

unsigned long
writtenCompressedIntSize(num)
     unsigned long num;
     /* return the number of bytes needed to represnet the value num in
	compressed format.  curently limited to 4 bytes
	*/
{
  if (num < CompressedInt1Byte) 
    return(1);
  else if (num < CompressedInt2Byte) 
    return(2);
  else if (num < CompressedInt3Byte)
    return(3);
  else
    return(4);    
}

/*----------------------------------------------------------------------*/

char*
writeTag(tag,buf,len)
     data_tag tag;
     char* buf;
     long* len;
     /* write out a data tag */
{ 
  return(writeCompressedInteger(tag,buf,len));
} 

/*----------------------------------------------------------------------*/

char*
readTag(tag,buf)
     data_tag* tag;
     char* buf;
     /* read a data tag */
{ 
  return(readCompressedInteger(tag,buf));
} 

/*----------------------------------------------------------------------*/

unsigned long 
writtenTagSize(tag)
     data_tag tag;
{ 
  return(writtenCompressedIntSize(tag));
}

/*----------------------------------------------------------------------*/

data_tag
peekTag(buf)
     char* buf;
     /* read a data tag without advancing the buffer */
{
  data_tag tag;
  readTag(&tag,buf);
  return(tag);
} 

/*----------------------------------------------------------------------*/

any* 
makeAny(size,data)
     unsigned long size;
     char* data;
{
  any* a = (any*)s_malloc((size_t)sizeof(any));
  a->size = size;
  a->bytes = data;
  return(a);
}

/*----------------------------------------------------------------------*/

void
freeAny(a)
     any* a;
     /* destroy an any and its associated data. Assumes a->bytes was
	allocated using the s_malloc family of libraries 
	*/
{
  if (a != NULL)
    { if (a->bytes != NULL)
	s_free(a->bytes);
	s_free(a);
      }
}

/*----------------------------------------------------------------------*/

any* 
duplicateAny(a)
     any* a;
{
  any* copy = NULL;

  if (a == NULL)
    return(NULL);

  copy = (any*)s_malloc((size_t)sizeof(any));
  copy->size = a->size;
  if (a->bytes == NULL)
    copy->bytes = NULL;
  else
    { copy->bytes = (char*)s_malloc((size_t)copy->size);
      memcpy(copy->bytes,a->bytes,(size_t)copy->size);
    }
  return(copy);
}

/*----------------------------------------------------------------------*/

char* 
writeAny(a,tag,buffer,len)
     any* a;
     data_tag tag;
     char* buffer;
     long* len;
     /* write an any + tag and size info */
{
  char* buf = buffer;

  if (a == NULL)		/* handle unspecified optional args */
    return(buf);
  
  /* write the tags */
  buf = writeTag(tag,buf,len);
  buf = writeCompressedInteger(a->size,buf,len);

  /* write the bytes */
  CHECK_FOR_SPACE_LEFT(a->size,len);
  memcpy(buf,a->bytes,(size_t)a->size);

  return(buf+a->size);
}

/*----------------------------------------------------------------------*/

char* 
readAny(anAny,buffer)
     any** anAny;
     char* buffer;
     /* read an any + tag and size info */
{
  char* buf = buffer;
  any* a = (any*)s_malloc((size_t)sizeof(any));
  data_tag tag;
  
  buf = readTag(&tag,buf);
  buf = readCompressedInteger(&a->size,buf);
  /* now simply copy the bytes */
  a->bytes = (char*)s_malloc((size_t)a->size);
  memcpy(a->bytes,buf,(size_t)a->size);
  *anAny = a;
  return(buf+a->size);
}

/*----------------------------------------------------------------------*/

unsigned long 
writtenAnySize(tag,a)
     data_tag tag;
     any* a;
{
  unsigned long size;

  if (a == NULL)
    return(0);

  size = writtenTagSize(tag);
  size += writtenCompressedIntSize(a->size);
  size += a->size;
  return(size);
}

/*----------------------------------------------------------------------*/

any*
stringToAny(s)
     char* s;
{
  any* a = NULL;
  
  if (s == NULL)
    return(NULL);
    
  a = (any*)s_malloc((size_t)sizeof(any));
  a->size = strlen(s);
  a->bytes = (char*)s_malloc((size_t)a->size);
  memcpy(a->bytes,s,(size_t)a->size);
  return(a);
}

/*----------------------------------------------------------------------*/

char*
anyToString(a)
     any* a;
{
  char* s = NULL;
  
  if (a == NULL)
    return(NULL);
    
  s = s_malloc((size_t)(a->size + 1));
  memcpy(s,a->bytes,(size_t)a->size);
  s[a->size] = '\0';
  return(s);
}

/*----------------------------------------------------------------------*/

char* 
writeString(s,tag,buffer,len)
     char* s;
     data_tag tag;
     char* buffer;
     long* len;
     /* Write a C style string.  The terminating null is not written. 
	This function is not part of the Z39.50 spec.  It is provided
	for the convienience of those wishing to pass C strings in 
	the place of an any.
	*/
{
  char* buf = buffer;
  any* data = NULL;
  if (s == NULL)
    return(buffer);		/* handle unused optional item before making an any */
  data = (any*)s_malloc((size_t)sizeof(any)); 
  data->size = strlen(s);
  data->bytes = s;		/* save a copy here by not using stringToAny() */
  buf = writeAny(data,tag,buf,len);
  s_free(data);			/* don't use freeAny() since it will free s too */
  return(buf);
}

/*----------------------------------------------------------------------*/

char* 
readString(s ,buffer)
     char** s ;
     char* buffer;
     /* Read an any and convert it into a C style string.
	This function is not part of the Z39.50 spec.  It is provided
	for the convienience of those wishing to pass C strings in 
	the place of an any. 
	*/
{
  any* data = NULL;
  char* buf = readAny(&data,buffer);
  *s = anyToString(data);
  freeAny(data);
  return(buf);
}

/*----------------------------------------------------------------------*/

unsigned long 
writtenStringSize(tag,s)
     data_tag tag;
     char* s;
{
  unsigned long size;

  if (s == NULL)
    return(0);

  size = writtenTagSize(tag);
  size += writtenCompressedIntSize(size);
  size += strlen(s);
  return(size);
}

/*----------------------------------------------------------------------*/

any* 
longToAny(num)
     long num;
     /* a convienience function */
{
  char s[40];

  sprintf(s,"%ld",num);
  
  return(stringToAny(s));
}

/*----------------------------------------------------------------------*/

long
anyToLong(a)
     any* a;
     /* a convienience function */
{
  long num;
  char* str = NULL;
  str = anyToString(a);
  sscanf(str,"%ld",&num);	/* could check the result and return
				   an error */
  s_free(str);
  return(num);
}

/*----------------------------------------------------------------------*/

#define bitsPerByte	8

#ifdef ANSI_LIKE /* use ansi */

bit_map*
makeBitMap(unsigned long numBits,...)
     /* construct and return a bitmap with numBits elements */
{
  va_list ap;
  long i,j;
  bit_map* bm = NULL;

  va_start(ap,numBits);
  
  bm = (bit_map*)s_malloc((size_t)sizeof(bit_map));
  bm->size = (unsigned long)ceil((double)numBits / bitsPerByte); 
  bm->bytes = (char*)s_malloc((size_t)bm->size);
  
  /* fill up the bits */
  for (i = 0; i < bm->size; i++) /* iterate over bytes */
    { char byte = 0;
      for (j = 0; j < bitsPerByte; j++) /* iterate over bits */
	{ if ((i * bitsPerByte + j) < numBits)
	    { boolean bit = false;
	      bit = (boolean)va_arg(ap,boolean); 
	      if (bit)
	        { byte = byte | (1 << (bitsPerByte - j - 1));
	        }
	    }
	  }
      bm->bytes[i] = byte;
    }

  va_end(ap);
  return(bm);
}

#else /* use K & R */

bit_map*
makeBitMap(va_alist)
     va_dcl
     /* construct and return a bitmap with numBits elements */
{
  va_list ap;
  long i,j;
  unsigned long numBits;
  bit_map* bm = (bit_map*)s_malloc((size_t)sizeof(bit_map));

  va_start(ap);
  
  numBits = va_arg(ap,unsigned long);

  bm->size = (unsigned long)ceil((double)numBits / bitsPerByte);
  
  bm->bytes = (char*)s_malloc((size_t)bm->size);
  
  /* fill up the bits */
  for (i = 0; i < bm->size; i++) /* iterate over bytes */
    { char byte = 0;
      for (j = 0; j < bitsPerByte; j++) /* iterate over bits */
	{ if ((i * bitsPerByte + j) < numBits)
	    { boolean bit;
	      bit = (boolean)va_arg(ap,int); /* really boolean, but this 
						is how it is passed on stack */
	      if (bit)
	        { byte = byte | (1 << (bitsPerByte - j - 1));
	        }
	    }
	  }
      bm->bytes[i] = byte;
    }

  va_end(ap);

  return(bm);
}

#endif 

/*----------------------------------------------------------------------*/

void
freeBitMap(bm)
     bit_map* bm;
     /* destroy a bit map created by makeBitMap() */
{
  s_free(bm->bytes);
  s_free(bm);
}

/*----------------------------------------------------------------------*/

/* use this routine to interpret a bit map.  pos specifies the bit 
   number.  bit 0 is the Leftmost bit of the first byte.  
   Could do bounds checking.
   */

boolean
bitAtPos(pos,bm)
     long pos;
     bit_map* bm;
{
  if (pos > bm->size*bitsPerByte)
    return false;
  else
    return((bm->bytes[(pos / bitsPerByte)] & 
	    (0x80>>(pos % bitsPerByte))) ?
	   true : false);
}

/*----------------------------------------------------------------------*/

char*
writeBitMap(bm,tag,buffer,len)
     bit_map* bm;
     data_tag tag;
     char* buffer;
     long* len;
     /* write a bitmap + type and size info */
{ 
  return(writeAny((any*)bm,tag,buffer,len));
}

/*----------------------------------------------------------------------*/

char*
readBitMap(bm,buffer)
     bit_map** bm;
     char* buffer;
     /* read a bitmap + type and size info */
{ 
  return(readAny((any**)bm,buffer));
}

/*----------------------------------------------------------------------*/

char* 
writeByte(byte,buf,len)
     unsigned long byte;
     char* buf;
     long* len;
{
  CHECK_FOR_SPACE_LEFT(1L,len);
  buf[0] = byte & 0xFF;		/* we really only want the first byte */
  return(buf + 1);
}

/*----------------------------------------------------------------------*/

char* 
readByte(byte,buf)
     unsigned char* byte;
     char* buf;
{
  *byte = buf[0];
  return(buf + 1);
}

/*----------------------------------------------------------------------*/

char* 
writeBoolean(flag,buf,len)
     boolean flag;
     char* buf;
     long* len;
{
  return(writeByte(flag,buf,len));
}

/*----------------------------------------------------------------------*/

char* 
readBoolean(flag,buffer)
     boolean* flag;
     char* buffer;
{
  unsigned char byte;
  char* buf = readByte(&byte,buffer);
  *flag = (byte == true) ? true : false;
  return(buf);
}

/*----------------------------------------------------------------------*/

char*
writePDUType(pduType,buf,len)
     pdu_type pduType;
     char* buf;
     long* len;
     /* PDUType is a single byte */
{
  return(writeBinaryInteger((long)pduType,(unsigned long)1,buf,len));
} 

/*----------------------------------------------------------------------*/

char*
readPDUType(pduType,buf)
     pdu_type* pduType;
     char* buf;
     /* PDUType is a single byte */
{
  return(readBinaryInteger((long*)pduType,(unsigned long)1,buf));
} 

/*----------------------------------------------------------------------*/

pdu_type
peekPDUType(buf)
     char* buf;
     /* read the next pdu without advancing the buffer, Note that this 
	function is to be used on a buffer that is known to contain an
	APDU.  The pdu_type is written HEADER_LEN bytes into the buffer
	*/
{
  pdu_type pdu;
  readPDUType(&pdu,buf + HEADER_LEN);
  return(pdu);
}

/*----------------------------------------------------------------------*/

#define BINARY_INTEGER_BYTES	sizeof(long) /* the number of bytes used by
						a "binary integer" */
char*
writeBinaryInteger(num,size,buf,len)
     long num;
     unsigned long size;
     char* buf;
     long* len;
     /* write out first size bytes of num - no type info
	XXX should this take unsigned longs instead ???  */
{
  long i;
  char byte;

  if (size < 1 || size > BINARY_INTEGER_BYTES)
    return(NULL);		/* error */

  CHECK_FOR_SPACE_LEFT(size,len);

  for (i = size - 1; i >= 0; i--)
    { byte = (char)(num & 255);
      buf[i] = byte;
      num = num >> bitsPerByte; /* don't and here */
    }

  return(buf + size);
}

/*----------------------------------------------------------------------*/

char*
readBinaryInteger(num,size,buf)
     long* num;
     unsigned long size;
     char* buf;
     /* read in first size bytes of num - no type info
	XXX this should take unsigned longs instead !!! */
{
  long i;
  unsigned char byte;
  if (size < 1 || size > BINARY_INTEGER_BYTES)
    return(buf);		/* error */
  *num = 0;
  for (i = 0; i < size; i++)
    { byte = buf[i];
      *num = *num << bitsPerByte;
      *num += byte;
    }
  return(buf + size);
}

/*----------------------------------------------------------------------*/

unsigned long 
writtenCompressedBinIntSize(num)
     long num;
     /* return the number of bytes needed to represent the value num.
	currently limited to max of 4 bytes 
	Only compresses for positive nums - negatives get whole 4 bytes
	*/
{
  if (num < 0L)
    return(4);
  else if (num < 256L)		/* 2**8 */
    return(1);
  else if (num < 65536L)	/* 2**16 */
    return(2);
  else if (num < 16777216L)	/* 2**24 */
    return(3);
  else
    return(4);
}

/*----------------------------------------------------------------------*/

char*
writeNum(num,tag,buffer,len)
     long num;
     data_tag tag;
     char* buffer;
     long* len;
     /* write a binary integer + size and tag info */
{
  char* buf = buffer;
  long size = writtenCompressedBinIntSize(num);
  
  if (num == UNUSED)
    return(buffer);
    
  buf = writeTag(tag,buf,len);
  buf = writeCompressedInteger(size,buf,len); 
  buf = writeBinaryInteger(num,(unsigned long)size,buf,len); 
  return(buf);
}

/*----------------------------------------------------------------------*/

char*
readNum(num,buffer)
     long* num;
     char* buffer;
     /* read a binary integer + size and tag info */
{
  char* buf = buffer;
  data_tag tag;
  unsigned long size;
  unsigned long val;
  
  buf = readTag(&tag,buf);
  buf = readCompressedInteger(&val,buf);
  size = (unsigned long)val;
  buf = readBinaryInteger(num,size,buf);
  return(buf);
}

/*----------------------------------------------------------------------*/

unsigned long 
writtenNumSize(tag,num)
     data_tag tag;
     long num;
{
  long dataSize = writtenCompressedBinIntSize(num);
  long size;
  
  size = writtenTagSize(tag);	/* space for the tag */
  size += writtenCompressedIntSize(dataSize); /* space for the size */
  size += dataSize;		/* space for the data */
  
  return(size);
}

/*----------------------------------------------------------------------*/

typedef void (voidfunc)();
     
     void
     doList(list,func)
     void** list;
     voidfunc *func;
     /* call func on each element of the NULL terminated list of pointers */
{
  register long i;
  register void* ptr = NULL;
  if (list == NULL)
    return;
  for (i = 0,ptr = list[i]; ptr != NULL; ptr = list[++i])
    (*func)(ptr);
}

/*----------------------------------------------------------------------*/

char* 
writeProtocolVersion(buf,len)
     char* buf;
     long* len;
     /* write a bitmap describing the protocols available */
{
  static bit_map* version = NULL;

  if (version == NULL)
    { version = makeBitMap((unsigned long)1,true); /* version 1! */
    }
    
  return(writeBitMap(version,DT_ProtocolVersion,buf,len));
}

/*----------------------------------------------------------------------*/

char*
 defaultImplementationID()
{
  static char	ImplementationID[] = "TMC";
  return(ImplementationID);
}

/*----------------------------------------------------------------------*/

char*
 defaultImplementationName()
{
  static char ImplementationName[] = "Thinking Machines Corporation Z39.50";
  return(ImplementationName);
}

/*----------------------------------------------------------------------*/

char*
 defaultImplementationVersion()
{
  static char	ImplementationVersion[] = "2.0A";
  return(ImplementationVersion);
}

/*----------------------------------------------------------------------*/

