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)
OPTIONS=(
"pie:yes"
"ccdebug:no"
+ "cardclient:auto"
"cwc:yes"
"cccam:yes"
"capmt:yes"
fi
fi
+#
+# common card client
+#
+if enabled cwc || enabled cccam; then
+ enable cardclient
+fi
+
#
# libdvbcsa, tvhcsa
#
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;
- /*
+/*
* tvheadend, CCCAM interface
* Copyright (C) 2007 Andreas Ă–man
* Copyright (C) 2017 Luis Alves
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <pthread.h>
-#include <assert.h>
-#include <string.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <errno.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
#include <ctype.h>
-#include <signal.h>
-#include <sys/types.h>
-#include <sys/socket.h>
#include <openssl/sha.h>
-#include <endian.h>
-#include <semaphore.h>
-
#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 {
"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;
-
/**
*
*/
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";
/**
}
}
-
-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);
}
/**
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;
*
*/
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);
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 {
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;
}
* 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;
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;
-}
-
/**
*
*/
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);
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;
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);
}
/**
*
*/
-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;
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);
* 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);
}
/**
{
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);
}
.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",
.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,
.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,
},
{ }
}
{
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;
}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <signal.h>
+#include <pthread.h>
+#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,
+ },
+ { }
+ }
+};
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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);
/*
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <pthread.h>
-#include <assert.h>
-#include <string.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <errno.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
#include <ctype.h>
-#include <signal.h>
-#include <sys/types.h>
-#include <sys/socket.h>
#include <openssl/des.h>
#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
} 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);
/**
*
/**
* 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;
// 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;
* 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;
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;
}
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;
}
}
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;
}
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)
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);
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
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) {
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;
*
*/
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);
* 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);
}
/**
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);
}
/**
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);
}
/**
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",
.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,
},
{ }
}
{
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;
}
#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 {
int shared_len;
} viacess[2];
} u;
-} emm_provider_t;
+};
#define EMM_CACHE_SIZE (1<<5)
#define EMM_CACHE_MASK (EMM_CACHE_SIZE-1)
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;
} 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);
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 );
/* **************************************************************************
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)