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