/* vim: set et ts=4 sts=4 sw=4 : */
/********************************************************************\
 * This program is free software; you can redistribute it and/or    *
 * modify it under the terms of the GNU General Public License as   *
 * published by the Free Software Foundation; either version 2 of   *
 * the License, or (at your option) any later version.              *
 *                                                                  *
 * This program is distributed in the hope that it will be useful,  *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
 * GNU General Public License for more details.                     *
 *                                                                  *
 * You should have received a copy of the GNU General Public License*
 * along with this program; if not, contact:                        *
 *                                                                  *
 * Free Software Foundation           Voice:  +1-617-542-5942       *
 * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
 * Boston, MA  02111-1307,  USA       gnu@gnu.org                   *
 *                                                                  *
\********************************************************************/

/** @file control.c
    @brief xfrp control protocol implemented
    @author Copyright (C) 2016 Dengfeng Liu <liu_df@qq.com>
*/

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <json-c/json.h>
#include <syslog.h>
#include <unistd.h>
#include <time.h>

#include "debug.h"
#include "client.h"
#include "uthash.h"
#include "config.h"
#include "msg.h"
#include "control.h"
#include "uthash.h"
#include "crypto.h"
#include "utils.h"
#include "common.h"
#include "login.h"
#include "tcpmux.h"
#include "proxy.h"

static struct control *main_ctl;
static int client_connected = 0;
static int is_login = 0;
static time_t pong_time = 0;

static void new_work_connection(struct bufferevent *bev, struct tmux_stream *stream);
static void recv_cb(struct bufferevent *bev, void *ctx);
static void clear_main_control();
static void start_base_connect();
static void keep_control_alive();

static int 
is_client_connected()
{
	return client_connected;
}

static int 
set_client_status(int is_connected)
{
	if (is_connected)
		client_connected = 1;
	else
		client_connected = 0;

	return client_connected;
}

static int 
set_client_work_start(struct proxy_client *client, int is_start_work)
{
	assert(client->ps);
	if (is_start_work) {
		client->work_started = 1;
	}else
		client->work_started = 0;

	return client->work_started;
}

static void 
client_start_event_cb(struct bufferevent *bev, short what, void *ctx)
{
	struct proxy_client *client = ctx;
	assert(client);
	struct common_conf 	*c_conf = get_common_config();

	if (what & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) {
		if (client->ctl_bev != bev) {
			debug(LOG_ERR, "Error: should be equal");
			bufferevent_free(client->ctl_bev);
			client->ctl_bev = NULL;
		}
		debug(LOG_ERR, "Proxy connect server [%s:%d] error: %s", c_conf->server_addr, c_conf->server_port, strerror(errno));
		bufferevent_free(bev);
		del_proxy_client_by_stream_id(client->stream_id);
	} else if (what & BEV_EVENT_CONNECTED) {
		bufferevent_setcb(bev, recv_cb, NULL, client_start_event_cb, client);
		bufferevent_enable(bev, EV_READ|EV_WRITE);
		new_work_connection(bev, &main_ctl->stream);
		set_client_status(1);
		debug(LOG_INFO, "proxy service start");
	}
}

static void 
new_client_connect()
{
	struct proxy_client *client = new_proxy_client();
	struct common_conf *c_conf = get_common_config();
	assert(c_conf);
	client->base = main_ctl->connect_base;
	
	if (c_conf->tcp_mux) {
		debug(LOG_DEBUG, "new client through tcp mux: %d", client->stream_id);
		client->ctl_bev 	= main_ctl->connect_bev;
		send_window_update(client->ctl_bev, &client->stream, 0);
		new_work_connection(client->ctl_bev, &client->stream);
		return;
	}

	struct bufferevent *bev = connect_server(client->base, c_conf->server_addr, c_conf->server_port);
	if (!bev) {
		debug(LOG_DEBUG, "Connect server [%s:%d] failed", c_conf->server_addr, c_conf->server_port);
		return;
	}

	debug(LOG_INFO, "work connection: connect server [%s:%d] ......", c_conf->server_addr, c_conf->server_port);

	client->ctl_bev = bev;
	bufferevent_enable(bev, EV_WRITE);
	bufferevent_setcb(bev, NULL, NULL, client_start_event_cb, client);
}

static void 
start_proxy_services()
{
	struct proxy_service *all_ps = get_all_proxy_services();
	struct proxy_service *ps = NULL, *tmp = NULL;
	
	if (!all_ps) {
		debug(LOG_INFO, "no proxy service configure by user");
		return;
	}

	debug(LOG_INFO, "Start xfrp proxy services ...");
	
	HASH_ITER(hh, all_ps, ps, tmp) {
		if(ps == NULL) {
			debug(LOG_ERR, "proxy service is invalid!");
			return;
		}
		if (strcmp(ps->proxy_type, "mstsc") == 0) {
			debug(LOG_ERR, "no need to send mstsc service!");
			continue;
		}
		send_new_proxy(ps);
	}
}

static void 
ping()
{
	struct bufferevent *bout = main_ctl->connect_bev;

	if ( ! bout) {
		debug(LOG_ERR, "bufferevent is not legal!");
		return;
	}
	
	char *ping_msg = "{}";
	send_enc_msg_frp_server(bout, TypePing, ping_msg, strlen(ping_msg), &main_ctl->stream);
}

static void 
new_work_connection(struct bufferevent *bev, struct tmux_stream *stream)
{
	assert(bev);
	
	/* send new work session regist request to frps*/
	struct work_conn *work_c = new_work_conn();
	work_c->run_id = get_run_id();
	if (! work_c->run_id) {
		debug(LOG_ERR, "cannot found run ID, it should inited when login!");
		SAFE_FREE(work_c);
		return;
	}
	char *new_work_conn_request_message = NULL;
	int nret = new_work_conn_marshal(work_c, &new_work_conn_request_message);
	if (0 == nret) {
		debug(LOG_ERR, "new work connection request run_id marshal failed!");
		return;
	}

	send_msg_frp_server(bev, TypeNewWorkConn, new_work_conn_request_message, nret, stream);

	SAFE_FREE(new_work_conn_request_message);
	SAFE_FREE(work_c);
}

struct bufferevent *
connect_server(struct event_base *base, const char *name, const int port)
{
	struct bufferevent *bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
	assert(bev);

	if (bufferevent_socket_connect_hostname(bev, main_ctl->dnsbase, 
											AF_INET, name, port) < 0 ) {
		bufferevent_free(bev);
		return NULL;
	}
	return bev;
}

struct bufferevent *
connect_udp_server(struct event_base *base)
{
	evutil_socket_t fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (fd < 0) {
		debug(LOG_ERR, "create udp socket failed!");
		return NULL;
	}

	if (evutil_make_socket_nonblocking(fd) < 0) {
		debug(LOG_ERR, "make udp socket nonblocking failed!");
		evutil_closesocket(fd);
		return NULL;
	}

	
	struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
	assert(bev);
	if (!bev) {
		evutil_closesocket(fd);
		debug(LOG_ERR, "create udp bufferevent failed!");
		return NULL;
	}

	return bev;
}

static void 
set_ticker_ping_timer(struct event *timeout)
{
	struct timeval tv;
	struct common_conf *c_conf = get_common_config();
	evutil_timerclear(&tv);
	tv.tv_sec = c_conf->heartbeat_interval;
	event_add(timeout, &tv);
}

static void 
hb_sender_cb(evutil_socket_t fd, short event, void *arg)
{
	if (is_client_connected()) {
		debug(LOG_INFO, "ping frps");
		ping(NULL);
	}

	set_ticker_ping_timer(main_ctl->ticker_ping);	
	
	struct common_conf 	*c_conf = get_common_config();
	time_t current_time = time(NULL);
	int interval = current_time - pong_time;
	if (pong_time && interval > c_conf->heartbeat_timeout) {
		debug(LOG_INFO, " interval [%d] greater than heartbeat_timeout [%d]", interval, c_conf->heartbeat_timeout);

		reset_session_id();
		clear_main_control();
		run_control();
		return;
	}
}

// return: 0: raw succeed 1: raw failed
static int 
proxy_service_resp_raw(struct new_proxy_response *npr)
{
	if (npr->error && strlen(npr->error) > 2) {
		debug(LOG_ERR, "error: new proxy response error_field:%s", npr->error);
		return 1;
	}
	
	if ((! npr->proxy_name) || (strlen(npr->proxy_name) <= 0)) {
		debug(LOG_ERR, "error: new proxy response proxy name unmarshal failed!");
		return 1;
	}

	struct proxy_service *ps = NULL;
	ps = get_proxy_service(npr->proxy_name);
	if (! ps) {
		debug(LOG_ERR, "error: proxy_name responsed by TypeNewProxyResp not found!");
		return 1;
	}

	if (! ps->proxy_type) {
		debug(LOG_ERR, "error: proxy_type is NULL, it should be never happend!");
		return 1;
	}

	if (ps->ftp_cfg_proxy_name) {
		struct proxy_service *main_ps = get_proxy_service(ps->ftp_cfg_proxy_name);
		if (main_ps) {
			debug(LOG_DEBUG, "find main ftp proxy service name [%s]", main_ps->proxy_name);
		} else {
			debug(LOG_ERR, "error: cannot find main ftp proxy service!");
			return 1;
		}

		if (npr->remote_port <= 0) {
			debug(LOG_ERR, "error: ftp remote_data_port [%d] that request from server is invalid!", npr->remote_port);
			return 1;
		}
		main_ps->remote_data_port = npr->remote_port;
	}

	return 0;
}

static int
handle_enc_msg(const uint8_t *enc_msg, int ilen, uint8_t **out)
{
	if (ilen <= 0) {
		debug(LOG_INFO, "enc_msg length should not be %d", ilen);
		return -1;
	}

	const uint8_t *buf = enc_msg;
	if ( !is_decoder_inited() && get_block_size() <= ilen) {
		init_main_decoder(buf);
		buf += get_block_size();
		ilen -= get_block_size();
		if (!ilen) {
			// recv only iv
			debug(LOG_DEBUG, "recv eas1238 iv data");
			return 0;
		}	
	}

	uint8_t *dec_msg = NULL;
	size_t len = decrypt_data(buf, ilen, get_main_decoder(), &dec_msg);
	*out = dec_msg;

	return len;	
}

static void
handle_control_work(const uint8_t *buf, int len, void *ctx)
{
	uint8_t *frps_cmd = NULL;
	uint8_t cmd_type;
	const uint8_t *enc_msg = buf;

	if (!ctx) {	
		//debug(LOG_DEBUG, "main control message");
		handle_enc_msg(enc_msg, len, &frps_cmd);
	} else {
		//debug(LOG_DEBUG, "worker message");
		frps_cmd = (uint8_t *)buf;
	}

	if (!frps_cmd)	
		return; // only recv iv

	struct msg_hdr *msg = (struct msg_hdr *)frps_cmd;

	cmd_type = msg->type;
	switch(cmd_type) {
	case TypeReqWorkConn: 
	{
		if (! is_client_connected()) {
			start_proxy_services();
			set_client_status(1);
		}
		new_client_connect();
		break;
	}
	case TypeNewProxyResp:
	{
		struct new_proxy_response *npr = new_proxy_resp_unmarshal((const char *)msg->data);
		if (npr == NULL) {
			debug(LOG_ERR, "new proxy response buffer unmarshal faild!");
			return;
		}

		proxy_service_resp_raw(npr);
		SAFE_FREE(npr);
		break;
	}
	case TypeStartWorkConn:
	{
		struct start_work_conn_resp *sr = start_work_conn_resp_unmarshal((const char *)msg->data); 
		if (! sr) {
			debug(LOG_ERR, 
				"TypeStartWorkConn unmarshal failed, it should never be happend!");
			break;
		}

		struct proxy_service *ps = get_proxy_service(sr->proxy_name);
		if (! ps) {
			debug(LOG_ERR, 
				"TypeStartWorkConn requested proxy service [%s] not found, it should nerver be happend!", 
				sr->proxy_name);
			break;
		}

		assert(ctx);
		struct proxy_client *client = ctx;
		client->ps = ps;
		int r_len = len - sizeof(struct msg_hdr) - msg_hton(msg->length); 
		debug(LOG_DEBUG, 
			"proxy service [%s] [%s:%d] start work connection. remain data length %d", 
			sr->proxy_name, 
			ps->local_ip, 
			ps->local_port,
			r_len);
		if (r_len > 0) {
			client->data_tail_size = r_len;
			client->data_tail = msg->data + msg_hton(msg->length);
			debug(LOG_DEBUG, "data_tail is %s", client->data_tail); 
		}
		start_xfrp_tunnel(client);
		set_client_work_start(client, 1);

		break;
	}
	case TypeUDPPacket:
	{
		struct udp_packet *udp = udp_packet_unmarshal((const char *)msg->data);
		if (!udp) {
			debug(LOG_ERR, "TypeUDPPacket unmarshal failed!");
			break;
		}
		debug(LOG_DEBUG, "recv udp packet from server, content is %s", 
			udp->content);
		assert(ctx);
		struct proxy_client *client = ctx;
		assert(client->ps);
		handle_udp_packet(udp, client);
		SAFE_FREE(udp);
		break;
	}
	case TypePong:
		pong_time = time(NULL);
		break;
	default:
		debug(LOG_INFO, "command type dont support: ctx is %d", ctx?1:0);
	}
	
	if (!ctx)
		free(frps_cmd);
}

static int
handle_login_response(const uint8_t *buf, int len)
{
	struct msg_hdr *mhdr = (struct msg_hdr *)buf;
	if (mhdr->type != TypeLoginResp) {
		debug(LOG_ERR, "type incorrect: it should be login response, but %d", mhdr->type);
		return 0;
	}	
	
	struct login_resp *lres = login_resp_unmarshal((const char *)mhdr->data); 
	if (!lres) {
		return 0;
	}

	if (!login_resp_check(lres)) {
		debug(LOG_ERR, "login failed");	
		free(lres);
		return 0;
	}
	free(lres);
	
	is_login = 1;

	int login_len = msg_hton(mhdr->length);
	int ilen = len - login_len - sizeof(struct msg_hdr);
	debug(LOG_ERR, "login success! login_len %d len %d ilen %d", login_len, len, ilen);
	assert(ilen >= 0);
	if (ilen <= 0)
		return 1;
	
	// in case, libevent reveive continue packet together
	struct common_conf 	*c_conf = get_common_config();
	assert(c_conf->tcp_mux == 0);
	uint8_t *enc_msg = mhdr->data + login_len; 
	uint8_t *frps_cmd = NULL;
	int nret = handle_enc_msg(enc_msg, ilen, &frps_cmd);
	assert(nret > 0);
	// start proxy services must first send
	start_proxy_services();
	set_client_status(1);
	debug(LOG_DEBUG, "TypeReqWorkConn cmd, msg :%s", &frps_cmd[8]);
	assert (frps_cmd[0] == TypeReqWorkConn);
	new_client_connect();

	return 1;
}

static void
handle_frps_msg(uint8_t *buf, int len, void *ctx)
{
	if (!is_login) {
		// login response
		handle_login_response(buf, len);
	}else {
		handle_control_work(buf, len, ctx);
	}	
}

static struct tmux_stream abandon_stream;

// ctx: if recv_cb was called by common control, ctx == NULL
//		else ctx == client struct
static void 
recv_cb(struct bufferevent *bev, void *ctx)
{
	struct evbuffer *input = bufferevent_get_input(bev);
	int len = evbuffer_get_length(input);
	if (len <= 0) {
			return;
	}

	struct common_conf 	*c_conf = get_common_config();
	if (c_conf->tcp_mux) {
		static struct tcp_mux_header tmux_hdr;
		static uint32_t stream_len = 0;
		while (len > 0) {
				struct tmux_stream *cur = get_cur_stream();
				size_t nr = 0;
				if (!cur) {
					memset(&tmux_hdr, 0, sizeof(tmux_hdr));
					uint8_t *data = (uint8_t *)&tmux_hdr;
					if (len < sizeof(tmux_hdr)) {
						debug(LOG_INFO, "len [%d] < sizeof tmux_hdr", len);
						break;
					} 
					nr = bufferevent_read(bev, data, sizeof(tmux_hdr));
					assert(nr == sizeof(tmux_hdr));
					assert(validate_tcp_mux_protocol(&tmux_hdr) > 0);
					len -= nr;
					if (tmux_hdr.type == DATA) {
						uint32_t stream_id = ntohl(tmux_hdr.stream_id);
						stream_len = ntohl(tmux_hdr.length);
						cur = get_stream_by_id(stream_id);
						if (!cur) {
							debug(LOG_INFO, "cur is NULL stream_id is %d, stream_len is %d len is %d", 
										stream_id, stream_len, len);
							if (stream_len > 0)
								cur = &abandon_stream;
							else
								continue;
						}

						if (len == 0) {
							set_cur_stream(cur);
							break;
						}
						if (len >= stream_len) {
							nr = tmux_stream_read(bev, cur, stream_len);
							assert(nr == stream_len);
							len -= stream_len;
						} else {
							nr = tmux_stream_read(bev, cur, len);
							stream_len -= len;
							assert(nr == len);
							set_cur_stream(cur);
							len -= nr;
							break;	
						} 
					}
				} else {
					assert(tmux_hdr.type == DATA);
					if (len >= stream_len ) {
						nr = tmux_stream_read(bev, cur, stream_len);
						assert(nr == stream_len);
						len -= stream_len;
					} else {
						nr = tmux_stream_read(bev, cur, len);
						stream_len -= len;
						assert(nr == len);
						len -= nr;
						break;
					}	
				}
				
				if (cur == &abandon_stream) {
					debug(LOG_INFO, "abandon stream data ...");
					memset(cur , 0, sizeof(abandon_stream));
					set_cur_stream(NULL);
					continue;
				}

				switch(tmux_hdr.type) {
				case DATA:
				case WINDOW_UPDATE:
				{
					handle_tcp_mux_stream(&tmux_hdr, handle_frps_msg);
					break;
				}
				case PING:
					handle_tcp_mux_ping(&tmux_hdr);
					break;
				case GO_AWAY:
					handle_tcp_mux_go_away(&tmux_hdr);
					break;
				default:
					debug(LOG_ERR, "impossible here!!!!");
					exit(-1);
				}

				set_cur_stream(NULL);
		}
	} else {	
		uint8_t *buf = calloc(len, 1);
		assert(buf);
		evbuffer_remove(input, buf, len);

		handle_frps_msg(buf, len, ctx);
		SAFE_FREE(buf);
	}
		

	return;
}

static void 
connect_event_cb (struct bufferevent *bev, short what, void *ctx)
{
	struct common_conf 	*c_conf = get_common_config();
	static int retry_times = 1;
	if (what & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) {
		if (retry_times >= 100) {
			debug(LOG_INFO, 
				"have retry connect to xfrp server for %d times, exit?", 
				retry_times);
		}
		sleep(2);
		retry_times++;
		debug(LOG_ERR, "error: connect server [%s:%d] failed %s", 
				c_conf->server_addr, 
				c_conf->server_port,
				strerror(errno));
		reset_session_id();
		clear_main_control();
		run_control();
	} else if (what & BEV_EVENT_CONNECTED) {
		debug(LOG_DEBUG, "xfrp server connected");
		retry_times = 0;
		send_window_update(bev, &main_ctl->stream, 0);
		login();
		
		keep_control_alive();
	}
}

static void 
keep_control_alive() 
{
	debug(LOG_DEBUG, "start keep_control_alive");
	main_ctl->ticker_ping = evtimer_new(main_ctl->connect_base, hb_sender_cb, NULL);
	if ( !main_ctl->ticker_ping) {
		debug(LOG_ERR, "Ping Ticker init failed!");
		return;
	}
	pong_time = time(NULL);
	set_ticker_ping_timer(main_ctl->ticker_ping);
}

static void 
start_base_connect()
{
	struct common_conf *c_conf = get_common_config();
	if (main_ctl->connect_bev)
		bufferevent_free(main_ctl->connect_bev);

	main_ctl->connect_bev = connect_server(main_ctl->connect_base, 
						c_conf->server_addr, 
						c_conf->server_port);
	if ( ! main_ctl->connect_bev) {
		debug(LOG_ERR, "error: connect server [%s:%d] failed: [%d: %s]", 
						c_conf->server_addr, c_conf->server_port, errno, strerror(errno));
		exit(0);
	}

	debug(LOG_INFO, "connect server [%s:%d]...", c_conf->server_addr, c_conf->server_port);
	bufferevent_enable(main_ctl->connect_bev, EV_WRITE|EV_READ);
	bufferevent_setcb(main_ctl->connect_bev, recv_cb, NULL, connect_event_cb, NULL);
}

void 
login()
{
	char *lg_msg = NULL;
	int len = login_request_marshal(&lg_msg); //marshal login request
	if ( !lg_msg ) {
		debug(LOG_ERR, 
			"error: login_request_marshal failed, it should never be happenned");
		exit(0);
	}
	
	send_msg_frp_server(NULL, TypeLogin, lg_msg, len, &main_ctl->stream);
	SAFE_FREE(lg_msg);
}

void 
send_msg_frp_server(struct bufferevent *bev, 
			 const enum msg_type type, 
			 const char *msg, 
			 const size_t msg_len, 
			 struct tmux_stream *stream)
{
	struct bufferevent *bout = NULL;
	if (bev) {
		bout = bev;
	} else {
		bout = main_ctl->connect_bev;
	}
	assert(bout);

	

	debug(LOG_DEBUG, "send plain msg ----> [%c: %s]", type, msg);
	
	size_t len = msg_len + sizeof(struct msg_hdr);
	struct msg_hdr *req_msg = calloc(len, 1);
	assert(req_msg);
	req_msg->type = type;
	req_msg->length = msg_hton((uint64_t)msg_len);
	memcpy(req_msg->data, msg, msg_len);
	
	struct common_conf *c_conf = get_common_config();
	if (c_conf->tcp_mux)
		tmux_stream_write(bout, (uint8_t *)req_msg, len, stream);
	else
		bufferevent_write(bout, (uint8_t *)req_msg, len);
	
	free(req_msg);
}

void 
send_enc_msg_frp_server(struct bufferevent *bev,
			 const enum msg_type type, 
			 const char *msg, 
			 const size_t msg_len, 
			 struct tmux_stream *stream)
{
	struct bufferevent *bout = NULL;
	if (bev) {
		bout = bev;
	} else {
		bout = main_ctl->connect_bev;
	}
	assert(bout);

	struct msg_hdr *req_msg = calloc(msg_len+sizeof(struct msg_hdr), 1);
	assert(req_msg);
	req_msg->type = type;
	req_msg->length = msg_hton((uint64_t)msg_len);
	memcpy(req_msg->data, msg, msg_len);

	struct common_conf *c_conf = get_common_config();
	if (get_main_encoder() == NULL) {
		struct frp_coder *coder = init_main_encoder();
		if (c_conf->tcp_mux) 
			tmux_stream_write(bout, coder->iv, 16, stream);
		else
			bufferevent_write(bout, coder->iv, 16);
	}

	uint8_t *enc_msg = NULL;
	size_t olen = encrypt_data((uint8_t *)req_msg, msg_len+sizeof(struct msg_hdr), get_main_encoder(), &enc_msg);
	assert(olen > 0);
	if (c_conf->tcp_mux)
		tmux_stream_write(bout, enc_msg, olen, stream);
	else
		bufferevent_write(bout, enc_msg, olen);

	free(enc_msg);	
	free(req_msg);
}

struct control *
get_main_control() 
{
	return main_ctl;
}

void 
start_login_frp_server(struct event_base *base)
{
	struct common_conf *c_conf = get_common_config();
	struct bufferevent *bev = connect_server(base, c_conf->server_addr, c_conf->server_port);
	if (!bev) {
		debug(LOG_DEBUG, 
			"Connect server [%s:%d] failed", 
			c_conf->server_addr, 
			c_conf->server_port);
		return;
	}

	debug(LOG_INFO, "Xfrpc login: connect server [%s:%d] ...", c_conf->server_addr, c_conf->server_port);

	bufferevent_enable(bev, EV_WRITE|EV_READ);
	bufferevent_setcb(bev, NULL, NULL, connect_event_cb, NULL);
}

void 
send_new_proxy(struct proxy_service *ps)
{
	if (! ps) {
		debug(LOG_ERR, "proxy service is invalid!");
		return;
	}

	char *new_proxy_msg = NULL;
	int len = new_proxy_service_marshal(ps, &new_proxy_msg);
	if ( ! new_proxy_msg) {
		debug(LOG_ERR, "proxy service request marshal failed");
		return;
	}

	debug(LOG_DEBUG, "control proxy client: [Type %d : proxy_name %s : msg_len %d]", TypeNewProxy, ps->proxy_name, len);

	send_enc_msg_frp_server(NULL, TypeNewProxy, new_proxy_msg, len, &main_ctl->stream);
	SAFE_FREE(new_proxy_msg);
}

void 
init_main_control()
{
	if (main_ctl && main_ctl->connect_base) {
		event_base_loopbreak(main_ctl->connect_base);
		free(main_ctl);
	}

	main_ctl = calloc(sizeof(struct control), 1);
	assert(main_ctl);

	struct common_conf *c_conf = get_common_config();
	struct event_base *base = NULL;
	struct evdns_base *dnsbase = NULL; 
	base = event_base_new();
	if (! base) {
		debug(LOG_ERR, "error: event base init failed!");
		exit(0);
	}
	main_ctl->connect_base = base;
	
	if (c_conf->tcp_mux) {
		init_tmux_stream(&main_ctl->stream, get_next_session_id(), INIT);
	}

	// if server_addr is ip, done control init.
	if (is_valid_ip_address((const char *)c_conf->server_addr))
		return;

	dnsbase = evdns_base_new(base, 1);
	if (! dnsbase) {
		debug(LOG_ERR, "error: evdns base init failed!");
		exit(0);
	}
	main_ctl->dnsbase = dnsbase;

	evdns_base_set_option(dnsbase, "timeout", "1.0");

   	// thanks to the following article
    // http://www.wuqiong.info/archives/13/
    evdns_base_set_option(dnsbase, "randomize-case:", "0");		//TurnOff DNS-0x20 encoding
    evdns_base_nameserver_ip_add(dnsbase, "180.76.76.76");		//BaiduDNS
	evdns_base_nameserver_ip_add(dnsbase, "223.5.5.5");			//AliDNS
    evdns_base_nameserver_ip_add(dnsbase, "223.6.6.6");			//AliDNS
	evdns_base_nameserver_ip_add(dnsbase, "114.114.114.114");	//114DNS
}

static void 
free_main_control()
{
	SAFE_FREE(main_ctl);
	main_ctl = NULL;
}

static void
clear_main_control()
{
	assert(main_ctl);
	if (main_ctl->ticker_ping) evtimer_del(main_ctl->ticker_ping);
	if (main_ctl->tcp_mux_ping_event) evtimer_del(main_ctl->tcp_mux_ping_event);
	clear_all_proxy_client();
	free_evp_cipher_ctx();
	set_client_status(0);
	pong_time = 0;	
	is_login = 0;
	if (get_common_config()->tcp_mux)
		init_tmux_stream(&main_ctl->stream, get_next_session_id(), INIT);
}

void 
close_main_control()
{
	clear_main_control();

	event_base_dispatch(main_ctl->connect_base);
	evdns_base_free(main_ctl->dnsbase, 0);
	event_base_free(main_ctl->connect_base);

	free_main_control();
}

void 
run_control() 
{
	start_base_connect();
}


