X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Faccess%2Fdvb%2Fen50221.c;h=b2d899121367e73ea1776f5549da1e3ba6a0e6f7;hb=e3a97fd38539016117478ddeffc46f3dabffbdf8;hp=f2a65c8b15f052aa64f295c90fec1f757e474e4f;hpb=f324c82bb4fc624d99cff09088c1ebb037534e8d;p=vlc diff --git a/modules/access/dvb/en50221.c b/modules/access/dvb/en50221.c index f2a65c8b15..b2d8991213 100644 --- a/modules/access/dvb/en50221.c +++ b/modules/access/dvb/en50221.c @@ -2,7 +2,7 @@ * en50221.c : implementation of the transport, session and applications * layers of EN 50 221 ***************************************************************************** - * Copyright (C) 2004 VideoLAN + * Copyright (C) 2004-2005 the VideoLAN team * * Authors: Christophe Massiot * Based on code from libdvbci Copyright (C) 2000 Klaus Schmidinger @@ -22,8 +22,12 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. *****************************************************************************/ -#include -#include +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include #include #include @@ -35,6 +39,7 @@ #include #include #include +#include /* DVB Card Drivers */ #include @@ -42,9 +47,38 @@ #include #include +/* Include dvbpsi headers */ +#ifdef HAVE_DVBPSI_DR_H +# include +# include +# include +# include +# include +# include +# include +# include +#else +# include "dvbpsi.h" +# include "descriptor.h" +# include "tables/pat.h" +# include "tables/pmt.h" +# include "descriptors/dr.h" +# include "psi.h" +# include "demux.h" +# include "tables/sdt.h" +#endif + +#ifdef ENABLE_HTTPD +# include "vlc_httpd.h" +#endif + #include "dvb.h" +#include + #undef DEBUG_TPDU +#define HLCI_WAIT_CAM_READY 0 +#define CAM_PROG_MAX MAX_PROGRAMS static void ResourceManagerOpen( access_t * p_access, int i_session_id ); static void ApplicationInformationOpen( access_t * p_access, int i_session_id ); @@ -134,7 +168,7 @@ static uint8_t *SetLength( uint8_t *p_data, int i_length ) #define T_DATA_LAST 0xA0 #define T_DATA_MORE 0xA1 -static void Dump( vlc_bool_t b_outgoing, uint8_t *p_data, int i_size ) +static void Dump( bool b_outgoing, uint8_t *p_data, int i_size ) { #ifdef DEBUG_TPDU int i; @@ -143,6 +177,8 @@ static void Dump( vlc_bool_t b_outgoing, uint8_t *p_data, int i_size ) for ( i = 0; i < i_size && i < MAX_DUMP; i++) fprintf(stderr, "%02X ", p_data[i]); fprintf(stderr, "%s\n", i_size >= MAX_DUMP ? "..." : ""); +#else + VLC_UNUSED(b_outgoing); VLC_UNUSED(p_data); VLC_UNUSED(i_size); #endif } @@ -200,12 +236,11 @@ static int TPDUSend( access_t * p_access, uint8_t i_slot, uint8_t i_tag, default: break; } - Dump( VLC_TRUE, p_data, i_size ); + Dump( true, p_data, i_size ); if ( write( p_sys->i_ca_handle, p_data, i_size ) != i_size ) { - msg_Err( p_access, "cannot write to CAM device (%s)", - strerror(errno) ); + msg_Err( p_access, "cannot write to CAM device (%m)" ); return VLC_EGENERIC; } @@ -249,8 +284,9 @@ static int TPDURecv( access_t * p_access, uint8_t i_slot, uint8_t *pi_tag, if ( i_size < 5 ) { - msg_Err( p_access, "cannot read from CAM device (%d:%s)", i_size, - strerror(errno) ); + msg_Err( p_access, "cannot read from CAM device (%d:%m)", i_size ); + if( pi_size == NULL ) + free( p_data ); return VLC_EGENERIC; } @@ -258,6 +294,8 @@ static int TPDURecv( access_t * p_access, uint8_t i_slot, uint8_t *pi_tag, { msg_Err( p_access, "invalid read from CAM device (%d instead of %d)", p_data[1], i_tcid ); + if( pi_size == NULL ) + free( p_data ); return VLC_EGENERIC; } @@ -266,9 +304,9 @@ static int TPDURecv( access_t * p_access, uint8_t i_slot, uint8_t *pi_tag, && p_data[i_size - 4] == T_SB && p_data[i_size - 3] == 2 && (p_data[i_size - 1] & DATA_INDICATOR)) - ? VLC_TRUE : VLC_FALSE; + ? true : false; - Dump( VLC_FALSE, p_data, i_size ); + Dump( false, p_data, i_size ); if ( pi_size == NULL ) free( p_data ); @@ -389,7 +427,7 @@ static void SessionOpen( access_t * p_access, uint8_t i_slot, if ( !p_sys->p_sessions[i_session_id - 1].i_resource_id ) break; } - if ( i_session_id == MAX_SESSIONS ) + if ( i_session_id > MAX_SESSIONS ) { msg_Err( p_access, "too many sessions !" ); return; @@ -435,15 +473,108 @@ static void SessionOpen( access_t * p_access, uint8_t i_slot, switch ( i_resource_id ) { case RI_RESOURCE_MANAGER: - ResourceManagerOpen( p_access, i_session_id ); break; + ResourceManagerOpen( p_access, i_session_id ); break; + case RI_APPLICATION_INFORMATION: + ApplicationInformationOpen( p_access, i_session_id ); break; + case RI_CONDITIONAL_ACCESS_SUPPORT: + ConditionalAccessOpen( p_access, i_session_id ); break; + case RI_DATE_TIME: + DateTimeOpen( p_access, i_session_id ); break; + case RI_MMI: + MMIOpen( p_access, i_session_id ); break; + + case RI_HOST_CONTROL: + default: + msg_Err( p_access, "unknown resource id (0x%x)", i_resource_id ); + p_sys->p_sessions[i_session_id - 1].i_resource_id = 0; + } +} + +#if 0 +/* unused code for the moment - commented out to keep gcc happy */ +/***************************************************************************** + * SessionCreate + *****************************************************************************/ +static void SessionCreate( access_t * p_access, int i_slot, int i_resource_id ) +{ + access_sys_t *p_sys = p_access->p_sys; + uint8_t p_response[16]; + uint8_t i_tag; + int i_session_id; + + for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ ) + { + if ( !p_sys->p_sessions[i_session_id - 1].i_resource_id ) + break; + } + if ( i_session_id == MAX_SESSIONS ) + { + msg_Err( p_access, "too many sessions !" ); + return; + } + p_sys->p_sessions[i_session_id - 1].i_slot = i_slot; + p_sys->p_sessions[i_session_id - 1].i_resource_id = i_resource_id; + p_sys->p_sessions[i_session_id - 1].pf_close = NULL; + p_sys->p_sessions[i_session_id - 1].pf_manage = NULL; + p_sys->p_sessions[i_session_id - 1].p_sys = NULL; + + p_response[0] = ST_CREATE_SESSION; + p_response[1] = 0x6; + p_response[2] = i_resource_id >> 24; + p_response[3] = (i_resource_id >> 16) & 0xff; + p_response[4] = (i_resource_id >> 8) & 0xff; + p_response[5] = i_resource_id & 0xff; + p_response[6] = i_session_id >> 8; + p_response[7] = i_session_id & 0xff; + + if ( TPDUSend( p_access, i_slot, T_DATA_LAST, p_response, 4 ) != + VLC_SUCCESS ) + { + msg_Err( p_access, + "SessionCreate: couldn't send TPDU on slot %d", i_slot ); + return; + } + if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS ) + { + msg_Err( p_access, + "SessionCreate: couldn't recv TPDU on slot %d", i_slot ); + return; + } +} +#endif + +/***************************************************************************** + * SessionCreateResponse + *****************************************************************************/ +static void SessionCreateResponse( access_t * p_access, uint8_t i_slot, + uint8_t *p_spdu, int i_size ) +{ + access_sys_t *p_sys = p_access->p_sys; + int i_status = p_spdu[2]; + int i_resource_id = ResourceIdToInt( &p_spdu[3] ); + int i_session_id = ((int)p_spdu[7] << 8) | p_spdu[8]; + + if ( i_status != SS_OK ) + { + msg_Err( p_access, "SessionCreateResponse: failed to open session %d" + " resource=0x%x status=0x%x", i_session_id, i_resource_id, + i_status ); + p_sys->p_sessions[i_session_id - 1].i_resource_id = 0; + return; + } + + switch ( i_resource_id ) + { + case RI_RESOURCE_MANAGER: + ResourceManagerOpen( p_access, i_session_id ); break; case RI_APPLICATION_INFORMATION: - ApplicationInformationOpen( p_access, i_session_id ); break; + ApplicationInformationOpen( p_access, i_session_id ); break; case RI_CONDITIONAL_ACCESS_SUPPORT: - ConditionalAccessOpen( p_access, i_session_id ); break; + ConditionalAccessOpen( p_access, i_session_id ); break; case RI_DATE_TIME: - DateTimeOpen( p_access, i_session_id ); break; + DateTimeOpen( p_access, i_session_id ); break; case RI_MMI: - MMIOpen( p_access, i_session_id ); break; + MMIOpen( p_access, i_session_id ); break; case RI_HOST_CONTROL: default: @@ -452,6 +583,36 @@ static void SessionOpen( access_t * p_access, uint8_t i_slot, } } +/***************************************************************************** + * SessionSendClose + *****************************************************************************/ +static void SessionSendClose( access_t * p_access, int i_session_id ) +{ + access_sys_t *p_sys = p_access->p_sys; + uint8_t p_response[16]; + uint8_t i_tag; + uint8_t i_slot = p_sys->p_sessions[i_session_id - 1].i_slot; + + p_response[0] = ST_CLOSE_SESSION_REQUEST; + p_response[1] = 0x2; + p_response[2] = i_session_id >> 8; + p_response[3] = i_session_id & 0xff; + + if ( TPDUSend( p_access, i_slot, T_DATA_LAST, p_response, 4 ) != + VLC_SUCCESS ) + { + msg_Err( p_access, + "SessionSendClose: couldn't send TPDU on slot %d", i_slot ); + return; + } + if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS ) + { + msg_Err( p_access, + "SessionSendClose: couldn't recv TPDU on slot %d", i_slot ); + return; + } +} + /***************************************************************************** * SessionClose *****************************************************************************/ @@ -476,13 +637,13 @@ static void SessionClose( access_t * p_access, int i_session_id ) VLC_SUCCESS ) { msg_Err( p_access, - "SessionOpen: couldn't send TPDU on slot %d", i_slot ); + "SessionClose: couldn't send TPDU on slot %d", i_slot ); return; } if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS ) { msg_Err( p_access, - "SessionOpen: couldn't recv TPDU on slot %d", i_slot ); + "SessionClose: couldn't recv TPDU on slot %d", i_slot ); return; } } @@ -512,12 +673,39 @@ static void SPDUHandle( access_t * p_access, uint8_t i_slot, SessionOpen( p_access, i_slot, p_spdu, i_size ); break; + case ST_CREATE_SESSION_RESPONSE: + if ( i_size != 9 || p_spdu[1] != 0x7 ) + return; + SessionCreateResponse( p_access, i_slot, p_spdu, i_size ); + break; + case ST_CLOSE_SESSION_REQUEST: + if ( i_size != 4 || p_spdu[1] != 0x2 ) + return; i_session_id = ((int)p_spdu[2] << 8) | p_spdu[3]; SessionClose( p_access, i_session_id ); break; + case ST_CLOSE_SESSION_RESPONSE: + if ( i_size != 5 || p_spdu[1] != 0x3 ) + return; + i_session_id = ((int)p_spdu[3] << 8) | p_spdu[4]; + if ( p_spdu[2] ) + { + msg_Err( p_access, "closing a session which is not allocated (%d)", + i_session_id ); + } + else + { + if ( p_sys->p_sessions[i_session_id - 1].pf_close != NULL ) + p_sys->p_sessions[i_session_id - 1].pf_close( p_access, + i_session_id ); + p_sys->p_sessions[i_session_id - 1].i_resource_id = 0; + } + break; + default: + msg_Err( p_access, "unexpected tag in SPDUHandle (%x)", p_spdu[0] ); break; } } @@ -606,8 +794,10 @@ static uint8_t *APDUGetLength( uint8_t *p_apdu, int *pi_size ) static int APDUSend( access_t * p_access, int i_session_id, int i_tag, uint8_t *p_data, int i_size ) { + access_sys_t *p_sys = p_access->p_sys; uint8_t *p_apdu = malloc( i_size + 12 ); uint8_t *p = p_apdu; + ca_msg_t ca_msg; int i_ret; *p++ = (i_tag >> 16); @@ -616,12 +806,38 @@ static int APDUSend( access_t * p_access, int i_session_id, int i_tag, p = SetLength( p, i_size ); if ( i_size ) memcpy( p, p_data, i_size ); - - i_ret = SPDUSend( p_access, i_session_id, p_apdu, i_size + p - p_apdu ); + if ( p_sys->i_ca_type == CA_CI_LINK ) + { + i_ret = SPDUSend( p_access, i_session_id, p_apdu, i_size + p - p_apdu ); + } + else + { + if ( i_size + p - p_apdu > 256 ) + { + msg_Err( p_access, "CAM: apdu overflow" ); + i_ret = VLC_EGENERIC; + } + else + { + ca_msg.length = i_size + p - p_apdu; + if ( i_size == 0 ) ca_msg.length=3; + memcpy( ca_msg.msg, p_apdu, i_size + p - p_apdu ); + i_ret = ioctl(p_sys->i_ca_handle, CA_SEND_MSG, &ca_msg ); + if ( i_ret < 0 ) + { + msg_Err( p_access, "Error sending to CAM: %m" ); + i_ret = VLC_EGENERIC; + } + } + } free( p_apdu ); return i_ret; } +/* + * Resource Manager + */ + /***************************************************************************** * ResourceManagerHandle *****************************************************************************/ @@ -668,6 +884,24 @@ static void ResourceManagerOpen( access_t * p_access, int i_session_id ) APDUSend( p_access, i_session_id, AOT_PROFILE_ENQ, NULL, 0 ); } +/* + * Application Information + */ + +/***************************************************************************** + * ApplicationInformationEnterMenu + *****************************************************************************/ +static void ApplicationInformationEnterMenu( access_t * p_access, + int i_session_id ) +{ + access_sys_t *p_sys = p_access->p_sys; + int i_slot = p_sys->p_sessions[i_session_id - 1].i_slot; + + msg_Dbg( p_access, "entering MMI menus on session %d", i_session_id ); + APDUSend( p_access, i_session_id, AOT_ENTER_MENU, NULL, 0 ); + p_sys->pb_slot_mmi_expected[i_slot] = true; +} + /***************************************************************************** * ApplicationInformationHandle *****************************************************************************/ @@ -685,7 +919,7 @@ static void ApplicationInformationHandle( access_t * p_access, int i_session_id, uint8_t *d = APDUGetLength( p_apdu, &l ); if ( l < 4 ) break; - p_apdu[l + 3] = '\0'; + p_apdu[l + 4] = '\0'; i_type = *d++; i_manufacturer = ((int)d[0] << 8) | d[1]; @@ -719,196 +953,851 @@ static void ApplicationInformationOpen( access_t * p_access, int i_session_id ) APDUSend( p_access, i_session_id, AOT_APPLICATION_INFO_ENQ, NULL, 0 ); } +/* + * Conditional Access + */ + +#define MAX_CASYSTEM_IDS 16 + +typedef struct +{ + uint16_t pi_system_ids[MAX_CASYSTEM_IDS + 1]; +} system_ids_t; + +static bool CheckSystemID( system_ids_t *p_ids, uint16_t i_id ) +{ + int i = 0; + if( !p_ids ) return true; + + while ( p_ids->pi_system_ids[i] ) + { + if ( p_ids->pi_system_ids[i] == i_id ) + return true; + i++; + } + + return false; +} + /***************************************************************************** - * ConditionalAccessHandle + * CAPMTNeedsDescrambling *****************************************************************************/ -static void ConditionalAccessHandle( access_t * p_access, int i_session_id, - uint8_t *p_apdu, int i_size ) +static bool CAPMTNeedsDescrambling( dvbpsi_pmt_t *p_pmt ) { - access_sys_t *p_sys = p_access->p_sys; - int i_tag = APDUGetTag( p_apdu, i_size ); + dvbpsi_descriptor_t *p_dr; + dvbpsi_pmt_es_t *p_es; - switch ( i_tag ) + for( p_dr = p_pmt->p_first_descriptor; p_dr != NULL; p_dr = p_dr->p_next ) { - case AOT_CA_INFO: + if( p_dr->i_tag == 0x9 ) + { + return true; + } + } + + for( p_es = p_pmt->p_first_es; p_es != NULL; p_es = p_es->p_next ) { - if ( p_sys->i_nb_capmts ) + for( p_dr = p_es->p_first_descriptor; p_dr != NULL; + p_dr = p_dr->p_next ) { - int i; - msg_Dbg( p_access, "sending CAPMT on session %d", i_session_id ); - for ( i = 0; i < p_sys->i_nb_capmts; i++ ) + if( p_dr->i_tag == 0x9 ) { - int i_size; - uint8_t *p; - p = GetLength( &p_sys->pp_capmts[i][3], &i_size ); - SPDUSend( p_access, i_session_id, p_sys->pp_capmts[i], - i_size + (p - p_sys->pp_capmts[i]) ); + return true; } - - p_sys->i_ca_timeout = 100000; } - break; - } - default: - msg_Err( p_access, - "unexpected tag in ConditionalAccessHandle (0x%x)", - i_tag ); } + + return false; } /***************************************************************************** - * ConditionalAccessOpen + * CAPMTBuild *****************************************************************************/ -static void ConditionalAccessOpen( access_t * p_access, int i_session_id ) +static int GetCADSize( system_ids_t *p_ids, dvbpsi_descriptor_t *p_dr ) { - access_sys_t *p_sys = p_access->p_sys; - - msg_Dbg( p_access, "opening ConditionalAccess session (%d)", i_session_id ); + int i_cad_size = 0; - p_sys->p_sessions[i_session_id - 1].pf_handle = ConditionalAccessHandle; + while ( p_dr != NULL ) + { + if( p_dr->i_tag == 0x9 ) + { + uint16_t i_sysid = ((uint16_t)p_dr->p_data[0] << 8) + | p_dr->p_data[1]; + if ( CheckSystemID( p_ids, i_sysid ) ) + i_cad_size += p_dr->i_length + 2; + } + p_dr = p_dr->p_next; + } - APDUSend( p_access, i_session_id, AOT_CA_INFO_ENQ, NULL, 0 ); + return i_cad_size; } -typedef struct +static uint8_t *CAPMTHeader( system_ids_t *p_ids, uint8_t i_list_mgt, + uint16_t i_program_number, uint8_t i_version, + int i_size, dvbpsi_descriptor_t *p_dr, + uint8_t i_cmd ) { - int i_interval; - mtime_t i_last; -} date_time_t; + uint8_t *p_data; -/***************************************************************************** - * DateTimeSend - *****************************************************************************/ -static void DateTimeSend( access_t * p_access, int i_session_id ) -{ - access_sys_t *p_sys = p_access->p_sys; - date_time_t *p_date = - (date_time_t *)p_sys->p_sessions[i_session_id - 1].p_sys; + if ( i_size ) + p_data = malloc( 7 + i_size ); + else + p_data = malloc( 6 ); - time_t t = time(NULL); - struct tm tm_gmt; - struct tm tm_loc; + p_data[0] = i_list_mgt; + p_data[1] = i_program_number >> 8; + p_data[2] = i_program_number & 0xff; + p_data[3] = ((i_version & 0x1f) << 1) | 0x1; - if ( gmtime_r(&t, &tm_gmt) && localtime_r(&t, &tm_loc) ) + if ( i_size ) { - int Y = tm_gmt.tm_year; - int M = tm_gmt.tm_mon + 1; - int D = tm_gmt.tm_mday; - int L = (M == 1 || M == 2) ? 1 : 0; - int MJD = 14956 + D + (int)((Y - L) * 365.25) - + (int)((M + 1 + L * 12) * 30.6001); - uint8_t p_response[7]; - -#define DEC2BCD(d) (((d / 10) << 4) + (d % 10)) - - p_response[0] = htons(MJD) >> 8; - p_response[1] = htons(MJD) & 0xff; - p_response[2] = DEC2BCD(tm_gmt.tm_hour); - p_response[3] = DEC2BCD(tm_gmt.tm_min); - p_response[4] = DEC2BCD(tm_gmt.tm_sec); - p_response[5] = htons(tm_loc.tm_gmtoff / 60) >> 8; - p_response[6] = htons(tm_loc.tm_gmtoff / 60) & 0xff; + int i; - APDUSend( p_access, i_session_id, AOT_DATE_TIME, p_response, 7 ); + p_data[4] = (i_size + 1) >> 8; + p_data[5] = (i_size + 1) & 0xff; + p_data[6] = i_cmd; + i = 7; - p_date->i_last = mdate(); + while ( p_dr != NULL ) + { + if( p_dr->i_tag == 0x9 ) + { + uint16_t i_sysid = ((uint16_t)p_dr->p_data[0] << 8) + | p_dr->p_data[1]; + if ( CheckSystemID( p_ids, i_sysid ) ) + { + p_data[i] = 0x9; + p_data[i + 1] = p_dr->i_length; + memcpy( &p_data[i + 2], p_dr->p_data, p_dr->i_length ); +// p_data[i+4] &= 0x1f; + i += p_dr->i_length + 2; + } + } + p_dr = p_dr->p_next; + } + } + else + { + p_data[4] = 0; + p_data[5] = 0; } + + return p_data; } -/***************************************************************************** - * DateTimeHandle - *****************************************************************************/ -static void DateTimeHandle( access_t * p_access, int i_session_id, - uint8_t *p_apdu, int i_size ) +static uint8_t *CAPMTES( system_ids_t *p_ids, uint8_t *p_capmt, + int i_capmt_size, uint8_t i_type, uint16_t i_pid, + int i_size, dvbpsi_descriptor_t *p_dr, + uint8_t i_cmd ) { - access_sys_t *p_sys = p_access->p_sys; - date_time_t *p_date = - (date_time_t *)p_sys->p_sessions[i_session_id - 1].p_sys; + uint8_t *p_data; + int i; + + if ( i_size ) + p_data = realloc( p_capmt, i_capmt_size + 6 + i_size ); + else + p_data = realloc( p_capmt, i_capmt_size + 5 ); - int i_tag = APDUGetTag( p_apdu, i_size ); + i = i_capmt_size; - switch ( i_tag ) - { - case AOT_DATE_TIME_ENQ: + p_data[i] = i_type; + p_data[i + 1] = i_pid >> 8; + p_data[i + 2] = i_pid & 0xff; + + if ( i_size ) { - int l; - const uint8_t *d = APDUGetLength( p_apdu, &l ); + p_data[i + 3] = (i_size + 1) >> 8; + p_data[i + 4] = (i_size + 1) & 0xff; + p_data[i + 5] = i_cmd; + i += 6; - if ( l > 0 ) + while ( p_dr != NULL ) { - p_date->i_interval = *d; - msg_Dbg( p_access, "DateTimeHandle : interval set to %d", - p_date->i_interval ); + if( p_dr->i_tag == 0x9 ) + { + uint16_t i_sysid = ((uint16_t)p_dr->p_data[0] << 8) + | p_dr->p_data[1]; + if ( CheckSystemID( p_ids, i_sysid ) ) + { + p_data[i] = 0x9; + p_data[i + 1] = p_dr->i_length; + memcpy( &p_data[i + 2], p_dr->p_data, p_dr->i_length ); + i += p_dr->i_length + 2; + } + } + p_dr = p_dr->p_next; } - else - p_date->i_interval = 0; - - DateTimeSend( p_access, i_session_id ); - break; } - default: - msg_Err( p_access, "unexpected tag in DateTimeHandle (0x%x)", i_tag ); + else + { + p_data[i + 3] = 0; + p_data[i + 4] = 0; } + + return p_data; +} + +static uint8_t *CAPMTBuild( access_t * p_access, int i_session_id, + dvbpsi_pmt_t *p_pmt, uint8_t i_list_mgt, + uint8_t i_cmd, int *pi_capmt_size ) +{ + access_sys_t *p_sys = p_access->p_sys; + system_ids_t *p_ids = + (system_ids_t *)p_sys->p_sessions[i_session_id - 1].p_sys; + dvbpsi_pmt_es_t *p_es; + int i_cad_size, i_cad_program_size; + uint8_t *p_capmt; + + i_cad_size = i_cad_program_size = + GetCADSize( p_ids, p_pmt->p_first_descriptor ); + for( p_es = p_pmt->p_first_es; p_es != NULL; p_es = p_es->p_next ) + { + i_cad_size += GetCADSize( p_ids, p_es->p_first_descriptor ); + } + + if ( !i_cad_size ) + { + msg_Warn( p_access, + "no compatible scrambling system for SID %d on session %d", + p_pmt->i_program_number, i_session_id ); + *pi_capmt_size = 0; + return NULL; + } + + p_capmt = CAPMTHeader( p_ids, i_list_mgt, p_pmt->i_program_number, + p_pmt->i_version, i_cad_program_size, + p_pmt->p_first_descriptor, i_cmd ); + + if ( i_cad_program_size ) + *pi_capmt_size = 7 + i_cad_program_size; + else + *pi_capmt_size = 6; + + for( p_es = p_pmt->p_first_es; p_es != NULL; p_es = p_es->p_next ) + { + i_cad_size = GetCADSize( p_ids, p_es->p_first_descriptor ); + + if ( i_cad_size || i_cad_program_size ) + { + p_capmt = CAPMTES( p_ids, p_capmt, *pi_capmt_size, p_es->i_type, + p_es->i_pid, i_cad_size, + p_es->p_first_descriptor, i_cmd ); + if ( i_cad_size ) + *pi_capmt_size += 6 + i_cad_size; + else + *pi_capmt_size += 5; + } + } + + return p_capmt; +} + +/***************************************************************************** + * CAPMTFirst + *****************************************************************************/ +static void CAPMTFirst( access_t * p_access, int i_session_id, + dvbpsi_pmt_t *p_pmt ) +{ + uint8_t *p_capmt; + int i_capmt_size; + + msg_Dbg( p_access, "adding first CAPMT for SID %d on session %d", + p_pmt->i_program_number, i_session_id ); + + p_capmt = CAPMTBuild( p_access, i_session_id, p_pmt, + 0x3 /* only */, 0x1 /* ok_descrambling */, + &i_capmt_size ); + + if( i_capmt_size ) + { + APDUSend( p_access, i_session_id, AOT_CA_PMT, p_capmt, i_capmt_size ); + free( p_capmt ); + } +} + +/***************************************************************************** + * CAPMTAdd + *****************************************************************************/ +static void CAPMTAdd( access_t * p_access, int i_session_id, + dvbpsi_pmt_t *p_pmt ) +{ + uint8_t *p_capmt; + int i_capmt_size; + + if( p_access->p_sys->i_selected_programs >= CAM_PROG_MAX ) + { + msg_Warn( p_access, "Not adding CAPMT for SID %d, too many programs", + p_pmt->i_program_number ); + return; + } + p_access->p_sys->i_selected_programs++; + if( p_access->p_sys->i_selected_programs == 1 ) + { + CAPMTFirst( p_access, i_session_id, p_pmt ); + return; + } + + + msg_Dbg( p_access, "adding CAPMT for SID %d on session %d", + p_pmt->i_program_number, i_session_id ); + + p_capmt = CAPMTBuild( p_access, i_session_id, p_pmt, + 0x4 /* add */, 0x1 /* ok_descrambling */, + &i_capmt_size ); + + if( i_capmt_size ) + { + APDUSend( p_access, i_session_id, AOT_CA_PMT, p_capmt, i_capmt_size ); + free( p_capmt ); + } +} + +/***************************************************************************** + * CAPMTUpdate + *****************************************************************************/ +static void CAPMTUpdate( access_t * p_access, int i_session_id, + dvbpsi_pmt_t *p_pmt ) +{ + uint8_t *p_capmt; + int i_capmt_size; + + msg_Dbg( p_access, "updating CAPMT for SID %d on session %d", + p_pmt->i_program_number, i_session_id ); + + p_capmt = CAPMTBuild( p_access, i_session_id, p_pmt, + 0x5 /* update */, 0x1 /* ok_descrambling */, + &i_capmt_size ); + + if( i_capmt_size ) + { + APDUSend( p_access, i_session_id, AOT_CA_PMT, p_capmt, i_capmt_size ); + free( p_capmt ); + } +} + +/***************************************************************************** + * CAPMTDelete + *****************************************************************************/ +static void CAPMTDelete( access_t * p_access, int i_session_id, + dvbpsi_pmt_t *p_pmt ) +{ + uint8_t *p_capmt; + int i_capmt_size; + + p_access->p_sys->i_selected_programs--; + msg_Dbg( p_access, "deleting CAPMT for SID %d on session %d", + p_pmt->i_program_number, i_session_id ); + + p_capmt = CAPMTBuild( p_access, i_session_id, p_pmt, + 0x5 /* update */, 0x4 /* not selected */, + &i_capmt_size ); + + if( i_capmt_size ) + { + APDUSend( p_access, i_session_id, AOT_CA_PMT, p_capmt, i_capmt_size ); + free( p_capmt ); + } +} + +/***************************************************************************** + * ConditionalAccessHandle + *****************************************************************************/ +static void ConditionalAccessHandle( access_t * p_access, int i_session_id, + uint8_t *p_apdu, int i_size ) +{ + access_sys_t *p_sys = p_access->p_sys; + system_ids_t *p_ids = + (system_ids_t *)p_sys->p_sessions[i_session_id - 1].p_sys; + int i_tag = APDUGetTag( p_apdu, i_size ); + + switch ( i_tag ) + { + case AOT_CA_INFO: + { + int i; + int l = 0; + uint8_t *d = APDUGetLength( p_apdu, &l ); + msg_Dbg( p_access, "CA system IDs supported by the application :" ); + + for ( i = 0; i < l / 2; i++ ) + { + p_ids->pi_system_ids[i] = ((uint16_t)d[0] << 8) | d[1]; + d += 2; + msg_Dbg( p_access, "- 0x%x", p_ids->pi_system_ids[i] ); + } + p_ids->pi_system_ids[i] = 0; + + for ( i = 0; i < MAX_PROGRAMS; i++ ) + { + if ( p_sys->pp_selected_programs[i] != NULL ) + { + CAPMTAdd( p_access, i_session_id, + p_sys->pp_selected_programs[i] ); + } + } + break; + } + + default: + msg_Err( p_access, + "unexpected tag in ConditionalAccessHandle (0x%x)", + i_tag ); + } +} + +/***************************************************************************** + * ConditionalAccessClose + *****************************************************************************/ +static void ConditionalAccessClose( access_t * p_access, int i_session_id ) +{ + access_sys_t *p_sys = p_access->p_sys; + + msg_Dbg( p_access, "closing ConditionalAccess session (%d)", i_session_id ); + + free( p_sys->p_sessions[i_session_id - 1].p_sys ); +} + +/***************************************************************************** + * ConditionalAccessOpen + *****************************************************************************/ +static void ConditionalAccessOpen( access_t * p_access, int i_session_id ) +{ + access_sys_t *p_sys = p_access->p_sys; + + msg_Dbg( p_access, "opening ConditionalAccess session (%d)", i_session_id ); + + p_sys->p_sessions[i_session_id - 1].pf_handle = ConditionalAccessHandle; + p_sys->p_sessions[i_session_id - 1].pf_close = ConditionalAccessClose; + p_sys->p_sessions[i_session_id - 1].p_sys = calloc( 1, sizeof(system_ids_t) ); + + APDUSend( p_access, i_session_id, AOT_CA_INFO_ENQ, NULL, 0 ); +} + +/* + * Date Time + */ + +typedef struct +{ + int i_interval; + mtime_t i_last; +} date_time_t; + +/***************************************************************************** + * DateTimeSend + *****************************************************************************/ +static void DateTimeSend( access_t * p_access, int i_session_id ) +{ + access_sys_t *p_sys = p_access->p_sys; + date_time_t *p_date = + (date_time_t *)p_sys->p_sessions[i_session_id - 1].p_sys; + + time_t t = time(NULL); + struct tm tm_gmt; + struct tm tm_loc; + + if ( gmtime_r(&t, &tm_gmt) && localtime_r(&t, &tm_loc) ) + { + int Y = tm_gmt.tm_year; + int M = tm_gmt.tm_mon + 1; + int D = tm_gmt.tm_mday; + int L = (M == 1 || M == 2) ? 1 : 0; + int MJD = 14956 + D + (int)((Y - L) * 365.25) + + (int)((M + 1 + L * 12) * 30.6001); + uint8_t p_response[7]; + +#define DEC2BCD(d) (((d / 10) << 4) + (d % 10)) + + p_response[0] = htons(MJD) >> 8; + p_response[1] = htons(MJD) & 0xff; + p_response[2] = DEC2BCD(tm_gmt.tm_hour); + p_response[3] = DEC2BCD(tm_gmt.tm_min); + p_response[4] = DEC2BCD(tm_gmt.tm_sec); + p_response[5] = htons(tm_loc.tm_gmtoff / 60) >> 8; + p_response[6] = htons(tm_loc.tm_gmtoff / 60) & 0xff; + + APDUSend( p_access, i_session_id, AOT_DATE_TIME, p_response, 7 ); + + p_date->i_last = mdate(); + } +} + +/***************************************************************************** + * DateTimeHandle + *****************************************************************************/ +static void DateTimeHandle( access_t * p_access, int i_session_id, + uint8_t *p_apdu, int i_size ) +{ + access_sys_t *p_sys = p_access->p_sys; + date_time_t *p_date = + (date_time_t *)p_sys->p_sessions[i_session_id - 1].p_sys; + + int i_tag = APDUGetTag( p_apdu, i_size ); + + switch ( i_tag ) + { + case AOT_DATE_TIME_ENQ: + { + int l; + const uint8_t *d = APDUGetLength( p_apdu, &l ); + + if ( l > 0 ) + { + p_date->i_interval = *d; + msg_Dbg( p_access, "DateTimeHandle : interval set to %d", + p_date->i_interval ); + } + else + p_date->i_interval = 0; + + DateTimeSend( p_access, i_session_id ); + break; + } + default: + msg_Err( p_access, "unexpected tag in DateTimeHandle (0x%x)", i_tag ); + } +} + +/***************************************************************************** + * DateTimeManage + *****************************************************************************/ +static void DateTimeManage( access_t * p_access, int i_session_id ) +{ + access_sys_t *p_sys = p_access->p_sys; + date_time_t *p_date = + (date_time_t *)p_sys->p_sessions[i_session_id - 1].p_sys; + + if ( p_date->i_interval + && mdate() > p_date->i_last + (mtime_t)p_date->i_interval * 1000000 ) + { + DateTimeSend( p_access, i_session_id ); + } +} + +/***************************************************************************** + * DateTimeClose + *****************************************************************************/ +static void DateTimeClose( access_t * p_access, int i_session_id ) +{ + access_sys_t *p_sys = p_access->p_sys; + + msg_Dbg( p_access, "closing DateTime session (%d)", i_session_id ); + + free( p_sys->p_sessions[i_session_id - 1].p_sys ); +} + +/***************************************************************************** + * DateTimeOpen + *****************************************************************************/ +static void DateTimeOpen( access_t * p_access, int i_session_id ) +{ + access_sys_t *p_sys = p_access->p_sys; + + msg_Dbg( p_access, "opening DateTime session (%d)", i_session_id ); + + p_sys->p_sessions[i_session_id - 1].pf_handle = DateTimeHandle; + p_sys->p_sessions[i_session_id - 1].pf_manage = DateTimeManage; + p_sys->p_sessions[i_session_id - 1].pf_close = DateTimeClose; + p_sys->p_sessions[i_session_id - 1].p_sys = calloc( 1, sizeof(date_time_t) ); + + DateTimeSend( p_access, i_session_id ); +} + +/* + * MMI + */ + +/* Display Control Commands */ + +#define DCC_SET_MMI_MODE 0x01 +#define DCC_DISPLAY_CHARACTER_TABLE_LIST 0x02 +#define DCC_INPUT_CHARACTER_TABLE_LIST 0x03 +#define DCC_OVERLAY_GRAPHICS_CHARACTERISTICS 0x04 +#define DCC_FULL_SCREEN_GRAPHICS_CHARACTERISTICS 0x05 + +/* MMI Modes */ + +#define MM_HIGH_LEVEL 0x01 +#define MM_LOW_LEVEL_OVERLAY_GRAPHICS 0x02 +#define MM_LOW_LEVEL_FULL_SCREEN_GRAPHICS 0x03 + +/* Display Reply IDs */ + +#define DRI_MMI_MODE_ACK 0x01 +#define DRI_LIST_DISPLAY_CHARACTER_TABLES 0x02 +#define DRI_LIST_INPUT_CHARACTER_TABLES 0x03 +#define DRI_LIST_GRAPHIC_OVERLAY_CHARACTERISTICS 0x04 +#define DRI_LIST_FULL_SCREEN_GRAPHIC_CHARACTERISTICS 0x05 +#define DRI_UNKNOWN_DISPLAY_CONTROL_CMD 0xF0 +#define DRI_UNKNOWN_MMI_MODE 0xF1 +#define DRI_UNKNOWN_CHARACTER_TABLE 0xF2 + +/* Enquiry Flags */ + +#define EF_BLIND 0x01 + +/* Answer IDs */ + +#define AI_CANCEL 0x00 +#define AI_ANSWER 0x01 + +typedef struct +{ + en50221_mmi_object_t last_object; +} mmi_t; + +/***************************************************************************** + * MMISendObject + *****************************************************************************/ +static void MMISendObject( access_t *p_access, int i_session_id, + en50221_mmi_object_t *p_object ) +{ + access_sys_t *p_sys = p_access->p_sys; + int i_slot = p_sys->p_sessions[i_session_id - 1].i_slot; + uint8_t *p_data; + int i_size, i_tag; + + switch ( p_object->i_object_type ) + { + case EN50221_MMI_ANSW: + i_tag = AOT_ANSW; + i_size = 1 + strlen( p_object->u.answ.psz_answ ); + p_data = malloc( i_size ); + p_data[0] = (p_object->u.answ.b_ok == true) ? 0x1 : 0x0; + strncpy( (char *)&p_data[1], p_object->u.answ.psz_answ, i_size - 1 ); + break; + + case EN50221_MMI_MENU_ANSW: + i_tag = AOT_MENU_ANSW; + i_size = 1; + p_data = malloc( i_size ); + p_data[0] = p_object->u.menu_answ.i_choice; + break; + + default: + msg_Err( p_access, "unknown MMI object %d", p_object->i_object_type ); + return; + } + + APDUSend( p_access, i_session_id, i_tag, p_data, i_size ); + free( p_data ); + + p_sys->pb_slot_mmi_expected[i_slot] = true; +} + +/***************************************************************************** + * MMISendClose + *****************************************************************************/ +static void MMISendClose( access_t *p_access, int i_session_id ) +{ + access_sys_t *p_sys = p_access->p_sys; + int i_slot = p_sys->p_sessions[i_session_id - 1].i_slot; + + APDUSend( p_access, i_session_id, AOT_CLOSE_MMI, NULL, 0 ); + + p_sys->pb_slot_mmi_expected[i_slot] = true; +} + +/***************************************************************************** + * MMIDisplayReply + *****************************************************************************/ +static void MMIDisplayReply( access_t *p_access, int i_session_id ) +{ + uint8_t p_response[2]; + + p_response[0] = DRI_MMI_MODE_ACK; + p_response[1] = MM_HIGH_LEVEL; + + APDUSend( p_access, i_session_id, AOT_DISPLAY_REPLY, p_response, 2 ); + + msg_Dbg( p_access, "sending DisplayReply on session (%d)", i_session_id ); } /***************************************************************************** - * DateTimeManage + * MMIGetText *****************************************************************************/ -static void DateTimeManage( access_t * p_access, int i_session_id ) +static char *MMIGetText( access_t *p_access, uint8_t **pp_apdu, int *pi_size ) { - access_sys_t *p_sys = p_access->p_sys; - date_time_t *p_date = - (date_time_t *)p_sys->p_sessions[i_session_id - 1].p_sys; + int i_tag = APDUGetTag( *pp_apdu, *pi_size ); + int l; + uint8_t *d; - if ( p_date->i_interval - && mdate() > p_date->i_last + (mtime_t)p_date->i_interval * 1000000 ) + if ( i_tag != AOT_TEXT_LAST ) { - DateTimeSend( p_access, i_session_id ); + msg_Err( p_access, "unexpected text tag: %06x", i_tag ); + *pi_size = 0; + return strdup( "" ); } + + d = APDUGetLength( *pp_apdu, &l ); + + *pp_apdu += l + 4; + *pi_size -= l + 4; + + return dvbsi_to_utf8((char*)d,l); } /***************************************************************************** - * DateTimeOpen + * MMIHandleEnq *****************************************************************************/ -static void DateTimeOpen( access_t * p_access, int i_session_id ) +static void MMIHandleEnq( access_t *p_access, int i_session_id, + uint8_t *p_apdu, int i_size ) { access_sys_t *p_sys = p_access->p_sys; + mmi_t *p_mmi = (mmi_t *)p_sys->p_sessions[i_session_id - 1].p_sys; + int i_slot = p_sys->p_sessions[i_session_id - 1].i_slot; + int l; + uint8_t *d = APDUGetLength( p_apdu, &l ); + + en50221_MMIFree( &p_mmi->last_object ); + p_mmi->last_object.i_object_type = EN50221_MMI_ENQ; + p_mmi->last_object.u.enq.b_blind = (*d & 0x1) ? true : false; + d += 2; /* skip answer_text_length because it is not mandatory */ + l -= 2; + p_mmi->last_object.u.enq.psz_text = malloc( l + 1 ); + strncpy( p_mmi->last_object.u.enq.psz_text, (char *)d, l ); + p_mmi->last_object.u.enq.psz_text[l] = '\0'; + + msg_Dbg( p_access, "MMI enq: %s%s", p_mmi->last_object.u.enq.psz_text, + p_mmi->last_object.u.enq.b_blind == true ? " (blind)" : "" ); + p_sys->pb_slot_mmi_expected[i_slot] = false; + p_sys->pb_slot_mmi_undisplayed[i_slot] = true; +} - msg_Dbg( p_access, "opening DateTime session (%d)", i_session_id ); +/***************************************************************************** + * MMIHandleMenu + *****************************************************************************/ +static void MMIHandleMenu( access_t *p_access, int i_session_id, int i_tag, + uint8_t *p_apdu, int i_size ) +{ + access_sys_t *p_sys = p_access->p_sys; + mmi_t *p_mmi = (mmi_t *)p_sys->p_sessions[i_session_id - 1].p_sys; + int i_slot = p_sys->p_sessions[i_session_id - 1].i_slot; + int l; + uint8_t *d = APDUGetLength( p_apdu, &l ); + + en50221_MMIFree( &p_mmi->last_object ); + p_mmi->last_object.i_object_type = (i_tag == AOT_MENU_LAST) ? + EN50221_MMI_MENU : EN50221_MMI_LIST; + p_mmi->last_object.u.menu.i_choices = 0; + p_mmi->last_object.u.menu.ppsz_choices = NULL; + + if ( l > 0 ) + { + l--; d++; /* choice_nb */ + +#define GET_FIELD( x ) \ + if ( l > 0 ) \ + { \ + p_mmi->last_object.u.menu.psz_##x \ + = MMIGetText( p_access, &d, &l ); \ + msg_Dbg( p_access, "MMI " STRINGIFY( x ) ": %s", \ + p_mmi->last_object.u.menu.psz_##x ); \ + } - p_sys->p_sessions[i_session_id - 1].pf_handle = DateTimeHandle; - p_sys->p_sessions[i_session_id - 1].pf_manage = DateTimeManage; - p_sys->p_sessions[i_session_id - 1].p_sys = malloc(sizeof(date_time_t)); - memset( p_sys->p_sessions[i_session_id - 1].p_sys, 0, sizeof(date_time_t) ); + GET_FIELD( title ); + GET_FIELD( subtitle ); + GET_FIELD( bottom ); +#undef GET_FIELD - DateTimeSend( p_access, i_session_id ); + while ( l > 0 ) + { + char *psz_text = MMIGetText( p_access, &d, &l ); + TAB_APPEND( p_mmi->last_object.u.menu.i_choices, + p_mmi->last_object.u.menu.ppsz_choices, + psz_text ); + msg_Dbg( p_access, "MMI choice: %s", psz_text ); + } + } + p_sys->pb_slot_mmi_expected[i_slot] = false; + p_sys->pb_slot_mmi_undisplayed[i_slot] = true; } /***************************************************************************** * MMIHandle *****************************************************************************/ -static void MMIHandle( access_t * p_access, int i_session_id, - uint8_t *p_apdu, int i_size ) +static void MMIHandle( access_t *p_access, int i_session_id, + uint8_t *p_apdu, int i_size ) { int i_tag = APDUGetTag( p_apdu, i_size ); switch ( i_tag ) { + case AOT_DISPLAY_CONTROL: + { + int l; + uint8_t *d = APDUGetLength( p_apdu, &l ); + + if ( l > 0 ) + { + switch ( *d ) + { + case DCC_SET_MMI_MODE: + if ( l == 2 && d[1] == MM_HIGH_LEVEL ) + MMIDisplayReply( p_access, i_session_id ); + else + msg_Err( p_access, "unsupported MMI mode %02x", d[1] ); + break; + + default: + msg_Err( p_access, "unsupported display control command %02x", + *d ); + break; + } + } + break; + } + + case AOT_ENQ: + MMIHandleEnq( p_access, i_session_id, p_apdu, i_size ); + break; + + case AOT_LIST_LAST: + case AOT_MENU_LAST: + MMIHandleMenu( p_access, i_session_id, i_tag, p_apdu, i_size ); + break; + + case AOT_CLOSE_MMI: + SessionSendClose( p_access, i_session_id ); + break; + default: msg_Err( p_access, "unexpected tag in MMIHandle (0x%x)", i_tag ); } } +/***************************************************************************** + * MMIClose + *****************************************************************************/ +static void MMIClose( access_t *p_access, int i_session_id ) +{ + access_sys_t *p_sys = p_access->p_sys; + int i_slot = p_sys->p_sessions[i_session_id - 1].i_slot; + mmi_t *p_mmi = (mmi_t *)p_sys->p_sessions[i_session_id - 1].p_sys; + + en50221_MMIFree( &p_mmi->last_object ); + free( p_sys->p_sessions[i_session_id - 1].p_sys ); + + msg_Dbg( p_access, "closing MMI session (%d)", i_session_id ); + p_sys->pb_slot_mmi_expected[i_slot] = false; + p_sys->pb_slot_mmi_undisplayed[i_slot] = true; +} + /***************************************************************************** * MMIOpen *****************************************************************************/ -static void MMIOpen( access_t * p_access, int i_session_id ) +static void MMIOpen( access_t *p_access, int i_session_id ) { access_sys_t *p_sys = p_access->p_sys; + mmi_t *p_mmi; msg_Dbg( p_access, "opening MMI session (%d)", i_session_id ); p_sys->p_sessions[i_session_id - 1].pf_handle = MMIHandle; + p_sys->p_sessions[i_session_id - 1].pf_close = MMIClose; + p_sys->p_sessions[i_session_id - 1].p_sys = malloc(sizeof(mmi_t)); + p_mmi = (mmi_t *)p_sys->p_sessions[i_session_id - 1].p_sys; + p_mmi->last_object.i_object_type = EN50221_MMI_NONE; } @@ -941,7 +1830,7 @@ static int InitSlot( access_t * p_access, int i_slot ) if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) == VLC_SUCCESS && i_tag == T_CTC_REPLY ) { - p_sys->pb_active_slot[i_slot] = VLC_TRUE; + p_sys->pb_active_slot[i_slot] = true; break; } @@ -954,9 +1843,10 @@ static int InitSlot( access_t * p_access, int i_slot ) continue; } } + if ( p_sys->pb_active_slot[i_slot] ) { - p_sys->i_ca_timeout = 1000; + p_sys->i_ca_timeout = 100000; return VLC_SUCCESS; } @@ -968,10 +1858,107 @@ static int InitSlot( access_t * p_access, int i_slot ) * External entry points */ +/***************************************************************************** + * en50221_Init : Initialize the CAM for en50221 + *****************************************************************************/ +int en50221_Init( access_t * p_access ) +{ + access_sys_t *p_sys = p_access->p_sys; + + if( p_sys->i_ca_type & CA_CI_LINK ) + { + int i_slot; + for ( i_slot = 0; i_slot < p_sys->i_nb_slots; i_slot++ ) + { + if ( ioctl( p_sys->i_ca_handle, CA_RESET, 1 << i_slot) != 0 ) + { + msg_Err( p_access, "en50221_Init: couldn't reset slot %d", + i_slot ); + } + } + + p_sys->i_ca_timeout = 100000; + /* Wait a bit otherwise it doesn't initialize properly... */ + msleep( 1000000 ); + + return VLC_SUCCESS; + } + else + { + struct ca_slot_info info; + info.num = 0; + + /* We don't reset the CAM in that case because it's done by the + * ASIC. */ + if ( ioctl( p_sys->i_ca_handle, CA_GET_SLOT_INFO, &info ) < 0 ) + { + msg_Err( p_access, "en50221_Init: couldn't get slot info" ); + close( p_sys->i_ca_handle ); + p_sys->i_ca_handle = 0; + return VLC_EGENERIC; + } + if( info.flags == 0 ) + { + msg_Err( p_access, "en50221_Init: no CAM inserted" ); + close( p_sys->i_ca_handle ); + p_sys->i_ca_handle = 0; + return VLC_EGENERIC; + } + + /* Allocate a dummy sessions */ + p_sys->p_sessions[ 0 ].i_resource_id = RI_CONDITIONAL_ACCESS_SUPPORT; + + /* Get application info to find out which cam we are using and make + sure everything is ready to play */ + ca_msg_t ca_msg; + ca_msg.length=3; + ca_msg.msg[0] = ( AOT_APPLICATION_INFO & 0xFF0000 ) >> 16; + ca_msg.msg[1] = ( AOT_APPLICATION_INFO & 0x00FF00 ) >> 8; + ca_msg.msg[2] = ( AOT_APPLICATION_INFO & 0x0000FF ) >> 0; + memset( &ca_msg.msg[3], 0, 253 ); + APDUSend( p_access, 1, AOT_APPLICATION_INFO_ENQ, NULL, 0 ); + if ( ioctl( p_sys->i_ca_handle, CA_GET_MSG, &ca_msg ) < 0 ) + { + msg_Err( p_access, "en50221_Init: failed getting message" ); + return VLC_EGENERIC; + } + +#if HLCI_WAIT_CAM_READY + while( ca_msg.msg[8] == 0xff && ca_msg.msg[9] == 0xff ) + { + if( !vlc_object_alive (p_access) ) return VLC_EGENERIC; + msleep(1); + msg_Dbg( p_access, "CAM: please wait" ); + APDUSend( p_access, 1, AOT_APPLICATION_INFO_ENQ, NULL, 0 ); + ca_msg.length=3; + ca_msg.msg[0] = ( AOT_APPLICATION_INFO & 0xFF0000 ) >> 16; + ca_msg.msg[1] = ( AOT_APPLICATION_INFO & 0x00FF00 ) >> 8; + ca_msg.msg[2] = ( AOT_APPLICATION_INFO & 0x0000FF ) >> 0; + memset( &ca_msg.msg[3], 0, 253 ); + if ( ioctl( p_sys->i_ca_handle, CA_GET_MSG, &ca_msg ) < 0 ) + { + msg_Err( p_access, "en50221_Init: failed getting message" ); + return VLC_EGENERIC; + } + msg_Dbg( p_access, "en50221_Init: Got length: %d, tag: 0x%x", ca_msg.length, APDUGetTag( ca_msg.msg, ca_msg.length ) ); + } +#else + if( ca_msg.msg[8] == 0xff && ca_msg.msg[9] == 0xff ) + { + msg_Err( p_access, "CAM returns garbage as application info!" ); + return VLC_EGENERIC; + } +#endif + msg_Dbg( p_access, "found CAM %s using id 0x%x", &ca_msg.msg[12], + (ca_msg.msg[8]<<8)|ca_msg.msg[9] ); + return VLC_SUCCESS; + } +} + /***************************************************************************** * en50221_Poll : Poll the CAM for TPDUs *****************************************************************************/ -int E_(en50221_Poll)( access_t * p_access ) +int en50221_Poll( access_t * p_access ) { access_sys_t *p_sys = p_access->p_sys; int i_slot; @@ -980,28 +1967,65 @@ int E_(en50221_Poll)( access_t * p_access ) for ( i_slot = 0; i_slot < p_sys->i_nb_slots; i_slot++ ) { uint8_t i_tag; + ca_slot_info_t sinfo; + + sinfo.num = i_slot; + if ( ioctl( p_sys->i_ca_handle, CA_GET_SLOT_INFO, &sinfo ) != 0 ) + { + msg_Err( p_access, "en50221_Poll: couldn't get info on slot %d", + i_slot ); + continue; + } - if ( !p_sys->pb_active_slot[i_slot] ) + if ( !(sinfo.flags & CA_CI_MODULE_READY) ) { - ca_slot_info_t sinfo; - sinfo.num = i_slot; - if ( ioctl( p_sys->i_ca_handle, CA_GET_SLOT_INFO, &sinfo ) != 0 ) + if ( p_sys->pb_active_slot[i_slot] ) { - msg_Err( p_access, "en50221_Poll: couldn't get info on slot %d", + msg_Dbg( p_access, "en50221_Poll: slot %d has been removed", i_slot ); - continue; + p_sys->pb_active_slot[i_slot] = false; + p_sys->pb_slot_mmi_expected[i_slot] = false; + p_sys->pb_slot_mmi_undisplayed[i_slot] = false; + + /* Close all sessions for this slot. */ + for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; + i_session_id++ ) + { + if ( p_sys->p_sessions[i_session_id - 1].i_resource_id + && p_sys->p_sessions[i_session_id - 1].i_slot + == i_slot ) + { + if ( p_sys->p_sessions[i_session_id - 1].pf_close + != NULL ) + { + p_sys->p_sessions[i_session_id - 1].pf_close( + p_access, i_session_id ); + } + p_sys->p_sessions[i_session_id - 1].i_resource_id = 0; + } + } } - if ( sinfo.flags & CA_CI_MODULE_READY ) + continue; + } + else if ( !p_sys->pb_active_slot[i_slot] ) + { + InitSlot( p_access, i_slot ); + + if ( !p_sys->pb_active_slot[i_slot] ) { - msg_Dbg( p_access, "en50221_Poll: slot %d is active", - i_slot ); - p_sys->pb_active_slot[i_slot] = VLC_TRUE; - } - else + msg_Dbg( p_access, "en50221_Poll: resetting slot %d", i_slot ); + + if ( ioctl( p_sys->i_ca_handle, CA_RESET, 1 << i_slot) != 0 ) + { + msg_Err( p_access, "en50221_Poll: couldn't reset slot %d", + i_slot ); + } continue; + } - InitSlot( p_access, i_slot ); + msg_Dbg( p_access, "en50221_Poll: slot %d is active", + i_slot ); } if ( !p_sys->pb_tc_has_data[i_slot] ) @@ -1081,64 +2105,330 @@ int E_(en50221_Poll)( access_t * p_access ) /***************************************************************************** * en50221_SetCAPMT : *****************************************************************************/ -int E_(en50221_SetCAPMT)( access_t * p_access, uint8_t **pp_capmts, - int i_nb_capmts ) +int en50221_SetCAPMT( access_t * p_access, dvbpsi_pmt_t *p_pmt ) { access_sys_t *p_sys = p_access->p_sys; - int i_session_id; + int i, i_session_id; + bool b_update = false; + bool b_needs_descrambling = CAPMTNeedsDescrambling( p_pmt ); - for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ ) + for ( i = 0; i < MAX_PROGRAMS; i++ ) { - int i; + if ( p_sys->pp_selected_programs[i] != NULL + && p_sys->pp_selected_programs[i]->i_program_number + == p_pmt->i_program_number ) + { + b_update = true; - if ( p_sys->p_sessions[i_session_id - 1].i_resource_id - != RI_CONDITIONAL_ACCESS_SUPPORT ) - continue; + if ( !b_needs_descrambling ) + { + dvbpsi_DeletePMT( p_pmt ); + p_pmt = p_sys->pp_selected_programs[i]; + p_sys->pp_selected_programs[i] = NULL; + } + else if( p_pmt != p_sys->pp_selected_programs[i] ) + { + dvbpsi_DeletePMT( p_sys->pp_selected_programs[i] ); + p_sys->pp_selected_programs[i] = p_pmt; + } - msg_Dbg( p_access, "sending CAPMT on session %d", i_session_id ); - for ( i = 0; i < i_nb_capmts; i++ ) - { - int i_size; - uint8_t *p; - p = GetLength( &pp_capmts[i][3], &i_size ); - SPDUSend( p_access, i_session_id, pp_capmts[i], - i_size + (p - pp_capmts[i]) ); + break; } + } - p_sys->i_ca_timeout = 100000; + if ( !b_update && b_needs_descrambling ) + { + for ( i = 0; i < MAX_PROGRAMS; i++ ) + { + if ( p_sys->pp_selected_programs[i] == NULL ) + { + p_sys->pp_selected_programs[i] = p_pmt; + break; + } + } } - if ( p_sys->i_nb_capmts ) + if ( b_update || b_needs_descrambling ) { - int i; - for ( i = 0; i < p_sys->i_nb_capmts; i++ ) + for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ ) { - free( p_sys->pp_capmts[i] ); + if ( p_sys->p_sessions[i_session_id - 1].i_resource_id + == RI_CONDITIONAL_ACCESS_SUPPORT ) + { + if ( b_update && b_needs_descrambling ) + CAPMTUpdate( p_access, i_session_id, p_pmt ); + else if ( b_update ) + CAPMTDelete( p_access, i_session_id, p_pmt ); + else + CAPMTAdd( p_access, i_session_id, p_pmt ); + } } - free( p_sys->pp_capmts ); } - p_sys->pp_capmts = pp_capmts; - p_sys->i_nb_capmts = i_nb_capmts; + + if ( !b_needs_descrambling ) + { + dvbpsi_DeletePMT( p_pmt ); + } return VLC_SUCCESS; } +/***************************************************************************** + * en50221_OpenMMI : + *****************************************************************************/ +int en50221_OpenMMI( access_t * p_access, int i_slot ) +{ + access_sys_t *p_sys = p_access->p_sys; + + if( p_sys->i_ca_type & CA_CI_LINK ) + { + int i_session_id; + for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ ) + { + if ( p_sys->p_sessions[i_session_id - 1].i_resource_id == RI_MMI + && p_sys->p_sessions[i_session_id - 1].i_slot == i_slot ) + { + msg_Dbg( p_access, + "MMI menu is already opened on slot %d (session=%d)", + i_slot, i_session_id ); + return VLC_SUCCESS; + } + } + + for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ ) + { + if ( p_sys->p_sessions[i_session_id - 1].i_resource_id + == RI_APPLICATION_INFORMATION + && p_sys->p_sessions[i_session_id - 1].i_slot == i_slot ) + { + ApplicationInformationEnterMenu( p_access, i_session_id ); + return VLC_SUCCESS; + } + } + + msg_Err( p_access, "no application information on slot %d", i_slot ); + return VLC_EGENERIC; + } + else + { + msg_Err( p_access, "MMI menu not supported" ); + return VLC_EGENERIC; + } +} + +/***************************************************************************** + * en50221_CloseMMI : + *****************************************************************************/ +int en50221_CloseMMI( access_t * p_access, int i_slot ) +{ + access_sys_t *p_sys = p_access->p_sys; + + if( p_sys->i_ca_type & CA_CI_LINK ) + { + int i_session_id; + for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ ) + { + if ( p_sys->p_sessions[i_session_id - 1].i_resource_id == RI_MMI + && p_sys->p_sessions[i_session_id - 1].i_slot == i_slot ) + { + MMISendClose( p_access, i_session_id ); + return VLC_SUCCESS; + } + } + + msg_Warn( p_access, "closing a non-existing MMI session on slot %d", + i_slot ); + return VLC_EGENERIC; + } + else + { + msg_Err( p_access, "MMI menu not supported" ); + return VLC_EGENERIC; + } +} + +/***************************************************************************** + * en50221_GetMMIObject : + *****************************************************************************/ +en50221_mmi_object_t *en50221_GetMMIObject( access_t * p_access, + int i_slot ) +{ + access_sys_t *p_sys = p_access->p_sys; + int i_session_id; + + if ( p_sys->pb_slot_mmi_expected[i_slot] == true ) + return NULL; /* should not happen */ + + for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ ) + { + if ( p_sys->p_sessions[i_session_id - 1].i_resource_id == RI_MMI + && p_sys->p_sessions[i_session_id - 1].i_slot == i_slot ) + { + mmi_t *p_mmi = + (mmi_t *)p_sys->p_sessions[i_session_id - 1].p_sys; + if ( p_mmi == NULL ) + return NULL; /* should not happen */ + return &p_mmi->last_object; + } + } + + return NULL; +} + + +/***************************************************************************** + * en50221_SendMMIObject : + *****************************************************************************/ +void en50221_SendMMIObject( access_t * p_access, int i_slot, + en50221_mmi_object_t *p_object ) +{ + access_sys_t *p_sys = p_access->p_sys; + int i_session_id; + + for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ ) + { + if ( p_sys->p_sessions[i_session_id - 1].i_resource_id == RI_MMI + && p_sys->p_sessions[i_session_id - 1].i_slot == i_slot ) + { + MMISendObject( p_access, i_session_id, p_object ); + return; + } + } + + msg_Err( p_access, "SendMMIObject when no MMI session is opened !" ); +} + /***************************************************************************** * en50221_End : *****************************************************************************/ -void E_(en50221_End)( access_t * p_access ) +void en50221_End( access_t * p_access ) { access_sys_t *p_sys = p_access->p_sys; + int i_session_id, i; - if ( p_sys->i_nb_capmts ) + for ( i = 0; i < MAX_PROGRAMS; i++ ) { - int i; - for ( i = 0; i < p_sys->i_nb_capmts; i++ ) + if ( p_sys->pp_selected_programs[i] != NULL ) + { + dvbpsi_DeletePMT( p_sys->pp_selected_programs[i] ); + } + } + + for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ ) + { + if ( p_sys->p_sessions[i_session_id - 1].i_resource_id + && p_sys->p_sessions[i_session_id - 1].pf_close != NULL ) { - free( p_sys->pp_capmts[i] ); + p_sys->p_sessions[i_session_id - 1].pf_close( p_access, + i_session_id ); } - free( p_sys->pp_capmts ); } - /* TODO */ + /* Leave the CAM configured, so that it can be reused in another + * program. */ +} + +static inline void *FixUTF8( char *p ) +{ + EnsureUTF8( p ); + return p; +} + +char *dvbsi_to_utf8( char *psz_instring, size_t i_length ) +{ + const char *psz_encoding, *psz_stringstart; + char *psz_outstring, *psz_tmp; + char psz_encbuf[12]; + size_t i_in, i_out; + vlc_iconv_t iconv_handle; + if( i_length < 1 ) return NULL; + if( psz_instring[0] < 0 || psz_instring[0] >= 0x20 ) + { + psz_stringstart = psz_instring; + psz_encoding = "ISO_8859-1"; /* should be ISO6937 according to spec, but this seems to be the one used */ + } else switch( psz_instring[0] ) + { + case 0x01: + psz_stringstart = &psz_instring[1]; + psz_encoding = "ISO_8859-5"; + break; + case 0x02: + psz_stringstart = &psz_instring[1]; + psz_encoding = "ISO_8859-6"; + break; + case 0x03: + psz_stringstart = &psz_instring[1]; + psz_encoding = "ISO_8859-7"; + break; + case 0x04: + psz_stringstart = &psz_instring[1]; + psz_encoding = "ISO_8859-8"; + break; + case 0x05: + psz_stringstart = &psz_instring[1]; + psz_encoding = "ISO_8859-9"; + break; + case 0x06: + psz_stringstart = &psz_instring[1]; + psz_encoding = "ISO_8859-10"; + break; + case 0x07: + psz_stringstart = &psz_instring[1]; + psz_encoding = "ISO_8859-11"; + break; + case 0x08: + psz_stringstart = &psz_instring[1]; /*possibly reserved?*/ + psz_encoding = "ISO_8859-12"; + break; + case 0x09: + psz_stringstart = &psz_instring[1]; + psz_encoding = "ISO_8859-13"; + break; + case 0x0a: + psz_stringstart = &psz_instring[1]; + psz_encoding = "ISO_8859-14"; + break; + case 0x0b: + psz_stringstart = &psz_instring[1]; + psz_encoding = "ISO_8859-15"; + break; + case 0x10: + if( i_length < 3 || psz_instring[1] != '\0' || psz_instring[2] > 0x0f + || psz_instring[2] == 0 ) + return FixUTF8(strndup(psz_instring,i_length)); + sprintf( psz_encbuf, "ISO_8859-%d", psz_instring[2] ); + psz_stringstart = &psz_instring[3]; + psz_encoding = psz_encbuf; + break; + case 0x11: + psz_stringstart = &psz_instring[1]; + psz_encoding = "UTF-16"; + break; + case 0x12: + psz_stringstart = &psz_instring[1]; + psz_encoding = "KSC5601-1987"; + break; + case 0x13: + psz_stringstart = &psz_instring[1]; + psz_encoding = "GB2312";/*GB-2312-1980 */ + break; + case 0x14: + psz_stringstart = &psz_instring[1]; + psz_encoding = "BIG-5"; + break; + case 0x15: + return FixUTF8(strndup(&psz_instring[1],i_length-1)); + break; + default: + /* invalid */ + return FixUTF8(strndup(psz_instring,i_length)); + } + iconv_handle = vlc_iconv_open( "UTF-8", psz_encoding ); + i_in = i_length - (psz_stringstart - psz_instring ); + i_out = i_in * 6; + psz_outstring = psz_tmp = (char*)malloc( i_out + 1 ); + vlc_iconv( iconv_handle, &psz_stringstart, &i_in, &psz_tmp, &i_out ); + vlc_iconv_close( iconv_handle ); + *psz_tmp = '\0'; + return psz_outstring; }