/*
 * quic_analysis.c
 *
 *  Created on: 2019Äê4ÔÂ2ÈÕ
 *      Author: root
 */
#include "gquic.h"
#include "quic_analysis.h"
#include "gquic_process.h"
#include "parser_quic.h"

#include <stdio.h>
#include <MESA/stream_inc/stream_base.h>
#include <MESA/MESA_handle_logger.h>
#include <MESA/MESA_prof_load.h>

struct _quic_param_t g_quic_param;
const char *g_quic_proto_conffile="./conf/quic/main.conf";
const char *g_quic_regionname_conffile="./conf/quic/quic.conf";

#ifdef __cplusplus
extern "C"
{
#endif

#define GIT_VERSION_CATTER(v) __attribute__((__used__)) const char * GIT_VERSION_##v = NULL
#define GIT_VERSION_EXPEND(v) GIT_VERSION_CATTER(v)

/* VERSION TAG */
#ifdef GIT_VERSION
GIT_VERSION_EXPEND(GIT_VERSION);
#else
static __attribute__((__used__)) const char * GIT_VERSION_UNKNOWN = NULL;
#endif
#undef GIT_VERSION_CATTER
#undef GIT_VERSION_EXPEND

#ifdef __cplusplus
}
#endif

const char QUIC_VERSION_20200603=0;

static int parse_quic_port(char *port_list, unsigned short *quic_port, int quic_port_num)
{
	int	i=0,ret=0;
	int port_num=0;
	int range_len=0,used_len=0;
	char buf[256]={0};
	unsigned short s_port=0,e_port=0;
	char *begin=NULL,*end=NULL,*pchr=NULL;

	if(port_list==NULL)
	{
		return 0;
	}
	
	begin=port_list;
	end=NULL;
	range_len=strlen(port_list);
	
	while(range_len>used_len)
	{
		end=index(begin, ';');
		if(end==NULL)
		{
			end=begin+range_len-used_len;
		}

		if(end==begin)
		{
			break;
		}
		
		memset(buf, 0, sizeof(buf));
		strncpy(buf, begin, end-begin);
		used_len+=end-begin+1;
		if(range_len>used_len)
		{			
			begin=end+1;
		}
		
		pchr=strchr(buf, '-');
		if(pchr == NULL)
		{
			s_port=(unsigned short)atoi(buf);
			e_port=s_port;

		}
		else
		{
			ret=sscanf(buf, "%hu-%hu", &s_port, &e_port);
			if(ret!=2)
			{
				continue;
			}
		}
		
		for(i=s_port; i<=e_port && port_num<quic_port_num; i++)
		{
			quic_port[port_num++]=i;
		}
	}

	return port_num;
}


int quic_init_stream(void **pme, int thread_seq)
{
	struct _quic_context *_context=(struct _quic_context *)dictator_malloc(thread_seq, sizeof(struct _quic_context));
	memset(_context, 0, sizeof(struct _quic_context));

	*pme=(void*)_context;
	
	return 0;
}

void quic_release_exts(int thread_seq, quic_tlv_t *ext_tags, int ext_tag_num)
{
	int i=0;
	
	if(ext_tags!=NULL)
	{
		for(i=0; i<ext_tag_num; i++)
		{
			if(ext_tags[i].value!=NULL)
			{
				dictator_free(thread_seq, ext_tags[i].value);
				ext_tags[i].value=NULL;
			}
		}

		dictator_free(thread_seq, ext_tags);
		ext_tags=NULL;
	}

}

void quic_release_stream(void** pme, int thread_seq)
{
	struct _quic_context *_context = (struct _quic_context *)*pme;
	if(NULL!=_context)
	{	
		if(_context->quic_info.client_hello!=NULL)
		{
			quic_release_exts(thread_seq, _context->quic_info.client_hello->ext_tags, _context->quic_info.client_hello->ext_tag_num);
			dictator_free(thread_seq, _context->quic_info.client_hello);
			_context->quic_info.client_hello=NULL;
		}

		if(_context->quic_info.server_hello!=NULL)
		{
			quic_release_exts(thread_seq, _context->quic_info.server_hello->ext_tags, _context->quic_info.server_hello->ext_tag_num);
			dictator_free(thread_seq, _context->quic_info.server_hello);
			_context->quic_info.server_hello=NULL;
		}
		
		if(_context->quic_info.rejection!=NULL)
		{
			quic_release_exts(thread_seq, _context->quic_info.rejection->ext_tags, _context->quic_info.rejection->ext_tag_num);
			dictator_free(thread_seq, _context->quic_info.rejection);
			_context->quic_info.rejection=NULL;
		}

		dictator_free(thread_seq, _context);
		_context=NULL;
		*pme=NULL;
	}
	
    return;
}

extern "C" int QUIC_INIT(void)
{
	int ret=0;
	FILE *fp=NULL;
	char buff[2048]={0};
	int region_id=0;
	char region_name[REGION_NAME_LEN]={0};

	memset(&g_quic_param,0,sizeof(struct _quic_param_t));

	MESA_load_profile_int_def(g_quic_proto_conffile, "QUIC", "LOG_LEVEL", &g_quic_param.level, RLOG_LV_FATAL);
	MESA_load_profile_string_def(g_quic_proto_conffile, "QUIC", "LOG_PATH", g_quic_param.log_path, sizeof(g_quic_param.log_path), "./log/quic/quic");
	
	MESA_load_profile_int_def(g_quic_proto_conffile, "QUIC", "DUMP_PCAKET_SWITCH", &g_quic_param.dump_packet_switch, 0);

	MESA_load_profile_string_def(g_quic_proto_conffile, "QUIC", "QUIC_PORT_LIST", buff, sizeof(buff), "443;8443;");
	g_quic_param.quic_port_num=parse_quic_port(buff, g_quic_param.quic_port_list, SUPPORT_QUIC_PORT_NUM);

	g_quic_param.logger=MESA_create_runtime_log_handle(g_quic_param.log_path, g_quic_param.level);
	if(g_quic_param.logger==NULL)
	{
		printf("MESA_create_runtime_log_handle failed, level: %d log_path: %s", g_quic_param.level, g_quic_param.log_path);
		return -1;
	}

	if(((fp = fopen(g_quic_regionname_conffile, "r"))!=NULL))
	{
		 while(fgets(buff, sizeof(buff), fp))
		 {
			 ret = sscanf(buff, "%d\t%s", &region_id, region_name);
			 if(2>ret)
			 {
			 	fclose(fp);
				 MESA_handle_runtime_log(g_quic_param.logger, RLOG_LV_FATAL, "QUIC_READCONF", "Read error, Please check %s, region_line: %s", g_quic_regionname_conffile, buff);
				 return -1;
			 }
			 if(region_id>MAX_REGION_NUM)
			 {
			 	fclose(fp);
				 MESA_handle_runtime_log(g_quic_param.logger, RLOG_LV_FATAL, "QUIC_READCONF", "Read error, Please check %s, bigger than MAX_REGION_NUM, region_line: %s", g_quic_regionname_conffile, buff);
				 return -1;
			 }
			 
			 strncpy(g_quic_param.quic_conf_regionname[region_id], region_name, strlen(region_name));
			 g_quic_param.quic_region_cnt++;
			 memset(region_name, 0, sizeof(region_name));
		}
		 
		fclose(fp);
	}
	else
	{
		 MESA_handle_runtime_log(g_quic_param.logger, RLOG_LV_FATAL, "QUIC_READCONF", "Open %s error, Please check %s", g_quic_regionname_conffile, g_quic_regionname_conffile);
		 return -1;
	}

	gcry_init();

	return 0;
}/*QUICINIT*/

extern "C" void QUIC_DESTROY(void)
{
	return ;
}/*QUICDESTROY*/

extern "C" void QUIC_GETPLUGID(unsigned short plugid)
{
	g_quic_param.quic_plugid = plugid;
	MESA_handle_runtime_log(g_quic_param.logger, RLOG_LV_FATAL, "QUIC_GETPLUGID", "quic_plugid: %d", plugid);
}

extern "C" void QUIC_PROT_FUNSTAT(unsigned long long protflag)
{
	if(0==protflag){
		return;
	}
	g_quic_param.quic_interested_region_flag=protflag;
	MESA_handle_runtime_log(g_quic_param.logger, RLOG_LV_FATAL, "QUIC_PROT_FUNSTAT", "interested_region_flag: %llu", g_quic_param.quic_interested_region_flag);
	return;
}/*PROT_FUNSTAT*/

extern "C" unsigned long long quic_getRegionID(char *string, int str_len,const char g_string[MAX_REGION_NUM][REGION_NAME_LEN])
{
	unsigned long long i=0;
	for(i=0;i<g_quic_param.quic_region_cnt;i++)
	{
        if(0==strcasecmp(g_string[i], string))
		{
			return i;
		}
	}
	return 0;
}

extern "C" long long QUIC_FLAG_CHANGE(char* flag_str)
{
	if(flag_str==NULL) return -1;
	long long protflag = 0;
	long long region_id = 0;
	char *start_token = flag_str;
	char *end_token = flag_str;
	char *end_pos = flag_str+strlen(flag_str);
	char region_name[REGION_NAME_LEN] = {0};

	while (end_token < end_pos)
	{
		end_token = (char*)memchr(start_token, ',', end_pos-start_token);
		if(end_token!=NULL)
		{
			memcpy(region_name, start_token, end_token-start_token);
			start_token = end_token+1;
			end_token += 1;
		}
		else
		{
			memcpy(region_name, start_token, end_pos-start_token);
			end_token = end_pos;
		}
		region_id = quic_getRegionID(region_name, strlen(region_name), g_quic_param.quic_conf_regionname);
		if(-1==region_id)
		{
			MESA_handle_runtime_log(g_quic_param.logger, RLOG_LV_FATAL, "QUIC_FLAG_CHANGE", "Read %s error, flag_str: %d", region_name, flag_str);
			return -1;
		}
		protflag |= ((long long)1)<<region_id;
		memset(region_name, 0, REGION_NAME_LEN);
	}

	
	MESA_handle_runtime_log(g_quic_param.logger, RLOG_LV_FATAL, "QUIC_FLAG_CHANGE", "protflag: %llu", protflag);
	return protflag;
}

extern "C" char QUIC_ENTRY(struct streaminfo *pstream, void**pme, int thread_seq, void *a_packet)
{
	int ret=0;
	struct _quic_context *_context=(struct _quic_context *)*pme;

	if(g_quic_param.quic_interested_region_flag<QUIC_KEY)
	{
		return APP_STATE_DROPME;
	}

	if(!is_quic_port(pstream))
	{
		return APP_STATE_DROPME;
	}

	if(*pme==NULL)
	{
		quic_init_stream(pme, thread_seq);
		_context=(struct _quic_context *)*pme;
		_context->call_business=TRUE;
	}
	
	switch(pstream->opstate)
	{
		case OP_STATE_PENDING:
		case OP_STATE_DATA:
			ret=quic_process(pstream, _context, thread_seq, a_packet);
			break;
		case OP_STATE_CLOSE:
			ret=quic_process(pstream, _context, thread_seq, a_packet);
			quic_callPlugins(pstream, _context, NULL, 0, QUIC_INTEREST_KEY_MASK, a_packet);
			break;
		default:
			break;
	}
	
	if(ret&APP_STATE_DROPME || ret&APP_STATE_DROPPKT || pstream->opstate==OP_STATE_CLOSE)
	{
		quic_release_stream(pme, thread_seq);
		*pme=NULL;
		return ret;
	}

	return APP_STATE_GIVEME;
}

