/*
 * Copyright (C) 2011, 2012, 2013 Citrix Systems
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "ns_turn_allocation.h"

/////////////// Permission forward declarations /////////////////

static void init_turn_permission_hashtable(turn_permission_hashtable *map);
static void free_turn_permission_hashtable(turn_permission_hashtable *map);
static turn_permission_info* get_from_turn_permission_hashtable(turn_permission_hashtable *map, const ioa_addr *addr);

/////////////// ALLOCATION //////////////////////////////////////

void init_allocation(void *owner, allocation* a, ur_map *tcp_connections) {
  if(a) {
    ns_bzero(a,sizeof(allocation));
    a->owner = owner;
    a->tcp_connections = tcp_connections;
    init_turn_permission_hashtable(&(a->addr_to_perm));
  }
}

void clear_allocation(allocation *a)
{
	if (a) {

		if(a->is_valid)
			turn_report_allocation_delete(a);

		if(a->tcs.elems) {
			size_t i;
			size_t sz = a->tcs.sz;
			for(i=0;i<sz;++i) {
				tcp_connection *tc = a->tcs.elems[i];
				if(tc) {
					delete_tcp_connection(tc);
					a->tcs.elems[i] = NULL;
					break;
				}
			}
			turn_free(a->tcs.elems,sz*sizeof(tcp_connection*));
			a->tcs.elems = NULL;
		}
		a->tcs.sz = 0;

		clear_ioa_socket_session_if(a->relay_session.s, a->owner);
		clear_ts_ur_session_data(&(a->relay_session));

		IOA_EVENT_DEL(a->lifetime_ev);

		/* The order is important here: */
		free_turn_permission_hashtable(&(a->addr_to_perm));
		ch_map_clean(&(a->chns));

		a->is_valid=0;
	}
}

ts_ur_session *get_relay_session(allocation *a)
{
	return &(a->relay_session);
}

ioa_socket_handle get_relay_socket(allocation *a)
{
	return a->relay_session.s;
}

void set_allocation_lifetime_ev(allocation *a, turn_time_t exp_time, ioa_timer_handle ev)
{
	if (a) {
		IOA_EVENT_DEL(a->lifetime_ev);
		a->expiration_time = exp_time;
		a->lifetime_ev = ev;
	}
}

int is_allocation_valid(const allocation* a) {
  if(a) return a->is_valid;
  else return 0;
}

void set_allocation_valid(allocation* a, int value) {
  if(a) a->is_valid=value;
}

turn_permission_info* allocation_get_permission(allocation* a, const ioa_addr *addr) {
  if(a) {
    return get_from_turn_permission_hashtable(&(a->addr_to_perm), addr);
  }
  return NULL;
}

///////////////////////////// TURN_PERMISSION /////////////////////////////////

static int delete_channel_info_from_allocation_map(ur_map_key_type key, ur_map_value_type value);

void turn_permission_clean(turn_permission_info* tinfo)
{
	if (tinfo && tinfo->allocated) {

		if(!(tinfo->lifetime_ev)) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s: strange (1) permission to be cleaned\n",__FUNCTION__);
		}

		IOA_EVENT_DEL(tinfo->lifetime_ev);
		lm_map_foreach(&(tinfo->chns), (foreachcb_type) delete_channel_info_from_allocation_map);
		lm_map_clean(&(tinfo->chns));
		ns_bzero(tinfo,sizeof(turn_permission_info));
	}
}

static void init_turn_permission_hashtable(turn_permission_hashtable *map)
{
	if (map)
		ns_bzero(map,sizeof(turn_permission_hashtable));
}

static void free_turn_permission_hashtable(turn_permission_hashtable *map)
{
	if(map) {

		size_t i;
		for(i=0;i<TURN_PERMISSION_HASHTABLE_SIZE;++i) {

			turn_permission_array *parray = &(map->table[i]);

			{
				size_t j;
				for(j=0;j<TURN_PERMISSION_ARRAY_SIZE;++j) {
					turn_permission_slot *slot = &(parray->main_slots[j]);
					if(slot->info.allocated) {
						turn_permission_clean(&(slot->info));
					}
				}
			}

			if(parray->extra_slots) {
				size_t j;
				for(j=0;j<parray->extra_sz;++j) {
					turn_permission_slot *slot = parray->extra_slots[j];
					if(slot) {
						if(slot->info.allocated) {
							turn_permission_clean(&(slot->info));
						}
						turn_free(slot,sizeof(turn_permission_slot));
					}
				}
				turn_free(parray->extra_slots, parray->extra_sz * sizeof(turn_permission_slot*));
				parray->extra_slots = NULL;
			}
			parray->extra_sz = 0;
		}
	}
}

static turn_permission_info* get_from_turn_permission_hashtable(turn_permission_hashtable *map, const ioa_addr *addr)
{
	if (!addr || !map)
		return NULL;

	u32bits index = addr_hash_no_port(addr) & (TURN_PERMISSION_HASHTABLE_SIZE-1);
	turn_permission_array *parray = &(map->table[index]);

	{
		size_t i;
		for (i = 0; i < TURN_PERMISSION_ARRAY_SIZE; ++i) {
			turn_permission_slot *slot = &(parray->main_slots[i]);
			if (slot->info.allocated && addr_eq_no_port(&(slot->info.addr), addr)) {
				return &(slot->info);
			}
		}
	}

	if(parray->extra_slots) {

		size_t i;
		size_t sz = parray->extra_sz;
		for (i = 0; i < sz; ++i) {
			turn_permission_slot *slot = parray->extra_slots[i];
			if (slot->info.allocated && addr_eq_no_port(&(slot->info.addr), addr)) {
				return &(slot->info);
			}
		}
	}

	return NULL;
}

static void ch_info_clean(ch_info* c) {
  if(c) {
    IOA_EVENT_DEL(c->lifetime_ev);
    ns_bzero(c,sizeof(ch_info));
  }
}

static int delete_channel_info_from_allocation_map(ur_map_key_type key, ur_map_value_type value)
{
	UNUSED_ARG(key);

	if(value) {
		ch_info* chn = (ch_info*)value;

		if(chn->chnum <1) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s: strange (0) channel to be cleaned: chnum<1\n",__FUNCTION__);
		}

		ch_info_clean(chn);
	}

	return 0;
}

void turn_channel_delete(ch_info* chn)
{
	if(chn) {
		int port = addr_get_port(&(chn->peer_addr));
		if(port<1) {
			char s[129];
			addr_to_string(&(chn->peer_addr),(u08bits*)s);
			TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s: strange (1) channel to be cleaned: port is empty: %s\n",__FUNCTION__,s);
		}
		{
			turn_permission_info* tinfo = (turn_permission_info*)chn->owner;
			if(tinfo) {
				lm_map_del(&(tinfo->chns), (ur_map_key_type)port,NULL);
			} else {
				TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s: strange (2) channel to be cleaned: permission is empty\n",__FUNCTION__);
			}
		}
		delete_channel_info_from_allocation_map((ur_map_key_type)port,(ur_map_value_type)chn);
	}
}

ch_info* allocation_get_new_ch_info(allocation* a, u16bits chnum, ioa_addr* peer_addr)
{

	turn_permission_info* tinfo = get_from_turn_permission_hashtable(&(a->addr_to_perm), peer_addr);

	if (!tinfo)
		tinfo = allocation_add_permission(a, peer_addr);

	ch_info* chn = ch_map_get(&(a->chns), chnum, 1);

	chn->allocated = 1;
	chn->chnum = chnum;
	chn->port = addr_get_port(peer_addr);
	addr_cpy(&(chn->peer_addr), peer_addr);
	chn->owner = tinfo;

	lm_map_put(&(tinfo->chns), (ur_map_key_type) addr_get_port(peer_addr), (ur_map_value_type) chn);

	return chn;
}

ch_info* allocation_get_ch_info(allocation* a, u16bits chnum) {
	return ch_map_get(&(a->chns), chnum, 0);
}

ch_info* allocation_get_ch_info_by_peer_addr(allocation* a, ioa_addr* peer_addr) {
	turn_permission_info* tinfo = get_from_turn_permission_hashtable(&(a->addr_to_perm), peer_addr);
	if(tinfo) {
		return get_turn_channel(tinfo,peer_addr);
	}
	return NULL;
}

u16bits get_turn_channel_number(turn_permission_info* tinfo, ioa_addr *addr)
{
	if (tinfo) {
		ur_map_value_type t = 0;
		if (lm_map_get(&(tinfo->chns), (ur_map_key_type)addr_get_port(addr), &t) && t) {
			ch_info* chn = (ch_info*) t;
			if (STUN_VALID_CHANNEL(chn->chnum)) {
				return chn->chnum;
			}
		}
	}

	return 0;
}

ch_info *get_turn_channel(turn_permission_info* tinfo, ioa_addr *addr)
{
	if (tinfo) {
		ur_map_value_type t = 0;
		if (lm_map_get(&(tinfo->chns), (ur_map_key_type)addr_get_port(addr), &t) && t) {
			ch_info* chn = (ch_info*) t;
			if (STUN_VALID_CHANNEL(chn->chnum)) {
				return chn;
			}
		}
	}

	return NULL;
}

turn_permission_hashtable *allocation_get_turn_permission_hashtable(allocation *a)
{
  return &(a->addr_to_perm);
}

turn_permission_info* allocation_add_permission(allocation *a, const ioa_addr* addr)
{
	if (a && addr) {

		turn_permission_hashtable *map = &(a->addr_to_perm);
		u32bits hash = addr_hash_no_port(addr);
		size_t fds = (size_t) (hash & (TURN_PERMISSION_HASHTABLE_SIZE-1));

		turn_permission_array *parray = &(map->table[fds]);

		turn_permission_slot *slot = NULL;

		{
			size_t i;
			for(i=0;i<TURN_PERMISSION_ARRAY_SIZE;++i) {
				slot = &(parray->main_slots[i]);
				if(!(slot->info.allocated)) {
					break;
				} else {
					slot=NULL;
				}
			}
		}

		if(!slot) {

			size_t old_sz = parray->extra_sz;

			turn_permission_slot **slots = parray->extra_slots;

			if(slots) {
				size_t i;
				for(i=0;i<old_sz;++i) {
					slot = slots[i];
					if(!(slot->info.allocated)) {
						break;
					} else {
						slot=NULL;
					}
				}
			}

			if(!slot) {
				size_t old_sz_mem = old_sz * sizeof(turn_permission_slot*);
				parray->extra_slots = (turn_permission_slot **) turn_realloc(parray->extra_slots,
						old_sz_mem, old_sz_mem + sizeof(turn_permission_slot*));
				slots = parray->extra_slots;
				parray->extra_sz = old_sz + 1;
				slots[old_sz] = (turn_permission_slot *)turn_malloc(sizeof(turn_permission_slot));
				slot = slots[old_sz];
			}
		}

		ns_bzero(slot,sizeof(turn_permission_slot));
		slot->info.allocated = 1;
		turn_permission_info *elem = &(slot->info);
		addr_cpy(&(elem->addr), addr);
		elem->owner = a;

		return elem;
	} else {
		return NULL;
	}
}

ch_info *ch_map_get(ch_map* map, u16bits chnum, int new_chn)
{
	ch_info *ret = NULL;
	if(map) {
		size_t index = (size_t)(chnum & (CH_MAP_HASH_SIZE-1));
		ch_map_array *a = &(map->table[index]);

		size_t i;
		for(i=0;i<CH_MAP_ARRAY_SIZE;++i) {
			ch_info *chi = &(a->main_chns[i]);
			if(chi->allocated) {
				if(!new_chn && (chi->chnum == chnum)) {
					return chi;
				}
			} else if(new_chn) {
				return chi;
			}
		}

		size_t old_sz = a->extra_sz;
		if(old_sz && a->extra_chns) {
			for(i=0;i<old_sz;++i) {
				ch_info *chi = a->extra_chns[i];
				if(chi) {
					if(chi->allocated) {
						if(!new_chn && (chi->chnum == chnum)) {
							return chi;
						}
					} else if(new_chn) {
						return chi;
					}
				}
			}
		}

		if(new_chn) {
			size_t old_sz_mem = old_sz * sizeof(ch_info*);
			a->extra_chns = (ch_info**)turn_realloc(a->extra_chns,old_sz_mem,old_sz_mem + sizeof(ch_info*));
			a->extra_chns[old_sz] = (ch_info*)turn_malloc(sizeof(ch_info));
			ns_bzero(a->extra_chns[old_sz],sizeof(ch_info));
			a->extra_sz += 1;

			return a->extra_chns[old_sz];
		}
	}

	return ret;
}

void ch_map_clean(ch_map* map)
{
	if(map) {
		size_t index;
		for(index = 0; index < CH_MAP_HASH_SIZE; ++index) {

			ch_map_array *a = &(map->table[index]);

			size_t i;
			for(i=0;i<CH_MAP_ARRAY_SIZE;++i) {
				ch_info *chi = &(a->main_chns[i]);
				if(chi->allocated) {
					ch_info_clean(chi);
				}
			}

			if(a->extra_chns) {
				size_t sz = a->extra_sz;
				for(i=0;i<sz;++i) {
					ch_info *chi = a->extra_chns[i];
					if(chi) {
						if(chi->allocated) {
							ch_info_clean(chi);
						}
						turn_free(chi,sizeof(ch_info));
						a->extra_chns[i] = NULL;
					}
				}
				turn_free(a->extra_chns, sizeof(ch_info*)*sz);
				a->extra_chns = NULL;
			}
			a->extra_sz = 0;
		}
	}
}

////////////////// TCP connections ///////////////////////////////

static void set_new_tc_id(u08bits server_id, tcp_connection *tc) {
	allocation *a = (allocation*)(tc->owner);
	ur_map *map = a->tcp_connections;
	u32bits newid;
	u32bits sid = server_id;
	sid = sid<<24;
	do {
		newid = 0;
		while (!newid) {
			newid = (u32bits)turn_random();
			if(!newid) {
				continue;
			}
			newid = newid & 0x00FFFFFF;
			if(!newid) {
				continue;
			}
			newid = newid | sid;
		}
	} while(ur_map_get(map, (ur_map_key_type)newid, NULL));
	tc->id = newid;
	ur_map_put(map, (ur_map_key_type)newid, (ur_map_value_type)tc);
}

tcp_connection *create_tcp_connection(u08bits server_id, allocation *a, stun_tid *tid, ioa_addr *peer_addr, int *err_code)
{
	tcp_connection_list *tcl = &(a->tcs);
	if(tcl->elems) {
		size_t i;
		for(i=0;i<tcl->sz;++i) {
			tcp_connection *otc = tcl->elems[i];
			if(otc) {
				if(addr_eq(&(otc->peer_addr),peer_addr)) {
					*err_code = 446;
					return NULL;
				}
			}
		}
	}
	tcp_connection *tc = (tcp_connection*)turn_malloc(sizeof(tcp_connection));
	ns_bzero(tc,sizeof(tcp_connection));
	addr_cpy(&(tc->peer_addr),peer_addr);
	if(tid)
		ns_bcopy(tid,&(tc->tid),sizeof(stun_tid));
	tc->owner = a;

	int found = 0;
	if(a->tcs.elems) {
		size_t i;
		for(i=0;i<tcl->sz;++i) {
			tcp_connection *otc = tcl->elems[i];
			if(!otc) {
				tcl->elems[i] = tc;
				found = 1;
				break;
			}
		}
	}

	if(!found) {
		size_t old_sz_mem = a->tcs.sz * sizeof(tcp_connection*);
		a->tcs.elems = (tcp_connection**)turn_realloc(a->tcs.elems,old_sz_mem,old_sz_mem+sizeof(tcp_connection*));
		a->tcs.elems[a->tcs.sz] = tc;
		a->tcs.sz += 1;
		tcl = &(a->tcs);
	}

	set_new_tc_id(server_id, tc);
	return tc;
}

void delete_tcp_connection(tcp_connection *tc)
{
	if(tc) {
		if(tc->done) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s: check on already closed tcp data connection: 0x%lx\n",__FUNCTION__,(unsigned long)tc);
			return;
		}
		tc->done = 1;

		clear_unsent_buffer(&(tc->ub_to_client));

		IOA_EVENT_DEL(tc->peer_conn_timeout);
		IOA_EVENT_DEL(tc->conn_bind_timeout);
		allocation *a = (allocation*)(tc->owner);
		if(a) {
			ur_map *map = a->tcp_connections;
			if(map) {
				ur_map_del(map, (ur_map_key_type)(tc->id),NULL);
			}
			tcp_connection_list *tcl = &(a->tcs);
			if(tcl->elems) {
				size_t i;
				for(i=0;i<tcl->sz;++i) {
					if(tcl->elems[i] == tc) {
						tcl->elems[i] = NULL;
						break;
					}
				}
			}
		}
		IOA_CLOSE_SOCKET(tc->client_s);
		IOA_CLOSE_SOCKET(tc->peer_s);
		turn_free(tc,sizeof(tcp_connection));
	}
}

tcp_connection *get_and_clean_tcp_connection_by_id(ur_map *map, tcp_connection_id id)
{
	if(map) {
		ur_map_value_type t = 0;
		if (ur_map_get(map, (ur_map_key_type)id, &t) && t) {
			ur_map_del(map, (ur_map_key_type)id,NULL);
			return (tcp_connection*)t;
		}
	}
	return NULL;
}

tcp_connection *get_tcp_connection_by_peer(allocation *a, ioa_addr *peer_addr)
{
	if(a && peer_addr) {
		tcp_connection_list *tcl = &(a->tcs);
		if(tcl->elems) {
			size_t i;
			size_t sz = tcl->sz;
			for(i=0;i<sz;++i) {
				tcp_connection *tc = tcl->elems[i];
				if(tc) {
					if(addr_eq(&(tc->peer_addr),peer_addr)) {
						return tc;
					}
				}
			}
		}
	}
	return NULL;
}

int can_accept_tcp_connection_from_peer(allocation *a, ioa_addr *peer_addr, int server_relay)
{
	if(server_relay)
		return 1;

	if(a && peer_addr) {
		return (get_from_turn_permission_hashtable(&(a->addr_to_perm), peer_addr) != NULL);
	}

	return 0;
}

//////////////// Unsent buffers //////////////////////

void clear_unsent_buffer(unsent_buffer *ub)
{
	if(ub) {
		if(ub->bufs) {
			size_t sz;
			for(sz = 0; sz<ub->sz; sz++) {
				ioa_network_buffer_handle nbh = ub->bufs[sz];
				if(nbh) {
					ioa_network_buffer_delete(NULL, nbh);
					ub->bufs[sz] = NULL;
				}
			}
			turn_free(ub->bufs,sizeof(ioa_network_buffer_handle) * ub->sz);
			ub->bufs = NULL;
		}
		ub->sz = 0;
	}
}

void add_unsent_buffer(unsent_buffer *ub, ioa_network_buffer_handle nbh)
{
	if(!ub || (ub->sz >= MAX_UNSENT_BUFFER_SIZE)) {
		ioa_network_buffer_delete(NULL, nbh);
	} else {
		ub->bufs = (ioa_network_buffer_handle*)turn_realloc(ub->bufs, sizeof(ioa_network_buffer_handle) * ub->sz, sizeof(ioa_network_buffer_handle) * (ub->sz+1));
		ub->bufs[ub->sz] = nbh;
		ub->sz +=1;
	}
}

ioa_network_buffer_handle top_unsent_buffer(unsent_buffer *ub)
{
	ioa_network_buffer_handle ret = NULL;
	if(ub && ub->bufs && ub->sz) {
		size_t sz;
		for(sz=0; sz<ub->sz; ++sz) {
			if(ub->bufs[sz]) {
				ret = ub->bufs[sz];
				break;
			}
		}
	}
	return ret;
}

void pop_unsent_buffer(unsent_buffer *ub)
{
	if(ub && ub->bufs && ub->sz) {
		size_t sz;
		for(sz=0; sz<ub->sz; ++sz) {
			if(ub->bufs[sz]) {
				ub->bufs[sz] = NULL;
				break;
			}
		}
	}
}

//////////////////////////////////////////////////////////////////

