/*************************************************************************
	> File Name: detect_http2_plugin.cpp
	> Author:
	> Mail:
	> Created Time: 2018年09月11日 星期二 15时38分54秒
 ************************************************************************/

#include <stdio.h>
#include <stdint.h>
#include <string.h>

#include <assert.h>
#include <event.h>

#include <tfe_stream.h>
#include <tfe_utils.h>
#include <tfe_plugin.h>
#include <tfe_proxy.h>
#include <malloc.h>

#include <http2_stream.h>
#include <http2_common.h>

#include <MESA/MESA_prof_load.h>

/* Magic Header : PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n */
static const uint8_t kMagicHello[] = {
        0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54,
        0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a,
        0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a
};

#define get_u_int8_t(X,O)  (*(u_int8_t *)(((u_int8_t *)X) + O))

#define TFE_BASIC_CFG "conf/tfe/tfe.conf"

#define MAGIC_FRAME_LENGTH      24
#define SET_FRAME_LENGTH	    9

struct event_timer_ctx
{
	Http2Plugin *plugin;
	unsigned int thread_id;
};

void load_logging_conf(const char *config)
{
	RTLogInit2Data *logging_sc_lid = logger();
    logging_sc_lid->handle = MESA_create_runtime_log_handle("http2", RLOG_LV_DEBUG);
    if(logging_sc_lid->handle == NULL){
		TFE_LOG_ERROR(logging_sc_lid->handle, "Create log runtime_log_handle error, init failed!");
    }
	return;
}

UNUSED static void http2_plugin_timer_gc_cb(evutil_socket_t fd, short what, void * arg)
{
	struct tfe_h2_stream *h2_stream_info = (struct tfe_h2_stream *)arg;
	sess_data_ctx_fini(h2_stream_info, NULL, h2_stream_info->thread_id);

	// Although this function is not called now,
	// there is a potential bug in memory leaks where it is called.
	// A better practice is to free the memory in the sess_data_ctx_fini() function.
	free(h2_stream_info);
	h2_stream_info = NULL;
}

static int
http2_plugin_init(struct tfe_proxy * proxy)
{
	UNUSED Http2Plugin *plugin = http2_plugin();

	unsigned int thread_nu = tfe_proxy_get_work_thread_count();

	load_logging_conf(TFE_BASIC_CFG);

	for (unsigned int thread_id = 0; thread_id < thread_nu; thread_id++){
#if 0
		struct tfe_session_info_t h2_stream_info = plugin->h2_stream_info[thread_id];
		TAILQ_INIT(&(h2_stream_info.list));
		h2_stream_info.thread_id = thread_id;
		h2_stream_info.as_client = 0;

		struct event_base * ev_base = tfe_proxy_get_work_thread_evbase(thread_id);

		struct timeval timer = {0, 500 * 1000};
		struct event * event = event_new(ev_base, -1, EV_PERSIST, http2_plugin_timer_gc_cb, &h2_stream_info);

		if (unlikely(event == NULL)){
			TFE_LOG_ERROR(logger()->handle, "Create timer error, init failed!");
		}

		evtimer_add(event, &timer);
		plugin->event[thread_id] = event;
#endif
	}

    return 0;
}

static void
http2_plugin_deinit(struct tfe_proxy * proxy)
{
    return;
}

static int
http2_stream_open(const struct tfe_stream *stream, unsigned int thread_id,
                  enum tfe_conn_dir dir, void ** pme)
{
	struct stream_tap_info_t *tapinfo = ALLOC(struct stream_tap_info_t, 1);
	assert(tapinfo);

	memset(tapinfo, 0, sizeof(struct stream_tap_info_t));
	tapinfo->preempted = 0;
	tapinfo->h2_stream_info = tfe_session_info_init();

	*pme = (void *)tapinfo;

	return 0;
}

/*setting frame: 00 00 06 04 00 00 00 00 00 00 04**/
static int
search_up_stream_data(const unsigned char * data, size_t len)
{
	uint8_t indetifier = 0;

	if ((get_u_int8_t(data, 3) != 0x4) && (get_u_int8_t(data, 4) != 0) && (get_u_int8_t(data, 9) != 0))
	{
		return 0;
	}
	indetifier = get_u_int8_t(data, 10);
	if (indetifier < NGHTTP2_SETTINGS_HEADER_TABLE_SIZE || indetifier > NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE)
	{
		return 0;
	}

	return 1;
}


static enum tfe_stream_action
http2_stream_data(const struct tfe_stream * stream, unsigned int thread_id,
	              enum tfe_conn_dir dir, const unsigned char * data, size_t len, void ** pme)
{
	static unsigned int defer_bytes = 0;
	struct stream_tap_info_t *tapinfo = (struct stream_tap_info_t *)(*pme);

	if (tapinfo->preempted == 0){
		if (dir == CONN_DIR_UPSTREAM){
			if (len < SET_FRAME_LENGTH){
				defer_bytes = SET_FRAME_LENGTH;
				tfe_stream_action_set_opt(stream, ACTION_OPT_DEFER_BYTES, (void *) &defer_bytes, sizeof(defer_bytes));
				return ACTION_DEFER_DATA;
			}
			if (search_up_stream_data(data, len)){
			}else{
				goto detach;
			}
		}
		if (dir == CONN_DIR_DOWNSTREAM){
			/* Protocol Identification, we need 24 bytes at least to tell it is HTTP2 or not */
			if (len < MAGIC_FRAME_LENGTH){
				defer_bytes = MAGIC_FRAME_LENGTH;
				tfe_stream_action_set_opt(stream, ACTION_OPT_DEFER_BYTES, (void *) &defer_bytes, sizeof(defer_bytes));
				return ACTION_DEFER_DATA;
			}
			if (memcmp(data, kMagicHello, MAGIC_FRAME_LENGTH) != 0){
				goto finish;
			}
		}
		if (tfe_stream_preempt(stream) != 0)
			goto detach;

		tapinfo->preempted = 1;
	}

	return (dir == CONN_DIR_DOWNSTREAM) ? detect_down_stream_protocol(tapinfo->h2_stream_info, stream, thread_id,
			data, len) : detect_up_stream_protocol(tapinfo->h2_stream_info, stream, thread_id, data, len);

detach:
	tfe_stream_detach(stream);
finish:
	return ACTION_FORWARD_DATA;
}

static void
http2_stream_close(const struct tfe_stream * stream, unsigned int thread_id,
        	             enum tfe_stream_close_reason reason, void ** pme)
{
	struct stream_tap_info_t *tapinfo = (struct stream_tap_info_t *)(*pme);

	sess_data_ctx_fini(tapinfo->h2_stream_info, stream, thread_id);

	free(tapinfo->h2_stream_info);
	tapinfo->h2_stream_info = NULL;

	free(tapinfo);
	tapinfo = NULL;

	*pme = NULL;

	return;
}

static struct tfe_plugin __nghttp2_plugin_info =
{
	.symbol    = "HTTP2",
	.type      = TFE_PLUGIN_TYPE_PROTOCOL,
	.on_init   = http2_plugin_init,
	.on_deinit  = http2_plugin_deinit,
	.on_open   = http2_stream_open,
	.on_data   = http2_stream_data,
	.on_close  = http2_stream_close
};

TFE_PLUGIN_REGISTER(HTTP2, __nghttp2_plugin_info)

