| @ -0,0 +1,459 @@ | |||
| /***************************************************************************** | |||
| * sim.h: ETSI TS 103 194 DVB Simulcrypt | |||
| ***************************************************************************** | |||
| * Copyright (C) 2010 VideoLAN | |||
| * $Id$ | |||
| * | |||
| * Authors: Christophe Massiot <massiot@via.ecp.fr> | |||
| * | |||
| * This program is free software. It comes without any warranty, to | |||
| * the extent permitted by applicable law. You can redistribute it | |||
| * and/or modify it under the terms of the Do What The Fuck You Want | |||
| * To Public License, Version 2, as published by Sam Hocevar. See | |||
| * http://sam.zoy.org/wtfpl/COPYING for more details. | |||
| *****************************************************************************/ | |||
| /* | |||
| * Normative references: | |||
| * - ETSI TS 101 197 V1.2.1 (2002-02) (DVB Simulcrypt - part 1) | |||
| * - ETSI TR 102 035 V1.1.1 (2002-04) (Implementation guidelines of Simulcrypt) | |||
| * - ETSI TS 103 197 V1.5.1 (2008-10) (DVB Simulcrypt) | |||
| */ | |||
| #ifndef __BITSTREAM_DVB_SIM_H__ | |||
| #define __BITSTREAM_DVB_SIM_H__ | |||
| #ifdef __cplusplus | |||
| extern "C" | |||
| { | |||
| #endif | |||
| /***************************************************************************** | |||
| * Generic "Type, Length, Value" message | |||
| *****************************************************************************/ | |||
| #define TLV_HEADER_SIZE 5 | |||
| #define TLV_PARAM_SIZE 4 | |||
| #define TLV_PARAM_EMPTY 0 | |||
| #define TLV_PARAM_EMPTY_SIZE 2 | |||
| typedef struct tlv_param_count_t { | |||
| uint16_t i_type; | |||
| uint16_t i_min; | |||
| uint16_t i_max; | |||
| } tlv_param_count_t; | |||
| static inline void tlv_append_empty(uint8_t *p_tlv) | |||
| { | |||
| /* prepare a dummy parameter for later appending */ | |||
| p_tlv[TLV_HEADER_SIZE] = TLV_PARAM_EMPTY >> 8; | |||
| p_tlv[TLV_HEADER_SIZE + 1] = TLV_PARAM_EMPTY & 0xff; | |||
| } | |||
| static inline void tlv_set_version(uint8_t *p_tlv, uint8_t i_version) | |||
| { | |||
| p_tlv[0] = i_version; | |||
| } | |||
| static inline uint8_t tlv_get_version(uint8_t *p_tlv) | |||
| { | |||
| return p_tlv[0]; | |||
| } | |||
| static inline void tlv_set_type(uint8_t *p_tlv, uint16_t i_type) | |||
| { | |||
| p_tlv[1] = i_type >> 8; | |||
| p_tlv[2] = i_type & 0xff; | |||
| } | |||
| static inline uint16_t tlv_get_type(uint8_t *p_tlv) | |||
| { | |||
| return (p_tlv[1] << 8) | p_tlv[2]; | |||
| } | |||
| static inline void tlv_set_length(uint8_t *p_tlv, uint16_t i_length) | |||
| { | |||
| p_tlv[3] = i_length >> 8; | |||
| p_tlv[4] = i_length & 0xff; | |||
| } | |||
| static inline uint16_t tlv_get_length(uint8_t *p_tlv) | |||
| { | |||
| return (p_tlv[3] << 8) | p_tlv[4]; | |||
| } | |||
| static inline void tlvn_set_type(uint8_t *p_tlv_n, uint16_t i_type) | |||
| { | |||
| p_tlv_n[0] = i_type >> 8; | |||
| p_tlv_n[1] = i_type & 0xff; | |||
| } | |||
| static inline uint16_t tlvn_get_type(uint8_t *p_tlv_n) | |||
| { | |||
| return (p_tlv_n[0] << 8) | p_tlv_n[1]; | |||
| } | |||
| static inline void tlvn_set_length(uint8_t *p_tlv_n, uint16_t i_length) | |||
| { | |||
| p_tlv_n[2] = i_length >> 8; | |||
| p_tlv_n[3] = i_length & 0xff; | |||
| } | |||
| static inline uint16_t tlvn_get_length(uint8_t *p_tlv_n) | |||
| { | |||
| return (p_tlv_n[2] << 8) | p_tlv_n[3]; | |||
| } | |||
| static inline void tlvn_append_empty(uint8_t *p_tlv_n) | |||
| { | |||
| uint16_t i_length = tlvn_get_length(p_tlv_n); | |||
| p_tlv_n[TLV_PARAM_SIZE + i_length] = TLV_PARAM_EMPTY >> 8; | |||
| p_tlv_n[TLV_PARAM_SIZE + i_length + 1] = TLV_PARAM_EMPTY & 0xff; | |||
| } | |||
| static inline uint8_t *tlv_get_param(uint8_t *p_tlv, uint16_t n) | |||
| { | |||
| uint16_t i_tlv_size = tlv_get_length(p_tlv); | |||
| uint8_t *p_tlv_n = p_tlv + TLV_HEADER_SIZE; | |||
| if (p_tlv_n - p_tlv - TLV_HEADER_SIZE > i_tlv_size) return NULL; | |||
| while (n) { | |||
| if (p_tlv_n + TLV_PARAM_SIZE - p_tlv - TLV_HEADER_SIZE > i_tlv_size) | |||
| return NULL; | |||
| p_tlv_n += TLV_PARAM_SIZE + tlvn_get_length(p_tlv_n); | |||
| n--; | |||
| } | |||
| if (p_tlv_n - p_tlv - TLV_HEADER_SIZE >= i_tlv_size) return NULL; | |||
| return p_tlv_n; | |||
| } | |||
| static inline bool tlv_validate_param(const uint8_t *p_tlv, | |||
| const uint8_t *p_tlv_n, uint16_t i_length) | |||
| { | |||
| uint16_t i_tlv_size = tlv_get_length(p_tlv); | |||
| return (p_tlv_n + TLV_PARAM_SIZE + i_length - p_tlv - TLV_HEADER_SIZE | |||
| <= i_tlv_size); | |||
| } | |||
| static inline bool tlv_validate(const uint8_t *p_tlv) | |||
| { | |||
| uint16_t i_tlv_size = tlv_get_length(p_tlv); | |||
| const uint8_t *p_tlv_n = p_tlv + TLV_HEADER_SIZE; | |||
| while (p_tlv_n + TLV_PARAM_SIZE - p_tlv - TLV_HEADER_SIZE <= i_tlv_size | |||
| && p_tlv_n + TLV_PARAM_SIZE + tlvn_get_length(p_tlv_n) | |||
| - p_tlv - TLV_HEADER_SIZE <= i_tlv_size) | |||
| p_tlv_n += TLV_PARAM_SIZE + tlvn_get_length(p_tlv_n); | |||
| return (p_tlv_n - p_tlv - TLV_HEADER_SIZE == i_tlv_size); | |||
| } | |||
| static inline uint8_t *tlv_find_param(uint8_t *p_tlv, uint16_t i_type, | |||
| uint16_t n) | |||
| { | |||
| uint8_t *p_param; | |||
| uint8_t j = 0; | |||
| while ((p_param = tlv_get_param(p_tlv, j)) != NULL) { | |||
| j++; | |||
| if (tlvn_get_type(p_param) == i_type) { | |||
| if (!n) return p_param; | |||
| n--; | |||
| } | |||
| } | |||
| return NULL; | |||
| } | |||
| static inline uint16_t tlv_count_param(uint8_t *p_tlv, uint16_t i_type) | |||
| { | |||
| uint16_t i_count = 0; | |||
| uint8_t *p_tlv_n; | |||
| uint16_t j = 0; | |||
| while ((p_tlv_n = tlv_get_param(p_tlv, j)) != NULL) { | |||
| j++; | |||
| if (tlvn_get_type(p_tlv_n) == i_type) | |||
| i_count++; | |||
| } | |||
| return i_count; | |||
| } | |||
| static inline bool tlv_validate_count_param(uint8_t *p_tlv, | |||
| tlv_param_count_t *p_count) | |||
| { | |||
| uint16_t i_count = tlv_count_param(p_tlv, p_count->i_type); | |||
| return (i_count >= p_count->i_min) && (i_count <= p_count->i_max); | |||
| } | |||
| #define TLV_DECLARE_PARAM(intf, name, param, utype, type) \ | |||
| static inline bool intf##_append_##name(uint8_t *p_tlv, type i_##name) \ | |||
| { \ | |||
| uint8_t *p_tlv_n = tlv_find_param(p_tlv, TLV_PARAM_EMPTY, 0); \ | |||
| int i; \ | |||
| if (!tlv_validate_param(p_tlv, p_tlv_n, \ | |||
| sizeof(type) + TLV_PARAM_EMPTY_SIZE)) \ | |||
| return false; \ | |||
| tlvn_set_type(p_tlv_n, param); \ | |||
| tlvn_set_length(p_tlv_n, sizeof(type)); \ | |||
| for (i = 0; i < sizeof(type); i++) \ | |||
| p_tlv_n[4 + i] = ((utype)(i_##name) >> 8 * (sizeof(type) - i - 1)) \ | |||
| & 0xff; \ | |||
| tlvn_append_empty(p_tlv_n); \ | |||
| return true; \ | |||
| } \ | |||
| \ | |||
| static inline type intf##_find_##name(uint8_t *p_tlv, uint16_t n) \ | |||
| { \ | |||
| const uint8_t *p_tlv_n = tlv_find_param(p_tlv, param, n); \ | |||
| type i_##name = (type)(p_tlv_n[4]) << 8 * (sizeof(type) - 1); \ | |||
| int i; \ | |||
| for (i = 1; i < sizeof(type); i++) \ | |||
| i_##name |= (utype)(p_tlv_n[4 + i]) << 8 * (sizeof(type) - i - 1); \ | |||
| return i_##name; \ | |||
| } | |||
| static inline bool tlv_append_data(uint8_t *p_tlv, uint16_t i_type, | |||
| const uint8_t *p_data, uint16_t i_length) | |||
| { | |||
| uint8_t *p_tlv_n = tlv_find_param(p_tlv, TLV_PARAM_EMPTY, 0); | |||
| int i; | |||
| if (!tlv_validate_param(p_tlv, p_tlv_n, | |||
| i_length + TLV_PARAM_EMPTY_SIZE)) | |||
| return false; | |||
| tlvn_set_type(p_tlv_n, i_type); | |||
| tlvn_set_length(p_tlv_n, i_length); | |||
| memcpy(p_tlv_n + 4, p_data, i_length); | |||
| tlvn_append_empty(p_tlv_n); | |||
| return true; | |||
| } | |||
| static inline uint8_t *tlv_find_data(uint8_t *p_tlv, uint16_t i_type, | |||
| uint16_t n, uint16_t *pi_length) | |||
| { | |||
| const uint8_t *p_tlv_n = tlv_find_param(p_tlv, i_type, n); | |||
| *pi_length = tlvn_get_length(p_tlv_n); | |||
| return p_tlv_n + 4; | |||
| } | |||
| /***************************************************************************** | |||
| * ECMG | |||
| *****************************************************************************/ | |||
| /* message types */ | |||
| #define ECMG_TYPE_CHANNEL_SETUP 0x1 | |||
| #define ECMG_TYPE_CHANNEL_TEST 0x2 | |||
| #define ECMG_TYPE_CHANNEL_STATUS 0x3 | |||
| #define ECMG_TYPE_CHANNEL_CLOSE 0x4 | |||
| #define ECMG_TYPE_CHANNEL_ERROR 0x5 | |||
| #define ECMG_TYPE_STREAM_SETUP 0x101 | |||
| #define ECMG_TYPE_STREAM_TEST 0x102 | |||
| #define ECMG_TYPE_STREAM_STATUS 0x103 | |||
| #define ECMG_TYPE_STREAM_CLOSEREQ 0x104 | |||
| #define ECMG_TYPE_STREAM_CLOSERESP 0x105 | |||
| #define ECMG_TYPE_STREAM_ERROR 0x106 | |||
| #define ECMG_TYPE_CW 0x201 | |||
| #define ECMG_TYPE_ECM 0x202 | |||
| /* parameter types */ | |||
| #define ECMG_PARAM_SUPERCASID 0x1 | |||
| #define ECMG_PARAM_SECTIONTSPKT 0x2 | |||
| #define ECMG_PARAM_DELAYSTART 0x3 | |||
| #define ECMG_PARAM_DELAYSTOP 0x4 | |||
| #define ECMG_PARAM_TRANSDELAYSTART 0x5 | |||
| #define ECMG_PARAM_TRANSDELAYSTOP 0x6 | |||
| #define ECMG_PARAM_REPPERIOD 0x7 | |||
| #define ECMG_PARAM_MAXSTREAMS 0x8 | |||
| #define ECMG_PARAM_MINCPDUR 0x9 | |||
| #define ECMG_PARAM_LEADCW 0xa | |||
| #define ECMG_PARAM_CWPERMSG 0xb | |||
| #define ECMG_PARAM_MAXCOMPTIME 0xc | |||
| #define ECMG_PARAM_ACCESSCRIT 0xd | |||
| #define ECMG_PARAM_CHANNELID 0xe | |||
| #define ECMG_PARAM_STREAMID 0xf | |||
| #define ECMG_PARAM_NOMCPDUR 0x10 | |||
| #define ECMG_PARAM_ACCESSCRITMODE 0x11 | |||
| #define ECMG_PARAM_CPNUMBER 0x12 | |||
| #define ECMG_PARAM_CPDUR 0x13 | |||
| #define ECMG_PARAM_CPCWCOMB 0x14 | |||
| #define ECMG_PARAM_ECM 0x15 | |||
| #define ECMG_PARAM_ACDELAYSTART 0x16 | |||
| #define ECMG_PARAM_ACDELAYSTOP 0x17 | |||
| #define ECMG_PARAM_CWENCRYPT 0x18 | |||
| #define ECMG_PARAM_ECMID 0x19 | |||
| #define ECMG_PARAM_ERRORSTATUS 0x7000 | |||
| #define ECMG_PARAM_ERRORINFO 0x7001 | |||
| static inline void ecmg_init(uint8_t *p_tlv) | |||
| { | |||
| tlv_append_empty(p_tlv); | |||
| } | |||
| TLV_DECLARE_PARAM(ecmg, supercasid, ECMG_PARAM_SUPERCASID, uint32_t, uint32_t) | |||
| TLV_DECLARE_PARAM(ecmg, sectiontspkt, ECMG_PARAM_SECTIONTSPKT, uint8_t, uint8_t) | |||
| TLV_DECLARE_PARAM(ecmg, delaystart, ECMG_PARAM_DELAYSTART, uint16_t, int16_t) | |||
| TLV_DECLARE_PARAM(ecmg, delaystop, ECMG_PARAM_DELAYSTOP, uint16_t, int16_t) | |||
| TLV_DECLARE_PARAM(ecmg, transdelaystart, ECMG_PARAM_TRANSDELAYSTART, | |||
| uint16_t, int16_t) | |||
| TLV_DECLARE_PARAM(ecmg, transdelaystop, ECMG_PARAM_TRANSDELAYSTOP, | |||
| uint16_t, int16_t) | |||
| TLV_DECLARE_PARAM(ecmg, repperiod, ECMG_PARAM_REPPERIOD, uint16_t, uint16_t) | |||
| TLV_DECLARE_PARAM(ecmg, maxstreams, ECMG_PARAM_MAXSTREAMS, uint16_t, uint16_t) | |||
| TLV_DECLARE_PARAM(ecmg, mincpdur, ECMG_PARAM_MINCPDUR, uint16_t, uint16_t) | |||
| TLV_DECLARE_PARAM(ecmg, leadcw, ECMG_PARAM_LEADCW, uint8_t, uint8_t) | |||
| TLV_DECLARE_PARAM(ecmg, cwpermsg, ECMG_PARAM_CWPERMSG, uint8_t, uint8_t) | |||
| TLV_DECLARE_PARAM(ecmg, maxcomptime, ECMG_PARAM_MAXCOMPTIME, uint16_t, uint16_t) | |||
| TLV_DECLARE_PARAM(ecmg, channelid, ECMG_PARAM_CHANNELID, uint16_t, uint16_t) | |||
| TLV_DECLARE_PARAM(ecmg, streamid, ECMG_PARAM_STREAMID, uint16_t, uint16_t) | |||
| TLV_DECLARE_PARAM(ecmg, nomcpdur, ECMG_PARAM_NOMCPDUR, uint16_t, uint16_t) | |||
| TLV_DECLARE_PARAM(ecmg, accesscritmode, ECMG_PARAM_ACCESSCRITMODE, | |||
| uint8_t, bool) | |||
| TLV_DECLARE_PARAM(ecmg, cpnumber, ECMG_PARAM_CPNUMBER, uint16_t, uint16_t) | |||
| TLV_DECLARE_PARAM(ecmg, cpdur, ECMG_PARAM_CPDUR, uint16_t, uint16_t) | |||
| TLV_DECLARE_PARAM(ecmg, acdelaystart, ECMG_PARAM_ACDELAYSTART, | |||
| uint16_t, int16_t) | |||
| TLV_DECLARE_PARAM(ecmg, acdelaystop, ECMG_PARAM_ACDELAYSTOP, uint16_t, int16_t) | |||
| TLV_DECLARE_PARAM(ecmg, ecmid, ECMG_PARAM_ECMID, uint16_t, uint16_t) | |||
| TLV_DECLARE_PARAM(ecmg, errorstatus, ECMG_PARAM_ERRORSTATUS, uint16_t, uint16_t) | |||
| static inline void ecmgcw_set_cpnum(uint8_t *p_param, uint16_t i_cpnum) | |||
| { | |||
| p_param[0] = i_cpnum >> 8; | |||
| p_param[1] = i_cpnum & 0xff; | |||
| } | |||
| static inline uint16_t ecmgcw_get_cpnum(uint8_t *p_param) | |||
| { | |||
| return (p_param[0] << 8) | p_param[1]; | |||
| } | |||
| static inline uint8_t *ecmgcw_get_cw(uint8_t *p_param) | |||
| { | |||
| return p_param + 2; | |||
| } | |||
| static inline bool ecmg_validate_param(const uint8_t *p_tlv_n) | |||
| { | |||
| static const uint16_t pi_ecmg_params_minlength[] = { | |||
| /* 0x0 */ 0, 4, 1, 2, 2, 2, 2, 2, | |||
| /* 0x8 */ 2, 2, 1, 1, 2, 0, 2, 2, | |||
| /* 0x10 */ 2, 1, 2, 2, 0, 0, 2, 2, | |||
| /* 0x18 */ 0, 2 | |||
| }; | |||
| uint16_t i_type = tlvn_get_type(p_tlv_n); | |||
| uint16_t i_length = tlvn_get_length(p_tlv_n); | |||
| if (i_type <= ECMG_PARAM_ECMID) { | |||
| if (i_length < pi_ecmg_params_minlength[i_type]) return false; | |||
| } else if (i_type == ECMG_PARAM_ERRORSTATUS) { | |||
| if (i_length < 2) return false; | |||
| } | |||
| return true; | |||
| } | |||
| static inline bool ecmg_validate(uint8_t *p_tlv) | |||
| { | |||
| static const tlv_param_count_t p_ecmg_params_channel_setup[] = { | |||
| {ECMG_PARAM_CHANNELID, 1, 1}, {ECMG_PARAM_SUPERCASID, 1, 1}, {0, 0, 0} | |||
| }; | |||
| static const tlv_param_count_t p_ecmg_params_channel_test[] = { | |||
| {ECMG_PARAM_CHANNELID, 1, 1}, {0, 0, 0} | |||
| }; | |||
| static const tlv_param_count_t p_ecmg_params_channel_status[] = { | |||
| {ECMG_PARAM_CHANNELID, 1, 1}, {ECMG_PARAM_SECTIONTSPKT, 1, 1}, | |||
| {ECMG_PARAM_ACDELAYSTART, 0, 1}, {ECMG_PARAM_ACDELAYSTOP, 0, 1}, | |||
| {ECMG_PARAM_DELAYSTART, 1, 1}, {ECMG_PARAM_DELAYSTOP, 1, 1}, | |||
| {ECMG_PARAM_TRANSDELAYSTART, 0, 1}, {ECMG_PARAM_TRANSDELAYSTOP, 0, 1}, | |||
| {ECMG_PARAM_REPPERIOD, 1, 1}, {ECMG_PARAM_MAXSTREAMS, 1, 1}, | |||
| {ECMG_PARAM_MINCPDUR, 1, 1}, {ECMG_PARAM_LEADCW, 1, 1}, | |||
| {ECMG_PARAM_CWPERMSG, 1, 1}, {ECMG_PARAM_MAXCOMPTIME, 1, 1}, {0, 0, 0} | |||
| }; | |||
| static const tlv_param_count_t p_ecmg_params_channel_close[] = { | |||
| {ECMG_PARAM_CHANNELID, 1, 1}, {0, 0, 0} | |||
| }; | |||
| static const tlv_param_count_t p_ecmg_params_channel_error[] = { | |||
| {ECMG_PARAM_CHANNELID, 1, 1}, {ECMG_PARAM_ERRORSTATUS, 1, UINT16_MAX}, | |||
| {0, 0, 0} | |||
| }; | |||
| static const tlv_param_count_t p_ecmg_params_stream_setup[] = { | |||
| {ECMG_PARAM_CHANNELID, 1, 1}, {ECMG_PARAM_STREAMID, 1, 1}, | |||
| {ECMG_PARAM_ECMID, 0, 1}, {ECMG_PARAM_NOMCPDUR, 1, 1}, {0, 0, 0} | |||
| }; | |||
| static const tlv_param_count_t p_ecmg_params_stream_test[] = { | |||
| {ECMG_PARAM_CHANNELID, 1, 1}, {ECMG_PARAM_STREAMID, 1, 1}, {0, 0, 0} | |||
| }; | |||
| static const tlv_param_count_t p_ecmg_params_stream_status[] = { | |||
| {ECMG_PARAM_CHANNELID, 1, 1}, {ECMG_PARAM_STREAMID, 1, 1}, | |||
| {ECMG_PARAM_ECMID, 0, 1}, {ECMG_PARAM_ACCESSCRITMODE, 1, 1}, {0, 0, 0} | |||
| }; | |||
| static const tlv_param_count_t p_ecmg_params_stream_close[] = { | |||
| {ECMG_PARAM_CHANNELID, 1, 1}, {ECMG_PARAM_STREAMID, 1, 1}, {0, 0, 0} | |||
| }; | |||
| static const tlv_param_count_t p_ecmg_params_stream_error[] = { | |||
| {ECMG_PARAM_CHANNELID, 1, 1}, {ECMG_PARAM_STREAMID, 1, 1}, | |||
| {ECMG_PARAM_ERRORSTATUS, 1, UINT16_MAX}, {0, 0, 0} | |||
| }; | |||
| static const tlv_param_count_t p_ecmg_params_cw[] = { | |||
| {ECMG_PARAM_CHANNELID, 1, 1}, {ECMG_PARAM_STREAMID, 1, 1}, | |||
| {ECMG_PARAM_CPNUMBER, 1, 1}, {ECMG_PARAM_CWENCRYPT, 0, 1}, | |||
| {ECMG_PARAM_CPCWCOMB, 1, UINT16_MAX}, {ECMG_PARAM_CPDUR, 0, 1}, | |||
| {ECMG_PARAM_ACCESSCRIT, 0, 1}, {0, 0, 0} | |||
| }; | |||
| static const tlv_param_count_t p_ecmg_params_ecm[] = { | |||
| {ECMG_PARAM_CHANNELID, 1, 1}, {ECMG_PARAM_STREAMID, 1, 1}, | |||
| {ECMG_PARAM_CPNUMBER, 1, 1}, {ECMG_PARAM_ECM, 1, 1}, {0, 0, 0} | |||
| }; | |||
| const tlv_param_count_t *p_param = {0, 0, 0}; | |||
| uint8_t *p_tlv_n; | |||
| int j = 0; | |||
| switch (tlv_get_type(p_tlv)) { | |||
| case ECMG_TYPE_CHANNEL_SETUP: | |||
| p_param = p_ecmg_params_channel_setup; break; | |||
| case ECMG_TYPE_CHANNEL_TEST: | |||
| p_param = p_ecmg_params_channel_test; break; | |||
| case ECMG_TYPE_CHANNEL_STATUS: | |||
| p_param = p_ecmg_params_channel_status; break; | |||
| case ECMG_TYPE_CHANNEL_CLOSE: | |||
| p_param = p_ecmg_params_channel_close; break; | |||
| case ECMG_TYPE_CHANNEL_ERROR: | |||
| p_param = p_ecmg_params_channel_error; break; | |||
| case ECMG_TYPE_STREAM_SETUP: | |||
| p_param = p_ecmg_params_stream_setup; break; | |||
| case ECMG_TYPE_STREAM_TEST: | |||
| p_param = p_ecmg_params_stream_test; break; | |||
| case ECMG_TYPE_STREAM_STATUS: | |||
| p_param = p_ecmg_params_stream_status; break; | |||
| case ECMG_TYPE_STREAM_CLOSEREQ: | |||
| case ECMG_TYPE_STREAM_CLOSERESP: | |||
| p_param = p_ecmg_params_stream_close; break; | |||
| case ECMG_TYPE_STREAM_ERROR: | |||
| p_param = p_ecmg_params_stream_error; break; | |||
| case ECMG_TYPE_CW: | |||
| p_param = p_ecmg_params_cw; break; | |||
| case ECMG_TYPE_ECM: | |||
| p_param = p_ecmg_params_ecm; break; | |||
| default: | |||
| break; | |||
| } | |||
| while (p_param->i_type) | |||
| if (!tlv_validate_count_param(p_tlv, p_param++)) | |||
| return false; | |||
| while ((p_tlv_n = tlv_get_param(p_tlv, j)) != NULL) { | |||
| j++; | |||
| if (!ecmg_validate_param(p_tlv_n)) return false; | |||
| } | |||
| return true; | |||
| } | |||
| #ifdef __cplusplus | |||
| } | |||
| #endif | |||
| #endif | |||
| @ -0,0 +1,717 @@ | |||
| /***************************************************************************** | |||
| * dvb_ecmg.c: Example of a basic DVB Simulcrypt ECMG server (ETSI TS 103 197) | |||
| ***************************************************************************** | |||
| * Copyright (C) 2010 VideoLAN | |||
| * $Id$ | |||
| * | |||
| * Authors: Christophe Massiot <massiot@via.ecp.fr> | |||
| * | |||
| * This program is free software. It comes without any warranty, to | |||
| * the extent permitted by applicable law. You can redistribute it | |||
| * and/or modify it under the terms of the Do What The Fuck You Want | |||
| * To Public License, Version 2, as published by Sam Hocevar. See | |||
| * http://sam.zoy.org/wtfpl/COPYING for more details. | |||
| *****************************************************************************/ | |||
| /* | |||
| * 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 <stdlib.h> | |||
| #include <stdint.h> | |||
| #include <stdbool.h> | |||
| #include <string.h> | |||
| #include <stdio.h> | |||
| #include <unistd.h> | |||
| #include <sys/select.h> | |||
| #include <errno.h> | |||
| #include <bitstream/dvb/sim.h> | |||
| #include <bitstream/mpeg/psi.h> | |||
| /* 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[MAX_TLV_SIZE]; | |||
| uint8_t *p_tlv_n; | |||
| ecmg_init(p_tlv); | |||
| tlv_set_version(p_tlv, i_version); | |||
| tlv_set_type(p_tlv, ECMG_TYPE_CHANNEL_STATUS); | |||
| /* length will be written at the end */ | |||
| tlv_set_length(p_tlv, MAX_TLV_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, p_tlv_n - p_tlv); | |||
| } | |||
| /***************************************************************************** | |||
| * send_channel_test | |||
| *****************************************************************************/ | |||
| static void send_channel_test(void) | |||
| { | |||
| uint8_t p_tlv[MAX_TLV_SIZE]; | |||
| uint8_t *p_tlv_n; | |||
| fprintf(stderr, "sending test on channel ID=0x%hx\n", i_channelid); | |||
| ecmg_init(p_tlv); | |||
| tlv_set_version(p_tlv, i_version); | |||
| tlv_set_type(p_tlv, ECMG_TYPE_CHANNEL_TEST); | |||
| /* length will be written at the end */ | |||
| tlv_set_length(p_tlv, MAX_TLV_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, p_tlv_n - p_tlv); | |||
| } | |||
| /***************************************************************************** | |||
| * send_channel_error | |||
| *****************************************************************************/ | |||
| static void send_channel_error(uint16_t i_wanted_channelid, uint16_t i_error) | |||
| { | |||
| uint8_t p_tlv[MAX_TLV_SIZE]; | |||
| uint8_t *p_tlv_n; | |||
| fprintf(stderr, "sending error on channel ID=0x%hx error=0x%hx\n", | |||
| i_wanted_channelid, i_error); | |||
| ecmg_init(p_tlv); | |||
| tlv_set_version(p_tlv, i_version); | |||
| tlv_set_type(p_tlv, ECMG_TYPE_CHANNEL_ERROR); | |||
| /* length will be written at the end */ | |||
| tlv_set_length(p_tlv, MAX_TLV_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, p_tlv_n - p_tlv); | |||
| } | |||
| /***************************************************************************** | |||
| * send_stream_status | |||
| *****************************************************************************/ | |||
| static void send_stream_status(stream_t *p_stream) | |||
| { | |||
| uint8_t p_tlv[MAX_TLV_SIZE]; | |||
| uint8_t *p_tlv_n; | |||
| ecmg_init(p_tlv); | |||
| tlv_set_version(p_tlv, i_version); | |||
| tlv_set_type(p_tlv, ECMG_TYPE_STREAM_STATUS); | |||
| /* length will be written at the end */ | |||
| tlv_set_length(p_tlv, MAX_TLV_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, p_tlv_n - p_tlv); | |||
| } | |||
| /***************************************************************************** | |||
| * send_stream_close | |||
| *****************************************************************************/ | |||
| static void send_stream_close(stream_t *p_stream) | |||
| { | |||
| uint8_t p_tlv[MAX_TLV_SIZE]; | |||
| uint8_t *p_tlv_n; | |||
| ecmg_init(p_tlv); | |||
| tlv_set_version(p_tlv, i_version); | |||
| tlv_set_type(p_tlv, ECMG_TYPE_STREAM_CLOSERESP); | |||
| /* length will be written at the end */ | |||
| tlv_set_length(p_tlv, MAX_TLV_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, p_tlv_n - p_tlv); | |||
| } | |||
| /***************************************************************************** | |||
| * send_stream_error | |||
| *****************************************************************************/ | |||
| static void send_stream_error(uint16_t i_wanted_streamid, uint16_t i_error) | |||
| { | |||
| uint8_t p_tlv[MAX_TLV_SIZE]; | |||
| uint8_t *p_tlv_n; | |||
| fprintf(stderr, "sending error on stream ID=0x%hx error=0x%hx\n", | |||
| i_wanted_streamid, i_error); | |||
| ecmg_init(p_tlv); | |||
| tlv_set_version(p_tlv, i_version); | |||
| tlv_set_type(p_tlv, ECMG_TYPE_STREAM_ERROR); | |||
| /* length will be written at the end */ | |||
| tlv_set_length(p_tlv, MAX_TLV_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, p_tlv_n - p_tlv); | |||
| } | |||
| /***************************************************************************** | |||
| * 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[MAX_TLV_SIZE + i_length]; /* this is oversized */ | |||
| uint8_t *p_tlv_n; | |||
| ecmg_init(p_tlv); | |||
| tlv_set_version(p_tlv, i_version); | |||
| tlv_set_type(p_tlv, ECMG_TYPE_ECM); | |||
| /* length will be written at the end */ | |||
| tlv_set_length(p_tlv, MAX_TLV_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, p_tlv_n - p_tlv); | |||
| } | |||
| /***************************************************************************** | |||
| * 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_version = tlv_get_version(p_tlv); | |||
| 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; | |||
| } | |||
| for (i = 0; i < i_nb_streams; i++) { | |||
| p_stream = pp_streams[i]; | |||
| if (p_stream == NULL) | |||
| break; | |||
| } | |||
| if (i == i_nb_streams) { | |||
| p_stream = malloc(sizeof(stream_t)); | |||
| 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 < <input stream> > <output stream>\n", | |||
| ppsz_argv[0]); | |||
| return EXIT_FAILURE; | |||
| } | |||
| for ( ; ; ) { | |||
| uint8_t *p_tlv = malloc(TLV_HEADER_SIZE); | |||
| 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, TLV_HEADER_SIZE) <= 0) | |||
| return EXIT_FAILURE; | |||
| p_tlv = realloc(p_tlv, TLV_HEADER_SIZE + tlv_get_length(p_tlv)); | |||
| if (read_wrapper(p_tlv + TLV_HEADER_SIZE, tlv_get_length(p_tlv)) <= 0) | |||
| return EXIT_FAILURE; | |||
| i_type = tlv_get_type(p_tlv); | |||
| if (!tlv_validate(p_tlv)) { | |||
| send_channel_error(i_channelid, 0x1); | |||
| free(p_tlv); | |||
| continue; | |||
| } | |||
| if (!ecmg_validate(p_tlv)) { | |||
| send_channel_error(i_channelid, 0xf); | |||
| free(p_tlv); | |||
| continue; | |||
| } | |||
| if (b_init && i_type != ECMG_TYPE_CHANNEL_SETUP) { | |||
| send_channel_error(i_channelid, 0x6); | |||
| free(p_tlv); | |||
| continue; | |||
| } | |||
| if (tlv_get_version(p_tlv) > 3) { | |||
| send_channel_error(i_channelid, 0x2); | |||
| free(p_tlv); | |||
| continue; | |||
| } | |||
| switch (i_type) { | |||
| case ECMG_TYPE_CHANNEL_SETUP: | |||
| 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); | |||
| } | |||
| return EXIT_SUCCESS; | |||
| } | |||
| @ -0,0 +1,587 @@ | |||
| /***************************************************************************** | |||
| * dvb_ecmg_test.c: Simulation of an SCS to stress-test an ECMG | |||
| ***************************************************************************** | |||
| * Copyright (C) 2010 VideoLAN | |||
| * $Id$ | |||
| * | |||
| * Authors: Christophe Massiot <massiot@via.ecp.fr> | |||
| * | |||
| * This program is free software. It comes without any warranty, to | |||
| * the extent permitted by applicable law. You can redistribute it | |||
| * and/or modify it under the terms of the Do What The Fuck You Want | |||
| * To Public License, Version 2, as published by Sam Hocevar. See | |||
| * http://sam.zoy.org/wtfpl/COPYING for more details. | |||
| *****************************************************************************/ | |||
| /* | |||
| * You want to use this in conjunction with netcat, for instance: | |||
| * | |||
| * mkfifo /tmp/ecmg_in | |||
| * mkfifo /tmp/ecmg_out | |||
| * nc 192.168.0.1 6900 < /tmp/ecmg_out > /tmp/ecmg_in & | |||
| * dvb_ecmg_test 10 10 > /tmp/ecmg_out < /tmp/ecmg_in | |||
| */ | |||
| #include <stdlib.h> | |||
| #include <stdint.h> | |||
| #include <stdbool.h> | |||
| #include <string.h> | |||
| #include <stdio.h> | |||
| #include <unistd.h> | |||
| #include <sys/select.h> | |||
| #include <errno.h> | |||
| #include <time.h> | |||
| #include <bitstream/dvb/sim.h> | |||
| #include <bitstream/mpeg/psi.h> | |||
| /* 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 1 /* s */ | |||
| /***************************************************************************** | |||
| * Local declarations | |||
| *****************************************************************************/ | |||
| typedef struct stream_t { | |||
| uint16_t i_streamid; | |||
| uint16_t i_ecmid; | |||
| uint16_t i_cp_number; | |||
| bool b_init; | |||
| } stream_t; | |||
| static stream_t **pp_streams = NULL; | |||
| static int i_nb_streams = 0; | |||
| static time_t i_last_time; | |||
| static bool b_init = true; | |||
| static uint8_t i_version = 1; | |||
| static uint16_t i_channelid = 0; | |||
| static uint32_t i_supercasid; | |||
| static uint8_t i_sectiontspkt; | |||
| static uint8_t i_nb_cw; | |||
| static uint8_t i_lead_cw; | |||
| static int i_wanted_streams; | |||
| static int i_period; | |||
| /***************************************************************************** | |||
| * 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 (%m)"); | |||
| 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_setup | |||
| *****************************************************************************/ | |||
| static void send_channel_setup(void) | |||
| { | |||
| uint8_t p_tlv[MAX_TLV_SIZE]; | |||
| uint8_t *p_tlv_n; | |||
| ecmg_init(p_tlv); | |||
| tlv_set_version(p_tlv, i_version); | |||
| tlv_set_type(p_tlv, ECMG_TYPE_CHANNEL_SETUP); | |||
| /* length will be written at the end */ | |||
| tlv_set_length(p_tlv, MAX_TLV_SIZE); | |||
| ecmg_append_channelid(p_tlv, i_channelid); | |||
| ecmg_append_supercasid(p_tlv, i_supercasid); | |||
| 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, p_tlv_n - p_tlv); | |||
| } | |||
| /***************************************************************************** | |||
| * send_channel_error | |||
| *****************************************************************************/ | |||
| static void send_channel_error(uint16_t i_wanted_channelid, uint16_t i_error) | |||
| { | |||
| uint8_t p_tlv[MAX_TLV_SIZE]; | |||
| uint8_t *p_tlv_n; | |||
| fprintf(stderr, "sending error on channel ID=0x%hx error=0x%hx\n", | |||
| i_wanted_channelid, i_error); | |||
| ecmg_init(p_tlv); | |||
| tlv_set_version(p_tlv, i_version); | |||
| tlv_set_type(p_tlv, ECMG_TYPE_CHANNEL_ERROR); | |||
| /* length will be written at the end */ | |||
| tlv_set_length(p_tlv, MAX_TLV_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, p_tlv_n - p_tlv); | |||
| } | |||
| /***************************************************************************** | |||
| * send_stream_setup | |||
| *****************************************************************************/ | |||
| static void send_stream_setup(stream_t *p_stream) | |||
| { | |||
| uint8_t p_tlv[MAX_TLV_SIZE]; | |||
| uint8_t *p_tlv_n; | |||
| ecmg_init(p_tlv); | |||
| tlv_set_version(p_tlv, i_version); | |||
| tlv_set_type(p_tlv, ECMG_TYPE_STREAM_SETUP); | |||
| /* length will be written at the end */ | |||
| tlv_set_length(p_tlv, MAX_TLV_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_nomcpdur(p_tlv, i_period * 10); | |||
| 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, p_tlv_n - p_tlv); | |||
| } | |||
| /***************************************************************************** | |||
| * send_stream_error | |||
| *****************************************************************************/ | |||
| static void send_stream_error(uint16_t i_wanted_streamid, uint16_t i_error) | |||
| { | |||
| uint8_t p_tlv[MAX_TLV_SIZE]; | |||
| uint8_t *p_tlv_n; | |||
| fprintf(stderr, "sending error on stream ID=0x%hx error=0x%hx\n", | |||
| i_wanted_streamid, i_error); | |||
| ecmg_init(p_tlv); | |||
| tlv_set_version(p_tlv, i_version); | |||
| tlv_set_type(p_tlv, ECMG_TYPE_STREAM_ERROR); | |||
| /* length will be written at the end */ | |||
| tlv_set_length(p_tlv, MAX_TLV_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, p_tlv_n - p_tlv); | |||
| } | |||
| /***************************************************************************** | |||
| * send_cw | |||
| *****************************************************************************/ | |||
| static void send_cw(stream_t *p_stream) | |||
| { | |||
| uint8_t p_tlv[MAX_TLV_SIZE]; | |||
| uint8_t *p_tlv_n; | |||
| int i; | |||
| ecmg_init(p_tlv); | |||
| tlv_set_version(p_tlv, i_version); | |||
| tlv_set_type(p_tlv, ECMG_TYPE_CW); | |||
| /* length will be written at the end */ | |||
| tlv_set_length(p_tlv, MAX_TLV_SIZE); | |||
| ecmg_append_channelid(p_tlv, i_channelid); | |||
| ecmg_append_streamid(p_tlv, p_stream->i_streamid); | |||
| ecmg_append_cpnumber(p_tlv, p_stream->i_cp_number); | |||
| for (i = 1; i <= i_nb_cw; i++) { | |||
| uint8_t p_cw[8 + 2]; | |||
| uint8_t *pi_cw = ecmgcw_get_cw(p_cw); | |||
| uint32_t i_rand; | |||
| ecmgcw_set_cpnum(p_cw, | |||
| p_stream->i_cp_number + i - (i_nb_cw - i_lead_cw)); | |||
| i_rand = rand(); | |||
| memcpy(pi_cw, &i_rand, 4); | |||
| i_rand = rand(); | |||
| memcpy(pi_cw + 4, &i_rand, 4); | |||
| tlv_append_data(p_tlv, ECMG_PARAM_CPCWCOMB, p_cw, 8 + 2); | |||
| } | |||
| 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, p_tlv_n - p_tlv); | |||
| } | |||
| /***************************************************************************** | |||
| * check_cw | |||
| *****************************************************************************/ | |||
| static void check_cw(void) | |||
| { | |||
| int i_turn = i_last_time % i_period; | |||
| int i; | |||
| for (i = 0; i < i_nb_streams; i++) | |||
| if (!pp_streams[i]->b_init && (i % i_period) == i_turn) | |||
| send_cw(pp_streams[i]); | |||
| } | |||
| /***************************************************************************** | |||
| * 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; | |||
| } | |||
| /***************************************************************************** | |||
| * create_stream | |||
| *****************************************************************************/ | |||
| static void create_stream(void) | |||
| { | |||
| stream_t *p_stream = malloc(sizeof(stream_t)); | |||
| pp_streams = realloc(pp_streams, ++i_nb_streams * sizeof(stream_t *)); | |||
| pp_streams[i_nb_streams - 1] = p_stream; | |||
| p_stream->i_streamid = i_nb_streams; | |||
| p_stream->i_ecmid = i_nb_streams; | |||
| p_stream->i_cp_number = UINT8_MAX; | |||
| p_stream->b_init = true; | |||
| send_stream_setup(p_stream); | |||
| fprintf(stderr, "starting new stream id=0x%hx\n", p_stream->i_streamid); | |||
| } | |||
| /***************************************************************************** | |||
| * handle_channel_status | |||
| *****************************************************************************/ | |||
| static void handle_channel_status(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); | |||
| exit(EXIT_FAILURE); | |||
| } | |||
| if (!b_init) | |||
| return; | |||
| if (ecmg_find_maxstreams(p_tlv, 0) < i_wanted_streams) { | |||
| fprintf(stderr, "not enough supported streams (%hu)\n", | |||
| ecmg_find_maxstreams(p_tlv, 0)); | |||
| exit(EXIT_FAILURE); | |||
| } | |||
| if (ecmg_find_mincpdur(p_tlv, 0) > i_period * 10) { | |||
| fprintf(stderr, "crypto-period too short (%hu)\n", | |||
| ecmg_find_mincpdur(p_tlv, 0)); | |||
| exit(EXIT_FAILURE); | |||
| } | |||
| i_sectiontspkt = ecmg_find_sectiontspkt(p_tlv, 0); | |||
| i_nb_cw = ecmg_find_cwpermsg(p_tlv, 0); | |||
| i_lead_cw = ecmg_find_leadcw(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; | |||
| } | |||
| /***************************************************************************** | |||
| * 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); | |||
| exit(EXIT_FAILURE); | |||
| } | |||
| fprintf(stderr, "channel test received\n"); | |||
| exit(EXIT_FAILURE); | |||
| } | |||
| /***************************************************************************** | |||
| * 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)); | |||
| exit(EXIT_FAILURE); | |||
| } | |||
| /***************************************************************************** | |||
| * handle_stream_status | |||
| *****************************************************************************/ | |||
| static void handle_stream_status(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); | |||
| exit(EXIT_FAILURE); | |||
| } | |||
| if ((p_stream = find_stream(i_streamid)) == NULL) { | |||
| send_stream_error(i_streamid, 0x7); | |||
| exit(EXIT_FAILURE); | |||
| } | |||
| if (i_version >= 2 && tlv_count_param(p_tlv, ECMG_PARAM_ECMID) != 1) { | |||
| send_stream_error(i_streamid, 0x10); | |||
| exit(EXIT_FAILURE); | |||
| } | |||
| p_stream->b_init = false; | |||
| } | |||
| /***************************************************************************** | |||
| * 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); | |||
| exit(EXIT_FAILURE); | |||
| } | |||
| if ((p_stream = find_stream(i_streamid)) == NULL) { | |||
| send_stream_error(i_streamid, 0x7); | |||
| exit(EXIT_FAILURE); | |||
| } | |||
| fprintf(stderr, "stream test received\n"); | |||
| exit(EXIT_FAILURE); | |||
| } | |||
| /***************************************************************************** | |||
| * 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_ecm | |||
| *****************************************************************************/ | |||
| static void handle_ecm(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; | |||
| uint8_t *p_ecm; | |||
| uint16_t i_ecm_length; | |||
| 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; | |||
| } | |||
| i_cp_number = ecmg_find_cpnumber(p_tlv, 0); | |||
| if (i_cp_number != p_stream->i_cp_number) | |||
| fprintf(stderr, "late ECM packet\n"); | |||
| if (i_sectiontspkt != 0) return; | |||
| p_ecm = tlv_find_data(p_tlv, ECMG_PARAM_ECM, 0, &i_ecm_length); | |||
| if (i_ecm_length < PSI_HEADER_SIZE) { | |||
| fprintf(stderr, "too short ECM (%hu)\n", i_ecm_length); | |||
| return; | |||
| } | |||
| if (i_ecm_length != psi_get_length(p_ecm) + PSI_HEADER_SIZE) { | |||
| fprintf(stderr, "ECM size mismatch\n"); | |||
| return; | |||
| } | |||
| if (!psi_validate(p_ecm)) | |||
| fprintf(stderr, "invalid PSI section\n"); | |||
| } | |||
| /***************************************************************************** | |||
| * Main loop | |||
| *****************************************************************************/ | |||
| int main(int i_argc, char **ppsz_argv) | |||
| { | |||
| if (i_argc != 3) { | |||
| fprintf(stderr, "usage: %s <# streams> <crypto-period (s)> < < <input stream> > <output stream>\n", | |||
| ppsz_argv[0]); | |||
| return EXIT_FAILURE; | |||
| } | |||
| i_wanted_streams = atoi(ppsz_argv[1]); | |||
| i_period = atoi(ppsz_argv[2]); | |||
| i_last_time = time(NULL); | |||
| srand(time(NULL)); | |||
| i_channelid = rand(); | |||
| i_supercasid = rand(); | |||
| send_channel_setup(); | |||
| for ( ; ; ) { | |||
| uint8_t *p_tlv = malloc(TLV_HEADER_SIZE); | |||
| 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) { | |||
| goto no_packet; | |||
| } | |||
| if (read_wrapper(p_tlv, TLV_HEADER_SIZE) <= 0) | |||
| return EXIT_FAILURE; | |||
| p_tlv = realloc(p_tlv, TLV_HEADER_SIZE + tlv_get_length(p_tlv)); | |||
| if (read_wrapper(p_tlv + TLV_HEADER_SIZE, tlv_get_length(p_tlv)) <= 0) | |||
| return EXIT_FAILURE; | |||
| i_type = tlv_get_type(p_tlv); | |||
| if (!tlv_validate(p_tlv)) { | |||
| send_channel_error(i_channelid, 0x1); | |||
| free(p_tlv); | |||
| exit(EXIT_FAILURE); | |||
| } | |||
| if (!ecmg_validate(p_tlv)) { | |||
| send_channel_error(i_channelid, 0xf); | |||
| free(p_tlv); | |||
| exit(EXIT_FAILURE); | |||
| } | |||
| if (b_init && i_type != ECMG_TYPE_CHANNEL_STATUS) { | |||
| send_channel_error(i_channelid, 0x6); | |||
| free(p_tlv); | |||
| exit(EXIT_FAILURE); | |||
| } | |||
| if (tlv_get_version(p_tlv) > 3) { | |||
| send_channel_error(i_channelid, 0x2); | |||
| free(p_tlv); | |||
| exit(EXIT_FAILURE); | |||
| } | |||
| switch (i_type) { | |||
| case ECMG_TYPE_CHANNEL_STATUS: | |||
| handle_channel_status(p_tlv); | |||
| break; | |||
| case ECMG_TYPE_CHANNEL_TEST: | |||
| handle_channel_test(p_tlv); | |||
| break; | |||
| case ECMG_TYPE_CHANNEL_ERROR: | |||
| handle_channel_error(p_tlv); | |||
| break; | |||
| case ECMG_TYPE_STREAM_STATUS: | |||
| handle_stream_status(p_tlv); | |||
| break; | |||
| case ECMG_TYPE_STREAM_TEST: | |||
| handle_stream_test(p_tlv); | |||
| break; | |||
| case ECMG_TYPE_STREAM_ERROR: | |||
| handle_stream_error(p_tlv); | |||
| break; | |||
| case ECMG_TYPE_ECM: | |||
| handle_ecm(p_tlv); | |||
| break; | |||
| default: | |||
| send_channel_error(i_channelid, 0x3); | |||
| break; | |||
| } | |||
| free(p_tlv); | |||
| no_packet: | |||
| if (!b_init) { | |||
| time_t i_time = time(NULL); | |||
| if (i_nb_streams < i_wanted_streams) | |||
| create_stream(); | |||
| while (i_last_time < i_time) { | |||
| i_last_time++; | |||
| check_cw(); | |||
| } | |||
| } | |||
| } | |||
| return EXIT_SUCCESS; | |||
| } | |||