descrambler: add constcw DES & AES clients

This commit is contained in:
Jaroslav Kysela 2014-10-02 08:40:01 +02:00
parent e6b1d4ba2a
commit 4f7e9284af
11 changed files with 455 additions and 9 deletions

View file

@ -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

3
configure vendored
View file

@ -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 &&\

View 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();

View 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__ */

411
src/descrambler/constcw.c Normal file
View file

@ -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;
}

View file

@ -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

View 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

View 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)

View 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

View 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',

View 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();