/*
 *  snmp/snmptable.c
 *
 *  Copyright (C) 2008  ADC, Inc
 *  Written by: Xinyu Yan <xinyu.yan@adc.com>
 *
 *  Description: provide a mechanism to register and maintain snmp table, simplify app programing
 */

#include "./include/snmp.h"


#define MAX_SNMP_REGISTER	32	/* Maximum of modules registering snmp table */

/* It's great chance that next object and current one in same table, so cache improves searching efficiency */
static struct snmp_table_profile *cache;
static struct snmp_register *reg_cache;
static struct snmp_register snmpregister[MAX_SNMP_REGISTER];

//#define PRINT_OID

#ifdef PRINT_OID
static void printoid(u32 *oid, u8 len)
{
	int i;
	
	if(len == 0)
		printf("OID length is 0!\n");
	
	printf("%ld", oid[0]);
	for(i = 1; i < len; i++)
	{
		printf(".%ld", oid[i]);
	}
	printf("\n");
}
#endif

/* To check if oid matches oid prefix */
static int oid_match_prefix(u8 prefix_len, u32 *prefix, u8 oid_len, u32 *oid)
{
	if(prefix_len == 0)
		return 0;
	if(oid_len < prefix_len)
		return 0;
	if(!memcmp(prefix, oid, prefix_len*sizeof(u32)))
		return 1;
	else
		return 0;
}

static struct snmp_register *find_snmp_register(u8 oid_len, u32 *oid)
{
	int i;
	struct snmp_register *reg;

	if(reg_cache != NULL)
	{
		if(oid_match_prefix(reg_cache->prefix_len, reg_cache->prefix_oid, oid_len, oid) == 1)
			return reg_cache;
	}
	
	for(i = 0; i < MAX_SNMP_REGISTER; i++)
	{
		reg = &snmpregister[i];
		if(oid_match_prefix(reg->prefix_len, reg->prefix_oid, oid_len, oid) == 1)
		{
			reg_cache = reg;
			return reg;
		}
	}
	return NULL;
}

/* Check cache first, otherwise check all snmp tables by matching oid with table index */
static struct snmp_table_profile *find_snmp_table(u8 oid_len, u32 *oid)
{
	int i;
	struct snmp_register *reg;
	struct snmp_table_profile *t;

	if(cache != NULL)
	{
		if(oid_match_prefix(cache->index.len, cache->index.oid, oid_len, oid) == 1)
			return cache;
	}

	if((reg = find_snmp_register(oid_len, oid)) == NULL)
		return NULL;
	
	for(i = 0; i < reg->num_of_table; i++)
	{
		t = &reg->table[i];
		if(oid_match_prefix(t->index.len, t->index.oid, oid_len, oid) == 1)
		{
			cache = t;
			return t;
		}
	}
	return NULL;
}

static u32 find_next_column(u32 current_column, u32 *columns)
{
	int i;

	for(i = 0; i < MAX_COLUMNAR_OF_TABLE-1; i++)
	{
		if(current_column == columns[i])
			return columns[i+1];
	}
	return 0;
}

static void point_to_column_and_instance(u32 **column, u32 **instance, u32 *oid, struct snmp_table_profile *t)
{
	if(t->level != 0)
	{
		*column = oid + t->index.len + 1;	/* count the entry, hence plus 1 */
	}
	else
	{
		*column = oid + t->index.len;
	}
	*instance = *column + 1;
}

static int table_set_response(u8 oidlen, u32 *oid, u8 *pdata, u16 datalen)
{
	struct snmp_table_profile *t;
	struct snmp_register *reg;
	u32 *column;
	u32 *instance;

	if((t = find_snmp_table(oidlen, oid)) == NULL)
	{
		if((reg = find_snmp_register(oidlen, oid)) == NULL)
			return -1;
		if(reg->default_set_resp == NULL)
			return -1;
		else
			return reg->default_set_resp(oidlen, oid, pdata, datalen);
	}
	
	point_to_column_and_instance(&column, &instance, oid, t);
	
	if(t->set_resp == NULL)
		return -1;
	else
		return t->set_resp(*column, instance, pdata, datalen);
}

static int table_get_response(u8 oidlen, u32 *oid, u8 *pdata, u8 *vartype)
{
	struct snmp_table_profile *t;
	struct snmp_register *reg;
	u32 *column;
	u32 *instance;

	if((t = find_snmp_table(oidlen, oid)) == NULL)
	{
		if((reg = find_snmp_register(oidlen, oid)) == NULL)
			return -1;
		if(reg->default_get_resp == NULL)
			return -1;
		else
			return reg->default_get_resp(oidlen, oid, pdata, vartype);
	}
	
	point_to_column_and_instance(&column, &instance, oid, t);
	
	if(t->get_resp == NULL)
		return -1;
	else
		return t->get_resp(*column, instance, pdata, vartype);
}

static int get_next_oid(u8 *oidlen, u32 *oid)
{
	struct snmp_table_profile *t;
	u32 *entry;
	u8 entry_oid_len;
	u32 *column;
	u8 column_oid_len;
	u32 *instance;
	u8 full_oid_len;
	u8 plus_one_suppress = 0;

	if((t = find_snmp_table(*oidlen, oid)) == NULL)
		return 0;
	if(t->level == 0)	/* If MIB is not table basis, do not support get next */
		return 0;

	entry = oid + t->index.len;
	entry_oid_len = t->index.len + 1;
	column = entry + 1;
	column_oid_len = entry_oid_len + 1;
	instance = column + 1;
	full_oid_len = column_oid_len + t->level;

	if(*oidlen < full_oid_len)
	{
		memset(oid+*oidlen, 0, (full_oid_len-*oidlen)*sizeof(u32));
		if(*oidlen < entry_oid_len)
			*entry = 1;
		if(*oidlen < column_oid_len)
			*column = t->column[0];
		*oidlen = full_oid_len;
		plus_one_suppress = 1;
        }
	for(; *column != 0; *column = find_next_column(*column, t->column))
	{
		if(plus_one_suppress == 0)
			instance[t->level-1]++;
		
		if(t->get_instance(instance) == 1)
			return 1;
		else
		{
			memset(instance, 0, t->level*sizeof(u32));
			plus_one_suppress = 1;
		}
	}	
	return 0;
}

static int table_getnext_response(u8 prev_oidlen, u32 *prev_oid, u8 *oidlen, u32 *oid, u8 *pdata, u8 *vartype)
{
	memcpy(oid, prev_oid, prev_oidlen*sizeof(u32));
	*oidlen = prev_oidlen;
#ifdef PRINT_OID
	printf("Prev oid: ");
	printoid(oid, *oidlen);
#endif
	if(get_next_oid(oidlen, oid) == 1)
	{
#ifdef PRINT_OID
		printf("Next oid: ");
		printoid(oid, *oidlen);
#endif
		return table_get_response(*oidlen, oid, pdata, vartype);
	}
	else
		return -1;
}

int register_snmp_table(struct snmp_register *reg)
{
	int i;
	struct snmp_register *r;
	
	/* TODO: check table validation */

	for(i = 0; i < MAX_SNMP_REGISTER; i++)
	{
		r = &snmpregister[i];
		if(r->prefix_len == 0)
		{
			memcpy(r, reg, sizeof(struct snmp_register));
			inquire_getmsg(r->prefix_len, r->prefix_oid, table_get_response);
			inquire_setmsg(r->prefix_len, r->prefix_oid, table_set_response);
			inquire_getnextmsg(r->prefix_len, r->prefix_oid, table_getnext_response);
			return 1;
		}
	}
	return 0;
}


