/**
 * asn.cpp
 * This file is part of the YATE Project http://YATE.null.ro
 *
 * ASN.1 Library
 *
 * Yet Another Telephony Engine - a fully featured software PBX and IVR
 * Copyright (C) 2004-2014 Null Team
 *
 * This software is distributed under multiple licenses;
 * see the COPYING file in the main directory for licensing
 * information for this specific distribution.
 *
 * This use of this software may be subject to additional restrictions.
 * See the LEGAL file in the main directory for details.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "yateasn.h"

using namespace TelEngine;

static String s_libName = "ASNLib";

ASNLib::ASNLib()
{}

ASNLib::~ASNLib()
{}

int ASNLib::decodeLength(DataBlock& data) {

    XDebug(s_libName.c_str(),DebugAll,"::decodeLength() - from data='%p'",&data);
    int length = 0;
    uint8_t lengthByte = data[0];

    if (lengthByte & ASN_LONG_LENGTH) { // the length is represented on more than one byte

	lengthByte &= ~ASN_LONG_LENGTH;	/* turn MSB off */
	if (lengthByte == 0) {
	    data.cut(-1);
	    return IndefiniteForm;
	}

	if (lengthByte > sizeof(int))
	    return InvalidLengthOrTag;

	for (int i = 0 ; i < lengthByte ; i++)
	    length = (length << 8) + data[1 + i];

	data.cut(-lengthByte - 1);
	return length;

    } else { // one byte for length
	length = (int) lengthByte;
	data.cut(-1);
	return length;
    }
}

DataBlock ASNLib::buildLength(DataBlock& data)
{
    XDebug(s_libName.c_str(),DebugAll,"::buildLength() - encode length=%d",data.length());
    DataBlock lenDb;
    if (data.length() < 0)
	return lenDb;
    if (data.length() < ASN_LONG_LENGTH) {
	uint8_t l = data.length();
	lenDb.append(&l, 1);
	return lenDb;
    }
    else {
    	uint8_t longLen = ASN_LONG_LENGTH;
	int len = data.length();
	while (len > 0) {
	    uint8_t v = len & 0xFF;
	    lenDb.insert(DataBlock(&v,1));
	    len >>= 8;
	}
	longLen |= lenDb.length();
	lenDb.insert(DataBlock(&longLen,1));
	return lenDb;
    }
   return lenDb;
}

int ASNLib::matchEOC(DataBlock& data)
{
    /**
     * EoC = 00 00
     */
    XDebug(s_libName.c_str(),DebugAll,"::matchEOC() in data='%p'",&data);
    if (data.length() < 2)
	return InvalidLengthOrTag;
    if (data[0] == 0 && data[1] == 0) {
    	data.cut(-2);
    	return 2;
    }
    return InvalidLengthOrTag;
}


int ASNLib::parseUntilEoC(DataBlock& data, int length)
{
    if (length >= (int)data.length() || ASNLib::matchEOC(data) > 0)
	return length;
    while (data.length() && ASNLib::matchEOC(data) < 0) {
	// compute tag portion length
	AsnTag tag;
	AsnTag::decode(tag,data);
	length += tag.coding().length();
	data.cut(-(int)tag.coding().length());
	// compute length portion length
	int initLen = data.length();
	int len = ASNLib::decodeLength(data);
	length += initLen - data.length();

	bool checkEoC = (len == ASNLib::IndefiniteForm);
	if (!checkEoC && len < 0)
	    return length;

	if (checkEoC) {
	    length = parseUntilEoC(data,length);
	    if (ASNLib::matchEOC(data) > 0)
		length += 2;
	}
	else {
	    length += len;
	    data.cut(-len);
	}
    }
    return length;
}

int ASNLib::decodeBoolean(DataBlock& data, bool* val, bool tagCheck)
{
    /**
     * boolean = 0x01 length byte (byte == 0 => false, byte != 0 => true)
     */
    XDebug(s_libName.c_str(),DebugAll,"::decodeBoolean() from data='%p'",&data);
    if (data.length() < 2)
	return InvalidLengthOrTag;
#ifdef DEBUG
    unsigned int initLen = data.length();
#endif
    if (tagCheck) {
	int type = data[0];
	if ((type != BOOLEAN)) {
	    XDebug(s_libName.c_str(),DebugAll,"::decodeBoolean() - Invalid Tag in data='%p'",&data);
	    return InvalidLengthOrTag;
	}
	data.cut(-1);
    }
    int length = decodeLength(data);
    if (length < 0) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeBoolean() - Invalid Length in data='%p'",&data);
	return length;
    }

    if ((unsigned int)length > data.length() || length != 1) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeBoolean() - Invalid Length in data='%p'",&data);
	return InvalidLengthOrTag;
    }
    if (!val) {
        data.cut(-1);
        DDebug(s_libName.c_str(),DebugAll,"::decodeBoolean() - Invalid buffer for return data");
        return InvalidContentsError;
    }
    *val = false;
    if ((data[0] & 0xFF) != 0)
	*val = true;
    data.cut(-1);
#ifdef DEBUG
    Debug(s_libName.c_str(),DebugAll,"::decodeBoolean() - decoded boolean value from data='%p', consumed %u bytes",
    	&data, initLen - data.length());
#endif
    return length;
}

int ASNLib::decodeInteger(DataBlock& data, u_int64_t& intVal, unsigned int bytes, bool tagCheck)
{
    /**
     * integer = 0x02 length byte {byte}*
     */
    XDebug(s_libName.c_str(),DebugAll,"::decodeInteger() from  data='%p'",&data);
    int64_t value = 0;
    if (data.length() < 2)
	return InvalidLengthOrTag;
#ifdef DEBUG
    unsigned int initLen = data.length();
#endif
    if (tagCheck) {
	int type = data[0];
	if ((type != INTEGER)) {
	    XDebug(s_libName.c_str(),DebugAll,"::decodeInteger() - Invalid Tag in data='%p'",&data);
	    return InvalidLengthOrTag;
	}
	data.cut(-1);
    }
    int length = decodeLength(data);
    if (length < 0) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeInteger() - Invalid Length in data='%p'",&data);
	return length;
    }

    if ((unsigned int)length > data.length()) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeInteger() - Invalid Length in data='%p'",&data);
	return InvalidLengthOrTag;
    }

    if ((unsigned int)length > bytes) {
    	DDebug(s_libName.c_str(),DebugAll,"::decodeInteger() - Invalid Length: decoded length=%d greater than requested length=%u in data='%p'",
    			length,bytes,&data);
    	return InvalidLengthOrTag;
    }

    if (data[0] & 0x80)
	value = -1; /* integer is negative */
    int j = 0;
    while (j < length) {
	value = (value << 8) | data[j];
	j++;
    }
    intVal = (u_int64_t) value;
    data.cut(-length);
#ifdef DEBUG
    Debug(s_libName.c_str(),DebugAll,"::decodeInteger() - decoded integer value from  data='%p', consumed %u bytes",
    	&data, initLen - data.length());
#endif
    return length;
}

int ASNLib::decodeUINT8(DataBlock& data, u_int8_t* intVal, bool tagCheck)
{
    XDebug(s_libName.c_str(),DebugAll,"::decodeUINT8()");
    u_int64_t val;
    int l = decodeInteger(data,val,sizeof(u_int8_t),tagCheck);
    if (!intVal) {
        DDebug(s_libName.c_str(),DebugAll,"::decodeUINT8() - Invalid buffer for return data");
        return InvalidContentsError;
    }
    *intVal = (u_int8_t) val;
    return l;
}

int ASNLib::decodeUINT16(DataBlock& data, u_int16_t* intVal, bool tagCheck)
{
    XDebug(s_libName.c_str(),DebugAll,"::decodeUINT16() from data='%p'",&data);
    u_int64_t val;
    int l = decodeInteger(data,val,sizeof(u_int16_t),tagCheck);
    if (!intVal) {
        DDebug(s_libName.c_str(),DebugAll,"::decodeUINT16() - Invalid buffer for return data");
        return InvalidContentsError;
    }
    *intVal = (u_int16_t) val;
    return l;
}

int ASNLib::decodeUINT32(DataBlock& data, u_int32_t* intVal, bool tagCheck)
{
    XDebug(s_libName.c_str(),DebugAll,"::decodeUINT32() from data='%p'",&data);
    u_int64_t val;
    int l = decodeInteger(data,val,sizeof(u_int32_t),tagCheck);
    if (!intVal) {
        DDebug(s_libName.c_str(),DebugAll,"::decodeUINT32() - Invalid buffer for return data");
        return InvalidContentsError;
    }
    *intVal = (u_int32_t) val;
    return l;
}

int ASNLib::decodeUINT64(DataBlock& data, u_int64_t* intVal, bool tagCheck)
{
    XDebug(s_libName.c_str(),DebugAll,"::decodeUINT64() from data='%p'",&data);
    u_int64_t val;
    int l = decodeInteger(data,val,sizeof(u_int64_t),tagCheck);
    if (!intVal) {
        DDebug(s_libName.c_str(),DebugAll,"::decodeUINT64() - Invalid buffer for return data");
        return InvalidContentsError;
    }
    *intVal = val;
    return l;
}

int ASNLib::decodeINT8(DataBlock& data, int8_t* intVal, bool tagCheck)
{
    XDebug(s_libName.c_str(),DebugAll,"::decodeINT8() from data='%p'",&data);
    u_int64_t val;
    int l = decodeInteger(data,val,sizeof(int8_t),tagCheck);
    if (!intVal) {
        DDebug(s_libName.c_str(),DebugAll,"::decodeINT8() - Invalid buffer for return data");
        return InvalidContentsError;
    }
    *intVal = (int8_t) val;
    return l;
}

int ASNLib::decodeINT16(DataBlock& data, int16_t* intVal, bool tagCheck)
{
    XDebug(s_libName.c_str(),DebugAll,"::decodeINT16() from data='%p'",&data);
    u_int64_t val;
    int l = decodeInteger(data,val,sizeof(int16_t),tagCheck);
    if (!intVal) {
        DDebug(s_libName.c_str(),DebugAll,"::decodeINT16() - Invalid buffer for return data");
        return InvalidContentsError;
    }
    *intVal = (int16_t) val;
    return l;
}

int ASNLib::decodeINT32(DataBlock& data, int32_t* intVal, bool tagCheck)
{
    XDebug(s_libName.c_str(),DebugAll,"::decodeINT32() from data='%p'",&data);
    u_int64_t val;
    int l = decodeInteger(data,val,sizeof(int32_t),tagCheck);
    if (!intVal) {
        DDebug(s_libName.c_str(),DebugAll,"::decodeINT32() - Invalid buffer for return data");
        return InvalidContentsError;
    }
    *intVal = (int32_t) val;
    return l;
}

int ASNLib::decodeINT64(DataBlock& data, int64_t* intVal, bool tagCheck)
{
    XDebug(s_libName.c_str(),DebugAll,"::decodeINT64() from data='%p'",&data);
    u_int64_t val;
    int l = decodeInteger(data,val,sizeof(int64_t),tagCheck);
    if (!intVal) {
        DDebug(s_libName.c_str(),DebugAll,"::decodeINT64() - Invalid buffer for return data");
        return InvalidContentsError;
    }
    *intVal = val;
    return l;
}

int ASNLib::decodeBitString(DataBlock& data, String* val, bool tagCheck)
{
    /**
     * bitstring ::= 0x03 asnlength unusedBytes {byte}*
     */
    XDebug(s_libName.c_str(),DebugAll, "::decodeBitString() from data='%p'",&data);
    if (data.length() < 2)
	return InvalidLengthOrTag;
#ifdef DEBUG
    unsigned int initLen = data.length();
#endif
    if (tagCheck) {
	int type = data[0];
	if ((type != BIT_STRING)) {
	    XDebug(s_libName.c_str(),DebugAll,"::decodeBitString() - Invalid Tag in data='%p'",&data);
	    return InvalidLengthOrTag;
	}
	data.cut(-1);
    }
    int length = decodeLength(data);
    if (length < 0) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeBitString() - Invalid Length in data='%p'",&data);
	return length;
    }

    if ((unsigned int)length > data.length()) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeBitString() - Invalid Length in data='%p'",&data);
	return InvalidLengthOrTag;
    }

    if (data[0] > 7){
	DDebug(s_libName.c_str(),DebugAll, "::decodeBitString() - Invalid bitstring, unused bytes > 7 in data='%p'",&data);
	return InvalidLengthOrTag;
    }
    int unused = data[0];
    data.cut(-1);
    length--;
    int j = 0;
    if (!val) {
        DDebug(s_libName.c_str(),DebugAll,"::decodeBitString() - Invalid buffer for return data");
        data.cut(-length);
        return InvalidContentsError;
    }
    *val = "";
    while (j < length) {
	uint8_t byte = data[j];
	for (int i = 7; i > -1; i--) {
	    int c = (byte >> i) % 2;
	    *val += c;
	}
	j++;
    }
    *val = val->substr(0, length * 8 - unused);
    data.cut(-length);
#ifdef DEBUG
    Debug(s_libName.c_str(),DebugAll,"::decodeBitString() - decoded bit string value from  data='%p', consumed %u bytes",
    	&data, initLen - data.length());
#endif
    return length;
}

int ASNLib::decodeOctetString(DataBlock& db, OctetString* strVal, bool tagCheck)
{
    /**
     *  octet string ::= 0x04 asnlength {byte}*
     */
    XDebug(s_libName.c_str(),DebugAll,":decodeOctetString() from data='%p'",&db);
    if (db.length() < 2)
	return InvalidLengthOrTag;
#ifdef DEBUG
    unsigned int initLen = db.length();
#endif
    if (tagCheck) {
	int type = db[0];
	if (type != OCTET_STRING) {
	    XDebug(s_libName.c_str(),DebugAll,"::decodeOctetString() - Invalid Tag in data='%p'",&db);
	    return InvalidLengthOrTag;
	}
	db.cut(-1);
    }
    int length = decodeLength(db);
    if (length < 0) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeOctetString() - Invalid Length in data='%p'",&db);
	return length;
    }
    if ((unsigned int)length > db.length()) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeOctetString() - Invalid Length in data='%p'",&db);
	return InvalidLengthOrTag;
    }
    if (!strVal) {
        DDebug(s_libName.c_str(),DebugAll,"::decodeOctetString() - Invalid buffer for return data");
        return InvalidContentsError;
    }
    strVal->assign((void*)db.data(0,length),length);
    db.cut(-length);
#ifdef DEBUG
    Debug(s_libName.c_str(),DebugAll,"::decodeOctetString() - decoded octet string value from  data='%p', consumed %u bytes",
    	&db, initLen - db.length());
#endif
    return length;
}

int ASNLib::decodeNull(DataBlock& data, bool tagCheck)
{
    /**
     * ASN.1 null := 0x05 00
     */
    XDebug(s_libName.c_str(),DebugAll,"::decodeNull() from data='%p'",&data);
    if (tagCheck) {
	if (data.length() < 2)
	    return InvalidLengthOrTag;

	if (data[0] != NULL_ID) {
	    XDebug(s_libName.c_str(),DebugAll, "::decodeNull() - Invalid Tag in data='%p'",&data);
	    return InvalidLengthOrTag;
	}
	data.cut(-1);
    }
    int length = decodeLength(data);
    if (length != 0) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeNull() - Invalid Length in data='%p'",&data);
	return InvalidLengthOrTag;;
    }
    DDebug(s_libName.c_str(),DebugAll,"::decodeNull() - decoded null value from  data='%p', consumed %u bytes",
    		&data, (tagCheck ? 2 : 1));
    return length;
}

int ASNLib::decodeOID(DataBlock& data, ASNObjId* obj, bool tagCheck)
{
   /**
    * ASN.1 objid ::= 0x06 asnlength subidentifier {subidentifier}*
    * subidentifier ::= {leadingbyte}* lastbyte
    * leadingbyte ::= 1 7bites
    * lastbyte ::= 0 7bites
    */
    XDebug(s_libName.c_str(),DebugAll,"::decodeOID() from data='%p'",&data);
    if (data.length() < 2)
	return InvalidLengthOrTag;
#ifdef DEBUG
    unsigned int initLen = data.length();
#endif
    if (tagCheck) {
	if (data[0] != OBJECT_ID) {
	    XDebug(s_libName.c_str(),DebugAll,"::decodeOID() - Invalid Tag in data='%p'",&data);
	    return InvalidLengthOrTag;
	}
	data.cut(-1);
    }
    int length = decodeLength(data);
    if (length < 0) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeOID() - Invalid Length in data='%p'",&data);
	return length;
    }

    if ((unsigned int)length > data.length()) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeOID() - Invalid Length in data='%p'",&data);
	return InvalidLengthOrTag;
    }

    if (length == 0) {
	obj = 0;
	return length;
    }

    int j = 0;
    String oid = "";
    unsigned int longNo = 0;
    while (j < length) {
    // first byte contains 2 identifiers : x,y. The byte is 40 * x + y . x can only be 0,1,2 so if x > 2, x stays 2 and the rest goes into y
	if (j == 0) {
	    unsigned int x = data[j] / 40;
	    unsigned int y = data[j] % 40;
	    if (x > 2) {
		y = (x - 2) * 40 + y;
		x = 2;
	    }
	    oid += x;
	    oid += ".";
	    oid += y;
	    oid += ".";
	}
	else {
	    uint8_t byte = data[j];
	    longNo += byte & ~ASN_BIT8;
	    if ((byte & ASN_BIT8) == ASN_BIT8)
		longNo <<= 7;
	    else {
		oid += longNo;
		longNo = 0;
		if (j != length -1)
		    oid += ".";
	    }
      }
      j++;
    }
    data.cut(-length);
    if (!obj) {
        DDebug(s_libName.c_str(),DebugAll,"::decodeOID() - Invalid buffer for return data");
        return InvalidContentsError;
    }
    *obj = oid;
#ifdef DEBUG
    Debug(s_libName.c_str(),DebugAll,"::decodeOID() - decoded object ID from  data='%p', consumed %u bytes",
    	&data, initLen - data.length());
#endif
    return length;
}

int ASNLib::decodeReal(DataBlock& db, float* realVal, bool tagCheck)
{
    if (db.length() < 2)
	return InvalidLengthOrTag;
    unsigned int initLen = db.length();
    if (tagCheck) {
	if (db[0] != REAL) {
	    XDebug(s_libName.c_str(),DebugAll,"::decodeReal() - Invalid Tag in data='%p'",&db);
	    return InvalidLengthOrTag;
	}
	db.cut(-1);
    }
    int length = decodeLength(db);
    if (length < 0) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeReal() - Invalid Length in data='%p'",&db);
	return length;
    }
    if ((unsigned int)length > db.length()) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeReal() - Invalid Length in data='%p'",&db);
	return InvalidLengthOrTag;
    }
    db.cut(-length);
    Debug(s_libName.c_str(),DebugInfo,"::decodeReal() - real value decoding not implemented, skipping over the %u bytes of the encoding",
    		initLen - db.length());
    return 0;
}

int ASNLib::decodeString(DataBlock& data, String* str, int* type, bool tagCheck)
{
    XDebug(s_libName.c_str(),DebugAll,"::decodeString() from data='%p'",&data);
    if (data.length() < 2)
	return InvalidLengthOrTag;
#ifdef DEBUG
    unsigned int initLen = data.length();
#endif
    if (tagCheck) {
	if (data[0] != NUMERIC_STR ||
	    data[0] != PRINTABLE_STR ||
	    data[0] != IA5_STR ||
	    data[0] != VISIBLE_STR
	    ) {
	    XDebug(s_libName.c_str(),DebugAll,"::decodeString() -  Invalid Tag in data='%p'",&data);
	    return InvalidLengthOrTag;
	}
	if (type)
	    *type = data[0];
	data.cut(-1);
    }
    int length = decodeLength(data);
    if (length < 0) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeString() -Invalid Length in data='%p'",&data);
	return length;
    }

    if ((unsigned int)length > data.length()) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeString() -Invalid Length in data='%p'",&data);
	return InvalidLengthOrTag;
    }

    String var = "";
    for (int i = 0; i < length; i++)
	var += (char) (data[i] & 0x7f);
    data.cut(-length);
    if (!str || !type) {
        DDebug(s_libName.c_str(),DebugAll,"::decodeString() - Invalid buffer for return data");
        return InvalidContentsError;
    }
    *str = var;
#ifdef DEBUG
    Debug(s_libName.c_str(),DebugInfo,"::decodeString() - decode string value from data='%p', consumed %u bytes",
    	&data,initLen - data.length());
#endif
    return length;
}


int ASNLib::decodeUtf8(DataBlock& data, String* str, bool tagCheck)
{
    XDebug(s_libName.c_str(),DebugAll,"::decodeUtf8() from data='%p'",&data);
    if (data.length() < 2)
	return InvalidLengthOrTag;
#ifdef DEBUG
    unsigned int initLen = data.length();
#endif
    if (tagCheck) {
	if (data[0] != UTF8_STR) {
	    XDebug(s_libName.c_str(),DebugAll,"::decodeUtf8() - Invalid Tag in data='%p'",&data);
	    return InvalidLengthOrTag;
	}
	data.cut(-1);
    }
    int length = decodeLength(data);
    if (length < 0) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeUtf8() -Invalid Length in data='%p'",&data);
	return length;
    }

    if ((unsigned int)length > data.length()) {
	Debug(s_libName.c_str(),DebugAll,"::decodeUtf8() - Invalid Length in data='%p'",&data);
	return InvalidLengthOrTag;
    }

    String var = "";
    for (int i = 0; i < length; i++)
	var += (char) (data[i]);
    data.cut(-length);
    if (String::lenUtf8(var.c_str()) < 0)
	return ParseError;
    if (!str) {
        DDebug(s_libName.c_str(),DebugAll,"::decodeUTF8() - Invalid buffer for return data");
        return InvalidContentsError;
    }
    *str = var;
#ifdef DEBUG
    Debug(s_libName.c_str(),DebugAll,"::decodeUtf8() - decoded an UTF8 string value from data='%p', consumed %u bytes",&data,initLen - data.length());
#endif
    return length;
}

int ASNLib::decodeGenTime(DataBlock& data, unsigned int* time, unsigned int* fractions, bool* utc, bool tagCheck)
{
    XDebug(s_libName.c_str(),DebugAll,"::decodeGenTime() from data='%p'",&data);
    if (data.length() < 2)
	return InvalidLengthOrTag;
#ifdef DEBUG
    unsigned int initLen = data.length();
#endif
    if (tagCheck) {
	if (data[0] != GENERALIZED_TIME) {
	    XDebug(s_libName.c_str(),DebugAll,"::decodeGenTime() - Invalid Tag in data='%p'",&data);
	    return InvalidLengthOrTag;
	}
	data.cut(-1);
    }
    int length = decodeLength(data);
    if (length < 0) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeGenTime() - Invalid Length in data='%p'",&data);
	return length;
    }

    if ((unsigned int)length > data.length() || length < 14) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeGenTime() - Invalid Length in data='%p'",&data);
	return InvalidLengthOrTag;
    }
    String date = "";
    for (int i = 0; i < length; i++)
	date += (char) (data[i]);
    data.cut(-length);

    if (!(utc && fractions && time)) {
        DDebug(s_libName.c_str(),DebugAll,"::decodeGenTime() - Invalid buffer for return data");
        return InvalidContentsError;
    }

    unsigned int year, month, day, hours, minutes, seconds;
    int timeDiff = 0;

    *utc = false;
    *fractions = 0;

    if (date[date.length() - 1] == 'Z') {
	*utc = true;
	date = date.substr(0,date.length() - 1);
    }
    else {
	int pos = date.find('-');
	if (pos < 0)
	    pos = date.find('+');
	if (pos >0 && pos != (int)date.length() - 5)
	    return InvalidContentsError;
	if (pos > 0) {
	    char sign = date.at(pos);
	    unsigned int hDiff = (unsigned int) date.substr(date.length() - 4,2).toInteger(-1,10);
	    if (hDiff > 11)
		return InvalidContentsError;
	    unsigned int mDiff = (unsigned int) date.substr(date.length() - 2,2).toInteger(-1,10);
	    if (mDiff > 59)
		return InvalidContentsError;
	    unsigned int diff = Time::toEpoch(1970,1,1,hDiff,mDiff,0);
	    if (sign == '-')
		timeDiff = diff;
	    else
		timeDiff -= diff;
	    *utc = true;
	    date = date.substr(0,date.length() - 5);
	}
    }
    ObjList* list = date.split('.');
    if (!list || list->count() > 2)
	return InvalidContentsError;
    if (list->count() == 2)
	*fractions = (*list)[1]->toString().toInteger(0,10);

    String dateTime = (*list)[0]->toString();
    TelEngine::destruct(list);

    year = dateTime.substr(0,4).toInteger(-1,10);
    month = dateTime.substr(4,2).toInteger(-1,10);
    day = dateTime.substr(6,2).toInteger(-1,10);
    hours = dateTime.substr(8,2).toInteger(-1,10);
    minutes = dateTime.substr(10,2).toInteger(-1,10);
    seconds = dateTime.substr(12,2).toInteger(-1,10);
    if (year < 1970 || month > 12 || day > 31 || hours > 23 || minutes > 59 || seconds > 59)
	return InvalidContentsError;

    unsigned int epochTime = Time::toEpoch(year,month,day,hours,minutes,seconds);
    if (epochTime == (unsigned int) -1)
	return InvalidContentsError;
    *time = epochTime + timeDiff;
#ifdef DEBUG
    Debug(s_libName.c_str(),DebugAll,"::decodeGenTime() - decoded time value from data='%p', consumed %u bytes",&data,initLen - data.length());
#endif
    return length;
}

int ASNLib::decodeUTCTime(DataBlock& data, unsigned int* time, bool tagCheck)
{
    XDebug(s_libName.c_str(),DebugAll,"::decodeUTCTime() from data='%p'",&data);
    if (data.length() < 2)
	return InvalidLengthOrTag;
#ifdef DEBUG
    unsigned int initLen = data.length();
#endif
    if (tagCheck) {
	if (data[0] != UTC_TIME) {
	    XDebug(s_libName.c_str(),DebugAll,"::decodeUTCTime() - Invalid Tag in data='%p'",&data);
	    return InvalidLengthOrTag;
	}
	data.cut(-1);
    }
    int length = decodeLength(data);
    if (length < 0) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeUTCTime() - Invalid Length in data='%p'",&data);
	return length;
    }

    if ((unsigned int)length > data.length() || length < 11) {
	DDebug(s_libName.c_str(),DebugAll,"::decodeUTCTime() - Invalid Length in data='%p'",&data);
	return InvalidLengthOrTag;
    }
    String date = "";
    for (int i = 0; i < length; i++)
	date += (char) (data[i]);
    data.cut(-length);

    if (!time) {
        DDebug(s_libName.c_str(),DebugAll,"::decodeUTCTime() - Invalid buffer for return data");
        return InvalidContentsError;
    }
    unsigned int year = 0, month = 0, day = 0, hours = 0, minutes = 0, seconds = 0;
    int timeDiff = 0;

    int len = date.length();
    if (date[date.length() - 1] == 'Z')
	date = date.substr(0,len - 1);
    else {
	int pos = date.find('-');
	if (pos < 0)
	    pos = date.find('+');
	if ((pos >0 && pos != len - 5) || pos < 0)
	    return InvalidContentsError;
	if (pos > 0) {
	    char sign = date.at(pos);
	    unsigned int hDiff = (unsigned int) date.substr(len - 4,2).toInteger(-1,10);
	    if (hDiff > 11)
		return InvalidContentsError;
	    unsigned int mDiff = (unsigned int) date.substr(len - 2,2).toInteger(-1,10);
	    if (mDiff > 59)
		return InvalidContentsError;
	    unsigned int diff = Time::toEpoch(1970,1,1,hDiff,mDiff,0);
	    if (sign == '-')
		timeDiff = diff;
	    else
		timeDiff -= diff;
	    date = date.substr(0,len - 5);
	}
    }
    year = date.substr(0,2).toInteger(-1,10);
    year = (year > 50) ? 1900 + year : 2000 + year;
    month = date.substr(2,2).toInteger(-1,10);
    day = date.substr(4,2).toInteger(-1,10);
    hours = date.substr(6,2).toInteger(-1,10);
    minutes = date.substr(8,2).toInteger(-1,10);
    if (date.length() > 10)
	seconds = date.substr(10,2).toInteger(-1,10);
    if (year < 1970 || month > 12 || day > 31 || hours > 23 || minutes > 59 || seconds > 59)
	return InvalidContentsError;

    unsigned int epochTime = Time::toEpoch(year,month,day,hours,minutes,seconds);
    if (epochTime == (unsigned int) -1)
	return InvalidContentsError;
    *time = epochTime + timeDiff;
#ifdef DEBUG
    Debug(s_libName.c_str(),DebugAll,"::decodeUTCTime() - decoded time value from data='%p', consumed %u bytes",&data,initLen - data.length());
#endif
    return length;
}

int ASNLib::decodeAny(DataBlock data, DataBlock* val, bool tagCheck)
{
    XDebug(s_libName.c_str(),DebugAll,"::decodeAny() from data='%p'",&data);
    if (!val) {
        DDebug(s_libName.c_str(),DebugAll,"::decodeAny() - Invalid buffer for return data");
        return InvalidContentsError;
    }
    val->append(data);
    return data.length();
}

int ASNLib::decodeSequence(DataBlock& data, bool tagCheck)
{
    XDebug(s_libName.c_str(),DebugAll,"::decodeSequence() from data='%p'",&data);
    if (data.length() < 2)
	return InvalidLengthOrTag;
#ifdef DEBUG
    unsigned int initLen = data.length();
#endif
    if (tagCheck) {
	if (data[0] != SEQUENCE) {
	    DDebug(s_libName.c_str(),DebugAll,"::decodeSequence() - Invalid Tag in data='%p'",&data);
	    return InvalidLengthOrTag;
	}
	data.cut(-1);
    }
    int length = decodeLength(data);
    if (length < 0)
	Debug(s_libName.c_str(),DebugAll,"::decodeSequence() - Invalid Length in data='%p'",&data);
#ifdef DEBUG
    Debug(s_libName.c_str(),DebugAll,"::decodeSequence() - decoded sequence tags from data='%p', consumed %u bytes",&data,initLen - data.length());
#endif
    return length;
}

int ASNLib::decodeSet(DataBlock& data, bool tagCheck)
{
    XDebug(s_libName.c_str(),DebugAll,"::decodeSet() from data='%p",&data);
    if (data.length() < 2)
	return InvalidLengthOrTag;
#ifdef DEBUG
    unsigned int initLen = data.length();
#endif
    if (tagCheck) {
	if (data[0] != SET) {
	    DDebug(s_libName.c_str(),DebugAll,"::decodeSet() - Invalid Tag in data='%p'",&data);
	    return InvalidLengthOrTag;
	}
	data.cut(-1);
    }
    int length = decodeLength(data);
#ifdef DEBUG
    if (length < 0)
	Debug(s_libName.c_str(),DebugAll,"::decodeSet() - Invalid Length in data='%p'",&data);
    else
	Debug(s_libName.c_str(),DebugAll,"::decodeSet() - decoded set tags from data='%p', consumed %u bytes",&data,initLen - data.length());
#endif
    return length;
}

DataBlock ASNLib::encodeBoolean(bool val, bool tagCheck)
{
    /**
     * ASN.1 boolean ::= 0x01 asnlength=0x01 byte
     */
    DataBlock data;
    uint8_t b = BOOLEAN;
    if (tagCheck) {
	data.append(&b, 1);
	b = 1;
	data.append(&b, 1);
    }
    b = val ? 1 : 0;
    data.append(&b, 1);
    XDebug(s_libName.c_str(),DebugAll,"::encodeBoolean('%s') - encoded boolean value into %u bytes",String::boolText(val),data.length());
    return data;
}

DataBlock ASNLib::encodeInteger(u_int64_t intVal, bool tagCheck)
{
    /**
     * ASN.1 integer ::= 0x02 asnlength byte {byte}*
     */
    DataBlock data;
    uint8_t tag = INTEGER;

    // 9 consecutive ones or zeros are not allowed at the beginning of an integer
    int size = sizeof(u_int64_t);
    uint16_t msb = (uint16_t)(intVal >> ((size - 1) * 8 - 1));

    while (((msb & 0x1FF) == 0 || (msb & 0x1FF) == 0x1FF) && (size - 1 >= 1)) {
	size--;
	msb = (uint16_t)(intVal >> ((size - 1) * 8 - 1));
    }
    if (size == 0)
	return data;

    DataBlock contents;
    while(size) {
	uint8_t byte = (uint8_t)(intVal >> ((size - 1) * 8));
	contents.append(&byte, 1);
	size--;
    }

    if (contents.length() == 0)
	return data;
    if (tagCheck) {
	data.append(&tag, 1);
	DataBlock len = buildLength(contents);
	data.append(len);
    }
    data.append(contents);
    XDebug(s_libName.c_str(),DebugAll,"::encodeInteger('"FMT64"') - encoded into %u bytes",intVal,data.length());
    return data;
}

DataBlock ASNLib::encodeBitString(String val, bool tagCheck)
{
    /**
     * ASN.1 bit string ::= 0x03 asnlength unused {byte}*
     */
    DataBlock data;
    uint8_t tag = BIT_STRING;
    DataBlock contents;

    int l = val.length();
    uint8_t trail = (-l) & 0x07;
    for (int i = 0; i < trail; i++)
	val += "0";
    contents.append(&trail, 1);

    for (unsigned int i = 0; i < val.length(); i += 8) {
	uint8_t hex = val.substr(i, 8).toInteger(0,2);
	contents.append(&hex, 1);
    }

    if (tagCheck) {
	data.append(&tag,1);
	DataBlock len = buildLength(contents);
	data.append(len);
    }
    data.append(contents);
    XDebug(s_libName.c_str(),DebugAll,"::encodeBitString('%s') - encoded bit string into %u bytes",val.c_str(),data.length());
    return data;
}

DataBlock ASNLib::encodeOctetString(OctetString strVal, bool tagCheck)
{
    /**
      * ASN.1 octet string ::= 0x04 asnlength byte {byte}*
      */
    DataBlock data;
    uint8_t tag = OCTET_STRING;
    if (tagCheck) {
	data.append(&tag, 1);
	DataBlock len = buildLength(strVal);
	data.append(len);
    }
    data.append(strVal);
    XDebug(s_libName.c_str(),DebugAll,"ASNLib::encodeOctetString('%s') - encoded octet string into %u bytes",
    		strVal.toHexString().c_str(),data.length());
    return data;
}

DataBlock ASNLib::encodeNull(bool tagCheck)
{
    /**
      *	ASN.1 null ::= 0x05 00
      */
    XDebug(s_libName.c_str(),DebugAll,"::encodeNull()");
    DataBlock data;
    uint8_t tag = NULL_ID;
    if (tagCheck) {
	data.append(&tag, 1);
	tag = 0;
	data.append(&tag, 1);
    }
    return data;
}

DataBlock ASNLib::encodeOID(ASNObjId obj, bool tagCheck)
{
    /**
      * ASN.1 object id ::= 0x06 asnlength byte {byte}*
      */
    DataBlock data;
    uint8_t tag = OBJECT_ID;

    DataBlock cont = obj.getIds();
    DataBlock contents;

    if (cont.length() == 0)
	return data;

    // first byte is built following the rule first = 40 * x + y
    // x must not be greater than 2 (joint-iso-ccitt identifier)
    if (cont[0] > 2) {
    	Debug(s_libName.c_str(),DebugAll,"::encodeOID('%s') - first identifier is greater than the maximum allowed identifier 'joint-iso-ccitt'(2)",
    			obj.toString().c_str());
    	return data;
    }
    uint8_t first = 40 * cont[0];
    if (cont.length() > 1) {
    	// y must not be greater than 39 if x < 2
    	if (cont[0] < 2 && cont[1] > 39) {
    	    Debug(s_libName.c_str(),DebugAll,"::encodeOID('%s') - cannot encode second identifier, its value is not allowed for the first identifier",
    			obj.toString().c_str());
    	    return data;
    	}
    	first += cont[1];
    	cont.cut(-1);
    }
    contents.append(&first, 1);
    cont.cut(-1);

    contents.append(cont);
    if (tagCheck) {
	data.append(&tag,1);
	DataBlock len = buildLength(contents);
	data.append(len);
    }
    data.append(contents);
    XDebug(s_libName.c_str(),DebugAll,"::encodeOID('%s') - encoded object ID into %u bytes",obj.toString().c_str(),data.length());
    return data;
}

DataBlock ASNLib::encodeReal(float val, bool tagCheck)
{
    Debug(s_libName.c_str(),DebugInfo,"::encodeReal() - STUB: encoding for real values not implemented");
    DataBlock data;
    return data;
}

DataBlock ASNLib::encodeString(String str, int type, bool tagCheck)
{
    DataBlock data;
    uint8_t tag = type;

    DataBlock contents;
    if (type == NUMERIC_STR ||
	type == PRINTABLE_STR ||
	type == IA5_STR ||
	type == VISIBLE_STR )
	contents.append(str);

    if (contents.length() == 0)
	return data;

    if (tagCheck) {
	data.append(&tag, 1);
	DataBlock len = buildLength(contents);
	data.append(len);
    }
    data.append(contents);
    XDebug(s_libName.c_str(),DebugAll,"::encodeString() - encoded string into %u bytes",data.length());
    return data;
}

DataBlock ASNLib::encodeUtf8(String str, bool tagCheck)
{
    DDebug(s_libName.c_str(),DebugAll,"::encodeUtf8()");
    DataBlock data;
    uint8_t tag = UTF8_STR;
    DataBlock contents;
    contents.append(str);
    if (tagCheck) {
	data.append(&tag, 1);
	DataBlock len = buildLength(contents);
	data.append(len);
    }
    data.append(contents);
    XDebug(s_libName.c_str(),DebugAll,"::encodeString() - encoded UTF8 string into %u bytes",data.length());
    return data;
}

DataBlock ASNLib::encodeGenTime(unsigned int time, unsigned int fractions, bool tagCheck)
{
    DataBlock data;
    uint8_t tag = GENERALIZED_TIME;

    int year;
    unsigned int month, day, hours, minutes, seconds;
    if (!Time::toDateTime(time, year, month, day, hours, minutes, seconds))
	return data;
    String dateTime = "";
    dateTime += year;
    (month < 10) ? dateTime += 0 : "";
    dateTime += month;
    (day < 10) ? dateTime += 0 : "";
    dateTime += day;
    (hours < 10) ? dateTime += 0 : "";
    dateTime += hours;
    (minutes < 10) ? dateTime += 0 : "";
    dateTime += minutes;
    (seconds < 10) ? dateTime += 0 : "";
    dateTime += seconds;
    if (fractions != 0) {
	dateTime += ".";
	dateTime += fractions;
    }
    dateTime += 'Z';
    DataBlock contents;
    contents.append(dateTime);
    if (tagCheck) {
	data.append(&tag, 1);
	DataBlock len = buildLength(contents);
	data.append(len);
    }
    data.append(contents);
    XDebug(s_libName.c_str(),DebugAll,"::encodeGenTime(time='%u', fractions='%u') - encoded time value into %u bytes",time,fractions,data.length());
    return data;
}

DataBlock ASNLib::encodeUTCTime(unsigned int time, bool tagCheck)
{
    DataBlock data;
    uint8_t tag = UTC_TIME;

    int year;
    unsigned int month, day, hours, minutes, seconds;
    if (!Time::toDateTime(time, year, month, day, hours, minutes, seconds))
	return data;
    String dateTime = "";
    (year % 100 < 10) ?  dateTime += 0 : "";
    dateTime += (year % 100);
    (month < 10) ? dateTime += 0 : "";
    dateTime += month;
    (day < 10) ? dateTime += 0 : "";
    dateTime += day;
    (hours < 10) ? dateTime += 0 : "";
    dateTime += hours;
    (minutes < 10) ? dateTime += 0 : "";
    dateTime += minutes;
    (seconds < 10) ? dateTime += 0 : "";
    dateTime += seconds;

    dateTime += 'Z';

    DataBlock contents;
    contents.append(dateTime);
    if (tagCheck) {
	data.append(&tag, 1);
	DataBlock len = buildLength(contents);
	data.append(len);
    }
    data.append(contents);
    XDebug(s_libName.c_str(),DebugAll,"::encodeUTCTime(time='%u') - encoded time value into %u bytes",time,data.length());
    return data;
}

DataBlock ASNLib::encodeAny(DataBlock data, bool tagCheck)
{
    XDebug(s_libName.c_str(),DebugAll,"::encodeAny()");
    DataBlock db;
    db.append(data);
    return db;
}

int ASNLib::encodeSequence(DataBlock& data, bool tagCheck)
{
    DataBlock len;
    if (tagCheck) {
	len = buildLength(data);
	data.insert(len);
	DataBlock db;
	u_int8_t tag = SEQUENCE;
	db.append(&tag, 1);
	data.insert(db);
    }
    XDebug(s_libName.c_str(),DebugAll,"::encodeSequence() - added sequence tag and length for a block of %d bytes",data.length());
    return len.length();
}

int ASNLib::encodeSet(DataBlock& data, bool tagCheck)
{
    DDebug(s_libName.c_str(),DebugAll,"::encodeSet()");
    DataBlock len;
    if (tagCheck) {
	len = buildLength(data);
	data.insert(len);
	DataBlock db;
	u_int8_t tag = SET;
	db.append(&tag, 1);
	data.insert(db);
    }
    XDebug(s_libName.c_str(),DebugAll,"::encodeSet() - added set tag and length for a block of %d bytes",data.length());
    return len.length();
}

/**
  * AsnTag
  */
void AsnTag::decode(AsnTag& tag, DataBlock& data)
{
    XDebug(s_libName.c_str(),DebugAll,"AsnTag::decode()");
    tag.classType((Class)(data[0] & 0xc0));
    tag.type((Type)(data[0] & 0x20));

    unsigned int code = 0;
    code |= data[0] & 0x1f;
    unsigned int len = 1;
    if (IS_EXTENSION_ID(code) && data.length() >= 2) { // extended tag
	code = 0;
	while (len < data.length() && (data[len] & ASN_BIT8) == ASN_BIT8) {
	    code = code << 8;
	    code |= (data[len] & 0x7f);
	    len++;
	}
	code |= data[len] & 0x7f;
    }
    tag.code(code);
    tag.encode();
}

void AsnTag::encode(Class clas, Type type, unsigned int code, DataBlock& data)
{
    XDebug(s_libName.c_str(),DebugAll,"AsnTag::encode(clas=0x%x, type=0x%x, code=%u)",clas,type,code);
    if (code < 31) {
	u_int8_t tag = clas | type | code;
	data.insert(DataBlock(&tag,sizeof(tag)));
    }
    else {
	u_int8_t last = clas | type | 31;
	DataBlock coding;
 	coding.append(&last,sizeof(last));
	int size = sizeof(unsigned int);
	bool start = false;
	while (size > 1) {
	    u_int8_t msb = (code >> ((size - 1) * 8));
	    if (start) {
		msb |= 0x80;
		coding.append(&msb,sizeof(msb));
	    }
	    else {
		if (msb == 0) {
		    size--;
		    continue;
		}
		else {
		    start = true;
		    msb |= 0x80;
		    coding.append(&msb,sizeof(msb));
		}
	    }
	    size--;
	}
	last = code;
	coding.append(&last,sizeof(last));
	data.insert(coding);
    }
#ifdef XDEBUG
    String str;
    str.hexify(data.data(),data.length(),' ');
    Debug(s_libName.c_str(),DebugAll,"AsnTag::encode(clas=0x%x, type=0x%x, code=%u) tag=%s",clas,type,code,str.c_str());
#endif
}

/**
  * ASNObjId
  */
ASNObjId::ASNObjId()
{
}

ASNObjId::ASNObjId(const String& val)
    : m_value(val)
{
    DDebug(s_libName.c_str(),DebugAll,"ASNObjId('%s') created",val.c_str());
}

ASNObjId::ASNObjId(const String& name, const String& val)
    : m_value(val), m_name(name)
{
    DDebug(s_libName.c_str(),DebugAll,"ASNObjId('%s', '%s') created",name.c_str(),val.c_str());
}

ASNObjId::ASNObjId(AsnMib* mib)
{
    DDebug(s_libName.c_str(),DebugAll,"ASNObjId() created from AsnMib [%p]",mib);
    if (mib) {
	m_name = mib->getName();
	m_value = mib->getOID();
    }
}

ASNObjId::~ASNObjId()
{
    m_ids.clear();
}

void ASNObjId::toDataBlock()
{
    DDebug(s_libName.c_str(),DebugAll,"ASNObjId::toDataBlock() '%s'", m_value.c_str());
    m_ids.clear();
    ObjList* list = m_value.split('.',false);
    if (list) {
    	for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
    	    String* s = static_cast<String*>(o->get());
    	    int val = s->toInteger();
    	    if (val < 128)
	    	m_ids.append(&val, 1);
	    else {
	        DataBlock db;
	        int size = sizeof(int);
	        uint8_t v = val;
	        v = v & 0x7f;
	        db.append(&v,1);
	        size--;
	        val = val >> 7;
	        while (val != 0) {
		    v = val;
		    v = v & 0x7f;
		    v = v | 0x80;
		    DataBlock tmp;
		    tmp.append(&v,1);
		    db.insert(tmp);
		    size--;
		    val = val >> 7;
	    	}
	    	m_ids.append(db);
    	    }
	}
	TelEngine::destruct(list);
    }
}

DataBlock ASNObjId::getIds()
{
    toDataBlock();
    return m_ids;
}

/**
  * AsnMib
  */
TokenDict AsnMib::s_access[] = {
    {"accessible-for-notify",	AsnMib::accessibleForNotify},
    {"read-only",		AsnMib::readOnly},
    {"read-write", 		AsnMib::readWrite},
    {"read-create",		AsnMib::readCreate},
    {0,0}
};

AsnMib::AsnMib(NamedList& params)
{
    if (!params)
	return;
    m_index = 0;
    m_oid = params;
    m_name = params.getValue("name","");
    m_access = params.getValue("access","");
    m_accessVal = lookup(m_access,s_access,0);
    m_type = params.getValue("type","");
    m_revision = params.getValue("revision","");
    XDebug(s_libName.c_str(),DebugAll,"new AsnMib created with oid : '%s', access : '%s', type : '%s'",
	    m_oid.c_str(),m_access.c_str(),m_type.c_str());
}

int AsnMib::compareTo(AsnMib* mib)
{
    if (!mib)
	return 1;
    DDebug(s_libName,DebugInfo,"AsnMib::compareTo('%s'='%s' [%p]) this=%s[%s] [%p]",
    		mib->getName().c_str(),mib->toString().c_str(),mib,getName().c_str(),toString().c_str(),this);

    // they're equal
    if (toString() == mib->toString())
    	return 0;

    ObjList* myIDs = toString().split('.',false);
    ObjList* mibIDs = mib->toString().split('.',false);

    ObjList* o1 = myIDs->skipNull();
    ObjList* o2 = mibIDs->skipNull();
    while (o1 && o2) {
    	String* str1 = static_cast<String*>(o1->get());
    	o1 = o1->skipNext();
    	String* str2 = static_cast<String*>(o2->get());
    	o2 = o2->skipNext();
    	int diff = str1->toInteger() - str2->toInteger();
    	if (diff == 0)
    	    continue;
    	if (diff > 0) {
    	    TelEngine::destruct(myIDs);
    	    TelEngine::destruct(mibIDs);
    	    return 1;
    	}
    	if (diff < 0) {
    	    TelEngine::destruct(myIDs);
    	    TelEngine::destruct(mibIDs);
    	    return -1;
    	}
    }

    int retValue = 0;
    if (!o1)
    	retValue = -1;
    else if (!o2)
    	retValue = 1;
    TelEngine::destruct(myIDs);
    TelEngine::destruct(mibIDs);
    return retValue;
}

/* vi: set ts=8 sw=4 sts=4 noet: */
