/**
 * \file asn1gen.c
 * \brief ASN.1 generator.
 * \author Dmytro Podgornyi (d.podgornyi@samsung.com)
 * \version 0.1
 * \date Created May 28, 2013
 * \par In Samsung Ukraine R&D Center (SURC) under a contract between
 * \par LLC "Samsung Electronics Ukraine Company" (Kiev, Ukraine) and
 * \par "Samsung Elecrtronics Co", Ltd (Seoul, Republic of Korea)
 * \par Copyright: (c) Samsung Electronics Co, Ltd 2012. All rights reserved.
 **/

#include <stdint.h>
#include <string.h>

#include "asn1gen.h"
#include "asn1.h"

/* error codes */
//#TIMA_P_Port
#include "libdk_native_client.h"

static uint32_t asn1_get_seq_bytes(
	const asn1_gen_t * seq
);
static int32_t asn1_gen_seq_helper(
	uint8_t * out,
	uint32_t out_len,
	const asn1_gen_t * seq
);

static uint32_t asn1_get_val_bytes(
	const asn1_gen_t * field
)
{
	void *value = field->value;
	asn1_type_t type = field->type;
	asn1_object_t *obj_val = NULL;
	asn1_string_t *str_val = NULL;
	unsigned long long_val = 0;
	uint32_t result = 0;
	int i = 0;

	switch (type) {
	case ASN1_TYPE_BOOLEAN:
		result = 1;
		break;

	case ASN1_TYPE_LONG:
		long_val = (unsigned long)value;
		result = 0;
		do {
			++result;
			i = long_val & 0x80;
			long_val >>= 8;
		} while (long_val > 0);

		if (i) {
			++result;
		}
		break;

	case ASN1_TYPE_BITSTRING:
		str_val = (asn1_string_t *) value;
		result = str_val->size + 1;
		break;

	case ASN1_TYPE_OCTETSTRING:
		str_val = (asn1_string_t *) value;
		result = str_val->size;
		break;

	case ASN1_TYPE_NULL:
		result = 0;
		break;

	case ASN1_TYPE_OBJECT:
		obj_val = (asn1_object_t *) value;
		result = 0;
		for (i = 1; i < obj_val->size; i++) {
			long_val = obj_val->content[i];
			if (i == 1) {
				long_val += obj_val->content[0] * 40;
			}
			do {
				++result;
				long_val >>= 7;
			} while (long_val > 0);
		}
		break;

	case ASN1_TYPE_UTCTIME:
	case ASN1_TYPE_UTF8STRING:
	case ASN1_TYPE_IA5STRING:
	case ASN1_TYPE_PRINTABLE_STRING:
		result = strlen((char *)value);
		break;

	case ASN1_TYPE_SET:
	case ASN1_TYPE_EXPL_0:
	case ASN1_TYPE_IMPL_0:
	case ASN1_TYPE_EXPL_1:
	case ASN1_TYPE_EXPL_3:
	case ASN1_TYPE_SEQUENCE:
		/* size of the entry of sequence */
		result = asn1_get_seq_bytes((const asn1_gen_t *)value);
		break;

	case ASN1_TYPE_RAW:
		str_val = (asn1_string_t *) value;
		result = str_val->size;
		break;

	default:
		result = 0;
		break;
	}

	return result;
}

/* return value 0 means error */
static uint32_t asn1_get_len_bytes(
	uint32_t len
)
{
	if (len < 128) {
		return 1;
	}
	if (len < 256) {
		return 2;
	}
	if (len < 65536) {
		return 3;
	}
	if (len < 0x1000000) {
		return 4;
	}

	return 0;
}

uint32_t asn1_get_field_bytes(
	const asn1_gen_t * field
)
{
	uint32_t len = 0;

	len = asn1_get_val_bytes(field);

	if (field->type == ASN1_TYPE_RAW) {
		return len;
	} else {
		/* tag + length + value */
		return (1 + asn1_get_len_bytes(len) + len);
	}
}

static uint32_t asn1_get_seq_bytes(
	const asn1_gen_t * seq
)
{
	uint32_t result;

	result = 0;
	while (seq->type != ASN1_TYPE_END) {
		result += asn1_get_field_bytes(seq);
		++seq;
	}

	return result;
}

static void asn1_set_len(
	uint8_t * out,
	uint32_t len
)
{
	/* buffer overflow is checked in asn1_gen_field() */

	if (len < 128) {
		*out = len;
	} else if (len < 256) {
		*out++ = 0x81;
		*out = len;
	} else if (len < 65536) {
		*out++ = 0x82;
		*out++ = len >> 8;
		*out = len & 0xff;
	} else {
		*out++ = 0x83;
		*out++ = (len >> 16) & 0xff;
		*out++ = (len >> 8) & 0xff;
		*out = len & 0xff;
	}
}

static unsigned long asn1_set_object_recurse(
	uint8_t * out,
	unsigned long value
)
{
	int i;

	if (value < 128) {
		*out = value | 0x80;
		return 1;
	}

	i = asn1_set_object_recurse(out, value >> 7);
	out[i] = (value & 0x7f) | 0x80;
	return i + 1;
}

static void asn1_set_object(
	uint8_t * out,
	const asn1_object_t * obj
)
{
	int i = 0;
	unsigned long tmp = 0;

	if (obj->size > 1) {
		tmp = obj->content[0] * 40 + obj->content[1];
		out += asn1_set_object_recurse(out, tmp);
		*(out - 1) &= 0x7f;
	}
	for (i = 2; i < obj->size; i++) {
		out += asn1_set_object_recurse(out, obj->content[i]);
		*(out - 1) &= 0x7f;
	}
}

static unsigned long asn1_set_long_recurse(
	uint8_t * out,
	unsigned long value
)
{
	int i = 0;

	if (value < 0x80) {
		*out = value;
		return 1;
	}

	i = asn1_set_long_recurse(out, value >> 8);
	out[i] = value & 0xff;
	return i + 1;
}

int32_t asn1_gen_field(
	uint8_t * out,
	uint32_t out_len,
	const asn1_gen_t * field
)
{
	void *value = field->value;
	asn1_type_t type = field->type;
	uint32_t len = 0;
	uint32_t len_len = 0;
	int32_t result = OPERATION_FAILED;

	len = asn1_get_field_bytes(field);
	if (out_len < len) {
		return INVALID_DATA;
	}

	switch (type) {
	case ASN1_TYPE_BOOLEAN:
		*out = ASN1_TAG_BOOLEAN;
		break;
	case ASN1_TYPE_LONG:
		*out = ASN1_TAG_INTEGER;
		break;
	case ASN1_TYPE_BITSTRING:
		*out = ASN1_TAG_BITSTRING;
		break;
	case ASN1_TYPE_OCTETSTRING:
		*out = ASN1_TAG_OCTETSTRING;
		break;
	case ASN1_TYPE_NULL:
		*out = ASN1_TAG_NULL;
		break;
	case ASN1_TYPE_OBJECT:
		*out = ASN1_TAG_OID;
		break;
	case ASN1_TYPE_UTF8STRING:
		*out = ASN1_TAG_UTF8STRING;
		break;
	case ASN1_TYPE_IA5STRING:
		*out = ASN1_TAG_IA5STRING;
		break;
	case ASN1_TYPE_PRINTABLE_STRING:
		*out = ASN1_TAG_PRINTABLESTRING;
		break;
	case ASN1_TYPE_UTCTIME:
		*out = ASN1_TAG_UTCTIME;
		break;
	case ASN1_TYPE_SEQUENCE:
		*out = ASN1_TAG_SEQUENCE | 0x20;
		break;
	case ASN1_TYPE_SET:
		*out = ASN1_TAG_SET | 0x20;
		break;
	case ASN1_TYPE_EXPL_0:
		*out = 0xA0;
		break;
	case ASN1_TYPE_IMPL_0:
		*out = 0x80;
		break;
	case ASN1_TYPE_EXPL_1:
		*out = 0xA1;
		break;
	case ASN1_TYPE_EXPL_3:
		*out = 0xA3;
		break;
	case ASN1_TYPE_RAW:
		/* do nothing */
		break;
	default:
		return INVALID_DATA;
	}

	if (type != ASN1_TYPE_RAW) {
		++out;
		--out_len;

		len = asn1_get_val_bytes(field);
		asn1_set_len(out, len);
		len_len = asn1_get_len_bytes(len);
		out += len_len;
		out_len -= len_len;
	}

	result = NO_ERROR;

	switch (type) {
	case ASN1_TYPE_BOOLEAN:
		if (value == (void *)0) {
			*out = 0;
		} else {
			*out = 0xff;
		}
		break;

	case ASN1_TYPE_LONG:
		(void)asn1_set_long_recurse(out, (unsigned long)value);
		break;

	case ASN1_TYPE_BITSTRING:
		*out = ((asn1_string_t *) value)->unused;
		memcpy(out + 1, ((asn1_string_t *) value)->data,
		       ((asn1_string_t *) value)->size);
		break;

	case ASN1_TYPE_OCTETSTRING:
		memcpy(out, ((asn1_string_t *) value)->data,
		       ((asn1_string_t *) value)->size);
		break;

	case ASN1_TYPE_NULL:
		break;

	case ASN1_TYPE_OBJECT:
		asn1_set_object(out, (asn1_object_t *) value);
		break;

	case ASN1_TYPE_UTCTIME:
	case ASN1_TYPE_UTF8STRING:
	case ASN1_TYPE_IA5STRING:
	case ASN1_TYPE_PRINTABLE_STRING:
		strncpy((char *)out, (char *)value, strlen((char *)value));
		break;

	case ASN1_TYPE_SET:
	case ASN1_TYPE_EXPL_0:
	case ASN1_TYPE_IMPL_0:
	case ASN1_TYPE_EXPL_1:
	case ASN1_TYPE_EXPL_3:
	case ASN1_TYPE_SEQUENCE:
		result =
		    asn1_gen_seq_helper(out, out_len, (asn1_gen_t *) value);
		break;

	case ASN1_TYPE_RAW:
		memcpy(out, ((asn1_string_t *) value)->data,
		       ((asn1_string_t *) value)->size);
		break;

/* Prevent 40379
	default:
		return INVALID_DATA;
*/
	}

	if (result < 0) {
		return result;
	}
	return asn1_get_field_bytes(field);
}

static int32_t asn1_gen_seq_helper(
	uint8_t * out,
	uint32_t out_len,
	const asn1_gen_t * seq
)
{
	uint32_t len = 0;
	int32_t result = 0;

	while (seq->type != ASN1_TYPE_END) {
		len = asn1_get_field_bytes(seq);
		if (out_len < len) {
			return INVALID_DATA;
		}

		result = asn1_gen_field(out, out_len, seq);
		if (result < 0) {
			break;
		}
		out += len;
		out_len -= len;
		++seq;
	}

	return result;
}

int32_t asn1_gen_sequence(
	uint8_t * out,
	uint32_t out_len,
	const asn1_gen_t * seq
)
{
	int32_t result = 0;
	asn1_gen_t seq_stub = ASN1GEN_INIT;

	seq_stub.type = ASN1_TYPE_SEQUENCE;
	seq_stub.value = (void *)seq;

	if (!out || !seq) {
		return INVALID_DATA;
	}

	result = asn1_gen_field(out, out_len, &seq_stub);
	if (result < 0) {
		return result;
	}

	return asn1_get_field_bytes(&seq_stub);
}

int32_t asn1_validate(
	const asn1_gen_t * seq
)
{
	while (seq->type != ASN1_TYPE_END) {
		if (seq->value == NULL && seq->type != ASN1_TYPE_NULL &&
		    seq->type != ASN1_TYPE_LONG) {
			return INVALID_DATA;
		}

		switch (seq->type) {
		case ASN1_TYPE_SET:
		case ASN1_TYPE_EXPL_0:
		case ASN1_TYPE_IMPL_0:
		case ASN1_TYPE_EXPL_1:
		case ASN1_TYPE_EXPL_3:
		case ASN1_TYPE_SEQUENCE:
			if (asn1_validate((asn1_gen_t *) seq->value) !=
			    NO_ERROR) {
				return INVALID_DATA;
			}
			break;

		default:
			break;
		}
		++seq;
	}

	return NO_ERROR;
}
