You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2669 lines
81 KiB

/*****************************************************************************
* en50221.c : implementation of the transport, session and applications
* layers of EN 50 221
*****************************************************************************
* Copyright (C) 2004-2005 the VideoLAN team
*
* Authors: Christophe Massiot <massiot@via.ecp.fr>
* 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 General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_common.h>
#include <vlc_charset.h>
#include <errno.h>
#include <time.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <netinet/in.h>
/* DVB Card Drivers */
#include <linux/dvb/ca.h>
/* Include dvbpsi headers */
# include <dvbpsi/dvbpsi.h>
# include <dvbpsi/descriptor.h>
# include <dvbpsi/pat.h>
# include <dvbpsi/pmt.h>
# include <dvbpsi/dr.h>
# include <dvbpsi/psi.h>
# include <dvbpsi/demux.h>
# include <dvbpsi/sdt.h>
#ifdef ENABLE_HTTPD
# include <vlc_httpd.h>
#endif
#include "dvb.h"
#include "../../demux/dvb-text.h"
#include "en50221.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 HLCI_WAIT_CAM_READY 0
#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;
mtime_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];
dvbpsi_pmt_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;
int i;
*pi_length = 0;
for ( 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
int i;
#define MAX_DUMP 256
fprintf(stderr, "%s ", b_outgoing ? "-->" : "<--");
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
}
/*****************************************************************************
* TPDUSend
*****************************************************************************/
static int TPDUSend( cam_t * p_cam, uint8_t i_slot, uint8_t i_tag,
const uint8_t *p_content, int i_length )
{
uint8_t i_tcid = i_slot + 1;
uint8_t p_data[MAX_TPDU_SIZE];
int i_size;
i_size = 0;
p_data[0] = i_slot;
p_data[1] = i_tcid;
p_data[2] = i_tag;
switch ( i_tag )
{
case T_RCV:
case T_CREATE_TC:
case T_CTC_REPLY:
case T_DELETE_TC:
case T_DTC_REPLY:
case T_REQUEST_TC:
p_data[3] = 1; /* length */
p_data[4] = i_tcid;
i_size = 5;
break;
case T_NEW_TC:
case T_TC_ERROR:
p_data[3] = 2; /* length */
p_data[4] = i_tcid;
p_data[5] = p_content[0];
i_size = 6;
break;
case T_DATA_LAST:
case T_DATA_MORE:
{
/* i_length <= MAX_TPDU_DATA */
uint8_t *p = p_data + 3;
p = SetLength( p, i_length + 1 );
*p++ = i_tcid;
if ( i_length )
memcpy( p, p_content, i_length );
i_size = i_length + (p - p_data);
}
break;
default:
break;
}
Dump( true, p_data, i_size );
if ( write( p_cam->fd, p_data, i_size ) != i_size )
{
msg_Err( p_cam->obj, "cannot write to CAM device (%m)" );
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: %m" );
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:%m)", i_size );
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, int 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: %m" );
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
*/
/*****************************************************************************
* 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;
}
/*****************************************************************************
* 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( 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( dvbpsi_pmt_t *p_pmt )
{
dvbpsi_descriptor_t *p_dr;
dvbpsi_pmt_es_t *p_es;
for( p_dr = p_pmt->p_first_descriptor; p_dr != NULL; p_dr = p_dr->p_next )
{
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 )
{
for( p_dr = p_es->p_first_descriptor; p_dr != NULL;
p_dr = p_dr->p_next )
{
if( p_dr->i_tag == 0x9 )
{
return true;
}
}
}
return false;
}
/*****************************************************************************
* CAPMTBuild
*****************************************************************************/
static int GetCADSize( system_ids_t *p_ids, dvbpsi_descriptor_t *p_dr )
{
int i_cad_size = 0;
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;
}
return i_cad_size;
}
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 )
{
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] = i_program_number >> 8;
p_data[2] = i_program_number & 0xff;
p_data[3] = ((i_version & 0x1f) << 1) | 0x1;
if ( i_size )
{
int i;
p_data[4] = (i_size + 1) >> 8;
p_data[5] = (i_size + 1) & 0xff;
p_data[6] = i_cmd;
i = 7;
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;
}
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 )
{
uint8_t *p_data;
int i;
if ( i_size )
p_data = xrealloc( p_capmt, i_capmt_size + 6 + i_size );
else
p_data = xrealloc( p_capmt, i_capmt_size + 5 );
i = i_capmt_size;
p_data[i] = i_type;
p_data[i + 1] = i_pid >> 8;
p_data[i + 2] = i_pid & 0xff;
if ( i_size )
{
p_data[i + 3] = (i_size + 1) >> 8;
p_data[i + 4] = (i_size + 1) & 0xff;
p_data[i + 5] = i_cmd;
i += 6;
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 );
i += p_dr->i_length + 2;
}
}
p_dr = p_dr->p_next;
}
}
else
{
p_data[i + 3] = 0;
p_data[i + 4] = 0;
}
return p_data;
}
static uint8_t *CAPMTBuild( cam_t * p_cam, int i_session_id,
dvbpsi_pmt_t *p_pmt, uint8_t i_list_mgt,
uint8_t i_cmd, int *pi_capmt_size )
{
system_ids_t *p_ids =
(system_ids_t *)p_cam->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_cam->obj,
"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( cam_t * p_cam, int i_session_id,
dvbpsi_pmt_t *p_pmt )
{
uint8_t *p_capmt;
int i_capmt_size;
msg_Dbg( p_cam->obj, "adding first CAPMT for SID %d on session %d",
p_pmt->i_program_number, i_session_id );
p_capmt = CAPMTBuild( p_cam, i_session_id, p_pmt,
0x3 /* only */, 0x1 /* ok_descrambling */,
&i_capmt_size );
if( i_capmt_size )
{
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,
dvbpsi_pmt_t *p_pmt )
{
uint8_t *p_capmt;
int 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_pmt->i_program_number );
return;
}
p_cam->i_selected_programs++;
if( p_cam->i_selected_programs == 1 )
{
CAPMTFirst( p_cam, i_session_id, p_pmt );
return;
}
#ifdef CAPMT_WAIT
msleep( CAPMT_WAIT * 1000 );
#endif
msg_Dbg( p_cam->obj, "adding CAPMT for SID %d on session %d",
p_pmt->i_program_number, i_session_id );
p_capmt = CAPMTBuild( p_cam, i_session_id, p_pmt,
0x4 /* add */, 0x1 /* ok_descrambling */,
&i_capmt_size );
if( i_capmt_size )
{
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,
dvbpsi_pmt_t *p_pmt )
{
uint8_t *p_capmt;
int i_capmt_size;
msg_Dbg( p_cam->obj, "updating CAPMT for SID %d on session %d",
p_pmt->i_program_number, i_session_id );
p_capmt = CAPMTBuild( p_cam, i_session_id, p_pmt,
0x5 /* update */, 0x1 /* ok_descrambling */,
&i_capmt_size );
if( i_capmt_size )
{
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,
dvbpsi_pmt_t *p_pmt )
{
uint8_t *p_capmt;
int i_capmt_size;
p_cam->i_selected_programs--;
msg_Dbg( p_cam->obj, "deleting CAPMT for SID %d on session %d",
p_pmt->i_program_number, i_session_id );
p_capmt = CAPMTBuild( p_cam, i_session_id, p_pmt,
0x5 /* update */, 0x4 /* not selected */,
&i_capmt_size );
if( i_capmt_size )
{
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;
mtime_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 + (mtime_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;
}
}
/*****************************************************************************
* 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;
}
/*****************************************************************************
* 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 = malloc( sizeof( *p_cam ) );
if( unlikely(p_cam == NULL) )
goto error;
p_cam->obj = obj;
p_cam->fd = fd;
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: %m" );
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 HLCI_WAIT_CAM_READY
while( ca_msg.msg[8] == 0xff && ca_msg.msg[9] == 0xff )
{
if( !vlc_object_alive (obj) )
goto error;
msleep(1);
msg_Dbg( obj, "CAM: please wait" );
APDUSend( p_cam, 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( fd, CA_GET_MSG, &ca_msg ) < 0 )
{
msg_Err( obj, "en50221_Init: failed getting message" );
goto error;
}
msg_Dbg( p_cam->obj, "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( obj, "CAM returns garbage as application info!" );
goto error;
}
#endif
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:
assert( 0 );
}
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, dvbpsi_pmt_t *p_pmt )
{
bool b_update = false;
bool b_needs_descrambling = CAPMTNeedsDescrambling( p_pmt );
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_pmt->i_program_number )
{
b_update = true;
if ( !b_needs_descrambling )
{
dvbpsi_DeletePMT( p_pmt );
p_pmt = p_cam->pp_selected_programs[i];
p_cam->pp_selected_programs[i] = NULL;
}
else if( p_pmt != p_cam->pp_selected_programs[i] )
{
dvbpsi_DeletePMT( p_cam->pp_selected_programs[i] );
p_cam->pp_selected_programs[i] = p_pmt;
}
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_pmt;
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_pmt );
else if ( b_update )
CAPMTDelete( p_cam, i, p_pmt );
else
CAPMTAdd( p_cam, i, p_pmt );
}
}
}
if ( !b_needs_descrambling )
{
dvbpsi_DeletePMT( p_pmt );
}
return VLC_SUCCESS;
}
/*****************************************************************************
* 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 !" );
}
#ifdef ENABLE_HTTPD
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 == false )
{
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 == false )
{
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 (%m)\n" );
goto out;
}
/* Output CA capabilities */
fprintf( p, "CA interface with %d %s, type:\n<table>", caps.slot_num,
caps.slot_num == 1 ? "slot" : "slots" );
#define CHECK_CAPS( x, s ) \
if ( caps.slot_type & (CA_##x) ) \
fprintf( p, "<tr><td>%s</td></tr>\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, "</table>%d available %s\n<table>", caps.descr_num,
caps.descr_num == 1 ? "descrambler (key)" : "descramblers (keys)" );
#define CHECK_DESC( x ) \
if ( caps.descr_type & (CA_##x) ) \
fprintf( p, "<tr><td>%s</td></tr>", STRINGIFY(x) )
CHECK_DESC( ECD );
CHECK_DESC( NDS );
CHECK_DESC( DSS );
#undef CHECK_DESC
fputs( "</table>", 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, "<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 (%m)<br>\n" );
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<p>\n", p );
fputs( "<form action=index.html method=get>\n", p );
fprintf( p, "<input type=hidden name=slot value=\"%d\">\n",
i_slot );
if ( p_object == NULL )
{
fputs( "<input type=submit name=open"
" value=\"Open session\">\n", p );
}
else
{
switch ( p_object->i_object_type )
{
case EN50221_MMI_ENQ:
fputs( "<input type=hidden name=type value=enq>\n", p );
fprintf( p, "<table border=1><tr><th>%s</th></tr>\n",
p_object->u.enq.psz_text );
fprintf( p, "<tr><td><input type=%s name=answ>"
"</td></tr>\n",
p_object->u.enq.b_blind ? "password" : "text" );
break;
case EN50221_MMI_MENU:
fputs( "<input type=hidden name=type value=menu>\n", p );
fprintf( p, "<table border=1><tr><th>%s</th></tr>\n",
p_object->u.menu.psz_title );
fprintf( p, "<tr><td>%s</td></tr><tr><td>\n",
p_object->u.menu.psz_subtitle );
for ( int i = 0; i < p_object->u.menu.i_choices; i++ )
fprintf( p, "<input type=radio name=choice"
" value=\"%d\">%s<br>\n", i + 1,
p_object->u.menu.ppsz_choices[i] );
fprintf( p, "</td></tr><tr><td>%s</td></tr>\n",
p_object->u.menu.psz_bottom );
break;
case EN50221_MMI_LIST:
fputs( "<input type=hidden name=type value=menu>\n", p );
fputs( "<input type=hidden name=choice value=0>\n", p );
fprintf( p, "<table border=1><tr><th>%s</th></tr>\n",
p_object->u.menu.psz_title );
fprintf( p, "<tr><td>%s</td></tr><tr><td>\n",
p_object->u.menu.psz_subtitle );
for ( int i = 0; i < p_object->u.menu.i_choices; i++ )
fprintf( p, "%s<br>\n",
p_object->u.menu.ppsz_choices[i] );
fprintf( p, "</td></tr><tr><td>%s</td></tr>\n",
p_object->u.menu.psz_bottom );
break;
default:
fputs( "<table><tr><th>Unknown MMI object type</th></tr>\n", p );
}
fputs( "</table><p><input type=submit name=ok value=\"OK\">\n", p );
fputs( "<input type=submit name=cancel value=\"Cancel\">\n", p );
fputs( "<input type=submit name=close value=\"Close Session\">\n", p );
}
fputs( "</form>\n", p );
}
else if ( sinfo.flags & CA_CI_MODULE_PRESENT )
fputs( "module present, not ready<br>\n", p );
else
fputs( "module not present<br>\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 )
{
dvbpsi_DeletePMT( 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 );
}
}
close( p_cam->fd );
free( p_cam );
}