#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <limits.h>
#include <errno.h>
#include <assert.h>
#include <ctype.h>
#include <sys/types.h>
#include <netinet/in.h>

#include "ber.h"
#include "compat.h"

BerEncodedData::BerEncodedData(unsigned char *dat, unsigned long len): 
  data(dat),length(len),next(NULL){
  assert(data);
}

unsigned char *start_data(Tags type,unsigned int len, unsigned char &headlen){
  unsigned char *retstr;
  if(len<128){
    assert(retstr=new unsigned char[len+2]);
    retstr[1]=len;
    headlen=2;
  } else {
    unsigned char buf[sizeof(unsigned long)];
    unsigned long nlen=htonl(len);
    memcpy(buf,&nlen,sizeof(unsigned long));

    //counts the number of leading 0's
    for(headlen=0;buf[headlen]==0 && headlen<3;headlen++);
    //magic function to to turn this into header length
    headlen=sizeof(unsigned long)-headlen+2;

    assert(retstr=new unsigned char[len+headlen]);
    retstr[1]=0x80-2+headlen; 

    char k=2;
    for(char j=sizeof(unsigned long)+2-headlen;j<=3;j++)
      retstr[k++]=buf[j];
  }
  retstr[0]=type;
  return retstr;
}

unsigned long unpack_len(unsigned char *start,unsigned char &headerlen){
  if(start[1]<128){
    headerlen=2;    
    return start[1];
  }
  long sizeofsize=start[1]&0x7f;
  assert(sizeofsize<=sizeof(long));
  long size=0;
  memcpy(((unsigned char*)(&size))+sizeof(long)-sizeofsize,start+2,sizeofsize);
  headerlen=2+sizeofsize;
  return htonl(size);
}

/* ber class is the basic encoding rules for ASN.1 class. All the classes that
 *     have encoding are derived from this class.
 * len - is the length of the encoded sequence.
 * mbufcache - is the the head of the mbuf chain that this class will hand out 
 *     when getmbuf is called. It is set to NULL initially and getmbuf fills 
 *     it in and subsequent calls just return it.
 * getmbuf - most classes just use the default getmbuf but sequences need to 
 *     do some special processing and so it must be used be virtual.
 * encode - does whatever is necessary to come up with a string to pass out
 *     the calling function. The string is probably not NULL terminated and 
 *     probably has NULLs in it.
 * Len - is used to find out the length of the encoded sequence. The value is
 *     undefined before an encode is called.
 */

BerEncodedData *BerBase::encode(){
  assert(0);
  return NULL;
}

BerEncodedData *BerBase::getdata(){
  assert(edatacache || (edatacache=encode()));
  return edatacache;
}

unsigned long BerBase::fulllen(){
  BerEncodedData *bed=getdata();
  unsigned long retval=bed->Length();
  while(bed->next){
    bed=bed->next;
    retval+=bed->Length();
  }
  return retval;
}

BerBase::BerBase(unsigned char* str):next(NULL){
  unsigned char *retstr;
  unsigned char headerlen;
  unsigned long size=unpack_len(str,headerlen);
  unsigned long fullen=size+headerlen;
  assert(retstr=new unsigned char[fullen]);
  memcpy(retstr,str,fullen);
  assert(edatacache=new BerEncodedData(retstr,fullen));
}

/* 
 *
 * berNull - is the string 0x05 0x00 there is really not much too it.
 *
 */

int BerNull::print(char *buf, unsigned int len){
  if(len<4) return -1;
  strncpy(buf,"NULL",4);
  return 4;
}

BerNull::BerNull(unsigned char *str): BerBase(str){
  assert(str[0]==NULL_TAG);
  assert(str[1]==0);
}

BerEncodedData *BerNull::encode(){
  unsigned char headlen;
  return new BerEncodedData(start_data(NULL_TAG,0,headlen),headlen);
}

/* berInt is the encoding of an int in ASN.1
 * The encoding is a 0x02 followed by the size of the data then the bytes of
 * of the data. The data is stored in the big endian 2's complement using the
 * fewest bytes possible. However a 0 still uses one byte. i.e. 0x20 0x01 0x00
 */

int BerInt::print(char *buf, unsigned int len){
  return snprintf(buf,len,"%d",val);
}

BerInt::BerInt(unsigned char *str): BerBase(str),val(0){
  /* this might cause some problems with the counters that exceede 31 bits in 
     length but for now it seems ok */
  assert(str[0]==INT_TAG||str[0]==COUNTER_TAG);
  assert(str[1]<=sizeof(long));
  memcpy(((unsigned char*)&val)+sizeof(long)-str[1],str+2,str[1]);
  //extend out the sign if necessary
  if(str[0]==INT_TAG && str[1]!=sizeof(long) && str[2]&0x80)
    memset(&val,0xff,sizeof(long)-str[1]);
  // being lazy again some high bit counters may cause a problem.
  assert(!(str[0]==COUNTER_TAG && str[1]==sizeof(long) && str[2]&0x80));
  val=ntohl(val);
}

BerEncodedData *BerInt::encode(){
  unsigned char buf[sizeof(long)];
  unsigned char *retval;
  unsigned char i,headerlen;
  long value=htonl(val);
  memcpy(buf,&value,sizeof(long));
  //  printf("0=%d 1=%d 2=%d 3=%d ",(int)buf[0],(int)buf[1],(int)buf[2],
  // (int)buf[3]);

  //count the number of bytes that are actually used
  for(i=sizeof(long);!buf[sizeof(long)-i] && i>1;i--);
  /* zero pad the case when the most significant byte has a 1 in the most 
     significant bit -- otherwise the other end will interpret it as a negative
     number */
  if(i!=sizeof(long) && buf[sizeof(long)-i]&0x7f)
    i++;

  //  printf("val=%ld len=%u buf[0]=%u buf[1]=%u buf[2]=%u buf[3]=%u\n",val,
  //	 (unsigned) i,(unsigned) buf[0],(unsigned) buf[1],(unsigned) buf[2],
  //	 (unsigned) buf[3]);

  retval=start_data(INT_TAG,i,headerlen);
  memcpy(retval+headerlen,buf+sizeof(long)-i,i);
  //  printf("i=%d ",(int) i);
  
  //  printf("va// lue=%d ",val);
//   for(int j=0;j<i;j++){
//     printf("i=%d val=%d ",j,(int) *(retval+headerlen+j));
//   }
//   printf("\n");

  return new BerEncodedData(retval,headerlen+i);
}

/*
 * Number of timeticks since some epoch. In 1/100 seconds
 */

BerTimeTick::BerTimeTick(unsigned char *str): BerBase(str),val(0){
  assert(str[0]==TIME_TICK_TAG);
  assert(str[1]<=sizeof(unsigned long));
  memcpy(((unsigned char*)&val)+sizeof(unsigned long)-str[1],str+2,str[1]);
  val=ntohl(val);
}

BerEncodedData *BerTimeTick::encode(){
  unsigned char buf[sizeof(unsigned long)];
  unsigned char *retval;
  unsigned char i,headerlen;
  long value=htonl(val);
  memcpy(buf,&value,sizeof(unsigned long));

  //count the number of bytes that are actually used
  for(i=sizeof(long);!buf[sizeof(unsigned long)-i] && i>1;i--);

  retval=start_data(TIME_TICK_TAG,i,headerlen);
  memcpy(retval+headerlen,buf,i);
  return new BerEncodedData(retval,headerlen+i);
}

int BerTimeTick::print(char *buf, unsigned int len){
  // hopefully this works by just assming the bits are right rather than 
  // coercing the value into a different format.
  unsigned t1=val%8640000;
  unsigned t2=t1%360000;
  unsigned t3=t2%6000;
  return snprintf(buf,len,"Time: %ud %uh %um %u.%usec (%u)",val/8640000,
		  t1/360000,t2/6000,t3/100,t3%100,val);
}


/* 
 * berString is used to encode a string. It is simply the length and the data.
 * the only complication is the encoding of the length.
 * Also terminates string.
 */

int BerString::copy(char *buf, unsigned int len){
  assert(len>stringlen+1);
  memcpy(buf,str,stringlen);
  buf[stringlen]='\0';
  return stringlen;
}

int BerString::print(char *buf, unsigned int len){
  int totlenused=0;
  /* strings are used for binary data as well as text. We need to handle 8 bit
     data as well as normal printable strings */
  for(char *cur=str;cur<str+stringlen;cur++){
    int lenused=snprintf(buf,len,isprint(*cur)?"%c":"\\0x%02x",
			 *(unsigned char*)cur);
    assert(lenused!=-1);
    totlenused+=lenused;
    len-=lenused;
    buf+=lenused;
  }
  return totlenused;
}

BerString::BerString(CONST char *strng,unsigned int length): 
  stringlen(length){ 
  assert(strng);
  assert(str=new char[length]);
  memcpy(str,strng,length);
}

BerString::BerString(unsigned char *strn): BerBase(strn){ // Wire protocol only
  assert(strn[0]==STRING_TAG);
  unsigned char headerlen;
  unpack_len(strn,headerlen);
  // I can use edatacache->len here because BerBase(strn) fills in edatacache.
  assert(str=new char[edatacache->Length()-headerlen]);
  stringlen=edatacache->Length()-headerlen;
  memcpy(str,strn+headerlen,stringlen);
}

BerEncodedData *BerString::encode(){
  unsigned char headerlen;
  unsigned char *retval=start_data(STRING_TAG,stringlen,headerlen);
  memcpy(retval+headerlen,str,stringlen);
  return new BerEncodedData(retval,headerlen+stringlen);
}

/* 
 * BerIPAddr is used to encode an ipaddress. 
 */

int BerIPAddr::print(char *buf, unsigned int length){
  assert(len==4); // don't know how to deal with v6 yet
  // might be a problem with endian but I borrowed this code out of nslookup
  return snprintf(buf,length,"%u.%u.%u.%u;",
		  ((unsigned)ipaddr[0] & 0xff),
		  ((unsigned)ipaddr[1] & 0xff),
		  ((unsigned)ipaddr[2] & 0xff),
		  ((unsigned)ipaddr[3] & 0xff));
}

BerIPAddr::BerIPAddr(const char *addr,int length): 
  len(length){ 
  assert(addr);
  assert(len==4); //don't know how to deal with anything but v4
  assert(ipaddr=new unsigned char[4]);
  memcpy(ipaddr,addr,4);
}

// Wire protocol only
BerIPAddr::BerIPAddr(unsigned char *strn): BerBase(strn),len(4){ 
  assert(strn[0]==IPADDR_TAG);
  unsigned char headerlen;
  unpack_len(strn,headerlen);
  // I can use edatacache->len here because BerBase(strn) fills in edatacache.
  assert(edatacache->Length()==6);
  assert(ipaddr=new unsigned char[4]);
  memcpy(ipaddr,strn+headerlen,4);
}

BerEncodedData *BerIPAddr::encode(){
  unsigned char headerlen;
  unsigned char *retval=start_data(IPADDR_TAG,4,headerlen);
  memcpy(retval+headerlen,ipaddr,4);
  return new BerEncodedData(retval,headerlen+len);
}



syntax highlighted by Code2HTML, v. 0.9.1