From: Jaroslav Kysela Date: Thu, 2 Oct 2014 06:40:01 +0000 (+0200) Subject: descrambler: add constcw DES & AES clients X-Git-Tag: v4.1~1236 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4f7e9284af84d70284eb13055ca4b68fdfa40560;p=thirdparty%2Ftvheadend.git descrambler: add constcw DES & AES clients --- diff --git a/Makefile b/Makefile index 341167b7f..02ad2722e 100644 --- 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 diff --git a/configure b/configure index 18f0c8a89..b11be3792 100755 --- 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 &&\ diff --git a/src/descrambler/caclient.c b/src/descrambler/caclient.c index 3ab20f5b8..692ce1817 100644 --- a/src/descrambler/caclient.c +++ b/src/descrambler/caclient.c @@ -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(); diff --git a/src/descrambler/caclient.h b/src/descrambler/caclient.h index 1bb0ed2a4..6a6543ff7 100644 --- a/src/descrambler/caclient.h +++ b/src/descrambler/caclient.h @@ -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 index 000000000..074826850 --- /dev/null +++ b/src/descrambler/constcw.c @@ -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 . + */ + +#include +#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; +} diff --git a/src/descrambler/descrambler.c b/src/descrambler/descrambler.c index 3ab4a73eb..185c5b458 100755 --- a/src/descrambler/descrambler.c +++ b/src/descrambler/descrambler.c @@ -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 diff --git a/src/main.c b/src/main.c index 9cafab08c..a83ad9dcb 100644 --- a/src/main.c +++ b/src/main.c @@ -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 diff --git a/src/prop.c b/src/prop.c index ea709468b..171e8efd2 100644 --- a/src/prop.c +++ b/src/prop.c @@ -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) diff --git a/src/prop.h b/src/prop.h index 541ff6bc9..aa4fed053 100644 --- a/src/prop.h +++ b/src/prop.h @@ -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 diff --git a/src/webui/static/app/caclient.js b/src/webui/static/app/caclient.js index d1299b602..40e00a930 100644 --- a/src/webui/static/app/caclient.js +++ b/src/webui/static/app/caclient.js @@ -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', diff --git a/src/webui/static/app/idnode.js b/src/webui/static/app/idnode.js index 5fef4a568..952ee84a8 100644 --- a/src/webui/static/app/idnode.js +++ b/src/webui/static/app/idnode.js @@ -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();