/*
 * libZRTP SDK library, implements the ZRTP secure VoIP protocol.
 * Copyright (c) 2006-2009 Philip R. Zimmermann.  All rights reserved.
 * Contact: http://philzimmermann.com
 * For licensing and other legal details, see the file zrtp_legal.c.
 *
 * Viktor Krykun <v.krikun at zfoneproject.com>
 */

#include <stdio.h>	/* for sprintf(), remove() */
#include <string.h>	/* for string operations */

#include "test_engine.h"
#include "queue.h"

#define _ZTU_ "test engine"

#define K_ZRTP_TEST_MAX_ENDPOINTS 10
#define K_ZRTP_TEST_MAX_SESSIONS_PER_ENDPOINT 100
#define K_ZRTP_TEST_MAX_CHANNELS (K_ZRTP_TEST_MAX_ENDPOINTS * K_ZRTP_TEST_MAX_ENDPOINTS * ZRTP_MAX_STREAMS_PER_SESSION)

#define K_ZRTP_TEST_PROCESSORS_COUNT 2
#define K_ZRTP_TEST_RTP_RATE		200

extern uint8_t hash_word_list_odd[256][12];
extern uint8_t hash_word_list_even[256][10];

typedef struct {
	zrtp_test_id_t 		id;
	zrtp_test_id_t 		session_id;
	zrtp_test_id_t		channel_id;
	zrtp_test_id_t		endpoint_id;
	zrtp_stream_t 		*zrtp;
	uint16_t			seq;
	zrtp_queue_t 		*input;
	zrtp_queue_t 		*output;
	unsigned 			zrtp_events_queueu[128];
	unsigned 			zrtp_events_count;
} zrtp_test_stream_t;

typedef struct {
	zrtp_test_id_t id;
	zrtp_test_id_t endpoint_id;
	zrtp_test_session_cfg_t cfg;
	zrtp_session_t *zrtp;
	zrtp_test_stream_t streams[ZRTP_MAX_STREAMS_PER_SESSION];
	unsigned streams_count;
} zrtp_test_session_t;

typedef struct {
	zrtp_test_id_t id;
	char name[ZRTP_TEST_STR_LEN];
	zrtp_zid_t zid;
	zrtp_test_endpoint_cfg_t cfg;
	zrtp_test_session_t sessions[K_ZRTP_TEST_MAX_SESSIONS_PER_ENDPOINT];
	unsigned sessions_count;
	zrtp_global_t *zrtp;
	unsigned is_running;
	zrtp_queue_t *input_queue;
} zrtp_endpoint_t;


typedef struct {
	zrtp_test_id_t 		id;
	zrtp_test_stream_t	*left;
	zrtp_test_stream_t	*right;
	unsigned			is_attached;
	unsigned			is_secure;
} zrtp_test_channel_t;

typedef struct zrtp_test_packet {
	uint32_t			is_rtp;			/*! Defines is packet RTP or RTCP */
	uint32_t			length;			/*! Packet Length in bytes */
	char				body[1024];		/*! Packet body */
} zrtp_test_packet_t;


static zrtp_endpoint_t g_test_endpoints[K_ZRTP_TEST_MAX_ENDPOINTS];
static unsigned g_test_endpoints_count = 0;

static zrtp_test_channel_t g_test_channels[K_ZRTP_TEST_MAX_CHANNELS];
static unsigned g_test_channels_count = 0;

static int g_endpoints_counter = 7;
static int g_channels_counter = 7;
static int g_sessions_counter = 7;
static int g_streams_counter = 7;


zrtp_endpoint_t *zrtp_test_endpoint_by_id(zrtp_test_id_t id);
zrtp_test_stream_t *zrtp_test_stream_by_id(zrtp_test_id_t id);
zrtp_test_stream_t *zrtp_test_stream_by_peerid(zrtp_test_id_t id);
zrtp_test_session_t *zrtp_test_session_by_id(zrtp_test_id_t id);
zrtp_test_channel_t *zrtp_test_channel_by_id(zrtp_test_id_t id);


/******************************************************************************
 * libzrtp interface implementation
 */

static void on_zrtp_event(zrtp_stream_t *ctx, zrtp_protocol_event_t event) {
	zrtp_test_id_t *stream_id = zrtp_stream_get_userdata(ctx);
	zrtp_test_stream_t *stream = zrtp_test_stream_by_id(*stream_id);

	stream->zrtp_events_queueu[stream->zrtp_events_count++] = event;
}


static void on_zrtp_secure(zrtp_stream_t *ctx) {
	zrtp_test_id_t *stream_id = zrtp_stream_get_userdata(ctx);
	zrtp_test_stream_t *stream = zrtp_test_stream_by_id(*stream_id);
	zrtp_test_channel_t *channel = zrtp_test_channel_by_id(stream->channel_id);
	zrtp_test_stream_t *remote_stream = (channel->left == stream) ? channel->right : channel->left;

	if (stream->zrtp->state == ZRTP_STATE_SECURE &&
		remote_stream->zrtp->state == ZRTP_STATE_SECURE) {
		channel->is_secure = 1;
	}

}

static int on_send_packet(const zrtp_stream_t* ctx, char* message, unsigned int length) {
	zrtp_queue_elem_t* elem = zrtp_sys_alloc(sizeof(zrtp_queue_elem_t));
	if (elem) {
		zrtp_test_packet_t* packet = (zrtp_test_packet_t*) elem->data;
		elem->size = length;

		packet->is_rtp = 1;
		packet->length = length;
		zrtp_memcpy(packet->body, message, length);

		zrtp_test_id_t *stream_id = zrtp_stream_get_userdata(ctx);
		zrtp_test_stream_t *stream = zrtp_test_stream_by_id(*stream_id);
		if (stream) {
			zrtp_test_queue_push(stream->output, elem);
			return zrtp_status_ok;
		} else {
			return zrtp_status_fail;
		}
	} else {
		return zrtp_status_alloc_fail;
	}
}


/******************************************************************************
 * Processing Loop
 */

static zrtp_test_stream_t *get_stream_to_process_(zrtp_endpoint_t *endpoint) {
	zrtp_test_id_t all_streams[K_ZRTP_TEST_MAX_SESSIONS_PER_ENDPOINT*ZRTP_MAX_STREAMS_PER_SESSION];
	unsigned streams_count = 0;
	unsigned i, j;

	for (i=0; i<endpoint->sessions_count; i++) {
		for (j=0; j<endpoint->sessions[i].streams_count; j++) {
			zrtp_test_stream_t *stream = &endpoint->sessions[i].streams[j];
			if (stream->input && stream->output)
				all_streams[streams_count++] = stream->id;
		}
	}

	if (0 == streams_count)
		return NULL;

	zrtp_randstr(endpoint->zrtp, (unsigned char*)&i, sizeof(i));
	j = (unsigned)i;
	j = j % streams_count;

	//printf("trace>>> CHOOSE stream Endpoint=%u IDX=%u ID=%u\n", endpoint->id,  j, all_streams[j]);
	return zrtp_test_stream_by_id(all_streams[j]);
}


#if   (ZRTP_PLATFORM == ZP_WIN32) || (ZRTP_PLATFORM == ZP_WINCE)
DWORD WINAPI process_incoming(void *param)
#else
void *process_incoming(void *param)
#endif
{
	zrtp_endpoint_t *the_endpoint = (zrtp_endpoint_t *)param;

	while (the_endpoint->is_running) {
		zrtp_test_packet_t* packet = NULL;
		zrtp_queue_elem_t* elem = NULL;
		zrtp_status_t s = zrtp_status_fail;
		zrtp_test_stream_t *stream;
		int is_protocol = 0;

		// TODO: use peak to not to block processing if queue for this stream is empty
		elem = zrtp_test_queue_pop(the_endpoint->input_queue);
		if (!elem || elem->size <= 0) {
			if (elem) zrtp_sys_free(elem);
			break;
		}

		packet = (zrtp_test_packet_t*) elem->data;
		zrtp_test_id_t stream_id;
		{
			if (packet->is_rtp) {
				ZRTP_UNALIGNED(zrtp_rtp_hdr_t) *rtp_hdr = (zrtp_rtp_hdr_t*)packet->body;
				stream_id = zrtp_ntoh32(rtp_hdr->ssrc); /* remember, we use stream Id as it's RTP SSRC */
			} else {
				ZRTP_UNALIGNED(zrtp_rtcp_hdr_t) *rtcp_hdr = (zrtp_rtcp_hdr_t*)packet->body;
				stream_id = zrtp_ntoh32(rtcp_hdr->ssrc); /* remember, we use stream Id as it's RTP SSRC */
			}
			stream = zrtp_test_stream_by_peerid(stream_id);
		}

		/*
		 * Process incoming packet by libzrtp. Is this a RTP media packet - copy it to the buffer
		 * to print out later.
		 */
		if (packet->is_rtp) {
			s = zrtp_process_srtp(stream->zrtp, packet->body, &packet->length);
		} else {
			s = zrtp_process_srtcp(stream->zrtp, packet->body, &packet->length);
		}

		if (!is_protocol) {
			char *body;
			if (packet->is_rtp) {
				body = packet->body + sizeof(zrtp_rtp_hdr_t);
				body[packet->length - sizeof(zrtp_rtp_hdr_t)] = 0;
			} else {
				body = packet->body + sizeof(zrtp_rtcp_hdr_t);
				body[packet->length - sizeof(zrtp_rtcp_hdr_t)] = 0;
			}

			switch (s)
			{
			case zrtp_status_ok: {
				ZRTP_LOG(1, (_ZTU_,"Incoming: (%s) [%p:ssrc=%u] OK. <%s> decrypted %d bytes.\n",
						zrtp_log_state2str(stream->zrtp->state), stream->zrtp,  stream->id, body, packet->length));
			} break;

			case zrtp_status_drop: {
				ZRTP_LOG(1, (_ZTU_,"Incoming: (%s) [%p:ssrc=%u] DROPPED. <%s>\n",
						zrtp_log_state2str(stream->zrtp->state), stream->zrtp, stream->id, body));
			} break;

			case zrtp_status_fail: {
				ZRTP_LOG(1, (_ZTU_,"Incoming: (%s) [%p:ssrc=%u] DECRYPT FAILED. <%s>\n",
						zrtp_log_state2str(stream->zrtp->state), stream->zrtp, stream->id, body));
			} break;

			default:
				break;
			}
		}

		zrtp_sys_free(elem);

		/*
		 * When zrtp_stream is in the pending clear state and other side wants to send plain
		 * traffic. We have to call zrtp_clear_stream().
		 */
		if (stream->zrtp->state == ZRTP_STATE_PENDINGCLEAR) {
			zrtp_stream_clear(stream->zrtp);
		}
	}
#if   (ZRTP_PLATFORM == ZP_WIN32) || (ZRTP_PLATFORM == ZP_WINCE)
	return 0;
#else
	return NULL;
#endif
}

#if   (ZRTP_PLATFORM == ZP_WIN32) || (ZRTP_PLATFORM == ZP_WINCE)
DWORD WINAPI process_outgoing(void *param)
#else
void *process_outgoing(void *param)
#endif
{
	unsigned packets_counter = 0;
	zrtp_endpoint_t *the_endpoint = (zrtp_endpoint_t *)param;

	while (the_endpoint->is_running) {
		zrtp_test_stream_t* stream = NULL;
		unsigned i;

		zrtp_status_t s = zrtp_status_fail;
		zrtp_test_packet_t* packet;
		zrtp_queue_elem_t* elem;
		char* word = NULL;

		zrtp_sleep(K_ZRTP_TEST_RTP_RATE);

		/* Get random channel to operate with and select random peer */
		stream = get_stream_to_process_(the_endpoint);
		if (!stream) {
			continue;
		}

		elem = zrtp_sys_alloc(sizeof(zrtp_queue_elem_t));
		if (!elem) {
			break;
		}
		packet = (zrtp_test_packet_t*) elem->data;
		packet->is_rtp = (packets_counter++ % 20); /* Every 20-th packet is RTCP */

		/*
		 * Construct RTP/RTCP Packet
		 */
		if (packet->is_rtp)
		{
			ZRTP_UNALIGNED(zrtp_rtp_hdr_t) *rtp_hdr = (zrtp_rtp_hdr_t*)packet->body;

			/* Fill RTP Header according to the specification */
			zrtp_memset(rtp_hdr, 0, sizeof(zrtp_rtp_hdr_t));
			rtp_hdr->version = 2;			/* Current RTP version 2 */
			rtp_hdr->pt = 0;				/* PCMU padding type */
			rtp_hdr->ssrc = zrtp_hton32(stream->id);		/* Use stream Identifier as it's SSRC */
			if (stream->seq >= 0xFFFF) {
				stream->seq = 0;
			}
			rtp_hdr->seq = zrtp_hton16(stream->seq++);
			rtp_hdr->ts = zrtp_hton32((uint32_t)(zrtp_time_now()/1000));

			/* Get RTP body from PGP words lists */
			word = (char*)(i ? hash_word_list_odd[packets_counter % 256] : hash_word_list_even[packets_counter % 256]);

			zrtp_memcpy(packet->body + sizeof(zrtp_rtp_hdr_t), word, (uint32_t)strlen(word));
			packet->length = sizeof(zrtp_rtp_hdr_t) + (uint32_t)strlen(word);

			/* Process RTP media with libzrtp */
			s = zrtp_process_rtp(stream->zrtp, packet->body, &packet->length);
		}
		else {
			ZRTP_UNALIGNED(zrtp_rtcp_hdr_t) *rtcp_hdr = (zrtp_rtcp_hdr_t*)packet->body;

			/* Fill RTCP Header according to the specification */
			rtcp_hdr->rc = 0;
			rtcp_hdr->version = 2;
			rtcp_hdr->ssrc = stream->id;

			/* Get RTP body from PGP words lists. Put RTCP marker at the beginning */
			zrtp_memcpy(packet->body + sizeof(zrtp_rtcp_hdr_t), "RTCP", 4);
			word = (char*)( i ? hash_word_list_odd[packets_counter % 256] : hash_word_list_even[packets_counter % 256]);

			zrtp_memcpy(packet->body + sizeof(zrtp_rtcp_hdr_t) + 4, word, (uint32_t)strlen(word));
			packet->length = sizeof(zrtp_rtcp_hdr_t) + (uint32_t)strlen(word) + 4;
			/* RTCP packets sould be 32 byes aligned */
			packet->length += (packet->length % 4) ? (4 - packet->length % 4) : 0;

			/* Process RTCP control with libzrtp */
			s = zrtp_process_rtcp(stream->zrtp, packet->body, &packet->length);
		}

		elem->size = packet->length;

		/* Handle zrtp_process_xxx() instructions */
		switch (s) {
		/* Put the packet to the queue ==> send packet to the other side pear */
		case zrtp_status_ok: {
			ZRTP_LOG(3, (_ZTU_,"Outgoing: (%s) [%p:ssrc=%u] OK. <%s%s> encrypted %d bytes.\n",
					zrtp_log_state2str(stream->zrtp->state), stream->zrtp, stream->id, packet->is_rtp ? "" : "RTCP", word, packet->length));
			zrtp_test_queue_push(stream->output, elem);
		} break;

		case zrtp_status_drop: {
			ZRTP_LOG(1, (_ZTU_,"Outgoing: (%s) [%p:ssrc=%u] DROPPED.\n",
					zrtp_log_state2str(stream->zrtp->state), stream->zrtp, stream->id));
		} break;

		case zrtp_status_fail: {
			ZRTP_LOG(1, (_ZTU_,"Outgoing: (%s) [%p:ssrc=%u] ENCRYPT FAILED.\n",
					zrtp_log_state2str(stream->zrtp->state), stream->zrtp, stream->id));
		}	break;

		default:
			break;
		}

		if (zrtp_status_ok != s) {
			zrtp_sys_free(packet);
		}
	}
#if   (ZRTP_PLATFORM == ZP_WIN32) || (ZRTP_PLATFORM == ZP_WINCE)
	return 0;
#else
	return NULL;
#endif
}


/******************************************************************************
 * Test Engine Public API
 */

void zrtp_test_endpoint_config_defaults(zrtp_test_endpoint_cfg_t* cfg) {

	zrtp_memset(cfg, 0, sizeof(zrtp_test_endpoint_cfg_t));

	cfg->generate_traffic = 0;

	/* It's always a good idea to start with default values */
	zrtp_config_defaults(&cfg->zrtp);

	/* Set ZRTP client id */
	strcpy(cfg->zrtp.client_id, "zrtp-test-engine");

	cfg->zrtp.is_mitm = 0;
	cfg->zrtp.lic_mode = ZRTP_LICENSE_MODE_ACTIVE;

	cfg->zrtp.cb.event_cb.on_zrtp_secure			= &on_zrtp_secure;
	cfg->zrtp.cb.event_cb.on_zrtp_security_event	= &on_zrtp_event;
	cfg->zrtp.cb.event_cb.on_zrtp_protocol_event	= &on_zrtp_event;
	cfg->zrtp.cb.misc_cb.on_send_packet				= &on_send_packet;
}

zrtp_status_t zrtp_test_endpoint_create(zrtp_test_endpoint_cfg_t* cfg,
										const char *name,
										zrtp_test_id_t* id) {
	zrtp_status_t s;
	unsigned i;
	char cache_file_path[ZRTP_TEST_STR_LEN];
	zrtp_endpoint_t *new_endpoint;

	if (g_test_endpoints_count >= K_ZRTP_TEST_MAX_ENDPOINTS)
		return zrtp_status_alloc_fail;

	new_endpoint = &g_test_endpoints[g_test_endpoints_count++];
	zrtp_memset(new_endpoint, 0, sizeof(zrtp_endpoint_t));

	/* Copy configuration, we will use it later to clean up after ourselves */
	zrtp_memcpy(&new_endpoint->cfg, cfg, sizeof(zrtp_test_endpoint_cfg_t));

	/* Remember endpoint name */
	strcpy(new_endpoint->name, name);

	new_endpoint->id = g_endpoints_counter++;

	/* Adjust cache file path so each endpoint will use it's own file. */
	sprintf(cache_file_path, "./%s_cache.dat", name);
	zrtp_zstrcpyc(ZSTR_GV(new_endpoint->cfg.zrtp.def_cache_path), cache_file_path);

	/* Initialize libzrtp engine for this endpoint */
	s = zrtp_init(&new_endpoint->cfg.zrtp, &new_endpoint->zrtp);
	if (zrtp_status_ok == s) {
		*id = new_endpoint->id;

		/* Generate random ZID */
		zrtp_randstr(new_endpoint->zrtp, new_endpoint->zid, sizeof(new_endpoint->zid));
	}

	/* Create Input queue*/
	s = zrtp_test_queue_create(&new_endpoint->input_queue);
	if (zrtp_status_ok != s) {
		return s;
	}

	/* Start processing loop */
	new_endpoint->is_running = 1;

	for (i = 0; i<K_ZRTP_TEST_PROCESSORS_COUNT; i++) {
		if (0 != zrtp_thread_create(process_incoming, new_endpoint)) {
			return zrtp_status_fail;
		}

		if (cfg->generate_traffic) {
			if (0 != zrtp_thread_create(process_outgoing, new_endpoint)) {
				return zrtp_status_fail;
			}
		}
	}

	return s;
}

zrtp_status_t zrtp_test_endpoint_destroy(zrtp_test_id_t id) {
	unsigned i;
	zrtp_status_t s = zrtp_status_ok;
	zrtp_endpoint_t *endpoint = zrtp_test_endpoint_by_id(id);

	endpoint->is_running = 0;

	if (endpoint->input_queue) {
		/* Push faked element to the queue to unlock incoming threads */
		for (i=0; i<K_ZRTP_TEST_PROCESSORS_COUNT; i++) {
			zrtp_queue_elem_t *elem = malloc(sizeof(zrtp_queue_elem_t));
			elem->size = 0;
			zrtp_test_queue_push(endpoint->input_queue, elem);
		}
		zrtp_sleep(0.5*1000);

		zrtp_test_queue_destroy(endpoint->input_queue);
	}

	for (i=0; i<20; i++) zrtp_sleep(100);

	if (endpoint) {
		/* Shut down libzrtp */
		if (endpoint->zrtp)
			s = zrtp_down(endpoint->zrtp);

		/* Clean-up ZRTP cache after ourselves */
		remove(endpoint->cfg.zrtp.def_cache_path.buffer);
	} else {
		s = zrtp_status_fail;
	}

	return s;
}

zrtp_status_t zrtp_test_stream_get(zrtp_test_id_t id,
								   zrtp_test_stream_info_t* info) {

	zrtp_test_stream_t *stream = zrtp_test_stream_by_id(id);
	if (stream) {
		zrtp_status_t s;
		zrtp_memset(info, 0, sizeof(zrtp_test_stream_info_t));

		zrtp_memcpy(info->zrtp_events_queueu, stream->zrtp_events_queueu, sizeof(info->zrtp_events_queueu));
		info->zrtp_events_count = stream->zrtp_events_count;

		s = zrtp_stream_get(stream->zrtp, &info->zrtp);
		return s;
	} else {
		return zrtp_status_bad_param;
	}
}

void zrtp_test_session_config_defaults(zrtp_test_session_cfg_t* cfg) {
	cfg->streams_count = 1;
	cfg->role = ZRTP_SIGNALING_ROLE_UNKNOWN;
	cfg->is_enrollment = 0;

	zrtp_profile_defaults(&cfg->zrtp, NULL);
}

zrtp_status_t zrtp_test_session_create(zrtp_test_id_t endpoint_id,
									   zrtp_test_session_cfg_t* cfg,
									   zrtp_test_id_t* id) {
	zrtp_status_t s;
	unsigned i;
	zrtp_test_session_t *the_session;
	zrtp_endpoint_t *the_endpoint = zrtp_test_endpoint_by_id(endpoint_id);

	if (!the_endpoint)
		return zrtp_status_fail;

	if (the_endpoint->sessions_count >= K_ZRTP_TEST_MAX_SESSIONS_PER_ENDPOINT)
		return zrtp_status_fail;

	the_session = &the_endpoint->sessions[the_endpoint->sessions_count++];

	zrtp_memset(the_session, 0, sizeof(zrtp_test_session_t));

	zrtp_memcpy(&the_session->cfg, cfg, sizeof(zrtp_test_session_cfg_t));

	the_session->id = g_sessions_counter++;
	the_session->endpoint_id = endpoint_id;

	s = zrtp_session_init(the_endpoint->zrtp,
						  &cfg->zrtp,
						  the_endpoint->zid,
						  cfg->role,
						  &the_session->zrtp);

	if (zrtp_status_ok == s) {

		zrtp_session_set_userdata(the_session->zrtp, &the_session->id);

		for (i=0; i<cfg->streams_count; i++) {
			zrtp_test_stream_t *the_stream = &the_session->streams[i];
			zrtp_memset(the_stream, 0, sizeof(zrtp_test_stream_t));

			the_stream->id = g_streams_counter++;
			the_stream->session_id = the_session->id;
			the_stream->endpoint_id = endpoint_id;

			s = zrtp_stream_attach(the_session->zrtp, &the_stream->zrtp);
			if (zrtp_status_ok == s) {
				zrtp_stream_set_userdata(the_stream->zrtp, &the_stream->id);
				the_session->streams_count++;
			} else {
				break;
			}
		}
	}

	if (zrtp_status_ok == s) {
		*id = the_session->id;
	}

	return s;
}

zrtp_status_t zrtp_test_session_destroy(zrtp_test_id_t id) {
	zrtp_test_session_t *session = zrtp_test_session_by_id(id);
	if (session) {
		/* NOTE: we don't release session slots here due to nature of testing
		 * engine: test configuration constructed from scratch for every single test.
		 */
		zrtp_session_down(session->zrtp);
	}
	return zrtp_status_ok;
}

zrtp_status_t zrtp_test_session_get(zrtp_test_id_t id, zrtp_test_session_info_t* info) {
	zrtp_status_t s;
	zrtp_test_session_t *session = zrtp_test_session_by_id(id);
	if (session) {
		s = zrtp_session_get(session->zrtp, &info->zrtp);
		if (zrtp_status_ok == s) {
			unsigned i;
			for (i=0; i<session->streams_count; i++) {
				s = zrtp_test_stream_get(session->streams[i].id, &info->streams[i]);
				if (zrtp_status_ok != s)
					break;
			}
		}

		return s;
	} else {
		return zrtp_status_bad_param;
	}
}

zrtp_status_t zrtp_test_channel_create(zrtp_test_id_t left_id, zrtp_test_id_t right_id, zrtp_test_id_t* id) {
	zrtp_test_channel_t *the_channel;
	zrtp_test_stream_t *left = zrtp_test_stream_by_id(left_id);
	zrtp_test_stream_t *right = zrtp_test_stream_by_id(right_id);

	if (!left || !right)
		return zrtp_status_bad_param;

	if (g_test_channels_count >= K_ZRTP_TEST_MAX_CHANNELS)
		return zrtp_status_bad_param;

	zrtp_endpoint_t *left_endpoint = zrtp_test_endpoint_by_id(left->endpoint_id);
	zrtp_endpoint_t *right_endpoint = zrtp_test_endpoint_by_id(right->endpoint_id);

	the_channel = &g_test_channels[g_test_channels_count++];
	zrtp_memset(the_channel, 0, sizeof(zrtp_test_channel_t));

	the_channel->id = g_channels_counter++;
	the_channel->left = left;
	the_channel->right = right;

	left->output = right_endpoint->input_queue;
	left->input = left_endpoint->input_queue;
	right->output = left_endpoint->input_queue;
	right->input = right_endpoint->input_queue;

	right->channel_id = the_channel->id;
	left->channel_id = the_channel->id;

	the_channel->is_attached = 1;

	*id = the_channel->id;

	return zrtp_status_ok;
}

zrtp_status_t zrtp_test_channel_create2(zrtp_test_id_t left_session,
										zrtp_test_id_t right_session,
										unsigned stream_idx,
										zrtp_test_id_t *id) {
	zrtp_test_session_t *left = zrtp_test_session_by_id(left_session);
	zrtp_test_session_t *right = zrtp_test_session_by_id(right_session);

	if (!left || !right)
		return zrtp_status_bad_param;

	if (left->streams_count <= stream_idx || right->streams_count <= stream_idx)
		return zrtp_status_bad_param;

	return zrtp_test_channel_create(left->streams[stream_idx].id, right->streams[stream_idx].id, id);
}

zrtp_status_t zrtp_test_channel_destroy(zrtp_test_id_t id) {
	zrtp_test_channel_t *channel = zrtp_test_channel_by_id(id);
	if (!channel)
		return zrtp_status_bad_param;

	return zrtp_status_ok;
}

zrtp_status_t zrtp_test_channel_start(zrtp_test_id_t id) {
	zrtp_status_t s1, s2;
	zrtp_test_channel_t *the_channel = zrtp_test_channel_by_id(id);
	zrtp_test_session_t *the_session;

	the_session = zrtp_test_session_by_id(the_channel->left->session_id);
	if (the_session->cfg.is_enrollment)
		s1 = zrtp_stream_registration_start(the_channel->left->zrtp, the_channel->left->id); /* use stream Id as ssrc */
	else
		s1 = zrtp_stream_start(the_channel->left->zrtp, the_channel->left->id); /* use stream Id as ssrc */
	if (s1 == zrtp_status_ok) {
		the_session = zrtp_test_session_by_id(the_channel->right->session_id);
		if (the_session->cfg.is_enrollment)
			s2 = zrtp_stream_registration_start(the_channel->right->zrtp, the_channel->right->id);
		else
			s2 = zrtp_stream_start(the_channel->right->zrtp, the_channel->right->id);
	} else {
		return s1;
	}

	return s2;
}

zrtp_status_t zrtp_test_channel_get(zrtp_test_id_t id,
									zrtp_test_channel_info_t* info) {

	zrtp_test_channel_t *channel = zrtp_test_channel_by_id(id);
	if (channel) {
		zrtp_status_t s;

		zrtp_memset(info, 0, sizeof(zrtp_test_channel_info_t));

		s = zrtp_test_stream_get(channel->left->id, &info->left);
		if (zrtp_status_ok == s) {
			s = zrtp_test_stream_get(channel->right->id, &info->right);
			if (zrtp_status_ok == s) {
				info->is_secure = channel->is_secure;
			}
		}

		return s;
	} else {
		return zrtp_status_bad_param;
	}
}


/******************************************************************************
 * Helpers
 */

zrtp_endpoint_t *zrtp_test_endpoint_by_id(zrtp_test_id_t id) {
	int i;

	if (ZRTP_TEST_UNKNOWN_ID == id) return NULL;

	for (i=0; i<g_test_endpoints_count; i++) {
		if (g_test_endpoints[i].id == id) {
			return &g_test_endpoints[i];
		}
	}

	return NULL;
}

zrtp_test_session_t *zrtp_test_session_by_id(zrtp_test_id_t id) {
	int i, j;

	if (ZRTP_TEST_UNKNOWN_ID == id) return NULL;

	for (i=0; i<g_test_endpoints_count; i++) {
		zrtp_endpoint_t *endpoint = &g_test_endpoints[i];
		if (endpoint->id == ZRTP_TEST_UNKNOWN_ID)
			continue;

		for (j=0; j<endpoint->sessions_count; j++) {
			if (endpoint->sessions[j].id == id) {
				return  &endpoint->sessions[j];
			}
		}
	}

	return NULL;
}

zrtp_test_stream_t *zrtp_test_stream_by_id(zrtp_test_id_t id) {
	int i, j, k;

	if (ZRTP_TEST_UNKNOWN_ID == id) return NULL;

	for (i=0; i<g_test_endpoints_count; i++) {
		zrtp_endpoint_t *endpoint = &g_test_endpoints[i];
		if (endpoint->id == ZRTP_TEST_UNKNOWN_ID)
			continue;

		for (j=0; j<endpoint->sessions_count; j++) {
			zrtp_test_session_t *session = &endpoint->sessions[j];
			if (session->id == ZRTP_TEST_UNKNOWN_ID)
				continue;

			for (k=0; k<session->streams_count; k++) {
				if (session->streams[k].id  == id) {
					return &session->streams[k];
				}
			}
		}
	}

	return NULL;
}

zrtp_test_channel_t *zrtp_test_channel_by_id(zrtp_test_id_t id) {
	int i;
	zrtp_test_channel_t *channel = NULL;

	if (ZRTP_TEST_UNKNOWN_ID == id) return NULL;

	for (i=0; i<g_test_channels_count; i++) {
		if (g_test_channels[i].id != ZRTP_TEST_UNKNOWN_ID && g_test_channels[i].id == id) {
			channel = &g_test_channels[i];
			break;
		}
	}

	return channel;
}

zrtp_test_stream_t *zrtp_test_stream_by_peerid(zrtp_test_id_t id) {
	int i;
	if (ZRTP_TEST_UNKNOWN_ID == id) return NULL;

	for (i=0; i<g_test_channels_count; i++) {
		if (g_test_channels[i].id != ZRTP_TEST_UNKNOWN_ID) {
			if (g_test_channels[i].left->id == id)
				return g_test_channels[i].right;
			else if (g_test_channels[i].right->id == id)
				return g_test_channels[i].left;
		}
	}

	return NULL;
}

zrtp_test_id_t zrtp_test_session_get_stream_by_idx(zrtp_test_id_t session_id, unsigned idx) {
	zrtp_test_session_t *session = zrtp_test_session_by_id(session_id);
	if (session && session->streams_count > idx) {
		return session->streams[idx].id;
	} else {
		return ZRTP_TEST_UNKNOWN_ID;
	}
}

zrtp_stream_t *zrtp_stream_for_test_stream(zrtp_test_id_t stream_id) {
	zrtp_test_stream_t *stream = zrtp_test_stream_by_id(stream_id);
	if (stream) {
		return stream->zrtp;
	} else {
		return NULL;
	}
}

unsigned zrtp_stream_did_event_receive(zrtp_test_id_t stream_id, unsigned event) {
	unsigned i;
	zrtp_test_stream_info_t stream_info;

	zrtp_test_stream_get(stream_id, &stream_info);
	for (i=0; i<stream_info.zrtp_events_count; i++) {
		if (stream_info.zrtp_events_queueu[i] == event)
			break;
	}

	return (i != stream_info.zrtp_events_count);
}