/*
 * 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.
 */

#ifndef __TURN_TURN_A_LIB__
#define __TURN_TURN_A_LIB__

#include "ns_turn_utils.h"
#include "ns_turn_msg.h"
#include "ns_turn_ioalib.h"
#include "ns_turn_maps.h"

#ifdef __cplusplus
extern "C" {
#endif

///////// Defines //////////

#define TCP_PEER_CONN_TIMEOUT (30)
#define TCP_CONN_BIND_TIMEOUT (30)

///////// types ////////////

enum _UR_STATE {
  UR_STATE_UNKNOWN=0,
  UR_STATE_READY,
  UR_STATE_DONE
};

typedef enum _UR_STATE UR_STATE;

////////////// Network session ////////////////

typedef struct
{
	UR_STATE state;
	ioa_socket_handle s;
	int known_mtu;
} ts_ur_session;

static inline void clear_ts_ur_session_data(ts_ur_session* cdi)
{
	if (cdi)
		IOA_CLOSE_SOCKET(cdi->s);
}

////////// RFC 6062 TCP connection ////////

#define MAX_UNSENT_BUFFER_SIZE (0x10)

enum _TC_STATE {
	TC_STATE_UNKNOWN=0,
	TC_STATE_CLIENT_TO_PEER_CONNECTING,
	TC_STATE_PEER_CONNECTING,
	TC_STATE_PEER_CONNECTED,
	TC_STATE_READY,
	TC_STATE_FAILED
};

typedef enum _TC_STATE TC_STATE;

typedef u32bits tcp_connection_id;

typedef struct {
	size_t sz;
	ioa_network_buffer_handle *bufs;
} unsent_buffer;

struct _tcp_connection
{
	TC_STATE state;
	tcp_connection_id id;
	ioa_addr peer_addr;
	ioa_socket_handle client_s;
	ioa_socket_handle peer_s;
	ioa_timer_handle peer_conn_timeout;
	ioa_timer_handle conn_bind_timeout;
	stun_tid tid;
	void *owner; //a
	int done;
	unsent_buffer ub_to_client;
};

typedef struct _tcp_connection_list {
	size_t sz;
	tcp_connection **elems;
} tcp_connection_list;

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

#define TURN_PERMISSION_HASHTABLE_SIZE (0x8)
#define TURN_PERMISSION_ARRAY_SIZE (0x3)

struct _allocation;

typedef struct _ch_info {
  u16bits chnum;
  int allocated;
  u16bits port;
  ioa_addr peer_addr;
  turn_time_t expiration_time;
  ioa_timer_handle lifetime_ev;
  void *owner; //perm
} ch_info;

///////////// "channel" map /////////////////////

#define CH_MAP_HASH_SIZE (0x8)
#define CH_MAP_ARRAY_SIZE (0x3)

typedef struct _chn_map_array {
	ch_info main_chns[CH_MAP_ARRAY_SIZE];
	size_t extra_sz;
	ch_info **extra_chns;
} ch_map_array;

typedef struct _ch_map {
	ch_map_array table[CH_MAP_HASH_SIZE];
} ch_map;

ch_info *ch_map_get(ch_map* map, u16bits chnum, int new_chn);
void ch_map_clean(ch_map* map);

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

typedef struct _turn_permission_info {
	int allocated;
	lm_map chns;
	ioa_addr addr;
	turn_time_t expiration_time;
	ioa_timer_handle lifetime_ev;
	void* owner; //a
} turn_permission_info;

typedef struct _turn_permission_slot {
	turn_permission_info info;
} turn_permission_slot;

typedef struct _turn_permission_array {
	turn_permission_slot main_slots[TURN_PERMISSION_ARRAY_SIZE];
	size_t extra_sz;
	turn_permission_slot **extra_slots;
} turn_permission_array;

typedef struct _turn_permission_hashtable {
	turn_permission_array table[TURN_PERMISSION_HASHTABLE_SIZE];
} turn_permission_hashtable;

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

typedef struct _allocation {
  int is_valid;
  stun_tid tid;
  turn_time_t expiration_time;
  ioa_timer_handle lifetime_ev;
  turn_permission_hashtable addr_to_perm;
  ts_ur_session relay_session;
  ch_map chns; /* chnum-to-ch_info* */
  void *owner; //ss
  ur_map *tcp_connections; //global (per turn server) reference
  tcp_connection_list tcs; //local reference
} allocation;

//////////// CHANNELS ////////////////////

u16bits get_turn_channel_number(turn_permission_info* tinfo, ioa_addr *addr);
ch_info *get_turn_channel(turn_permission_info* tinfo, ioa_addr *addr);

void turn_channel_delete(ch_info* chn);

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

void init_allocation(void *owner, allocation* a, ur_map *tcp_connections);
void clear_allocation(allocation *a);

void turn_permission_clean(turn_permission_info* tinfo);

void set_allocation_lifetime_ev(allocation *a, turn_time_t exp_time, ioa_timer_handle ev);
int is_allocation_valid(const allocation* a);
void set_allocation_valid(allocation* a, int value);
turn_permission_info* allocation_get_permission(allocation* a, const ioa_addr *addr);
turn_permission_hashtable* allocation_get_turn_permission_hashtable(allocation *a);
turn_permission_info* allocation_add_permission(allocation *a, const ioa_addr* addr);

ch_info* allocation_get_new_ch_info(allocation* a, u16bits chnum, ioa_addr* peer_addr);
ch_info* allocation_get_ch_info(allocation* a, u16bits chnum);
ch_info* allocation_get_ch_info_by_peer_addr(allocation* a, ioa_addr* peer_addr);

ts_ur_session *get_relay_session(allocation *a);
ioa_socket_handle get_relay_socket(allocation *a);

tcp_connection *get_and_clean_tcp_connection_by_id(ur_map *map, tcp_connection_id id);
tcp_connection *get_tcp_connection_by_peer(allocation *a, ioa_addr *peer_addr);
int can_accept_tcp_connection_from_peer(allocation *a, ioa_addr *peer_addr, int server_relay);
tcp_connection *create_tcp_connection(u08bits server_id, allocation *a, stun_tid *tid, ioa_addr *peer_addr, int *err_code);
void delete_tcp_connection(tcp_connection *tc);

void clear_unsent_buffer(unsent_buffer *ub);
void add_unsent_buffer(unsent_buffer *ub, ioa_network_buffer_handle nbh);
ioa_network_buffer_handle top_unsent_buffer(unsent_buffer *ub);
void pop_unsent_buffer(unsent_buffer *ub);

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

#ifdef __cplusplus
}
#endif

#endif //__TURN_TURN_A_LIB__
