#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <limits.h>
#include <errno.h>

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

/* berOid - the encoding of an object identifier.
 * the encoding of this is a real hassel. First of all you have the same 
 * anoying problem with length as you do with the strings and sequence. Then
 * you have to take the first two oids multiply the first one by 40 and add the
 * second one. Then you can go through the rest of the oids and encode them
 * however they are not encoded as simple integers or anything they are big
 * endian two's compliment stuffed into as few bytes as possible. The way that 
 * you stuff them into as few bytes as possible and deal with the fact that 
 * that you don't know how many bytes each suboid is going to be is to use
 * the MSB to indicate if there are more bits to follow. Thus you can only 
 * use the 7 LSB to represent the data. So a normal long can end up being 
 * up to 5 bytes long.
 */

BerOid::BerOid(unsigned char *str): BerBase(str){
  assert(str[0]==OID_TAG);
  unsigned char headerlen;
  unpack_len(str,headerlen);
  assert(eoid=new unsigned char[oidbytes=edatacache->Length()-headerlen]);
  memcpy(eoid,str+headerlen,oidbytes);
}

BerEncodedData *BerOid::encode(){
  unsigned char headerlen;
  unsigned char *retval=start_data(OID_TAG,oidbytes,headerlen);
  
  memcpy(retval+headerlen,eoid,oidbytes);
  return new BerEncodedData(retval,headerlen+oidbytes);
}

int unpack_suboid(unsigned char* buf, unsigned int &len){
  for(len=0;len<sizeof(long)+1;len++)
    if(buf[len]<128)
      break;
  long val=0;
  switch(len){
  case 4:
    val|=(buf[0]&0x7f)<<28;
    buf++;
  case 3:
    val|=(buf[0]&0x7f)<<21;
    buf++;
  case 2:
    val|=(buf[0]&0x7f)<<14;
    buf++;
  case 1:
    val|=(buf[0]&0x7f)<<7;
    buf++;
  default:
    val|=buf[0];
  }
  len++;
  return val;

}

int pack_suboid(long suboid, unsigned char *outbuf){
  unsigned char buf[sizeof(long)+1];
  unsigned char len=0;
  // spread out the bits
  signed char i;
  for(i=sizeof(long);i>=0;i--)
    buf[i]=(suboid>>7*i)&0x7f;

  // mark the bits
  for(i=sizeof(long)-1;i>=0;i--)
    if(buf[i]>0) {
      for(char j=i;j>0;j--)
	buf[j]|=0x80;
      len=i+1;
      break;
    }

  if(len==0)
    len=1;

  unsigned char j=0;
  for(i=len-1;i>=0;i--)
    outbuf[j++]=buf[i];

  return len;
}         

BerOid::BerOid(CONST char *oidstr,unsigned int len){
  assert(oidstr!=NULL);
  // should be big enough. I can't think of a case where an oid would expand
  // when encoded.
  assert(eoid=new unsigned char[len]);
  
  const char *end=oidstr+len;
  char *curstr;
  long val=strtol(oidstr,&curstr,10);
  assert(val!=LONG_MAX && errno!=ERANGE);
  if(*curstr==0){
    assert(curstr==end);
    // only one suboid
    oidbytes=pack_suboid(val*40,eoid)+2;
    assert(oidbytes<=len);
    eoid=(unsigned char*)realloc(eoid,oidbytes);
    return;
  } 
  assert(*curstr=='.');
  oidstr=curstr+1;

  long val2=strtol(oidstr,&curstr,10);
  assert(val2!=LONG_MAX && errno!=ERANGE);
  oidbytes=pack_suboid(val*40+val2,eoid);
  assert(oidbytes<=len);

  if(*curstr==0){
    assert(curstr==end);
    // only two suboids
    eoid=(unsigned char*)realloc(eoid,oidbytes);
    return;
  }
  assert(*curstr=='.');
  oidstr=curstr+1;

  while(oidstr<end){
    val=strtol(oidstr,&curstr,10);    
    assert(val!=LONG_MAX && errno!=ERANGE);
    assert(!(*curstr==0 && curstr!=end)); // string too short
    assert(!(*curstr!='.' && curstr!=end));
    oidstr=curstr+1;
    oidbytes+=pack_suboid(val,eoid+oidbytes);
    assert(oidbytes<=len);
  }
  eoid=(unsigned char*)realloc(eoid,oidbytes);
}

int BerOid::print(char *buf, unsigned int len){
  unsigned char *cur=eoid;
  unsigned int elen=0,totlen=0;
  int slen;
  int val=unpack_suboid(cur,elen);
  cur+=elen;
  slen=snprintf(buf,len,"%d.%d",val/40,val%40);
  if(slen==-1) return -1;
  buf+=slen;
  totlen=slen;
  len-=slen;
  while(cur<eoid+oidbytes){
    val=unpack_suboid(cur,elen);
    cur+=elen;
    slen=snprintf(buf,len,".%d",val);
    if(slen==-1) return -1;
    buf+=slen;
    len-=slen;
    totlen+=slen;
  }
  return totlen;
}

int BerOid::operator==(BerOid &other){
  unsigned int len=oidbytes>other.oidbytes?other.oidbytes:oidbytes;
  return !memcmp(eoid,other.eoid,len);
}


syntax highlighted by Code2HTML, v. 0.9.1