/***************************************************************************** * dvb_ecmg.c: Example of a basic DVB Simulcrypt ECMG server (ETSI TS 103 197) ***************************************************************************** * Copyright (C) 2010 VideoLAN * * Authors: Christophe Massiot * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject * to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *****************************************************************************/ /* * You usually want to start this from xinetd, like: * * service dvb_ecmg * { * port = 6900 * disable = no * type = UNLISTED * id = dvb_ecmg * socket_type = stream * protocol = tcp * user = nobody * wait = no * server = /usr/local/bin/dvb_ecmg.sh * } * * With the following dvb_ecmg.sh wrapper: * * #!/bin/sh * mkfifo /tmp/dvb_ecmg_stderr.$$ * logger -t "dvb_ecmg[$$]" < /tmp/dvb_ecmg_stderr.$$ & * /usr/local/bin/dvb_ecmg 2>/tmp/dvb_ecmg_stderr.$$ * rm /tmp/dvb_ecmg_stderr.$$ */ #include #include #include #include #include #include #include #include #include #include /* normative size is 64 ko, but we will not output messages longer than this */ #define MAX_TLV_SIZE (1024 - TLV_HEADER_SIZE) #define SELECT_TIMEOUT 10 /* s */ #define DELAY_START 200 /* ms */ #define DELAY_STOP 0 #define REP_PERIOD 300 /* ms */ #define MAX_STREAMS 500 #define MIN_CP_DUR 3 /* 100 ms */ #define LEAD_CW 1 #define CW_PER_MSG 2 #define MAX_COMP_TIME 100 /* ms */ /***************************************************************************** * Local declarations *****************************************************************************/ typedef struct stream_t { uint16_t i_streamid; uint16_t i_ecmid; uint16_t i_cp_duration; } stream_t; static stream_t **pp_streams = NULL; static int i_nb_streams = 0; static bool b_init = true; static uint8_t i_version = 1; static uint16_t i_channelid = 0; static uint32_t i_supercasid; /***************************************************************************** * read_wrapper *****************************************************************************/ ssize_t read_wrapper(void *p_buf, size_t i_count) { size_t i_received = 0; do { ssize_t i_read = read(STDIN_FILENO, p_buf + i_received, i_count - i_received); if (i_read < 0 && errno == EINTR) continue; if (i_read < 0) { fprintf(stderr, "read error, aborting (%m)"); return i_read; } if (!i_read) { fprintf(stderr, "read end-of-file, aborting"); return i_read; } i_received += i_read; } while (i_received < i_count); return i_count; } /***************************************************************************** * write_wrapper *****************************************************************************/ ssize_t write_wrapper(const void *p_buf, size_t i_count) { size_t i_sent = 0; do { ssize_t i_written = write(STDOUT_FILENO, p_buf + i_sent, i_count - i_sent); if (i_written < 0 && errno == EINTR) continue; if (i_written < 0) { fprintf(stderr, "write error (%m)"); return i_written; } i_sent += i_written; } while (i_sent < i_count); return i_count; } /***************************************************************************** * send_channel_status *****************************************************************************/ static void send_channel_status(void) { uint8_t p_tlv_h[MAX_TLV_SIZE]; uint8_t *p_tlv = tlvh_get_tlv(p_tlv_h); uint8_t *p_tlv_n; tlvh_set_version(p_tlv_h, i_version); ecmg_init(p_tlv); tlv_set_type(p_tlv, ECMG_TYPE_CHANNEL_STATUS); /* length will be written at the end */ tlv_set_length(p_tlv, MAX_TLV_SIZE - TLV_HEADER_SIZE - TLVH_HEADER_SIZE); ecmg_append_channelid(p_tlv, i_channelid); ecmg_append_sectiontspkt(p_tlv, 0x0); /* sections */ ecmg_append_delaystart(p_tlv, DELAY_START); ecmg_append_delaystop(p_tlv, DELAY_STOP); ecmg_append_repperiod(p_tlv, REP_PERIOD); ecmg_append_maxstreams(p_tlv, MAX_STREAMS); ecmg_append_mincpdur(p_tlv, MIN_CP_DUR); ecmg_append_leadcw(p_tlv, LEAD_CW); ecmg_append_cwpermsg(p_tlv, CW_PER_MSG); ecmg_append_maxcomptime(p_tlv, MAX_COMP_TIME); p_tlv_n = tlv_find_param(p_tlv, TLV_PARAM_EMPTY, 0); tlv_set_length(p_tlv, p_tlv_n - p_tlv - TLV_HEADER_SIZE); write_wrapper(p_tlv_h, p_tlv_n - p_tlv_h); } /***************************************************************************** * send_channel_test *****************************************************************************/ static void send_channel_test(void) { uint8_t p_tlv_h[MAX_TLV_SIZE]; uint8_t *p_tlv = tlvh_get_tlv(p_tlv_h); uint8_t *p_tlv_n; fprintf(stderr, "sending test on channel ID=0x%hx\n", i_channelid); tlvh_set_version(p_tlv_h, i_version); ecmg_init(p_tlv); tlv_set_type(p_tlv, ECMG_TYPE_CHANNEL_TEST); /* length will be written at the end */ tlv_set_length(p_tlv, MAX_TLV_SIZE - TLV_HEADER_SIZE - TLVH_HEADER_SIZE); ecmg_append_channelid(p_tlv, i_channelid); p_tlv_n = tlv_find_param(p_tlv, TLV_PARAM_EMPTY, 0); tlv_set_length(p_tlv, p_tlv_n - p_tlv - TLV_HEADER_SIZE); write_wrapper(p_tlv_h, p_tlv_n - p_tlv_h); } /***************************************************************************** * send_channel_error *****************************************************************************/ static void send_channel_error(uint16_t i_wanted_channelid, uint16_t i_error) { uint8_t p_tlv_h[MAX_TLV_SIZE]; uint8_t *p_tlv = tlvh_get_tlv(p_tlv_h); uint8_t *p_tlv_n; fprintf(stderr, "sending error on channel ID=0x%hx error=0x%hx\n", i_wanted_channelid, i_error); tlvh_set_version(p_tlv_h, i_version); ecmg_init(p_tlv); tlv_set_type(p_tlv, ECMG_TYPE_CHANNEL_ERROR); /* length will be written at the end */ tlv_set_length(p_tlv, MAX_TLV_SIZE - TLV_HEADER_SIZE - TLVH_HEADER_SIZE); ecmg_append_channelid(p_tlv, i_wanted_channelid); ecmg_append_errorstatus(p_tlv, i_error); p_tlv_n = tlv_find_param(p_tlv, TLV_PARAM_EMPTY, 0); tlv_set_length(p_tlv, p_tlv_n - p_tlv - TLV_HEADER_SIZE); write_wrapper(p_tlv_h, p_tlv_n - p_tlv_h); } /***************************************************************************** * send_stream_status *****************************************************************************/ static void send_stream_status(stream_t *p_stream) { uint8_t p_tlv_h[MAX_TLV_SIZE]; uint8_t *p_tlv = tlvh_get_tlv(p_tlv_h); uint8_t *p_tlv_n; tlvh_set_version(p_tlv_h, i_version); ecmg_init(p_tlv); tlv_set_type(p_tlv, ECMG_TYPE_STREAM_STATUS); /* length will be written at the end */ tlv_set_length(p_tlv, MAX_TLV_SIZE - TLV_HEADER_SIZE - TLVH_HEADER_SIZE); ecmg_append_channelid(p_tlv, i_channelid); ecmg_append_streamid(p_tlv, p_stream->i_streamid); if (i_version >= 2) ecmg_append_ecmid(p_tlv, p_stream->i_ecmid); ecmg_append_accesscritmode(p_tlv, true); p_tlv_n = tlv_find_param(p_tlv, TLV_PARAM_EMPTY, 0); tlv_set_length(p_tlv, p_tlv_n - p_tlv - TLV_HEADER_SIZE); write_wrapper(p_tlv_h, p_tlv_n - p_tlv_h); } /***************************************************************************** * send_stream_close *****************************************************************************/ static void send_stream_close(stream_t *p_stream) { uint8_t p_tlv_h[MAX_TLV_SIZE]; uint8_t *p_tlv = tlvh_get_tlv(p_tlv_h); uint8_t *p_tlv_n; tlvh_set_version(p_tlv_h, i_version); ecmg_init(p_tlv); tlv_set_type(p_tlv, ECMG_TYPE_STREAM_CLOSERESP); /* length will be written at the end */ tlv_set_length(p_tlv, MAX_TLV_SIZE - TLV_HEADER_SIZE - TLVH_HEADER_SIZE); ecmg_append_channelid(p_tlv, i_channelid); ecmg_append_streamid(p_tlv, p_stream->i_streamid); p_tlv_n = tlv_find_param(p_tlv, TLV_PARAM_EMPTY, 0); tlv_set_length(p_tlv, p_tlv_n - p_tlv - TLV_HEADER_SIZE); write_wrapper(p_tlv_h, p_tlv_n - p_tlv_h); } /***************************************************************************** * send_stream_error *****************************************************************************/ static void send_stream_error(uint16_t i_wanted_streamid, uint16_t i_error) { uint8_t p_tlv_h[MAX_TLV_SIZE]; uint8_t *p_tlv = tlvh_get_tlv(p_tlv_h); uint8_t *p_tlv_n; fprintf(stderr, "sending error on stream ID=0x%hx error=0x%hx\n", i_wanted_streamid, i_error); tlvh_set_version(p_tlv_h, i_version); ecmg_init(p_tlv); tlv_set_type(p_tlv, ECMG_TYPE_STREAM_ERROR); /* length will be written at the end */ tlv_set_length(p_tlv, MAX_TLV_SIZE - TLV_HEADER_SIZE - TLVH_HEADER_SIZE); ecmg_append_channelid(p_tlv, i_channelid); ecmg_append_streamid(p_tlv, i_wanted_streamid); ecmg_append_errorstatus(p_tlv, i_error); p_tlv_n = tlv_find_param(p_tlv, TLV_PARAM_EMPTY, 0); tlv_set_length(p_tlv, p_tlv_n - p_tlv - TLV_HEADER_SIZE); write_wrapper(p_tlv_h, p_tlv_n - p_tlv_h); } /***************************************************************************** * send_ecm *****************************************************************************/ static void send_ecm(stream_t *p_stream, uint16_t i_cp_number, const uint8_t *p_ecm, uint16_t i_length) { uint8_t p_tlv_h[MAX_TLV_SIZE + i_length]; /* this is oversized */ uint8_t *p_tlv = tlvh_get_tlv(p_tlv_h); uint8_t *p_tlv_n; tlvh_set_version(p_tlv_h, i_version); ecmg_init(p_tlv); tlv_set_type(p_tlv, ECMG_TYPE_ECM); /* length will be written at the end */ tlv_set_length(p_tlv, MAX_TLV_SIZE - TLV_HEADER_SIZE - TLVH_HEADER_SIZE + i_length); ecmg_append_channelid(p_tlv, i_channelid); ecmg_append_streamid(p_tlv, p_stream->i_streamid); ecmg_append_cpnumber(p_tlv, i_cp_number); tlv_append_data(p_tlv, ECMG_PARAM_ECM, p_ecm, i_length); p_tlv_n = tlv_find_param(p_tlv, TLV_PARAM_EMPTY, 0); tlv_set_length(p_tlv, p_tlv_n - p_tlv - TLV_HEADER_SIZE); write_wrapper(p_tlv_h, p_tlv_n - p_tlv_h); } /***************************************************************************** * build_ecm *****************************************************************************/ static void build_ecm(stream_t *p_stream, uint16_t i_cp_number, uint8_t * const ppi_cw[2], uint8_t *p_accesscrit_param) { /* you will want to customize this function */ static const uint8_t pi_xor[8] = {0x42, 0x12, 0x02, 0x24, 0x21, 0x20, 0x66, 0x88}; PSI_DECLARE(p_section); uint8_t *pi_ecm; int i, j; psi_init(p_section, false); psi_set_tableid(p_section, 0x80 | (i_cp_number & 0x1)); psi_set_length(p_section, 16); pi_ecm = p_section + 3; for (i = 0; i < 2; i++) for (j = 0; j < 8; j++) pi_ecm[i * 8 + j] = ppi_cw[i][j] ^ pi_xor[j]; send_ecm(p_stream, i_cp_number, p_section, psi_get_length(p_section) + PSI_HEADER_SIZE); } /***************************************************************************** * find_stream *****************************************************************************/ static stream_t *find_stream(uint16_t i_streamid) { int i; for (i = 0; i < i_nb_streams; i++) if (pp_streams[i] != NULL && pp_streams[i]->i_streamid == i_streamid) return pp_streams[i]; return NULL; } /***************************************************************************** * handle_channel_setup *****************************************************************************/ static void handle_channel_setup(uint8_t *p_tlv) { uint16_t i_wanted_channelid = ecmg_find_channelid(p_tlv, 0); if (!b_init) { if (i_wanted_channelid != i_channelid) send_channel_error(i_wanted_channelid, 0x6); return; } i_channelid = i_wanted_channelid; i_supercasid = ecmg_find_supercasid(p_tlv, 0); fprintf(stderr, "starting channel ID=0x%hx version=%hhu super CAS ID=0x%x\n", i_channelid, i_version, i_supercasid); b_init = false; send_channel_status(); } /***************************************************************************** * handle_channel_test *****************************************************************************/ static void handle_channel_test(uint8_t *p_tlv) { uint16_t i_wanted_channelid = ecmg_find_channelid(p_tlv, 0); if (i_wanted_channelid != i_channelid) { send_channel_error(i_wanted_channelid, 0x6); return; } send_channel_status(); } /***************************************************************************** * handle_channel_close *****************************************************************************/ static void handle_channel_close(uint8_t *p_tlv) { uint16_t i_wanted_channelid = ecmg_find_channelid(p_tlv, 0); int i; if (i_wanted_channelid != i_channelid) { send_channel_error(i_wanted_channelid, 0x6); return; } fprintf(stderr, "stopping channel ID=0x%x\n", i_channelid); b_init = true; for (i = 0; i < i_nb_streams; i++) free(pp_streams[i]); free(pp_streams); pp_streams = NULL; i_nb_streams = 0; } /***************************************************************************** * handle_channel_error *****************************************************************************/ static void handle_channel_error(uint8_t *p_tlv) { uint16_t i_wanted_channelid = ecmg_find_channelid(p_tlv, 0); fprintf(stderr, "receiving error channel ID=0x%hu error=0x%hx\n", i_wanted_channelid, ecmg_find_errorstatus(p_tlv, 0)); } /***************************************************************************** * handle_stream_setup *****************************************************************************/ static void handle_stream_setup(uint8_t *p_tlv) { uint16_t i_wanted_channelid = ecmg_find_channelid(p_tlv, 0); uint16_t i_streamid = ecmg_find_streamid(p_tlv, 0); stream_t *p_stream; int i; if (i_wanted_channelid != i_channelid) { send_channel_error(i_wanted_channelid, 0x6); return; } if ((p_stream = find_stream(i_streamid)) != NULL) { send_stream_error(i_streamid, 0x14); return; } if (i_version >= 2 && tlv_count_param(p_tlv, ECMG_PARAM_ECMID) != 1) { send_stream_error(i_streamid, 0x10); return; } p_stream = malloc(sizeof(stream_t)); for (i = 0; i < i_nb_streams; i++) { if (pp_streams[i] == NULL) { pp_streams[i] = p_stream; break; } } if (i == i_nb_streams) { pp_streams = realloc(pp_streams, ++i_nb_streams * sizeof(stream_t *)); pp_streams[i_nb_streams - 1] = p_stream; } p_stream->i_streamid = i_streamid; if (i_version >= 2) p_stream->i_ecmid = ecmg_find_ecmid(p_tlv, 0); else p_stream->i_ecmid = 0; p_stream->i_cp_duration = ecmg_find_nomcpdur(p_tlv, 0); fprintf(stderr, "starting new stream id=0x%hx ecmid=0x%hx CP duration=%u ms\n", p_stream->i_streamid, p_stream->i_ecmid, p_stream->i_cp_duration * 100); send_stream_status(p_stream); } /***************************************************************************** * handle_stream_test *****************************************************************************/ static void handle_stream_test(uint8_t *p_tlv) { uint16_t i_wanted_channelid = ecmg_find_channelid(p_tlv, 0); uint16_t i_streamid = ecmg_find_streamid(p_tlv, 0); stream_t *p_stream; if (i_wanted_channelid != i_channelid) { send_channel_error(i_wanted_channelid, 0x6); return; } if ((p_stream = find_stream(i_streamid)) == NULL) { send_stream_error(i_streamid, 0x7); return; } send_stream_status(p_stream); } /***************************************************************************** * handle_stream_close *****************************************************************************/ static void handle_stream_close(uint8_t *p_tlv) { uint16_t i_wanted_channelid = ecmg_find_channelid(p_tlv, 0); uint16_t i_streamid = ecmg_find_streamid(p_tlv, 0); stream_t *p_stream; int i; if (i_wanted_channelid != i_channelid) { send_channel_error(i_wanted_channelid, 0x6); return; } if ((p_stream = find_stream(i_streamid)) == NULL) { send_stream_error(i_streamid, 0x7); return; } fprintf(stderr, "stopping stream ID=0x%hx\n", i_streamid); send_stream_close(p_stream); for (i = 0; i < i_nb_streams; i++) { if (pp_streams[i] == p_stream) { pp_streams[i] = NULL; break; } } free(p_stream); } /***************************************************************************** * handle_stream_error *****************************************************************************/ static void handle_stream_error(uint8_t *p_tlv) { uint16_t i_wanted_channelid = ecmg_find_channelid(p_tlv, 0); uint16_t i_streamid = ecmg_find_streamid(p_tlv, 0); fprintf(stderr, "receiving error channel ID=0x%hu stream ID=0x%hx error=0x%hx\n", i_wanted_channelid, i_streamid, ecmg_find_errorstatus(p_tlv, 0)); } /***************************************************************************** * handle_cw *****************************************************************************/ static void handle_cw(uint8_t *p_tlv) { uint16_t i_wanted_channelid = ecmg_find_channelid(p_tlv, 0); uint16_t i_streamid = ecmg_find_streamid(p_tlv, 0); stream_t *p_stream; uint16_t i_cp_number, i_cw_cp_number; uint16_t i_cw_length; uint8_t *p_cw_param, *p_accesscrit_param; uint8_t *ppi_cw[2] = {NULL, NULL}; if (i_wanted_channelid != i_channelid) { send_channel_error(i_wanted_channelid, 0x6); return; } if ((p_stream = find_stream(i_streamid)) == NULL) { send_stream_error(i_streamid, 0x7); return; } if (tlv_count_param(p_tlv, ECMG_PARAM_CWENCRYPT)) { send_stream_error(i_streamid, 0xe); return; } if (tlv_count_param(p_tlv, ECMG_PARAM_CPCWCOMB) != 2) { send_stream_error(i_streamid, 0xb); return; } i_cp_number = ecmg_find_cpnumber(p_tlv, 0); p_cw_param = tlv_find_data(p_tlv, ECMG_PARAM_CPCWCOMB, 0, &i_cw_length); if (i_cw_length != 10) { send_stream_error(i_streamid, 0xf); return; } i_cw_cp_number = ecmgcw_get_cpnum(p_cw_param); ppi_cw[i_cw_cp_number & 0x1] = ecmgcw_get_cw(p_cw_param); p_cw_param = tlv_find_data(p_tlv, ECMG_PARAM_CPCWCOMB, 1, &i_cw_length); if (i_cw_length != 10) { send_stream_error(i_streamid, 0xf); return; } i_cw_cp_number = ecmgcw_get_cpnum(p_cw_param); ppi_cw[i_cw_cp_number & 0x1] = ecmgcw_get_cw(p_cw_param); if (ppi_cw[0] == NULL || ppi_cw[1] == NULL) { send_stream_error(i_streamid, 0xb); return; } p_accesscrit_param = tlv_find_param(p_tlv, ECMG_PARAM_ACCESSCRIT, 0); build_ecm(p_stream, i_cp_number, ppi_cw, p_accesscrit_param); } /***************************************************************************** * Main loop *****************************************************************************/ int main(int i_argc, char **ppsz_argv) { if (i_argc > 1) { fprintf(stderr, "usage: %s < > \n", ppsz_argv[0]); return EXIT_FAILURE; } for ( ; ; ) { uint8_t *p_tlv_h = malloc(TLVH_HEADER_SIZE + TLV_HEADER_SIZE); uint8_t *p_tlv = tlvh_get_tlv(p_tlv_h); uint16_t i_type; fd_set rset; struct timeval timeout; int i_ret; FD_ZERO(&rset); FD_SET(STDIN_FILENO, &rset); timeout.tv_sec = SELECT_TIMEOUT; timeout.tv_usec = 0; if ((i_ret = select(STDIN_FILENO + 1, &rset, NULL, NULL, &timeout)) < 0) return EXIT_FAILURE; if (!i_ret) { if (b_init) return EXIT_FAILURE; /* timeout - send a packet so that the communication is reset * if it is really dead */ send_channel_test(); continue; } if (read_wrapper(p_tlv_h, TLVH_HEADER_SIZE + TLV_HEADER_SIZE) <= 0) return EXIT_FAILURE; p_tlv_h = realloc(p_tlv_h, TLVH_HEADER_SIZE + TLV_HEADER_SIZE + tlv_get_length(p_tlv)); p_tlv = tlvh_get_tlv(p_tlv_h); if (read_wrapper(p_tlv + TLV_HEADER_SIZE, tlv_get_length(p_tlv)) <= 0) return EXIT_FAILURE; if ((b_init && tlvh_get_version(p_tlv_h) > 3) || (!b_init && tlvh_get_version(p_tlv_h) != i_version)) { send_channel_error(i_channelid, 0x2); free(p_tlv_h); continue; } i_type = tlv_get_type(p_tlv); if (!tlv_validate(p_tlv)) { send_channel_error(i_channelid, 0x1); free(p_tlv_h); continue; } if (!ecmg_validate(p_tlv)) { send_channel_error(i_channelid, 0xf); free(p_tlv_h); continue; } if (b_init && i_type != ECMG_TYPE_CHANNEL_SETUP) { send_channel_error(i_channelid, 0x6); free(p_tlv_h); continue; } switch (i_type) { case ECMG_TYPE_CHANNEL_SETUP: if (b_init) i_version = tlvh_get_version(p_tlv_h); handle_channel_setup(p_tlv); break; case ECMG_TYPE_CHANNEL_TEST: handle_channel_test(p_tlv); break; case ECMG_TYPE_CHANNEL_CLOSE: handle_channel_close(p_tlv); break; case ECMG_TYPE_CHANNEL_ERROR: handle_channel_error(p_tlv); break; case ECMG_TYPE_STREAM_SETUP: handle_stream_setup(p_tlv); break; case ECMG_TYPE_STREAM_TEST: handle_stream_test(p_tlv); break; case ECMG_TYPE_STREAM_CLOSEREQ: handle_stream_close(p_tlv); break; case ECMG_TYPE_STREAM_ERROR: handle_stream_error(p_tlv); break; case ECMG_TYPE_CHANNEL_STATUS: case ECMG_TYPE_STREAM_STATUS: break; case ECMG_TYPE_CW: handle_cw(p_tlv); break; default: send_channel_error(i_channelid, 0x3); break; } free(p_tlv_h); } return EXIT_SUCCESS; }