Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  en50221.c   Sprache: C

 

/*****************************************************************************
 * en50221.c : implementation of the transport, session and applications
 * layers of EN 50 221
 *****************************************************************************
 * Copyright (C) 2004-2005 VLC authors and VideoLAN
 *
 * Authors: Christophe Massiot <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 Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA    02111, USA.
 *****************************************************************************/


#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <vlc_common.h>
#include <vlc_arrays.h>
#include <vlc_tick.h>
#include <vlc_threads.h>
#include <vlc_poll.h>
#include <vlc_charset.h>
#include <vlc_fs.h>

#include <errno.h>
#include <time.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <poll.h>
#include <netinet/in.h>

/* DVB Card Drivers */
#include <linux/dvb/ca.h>

#undef ENABLE_HTTPD
#ifdef ENABLE_HTTPD
#   include <vlc_httpd.h>
#endif

#include "../../demux/dvb-text.h"
#include "en50221.h"
#include "en50221_capmt.h"

typedef struct en50221_session_t
{
    unsigned i_slot;
    int i_resource_id;
    void (* pf_handle)( cam_t *, int, uint8_t *, int );
    void (* pf_close)( cam_t *, int );
    void (* pf_manage)( cam_t *, int );
    void *p_sys;
} en50221_session_t;

#define EN50221_MMI_NONE 0
#define EN50221_MMI_ENQ 1
#define EN50221_MMI_ANSW 2
#define EN50221_MMI_MENU 3
#define EN50221_MMI_MENU_ANSW 4
#define EN50221_MMI_LIST 5

typedef struct en50221_mmi_object_t
{
    int i_object_type;

    union
    {
        struct
        {
            bool b_blind;
            char *psz_text;
        } enq;

        struct
        {
            bool b_ok;
            char *psz_answ;
        } answ;

        struct
        {
            char *psz_title, *psz_subtitle, *psz_bottom;
            char **ppsz_choices;
            int i_choices;
        } menu; /* menu and list are the same */

        struct
        {
            int i_choice;
        } menu_answ;
    } u;
} mmi_t;


#undef DEBUG_TPDU
#define CAM_PROG_MAX MAX_PROGRAMS
//#define CAPMT_WAIT 100             /* uncomment this for slow CAMs */

static void ResourceManagerOpen( cam_t *, unsigned i_session_id );
static void ApplicationInformationOpen( cam_t *, unsigned i_session_id );
static void ConditionalAccessOpen( cam_t *, unsigned i_session_id );
static void DateTimeOpen( cam_t *, unsigned i_session_id );
static void MMIOpen( cam_t *, unsigned i_session_id );

#define MAX_CI_SLOTS 16
#define MAX_SESSIONS 32
#define MAX_PROGRAMS 24

struct cam
{
    vlc_object_t *obj;
    int fd;
    int i_ca_type;
    vlc_tick_t i_timeout, i_next_event;

    unsigned i_nb_slots;
    bool pb_active_slot[MAX_CI_SLOTS];
    bool pb_tc_has_data[MAX_CI_SLOTS];
    bool pb_slot_mmi_expected[MAX_CI_SLOTS];
    bool pb_slot_mmi_undisplayed[MAX_CI_SLOTS];
    en50221_session_t p_sessions[MAX_SESSIONS];

    en50221_capmt_info_t *pp_selected_programs[MAX_PROGRAMS];
    int i_selected_programs;
};

/*****************************************************************************
 * Utility functions
 *****************************************************************************/

#define SIZE_INDICATOR 0x80

static uint8_t *GetLength( uint8_t *p_data, int *pi_length )
{
    *pi_length = *p_data++;

    if ( (*pi_length & SIZE_INDICATOR) != 0 )
    {
        int l = *pi_length & ~SIZE_INDICATOR;

        *pi_length = 0;
        for ( int i = 0; i < l; i++ )
            *pi_length = (*pi_length << 8) | *p_data++;
    }

    return p_data;
}

static uint8_t *SetLength( uint8_t *p_data, int i_length )
{
    uint8_t *p = p_data;

    if ( i_length < 128 )
    {
        *p++ = i_length;
    }
    else if ( i_length < 256 )
    {
        *p++ = SIZE_INDICATOR | 0x1;
        *p++ = i_length;
    }
    else if ( i_length < 65536 )
    {
        *p++ = SIZE_INDICATOR | 0x2;
        *p++ = i_length >> 8;
        *p++ = i_length & 0xff;
    }
    else if ( i_length < 16777216 )
    {
        *p++ = SIZE_INDICATOR | 0x3;
        *p++ = i_length >> 16;
        *p++ = (i_length >> 8) & 0xff;
        *p++ = i_length & 0xff;
    }
    else
    {
        *p++ = SIZE_INDICATOR | 0x4;
        *p++ = i_length >> 24;
        *p++ = (i_length >> 16) & 0xff;
        *p++ = (i_length >> 8) & 0xff;
        *p++ = i_length & 0xff;
    }

    return p;
}


/*
 * Transport layer
 */


#define MAX_TPDU_SIZE  4096
#define MAX_TPDU_DATA  (MAX_TPDU_SIZE - 4)

#define DATA_INDICATOR 0x80

#define T_SB           0x80
#define T_RCV          0x81
#define T_CREATE_TC    0x82
#define T_CTC_REPLY    0x83
#define T_DELETE_TC    0x84
#define T_DTC_REPLY    0x85
#define T_REQUEST_TC   0x86
#define T_NEW_TC       0x87
#define T_TC_ERROR     0x88
#define T_DATA_LAST    0xA0
#define T_DATA_MORE    0xA1

static void Dump( bool b_outgoing, uint8_t *p_data, int i_size )
{
#ifdef DEBUG_TPDU
#define MAX_DUMP 256
    fprintf(stderr, "%s ", b_outgoing ? "-->" : "<--");
    for ( int i = 0; i < i_size && i < MAX_DUMP; i++)
        fprintf(stderr, "%02X ", p_data[i]);
    fprintf(stderr, "%s\n", i_size >= MAX_DUMP ? "..." : "");
#else
    VLC_UNUSED(b_outgoing); VLC_UNUSED(p_data); VLC_UNUSED(i_size);
#endif
}

/*****************************************************************************
 * TPDUSend
 *****************************************************************************/

static int TPDUSend( cam_t * p_cam, uint8_t i_slot, uint8_t i_tag,
                     const uint8_t *p_content, size_t i_length )
{
    uint8_t p_data[9], *p = p_data;

    *(p++) = i_slot;
    *(p++) = i_slot + 1; /* TCID */
    *(p++) = i_tag;
    p = SetLength( p, i_length + 1 );

    *(p++) = i_slot + 1;
    Dump( true, p_data, p - p_data );

    const struct iovec iov[2] = {
        { .iov_base = p_data, .iov_len = p - p_data },
        { .iov_base = (void *)p_content, .iov_len = i_length },
    };

    if ( writev( p_cam->fd, iov, 2 ) <= 0 )
    {
        msg_Err( p_cam->obj, "cannot write to CAM device: %s",
                 vlc_strerror_c(errno) );
        return VLC_EGENERIC;
    }
    return VLC_SUCCESS;
}


/*****************************************************************************
 * TPDURecv
 *****************************************************************************/

#define CAM_READ_TIMEOUT  3500 // ms

static int TPDURecv( cam_t *p_cam, uint8_t i_slot, uint8_t *pi_tag,
                     uint8_t *p_data, int *pi_size )
{
    uint8_t i_tcid = i_slot + 1;
    int i_size;
    struct pollfd pfd[1];

    pfd[0].fd = p_cam->fd;
    pfd[0].events = POLLIN;

    while( poll(pfd, 1, CAM_READ_TIMEOUT ) == -1 )
        if( errno != EINTR )
        {
            msg_Err( p_cam->obj, "poll error: %s", vlc_strerror_c(errno) );
            return VLC_EGENERIC;
        }

    if ( !(pfd[0].revents & POLLIN) )
    {
        msg_Err( p_cam->obj, "CAM device poll time-out" );
        return VLC_EGENERIC;
    }

    if ( pi_size == NULL )
    {
        p_data = xmalloc( MAX_TPDU_SIZE );
    }

    for ( ; ; )
    {
        i_size = read( p_cam->fd, p_data, MAX_TPDU_SIZE );

        if ( i_size >= 0 || errno != EINTR )
            break;
    }

    if ( i_size < 5 )
    {
        msg_Err( p_cam->obj, "cannot read from CAM device (%d): %s", i_size,
                 vlc_strerror_c(errno) );
        if( pi_size == NULL )
            free( p_data );
        return VLC_EGENERIC;
    }

    if ( p_data[1] != i_tcid )
    {
        msg_Err( p_cam->obj, "invalid read from CAM device (%d instead of %d)",
                 p_data[1], i_tcid );
        if( pi_size == NULL )
            free( p_data );
        return VLC_EGENERIC;
    }

    *pi_tag = p_data[2];
    p_cam->pb_tc_has_data[i_slot] = (i_size >= 4
                                      && p_data[i_size - 4] == T_SB
                                      && p_data[i_size - 3] == 2
                                      && (p_data[i_size - 1] & DATA_INDICATOR))
                                        ?  true : false;

    Dump( false, p_data, i_size );

    if ( pi_size == NULL )
        free( p_data );
    else
        *pi_size = i_size;

    return VLC_SUCCESS;
}


/*
 * Session layer
 */


#define ST_SESSION_NUMBER           0x90
#define ST_OPEN_SESSION_REQUEST     0x91
#define ST_OPEN_SESSION_RESPONSE    0x92
#define ST_CREATE_SESSION           0x93
#define ST_CREATE_SESSION_RESPONSE  0x94
#define ST_CLOSE_SESSION_REQUEST    0x95
#define ST_CLOSE_SESSION_RESPONSE   0x96

#define SS_OK             0x00
#define SS_NOT_ALLOCATED  0xF0

#define RI_RESOURCE_MANAGER            0x00010041
#define RI_APPLICATION_INFORMATION     0x00020041
#define RI_CONDITIONAL_ACCESS_SUPPORT  0x00030041
#define RI_HOST_CONTROL                0x00200041
#define RI_DATE_TIME                   0x00240041
#define RI_MMI                         0x00400041

static int ResourceIdToInt( uint8_t *p_data )
{
    return ((int)p_data[0] << 24) | ((int)p_data[1] << 16)
            | ((int)p_data[2] << 8) | p_data[3];
}

/*****************************************************************************
 * SPDUSend
 *****************************************************************************/

static int SPDUSend( cam_t * p_cam, int i_session_id,
                     uint8_t *p_data, int i_size )
{
    uint8_t *p_spdu = xmalloc( i_size + 4 );
    uint8_t *p = p_spdu;
    uint8_t i_tag;
    uint8_t i_slot = p_cam->p_sessions[i_session_id - 1].i_slot;

    *p++ = ST_SESSION_NUMBER;
    *p++ = 0x02;
    *p++ = (i_session_id >> 8);
    *p++ = i_session_id & 0xff;

    memcpy( p, p_data, i_size );

    i_size += 4;
    p = p_spdu;

    while ( i_size > 0 )
    {
        if ( i_size > MAX_TPDU_DATA )
        {
            if ( TPDUSend( p_cam, i_slot, T_DATA_MORE, p,
                           MAX_TPDU_DATA ) != VLC_SUCCESS )
            {
                msg_Err( p_cam->obj, "couldn't send TPDU on session %d",
                         i_session_id );
                free( p_spdu );
                return VLC_EGENERIC;
            }
            p += MAX_TPDU_DATA;
            i_size -= MAX_TPDU_DATA;
        }
        else
        {
            if ( TPDUSend( p_cam, i_slot, T_DATA_LAST, p, i_size )
                    != VLC_SUCCESS )
            {
                msg_Err( p_cam->obj, "couldn't send TPDU on session %d",
                         i_session_id );
                free( p_spdu );
                return VLC_EGENERIC;
            }
            i_size = 0;
        }

        if ( TPDURecv( p_cam, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS
               || i_tag != T_SB )
        {
            msg_Err( p_cam->obj, "couldn't recv TPDU on session %d",
                     i_session_id );
            free( p_spdu );
            return VLC_EGENERIC;
        }
    }

    free( p_spdu );
    return VLC_SUCCESS;
}

/*****************************************************************************
 * SessionOpen
 *****************************************************************************/

static void SessionOpen( cam_t * p_cam, uint8_t i_slot,
                         uint8_t *p_spdu, int i_size )
{
    VLC_UNUSED( i_size );

    int i_session_id;
    int i_resource_id = ResourceIdToInt( &p_spdu[2] );
    uint8_t p_response[16];
    int i_status = SS_NOT_ALLOCATED;
    uint8_t i_tag;

    for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
    {
        if ( !p_cam->p_sessions[i_session_id - 1].i_resource_id )
            break;
    }
    if ( i_session_id > MAX_SESSIONS )
    {
        msg_Err( p_cam->obj, "too many sessions !" );
        return;
    }
    p_cam->p_sessions[i_session_id - 1].i_slot = i_slot;
    p_cam->p_sessions[i_session_id - 1].i_resource_id = i_resource_id;
    p_cam->p_sessions[i_session_id - 1].pf_close = NULL;
    p_cam->p_sessions[i_session_id - 1].pf_manage = NULL;

    if ( i_resource_id == RI_RESOURCE_MANAGER
          || i_resource_id == RI_APPLICATION_INFORMATION
          || i_resource_id == RI_CONDITIONAL_ACCESS_SUPPORT
          || i_resource_id == RI_DATE_TIME
          || i_resource_id == RI_MMI )
    {
        i_status = SS_OK;
    }

    p_response[0] = ST_OPEN_SESSION_RESPONSE;
    p_response[1] = 0x7;
    p_response[2] = i_status;
    p_response[3] = p_spdu[2];
    p_response[4] = p_spdu[3];
    p_response[5] = p_spdu[4];
    p_response[6] = p_spdu[5];
    p_response[7] = i_session_id >> 8;
    p_response[8] = i_session_id & 0xff;

    if ( TPDUSend( p_cam, i_slot, T_DATA_LAST, p_response, 9 ) !=
            VLC_SUCCESS )
    {
        msg_Err( p_cam->obj,
                 "SessionOpen: couldn't send TPDU on slot %d", i_slot );
        return;
    }
    if ( TPDURecv( p_cam, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS )
    {
        msg_Err( p_cam->obj,
                 "SessionOpen: couldn't recv TPDU on slot %d", i_slot );
        return;
    }

    switch ( i_resource_id )
    {
    case RI_RESOURCE_MANAGER:
        ResourceManagerOpen( p_cam, i_session_id ); break;
    case RI_APPLICATION_INFORMATION:
        ApplicationInformationOpen( p_cam, i_session_id ); break;
    case RI_CONDITIONAL_ACCESS_SUPPORT:
        ConditionalAccessOpen( p_cam, i_session_id ); break;
    case RI_DATE_TIME:
        DateTimeOpen( p_cam, i_session_id ); break;
    case RI_MMI:
        MMIOpen( p_cam, i_session_id ); break;

    case RI_HOST_CONTROL:
    default:
        msg_Err( p_cam->obj, "unknown resource id (0x%x)", i_resource_id );
        p_cam->p_sessions[i_session_id - 1].i_resource_id = 0;
    }
}

#if 0
/* unused code for the moment - commented out to keep gcc happy */
/*****************************************************************************
 * SessionCreate
 *****************************************************************************/

static void SessionCreate( cam_t * p_cam, int i_slot, int i_resource_id )
{
    uint8_t p_response[16];
    uint8_t i_tag;
    int i_session_id;

    for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
    {
        if ( !p_cam->p_sessions[i_session_id - 1].i_resource_id )
            break;
    }
    if ( i_session_id == MAX_SESSIONS )
    {
        msg_Err( p_cam->obj, "too many sessions !" );
        return;
    }
    p_cam->p_sessions[i_session_id - 1].i_slot = i_slot;
    p_cam->p_sessions[i_session_id - 1].i_resource_id = i_resource_id;
    p_cam->p_sessions[i_session_id - 1].pf_close = NULL;
    p_cam->p_sessions[i_session_id - 1].pf_manage = NULL;
    p_cam->p_sessions[i_session_id - 1].p_sys = NULL;

    p_response[0] = ST_CREATE_SESSION;
    p_response[1] = 0x6;
    SetDWBE( &p_resource[2], i_resource_id );
    SetWBE( &p_response[6]. i_session_id);

    if ( TPDUSend( p_cam, i_slot, T_DATA_LAST, p_response, 4 ) !=
            VLC_SUCCESS )
    {
        msg_Err( p_cam->obj,
                 "SessionCreate: couldn't send TPDU on slot %d", i_slot );
        return;
    }
    if ( TPDURecv( p_cam, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS )
    {
        msg_Err( p_cam->obj,
                 "SessionCreate: couldn't recv TPDU on slot %d", i_slot );
        return;
    }
}
#endif

/*****************************************************************************
 * SessionCreateResponse
 *****************************************************************************/

static void SessionCreateResponse( cam_t * p_cam, uint8_t i_slot,
                                   uint8_t *p_spdu, int i_size )
{
    VLC_UNUSED( i_size );
    VLC_UNUSED( i_slot );

    int i_status = p_spdu[2];
    int i_resource_id = ResourceIdToInt( &p_spdu[3] );
    int i_session_id = ((int)p_spdu[7] << 8) | p_spdu[8];

    if ( i_status != SS_OK )
    {
        msg_Err( p_cam->obj, "SessionCreateResponse: failed to open session %d"
                 " resource=0x%x status=0x%x", i_session_id, i_resource_id,
                 i_status );
        p_cam->p_sessions[i_session_id - 1].i_resource_id = 0;
        return;
    }

    switch ( i_resource_id )
    {
    case RI_RESOURCE_MANAGER:
        ResourceManagerOpen( p_cam, i_session_id ); break;
    case RI_APPLICATION_INFORMATION:
        ApplicationInformationOpen( p_cam, i_session_id ); break;
    case RI_CONDITIONAL_ACCESS_SUPPORT:
        ConditionalAccessOpen( p_cam, i_session_id ); break;
    case RI_DATE_TIME:
        DateTimeOpen( p_cam, i_session_id ); break;
    case RI_MMI:
        MMIOpen( p_cam, i_session_id ); break;

    case RI_HOST_CONTROL:
    default:
        msg_Err( p_cam->obj, "unknown resource id (0x%x)", i_resource_id );
        p_cam->p_sessions[i_session_id - 1].i_resource_id = 0;
    }
}

/*****************************************************************************
 * SessionSendClose
 *****************************************************************************/

static void SessionSendClose( cam_t *p_cam, int i_session_id )
{
    uint8_t p_response[16];
    uint8_t i_tag;
    uint8_t i_slot = p_cam->p_sessions[i_session_id - 1].i_slot;

    p_response[0] = ST_CLOSE_SESSION_REQUEST;
    p_response[1] = 0x2;
    SetWBE( &p_response[2], i_session_id );

    if ( TPDUSend( p_cam, i_slot, T_DATA_LAST, p_response, 4 ) !=
            VLC_SUCCESS )
    {
        msg_Err( p_cam->obj,
                 "SessionSendClose: couldn't send TPDU on slot %d", i_slot );
        return;
    }
    if ( TPDURecv( p_cam, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS )
    {
        msg_Err( p_cam->obj,
                 "SessionSendClose: couldn't recv TPDU on slot %d", i_slot );
        return;
    }
}

/*****************************************************************************
 * SessionClose
 *****************************************************************************/

static void SessionClose( cam_t * p_cam, int i_session_id )
{
    uint8_t p_response[16];
    uint8_t i_tag;
    uint8_t i_slot = p_cam->p_sessions[i_session_id - 1].i_slot;

    if ( p_cam->p_sessions[i_session_id - 1].pf_close != NULL )
        p_cam->p_sessions[i_session_id - 1].pf_close( p_cam, i_session_id );
    p_cam->p_sessions[i_session_id - 1].i_resource_id = 0;

    p_response[0] = ST_CLOSE_SESSION_RESPONSE;
    p_response[1] = 0x3;
    p_response[2] = SS_OK;
    p_response[3] = i_session_id >> 8;
    p_response[4] = i_session_id & 0xff;

    if ( TPDUSend( p_cam, i_slot, T_DATA_LAST, p_response, 5 ) !=
            VLC_SUCCESS )
    {
        msg_Err( p_cam->obj,
                 "SessionClose: couldn't send TPDU on slot %d", i_slot );
        return;
    }
    if ( TPDURecv( p_cam, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS )
    {
        msg_Err( p_cam->obj,
                 "SessionClose: couldn't recv TPDU on slot %d", i_slot );
        return;
    }
}

/*****************************************************************************
 * SPDUHandle
 *****************************************************************************/

static void SPDUHandle( cam_t * p_cam, uint8_t i_slot,
                        uint8_t *p_spdu, int i_size )
{
    int i_session_id;

    switch ( p_spdu[0] )
    {
    case ST_SESSION_NUMBER:
        if ( i_size <= 4 )
            return;
        i_session_id = ((int)p_spdu[2] << 8) | p_spdu[3];
        p_cam->p_sessions[i_session_id - 1].pf_handle( p_cam, i_session_id,
                                                       p_spdu + 4, i_size - 4 );
        break;

    case ST_OPEN_SESSION_REQUEST:
        if ( i_size != 6 || p_spdu[1] != 0x4 )
            return;
        SessionOpen( p_cam, i_slot, p_spdu, i_size );
        break;

    case ST_CREATE_SESSION_RESPONSE:
        if ( i_size != 9 || p_spdu[1] != 0x7 )
            return;
        SessionCreateResponse( p_cam, i_slot, p_spdu, i_size );
        break;

    case ST_CLOSE_SESSION_REQUEST:
        if ( i_size != 4 || p_spdu[1] != 0x2 )
            return;
        i_session_id = ((int)p_spdu[2] << 8) | p_spdu[3];
        SessionClose( p_cam, i_session_id );
        break;

    case ST_CLOSE_SESSION_RESPONSE:
        if ( i_size != 5 || p_spdu[1] != 0x3 )
            return;
        i_session_id = ((int)p_spdu[3] << 8) | p_spdu[4];
        if ( p_spdu[2] )
        {
            msg_Err( p_cam->obj,
                     "closing a session which is not allocated (%d)",
                     i_session_id );
        }
        else
        {
            if ( p_cam->p_sessions[i_session_id - 1].pf_close != NULL )
                p_cam->p_sessions[i_session_id - 1].pf_close( p_cam,
                                                              i_session_id );
            p_cam->p_sessions[i_session_id - 1].i_resource_id = 0;
        }
        break;

    default:
        msg_Err( p_cam->obj, "unexpected tag in SPDUHandle (%x)", p_spdu[0] );
        break;
    }
}


/*
 * Application layer
 */


#define AOT_NONE                    0x000000
#define AOT_PROFILE_ENQ             0x9F8010
#define AOT_PROFILE                 0x9F8011
#define AOT_PROFILE_CHANGE          0x9F8012
#define AOT_APPLICATION_INFO_ENQ    0x9F8020
#define AOT_APPLICATION_INFO        0x9F8021
#define AOT_ENTER_MENU              0x9F8022
#define AOT_CA_INFO_ENQ             0x9F8030
#define AOT_CA_INFO                 0x9F8031
#define AOT_CA_PMT                  0x9F8032
#define AOT_CA_PMT_REPLY            0x9F8033
#define AOT_TUNE                    0x9F8400
#define AOT_REPLACE                 0x9F8401
#define AOT_CLEAR_REPLACE           0x9F8402
#define AOT_ASK_RELEASE             0x9F8403
#define AOT_DATE_TIME_ENQ           0x9F8440
#define AOT_DATE_TIME               0x9F8441
#define AOT_CLOSE_MMI               0x9F8800
#define AOT_DISPLAY_CONTROL         0x9F8801
#define AOT_DISPLAY_REPLY           0x9F8802
#define AOT_TEXT_LAST               0x9F8803
#define AOT_TEXT_MORE               0x9F8804
#define AOT_KEYPAD_CONTROL          0x9F8805
#define AOT_KEYPRESS                0x9F8806
#define AOT_ENQ                     0x9F8807
#define AOT_ANSW                    0x9F8808
#define AOT_MENU_LAST               0x9F8809
#define AOT_MENU_MORE               0x9F880A
#define AOT_MENU_ANSW               0x9F880B
#define AOT_LIST_LAST               0x9F880C
#define AOT_LIST_MORE               0x9F880D
#define AOT_SUBTITLE_SEGMENT_LAST   0x9F880E
#define AOT_SUBTITLE_SEGMENT_MORE   0x9F880F
#define AOT_DISPLAY_MESSAGE         0x9F8810
#define AOT_SCENE_END_MARK          0x9F8811
#define AOT_SCENE_DONE              0x9F8812
#define AOT_SCENE_CONTROL           0x9F8813
#define AOT_SUBTITLE_DOWNLOAD_LAST  0x9F8814
#define AOT_SUBTITLE_DOWNLOAD_MORE  0x9F8815
#define AOT_FLUSH_DOWNLOAD          0x9F8816
#define AOT_DOWNLOAD_REPLY          0x9F8817
#define AOT_COMMS_CMD               0x9F8C00
#define AOT_CONNECTION_DESCRIPTOR   0x9F8C01
#define AOT_COMMS_REPLY             0x9F8C02
#define AOT_COMMS_SEND_LAST         0x9F8C03
#define AOT_COMMS_SEND_MORE         0x9F8C04
#define AOT_COMMS_RCV_LAST          0x9F8C05
#define AOT_COMMS_RCV_MORE          0x9F8C06

/*****************************************************************************
 * APDUGetTag
 *****************************************************************************/

static int APDUGetTag( const uint8_t *p_apdu, int i_size )
{
    if ( i_size >= 3 )
    {
        int i, t = 0;
        for ( i = 0; i < 3; i++ )
            t = (t << 8) | *p_apdu++;
        return t;
    }

    return AOT_NONE;
}

/*****************************************************************************
 * APDUGetLength
 *****************************************************************************/

static uint8_t *APDUGetLength( uint8_t *p_apdu, int *pi_size )
{
    return GetLength( &p_apdu[3], pi_size );
}

/*****************************************************************************
 * APDUSend
 *****************************************************************************/

static int APDUSend( cam_t * p_cam, int i_session_id, int i_tag,
                     uint8_t *p_data, size_t i_size )
{
    uint8_t *p_apdu = xmalloc( i_size + 12 );
    uint8_t *p = p_apdu;
    ca_msg_t ca_msg;
    int i_ret;

    *p++ = (i_tag >> 16);
    *p++ = (i_tag >> 8) & 0xff;
    *p++ = i_tag & 0xff;
    p = SetLength( p, i_size );
    if ( i_size )
        memcpy( p, p_data, i_size );
    if ( p_cam->i_ca_type == CA_CI_LINK )
    {
        i_ret = SPDUSend( p_cam, i_session_id, p_apdu, i_size + p - p_apdu );
    }
    else
    {
        if ( i_size + p - p_apdu > 256 )
        {
            msg_Err( p_cam->obj, "CAM: apdu overflow" );
            i_ret = VLC_EGENERIC;
        }
        else
        {
            ca_msg.length = i_size + p - p_apdu;
            if ( i_size == 0 ) ca_msg.length=3;
            memcpy( ca_msg.msg, p_apdu, i_size + p - p_apdu );
            i_ret = ioctl( p_cam->fd, CA_SEND_MSG, &ca_msg );
            if ( i_ret < 0 )
            {
                msg_Err( p_cam->obj, "Error sending to CAM: %s",
                         vlc_strerror_c(errno) );
                i_ret = VLC_EGENERIC;
            }
        }
    }
    free( p_apdu );
    return i_ret;
}

/*
 * Resource Manager
 */


/*****************************************************************************
 * ResourceManagerHandle
 *****************************************************************************/

static void ResourceManagerHandle( cam_t * p_cam, int i_session_id,
                                   uint8_t *p_apdu, int i_size )
{
    int i_tag = APDUGetTag( p_apdu, i_size );

    switch ( i_tag )
    {
    case AOT_PROFILE_ENQ:
    {
        int resources[] = { htonl(RI_RESOURCE_MANAGER),
                            htonl(RI_APPLICATION_INFORMATION),
                            htonl(RI_CONDITIONAL_ACCESS_SUPPORT),
                            htonl(RI_DATE_TIME),
                            htonl(RI_MMI)
                          };
        APDUSend( p_cam, i_session_id, AOT_PROFILE, (uint8_t*)resources,
                  sizeof(resources) );
        break;
    }
    case AOT_PROFILE:
        APDUSend( p_cam, i_session_id, AOT_PROFILE_CHANGE, NULL, 0 );
        break;

    default:
        msg_Err( p_cam->obj, "unexpected tag in ResourceManagerHandle (0x%x)",
                 i_tag );
    }
}

/*****************************************************************************
 * ResourceManagerOpen
 *****************************************************************************/

static void ResourceManagerOpen( cam_t * p_cam, unsigned i_session_id )
{
    msg_Dbg( p_cam->obj, "opening ResourceManager session (%u)",
             i_session_id );
    p_cam->p_sessions[i_session_id - 1].pf_handle = ResourceManagerHandle;
    APDUSend( p_cam, i_session_id, AOT_PROFILE_ENQ, NULL, 0 );
}

/*
 * Application Information
 */


#ifdef ENABLE_HTTPD

/****************************************************************************
 * HTTPExtractValue: Extract a GET variable from psz_request
 ****************************************************************************/

static const char *HTTPExtractValue( const char *psz_uri, const char *psz_name,
                        char *psz_value, int i_value_max )
{
    const char *p = psz_uri;

    while( (p = strstr( p, psz_name )) )
    {
        /* Verify that we are dealing with a post/get argument */
        if( (p == psz_uri || *(p - 1) == '&' || *(p - 1) == '\n')
              && p[strlen(psz_name)] == '=' )
            break;
        p++;
    }

    if( p )
    {
        int i_len;

        p += strlen( psz_name );
        if( *p == '=' ) p++;

        if( strchr( p, '&' ) )
        {
            i_len = strchr( p, '&' ) - p;
        }
        else
        {
            /* for POST method */
            if( strchr( p, '\n' ) )
            {
                i_len = strchr( p, '\n' ) - p;
                if( i_len && *(p+i_len-1) == '\r' ) i_len--;
            }
            else
            {
                i_len = strlen( p );
            }
        }
        i_len = __MIN( i_value_max - 1, i_len );
        if( i_len > 0 )
        {
            strncpy( psz_value, p, i_len );
            psz_value[i_len] = '\0';
        }
        else
        {
            strncpy( psz_value, "", i_value_max );
        }
        p += i_len;
    }
    else
    {
        strncpy( psz_value, "", i_value_max );
    }

    return p;
}

/*****************************************************************************
 * ApplicationInformationEnterMenu
 *****************************************************************************/

static void ApplicationInformationEnterMenu( cam_t * p_cam, int i_session_id )
{
    int i_slot = p_cam->p_sessions[i_session_id - 1].i_slot;

    msg_Dbg( p_cam->obj, "entering MMI menus on session %d", i_session_id );
    APDUSend( p_cam, i_session_id, AOT_ENTER_MENU, NULL, 0 );
    p_cam->pb_slot_mmi_expected[i_slot] = true;
}
#endif

/*****************************************************************************
 * ApplicationInformationHandle
 *****************************************************************************/

static void ApplicationInformationHandle( cam_t * p_cam, int i_session_id,
                                          uint8_t *p_apdu, int i_size )
{
    VLC_UNUSED(i_session_id);
    int i_tag = APDUGetTag( p_apdu, i_size );

    switch ( i_tag )
    {
    case AOT_APPLICATION_INFO:
    {
        int i_type, i_manufacturer, i_code;
        int l = 0;
        uint8_t *d = APDUGetLength( p_apdu, &l );

        if ( l < 4 ) break;
        p_apdu[l + 4] = '\0';

        i_type = *d++;
        i_manufacturer = ((int)d[0] << 8) | d[1];
        d += 2;
        i_code = ((int)d[0] << 8) | d[1];
        d += 2;
        d = GetLength( d, &l );
        d[l] = '\0';
        msg_Info( p_cam->obj, "CAM: %s, %02X, %04X, %04X",
                  d, i_type, i_manufacturer, i_code );
        break;
    }
    default:
        msg_Err( p_cam->obj,
                 "unexpected tag in ApplicationInformationHandle (0x%x)",
                 i_tag );
    }
}

/*****************************************************************************
 * ApplicationInformationOpen
 *****************************************************************************/

static void ApplicationInformationOpen( cam_t * p_cam, unsigned i_session_id )
{
    msg_Dbg( p_cam->obj, "opening ApplicationInformation session (%u)",
             i_session_id );
    p_cam->p_sessions[i_session_id - 1].pf_handle =
        ApplicationInformationHandle;
    APDUSend( p_cam, i_session_id, AOT_APPLICATION_INFO_ENQ, NULL, 0 );
}

/*
 * Conditional Access
 */


#define MAX_CASYSTEM_IDS 64

typedef struct
{
    uint16_t pi_system_ids[MAX_CASYSTEM_IDS + 1];
} system_ids_t;

static bool CheckSystemID( const system_ids_t *p_ids, uint16_t i_id )
{
    int i = 0;
    if( !p_ids ) return true;      /* dummy session for high-level CI intf */

    while ( p_ids->pi_system_ids[i] )
    {
        if ( p_ids->pi_system_ids[i] == i_id )
            return true;
        i++;
    }

    return false;
}

/*****************************************************************************
 * CAPMTNeedsDescrambling
 *****************************************************************************/

static bool CAPMTNeedsDescrambling( const en50221_capmt_info_t *p_info )
{
    if( p_info->p_program_descriptors )
        return true;

    for( size_t i=0; i<p_info->i_es_count; i++ )
    {
        if( p_info->p_es[i].p_descriptors )
            return true;
    }

    return false;
}

/*****************************************************************************
 * CAPMTBuild
 *****************************************************************************/

static size_t CopyDescriptors( const uint8_t *p_drdata, size_t i_drdata,
                               const system_ids_t *p_ids, uint8_t *p_dest )
{
    size_t i_total = 0;
    while( i_drdata > 0 )
    {
        assert( p_drdata[0] == 0x09 );
        uint8_t i_dr_len = p_drdata[1];
        uint16_t i_sysid = GetWBE( &p_drdata[2] );
        if( CheckSystemID( p_ids, i_sysid ) )
        {
            if( p_dest ) /* if p_dest is NULL, just count required space */
                memcpy( &p_dest[i_total], p_drdata, (size_t) i_dr_len + 2 );
            i_total += i_dr_len + 2;
        }
        i_drdata = i_drdata - i_dr_len - 2;
        p_drdata += i_dr_len + 2;
    }
    return i_total;
}

static size_t GetCADSize( const system_ids_t *p_ids,
                          const uint8_t *p_drdata, size_t i_drdata )
{
    return CopyDescriptors( p_drdata, i_drdata, p_ids, NULL );
}

static uint8_t *CAPMTHeader( const en50221_capmt_info_t *p_info,
                             const system_ids_t *p_ids, uint8_t i_list_mgt,
                             size_t i_size, uint8_t i_cmd )
{
    uint8_t *p_data;

    if ( i_size )
        p_data = xmalloc( 7 + i_size );
    else
        p_data = xmalloc( 6 );

    p_data[0] = i_list_mgt;
    p_data[1] = p_info->i_program_number >> 8;
    p_data[2] = p_info->i_program_number & 0xff;
    p_data[3] = ((p_info->i_version & 0x1f) << 1) | 0x1;

    if ( i_size )
    {
        p_data[4] = (i_size + 1) >> 8;
        p_data[5] = (i_size + 1) & 0xff;
        p_data[6] = i_cmd;

        CopyDescriptors( p_info->p_program_descriptors,
                         p_info->i_program_descriptors,
                         p_ids, &p_data[7] );
    }
    else
    {
        p_data[4] = 0;
        p_data[5] = 0;
    }

    return p_data;
}

static uint8_t *CAPMTES( const en50221_capmt_es_info_t *p_es,
                         const system_ids_t *p_ids,
                         size_t i_capmt_size, size_t i_size,
                         uint8_t i_cmd, uint8_t *p_capmt )
{
    uint8_t *p_data;

    if ( i_size )
        p_data = xrealloc( p_capmt, i_capmt_size + 6 + i_size );
    else
        p_data = xrealloc( p_capmt, i_capmt_size + 5 );

    uint8_t *p_dest = &p_data[ i_capmt_size ];

    p_dest[0] = p_es->i_stream_type;
    p_dest[1] = p_es->i_es_pid >> 8;
    p_dest[2] = p_es->i_es_pid & 0xff;

    if ( i_size )
    {
        p_dest[3] = (i_size + 1) >> 8;
        p_dest[4] = (i_size + 1) & 0xff;
        p_dest[5] = i_cmd;

        CopyDescriptors( p_es->p_descriptors,
                         p_es->i_descriptors,
                         p_ids, &p_dest[6] );
    }
    else
    {
        p_dest[3] = 0;
        p_dest[4] = 0;
    }

    return p_data;
}

static uint8_t *CAPMTBuild( cam_t * p_cam, int i_session_id,
                            const en50221_capmt_info_t *p_info,
                            uint8_t i_list_mgt,
                            uint8_t i_cmd, size_t *restrict pi_capmt_size )
{
    system_ids_t *p_ids =
        (system_ids_t *)p_cam->p_sessions[i_session_id - 1].p_sys;
    size_t i_cad_size, i_cad_program_size;
    uint8_t *p_capmt;

    i_cad_size = i_cad_program_size = GetCADSize( p_ids,
                                                  p_info->p_program_descriptors,
                                                  p_info->i_program_descriptors );
    for( size_t i=0; i < p_info->i_es_count; i++ )
    {
        const en50221_capmt_es_info_t *p_es = &p_info->p_es[i];
        i_cad_size += GetCADSize( p_ids, p_es->p_descriptors, p_es->i_descriptors );
    }

    if ( !i_cad_size )
    {
        msg_Warn( p_cam->obj,
                  "no compatible scrambling system for SID %d on session %d",
                  p_info->i_program_number, i_session_id );
        return NULL;
    }

    p_capmt = CAPMTHeader( p_info, p_ids, i_list_mgt,
                           i_cad_program_size, i_cmd );

    if ( i_cad_program_size )
        *pi_capmt_size = 7 + i_cad_program_size;
    else
        *pi_capmt_size = 6;

    for( size_t i=0; i < p_info->i_es_count; i++ )
    {
        const en50221_capmt_es_info_t *p_es = &p_info->p_es[i];
        i_cad_size = GetCADSize( p_ids, p_es->p_descriptors, p_es->i_descriptors );

        if ( i_cad_size || i_cad_program_size )
        {
            p_capmt = CAPMTES( p_es, p_ids, *pi_capmt_size, i_cad_size,
                               i_cmd, p_capmt );
            if ( i_cad_size )
                *pi_capmt_size += 6 + i_cad_size;
            else
                *pi_capmt_size += 5;
        }
    }

    return p_capmt;
}

/*****************************************************************************
 * CAPMTFirst
 *****************************************************************************/

static void CAPMTFirst( cam_t * p_cam, int i_session_id,
                        const en50221_capmt_info_t *p_info )
{
    uint8_t *p_capmt;
    size_t i_capmt_size;

    msg_Dbg( p_cam->obj, "adding first CAPMT for SID %d on session %d",
             p_info->i_program_number, i_session_id );

    p_capmt = CAPMTBuild( p_cam, i_session_id, p_info,
                          0x3 /* only */, 0x1 /* ok_descrambling */,
                          &i_capmt_size );
    if( p_capmt != NULL )
    {
        APDUSend( p_cam, i_session_id, AOT_CA_PMT, p_capmt, i_capmt_size );
        free( p_capmt );
    }
}

/*****************************************************************************
 * CAPMTAdd
 *****************************************************************************/

static void CAPMTAdd( cam_t * p_cam, int i_session_id,
                      const en50221_capmt_info_t *p_info )
{
    uint8_t *p_capmt;
    size_t i_capmt_size;

    if( p_cam->i_selected_programs >= CAM_PROG_MAX )
    {
        msg_Warn( p_cam->obj, "Not adding CAPMT for SID %d, too many programs",
                  p_info->i_program_number );
        return;
    }
    p_cam->i_selected_programs++;
    if( p_cam->i_selected_programs == 1 )
    {
        CAPMTFirst( p_cam, i_session_id, p_info );
        return;
    }

#ifdef CAPMT_WAIT
    vlc_tick_sleep( VLC_TICK_FROM_MS(CAPMT_WAIT) );
#endif

    msg_Dbg( p_cam->obj, "adding CAPMT for SID %d on session %d",
             p_info->i_program_number, i_session_id );

    p_capmt = CAPMTBuild( p_cam, i_session_id, p_info,
                          0x4 /* add */, 0x1 /* ok_descrambling */,
                          &i_capmt_size );
    if( p_capmt != NULL )
    {
        APDUSend( p_cam, i_session_id, AOT_CA_PMT, p_capmt, i_capmt_size );
        free( p_capmt );
    }
}

/*****************************************************************************
 * CAPMTUpdate
 *****************************************************************************/

static void CAPMTUpdate( cam_t * p_cam, int i_session_id,
                         const en50221_capmt_info_t *p_info )
{
    uint8_t *p_capmt;
    size_t i_capmt_size;

    msg_Dbg( p_cam->obj, "updating CAPMT for SID %d on session %d",
             p_info->i_program_number, i_session_id );

    p_capmt = CAPMTBuild( p_cam, i_session_id, p_info,
                          0x5 /* update */, 0x1 /* ok_descrambling */,
                          &i_capmt_size );
    if( p_capmt != NULL )
    {
        APDUSend( p_cam, i_session_id, AOT_CA_PMT, p_capmt, i_capmt_size );
        free( p_capmt );
    }
}

/*****************************************************************************
 * CAPMTDelete
 *****************************************************************************/

static void CAPMTDelete( cam_t * p_cam, int i_session_id,
                         const en50221_capmt_info_t *p_info )
{
    uint8_t *p_capmt;
    size_t i_capmt_size;

    p_cam->i_selected_programs--;
    msg_Dbg( p_cam->obj, "deleting CAPMT for SID %d on session %d",
             p_info->i_program_number, i_session_id );

    p_capmt = CAPMTBuild( p_cam, i_session_id, p_info,
                          0x5 /* update */, 0x4 /* not selected */,
                          &i_capmt_size );
    if( p_capmt != NULL )
    {
        APDUSend( p_cam, i_session_id, AOT_CA_PMT, p_capmt, i_capmt_size );
        free( p_capmt );
    }
}

/*****************************************************************************
 * ConditionalAccessHandle
 *****************************************************************************/

static void ConditionalAccessHandle( cam_t * p_cam, int i_session_id,
                                     uint8_t *p_apdu, int i_size )
{
    system_ids_t *p_ids =
        (system_ids_t *)p_cam->p_sessions[i_session_id - 1].p_sys;
    int i_tag = APDUGetTag( p_apdu, i_size );

    switch ( i_tag )
    {
    case AOT_CA_INFO:
    {
        int i;
        int l = 0;
        uint8_t *d = APDUGetLength( p_apdu, &l );
        msg_Dbg( p_cam->obj, "CA system IDs supported by the application :" );

        for ( i = 0; i < l / 2; i++ )
        {
            p_ids->pi_system_ids[i] = ((uint16_t)d[0] << 8) | d[1];
            d += 2;
            msg_Dbg( p_cam->obj, "- 0x%x", p_ids->pi_system_ids[i] );
        }
        p_ids->pi_system_ids[i] = 0;

        for ( i = 0; i < MAX_PROGRAMS; i++ )
        {
            if ( p_cam->pp_selected_programs[i] != NULL )
            {
                CAPMTAdd( p_cam, i_session_id,
                          p_cam->pp_selected_programs[i] );
            }
        }
        break;
    }

    default:
        msg_Err( p_cam->obj,
                 "unexpected tag in ConditionalAccessHandle (0x%x)",
                 i_tag );
    }
}

/*****************************************************************************
 * ConditionalAccessClose
 *****************************************************************************/

static void ConditionalAccessClose( cam_t * p_cam, int i_session_id )
{
    msg_Dbg( p_cam->obj, "closing ConditionalAccess session (%d)",
             i_session_id );
    free( p_cam->p_sessions[i_session_id - 1].p_sys );
}

/*****************************************************************************
 * ConditionalAccessOpen
 *****************************************************************************/

static void ConditionalAccessOpen( cam_t * p_cam, unsigned i_session_id )
{
    msg_Dbg( p_cam->obj, "opening ConditionalAccess session (%u)",
             i_session_id );
    p_cam->p_sessions[i_session_id - 1].pf_handle = ConditionalAccessHandle;
    p_cam->p_sessions[i_session_id - 1].pf_close = ConditionalAccessClose;
    p_cam->p_sessions[i_session_id - 1].p_sys = calloc( 1, sizeof(system_ids_t) );

    APDUSend( p_cam, i_session_id, AOT_CA_INFO_ENQ, NULL, 0 );
}

/*
 * Date Time
 */


typedef struct
{
    int i_interval;
    vlc_tick_t i_last;
} date_time_t;

/*****************************************************************************
 * DateTimeSend
 *****************************************************************************/

static void DateTimeSend( cam_t * p_cam, int i_session_id )
{
    date_time_t *p_date =
        (date_time_t *)p_cam->p_sessions[i_session_id - 1].p_sys;

    time_t t = time(NULL);
    struct tm tm_gmt;
    struct tm tm_loc;

    if ( gmtime_r(&t, &tm_gmt) && localtime_r(&t, &tm_loc) )
    {
        int Y = tm_gmt.tm_year;
        int M = tm_gmt.tm_mon + 1;
        int D = tm_gmt.tm_mday;
        int L = (M == 1 || M == 2) ? 1 : 0;
        int MJD = 14956 + D + (int)((Y - L) * 365.25)
                    + (int)((M + 1 + L * 12) * 30.6001);
        uint8_t p_response[7];

#define DEC2BCD(d) (((d / 10) << 4) + (d % 10))

        SetWBE( &p_response[0], MJD );
        p_response[2] = DEC2BCD(tm_gmt.tm_hour);
        p_response[3] = DEC2BCD(tm_gmt.tm_min);
        p_response[4] = DEC2BCD(tm_gmt.tm_sec);
        SetWBE( &p_response[5], tm_loc.tm_gmtoff / 60 );

        APDUSend( p_cam, i_session_id, AOT_DATE_TIME, p_response, 7 );

        p_date->i_last = vlc_tick_now();
    }
}

/*****************************************************************************
 * 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
          && vlc_tick_now() > p_date->i_last + vlc_tick_from_sec( p_date->i_interval ) )
    {
        DateTimeSend( p_cam, i_session_id );
    }
}

/*****************************************************************************
 * DateTimeClose
 *****************************************************************************/

static void DateTimeClose( cam_t * p_cam, int i_session_id )
{
    msg_Dbg( p_cam->obj, "closing DateTime session (%d)", i_session_id );

    free( p_cam->p_sessions[i_session_id - 1].p_sys );
}

/*****************************************************************************
 * DateTimeOpen
 *****************************************************************************/

static void DateTimeOpen( cam_t * p_cam, unsigned i_session_id )
{
    msg_Dbg( p_cam->obj, "opening DateTime session (%u)", i_session_id );

    p_cam->p_sessions[i_session_id - 1].pf_handle = DateTimeHandle;
    p_cam->p_sessions[i_session_id - 1].pf_manage = DateTimeManage;
    p_cam->p_sessions[i_session_id - 1].pf_close = DateTimeClose;
    p_cam->p_sessions[i_session_id - 1].p_sys = calloc( 1, sizeof(date_time_t) );

    DateTimeSend( p_cam, i_session_id );
}

/*
 * MMI
 */


/* Display Control Commands */

#define DCC_SET_MMI_MODE                          0x01
#define DCC_DISPLAY_CHARACTER_TABLE_LIST          0x02
#define DCC_INPUT_CHARACTER_TABLE_LIST            0x03
#define DCC_OVERLAY_GRAPHICS_CHARACTERISTICS      0x04
#define DCC_FULL_SCREEN_GRAPHICS_CHARACTERISTICS  0x05

/* MMI Modes */

#define MM_HIGH_LEVEL                      0x01
#define MM_LOW_LEVEL_OVERLAY_GRAPHICS      0x02
#define MM_LOW_LEVEL_FULL_SCREEN_GRAPHICS  0x03

/* Display Reply IDs */

#define DRI_MMI_MODE_ACK                              0x01
#define DRI_LIST_DISPLAY_CHARACTER_TABLES             0x02
#define DRI_LIST_INPUT_CHARACTER_TABLES               0x03
#define DRI_LIST_GRAPHIC_OVERLAY_CHARACTERISTICS      0x04
#define DRI_LIST_FULL_SCREEN_GRAPHIC_CHARACTERISTICS  0x05
#define DRI_UNKNOWN_DISPLAY_CONTROL_CMD               0xF0
#define DRI_UNKNOWN_MMI_MODE                          0xF1
#define DRI_UNKNOWN_CHARACTER_TABLE                   0xF2

/* Enquiry Flags */

#define EF_BLIND  0x01

/* Answer IDs */

#define AI_CANCEL  0x00
#define AI_ANSWER  0x01

static void MMIFree( mmi_t *p_object )
{
    switch ( p_object->i_object_type )
    {
    case EN50221_MMI_ENQ:
        FREENULL( p_object->u.enq.psz_text );
        break;

    case EN50221_MMI_ANSW:
        if ( p_object->u.answ.b_ok )
        {
            FREENULL( p_object->u.answ.psz_answ );
        }
        break;

    case EN50221_MMI_MENU:
    case EN50221_MMI_LIST:
        FREENULL( p_object->u.menu.psz_title );
        FREENULL( p_object->u.menu.psz_subtitle );
        FREENULL( p_object->u.menu.psz_bottom );
        for ( int i = 0; i < p_object->u.menu.i_choices; i++ )
        {
            free( p_object->u.menu.ppsz_choices[i] );
        }
        FREENULL( p_object->u.menu.ppsz_choices );
        break;

    default:
        break;
    }
}

#ifdef ENABLE_HTTPD
/*****************************************************************************
 * MMISendObject
 *****************************************************************************/

static void MMISendObject( cam_t *p_cam, int i_session_id,
                           mmi_t *p_object )
{
    int i_slot = p_cam->p_sessions[i_session_id - 1].i_slot;
    uint8_t *p_data;
    int i_size, i_tag;

    switch ( p_object->i_object_type )
    {
    case EN50221_MMI_ANSW:
        i_tag = AOT_ANSW;
        i_size = 1 + strlen( p_object->u.answ.psz_answ );
        p_data = xmalloc( i_size );
        p_data[0] = p_object->u.answ.b_ok ? 0x1 : 0x0;
        strncpy( (char *)&p_data[1], p_object->u.answ.psz_answ, i_size - 1 );
        break;

    case EN50221_MMI_MENU_ANSW:
        i_tag = AOT_MENU_ANSW;
        i_size = 1;
        p_data = xmalloc( i_size );
        p_data[0] = p_object->u.menu_answ.i_choice;
        break;

    default:
        msg_Err( p_cam->obj, "unknown MMI object %d", p_object->i_object_type );
        return;
    }

    APDUSend( p_cam, i_session_id, i_tag, p_data, i_size );
    free( p_data );

    p_cam->pb_slot_mmi_expected[i_slot] = true;
}

/*****************************************************************************
 * MMISendClose
 *****************************************************************************/

static void MMISendClose( cam_t *p_cam, int i_session_id )
{
    int i_slot = p_cam->p_sessions[i_session_id - 1].i_slot;

    APDUSend( p_cam, i_session_id, AOT_CLOSE_MMI, NULL, 0 );

    p_cam->pb_slot_mmi_expected[i_slot] = true;
}
#endif

/*****************************************************************************
 * MMIDisplayReply
 *****************************************************************************/

static void MMIDisplayReply( cam_t *p_cam, int i_session_id )
{
    uint8_t p_response[2];

    p_response[0] = DRI_MMI_MODE_ACK;
    p_response[1] = MM_HIGH_LEVEL;

    APDUSend( p_cam, i_session_id, AOT_DISPLAY_REPLY, p_response, 2 );

    msg_Dbg( p_cam->obj, "sending DisplayReply on session (%d)", i_session_id );
}

/*****************************************************************************
 * MMIGetText
 *****************************************************************************/

static char *MMIGetText( cam_t *p_cam, uint8_t **pp_apdu, int *pi_size )
{
    int i_tag = APDUGetTag( *pp_apdu, *pi_size );
    int l;
    uint8_t *d;

    if ( i_tag != AOT_TEXT_LAST )
    {
        msg_Err( p_cam->obj, "unexpected text tag: %06x", i_tag );
        *pi_size = 0;
        return strdup( "" );
    }

    d = APDUGetLength( *pp_apdu, &l );

    *pp_apdu += l + 4;
    *pi_size -= l + 4;

    return vlc_from_EIT(d,l);
}

/*****************************************************************************
 * MMIHandleEnq
 *****************************************************************************/

static void MMIHandleEnq( cam_t *p_cam, int i_session_id,
                          uint8_t *p_apdu, int i_size )
{
    VLC_UNUSED( i_size );

    mmi_t *p_mmi = (mmi_t *)p_cam->p_sessions[i_session_id - 1].p_sys;
    int i_slot = p_cam->p_sessions[i_session_id - 1].i_slot;
    int l;
    uint8_t *d = APDUGetLength( p_apdu, &l );

    MMIFree( p_mmi );
    p_mmi->i_object_type = EN50221_MMI_ENQ;
    p_mmi->u.enq.b_blind = (*d & 0x1) ? true : false;
    d += 2; /* skip answer_text_length because it is not mandatory */
    l -= 2;
    p_mmi->u.enq.psz_text = xmalloc( l + 1 );
    strncpy( p_mmi->u.enq.psz_text, (char *)d, l );
    p_mmi->u.enq.psz_text[l] = '\0';

    msg_Dbg( p_cam->obj, "MMI enq: %s%s", p_mmi->u.enq.psz_text,
             p_mmi->u.enq.b_blind ? " (blind)" : "" );
    p_cam->pb_slot_mmi_expected[i_slot] = false;
    p_cam->pb_slot_mmi_undisplayed[i_slot] = true;
}

/*****************************************************************************
 * MMIHandleMenu
 *****************************************************************************/

static void MMIHandleMenu( cam_t *p_cam, int i_session_id, int i_tag,
                           uint8_t *p_apdu, int i_size )
{
    VLC_UNUSED(i_size);

    mmi_t *p_mmi = (mmi_t *)p_cam->p_sessions[i_session_id - 1].p_sys;
    int i_slot = p_cam->p_sessions[i_session_id - 1].i_slot;
    int l;
    uint8_t *d = APDUGetLength( p_apdu, &l );

    MMIFree( p_mmi );
    p_mmi->i_object_type = (i_tag == AOT_MENU_LAST) ?
                                       EN50221_MMI_MENU : EN50221_MMI_LIST;
    p_mmi->u.menu.i_choices = 0;
    p_mmi->u.menu.ppsz_choices = NULL;

    if ( l > 0 )
    {
        l--; d++; /* choice_nb */

#define GET_FIELD( x )                                                      \
        if ( l > 0 )                                                        \
        {                                                                   \
            p_mmi->u.menu.psz_##x = MMIGetText( p_cam, &d, &l );            \
            msg_Dbg( p_cam->obj, "MMI " STRINGIFY( x ) ": %s",              \
                     p_mmi->u.menu.psz_##x );                               \
        }

        GET_FIELD( title );
        GET_FIELD( subtitle );
        GET_FIELD( bottom );
#undef GET_FIELD

        while ( l > 0 )
        {
            char *psz_text = MMIGetText( p_cam, &d, &l );
            TAB_APPEND( p_mmi->u.menu.i_choices,
                        p_mmi->u.menu.ppsz_choices,
                        psz_text );
            msg_Dbg( p_cam->obj, "MMI choice: %s", psz_text );
        }
    }
    p_cam->pb_slot_mmi_expected[i_slot] = false;
    p_cam->pb_slot_mmi_undisplayed[i_slot] = true;
}

/*****************************************************************************
 * MMIHandle
 *****************************************************************************/

static void MMIHandle( cam_t *p_cam, int i_session_id,
                       uint8_t *p_apdu, int i_size )
{
    int i_tag = APDUGetTag( p_apdu, i_size );

    switch ( i_tag )
    {
    case AOT_DISPLAY_CONTROL:
    {
        int l;
        uint8_t *d = APDUGetLength( p_apdu, &l );

        if ( l > 0 )
        {
            switch ( *d )
            {
            case DCC_SET_MMI_MODE:
                if ( l == 2 && d[1] == MM_HIGH_LEVEL )
                    MMIDisplayReply( p_cam, i_session_id );
                else
                    msg_Err( p_cam->obj, "unsupported MMI mode %02x", d[1] );
                break;

            default:
                msg_Err( p_cam->obj, "unsupported display control command %02x",
                         *d );
                break;
            }
        }
        break;
    }

    case AOT_ENQ:
        MMIHandleEnq( p_cam, i_session_id, p_apdu, i_size );
        break;

    case AOT_LIST_LAST:
    case AOT_MENU_LAST:
        MMIHandleMenu( p_cam, i_session_id, i_tag, p_apdu, i_size );
        break;

    case AOT_CLOSE_MMI:
        SessionSendClose( p_cam, i_session_id );
        break;

    default:
        msg_Err( p_cam->obj, "unexpected tag in MMIHandle (0x%x)", i_tag );
    }
}

/*****************************************************************************
 * MMIClose
 *****************************************************************************/

static void MMIClose( cam_t *p_cam, int i_session_id )
{
    int i_slot = p_cam->p_sessions[i_session_id - 1].i_slot;
    mmi_t *p_mmi = (mmi_t *)p_cam->p_sessions[i_session_id - 1].p_sys;

    MMIFree( p_mmi );
    free( p_cam->p_sessions[i_session_id - 1].p_sys );

    msg_Dbg( p_cam->obj, "closing MMI session (%d)", i_session_id );
    p_cam->pb_slot_mmi_expected[i_slot] = false;
    p_cam->pb_slot_mmi_undisplayed[i_slot] = true;
}

/*****************************************************************************
 * MMIOpen
 *****************************************************************************/

static void MMIOpen( cam_t *p_cam, unsigned i_session_id )
{
    mmi_t *p_mmi;

    msg_Dbg( p_cam->obj, "opening MMI session (%u)", i_session_id );

    p_cam->p_sessions[i_session_id - 1].pf_handle = MMIHandle;
    p_cam->p_sessions[i_session_id - 1].pf_close = MMIClose;
    p_cam->p_sessions[i_session_id - 1].p_sys = xmalloc(sizeof(mmi_t));
    p_mmi = (mmi_t *)p_cam->p_sessions[i_session_id - 1].p_sys;
    p_mmi->i_object_type = EN50221_MMI_NONE;
}


/*
 * Hardware handling
 */


/*****************************************************************************
 * InitSlot: Open the transport layer
 *****************************************************************************/

#define MAX_TC_RETRIES 20

static int InitSlot( cam_t * p_cam, int i_slot )
{
    if ( TPDUSend( p_cam, i_slot, T_CREATE_TC, NULL, 0 ) != VLC_SUCCESS )
    {
        msg_Err( p_cam->obj, "en50221_Init: couldn't send TPDU on slot %d",
                 i_slot );
        return VLC_EGENERIC;
    }

    /* This is out of the spec */
    for ( int i = 0; i < MAX_TC_RETRIES; i++ )
    {
        uint8_t i_tag;
        if ( TPDURecv( p_cam, i_slot, &i_tag, NULL, NULL ) == VLC_SUCCESS
              && i_tag == T_CTC_REPLY )
        {
            p_cam->pb_active_slot[i_slot] = true;
            break;
        }

        if ( TPDUSend( p_cam, i_slot, T_CREATE_TC, NULL, 0 )
                != VLC_SUCCESS )
        {
            msg_Err( p_cam->obj,
                     "en50221_Init: couldn't send TPDU on slot %d",
                     i_slot );
            continue;
        }
    }

    if ( p_cam->pb_active_slot[i_slot] )
    {
        p_cam->i_timeout = VLC_TICK_FROM_MS(100);
        return VLC_SUCCESS;
    }

    return VLC_EGENERIC;
}


/*
 * External entry points
 */


/*****************************************************************************
 * en50221_Init : Initialize the CAM for en50221
 *****************************************************************************/

cam_t *en50221_Init( vlc_object_t *obj, int fd )
{
    ca_caps_t caps;

    memset( &caps, 0, sizeof( caps ));
    if( ioctl( fd, CA_GET_CAP, &caps ) < 0 )
    {
        msg_Err( obj, "CAMInit: ioctl() error getting CAM capabilities" );
        return NULL;
    }

    /* Output CA capabilities */
    msg_Dbg( obj, "CA interface with %d slot(s)", caps.slot_num );
    if( caps.slot_type & CA_CI )
        msg_Dbg( obj, " CI high level interface type" );
    if( caps.slot_type & CA_CI_LINK )
        msg_Dbg( obj, " CI link layer level interface type" );
    if( caps.slot_type & CA_CI_PHYS )
        msg_Dbg( obj, " CI physical layer level interface type (not supported) " );
    if( caps.slot_type & CA_DESCR )
        msg_Dbg( obj, " built-in descrambler detected" );
    if( caps.slot_type & CA_SC )
        msg_Dbg( obj, " simple smart card interface" );

    msg_Dbg( obj, "%d available descrambler(s) (keys)", caps.descr_num );
    if( caps.descr_type & CA_ECD )
        msg_Dbg( obj, " ECD scrambling system supported" );
    if( caps.descr_type & CA_NDS )
        msg_Dbg( obj, " NDS scrambling system supported" );
    if( caps.descr_type & CA_DSS )
        msg_Dbg( obj, " DSS scrambling system supported" );

    if( caps.slot_num == 0 )
    {
        msg_Err( obj, "CAM module without slots" );
        return NULL;
    }

    cam_t *p_cam = calloc( 1, sizeof( *p_cam ) );
    if( unlikely(p_cam == NULL) )
        goto error;

    p_cam->obj = obj;
    p_cam->fd = fd;
    p_cam->i_nb_slots = caps.slot_num;

    if( caps.slot_type & CA_CI_LINK )
    {
        p_cam->i_ca_type = CA_CI_LINK;

        for ( unsigned i_slot = 0; i_slot < p_cam->i_nb_slots; i_slot++ )
        {
            if ( ioctl( p_cam->fd, CA_RESET, 1 << i_slot) != 0 )
            {
                msg_Err( p_cam->obj, "en50221_Init: couldn't reset slot %d",
                         i_slot );
            }
        }

        p_cam->i_timeout = VLC_TICK_FROM_MS(100);
        /* Wait a bit otherwise it doesn't initialize properly... */
        vlc_tick_sleep( VLC_TICK_FROM_MS(100) );
        p_cam->i_next_event = 0;
    }
    else
    if( caps.slot_type & CA_CI )
    {
        p_cam->i_ca_type = CA_CI;

        struct ca_slot_info info;
        info.num = 0;
        /* We don't reset the CAM in that case because it's done by the
         * ASIC. */

        if ( ioctl( fd, CA_GET_SLOT_INFO, &info ) < 0 )
        {
            msg_Err( obj, "cannot get slot info: %s", vlc_strerror_c(errno) );
            goto error;
        }
        if( info.flags == 0 )
        {
            msg_Err( obj, "no CAM inserted" );
            goto error;
        }

        /* Allocate a dummy sessions */
        p_cam->p_sessions[ 0 ].i_resource_id = RI_CONDITIONAL_ACCESS_SUPPORT;

        /* Get application info to find out which cam we are using and make
           sure everything is ready to play */

        ca_msg_t ca_msg;
        ca_msg.length=3;
        ca_msg.msg[0] = ( AOT_APPLICATION_INFO & 0xFF0000 ) >> 16;
        ca_msg.msg[1] = ( AOT_APPLICATION_INFO & 0x00FF00 ) >> 8;
        ca_msg.msg[2] = ( AOT_APPLICATION_INFO & 0x0000FF ) >> 0;
        memset( &ca_msg.msg[3], 0, 253 );
        APDUSend( p_cam, 1, AOT_APPLICATION_INFO_ENQ, NULL, 0 );
        if ( ioctl( fd, CA_GET_MSG, &ca_msg ) < 0 )
        {
            msg_Err( obj, "en50221_Init: failed getting message" );
            goto error;
        }

        if( ca_msg.msg[8] == 0xff && ca_msg.msg[9] == 0xff )
        {
            msg_Err( obj, "CAM returns garbage as application info!" );
            goto error;
        }
        msg_Dbg( obj, "found CAM %s using id 0x%x", &ca_msg.msg[12],
                 (ca_msg.msg[8]<<8)|ca_msg.msg[9] );
    }
    else
    {
        msg_Err( obj, "CAM interface incompatible" );
        goto error;
    }
    return p_cam;

error:
    free( p_cam );
    return NULL;
}


/*****************************************************************************
 * en50221_Poll : Poll the CAM for TPDUs
 *****************************************************************************/

void en50221_Poll( cam_t * p_cam )
{
    switch( p_cam->i_ca_type )
    {
    case CA_CI_LINK:
        if( vlc_tick_now() > p_cam->i_next_event )
            break;
    case CA_CI:
        return;
    default:
        vlc_assert_unreachable();
    }

    for ( unsigned i_slot = 0; i_slot < p_cam->i_nb_slots; i_slot++ )
    {
        uint8_t i_tag;
        ca_slot_info_t sinfo;

        sinfo.num = i_slot;
        if ( ioctl( p_cam->fd, CA_GET_SLOT_INFO, &sinfo ) != 0 )
        {
            msg_Err( p_cam->obj, "en50221_Poll: couldn't get info on slot %d",
                     i_slot );
            continue;
        }

        if ( !(sinfo.flags & CA_CI_MODULE_READY) )
        {
            if ( p_cam->pb_active_slot[i_slot] )
            {
                msg_Dbg( p_cam->obj, "en50221_Poll: slot %d has been removed",
                         i_slot );
                p_cam->pb_active_slot[i_slot] = false;
                p_cam->pb_slot_mmi_expected[i_slot] = false;
                p_cam->pb_slot_mmi_undisplayed[i_slot] = false;

                /* Close all sessions for this slot. */
                for ( unsigned i = 1; i <= MAX_SESSIONS; i++ )
                {
                    if ( p_cam->p_sessions[i - 1].i_resource_id
                          && p_cam->p_sessions[i - 1].i_slot == i_slot )
                    {
                        if ( p_cam->p_sessions[i - 1].pf_close != NULL )
                        {
                            p_cam->p_sessions[i - 1].pf_close( p_cam, i );
                        }
                        p_cam->p_sessions[i - 1].i_resource_id = 0;
                    }
                }
            }

            continue;
        }
        else if ( !p_cam->pb_active_slot[i_slot] )
        {
            InitSlot( p_cam, i_slot );

            if ( !p_cam->pb_active_slot[i_slot] )
            {
                msg_Dbg( p_cam->obj, "en50221_Poll: resetting slot %d", i_slot );

                if ( ioctl( p_cam->fd, CA_RESET, 1 << i_slot) != 0 )
                {
                    msg_Err( p_cam->obj, "en50221_Poll: couldn't reset slot %d",
                             i_slot );
                }
                continue;
            }

            msg_Dbg( p_cam->obj, "en50221_Poll: slot %d is active",
                     i_slot );
        }

        if ( !p_cam->pb_tc_has_data[i_slot] )
        {
            if ( TPDUSend( p_cam, i_slot, T_DATA_LAST, NULL, 0 ) !=
                    VLC_SUCCESS )
            {
                msg_Err( p_cam->obj,
                         "en50221_Poll: couldn't send TPDU on slot %d",
                         i_slot );
                continue;
            }
            if ( TPDURecv( p_cam, i_slot, &i_tag, NULL, NULL ) !=
                    VLC_SUCCESS )
            {
                msg_Err( p_cam->obj,
                         "en50221_Poll: couldn't recv TPDU on slot %d",
                         i_slot );
                continue;
            }
        }

        while ( p_cam->pb_tc_has_data[i_slot] )
        {
            uint8_t p_tpdu[MAX_TPDU_SIZE];
            int i_size, i_session_size;
            uint8_t *p_session;

            if ( TPDUSend( p_cam, i_slot, T_RCV, NULL, 0 ) != VLC_SUCCESS )
            {
                msg_Err( p_cam->obj,
                         "en50221_Poll: couldn't send TPDU on slot %d",
                         i_slot );
                continue;
            }
            if ( TPDURecv( p_cam, i_slot, &i_tag, p_tpdu, &i_size ) !=
                    VLC_SUCCESS )
            {
                msg_Err( p_cam->obj,
                         "en50221_Poll: couldn't recv TPDU on slot %d",
                         i_slot );
                continue;
            }

            p_session = GetLength( &p_tpdu[3], &i_session_size );
            if ( i_session_size <= 1 )
                continue;

            p_session++;
            i_session_size--;

            if ( i_tag != T_DATA_LAST )
            {
                msg_Err( p_cam->obj,
                         "en50221_Poll: fragmented TPDU not supported" );
                break;
            }

            SPDUHandle( p_cam, i_slot, p_session, i_session_size );
        }
    }

    for ( int i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
    {
        if ( p_cam->p_sessions[i_session_id - 1].i_resource_id
              && p_cam->p_sessions[i_session_id - 1].pf_manage )
        {
            p_cam->p_sessions[i_session_id - 1].pf_manage( p_cam,
                                                           i_session_id );
        }
    }

    p_cam->i_next_event = vlc_tick_now() + p_cam->i_timeout;
}


/*****************************************************************************
 * en50221_SetCAPMT :
 *****************************************************************************/

int en50221_SetCAPMT( cam_t * p_cam, const en50221_capmt_info_t *p_info )
{
    bool b_update = false;
    bool b_needs_descrambling = CAPMTNeedsDescrambling( p_info );
    en50221_capmt_info_t *p_oldinfo = NULL;

    for ( unsigned i = 0; i < MAX_PROGRAMS; i++ )
    {
        if ( p_cam->pp_selected_programs[i] != NULL
              && p_cam->pp_selected_programs[i]->i_program_number
                  == p_info->i_program_number )
        {
            b_update = true;

            /* Existing scrambled program went clear now */
            if ( !b_needs_descrambling )
            {
                p_oldinfo = p_cam->pp_selected_programs[i];
                p_cam->pp_selected_programs[i] = NULL;
            }
            /* Existing scrambled program has been updated */
            else if( p_info != p_cam->pp_selected_programs[i] )
            {
                en50221_capmt_Delete( p_cam->pp_selected_programs[i] );
                p_cam->pp_selected_programs[i] = en50221_capmt_Duplicate( p_info );
            }

            break;
        }
    }

    /* New selection of scrambled program */
    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] = en50221_capmt_Duplicate( p_info );
                break;
            }
        }
    }

    /* Serialization of CAPMT commands to CI */
    if ( b_update || b_needs_descrambling )
    {
        for ( unsigned i = 1; i <= MAX_SESSIONS; i++ )
        {
            if ( p_cam->p_sessions[i - 1].i_resource_id
                    == RI_CONDITIONAL_ACCESS_SUPPORT )
            {
                if ( b_update && b_needs_descrambling )
                    CAPMTUpdate( p_cam, i, p_info );
                else if ( b_update )
                    CAPMTDelete( p_cam, i, p_oldinfo );
                else
                    CAPMTAdd( p_cam, i, p_info );
            }
        }
    }

    if( p_oldinfo )
        en50221_capmt_Delete( p_oldinfo );

    return VLC_SUCCESS;
}

#ifdef ENABLE_HTTPD
/*****************************************************************************
 * en50221_OpenMMI :
 *****************************************************************************/

static int en50221_OpenMMI( cam_t * p_cam, unsigned i_slot )
{
    if( p_cam->i_ca_type & CA_CI_LINK )
    {
        for ( unsigned i = 1; i <= MAX_SESSIONS; i++ )
        {
            if ( p_cam->p_sessions[i - 1].i_resource_id == RI_MMI
                  && p_cam->p_sessions[i - 1].i_slot == i_slot )
            {
                msg_Dbg( p_cam->obj,
                         "MMI menu is already opened on slot %d (session=%u)",
                         i_slot, i );
                return VLC_SUCCESS;
            }
        }

        for ( unsigned i = 1; i <= MAX_SESSIONS; i++ )
        {
            if ( p_cam->p_sessions[i - 1].i_resource_id
                    == RI_APPLICATION_INFORMATION
                  && p_cam->p_sessions[i - 1].i_slot == i_slot )
            {
                ApplicationInformationEnterMenu( p_cam, i );
                return VLC_SUCCESS;
            }
        }

        msg_Err( p_cam->obj, "no application information on slot %d", i_slot );
        return VLC_EGENERIC;
    }
    else
    {
        msg_Err( p_cam->obj, "MMI menu not supported" );
        return VLC_EGENERIC;
    }
}

/*****************************************************************************
 * en50221_CloseMMI :
 *****************************************************************************/

static int en50221_CloseMMI( cam_t * p_cam, unsigned i_slot )
{
    if( p_cam->i_ca_type & CA_CI_LINK )
    {
        for( unsigned i = 1; i <= MAX_SESSIONS; i++ )
        {
            if ( p_cam->p_sessions[i - 1].i_resource_id == RI_MMI
                  && p_cam->p_sessions[i - 1].i_slot == i_slot )
            {
                MMISendClose( p_cam, i );
                return VLC_SUCCESS;
            }
        }

        msg_Warn( p_cam->obj, "closing a non-existing MMI session on slot %d",
                  i_slot );
        return VLC_EGENERIC;
    }
    else
    {
        msg_Err( p_cam->obj, "MMI menu not supported" );
        return VLC_EGENERIC;
    }
}

/*****************************************************************************
 * en50221_GetMMIObject :
 *****************************************************************************/

static mmi_t *en50221_GetMMIObject( cam_t * p_cam, unsigned i_slot )
{
    if( p_cam->pb_slot_mmi_expected[i_slot] )
        return NULL; /* should not happen */

    for( unsigned i = 1; i <= MAX_SESSIONS; i++ )
    {
        if ( p_cam->p_sessions[i - 1].i_resource_id == RI_MMI
              && p_cam->p_sessions[i - 1].i_slot == i_slot )
        {
            mmi_t *p_mmi =
                (mmi_t *)p_cam->p_sessions[i - 1].p_sys;
            if ( p_mmi == NULL )
                return NULL; /* should not happen */
            return p_mmi;
        }
    }

    return NULL;
}


/*****************************************************************************
 * en50221_SendMMIObject :
 *****************************************************************************/

static void en50221_SendMMIObject( cam_t * p_cam, unsigned i_slot,
                                   mmi_t *p_object )
{
    for( unsigned i = 1; i <= MAX_SESSIONS; i++ )
    {
        if ( p_cam->p_sessions[i - 1].i_resource_id == RI_MMI
              && p_cam->p_sessions[i - 1].i_slot == i_slot )
        {
            MMISendObject( p_cam, i, p_object );
            return;
        }
    }

    msg_Err( p_cam->obj, "SendMMIObject when no MMI session is opened !" );
}

char *en50221_Status( cam_t *p_cam, char *psz_request )
{
    if( psz_request != NULL && *psz_request )
    {
        /* Check if we have an undisplayed MMI message : in that case we ignore
         * the user input to avoid confusing the CAM. */

        for ( unsigned i_slot = 0; i_slot < p_cam->i_nb_slots; i_slot++ )
        {
            if ( p_cam->pb_slot_mmi_undisplayed[i_slot] )
            {
                psz_request = NULL;
                msg_Dbg( p_cam->obj,
                         "ignoring user request because of a new MMI object" );
                break;
            }
        }
    }

    if( psz_request != NULL && *psz_request )
    {
        /* We have a mission to accomplish. */
        mmi_t mmi_object;
        char psz_value[255];
        int i_slot;
        bool b_ok = false;

        if ( HTTPExtractValue( psz_request, "slot", psz_value,
                                   sizeof(psz_value) ) == NULL )
        {
            return strdup( "invalid request parameter\n" );
        }
        i_slot = atoi(psz_value);

        if ( HTTPExtractValue( psz_request, "open", psz_value,
                                   sizeof(psz_value) ) != NULL )
        {
            en50221_OpenMMI( p_cam, i_slot );
            return NULL;
        }

        if ( HTTPExtractValue( psz_request, "close", psz_value,
                                   sizeof(psz_value) ) != NULL )
        {
            en50221_CloseMMI( p_cam, i_slot );
            return NULL;
        }

        if ( HTTPExtractValue( psz_request, "cancel", psz_value,
                                   sizeof(psz_value) ) == NULL )
        {
            b_ok = true;
        }

        if ( HTTPExtractValue( psz_request, "type", psz_value,
                                   sizeof(psz_value) ) == NULL )
        {
            return strdup( "invalid request parameter\n" );
        }

        if ( !strcmp( psz_value, "enq" ) )
        {
            mmi_object.i_object_type = EN50221_MMI_ANSW;
            mmi_object.u.answ.b_ok = b_ok;
            if ( !b_ok )
            {
                mmi_object.u.answ.psz_answ = strdup("");
            }
            else
            {
                if ( HTTPExtractValue( psz_request, "answ", psz_value,
                                           sizeof(psz_value) ) == NULL )
                {
                    return strdup( "invalid request parameter\n" );
                }

                mmi_object.u.answ.psz_answ = strdup(psz_value);
            }
        }
        else
        {
            mmi_object.i_object_type = EN50221_MMI_MENU_ANSW;
            if ( !b_ok )
            {
                mmi_object.u.menu_answ.i_choice = 0;
            }
            else
            {
                if ( HTTPExtractValue( psz_request, "choice", psz_value,
                                           sizeof(psz_value) ) == NULL )
                    mmi_object.u.menu_answ.i_choice = 0;
                else
                    mmi_object.u.menu_answ.i_choice = atoi(psz_value);
            }
        }

        en50221_SendMMIObject( p_cam, i_slot, &mmi_object );
        return NULL;
    }

    /* Check that we have all necessary MMI information. */
    for( unsigned i_slot = 0; i_slot < p_cam->i_nb_slots; i_slot++ )
    {
        if ( p_cam->pb_slot_mmi_expected[i_slot] )
            return NULL;
    }

    char *buf;
    size_t len;
    FILE *p = open_memstream( &buf, &len );
    if( unlikely(p == NULL) )
        return NULL;

    ca_caps_t caps;

    if( ioctl( p_cam->fd, CA_GET_CAP, &caps ) < 0 )
    {
        fprintf( p, "ioctl(CA_GET_CAP) failed: %s\n", vlc_strerror_c(errno) );
        goto out;
    }

    /* Output CA capabilities */
    fprintf( p, "CA interface with %d %s, type:\n<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: %s<br>\n",
                     vlc_strerror_c(errno) );
            continue;
        }

#define CHECK_TYPE( x, s )                                                  \
        if ( sinfo.type & (CA_##x) )                                        \
            fputs( s, p )

        CHECK_TYPE( CI, "high level, " );
        CHECK_TYPE( CI_LINK, "link layer level, " );
        CHECK_TYPE( CI_PHYS, "physical layer level, " );
#undef CHECK_TYPE

        if ( sinfo.flags & CA_CI_MODULE_READY )
        {
            mmi_t *p_object = en50221_GetMMIObject( p_cam, i_slot );

            fputs( "module present and ready<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 )
        {
            en50221_capmt_Delete( p_cam->pp_selected_programs[i] );
        }
    }

    for( unsigned i = 1; i <= MAX_SESSIONS; i++ )
    {
        if( p_cam->p_sessions[i - 1].i_resource_id
              && p_cam->p_sessions[i - 1].pf_close != NULL )
        {
            p_cam->p_sessions[i - 1].pf_close( p_cam, i );
        }
    }

    vlc_close( p_cam->fd );
    free( p_cam );
}

Messung
CHZG
85969993

¤ Dauer der Verarbeitung: 0.34 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104856
#Domains=417349