/*
 *
 * Copyright (c) 2011-2016 The University of Waikato, Hamilton, New Zealand.
 * All rights reserved.
 *
 * This file is part of libprotoident.
 *
 * This code has been developed by the University of Waikato WAND
 * research group. For further information please see http://www.wand.net.nz/
 *
 * libprotoident is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * libprotoident 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 */

#define __STDC_FORMAT_MACROS
#define __STDC_LIMIT_MACROS

#include <stdio.h>
#include <assert.h>
//#include <libtrace.h>
#include <inttypes.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdlib.h>
#include <signal.h>

#include "libprotoident.h"
#include "proto_manager.h"

bool init_called = false;
LPIModuleMap TCP_protocols;
LPIModuleMap UDP_protocols;

lpi_module_t *lpi_icmp = NULL;
lpi_module_t *lpi_unsupported = NULL;
lpi_module_t *lpi_unknown_tcp = NULL;
lpi_module_t *lpi_unknown_udp = NULL;

static LPINameMap lpi_names;
static LPIProtocolMap lpi_protocols;
static LPICategoryMap lpi_categories;
static LPICategoryProtocolMap lpi_category_protocols;

int lpi_init_library(int level) {

	if (init_called) {
		fprintf(stderr, "WARNING: lpi_init_library has already been called\n");
		return 0;
	}
	
	if (register_tcp_protocols(&TCP_protocols) == -1) 
		return -1;
	
	if (register_udp_protocols(&UDP_protocols) == -1) 
		return -1;

	init_other_protocols(&lpi_names, &lpi_protocols, &lpi_category_protocols);

	register_names(&TCP_protocols, &lpi_names, &lpi_protocols, &lpi_category_protocols);
	register_names(&UDP_protocols, &lpi_names, &lpi_protocols, &lpi_category_protocols);

	register_category_names(&lpi_categories);

	init_called = true;

	if (TCP_protocols.empty() && UDP_protocols.empty()) {
		fprintf(stderr, "WARNING: No protocol modules loaded\n");
		return -1;
	}


	return 0;

}

void lpi_free_library() {

	free_protocols(&TCP_protocols);
	free_protocols(&UDP_protocols);

   if (lpi_icmp != NULL) {
      delete lpi_icmp;
      lpi_icmp = NULL;
   }

   if (lpi_unsupported != NULL) {
      delete lpi_unsupported;
      lpi_unsupported = NULL;
   }

   if (lpi_unknown_tcp != NULL) {
      delete lpi_unknown_tcp;
      lpi_unknown_tcp = NULL;
   }

   if (lpi_unknown_udp != NULL) {
      delete lpi_unknown_udp;
      lpi_unknown_udp = NULL;
   }

	init_called = false;
}

void lpi_init_data(lpi_data_t *data) {

	data->payload[0] = 0;
	data->payload[1] = 0;
	data->seen_syn[0] = false;
	data->seen_syn[1] = false;
	data->seqno[0] = 0;
	data->seqno[1] = 0;
	data->observed[0] = 0;
	data->observed[1] = 0;
	data->server_port = 0;
	data->client_port = 0;
	data->trans_proto = 0;
	data->payload_len[0] = 0;
	data->payload_len[1] = 0;
	data->ips[0] = 0;
	data->ips[1] = 0;

}



typedef enum {
    TRACE_IPPROTO_IP    = 0,    /**< IP pseudo protocol number */
    TRACE_IPPROTO_ICMP  = 1,    /**< Internet Control Message protocol */
    TRACE_IPPROTO_IGMP  = 2,    /**< Internet Group Management Protocol */
    TRACE_IPPROTO_IPIP  = 4,    /**< IP encapsulated in IP */
    TRACE_IPPROTO_TCP   = 6,    /**< Transmission Control Protocol */
    TRACE_IPPROTO_UDP   = 17,   /**< User Datagram Protocol */
    TRACE_IPPROTO_IPV6  = 41,   /**< IPv6 over IPv4 */
    TRACE_IPPROTO_ROUTING   = 43,   /**< IPv6 Routing header */
    TRACE_IPPROTO_FRAGMENT  = 44,   /**< IPv6 Fragmentation header */
    TRACE_IPPROTO_RSVP  = 46,   /**< Resource Reservation Protocol */
    TRACE_IPPROTO_GRE   = 47,   /**< General Routing Encapsulation */
    TRACE_IPPROTO_ESP   = 50,   /**< Encapsulated Security Payload [RFC2406] */
    TRACE_IPPROTO_AH    = 51,   /**< Authentication Header [RFC2402] */
    TRACE_IPPROTO_ICMPV6    = 58,   /**< ICMPv6 */
    TRACE_IPPROTO_NONE  = 59,   /**< IPv6 no next header */
    TRACE_IPPROTO_DSTOPTS   = 60,   /**< IPv6 destination options */
    TRACE_IPPROTO_OSPF  = 89,   /**< Open Shortest Path First routing protocol */
    TRACE_IPPROTO_PIM   = 103,  /**< Protocol Independant Multicast */
    TRACE_IPPROTO_SCTP  = 132   /**< Stream Control Transmission Protocol */
} libtrace_ipproto_t;


static lpi_module_t *test_protocol_list(LPIModuleList *ml, lpi_data_t *data) {

	LPIModuleList::iterator l_it;
	
	/* Turns out naively looping through the modules is quicker
	 * than trying to do intelligent stuff with threads. Most
	 * callbacks complete very quickly so threading overhead is a
	 * major problem */
	for (l_it = ml->begin(); l_it != ml->end(); ++ l_it) {
		lpi_module_t *module = *l_it;

		/* To save time, I'm going to break on the first successful
		 * match. A threaded version would wait for all the modules
		 * to run, storing all successful results in a list of some
		 * sort and selecting an appropriate result from there.
		 */

		if (module->lpi_callback(data, module)) 
			return module;
		
	}

	return NULL;
}
static lpi_module_t *guess_protocol(LPIModuleMap *modmap, lpi_data_t *data) {

	lpi_module_t *proto = NULL;

	LPIModuleMap::iterator m_it;

	/* Deal with each priority in turn - want to match higher priority
	 * rules first. 
	 */

	for (m_it = modmap->begin(); m_it != modmap->end(); ++ m_it) {
		LPIModuleList *ml = m_it->second;
		
		proto = test_protocol_list(ml, data);

		if (proto != NULL)
			break;
	}

	return proto;

}

lpi_module_t *lpi_guess_protocol(lpi_data_t *data) {

	lpi_module_t *p = NULL;

	if (!init_called) {
		fprintf(stderr, "lpi_init_library was never called - cannot guess the protocol\n");
		return NULL;
	}

	switch(data->trans_proto) {
		case TRACE_IPPROTO_ICMP:
			return lpi_icmp;
		case TRACE_IPPROTO_TCP:
			p = guess_protocol(&TCP_protocols, data);
			if (p == NULL)
				p = lpi_unknown_tcp;
			return p;

		case TRACE_IPPROTO_UDP:
			p = guess_protocol(&UDP_protocols, data);
			if (p == NULL)
				p = lpi_unknown_udp;
			return p;
		default:
			return lpi_unsupported;
	}


	return p;
}
	
lpi_category_t lpi_categorise(lpi_module_t *module) {

	if (module == NULL)
		return LPI_CATEGORY_NO_CATEGORY;

	return module->category;

}

const char *lpi_print_category(lpi_category_t category) {

	switch(category) {
		case LPI_CATEGORY_WEB:
			return "Web";
		case LPI_CATEGORY_MAIL:
			return "Mail";
		case LPI_CATEGORY_CHAT:
			return "Chat";
		case LPI_CATEGORY_P2P:
			return "P2P";
		case LPI_CATEGORY_P2P_STRUCTURE:
			return "P2P_Structure";
		case LPI_CATEGORY_KEY_EXCHANGE:
			return "Key_Exchange";
		case LPI_CATEGORY_ECOMMERCE:
			return "ECommerce";
		case LPI_CATEGORY_GAMING:
			return "Gaming";
		case LPI_CATEGORY_ENCRYPT:
			return "Encryption";
		case LPI_CATEGORY_MONITORING:
			return "Measurement";
		case LPI_CATEGORY_NEWS:
			return "News";
		case LPI_CATEGORY_MALWARE:
			return "Malware";
		case LPI_CATEGORY_SECURITY:
			return "Security";
		case LPI_CATEGORY_ANTISPAM:
			return "Antispam";
		case LPI_CATEGORY_VOIP:
			return "VOIP";
		case LPI_CATEGORY_TUNNELLING:
			return "Tunnelling";
		case LPI_CATEGORY_NAT:
			return "NAT_Traversal";
		case LPI_CATEGORY_STREAMING:
			return "Streaming";
		case LPI_CATEGORY_SERVICES:
			return "Services";
		case LPI_CATEGORY_DATABASES:
			return "Databases";
		case LPI_CATEGORY_FILES:
			return "File_Transfer";
		case LPI_CATEGORY_REMOTE:
			return "Remote_Access";
		case LPI_CATEGORY_TELCO:
			return "Telco_Services";
		case LPI_CATEGORY_P2PTV:
			return "P2PTV";
		case LPI_CATEGORY_RCS:
			return "Revision_Control";
		case LPI_CATEGORY_LOGGING:
			return "Logging";
		case LPI_CATEGORY_PRINTING:
			return "Printing";
		case LPI_CATEGORY_TRANSLATION:
			return "Translation";
		case LPI_CATEGORY_CDN:
			return "CDN";
		case LPI_CATEGORY_CLOUD:
			return "Cloud";
		case LPI_CATEGORY_NOTIFICATION:
			return "Notification";
		case LPI_CATEGORY_SERIALISATION:
			return "Serialisation";
		case LPI_CATEGORY_BROADCAST:
			return "Broadcast";
		case LPI_CATEGORY_LOCATION:
			return "Location";
		case LPI_CATEGORY_CACHING:
			return "Caching";
		case LPI_CATEGORY_ICS:
			return "ICS";
		case LPI_CATEGORY_MOBILE_APP:
			return "Mobile App";
		case LPI_CATEGORY_IPCAMERAS:
			return "IP Cameras";
		case LPI_CATEGORY_EDUCATIONAL:
			return "Educational";
                case LPI_CATEGORY_MESSAGE_QUEUE:
                        return "Message_Queuing";
		case LPI_CATEGORY_ICMP:
			return "ICMP";
		case LPI_CATEGORY_MIXED:
			return "Mixed";
		case LPI_CATEGORY_NOPAYLOAD:
			return "No_Payload";
		case LPI_CATEGORY_UNKNOWN:
			return "Unknown";
		case LPI_CATEGORY_UNSUPPORTED:
			return "Unsupported";
		case LPI_CATEGORY_NO_CATEGORY:
			return "Uncategorised";
		case LPI_CATEGORY_LAST:
			return "Invalid_Category";
	}

	return "Invalid_Category";

}
			
const char *lpi_print(lpi_protocol_t proto) {

	LPINameMap::iterator it;

	it = lpi_names.find(proto);

	if (it == lpi_names.end()) {
		return "NULL";
	}	
	return (it->second);
	
}

lpi_protocol_t lpi_get_protocol_by_name(char *name) {

	LPIProtocolMap::iterator it;

	it = lpi_protocols.find(name);

	if (it == lpi_protocols.end()) {
		return LPI_PROTO_UNKNOWN;
	}

	return (it->second);
}

lpi_category_t lpi_get_category_by_name(char *name) {

	LPICategoryMap::iterator it;

	it = lpi_categories.find(name);

	if (it == lpi_categories.end()) {
		return LPI_CATEGORY_UNKNOWN;
	}

	return (it->second);
}

lpi_category_t lpi_get_category_by_protocol(lpi_protocol_t protocol) {

	LPICategoryProtocolMap::iterator it;

	it = lpi_category_protocols.find(protocol);

	if (it == lpi_category_protocols.end()) {
		return LPI_CATEGORY_UNKNOWN;
	}

	return (it->second);
}

bool lpi_is_protocol_inactive(lpi_protocol_t proto) {

	LPINameMap::iterator it;

	it = lpi_names.find(proto);

	if (it == lpi_names.end()) {
		return true;
	}	
	return false;

}

