2231 lines
57 KiB
C
Executable File
2231 lines
57 KiB
C
Executable File
#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
|