From: Jaroslav Kysela Date: Thu, 28 Dec 2017 15:04:29 +0000 (+0100) Subject: cwc, cccam: rewrite, move common code to cclient X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c8723a4aacd6787dad9448cdcac13f85ed786a28;p=thirdparty%2Ftvheadend.git cwc, cccam: rewrite, move common code to cclient --- diff --git a/Makefile b/Makefile index 9dedae33c..9eb1db8b3 100644 --- a/Makefile +++ b/Makefile @@ -545,10 +545,16 @@ SRCS-TVHCSA = \ SRCS-${CONFIG_TVHCSA} += $(SRCS-TVHCSA) I18N-C += $(SRCS-TVHCSA) +# Cardclient +SRCS-CARDCLIENT = \ + src/descrambler/cclient.c \ + src/descrambler/emm_reass.c +SRCS-${CONFIG_CARDCLIENT} += $(SRCS-CARDCLIENT) +I18N-C += $(SRCS-CARDCLIENT) + # CWC SRCS-CWC = \ - src/descrambler/cwc.c \ - src/descrambler/emm_reass.c + src/descrambler/cwc.c SRCS-${CONFIG_CWC} += $(SRCS-CWC) I18N-C += $(SRCS-CWC) diff --git a/configure b/configure index 8bf405647..58c951c8c 100755 --- a/configure +++ b/configure @@ -19,6 +19,7 @@ test -z "$PKG_CONFIG" && PKG_CONFIG=pkg-config OPTIONS=( "pie:yes" "ccdebug:no" + "cardclient:auto" "cwc:yes" "cccam:yes" "capmt:yes" @@ -618,6 +619,13 @@ if enabled_or_auto inotify; then fi fi +# +# common card client +# +if enabled cwc || enabled cccam; then + enable cardclient +fi + # # libdvbcsa, tvhcsa # diff --git a/src/descrambler/caclient.h b/src/descrambler/caclient.h index 83596523c..68a5336e5 100644 --- a/src/descrambler/caclient.h +++ b/src/descrambler/caclient.h @@ -26,6 +26,7 @@ struct mpegts_mux; extern const idclass_t caclient_class; extern const idclass_t caclient_dvbcam_class; +extern const idclass_t caclient_cc_class; extern const idclass_t caclient_cwc_class; extern const idclass_t caclient_cccam_class; extern const idclass_t caclient_capmt_class; diff --git a/src/descrambler/cccam.c b/src/descrambler/cccam.c index 00ce5f48a..fff7ade40 100644 --- a/src/descrambler/cccam.c +++ b/src/descrambler/cccam.c @@ -1,4 +1,4 @@ - /* +/* * tvheadend, CCCAM interface * Copyright (C) 2007 Andreas Öman * Copyright (C) 2017 Luis Alves @@ -17,42 +17,17 @@ * along with this program. If not, see . */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include #include -#include -#include - #include "tvheadend.h" #include "tcp.h" -#include "caclient.h" -#include "descrambler/caid.h" -#include "descrambler/emm_reass.h" -#include "atomic.h" -#include "subscriptions.h" -#include "service.h" -#include "input.h" - +#include "cclient.h" /** * */ #define CCCAM_KEEPALIVE_INTERVAL 0 -#define CCCAM_ES_PIDS 8 -#define CCCAM_MAX_NOKS 3 -#define CCCAM_NETMSGSIZE 0x400 +#define CCCAM_NETMSGSIZE 1024 typedef enum { @@ -93,112 +68,6 @@ static const char *cccam_version_str[CCCAM_VERSION_COUNT] = { "2.1.4", "2.2.0", "2.2.1", "2.3.0" }; -/** - * - */ -//TAILQ_HEAD(cccam_queue, cccam); -LIST_HEAD(cccam_cards_list, cs_card_data); -LIST_HEAD(cccam_service_list, cccam_service); -TAILQ_HEAD(cccam_message_queue, cccam_message); -//LIST_HEAD(ecm_section_list, ecm_section); - -/** - * - */ -typedef struct ecm_section { - LIST_ENTRY(ecm_section) es_link; - - enum { - ES_UNKNOWN, - ES_RESOLVED, - ES_FORBIDDEN, - ES_IDLE - } es_keystate; - - int es_section; - int es_channel; - uint16_t es_caid; - uint16_t es_provid; - - uint8_t es_seq; - char es_nok; - char es_pending; - char es_resolved; - int64_t es_time; // time request was sent - -} ecm_section_t; - -/** - * - */ -typedef struct ecm_pid { - LIST_ENTRY(ecm_pid) ep_link; - - uint16_t ep_pid; - - int ep_last_section; - LIST_HEAD(, ecm_section) ep_sections; -} ecm_pid_t; - -/** - * - */ -typedef struct cccam_service { - th_descrambler_t; - - struct cccam *cs_cccam; - - LIST_ENTRY(cccam_service) cs_link; - - int cs_channel; - int cs_epids[CCCAM_ES_PIDS]; - mpegts_mux_t *cs_mux; - - /** - * ECM Status - */ - enum { - ECM_INIT, - ECM_VALID, - ECM_RESET - } ecm_state; - - LIST_HEAD(, ecm_pid) cs_pids; - -} cccam_service_t; - - -/** - * - */ -typedef struct cccam_message { - TAILQ_ENTRY(cccam_message) cm_link; - int cm_len; - int seq; - int card_id; - uint8_t cm_data[CCCAM_NETMSGSIZE]; -} cccam_message_t; - - -struct cccam; - -typedef struct cs_card_data { - - LIST_ENTRY(cs_card_data) cs_card; - - emm_reass_t cs_ra; - - struct cccam *cccam; - mpegts_mux_t *cccam_mux; - int running; - - uint32_t id; - uint32_t remote_id; - uint8_t hop; - uint8_t reshare; - -} cs_card_data_t; - /** * */ @@ -209,66 +78,23 @@ struct cccam_crypt_block { uint8_t sum; }; - /** * */ typedef struct cccam { - caclient_t; - - int cccam_fd; - - pthread_t cccam_tid; - tvh_cond_t cccam_cond; - - pthread_mutex_t cccam_mutex; - pthread_mutex_t cccam_writer_mutex; - tvh_cond_t cccam_writer_cond; - int cccam_writer_running; - struct cccam_message_queue cccam_writeq; - - struct cccam_service_list cccam_services; - - struct cccam_cards_list cccam_cards; - int cccam_seq; - -#if 0 - /* Emm forwarding */ - int cccam_forward_emm; -#endif - - /* Emm exclusive update */ - int64_t cccam_emm_update_time; - void *cccam_emm_mux; - + cclient_t; /* From configuration */ - char *cccam_username; - char *cccam_password; - char *cccam_hostname; uint8_t cccam_nodeid[8]; - - int cccam_port; - int cccam_emm; - int cccam_emmex; int cccam_version; - int cccam_keepalive_interval; - - int cccam_running; - int cccam_reconfigure; struct cccam_crypt_block sendblock; struct cccam_crypt_block recvblock; - sem_t ecm_mutex; - - int seq; - uint32_t card_id; - } cccam_t; -const uint8_t cccam_str[] = "CCcam"; +static const uint8_t cccam_str[] = "CCcam"; /** @@ -371,92 +197,23 @@ cccam_decrypt_cw(uint8_t *nodeid, uint32_t card_id, uint8_t *cws) } } - -static void cccam_service_pid_free(cccam_service_t *ct); - - /** * */ -static cs_card_data_t * -cccam_new_card(cccam_t *cccam, uint8_t *buf, uint8_t *ua, - int pcount, uint8_t **pid, uint8_t **psa) +static inline uint8_t * +cccam_set_ua(uint8_t *dst, uint8_t *src) { - cs_card_data_t *pcard = NULL; - emm_provider_t *ep; - const char *n; - const uint8_t *id, *sa; - int i, allocated = 0; - - uint16_t caid = (buf[12] << 8) | buf[13]; - - LIST_FOREACH(pcard, &cccam->cccam_cards, cs_card) - if (pcard->cs_ra.caid == caid) - break; - - if (pcard == NULL) { - pcard = calloc(1, sizeof(cs_card_data_t)); - emm_reass_init(&pcard->cs_ra, caid); - allocated = 1; - } - - pcard->id = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7]; - pcard->remote_id = (buf[8] << 24) | (buf[9] << 16) | (buf[10] << 8) | buf[11]; - pcard->hop = buf[14]; - pcard->reshare = buf[15]; - - if (ua) - memcpy(pcard->cs_ra.ua, ua, 8); - else - memset(pcard->cs_ra.ua, 0, 8); - - free(pcard->cs_ra.providers); - pcard->cs_ra.providers_count = pcount; - pcard->cs_ra.providers = calloc(pcount, sizeof(pcard->cs_ra.providers[0])); - - for (i = 0, ep = pcard->cs_ra.providers; i < pcount; i++, ep++) { - id = pid[i]; - if (id) - ep->id = (id[0] << 16) | (id[1] << 8) | id[2]; - if (psa) - memcpy(ep->sa, psa[i], 8); - } - - n = caid2name(caid) ?: "Unknown"; - - if (ua) { - tvhinfo(LS_CCCAM, "%s:%i: Connected as user %s " - "to a %s-card [0x%04x : %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x] " - "with %d provider%s", - cccam->cccam_hostname, cccam->cccam_port, - cccam->cccam_username, n, caid, - ua[0], ua[1], ua[2], ua[3], ua[4], ua[5], ua[6], ua[7], - pcount, pcount > 1 ? "s" : ""); - } else { - tvhinfo(LS_CCCAM, "%s:%i: Connected as user %s " - "to a %s-card [0x%04x] id=%08x remoteid=%08x hop=%i reshare=%i with %d provider%s", - cccam->cccam_hostname, cccam->cccam_port, - cccam->cccam_username, n, caid, - pcard->id, pcard->remote_id, pcard->hop, pcard->reshare, - pcount, pcount > 1 ? "s" : ""); - } - - for (i = 0, ep = pcard->cs_ra.providers; i < pcount; i++, ep++) { - if (psa) { - sa = ep->sa; - tvhinfo(LS_CCCAM, "%s:%i: Provider ID #%d: 0x%04x:0x%06x %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x", - cccam->cccam_hostname, cccam->cccam_port, i + 1, caid, ep->id, - sa[0], sa[1], sa[2], sa[3], sa[4], sa[5], sa[6], sa[7]); - } else { - tvhinfo(LS_CCCAM, "%s:%i: Provider ID #%d: 0x%04x:0x%06x", - cccam->cccam_hostname, cccam->cccam_port, i + 1, caid, ep->id); - } - } + /* FIXME */ + return memcpy(dst, src, 8); +} - pcard->running = 1; - if (allocated) - LIST_INSERT_HEAD(&cccam->cccam_cards, pcard, cs_card); - return pcard; +/** + * + */ +static inline uint8_t * +cccam_set_sa(uint8_t *dst, uint8_t *src) +{ + return memcpy(dst, src, 4); } /** @@ -465,332 +222,140 @@ cccam_new_card(cccam_t *cccam, uint8_t *buf, uint8_t *ua, static int cccam_decode_card_data_reply(cccam_t *cccam, uint8_t *msg) { - /*int emm_allowed; */ - - //cs_card_data_t *pcard; + cc_card_data_t *pcard; int i; unsigned int nprov; - uint8_t **pid, **psa, *msg2; //, *ua + uint8_t **pid, **psa, *saa, *msg2, ua[8]; /* nr of providers */ nprov = msg[24]; pid = nprov ? alloca(nprov * sizeof(uint8_t *)) : NULL; psa = nprov ? alloca(nprov * sizeof(uint8_t *)) : NULL; + saa = nprov ? alloca(nprov * 8) : NULL; msg2 = msg + 25; + memset(saa, 0, nprov * 8); for (i = 0; i < nprov; i++) { pid[i] = msg2; - psa[i] = msg2 + 3; + psa[i] = cccam_set_sa(saa + i * 8, msg2 + 3); msg2 += 7; } caclient_set_status((caclient_t *)cccam, CACLIENT_STATUS_CONNECTED); - cccam_new_card(cccam, msg, NULL, nprov, pid, NULL); /* TODO: psa? 4bytes cccam / 8bytes cwc */ -/* TODO: EMM - cccam->cccam_forward_emm = 0; - if (cccam->cccam_emm) { - ua = pcard->cs_ra.ua; - emm_allowed = ua[0] || ua[1] || ua[2] || ua[3] || - ua[4] || ua[5] || ua[6] || ua[7]; - - if (!emm_allowed) { - tvhinfo(LS_CCCAM, - "%s:%i: Will not forward EMMs (not allowed by server)", - cccam->cccam_hostname, cccam->cccam_port); - } else if (pcard->cs_ra.type != CARD_UNKNOWN) { - tvhinfo(LS_CCCAM, "%s:%i: Will forward EMMs", - cccam->cccam_hostname, cccam->cccam_port); - cccam->cccam_forward_emm = 1; - } else { - tvhinfo(LS_CCCAM, - "%s:%i: Will not forward EMMs (unsupported CA system)", - cccam->cccam_hostname, cccam->cccam_port); - } + cccam_set_ua(ua, msg + 16); + pcard = cc_new_card((cclient_t *)cccam, (msg[12] << 8) | msg[13], ua, nprov, pid, psa); + if (pcard) { + pcard->cccam.cs_id = (msg[4] << 24) | (msg[5] << 16) | (msg[6] << 8) | msg[7]; + pcard->cccam.cs_remote_id = (msg[8] << 24) | (msg[9] << 16) | (msg[10] << 8) | msg[11]; + pcard->cccam.cs_hop = msg[14]; + pcard->cccam.cs_reshare = msg[15]; } -*/ - return 0; -} - -static int -cccam_ecm_reset(th_descrambler_t *th) -{ - cccam_service_t *ct = (cccam_service_t *)th; - cccam_t *cccam = ct->cs_cccam; - ecm_pid_t *ep; - ecm_section_t *es; - - pthread_mutex_lock(&cccam->cccam_mutex); - descrambler_change_keystate(th, DS_READY, 1); - LIST_FOREACH(ep, &ct->cs_pids, ep_link) - LIST_FOREACH(es, &ep->ep_sections, es_link) - es->es_keystate = ES_UNKNOWN; - ct->ecm_state = ECM_RESET; - pthread_mutex_unlock(&cccam->cccam_mutex); return 0; } - -static void -cccam_ecm_idle(th_descrambler_t *th) -{ - cccam_service_t *ct = (cccam_service_t *)th; - cccam_t *cccam = ct->cs_cccam; - ecm_pid_t *ep; - ecm_section_t *es; - - pthread_mutex_lock(&cccam->cccam_mutex); - LIST_FOREACH(ep, &ct->cs_pids, ep_link) - LIST_FOREACH(es, &ep->ep_sections, es_link) - es->es_keystate = ES_IDLE; - ct->ecm_state = ECM_RESET; - pthread_mutex_unlock(&cccam->cccam_mutex); -} - - +/** + * + */ static void -handle_ecm_reply(cccam_service_t *ct, ecm_section_t *es, uint8_t *msg, - int len, int seq, uint8_t *dcw) +cccam_handle_keys(cccam_t *cccam, cc_service_t *ct, cc_ecm_section_t *es, + uint8_t *buf, int len, int seq) { - mpegts_service_t *t = (mpegts_service_t *)ct->td_service; - cccam_t *cccam = ct->cs_cccam; - ecm_pid_t *ep; - ecm_section_t *es2, es3; - char chaninfo[128]; - int i; - uint32_t off; - int64_t delay = (getfastmonoclock() - es->es_time) / 1000LL; // in ms - - uint8_t msg_type = msg[1]; - - es->es_pending = 0; + uint8_t *dcw_even, *dcw_odd, _dcw[16]; - snprintf(chaninfo, sizeof(chaninfo), " (PID %d)", es->es_channel); + cccam_decrypt_cw(cccam->cccam_nodeid, es->cccam.es_card_id, buf + 4); + memcpy(_dcw, buf + 4, 16); + cccam_decrypt(&cccam->recvblock, buf + 4, len - 4); - if (msg_type != MSG_ECM_REQUEST) { + dcw_even = buf[1] == MSG_ECM_REQUEST ? _dcw : NULL; + dcw_odd = buf[1] == MSG_ECM_REQUEST ? _dcw + 8 : NULL; - /* ERROR */ - if (es->es_nok < CCCAM_MAX_NOKS) - es->es_nok++; + tvhtrace(cccam->cc_subsys, "HEADER:"); + tvhlog_hexdump(cccam->cc_subsys, buf, 4); - if(es->es_keystate == ES_FORBIDDEN) - return; // We already know it's bad - - if (es->es_nok >= CCCAM_MAX_NOKS) { - tvhdebug(LS_CCCAM, - "Too many NOKs[%i] for service \"%s\"%s from %s", - es->es_section, t->s_dvb_svcname, chaninfo, ct->td_nicename); - es->es_keystate = ES_FORBIDDEN; - goto forbid; - } - - if (descrambler_resolved((service_t *)t, (th_descrambler_t *)ct)) { - tvhdebug(LS_CCCAM, - "NOK[%i] from %s: Already has a key for service \"%s\"", - es->es_section, ct->td_nicename, t->s_dvb_svcname); - es->es_nok = CCCAM_MAX_NOKS; /* do not send more ECM requests */ - es->es_keystate = ES_IDLE; - if (ct->td_keystate == DS_READY) - descrambler_change_keystate((th_descrambler_t *)ct, DS_IDLE, 1); - } - - tvhdebug(LS_CCCAM, - "Received NOK[%i] for service \"%s\"%s " - "(seqno: %d Req delay: %"PRId64" ms)", - es->es_section, t->s_dvb_svcname, chaninfo, seq, delay); - -forbid: - i = 0; - LIST_FOREACH(ep, &ct->cs_pids, ep_link) - LIST_FOREACH(es2, &ep->ep_sections, es_link) - if(es2 && es2 != es && es2->es_nok == 0) { - if (es2->es_pending) - return; - i++; - } - if (i && es->es_nok < CCCAM_MAX_NOKS) - return; - - es->es_keystate = ES_FORBIDDEN; - LIST_FOREACH(ep, &ct->cs_pids, ep_link) { - LIST_FOREACH(es2, &ep->ep_sections, es_link) - if (es2->es_keystate == ES_UNKNOWN || - es2->es_keystate == ES_RESOLVED) - break; - if (es2) - break; - } - - if (ep == NULL) { /* !UNKNOWN && !RESOLVED */ - tvherror(LS_CCCAM, - "Can not descramble service \"%s\", access denied (seqno: %d " - "Req delay: %"PRId64" ms) from %s", - t->s_dvb_svcname, seq, delay, ct->td_nicename); - descrambler_change_keystate((th_descrambler_t *)ct, DS_FORBIDDEN, 1); - ct->ecm_state = ECM_RESET; - /* this pid is not valid, force full scan */ - if (t->s_dvb_prefcapid == ct->cs_channel && t->s_dvb_prefcapid_lock == PREFCAPID_OFF) - t->s_dvb_prefcapid = 0; - } - return; - - } else { - - es->es_nok = 0; - ct->cs_channel = es->es_channel; - ct->ecm_state = ECM_VALID; - - if(t->s_dvb_prefcapid == 0 || - (t->s_dvb_prefcapid != ct->cs_channel && - t->s_dvb_prefcapid_lock == PREFCAPID_OFF)) { - t->s_dvb_prefcapid = ct->cs_channel; - tvhdebug(LS_CCCAM, "Saving prefered PID %d for %s", - t->s_dvb_prefcapid, ct->td_nicename); - service_request_save((service_t*)t, 0); - } - - //if(len < 35) { - tvhdebug(LS_CCCAM, - "Received ECM reply%s for service \"%s\" [%d] " - "even: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x" - " odd: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x (seqno: %d " - "Req delay: %"PRId64" ms)", - chaninfo, - t->s_dvb_svcname, es->es_section, - msg[3 + 0], msg[3 + 1], msg[3 + 2], msg[3 + 3], msg[3 + 4], - msg[3 + 5], msg[3 + 6], msg[3 + 7], msg[3 + 8], msg[3 + 9], - msg[3 + 10],msg[3 + 11],msg[3 + 12],msg[3 + 13],msg[3 + 14], - msg[3 + 15], seq, delay); - - if(es->es_keystate != ES_RESOLVED) - tvhdebug(LS_CCCAM, - "Obtained DES keys for service \"%s\" in %"PRId64" ms, from %s", - t->s_dvb_svcname, delay, ct->td_nicename); - es->es_keystate = ES_RESOLVED; - es->es_resolved = 1; - off = 8; - //} - //TODO: AES - #if 0 - else { - tvhdebug(LS_CCCAM, - "Received ECM reply%s for service \"%s\" " - "even: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x" - " odd: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x" - "(seqno: %d Req delay: %"PRId64" ms)", - chaninfo, - t->s_dvb_svcname, - msg[3 + 0], msg[3 + 1], msg[3 + 2], msg[3 + 3], msg[3 + 4], - msg[3 + 5], msg[3 + 6], msg[3 + 7], msg[3 + 8], msg[3 + 9], - msg[3 + 10],msg[3 + 11],msg[3 + 12],msg[3 + 13],msg[3 + 14], - msg[3 + 15],msg[3 + 16],msg[3 + 17],msg[3 + 18],msg[3 + 19], - msg[3 + 20],msg[3 + 21],msg[3 + 22],msg[3 + 23],msg[3 + 24], - msg[3 + 25],msg[3 + 26],msg[3 + 27],msg[3 + 28],msg[3 + 29], - msg[3 + 30],msg[3 + 31], seq, delay); - - if(es->es_keystate != ES_RESOLVED) - tvhdebug(LS_CCCAM, - "Obtained AES keys for service \"%s\" in %"PRId64" ms, from %s", - t->s_dvb_svcname, delay, ct->td_nicename); - es->es_keystate = ES_RESOLVED; - es->es_resolved = 1; - off = 16; - } -#endif - es3 = *es; - pthread_mutex_unlock(&cccam->cccam_mutex); - descrambler_keys((th_descrambler_t *)ct, - off == 16 ? DESCRAMBLER_AES128_ECB : DESCRAMBLER_CSA_CBC, - 0, dcw, dcw + off); - snprintf(chaninfo, sizeof(chaninfo), "%s:%i", cccam->cccam_hostname, cccam->cccam_port); - descrambler_notify((th_descrambler_t *)ct, - es3.es_caid, es3.es_provid, - caid2name(es3.es_caid), - es3.es_channel, delay, - 1, "", chaninfo, "cccam"); - pthread_mutex_lock(&cccam->cccam_mutex); - } + cc_ecm_reply(ct, es, DESCRAMBLER_CSA_CBC, dcw_even, dcw_odd, seq); } /** * Handle running reply - * cccam_mutex is held + * cc_mutex is held */ static int cccam_running_reply(cccam_t *cccam, uint8_t *buf, int len) { - cccam_service_t *ct; - ecm_pid_t *ep; - ecm_section_t *es; + cc_service_t *ct; + cc_ecm_pid_t *ep; + cc_ecm_section_t *es; + uint8_t seq; - tvhtrace(LS_CCCAM, "response msg type=%d, response:", buf[1]); - tvhlog_hexdump(LS_CCCAM, buf, len); + if (len < 4) + return -1; + + tvhtrace(cccam->cc_subsys, "%s:%i: response msg type=%d, response:", + cccam->cc_hostname, cccam->cc_port, buf[1]); + tvhlog_hexdump(cccam->cc_subsys, buf, len); switch (buf[1]) { case MSG_NEW_CARD_SIDINFO: case MSG_NEW_CARD: - tvhdebug(LS_CCCAM, "add card message received"); + tvhtrace(cccam->cc_subsys, "%s:%i: add card message received", + cccam->cc_hostname, cccam->cc_port); cccam_decode_card_data_reply(cccam, buf); break; case MSG_CARD_REMOVED: - tvhdebug(LS_CCCAM, "del card message received"); + tvhtrace(cccam->cc_subsys, "%s:%i: del card message received", + cccam->cc_hostname, cccam->cc_port); break; case MSG_KEEPALIVE: - tvhdebug(LS_CCCAM, "keepalive"); + tvhtrace(cccam->cc_subsys, "%s:%i: keepalive", + cccam->cc_hostname, cccam->cc_port); break; case MSG_EMM_REQUEST: /* emm ack */ //cccam_send_msg(cccam, MSG_EMM_REQUEST, NULL, 0, 0, 0, 0); - sem_post(&cccam->ecm_mutex); - tvhtrace(LS_CCCAM, "EMM message ACK received"); + //sem_post(&cccam->ecm_mutex); + tvhtrace(cccam->cc_subsys, "%s:%i: EMM message ACK received", + cccam->cc_hostname, cccam->cc_port); break; case MSG_ECM_NOK1: /* retry */ case MSG_ECM_NOK2: /* decode failed */ //case MSG_CMD_05: /* ? */ case MSG_ECM_REQUEST: { /* request reply */ - - uint8_t dcw[16]; - uint8_t seq = buf[0]; - - cccam_decrypt_cw(cccam->cccam_nodeid, cccam->card_id, &buf[4]); - memcpy(dcw, buf + 4, 16); - cccam_decrypt(&cccam->recvblock, buf + 4, len - 4); - tvhtrace(LS_CCCAM, "HEADER:"); - tvhlog_hexdump(LS_CCCAM, buf, 4); - - seq = cccam->seq; - sem_post(&cccam->ecm_mutex); - - LIST_FOREACH(ct, &cccam->cccam_services, cs_link) + seq = buf[0]; + LIST_FOREACH(ct, &cccam->cc_services, cs_link) LIST_FOREACH(ep, &ct->cs_pids, ep_link) LIST_FOREACH(es, &ep->ep_sections, es_link) if(es->es_seq == seq) { if (es->es_resolved) { mpegts_service_t *t = (mpegts_service_t *)ct->td_service; - tvhdebug(LS_CCCAM, - "Ignore %sECM (PID %d) for service \"%s\" from %s (seq %i)", + tvhdebug(cccam->cc_subsys, + "%s:%i: Ignore %sECM (PID %d) for service \"%s\" from %s (seq %i)", + cccam->cc_hostname, cccam->cc_port, es->es_pending ? "duplicate " : "", - ep->ep_pid, t->s_dvb_svcname, ct->td_nicename, es->es_seq); + ep->ep_capid, t->s_dvb_svcname, ct->td_nicename, es->es_seq); return 0; } if (es->es_pending) { - handle_ecm_reply(ct, es, buf, len, seq, dcw); + cccam_handle_keys(cccam, ct, es, buf, len, seq); return 0; } } - tvhwarn(LS_CCCAM, "Got unexpected ECM reply (seqno: %d)", seq); + tvhwarn(cccam->cc_subsys, "Got unexpected ECM reply (seqno: %d)", seq); break; } case MSG_SRV_DATA: - tvhinfo(LS_CCCAM, "CCcam server version %s nodeid=%02x%02x%02x%02x%02x%02x%02x%02x", - &buf[12], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10], buf[11]); + tvhinfo(cccam->cc_subsys, "%s:%i: CCcam server version %s nodeid=%02x%02x%02x%02x%02x%02x%02x%02x", + cccam->cc_hostname, cccam->cc_port, buf + 12, + buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10], buf[11]); break; case MSG_CLI_DATA: - tvhinfo(LS_CCCAM, "CCcam server authentication completed"); + tvhinfo(cccam->cc_subsys, "%s:%i: CCcam server authentication completed", + cccam->cc_hostname, cccam->cc_port); break; default: - tvhwarn(LS_CCCAM, "Unknown message received"); + tvhwarn(cccam->cc_subsys, "%s:%i: Unknown message received", + cccam->cc_hostname, cccam->cc_port); break; } return 0; @@ -800,37 +365,33 @@ cccam_running_reply(cccam_t *cccam, uint8_t *buf, int len) * */ static int -cccam_must_break(cccam_t *cccam) -{ - return !cccam->cccam_running || !cccam->cac_enabled || cccam->cccam_reconfigure; -} - - -static int -cccam_read_message(cccam_t *cccam, uint8_t *buf, const char *state, int timeout) +cccam_read_message(cccam_t *cccam, const char *state, uint8_t *buf, int len, int timeout) { int32_t ret; uint16_t msglen; - pthread_mutex_unlock(&cccam->cccam_mutex); - ret = tcp_read_timeout(cccam->cccam_fd, buf, 4, timeout); - pthread_mutex_lock(&cccam->cccam_mutex); + pthread_mutex_unlock(&cccam->cc_mutex); + ret = tcp_read_timeout(cccam->cc_fd, buf, 4, timeout); + pthread_mutex_lock(&cccam->cc_mutex); if (ret) { - tvhdebug(LS_CCCAM, "recv error %d or timeout", ret); + tvhdebug(cccam->cc_subsys, "%s:%i: recv error %d or timeout", + cccam->cc_hostname, cccam->cc_port, ret); return -1; } cccam_decrypt(&cccam->recvblock, buf, 4); msglen = (buf[2] << 8) | buf[3]; if (msglen > 0) { - if (msglen > CCCAM_NETMSGSIZE - 2) { - tvhdebug(LS_CCCAM, "received message too large"); + if (msglen > len - 2) { + tvhdebug(cccam->cc_subsys, "%s:%i: received message too large", + cccam->cc_hostname, cccam->cc_port); return -1; } - pthread_mutex_unlock(&cccam->cccam_mutex); - ret = tcp_read_timeout(cccam->cccam_fd, buf + 4, msglen, 5000); - pthread_mutex_lock(&cccam->cccam_mutex); + pthread_mutex_unlock(&cccam->cc_mutex); + ret = tcp_read_timeout(cccam->cc_fd, buf + 4, msglen, 5000); + pthread_mutex_lock(&cccam->cc_mutex); if (ret) { - tvhdebug(LS_CCCAM, "timeout reading message"); + tvhdebug(cccam->cc_subsys, "%s:%i: timeout reading message", + cccam->cc_hostname, cccam->cc_port); return -1; } cccam_decrypt(&cccam->recvblock, buf + 4, msglen); @@ -838,85 +399,24 @@ cccam_read_message(cccam_t *cccam, uint8_t *buf, const char *state, int timeout) return msglen + 4; } - -/** - * - */ -static int -cccam_read(cccam_t *cccam, void *buf, size_t len, int timeout) -{ - int r; - - pthread_mutex_unlock(&cccam->cccam_mutex); - r = tcp_read_timeout(cccam->cccam_fd, buf, len, timeout); - pthread_mutex_lock(&cccam->cccam_mutex); - - if (r && tvheadend_is_running()) - tvhwarn(LS_CCCAM, "read error %d (%s)", r, strerror(r)); - - if(cccam_must_break(cccam)) - return ECONNABORTED; - - return r; -} - - -/** - * - */ -static void -cccam_invalidate_cards(cccam_t *cccam) -{ - cs_card_data_t *cd; - - LIST_FOREACH(cd, &cccam->cccam_cards, cs_card) - cd->running = 0; -} - -/** - * - */ -static void -cccam_free_cards(cccam_t *cccam) -{ - cs_card_data_t *cd; - - while((cd = LIST_FIRST(&cccam->cccam_cards)) != NULL) { - LIST_REMOVE(cd, cs_card); - descrambler_close_emm(cd->cccam_mux, cd, cd->cs_ra.caid); - emm_reass_done(&cd->cs_ra); - free(cd); - } -} - /** * */ -static void -cccam_flush_services(cccam_t *cccam) +static uint32_t +cccam_send_msg(cccam_t *cccam, cccam_msg_type_t cmd, + uint8_t *buf, size_t len, int enq, int seq, uint32_t card_id) { - cccam_service_t *ct; + cc_message_t *cm; + uint8_t *netbuf; - LIST_FOREACH(ct, &cccam->cccam_services, cs_link) - descrambler_flush_table_data(ct->td_service); -} - -/** - * - */ -static uint8_t -cccam_send_msg(cccam_t *cccam, - cccam_msg_type_t cmd, uint8_t *buf, size_t len, int enq, int seq, uint32_t card_id) -{ - cccam_message_t *cm = malloc(sizeof(cccam_message_t)); - uint8_t *netbuf = cm->cm_data; + if (len + 4 > CCCAM_NETMSGSIZE) + return -1; - if ((len + 4) > CCCAM_NETMSGSIZE) { - free(cm); - return 0; - } + cm = malloc(sizeof(cc_message_t) + len + 4); + if (cm == NULL) + return -1; - memset(netbuf, 0, len + 4); + netbuf = cm->cm_data; if (cmd == MSG_NO_HEADER) { memcpy(netbuf, buf, len); } else { @@ -929,23 +429,9 @@ cccam_send_msg(cccam_t *cccam, len += 4; } - tvhtrace(LS_CCCAM, "sending message len %zu enq %d", len, enq); - tvhlog_hexdump(LS_CCCAM, buf, len); - - if(enq) { - cm->cm_len = len; - cm->seq = seq; - cm->card_id = card_id; - pthread_mutex_lock(&cccam->cccam_writer_mutex); - TAILQ_INSERT_TAIL(&cccam->cccam_writeq, cm, cm_link); - tvh_cond_signal(&cccam->cccam_writer_cond, 0); - pthread_mutex_unlock(&cccam->cccam_writer_mutex); - } else { - cccam_encrypt(&cccam->sendblock, netbuf, len); - if (tvh_write(cccam->cccam_fd, netbuf, len)) - tvhinfo(LS_CCCAM, "write error"); - free(cm); - } + cm->cm_len = len; + cc_write_message((cclient_t *)cccam, cm, enq); + return seq; } @@ -953,8 +439,9 @@ cccam_send_msg(cccam_t *cccam, * Send keep alive */ static void -cccam_send_ka(cccam_t *cccam) +cccam_send_ka(void *cc) { + cccam_t *cccam = cc; uint8_t buf[4]; buf[0] = 0; @@ -962,64 +449,10 @@ cccam_send_ka(cccam_t *cccam) buf[2] = 0; buf[3] = 0; - tvhdebug(LS_CCCAM, "send keepalive"); + tvhdebug(cccam->cc_subsys, "send keepalive"); cccam_send_msg(cccam, MSG_NO_HEADER, buf, 4, 0, 0, 0); } -/** - * - */ -static void * -cccam_writer_thread(void *aux) -{ - cccam_t *cccam = aux; - cccam_message_t *cm; - - int64_t mono; - int r; - - pthread_mutex_lock(&cccam->cccam_writer_mutex); - - while(cccam->cccam_writer_running) { - - if((cm = TAILQ_FIRST(&cccam->cccam_writeq)) != NULL) { - TAILQ_REMOVE(&cccam->cccam_writeq, cm, cm_link); - - cccam_encrypt(&cccam->sendblock, cm->cm_data, cm->cm_len); - sem_wait(&cccam->ecm_mutex); - cccam->seq = cm->seq; - cccam->card_id = cm->card_id; - - pthread_mutex_unlock(&cccam->cccam_writer_mutex); - // int64_t ts = getfastmonoclock(); - r = tvh_write(cccam->cccam_fd, cm->cm_data, cm->cm_len); - if (r) - tvhinfo(LS_CCCAM, "write error"); - // printf("Write took %lld usec\n", getfastmonoclock() - ts); - free(cm); - pthread_mutex_lock(&cccam->cccam_writer_mutex); - continue; - } - - /* If nothing is to be sent in keepalive interval seconds we - need to send a keepalive - when disabled default to 1 min but don't send ka messages */ - int delay = cccam->cccam_keepalive_interval ? cccam->cccam_keepalive_interval : 60; - mono = mclk() + sec2mono(delay); - do { - r = tvh_cond_timedwait(&cccam->cccam_writer_cond, &cccam->cccam_writer_mutex, mono); - if(r == ETIMEDOUT) { - if (cccam->cccam_keepalive_interval) - cccam_send_ka(cccam); - break; - } - } while (ERRNO_AGAIN(r)); - } - - pthread_mutex_unlock(&cccam->cccam_writer_mutex); - return NULL; -} - /** * */ @@ -1035,8 +468,8 @@ sha1_make_login_key(cccam_t *cccam, uint8_t *buf) SHA1_Update(&sha1, buf, 16); SHA1_Final(hash, &sha1); - tvhdebug(LS_CCCAM, "sha1 hash:"); - tvhlog_hexdump(LS_CCCAM, hash, sizeof(hash)); + tvhdebug(cccam->cc_subsys, "sha1 hash:"); + tvhlog_hexdump(cccam->cc_subsys, hash, sizeof(hash)); cccam_crypt_init(&cccam->recvblock, hash, 20); cccam_decrypt(&cccam->recvblock, buf, 16); @@ -1059,40 +492,40 @@ cccam_send_login(cccam_t *cccam) char pwd[255]; uint8_t data[20]; - if (cccam->cccam_username == NULL) + if (cccam->cc_username == NULL) return 1; - ul = strlen(cccam->cccam_username) + 1; + ul = strlen(cccam->cc_username) + 1; if (ul > 128) return 1; /* send username */ memset(buf, 0, CCCAM_NETMSGSIZE); - memcpy(buf, cccam->cccam_username, ul); + memcpy(buf, cccam->cc_username, ul); cccam_send_msg(cccam, MSG_NO_HEADER, buf, 20, 0, 0, 0); /* send password 'xored' with CCcam */ memset(buf, 0, CCCAM_NETMSGSIZE); memset(pwd, 0, sizeof(pwd)); memcpy(buf, cccam_str, 5); - strncpy(pwd, cccam->cccam_password, sizeof(pwd) - 1); + strncpy(pwd, cccam->cc_password, sizeof(pwd) - 1); cccam_encrypt(&cccam->sendblock, (uint8_t *) pwd, strlen(pwd)); cccam_send_msg(cccam, MSG_NO_HEADER, buf, 6, 0, 0, 0); - if (cccam_read(cccam, data, 20, 5000)) { - tvherror(LS_CCCAM, "login failed, pwd ack not received"); + if (cc_read((cclient_t *)cccam, data, 20, 5000)) { + tvherror(cccam->cc_subsys, "login failed, pwd ack not received"); return -2; } cccam_decrypt(&cccam->recvblock, data, 20); - tvhdebug(LS_CCCAM, "login ack, response:"); - tvhlog_hexdump(LS_CCCAM, data, 20); + tvhdebug(cccam->cc_subsys, "login ack, response:"); + tvhlog_hexdump(cccam->cc_subsys, data, 20); if (memcmp(data, "CCcam", 5)) { - tvherror(LS_CCCAM, "login failed, usr/pwd invalid"); + tvherror(cccam->cc_subsys, "login failed, usr/pwd invalid"); return -2; } else { - tvhinfo(LS_CCCAM, "login succeeded"); + tvhinfo(cccam->cc_subsys, "login succeeded"); } return 0; @@ -1107,10 +540,10 @@ cccam_send_cli_data(cccam_t *cccam) uint8_t buf[CCCAM_NETMSGSIZE]; memset(buf, 0, CCCAM_NETMSGSIZE); - memcpy(buf, cccam->cccam_username, 20); + memcpy(buf, cccam->cc_username, 20); memcpy(buf + 20, cccam->cccam_nodeid, 8); buf[28] = 0; // TODO: wantemus = 1; - memcpy(buf + 29, cccam_version_str[cccam->cccam_version], 32); + strncpy((char *)buf + 29, cccam_version_str[cccam->cccam_version], 31); memcpy(buf + 61, "tvh", 3); // build number (ascii) cccam_send_msg(cccam, MSG_CLI_DATA, buf, 20 + 8 + 1 + 64, 0, 0, 0); } @@ -1118,23 +551,24 @@ cccam_send_cli_data(cccam_t *cccam) /** * */ -static void -cccam_session(cccam_t *cccam) +static int +cccam_init_session(void *cc) { - int r; + cccam_t *cccam = cc; uint8_t buf[CCCAM_NETMSGSIZE]; - pthread_t writer_thread_id; + int r; /** * Get init seed */ - if((r = cccam_read(cccam, buf, 16, 5000))) { - tvhinfo(LS_CCCAM, "%s:%i: init error (no init seed received)", - cccam->cccam_hostname, cccam->cccam_port); - return; + if((r = cc_read(cc, buf, 16, 5000))) { + tvhinfo(cccam->cc_subsys, "%s:%i: init error (no init seed received)", + cccam->cc_hostname, cccam->cc_port); + return -1; } - tvhdebug(LS_CCCAM, "init seed received:"); - tvhlog_hexdump(LS_CCCAM, buf, 16); + tvhtrace(cccam->cc_subsys, "%s:%d: init seed received:", + cccam->cc_hostname, cccam->cc_port); + tvhlog_hexdump(cccam->cc_subsys, buf, 16); /* check for oscam-cccam */ uint16_t sum = 0x1234; @@ -1144,7 +578,8 @@ cccam_session(cccam_t *cccam) sum += buf[i]; } if (sum == recv_sum) - tvhinfo(LS_CCCAM, "oscam server detected"); + tvhinfo(cccam->cc_subsys, "%s:%i: oscam server detected", + cccam->cc_hostname, cccam->cc_port); sha1_make_login_key(cccam, buf); @@ -1152,678 +587,110 @@ cccam_session(cccam_t *cccam) * Login */ if (cccam_send_login(cccam)) - return; + return -1; cccam_send_cli_data(cccam); - /** - * Ok, connection good, reset retry delay to zero - */ - cccam_flush_services(cccam); - - /** - * We do all requests from now on in a separate thread - */ - sem_init(&cccam->ecm_mutex, 1, 1); - cccam->cccam_writer_running = 1; - tvh_cond_init(&cccam->cccam_writer_cond); - pthread_mutex_init(&cccam->cccam_writer_mutex, NULL); - TAILQ_INIT(&cccam->cccam_writeq); - tvhthread_create(&writer_thread_id, NULL, cccam_writer_thread, cccam, "cccam-writer"); - - /** - * Mainloop - */ - while (!cccam_must_break(cccam)) { - if ((r = cccam_read_message(cccam, buf, "Decoderloop", 30000)) < 0) - break; - if (r > 0) - cccam_running_reply(cccam, buf, r); - }; - - tvhdebug(LS_CCCAM, "session thread exiting"); - - /** - * Collect the writer thread - */ - cccam->cccam_writer_running = 0; - tvh_cond_signal(&cccam->cccam_writer_cond, 0); - pthread_join(writer_thread_id, NULL); - - shutdown(cccam->cccam_fd, SHUT_RDWR); - tvhdebug(LS_CCCAM, "Write thread joined"); -} - -/** - * - */ -static void * -cccam_thread(void *aux) -{ - char hostname[256]; - int port; - cccam_t *cccam = aux; - int fd, d, r; - char errbuf[100]; - int attempts = 0; - int64_t mono; - - pthread_mutex_lock(&cccam->cccam_mutex); - - while(cccam->cccam_running) { - cccam_invalidate_cards(cccam); - caclient_set_status((caclient_t *)cccam, CACLIENT_STATUS_READY); - - snprintf(hostname, sizeof(hostname), "%s", cccam->cccam_hostname); - port = cccam->cccam_port; - - tvhinfo(LS_CCCAM, "Attemping to connect to %s:%d", hostname, port); - - pthread_mutex_unlock(&cccam->cccam_mutex); - fd = tcp_connect(hostname, port, NULL, errbuf, sizeof(errbuf), 10); - pthread_mutex_lock(&cccam->cccam_mutex); - - if(fd == -1) { - attempts++; - tvhinfo(LS_CCCAM, - "Connection attempt to %s:%d failed: %s", - hostname, port, errbuf); - } else { - - if(cccam->cccam_running == 0) { - close(fd); - break; - } - - tvhinfo(LS_CCCAM, "Connected to %s:%d", hostname, port); - attempts = 0; - - cccam->cccam_fd = fd; - cccam->cccam_reconfigure = 0; - - cccam_session(cccam); - - cccam->cccam_fd = -1; - close(fd); - tvhinfo(LS_CCCAM, "Disconnected from %s:%i", - cccam->cccam_hostname, cccam->cccam_port); - } - - if(cccam->cccam_running == 0) continue; - if(attempts == 1 || cccam->cccam_reconfigure) { - cccam->cccam_reconfigure = 0; - continue; // Retry immediately - } - - caclient_set_status((caclient_t *)cccam, CACLIENT_STATUS_DISCONNECTED); - - d = 3; - - tvhinfo(LS_CCCAM, - "%s:%i: Automatic connection attempt in %d seconds", - cccam->cccam_hostname, cccam->cccam_port, d-1); - - mono = mclk() + sec2mono(d); - do { - r = tvh_cond_timedwait(&cccam->cccam_cond, &cccam->cccam_mutex, mono); - if (r == ETIMEDOUT) - break; - } while (ERRNO_AGAIN(r)); - } - - tvhinfo(LS_CCCAM, "%s:%i inactive", - cccam->cccam_hostname, cccam->cccam_port); - cccam_free_cards(cccam); - pthread_mutex_unlock(&cccam->cccam_mutex); - return NULL; -} - -/** - * - */ -static int -verify_provider(cs_card_data_t *pcard, uint32_t providerid) -{ - int i; - - if(providerid == 0) - return 1; - - for(i = 0; i < pcard->cs_ra.providers_count; i++) - if(providerid == pcard->cs_ra.providers[i].id) - return 1; return 0; } /** * */ -static void -cccam_emm_send(void *aux, const uint8_t *radata, int ralen, void *mux) -{ - cs_card_data_t *pcard = aux; - //cccam_t *cccam = pcard->cccam; - - tvhtrace(LS_CCCAM, "sending EMM for %04x mux %p", pcard->cs_ra.caid, mux); - tvhlog_hexdump(LS_CCCAM, radata, ralen); - - // FIXME: missing arguments - //cccam_send_emm(cccam, radata, ralen, pcard->SID, - // pcard->cs_ra.caid, ->provider, pcard->id); -} - -/** - * - */ -static void -cccam_emm(void *opaque, int pid, const uint8_t *data, int len, int emm) +static uint32_t +cccam_send_ecm(void *cc, cc_service_t *ct, cc_ecm_section_t *es, + cc_card_data_t *pcard, const uint8_t *msg, int len) { - cs_card_data_t *pcard = opaque; - cccam_t *cccam; - void *mux; + mpegts_service_t *t = (mpegts_service_t *)ct->td_service; + cccam_t *cccam = cc; + uint8_t buf[CCCAM_NETMSGSIZE-4]; + uint16_t caid, sid; + uint32_t provid, card_id; + int seq; - if (data == NULL) { /* end-of-data */ - pcard->cccam_mux = NULL; - return; - } - if (pcard->cccam_mux == NULL) - return; - cccam = pcard->cccam; - pthread_mutex_lock(&cccam->cccam_mutex); - mux = pcard->cccam_mux; - - //orig: if (pcard->running && cccam->cccam_forward_emm && cccam->cccam_writer_running) { - if (pcard->running && cccam->cccam_writer_running) { - if (cccam->cccam_emmex) { - if (cccam->cccam_emm_mux && cccam->cccam_emm_mux != mux) { - if (cccam->cccam_emm_update_time + sec2mono(25) > mclk()) - goto end_of_job; - } - cccam->cccam_emm_update_time = mclk(); - } - cccam->cccam_emm_mux = mux; - emm_filter(&pcard->cs_ra, data, len, mux, cccam_emm_send, pcard); + if (len > 255) { + tvherror(cccam->cc_subsys, "%s:%i: ECM too big (%d bytes)", + cccam->cc_hostname, cccam->cc_port, len); + return 0; } -end_of_job: - pthread_mutex_unlock(&cccam->cccam_mutex); -} - - -static int -cccam_send_ecm(cccam_t *cccam, const uint8_t *msg, size_t len, int sid, - uint16_t st_caid, uint32_t st_provider, uint32_t card_id) -{ - unsigned char buf[CCCAM_NETMSGSIZE-4]; - int seq = atomic_add(&cccam->cccam_seq, 1); - - buf[0] = st_caid >> 8; - buf[1] = st_caid & 0xff; - buf[2] = st_provider >> 24; - buf[3] = st_provider >> 16; - buf[4] = st_provider >> 8; - buf[5] = st_provider & 0xff; - buf[6] = card_id >> 24; - buf[7] = card_id >> 16; - buf[8] = card_id >> 8; - buf[9] = card_id & 0xff; + seq = atomic_add(&cccam->cc_seq, 1); + caid = es->es_caid; + provid = es->es_provid; + card_id = pcard->cccam.cs_id; + es->cccam.es_card_id = card_id; + sid = t->s_dvb_service_id; + + buf[ 0] = caid >> 8; + buf[ 1] = caid & 0xff; + buf[ 2] = provid >> 24; + buf[ 3] = provid >> 16; + buf[ 4] = provid >> 8; + buf[ 5] = provid & 0xff; + buf[ 6] = card_id >> 24; + buf[ 7] = card_id >> 16; + buf[ 8] = card_id >> 8; + buf[ 9] = card_id & 0xff; buf[10] = sid >> 8; buf[11] = sid & 0xff; buf[12] = len; memcpy(buf + 13, msg, len); + return cccam_send_msg(cccam, MSG_ECM_REQUEST, buf, 13 + len, 1, seq, card_id); } -#if 0 -static int -cccam_send_emm(cccam_t *cccam, const uint8_t *msg, size_t len, int sid, - uint16_t st_caid, uint32_t st_provider, uint32_t card_id) -{ - unsigned char buf[CCCAM_NETMSGSIZE-4]; - - int seq = atomic_add(&cccam->cccam_seq, 1); - - buf[0] = st_caid >> 8; - buf[1] = st_caid & 0xff; - buf[2] = 0; - buf[3] = st_provider >> 24; - buf[4] = st_provider >> 16; - buf[5] = st_provider >> 8; - buf[6] = st_provider & 0xff; - buf[7] = card_id >> 24; - buf[8] = card_id >> 16; - buf[9] = card_id >> 8; - buf[10] = card_id & 0xff; - buf[11] = len & 0xff; - memcpy(buf + 12, msg, len); - return cccam_send_msg(cccam, MSG_EMM_REQUEST, buf, 12 + len, 1, seq, card_id); -} -#endif /** * */ static void -cccam_table_input(void *opaque, int pid, const uint8_t *data, int len, int emm) +cccam_send_emm(void *cc, cc_service_t *ct, cc_card_data_t *pcard, + uint32_t provid, const uint8_t *msg, int len) { - cccam_service_t *ct = opaque; - elementary_stream_t *st; - mpegts_service_t *t = (mpegts_service_t*)ct->td_service; - uint16_t sid = t->s_dvb_service_id; - cccam_t *cccam = ct->cs_cccam; - int channel, section, ecm; - ecm_pid_t *ep; - ecm_section_t *es; - char chaninfo[32]; - cs_card_data_t *pcard = NULL; - caid_t *c; + cccam_t *cccam = cc; + unsigned char buf[CCCAM_NETMSGSIZE-4]; uint16_t caid; - uint32_t provid; - - if (data == NULL) - return; - - if(len > 4096) - return; - - pthread_mutex_lock(&cccam->cccam_mutex); - pthread_mutex_lock(&t->s_stream_mutex); - - if (ct->td_keystate == DS_IDLE) - goto end; - - if (ct->ecm_state == ECM_RESET) { - /* clean all */ - cccam_service_pid_free(ct); - /* move to init state */ - ct->ecm_state = ECM_INIT; - ct->cs_channel = -1; - t->s_dvb_prefcapid = 0; - tvhdebug(LS_CCCAM, "Reset after unexpected or no reply for service \"%s\"", t->s_dvb_svcname); - } - - LIST_FOREACH(ep, &ct->cs_pids, ep_link) - if(ep->ep_pid == pid) break; - - if(ep == NULL) { - if (ct->ecm_state == ECM_INIT) { - // Validate prefered ECM PID - tvhdebug(LS_CCCAM, "ECM state INIT"); - - if(t->s_dvb_prefcapid_lock != PREFCAPID_OFF) { - st = service_stream_find((service_t*)t, t->s_dvb_prefcapid); - if (st && st->es_type == SCT_CA) - LIST_FOREACH(c, &st->es_caids, link) - LIST_FOREACH(pcard, &cccam->cccam_cards, cs_card) - if(pcard->running && - pcard->cs_ra.caid == c->caid && - verify_provider(pcard, c->providerid)) - goto prefcapid_ok; - tvhdebug(LS_CCCAM, "Invalid prefered ECM (PID %d) found for service \"%s\"", t->s_dvb_prefcapid, t->s_dvb_svcname); - t->s_dvb_prefcapid = 0; - } - -prefcapid_ok: - if(t->s_dvb_prefcapid == pid || t->s_dvb_prefcapid == 0 || - t->s_dvb_prefcapid_lock == PREFCAPID_OFF) { - ep = calloc(1, sizeof(ecm_pid_t)); - ep->ep_pid = pid; - - LIST_INSERT_HEAD(&ct->cs_pids, ep, ep_link); - tvhdebug(LS_CCCAM, "Insert %s ECM (PID %d) for service \"%s\"", - t->s_dvb_prefcapid ? "preferred" : "new", pid, t->s_dvb_svcname); - } - } - if(ep == NULL) - goto end; - } - - st = service_stream_find((service_t *)t, pid); - if (st) { - LIST_FOREACH(c, &st->es_caids, link) - LIST_FOREACH(pcard, &cccam->cccam_cards, cs_card) - if(pcard->running && - pcard->cs_ra.caid == c->caid && - verify_provider(pcard, c->providerid)) - goto found; - } - - goto end; - -found: - caid = c->caid; - provid = c->providerid; - - ecm = data[0] == 0x80 || data[0] == 0x81; - if (pcard->cs_ra.caid == 0x4a30) ecm |= data[0] == 0x50; /* DVN */ - - if (ecm) { - if((pcard->cs_ra.caid >> 8) == 6) { - ep->ep_last_section = data[5]; - section = data[4]; - - } else { - ep->ep_last_section = 0; - section = 0; - } - - channel = pid; - snprintf(chaninfo, sizeof(chaninfo), " (PID %d)", channel); - - LIST_FOREACH(es, &ep->ep_sections, es_link) - if (es->es_section == section) - break; - if (es == NULL) { - es = calloc(1, sizeof(ecm_section_t)); - es->es_section = section; - LIST_INSERT_HEAD(&ep->ep_sections, es, es_link); - } - - if(cccam->cccam_fd == -1) { - // New key, but we are not connected (anymore), can not descramble - descrambler_change_keystate((th_descrambler_t *)ct, DS_READY, 0); - goto end; - } - - if (es->es_keystate == ES_FORBIDDEN || es->es_keystate == ES_IDLE) - goto end; - - es->es_caid = caid; - es->es_provid = provid; - es->es_channel = channel; - es->es_pending = 1; - es->es_resolved = 0; - - if(ct->cs_channel >= 0 && channel != -1 && - ct->cs_channel != channel) { - tvhdebug(LS_CCCAM, "Filtering ECM (PID %d)", channel); - goto end; - } - - //tvhtrace(LS_CCCAM, "send ecm: len=%d sid=%04x caid=%04x provid=%06x", len, sid, caid, provid); - //tvhlog_hexdump(LS_CCCAM, data, len); - - es->es_seq = cccam_send_ecm(cccam, data, len, sid, caid, provid, pcard->id); - - tvhdebug(LS_CCCAM, - "Sending ECM%s section=%d/%d, for service \"%s\" (seqno: %d)", - chaninfo, section, ep->ep_last_section, t->s_dvb_svcname, es->es_seq); - es->es_time = getfastmonoclock(); - } else { - /* TODO: EMM */ - //if (cccam->cccam_forward_emm) - // cccam_send_msg(cccam, data, len, sid, 1, 0, 0); - } - -end: - pthread_mutex_unlock(&t->s_stream_mutex); - pthread_mutex_unlock(&cccam->cccam_mutex); -} - -/** - * cccam_mutex is held - */ -static void -cccam_service_pid_free(cccam_service_t *ct) -{ - ecm_pid_t *ep; - ecm_section_t *es; - - while((ep = LIST_FIRST(&ct->cs_pids)) != NULL) { - while ((es = LIST_FIRST(&ep->ep_sections)) != NULL) { - LIST_REMOVE(es, es_link); - free(es); - } - LIST_REMOVE(ep, ep_link); - free(ep); - } -} - -/** - * cccam_mutex is held - */ -static void -cccam_service_destroy0(th_descrambler_t *td) -{ - cccam_service_t *ct = (cccam_service_t *)td; - int i; - - for (i = 0; i < CCCAM_ES_PIDS; i++) - if (ct->cs_epids[i]) - descrambler_close_pid(ct->cs_mux, ct, - DESCRAMBLER_ECM_PID(ct->cs_epids[i])); - - cccam_service_pid_free(ct); - - LIST_REMOVE(td, td_service_link); - - LIST_REMOVE(ct, cs_link); - - free(ct->td_nicename); - free(ct); -} - -/** - * - */ -static void -cccam_service_destroy(th_descrambler_t *td) -{ - cccam_service_t *ct = (cccam_service_t *)td; - cccam_t *cccam = ct->cs_cccam; - - pthread_mutex_lock(&cccam->cccam_mutex); - cccam_service_destroy0(td); - pthread_mutex_unlock(&cccam->cccam_mutex); -} + uint32_t card_id; + int seq; -/** - * Check if our CAID's matches, and if so, link - * - * global_lock is held. Not that we care about that, but either way, it is. - */ -static void -cccam_service_start(caclient_t *cac, service_t *t) -{ - cccam_t *cccam = (cccam_t *)cac; - cccam_service_t *ct; - th_descrambler_t *td; - elementary_stream_t *st; - caid_t *c; - cs_card_data_t *pcard; - char buf[512]; - int i, reuse = 0, prefpid, prefpid_lock, forcecaid; - - extern const idclass_t mpegts_service_class; - if (!idnode_is_instance(&t->s_id, &mpegts_service_class)) + if (len > 255) { + tvherror(cccam->cc_subsys, "%s:%i: EMM too big (%d bytes)", + cccam->cc_hostname, cccam->cc_port, len); return; - - pthread_mutex_lock(&cccam->cccam_mutex); - pthread_mutex_lock(&t->s_stream_mutex); - - LIST_FOREACH(ct, &cccam->cccam_services, cs_link) { - if (ct->td_service == t && ct->cs_cccam == cccam) - break; - } - prefpid = ((mpegts_service_t *)t)->s_dvb_prefcapid; - prefpid_lock = ((mpegts_service_t *)t)->s_dvb_prefcapid_lock; - forcecaid = ((mpegts_service_t *)t)->s_dvb_forcecaid; - LIST_FOREACH(pcard, &cccam->cccam_cards, cs_card) { - if (!pcard->running) continue; - if (pcard->cs_ra.caid == 0) continue; - TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) { - if (prefpid_lock == PREFCAPID_FORCE && prefpid != st->es_pid) - continue; - LIST_FOREACH(c, &st->es_caids, link) { - if (c->use && c->caid == pcard->cs_ra.caid) - if (!forcecaid || forcecaid == c->caid) - break; - } - if (c) break; - } - if (st) break; - } - if (!pcard) { - if (ct) cccam_service_destroy0((th_descrambler_t*)ct); - goto end; } - if (ct) { - reuse = 1; - for (i = 0; i < CCCAM_ES_PIDS; i++) { - if (!ct->cs_epids[i]) continue; - TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) { - if (st->es_pid != ct->cs_epids[i]) continue; - LIST_FOREACH(c, &st->es_caids, link) - if (c->use && c->caid == pcard->cs_ra.caid) - break; - if (c) break; - } - if (st == NULL) { - descrambler_close_pid(ct->cs_mux, ct, - DESCRAMBLER_ECM_PID(ct->cs_epids[i])); - reuse |= 2; - } - } - goto add; - } - - ct = calloc(1, sizeof(cccam_service_t)); - ct->cs_cccam = cccam; - ct->cs_channel = -1; - ct->cs_mux = ((mpegts_service_t *)t)->s_dvb_mux; - ct->ecm_state = ECM_INIT; - - td = (th_descrambler_t *)ct; - snprintf(buf, sizeof(buf), "cccam-%s-%i-%04X", cccam->cccam_hostname, cccam->cccam_port, pcard->cs_ra.caid); - td->td_nicename = strdup(buf); - td->td_service = t; - td->td_stop = cccam_service_destroy; - td->td_ecm_reset = cccam_ecm_reset; - td->td_ecm_idle = cccam_ecm_idle; - LIST_INSERT_HEAD(&t->s_descramblers, td, td_service_link); - - LIST_INSERT_HEAD(&cccam->cccam_services, ct, cs_link); - - descrambler_change_keystate((th_descrambler_t *)td, DS_READY, 0); - -add: - i = 0; - TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) { - LIST_FOREACH(c, &st->es_caids, link) - if (c->use && c->caid == pcard->cs_ra.caid) { - if (reuse && ct->cs_epids[i] != st->es_pid) reuse |= 2; - ct->cs_epids[i++] = st->es_pid; - break; - } - if (i == CCCAM_ES_PIDS) break; - } - - for (i = 0; i < CCCAM_ES_PIDS; i++) - if (ct->cs_epids[i]) - descrambler_open_pid(ct->cs_mux, ct, - DESCRAMBLER_ECM_PID(ct->cs_epids[i]), - cccam_table_input, t); - - if (reuse & 2) { - ct->cs_channel = -1; - ct->ecm_state = ECM_INIT; - } - - if (reuse != 1) - tvhdebug(LS_CCCAM, "%s %susing CCCAM %s:%d", - service_nicename(t), reuse ? "re" : "", cccam->cccam_hostname, cccam->cccam_port); -end: - pthread_mutex_unlock(&t->s_stream_mutex); - pthread_mutex_unlock(&cccam->cccam_mutex); -} - -/** - * - */ -static void -cccam_free(caclient_t *cac) -{ - cccam_t *cccam = (cccam_t *)cac; - cccam_service_t *ct; - while((ct = LIST_FIRST(&cccam->cccam_services)) != NULL) - cccam_service_destroy((th_descrambler_t *)ct); - - cccam_free_cards(cccam); - free((void *)cccam->cccam_password); - free((void *)cccam->cccam_username); - free((void *)cccam->cccam_hostname); -} - -/** - * - */ -static void -cccam_caid_update(caclient_t *cac, mpegts_mux_t *mux, uint16_t caid, uint16_t pid, int valid) -{ - cccam_t *cccam = (cccam_t *)cac;; - cs_card_data_t *pcard; - - tvhtrace(LS_CCCAM, - "caid update event - client %s mux %p caid %04x (%i) pid %04x (%i) valid %i", - cac->cac_name, mux, caid, caid, pid, pid, valid); - pthread_mutex_lock(&cccam->cccam_mutex); - if (valid < 0 || cccam->cccam_running) { - LIST_FOREACH(pcard, &cccam->cccam_cards, cs_card) { - if (valid < 0 || pcard->cs_ra.caid == caid) { - if (pcard->cccam_mux && pcard->cccam_mux != mux) continue; - if (valid > 0) { - pcard->cccam = cccam; - pcard->cccam_mux = mux; - descrambler_open_emm(mux, pcard, caid, cccam_emm); - } else { - pcard->cccam_mux = NULL; - descrambler_close_emm(mux, pcard, caid); - } - } - } - } - pthread_mutex_unlock(&cccam->cccam_mutex); + seq = atomic_add(&cccam->cc_seq, 1); + caid = pcard->cs_ra.caid; + card_id = pcard->cccam.cs_id; + + buf[ 0] = caid >> 8; + buf[ 1] = caid & 0xff; + buf[ 2] = 0; + buf[ 3] = provid >> 24; + buf[ 4] = provid >> 16; + buf[ 5] = provid >> 8; + buf[ 6] = provid & 0xff; + buf[ 7] = card_id >> 24; + buf[ 8] = card_id >> 16; + buf[ 9] = card_id >> 8; + buf[10] = card_id & 0xff; + buf[11] = len; + memcpy(buf + 12, msg, len); + cccam_send_msg(cccam, MSG_EMM_REQUEST, buf, 12 + len, 1, seq, card_id); } /** * */ -static void -cccam_conf_changed(caclient_t *cac) +static int +cccam_read(void *cc) { - cccam_t *cccam = (cccam_t *)cac; - pthread_t tid; - - if (cac->cac_enabled) { - if (cccam->cccam_hostname == NULL || cccam->cccam_hostname[0] == '\0') { - caclient_set_status(cac, CACLIENT_STATUS_NONE); - return; - } - if (!cccam->cccam_running) { - cccam->cccam_running = 1; - tvhthread_create(&cccam->cccam_tid, NULL, cccam_thread, cccam, "cccam"); - return; - } - pthread_mutex_lock(&cccam->cccam_mutex); - cccam->cccam_reconfigure = 1; - if(cccam->cccam_fd >= 0) - shutdown(cccam->cccam_fd, SHUT_RDWR); - tvh_cond_signal(&cccam->cccam_cond, 0); - pthread_mutex_unlock(&cccam->cccam_mutex); - } else { - if (!cccam->cccam_running) - return; - pthread_mutex_lock(&cccam->cccam_mutex); - cccam->cccam_running = 0; - tvh_cond_signal(&cccam->cccam_cond, 0); - tid = cccam->cccam_tid; - if (cccam->cccam_fd >= 0) - shutdown(cccam->cccam_fd, SHUT_RDWR); - pthread_mutex_unlock(&cccam->cccam_mutex); - pthread_kill(tid, SIGHUP); - pthread_join(tid, NULL); - caclient_set_status(cac, CACLIENT_STATUS_NONE); - } + cccam_t *cccam = cc; + uint8_t buf[CCCAM_NETMSGSIZE]; + const int ka_interval = cccam->cc_keepalive_interval * 2 * 1000; + int r = cccam_read_message(cccam, "Decoderloop", buf, sizeof(buf), ka_interval); + if (r < 0) + return -1; + return cccam_running_reply(cccam, buf, r); } /** @@ -1896,13 +763,13 @@ caclient_cccam_class_cccam_version_list ( void *o, const char *lang ) { static const struct strtab tab[] = { { N_("2.0.11"), CCCAM_VERSION_2_0_11 }, - { N_("2.1.1"), CCCAM_VERSION_2_1_1 }, - { N_("2.1.2"), CCCAM_VERSION_2_1_2 }, - { N_("2.1.3"), CCCAM_VERSION_2_1_3 }, - { N_("2.1.4"), CCCAM_VERSION_2_1_4 }, - { N_("2.2.0"), CCCAM_VERSION_2_2_0 }, - { N_("2.2.1"), CCCAM_VERSION_2_2_1 }, - { N_("2.3.0"), CCCAM_VERSION_2_3_0 }, + { N_("2.1.1"), CCCAM_VERSION_2_1_1 }, + { N_("2.1.2"), CCCAM_VERSION_2_1_2 }, + { N_("2.1.3"), CCCAM_VERSION_2_1_3 }, + { N_("2.1.4"), CCCAM_VERSION_2_1_4 }, + { N_("2.2.0"), CCCAM_VERSION_2_2_0 }, + { N_("2.2.1"), CCCAM_VERSION_2_2_1 }, + { N_("2.3.0"), CCCAM_VERSION_2_3_0 }, }; return strtab2htsmsg(tab, 1, lang); } @@ -1914,38 +781,6 @@ const idclass_t caclient_cccam_class = .ic_class = "caclient_cccam", .ic_caption = N_("CCcam"), .ic_properties = (const property_t[]){ - { - .type = PT_STR, - .id = "username", - .name = N_("Username"), - .desc = N_("Login username."), - .off = offsetof(cccam_t, cccam_username), - .opts = PO_TRIM, - }, - { - .type = PT_STR, - .id = "password", - .name = N_("Password"), - .desc = N_("Login password."), - .off = offsetof(cccam_t, cccam_password), - .opts = PO_PASSWORD - }, - { - .type = PT_STR, - .id = "hostname", - .name = N_("Hostname/IP"), - .desc = N_("Hostname (or IP) of the server."), - .off = offsetof(cccam_t, cccam_hostname), - .def.s = "localhost", - .opts = PO_TRIM, - }, - { - .type = PT_INT, - .id = "port", - .name = N_("Port"), - .desc = N_("Port to connect to."), - .off = offsetof(cccam_t, cccam_port), - }, { .type = PT_STR, .id = "nodeid", @@ -1953,6 +788,7 @@ const idclass_t caclient_cccam_class = .desc = N_("Client node ID. Leave field empty to generate a random ID."), .set = caclient_cccam_nodeid_set, .get = caclient_cccam_nodeid_get, + .group = 1, }, { .type = PT_INT, @@ -1963,32 +799,16 @@ const idclass_t caclient_cccam_class = .list = caclient_cccam_class_cccam_version_list, .def.i = CCCAM_VERSION_2_3_0, .opts = PO_DOC_NLIST, + .group = 1, }, -#if 0 - { - .type = PT_BOOL, - .id = "emm", - .name = N_("Update card (EMM)"), - .desc = N_("Enable/disable offering of Entitlement Management Message updates."), - .off = offsetof(cccam_t, cccam_emm), - .def.i = 1 - }, - { - .type = PT_BOOL, - .id = "emmex", - .name = N_("Updates from one mux (EMM)"), - .desc = N_("Update Entitlement Management Messages from one mux only."), - .off = offsetof(cccam_t, cccam_emmex), - .def.i = 1 - }, -#endif { .type = PT_INT, .id = "keepalive_interval", .name = N_("Keepalive interval (0=disable)"), .desc = N_("Keepalive interval in seconds"), - .off = offsetof(cccam_t, cccam_keepalive_interval), + .off = offsetof(cccam_t, cc_keepalive_interval), .def.i = CCCAM_KEEPALIVE_INTERVAL, + .group = 3, }, { } } @@ -2001,13 +821,21 @@ caclient_t *cccam_create(void) { cccam_t *cccam = calloc(1, sizeof(*cccam)); - pthread_mutex_init(&cccam->cccam_mutex, NULL); - tvh_cond_init(&cccam->cccam_cond); - cccam->cac_free = cccam_free; - cccam->cac_start = cccam_service_start; - cccam->cac_conf_changed = cccam_conf_changed; - cccam->cac_caid_update = cccam_caid_update; - cccam->cccam_keepalive_interval = CCCAM_KEEPALIVE_INTERVAL; - cccam->cccam_version = CCCAM_VERSION_2_3_0; + cccam->cc_subsys = LS_CCCAM; + cccam->cc_id = "cccam"; + + pthread_mutex_init(&cccam->cc_mutex, NULL); + tvh_cond_init(&cccam->cc_cond); + cccam->cac_free = cc_free; + cccam->cac_start = cc_service_start; + cccam->cac_conf_changed = cc_conf_changed; + cccam->cac_caid_update = cc_caid_update; + cccam->cc_keepalive_interval = CCCAM_KEEPALIVE_INTERVAL; + cccam->cccam_version = CCCAM_VERSION_2_3_0; + cccam->cc_init_session = cccam_init_session; + cccam->cc_read = cccam_read; + cccam->cc_send_ecm = cccam_send_ecm; + cccam->cc_send_emm = cccam_send_emm; + cccam->cc_keepalive = cccam_send_ka; return (caclient_t *)cccam; } diff --git a/src/descrambler/cclient.c b/src/descrambler/cclient.c new file mode 100644 index 000000000..1c5a4c57c --- /dev/null +++ b/src/descrambler/cclient.c @@ -0,0 +1,1156 @@ +/* + * tvheadend, card client interface + * Copyright (C) 2007 Andreas Öman + * (C) 2017 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include "tvheadend.h" +#include "tcp.h" +#include "cclient.h" + +static void cc_service_pid_free(cc_service_t *ct); + + +/** + * + */ +cc_card_data_t * +cc_new_card(cclient_t *cc, uint16_t caid, uint8_t *ua, + int pcount, uint8_t **pid, uint8_t **psa) +{ + cc_card_data_t *pcard = NULL; + emm_provider_t *ep; + const char *n; + const uint8_t *id, *sa; + int i, allocated = 0; + + LIST_FOREACH(pcard, &cc->cc_cards, cs_card) + if (pcard->cs_ra.caid == caid) + break; + + if (pcard == NULL) { + pcard = calloc(1, sizeof(cc_card_data_t)); + emm_reass_init(&pcard->cs_ra, caid); + allocated = 1; + } + + if (ua) + memcpy(pcard->cs_ra.ua, ua, 8); + else + memset(pcard->cs_ra.ua, 0, 8); + + free(pcard->cs_ra.providers); + pcard->cs_ra.providers_count = pcount; + pcard->cs_ra.providers = calloc(pcount, sizeof(pcard->cs_ra.providers[0])); + + for (i = 0, ep = pcard->cs_ra.providers; i < pcount; i++, ep++) { + id = pid[i]; + if (id) + ep->id = (id[0] << 16) | (id[1] << 8) | id[2]; + if (psa) + memcpy(ep->sa, psa[i], 8); + } + + n = caid2name(caid) ?: "Unknown"; + + if (ua) { + tvhinfo(cc->cc_subsys, "%s:%i: Connected as user %s " + "to a %s-card [0x%04x : %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x] " + "with %d provider%s", + cc->cc_hostname, cc->cc_port, + cc->cc_username, n, caid, + ua[0], ua[1], ua[2], ua[3], ua[4], ua[5], ua[6], ua[7], + pcount, pcount > 1 ? "s" : ""); + } else { + tvhinfo(cc->cc_subsys, "%s:%i: Connected as user %s " + "to a %s-card [0x%04x] with %d provider%s", + cc->cc_hostname, cc->cc_port, + cc->cc_username, n, caid, + pcount, pcount > 1 ? "s" : ""); + } + + for (i = 0, ep = pcard->cs_ra.providers; i < pcount; i++, ep++) { + if (psa) { + sa = ep->sa; + tvhinfo(cc->cc_subsys, "%s:%i: Provider ID #%d: 0x%04x:0x%06x %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x", + cc->cc_hostname, cc->cc_port, i + 1, caid, ep->id, + sa[0], sa[1], sa[2], sa[3], sa[4], sa[5], sa[6], sa[7]); + } else { + tvhinfo(cc->cc_subsys, "%s:%i: Provider ID #%d: 0x%04x:0x%06x", + cc->cc_hostname, cc->cc_port, i + 1, caid, ep->id); + } + } + + if (cc->cc_emm && ua) { + ua = pcard->cs_ra.ua; + i = ua[0] || ua[1] || ua[2] || ua[3] || + ua[4] || ua[5] || ua[6] || ua[7]; + if (i) + cc_emm_set_allowed(cc, 1); + } + + pcard->cs_running = 1; + if (allocated) + LIST_INSERT_HEAD(&cc->cc_cards, pcard, cs_card); + return pcard; +} + +/** + * + */ +static int +cc_ecm_reset(th_descrambler_t *th) +{ + cc_service_t *ct = (cc_service_t *)th; + cclient_t *cc = ct->cs_client; + cc_ecm_pid_t *ep; + cc_ecm_section_t *es; + + pthread_mutex_lock(&cc->cc_mutex); + descrambler_change_keystate(th, DS_READY, 1); + LIST_FOREACH(ep, &ct->cs_pids, ep_link) + LIST_FOREACH(es, &ep->ep_sections, es_link) + es->es_keystate = ES_UNKNOWN; + ct->ecm_state = ECM_RESET; + pthread_mutex_unlock(&cc->cc_mutex); + return 0; +} + +/** + * + */ +static void +cc_ecm_idle(th_descrambler_t *th) +{ + cc_service_t *ct = (cc_service_t *)th; + cclient_t *cc = ct->cs_client; + cc_ecm_pid_t *ep; + cc_ecm_section_t *es; + + pthread_mutex_lock(&cc->cc_mutex); + LIST_FOREACH(ep, &ct->cs_pids, ep_link) + LIST_FOREACH(es, &ep->ep_sections, es_link) + es->es_keystate = ES_IDLE; + ct->ecm_state = ECM_RESET; + pthread_mutex_unlock(&cc->cc_mutex); +} + +/** + * + */ +void +cc_ecm_reply(cc_service_t *ct, cc_ecm_section_t *es, + int key_type, uint8_t *key_even, uint8_t *key_odd, + int seq) +{ + mpegts_service_t *t = (mpegts_service_t *)ct->td_service; + cclient_t *cc = ct->cs_client; + cc_ecm_pid_t *ep; + cc_ecm_section_t *es2, es3; + char chaninfo[128]; + int i; + int64_t delay = (getfastmonoclock() - es->es_time) / 1000LL; // in ms + + es->es_pending = 0; + + snprintf(chaninfo, sizeof(chaninfo), " (PID %d)", es->es_capid); + + if (key_even == NULL || key_odd == NULL) { + + /* ERROR */ + if (es->es_nok < CC_MAX_NOKS) + es->es_nok++; + + if(es->es_keystate == ES_FORBIDDEN) + return; // We already know it's bad + + if (es->es_nok >= CC_MAX_NOKS) { + tvhdebug(cc->cc_subsys, + "Too many NOKs[%i] for service \"%s\"%s from %s", + es->es_section, t->s_dvb_svcname, chaninfo, ct->td_nicename); + es->es_keystate = ES_FORBIDDEN; + goto forbid; + } + + if (descrambler_resolved((service_t *)t, (th_descrambler_t *)ct)) { + tvhdebug(cc->cc_subsys, + "NOK[%i] from %s: Already has a key for service \"%s\"", + es->es_section, ct->td_nicename, t->s_dvb_svcname); + es->es_nok = CC_MAX_NOKS; /* do not send more ECM requests */ + es->es_keystate = ES_IDLE; + if (ct->td_keystate == DS_READY) + descrambler_change_keystate((th_descrambler_t *)ct, DS_IDLE, 1); + } + + tvhdebug(cc->cc_subsys, + "Received NOK[%i] for service \"%s\"%s " + "(seqno: %d Req delay: %"PRId64" ms)", + es->es_section, t->s_dvb_svcname, chaninfo, seq, delay); + +forbid: + i = 0; + LIST_FOREACH(ep, &ct->cs_pids, ep_link) + LIST_FOREACH(es2, &ep->ep_sections, es_link) + if(es2 && es2 != es && es2->es_nok == 0) { + if (es2->es_pending) + return; + i++; + } + if (i && es->es_nok < CC_MAX_NOKS) + return; + + es->es_keystate = ES_FORBIDDEN; + LIST_FOREACH(ep, &ct->cs_pids, ep_link) { + LIST_FOREACH(es2, &ep->ep_sections, es_link) + if (es2->es_keystate == ES_UNKNOWN || + es2->es_keystate == ES_RESOLVED) + break; + if (es2) + break; + } + + if (ep == NULL) { /* !UNKNOWN && !RESOLVED */ + tvherror(cc->cc_subsys, + "Can not descramble service \"%s\", access denied (seqno: %d " + "Req delay: %"PRId64" ms) from %s", + t->s_dvb_svcname, seq, delay, ct->td_nicename); + descrambler_change_keystate((th_descrambler_t *)ct, DS_FORBIDDEN, 1); + ct->ecm_state = ECM_RESET; + /* this pid is not valid, force full scan */ + if (t->s_dvb_prefcapid == ct->cs_capid && t->s_dvb_prefcapid_lock == PREFCAPID_OFF) + t->s_dvb_prefcapid = 0; + } + return; + + } else { + + es->es_nok = 0; + ct->cs_capid = es->es_capid; + ct->ecm_state = ECM_VALID; + + if(t->s_dvb_prefcapid == 0 || + (t->s_dvb_prefcapid != ct->cs_capid && + t->s_dvb_prefcapid_lock == PREFCAPID_OFF)) { + t->s_dvb_prefcapid = ct->cs_capid; + tvhdebug(cc->cc_subsys, "Saving prefered PID %d for %s", + t->s_dvb_prefcapid, ct->td_nicename); + service_request_save((service_t*)t, 0); + } + + tvhdebug(cc->cc_subsys, + "Received ECM reply%s for service \"%s\" [%d] " + "(seqno: %d Req delay: %"PRId64" ms)", + chaninfo, t->s_dvb_svcname, es->es_section, seq, delay); + + if(es->es_keystate != ES_RESOLVED) + tvhdebug(cc->cc_subsys, + "Obtained DES keys for service \"%s\" in %"PRId64" ms, from %s", + t->s_dvb_svcname, delay, ct->td_nicename); + es->es_keystate = ES_RESOLVED; + es->es_resolved = 1; + + es3 = *es; + pthread_mutex_unlock(&cc->cc_mutex); + descrambler_keys((th_descrambler_t *)ct, key_type, 0, key_even, key_odd); + snprintf(chaninfo, sizeof(chaninfo), "%s:%i", cc->cc_hostname, cc->cc_port); + descrambler_notify((th_descrambler_t *)ct, + es3.es_caid, es3.es_provid, + caid2name(es3.es_caid), + es3.es_capid, delay, + 1, "", chaninfo, cc->cc_id); + pthread_mutex_lock(&cc->cc_mutex); + } +} + +/** + * + */ +static void +cc_invalidate_cards(cclient_t *cc) +{ + cc_card_data_t *cd; + + LIST_FOREACH(cd, &cc->cc_cards, cs_card) + cd->cs_running = 0; +} + +/** + * + */ +static void +cc_free_cards(cclient_t *cc) +{ + cc_card_data_t *cd; + + while((cd = LIST_FIRST(&cc->cc_cards)) != NULL) { + LIST_REMOVE(cd, cs_card); + descrambler_close_emm(cd->cs_mux, cd, cd->cs_ra.caid); + emm_reass_done(&cd->cs_ra); + free(cd); + } +} + +/** + * + */ +static void +cc_flush_services(cclient_t *cc) +{ + cc_service_t *ct; + + LIST_FOREACH(ct, &cc->cc_services, cs_link) + descrambler_flush_table_data(ct->td_service); +} + +/** + * + */ +int +cc_read(cclient_t *cc, void *buf, size_t len, int timeout) +{ + int r; + + pthread_mutex_unlock(&cc->cc_mutex); + r = tcp_read_timeout(cc->cc_fd, buf, len, timeout); + pthread_mutex_lock(&cc->cc_mutex); + + if (r && tvheadend_is_running()) + tvhwarn(cc->cc_subsys, "read error %d (%s)", r, strerror(r)); + + if(cc_must_break(cc)) + return ECONNABORTED; + + return r; +} + +/** + * + */ +void +cc_write_message(cclient_t *cc, cc_message_t *msg, int enq) +{ + tvhtrace(cc->cc_subsys, "%s:%i: sending message len %u enq %d", + cc->cc_hostname, cc->cc_port, msg->cm_len, enq); + tvhlog_hexdump(cc->cc_subsys, msg->cm_data, msg->cm_len); + + if (enq) { + pthread_mutex_lock(&cc->cc_writer_mutex); + TAILQ_INSERT_TAIL(&cc->cc_writeq, msg, cm_link); + tvh_cond_signal(&cc->cc_writer_cond, 0); + pthread_mutex_unlock(&cc->cc_writer_mutex); + } else { + if (tvh_write(cc->cc_fd, msg->cm_data, msg->cm_len)) + tvhinfo(cc->cc_subsys, "write error %s", strerror(errno)); + free(msg); + } +} + +/** + * + */ +static void * +cc_writer_thread(void *aux) +{ + cclient_t *cc = aux; + cc_message_t *cm; + int64_t mono; + int r; + + pthread_mutex_lock(&cc->cc_writer_mutex); + + while(cc->cc_writer_running) { + + if((cm = TAILQ_FIRST(&cc->cc_writeq)) != NULL) { + TAILQ_REMOVE(&cc->cc_writeq, cm, cm_link); + pthread_mutex_unlock(&cc->cc_writer_mutex); + // int64_t ts = getfastmonoclock(); + if (tvh_write(cc->cc_fd, cm->cm_data, cm->cm_len)) + tvhinfo(cc->cc_subsys, "write error %s", strerror(errno)); + // printf("Write took %lld usec\n", getfastmonoclock() - ts); + free(cm); + pthread_mutex_lock(&cc->cc_writer_mutex); + continue; + } + + + /* If nothing is to be sent in keepalive interval seconds we + need to send a keepalive */ + mono = mclk() + sec2mono(cc->cc_keepalive_interval); + do { + r = tvh_cond_timedwait(&cc->cc_writer_cond, &cc->cc_writer_mutex, mono); + if(r == ETIMEDOUT) { + if (cc->cc_keepalive) + cc->cc_keepalive(cc); + break; + } + } while (ERRNO_AGAIN(r)); + } + + pthread_mutex_unlock(&cc->cc_writer_mutex); + return NULL; +} + + + +/** + * + */ +static void +cc_session(cclient_t *cc) +{ + char buf[32]; + pthread_t writer_thread_id; + + if (cc->cc_init_session(cc)) + return; + + /** + * Ok, connection good, reset retry delay to zero + */ + cc->cc_retry_delay = 0; + cc_flush_services(cc); + + /** + * We do all requests from now on in a separate thread + */ + cc->cc_writer_running = 1; + tvh_cond_init(&cc->cc_writer_cond); + pthread_mutex_init(&cc->cc_writer_mutex, NULL); + TAILQ_INIT(&cc->cc_writeq); + snprintf(buf, sizeof(buf), "%s-writer", cc->cc_id); + tvhthread_create(&writer_thread_id, NULL, cc_writer_thread, cc, buf); + + /** + * Mainloop + */ + while(!cc_must_break(cc)) { + if (cc->cc_read(cc)) + break; + } + tvhdebug(cc->cc_subsys, "session thread exiting"); + + /** + * Collect the writer thread + */ + shutdown(cc->cc_fd, SHUT_RDWR); + cc->cc_writer_running = 0; + tvh_cond_signal(&cc->cc_writer_cond, 0); + pthread_join(writer_thread_id, NULL); + tvhdebug(cc->cc_subsys, "Write thread joined"); +} + +/** + * + */ +static void * +cc_thread(void *aux) +{ + cclient_t *cc = aux; + int fd, d, r; + char errbuf[100]; + char hostname[256]; + int port; + int attempts = 0; + int64_t mono; + + pthread_mutex_lock(&cc->cc_mutex); + + while(cc->cc_running) { + + cc_invalidate_cards(cc); + caclient_set_status((caclient_t *)cc, CACLIENT_STATUS_READY); + + snprintf(hostname, sizeof(hostname), "%s", cc->cc_hostname); + port = cc->cc_port; + + tvhinfo(cc->cc_subsys, "Attemping to connect to %s:%d", hostname, port); + + pthread_mutex_unlock(&cc->cc_mutex); + + fd = tcp_connect(hostname, port, NULL, errbuf, sizeof(errbuf), 10); + + pthread_mutex_lock(&cc->cc_mutex); + + if(fd == -1) { + attempts++; + tvhinfo(cc->cc_subsys, + "%s:%d: Connection failed: %s", + hostname, port, errbuf); + } else { + + if(cc->cc_running == 0) { + close(fd); + break; + } + + tvhinfo(cc->cc_subsys, "%s:%i: Connected", hostname, port); + attempts = 0; + + cc->cc_fd = fd; + cc->cc_reconfigure = 0; + + cc_session(cc); + + cc->cc_fd = -1; + close(fd); + tvhinfo(cc->cc_subsys, "%s:%i: Disconnected", hostname, port); + } + + if(cc->cc_running == 0) continue; + if(attempts == 1 || cc->cc_reconfigure) { + cc->cc_reconfigure = 0; + continue; // Retry immediately + } + + caclient_set_status((caclient_t *)cc, CACLIENT_STATUS_DISCONNECTED); + + d = 3; + + tvhinfo(cc->cc_subsys, + "%s:%i: Automatic connection attempt in %d seconds", + cc->cc_hostname, cc->cc_port, d-1); + + mono = mclk() + sec2mono(d); + do { + r = tvh_cond_timedwait(&cc->cc_cond, &cc->cc_mutex, mono); + if (r == ETIMEDOUT) + break; + } while (ERRNO_AGAIN(r)); + } + + tvhinfo(cc->cc_subsys, "%s:%i inactive", cc->cc_hostname, cc->cc_port); + cc_free_cards(cc); + pthread_mutex_unlock(&cc->cc_mutex); + return NULL; +} + + +/** + * + */ +static int +verify_provider(cc_card_data_t *pcard, uint32_t providerid) +{ + int i; + + if(providerid == 0) + return 1; + + for(i = 0; i < pcard->cs_ra.providers_count; i++) + if(providerid == pcard->cs_ra.providers[i].id) + return 1; + return 0; +} + +/** + * + */ +void +cc_emm_set_allowed(cclient_t *cc, int emm_allowed) +{ + cc_card_data_t *pcard; + + if (!emm_allowed) { + tvhinfo(cc->cc_subsys, + "%s:%i: Will not forward EMMs (not allowed by server)", + cc->cc_hostname, cc->cc_port); + } else { + LIST_FOREACH(pcard, &cc->cc_cards, cs_card) + if (pcard->cs_ra.type != CARD_UNKNOWN) break; + if (pcard) { + tvhinfo(cc->cc_subsys, "%s:%i: Will forward EMMs", + cc->cc_hostname, cc->cc_port); + } else { + tvhinfo(cc->cc_subsys, + "%s:%i: Will not forward EMMs (unsupported CA system)", + cc->cc_hostname, cc->cc_port); + emm_allowed = 0; + } + } + cc->cc_forward_emm = !!emm_allowed; +} + +/** + * + */ +static void +cc_emm_send(void *aux, const uint8_t *radata, int ralen, void *mux) +{ + cc_card_data_t *pcard = aux; + cclient_t *cc = pcard->cs_client; + + tvhtrace(cc->cc_subsys, "sending EMM for %04x mux %p", pcard->cs_ra.caid, mux); + tvhlog_hexdump(cc->cc_subsys, radata, ralen); + cc->cc_send_emm(cc, NULL, pcard, 0, radata, ralen); +} + +/** + * + */ +static void +cc_emm(void *opaque, int pid, const uint8_t *data, int len, int emm) +{ + cc_card_data_t *pcard = opaque; + cclient_t *cc; + void *mux; + + if (data == NULL) { /* end-of-data */ + pcard->cs_mux = NULL; + return; + } + if (pcard->cs_mux == NULL) + return; + cc = pcard->cs_client; + pthread_mutex_lock(&cc->cc_mutex); + mux = pcard->cs_mux; + if (pcard->cs_running && cc->cc_forward_emm && cc->cc_writer_running) { + if (cc->cc_emmex) { + if (cc->cc_emm_mux && cc->cc_emm_mux != mux) { + if (cc->cc_emm_update_time + sec2mono(25) > mclk()) + goto end_of_job; + } + cc->cc_emm_update_time = mclk(); + } + cc->cc_emm_mux = mux; + emm_filter(&pcard->cs_ra, data, len, mux, cc_emm_send, pcard); + } +end_of_job: + pthread_mutex_unlock(&cc->cc_mutex); +} + +/** + * + */ +static void +cc_table_input(void *opaque, int pid, const uint8_t *data, int len, int emm) +{ + cc_service_t *ct = opaque; + elementary_stream_t *st; + mpegts_service_t *t = (mpegts_service_t*)ct->td_service; + cclient_t *cc = ct->cs_client; + int capid, section, ecm; + cc_ecm_pid_t *ep; + cc_ecm_section_t *es; + char chaninfo[32]; + cc_card_data_t *pcard = NULL; + caid_t *c; + uint16_t caid; + uint32_t provid; + + if (data == NULL) + return; + + if(len > 4096) + return; + + pthread_mutex_lock(&cc->cc_mutex); + pthread_mutex_lock(&t->s_stream_mutex); + + if (ct->td_keystate == DS_IDLE) + goto end; + + if (ct->ecm_state == ECM_RESET) { + /* clean all */ + cc_service_pid_free(ct); + /* move to init state */ + ct->ecm_state = ECM_INIT; + ct->cs_capid = -1; + t->s_dvb_prefcapid = 0; + tvhdebug(cc->cc_subsys, "%s:%i: Reset after unexpected or no reply for service \"%s\"", + cc->cc_hostname, cc->cc_port, t->s_dvb_svcname); + } + + LIST_FOREACH(ep, &ct->cs_pids, ep_link) + if(ep->ep_capid == pid) break; + + if(ep == NULL) { + if (ct->ecm_state == ECM_INIT) { + // Validate prefered ECM PID + tvhdebug(cc->cc_subsys, "%s:%i: ECM state INIT", + cc->cc_hostname, cc->cc_port); + + if(t->s_dvb_prefcapid_lock != PREFCAPID_OFF) { + st = service_stream_find((service_t*)t, t->s_dvb_prefcapid); + if (st && st->es_type == SCT_CA) + LIST_FOREACH(c, &st->es_caids, link) + LIST_FOREACH(pcard, &cc->cc_cards, cs_card) + if(pcard->cs_running && + pcard->cs_ra.caid == c->caid && + verify_provider(pcard, c->providerid)) + goto prefcapid_ok; + tvhdebug(cc->cc_subsys, "%s:%i: Invalid prefered ECM (PID %d) found for service \"%s\"", + cc->cc_hostname, cc->cc_port, + t->s_dvb_prefcapid, t->s_dvb_svcname); + t->s_dvb_prefcapid = 0; + } + +prefcapid_ok: + if(t->s_dvb_prefcapid == pid || t->s_dvb_prefcapid == 0 || + t->s_dvb_prefcapid_lock == PREFCAPID_OFF) { + ep = calloc(1, sizeof(cc_ecm_pid_t)); + ep->ep_capid = pid; + LIST_INSERT_HEAD(&ct->cs_pids, ep, ep_link); + tvhdebug(cc->cc_subsys, "%s:%i: Insert %s ECM (PID %d) for service \"%s\"", + cc->cc_hostname, cc->cc_port, + t->s_dvb_prefcapid ? "preferred" : "new", pid, t->s_dvb_svcname); + } + } + if(ep == NULL) + goto end; + } + + st = service_stream_find((service_t *)t, pid); + if (st) { + LIST_FOREACH(c, &st->es_caids, link) + LIST_FOREACH(pcard, &cc->cc_cards, cs_card) + if(pcard->cs_running && + pcard->cs_ra.caid == c->caid && + verify_provider(pcard, c->providerid)) + goto found; + } + + goto end; + +found: + caid = c->caid; + provid = c->providerid; + + ecm = data[0] == 0x80 || data[1] == 0x81; + if (pcard->cs_ra.caid == 0x4a30) ecm |= data[0] == 0x50; /* DVN */ + + if (ecm) { + if((pcard->cs_ra.caid >> 8) == 6) { + ep->ep_last_section = data[5]; + section = data[4]; + } else { + ep->ep_last_section = 0; + section = 0; + } + + capid = pid; + snprintf(chaninfo, sizeof(chaninfo), " (PID %d)", capid); + + LIST_FOREACH(es, &ep->ep_sections, es_link) + if (es->es_section == section) + break; + if (es == NULL) { + es = calloc(1, sizeof(cc_ecm_section_t)); + es->es_section = section; + LIST_INSERT_HEAD(&ep->ep_sections, es, es_link); + } + + if(cc->cc_fd == -1) { + // New key, but we are not connected (anymore), can not descramble + descrambler_change_keystate((th_descrambler_t *)ct, DS_READY, 0); + goto end; + } + + if (es->es_keystate == ES_FORBIDDEN || es->es_keystate == ES_IDLE) + goto end; + + es->es_caid = caid; + es->es_provid = provid; + es->es_capid = capid; + es->es_pending = 1; + es->es_resolved = 0; + + if(ct->cs_capid >= 0 && capid > 0 && ct->cs_capid != capid) { + tvhdebug(cc->cc_subsys, "%s:%i: Filtering ECM (PID %d)", + cc->cc_hostname, cc->cc_port, capid); + goto end; + } + + es->es_seq = cc->cc_send_ecm(cc, ct, es, pcard, data, len); + + tvhdebug(cc->cc_subsys, + "%s:%i: Sending ECM%s section=%d/%d, for service \"%s\" (seqno: %d)", + cc->cc_hostname, cc->cc_port, chaninfo, section, + ep->ep_last_section, t->s_dvb_svcname, es->es_seq); + es->es_time = getfastmonoclock(); + } else { + if (cc->cc_forward_emm && data[0] >= 0x82 && data[0] <= 0x92) { + tvhtrace(cc->cc_subsys, "%s:%i: sending EMM for %04X:%06X service \"%s\"", + cc->cc_hostname, cc->cc_port, pcard->cs_ra.caid, provid, + t->s_dvb_svcname); + tvhlog_hexdump(cc->cc_subsys, data, len); + cc->cc_send_emm(cc, ct, pcard, provid, data, len); + } + } + +end: + pthread_mutex_unlock(&t->s_stream_mutex); + pthread_mutex_unlock(&cc->cc_mutex); +} + +/** + * cc_mutex is held + */ +static void +cc_service_pid_free(cc_service_t *ct) +{ + cc_ecm_pid_t *ep; + cc_ecm_section_t *es; + + while((ep = LIST_FIRST(&ct->cs_pids)) != NULL) { + while ((es = LIST_FIRST(&ep->ep_sections)) != NULL) { + LIST_REMOVE(es, es_link); + free(es); + } + LIST_REMOVE(ep, ep_link); + free(ep); + } +} + +/** + * cc_mutex is held + */ +static void +cc_service_destroy0(th_descrambler_t *td) +{ + cc_service_t *ct = (cc_service_t *)td; + int i; + + for (i = 0; i < ct->cs_epids.count; i++) + descrambler_close_pid(ct->cs_mux, ct, ct->cs_epids.pids[i].pid); + + cc_service_pid_free(ct); + + LIST_REMOVE(td, td_service_link); + + LIST_REMOVE(ct, cs_link); + + free(ct->td_nicename); + free(ct); +} + +/** + * cc_mutex is held + */ +static void +cc_service_destroy(th_descrambler_t *td) +{ + cc_service_t *ct = (cc_service_t *)td; + cclient_t *cc = ct->cs_client; + + pthread_mutex_lock(&cc->cc_mutex); + cc_service_destroy0(td); + pthread_mutex_unlock(&cc->cc_mutex); +} + +/** + * Check if our CAID's matches, and if so, link + * + * global_lock is held. Not that we care about that, but either way, it is. + */ +void +cc_service_start(caclient_t *cac, service_t *t) +{ + cclient_t *cc = (cclient_t *)cac; + cc_service_t *ct; + th_descrambler_t *td; + elementary_stream_t *st; + caid_t *c; + cc_card_data_t *pcard; + char buf[512]; + int i, reuse = 0, prefpid, prefpid_lock, forcecaid; + mpegts_apids_t epids; + + extern const idclass_t mpegts_service_class; + if (!idnode_is_instance(&t->s_id, &mpegts_service_class)) + return; + + pthread_mutex_lock(&cc->cc_mutex); + pthread_mutex_lock(&t->s_stream_mutex); + LIST_FOREACH(ct, &cc->cc_services, cs_link) { + if (ct->td_service == t && ct->cs_client == cc) + break; + } + prefpid = ((mpegts_service_t *)t)->s_dvb_prefcapid; + prefpid_lock = ((mpegts_service_t *)t)->s_dvb_prefcapid_lock; + forcecaid = ((mpegts_service_t *)t)->s_dvb_forcecaid; + LIST_FOREACH(pcard, &cc->cc_cards, cs_card) { + if (!pcard->cs_running) continue; + if (pcard->cs_ra.caid == 0) continue; + TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) { + if (prefpid_lock == PREFCAPID_FORCE && prefpid != st->es_pid) + continue; + LIST_FOREACH(c, &st->es_caids, link) { + if (c->use && c->caid == pcard->cs_ra.caid) + if (!forcecaid || forcecaid == c->caid) + break; + } + if (c) break; + } + if (st) break; + } + if (!pcard) { + if (ct) cc_service_destroy0((th_descrambler_t*)ct); + goto end; + } + if (ct) { + reuse = 1; + for (i = 0; i < ct->cs_epids.count; i++) { + TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) { + if (st->es_pid != ct->cs_epids.pids[i].pid) continue; + LIST_FOREACH(c, &st->es_caids, link) + if (c->use && c->caid == pcard->cs_ra.caid) + break; + if (c) break; + } + if (st == NULL) { + descrambler_close_pid(ct->cs_mux, ct, ct->cs_epids.pids[i].pid); + reuse |= 2; + } + } + goto add; + } + + if (cc->cc_alloc_service) { + ct = cc->cc_alloc_service(cc); + } else { + ct = calloc(1, sizeof(*ct)); + } + ct->cs_client = cc; + ct->cs_capid = -1; + ct->cs_mux = ((mpegts_service_t *)t)->s_dvb_mux; + ct->ecm_state = ECM_INIT; + + td = (th_descrambler_t *)ct; + snprintf(buf, sizeof(buf), "cc-%s-%i-%04X", cc->cc_hostname, cc->cc_port, pcard->cs_ra.caid); + td->td_nicename = strdup(buf); + td->td_service = t; + td->td_stop = cc_service_destroy; + td->td_ecm_reset = cc_ecm_reset; + td->td_ecm_idle = cc_ecm_idle; + LIST_INSERT_HEAD(&t->s_descramblers, td, td_service_link); + + LIST_INSERT_HEAD(&cc->cc_services, ct, cs_link); + + descrambler_change_keystate(td, DS_READY, 0); + +add: + i = 0; + mpegts_pid_init(&epids); + TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) { + LIST_FOREACH(c, &st->es_caids, link) + if (c->use && c->caid == pcard->cs_ra.caid) + mpegts_pid_add(&epids, st->es_pid, 0); + } + if (mpegts_pid_cmp(&ct->cs_epids, &epids)) + reuse |= 2; + mpegts_pid_copy(&ct->cs_epids, &epids); + mpegts_pid_done(&epids); + + for (i = 0; i < ct->cs_epids.count; i++) + descrambler_open_pid(ct->cs_mux, ct, ct->cs_epids.pids[i].pid, + cc_table_input, t); + + if (reuse & 2) { + ct->cs_capid = -1; + ct->ecm_state = ECM_INIT; + } + + if (reuse != 1) + tvhdebug(cc->cc_subsys, "%s %susing CWC %s:%d", + service_nicename(t), reuse ? "re" : "", cc->cc_hostname, cc->cc_port); + +end: + pthread_mutex_unlock(&t->s_stream_mutex); + pthread_mutex_unlock(&cc->cc_mutex); +} + +/** + * + */ +void +cc_free(caclient_t *cac) +{ + cclient_t *cc = (cclient_t *)cac; + cc_service_t *ct; + + while((ct = LIST_FIRST(&cc->cc_services)) != NULL) + cc_service_destroy((th_descrambler_t *)ct); + + cc_free_cards(cc); + free((void *)cc->cc_password); + free((void *)cc->cc_username); + free((void *)cc->cc_hostname); +} + +/** + * + */ +void +cc_caid_update(caclient_t *cac, mpegts_mux_t *mux, uint16_t caid, uint16_t pid, int valid) +{ + cclient_t *cc = (cclient_t *)cac;; + cc_card_data_t *pcard; + + tvhtrace(cc->cc_subsys, + "caid update event - client %s mux %p caid %04x (%i) pid %04x (%i) valid %i", + cac->cac_name, mux, caid, caid, pid, pid, valid); + pthread_mutex_lock(&cc->cc_mutex); + if (valid < 0 || cc->cc_running) { + LIST_FOREACH(pcard, &cc->cc_cards, cs_card) { + if (valid < 0 || pcard->cs_ra.caid == caid) { + if (pcard->cs_mux && pcard->cs_mux != mux) continue; + if (valid > 0) { + pcard->cs_client = cc; + pcard->cs_mux = mux; + descrambler_open_emm(mux, pcard, caid, cc_emm); + } else { + pcard->cs_mux = NULL; + descrambler_close_emm(mux, pcard, caid); + } + } + } + } + pthread_mutex_unlock(&cc->cc_mutex); +} + +/** + * + */ +void +cc_conf_changed(caclient_t *cac) +{ + cclient_t *cc = (cclient_t *)cac; + pthread_t tid; + + if (cac->cac_enabled) { + if (cc->cc_hostname == NULL || cc->cc_hostname[0] == '\0') { + caclient_set_status(cac, CACLIENT_STATUS_NONE); + return; + } + if (!cc->cc_running) { + cc->cc_running = 1; + tvhthread_create(&cc->cc_tid, NULL, cc_thread, cc, "cc"); + return; + } + pthread_mutex_lock(&cc->cc_mutex); + cc->cc_reconfigure = 1; + if(cc->cc_fd >= 0) + shutdown(cc->cc_fd, SHUT_RDWR); + tvh_cond_signal(&cc->cc_cond, 0); + pthread_mutex_unlock(&cc->cc_mutex); + } else { + if (!cc->cc_running) + return; + pthread_mutex_lock(&cc->cc_mutex); + cc->cc_running = 0; + tvh_cond_signal(&cc->cc_cond, 0); + tid = cc->cc_tid; + if (cc->cc_fd >= 0) + shutdown(cc->cc_fd, SHUT_RDWR); + pthread_mutex_unlock(&cc->cc_mutex); + pthread_kill(tid, SIGHUP); + pthread_join(tid, NULL); + caclient_set_status(cac, CACLIENT_STATUS_NONE); + } +} + +/** + * + */ +const idclass_t caclient_cc_class = +{ + .ic_super = &caclient_class, + .ic_class = "caclient_card", + .ic_caption = N_("Card client"), + .ic_groups = (const property_group_t[]) { + { + .name = N_("Login information"), + .number = 1, + }, + { + .name = N_("EMM"), + .number = 2, + }, + { + .name = N_("Connection"), + .number = 3, + }, + {} + }, + .ic_properties = (const property_t[]){ + { + .type = PT_STR, + .id = "username", + .name = N_("Username"), + .desc = N_("Login username."), + .off = offsetof(cclient_t, cc_username), + .opts = PO_TRIM, + .group = 1, + }, + { + .type = PT_STR, + .id = "password", + .name = N_("Password"), + .desc = N_("Login password."), + .off = offsetof(cclient_t, cc_password), + .opts = PO_PASSWORD, + .group = 1, + }, + { + .type = PT_STR, + .id = "hostname", + .name = N_("Hostname/IP"), + .desc = N_("Hostname (or IP) of the server."), + .off = offsetof(cclient_t, cc_hostname), + .def.s = "localhost", + .opts = PO_TRIM, + .group = 1, + }, + { + .type = PT_INT, + .id = "port", + .name = N_("Port"), + .desc = N_("Port to connect to."), + .off = offsetof(cclient_t, cc_port), + .group = 1, + }, + { + .type = PT_BOOL, + .id = "emm", + .name = N_("Update card (EMM)"), + .desc = N_("Enable/disable offering of Entitlement Management Message updates."), + .off = offsetof(cclient_t, cc_emm), + .def.i = 1, + .group = 2, + }, + { + .type = PT_BOOL, + .id = "emmex", + .name = N_("Updates from one mux (EMM)"), + .desc = N_("Update Entitlement Management Messages from one mux only."), + .off = offsetof(cclient_t, cc_emmex), + .def.i = 1, + .group = 2, + }, + { + .type = PT_INT, + .id = "keepalive_interval", + .name = N_("Keepalive interval"), + .desc = N_("Keepalive interval in seconds"), + .off = offsetof(cclient_t, cc_keepalive_interval), + .def.i = CC_KEEPALIVE_INTERVAL, + .group = 3, + }, + { } + } +}; diff --git a/src/descrambler/cclient.h b/src/descrambler/cclient.h new file mode 100644 index 000000000..e9049ad01 --- /dev/null +++ b/src/descrambler/cclient.h @@ -0,0 +1,211 @@ +/* + * tvheadend, network card client + * Copyright (C) 2007 Andreas Öman + * (C) 2017 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "caclient.h" +#include "descrambler.h" +#include "emm_reass.h" +#include "input.h" + +/** + * + */ +#define CC_KEEPALIVE_INTERVAL 30 +#define CC_MAX_NOKS 3 + +/** + * + */ +typedef struct cc_ecm_section { + LIST_ENTRY(cc_ecm_section) es_link; + + enum { + ES_UNKNOWN, + ES_RESOLVED, + ES_FORBIDDEN, + ES_IDLE + } es_keystate; + + int es_section; + + uint16_t es_capid; + uint16_t es_caid; + uint32_t es_provid; + + uint32_t es_seq; + uint8_t es_nok; + uint8_t es_pending; + uint8_t es_resolved; + int64_t es_time; // time request was sent + + union { + struct { + uint32_t es_card_id; + } cccam; + }; + +} cc_ecm_section_t; + +/** + * + */ +typedef struct cc_ecm_pid { + LIST_ENTRY(cc_ecm_pid) ep_link; + + uint16_t ep_capid; + + int ep_last_section; + LIST_HEAD(, cc_ecm_section) ep_sections; +} cc_ecm_pid_t; + +/** + * + */ +typedef struct cc_service { + th_descrambler_t; + + void *cs_client; + + LIST_ENTRY(cc_service) cs_link; + + uint16_t cs_capid; + mpegts_apids_t cs_epids; + + mpegts_mux_t *cs_mux; + + /** + * ECM Status + */ + enum { + ECM_INIT, + ECM_VALID, + ECM_RESET + } ecm_state; + + LIST_HEAD(, cc_ecm_pid) cs_pids; + +} cc_service_t; + +/** + * + */ +typedef struct cc_message { + TAILQ_ENTRY(cc_message) cm_link; + uint32_t cm_len; + uint8_t cm_data[0]; +} cc_message_t; + +/** + * + */ +typedef struct cc_card_data { + LIST_ENTRY(cc_card_data) cs_card; + emm_reass_t cs_ra; + void *cs_client; + mpegts_mux_t *cs_mux; + uint8_t cs_running; + union { + struct { + uint32_t cs_id; + uint32_t cs_remote_id; + uint8_t cs_hop; + uint8_t cs_reshare; + } cccam; + }; +} cc_card_data_t; + +/** + * + */ +typedef struct cclient { + caclient_t; + + int cc_subsys; + const char *cc_id; + + /* Callbacks */ + void *(*cc_alloc_service)(void *cc); + int (*cc_init_session)(void *cc); + int (*cc_read)(void *cc); + void (*cc_keepalive)(void *cc); + uint32_t (*cc_send_ecm)(void *cc, cc_service_t *ct, cc_ecm_section_t *es, + cc_card_data_t *pcard, const uint8_t *data, int len); + void (*cc_send_emm)(void *cc, cc_service_t *ct, cc_card_data_t *pcard, + uint32_t provid, const uint8_t *data, int len); + + /* Connection */ + int cc_fd; + int cc_keepalive_interval; + int cc_retry_delay; + + /* Thread */ + pthread_t cc_tid; + tvh_cond_t cc_cond; + pthread_mutex_t cc_mutex; + + /* Database */ + LIST_HEAD(, cc_service) cc_services; + LIST_HEAD(, cc_card_data) cc_cards; + + /* Writer */ + int cc_seq; + TAILQ_HEAD(, cc_message) cc_writeq; + pthread_mutex_t cc_writer_mutex; + tvh_cond_t cc_writer_cond; + uint8_t cc_writer_running; + + /* Emm forwarding */ + int cc_forward_emm; + + /* Emm exclusive update */ + int64_t cc_emm_update_time; + void *cc_emm_mux; + + /* From configuration */ + char *cc_username; + char *cc_password; + char *cc_hostname; + int cc_port; + int cc_emm; + int cc_emmex; + + uint8_t cc_running; + uint8_t cc_reconfigure; +} cclient_t; + +/* + * + */ +static inline int cc_must_break(cclient_t *cc) + { return !cc->cc_running || !cc->cac_enabled || cc->cc_reconfigure; } + +cc_card_data_t *cc_new_card + (cclient_t *cc, uint16_t caid, uint8_t *ua, int pcount, uint8_t **pid, uint8_t **psa); +void cc_emm_set_allowed(cclient_t *cc, int emm_allowed); + +void cc_ecm_reply + (cc_service_t *ct, cc_ecm_section_t *es, int key_type, + uint8_t *key_even, uint8_t *key_odd, int seq); + +void cc_write_message(cclient_t *cc, cc_message_t *msg, int enq); +int cc_read(cclient_t *cc, void *buf, size_t len, int timeout); + +void cc_service_start(caclient_t *cac, service_t *t); +void cc_free(caclient_t *cac); +void cc_caid_update(caclient_t *cac, mpegts_mux_t *mux, uint16_t caid, uint16_t pid, int valid); +void cc_conf_changed(caclient_t *cac); diff --git a/src/descrambler/cwc.c b/src/descrambler/cwc.c index 0ab76943a..94f0c65f5 100644 --- a/src/descrambler/cwc.c +++ b/src/descrambler/cwc.c @@ -1,6 +1,7 @@ /* * tvheadend, CWC interface * Copyright (C) 2007 Andreas Öman + * (C) 2017 Jaroslav Kysela * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,39 +17,16 @@ * along with this program. If not, see . */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include #include #include "tvheadend.h" -#include "tcp.h" -#include "caclient.h" -#include "descrambler/caid.h" -#include "descrambler/emm_reass.h" -#include "atomic.h" -#include "subscriptions.h" -#include "service.h" -#include "input.h" +#include "cclient.h" /** * */ #define TVHEADEND_PROTOCOL_ID 0x6502 -#define CWC_KEEPALIVE_INTERVAL 30 -#define CWC_ES_PIDS 8 -#define CWC_MAX_NOKS 3 #define CWS_NETMSGSIZE 1024 #define CWS_FIRSTCMDNO 0xe0 @@ -77,170 +55,25 @@ typedef enum { } net_msg_type_t; -/** - * - */ -TAILQ_HEAD(cwc_queue, cwc); -LIST_HEAD(cwc_cards_list, cs_card_data); -LIST_HEAD(cwc_service_list, cwc_service); -TAILQ_HEAD(cwc_message_queue, cwc_message); -LIST_HEAD(ecm_section_list, ecm_section); -static char *crypt_md5(const char *pw, const char *salt); - -/** - * - */ -typedef struct ecm_section { - LIST_ENTRY(ecm_section) es_link; - - enum { - ES_UNKNOWN, - ES_RESOLVED, - ES_FORBIDDEN, - ES_IDLE - } es_keystate; - - int es_section; - int es_channel; - uint16_t es_caid; - uint16_t es_provid; - - uint16_t es_seq; - char es_nok; - char es_pending; - char es_resolved; - int64_t es_time; // time request was sent - -} ecm_section_t; - - -/** - * - */ -typedef struct ecm_pid { - LIST_ENTRY(ecm_pid) ep_link; - - uint16_t ep_pid; - - int ep_last_section; - LIST_HEAD(, ecm_section) ep_sections; -} ecm_pid_t; - - -/** - * - */ -typedef struct cwc_service { - th_descrambler_t; - - struct cwc *cs_cwc; - - LIST_ENTRY(cwc_service) cs_link; - - int cs_channel; - int cs_epids[CWC_ES_PIDS]; - mpegts_mux_t *cs_mux; - - /** - * ECM Status - */ - enum { - ECM_INIT, - ECM_VALID, - ECM_RESET - } ecm_state; - - LIST_HEAD(, ecm_pid) cs_pids; - -} cwc_service_t; - - -/** - * - */ -typedef struct cwc_message { - TAILQ_ENTRY(cwc_message) cm_link; - int cm_len; - uint8_t cm_data[CWS_NETMSGSIZE]; -} cwc_message_t; - -struct cwc; - -typedef struct cs_card_data { - - LIST_ENTRY(cs_card_data) cs_card; - - emm_reass_t cs_ra; - - struct cwc *cwc; - mpegts_mux_t *cwc_mux; - int running; - -} cs_card_data_t; - /** * */ typedef struct cwc { - caclient_t; - - int cwc_fd; - - int cwc_keepalive_interval; - int cwc_retry_delay; - - pthread_t cwc_tid; - - tvh_cond_t cwc_cond; - - pthread_mutex_t cwc_mutex; - pthread_mutex_t cwc_writer_mutex; - tvh_cond_t cwc_writer_cond; - int cwc_writer_running; - struct cwc_message_queue cwc_writeq; - - struct cwc_service_list cwc_services; - - - struct cwc_cards_list cwc_cards; - int cwc_seq; + cclient_t; DES_key_schedule cwc_k1, cwc_k2; - uint8_t cwc_buf[256]; - int cwc_bufptr; - - /* Emm forwarding */ - int cwc_forward_emm; - - /* Emm exclusive update */ - int64_t cwc_emm_update_time; - void *cwc_emm_mux; - /* From configuration */ uint8_t cwc_confedkey[14]; - char *cwc_username; - char *cwc_password; char *cwc_password_salted; /* salted version */ - char *cwc_hostname; - int cwc_port; - int cwc_emm; - int cwc_emmex; - - const char *cwc_errtxt; - - int cwc_running; - int cwc_reconfigure; } cwc_t; /** * */ - -static void cwc_service_pid_free(cwc_service_t *ct); - +static char * crypt_md5(const char *pw, const char *salt); /** * @@ -387,23 +220,28 @@ des_make_session_key(cwc_t *cwc) /** * Note, this function is called from multiple threads so beware of - * locking / race issues (Note how we use atomic_add() to generate * the ID) */ -static int -cwc_send_msg(cwc_t *cwc, const uint8_t *msg, size_t len, int sid, int enq, uint16_t st_caid , uint32_t st_provider ) +static uint32_t +cwc_send_msg(void *cc, const uint8_t *msg, size_t len, + int sid, int enq, uint16_t st_caid, uint32_t st_provider) { - cwc_message_t *cm = malloc(sizeof(cwc_message_t)); - uint8_t *buf = cm->cm_data; + cwc_t *cwc = cc; + cc_message_t *cm; + uint8_t *buf; int seq, ret; - if (len < 3 || len + 12 > CWS_NETMSGSIZE) { - free(cm); + if (len < 3) + return -1; + + cm = malloc(sizeof(cc_message_t) + 12 + len); + + if (cm == NULL) return -1; - } - seq = atomic_add(&cwc->cwc_seq, 1); + seq = atomic_add(&cwc->cc_seq, 1); + buf = cm->cm_data; buf[0] = buf[1] = 0; buf[2] = (seq >> 8) & 0xff; buf[3] = seq & 0xff; @@ -420,43 +258,28 @@ cwc_send_msg(cwc_t *cwc, const uint8_t *msg, size_t len, int sid, int enq, uint1 // adding packet header size len += 12; - if((ret = des_encrypt(buf, len, cwc)) <= 0) { + if ((ret = des_encrypt(buf, len, cwc)) <= 0) { free(cm); return -1; } len = (size_t)ret; - tvhtrace(LS_CWC, "sending message sid %d len %zu enq %d", sid, len, enq); - tvhlog_hexdump(LS_CWC, buf, len); - buf[0] = (len - 2) >> 8; buf[1] = (len - 2) & 0xff; - if(enq) { - cm->cm_len = len; - pthread_mutex_lock(&cwc->cwc_writer_mutex); - TAILQ_INSERT_TAIL(&cwc->cwc_writeq, cm, cm_link); - tvh_cond_signal(&cwc->cwc_writer_cond, 0); - pthread_mutex_unlock(&cwc->cwc_writer_mutex); - } else { - if (tvh_write(cwc->cwc_fd, buf, len)) - tvhinfo(LS_CWC, "write error %s", strerror(errno)); + cm->cm_len = len; + cc_write_message(cc, cm, enq); - free(cm); - } return seq & 0xffff; } - - /** * Card data command */ - static void cwc_send_data_req(cwc_t *cwc) { - uint8_t buf[CWS_NETMSGSIZE]; + uint8_t buf[3]; buf[0] = MSG_CARD_DATA_REQ; buf[1] = 0; @@ -470,9 +293,10 @@ cwc_send_data_req(cwc_t *cwc) * Send keep alive */ static void -cwc_send_ka(cwc_t *cwc) +cwc_send_ka(void *cc) { - uint8_t buf[CWS_NETMSGSIZE]; + cwc_t *cwc = cc; + uint8_t buf[3]; buf[0] = MSG_KEEPALIVE; buf[1] = 0; @@ -481,105 +305,30 @@ cwc_send_ka(cwc_t *cwc) cwc_send_msg(cwc, buf, 3, 0, 0, 0, 0); } -/** - * - */ -static cs_card_data_t * -cwc_new_card(cwc_t *cwc, uint16_t caid, uint8_t *ua, - int pcount, uint8_t **pid, uint8_t **psa) -{ - cs_card_data_t *pcard = NULL; - emm_provider_t *ep; - const char *n; - const uint8_t *id, *sa; - int i, allocated = 0; - - LIST_FOREACH(pcard, &cwc->cwc_cards, cs_card) - if (pcard->cs_ra.caid == caid) - break; - - if (pcard == NULL) { - pcard = calloc(1, sizeof(cs_card_data_t)); - emm_reass_init(&pcard->cs_ra, caid); - allocated = 1; - } - - if (ua) - memcpy(pcard->cs_ra.ua, ua, 8); - else - memset(pcard->cs_ra.ua, 0, 8); - - free(pcard->cs_ra.providers); - pcard->cs_ra.providers_count = pcount; - pcard->cs_ra.providers = calloc(pcount, sizeof(pcard->cs_ra.providers[0])); - - for (i = 0, ep = pcard->cs_ra.providers; i < pcount; i++, ep++) { - id = pid[i]; - if (id) - ep->id = (id[0] << 16) | (id[1] << 8) | id[2]; - if (psa) - memcpy(ep->sa, psa[i], 8); - } - - n = caid2name(caid) ?: "Unknown"; - - if (ua) { - tvhinfo(LS_CWC, "%s:%i: Connected as user %s " - "to a %s-card [0x%04x : %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x] " - "with %d provider%s", - cwc->cwc_hostname, cwc->cwc_port, - cwc->cwc_username, n, caid, - ua[0], ua[1], ua[2], ua[3], ua[4], ua[5], ua[6], ua[7], - pcount, pcount > 1 ? "s" : ""); - } else { - tvhinfo(LS_CWC, "%s:%i: Connected as user %s " - "to a %s-card [0x%04x] with %d provider%s", - cwc->cwc_hostname, cwc->cwc_port, - cwc->cwc_username, n, caid, - pcount, pcount > 1 ? "s" : ""); - } - - for (i = 0, ep = pcard->cs_ra.providers; i < pcount; i++, ep++) { - if (psa) { - sa = ep->sa; - tvhinfo(LS_CWC, "%s:%i: Provider ID #%d: 0x%04x:0x%06x %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x", - cwc->cwc_hostname, cwc->cwc_port, i + 1, caid, ep->id, - sa[0], sa[1], sa[2], sa[3], sa[4], sa[5], sa[6], sa[7]); - } else { - tvhinfo(LS_CWC, "%s:%i: Provider ID #%d: 0x%04x:0x%06x", - cwc->cwc_hostname, cwc->cwc_port, i + 1, caid, ep->id); - } - } - - pcard->running = 1; - if (allocated) - LIST_INSERT_HEAD(&cwc->cwc_cards, pcard, cs_card); - return pcard; -} - /** * Handle reply to card data request */ static int cwc_decode_card_data_reply(cwc_t *cwc, uint8_t *msg, int len) { - int plen, i, emm_allowed; + int plen, i; unsigned int nprov; - uint8_t **pid, **psa, *ua, *msg2; - cs_card_data_t *pcard; + uint8_t **pid, **psa, *msg2; msg += 12; len -= 12; if(len < 3) { - tvhinfo(LS_CWC, "Invalid card data reply"); + tvhinfo(cwc->cc_subsys, "%s:%i: Invalid card data reply", + cwc->cc_hostname, cwc->cc_port); return -1; } plen = (msg[1] & 0xf) << 8 | msg[2]; if(plen < 14) { - tvhinfo(LS_CWC, "Invalid card data reply (message)"); + tvhinfo(cwc->cc_subsys, "%s:%i: Invalid card data reply (message)", + cwc->cc_hostname, cwc->cc_port); return -1; } @@ -589,7 +338,8 @@ cwc_decode_card_data_reply(cwc_t *cwc, uint8_t *msg, int len) plen -= 12; if(plen < nprov * 11) { - tvhinfo(LS_CWC, "Invalid card data reply (provider list)"); + tvhinfo(cwc->cc_subsys, "%s:%i: Invalid card data reply (provider list)", + cwc->cc_hostname, cwc->cc_port); return -1; } @@ -603,28 +353,8 @@ cwc_decode_card_data_reply(cwc_t *cwc, uint8_t *msg, int len) } caclient_set_status((caclient_t *)cwc, CACLIENT_STATUS_CONNECTED); - pcard = cwc_new_card(cwc, (msg[4] << 8) | msg[5], msg + 6, nprov, pid, psa); - - cwc->cwc_forward_emm = 0; - if (cwc->cwc_emm) { - ua = pcard->cs_ra.ua; - emm_allowed = ua[0] || ua[1] || ua[2] || ua[3] || - ua[4] || ua[5] || ua[6] || ua[7]; - - if (!emm_allowed) { - tvhinfo(LS_CWC, - "%s:%i: Will not forward EMMs (not allowed by server)", - cwc->cwc_hostname, cwc->cwc_port); - } else if (pcard->cs_ra.type != CARD_UNKNOWN) { - tvhinfo(LS_CWC, "%s:%i: Will forward EMMs", - cwc->cwc_hostname, cwc->cwc_port); - cwc->cwc_forward_emm = 1; - } else { - tvhinfo(LS_CWC, - "%s:%i: Will not forward EMMs (unsupported CA system)", - cwc->cwc_hostname, cwc->cwc_port); - } - } + cc_new_card((cclient_t *)cwc, (msg[4] << 8) | msg[5], + msg + 6, nprov, pid, psa); return 0; } @@ -638,11 +368,11 @@ cwc_send_login(cwc_t *cwc) uint8_t buf[CWS_NETMSGSIZE]; size_t ul, pl; - if (cwc->cwc_username == NULL || + if (cwc->cc_username == NULL || cwc->cwc_password_salted == NULL) return 1; - ul = strlen(cwc->cwc_username) + 1; + ul = strlen(cwc->cc_username) + 1; pl = strlen(cwc->cwc_password_salted) + 1; if (ul + pl > 255) @@ -651,7 +381,7 @@ cwc_send_login(cwc_t *cwc) buf[0] = MSG_CLIENT_2_SERVER_LOGIN; buf[1] = 0; buf[2] = ul + pl; - memcpy(buf + 3, cwc->cwc_username, ul); + memcpy(buf + 3, cwc->cc_username, ul); memcpy(buf + 3 + ul, cwc->cwc_password_salted, pl); cwc_send_msg(cwc, buf, ul + pl + 3, TVHEADEND_PROTOCOL_ID, 0, 0, 0); @@ -659,201 +389,34 @@ cwc_send_login(cwc_t *cwc) return 0; } -static int -cwc_ecm_reset(th_descrambler_t *th) -{ - cwc_service_t *ct = (cwc_service_t *)th; - cwc_t *cwc = ct->cs_cwc; - ecm_pid_t *ep; - ecm_section_t *es; - - pthread_mutex_lock(&cwc->cwc_mutex); - descrambler_change_keystate(th, DS_READY, 1); - LIST_FOREACH(ep, &ct->cs_pids, ep_link) - LIST_FOREACH(es, &ep->ep_sections, es_link) - es->es_keystate = ES_UNKNOWN; - ct->ecm_state = ECM_RESET; - pthread_mutex_unlock(&cwc->cwc_mutex); - return 0; -} - -static void -cwc_ecm_idle(th_descrambler_t *th) -{ - cwc_service_t *ct = (cwc_service_t *)th; - cwc_t *cwc = ct->cs_cwc; - ecm_pid_t *ep; - ecm_section_t *es; - - pthread_mutex_lock(&cwc->cwc_mutex); - LIST_FOREACH(ep, &ct->cs_pids, ep_link) - LIST_FOREACH(es, &ep->ep_sections, es_link) - es->es_keystate = ES_IDLE; - ct->ecm_state = ECM_RESET; - pthread_mutex_unlock(&cwc->cwc_mutex); -} - +/** + * + */ static void -handle_ecm_reply(cwc_service_t *ct, ecm_section_t *es, uint8_t *msg, - int len, int seq) +handle_ecm_reply(cc_service_t *ct, cc_ecm_section_t *es, + uint8_t *msg, int len, int seq) { - mpegts_service_t *t = (mpegts_service_t *)ct->td_service; - cwc_t *cwc = ct->cs_cwc; - ecm_pid_t *ep; - ecm_section_t *es2, es3; - char chaninfo[128]; - int i; + cwc_t *cwc = ct->cs_client; uint32_t off; - int64_t delay = (getfastmonoclock() - es->es_time) / 1000LL; // in ms - - es->es_pending = 0; - - snprintf(chaninfo, sizeof(chaninfo), " (PID %d)", es->es_channel); - - if(len < 19) { - - /* ERROR */ - - if (es->es_nok < CWC_MAX_NOKS) - es->es_nok++; - - if(es->es_keystate == ES_FORBIDDEN) - return; // We already know it's bad - - if (es->es_nok >= CWC_MAX_NOKS) { - tvhdebug(LS_CWC, - "Too many NOKs[%i] for service \"%s\"%s from %s", - es->es_section, t->s_dvb_svcname, chaninfo, ct->td_nicename); - es->es_keystate = ES_FORBIDDEN; - goto forbid; - } - - if (descrambler_resolved((service_t *)t, (th_descrambler_t *)ct)) { - tvhdebug(LS_CWC, - "NOK[%i] from %s: Already has a key for service \"%s\"", - es->es_section, ct->td_nicename, t->s_dvb_svcname); - es->es_nok = CWC_MAX_NOKS; /* do not send more ECM requests */ - es->es_keystate = ES_IDLE; - if (ct->td_keystate == DS_READY) - descrambler_change_keystate((th_descrambler_t *)ct, DS_IDLE, 1); - } - - tvhdebug(LS_CWC, - "Received NOK[%i] for service \"%s\"%s " - "(seqno: %d Req delay: %"PRId64" ms)", - es->es_section, t->s_dvb_svcname, chaninfo, seq, delay); - -forbid: - i = 0; - LIST_FOREACH(ep, &ct->cs_pids, ep_link) - LIST_FOREACH(es2, &ep->ep_sections, es_link) - if(es2 && es2 != es && es2->es_nok == 0) { - if (es2->es_pending) - return; - i++; - } - if (i && es->es_nok < CWC_MAX_NOKS) - return; - - es->es_keystate = ES_FORBIDDEN; - LIST_FOREACH(ep, &ct->cs_pids, ep_link) { - LIST_FOREACH(es2, &ep->ep_sections, es_link) - if (es2->es_keystate == ES_UNKNOWN || - es2->es_keystate == ES_RESOLVED) - break; - if (es2) - break; - } - - if (ep == NULL) { /* !UNKNOWN && !RESOLVED */ - tvherror(LS_CWC, - "Can not descramble service \"%s\", access denied (seqno: %d " - "Req delay: %"PRId64" ms) from %s", - t->s_dvb_svcname, seq, delay, ct->td_nicename); - descrambler_change_keystate((th_descrambler_t *)ct, DS_FORBIDDEN, 1); - ct->ecm_state = ECM_RESET; - /* this pid is not valid, force full scan */ - if (t->s_dvb_prefcapid == ct->cs_channel && t->s_dvb_prefcapid_lock == PREFCAPID_OFF) - t->s_dvb_prefcapid = 0; - } - return; + int type; + if (len < 19) { + cc_ecm_reply(ct, es, DESCRAMBLER_NONE, NULL, NULL, seq); } else { - - es->es_nok = 0; - ct->cs_channel = es->es_channel; - ct->ecm_state = ECM_VALID; - - if(t->s_dvb_prefcapid == 0 || - (t->s_dvb_prefcapid != ct->cs_channel && - t->s_dvb_prefcapid_lock == PREFCAPID_OFF)) { - t->s_dvb_prefcapid = ct->cs_channel; - tvhdebug(LS_CWC, "Saving prefered PID %d for %s", - t->s_dvb_prefcapid, ct->td_nicename); - service_request_save((service_t*)t, 0); - } - - if(len < 35) { - tvhdebug(LS_CWC, - "Received ECM reply%s for service \"%s\" [%d] " - "even: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x" - " odd: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x (seqno: %d " - "Req delay: %"PRId64" ms)", - chaninfo, - t->s_dvb_svcname, es->es_section, - msg[3 + 0], msg[3 + 1], msg[3 + 2], msg[3 + 3], msg[3 + 4], - msg[3 + 5], msg[3 + 6], msg[3 + 7], msg[3 + 8], msg[3 + 9], - msg[3 + 10],msg[3 + 11],msg[3 + 12],msg[3 + 13],msg[3 + 14], - msg[3 + 15], seq, delay); - - if(es->es_keystate != ES_RESOLVED) - tvhdebug(LS_CWC, - "Obtained DES keys for service \"%s\" in %"PRId64" ms, from %s", - t->s_dvb_svcname, delay, ct->td_nicename); - es->es_keystate = ES_RESOLVED; - es->es_resolved = 1; + type = DESCRAMBLER_CSA_CBC; + if (len == 3 + 8 + 8) { off = 8; - } else { - tvhdebug(LS_CWC, - "Received ECM reply%s for service \"%s\" " - "even: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x" - " odd: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x" - "(seqno: %d Req delay: %"PRId64" ms)", - chaninfo, - t->s_dvb_svcname, - msg[3 + 0], msg[3 + 1], msg[3 + 2], msg[3 + 3], msg[3 + 4], - msg[3 + 5], msg[3 + 6], msg[3 + 7], msg[3 + 8], msg[3 + 9], - msg[3 + 10],msg[3 + 11],msg[3 + 12],msg[3 + 13],msg[3 + 14], - msg[3 + 15],msg[3 + 16],msg[3 + 17],msg[3 + 18],msg[3 + 19], - msg[3 + 20],msg[3 + 21],msg[3 + 22],msg[3 + 23],msg[3 + 24], - msg[3 + 25],msg[3 + 26],msg[3 + 27],msg[3 + 28],msg[3 + 29], - msg[3 + 30],msg[3 + 31], seq, delay); - - if(es->es_keystate != ES_RESOLVED) - tvhdebug(LS_CWC, - "Obtained AES keys for service \"%s\" in %"PRId64" ms, from %s", - t->s_dvb_svcname, delay, ct->td_nicename); - es->es_keystate = ES_RESOLVED; - es->es_resolved = 1; + } else if (len == 3 + 16 + 16) { off = 16; + type = DESCRAMBLER_AES128_ECB; + } else { + tvherror(cwc->cc_subsys, "wrong ECM reply length %d", len); + return; } - - es3 = *es; - pthread_mutex_unlock(&cwc->cwc_mutex); - descrambler_keys((th_descrambler_t *)ct, - off == 16 ? DESCRAMBLER_AES128_ECB : DESCRAMBLER_CSA_CBC, - 0, msg + 3, msg + 3 + off); - snprintf(chaninfo, sizeof(chaninfo), "%s:%i", cwc->cwc_hostname, cwc->cwc_port); - descrambler_notify((th_descrambler_t *)ct, - es3.es_caid, es3.es_provid, - caid2name(es3.es_caid), - es3.es_channel, delay, - 1, "", chaninfo, "newcamd"); - pthread_mutex_lock(&cwc->cwc_mutex); + cc_ecm_reply(ct, es, type, msg + 3, msg + 3 + off, seq); } } - /** * Handle running reply * cwc_mutex is held @@ -861,29 +424,34 @@ forbid: static int cwc_running_reply(cwc_t *cwc, uint8_t msgtype, uint8_t *msg, int len) { - cwc_service_t *ct; - ecm_pid_t *ep; - ecm_section_t *es; - uint16_t seq = (msg[2] << 8) | msg[3]; + cc_service_t *ct; + cc_ecm_pid_t *ep; + cc_ecm_section_t *es; + uint16_t seq; int plen; short caid; - uint8_t **u8; + uint8_t *u8; switch(msgtype) { case 0x80: case 0x81: + if (len < 12) { + tvherror(cwc->cc_subsys, "wrong 0x%02X length %d", msgtype, len); + return -1; + } + seq = (msg[2] << 8) | msg[3]; len -= 12; msg += 12; - LIST_FOREACH(ct, &cwc->cwc_services, cs_link) + LIST_FOREACH(ct, &cwc->cc_services, cs_link) LIST_FOREACH(ep, &ct->cs_pids, ep_link) LIST_FOREACH(es, &ep->ep_sections, es_link) if(es->es_seq == seq) { if (es->es_resolved) { mpegts_service_t *t = (mpegts_service_t *)ct->td_service; - tvhdebug(LS_CWC, + tvhdebug(cwc->cc_subsys, "Ignore %sECM (PID %d) for service \"%s\" from %s (seq %i)", es->es_pending ? "duplicate " : "", - ep->ep_pid, t->s_dvb_svcname, ct->td_nicename, es->es_seq); + ep->ep_capid, t->s_dvb_svcname, ct->td_nicename, es->es_seq); return 0; } if (es->es_pending) { @@ -891,29 +459,33 @@ cwc_running_reply(cwc_t *cwc, uint8_t msgtype, uint8_t *msg, int len) return 0; } } - tvhwarn(LS_CWC, "Got unexpected ECM reply (seqno: %d)", seq); + tvhwarn(cwc->cc_subsys, "Got unexpected ECM reply (seqno: %d, len: %d)", seq, len); break; case 0xD3: + if (len < 16) { + tvherror(cwc->cc_subsys, "wrong 0xD3 length %d", len); + return -1; + } + caid = (msg[6] << 8) | msg[7]; if (caid){ if(len < 3) { - tvhinfo(LS_CWC, "Invalid card data reply"); + tvhinfo(cwc->cc_subsys, "Invalid card data reply"); return -1; } plen = (msg[1] & 0xf) << 8 | msg[2]; if(plen < 14) { - tvhinfo(LS_CWC, "Invalid card data reply (message)"); + tvhinfo(cwc->cc_subsys, "Invalid card data reply (message)"); return -1; } caclient_set_status((caclient_t *)cwc, CACLIENT_STATUS_CONNECTED); - u8 = alloca(sizeof(uint8_t *)); - u8[0] = &msg[8]; - cwc_new_card(cwc, caid, NULL, 1, u8, NULL); + u8 = &msg[8]; + cc_new_card((cclient_t *)cwc, caid, NULL, 1, &u8, NULL); } } return 0; @@ -923,194 +495,79 @@ cwc_running_reply(cwc_t *cwc, uint8_t msgtype, uint8_t *msg, int len) * */ static int -cwc_must_break(cwc_t *cwc) -{ - return !cwc->cwc_running || !cwc->cac_enabled || cwc->cwc_reconfigure; -} - -/** - * - */ -static int -cwc_read(cwc_t *cwc, void *buf, size_t len, int timeout) -{ - int r; - - pthread_mutex_unlock(&cwc->cwc_mutex); - r = tcp_read_timeout(cwc->cwc_fd, buf, len, timeout); - pthread_mutex_lock(&cwc->cwc_mutex); - - if (r && tvheadend_is_running()) - tvhwarn(LS_CWC, "read error %d (%s)", r, strerror(r)); - - if(cwc_must_break(cwc)) - return ECONNABORTED; - - return r; -} - - -/** - * - */ -static int -cwc_read_message(cwc_t *cwc, const char *state, int timeout) +cwc_read_message + (cwc_t *cwc, const char *state, uint8_t *buf, int len, int timeout) { - char buf[2]; int msglen, r; - if((r = cwc_read(cwc, buf, 2, timeout))) { + if((r = cc_read((cclient_t *)cwc, buf, 2, timeout))) { if (tvheadend_is_running()) - tvhinfo(LS_CWC, "%s:%i: %s: Read error (header): %s", - cwc->cwc_hostname, cwc->cwc_port, state, strerror(r)); + tvhinfo(cwc->cc_subsys, "%s:%i: %s: Read error (header): %s", + cwc->cc_hostname, cwc->cc_port, state, strerror(r)); return -1; } msglen = (buf[0] << 8) | buf[1]; - if(msglen >= CWS_NETMSGSIZE) { + if(msglen > len) { if (tvheadend_is_running()) - tvhinfo(LS_CWC, "%s:%i: %s: Invalid message size: %d", - cwc->cwc_hostname, cwc->cwc_port, state, msglen); + tvhinfo(cwc->cc_subsys, "%s:%i: %s: Invalid message size: %d", + cwc->cc_hostname, cwc->cc_port, state, msglen); return -1; } /* We expect the rest of the message to arrive fairly quick, so just wait 1 second here */ - if((r = cwc_read(cwc, cwc->cwc_buf + 2, msglen, 1000))) { + if((r = cc_read((cclient_t *)cwc, buf + 2, msglen, 1000))) { if (tvheadend_is_running()) - tvhinfo(LS_CWC, "%s:%i: %s: Read error: %s", - cwc->cwc_hostname, cwc->cwc_port, state, strerror(r)); + tvhinfo(cwc->cc_subsys, "%s:%i: %s: Read error: %s", + cwc->cc_hostname, cwc->cc_port, state, strerror(r)); return -1; } - if((msglen = des_decrypt(cwc->cwc_buf, msglen + 2, cwc)) < 15) { - tvhinfo(LS_CWC, "%s:%i: %s: Decrypt failed", - cwc->cwc_hostname, cwc->cwc_port, state); + if((msglen = des_decrypt(buf, msglen + 2, cwc)) < 15) { + tvhinfo(cwc->cc_subsys, "%s:%i: %s: Decrypt failed", + cwc->cc_hostname, cwc->cc_port, state); return -1; } - return msglen; -} - -/** - * - */ -static void -cwc_invalidate_cards(cwc_t *cwc) -{ - cs_card_data_t *cd; - - LIST_FOREACH(cd, &cwc->cwc_cards, cs_card) - cd->running = 0; -} - -/** - * - */ -static void -cwc_free_cards(cwc_t *cwc) -{ - cs_card_data_t *cd; - - while((cd = LIST_FIRST(&cwc->cwc_cards)) != NULL) { - LIST_REMOVE(cd, cs_card); - descrambler_close_emm(cd->cwc_mux, cd, cd->cs_ra.caid); - emm_reass_done(&cd->cs_ra); - free(cd); - } -} -/** - * - */ -static void -cwc_flush_services(cwc_t *cwc) -{ - cwc_service_t *ct; - - LIST_FOREACH(ct, &cwc->cwc_services, cs_link) - descrambler_flush_table_data(ct->td_service); -} - -/** - * - */ -static void * -cwc_writer_thread(void *aux) -{ - cwc_t *cwc = aux; - cwc_message_t *cm; - int64_t mono; - int r; - - pthread_mutex_lock(&cwc->cwc_writer_mutex); - - while(cwc->cwc_writer_running) { - - if((cm = TAILQ_FIRST(&cwc->cwc_writeq)) != NULL) { - TAILQ_REMOVE(&cwc->cwc_writeq, cm, cm_link); - pthread_mutex_unlock(&cwc->cwc_writer_mutex); - // int64_t ts = getfastmonoclock(); - if (tvh_write(cwc->cwc_fd, cm->cm_data, cm->cm_len)) - tvhinfo(LS_CWC, "write error %s", strerror(errno)); - // printf("Write took %lld usec\n", getfastmonoclock() - ts); - free(cm); - pthread_mutex_lock(&cwc->cwc_writer_mutex); - continue; - } - - - /* If nothing is to be sent in keepalive interval seconds we - need to send a keepalive */ - mono = mclk() + sec2mono(cwc->cwc_keepalive_interval); - do { - r = tvh_cond_timedwait(&cwc->cwc_writer_cond, &cwc->cwc_writer_mutex, mono); - if(r == ETIMEDOUT) { - cwc_send_ka(cwc); - break; - } - } while (ERRNO_AGAIN(r)); - } - - pthread_mutex_unlock(&cwc->cwc_writer_mutex); - return NULL; + return msglen; } - - /** * */ -static void -cwc_session(cwc_t *cwc) +static int +cwc_init_session(void *cc) { + cwc_t *cwc = cc; + uint8_t buf[CWS_NETMSGSIZE]; int r; - pthread_t writer_thread_id; /** * Get login key */ - if((r = cwc_read(cwc, cwc->cwc_buf, 14, 5000))) { - tvhinfo(LS_CWC, "%s:%i: No login key received: %s", - cwc->cwc_hostname, cwc->cwc_port, strerror(r)); - return; + if ((r = cc_read((cclient_t *)cwc, buf, 14, 5000))) { + tvhinfo(cwc->cc_subsys, "%s:%i: No login key received: %s", + cwc->cc_hostname, cwc->cc_port, strerror(r)); + return -1; } - des_make_login_key(cwc, cwc->cwc_buf); + des_make_login_key(cwc, buf); /** * Login */ if (cwc_send_login(cwc)) - return; + return -1; - if(cwc_read_message(cwc, "Wait login response", 5000) < 0) - return; + if(cwc_read_message(cwc, "Wait login response", buf, sizeof(buf), 5000) < 0) + return -1; - if(cwc->cwc_buf[12] != MSG_CLIENT_2_SERVER_LOGIN_ACK) { - tvhinfo(LS_CWC, "%s:%i: Login failed", - cwc->cwc_hostname, cwc->cwc_port); - return; + if(buf[12] != MSG_CLIENT_2_SERVER_LOGIN_ACK) { + tvhinfo(cwc->cc_subsys, "%s:%i: Login failed", + cwc->cc_hostname, cwc->cc_port); + return -1; } des_make_session_key(cwc); @@ -1119,539 +576,62 @@ cwc_session(cwc_t *cwc) * Request card data */ cwc_send_data_req(cwc); - if((r = cwc_read_message(cwc, "Request card data", 5000)) < 0) - return; - - if(cwc->cwc_buf[12] != MSG_CARD_DATA) { - tvhinfo(LS_CWC, "%s:%i: Card data request failed", - cwc->cwc_hostname, cwc->cwc_port); - return; - } - - if(cwc_decode_card_data_reply(cwc, cwc->cwc_buf, r) < 0) - return; - - /** - * Ok, connection good, reset retry delay to zero - */ - cwc->cwc_retry_delay = 0; - - cwc_flush_services(cwc); - - /** - * We do all requests from now on in a separate thread - */ - cwc->cwc_writer_running = 1; - tvh_cond_init(&cwc->cwc_writer_cond); - pthread_mutex_init(&cwc->cwc_writer_mutex, NULL); - TAILQ_INIT(&cwc->cwc_writeq); - tvhthread_create(&writer_thread_id, NULL, cwc_writer_thread, cwc, "cwc-writer"); - - /** - * Mainloop - */ - while(!cwc_must_break(cwc)) { - - if((r = cwc_read_message(cwc, "Decoderloop", - cwc->cwc_keepalive_interval * 2 * 1000)) < 0) - break; - cwc_running_reply(cwc, cwc->cwc_buf[12], cwc->cwc_buf, r); - } - tvhdebug(LS_CWC, "session thread exiting"); - - /** - * Collect the writer thread - */ - shutdown(cwc->cwc_fd, SHUT_RDWR); - cwc->cwc_writer_running = 0; - tvh_cond_signal(&cwc->cwc_writer_cond, 0); - pthread_join(writer_thread_id, NULL); - tvhdebug(LS_CWC, "Write thread joined"); -} - -/** - * - */ -static void * -cwc_thread(void *aux) -{ - cwc_t *cwc = aux; - int fd, d, r; - char errbuf[100]; - char hostname[256]; - int port; - int attempts = 0; - int64_t mono; - - pthread_mutex_lock(&cwc->cwc_mutex); - - while(cwc->cwc_running) { - - cwc_invalidate_cards(cwc); - caclient_set_status((caclient_t *)cwc, CACLIENT_STATUS_READY); - - snprintf(hostname, sizeof(hostname), "%s", cwc->cwc_hostname); - port = cwc->cwc_port; - - tvhinfo(LS_CWC, "Attemping to connect to %s:%d", hostname, port); - - pthread_mutex_unlock(&cwc->cwc_mutex); - - fd = tcp_connect(hostname, port, NULL, errbuf, sizeof(errbuf), 10); - - pthread_mutex_lock(&cwc->cwc_mutex); - - if(fd == -1) { - attempts++; - tvhinfo(LS_CWC, - "Connection attempt to %s:%d failed: %s", - hostname, port, errbuf); - } else { - - if(cwc->cwc_running == 0) { - close(fd); - break; - } - - tvhinfo(LS_CWC, "Connected to %s:%d", hostname, port); - attempts = 0; - - cwc->cwc_fd = fd; - cwc->cwc_reconfigure = 0; - - cwc_session(cwc); - - cwc->cwc_fd = -1; - close(fd); - tvhinfo(LS_CWC, "Disconnected from %s:%i", - cwc->cwc_hostname, cwc->cwc_port); - } - - if(cwc->cwc_running == 0) continue; - if(attempts == 1 || cwc->cwc_reconfigure) { - cwc->cwc_reconfigure = 0; - continue; // Retry immediately - } - - caclient_set_status((caclient_t *)cwc, CACLIENT_STATUS_DISCONNECTED); - - d = 3; - - tvhinfo(LS_CWC, - "%s:%i: Automatic connection attempt in %d seconds", - cwc->cwc_hostname, cwc->cwc_port, d-1); + if((r = cwc_read_message(cwc, "Request card data", buf, sizeof(buf), 5000)) < 0) + return -1; - mono = mclk() + sec2mono(d); - do { - r = tvh_cond_timedwait(&cwc->cwc_cond, &cwc->cwc_mutex, mono); - if (r == ETIMEDOUT) - break; - } while (ERRNO_AGAIN(r)); + if (buf[12] != MSG_CARD_DATA) { + tvhinfo(cwc->cc_subsys, "%s:%i: Card data request failed", + cwc->cc_hostname, cwc->cc_port); + return -1; } - tvhinfo(LS_CWC, "%s:%i inactive", - cwc->cwc_hostname, cwc->cwc_port); - cwc_free_cards(cwc); - pthread_mutex_unlock(&cwc->cwc_mutex); - return NULL; -} - - -/** - * - */ -static int -verify_provider(cs_card_data_t *pcard, uint32_t providerid) -{ - int i; - - if(providerid == 0) - return 1; - - for(i = 0; i < pcard->cs_ra.providers_count; i++) - if(providerid == pcard->cs_ra.providers[i].id) - return 1; + if (cwc_decode_card_data_reply(cwc, buf, r) < 0) + return -1; return 0; } /** * */ -static void -cwc_emm_send(void *aux, const uint8_t *radata, int ralen, void *mux) -{ - cs_card_data_t *pcard = aux; - cwc_t *cwc = pcard->cwc; - - tvhtrace(LS_CWC, "sending EMM for %04x mux %p", pcard->cs_ra.caid, mux); - tvhlog_hexdump(LS_CWC, radata, ralen); - cwc_send_msg(cwc, radata, ralen, 0, 1, 0, 0); -} - -/** - * - */ -static void -cwc_emm(void *opaque, int pid, const uint8_t *data, int len, int emm) +static int +cwc_read(void *cc) { - cs_card_data_t *pcard = opaque; - cwc_t *cwc; - void *mux; - - if (data == NULL) { /* end-of-data */ - pcard->cwc_mux = NULL; - return; - } - if (pcard->cwc_mux == NULL) - return; - cwc = pcard->cwc; - pthread_mutex_lock(&cwc->cwc_mutex); - mux = pcard->cwc_mux; - if (pcard->running && cwc->cwc_forward_emm && cwc->cwc_writer_running) { - if (cwc->cwc_emmex) { - if (cwc->cwc_emm_mux && cwc->cwc_emm_mux != mux) { - if (cwc->cwc_emm_update_time + sec2mono(25) > mclk()) - goto end_of_job; - } - cwc->cwc_emm_update_time = mclk(); - } - cwc->cwc_emm_mux = mux; - emm_filter(&pcard->cs_ra, data, len, mux, cwc_emm_send, pcard); - } -end_of_job: - pthread_mutex_unlock(&cwc->cwc_mutex); + cwc_t *cwc = cc; + uint8_t buf[CWS_NETMSGSIZE]; + const int ka_interval = cwc->cc_keepalive_interval * 2 * 1000; + int r = cwc_read_message(cwc, "Decoderloop", buf, sizeof(buf), ka_interval); + if (r < 0) + return -1; + if (r > 12) + return cwc_running_reply(cwc, buf[12], buf, r); + return -1; } /** * */ -static void -cwc_table_input(void *opaque, int pid, const uint8_t *data, int len, int emm) +static uint32_t +cwc_send_ecm(void *cc, cc_service_t *ct, cc_ecm_section_t *es, + cc_card_data_t *pcard, const uint8_t *data, int len) { - cwc_service_t *ct = opaque; - elementary_stream_t *st; - mpegts_service_t *t = (mpegts_service_t*)ct->td_service; + mpegts_service_t *t = (mpegts_service_t *)ct->td_service; uint16_t sid = t->s_dvb_service_id; - cwc_t *cwc = ct->cs_cwc; - int channel, section, ecm; - ecm_pid_t *ep; - ecm_section_t *es; - char chaninfo[32]; - cs_card_data_t *pcard = NULL; - caid_t *c; - uint16_t caid; - uint32_t provid; - - if (data == NULL) - return; - - if(len > 4096) - return; - - pthread_mutex_lock(&cwc->cwc_mutex); - pthread_mutex_lock(&t->s_stream_mutex); - - if (ct->td_keystate == DS_IDLE) - goto end; - - if (ct->ecm_state == ECM_RESET) { - /* clean all */ - cwc_service_pid_free(ct); - /* move to init state */ - ct->ecm_state = ECM_INIT; - ct->cs_channel = -1; - t->s_dvb_prefcapid = 0; - tvhdebug(LS_CWC, "Reset after unexpected or no reply for service \"%s\"", t->s_dvb_svcname); - } - - LIST_FOREACH(ep, &ct->cs_pids, ep_link) - if(ep->ep_pid == pid) break; - - if(ep == NULL) { - if (ct->ecm_state == ECM_INIT) { - // Validate prefered ECM PID - tvhdebug(LS_CWC, "ECM state INIT"); - - if(t->s_dvb_prefcapid_lock != PREFCAPID_OFF) { - st = service_stream_find((service_t*)t, t->s_dvb_prefcapid); - if (st && st->es_type == SCT_CA) - LIST_FOREACH(c, &st->es_caids, link) - LIST_FOREACH(pcard, &cwc->cwc_cards, cs_card) - if(pcard->running && - pcard->cs_ra.caid == c->caid && - verify_provider(pcard, c->providerid)) - goto prefcapid_ok; - tvhdebug(LS_CWC, "Invalid prefered ECM (PID %d) found for service \"%s\"", t->s_dvb_prefcapid, t->s_dvb_svcname); - t->s_dvb_prefcapid = 0; - } - -prefcapid_ok: - if(t->s_dvb_prefcapid == pid || t->s_dvb_prefcapid == 0 || - t->s_dvb_prefcapid_lock == PREFCAPID_OFF) { - ep = calloc(1, sizeof(ecm_pid_t)); - ep->ep_pid = pid; - LIST_INSERT_HEAD(&ct->cs_pids, ep, ep_link); - tvhdebug(LS_CWC, "Insert %s ECM (PID %d) for service \"%s\"", - t->s_dvb_prefcapid ? "preferred" : "new", pid, t->s_dvb_svcname); - } - } - if(ep == NULL) - goto end; - } - - st = service_stream_find((service_t *)t, pid); - if (st) { - LIST_FOREACH(c, &st->es_caids, link) - LIST_FOREACH(pcard, &cwc->cwc_cards, cs_card) - if(pcard->running && - pcard->cs_ra.caid == c->caid && - verify_provider(pcard, c->providerid)) - goto found; - } - - goto end; - -found: - caid = c->caid; - provid = c->providerid; - - ecm = data[0] == 0x80 || data[1] == 0x81; - if (pcard->cs_ra.caid == 0x4a30) ecm |= data[0] == 0x50; /* DVN */ - - if (ecm) { - if((pcard->cs_ra.caid >> 8) == 6) { - ep->ep_last_section = data[5]; - section = data[4]; - - } else { - ep->ep_last_section = 0; - section = 0; - } - - channel = pid; - snprintf(chaninfo, sizeof(chaninfo), " (PID %d)", channel); - - LIST_FOREACH(es, &ep->ep_sections, es_link) - if (es->es_section == section) - break; - if (es == NULL) { - es = calloc(1, sizeof(ecm_section_t)); - es->es_section = section; - LIST_INSERT_HEAD(&ep->ep_sections, es, es_link); - } - if(cwc->cwc_fd == -1) { - // New key, but we are not connected (anymore), can not descramble - descrambler_change_keystate((th_descrambler_t *)ct, DS_READY, 0); - goto end; - } - - if (es->es_keystate == ES_FORBIDDEN || es->es_keystate == ES_IDLE) - goto end; - - es->es_caid = caid; - es->es_provid = provid; - es->es_channel = channel; - es->es_pending = 1; - es->es_resolved = 0; - - if(ct->cs_channel >= 0 && channel != -1 && - ct->cs_channel != channel) { - tvhdebug(LS_CWC, "Filtering ECM (PID %d)", channel); - goto end; - } - - es->es_seq = cwc_send_msg(cwc, data, len, sid, 1, caid, provid); - - tvhdebug(LS_CWC, - "Sending ECM%s section=%d/%d, for service \"%s\" (seqno: %d)", - chaninfo, section, ep->ep_last_section, t->s_dvb_svcname, es->es_seq); - es->es_time = getfastmonoclock(); - } else { - if (cwc->cwc_forward_emm) - cwc_send_msg(cwc, data, len, sid, 1, 0, 0); - } - -end: - pthread_mutex_unlock(&t->s_stream_mutex); - pthread_mutex_unlock(&cwc->cwc_mutex); -} - -/** - * cwc_mutex is held - */ -static void -cwc_service_pid_free(cwc_service_t *ct) -{ - ecm_pid_t *ep; - ecm_section_t *es; - - while((ep = LIST_FIRST(&ct->cs_pids)) != NULL) { - while ((es = LIST_FIRST(&ep->ep_sections)) != NULL) { - LIST_REMOVE(es, es_link); - free(es); - } - LIST_REMOVE(ep, ep_link); - free(ep); - } + return cwc_send_msg(cc, data, len, sid, 1, es->es_caid, es->es_provid); } /** - * cwc_mutex is held - */ -static void -cwc_service_destroy0(th_descrambler_t *td) -{ - cwc_service_t *ct = (cwc_service_t *)td; - int i; - - for (i = 0; i < CWC_ES_PIDS; i++) - if (ct->cs_epids[i]) - descrambler_close_pid(ct->cs_mux, ct, - DESCRAMBLER_ECM_PID(ct->cs_epids[i])); - - cwc_service_pid_free(ct); - - LIST_REMOVE(td, td_service_link); - - LIST_REMOVE(ct, cs_link); - - free(ct->td_nicename); - free(ct); -} - -/** - * cwc_mutex is held - */ -static void -cwc_service_destroy(th_descrambler_t *td) -{ - cwc_service_t *ct = (cwc_service_t *)td; - cwc_t *cwc = ct->cs_cwc; - - pthread_mutex_lock(&cwc->cwc_mutex); - cwc_service_destroy0(td); - pthread_mutex_unlock(&cwc->cwc_mutex); -} - -/** - * Check if our CAID's matches, and if so, link * - * global_lock is held. Not that we care about that, but either way, it is. */ static void -cwc_service_start(caclient_t *cac, service_t *t) +cwc_send_emm(void *cc, cc_service_t *ct, + cc_card_data_t *pcard, uint32_t provid, + const uint8_t *data, int len) { - cwc_t *cwc = (cwc_t *)cac; - cwc_service_t *ct; - th_descrambler_t *td; - elementary_stream_t *st; - caid_t *c; - cs_card_data_t *pcard; - char buf[512]; - int i, reuse = 0, prefpid, prefpid_lock, forcecaid; - - extern const idclass_t mpegts_service_class; - if (!idnode_is_instance(&t->s_id, &mpegts_service_class)) - return; - - pthread_mutex_lock(&cwc->cwc_mutex); - pthread_mutex_lock(&t->s_stream_mutex); - LIST_FOREACH(ct, &cwc->cwc_services, cs_link) { - if (ct->td_service == t && ct->cs_cwc == cwc) - break; - } - prefpid = ((mpegts_service_t *)t)->s_dvb_prefcapid; - prefpid_lock = ((mpegts_service_t *)t)->s_dvb_prefcapid_lock; - forcecaid = ((mpegts_service_t *)t)->s_dvb_forcecaid; - LIST_FOREACH(pcard, &cwc->cwc_cards, cs_card) { - if (!pcard->running) continue; - if (pcard->cs_ra.caid == 0) continue; - TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) { - if (prefpid_lock == PREFCAPID_FORCE && prefpid != st->es_pid) - continue; - LIST_FOREACH(c, &st->es_caids, link) { - if (c->use && c->caid == pcard->cs_ra.caid) - if (!forcecaid || forcecaid == c->caid) - break; - } - if (c) break; - } - if (st) break; - } - if (!pcard) { - if (ct) cwc_service_destroy0((th_descrambler_t*)ct); - goto end; - } - if (ct) { - reuse = 1; - for (i = 0; i < CWC_ES_PIDS; i++) { - if (!ct->cs_epids[i]) continue; - TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) { - if (st->es_pid != ct->cs_epids[i]) continue; - LIST_FOREACH(c, &st->es_caids, link) - if (c->use && c->caid == pcard->cs_ra.caid) - break; - if (c) break; - } - if (st == NULL) { - descrambler_close_pid(ct->cs_mux, ct, - DESCRAMBLER_ECM_PID(ct->cs_epids[i])); - reuse |= 2; - } - } - goto add; - } - - ct = calloc(1, sizeof(cwc_service_t)); - ct->cs_cwc = cwc; - ct->cs_channel = -1; - ct->cs_mux = ((mpegts_service_t *)t)->s_dvb_mux; - ct->ecm_state = ECM_INIT; - - td = (th_descrambler_t *)ct; - snprintf(buf, sizeof(buf), "cwc-%s-%i-%04X", cwc->cwc_hostname, cwc->cwc_port, pcard->cs_ra.caid); - td->td_nicename = strdup(buf); - td->td_service = t; - td->td_stop = cwc_service_destroy; - td->td_ecm_reset = cwc_ecm_reset; - td->td_ecm_idle = cwc_ecm_idle; - LIST_INSERT_HEAD(&t->s_descramblers, td, td_service_link); - - LIST_INSERT_HEAD(&cwc->cwc_services, ct, cs_link); - - descrambler_change_keystate(td, DS_READY, 0); - -add: - i = 0; - TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) { - LIST_FOREACH(c, &st->es_caids, link) - if (c->use && c->caid == pcard->cs_ra.caid) { - if (reuse && ct->cs_epids[i] != st->es_pid) reuse |= 2; - ct->cs_epids[i++] = st->es_pid; - break; - } - if (i == CWC_ES_PIDS) break; - } - - for (i = 0; i < CWC_ES_PIDS; i++) - if (ct->cs_epids[i]) - descrambler_open_pid(ct->cs_mux, ct, - DESCRAMBLER_ECM_PID(ct->cs_epids[i]), - cwc_table_input, t); - - if (reuse & 2) { - ct->cs_channel = -1; - ct->ecm_state = ECM_INIT; - } - - if (reuse != 1) - tvhdebug(LS_CWC, "%s %susing CWC %s:%d", - service_nicename(t), reuse ? "re" : "", cwc->cwc_hostname, cwc->cwc_port); + mpegts_service_t *t = (mpegts_service_t *)ct->td_service; + uint16_t sid = t->s_dvb_service_id; -end: - pthread_mutex_unlock(&t->s_stream_mutex); - pthread_mutex_unlock(&cwc->cwc_mutex); + cwc_send_msg(cc, data, len, sid, 1, pcard->cs_ra.caid, provid); } /** @@ -1661,47 +641,9 @@ static void cwc_free(caclient_t *cac) { cwc_t *cwc = (cwc_t *)cac; - cwc_service_t *ct; - - while((ct = LIST_FIRST(&cwc->cwc_services)) != NULL) - cwc_service_destroy((th_descrambler_t *)ct); - - cwc_free_cards(cwc); - free((void *)cwc->cwc_password); - free((void *)cwc->cwc_password_salted); - free((void *)cwc->cwc_username); - free((void *)cwc->cwc_hostname); -} - -/** - * - */ -static void -cwc_caid_update(caclient_t *cac, mpegts_mux_t *mux, uint16_t caid, uint16_t pid, int valid) -{ - cwc_t *cwc = (cwc_t *)cac;; - cs_card_data_t *pcard; - - tvhtrace(LS_CWC, - "caid update event - client %s mux %p caid %04x (%i) pid %04x (%i) valid %i", - cac->cac_name, mux, caid, caid, pid, pid, valid); - pthread_mutex_lock(&cwc->cwc_mutex); - if (valid < 0 || cwc->cwc_running) { - LIST_FOREACH(pcard, &cwc->cwc_cards, cs_card) { - if (valid < 0 || pcard->cs_ra.caid == caid) { - if (pcard->cwc_mux && pcard->cwc_mux != mux) continue; - if (valid > 0) { - pcard->cwc = cwc; - pcard->cwc_mux = mux; - descrambler_open_emm(mux, pcard, caid, cwc_emm); - } else { - pcard->cwc_mux = NULL; - descrambler_close_emm(mux, pcard, caid); - } - } - } - } - pthread_mutex_unlock(&cwc->cwc_mutex); + char *salted = cwc->cwc_password_salted; + cc_free(cac); + free((void *)salted); } /** @@ -1729,43 +671,12 @@ static void cwc_conf_changed(caclient_t *cac) { cwc_t *cwc = (cwc_t *)cac; - pthread_t tid; free(cwc->cwc_password_salted); cwc->cwc_password_salted = - cwc->cwc_password ? crypt_md5(cwc->cwc_password, "$1$abcdefgh$") : NULL; - - if (cac->cac_enabled) { - if (cwc->cwc_hostname == NULL || cwc->cwc_hostname[0] == '\0') { - caclient_set_status(cac, CACLIENT_STATUS_NONE); - return; - } - if (!cwc->cwc_running) { - cwc->cwc_running = 1; - tvhthread_create(&cwc->cwc_tid, NULL, cwc_thread, cwc, "cwc"); - return; - } - pthread_mutex_lock(&cwc->cwc_mutex); - cwc->cwc_reconfigure = 1; - if(cwc->cwc_fd >= 0) - shutdown(cwc->cwc_fd, SHUT_RDWR); - tvh_cond_signal(&cwc->cwc_cond, 0); - pthread_mutex_unlock(&cwc->cwc_mutex); - } else { - if (!cwc->cwc_running) - return; - pthread_mutex_lock(&cwc->cwc_mutex); - cwc->cwc_running = 0; - tvh_cond_signal(&cwc->cwc_cond, 0); - tid = cwc->cwc_tid; - if (cwc->cwc_fd >= 0) - shutdown(cwc->cwc_fd, SHUT_RDWR); - pthread_mutex_unlock(&cwc->cwc_mutex); - pthread_kill(tid, SIGHUP); - pthread_join(tid, NULL); - caclient_set_status(cac, CACLIENT_STATUS_NONE); - } + cwc->cc_password ? crypt_md5(cwc->cc_password, "$1$abcdefgh$") : NULL; + cc_conf_changed(cac); } /** @@ -1817,42 +728,10 @@ caclient_cwc_class_deskey_get(void *o) const idclass_t caclient_cwc_class = { - .ic_super = &caclient_class, + .ic_super = &caclient_cc_class, .ic_class = "caclient_cwc", .ic_caption = N_("Code Word Client (newcamd)"), .ic_properties = (const property_t[]){ - { - .type = PT_STR, - .id = "username", - .name = N_("Username"), - .desc = N_("Login username."), - .off = offsetof(cwc_t, cwc_username), - .opts = PO_TRIM, - }, - { - .type = PT_STR, - .id = "password", - .name = N_("Password"), - .desc = N_("Login password."), - .off = offsetof(cwc_t, cwc_password), - .opts = PO_PASSWORD - }, - { - .type = PT_STR, - .id = "hostname", - .name = N_("Hostname/IP"), - .desc = N_("Hostname (or IP) of the server."), - .off = offsetof(cwc_t, cwc_hostname), - .def.s = "localhost", - .opts = PO_TRIM, - }, - { - .type = PT_INT, - .id = "port", - .name = N_("Port"), - .desc = N_("Port to connect to."), - .off = offsetof(cwc_t, cwc_port), - }, { .type = PT_STR, .id = "deskey", @@ -1862,30 +741,7 @@ const idclass_t caclient_cwc_class = .get = caclient_cwc_class_deskey_get, .opts = PO_PASSWORD, .def.s = "00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d", - }, - { - .type = PT_BOOL, - .id = "emm", - .name = N_("Update card (EMM)"), - .desc = N_("Enable/disable offering of Entitlement Management Message updates."), - .off = offsetof(cwc_t, cwc_emm), - .def.i = 1 - }, - { - .type = PT_BOOL, - .id = "emmex", - .name = N_("Updates from one mux (EMM)"), - .desc = N_("Update Entitlement Management Messages from one mux only."), - .off = offsetof(cwc_t, cwc_emmex), - .def.i = 1 - }, - { - .type = PT_INT, - .id = "keepalive_interval", - .name = N_("Keepalive interval"), - .desc = N_("Keepalive interval in seconds"), - .off = offsetof(cwc_t, cwc_keepalive_interval), - .def.i = CWC_KEEPALIVE_INTERVAL, + .group = 1, }, { } } @@ -1898,13 +754,21 @@ caclient_t *cwc_create(void) { cwc_t *cwc = calloc(1, sizeof(*cwc)); - pthread_mutex_init(&cwc->cwc_mutex, NULL); - tvh_cond_init(&cwc->cwc_cond); + cwc->cc_subsys = LS_CWC; + cwc->cc_id = "newcamd"; + + pthread_mutex_init(&cwc->cc_mutex, NULL); + tvh_cond_init(&cwc->cc_cond); cwc->cac_free = cwc_free; - cwc->cac_start = cwc_service_start; + cwc->cac_start = cc_service_start; cwc->cac_conf_changed = cwc_conf_changed; - cwc->cac_caid_update = cwc_caid_update; - cwc->cwc_keepalive_interval = CWC_KEEPALIVE_INTERVAL; + cwc->cac_caid_update = cc_caid_update; + cwc->cc_keepalive_interval = CC_KEEPALIVE_INTERVAL; + cwc->cc_init_session = cwc_init_session; + cwc->cc_read = cwc_read; + cwc->cc_send_ecm = cwc_send_ecm; + cwc->cc_send_emm = cwc_send_emm; + cwc->cc_keepalive = cwc_send_ka; return (caclient_t *)cwc; } diff --git a/src/descrambler/emm_reass.h b/src/descrambler/emm_reass.h index 48d70daf7..0dad333e5 100644 --- a/src/descrambler/emm_reass.h +++ b/src/descrambler/emm_reass.h @@ -22,7 +22,10 @@ #include "tvheadend.h" #include "descrambler/caid.h" -typedef struct emm_provider { +typedef struct emm_provider emm_provider_t; +typedef struct emm_reass emm_reass_t; + +struct emm_provider { uint32_t id; uint8_t sa[8]; union { @@ -32,7 +35,7 @@ typedef struct emm_provider { int shared_len; } viacess[2]; } u; -} emm_provider_t; +}; #define EMM_CACHE_SIZE (1<<5) #define EMM_CACHE_MASK (EMM_CACHE_SIZE-1) @@ -40,7 +43,7 @@ typedef struct emm_provider { typedef void (*emm_send_t) (void *aux, const uint8_t *radata, int ralen, void *mux); -typedef struct emm_reass { +struct emm_reass { uint16_t caid; card_type_t type; @@ -60,13 +63,13 @@ typedef struct emm_reass { } cryptoworks; } u; - void (*do_emm)(struct emm_reass *ra, const uint8_t *data, int len, + void (*do_emm)(emm_reass_t *ra, const uint8_t *data, int len, void *mux, emm_send_t send, void *aux); -} emm_reass_t; - +}; -void emm_filter(struct emm_reass *ra, const uint8_t *data, int len, +void emm_filter(emm_reass_t *ra, const uint8_t *data, int len, void *mux, emm_send_t send, void *aux); +emm_provider_t *emm_find_provider(emm_reass_t *ra, uint32_t provid); void emm_reass_init(emm_reass_t *ra, uint16_t caid); void emm_reass_done(emm_reass_t *ra); diff --git a/src/input/mpegts.h b/src/input/mpegts.h index a4ea64c4c..3c072f12f 100644 --- a/src/input/mpegts.h +++ b/src/input/mpegts.h @@ -110,9 +110,10 @@ static inline int mpegts_pid_wexists ( mpegts_apids_t *pids, uint16_t pid, uint1 static inline int mpegts_pid_rexists ( mpegts_apids_t *pids, uint16_t pid ) { return pids && (pids->all || mpegts_pid_find_rindex(pids, pid) >= 0); } int mpegts_pid_copy ( mpegts_apids_t *dst, mpegts_apids_t *src ); +int mpegts_pid_cmp ( mpegts_apids_t *a, mpegts_apids_t *b ); int mpegts_pid_compare ( mpegts_apids_t *dst, mpegts_apids_t *src, mpegts_apids_t *add, mpegts_apids_t *del ); -int mpegts_pid_weighted( mpegts_apids_t *dst, mpegts_apids_t *src, int limit ); +int mpegts_pid_weighted ( mpegts_apids_t *dst, mpegts_apids_t *src, int limit ); int mpegts_pid_dump ( mpegts_apids_t *pids, char *buf, int len, int wflag, int raw ); /* ************************************************************************** diff --git a/src/input/mpegts/mpegts_pid.c b/src/input/mpegts/mpegts_pid.c index b643eb899..0cc671bf4 100644 --- a/src/input/mpegts/mpegts_pid.c +++ b/src/input/mpegts/mpegts_pid.c @@ -244,6 +244,23 @@ mpegts_pid_copy(mpegts_apids_t *dst, mpegts_apids_t *src) return 0; } +int +mpegts_pid_cmp(mpegts_apids_t *a, mpegts_apids_t *b) +{ + int i; + mpegts_apid_t *p1, *p2; + + if (a->count != b->count) + return a->count - b->count; + for (i = 0; i < a->count; i++) { + p1 = &a->pids[i]; + p2 = &b->pids[i]; + if (p1->pid != p2->pid) + return p1->pid - p2->pid; + } + return 0; +} + int mpegts_pid_compare(mpegts_apids_t *dst, mpegts_apids_t *src, mpegts_apids_t *add, mpegts_apids_t *del)