#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <locale.h>
#include <langinfo.h>
#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <stdlib.h>
#include <arpa/inet.h>

#include "gquic_process.h"
#include "quic_analysis.h"
#include "parser_quic.h"

static int check_length(int last_len, int field_len)
{
	return ((last_len-field_len>0) ? 1 : 0);
}


int get_quic_tlv(char *start_pos, quic_tlv_t *tlv, int len, int type, int thread_seq)
{
	if(tlv->value==NULL && len>0)
	{
		tlv->value=(char *)calloc(1, len+1);
		memset(tlv->value, 0, len+1);
		tlv->length=len;
		tlv->type=type;
		memcpy(tlv->value, start_pos, tlv->length);
	}
	
	return 0;
}

static int get_value(unsigned char *payload, int *offset, int len)
{
	switch(len)
	{
		case	1:
			return (int)(payload[(*offset)++]);
			break;
		case	2:
			(*offset)+=len;
			return (int)ntohs(*(unsigned short *)(payload+*offset-len));
			break;
		case	3:
			(*offset)+=len;
			return ((int)*(payload-2+*offset)<<16|
		           (int)*(payload-1+*offset)<<8|
		           (int)*(payload+*offset)<<0);
			break;
		case	4:
			(*offset)+=len;
			return (int)ntohl(*(unsigned int *)(payload+*offset-len));
			break;
		case	32:
			(*offset)+=len;
			return 0;
			break;
		default:
			break;
	}

	return 0;
}

int parse_encrypt_parameter(struct _quic_stream *quic_stream, unsigned char *payload, int payload_len, int thread_seq)
{
	int used_len=0,length=0;
	while(payload_len>used_len)
	{
		if(payload[used_len]> 0x00 && payload[used_len]<=0x20)
		{
			get_value(payload, &used_len, 1);  //type=1
			length=get_value(payload, &used_len, 1); // length=1
			used_len+=length; 

			continue;
		}

		if((*(unsigned short *)(payload+used_len)) == htons(EXT_QUIC_PARAM_USER_AGENT))
		{
			quic_stream->ua_idx=quic_stream->ext_tag_num++;
			get_value(payload, &used_len, 2);  //type=2
			length=get_value(payload, &used_len, 1); // length=1
			get_quic_tlv((char *)payload+used_len, &(quic_stream->ext_tags[quic_stream->ua_idx]), length, EXT_QUIC_PARAM_USER_AGENT, thread_seq);
			used_len+=length;

			continue;
		}

		if(*(unsigned int *)(payload+used_len) == htonl(EXT_QUIC_PARAM_QUIC_VERSION))
		{
			quic_stream->ver_idx=quic_stream->ext_tag_num++;
			get_value(payload, &used_len, 4);  //type=4
			length=get_value(payload, &used_len, 1); // length=1
			get_quic_tlv((char *)payload+used_len, &(quic_stream->ext_tags[quic_stream->ver_idx]), length, EXT_QUIC_PARAM_QUIC_VERSION, thread_seq);
			*(unsigned int *)quic_stream->ext_tags[quic_stream->ver_idx].value=(unsigned int)htonl(*(unsigned int *)quic_stream->ext_tags[quic_stream->ver_idx].value);
			used_len+=length;

			continue;
		}

		if((*(unsigned int *)(payload+used_len))== htonl(EXT_QUIC_PARAM_GREASE_HIGH4) && (*(unsigned int *)(payload+used_len+4))== htonl(EXT_QUIC_PARAM_GREASE_LOW4))
		{
			used_len+=8;  //type=8
			length=get_value(payload, &used_len, 1); // length=1
			used_len+=length;

			continue;
		}

		break;
	}

	return 0;
}

int parse_encrypt_server_name(struct _quic_stream *quic_stream, unsigned char *payload, int payload_len, int thread_seq)
{
	int ext_len=0,used_len=0;
	
	ext_len=get_value(payload, &used_len, 2);  //Server Name List length
	if(ext_len>0 && ext_len>payload_len)
	{
		return 0;
	}
	
	if(get_value(payload, &used_len, 1)==0)  //Server Name type
	{
		ext_len=get_value(payload, &used_len, 2);  //Server Name length
		if(ext_len<0 || ext_len>payload_len)
		{
			return 0;
		}
		
		quic_stream->sni_idx=quic_stream->ext_tag_num++;
		get_quic_tlv((char *)payload+used_len, &(quic_stream->ext_tags[quic_stream->sni_idx]), ext_len, EXTENSION_SERVER_NAME, thread_seq);
	}
	else
	{
		return 0;
	}

	return 1;
}

int parse_encrypt_client_hello(void *pstream, struct _quic_stream *quic_stream, void *a_packet, unsigned char *payload, int payload_len)
{
	int used_len=0;
	int flags=0,ret=0;
	int skip_len=0,client_hello_len=0;
	int ext_type=0, extension_total_len=0;

	get_value(payload, &used_len, 1);  //handshake type
	client_hello_len=get_value(payload, &used_len, 3); //client hello length
	get_value(payload, &used_len, 2); //ssl_version

	get_value(payload, &used_len, 32);  //Random

	skip_len=(int)get_value(payload, &used_len, 1);  //Session ID length
	if(!check_length(payload_len-used_len, skip_len))
	{
		return flags;
	}
	used_len+=skip_len;

	skip_len=(int)get_value(payload, &used_len, 2);  //Ciper Suites length
	if(!check_length(payload_len-used_len, skip_len))
	{
		return flags;
	}
	used_len+=skip_len;

	skip_len=(int)get_value(payload, &used_len, 1);  //Compression Methods
	if(!check_length(payload_len-used_len, skip_len))
	{
		return flags;
	}
	used_len+=skip_len;

	extension_total_len=(int)get_value(payload, &used_len, 2);  //Extension length
	if(!check_length(payload_len-used_len, extension_total_len) && (extension_total_len!=payload_len-used_len))
	{
		return flags;
	}

	quic_stream->ext_tags=(quic_tlv_t *)calloc(1, sizeof(quic_tlv_t)*3);
	memset(quic_stream->ext_tags, 0, sizeof(quic_tlv_t)*3);
	
	while(extension_total_len>used_len)
	{
		ext_type=get_value(payload, &used_len, 2);  //Extension type		
		skip_len=get_value(payload, &used_len, 2);	//length
		if(!check_length(payload_len-used_len, skip_len) || skip_len==0)
		{
			return flags;
		}
				
		switch(ext_type)
		{
			case	EXTENSION_SERVER_NAME:
				ret=parse_encrypt_server_name(quic_stream, payload+used_len, skip_len, 0);
				if(ret==0)
				{
					break;
				}
				flags=1;
				break;
			case	EXTENSION_QUIC_PARAM:
				parse_encrypt_parameter(quic_stream, payload+used_len, skip_len, 0);
				break;
			case	EXTENSION_SUPPORT_GROUP:
			case	EXTENSION_APP_PROT_NEGO:
			case	EXTENSION_SIG_ALGORITHM:
			case	EXTENSION_KEY_SHARE:
			case	EXTENSION_PSK_EXCHANGE:
			case	EXTENSION_SUPP_SSL_VER:
			case	EXTENSION_COMPRESS_CERT:
				break;
			default:
				break;
		}
		
		used_len+=skip_len;
	}
	
	return flags;
}


int main(int argc, char *argv[])
{
	if(argc<1)
	{
		return -1;
	}

	struct stat statbuf;
	if (stat(argv[1], &statbuf) == -1)
	{
		return -1;
	}

	unsigned char *payload=(unsigned char *)calloc(1, statbuf.st_size);
	FILE *fp=fopen(argv[1], "rb");
	if(fp)
	{
		int size=fread(payload, 1, 1315, fp);
		assert(size==1315);
		//assert(size==statbuf.st_size);
			
		struct _quic_stream * quic_stream=(struct _quic_stream *)calloc(1, sizeof(struct _quic_stream));
		parse_encrypt_client_hello(NULL, quic_stream, NULL, payload+4, 1314)-4;

		fclose(fp);
		fp=NULL;

		free(quic_stream);
		quic_stream=NULL;

		free(payload);
		payload=NULL;
		
	}

	return 0;
}
