/*
 * dpkt_basic_proto_main.cpp
 *
 *  Created on: Aug 7, 2020
 *      Author: iie
 */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <assert.h>

#include "uthash/uthash.h"

//#include <MESA/MESA_prof_load.h>
//#include <MESA/MESA_handle_logger.h>


#include "stellar/utils.h"

#include "stellar/session.h"
#include "stellar/stellar_exdata.h"
#include "stellar/stellar_mq.h"

#include "stellar/packet.h"
#include "stellar/appid.h"

#include "app_l7_protocol.h"

const char *dns_name="DNS";
const char *imap_name="IMAP";
const char *quic_name="QUIC";

const char *g_app_proto_conffile="./tsgconf/main.conf";
const char *l7_protocol_file = (char *)"./tsgconf/tsg_l7_protocol.conf";
const char *log_path = (char *)"./tsglog/glimpse_detector/glimpse_detector";


struct glimpse_detector_schema
{
	int level;
	int max_pkts;
	int l7_label_id;
	int l7_bridge_id;	
	int plugin_exdata_idx;
	int l7_app_id_exdata_idx;
	int topic_id;
	int plugin_id;
	struct stellar *st;
	char log_path[128];
	char l7_label_name[128];
	char l7_bridge_name[128];
	char l7_library_name[128];
	char l7_protocol_file[128];
	struct l7_protocol_hash *name_by_id;
	void *logger;
	int tcp_topic_id;
	int udp_topic_id;
};

struct l7_protocol_hash g_app_name2lpi_id[]={
											{0, LPI_PROTO_UNKNOWN,		"UNCATEGORIZED",{}},
											//{0, LPI_PROTO_UNKNOWN, 		"Unknown_UDP"},
											//{0, LPI_PROTO_UNSUPPORTED, 	"Unknown_Other"},
											{0, LPI_PROTO_DNS, 			"DNS", {}},
											{0, LPI_PROTO_FTP_CONTROL, 	"FTP", {}},
											//{0, LPI_PROTO_FTP_DATA, 	"FTP", NULL, NULL},
											{0, LPI_PROTP_FTPS, 		"FTPS", {}},
											{0, LPI_PROTO_HTTP, 		"HTTP", {}},
											{0, LPI_PROTO_HTTPS, 		"HTTPS", {}},
											{0, LPI_PROTO_ICMP, 		"ICMP", {}},
											{0, LPI_PROTO_IKE, 			"IKE", {}},
											{0, LPI_PROTO_MAIL, 		"MAIL", {}},
											{0, LPI_PROTO_IMAP, 		"IMAP", {}},
											{0, LPI_PROTO_IMAPS, 		"IMAPS", {}},
											{0, LPI_PROTO_UDP_ESP, 		"IPSEC", {}},
											{0, LPI_PROTO_XMPP, 		"XMPP", {}},
											{0, LPI_PROTO_UDP_L2TP,		"L2TP", {}},
											{0, LPI_PROTO_UDP_NTP,		"NTP", {}},
											{0, LPI_PROTO_POP3, 		"POP3", {}},
											{0, LPI_PROTO_POP3S,		"POP3S", {}},
											{0, LPI_PROTO_PPTP, 		"PPTP", {}},
											{0, LPI_PROTO_UDP_QUIC, 	"QUIC", {}},
											{0, LPI_PROTO_SIP, 			"SIP", {}},
											{0, LPI_PROTO_SMB, 			"SMB", {}},
											{0, LPI_PROTO_SMTP, 		"SMTP", {}},
											{0, LPI_PROTO_SMTPS, 		"SMTPS", {}},
											{0, LPI_PROTO_SPDY, 		"SPDY", {}},
											{0, LPI_PROTO_SSH, 			"SSH", {}},
											{0, LPI_PROTO_SSL, 			"SSL", {}},
											{0, LPI_PROTO_SOCKS4, 		"SOCKS", {}},
											//{0, LPI_PROTO_SOCKS4, 	"SOCKS4", NULL, NULL},
											//{0, LPI_PROTO_SOCKS5, 	"SOCKS5", NULL, NULL},
											{0, LPI_PROTO_TELNET, 		"TELNET", {}},
											{0, LPI_PROTO_UDP_DHCP, 	"DHCP", {}},
											{0, LPI_PROTO_UDP_RADIUS, 	"RADIUS", {}},
											{0, LPI_PROTO_OPENVPN, 		"OPENVPN", {}},
											{0, LPI_PROTO_STUN, 		"STUN", {}},
											{0, LPI_PROTO_UDP_TEREDO, 	"TEREDO", {}},
											{0, LPI_PROTO_UDP_DTLS, 	"DTLS", {}},
											{0, LPI_PROTO_DOH, 			"DoH", {}},
											{0, LPI_PROTO_UDP_ISAKMP, 	"ISAKMP", {}},
											{0, LPI_PROTO_UDP_MDNS, 	"MDNS", {}},
											{0, LPI_PROTO_NETBIOS, 		"NETBIOS", {}},
											{0, LPI_PROTO_UDP_NETFLOW, 	"NETFLOW", {}},
											{0, LPI_PROTO_RDP, 			"RDP", {}},
											{0, LPI_PROTO_UDP_RTCP, 	"RTCP", {}},
											{0, LPI_PROTO_UDP_RTP, 		"RTP", {}},
											{0, LPI_PROTO_UDP_SLP, 		"SLP", {}},
											{0, LPI_PROTO_UDP_SNMP, 	"SNMP", {}},
											{0, LPI_PROTO_UDP_SSDP, 	"SSDP", {}},
											{0, LPI_PROTO_UDP_TFTP, 	"TFTP", {}},
											{0, LPI_PROTO_UDP_BJNP, 	"BJNP", {}},
											{0, LPI_PROTO_LDAP, 		"LDAP", {}},
											{0, LPI_PROTO_RTMP, 		"RTMP", {}},
											{0, LPI_PROTO_RTSP, 		"RTSP", {}},
											{0, LPI_PROTO_ESNI, 		"ESNI", {}},
											{0, LPI_PROTO_STRATUM, 		"Stratum", {}},
											{0, LPI_PROTO_QQ, 		"QQ", {}},
											{0, LPI_PROTO_WECHAT, 		"WeChat", {}},
											{0, LPI_PROTO_MMS, 		"MMS", {}},
											{0, LPI_PROTO_RSYNC, 		"RSYNC", {}},
											{0, LPI_PROTO_UDP_WIREGUARD, 		"WIREGUARD", {}},
										};

struct dns_header
{
	u_int16_t id;
#if __BYTE_ORDER == __LITTLE_ENDIAN
	u_char rd:1;
	u_char tc:1;
	u_char aa:1;
	u_char opcode:4;
	u_char qr:1;
	u_char rcode:4;
	u_char z:3;
	u_char ra:1;
#elif __BYTE_ORDER == __BIG_ENDIAN
	u_char qr:1;
	u_char opcode:4;
	u_char aa:1;
	u_char tc:1;
	u_char rd:1;
	u_char ra:1;
	u_char z:3;
	u_char rcode:4;
#endif
	u_int16_t qdcount;
	u_int16_t ancount;
	u_int16_t aucount;//authority count
	u_int16_t adcount;//additional count
};

static void get_host_order_port(struct session *sess __unused, unsigned short *sport, unsigned short *dport)
{
	*sport=0;
	*dport=0;
#if 0
	// DONE: get host order port from session api 1.0
	enum session_addr_type addr_type;
	struct session_addr *addr=session_get0_addr(sess, &addr_type);
	unsigned short n_sport = (addr_type == SESSION_ADDR_TYPE_IPV4_TCP || addr_type ==  SESSION_ADDR_TYPE_IPV4_UDP) ? (addr->ipv4.sport) : ( (addr_type == SESSION_ADDR_TYPE_IPV6_TCP || addr_type ==  SESSION_ADDR_TYPE_IPV6_UDP) ? (addr->ipv6.sport) : 0);
	unsigned short n_dport = (addr_type == SESSION_ADDR_TYPE_IPV4_TCP || addr_type == SESSION_ADDR_TYPE_IPV4_UDP) ? (addr->ipv4.dport) : ( (addr_type == SESSION_ADDR_TYPE_IPV6_TCP || addr_type == SESSION_ADDR_TYPE_IPV6_UDP) ? (addr->ipv6.dport) : 0);
	//unsigned short n_dport = (pstream->addr.addrtype == ADDR_TYPE_IPV4) ? (pstream->addr.tuple4_v4->dest) : ( (pstream->addr.addrtype == ADDR_TYPE_IPV6) ? (pstream->addr.tuple4_v6->dest) : 0);

	*sport=ntohs(n_sport);
	*dport=ntohs(n_dport);
#else
//get host order port from stellar session api

	#if 0
	const struct tuple6 *t6 = session_get_tuple6(sess);
	enum flow_direction flow_dir = session_get_tuple6_direction(sess);
	if(flow_dir == FLOW_DIRECTION_C2S)
	{
		*sport = ntohs(t6->src_port);
		*dport = ntohs(t6->dst_port);
	}
	else
	{
		*sport = ntohs(t6->dst_port);
		*dport = ntohs(t6->src_port);
	
	}
	#endif


	const struct packet *pkt = session_get0_current_packet(sess);
	enum flow_direction flow_dir=session_get_current_flow_direction(sess);
	if(pkt && (flow_dir==FLOW_DIRECTION_C2S || flow_dir==FLOW_DIRECTION_S2C))
	{
		int layer_cnt=packet_get_layer_count(pkt);
		const struct layer *layer = packet_get_layer_by_idx(pkt, layer_cnt - 1);
		if (layer && layer->proto == LAYER_PROTO_TCP)
		{
			*sport = ntohs(layer->hdr.tcp->th_sport);
			*dport = ntohs(layer->hdr.tcp->th_dport);
		}
		if (layer && layer->proto == LAYER_PROTO_UDP)
		{
			*sport = ntohs(layer->hdr.udp->uh_sport);
			*dport = ntohs(layer->hdr.udp->uh_dport);
		}
		//S2C, swap sport and dport
        if(flow_dir == FLOW_DIRECTION_S2C)
        {
            unsigned short tmp = *sport;
            *sport = *dport;
            *dport = tmp;
        }
    }
#endif	
	
	return;
}

static int revise_lpi_result(struct session *sess, const char *scan_data, const int scan_data_len, lpi_module_t* proto, struct traffic_context *context, int l4_is_tcp, int curdir_is_c2s)
{
	unsigned short sport=0, dport=0;

	get_host_order_port(sess, &sport, &dport);

	if(sport == 989 || sport == 990 || dport == 989 || dport == 990)
	{
		if(proto->protocol == LPI_PROTO_SSL)
		{
			proto->protocol=(lpi_protocol_t)LPI_PROTP_FTPS;
			proto->category=LPI_CATEGORY_FILES;
			proto->priority=3;
			proto->name="FTPS";
			proto->lpi_callback=NULL;
			return 1;
		}
	}

	if(sport==443 || dport==443 || sport==80 || dport==80 || sport==8443 || dport==8443)
	{
		if(app_identify_get_quic_protocol(curdir_is_c2s, (const unsigned char *)scan_data, scan_data_len) != QUIC_VERSION_UNKNOWN)
		{
			//lpi_quic
			proto->protocol=LPI_PROTO_UDP_QUIC;
			proto->category=LPI_CATEGORY_WEB;
			proto->priority=9;
			proto->name=quic_name;
			proto->lpi_callback=NULL;
			return 1;
		}
	}

	if((sport==53 || dport==53) && (scan_data_len>12))
	{
		struct dns_header *dns_hdr=NULL;
		if(l4_is_tcp)
		{
			dns_hdr=(struct dns_header *)(scan_data+2);
		}
		else
		{
			dns_hdr=(struct dns_header *)scan_data;
		}
		if((dns_hdr->qr==1 && ntohs(dns_hdr->qdcount)==1) || 
			(dns_hdr->qr==0 && ntohs(dns_hdr->qdcount)==1 && ntohs(dns_hdr->aucount)==0 && ntohs(dns_hdr->ancount)==0))
		{
			proto->protocol=LPI_PROTO_DNS;
			proto->category=LPI_CATEGORY_SERVICES;
			proto->priority=10;
			proto->name=dns_name;
			proto->lpi_callback=NULL;
			return 1;
		}
	}

	if(((sport==143 || dport==143)) && 
			(
				(scan_data_len>=11 && memmem(scan_data, scan_data_len, " CAPABILITY", 11)!=NULL) ||
				(scan_data_len>=7 && memmem(scan_data, scan_data_len, " LOGIN ", 7)!=NULL)
			)
		)
	{
		proto->protocol=LPI_PROTO_IMAP;
		proto->category=LPI_CATEGORY_MAIL;
		proto->priority=2;
		proto->name=imap_name;
		proto->lpi_callback=NULL;
		return 1;
	}
	int c2s_pkt_cnt=context->c2s_pkt;
	int s2c_pkt_cnt=context->s2c_pkt;

	if(app_identify_guess_openvpn((const unsigned char *)scan_data, scan_data_len, c2s_pkt_cnt, s2c_pkt_cnt, &context->ovpn, l4_is_tcp, curdir_is_c2s)==1)
	{
		proto->protocol=LPI_PROTO_OPENVPN;
		proto->category=LPI_CATEGORY_TUNNELLING;
		proto->priority=4;
		proto->name="OpenVPN";
		proto->lpi_callback=NULL;
		return 1;
	}

	return 0;
}


static int set_lpi_same_name(struct l7_protocol_hash *l7_protocol_hash, struct l7_protocol_hash *l7_protocol_elem, int lpi_protocol_id)
{
	struct l7_protocol_hash *tmp=(struct l7_protocol_hash *)calloc(1, sizeof(struct l7_protocol_hash));
	memcpy(tmp, l7_protocol_elem, sizeof(struct l7_protocol_hash));

	tmp->lpi_protocol=lpi_protocol_id;
	HASH_ADD(hh, l7_protocol_hash, lpi_protocol, sizeof(int), tmp);

	return 0;		
}

static int get_protocol_id(struct l7_protocol_hash *name_by_id, const lpi_module_t* proto)
{
	struct l7_protocol_hash *out=NULL;
	
	HASH_FIND(hh, name_by_id, &(proto->protocol), sizeof(int), out);
	if(out!=NULL)
	{
		return out->protocol_id;
	}

	return -1;
}

static struct app_id_message *app_id_messgae_create(unsigned short *id_array, int id_num)
{
	struct app_id_message *result=CALLOC(struct app_id_message, 1);
	result->origin=ORIGIN_PROTO_IDENTIFY;
	result->magic=MESSAGE_MAGIC;
	result->app_id_num=id_num;
	for(unsigned int i=0; i<result->app_id_num; i++)
	{
		result->app_id[i]=(int)(id_array[i]);
		result->surrogate_id[i]=0;
	}

	return result;
}

static int update_protocol_id(unsigned short *old_id_array, int *old_id_num, unsigned short new_id)
{
	int i=0;

	if(*old_id_num>=MAX_APP_ID_NUM)
	{
		return 0;
	}

	for(i=0; i<(*old_id_num); i++)
	{
		if(new_id==old_id_array[i])
		{
			return 0;
		}
	}

	old_id_array[(*old_id_num)]=new_id;
	(*old_id_num)++;

	return 1;
}

static int l7_protocol_mapper(struct glimpse_detector_schema *g_app_proto_para, const char *filename)
{
	size_t i=0,ret=0;
	FILE *fp=NULL;
	char line[1024]={0};
	char type_name[32]={0};
	struct l7_protocol_hash *name2lpi=NULL;
	struct l7_protocol_hash *l7_protocol=NULL;
	struct l7_protocol_hash *out=NULL;

	fp=fopen(filename, "r");
	if(fp==NULL)
	{
		printf("Open %s failed ...", filename);
		return -1;
	}

	for(i=0; i<(sizeof(g_app_name2lpi_id)/sizeof(struct l7_protocol_hash)); i++)
	{		
 		HASH_ADD(hh, name2lpi, name, strlen(g_app_name2lpi_id[i].name), &(g_app_name2lpi_id[i]));
	}
	
	memset(line, 0, sizeof(line));

	while((fgets(line, sizeof(line), fp))!=NULL)
	{
		if(line[0]=='#' || line[0]=='\n' || line[0]=='\r' ||line[0]=='\0')
		{
			continue;
		}
		
	    l7_protocol=(struct l7_protocol_hash *)calloc(1, sizeof(struct l7_protocol_hash));
		ret=sscanf(line, "%31s %63s %d", type_name, l7_protocol->name, &l7_protocol->protocol_id);
		assert(ret==3);

		HASH_FIND(hh, name2lpi, l7_protocol->name, strlen(l7_protocol->name), out);
		if(out==NULL)
		{
			//MESA_handle_runtime_log(g_app_proto_para->logger, RLOG_LV_FATAL, "INIT_HASH", "Protocol %s identified without registered LPI ID", l7_protocol->name);
			free(l7_protocol);
			continue;
		}
		
		l7_protocol->lpi_protocol=out->lpi_protocol;
	    HASH_ADD(hh, g_app_proto_para->name_by_id, lpi_protocol, sizeof(int), l7_protocol);

		switch(out->lpi_protocol)
		{
			case	LPI_PROTO_DNS:				
				set_lpi_same_name(g_app_proto_para->name_by_id, l7_protocol, LPI_PROTO_UDP_DNS);
				break;
			case	LPI_PROTO_SIP:				
				set_lpi_same_name(g_app_proto_para->name_by_id, l7_protocol, LPI_PROTO_UDP_SIP);
				break;
			case	LPI_PROTO_SOCKS4:
				set_lpi_same_name(g_app_proto_para->name_by_id, l7_protocol, LPI_PROTO_SOCKS5);
				break;
			case	LPI_PROTO_FTP_CONTROL:
				set_lpi_same_name(g_app_proto_para->name_by_id, l7_protocol, LPI_PROTO_FTP_DATA);
				break;
			case	LPI_PROTO_OPENVPN:				
				set_lpi_same_name(g_app_proto_para->name_by_id, l7_protocol, LPI_PROTO_UDP_OPENVPN);
				break;
			case	LPI_PROTO_LDAP:				
				set_lpi_same_name(g_app_proto_para->name_by_id, l7_protocol, LPI_PROTO_TCP_LDAP_AD);
				set_lpi_same_name(g_app_proto_para->name_by_id, l7_protocol, LPI_PROTO_UDP_LDAP_AD);
				break;
			case	LPI_PROTO_HTTP:				
				set_lpi_same_name(g_app_proto_para->name_by_id, l7_protocol, LPI_PROTO_HTTP_BADPORT);
				set_lpi_same_name(g_app_proto_para->name_by_id, l7_protocol, LPI_PROTO_HTTP_TUNNEL);
				set_lpi_same_name(g_app_proto_para->name_by_id, l7_protocol, LPI_PROTO_NONSTANDARD_HTTP);
				break;
			case	LPI_PROTO_STUN:
				set_lpi_same_name(g_app_proto_para->name_by_id, l7_protocol, LPI_PROTO_UDP_STUN);
				break;
			case	LPI_PROTO_NETBIOS:
				set_lpi_same_name(g_app_proto_para->name_by_id, l7_protocol, LPI_PROTO_UDP_NETBIOS);
				break;
			case	LPI_PROTO_QQ:				
				set_lpi_same_name(g_app_proto_para->name_by_id, l7_protocol, LPI_PROTO_UDP_QQ);
				break;
			case	LPI_PROTO_WECHAT:				
				set_lpi_same_name(g_app_proto_para->name_by_id, l7_protocol, LPI_PROTO_UDP_WECHAT);
				break;
			default:
				break;
		}
		
		memset(line, 0, sizeof(line));
	}

	fclose(fp);
	fp=NULL;

	HASH_CLEAR(hh, name2lpi);

#if 0
	HASH_ITER(hh, g_app_proto_para->name_by_id, out ,tmp)
	{		
		MESA_handle_runtime_log(g_app_proto_para->logger,
									RLOG_LV_DEBUG, 
									"ID_MAPPER",
									"%s lpi_protocol built-in ID is %d and TSG ID is %d",
									out->name,
									out->lpi_protocol,
									out->protocol_id
								);
	}
#endif

	return ret;
}

static  void free_l7_protocol_mapper(struct l7_protocol_hash *hash)
{
	if(hash==NULL)
	{
		return ;
	}
	struct l7_protocol_hash *out=NULL, *tmp=NULL;
	HASH_ITER(hh, hash, out ,tmp)
	{		
		if(out!=NULL)
		{
			HASH_DEL(hash, out);
			free(out);
			out=NULL;
		}
	}
}

int lpi_update_dpkt(lpi_data_t *data, struct session *sess,
        const char *scan_data, int scan_data_len, char tsproto,
        uint32_t is_c2s)
{

    uint32_t dir = 0;
    if(is_c2s == 1)
    {
        dir = 0;
    }
    else 
    {
        dir = 1;
	}
    uint32_t four_bytes;

    if (data->observed[dir] > 32 * 1024)
        return 0;

    data->observed[dir] += scan_data_len;

    if (data->trans_proto == 0)data->trans_proto = tsproto;

    if (scan_data == NULL)return 0;
    if (scan_data_len <= 0)return 0;

	if (scan_data_len < 4)
	{
		memcpy((char *)&four_bytes, scan_data, scan_data_len);
		four_bytes = (ntohl(four_bytes)) >> (8 * (4 - scan_data_len));
		four_bytes = htonl(four_bytes << (8 * (4 - scan_data_len)));
	}
	else
	{
		four_bytes = (*(uint32_t *)scan_data);
	}

	data->payload[dir] = four_bytes;
    data->payload_len[dir] = scan_data_len;

    uint16_t  source=0;
    uint16_t  dest=0;
    get_host_order_port(sess,&source ,&dest);

    data->client_port = source;
    data->server_port = dest;

// DONE: no need to set saddr and daddr
/*

    if (pstream != NULL && data->ips[0] == 0
            && (pstream->addr.addrtype == __ADDR_TYPE_IP_PAIR_V4
                    || pstream->addr.addrtype == ADDR_TYPE_IPV4))
    {
        if (dir == 1)
        {
            data->ips[0] = pstream->addr.ipv4->saddr;
            data->ips[1] = pstream->addr.ipv4->daddr;
        }
        else
        {
            data->ips[1] = pstream->addr.ipv4->saddr;
            data->ips[0] = pstream->addr.ipv4->daddr;
        }
    }
*/
    return 1;

}



static int app_glimpse_detector(struct session *sess, struct traffic_context *context, struct glimpse_detector_schema *g_app_proto_para, const char *scan_data, const int scan_data_len, int l4_proto)
{
	int ret=0,is_update=0;
	//unsigned short sport=0, dport=0;
	lpi_module_t modify_lpi_result={(lpi_protocol_t)0, (lpi_category_t)0, NULL, 0, NULL};
	unsigned short new_protocol_id=0;
	struct l7_protocol_label *l7_protocol=NULL;
	//const struct packet *raw_packet=session_get0_current_packet(sess);
	//int cur_pkt_dir= packet_get_direction(raw_packet);  
	int cur_pkt_dir=session_get_current_flow_direction(sess);
	context->pkt_cnt++;
	(cur_pkt_dir==FLOW_DIRECTION_C2S) ? (context->c2s_pkt++) : (context->s2c_pkt++);
	int is_c2s_pkt=((cur_pkt_dir==FLOW_DIRECTION_C2S) ? 1 : 0);
	ret=lpi_update_dpkt(&(context->lpi_data), sess, scan_data, scan_data_len, l4_proto, is_c2s_pkt);
	if(!ret)
	{
		return 1;
	}

	lpi_module_t *plpi_mod=lpi_guess_protocol(&(context->lpi_data));
	if(plpi_mod==NULL)
	{
		return 1;
	}

	if((plpi_mod->protocol==LPI_PROTO_UNKNOWN || plpi_mod->protocol==LPI_PROTO_UDP) &&
		(context->pkt_cnt<g_app_proto_para->max_pkts || context->is_identify==1))
	{
		ret=revise_lpi_result(sess, scan_data, scan_data_len, &modify_lpi_result, context, 
							(l4_proto==6?1:0), 
							(cur_pkt_dir==FLOW_DIRECTION_C2S?1:0));
		if(ret==0)
		{
			return 1;
		}
		
		plpi_mod=&modify_lpi_result;
	}

	if (plpi_mod->protocol == LPI_PROTO_FTP_CONTROL && context->is_first_payload == 1 && cur_pkt_dir == FLOW_DIRECTION_S2C)
	{
		if ((((scan_data_len >= 4 && (memcmp(scan_data, "220-", 4) == 0)) || memcmp(scan_data, "220 ", 4) == 0)) &&
			(((scan_data_len >= 7 && (memmem(scan_data, scan_data_len, " ESMTP ", 7) != NULL)) ||
			memmem(scan_data, scan_data_len, " esmtp ", 7) != NULL)))
		{
			// lpi_smtp
			modify_lpi_result.protocol = LPI_PROTO_SMTP;
			modify_lpi_result.category = LPI_CATEGORY_MAIL;
			modify_lpi_result.priority = 2;
			modify_lpi_result.name = "SMTP";

			plpi_mod = &modify_lpi_result;
		}
	}

	if (plpi_mod->protocol == LPI_PROTO_SMTP && context->is_first_payload == 1 && cur_pkt_dir == FLOW_DIRECTION_S2C)
	{
		if (
			(scan_data_len >= 4) &&
			(memcmp(scan_data, "220 ", 4) == 0 || memcmp(scan_data, "220-", 4) == 0) &&
			(memmem(scan_data, scan_data_len, "FTPd", 4) != NULL ||
			memmem(scan_data, scan_data_len, "ftpd", 4) != NULL ||
			memmem(scan_data, scan_data_len, "FTPD", 4) != NULL))
		{
			// lpi_ftpcontrol
			modify_lpi_result.protocol = LPI_PROTO_FTP_CONTROL;
			modify_lpi_result.category = LPI_CATEGORY_FILES;
			modify_lpi_result.priority = 3;
			modify_lpi_result.name = "FTP_Control";

			plpi_mod = &modify_lpi_result;
		}
	}

	new_protocol_id=get_protocol_id(g_app_proto_para->name_by_id, plpi_mod);
#if 0
	if(new_protocol_id==(unsigned short)-1)
	{

		if(plpi_mod->protocol!=LPI_PROTO_UNKNOWN && plpi_mod->protocol!=LPI_PROTO_UDP && plpi_mod->protocol!=LPI_PROTO_UNSUPPORTED)
		{
			MESA_handle_runtime_log(g_app_proto_para->logger, 
										RLOG_LV_INFO,
										"UNDEFINE", 
										"Protocol Name: %s identified without registered LPI ID=%d, category: %s priority: %d addr: %s", 
										plpi_mod->name,
										plpi_mod->protocol, 
										lpi_print_category(plpi_mod->category),
										plpi_mod->priority,
										session_get0_readable_addr(sess)
									);
		}
		return 1;
	}
	else
	{
		MESA_handle_runtime_log(g_app_proto_para->logger, 
									RLOG_LV_INFO,
									"DEFINE", 
									"Protocol Name: %s identified registered LPI ID(%d), category: %s priority: %d addr: %s", 
									plpi_mod->name,
									plpi_mod->protocol, 
									lpi_print_category(plpi_mod->category),
									plpi_mod->priority,
									session_get0_readable_addr(sess)
								);
	}
#endif		

	l7_protocol = (struct l7_protocol_label *)session_exdata_get(sess, g_app_proto_para->l7_app_id_exdata_idx);
	if(l7_protocol==NULL)
	{
		l7_protocol=CALLOC(struct l7_protocol_label, 1);
		l7_protocol->continue_scan_flag = 1;
		session_exdata_set(sess, g_app_proto_para->l7_app_id_exdata_idx, l7_protocol);	
	}
	
	is_update=update_protocol_id(l7_protocol->protocol_id, &(l7_protocol->protocol_id_num), new_protocol_id);
	if(is_update==1)
	{
		context->is_identify=1;
		struct app_id_message *msg=app_id_messgae_create(l7_protocol->protocol_id, l7_protocol->protocol_id_num);
		if(session_mq_publish_message(sess, g_app_proto_para->topic_id, msg) < 0)
		{
			FREE(msg);
		}
		//MESA_handle_runtime_log(g_app_proto_para->logger, RLOG_LV_DEBUG, "ADD_PROJECT", "%s identified, ID %d addr: %s", plpi_mod->name, new_protocol_id, session_get0_readable_addr(sess));
	}

	if (context->pkt_cnt >= g_app_proto_para->max_pkts || (plpi_mod->category != LPI_CATEGORY_TUNNELLING && plpi_mod->category != LPI_CATEGORY_NAT && plpi_mod->protocol != LPI_PROTO_RDP && plpi_mod->protocol != LPI_PROTO_UDP_RDP && plpi_mod->protocol != LPI_PROTO_UDP_RTP && plpi_mod->protocol != LPI_PROTO_UDP_RTCP))
	{
		l7_protocol->continue_scan_flag=0;
		return 0;
	}
	
	return 1;
}

static void glimpse_detector_on_session_msg(struct session *sess, int topic_id __unused, const void *msg, void *per_session_ctx __unused, void *plugin_env)
{
	assert(plugin_env!=NULL);

	if(msg==NULL)return;

	struct glimpse_detector_schema *glimpse_detector_env=(struct glimpse_detector_schema *)plugin_env;
	int identify_continue=0;
	struct traffic_context *context = (struct traffic_context *)session_exdata_get(sess, glimpse_detector_env->plugin_exdata_idx);
	if(context==NULL)
	{
		context= CALLOC(struct traffic_context, 1);
		context->is_first_payload=1;
		session_exdata_set(sess, glimpse_detector_env->plugin_exdata_idx, context);	
	}
	//size_t payload_len=0;
	//const char *payload=session_get0_current_payload(sess, &payload_len);

	uint16_t payload_len=packet_get_payload_len((struct packet*)msg);
	const char *payload=packet_get_payload((struct packet*)msg);
	if (payload!=NULL && payload_len>0 && context->pkt_cnt<glimpse_detector_env->max_pkts)
	{
		int l4_proto=0;
		enum session_type type= session_get_type(sess);
		if(type == SESSION_TYPE_TCP)
		{
			l4_proto=6;
		}
		if(type == SESSION_TYPE_UDP)
		{
			l4_proto=17;
		}
		identify_continue=app_glimpse_detector(sess, context, glimpse_detector_env, payload, payload_len, l4_proto);	
		context->is_first_payload=0;
	}
	else
	{
		identify_continue=1;
	}
	
	if (identify_continue==0)
	{
		if(context->is_identify==0)
		{
			//MESA_handle_runtime_log(glimpse_detector_env->logger, RLOG_LV_INFO, "UNKNOWN", "addr: %s", session_get0_readable_addr(sess));
		}
		FREE(context);
		session_exdata_set(sess, glimpse_detector_env->plugin_exdata_idx, NULL);
		stellar_session_plugin_dettach_current_session(sess);
    }
	return;
}


extern "C" void APP_GLIMPSE_DETECTOR_UNLOAD(void *plugin_env)
{
	if(plugin_env)
	{
		struct glimpse_detector_schema *glimpse_detector_env=(struct glimpse_detector_schema *)plugin_env;
		lpi_free_library();
		free_l7_protocol_mapper(glimpse_detector_env->name_by_id);
		//if(glimpse_detector_env->logger)
		//{
		//	MESA_destroy_runtime_log_handle(glimpse_detector_env->logger);
		//}
		FREE(plugin_env);
	}
}

extern "C"  void * APP_GLIMPSE_DETECTOR_LOAD(struct stellar *st)
{
	int ret=0;
	struct glimpse_detector_schema *glimpse_detector_env=CALLOC(struct glimpse_detector_schema, 1);
	glimpse_detector_env->st=st;
	//MESA_load_profile_short_def(g_app_proto_conffile, "PROTO_IDENTIFY", "LOG_LEVEL", (short *)&glimpse_detector_env->level, 30);	
	glimpse_detector_env->max_pkts=16;
	strcpy(glimpse_detector_env->l7_bridge_name,"APP_IDENTIFY_RESULT_BRIDGE");
	strcpy( glimpse_detector_env->l7_protocol_file, l7_protocol_file);
	//MESA_load_profile_int_def(g_app_proto_conffile, "PROTO_IDENTIFY", "MAX_IDENTIFY_PACKETS", &glimpse_detector_env->max_pkts, 16);
	//MESA_load_profile_string_def(g_app_proto_conffile, "PROTO_IDENTIFY", "LOG_PATH", glimpse_detector_env->log_path, sizeof(glimpse_detector_env->log_path), log_path);
	//MESA_load_profile_string_def(g_app_proto_conffile, "SYSTEM", "APP_IDENTIFY_RESULT_BRIDGE", glimpse_detector_env->l7_bridge_name, sizeof(glimpse_detector_env->l7_bridge_name), "APP_IDENTIFY_RESULT_BRIDGE");
	//MESA_load_profile_string_def(g_app_proto_conffile, "SYSTEM", "L7_PROTOCOL_FILE", glimpse_detector_env->l7_protocol_file, sizeof(glimpse_detector_env->l7_protocol_file), l7_protocol_file);

	//glimpse_detector_env->logger=MESA_create_runtime_log_handle(glimpse_detector_env->log_path, glimpse_detector_env->level);
	//if(glimpse_detector_env->logger==NULL)
	//{
	//	goto INIT_ERROR;
	//}
	ret=lpi_init_library(1); // 1, 2 or 3
	if(ret<0)
	{
		goto INIT_ERROR;
	}

	ret=l7_protocol_mapper(glimpse_detector_env,glimpse_detector_env->l7_protocol_file);
	if(ret<0)
	{
		goto INIT_ERROR;
	}

	glimpse_detector_env->plugin_id=stellar_session_plugin_register(st, NULL, NULL, glimpse_detector_env);
	if(glimpse_detector_env->plugin_id < 0)
	{
		goto INIT_ERROR;
	}
	glimpse_detector_env->tcp_topic_id=stellar_mq_get_topic_id(st, TOPIC_TCP);
	glimpse_detector_env->udp_topic_id=stellar_mq_get_topic_id(st, TOPIC_UDP);
	if(glimpse_detector_env->tcp_topic_id < 0 || glimpse_detector_env->udp_topic_id < 0)
	{
		perror("get tcp or udp topic id failed\n");
		exit(-1);
	}

	stellar_session_mq_subscribe(st, glimpse_detector_env->tcp_topic_id, glimpse_detector_on_session_msg, glimpse_detector_env->plugin_id);
	stellar_session_mq_subscribe(st, glimpse_detector_env->udp_topic_id, glimpse_detector_on_session_msg, glimpse_detector_env->plugin_id);

	glimpse_detector_env->plugin_exdata_idx = stellar_exdata_new_index(st, "EXDATA_LPI", stellar_exdata_free_default, NULL);	
	glimpse_detector_env->l7_app_id_exdata_idx = stellar_exdata_new_index(st, "L7_PROTOCOL", stellar_exdata_free_default, NULL);	
	glimpse_detector_env->topic_id=stellar_mq_get_topic_id(st, APP_ID_MESSAGE_TOPIC);
	if(glimpse_detector_env->topic_id < 0)
	{
		glimpse_detector_env->topic_id=stellar_mq_create_topic(st, APP_ID_MESSAGE_TOPIC, stellar_msg_free_default, NULL);
	}
	return glimpse_detector_env;

INIT_ERROR:
	APP_GLIMPSE_DETECTOR_UNLOAD(glimpse_detector_env);
	exit(-1);
	return NULL;

}

