/***************************************************************************** * en50221.c : implementation of the transport, session and applications * layers of EN 50 221 ***************************************************************************** * Copyright (C) 2004-2005 VLC authors and VideoLAN * * Authors: Christophe Massiot * Based on code from libdvbci Copyright (C) 2000 Klaus Schmidinger * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. *****************************************************************************/ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include /* DVB Card Drivers */ #include #undef ENABLE_HTTPD #ifdef ENABLE_HTTPD # include #endif #include "../demux/dvb-text.h" #include "dtv/en50221.h" #include "dtv/en50221_capmt.h" typedef struct en50221_session_t { unsigned i_slot; int i_resource_id; void (* pf_handle)( cam_t *, int, uint8_t *, int ); void (* pf_close)( cam_t *, int ); void (* pf_manage)( cam_t *, int ); void *p_sys; } en50221_session_t; #define EN50221_MMI_NONE 0 #define EN50221_MMI_ENQ 1 #define EN50221_MMI_ANSW 2 #define EN50221_MMI_MENU 3 #define EN50221_MMI_MENU_ANSW 4 #define EN50221_MMI_LIST 5 typedef struct en50221_mmi_object_t { int i_object_type; union { struct { bool b_blind; char *psz_text; } enq; struct { bool b_ok; char *psz_answ; } answ; struct { char *psz_title, *psz_subtitle, *psz_bottom; char **ppsz_choices; int i_choices; } menu; /* menu and list are the same */ struct { int i_choice; } menu_answ; } u; } mmi_t; #undef DEBUG_TPDU #define CAM_PROG_MAX MAX_PROGRAMS //#define CAPMT_WAIT 100 /* uncomment this for slow CAMs */ static void ResourceManagerOpen( cam_t *, unsigned i_session_id ); static void ApplicationInformationOpen( cam_t *, unsigned i_session_id ); static void ConditionalAccessOpen( cam_t *, unsigned i_session_id ); static void DateTimeOpen( cam_t *, unsigned i_session_id ); static void MMIOpen( cam_t *, unsigned i_session_id ); #define MAX_CI_SLOTS 16 #define MAX_SESSIONS 32 #define MAX_PROGRAMS 24 struct cam { vlc_object_t *obj; int fd; int i_ca_type; vlc_tick_t i_timeout, i_next_event; unsigned i_nb_slots; bool pb_active_slot[MAX_CI_SLOTS]; bool pb_tc_has_data[MAX_CI_SLOTS]; bool pb_slot_mmi_expected[MAX_CI_SLOTS]; bool pb_slot_mmi_undisplayed[MAX_CI_SLOTS]; en50221_session_t p_sessions[MAX_SESSIONS]; en50221_capmt_info_t *pp_selected_programs[MAX_PROGRAMS]; int i_selected_programs; }; /***************************************************************************** * Utility functions *****************************************************************************/ #define SIZE_INDICATOR 0x80 static uint8_t *GetLength( uint8_t *p_data, int *pi_length ) { *pi_length = *p_data++; if ( (*pi_length & SIZE_INDICATOR) != 0 ) { int l = *pi_length & ~SIZE_INDICATOR; *pi_length = 0; for ( int i = 0; i < l; i++ ) *pi_length = (*pi_length << 8) | *p_data++; } return p_data; } static uint8_t *SetLength( uint8_t *p_data, int i_length ) { uint8_t *p = p_data; if ( i_length < 128 ) { *p++ = i_length; } else if ( i_length < 256 ) { *p++ = SIZE_INDICATOR | 0x1; *p++ = i_length; } else if ( i_length < 65536 ) { *p++ = SIZE_INDICATOR | 0x2; *p++ = i_length >> 8; *p++ = i_length & 0xff; } else if ( i_length < 16777216 ) { *p++ = SIZE_INDICATOR | 0x3; *p++ = i_length >> 16; *p++ = (i_length >> 8) & 0xff; *p++ = i_length & 0xff; } else { *p++ = SIZE_INDICATOR | 0x4; *p++ = i_length >> 24; *p++ = (i_length >> 16) & 0xff; *p++ = (i_length >> 8) & 0xff; *p++ = i_length & 0xff; } return p; } /* * Transport layer */ #define MAX_TPDU_SIZE 4096 #define MAX_TPDU_DATA (MAX_TPDU_SIZE - 4) #define DATA_INDICATOR 0x80 #define T_SB 0x80 #define T_RCV 0x81 #define T_CREATE_TC 0x82 #define T_CTC_REPLY 0x83 #define T_DELETE_TC 0x84 #define T_DTC_REPLY 0x85 #define T_REQUEST_TC 0x86 #define T_NEW_TC 0x87 #define T_TC_ERROR 0x88 #define T_DATA_LAST 0xA0 #define T_DATA_MORE 0xA1 static void Dump( bool b_outgoing, uint8_t *p_data, int i_size ) { #ifdef DEBUG_TPDU #define MAX_DUMP 256 fprintf(stderr, "%s ", b_outgoing ? "-->" : "<--"); for ( int 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 } /***************************************************************************** * TPDUSend *****************************************************************************/ static int TPDUSend( cam_t * p_cam, uint8_t i_slot, uint8_t i_tag, const uint8_t *p_content, size_t i_length ) { uint8_t p_data[9], *p = p_data; *(p++) = i_slot; *(p++) = i_slot + 1; /* TCID */ *(p++) = i_tag; p = SetLength( p, i_length + 1 ); *(p++) = i_slot + 1; Dump( true, p_data, p - p_data ); const struct iovec iov[2] = { { .iov_base = p_data, .iov_len = p - p_data }, { .iov_base = (void *)p_content, .iov_len = i_length }, }; if ( writev( p_cam->fd, iov, 2 ) <= 0 ) { msg_Err( p_cam->obj, "cannot write to CAM device: %s", vlc_strerror_c(errno) ); return VLC_EGENERIC; } return VLC_SUCCESS; } /***************************************************************************** * TPDURecv *****************************************************************************/ #define CAM_READ_TIMEOUT 3500 // ms static int TPDURecv( cam_t *p_cam, uint8_t i_slot, uint8_t *pi_tag, uint8_t *p_data, int *pi_size ) { uint8_t i_tcid = i_slot + 1; int i_size; struct pollfd pfd[1]; pfd[0].fd = p_cam->fd; pfd[0].events = POLLIN; while( poll(pfd, 1, CAM_READ_TIMEOUT ) == -1 ) if( errno != EINTR ) { msg_Err( p_cam->obj, "poll error: %s", vlc_strerror_c(errno) ); return VLC_EGENERIC; } if ( !(pfd[0].revents & POLLIN) ) { msg_Err( p_cam->obj, "CAM device poll time-out" ); return VLC_EGENERIC; } if ( pi_size == NULL ) { p_data = xmalloc( MAX_TPDU_SIZE ); } for ( ; ; ) { i_size = read( p_cam->fd, p_data, MAX_TPDU_SIZE ); if ( i_size >= 0 || errno != EINTR ) break; } if ( i_size < 5 ) { msg_Err( p_cam->obj, "cannot read from CAM device (%d): %s", i_size, vlc_strerror_c(errno) ); if( pi_size == NULL ) free( p_data ); return VLC_EGENERIC; } if ( p_data[1] != i_tcid ) { msg_Err( p_cam->obj, "invalid read from CAM device (%d instead of %d)", p_data[1], i_tcid ); if( pi_size == NULL ) free( p_data ); return VLC_EGENERIC; } *pi_tag = p_data[2]; p_cam->pb_tc_has_data[i_slot] = (i_size >= 4 && p_data[i_size - 4] == T_SB && p_data[i_size - 3] == 2 && (p_data[i_size - 1] & DATA_INDICATOR)) ? true : false; Dump( false, p_data, i_size ); if ( pi_size == NULL ) free( p_data ); else *pi_size = i_size; return VLC_SUCCESS; } /* * Session layer */ #define ST_SESSION_NUMBER 0x90 #define ST_OPEN_SESSION_REQUEST 0x91 #define ST_OPEN_SESSION_RESPONSE 0x92 #define ST_CREATE_SESSION 0x93 #define ST_CREATE_SESSION_RESPONSE 0x94 #define ST_CLOSE_SESSION_REQUEST 0x95 #define ST_CLOSE_SESSION_RESPONSE 0x96 #define SS_OK 0x00 #define SS_NOT_ALLOCATED 0xF0 #define RI_RESOURCE_MANAGER 0x00010041 #define RI_APPLICATION_INFORMATION 0x00020041 #define RI_CONDITIONAL_ACCESS_SUPPORT 0x00030041 #define RI_HOST_CONTROL 0x00200041 #define RI_DATE_TIME 0x00240041 #define RI_MMI 0x00400041 static int ResourceIdToInt( uint8_t *p_data ) { return ((int)p_data[0] << 24) | ((int)p_data[1] << 16) | ((int)p_data[2] << 8) | p_data[3]; } /***************************************************************************** * SPDUSend *****************************************************************************/ static int SPDUSend( cam_t * p_cam, int i_session_id, uint8_t *p_data, int i_size ) { uint8_t *p_spdu = xmalloc( i_size + 4 ); uint8_t *p = p_spdu; uint8_t i_tag; uint8_t i_slot = p_cam->p_sessions[i_session_id - 1].i_slot; *p++ = ST_SESSION_NUMBER; *p++ = 0x02; *p++ = (i_session_id >> 8); *p++ = i_session_id & 0xff; memcpy( p, p_data, i_size ); i_size += 4; p = p_spdu; while ( i_size > 0 ) { if ( i_size > MAX_TPDU_DATA ) { if ( TPDUSend( p_cam, i_slot, T_DATA_MORE, p, MAX_TPDU_DATA ) != VLC_SUCCESS ) { msg_Err( p_cam->obj, "couldn't send TPDU on session %d", i_session_id ); free( p_spdu ); return VLC_EGENERIC; } p += MAX_TPDU_DATA; i_size -= MAX_TPDU_DATA; } else { if ( TPDUSend( p_cam, i_slot, T_DATA_LAST, p, i_size ) != VLC_SUCCESS ) { msg_Err( p_cam->obj, "couldn't send TPDU on session %d", i_session_id ); free( p_spdu ); return VLC_EGENERIC; } i_size = 0; } if ( TPDURecv( p_cam, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS || i_tag != T_SB ) { msg_Err( p_cam->obj, "couldn't recv TPDU on session %d", i_session_id ); free( p_spdu ); return VLC_EGENERIC; } } free( p_spdu ); return VLC_SUCCESS; } /***************************************************************************** * SessionOpen *****************************************************************************/ static void SessionOpen( cam_t * p_cam, uint8_t i_slot, uint8_t *p_spdu, int i_size ) { VLC_UNUSED( i_size ); int i_session_id; int i_resource_id = ResourceIdToInt( &p_spdu[2] ); uint8_t p_response[16]; int i_status = SS_NOT_ALLOCATED; uint8_t i_tag; for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ ) { if ( !p_cam->p_sessions[i_session_id - 1].i_resource_id ) break; } if ( i_session_id > MAX_SESSIONS ) { msg_Err( p_cam->obj, "too many sessions !" ); return; } p_cam->p_sessions[i_session_id - 1].i_slot = i_slot; p_cam->p_sessions[i_session_id - 1].i_resource_id = i_resource_id; p_cam->p_sessions[i_session_id - 1].pf_close = NULL; p_cam->p_sessions[i_session_id - 1].pf_manage = NULL; if ( i_resource_id == RI_RESOURCE_MANAGER || i_resource_id == RI_APPLICATION_INFORMATION || i_resource_id == RI_CONDITIONAL_ACCESS_SUPPORT || i_resource_id == RI_DATE_TIME || i_resource_id == RI_MMI ) { i_status = SS_OK; } p_response[0] = ST_OPEN_SESSION_RESPONSE; p_response[1] = 0x7; p_response[2] = i_status; p_response[3] = p_spdu[2]; p_response[4] = p_spdu[3]; p_response[5] = p_spdu[4]; p_response[6] = p_spdu[5]; p_response[7] = i_session_id >> 8; p_response[8] = i_session_id & 0xff; if ( TPDUSend( p_cam, i_slot, T_DATA_LAST, p_response, 9 ) != VLC_SUCCESS ) { msg_Err( p_cam->obj, "SessionOpen: couldn't send TPDU on slot %d", i_slot ); return; } if ( TPDURecv( p_cam, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS ) { msg_Err( p_cam->obj, "SessionOpen: couldn't recv TPDU on slot %d", i_slot ); return; } switch ( i_resource_id ) { case RI_RESOURCE_MANAGER: ResourceManagerOpen( p_cam, i_session_id ); break; case RI_APPLICATION_INFORMATION: ApplicationInformationOpen( p_cam, i_session_id ); break; case RI_CONDITIONAL_ACCESS_SUPPORT: ConditionalAccessOpen( p_cam, i_session_id ); break; case RI_DATE_TIME: DateTimeOpen( p_cam, i_session_id ); break; case RI_MMI: MMIOpen( p_cam, i_session_id ); break; case RI_HOST_CONTROL: default: msg_Err( p_cam->obj, "unknown resource id (0x%x)", i_resource_id ); p_cam->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( cam_t * p_cam, int i_slot, int i_resource_id ) { 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_cam->p_sessions[i_session_id - 1].i_resource_id ) break; } if ( i_session_id == MAX_SESSIONS ) { msg_Err( p_cam->obj, "too many sessions !" ); return; } p_cam->p_sessions[i_session_id - 1].i_slot = i_slot; p_cam->p_sessions[i_session_id - 1].i_resource_id = i_resource_id; p_cam->p_sessions[i_session_id - 1].pf_close = NULL; p_cam->p_sessions[i_session_id - 1].pf_manage = NULL; p_cam->p_sessions[i_session_id - 1].p_sys = NULL; p_response[0] = ST_CREATE_SESSION; p_response[1] = 0x6; SetDWBE( &p_resource[2], i_resource_id ); SetWBE( &p_response[6]. i_session_id); if ( TPDUSend( p_cam, i_slot, T_DATA_LAST, p_response, 4 ) != VLC_SUCCESS ) { msg_Err( p_cam->obj, "SessionCreate: couldn't send TPDU on slot %d", i_slot ); return; } if ( TPDURecv( p_cam, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS ) { msg_Err( p_cam->obj, "SessionCreate: couldn't recv TPDU on slot %d", i_slot ); return; } } #endif /***************************************************************************** * SessionCreateResponse *****************************************************************************/ static void SessionCreateResponse( cam_t * p_cam, uint8_t i_slot, uint8_t *p_spdu, int i_size ) { VLC_UNUSED( i_size ); VLC_UNUSED( i_slot ); 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_cam->obj, "SessionCreateResponse: failed to open session %d" " resource=0x%x status=0x%x", i_session_id, i_resource_id, i_status ); p_cam->p_sessions[i_session_id - 1].i_resource_id = 0; return; } switch ( i_resource_id ) { case RI_RESOURCE_MANAGER: ResourceManagerOpen( p_cam, i_session_id ); break; case RI_APPLICATION_INFORMATION: ApplicationInformationOpen( p_cam, i_session_id ); break; case RI_CONDITIONAL_ACCESS_SUPPORT: ConditionalAccessOpen( p_cam, i_session_id ); break; case RI_DATE_TIME: DateTimeOpen( p_cam, i_session_id ); break; case RI_MMI: MMIOpen( p_cam, i_session_id ); break; case RI_HOST_CONTROL: default: msg_Err( p_cam->obj, "unknown resource id (0x%x)", i_resource_id ); p_cam->p_sessions[i_session_id - 1].i_resource_id = 0; } } /***************************************************************************** * SessionSendClose *****************************************************************************/ static void SessionSendClose( cam_t *p_cam, int i_session_id ) { uint8_t p_response[16]; uint8_t i_tag; uint8_t i_slot = p_cam->p_sessions[i_session_id - 1].i_slot; p_response[0] = ST_CLOSE_SESSION_REQUEST; p_response[1] = 0x2; SetWBE( &p_response[2], i_session_id ); if ( TPDUSend( p_cam, i_slot, T_DATA_LAST, p_response, 4 ) != VLC_SUCCESS ) { msg_Err( p_cam->obj, "SessionSendClose: couldn't send TPDU on slot %d", i_slot ); return; } if ( TPDURecv( p_cam, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS ) { msg_Err( p_cam->obj, "SessionSendClose: couldn't recv TPDU on slot %d", i_slot ); return; } } /***************************************************************************** * SessionClose *****************************************************************************/ static void SessionClose( cam_t * p_cam, int i_session_id ) { uint8_t p_response[16]; uint8_t i_tag; uint8_t i_slot = p_cam->p_sessions[i_session_id - 1].i_slot; if ( p_cam->p_sessions[i_session_id - 1].pf_close != NULL ) p_cam->p_sessions[i_session_id - 1].pf_close( p_cam, i_session_id ); p_cam->p_sessions[i_session_id - 1].i_resource_id = 0; p_response[0] = ST_CLOSE_SESSION_RESPONSE; p_response[1] = 0x3; p_response[2] = SS_OK; p_response[3] = i_session_id >> 8; p_response[4] = i_session_id & 0xff; if ( TPDUSend( p_cam, i_slot, T_DATA_LAST, p_response, 5 ) != VLC_SUCCESS ) { msg_Err( p_cam->obj, "SessionClose: couldn't send TPDU on slot %d", i_slot ); return; } if ( TPDURecv( p_cam, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS ) { msg_Err( p_cam->obj, "SessionClose: couldn't recv TPDU on slot %d", i_slot ); return; } } /***************************************************************************** * SPDUHandle *****************************************************************************/ static void SPDUHandle( cam_t * p_cam, uint8_t i_slot, uint8_t *p_spdu, int i_size ) { int i_session_id; switch ( p_spdu[0] ) { case ST_SESSION_NUMBER: if ( i_size <= 4 ) return; i_session_id = ((int)p_spdu[2] << 8) | p_spdu[3]; p_cam->p_sessions[i_session_id - 1].pf_handle( p_cam, i_session_id, p_spdu + 4, i_size - 4 ); break; case ST_OPEN_SESSION_REQUEST: if ( i_size != 6 || p_spdu[1] != 0x4 ) return; SessionOpen( p_cam, i_slot, p_spdu, i_size ); break; case ST_CREATE_SESSION_RESPONSE: if ( i_size != 9 || p_spdu[1] != 0x7 ) return; SessionCreateResponse( p_cam, 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_cam, 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_cam->obj, "closing a session which is not allocated (%d)", i_session_id ); } else { if ( p_cam->p_sessions[i_session_id - 1].pf_close != NULL ) p_cam->p_sessions[i_session_id - 1].pf_close( p_cam, i_session_id ); p_cam->p_sessions[i_session_id - 1].i_resource_id = 0; } break; default: msg_Err( p_cam->obj, "unexpected tag in SPDUHandle (%x)", p_spdu[0] ); break; } } /* * Application layer */ #define AOT_NONE 0x000000 #define AOT_PROFILE_ENQ 0x9F8010 #define AOT_PROFILE 0x9F8011 #define AOT_PROFILE_CHANGE 0x9F8012 #define AOT_APPLICATION_INFO_ENQ 0x9F8020 #define AOT_APPLICATION_INFO 0x9F8021 #define AOT_ENTER_MENU 0x9F8022 #define AOT_CA_INFO_ENQ 0x9F8030 #define AOT_CA_INFO 0x9F8031 #define AOT_CA_PMT 0x9F8032 #define AOT_CA_PMT_REPLY 0x9F8033 #define AOT_TUNE 0x9F8400 #define AOT_REPLACE 0x9F8401 #define AOT_CLEAR_REPLACE 0x9F8402 #define AOT_ASK_RELEASE 0x9F8403 #define AOT_DATE_TIME_ENQ 0x9F8440 #define AOT_DATE_TIME 0x9F8441 #define AOT_CLOSE_MMI 0x9F8800 #define AOT_DISPLAY_CONTROL 0x9F8801 #define AOT_DISPLAY_REPLY 0x9F8802 #define AOT_TEXT_LAST 0x9F8803 #define AOT_TEXT_MORE 0x9F8804 #define AOT_KEYPAD_CONTROL 0x9F8805 #define AOT_KEYPRESS 0x9F8806 #define AOT_ENQ 0x9F8807 #define AOT_ANSW 0x9F8808 #define AOT_MENU_LAST 0x9F8809 #define AOT_MENU_MORE 0x9F880A #define AOT_MENU_ANSW 0x9F880B #define AOT_LIST_LAST 0x9F880C #define AOT_LIST_MORE 0x9F880D #define AOT_SUBTITLE_SEGMENT_LAST 0x9F880E #define AOT_SUBTITLE_SEGMENT_MORE 0x9F880F #define AOT_DISPLAY_MESSAGE 0x9F8810 #define AOT_SCENE_END_MARK 0x9F8811 #define AOT_SCENE_DONE 0x9F8812 #define AOT_SCENE_CONTROL 0x9F8813 #define AOT_SUBTITLE_DOWNLOAD_LAST 0x9F8814 #define AOT_SUBTITLE_DOWNLOAD_MORE 0x9F8815 #define AOT_FLUSH_DOWNLOAD 0x9F8816 #define AOT_DOWNLOAD_REPLY 0x9F8817 #define AOT_COMMS_CMD 0x9F8C00 #define AOT_CONNECTION_DESCRIPTOR 0x9F8C01 #define AOT_COMMS_REPLY 0x9F8C02 #define AOT_COMMS_SEND_LAST 0x9F8C03 #define AOT_COMMS_SEND_MORE 0x9F8C04 #define AOT_COMMS_RCV_LAST 0x9F8C05 #define AOT_COMMS_RCV_MORE 0x9F8C06 /***************************************************************************** * APDUGetTag *****************************************************************************/ static int APDUGetTag( const uint8_t *p_apdu, int i_size ) { if ( i_size >= 3 ) { int i, t = 0; for ( i = 0; i < 3; i++ ) t = (t << 8) | *p_apdu++; return t; } return AOT_NONE; } /***************************************************************************** * APDUGetLength *****************************************************************************/ static uint8_t *APDUGetLength( uint8_t *p_apdu, int *pi_size ) { return GetLength( &p_apdu[3], pi_size ); } /***************************************************************************** * APDUSend *****************************************************************************/ static int APDUSend( cam_t * p_cam, int i_session_id, int i_tag, uint8_t *p_data, size_t i_size ) { uint8_t *p_apdu = xmalloc( i_size + 12 ); uint8_t *p = p_apdu; ca_msg_t ca_msg; int i_ret; *p++ = (i_tag >> 16); *p++ = (i_tag >> 8) & 0xff; *p++ = i_tag & 0xff; p = SetLength( p, i_size ); if ( i_size ) memcpy( p, p_data, i_size ); if ( p_cam->i_ca_type == CA_CI_LINK ) { i_ret = SPDUSend( p_cam, i_session_id, p_apdu, i_size + p - p_apdu ); } else { if ( i_size + p - p_apdu > 256 ) { msg_Err( p_cam->obj, "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_cam->fd, CA_SEND_MSG, &ca_msg ); if ( i_ret < 0 ) { msg_Err( p_cam->obj, "Error sending to CAM: %s", vlc_strerror_c(errno) ); i_ret = VLC_EGENERIC; } } } free( p_apdu ); return i_ret; } /* * Resource Manager */ /***************************************************************************** * ResourceManagerHandle *****************************************************************************/ static void ResourceManagerHandle( cam_t * p_cam, int i_session_id, uint8_t *p_apdu, int i_size ) { int i_tag = APDUGetTag( p_apdu, i_size ); switch ( i_tag ) { case AOT_PROFILE_ENQ: { int resources[] = { htonl(RI_RESOURCE_MANAGER), htonl(RI_APPLICATION_INFORMATION), htonl(RI_CONDITIONAL_ACCESS_SUPPORT), htonl(RI_DATE_TIME), htonl(RI_MMI) }; APDUSend( p_cam, i_session_id, AOT_PROFILE, (uint8_t*)resources, sizeof(resources) ); break; } case AOT_PROFILE: APDUSend( p_cam, i_session_id, AOT_PROFILE_CHANGE, NULL, 0 ); break; default: msg_Err( p_cam->obj, "unexpected tag in ResourceManagerHandle (0x%x)", i_tag ); } } /***************************************************************************** * ResourceManagerOpen *****************************************************************************/ static void ResourceManagerOpen( cam_t * p_cam, unsigned i_session_id ) { msg_Dbg( p_cam->obj, "opening ResourceManager session (%u)", i_session_id ); p_cam->p_sessions[i_session_id - 1].pf_handle = ResourceManagerHandle; APDUSend( p_cam, i_session_id, AOT_PROFILE_ENQ, NULL, 0 ); } /* * Application Information */ #ifdef ENABLE_HTTPD /**************************************************************************** * HTTPExtractValue: Extract a GET variable from psz_request ****************************************************************************/ static const char *HTTPExtractValue( const char *psz_uri, const char *psz_name, char *psz_value, int i_value_max ) { const char *p = psz_uri; while( (p = strstr( p, psz_name )) ) { /* Verify that we are dealing with a post/get argument */ if( (p == psz_uri || *(p - 1) == '&' || *(p - 1) == '\n') && p[strlen(psz_name)] == '=' ) break; p++; } if( p ) { int i_len; p += strlen( psz_name ); if( *p == '=' ) p++; if( strchr( p, '&' ) ) { i_len = strchr( p, '&' ) - p; } else { /* for POST method */ if( strchr( p, '\n' ) ) { i_len = strchr( p, '\n' ) - p; if( i_len && *(p+i_len-1) == '\r' ) i_len--; } else { i_len = strlen( p ); } } i_len = __MIN( i_value_max - 1, i_len ); if( i_len > 0 ) { strncpy( psz_value, p, i_len ); psz_value[i_len] = '\0'; } else { strncpy( psz_value, "", i_value_max ); } p += i_len; } else { strncpy( psz_value, "", i_value_max ); } return p; } /***************************************************************************** * ApplicationInformationEnterMenu *****************************************************************************/ static void ApplicationInformationEnterMenu( cam_t * p_cam, int i_session_id ) { int i_slot = p_cam->p_sessions[i_session_id - 1].i_slot; msg_Dbg( p_cam->obj, "entering MMI menus on session %d", i_session_id ); APDUSend( p_cam, i_session_id, AOT_ENTER_MENU, NULL, 0 ); p_cam->pb_slot_mmi_expected[i_slot] = true; } #endif /***************************************************************************** * ApplicationInformationHandle *****************************************************************************/ static void ApplicationInformationHandle( cam_t * p_cam, int i_session_id, uint8_t *p_apdu, int i_size ) { VLC_UNUSED(i_session_id); int i_tag = APDUGetTag( p_apdu, i_size ); switch ( i_tag ) { case AOT_APPLICATION_INFO: { int i_type, i_manufacturer, i_code; int l = 0; uint8_t *d = APDUGetLength( p_apdu, &l ); if ( l < 4 ) break; p_apdu[l + 4] = '\0'; i_type = *d++; i_manufacturer = ((int)d[0] << 8) | d[1]; d += 2; i_code = ((int)d[0] << 8) | d[1]; d += 2; d = GetLength( d, &l ); d[l] = '\0'; msg_Info( p_cam->obj, "CAM: %s, %02X, %04X, %04X", d, i_type, i_manufacturer, i_code ); break; } default: msg_Err( p_cam->obj, "unexpected tag in ApplicationInformationHandle (0x%x)", i_tag ); } } /***************************************************************************** * ApplicationInformationOpen *****************************************************************************/ static void ApplicationInformationOpen( cam_t * p_cam, unsigned i_session_id ) { msg_Dbg( p_cam->obj, "opening ApplicationInformation session (%u)", i_session_id ); p_cam->p_sessions[i_session_id - 1].pf_handle = ApplicationInformationHandle; APDUSend( p_cam, i_session_id, AOT_APPLICATION_INFO_ENQ, NULL, 0 ); } /* * Conditional Access */ #define MAX_CASYSTEM_IDS 64 typedef struct { uint16_t pi_system_ids[MAX_CASYSTEM_IDS + 1]; } system_ids_t; static bool CheckSystemID( const system_ids_t *p_ids, uint16_t i_id ) { int i = 0; if( !p_ids ) return true; /* dummy session for high-level CI intf */ while ( p_ids->pi_system_ids[i] ) { if ( p_ids->pi_system_ids[i] == i_id ) return true; i++; } return false; } /***************************************************************************** * CAPMTNeedsDescrambling *****************************************************************************/ static bool CAPMTNeedsDescrambling( const en50221_capmt_info_t *p_info ) { if( p_info->p_program_descriptors ) return true; for( size_t i=0; ii_es_count; i++ ) { if( p_info->p_es[i].p_descriptors ) return true; } return false; } /***************************************************************************** * CAPMTBuild *****************************************************************************/ static size_t CopyDescriptors( const uint8_t *p_drdata, size_t i_drdata, const system_ids_t *p_ids, uint8_t *p_dest ) { size_t i_total = 0; while( i_drdata > 0 ) { assert( p_drdata[0] == 0x09 ); uint8_t i_dr_len = p_drdata[1]; uint16_t i_sysid = GetWBE( &p_drdata[2] ); if( CheckSystemID( p_ids, i_sysid ) ) { if( p_dest ) /* if p_dest is NULL, just count required space */ memcpy( &p_dest[i_total], p_drdata, (size_t) i_dr_len + 2 ); i_total += i_dr_len + 2; } i_drdata = i_drdata - i_dr_len - 2; p_drdata += i_dr_len + 2; } return i_total; } static size_t GetCADSize( const system_ids_t *p_ids, const uint8_t *p_drdata, size_t i_drdata ) { return CopyDescriptors( p_drdata, i_drdata, p_ids, NULL ); } static uint8_t *CAPMTHeader( const en50221_capmt_info_t *p_info, const system_ids_t *p_ids, uint8_t i_list_mgt, size_t i_size, uint8_t i_cmd ) { uint8_t *p_data; if ( i_size ) p_data = xmalloc( 7 + i_size ); else p_data = xmalloc( 6 ); p_data[0] = i_list_mgt; p_data[1] = p_info->i_program_number >> 8; p_data[2] = p_info->i_program_number & 0xff; p_data[3] = ((p_info->i_version & 0x1f) << 1) | 0x1; if ( i_size ) { p_data[4] = (i_size + 1) >> 8; p_data[5] = (i_size + 1) & 0xff; p_data[6] = i_cmd; CopyDescriptors( p_info->p_program_descriptors, p_info->i_program_descriptors, p_ids, &p_data[7] ); } else { p_data[4] = 0; p_data[5] = 0; } return p_data; } static uint8_t *CAPMTES( const en50221_capmt_es_info_t *p_es, const system_ids_t *p_ids, size_t i_capmt_size, size_t i_size, uint8_t i_cmd, uint8_t *p_capmt ) { uint8_t *p_data; if ( i_size ) p_data = xrealloc( p_capmt, i_capmt_size + 6 + i_size ); else p_data = xrealloc( p_capmt, i_capmt_size + 5 ); uint8_t *p_dest = &p_data[ i_capmt_size ]; p_dest[0] = p_es->i_stream_type; p_dest[1] = p_es->i_es_pid >> 8; p_dest[2] = p_es->i_es_pid & 0xff; if ( i_size ) { p_dest[3] = (i_size + 1) >> 8; p_dest[4] = (i_size + 1) & 0xff; p_dest[5] = i_cmd; CopyDescriptors( p_es->p_descriptors, p_es->i_descriptors, p_ids, &p_dest[6] ); } else { p_dest[3] = 0; p_dest[4] = 0; } return p_data; } static uint8_t *CAPMTBuild( cam_t * p_cam, int i_session_id, const en50221_capmt_info_t *p_info, uint8_t i_list_mgt, uint8_t i_cmd, size_t *restrict pi_capmt_size ) { system_ids_t *p_ids = (system_ids_t *)p_cam->p_sessions[i_session_id - 1].p_sys; size_t i_cad_size, i_cad_program_size; uint8_t *p_capmt; i_cad_size = i_cad_program_size = GetCADSize( p_ids, p_info->p_program_descriptors, p_info->i_program_descriptors ); for( size_t i=0; i < p_info->i_es_count; i++ ) { const en50221_capmt_es_info_t *p_es = &p_info->p_es[i]; i_cad_size += GetCADSize( p_ids, p_es->p_descriptors, p_es->i_descriptors ); } if ( !i_cad_size ) { msg_Warn( p_cam->obj, "no compatible scrambling system for SID %d on session %d", p_info->i_program_number, i_session_id ); return NULL; } p_capmt = CAPMTHeader( p_info, p_ids, i_list_mgt, i_cad_program_size, i_cmd ); if ( i_cad_program_size ) *pi_capmt_size = 7 + i_cad_program_size; else *pi_capmt_size = 6; for( size_t i=0; i < p_info->i_es_count; i++ ) { const en50221_capmt_es_info_t *p_es = &p_info->p_es[i]; i_cad_size = GetCADSize( p_ids, p_es->p_descriptors, p_es->i_descriptors ); if ( i_cad_size || i_cad_program_size ) { p_capmt = CAPMTES( p_es, p_ids, *pi_capmt_size, i_cad_size, i_cmd, p_capmt ); if ( i_cad_size ) *pi_capmt_size += 6 + i_cad_size; else *pi_capmt_size += 5; } } return p_capmt; } /***************************************************************************** * CAPMTFirst *****************************************************************************/ static void CAPMTFirst( cam_t * p_cam, int i_session_id, const en50221_capmt_info_t *p_info ) { uint8_t *p_capmt; size_t i_capmt_size; msg_Dbg( p_cam->obj, "adding first CAPMT for SID %d on session %d", p_info->i_program_number, i_session_id ); p_capmt = CAPMTBuild( p_cam, i_session_id, p_info, 0x3 /* only */, 0x1 /* ok_descrambling */, &i_capmt_size ); if( p_capmt != NULL ) { APDUSend( p_cam, i_session_id, AOT_CA_PMT, p_capmt, i_capmt_size ); free( p_capmt ); } } /***************************************************************************** * CAPMTAdd *****************************************************************************/ static void CAPMTAdd( cam_t * p_cam, int i_session_id, const en50221_capmt_info_t *p_info ) { uint8_t *p_capmt; size_t i_capmt_size; if( p_cam->i_selected_programs >= CAM_PROG_MAX ) { msg_Warn( p_cam->obj, "Not adding CAPMT for SID %d, too many programs", p_info->i_program_number ); return; } p_cam->i_selected_programs++; if( p_cam->i_selected_programs == 1 ) { CAPMTFirst( p_cam, i_session_id, p_info ); return; } #ifdef CAPMT_WAIT msleep( CAPMT_WAIT * 1000 ); #endif msg_Dbg( p_cam->obj, "adding CAPMT for SID %d on session %d", p_info->i_program_number, i_session_id ); p_capmt = CAPMTBuild( p_cam, i_session_id, p_info, 0x4 /* add */, 0x1 /* ok_descrambling */, &i_capmt_size ); if( p_capmt != NULL ) { APDUSend( p_cam, i_session_id, AOT_CA_PMT, p_capmt, i_capmt_size ); free( p_capmt ); } } /***************************************************************************** * CAPMTUpdate *****************************************************************************/ static void CAPMTUpdate( cam_t * p_cam, int i_session_id, const en50221_capmt_info_t *p_info ) { uint8_t *p_capmt; size_t i_capmt_size; msg_Dbg( p_cam->obj, "updating CAPMT for SID %d on session %d", p_info->i_program_number, i_session_id ); p_capmt = CAPMTBuild( p_cam, i_session_id, p_info, 0x5 /* update */, 0x1 /* ok_descrambling */, &i_capmt_size ); if( p_capmt != NULL ) { APDUSend( p_cam, i_session_id, AOT_CA_PMT, p_capmt, i_capmt_size ); free( p_capmt ); } } /***************************************************************************** * CAPMTDelete *****************************************************************************/ static void CAPMTDelete( cam_t * p_cam, int i_session_id, const en50221_capmt_info_t *p_info ) { uint8_t *p_capmt; size_t i_capmt_size; p_cam->i_selected_programs--; msg_Dbg( p_cam->obj, "deleting CAPMT for SID %d on session %d", p_info->i_program_number, i_session_id ); p_capmt = CAPMTBuild( p_cam, i_session_id, p_info, 0x5 /* update */, 0x4 /* not selected */, &i_capmt_size ); if( p_capmt != NULL ) { APDUSend( p_cam, i_session_id, AOT_CA_PMT, p_capmt, i_capmt_size ); free( p_capmt ); } } /***************************************************************************** * ConditionalAccessHandle *****************************************************************************/ static void ConditionalAccessHandle( cam_t * p_cam, int i_session_id, uint8_t *p_apdu, int i_size ) { system_ids_t *p_ids = (system_ids_t *)p_cam->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_cam->obj, "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_cam->obj, "- 0x%x", p_ids->pi_system_ids[i] ); } p_ids->pi_system_ids[i] = 0; for ( i = 0; i < MAX_PROGRAMS; i++ ) { if ( p_cam->pp_selected_programs[i] != NULL ) { CAPMTAdd( p_cam, i_session_id, p_cam->pp_selected_programs[i] ); } } break; } default: msg_Err( p_cam->obj, "unexpected tag in ConditionalAccessHandle (0x%x)", i_tag ); } } /***************************************************************************** * ConditionalAccessClose *****************************************************************************/ static void ConditionalAccessClose( cam_t * p_cam, int i_session_id ) { msg_Dbg( p_cam->obj, "closing ConditionalAccess session (%d)", i_session_id ); free( p_cam->p_sessions[i_session_id - 1].p_sys ); } /***************************************************************************** * ConditionalAccessOpen *****************************************************************************/ static void ConditionalAccessOpen( cam_t * p_cam, unsigned i_session_id ) { msg_Dbg( p_cam->obj, "opening ConditionalAccess session (%u)", i_session_id ); p_cam->p_sessions[i_session_id - 1].pf_handle = ConditionalAccessHandle; p_cam->p_sessions[i_session_id - 1].pf_close = ConditionalAccessClose; p_cam->p_sessions[i_session_id - 1].p_sys = calloc( 1, sizeof(system_ids_t) ); APDUSend( p_cam, i_session_id, AOT_CA_INFO_ENQ, NULL, 0 ); } /* * Date Time */ typedef struct { int i_interval; vlc_tick_t i_last; } date_time_t; /***************************************************************************** * DateTimeSend *****************************************************************************/ static void DateTimeSend( cam_t * p_cam, int i_session_id ) { date_time_t *p_date = (date_time_t *)p_cam->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)) SetWBE( &p_response[0], MJD ); p_response[2] = DEC2BCD(tm_gmt.tm_hour); p_response[3] = DEC2BCD(tm_gmt.tm_min); p_response[4] = DEC2BCD(tm_gmt.tm_sec); SetWBE( &p_response[5], tm_loc.tm_gmtoff / 60 ); APDUSend( p_cam, i_session_id, AOT_DATE_TIME, p_response, 7 ); p_date->i_last = mdate(); } } /***************************************************************************** * DateTimeHandle *****************************************************************************/ static void DateTimeHandle( cam_t *p_cam, int i_session_id, uint8_t *p_apdu, int i_size ) { date_time_t *p_date = (date_time_t *)p_cam->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_cam->obj, "DateTimeHandle : interval set to %d", p_date->i_interval ); } else p_date->i_interval = 0; DateTimeSend( p_cam, i_session_id ); break; } default: msg_Err( p_cam->obj, "unexpected tag in DateTimeHandle (0x%x)", i_tag ); } } /***************************************************************************** * DateTimeManage *****************************************************************************/ static void DateTimeManage( cam_t * p_cam, int i_session_id ) { date_time_t *p_date = (date_time_t *)p_cam->p_sessions[i_session_id - 1].p_sys; if ( p_date->i_interval && mdate() > p_date->i_last + (vlc_tick_t)p_date->i_interval * 1000000 ) { DateTimeSend( p_cam, i_session_id ); } } /***************************************************************************** * DateTimeClose *****************************************************************************/ static void DateTimeClose( cam_t * p_cam, int i_session_id ) { msg_Dbg( p_cam->obj, "closing DateTime session (%d)", i_session_id ); free( p_cam->p_sessions[i_session_id - 1].p_sys ); } /***************************************************************************** * DateTimeOpen *****************************************************************************/ static void DateTimeOpen( cam_t * p_cam, unsigned i_session_id ) { msg_Dbg( p_cam->obj, "opening DateTime session (%u)", i_session_id ); p_cam->p_sessions[i_session_id - 1].pf_handle = DateTimeHandle; p_cam->p_sessions[i_session_id - 1].pf_manage = DateTimeManage; p_cam->p_sessions[i_session_id - 1].pf_close = DateTimeClose; p_cam->p_sessions[i_session_id - 1].p_sys = calloc( 1, sizeof(date_time_t) ); DateTimeSend( p_cam, 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 static void MMIFree( mmi_t *p_object ) { switch ( p_object->i_object_type ) { case EN50221_MMI_ENQ: FREENULL( p_object->u.enq.psz_text ); break; case EN50221_MMI_ANSW: if ( p_object->u.answ.b_ok ) { FREENULL( p_object->u.answ.psz_answ ); } break; case EN50221_MMI_MENU: case EN50221_MMI_LIST: FREENULL( p_object->u.menu.psz_title ); FREENULL( p_object->u.menu.psz_subtitle ); FREENULL( p_object->u.menu.psz_bottom ); for ( int i = 0; i < p_object->u.menu.i_choices; i++ ) { free( p_object->u.menu.ppsz_choices[i] ); } FREENULL( p_object->u.menu.ppsz_choices ); break; default: break; } } #ifdef ENABLE_HTTPD /***************************************************************************** * MMISendObject *****************************************************************************/ static void MMISendObject( cam_t *p_cam, int i_session_id, mmi_t *p_object ) { int i_slot = p_cam->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 = xmalloc( i_size ); p_data[0] = p_object->u.answ.b_ok ? 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 = xmalloc( i_size ); p_data[0] = p_object->u.menu_answ.i_choice; break; default: msg_Err( p_cam->obj, "unknown MMI object %d", p_object->i_object_type ); return; } APDUSend( p_cam, i_session_id, i_tag, p_data, i_size ); free( p_data ); p_cam->pb_slot_mmi_expected[i_slot] = true; } /***************************************************************************** * MMISendClose *****************************************************************************/ static void MMISendClose( cam_t *p_cam, int i_session_id ) { int i_slot = p_cam->p_sessions[i_session_id - 1].i_slot; APDUSend( p_cam, i_session_id, AOT_CLOSE_MMI, NULL, 0 ); p_cam->pb_slot_mmi_expected[i_slot] = true; } #endif /***************************************************************************** * MMIDisplayReply *****************************************************************************/ static void MMIDisplayReply( cam_t *p_cam, int i_session_id ) { uint8_t p_response[2]; p_response[0] = DRI_MMI_MODE_ACK; p_response[1] = MM_HIGH_LEVEL; APDUSend( p_cam, i_session_id, AOT_DISPLAY_REPLY, p_response, 2 ); msg_Dbg( p_cam->obj, "sending DisplayReply on session (%d)", i_session_id ); } /***************************************************************************** * MMIGetText *****************************************************************************/ static char *MMIGetText( cam_t *p_cam, uint8_t **pp_apdu, int *pi_size ) { int i_tag = APDUGetTag( *pp_apdu, *pi_size ); int l; uint8_t *d; if ( i_tag != AOT_TEXT_LAST ) { msg_Err( p_cam->obj, "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 vlc_from_EIT(d,l); } /***************************************************************************** * MMIHandleEnq *****************************************************************************/ static void MMIHandleEnq( cam_t *p_cam, int i_session_id, uint8_t *p_apdu, int i_size ) { VLC_UNUSED( i_size ); mmi_t *p_mmi = (mmi_t *)p_cam->p_sessions[i_session_id - 1].p_sys; int i_slot = p_cam->p_sessions[i_session_id - 1].i_slot; int l; uint8_t *d = APDUGetLength( p_apdu, &l ); MMIFree( p_mmi ); p_mmi->i_object_type = EN50221_MMI_ENQ; p_mmi->u.enq.b_blind = (*d & 0x1) ? true : false; d += 2; /* skip answer_text_length because it is not mandatory */ l -= 2; p_mmi->u.enq.psz_text = xmalloc( l + 1 ); strncpy( p_mmi->u.enq.psz_text, (char *)d, l ); p_mmi->u.enq.psz_text[l] = '\0'; msg_Dbg( p_cam->obj, "MMI enq: %s%s", p_mmi->u.enq.psz_text, p_mmi->u.enq.b_blind ? " (blind)" : "" ); p_cam->pb_slot_mmi_expected[i_slot] = false; p_cam->pb_slot_mmi_undisplayed[i_slot] = true; } /***************************************************************************** * MMIHandleMenu *****************************************************************************/ static void MMIHandleMenu( cam_t *p_cam, int i_session_id, int i_tag, uint8_t *p_apdu, int i_size ) { VLC_UNUSED(i_size); mmi_t *p_mmi = (mmi_t *)p_cam->p_sessions[i_session_id - 1].p_sys; int i_slot = p_cam->p_sessions[i_session_id - 1].i_slot; int l; uint8_t *d = APDUGetLength( p_apdu, &l ); MMIFree( p_mmi ); p_mmi->i_object_type = (i_tag == AOT_MENU_LAST) ? EN50221_MMI_MENU : EN50221_MMI_LIST; p_mmi->u.menu.i_choices = 0; p_mmi->u.menu.ppsz_choices = NULL; if ( l > 0 ) { l--; d++; /* choice_nb */ #define GET_FIELD( x ) \ if ( l > 0 ) \ { \ p_mmi->u.menu.psz_##x = MMIGetText( p_cam, &d, &l ); \ msg_Dbg( p_cam->obj, "MMI " STRINGIFY( x ) ": %s", \ p_mmi->u.menu.psz_##x ); \ } GET_FIELD( title ); GET_FIELD( subtitle ); GET_FIELD( bottom ); #undef GET_FIELD while ( l > 0 ) { char *psz_text = MMIGetText( p_cam, &d, &l ); TAB_APPEND( p_mmi->u.menu.i_choices, p_mmi->u.menu.ppsz_choices, psz_text ); msg_Dbg( p_cam->obj, "MMI choice: %s", psz_text ); } } p_cam->pb_slot_mmi_expected[i_slot] = false; p_cam->pb_slot_mmi_undisplayed[i_slot] = true; } /***************************************************************************** * MMIHandle *****************************************************************************/ static void MMIHandle( cam_t *p_cam, 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_cam, i_session_id ); else msg_Err( p_cam->obj, "unsupported MMI mode %02x", d[1] ); break; default: msg_Err( p_cam->obj, "unsupported display control command %02x", *d ); break; } } break; } case AOT_ENQ: MMIHandleEnq( p_cam, i_session_id, p_apdu, i_size ); break; case AOT_LIST_LAST: case AOT_MENU_LAST: MMIHandleMenu( p_cam, i_session_id, i_tag, p_apdu, i_size ); break; case AOT_CLOSE_MMI: SessionSendClose( p_cam, i_session_id ); break; default: msg_Err( p_cam->obj, "unexpected tag in MMIHandle (0x%x)", i_tag ); } } /***************************************************************************** * MMIClose *****************************************************************************/ static void MMIClose( cam_t *p_cam, int i_session_id ) { int i_slot = p_cam->p_sessions[i_session_id - 1].i_slot; mmi_t *p_mmi = (mmi_t *)p_cam->p_sessions[i_session_id - 1].p_sys; MMIFree( p_mmi ); free( p_cam->p_sessions[i_session_id - 1].p_sys ); msg_Dbg( p_cam->obj, "closing MMI session (%d)", i_session_id ); p_cam->pb_slot_mmi_expected[i_slot] = false; p_cam->pb_slot_mmi_undisplayed[i_slot] = true; } /***************************************************************************** * MMIOpen *****************************************************************************/ static void MMIOpen( cam_t *p_cam, unsigned i_session_id ) { mmi_t *p_mmi; msg_Dbg( p_cam->obj, "opening MMI session (%u)", i_session_id ); p_cam->p_sessions[i_session_id - 1].pf_handle = MMIHandle; p_cam->p_sessions[i_session_id - 1].pf_close = MMIClose; p_cam->p_sessions[i_session_id - 1].p_sys = xmalloc(sizeof(mmi_t)); p_mmi = (mmi_t *)p_cam->p_sessions[i_session_id - 1].p_sys; p_mmi->i_object_type = EN50221_MMI_NONE; } /* * Hardware handling */ /***************************************************************************** * InitSlot: Open the transport layer *****************************************************************************/ #define MAX_TC_RETRIES 20 static int InitSlot( cam_t * p_cam, int i_slot ) { if ( TPDUSend( p_cam, i_slot, T_CREATE_TC, NULL, 0 ) != VLC_SUCCESS ) { msg_Err( p_cam->obj, "en50221_Init: couldn't send TPDU on slot %d", i_slot ); return VLC_EGENERIC; } /* This is out of the spec */ for ( int i = 0; i < MAX_TC_RETRIES; i++ ) { uint8_t i_tag; if ( TPDURecv( p_cam, i_slot, &i_tag, NULL, NULL ) == VLC_SUCCESS && i_tag == T_CTC_REPLY ) { p_cam->pb_active_slot[i_slot] = true; break; } if ( TPDUSend( p_cam, i_slot, T_CREATE_TC, NULL, 0 ) != VLC_SUCCESS ) { msg_Err( p_cam->obj, "en50221_Init: couldn't send TPDU on slot %d", i_slot ); continue; } } if ( p_cam->pb_active_slot[i_slot] ) { p_cam->i_timeout = CLOCK_FREQ / 10; return VLC_SUCCESS; } return VLC_EGENERIC; } /* * External entry points */ /***************************************************************************** * en50221_Init : Initialize the CAM for en50221 *****************************************************************************/ cam_t *en50221_Init( vlc_object_t *obj, int fd ) { ca_caps_t caps; memset( &caps, 0, sizeof( caps )); if( ioctl( fd, CA_GET_CAP, &caps ) < 0 ) { msg_Err( obj, "CAMInit: ioctl() error getting CAM capabilities" ); return NULL; } /* Output CA capabilities */ msg_Dbg( obj, "CA interface with %d slot(s)", caps.slot_num ); if( caps.slot_type & CA_CI ) msg_Dbg( obj, " CI high level interface type" ); if( caps.slot_type & CA_CI_LINK ) msg_Dbg( obj, " CI link layer level interface type" ); if( caps.slot_type & CA_CI_PHYS ) msg_Dbg( obj, " CI physical layer level interface type (not supported) " ); if( caps.slot_type & CA_DESCR ) msg_Dbg( obj, " built-in descrambler detected" ); if( caps.slot_type & CA_SC ) msg_Dbg( obj, " simple smart card interface" ); msg_Dbg( obj, "%d available descrambler(s) (keys)", caps.descr_num ); if( caps.descr_type & CA_ECD ) msg_Dbg( obj, " ECD scrambling system supported" ); if( caps.descr_type & CA_NDS ) msg_Dbg( obj, " NDS scrambling system supported" ); if( caps.descr_type & CA_DSS ) msg_Dbg( obj, " DSS scrambling system supported" ); if( caps.slot_num == 0 ) { msg_Err( obj, "CAM module without slots" ); return NULL; } cam_t *p_cam = calloc( 1, sizeof( *p_cam ) ); if( unlikely(p_cam == NULL) ) goto error; p_cam->obj = obj; p_cam->fd = fd; p_cam->i_nb_slots = caps.slot_num; if( caps.slot_type & CA_CI_LINK ) { p_cam->i_ca_type = CA_CI_LINK; for ( unsigned i_slot = 0; i_slot < p_cam->i_nb_slots; i_slot++ ) { if ( ioctl( p_cam->fd, CA_RESET, 1 << i_slot) != 0 ) { msg_Err( p_cam->obj, "en50221_Init: couldn't reset slot %d", i_slot ); } } p_cam->i_timeout = CLOCK_FREQ / 10; /* Wait a bit otherwise it doesn't initialize properly... */ msleep( CLOCK_FREQ / 10 ); p_cam->i_next_event = 0; } else if( caps.slot_type & CA_CI ) { p_cam->i_ca_type = CA_CI; 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( fd, CA_GET_SLOT_INFO, &info ) < 0 ) { msg_Err( obj, "cannot get slot info: %s", vlc_strerror_c(errno) ); goto error; } if( info.flags == 0 ) { msg_Err( obj, "no CAM inserted" ); goto error; } /* Allocate a dummy sessions */ p_cam->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_cam, 1, AOT_APPLICATION_INFO_ENQ, NULL, 0 ); if ( ioctl( fd, CA_GET_MSG, &ca_msg ) < 0 ) { msg_Err( obj, "en50221_Init: failed getting message" ); goto error; } if( ca_msg.msg[8] == 0xff && ca_msg.msg[9] == 0xff ) { msg_Err( obj, "CAM returns garbage as application info!" ); goto error; } msg_Dbg( obj, "found CAM %s using id 0x%x", &ca_msg.msg[12], (ca_msg.msg[8]<<8)|ca_msg.msg[9] ); } else { msg_Err( obj, "CAM interface incompatible" ); goto error; } return p_cam; error: free( p_cam ); return NULL; } /***************************************************************************** * en50221_Poll : Poll the CAM for TPDUs *****************************************************************************/ void en50221_Poll( cam_t * p_cam ) { switch( p_cam->i_ca_type ) { case CA_CI_LINK: if( mdate() > p_cam->i_next_event ) break; case CA_CI: return; default: vlc_assert_unreachable(); } for ( unsigned i_slot = 0; i_slot < p_cam->i_nb_slots; i_slot++ ) { uint8_t i_tag; ca_slot_info_t sinfo; sinfo.num = i_slot; if ( ioctl( p_cam->fd, CA_GET_SLOT_INFO, &sinfo ) != 0 ) { msg_Err( p_cam->obj, "en50221_Poll: couldn't get info on slot %d", i_slot ); continue; } if ( !(sinfo.flags & CA_CI_MODULE_READY) ) { if ( p_cam->pb_active_slot[i_slot] ) { msg_Dbg( p_cam->obj, "en50221_Poll: slot %d has been removed", i_slot ); p_cam->pb_active_slot[i_slot] = false; p_cam->pb_slot_mmi_expected[i_slot] = false; p_cam->pb_slot_mmi_undisplayed[i_slot] = false; /* Close all sessions for this slot. */ for ( unsigned i = 1; i <= MAX_SESSIONS; i++ ) { if ( p_cam->p_sessions[i - 1].i_resource_id && p_cam->p_sessions[i - 1].i_slot == i_slot ) { if ( p_cam->p_sessions[i - 1].pf_close != NULL ) { p_cam->p_sessions[i - 1].pf_close( p_cam, i ); } p_cam->p_sessions[i - 1].i_resource_id = 0; } } } continue; } else if ( !p_cam->pb_active_slot[i_slot] ) { InitSlot( p_cam, i_slot ); if ( !p_cam->pb_active_slot[i_slot] ) { msg_Dbg( p_cam->obj, "en50221_Poll: resetting slot %d", i_slot ); if ( ioctl( p_cam->fd, CA_RESET, 1 << i_slot) != 0 ) { msg_Err( p_cam->obj, "en50221_Poll: couldn't reset slot %d", i_slot ); } continue; } msg_Dbg( p_cam->obj, "en50221_Poll: slot %d is active", i_slot ); } if ( !p_cam->pb_tc_has_data[i_slot] ) { if ( TPDUSend( p_cam, i_slot, T_DATA_LAST, NULL, 0 ) != VLC_SUCCESS ) { msg_Err( p_cam->obj, "en50221_Poll: couldn't send TPDU on slot %d", i_slot ); continue; } if ( TPDURecv( p_cam, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS ) { msg_Err( p_cam->obj, "en50221_Poll: couldn't recv TPDU on slot %d", i_slot ); continue; } } while ( p_cam->pb_tc_has_data[i_slot] ) { uint8_t p_tpdu[MAX_TPDU_SIZE]; int i_size, i_session_size; uint8_t *p_session; if ( TPDUSend( p_cam, i_slot, T_RCV, NULL, 0 ) != VLC_SUCCESS ) { msg_Err( p_cam->obj, "en50221_Poll: couldn't send TPDU on slot %d", i_slot ); continue; } if ( TPDURecv( p_cam, i_slot, &i_tag, p_tpdu, &i_size ) != VLC_SUCCESS ) { msg_Err( p_cam->obj, "en50221_Poll: couldn't recv TPDU on slot %d", i_slot ); continue; } p_session = GetLength( &p_tpdu[3], &i_session_size ); if ( i_session_size <= 1 ) continue; p_session++; i_session_size--; if ( i_tag != T_DATA_LAST ) { msg_Err( p_cam->obj, "en50221_Poll: fragmented TPDU not supported" ); break; } SPDUHandle( p_cam, i_slot, p_session, i_session_size ); } } for ( int i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ ) { if ( p_cam->p_sessions[i_session_id - 1].i_resource_id && p_cam->p_sessions[i_session_id - 1].pf_manage ) { p_cam->p_sessions[i_session_id - 1].pf_manage( p_cam, i_session_id ); } } p_cam->i_next_event = mdate() + p_cam->i_timeout; } /***************************************************************************** * en50221_SetCAPMT : *****************************************************************************/ int en50221_SetCAPMT( cam_t * p_cam, en50221_capmt_info_t *p_info ) { bool b_update = false; bool b_needs_descrambling = CAPMTNeedsDescrambling( p_info ); for ( unsigned i = 0; i < MAX_PROGRAMS; i++ ) { if ( p_cam->pp_selected_programs[i] != NULL && p_cam->pp_selected_programs[i]->i_program_number == p_info->i_program_number ) { b_update = true; if ( !b_needs_descrambling ) { en50221_capmt_Delete( p_info ); p_info = p_cam->pp_selected_programs[i]; p_cam->pp_selected_programs[i] = NULL; } else if( p_info != p_cam->pp_selected_programs[i] ) { en50221_capmt_Delete( p_cam->pp_selected_programs[i] ); p_cam->pp_selected_programs[i] = p_info; } break; } } if ( !b_update && b_needs_descrambling ) { for ( unsigned i = 0; i < MAX_PROGRAMS; i++ ) { if ( p_cam->pp_selected_programs[i] == NULL ) { p_cam->pp_selected_programs[i] = p_info; break; } } } if ( b_update || b_needs_descrambling ) { for ( unsigned i = 1; i <= MAX_SESSIONS; i++ ) { if ( p_cam->p_sessions[i - 1].i_resource_id == RI_CONDITIONAL_ACCESS_SUPPORT ) { if ( b_update && b_needs_descrambling ) CAPMTUpdate( p_cam, i, p_info ); else if ( b_update ) CAPMTDelete( p_cam, i, p_info ); else CAPMTAdd( p_cam, i, p_info ); } } } if ( !b_needs_descrambling ) { en50221_capmt_Delete( p_info ); } return VLC_SUCCESS; } #ifdef ENABLE_HTTPD /***************************************************************************** * en50221_OpenMMI : *****************************************************************************/ static int en50221_OpenMMI( cam_t * p_cam, unsigned i_slot ) { if( p_cam->i_ca_type & CA_CI_LINK ) { for ( unsigned i = 1; i <= MAX_SESSIONS; i++ ) { if ( p_cam->p_sessions[i - 1].i_resource_id == RI_MMI && p_cam->p_sessions[i - 1].i_slot == i_slot ) { msg_Dbg( p_cam->obj, "MMI menu is already opened on slot %d (session=%u)", i_slot, i ); return VLC_SUCCESS; } } for ( unsigned i = 1; i <= MAX_SESSIONS; i++ ) { if ( p_cam->p_sessions[i - 1].i_resource_id == RI_APPLICATION_INFORMATION && p_cam->p_sessions[i - 1].i_slot == i_slot ) { ApplicationInformationEnterMenu( p_cam, i ); return VLC_SUCCESS; } } msg_Err( p_cam->obj, "no application information on slot %d", i_slot ); return VLC_EGENERIC; } else { msg_Err( p_cam->obj, "MMI menu not supported" ); return VLC_EGENERIC; } } /***************************************************************************** * en50221_CloseMMI : *****************************************************************************/ static int en50221_CloseMMI( cam_t * p_cam, unsigned i_slot ) { if( p_cam->i_ca_type & CA_CI_LINK ) { for( unsigned i = 1; i <= MAX_SESSIONS; i++ ) { if ( p_cam->p_sessions[i - 1].i_resource_id == RI_MMI && p_cam->p_sessions[i - 1].i_slot == i_slot ) { MMISendClose( p_cam, i ); return VLC_SUCCESS; } } msg_Warn( p_cam->obj, "closing a non-existing MMI session on slot %d", i_slot ); return VLC_EGENERIC; } else { msg_Err( p_cam->obj, "MMI menu not supported" ); return VLC_EGENERIC; } } /***************************************************************************** * en50221_GetMMIObject : *****************************************************************************/ static mmi_t *en50221_GetMMIObject( cam_t * p_cam, unsigned i_slot ) { if( p_cam->pb_slot_mmi_expected[i_slot] ) return NULL; /* should not happen */ for( unsigned i = 1; i <= MAX_SESSIONS; i++ ) { if ( p_cam->p_sessions[i - 1].i_resource_id == RI_MMI && p_cam->p_sessions[i - 1].i_slot == i_slot ) { mmi_t *p_mmi = (mmi_t *)p_cam->p_sessions[i - 1].p_sys; if ( p_mmi == NULL ) return NULL; /* should not happen */ return p_mmi; } } return NULL; } /***************************************************************************** * en50221_SendMMIObject : *****************************************************************************/ static void en50221_SendMMIObject( cam_t * p_cam, unsigned i_slot, mmi_t *p_object ) { for( unsigned i = 1; i <= MAX_SESSIONS; i++ ) { if ( p_cam->p_sessions[i - 1].i_resource_id == RI_MMI && p_cam->p_sessions[i - 1].i_slot == i_slot ) { MMISendObject( p_cam, i, p_object ); return; } } msg_Err( p_cam->obj, "SendMMIObject when no MMI session is opened !" ); } char *en50221_Status( cam_t *p_cam, char *psz_request ) { if( psz_request != NULL && *psz_request ) { /* Check if we have an undisplayed MMI message : in that case we ignore * the user input to avoid confusing the CAM. */ for ( unsigned i_slot = 0; i_slot < p_cam->i_nb_slots; i_slot++ ) { if ( p_cam->pb_slot_mmi_undisplayed[i_slot] ) { psz_request = NULL; msg_Dbg( p_cam->obj, "ignoring user request because of a new MMI object" ); break; } } } if( psz_request != NULL && *psz_request ) { /* We have a mission to accomplish. */ mmi_t mmi_object; char psz_value[255]; int i_slot; bool b_ok = false; if ( HTTPExtractValue( psz_request, "slot", psz_value, sizeof(psz_value) ) == NULL ) { return strdup( "invalid request parameter\n" ); } i_slot = atoi(psz_value); if ( HTTPExtractValue( psz_request, "open", psz_value, sizeof(psz_value) ) != NULL ) { en50221_OpenMMI( p_cam, i_slot ); return NULL; } if ( HTTPExtractValue( psz_request, "close", psz_value, sizeof(psz_value) ) != NULL ) { en50221_CloseMMI( p_cam, i_slot ); return NULL; } if ( HTTPExtractValue( psz_request, "cancel", psz_value, sizeof(psz_value) ) == NULL ) { b_ok = true; } if ( HTTPExtractValue( psz_request, "type", psz_value, sizeof(psz_value) ) == NULL ) { return strdup( "invalid request parameter\n" ); } if ( !strcmp( psz_value, "enq" ) ) { mmi_object.i_object_type = EN50221_MMI_ANSW; mmi_object.u.answ.b_ok = b_ok; if ( !b_ok ) { mmi_object.u.answ.psz_answ = strdup(""); } else { if ( HTTPExtractValue( psz_request, "answ", psz_value, sizeof(psz_value) ) == NULL ) { return strdup( "invalid request parameter\n" ); } mmi_object.u.answ.psz_answ = strdup(psz_value); } } else { mmi_object.i_object_type = EN50221_MMI_MENU_ANSW; if ( !b_ok ) { mmi_object.u.menu_answ.i_choice = 0; } else { if ( HTTPExtractValue( psz_request, "choice", psz_value, sizeof(psz_value) ) == NULL ) mmi_object.u.menu_answ.i_choice = 0; else mmi_object.u.menu_answ.i_choice = atoi(psz_value); } } en50221_SendMMIObject( p_cam, i_slot, &mmi_object ); return NULL; } /* Check that we have all necessary MMI information. */ for( unsigned i_slot = 0; i_slot < p_cam->i_nb_slots; i_slot++ ) { if ( p_cam->pb_slot_mmi_expected[i_slot] ) return NULL; } char *buf; size_t len; FILE *p = open_memstream( &buf, &len ); if( unlikely(p == NULL) ) return NULL; ca_caps_t caps; if( ioctl( p_cam->fd, CA_GET_CAP, &caps ) < 0 ) { fprintf( p, "ioctl(CA_GET_CAP) failed: %s\n", vlc_strerror_c(errno) ); goto out; } /* Output CA capabilities */ fprintf( p, "CA interface with %d %s, type:\n", caps.slot_num, caps.slot_num == 1 ? "slot" : "slots" ); #define CHECK_CAPS( x, s ) \ if ( caps.slot_type & (CA_##x) ) \ fprintf( p, "\n", s ) CHECK_CAPS( CI, "CI high level interface" ); CHECK_CAPS( CI_LINK, "CI link layer level interface" ); CHECK_CAPS( CI_PHYS, "CI physical layer level interface (not supported)" ); CHECK_CAPS( DESCR, "built-in descrambler" ); CHECK_CAPS( SC, "simple smartcard interface" ); #undef CHECK_CAPS fprintf( p, "
%s
%d available %s\n", caps.descr_num, caps.descr_num == 1 ? "descrambler (key)" : "descramblers (keys)" ); #define CHECK_DESC( x ) \ if ( caps.descr_type & (CA_##x) ) \ fprintf( p, "", STRINGIFY(x) ) CHECK_DESC( ECD ); CHECK_DESC( NDS ); CHECK_DESC( DSS ); #undef CHECK_DESC fputs( "
%s
", p ); for( unsigned i_slot = 0; i_slot < p_cam->i_nb_slots; i_slot++ ) { ca_slot_info_t sinfo; p_cam->pb_slot_mmi_undisplayed[i_slot] = false; fprintf( p, "

CA slot #%d: ", i_slot ); sinfo.num = i_slot; if ( ioctl( p_cam->fd, CA_GET_SLOT_INFO, &sinfo ) < 0 ) { fprintf( p, "ioctl(CA_GET_SLOT_INFO) failed: %s
\n", vlc_strerror_c(errno) ); continue; } #define CHECK_TYPE( x, s ) \ if ( sinfo.type & (CA_##x) ) \ fputs( s, p ) CHECK_TYPE( CI, "high level, " ); CHECK_TYPE( CI_LINK, "link layer level, " ); CHECK_TYPE( CI_PHYS, "physical layer level, " ); #undef CHECK_TYPE if ( sinfo.flags & CA_CI_MODULE_READY ) { mmi_t *p_object = en50221_GetMMIObject( p_cam, i_slot ); fputs( "module present and ready

\n", p ); fputs( "

\n", p ); fprintf( p, "\n", i_slot ); if ( p_object == NULL ) { fputs( "\n", p ); } else { switch ( p_object->i_object_type ) { case EN50221_MMI_ENQ: fputs( "\n", p ); fprintf( p, "\n", p_object->u.enq.psz_text ); fprintf( p, "\n", p_object->u.enq.b_blind ? "password" : "text" ); break; case EN50221_MMI_MENU: fputs( "\n", p ); fprintf( p, "
%s
" "
\n", p_object->u.menu.psz_title ); fprintf( p, "\n", p_object->u.menu.psz_bottom ); break; case EN50221_MMI_LIST: fputs( "\n", p ); fputs( "\n", p ); fprintf( p, "
%s
%s
\n", p_object->u.menu.psz_subtitle ); for ( int i = 0; i < p_object->u.menu.i_choices; i++ ) fprintf( p, "%s
\n", i + 1, p_object->u.menu.ppsz_choices[i] ); fprintf( p, "
%s
\n", p_object->u.menu.psz_title ); fprintf( p, "\n", p_object->u.menu.psz_bottom ); break; default: fputs( "
%s
%s
\n", p_object->u.menu.psz_subtitle ); for ( int i = 0; i < p_object->u.menu.i_choices; i++ ) fprintf( p, "%s
\n", p_object->u.menu.ppsz_choices[i] ); fprintf( p, "
%s
\n", p ); } fputs( "
Unknown MMI object type

\n", p ); fputs( "\n", p ); fputs( "\n", p ); } fputs( "

\n", p ); } else if ( sinfo.flags & CA_CI_MODULE_PRESENT ) fputs( "module present, not ready
\n", p ); else fputs( "module not present
\n", p ); } out: fclose( p ); return buf; } #endif /***************************************************************************** * en50221_End : *****************************************************************************/ void en50221_End( cam_t * p_cam ) { for( unsigned i = 0; i < MAX_PROGRAMS; i++ ) { if( p_cam->pp_selected_programs[i] != NULL ) { en50221_capmt_Delete( p_cam->pp_selected_programs[i] ); } } for( unsigned i = 1; i <= MAX_SESSIONS; i++ ) { if( p_cam->p_sessions[i - 1].i_resource_id && p_cam->p_sessions[i - 1].pf_close != NULL ) { p_cam->p_sessions[i - 1].pf_close( p_cam, i ); } } vlc_close( p_cam->fd ); free( p_cam ); }