]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
descrambler: add constcw DES & AES clients
authorJaroslav Kysela <perex@perex.cz>
Thu, 2 Oct 2014 06:40:01 +0000 (08:40 +0200)
committerJaroslav Kysela <perex@perex.cz>
Thu, 2 Oct 2014 06:40:01 +0000 (08:40 +0200)
Makefile
configure
src/descrambler/caclient.c
src/descrambler/caclient.h
src/descrambler/constcw.c [new file with mode: 0644]
src/descrambler/descrambler.c
src/main.c
src/prop.c
src/prop.h
src/webui/static/app/caclient.js
src/webui/static/app/idnode.js

index 341167b7f3f2db85387d4aa7cae577ea4140fba6..02ad2722e833da23141ea750420bf4453d9b035b 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -296,6 +296,10 @@ SRCS-${CONFIG_CWC} += \
 SRCS-${CONFIG_CAPMT} += \
        src/descrambler/capmt.c
 
+# CONSTCW
+SRCS-${CONFIG_CONSTCW} += \
+       src/descrambler/constcw.c
+
 # FFdecsa
 ifneq ($(CONFIG_DVBCSA),yes)
 FFDECSA-$(CONFIG_CAPMT) = yes
index 18f0c8a89a443a49730ab6bd001208d82b875a6c..b11be3792cc4a1c6c62b7f52681436947f71f232 100755 (executable)
--- a/configure
+++ b/configure
@@ -18,6 +18,7 @@ ROOTDIR=$(cd "$(dirname "$0")"; pwd)
 OPTIONS=(
   "cwc:yes"
   "capmt:yes"
+  "constcw:yes"
   "v4l:no"
   "linuxdvb:yes"
   "satip_client:yes"
@@ -315,7 +316,7 @@ fi
 #
 # libdvbcsa, tvhcsa
 #
-if enabled cwc || enabled capmt; then
+if enabled cwc || enabled capmt || enabled constcw; then
   enable tvhcsa
   if enabled dvbcsa; then
     (check_cc_header "dvbcsa/dvbcsa" dvbcsa_h &&\
index 3ab20f5b86d5653f05c27c16ab51b76d6f06b40a..692ce18171a1fd700bd5efb07be7030db029cf02 100644 (file)
@@ -26,6 +26,10 @@ const idclass_t *caclient_classes[] = {
 #endif
 #if ENABLE_CAPMT
   &caclient_capmt_class,
+#endif
+#if ENABLE_CONSTCW
+  &caclient_ccw_des_class,
+  &caclient_ccw_aes_class,
 #endif
   NULL
 };
@@ -97,6 +101,12 @@ caclient_create
 #if ENABLE_CAPMT
   if (c == &caclient_capmt_class)
     cac = capmt_create();
+#endif
+#if ENABLE_CONSTCW
+  if (c == &caclient_ccw_des_class)
+    cac = constcw_create();
+  if (c == &caclient_ccw_aes_class)
+    cac = constcw_create();
 #endif
   if (cac == NULL)
     abort();
index 1bb0ed2a4ba8c24d81d35fedda9ab3ea705da90f..6a6543ff71841eec194fbaf74ee8972ef17733f6 100644 (file)
@@ -27,6 +27,8 @@ struct mpegts_mux;
 extern const idclass_t caclient_class;
 extern const idclass_t caclient_cwc_class;
 extern const idclass_t caclient_capmt_class;
+extern const idclass_t caclient_ccw_des_class;
+extern const idclass_t caclient_ccw_aes_class;
 
 TAILQ_HEAD(caclient_entry_queue, caclient);
 
@@ -75,5 +77,6 @@ void caclient_done(void);
 
 caclient_t *cwc_create(void);
 caclient_t *capmt_create(void);
+caclient_t *constcw_create(void);
 
 #endif /* __TVH_CACLIENT_H__ */
diff --git a/src/descrambler/constcw.c b/src/descrambler/constcw.c
new file mode 100644 (file)
index 0000000..0748268
--- /dev/null
@@ -0,0 +1,411 @@
+/*
+ *  tvheadend, constant code word interface
+ *  Copyright (C) 2014 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 <ctype.h>
+#include "tvheadend.h"
+#include "caclient.h"
+#include "service.h"
+#include "input.h"
+#include "tvhcsa.h"
+
+/**
+ *
+ */
+typedef struct constcw_service {
+  th_descrambler_t;  
+  LIST_ENTRY(constcw_service) cs_link;
+  tvhcsa_t cs_csa;
+} constcw_service_t;
+
+/**
+ *
+ */
+typedef struct constcw {
+  caclient_t;
+
+  /* From configuration */
+  uint16_t ccw_caid;           /* CA ID */
+  uint32_t ccw_providerid;     /* CA provider ID */
+  uint16_t ccw_tsid;           /* transponder ID */
+  uint16_t ccw_sid;            /* service ID */
+  uint8_t  ccw_key_even[16];   /* DES or AES key */
+  uint8_t  ccw_key_odd [16];   /* DES or AES key */
+  LIST_HEAD(, constcw_service) ccw_services; /* active services */
+} constcw_t;
+
+/*
+ *
+ */
+static const char *
+constcw_name(constcw_t *ccw)
+{
+  return idnode_get_title(&ccw->cac_id);
+}
+
+/**
+ *
+ */
+static int
+constcw_key_size(caclient_t *cac)
+{
+  constcw_t *ccw = (constcw_t *)cac;
+
+  if (idnode_is_instance(&ccw->cac_id, &caclient_ccw_des_class))
+    return 8;
+  return 16;
+}
+
+/*
+ *
+ */
+static int
+constcw_ecm_reset(th_descrambler_t *th)
+{
+  return 1;
+}
+
+/**
+ * s_stream_mutex is held
+ */
+static void 
+constcw_service_destroy(th_descrambler_t *td)
+{
+  constcw_service_t *ct = (constcw_service_t *)td; 
+  
+  LIST_REMOVE(td, td_service_link);
+  LIST_REMOVE(ct, cs_link);
+  tvhcsa_destroy(&ct->cs_csa);
+  free(ct->td_nicename);
+  free(ct);
+}   
+
+/**
+ * global_lock is held. Not that we care about that, but either way, it is.
+ */
+static void
+constcw_service_start(caclient_t *cac, service_t *t)
+{
+  constcw_t *ccw = (constcw_t *)cac;
+  constcw_service_t *ct;
+  th_descrambler_t *td;
+  elementary_stream_t *st;
+  mpegts_service_t *mt;
+  char buf[128];
+  caid_t *c;
+
+  extern const idclass_t mpegts_service_class;
+  if (!idnode_is_instance(&t->s_id, &mpegts_service_class))
+    return;
+  mt = (mpegts_service_t *)t;
+
+  if (mt->s_dvb_service_id != ccw->ccw_sid)
+    return;
+
+  if (mt->s_dvb_mux->mm_tsid != ccw->ccw_tsid)
+    return;
+
+  LIST_FOREACH(ct, &ccw->ccw_services, cs_link)
+    if (ct->td_service == t)
+      break;
+  if (ct)
+    return;
+
+  pthread_mutex_lock(&t->s_stream_mutex);
+  TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) {
+    LIST_FOREACH(c, &st->es_caids, link) {
+      if (c->use && c->caid == ccw->ccw_caid &&
+          c->providerid == ccw->ccw_providerid)
+        break;
+    }
+    if (c) break;
+  }
+  pthread_mutex_unlock(&t->s_stream_mutex);
+  if (st == NULL)
+    return;
+
+  ct                   = calloc(1, sizeof(constcw_service_t));
+  td                   = (th_descrambler_t *)ct;
+  tvhcsa_init(td->td_csa = &ct->cs_csa);
+  snprintf(buf, sizeof(buf), "constcw-%s", constcw_name(ccw));
+  td->td_nicename      = strdup(buf);
+  td->td_service       = t;
+  td->td_stop          = constcw_service_destroy;
+  td->td_ecm_reset     = constcw_ecm_reset;
+  LIST_INSERT_HEAD(&t->s_descramblers, td, td_service_link);
+
+  LIST_INSERT_HEAD(&ccw->ccw_services, ct, cs_link);
+
+  descrambler_keys(td, constcw_key_size(cac) == 8 ?
+                   DESCRAMBLER_DES : DESCRAMBLER_AES,
+                   ccw->ccw_key_even, ccw->ccw_key_odd);
+}
+
+
+/**
+ *
+ */
+static void
+constcw_free(caclient_t *cac)
+{
+  constcw_t *ccw = (constcw_t *)cac;
+  constcw_service_t *ct;
+
+  while((ct = LIST_FIRST(&ccw->ccw_services)) != NULL) {
+    service_t *t = ct->td_service;
+    pthread_mutex_lock(&t->s_stream_mutex);
+    constcw_service_destroy((th_descrambler_t *)&ct);
+    pthread_mutex_unlock(&t->s_stream_mutex);
+  }
+}
+
+/**
+ *
+ */
+static int
+nibble(char c)
+{
+  switch(c) {
+  case '0' ... '9':
+    return c - '0';
+  case 'a' ... 'f':
+    return c - 'a' + 10;
+  case 'A' ... 'F':
+    return c - 'A' + 10;
+  default:
+    return 0;
+  }
+}
+
+/**
+ *
+ */
+static void
+constcw_conf_changed(caclient_t *cac)
+{
+  if (cac->cac_enabled) {
+    caclient_set_status(cac, CACLIENT_STATUS_CONNECTED);
+  } else {
+    caclient_set_status(cac, CACLIENT_STATUS_NONE);
+  }
+}
+
+/**
+ *
+ */
+static int
+constcw_class_key_set(void *o, const void *v, uint8_t *dkey)
+{
+  const char *s = v ?: "";
+  int keysize = constcw_key_size(o);
+  char key[16];
+  int i, u, l;
+
+  for(i = 0; i < keysize; i++) {
+    while(*s != 0 && !isxdigit(*s)) s++;
+    u = *s ? nibble(*s++) : 0;
+    while(*s != 0 && !isxdigit(*s)) s++;
+    l = *s ? nibble(*s++) : 0;
+    key[i] = (u << 4) | l;
+  }
+  i = memcmp(dkey, key, keysize) != 0;
+  memcpy(dkey, key, keysize);
+  return i;
+}
+
+static int
+constcw_class_key_even_set(void *o, const void *v)
+{
+  constcw_t *ccw = o;
+  return constcw_class_key_set(o, v, ccw->ccw_key_even);
+}
+
+static int
+constcw_class_key_odd_set(void *o, const void *v)
+{
+  constcw_t *ccw = o;
+  return constcw_class_key_set(o, v, ccw->ccw_key_odd);
+}
+
+static const void *
+constcw_class_key_get(void *o, const uint8_t *key)
+{
+  static char buf[64];
+  static const char *ret = buf;
+  if (constcw_key_size(o) == 8) {
+    snprintf(buf, sizeof(buf),
+             "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
+             key[0x0], key[0x1], key[0x2], key[0x3],
+             key[0x4], key[0x5], key[0x6], key[0x7]);
+  } else {
+    snprintf(buf, sizeof(buf),
+             "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:"
+             "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
+             key[0x0], key[0x1], key[0x2], key[0x3],
+             key[0x4], key[0x5], key[0x6], key[0x7],
+             key[0x8], key[0x9], key[0xa], key[0xb],
+             key[0xc], key[0xd], key[0xe], key[0xf]);
+  }
+  return &ret;
+}
+
+static const void *
+constcw_class_key_even_get(void *o)
+{
+  constcw_t *ccw = o;
+  return constcw_class_key_get(o, ccw->ccw_key_even);
+}
+
+static const void *
+constcw_class_key_odd_get(void *o)
+{
+  constcw_t *ccw = o;
+  return constcw_class_key_get(o, ccw->ccw_key_odd);
+}
+
+const idclass_t caclient_ccw_des_class =
+{
+  .ic_super      = &caclient_class,
+  .ic_class      = "caclient_ccw_des",
+  .ic_caption    = "DES Constant Code Word Client",
+  .ic_properties = (const property_t[]){
+    {
+      .type     = PT_U16,
+      .id       = "caid",
+      .name     = "CA ID",
+      .off      = offsetof(constcw_t, ccw_caid),
+      .opts     = PO_HEXA,
+      .def.u16  = 0x2600
+    },
+    {
+      .type     = PT_U32,
+      .id       = "providerid",
+      .name     = "Provider ID",
+      .off      = offsetof(constcw_t, ccw_providerid),
+      .opts     = PO_HEXA,
+      .def.u32  = 0
+    },
+    {
+      .type     = PT_U16,
+      .id       = "tsid",
+      .name     = "Transponder ID",
+      .off      = offsetof(constcw_t, ccw_tsid),
+      .opts     = PO_HEXA,
+      .def.u16  = 1,
+    },
+    {
+      .type     = PT_U16,
+      .id       = "sid",
+      .name     = "Service ID",
+      .off      = offsetof(constcw_t, ccw_sid),
+      .opts     = PO_HEXA,
+      .def.u16  = 1,
+    },
+    {
+      .type     = PT_STR,
+      .id       = "key_even",
+      .name     = "Even Key",
+      .set      = constcw_class_key_even_set,
+      .get      = constcw_class_key_even_get,
+      .opts     = PO_PASSWORD,
+      .def.s    = "00:00:00:00:00:00:00:00",
+    },
+    {
+      .type     = PT_STR,
+      .id       = "key_odd",
+      .name     = "Odd Key",
+      .set      = constcw_class_key_odd_set,
+      .get      = constcw_class_key_odd_get,
+      .opts     = PO_PASSWORD,
+      .def.s    = "00:00:00:00:00:00:00:00",
+    },
+    { }
+  }
+};
+
+const idclass_t caclient_ccw_aes_class =
+{
+  .ic_super      = &caclient_class,
+  .ic_class      = "caclient_ccw_aes",
+  .ic_caption    = "AES Constant Code Word Client",
+  .ic_properties = (const property_t[]){
+    {
+      .type     = PT_U16,
+      .id       = "caid",
+      .name     = "CA ID",
+      .off      = offsetof(constcw_t, ccw_caid),
+      .opts     = PO_HEXA,
+      .def.u16  = 0x2600,
+    },
+    {
+      .type     = PT_U32,
+      .id       = "providerid",
+      .name     = "Provider ID",
+      .off      = offsetof(constcw_t, ccw_providerid),
+      .opts     = PO_HEXA,
+      .def.u32  = 0
+    },
+    {
+      .type     = PT_U16,
+      .id       = "tsid",
+      .name     = "Transponder ID",
+      .off      = offsetof(constcw_t, ccw_tsid),
+      .opts     = PO_HEXA,
+      .def.u16  = 1,
+    },
+    {
+      .type     = PT_U16,
+      .id       = "sid",
+      .name     = "Service ID",
+      .off      = offsetof(constcw_t, ccw_sid),
+      .opts     = PO_HEXA,
+      .def.u16  = 1,
+    },
+    {
+      .type     = PT_STR,
+      .id       = "key_even",
+      .name     = "Even Key",
+      .set      = constcw_class_key_even_set,
+      .get      = constcw_class_key_even_get,
+      .opts     = PO_PASSWORD,
+      .def.s    = "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
+    },
+    {
+      .type     = PT_STR,
+      .id       = "key_odd",
+      .name     = "Odd Key",
+      .set      = constcw_class_key_odd_set,
+      .get      = constcw_class_key_odd_get,
+      .opts     = PO_PASSWORD,
+      .def.s    = "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
+    },
+    { }
+  }
+};
+
+/*
+ *
+ */
+caclient_t *constcw_create(void)
+{
+  constcw_t *ccw = calloc(1, sizeof(*ccw));
+
+  ccw->cac_free         = constcw_free;
+  ccw->cac_start        = constcw_service_start;
+  ccw->cac_conf_changed = constcw_conf_changed;
+  return (caclient_t *)ccw;
+}
index 3ab4a73eb71516c96d7ff4e67a86510e3e2443d5..185c5b45840eabc2d2cb2eec3dca573729d16435 100755 (executable)
@@ -112,12 +112,12 @@ descrambler_service_start ( service_t *t )
     return;
 
   ((mpegts_service_t *)t)->s_dvb_mux->mm_descrambler_flush = 0;
-  caclient_start(t);
   if (t->s_descramble == NULL) {
     t->s_descramble = dr = calloc(1, sizeof(th_descrambler_runtime_t));
     sbuf_init(&dr->dr_buf);
     dr->dr_key_index = 0xff;
   }
+  caclient_start(t);
 }
 
 void
index 9cafab08ce0b5f07f4a884a8bcb75333f19db4ec..a83ad9dcbeaf886c40ebcabe30bf1946ed3fa389 100644 (file)
@@ -134,7 +134,7 @@ int              tvheadend_htsp_port_extra;
 const char      *tvheadend_cwd;
 const char      *tvheadend_webroot;
 const tvh_caps_t tvheadend_capabilities[] = {
-#if ENABLE_CWC || ENABLE_CAPMT
+#if ENABLE_CWC || ENABLE_CAPMT || ENABLE_CONSTCW
   { "caclient", NULL },
 #endif
 #if ENABLE_V4L
index ea709468bdc8b365929034e02b215d05de8166ff..171e8efd25c522b549e33e35dadf1d76177413f5 100644 (file)
@@ -451,6 +451,8 @@ prop_serialize_value
     htsmsg_add_bool(m, "password", 1);
   if (opts & PO_DURATION)
     htsmsg_add_bool(m, "duration", 1);
+  if (opts & PO_HEXA)
+    htsmsg_add_bool(m, "hexa", 1);
 
   /* Enum list */
   if (pl->list)
index 541ff6bc91ba411b7c3e254558663d58e9aed6a4..aa4fed053bdc0177d65187e0956c2bc45c2a93dc 100644 (file)
@@ -54,6 +54,7 @@ typedef enum {
 #define PO_SORTKEY  0x0040  // Sort using key (not display value)
 #define PO_PASSWORD 0x0080  // String is a password
 #define PO_DURATION 0x0100  // For PT_TIME - differentiate between duration and datetime
+#define PO_HEXA     0x0200  // Hexadecimal value
 
 /*
  * Property definition
index d1299b6023459810d09bbf785a28329705587b03..40e00a930a0bccf80c82c6877666ba2222056bee 100644 (file)
@@ -20,7 +20,8 @@ tvheadend.caclient = function(panel, index) {
     });
 
     var list = 'enabled,name,username,password,hostname,mode,camdfilename,' +
-               'port,deskey,emm,emmex,comment';
+               'port,deskey,emm,emmex,caid,providerid,tsid,sid,' +
+               'key_even,key_odd,comment';
 
     tvheadend.idnode_form_grid(panel, {
         url: 'api/caclient',
index 5fef4a5683e2cc26d1dcf5babede65fbef61d739..952ee84a8553e635d86ea1fc6acce5b487f85d02 100644 (file)
@@ -183,6 +183,7 @@ tvheadend.IdNodeField = function(conf)
     this.password = conf.showpwd ? false : conf.password;
     this.duration = conf.duration;
     this.intsplit = conf.intsplit;
+    this.hexa = conf.hexa;
     this.group = conf.group;
     this.enum = conf.enum;
     this.store = null;
@@ -217,7 +218,7 @@ tvheadend.IdNodeField = function(conf)
         } else if (this.type === 'int' || this.type === 'u32' ||
             this.type === 'u16' || this.type === 's64' ||
             this.type === 'dbl') {
-            ftype = 'numeric';
+            ftype = this.hexa ? 'string' : 'numeric';
             w = 80;
         } else if (this.type === 'time') {
             w = 120;
@@ -371,7 +372,9 @@ tvheadend.IdNodeField = function(conf)
                 case 's32':
                 case 'dbl':
                 case 'time':
-                    if (this.intsplit) {
+                    if (this.hexa) {
+                        cons = Ext.form.TextField;
+                    } else if (this.intsplit) {
                         c['maskRe'] = /[0-9\.]/;
                         cons = Ext.form.TextField;
                     } else
@@ -559,6 +562,16 @@ tvheadend.idnode_editor_field = function(f, conf)
         case 'u16':
         case 's64':
         case 'dbl':
+            if (f.hexa) {
+                return new Ext.form.TextField({
+                    fieldLabel: f.caption,
+                    name: f.id,
+                    value: '0x' + value.toString(16),
+                    disabled: d,
+                    width: 300,
+                    maskRe: /[xX0-9a-fA-F\.]/,
+                });
+            }
             if (f.intsplit) {
                 /* this should be improved */
                 return new Ext.form.TextField({
@@ -866,7 +879,7 @@ tvheadend.idnode_create = function(conf, onlyDefault)
                         pclass = r.get(conf.select.valueField);
                         win.setTitle('Add ' + s.lastSelectionText);
                         panel.remove(s);
-                        tvheadend.idnode_editor_form(d, null, panel, { create: true });
+                        tvheadend.idnode_editor_form(d, null, panel, { create: true, showpwd: true });
                         saveBtn.setVisible(true);
                     }
                 }
@@ -881,7 +894,7 @@ tvheadend.idnode_create = function(conf, onlyDefault)
                     success: function(d) {
                         panel.remove(s);
                         d = json_decode(d);
-                        tvheadend.idnode_editor_form(d.props, d, panel, { create: true });
+                        tvheadend.idnode_editor_form(d.props, d, panel, { create: true, showpwd: true });
                         saveBtn.setVisible(true);
                     }
                 });
@@ -912,7 +925,7 @@ tvheadend.idnode_create = function(conf, onlyDefault)
             params: conf.params,
             success: function(d) {
                 d = json_decode(d);
-                tvheadend.idnode_editor_form(d.props, d, panel, { create: true });
+                tvheadend.idnode_editor_form(d.props, d, panel, { create: true, showpwd: true });
                 saveBtn.setVisible(true);
                 if (onlyDefault) {
                     saveBtn.handler();