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