]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
cwc, cccam: rewrite, move common code to cclient
authorJaroslav Kysela <perex@perex.cz>
Thu, 28 Dec 2017 15:04:29 +0000 (16:04 +0100)
committerJaroslav Kysela <perex@perex.cz>
Thu, 4 Jan 2018 14:03:22 +0000 (15:03 +0100)
Makefile
configure
src/descrambler/caclient.h
src/descrambler/cccam.c
src/descrambler/cclient.c [new file with mode: 0644]
src/descrambler/cclient.h [new file with mode: 0644]
src/descrambler/cwc.c
src/descrambler/emm_reass.h
src/input/mpegts.h
src/input/mpegts/mpegts_pid.c

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