]> git.sesse.net Git - vlc/blob - modules/access/dvb/en50221.c
f2a65c8b15f052aa64f295c90fec1f757e474e4f
[vlc] / modules / access / dvb / en50221.c
1 /*****************************************************************************
2  * en50221.c : implementation of the transport, session and applications
3  * layers of EN 50 221
4  *****************************************************************************
5  * Copyright (C) 2004 VideoLAN
6  *
7  * Authors: Christophe Massiot <massiot@via.ecp.fr>
8  * Based on code from libdvbci Copyright (C) 2000 Klaus Schmidinger
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA    02111, USA.
23  *****************************************************************************/
24
25 #include <vlc/vlc.h>
26 #include <vlc/input.h>
27
28 #include <sys/ioctl.h>
29 #include <errno.h>
30
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <fcntl.h>
34 #include <time.h>
35 #include <unistd.h>
36 #include <sys/stat.h>
37 #include <sys/poll.h>
38
39 /* DVB Card Drivers */
40 #include <linux/dvb/version.h>
41 #include <linux/dvb/dmx.h>
42 #include <linux/dvb/frontend.h>
43 #include <linux/dvb/ca.h>
44
45 #include "dvb.h"
46
47 #undef DEBUG_TPDU
48
49 static void ResourceManagerOpen( access_t * p_access, int i_session_id );
50 static void ApplicationInformationOpen( access_t * p_access, int i_session_id );
51 static void ConditionalAccessOpen( access_t * p_access, int i_session_id );
52 static void DateTimeOpen( access_t * p_access, int i_session_id );
53 static void MMIOpen( access_t * p_access, int i_session_id );
54
55 /*****************************************************************************
56  * Utility functions
57  *****************************************************************************/
58 #define SIZE_INDICATOR 0x80
59
60 static uint8_t *GetLength( uint8_t *p_data, int *pi_length )
61 {
62     *pi_length = *p_data++;
63
64     if ( (*pi_length & SIZE_INDICATOR) != 0 )
65     {
66         int l = *pi_length & ~SIZE_INDICATOR;
67         int i;
68
69         *pi_length = 0;
70         for ( i = 0; i < l; i++ )
71             *pi_length = (*pi_length << 8) | *p_data++;
72     }
73
74     return p_data;
75 }
76
77 static uint8_t *SetLength( uint8_t *p_data, int i_length )
78 {
79     uint8_t *p = p_data;
80
81     if ( i_length < 128 )
82     {
83         *p++ = i_length;
84     }
85     else if ( i_length < 256 )
86     {
87         *p++ = SIZE_INDICATOR | 0x1;
88         *p++ = i_length;
89     }
90     else if ( i_length < 65536 )
91     {
92         *p++ = SIZE_INDICATOR | 0x2;
93         *p++ = i_length >> 8;
94         *p++ = i_length & 0xff;
95     }
96     else if ( i_length < 16777216 )
97     {
98         *p++ = SIZE_INDICATOR | 0x3;
99         *p++ = i_length >> 16;
100         *p++ = (i_length >> 8) & 0xff;
101         *p++ = i_length & 0xff;
102     }
103     else
104     {
105         *p++ = SIZE_INDICATOR | 0x4;
106         *p++ = i_length >> 24;
107         *p++ = (i_length >> 16) & 0xff;
108         *p++ = (i_length >> 8) & 0xff;
109         *p++ = i_length & 0xff;
110     }
111
112     return p;
113 }
114
115
116 /*
117  * Transport layer
118  */
119
120 #define MAX_TPDU_SIZE  2048
121 #define MAX_TPDU_DATA  (MAX_TPDU_SIZE - 4)
122
123 #define DATA_INDICATOR 0x80
124
125 #define T_SB           0x80
126 #define T_RCV          0x81
127 #define T_CREATE_TC    0x82
128 #define T_CTC_REPLY    0x83
129 #define T_DELETE_TC    0x84
130 #define T_DTC_REPLY    0x85
131 #define T_REQUEST_TC   0x86
132 #define T_NEW_TC       0x87
133 #define T_TC_ERROR     0x88
134 #define T_DATA_LAST    0xA0
135 #define T_DATA_MORE    0xA1
136
137 static void Dump( vlc_bool_t b_outgoing, uint8_t *p_data, int i_size )
138 {
139 #ifdef DEBUG_TPDU
140     int i;
141 #define MAX_DUMP 256
142     fprintf(stderr, "%s ", b_outgoing ? "-->" : "<--");
143     for ( i = 0; i < i_size && i < MAX_DUMP; i++)
144         fprintf(stderr, "%02X ", p_data[i]);
145     fprintf(stderr, "%s\n", i_size >= MAX_DUMP ? "..." : "");
146 #endif
147 }
148
149 /*****************************************************************************
150  * TPDUSend
151  *****************************************************************************/
152 static int TPDUSend( access_t * p_access, uint8_t i_slot, uint8_t i_tag,
153                      const uint8_t *p_content, int i_length )
154 {
155     access_sys_t *p_sys = p_access->p_sys;
156     uint8_t i_tcid = i_slot + 1;
157     uint8_t p_data[MAX_TPDU_SIZE];
158     int i_size;
159
160     i_size = 0;
161     p_data[0] = i_slot;
162     p_data[1] = i_tcid;
163     p_data[2] = i_tag;
164
165     switch ( i_tag )
166     {
167     case T_RCV:
168     case T_CREATE_TC:
169     case T_CTC_REPLY:
170     case T_DELETE_TC:
171     case T_DTC_REPLY:
172     case T_REQUEST_TC:
173         p_data[3] = 1; /* length */
174         p_data[4] = i_tcid;
175         i_size = 5;
176         break;
177
178     case T_NEW_TC:
179     case T_TC_ERROR:
180         p_data[3] = 2; /* length */
181         p_data[4] = i_tcid;
182         p_data[5] = p_content[0];
183         i_size = 6;
184         break;
185
186     case T_DATA_LAST:
187     case T_DATA_MORE:
188     {
189         /* i_length <= MAX_TPDU_DATA */
190         uint8_t *p = p_data + 3;
191         p = SetLength( p, i_length + 1 );
192         *p++ = i_tcid;
193
194         if ( i_length )
195             memcpy( p, p_content, i_length );
196             i_size = i_length + (p - p_data);
197         }
198         break;
199
200     default:
201         break;
202     }
203     Dump( VLC_TRUE, p_data, i_size );
204
205     if ( write( p_sys->i_ca_handle, p_data, i_size ) != i_size )
206     {
207         msg_Err( p_access, "cannot write to CAM device (%s)",
208                  strerror(errno) );
209         return VLC_EGENERIC;
210     }
211
212     return VLC_SUCCESS;
213 }
214
215
216 /*****************************************************************************
217  * TPDURecv
218  *****************************************************************************/
219 #define CAM_READ_TIMEOUT  3500 // ms
220
221 static int TPDURecv( access_t * p_access, uint8_t i_slot, uint8_t *pi_tag,
222                      uint8_t *p_data, int *pi_size )
223 {
224     access_sys_t *p_sys = p_access->p_sys;
225     uint8_t i_tcid = i_slot + 1;
226     int i_size;
227     struct pollfd pfd[1];
228
229     pfd[0].fd = p_sys->i_ca_handle;
230     pfd[0].events = POLLIN;
231     if ( !(poll(pfd, 1, CAM_READ_TIMEOUT) > 0 && (pfd[0].revents & POLLIN)) )
232     {
233         msg_Err( p_access, "cannot poll from CAM device" );
234         return VLC_EGENERIC;
235     }
236
237     if ( pi_size == NULL )
238     {
239         p_data = malloc( MAX_TPDU_SIZE );
240     }
241
242     for ( ; ; )
243     {
244         i_size = read( p_sys->i_ca_handle, p_data, MAX_TPDU_SIZE );
245
246         if ( i_size >= 0 || errno != EINTR )
247             break;
248     }
249
250     if ( i_size < 5 )
251     {
252         msg_Err( p_access, "cannot read from CAM device (%d:%s)", i_size,
253                  strerror(errno) );
254         return VLC_EGENERIC;
255     }
256
257     if ( p_data[1] != i_tcid )
258     {
259         msg_Err( p_access, "invalid read from CAM device (%d instead of %d)",
260                  p_data[1], i_tcid );
261         return VLC_EGENERIC;
262     }
263
264     *pi_tag = p_data[2];
265     p_sys->pb_tc_has_data[i_slot] = (i_size >= 4
266                                       && p_data[i_size - 4] == T_SB
267                                       && p_data[i_size - 3] == 2
268                                       && (p_data[i_size - 1] & DATA_INDICATOR))
269                                         ?  VLC_TRUE : VLC_FALSE;
270
271     Dump( VLC_FALSE, p_data, i_size );
272
273     if ( pi_size == NULL )
274         free( p_data );
275     else
276         *pi_size = i_size;
277
278     return VLC_SUCCESS;
279 }
280
281
282 /*
283  * Session layer
284  */
285
286 #define ST_SESSION_NUMBER           0x90
287 #define ST_OPEN_SESSION_REQUEST     0x91
288 #define ST_OPEN_SESSION_RESPONSE    0x92
289 #define ST_CREATE_SESSION           0x93
290 #define ST_CREATE_SESSION_RESPONSE  0x94
291 #define ST_CLOSE_SESSION_REQUEST    0x95
292 #define ST_CLOSE_SESSION_RESPONSE   0x96
293
294 #define SS_OK             0x00
295 #define SS_NOT_ALLOCATED  0xF0
296
297 #define RI_RESOURCE_MANAGER            0x00010041
298 #define RI_APPLICATION_INFORMATION     0x00020041
299 #define RI_CONDITIONAL_ACCESS_SUPPORT  0x00030041
300 #define RI_HOST_CONTROL                0x00200041
301 #define RI_DATE_TIME                   0x00240041
302 #define RI_MMI                         0x00400041
303
304 static int ResourceIdToInt( uint8_t *p_data )
305 {
306     return ((int)p_data[0] << 24) | ((int)p_data[1] << 16)
307             | ((int)p_data[2] << 8) | p_data[3];
308 }
309
310 /*****************************************************************************
311  * SPDUSend
312  *****************************************************************************/
313 static int SPDUSend( access_t * p_access, int i_session_id,
314                      uint8_t *p_data, int i_size )
315 {
316     access_sys_t *p_sys = p_access->p_sys;
317     uint8_t *p_spdu = malloc( i_size + 4 );
318     uint8_t *p = p_spdu;
319     uint8_t i_tag;
320     uint8_t i_slot = p_sys->p_sessions[i_session_id - 1].i_slot;
321
322     *p++ = ST_SESSION_NUMBER;
323     *p++ = 0x02;
324     *p++ = (i_session_id >> 8);
325     *p++ = i_session_id & 0xff;
326
327     memcpy( p, p_data, i_size );
328
329     i_size += 4;
330     p = p_spdu;
331
332     while ( i_size > 0 )
333     {
334         if ( i_size > MAX_TPDU_DATA )
335         {
336             if ( TPDUSend( p_access, i_slot, T_DATA_MORE, p,
337                            MAX_TPDU_DATA ) != VLC_SUCCESS )
338             {
339                 msg_Err( p_access, "couldn't send TPDU on session %d",
340                          i_session_id );
341                 free( p_spdu );
342                 return VLC_EGENERIC;
343             }
344             p += MAX_TPDU_DATA;
345             i_size -= MAX_TPDU_DATA;
346         }
347         else
348         {
349             if ( TPDUSend( p_access, i_slot, T_DATA_LAST, p, i_size )
350                     != VLC_SUCCESS )
351             {
352                 msg_Err( p_access, "couldn't send TPDU on session %d",
353                          i_session_id );
354                 free( p_spdu );
355                 return VLC_EGENERIC;
356             }
357             i_size = 0;
358         }
359
360         if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS
361                || i_tag != T_SB )
362         {
363             msg_Err( p_access, "couldn't recv TPDU on session %d",
364                      i_session_id );
365             free( p_spdu );
366             return VLC_EGENERIC;
367         }
368     }
369
370     free( p_spdu );
371     return VLC_SUCCESS;
372 }
373
374 /*****************************************************************************
375  * SessionOpen
376  *****************************************************************************/
377 static void SessionOpen( access_t * p_access, uint8_t i_slot,
378                          uint8_t *p_spdu, int i_size )
379 {
380     access_sys_t *p_sys = p_access->p_sys;
381     int i_session_id;
382     int i_resource_id = ResourceIdToInt( &p_spdu[2] );
383     uint8_t p_response[16];
384     int i_status = SS_NOT_ALLOCATED;
385     uint8_t i_tag;
386
387     for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
388     {
389         if ( !p_sys->p_sessions[i_session_id - 1].i_resource_id )
390             break;
391     }
392     if ( i_session_id == MAX_SESSIONS )
393     {
394         msg_Err( p_access, "too many sessions !" );
395         return;
396     }
397     p_sys->p_sessions[i_session_id - 1].i_slot = i_slot;
398     p_sys->p_sessions[i_session_id - 1].i_resource_id = i_resource_id;
399     p_sys->p_sessions[i_session_id - 1].pf_close = NULL;
400     p_sys->p_sessions[i_session_id - 1].pf_manage = NULL;
401
402     if ( i_resource_id == RI_RESOURCE_MANAGER
403           || i_resource_id == RI_APPLICATION_INFORMATION
404           || i_resource_id == RI_CONDITIONAL_ACCESS_SUPPORT
405           || i_resource_id == RI_DATE_TIME
406           || i_resource_id == RI_MMI )
407     {
408         i_status = SS_OK;
409     }
410
411     p_response[0] = ST_OPEN_SESSION_RESPONSE;
412     p_response[1] = 0x7;
413     p_response[2] = i_status;
414     p_response[3] = p_spdu[2];
415     p_response[4] = p_spdu[3];
416     p_response[5] = p_spdu[4];
417     p_response[6] = p_spdu[5];
418     p_response[7] = i_session_id >> 8;
419     p_response[8] = i_session_id & 0xff;
420
421     if ( TPDUSend( p_access, i_slot, T_DATA_LAST, p_response, 9 ) !=
422             VLC_SUCCESS )
423     {
424         msg_Err( p_access,
425                  "SessionOpen: couldn't send TPDU on slot %d", i_slot );
426         return;
427     }
428     if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS )
429     {
430         msg_Err( p_access,
431                  "SessionOpen: couldn't recv TPDU on slot %d", i_slot );
432         return;
433     }
434
435     switch ( i_resource_id )
436     {
437     case RI_RESOURCE_MANAGER:
438         ResourceManagerOpen( p_access, i_session_id ); break; 
439     case RI_APPLICATION_INFORMATION:
440         ApplicationInformationOpen( p_access, i_session_id ); break; 
441     case RI_CONDITIONAL_ACCESS_SUPPORT:
442         ConditionalAccessOpen( p_access, i_session_id ); break; 
443     case RI_DATE_TIME:
444         DateTimeOpen( p_access, i_session_id ); break; 
445     case RI_MMI:
446         MMIOpen( p_access, i_session_id ); break; 
447
448     case RI_HOST_CONTROL:
449     default:
450         msg_Err( p_access, "unknown resource id (0x%x)", i_resource_id );
451         p_sys->p_sessions[i_session_id - 1].i_resource_id = 0;
452     }
453 }
454
455 /*****************************************************************************
456  * SessionClose
457  *****************************************************************************/
458 static void SessionClose( access_t * p_access, int i_session_id )
459 {
460     access_sys_t *p_sys = p_access->p_sys;
461     uint8_t p_response[16];
462     uint8_t i_tag;
463     uint8_t i_slot = p_sys->p_sessions[i_session_id - 1].i_slot;
464
465     if ( p_sys->p_sessions[i_session_id - 1].pf_close != NULL )
466         p_sys->p_sessions[i_session_id - 1].pf_close( p_access, i_session_id );
467     p_sys->p_sessions[i_session_id - 1].i_resource_id = 0;
468
469     p_response[0] = ST_CLOSE_SESSION_RESPONSE;
470     p_response[1] = 0x3;
471     p_response[2] = SS_OK;
472     p_response[3] = i_session_id >> 8;
473     p_response[4] = i_session_id & 0xff;
474
475     if ( TPDUSend( p_access, i_slot, T_DATA_LAST, p_response, 5 ) !=
476             VLC_SUCCESS )
477     {
478         msg_Err( p_access,
479                  "SessionOpen: couldn't send TPDU on slot %d", i_slot );
480         return;
481     }
482     if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS )
483     {
484         msg_Err( p_access,
485                  "SessionOpen: couldn't recv TPDU on slot %d", i_slot );
486         return;
487     }
488 }
489
490 /*****************************************************************************
491  * SPDUHandle
492  *****************************************************************************/
493 static void SPDUHandle( access_t * p_access, uint8_t i_slot,
494                         uint8_t *p_spdu, int i_size )
495 {
496     access_sys_t *p_sys = p_access->p_sys;
497     int i_session_id;
498
499     switch ( p_spdu[0] )
500     {
501     case ST_SESSION_NUMBER:
502         if ( i_size <= 4 )
503             return;
504         i_session_id = ((int)p_spdu[2] << 8) | p_spdu[3];
505         p_sys->p_sessions[i_session_id - 1].pf_handle( p_access, i_session_id,
506                                                        p_spdu + 4, i_size - 4 );
507         break;
508
509     case ST_OPEN_SESSION_REQUEST:
510         if ( i_size != 6 || p_spdu[1] != 0x4 )
511             return;
512         SessionOpen( p_access, i_slot, p_spdu, i_size );
513         break;
514
515     case ST_CLOSE_SESSION_REQUEST:
516         i_session_id = ((int)p_spdu[2] << 8) | p_spdu[3];
517         SessionClose( p_access, i_session_id );
518         break;
519
520     default:
521         break;
522     }
523 }
524
525
526 /*
527  * Application layer
528  */
529
530 #define AOT_NONE                    0x000000
531 #define AOT_PROFILE_ENQ             0x9F8010
532 #define AOT_PROFILE                 0x9F8011
533 #define AOT_PROFILE_CHANGE          0x9F8012
534 #define AOT_APPLICATION_INFO_ENQ    0x9F8020
535 #define AOT_APPLICATION_INFO        0x9F8021
536 #define AOT_ENTER_MENU              0x9F8022
537 #define AOT_CA_INFO_ENQ             0x9F8030
538 #define AOT_CA_INFO                 0x9F8031
539 #define AOT_CA_PMT                  0x9F8032
540 #define AOT_CA_PMT_REPLY            0x9F8033
541 #define AOT_TUNE                    0x9F8400
542 #define AOT_REPLACE                 0x9F8401
543 #define AOT_CLEAR_REPLACE           0x9F8402
544 #define AOT_ASK_RELEASE             0x9F8403
545 #define AOT_DATE_TIME_ENQ           0x9F8440
546 #define AOT_DATE_TIME               0x9F8441
547 #define AOT_CLOSE_MMI               0x9F8800
548 #define AOT_DISPLAY_CONTROL         0x9F8801
549 #define AOT_DISPLAY_REPLY           0x9F8802
550 #define AOT_TEXT_LAST               0x9F8803
551 #define AOT_TEXT_MORE               0x9F8804
552 #define AOT_KEYPAD_CONTROL          0x9F8805
553 #define AOT_KEYPRESS                0x9F8806
554 #define AOT_ENQ                     0x9F8807
555 #define AOT_ANSW                    0x9F8808
556 #define AOT_MENU_LAST               0x9F8809
557 #define AOT_MENU_MORE               0x9F880A
558 #define AOT_MENU_ANSW               0x9F880B
559 #define AOT_LIST_LAST               0x9F880C
560 #define AOT_LIST_MORE               0x9F880D
561 #define AOT_SUBTITLE_SEGMENT_LAST   0x9F880E
562 #define AOT_SUBTITLE_SEGMENT_MORE   0x9F880F
563 #define AOT_DISPLAY_MESSAGE         0x9F8810
564 #define AOT_SCENE_END_MARK          0x9F8811
565 #define AOT_SCENE_DONE              0x9F8812
566 #define AOT_SCENE_CONTROL           0x9F8813
567 #define AOT_SUBTITLE_DOWNLOAD_LAST  0x9F8814
568 #define AOT_SUBTITLE_DOWNLOAD_MORE  0x9F8815
569 #define AOT_FLUSH_DOWNLOAD          0x9F8816
570 #define AOT_DOWNLOAD_REPLY          0x9F8817
571 #define AOT_COMMS_CMD               0x9F8C00
572 #define AOT_CONNECTION_DESCRIPTOR   0x9F8C01
573 #define AOT_COMMS_REPLY             0x9F8C02
574 #define AOT_COMMS_SEND_LAST         0x9F8C03
575 #define AOT_COMMS_SEND_MORE         0x9F8C04
576 #define AOT_COMMS_RCV_LAST          0x9F8C05
577 #define AOT_COMMS_RCV_MORE          0x9F8C06
578
579 /*****************************************************************************
580  * APDUGetTag
581  *****************************************************************************/
582 static int APDUGetTag( const uint8_t *p_apdu, int i_size )
583 {
584     if ( i_size >= 3 )
585     {
586         int i, t = 0;
587         for ( i = 0; i < 3; i++ )
588             t = (t << 8) | *p_apdu++;
589         return t;
590     }
591
592     return AOT_NONE;
593 }
594
595 /*****************************************************************************
596  * APDUGetLength
597  *****************************************************************************/
598 static uint8_t *APDUGetLength( uint8_t *p_apdu, int *pi_size )
599 {
600     return GetLength( &p_apdu[3], pi_size );
601 }
602
603 /*****************************************************************************
604  * APDUSend
605  *****************************************************************************/
606 static int APDUSend( access_t * p_access, int i_session_id, int i_tag,
607                      uint8_t *p_data, int i_size )
608 {
609     uint8_t *p_apdu = malloc( i_size + 12 );
610     uint8_t *p = p_apdu;
611     int i_ret;
612
613     *p++ = (i_tag >> 16);
614     *p++ = (i_tag >> 8) & 0xff;
615     *p++ = i_tag & 0xff;
616     p = SetLength( p, i_size );
617     if ( i_size )
618         memcpy( p, p_data, i_size );
619
620     i_ret = SPDUSend( p_access, i_session_id, p_apdu, i_size + p - p_apdu );
621     free( p_apdu );
622     return i_ret;
623 }
624
625 /*****************************************************************************
626  * ResourceManagerHandle
627  *****************************************************************************/
628 static void ResourceManagerHandle( access_t * p_access, int i_session_id,
629                                    uint8_t *p_apdu, int i_size )
630 {
631     int i_tag = APDUGetTag( p_apdu, i_size );
632
633     switch ( i_tag )
634     {
635     case AOT_PROFILE_ENQ:
636     {
637         int resources[] = { htonl(RI_RESOURCE_MANAGER),
638                             htonl(RI_APPLICATION_INFORMATION),
639                             htonl(RI_CONDITIONAL_ACCESS_SUPPORT),
640                             htonl(RI_DATE_TIME),
641                             htonl(RI_MMI)
642                           };
643         APDUSend( p_access, i_session_id, AOT_PROFILE, (uint8_t*)resources,
644                   sizeof(resources) );
645         break;
646     }
647     case AOT_PROFILE:
648         APDUSend( p_access, i_session_id, AOT_PROFILE_CHANGE, NULL, 0 );
649         break;
650
651     default:
652         msg_Err( p_access, "unexpected tag in ResourceManagerHandle (0x%x)",
653                  i_tag );
654     }
655 }
656
657 /*****************************************************************************
658  * ResourceManagerOpen
659  *****************************************************************************/
660 static void ResourceManagerOpen( access_t * p_access, int i_session_id )
661 {
662     access_sys_t *p_sys = p_access->p_sys;
663
664     msg_Dbg( p_access, "opening ResourceManager session (%d)", i_session_id );
665
666     p_sys->p_sessions[i_session_id - 1].pf_handle = ResourceManagerHandle;
667
668     APDUSend( p_access, i_session_id, AOT_PROFILE_ENQ, NULL, 0 );
669 }
670
671 /*****************************************************************************
672  * ApplicationInformationHandle
673  *****************************************************************************/
674 static void ApplicationInformationHandle( access_t * p_access, int i_session_id,
675                                           uint8_t *p_apdu, int i_size )
676 {
677     int i_tag = APDUGetTag( p_apdu, i_size );
678
679     switch ( i_tag )
680     {
681     case AOT_APPLICATION_INFO:
682     {
683         int i_type, i_manufacturer, i_code;
684         int l = 0;
685         uint8_t *d = APDUGetLength( p_apdu, &l );
686
687         if ( l < 4 ) break;
688         p_apdu[l + 3] = '\0';
689
690         i_type = *d++;
691         i_manufacturer = ((int)d[0] << 8) | d[1];
692         d += 2;
693         i_code = ((int)d[0] << 8) | d[1];
694         d += 2;
695         d = GetLength( d, &l );
696         d[l] = '\0';
697         msg_Info( p_access, "CAM: %s, %02X, %04X, %04X",
698                   d, i_type, i_manufacturer, i_code );
699         break;
700     }
701     default:
702         msg_Err( p_access,
703                  "unexpected tag in ApplicationInformationHandle (0x%x)",
704                  i_tag );
705     }
706 }
707
708 /*****************************************************************************
709  * ApplicationInformationOpen
710  *****************************************************************************/
711 static void ApplicationInformationOpen( access_t * p_access, int i_session_id )
712 {
713     access_sys_t *p_sys = p_access->p_sys;
714
715     msg_Dbg( p_access, "opening ApplicationInformation session (%d)", i_session_id );
716
717     p_sys->p_sessions[i_session_id - 1].pf_handle = ApplicationInformationHandle;
718
719     APDUSend( p_access, i_session_id, AOT_APPLICATION_INFO_ENQ, NULL, 0 );
720 }
721
722 /*****************************************************************************
723  * ConditionalAccessHandle
724  *****************************************************************************/
725 static void ConditionalAccessHandle( access_t * p_access, int i_session_id,
726                                      uint8_t *p_apdu, int i_size )
727 {
728     access_sys_t *p_sys = p_access->p_sys;
729     int i_tag = APDUGetTag( p_apdu, i_size );
730
731     switch ( i_tag )
732     {
733     case AOT_CA_INFO:
734     {
735         if ( p_sys->i_nb_capmts )
736         {
737             int i;
738             msg_Dbg( p_access, "sending CAPMT on session %d", i_session_id );
739             for ( i = 0; i < p_sys->i_nb_capmts; i++ )
740             {
741                 int i_size;
742                 uint8_t *p;
743                 p = GetLength( &p_sys->pp_capmts[i][3], &i_size );
744                 SPDUSend( p_access, i_session_id, p_sys->pp_capmts[i],
745                           i_size + (p - p_sys->pp_capmts[i]) );
746             }
747
748             p_sys->i_ca_timeout = 100000;
749         }
750         break;
751     }
752     default:
753         msg_Err( p_access,
754                  "unexpected tag in ConditionalAccessHandle (0x%x)",
755                  i_tag );
756     }
757 }
758
759 /*****************************************************************************
760  * ConditionalAccessOpen
761  *****************************************************************************/
762 static void ConditionalAccessOpen( access_t * p_access, int i_session_id )
763 {
764     access_sys_t *p_sys = p_access->p_sys;
765
766     msg_Dbg( p_access, "opening ConditionalAccess session (%d)", i_session_id );
767
768     p_sys->p_sessions[i_session_id - 1].pf_handle = ConditionalAccessHandle;
769
770     APDUSend( p_access, i_session_id, AOT_CA_INFO_ENQ, NULL, 0 );
771 }
772
773 typedef struct
774 {
775     int i_interval;
776     mtime_t i_last;
777 } date_time_t;
778
779 /*****************************************************************************
780  * DateTimeSend
781  *****************************************************************************/
782 static void DateTimeSend( access_t * p_access, int i_session_id )
783 {
784     access_sys_t *p_sys = p_access->p_sys;
785     date_time_t *p_date =
786         (date_time_t *)p_sys->p_sessions[i_session_id - 1].p_sys;
787
788     time_t t = time(NULL);
789     struct tm tm_gmt;
790     struct tm tm_loc;
791
792     if ( gmtime_r(&t, &tm_gmt) && localtime_r(&t, &tm_loc) )
793     {
794         int Y = tm_gmt.tm_year;
795         int M = tm_gmt.tm_mon + 1;
796         int D = tm_gmt.tm_mday;
797         int L = (M == 1 || M == 2) ? 1 : 0;
798         int MJD = 14956 + D + (int)((Y - L) * 365.25)
799                     + (int)((M + 1 + L * 12) * 30.6001);
800         uint8_t p_response[7];
801
802 #define DEC2BCD(d) (((d / 10) << 4) + (d % 10))
803
804         p_response[0] = htons(MJD) >> 8;
805         p_response[1] = htons(MJD) & 0xff;
806         p_response[2] = DEC2BCD(tm_gmt.tm_hour);
807         p_response[3] = DEC2BCD(tm_gmt.tm_min);
808         p_response[4] = DEC2BCD(tm_gmt.tm_sec);
809         p_response[5] = htons(tm_loc.tm_gmtoff / 60) >> 8;
810         p_response[6] = htons(tm_loc.tm_gmtoff / 60) & 0xff;
811
812         APDUSend( p_access, i_session_id, AOT_DATE_TIME, p_response, 7 );
813
814         p_date->i_last = mdate();
815     }
816 }
817
818 /*****************************************************************************
819  * DateTimeHandle
820  *****************************************************************************/
821 static void DateTimeHandle( access_t * p_access, int i_session_id,
822                             uint8_t *p_apdu, int i_size )
823 {
824     access_sys_t *p_sys = p_access->p_sys;
825     date_time_t *p_date =
826         (date_time_t *)p_sys->p_sessions[i_session_id - 1].p_sys;
827
828     int i_tag = APDUGetTag( p_apdu, i_size );
829
830     switch ( i_tag )
831     {
832     case AOT_DATE_TIME_ENQ:
833     {
834         int l;
835         const uint8_t *d = APDUGetLength( p_apdu, &l );
836
837         if ( l > 0 )
838         {
839             p_date->i_interval = *d;
840             msg_Dbg( p_access, "DateTimeHandle : interval set to %d",
841                      p_date->i_interval );
842         }
843         else
844             p_date->i_interval = 0;
845
846         DateTimeSend( p_access, i_session_id );
847         break;
848     }
849     default:
850         msg_Err( p_access, "unexpected tag in DateTimeHandle (0x%x)", i_tag );
851     }
852 }
853
854 /*****************************************************************************
855  * DateTimeManage
856  *****************************************************************************/
857 static void DateTimeManage( access_t * p_access, int i_session_id )
858 {
859     access_sys_t *p_sys = p_access->p_sys;
860     date_time_t *p_date =
861         (date_time_t *)p_sys->p_sessions[i_session_id - 1].p_sys;
862
863     if ( p_date->i_interval
864           && mdate() > p_date->i_last + (mtime_t)p_date->i_interval * 1000000 )
865     {
866         DateTimeSend( p_access, i_session_id );
867     }
868 }
869
870 /*****************************************************************************
871  * DateTimeOpen
872  *****************************************************************************/
873 static void DateTimeOpen( access_t * p_access, int i_session_id )
874 {
875     access_sys_t *p_sys = p_access->p_sys;
876
877     msg_Dbg( p_access, "opening DateTime session (%d)", i_session_id );
878
879     p_sys->p_sessions[i_session_id - 1].pf_handle = DateTimeHandle;
880     p_sys->p_sessions[i_session_id - 1].pf_manage = DateTimeManage;
881     p_sys->p_sessions[i_session_id - 1].p_sys = malloc(sizeof(date_time_t));
882     memset( p_sys->p_sessions[i_session_id - 1].p_sys, 0, sizeof(date_time_t) );
883
884     DateTimeSend( p_access, i_session_id );
885 }
886
887 /*****************************************************************************
888  * MMIHandle
889  *****************************************************************************/
890 static void MMIHandle( access_t * p_access, int i_session_id,
891                             uint8_t *p_apdu, int i_size )
892 {
893     int i_tag = APDUGetTag( p_apdu, i_size );
894
895     switch ( i_tag )
896     {
897     default:
898         msg_Err( p_access, "unexpected tag in MMIHandle (0x%x)", i_tag );
899     }
900 }
901
902 /*****************************************************************************
903  * MMIOpen
904  *****************************************************************************/
905 static void MMIOpen( access_t * p_access, int i_session_id )
906 {
907     access_sys_t *p_sys = p_access->p_sys;
908
909     msg_Dbg( p_access, "opening MMI session (%d)", i_session_id );
910
911     p_sys->p_sessions[i_session_id - 1].pf_handle = MMIHandle;
912 }
913
914
915 /*
916  * Hardware handling
917  */
918
919 /*****************************************************************************
920  * InitSlot: Open the transport layer
921  *****************************************************************************/
922 #define MAX_TC_RETRIES 20
923
924 static int InitSlot( access_t * p_access, int i_slot )
925 {
926     access_sys_t *p_sys = p_access->p_sys;
927     int i;
928
929     if ( TPDUSend( p_access, i_slot, T_CREATE_TC, NULL, 0 )
930             != VLC_SUCCESS )
931     {
932         msg_Err( p_access, "en50221_Init: couldn't send TPDU on slot %d",
933                  i_slot );
934         return VLC_EGENERIC;
935     }
936
937     /* This is out of the spec */
938     for ( i = 0; i < MAX_TC_RETRIES; i++ )
939     {
940         uint8_t i_tag;
941         if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) == VLC_SUCCESS
942               && i_tag == T_CTC_REPLY )
943         {
944             p_sys->pb_active_slot[i_slot] = VLC_TRUE;
945             break;
946         }
947
948         if ( TPDUSend( p_access, i_slot, T_CREATE_TC, NULL, 0 )
949                 != VLC_SUCCESS )
950         {
951             msg_Err( p_access,
952                      "en50221_Init: couldn't send TPDU on slot %d",
953                      i_slot );
954             continue;
955         }
956     }
957     if ( p_sys->pb_active_slot[i_slot] )
958     {
959         p_sys->i_ca_timeout = 1000;
960         return VLC_SUCCESS;
961     }
962
963     return VLC_EGENERIC;
964 }
965
966
967 /*
968  * External entry points
969  */
970
971 /*****************************************************************************
972  * en50221_Poll : Poll the CAM for TPDUs
973  *****************************************************************************/
974 int E_(en50221_Poll)( access_t * p_access )
975 {
976     access_sys_t *p_sys = p_access->p_sys;
977     int i_slot;
978     int i_session_id;
979
980     for ( i_slot = 0; i_slot < p_sys->i_nb_slots; i_slot++ )
981     {
982         uint8_t i_tag;
983
984         if ( !p_sys->pb_active_slot[i_slot] )
985         {
986             ca_slot_info_t sinfo;
987             sinfo.num = i_slot;
988             if ( ioctl( p_sys->i_ca_handle, CA_GET_SLOT_INFO, &sinfo ) != 0 )
989             {
990                 msg_Err( p_access, "en50221_Poll: couldn't get info on slot %d",
991                          i_slot );
992                 continue;
993             }
994
995             if ( sinfo.flags & CA_CI_MODULE_READY )
996             {
997                 msg_Dbg( p_access, "en50221_Poll: slot %d is active",
998                          i_slot );
999                 p_sys->pb_active_slot[i_slot] = VLC_TRUE;
1000             }
1001             else
1002                 continue;
1003
1004             InitSlot( p_access, i_slot );
1005         }
1006
1007         if ( !p_sys->pb_tc_has_data[i_slot] )
1008         {
1009             if ( TPDUSend( p_access, i_slot, T_DATA_LAST, NULL, 0 ) !=
1010                     VLC_SUCCESS )
1011             {
1012                 msg_Err( p_access,
1013                          "en50221_Poll: couldn't send TPDU on slot %d",
1014                          i_slot );
1015                 continue;
1016             }
1017             if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) !=
1018                     VLC_SUCCESS )
1019             {
1020                 msg_Err( p_access,
1021                          "en50221_Poll: couldn't recv TPDU on slot %d",
1022                          i_slot );
1023                 continue;
1024             }
1025         }
1026
1027         while ( p_sys->pb_tc_has_data[i_slot] )
1028         {
1029             uint8_t p_tpdu[MAX_TPDU_SIZE];
1030             int i_size, i_session_size;
1031             uint8_t *p_session;
1032
1033             if ( TPDUSend( p_access, i_slot, T_RCV, NULL, 0 ) != VLC_SUCCESS )
1034             {
1035                 msg_Err( p_access,
1036                          "en50221_Poll: couldn't send TPDU on slot %d",
1037                          i_slot );
1038                 continue;
1039             }
1040             if ( TPDURecv( p_access, i_slot, &i_tag, p_tpdu, &i_size ) !=
1041                     VLC_SUCCESS )
1042             {
1043                 msg_Err( p_access,
1044                          "en50221_Poll: couldn't recv TPDU on slot %d",
1045                          i_slot );
1046                 continue;
1047             }
1048
1049             p_session = GetLength( &p_tpdu[3], &i_session_size );
1050             if ( i_session_size <= 1 )
1051                 continue;
1052
1053             p_session++;
1054             i_session_size--;
1055
1056             if ( i_tag != T_DATA_LAST )
1057             {
1058                 msg_Err( p_access,
1059                          "en50221_Poll: fragmented TPDU not supported" );
1060                 break;
1061             }
1062
1063             SPDUHandle( p_access, i_slot, p_session, i_session_size );
1064         }
1065     }
1066
1067     for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
1068     {
1069         if ( p_sys->p_sessions[i_session_id - 1].i_resource_id
1070               && p_sys->p_sessions[i_session_id - 1].pf_manage )
1071         {
1072             p_sys->p_sessions[i_session_id - 1].pf_manage( p_access,
1073                                                            i_session_id );
1074         }
1075     }
1076
1077     return VLC_SUCCESS;
1078 }
1079
1080
1081 /*****************************************************************************
1082  * en50221_SetCAPMT :
1083  *****************************************************************************/
1084 int E_(en50221_SetCAPMT)( access_t * p_access, uint8_t **pp_capmts,
1085                           int i_nb_capmts )
1086 {
1087     access_sys_t *p_sys = p_access->p_sys;
1088     int i_session_id;
1089
1090     for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
1091     {
1092         int i;
1093
1094         if ( p_sys->p_sessions[i_session_id - 1].i_resource_id
1095               != RI_CONDITIONAL_ACCESS_SUPPORT )
1096             continue;
1097
1098         msg_Dbg( p_access, "sending CAPMT on session %d", i_session_id );
1099         for ( i = 0; i < i_nb_capmts; i++ )
1100         {
1101             int i_size;
1102             uint8_t *p;
1103             p = GetLength( &pp_capmts[i][3], &i_size );
1104             SPDUSend( p_access, i_session_id, pp_capmts[i],
1105                       i_size + (p - pp_capmts[i]) );
1106         }
1107
1108         p_sys->i_ca_timeout = 100000;
1109     }
1110
1111     if ( p_sys->i_nb_capmts )
1112     {
1113         int i;
1114         for ( i = 0; i < p_sys->i_nb_capmts; i++ )
1115         {
1116             free( p_sys->pp_capmts[i] );
1117         }
1118         free( p_sys->pp_capmts );
1119     }
1120     p_sys->pp_capmts = pp_capmts;
1121     p_sys->i_nb_capmts = i_nb_capmts;
1122
1123     return VLC_SUCCESS;
1124 }
1125
1126 /*****************************************************************************
1127  * en50221_End :
1128  *****************************************************************************/
1129 void E_(en50221_End)( access_t * p_access )
1130 {
1131     access_sys_t *p_sys = p_access->p_sys;
1132
1133     if ( p_sys->i_nb_capmts )
1134     {
1135         int i;
1136         for ( i = 0; i < p_sys->i_nb_capmts; i++ )
1137         {
1138             free( p_sys->pp_capmts[i] );
1139         }
1140         free( p_sys->pp_capmts );
1141     }
1142
1143     /* TODO */
1144 }