#define MODULE_LOG_PREFIX "relay"

#include "globals.h"

#ifdef MODULE_STREAMRELAY

#if !STATIC_LIBDVBCSA
#include <dlfcn.h>
#endif
#include "module-streamrelay.h"
#include "oscam-chk.h"
#include "oscam-client.h"
#include "oscam-config.h"
#include "oscam-net.h"
#include "oscam-string.h"
#include "oscam-time.h"

#ifdef WITH_EMU
#include "cscrypt/des.h"
#include "module-emulator-osemu.h"
#include "module-emulator-powervu.h"
#endif

#define STREAM_UNDEFINED 0x00
#define STREAM_VIDEO     0x01
#define STREAM_AUDIO     0x02
#define STREAM_SUBTITLE  0x03
#define STREAM_TELETEXT  0x04

extern int32_t exit_oscam;

typedef struct
{
	int32_t connfd;
	int32_t connid;
	char stream_host[256];
} stream_client_conn_data;

char *stream_source_auth = NULL;
uint32_t cluster_size = 50;
bool has_dvbcsa_ecm = 0, is_dvbcsa_static = 1;
#if DVBCSA_KEY_ECM
static uint8_t (*dvbcsa_get_ecm_table_fn)(void) = NULL;
#endif

static uint8_t stream_server_mutex_init = 0;
static pthread_mutex_t stream_server_mutex;
static int32_t glistenfd, mod_idx, gconncount = 0, gconnfd[STREAM_SERVER_MAX_CONNECTIONS], stream_resptime[STREAM_SERVER_MAX_CONNECTIONS];
static char ecm_src[STREAM_SERVER_MAX_CONNECTIONS][9];
static IN_ADDR_T client_ip[STREAM_SERVER_MAX_CONNECTIONS], stream_host_ip[STREAM_SERVER_MAX_CONNECTIONS];
#ifdef WITH_EMU
#define STATIC /* none */
#else
#define STATIC static
#endif
static in_port_t client_port[STREAM_SERVER_MAX_CONNECTIONS];
struct s_client *streamrelay_client[STREAM_SERVER_MAX_CONNECTIONS];

STATIC pthread_mutex_t fixed_key_srvid_mutex;
STATIC uint16_t stream_cur_srvid[STREAM_SERVER_MAX_CONNECTIONS];
STATIC stream_client_key_data key_data[STREAM_SERVER_MAX_CONNECTIONS];

#ifdef WITH_EMU
int8_t stream_server_thread_init = 0;
int8_t emu_stream_emm_enabled = 0;
uint8_t emu_stream_server_mutex_init = 0;
int8_t stream_server_has_ecm[EMU_STREAM_SERVER_MAX_CONNECTIONS];
pthread_mutex_t emu_fixed_key_data_mutex[EMU_STREAM_SERVER_MAX_CONNECTIONS];
emu_stream_client_key_data emu_fixed_key_data[EMU_STREAM_SERVER_MAX_CONNECTIONS];
LLIST *ll_emu_stream_delayed_keys[EMU_STREAM_SERVER_MAX_CONNECTIONS];
#endif

#if DVBCSA_KEY_ECM
static uint16_t last_srvid[STREAM_SERVER_MAX_CONNECTIONS];
#endif

#ifdef MODULE_RADEGAST
static int32_t gRadegastFd = 0;

static bool connect_to_radegast(void)
{
	struct SOCKADDR cservaddr;

	if (gRadegastFd == 0)
		{ gRadegastFd = socket(DEFAULT_AF, SOCK_STREAM, 0); }

	if (gRadegastFd < 0)
	{
		gRadegastFd = 0;
		return false;
	}

	int32_t flags = fcntl(gRadegastFd, F_GETFL);
	fcntl(gRadegastFd, F_SETFL, flags | O_NONBLOCK);

	bzero(&cservaddr, sizeof(cservaddr));
	SIN_GET_FAMILY(cservaddr) = DEFAULT_AF;
	SIN_GET_PORT(cservaddr) = htons(cfg.rad_port);
	SIN_GET_ADDR(cservaddr) = cfg.rad_srvip;

	if (connect(gRadegastFd, (struct sockaddr *)&cservaddr, sizeof(cservaddr)) == -1)
		{ return false; }

	return true;
}

static void close_radegast_connection(void)
{
	close(gRadegastFd);
	gRadegastFd = 0;
}

static bool send_to_radegast(uint8_t* data, int len)
{
	if (send(gRadegastFd, data, len, 0) < 0)
	{
		cs_log("send_to_radegast: Send failure! (errno=%d %s)", errno, strerror(errno));
		return false;
	}
	return true;
}

static void radegast_client_ecm(stream_client_data *cdata)
{
	uint16_t section_length = SCT_LEN(cdata->ecm_data);
	uint8_t md5tmp[MD5_DIGEST_LENGTH];
	MD5(cdata->ecm_data, section_length, md5tmp);

	if (!memcmp(cdata->ecm_md5, md5tmp, MD5_DIGEST_LENGTH))
		{ return; }
	memcpy(cdata->ecm_md5, md5tmp, MD5_DIGEST_LENGTH);

	uint16_t packet_len;
	static uint8_t header_len = 2;
	static uint8_t payload_static_len = 12;

	if (gRadegastFd <= 0)
	{
		cs_log("HTTP stream including ECM header. Connecting radegast server to parse ECM data");
		connect_to_radegast();
	}

	packet_len = header_len + payload_static_len + section_length;
	uint8_t outgoing_data[packet_len];
	outgoing_data[0] = 1;
	outgoing_data[1] = payload_static_len + section_length;
	outgoing_data[2] = 10;  // caid
	outgoing_data[3] = 2;
	outgoing_data[4] = cdata->caid >> 8;
	outgoing_data[5] = cdata->caid & 0xFF;
	outgoing_data[6] = 9;   // srvid
	outgoing_data[7] = 4;
	outgoing_data[8] = cdata->srvid & 0xFF;
	outgoing_data[10] = cdata->srvid >> 8;
	outgoing_data[12] = 3;
	outgoing_data[13] = section_length;

	memcpy(outgoing_data + header_len + payload_static_len, cdata->ecm_data, section_length);

	if (!send_to_radegast(outgoing_data, packet_len))
	{
		close_radegast_connection();
		if (connect_to_radegast())
			{ send_to_radegast(outgoing_data, packet_len); }
	}
}
#endif // MODULE_RADEGAST

#ifdef WITH_EMU
void ParseEcmData(stream_client_data *cdata)
{
	uint8_t *data = cdata->ecm_data;
	uint8_t dcw[16];
	uint16_t section_length = SCT_LEN(data);

	if (section_length < 11)
		{ return; }

	if (caid_is_powervu(cdata->caid))
	{
		if (data[11] > cdata->ecm_nb || (cdata->ecm_nb == 255 && data[11] == 0) || ((cdata->ecm_nb - data[11]) > 5))
		{
			cdata->ecm_nb = data[11];
			cdata->key.connid = cdata->connid;
			powervu_ecm(data, dcw, NULL, cdata->srvid, cdata->caid, cdata->tsid, cdata->onid, cdata->ens, &cdata->key);
		}
	}
#ifdef MODULE_RADEGAST
	else
	{
		cs_strncpy(ecm_src[cdata->connid], "radegast", sizeof(ecm_src[cdata->connid]));
		radegast_client_ecm(cdata);
	}
#endif
}
#else
#define ParseEcmData radegast_client_ecm
#endif

#if DVBCSA_KEY_ECM
#define DVBCSA_HEADER_ECM 1
#define dvbcsa_bs_key_set(a,b) dvbcsa_bs_key_set_ecm(ecm,a,b)
#else
#define DVBCSA_HEADER_ECM 0
#endif
#ifndef STATIC_LIBDVBCSA
#define STATIC_LIBDVBCSA 0
#endif

static void write_cw(ECM_REQUEST *er, int32_t connid)
{
#if DVBCSA_KEY_ECM
	const uint8_t ecm = (cfg.stream_relay_ctab.ctnum == 0 && !select_csa_alt(er))
		? 0
		: get_ecm_mode(er);
#endif
	if (memcmp(er->cw, "\x00\x00\x00\x00\x00\x00\x00\x00", 8) != 0)
	{
		if (has_dvbcsa_ecm)
		{
#ifdef WITH_EMU
			dvbcsa_bs_key_set(er->cw, key_data[connid].key[0][EVEN]);
#else
			dvbcsa_bs_key_set(er->cw, key_data[connid].key[EVEN]);
#endif
		}
	}

	if (memcmp(er->cw + 8, "\x00\x00\x00\x00\x00\x00\x00\x00", 8) != 0)
	{
		if (has_dvbcsa_ecm)
		{
#ifdef WITH_EMU
			dvbcsa_bs_key_set(er->cw + 8, key_data[connid].key[0][ODD]);
#else
			dvbcsa_bs_key_set(er->cw + 8, key_data[connid].key[ODD]);
#endif
		}
	}
#if DVBCSA_KEY_ECM
	if (er->srvid != last_srvid[connid] && ecm != 0 && has_dvbcsa_ecm)
	{
		cs_log(dvbcsa_get_ecm_table_fn
			? "Using ecm_mode=0x%02X, libdvbcsa table 0x%02X"
			: "Using ecm_mode=0x%02X",
			ecm,
			dvbcsa_get_ecm_table_fn ? dvbcsa_get_ecm_table_fn() : 0);
		last_srvid[connid] = er->srvid;
	}
#endif
#ifdef WITH_EMU
	SAFE_MUTEX_LOCK(&emu_fixed_key_data_mutex[connid]);
	emu_fixed_key_data[connid].csa_used = 1;
	SAFE_MUTEX_UNLOCK(&emu_fixed_key_data_mutex[connid]);
#endif
}

static void update_client_info(ECM_REQUEST *er, int32_t connid)
{
	time_t now;
	time(&now);
	if (cfg.stream_display_client)
	{
		streamrelay_client[connid]->ip = stream_host_ip[connid];
		streamrelay_client[connid]->port = cfg.stream_source_port;
	}
	else
	{
		streamrelay_client[connid]->ip = client_ip[connid];
		streamrelay_client[connid]->port = client_port[connid];
	}
	streamrelay_client[connid]->last_srvid = er->srvid;
	streamrelay_client[connid]->last_provid = er->prid;
	streamrelay_client[connid]->last_caid = er->caid;
#ifdef WEBIF
	snprintf(streamrelay_client[connid]->lastreader, sizeof(streamrelay_client[connid]->lastreader), "⇆ %.*s", 11, ecm_src[connid]);
#endif
	streamrelay_client[connid]->cwlastresptime = stream_resptime[connid];
	streamrelay_client[connid]->lastecm = now;
	streamrelay_client[connid]->lastswitch = streamrelay_client[connid]->last = time((time_t *)0); // reset idle-Time & last switch
}

bool stream_write_cw(ECM_REQUEST *er)
{
	int32_t i;
	bool cw_written = false;
	//SAFE_MUTEX_LOCK(&fixed_key_srvid_mutex);
	for (i = 0; i < STREAM_SERVER_MAX_CONNECTIONS; i++)
	{
		if (stream_cur_srvid[i] == er->srvid)
		{
			write_cw(er, i);
			update_client_info(er, i);
			cw_written = true;
			// don't return as there might be more connections for the same channel (e.g. recordings)
		}
	}
	//SAFE_MUTEX_UNLOCK(&fixed_key_srvid_mutex);
	return cw_written;
}

static void SearchTsPackets(const uint8_t *buf, const uint32_t bufLength, uint16_t *packetSize, uint16_t *startOffset)
{
	uint32_t i;

	for (i = 0; i < bufLength; i++)
	{
		if (buf[i] == 0x47)
		{
			// if three packets align, probably safe to assume correct size
			if ((buf[i + 188] == 0x47) & (buf[i + 376] == 0x47))
			{
				(*packetSize) = 188;
				(*startOffset) = i;
				return;
			}
			else if ((buf[i + 204] == 0x47) & (buf[i + 408] == 0x47))
			{
				(*packetSize) = 204;
				(*startOffset) = i;
				return;
			}
			else if ((buf[i + 208] == 0x47) & (buf[i + 416] == 0x47))
			{
				(*packetSize) = 208;
				(*startOffset) = i;
				return;
			}
		}
	}

	(*packetSize) = 0;
	(*startOffset) = 0;
}

typedef void (*ts_data_callback)(stream_client_data *cdata);

static void ParseTsData(const uint8_t table_id, const uint8_t table_mask, const uint8_t min_table_length, int8_t *flag,
						uint8_t *data, const uint16_t data_length, uint16_t *data_pos, const int8_t payloadStart,
						const uint8_t *buf, const int32_t len, ts_data_callback func, stream_client_data *cdata)
{
	int32_t i;
	uint16_t offset = 0;
	bool found_start = 0;

	if (len < 1)
		{ return; }

	if (*flag == 0 && !payloadStart)
		{ return; }

	if (*flag == 0)
	{
		*data_pos = 0;
		offset = 1 + buf[0];
	}
	else if (payloadStart)
		{ offset = 1; }

	if ((len - offset) < 1)
		{ return; }

	const int32_t free_data_length = (data_length - *data_pos);
	const int32_t copySize = (len - offset) > free_data_length ? free_data_length : (len - offset);

	memcpy(data + *data_pos, buf + offset, copySize);
	*data_pos += copySize;

	for (i = 0; i < *data_pos; i++)
	{
		if ((data[i] & table_mask) == table_id)
		{
			if (i != 0)
			{
				if (*data_pos - i > i)
					{ memmove(data, &data[i], *data_pos - i); }
				else
					{ memcpy(data, &data[i], *data_pos - i); }

				*data_pos -= i;
			}
			found_start = 1;
			break;
		}
	}

	const uint16_t section_length = SCT_LEN(data);

	if (!found_start || (section_length > data_length) || (section_length < min_table_length))
	{
		*flag = 0;
		return;
	}

	if ((*data_pos < section_length) || (*data_pos < 3))
	{
		*flag = 2;
		return;
	}

	func(cdata);

	found_start = 0;
	for (i = section_length; i < *data_pos; i++)
	{
		if ((data[i] & table_mask) == table_id)
		{
			if (*data_pos - i > i)
				{ memmove(data, &data[i], *data_pos - i); }
			else
				{ memcpy(data, &data[i], *data_pos - i); }

			*data_pos -= i;
			found_start = 1;
			break;
		}
	}

	if (!found_start || (data_length < *data_pos + copySize + 1))
		{ *data_pos = 0; }

	*flag = 1;
}

static void ParsePatData(stream_client_data *cdata)
{
	int32_t i;
	uint16_t srvid;
#ifdef __BISS__
	cdata->STREAMpidcount = 0;
#endif
	for (i = 8; i + 7 < SCT_LEN(cdata->pat_data); i += 4)
	{
		srvid = b2i(2, cdata->pat_data + i);
		if (srvid == 0)
			{ continue; }

		if (cdata->srvid == srvid)
		{
			cdata->pmt_pid = b2i(2, cdata->pat_data + i + 2) & 0x1FFF;
			cs_log_dbg(D_READER, "Stream client %i found pmt pid: 0x%04X (%i)",
						cdata->connid, cdata->pmt_pid, cdata->pmt_pid);
			break;
		}
	}
}

#ifdef WITH_EMU
static int8_t stream_client_get_caid(stream_client_data *cdata)
{
	uint32_t tmp1 = (cdata->srvid << 16) | cdata->pmt_pid;
	uint8_t tmp2[2];

	if (emu_find_key('A', tmp1, 0, "FAKE", tmp2, 2, 0, 0, 0, NULL))
	{
		cdata->caid = b2i(2, tmp2);
		return 1;
	}
	return 0;
}
#endif

static void ParseDescriptors(const uint8_t *buffer, const uint16_t info_length, uint8_t *type)
{
	uint32_t i;
	uint8_t j, descriptor_length = 0;

	if (info_length < 1)
		{ return; }

	for (i = 0; i + 1 < info_length; i += descriptor_length + 2)
	{
		descriptor_length = buffer[i + 1];
		switch (buffer[i]) // descriptor tag
		{
			case 0x05: // Registration descriptor
			{
				// "HDMV" format identifier is removed
				// Cam does not need to know about Blu-ray
				const char format_identifiers_audio[10][5] =
				{
					"AC-3", "BSSD", "dmat", "DRA1", "DTS1",
					"DTS2", "DTS3", "EAC3", "mlpa", "Opus",
				};
				for (j = 0; j < 10; j++)
				{
					if (memcmp(buffer + i + 2, format_identifiers_audio[j], 4) == 0)
					{
						*type = STREAM_AUDIO;
						break;
					}
				}
				break;
			}
			//case 0x09: // CA descriptor
			//{
			//	break;
			//}
			case 0x46: // VBI teletext descriptor (DVB)
			case 0x56: // teletext descriptor (DVB)
			{
				*type = STREAM_TELETEXT;
				break;
			}
			case 0x59: // subtitling descriptor (DVB)
			{
				*type = STREAM_SUBTITLE;
				break;
			}
			case 0x6A: // AC-3 descriptor (DVB)
			case 0x7A: // enhanced AC-3 descriptor (DVB)
			case 0x7B: // DTS descriptor (DVB)
			case 0x7C: // AAC descriptor (DVB)
			case 0x81: // AC-3 descriptor (ATSC)
			case 0xCC: // Enhanced AC-3 descriptor (ATSC)
			{
				*type = STREAM_AUDIO;
				break;
			}
			case 0x7F: // extension descriptor (DVB)
			{
				switch (buffer[i + 2]) // extension descriptor tag
				{
					case 0x0E: // DTS-HD descriptor (DVB)
					case 0x0F: // DTS Neural descriptor (DVB)
					case 0x15: // AC-4 descriptor (DVB)
						*type = STREAM_AUDIO;
						break;

					case 0x20: // TTML subtitling descriptor (DVB)
						*type = STREAM_SUBTITLE;
						break;

					default:
						*type = STREAM_UNDEFINED;
						break;
				}
				break;
			}
			default:
				break;
		}
	}
}

static void stream_parse_pmt_ca_descriptor(const uint8_t *data, const int32_t data_pos, const int32_t offset, const uint16_t info_length, stream_client_data *cdata)
{
	if (cdata->ecm_pid)
		{ return; }

	// parse program descriptors (we are looking only for CA descriptor here)
	int32_t i;
	uint16_t caid;
	uint8_t descriptor_tag, descriptor_length = 0;

	for (i = offset; i + 1 < offset + info_length; i += descriptor_length + 2)
	{
		descriptor_tag = data[i + data_pos];
		descriptor_length = data[i + 1 + data_pos];
		if (descriptor_length < 1)
			{ break; }

		if (i + 1 + descriptor_length >= offset + info_length)
			{ break; }

		if (descriptor_tag == 0x09 && descriptor_length >= 4)
		{
			caid = b2i(2, data + i + 2 + data_pos);
			if (cfg.stream_relay_ctab.ctnum == 0 || chk_ctab_ex(caid, &cfg.stream_relay_ctab))
			{
				if (cdata->caid == NO_CAID_VALUE)
					{ cdata->caid = caid; }

				if (cdata->caid != caid)
					{ continue; }
				cdata->ecm_pid = b2i(2, data + i + 4 + data_pos) & 0x1FFF;
				cs_log_dbg(D_READER, "Stream client %i found ecm pid: 0x%04X (%i)",
							cdata->connid, cdata->ecm_pid, cdata->ecm_pid);
			}
			else if (cdata->blocked_caid == NO_CAID_VALUE)
			{
				// Remember first blocked CAID for error message
				cdata->blocked_caid = caid;
			}
		}
	}
}

static void ParsePmtData(stream_client_data *cdata)
{
	int32_t i;
	uint16_t program_info_length = 0, es_info_length = 0, elementary_pid;
	const uint16_t section_length = SCT_LEN(cdata->pmt_data);
	uint8_t offset = 0;

	cdata->ecm_pid = 0;
	cdata->pcr_pid = b2i(2, cdata->pmt_data + 8) & 0x1FFF;

	if (cdata->pcr_pid != 0x1FFF)
		{ cs_log_dbg(D_READER, "Stream client %i found pcr pid: 0x%04X (%i)",
					cdata->connid, cdata->pcr_pid, cdata->pcr_pid); }
	program_info_length = b2i(2, cdata->pmt_data + 10) & 0xFFF;
	if (!program_info_length)
	{
		offset = 5;
		program_info_length = (b2i(2, cdata->pmt_data + 10 + offset) & 0xFFF);
	}
	if (12 + offset + program_info_length >= section_length)
		{ return; }
	stream_parse_pmt_ca_descriptor(cdata->pmt_data, 0, 12 + offset, program_info_length, cdata);

	offset = offset == 5 ? 0 : program_info_length;
	for (i = 12 + offset; i + 4 <  section_length; i += 5 + es_info_length)
	{
		elementary_pid = b2i(2, cdata->pmt_data + i + 1) & 0x1FFF;
		es_info_length = b2i(2, cdata->pmt_data + i + 3) & 0xFFF;
		switch (cdata->pmt_data[i]) // stream type
		{
			case 0x01:
			case 0x02:
			case 0x10:
			case 0x1B:
			case 0x20:
			case 0x24:
			case 0x25:
			case 0x42:
			case 0xD1:
			case 0xEA:
			{
#ifdef WITH_EMU
				cdata->video_pid = elementary_pid;
#endif
				cs_log_dbg(D_READER, "Stream client %i found video pid: 0x%04X (%i)",
							cdata->connid, elementary_pid, elementary_pid);
				stream_parse_pmt_ca_descriptor(cdata->pmt_data, i, 5, es_info_length, cdata);
				break;
			}
			case 0x03:
			case 0x04:
			case 0x0F:
			case 0x11:
			case 0x1C:
			case 0x2D:
			case 0x2E:
			case 0x81:
			{
#ifdef WITH_EMU
				if (cdata->audio_pid_count >= EMU_STREAM_MAX_AUDIO_SUB_TRACKS)
				{
					continue;
				}

				cdata->audio_pids[cdata->audio_pid_count] = elementary_pid;
				cdata->audio_pid_count++;
#endif
				cs_log_dbg(D_READER, "Stream client %i found audio pid: 0x%04X (%i)",
							cdata->connid, elementary_pid, elementary_pid);
				break;
			}
			case 0x06:
			//case 0x81: // some ATSC AC-3 streams do not contain the AC-3 descriptor!
			case 0x87:
			{
				uint8_t type = STREAM_UNDEFINED;
				ParseDescriptors(cdata->pmt_data + i + 5, es_info_length, &type);
				if (type == STREAM_AUDIO)
				{
#ifdef WITH_EMU
					if (cdata->audio_pid_count >= EMU_STREAM_MAX_AUDIO_SUB_TRACKS)
					{
						continue;
					}

					cdata->audio_pids[cdata->audio_pid_count] = elementary_pid;
					cdata->audio_pid_count++;
#endif
					cs_log_dbg(D_READER, "Stream client %i found audio pid: 0x%04X (%i)",
								cdata->connid, elementary_pid, elementary_pid);
				}
				else if (type == STREAM_TELETEXT)
				{
#ifdef WITH_EMU
					cdata->teletext_pid = elementary_pid;
#endif
					cs_log_dbg(D_READER, "Stream client %i found teletext pid: 0x%04X (%i)",
								cdata->connid, elementary_pid, elementary_pid);
				}
				break;
			}
		}
#ifdef __BISS__
		cdata->STREAMpids[cdata->STREAMpidcount] = elementary_pid;
		cdata->STREAMpidcount++;
#endif
	}
#ifdef WITH_EMU
	// If we haven't found a CAID for this service,
	// search the keyDB for a fake one
	if (cdata->caid == NO_CAID_VALUE && stream_client_get_caid(cdata) == 1)
	{
		cs_log_dbg(D_READER, "Stream client %i found fake caid: 0x%04X (%i)",
					cdata->connid, cdata->caid, cdata->caid);
	}
#endif
}

#ifdef WITH_EMU
static void ParseCatData(stream_client_data *cdata)
{
	uint8_t *data = cdata->cat_data;
	uint32_t i;

	for (i = 8; i < (b2i(2, data + 1) & 0xFFF) - 1; i += data[i + 1] + 2)
	{
		if (data[i] != 0x09)
		{
			continue;
		}

		uint16_t caid = b2i(2, data + i + 2);

		if (caid_is_powervu(caid)) // add all supported caids here
		{
			if (cdata->caid == NO_CAID_VALUE)
			{
				cdata->caid = caid;
			}
			cdata->emm_pid = b2i(2, data + i + 4) & 0x1FFF;;
			cs_log_dbg(D_READER, "Stream client %i found emm pid: 0x%04X (%i)",
						cdata->connid, cdata->emm_pid, cdata->emm_pid);
			break;
		}
	}
}

static void ParseEmmData(stream_client_data *cdata)
{
	uint32_t keysAdded = 0;

	emu_process_emm(NULL, cdata->caid, cdata->emm_data, &keysAdded);

	if (keysAdded)
	{
		//refresh_entitlements(rdr);
		cs_log("Stream client %i found %i keys", cdata->connid, keysAdded);
	}
}
#endif

static void ParseTsPackets(stream_client_data *data, uint8_t *stream_buf, uint32_t bufLength, uint16_t packetSize)
{
	uint8_t payloadStart;
	uint16_t pid, offset;
	uint32_t i, tsHeader;

	for (i = 0; i < bufLength; i += packetSize)
	{
		tsHeader = b2i(4, stream_buf + i);
		pid = (tsHeader & 0x1FFF00) >> 8;
		payloadStart = (tsHeader & 0x400000) >> 22;

		if (tsHeader & 0x20)
			{ offset = 4 + stream_buf[i + 4] + 1; }
		else
			{ offset = 4; }

		if (packetSize - offset < 1)
			{ continue; }

		if (pid == 0x0000 && data->have_pat_data != 1) // Search the PAT for the PMT pid
		{
			ParseTsData(0x00, 0xFF, 16, &data->have_pat_data, data->pat_data, sizeof(data->pat_data),
						&data->pat_data_pos, payloadStart, stream_buf + i + offset, packetSize - offset, ParsePatData, data);
			continue;
		}

		if (pid == data->pmt_pid && data->have_pmt_data != 1) // Search the PMT for PCR, ECM, Video and Audio pids
		{
			ParseTsData(0x02, 0xFF, 21, &data->have_pmt_data, data->pmt_data, sizeof(data->pmt_data),
						&data->pmt_data_pos, payloadStart, stream_buf + i + offset, packetSize - offset, ParsePmtData, data);
			continue;
		}

		// We have bot PAT and PMT data - No need to search the rest of the packets
		if (data->have_pat_data == 1 && data->have_pmt_data == 1)
			{ break; }
	}
}

#ifdef WITH_EMU
static void decrypt_csa(struct dvbcsa_bs_batch_s *tsbbatch, uint16_t fill[2], const uint8_t oddeven, const int32_t connid, uint8_t cw_type)
#else
static void decrypt_csa(struct dvbcsa_bs_batch_s *tsbbatch, uint16_t fill[2], const uint8_t oddeven, const int32_t connid)
#endif
{
	if (fill[oddeven] > 0)
	{
#if 0
		uint16_t i;
		for (i = fill[oddeven]; i <= cluster_size; i++)
		{
			tsbbatch[i].data = NULL;
			tsbbatch[i].len = 0;
		}
#else
		tsbbatch[fill[oddeven]].data = NULL;
#endif
		cs_log_dbg(D_READER, "dvbcsa (%s), batch=%d", oddeven == ODD ? "odd" : "even", fill[oddeven]);

		fill[oddeven] = 0;

#ifdef WITH_EMU
		dvbcsa_bs_decrypt(key_data[connid].key[cw_type][oddeven], tsbbatch, 184);
#else
		dvbcsa_bs_decrypt(key_data[connid].key[oddeven], tsbbatch, 184);
#endif
	}
}
#ifndef WITH_EMU
#define decrypt(a) decrypt_csa(tsbbatch, fill, a, data->connid)
#else // multiple cw
#define decrypt(a) decrypt_csa(tsbbatch, fill, a, data->connid, 0)
#define decrypt_pvu(a, b) decrypt_csa(tsbbatch, fill, a, data->connid, b)
#endif

#ifdef WITH_EMU
static void DescrambleTsPacketsPowervu(stream_client_data *data, uint8_t *stream_buf, uint32_t bufLength, uint16_t packetSize, struct dvbcsa_bs_batch_s *tsbbatch)
{
	uint32_t i, j, tsHeader, *deskey;
	uint16_t pid, offset, fill[2] = {0,0};
	uint8_t *pdata, payloadStart, oddeven = 0, cw_type = 0;
	int8_t oddKeyUsed;

	for (i = 0; i < bufLength; i += packetSize)
	{
		tsHeader = b2i(4, stream_buf + i);
		pid = (tsHeader & 0x1FFF00) >> 8;
		payloadStart = (tsHeader & 0x400000) >> 22;
		offset = (tsHeader & 0x20) ? 4 + stream_buf[i + 4] + 1 : 4;

		if (packetSize - offset < 1)
		{
			continue;
		}

		if (emu_stream_emm_enabled && pid == 0x0001 && data->have_cat_data != 1) // Search the CAT for EMM pids
		{
			// set to null pid
			stream_buf[i + 1] |= 0x1F;
			stream_buf[i + 2] = 0xFF;

			ParseTsData(0x01, 0xFF, 8, &data->have_cat_data, data->cat_data, sizeof(data->cat_data),
						&data->cat_data_pos, payloadStart, stream_buf + i + offset, packetSize - offset, ParseCatData, data);
			continue;
		}

		if (emu_stream_emm_enabled && data->emm_pid && pid == data->emm_pid) // Process the EMM data
		{
			// set to null pid
			stream_buf[i + 1] |= 0x1F;
			stream_buf[i + 2] = 0xFF;

			ParseTsData(0x80, 0xF0, 3, &data->have_emm_data, data->emm_data, sizeof(data->emm_data),
						&data->emm_data_pos, payloadStart, stream_buf + i + offset, packetSize - offset, ParseEmmData, data);
			continue;
		}

		if (data->ecm_pid && pid == data->ecm_pid) // Process the ECM data
		{
			stream_server_has_ecm[data->connid] = 1;
			// set to null pid
			stream_buf[i + 1] |= 0x1F;
			stream_buf[i + 2] = 0xFF;
			ParseTsData(0x80, 0xFE, 3, &data->have_ecm_data, data->ecm_data, sizeof(data->ecm_data),
						&data->ecm_data_pos, payloadStart, stream_buf + i + offset, packetSize - offset, ParseEcmData, data);
			continue;
		}

		if ((tsHeader & 0xC0) == 0)
		{
			continue;
		}

		if (data->key.csa_used)
		{
			stream_buf[i + 3] &= 0x3f; // consider it decrypted now
			oddeven = (tsHeader & 0xC0) == 0xC0 ? ODD: EVEN;
			decrypt_pvu(oddeven == ODD ? EVEN : ODD, cw_type);

			if (pid == data->video_pid) // start with video pid, since it is most dominant
			{
				cw_type = PVU_CW_VID;
				tsbbatch[fill[oddeven]].data = &stream_buf[i + offset];
				tsbbatch[fill[oddeven]].len = packetSize - offset;
				fill[oddeven]++;
				if (fill[oddeven] > cluster_size - 1)
				{
					decrypt_pvu(oddeven, cw_type);
				}
			}
			else
			{
				if (cw_type != PVU_CW_VID)
				{
					decrypt_pvu(oddeven, PVU_CW_VID);
				}
				for (j = 0; j < data->audio_pid_count; j++)
				{
					if (pid == data->audio_pids[j])
					{
						if (cw_type != PVU_CW_A1 + j)
						{
							decrypt_pvu(oddeven, PVU_CW_A1 + j);
						}
						cw_type = PVU_CW_A1 + j;
						tsbbatch[fill[oddeven]].data = &stream_buf[i + offset];
						tsbbatch[fill[oddeven]].len = packetSize - offset;
						fill[oddeven]++;
						if (fill[oddeven] > cluster_size - 1)
						{
							decrypt_pvu(oddeven, cw_type);
						}
					}
				}
			}
		}
		else
		{
			oddKeyUsed = (tsHeader & 0xC0) == 0xC0 ? 1 : 0;
			deskey = NULL;

			if (pid == data->video_pid)
			{
				deskey = data->key.pvu_des_ks[PVU_CW_VID][oddKeyUsed];
			}
			else
			{
				for (j = 0; j < data->audio_pid_count; j++)
				{
					if (pid == data->audio_pids[j])
					{
						deskey = data->key.pvu_des_ks[PVU_CW_A1 + j][oddKeyUsed];
					}
				}
			}

			if (deskey == NULL)
			{
				deskey = data->key.pvu_des_ks[PVU_CW_HSD][oddKeyUsed];
			}

			for (j = offset; j + 7 < 188; j += 8)
			{
				pdata = stream_buf + i + j;
				des(pdata, deskey, 0);
			}

			stream_buf[i + 3] &= 0x3F;
		}
	}
	if (data->key.csa_used) { decrypt_pvu(oddeven, cw_type); }
}
static void DescrambleTsPacketsRosscrypt1(emu_stream_client_data *data, uint8_t *stream_buf, uint32_t bufLength, uint16_t packetSize)
{
	int8_t is_av_pid;
	int32_t j;

	uint8_t scramblingControl;
	uint16_t pid, offset;
	uint32_t i, tsHeader;

	for (i = 0; i < bufLength; i += packetSize)
	{
		tsHeader = b2i(4, stream_buf + i);
		pid = (tsHeader & 0x1FFF00) >> 8;
		scramblingControl = tsHeader & 0xC0;

		if (tsHeader & 0x20)
		{
			offset = 4 + stream_buf[i + 4] + 1;
		}
		else
		{
			offset = 4;
		}

		if (packetSize - offset < 1)
		{
			continue;
		}

		if (scramblingControl == 0)
		{
			continue;
		}

		if (!(stream_buf[i + 3] & 0x10))
		{
			stream_buf[i + 3] &= 0x3F;
			continue;
		}

		is_av_pid = 0;

		if (pid == data->video_pid)
		{
			is_av_pid = 1;
		}
		else
		{
			for (j = 0; j < data->audio_pid_count; j++)
			{
				if (pid == data->audio_pids[j])
				{
					is_av_pid = 1;
					break;
				}
			}
		}

		if (is_av_pid)
		{
			static uint8_t dyn_key[184];
			static uint8_t last_packet[184];

			// Reset key on channel change
			if (data->reset_key_data == 1)
			{
				memset(dyn_key, 0x00, 184);
				memset(last_packet, 0x00, 184);
				data->reset_key_data = 0;
			}

			if (memcmp(last_packet, stream_buf + i + 4, 184) == 0)
			{
				if (memcmp(dyn_key, stream_buf + i + 4, 184) != 0)
				{
					memcpy(dyn_key, stream_buf + i + 4, 184);
				}
			}
			else
			{
				memcpy(last_packet, stream_buf + i + 4, 184);
			}

			for (j = 0; j < 184; j++)
			{
				stream_buf[i + 4 + j] ^= dyn_key[j];
			}

			stream_buf[i + 3] &= 0x3F;
		}
	}
}

static void DescrambleTsPacketsCompel(emu_stream_client_data *data, uint8_t *stream_buf, uint32_t bufLength, uint16_t packetSize)
{
	int8_t is_pes_pid; // any PES pid
	int32_t j;
	const int8_t limit = 4;

	uint8_t scramblingControl;
	uint16_t pid, offset;
	uint32_t i, tsHeader;

	for (i = 0; i < bufLength; i += packetSize)
	{
		tsHeader = b2i(4, stream_buf + i);
		pid = (tsHeader & 0x1FFF00) >> 8;
		scramblingControl = tsHeader & 0xC0;

		if (tsHeader & 0x20)
		{
			offset = 4 + stream_buf[i + 4] + 1;
		}
		else
		{
			offset = 4;
		}

		if (packetSize - offset < 1)
		{
			continue;
		}

		if (scramblingControl == 0)
		{
			continue;
		}

		if (!(stream_buf[i + 3] & 0x10))
		{
			stream_buf[i + 3] &= 0x3F;
			continue;
		}

		is_pes_pid = 0;

		if (pid == data->video_pid)
		{
			is_pes_pid = 1;
		}
		else if (pid == data->teletext_pid)
		{
			is_pes_pid = 1;
		}
		else
		{
			for (j = 0; j < data->audio_pid_count; j++)
			{
				if (pid == data->audio_pids[j])
				{
					is_pes_pid = 1;
					break;
				}
			}
		}

		if (is_pes_pid)
		{
			static uint8_t dyn_key[184];
			static uint8_t found_key_bytes[184];
			static uint8_t found_key_bytes_count = 8;
			static uint8_t lastScramblingControl = 0xFF;

			int8_t matches00 = 0;
			int8_t matchesFF = 0;
			int8_t last00_was_good = 0;
			int8_t lastFF_was_good = 0;

			// Reset key when scrambling control changes from odd to even
			// and vice versa (every ~53 seconds) or when we change channel
			if (lastScramblingControl != scramblingControl)
			{
				memset(dyn_key, 0x00, 184);
				memset(found_key_bytes, 0, 184);
				found_key_bytes_count = 8;
				lastScramblingControl = scramblingControl;

				//cs_log_dbg(D_READER, "resetting key data (scrambling control: %02X)", scramblingControl);
			}

			for (j = 8; j < 184; j++)
			{
				if (found_key_bytes_count == 184)
				{
					break;
				}

				if (stream_buf[i + 4 + j] == 0x00)
				{
					last00_was_good = 1;
					matches00++;

					if (matches00 > limit && found_key_bytes[j] == 0)
					{
						dyn_key[j] = 0x00;
						found_key_bytes[j] = 1;
						found_key_bytes_count++;
					}
				}
				else if (stream_buf[i + 4 + j] == 0x3F)
				{
					last00_was_good = 1;
					matches00++;

					if (matches00 > limit && found_key_bytes[j] == 0)
					{
						dyn_key[j] = 0x3F;
						found_key_bytes[j] = 1;
						found_key_bytes_count++;
					}
				}
				else
				{
					if (last00_was_good == 1)
					{
						last00_was_good = 0;
						matches00--;
					}
					else
					{
						matches00 -= 2;
					}

					if (matches00 < 0)
					{
						matches00 = 0;
					}
				}

				if (stream_buf[i + 4 + j] == 0xC0)
				{
					lastFF_was_good = 1;
					matchesFF++;

					if (matchesFF > limit && found_key_bytes[j] == 0)
					{
						dyn_key[j] = 0x3F;
						found_key_bytes[j] = 1;
						found_key_bytes_count++;
					}
				}
				else if (stream_buf[i + 4 + j] == 0xFF)
				{
					lastFF_was_good = 1;
					matchesFF++;

					if (matchesFF > limit && found_key_bytes[j] == 0)
					{
						dyn_key[j] = 0x00;
						found_key_bytes[j] = 1;
						found_key_bytes_count++;
					}
				}
				else
				{
					if (lastFF_was_good == 1)
					{
						lastFF_was_good = 0;
						matchesFF--;
					}
					else
					{
						matchesFF -= 2;
					}

					if (matchesFF < 0)
					{
						matchesFF = 0;
					}
				}
			}

			for (j = 183; j >= 8; j--)
			{
				if (found_key_bytes_count == 184)
				{
					break;
				}

				if (stream_buf[i + 4 + j] == 0x00)
				{
					last00_was_good = 1;
					matches00++;

					if (matches00 > limit && found_key_bytes[j] == 0)
					{
						dyn_key[j] = 0x00;
						found_key_bytes[j] = 1;
						found_key_bytes_count++;
					}
				}
				else if (stream_buf[i + 4 + j] == 0x3F)
				{
					last00_was_good = 1;
					matches00++;

					if (matches00 > limit && found_key_bytes[j] == 0)
					{
						dyn_key[j] = 0x3F;
						found_key_bytes[j] = 1;
						found_key_bytes_count++;
					}
				}
				else
				{
					if (last00_was_good == 1)
					{
						last00_was_good = 0;
						matches00--;
					}
					else
					{
						matches00 -= 2;
					}

					if (matches00 < 0)
					{
						matches00 = 0;
					}
				}

				if (stream_buf[i + 4 + j] == 0xC0)
				{
					lastFF_was_good = 1;
					matchesFF++;

					if (matchesFF > limit && found_key_bytes[j] == 0)
					{
						dyn_key[j] = 0x3F;
						found_key_bytes[j] = 1;
						found_key_bytes_count++;
					}
				}
				else if (stream_buf[i + 4 + j] == 0xFF)
				{
					lastFF_was_good = 1;
					matchesFF++;

					if (matchesFF > limit && found_key_bytes[j] == 0)
					{
						dyn_key[j] = 0x00;
						found_key_bytes[j] = 1;
						found_key_bytes_count++;
					}
				}
				else
				{
					if (lastFF_was_good == 1)
					{
						lastFF_was_good = 0;
						matchesFF--;
					}
					else
					{
						matchesFF -= 2;
					}

					if (matchesFF < 0)
					{
						matchesFF = 0;
					}
				}
			}

			for (j = 8; j < 184; j++)
			{
				stream_buf[i + 4 + j] ^= dyn_key[j];
			}
		}

		stream_buf[i + 3] &= 0x3F; // Clear scrambling bits
	}
}
#endif // WITH_EMU

static void DescrambleTsPackets(stream_client_data *data, uint8_t *stream_buf, uint32_t bufLength, uint16_t packetSize, struct dvbcsa_bs_batch_s *tsbbatch)
{
	uint32_t i, tsHeader;
	uint16_t offset, fill[2] = {0,0};
	uint8_t oddeven = 0;
#ifdef MODULE_RADEGAST
	uint16_t pid;
	uint8_t payloadStart;
#endif

	for (i = 0; i < bufLength; i += packetSize)
	{
		tsHeader = b2i(4, stream_buf + i);
#ifdef MODULE_RADEGAST
		pid = (tsHeader & 0x1FFF00) >> 8;
		payloadStart = (tsHeader & 0x400000) >> 22;
#endif
		offset = (tsHeader & 0x20) ? 4 + stream_buf[i + 4] + 1 : 4;
		if (packetSize - offset < 1)
			{ continue; }
#ifdef MODULE_RADEGAST
#ifdef __BISS__
		if(data->ecm_pid == 0x1FFF && caid_is_biss_fixed(data->caid))
		{
			uint32_t j, n;
			uint16_t ecm_len = 7;
			data->ecm_data[0] = 0x80; // to pass the cache check it must be 0x80 or 0x81
			data->ecm_data[1] = 0x00;
			data->ecm_data[2] = 0x04;
			i2b_buf(2, data->srvid, data->ecm_data + 3);
			i2b_buf(2, data->pmt_pid, data->ecm_data + 5);
			for(j = 0, n = 7; j < data->STREAMpidcount; j++, n += 2)
			{
				i2b_buf(2, data->STREAMpids[j], data->ecm_data + n);
				data->ecm_data[2] += 2;
				ecm_len += 2;
			}
			data->ens &= 0x0FFFFFFF; // clear top 4 bits (in case of DVB-T/C or garbage), prepare for flagging
			data->ens |= 0xA0000000; // flag to emu: this is the namespace, not a pid
			i2b_buf(2, data->tsid, data->ecm_data + ecm_len);     // place tsid after the last stream pid
			i2b_buf(2, data->onid, data->ecm_data + ecm_len + 2); // place onid right after tsid
			i2b_buf(4, data->ens, data->ecm_data + ecm_len + 4);  // place namespace at the end of the ecm
			data->ecm_data[2] += 8;
			ParseEcmData(data);
		} else
#endif // __BISS__
		if (data->ecm_pid && pid == data->ecm_pid) // Process the ECM data
		{
			// set to null pid
			stream_buf[i + 1] |= 0x1F;
			stream_buf[i + 2] = 0xFF;
			ParseTsData(0x80, 0xFE, 3, &data->have_ecm_data, data->ecm_data, sizeof(data->ecm_data),
						&data->ecm_data_pos, payloadStart, stream_buf + i + offset, packetSize - offset, ParseEcmData, data);
			continue;
		}
#endif // MODULE_RADEGAST
		if ((tsHeader & 0xC0) == 0)
			{ continue; }

		stream_buf[i + 3] &= 0x3f; // consider it decrypted now
		oddeven = (tsHeader & 0xC0) == 0xC0 ? ODD: EVEN;
		decrypt(oddeven == ODD ? EVEN : ODD);
		tsbbatch[fill[oddeven]].data = &stream_buf[i + offset];
		tsbbatch[fill[oddeven]].len = packetSize - offset;
		fill[oddeven]++;

		if (fill[oddeven] > cluster_size - 1)
			{ decrypt(oddeven); }
	}

	decrypt(oddeven);
}

static void http_log(char *log_txt, int32_t status, const char *msg)
{
	char *msg_stripped = remove_newline_chars(msg);
	cs_log_dbg(D_CLIENT, log_txt, status, msg_stripped);
	NULLFREE(msg_stripped);
}

static int32_t switch_stream_source_host(stream_client_conn_data *conndata, int8_t *errorCount, const char *msg)
{
	if (cfg.stream_client_source_host && !streq(conndata->stream_host, cfg.stream_source_host) && *errorCount > 0)
	{
		cs_strncpy(conndata->stream_host, cfg.stream_source_host, sizeof(conndata->stream_host));
		cs_log("FALLBACK: stream client %i - try using stream source host from config. host=%s reason=%s", conndata->connid, conndata->stream_host, msg);
		(*errorCount)--;
		return 1;
	}
	return 0;
}

static int32_t connect_to_stream(char *http_buf, int32_t http_buf_len, char *stream_path, char *stream_source_host, IN_ADDR_T *in_addr)
{
	struct SOCKADDR cservaddr;
	struct utsname buffer; uname(&buffer);
	int32_t streamfd, status;

	if ((streamfd = socket(DEFAULT_AF, SOCK_STREAM, 0)) == -1)
		{ return streamfd; }

	struct timeval tv;
	tv.tv_sec = 2;
	tv.tv_usec = 0;
	if (setsockopt(streamfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof tv))
	{
		cs_log("ERROR: setsockopt() failed for SO_RCVTIMEO!");
		close(streamfd);
		return -1;
	}

	bzero(&cservaddr, sizeof(cservaddr));
	SIN_GET_FAMILY(cservaddr) = DEFAULT_AF;
	SIN_GET_PORT(cservaddr) = htons(cfg.stream_source_port);
	cs_resolve(stream_source_host, in_addr, NULL, NULL);
	SIN_GET_ADDR(cservaddr) = *in_addr;

	if ((status = connect(streamfd, (struct sockaddr *)&cservaddr, sizeof(cservaddr))) == -1)
	{
		cs_log("WARNING: Connect to stream source port %d failed.", cfg.stream_source_port);
		close(streamfd);
		return status;
	}

	snprintf(http_buf, http_buf_len,
			"GET %s HTTP/1.1\nHost: %s:%u\n"
			"User-Agent: Mozilla/5.0 (%s %s %s; rv:133.0) Gecko/20100101 Firefox/133.0\n"
			"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n"
			"Accept-Language: en-US"
			"%s%s\n"
			"Connection: keep-alive\n\n",
			stream_path,
			cs_inet_ntoa(*in_addr),
			cfg.stream_source_port,
#if defined(__linux__)
			"X11;", buffer.sysname, buffer.machine,
#else
			"Windows NT 10.0;", "Win64;", "x64",
#endif
			(stream_source_auth) ? "\nAuthorization: Basic " : "",
			(stream_source_auth) ? stream_source_auth : "");

	status = send(streamfd, http_buf, cs_strlen(http_buf), 0);
	http_log("HTTP (send) (%i): %s", status, http_buf);
	return status == -1 ? status : streamfd;
}

static void stream_client_disconnect(stream_client_conn_data *conndata)
{
	int32_t i;

	SAFE_MUTEX_LOCK(&fixed_key_srvid_mutex);
	stream_cur_srvid[conndata->connid] = NO_SRVID_VALUE;
#if DVBCSA_KEY_ECM
	last_srvid[conndata->connid] = NO_SRVID_VALUE;
#endif
#ifdef WITH_EMU
	stream_server_has_ecm[conndata->connid] = 0;
#endif
	SAFE_MUTEX_UNLOCK(&fixed_key_srvid_mutex);

	SAFE_MUTEX_LOCK(&stream_server_mutex);
	for (i = 0; i < STREAM_SERVER_MAX_CONNECTIONS; i++)
	{
		if (gconnfd[i] == conndata->connfd)
		{
			gconnfd[i] = -1;
			gconncount--;
		}
	}
	SAFE_MUTEX_UNLOCK(&stream_server_mutex);

	if (conndata->connfd >= 0)
	{
		shutdown(conndata->connfd, 2);
		close(conndata->connfd);
	}

	cs_log("Stream client %i disconnected. ip=%s port=%d", conndata->connid, cs_inet_ntoa(client_ip[conndata->connid]), client_port[conndata->connid]);

	if (streamrelay_client[conndata->connid] && !cfg.stream_reuse_client && !streamrelay_client[conndata->connid]->kill_started)
	{
		cs_disconnect_client(streamrelay_client[conndata->connid]);
		free_client(streamrelay_client[conndata->connid]);
	}

	NULLFREE(conndata);
}

static void streamrelay_auth_client(struct s_client *cl)
{
	int32_t ok = 0;
	struct s_auth *account;

	for (account = cfg.account; cfg.stream_relay_user && account; account = account->next)
	{
		if ((ok = streq(cfg.stream_relay_user, account->usr)))
			{ break; }
	}

	cs_auth_client(cl, ok ? account : (struct s_auth *)(-1), "streamrelay");
}

static void create_streamrelay_client(stream_client_conn_data *conndata)
{
	int32_t i, exists = 0;

	if (cfg.stream_reuse_client)
	{
		for (i = 0; i < STREAM_SERVER_MAX_CONNECTIONS; i++)
		{
			if (streamrelay_client[i])
			{
				if (!streamrelay_client[i]->kill)
				{
					streamrelay_client[conndata->connid] = streamrelay_client[i];
					exists = 1;
					break;
				}
			}
		}
	}

	if (!exists)
		{ streamrelay_client[conndata->connid] = create_client(client_ip[conndata->connid]); }

	streamrelay_client[conndata->connid]->typ = 'c';
	streamrelay_client[conndata->connid]->module_idx = mod_idx;
	streamrelay_client[conndata->connid]->thread = pthread_self();
	streamrelay_client[conndata->connid]->ip = client_ip[conndata->connid];
	streamrelay_client[conndata->connid]->port = client_port[conndata->connid];
#ifdef WEBIF
	streamrelay_client[conndata->connid]->wihidden = cfg.stream_hide_client;
#endif
}

static void *stream_client_handler(void *arg)
{
	stream_client_conn_data *conndata = (stream_client_conn_data *)arg;
	stream_client_data *data = NULL;

	char *http_buf = NULL, stream_path[255], stream_path_copy[255];
	char *saveptr, *token, http_version[4], http_host[256];

	int8_t streamConnectErrorCount = 0, streamDataErrorCount = 0, streamReconnectCount = 0;
	int32_t bytesRead = 0, http_status_code = 0;
	int32_t i, clientStatus, streamStatus, streamfd, last_streamfd = 0;

	uint8_t *stream_buf = NULL;
	uint16_t packetCount = 0, packetSize = 0, startOffset = 0;
	uint32_t remainingDataPos, remainingDataLength, tmp_pids[4];
	uint8_t descrambling = 0;

	struct timeb start, end;
#ifdef WITH_EMU
	int32_t cur_dvb_buffer_size, cur_dvb_buffer_wait;
#else
	const int32_t cur_dvb_buffer_size = DVB_BUFFER_SIZE_CSA;
	const int32_t cur_dvb_buffer_wait = DVB_BUFFER_WAIT_CSA;
#endif
	struct dvbcsa_bs_batch_s *tsbbatch = NULL;

	create_streamrelay_client(conndata);
	SAFE_SETSPECIFIC(getclient, streamrelay_client[conndata->connid]);
	set_thread_name(__func__);

	if (!streamrelay_client[conndata->connid]->init_done)
	{
		streamrelay_auth_client(streamrelay_client[conndata->connid]);
		streamrelay_client[conndata->connid]->init_done = 1;
	}

	cs_log("Stream client %i connected. ip=%s port=%d", conndata->connid, cs_inet_ntoa(client_ip[conndata->connid]), client_port[conndata->connid]);

	do
	{
		if (!cs_malloc(&http_buf, 1024) ||
				!cs_malloc(&stream_buf, DVB_BUFFER_SIZE) ||
				!cs_malloc(&data, sizeof(stream_client_data)))
			{ break; }

		clientStatus = recv(conndata->connfd, http_buf, 1024, 0);
		http_log("HTTP (recv) (%i): %s", clientStatus, http_buf);

		if (clientStatus < 1)
			{ break; }

		http_buf[1023] = '\0';
		if (sscanf(http_buf, "GET %254s HTTP/%3s Host: %255s ", stream_path, http_version, http_host) < 1)
			{ break; }
		else
		{
			// use stream_source_host variable from config if host discovery is disabled
			if (!cfg.stream_client_source_host)
				{ cs_strncpy(conndata->stream_host, cfg.stream_source_host, sizeof(conndata->stream_host)); }
			// use host from stream client http request, if 'Host: host:port' header was send
			else if ((token = strchr(http_host,':')))
			{
				size_t len = (size_t)(token - http_host);
				if (len >= sizeof(conndata->stream_host))
					{ len = sizeof(conndata->stream_host) - 1; }
				memcpy(conndata->stream_host, http_host, len);
				conndata->stream_host[len] = '\0';
			}
			// use the IP address of the stream client itself
			else
				{ cs_strncpy(conndata->stream_host, cs_inet_ntoa(client_ip[conndata->connid]), sizeof(conndata->stream_host)); }
		}

		cs_strncpy(stream_path_copy, stream_path, sizeof(stream_path));

		token = strtok_r(stream_path_copy, ":", &saveptr); // token 0
		for (i = 1; token != NULL && i < 7; i++) // tokens 1 to 6
		{
			token = strtok_r(NULL, ":", &saveptr);
			if (token == NULL)
				{ break; }

			if (i >= 3) // We olny need token 3 (srvid), 4 (tsid), 5 (onid) and 6 (ens)
			{
				if (sscanf(token, "%x", &tmp_pids[i - 3]) != 1)
					{ tmp_pids[i - 3] = 0; }
			}
		}

		data->srvid = tmp_pids[0] & 0xFFFF;
		data->tsid = tmp_pids[1] & 0xFFFF;
		data->onid = tmp_pids[2] & 0xFFFF;
		data->ens = tmp_pids[3];

		if (data->srvid == 0) // We didn't get a srvid - Exit
			{ break; }

#ifndef WITH_EMU
		key_data[conndata->connid].key[ODD]  = dvbcsa_bs_key_alloc();
		key_data[conndata->connid].key[EVEN] = dvbcsa_bs_key_alloc();
#else
		for (i = 0; i < EMU_STREAM_MAX_AUDIO_SUB_TRACKS + 2; i++)
		{
			key_data[conndata->connid].key[i][ODD]  = dvbcsa_bs_key_alloc();
			key_data[conndata->connid].key[i][EVEN] = dvbcsa_bs_key_alloc();
		}
#endif

		if (!cs_malloc(&tsbbatch, (cluster_size + 1) * sizeof(struct dvbcsa_bs_batch_s)))
			{ break; }

		SAFE_MUTEX_LOCK(&fixed_key_srvid_mutex);
		stream_cur_srvid[conndata->connid] = data->srvid;
#ifdef WITH_EMU
		stream_server_has_ecm[conndata->connid] = 0;
#endif
		SAFE_MUTEX_UNLOCK(&fixed_key_srvid_mutex);

		cs_log("Stream client %i request. host=%s port=%d path=%s (%s)", conndata->connid, conndata->stream_host, cfg.stream_source_port, stream_path, !cfg.stream_client_source_host ? "config" : strchr(http_host,':') ? "http header" : "client ip");

		cs_log_dbg(D_READER, "Stream client %i received srvid: %04X tsid: %04X onid: %04X ens: %08X",
					conndata->connid, data->srvid, data->tsid, data->onid, data->ens);

		snprintf(http_buf, 1024,
				"HTTP/1.0 200 OK\n"
				"Connection: Close\n"
				"Content-Type: video/mpeg\n"
				"Server: stream_enigma2\n\n");
		clientStatus = send(conndata->connfd, http_buf, cs_strlen(http_buf), 0);
		http_log("HTTP (send) (%i): %s", clientStatus, http_buf);

		data->connid = conndata->connid;
		data->caid = NO_CAID_VALUE;
		data->blocked_caid = NO_CAID_VALUE;
		data->have_pat_data = 0;
		data->have_pmt_data = 0;
		data->have_cat_data = 0;
		data->have_ecm_data = 0;
		data->have_emm_data = 0;
#ifdef WITH_EMU
		data->reset_key_data = 1;
#endif

		while (!exit_oscam && clientStatus != -1 && streamConnectErrorCount < 3
				&& streamDataErrorCount < 15)
		{
			streamfd = connect_to_stream(http_buf, 1024, stream_path, conndata->stream_host, &stream_host_ip[conndata->connid]);
			if (streamfd == -1)
			{
				if (switch_stream_source_host(conndata, &streamConnectErrorCount, "no connection")) // fallback
					{ continue; }
				cs_log("WARNING: stream client %i - cannot connect to stream source host. ip=%s port=%d path=%s", conndata->connid, cs_inet_ntoa(stream_host_ip[conndata->connid]), cfg.stream_source_port, stream_path);
				streamConnectErrorCount++;
				cs_sleepms(500);
				continue;
			}

			streamStatus = 0;
			bytesRead = 0;
			while (!exit_oscam && clientStatus != -1 && streamStatus != -1
#if 0
					&& streamConnectErrorCount < 3 && streamDataErrorCount < 15)
#else
					&& (streamConnectErrorCount < 3 || streamDataErrorCount < 15))
#endif
			{
#ifdef WITH_EMU
				if (data->key.csa_used)
				{
					cur_dvb_buffer_size = EMU_DVB_BUFFER_SIZE_CSA;
					cur_dvb_buffer_wait = EMU_DVB_BUFFER_WAIT_CSA;
				}
				else
				{
					cur_dvb_buffer_size = EMU_DVB_BUFFER_SIZE_DES;
					cur_dvb_buffer_wait = EMU_DVB_BUFFER_WAIT_DES;
				}
#endif
				if (!streamrelay_client[conndata->connid] || streamrelay_client[conndata->connid]->kill)
				{
					clientStatus = -1;
					break;
				}

				cs_ftime(&start);
				streamStatus = recv(streamfd, stream_buf + bytesRead, cur_dvb_buffer_size - bytesRead, MSG_WAITALL);
				if (streamStatus == 0) // socket closed
				{
					cs_log_dbg(D_CLIENT, "STATUS: streamStatus=%i, streamfd=%i, last_streamfd=%i, streamConnectErrorCount=%i, streamDataErrorCount=%i, bytesRead=%i",
							streamStatus, streamfd, last_streamfd, streamConnectErrorCount, streamDataErrorCount, bytesRead);
					if (streamfd == last_streamfd)
					{
						cs_log("WARNING: stream client %i - stream source closed connection.", conndata->connid);
						switch_stream_source_host(conndata, &streamDataErrorCount, "connection closed"); // fallback
					}
					http_log("HTTP (recv) (%i): %s", streamStatus, (const char*)stream_buf);
					streamConnectErrorCount++;
					cs_sleepms(100);
					break;
				}

				if (streamStatus < 0) // error
				{
					http_log("HTTP (recv) (%i): %s", streamStatus, (const char*)stream_buf);
					if ((errno == EWOULDBLOCK) | (errno == EAGAIN))
					{
						if (cfg.stream_relay_reconnect_count > 0)
						{
							streamReconnectCount++; // 2 sec timeout * cfg.stream_relay_reconnect_count = seconds no data -> close
							cs_log("WARNING: stream client %i no data from stream source. Trying to reconnect (%i/%i)", conndata->connid, streamReconnectCount, cfg.stream_relay_reconnect_count);
							if (streamReconnectCount >= cfg.stream_relay_reconnect_count)
							{
								clientStatus = -1;
								break;
							}
						}
						else
						{
							cs_log("WARNING: stream client %i no data from stream source.", conndata->connid);
						}
						streamDataErrorCount++; // 2 sec timeout * 15 = seconds no data -> close
						cs_sleepms(100);
						continue;
					}
					cs_log("WARNING: stream client %i error receiving data from stream source.", conndata->connid);
					streamConnectErrorCount++;
					cs_sleepms(100);
					break;
				}

				if (streamStatus < cur_dvb_buffer_size - bytesRead) // probably just received header but no stream
				{
					if (!bytesRead && streamStatus > 13 &&
						sscanf((const char*)stream_buf, "HTTP/%3s %d ", http_version , &http_status_code) == 2 &&
						http_status_code != 200)
					{
						if (switch_stream_source_host(conndata, &streamConnectErrorCount, "bad response")) // fallback
							{ break; }
						http_log("HTTP (recv) (%i): %s", streamStatus, (const char*)stream_buf);
						cs_log("ERROR: stream client %i got %d response from stream source!", conndata->connid, http_status_code);
						streamConnectErrorCount++;
						cs_sleepms(100);
						break;
					}
					else
					{
						cs_log_dbg(0, "WARNING: stream client %i non-full buffer from stream source.", conndata->connid);
						streamDataErrorCount++;
						cs_sleepms(100);
					}
				}
				else
				{
					streamDataErrorCount = 0;
				}

				streamConnectErrorCount = 0;
				bytesRead += streamStatus;

				if (bytesRead >= cur_dvb_buffer_wait)
				{
					startOffset = 0;

					// only search if not starting on ts packet or unknown packet size
					if (stream_buf[0] != 0x47 || packetSize == 0)
						{ SearchTsPackets(stream_buf, bytesRead, &packetSize, &startOffset); }

					if (packetSize == 0)
						{ bytesRead = 0; }
					else
					{
						packetCount = ((bytesRead - startOffset) / packetSize);

						// We have both PAT and PMT data - We can start descrambling
						if (data->have_pat_data == 1 && data->have_pmt_data == 1)
						{
							if (data->caid == NO_CAID_VALUE && cfg.stream_relay_ctab.ctnum > 0)
							{
								// No allowed CAID found but CAID filter is configured, disconnect client
								cs_log("Stream client %i caid %04X not enabled in stream relay config",
											conndata->connid, data->blocked_caid);
								clientStatus = -1;
								break;
							}
							if (cfg.stream_relay_ctab.ctnum == 0 || chk_ctab_ex(data->caid, &cfg.stream_relay_ctab))
							{
#ifdef WITH_EMU
								if (caid_is_powervu(data->caid))
								{
									DescrambleTsPacketsPowervu(data, stream_buf + startOffset, packetCount * packetSize, packetSize, tsbbatch);
								}
								else if (data->caid == 0xA101) // Rosscrypt1
								{
									DescrambleTsPacketsRosscrypt1(data, stream_buf + startOffset, packetCount * packetSize, packetSize);
								}
								else if (data->caid == NO_CAID_VALUE) // Compel
								{
									DescrambleTsPacketsCompel(data, stream_buf + startOffset, packetCount * packetSize, packetSize);
								}
								else
#endif // WITH_EMU
								{
									DescrambleTsPackets(data, stream_buf + startOffset, packetCount * packetSize, packetSize, tsbbatch);
								}
								if (!descrambling)
								{
									if (cfg.stream_relay_buffer_time)
										{ cs_sleepms(cfg.stream_relay_buffer_time); }
									descrambling = 1;
								}
							}
						}
						else // Search PAT and PMT packets for service information
						{
							ParseTsPackets(data, stream_buf + startOffset, packetCount * packetSize, packetSize);
						}

						clientStatus = send(conndata->connfd, stream_buf + startOffset, packetCount * packetSize, 0);

						remainingDataPos = startOffset + (packetCount * packetSize);
						remainingDataLength = bytesRead - remainingDataPos;

						if (remainingDataPos < remainingDataLength)
							{ memmove(stream_buf, stream_buf + remainingDataPos, remainingDataLength); }
						else
							{ memcpy(stream_buf, stream_buf + remainingDataPos, remainingDataLength); }

						bytesRead = remainingDataLength;
					}
				}
				cs_ftime(&end);
				stream_resptime[conndata->connid] = comp_timeb(&end, &start);
			}

			last_streamfd = streamfd;
			close(streamfd);
		}
	} while (0);

	NULLFREE(http_buf);
	NULLFREE(stream_buf);
#ifndef WITH_EMU
	dvbcsa_bs_key_free(key_data[conndata->connid].key[ODD]);
	dvbcsa_bs_key_free(key_data[conndata->connid].key[EVEN]);
	key_data[conndata->connid].key[ODD] = NULL;
	key_data[conndata->connid].key[EVEN] = NULL;
#else
	for (i = 0; i < EMU_STREAM_MAX_AUDIO_SUB_TRACKS + 2; i++)
	{
		dvbcsa_bs_key_free(key_data[conndata->connid].key[i][ODD]);
		dvbcsa_bs_key_free(key_data[conndata->connid].key[i][EVEN]);
		key_data[conndata->connid].key[i][ODD] = NULL;
		key_data[conndata->connid].key[i][EVEN] = NULL;
	}
#endif
	NULLFREE(tsbbatch);
	NULLFREE(data);

	stream_client_disconnect(conndata);
	return NULL;
}

static void *stream_server(void)
{
#ifdef IPV6SUPPORT
	struct sockaddr_in6 servaddr, cliaddr;
#else
	struct sockaddr_in servaddr, cliaddr;
#endif
	socklen_t clilen;
	int32_t connfd, reuse = 1, i;
	int8_t connaccepted;
	stream_client_conn_data *conndata;

	struct s_client *client = first_client;
	client->thread = pthread_self();
	SAFE_SETSPECIFIC(getclient, client);
	set_thread_name(__func__);

	cluster_size = dvbcsa_bs_batch_size();
	has_dvbcsa_ecm = (DVBCSA_HEADER_ECM);
#if !DVBCSA_KEY_ECM
#pragma message "WARNING: Streamrelay is compiled without dvbcsa ecm headers! ECM processing via Streamrelay will not work!"
#endif
#if !STATIC_LIBDVBCSA
	has_dvbcsa_ecm = (dlsym(RTLD_DEFAULT, "dvbcsa_bs_key_set_ecm"));
	is_dvbcsa_static = 0;
#if DVBCSA_KEY_ECM
	dvbcsa_get_ecm_table_fn = dlsym(RTLD_DEFAULT, "dvbcsa_get_ecm_table");
#endif
#endif

	do
	{
		cs_log("%s: (%s) %s dvbcsa parallel mode = %d (relay buffer time: %d ms)%s%s",
			(!DVBCSA_HEADER_ECM || !has_dvbcsa_ecm) ? "WARNING" : "INFO",
			(!has_dvbcsa_ecm) ? "wrong" : "ecm",
			(!is_dvbcsa_static) ? "dynamic" : "static",
			cluster_size,
			cfg.stream_relay_buffer_time,
			(!DVBCSA_HEADER_ECM || !has_dvbcsa_ecm) ? "! ECM processing via Streamrelay does not work!" : "",
			(!DVBCSA_HEADER_ECM) ? " Missing dvbcsa ecm headers during build!" : "");

		if (!stream_server_mutex_init)
		{
			SAFE_MUTEX_INIT(&stream_server_mutex, NULL);
			stream_server_mutex_init = 1;
		}

		SAFE_MUTEX_LOCK(&fixed_key_srvid_mutex);
		for (i = 0; i < STREAM_SERVER_MAX_CONNECTIONS; i++)
		{
			stream_cur_srvid[i] = NO_SRVID_VALUE;
#ifdef WITH_EMU
			stream_server_has_ecm[i] = 0;
#endif
		}
		SAFE_MUTEX_UNLOCK(&fixed_key_srvid_mutex);

		for (i = 0; i < STREAM_SERVER_MAX_CONNECTIONS; i++)
		{
			gconnfd[i] = -1;
		}
#ifdef IPV6SUPPORT
		if ((glistenfd = socket(AF_INET6, SOCK_STREAM, 0)) == -1)
		{
			cs_log("ERROR: cannot create stream server socket! (errno=%d %s)", errno, strerror(errno));
			break;
		}

		bzero(&servaddr,sizeof(servaddr));
		servaddr.sin6_family = AF_INET6;
		servaddr.sin6_addr = in6addr_any;
		servaddr.sin6_port = htons(cfg.stream_relay_port);
#else
		if ((glistenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
		{
			cs_log("ERROR: cannot create stream server socket! (errno=%d %s)", errno, strerror(errno));
			break;
		}

		bzero(&servaddr,sizeof(servaddr));
		servaddr.sin_family = AF_INET;
		servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
		servaddr.sin_port = htons(cfg.stream_relay_port);
#endif
		setsockopt(glistenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

		if (bind(glistenfd,(struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
		{
			cs_log("ERROR: cannot bind to stream server socket! (errno=%d %s)", errno, strerror(errno));
			break;
		}

		if (listen(glistenfd, 3) == -1)
		{
			cs_log("ERROR: cannot listen to stream server socket! (errno=%d %s)", errno, strerror(errno));
			break;
		}

		cs_log("Stream Relay server initialized. ip=%s port=%d", cs_inet_ntoa(SIN_GET_ADDR(servaddr)), ntohs(SIN_GET_PORT(servaddr)));

		while (!exit_oscam)
		{
			clilen = sizeof(cliaddr);
			if ((connfd = accept(glistenfd,(struct sockaddr *)&cliaddr, &clilen)) == -1)
			{
				cs_log("ERROR: Calling accept() failed! (errno=%d %s)", errno, strerror(errno));
				break;
			}

			connaccepted = 0;

			if (cs_malloc(&conndata, sizeof(stream_client_conn_data)))
			{
				SAFE_MUTEX_LOCK(&stream_server_mutex);
				if (gconncount < STREAM_SERVER_MAX_CONNECTIONS)
				{
					for (i = 0; i < STREAM_SERVER_MAX_CONNECTIONS; i++)
					{
						if (gconnfd[i] == -1)
						{
							cs_strncpy(ecm_src[i], "dvbapi", sizeof(ecm_src[i]));
							gconnfd[i] = connfd;
							gconncount++;
							connaccepted = 1;

							conndata->connfd = connfd;
							conndata->connid = i;
							client_ip[i] = SIN_GET_ADDR(cliaddr);
							client_port[i] = ntohs(SIN_GET_PORT(cliaddr));
							break;
						}
					}
				}
				SAFE_MUTEX_UNLOCK(&stream_server_mutex);
			}

			if (connaccepted)
			{
				int on = 1;
				if (setsockopt(connfd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) < 0)
					{ cs_log("ERROR: stream client %i setsockopt() failed for TCP_NODELAY!", conndata->connid); }

				start_thread("stream client", stream_client_handler, (void*)conndata, NULL, 1, 0);
			}
			else
			{
				shutdown(connfd, 2);
				close(connfd);
				cs_log("ERROR: stream server client dropped because of too many connections (%i)!", STREAM_SERVER_MAX_CONNECTIONS);
			}

			cs_sleepms(20);
		}
	} while (0);

	if (glistenfd >= 0)
		{ close(glistenfd); }
	return NULL;
}

void *streamrelay_handler(struct s_client *UNUSED(cl), uint8_t *UNUSED(mbuf), int32_t module_idx)
{
	char authtmp[128];

	if (cfg.stream_relay_enabled)
	{

		if (cfg.stream_source_auth_user && cfg.stream_source_auth_password)
		{
			snprintf(authtmp, sizeof(authtmp), "%s:%s", cfg.stream_source_auth_user, cfg.stream_source_auth_password);
			b64encode(authtmp, cs_strlen(authtmp), &stream_source_auth);
		}

#ifdef WITH_EMU
		emu_stream_emm_enabled = cfg.emu_stream_emm_enabled;
#endif

		mod_idx = module_idx;
		start_thread("stream_server", stream_server, NULL, NULL, 1, 1);
	}
	return NULL;
}

#if WITH_EMU
void *stream_key_delayer(void *UNUSED(arg))
{
	int32_t i, j;
	const uint8_t ecm = 0;
	emu_stream_client_key_data *cdata;
	LL_ITER it;
	emu_stream_cw_item *item;
	struct timeb t_now;

	while (!exit_oscam)
	{
		cs_ftime(&t_now);

		for (i = 0; i < STREAM_SERVER_MAX_CONNECTIONS; i++)
		{
			it = ll_iter_create(ll_emu_stream_delayed_keys[i]);

			while ((item = ll_iter_next(&it)))
			{
				if (comp_timeb(&t_now, &item->write_time) < 0)
				{
					break;
				}

				SAFE_MUTEX_LOCK(&emu_fixed_key_data_mutex[i]);

				cdata = &emu_fixed_key_data[i];

				for (j = 0; j < EMU_STREAM_MAX_AUDIO_SUB_TRACKS + 2; j++)
				{
					if (item->csa_used)
					{
						if (item->is_even)
						{
							dvbcsa_bs_key_set(item->cw[j], key_data[cdata->connid].key[j][EVEN]);
						}
						else
						{
							dvbcsa_bs_key_set(item->cw[j], key_data[cdata->connid].key[j][ODD]);
						}

						cdata->csa_used = 1;
					}
					else
					{
						if (item->is_even)
						{
							des_set_key(item->cw[j], cdata->pvu_des_ks[j][0]);
						}
						else
						{
							des_set_key(item->cw[j], cdata->pvu_des_ks[j][1]);
						}

						cdata->csa_used = 0;
					}
				}
				SAFE_MUTEX_UNLOCK(&emu_fixed_key_data_mutex[i]);

				ll_iter_remove_data(&it);
			}
		}

		cs_sleepms(25);
	}

	return NULL;
}
#endif

void stop_stream_server(void)
{
	int32_t i;

	SAFE_MUTEX_LOCK(&stream_server_mutex);
	for (i = 0; i < STREAM_SERVER_MAX_CONNECTIONS; i++)
	{
		if (gconnfd[i] != -1)
		{
			shutdown(gconnfd[i], 2);
			close(gconnfd[i]);
			gconnfd[i] = -1;
		}
	}

	gconncount = 0;
	SAFE_MUTEX_UNLOCK(&stream_server_mutex);

#ifdef MODULE_RADEGAST
	close_radegast_connection();
#endif

	if (glistenfd >= 0)
	{
		shutdown(glistenfd, 2);
		close(glistenfd);
	}

	NULLFREE(stream_source_auth);
}

/*
 * protocol structure
 */
void module_streamrelay(struct s_module *ph)
{
	ph->desc = "streamrelay";
	ph->type = MOD_CONN_SERIAL;
	ph->s_handler = streamrelay_handler;
}
#endif // MODULE_STREAMRELAY
