Merging gst-plugins-bad
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-bad / sys / dvb / camtransport.c
1 /*
2  * camtransport.c - GStreamer CAM (EN50221) transport layer
3  * Copyright (C) 2007 Alessandro Decina
4  * 
5  * Authors:
6  *   Alessandro Decina <alessandro.d@gmail.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23
24 #include "camtransport.h"
25 #include <sys/select.h>
26 #include <sys/time.h>
27 #include <sys/types.h>
28 #include <unistd.h>
29 #include <errno.h>
30
31 #define GST_CAT_DEFAULT cam_debug_cat
32 #define READ_TIMEOUT_SEC 2
33 #define READ_TIMEOUT_USEC 0
34
35 #define POLL_INTERVAL 0.300
36
37 /* Layer tags */
38 #define TAG_SB 0x80
39 #define TAG_RCV 0x81
40 #define TAG_CREATE_T_C 0x82
41 #define TAG_C_T_C_REPLY 0x83
42 #define TAG_DELETE_T_C 0x84
43 #define TAG_D_T_C_REPLY 0x85
44 #define TAG_REQUEST_T_C 0x86
45 #define TAG_NEW_T_C 0x87
46 #define TAG_T_C_ERROR 0x88
47 #define TAG_DATA_MORE 0xA1
48 #define TAG_DATA_LAST 0xA0
49
50 /* Session tags */
51 #define TAG_SESSION_NUMBER 0x90
52 #define TAG_OPEN_SESSION_REQUEST 0x91
53 #define TAG_OPEN_SESSION_RESPONSE 0x92
54 #define TAG_CREATE_SESSION 0x93
55 #define TAG_CREATE_SESSION_RESPONSE 0x94
56 #define TAG_CLOSE_SESSION_REQUEST 0x95
57 #define TAG_CLOSE_SESSION_RESPONSE 0x96
58
59
60 typedef struct
61 {
62   guint tagid;
63   const gchar *description;
64 } CamTagMessage;
65
66 static const CamTagMessage debugmessage[] = {
67   {TAG_SB, "SB"},
68   {TAG_RCV, "RCV"},
69   {TAG_CREATE_T_C, "CREATE_T_C"},
70   {TAG_C_T_C_REPLY, "CREATE_T_C_REPLY"},
71   {TAG_DELETE_T_C, "DELETE_T_C"},
72   {TAG_D_T_C_REPLY, "DELETE_T_C_REPLY"},
73   {TAG_REQUEST_T_C, "REQUEST_T_C"},
74   {TAG_NEW_T_C, "NEW_T_C"},
75   {TAG_T_C_ERROR, "T_C_ERROR"},
76   {TAG_SESSION_NUMBER, "SESSION_NUMBER"},
77   {TAG_OPEN_SESSION_REQUEST, "OPEN_SESSION_REQUEST"},
78   {TAG_OPEN_SESSION_RESPONSE, "OPEN_SESSION_RESPONSE"},
79   {TAG_CREATE_SESSION, "CREATE_SESSION"},
80   {TAG_CREATE_SESSION_RESPONSE, "CREATE_SESSION_RESPONSE"},
81   {TAG_CLOSE_SESSION_REQUEST, "CLOSE_SESSION_REQUEST"},
82   {TAG_CLOSE_SESSION_RESPONSE, "CLOSE_SESSION_RESPONSE"},
83   {TAG_DATA_MORE, "DATA_MORE"},
84   {TAG_DATA_LAST, "DATA_LAST"}
85 };
86
87 static inline const gchar *
88 tag_get_name (guint tagid)
89 {
90   guint i;
91
92   for (i = 0; i < G_N_ELEMENTS (debugmessage); i++)
93     if (debugmessage[i].tagid == tagid)
94       return debugmessage[i].description;
95   return "UNKNOWN";
96 }
97
98 /* utility struct used to store the state of the connections in cam_tl_read_next
99  */
100 typedef struct
101 {
102   GList *active;
103   GList *idle;
104 } CamTLConnectionsStatus;
105
106 void cam_gst_util_dump_mem (const guchar * mem, guint size);
107
108 static CamTLConnection *
109 cam_tl_connection_new (CamTL * tl, guint8 id)
110 {
111   CamTLConnection *connection;
112
113   connection = g_new0 (CamTLConnection, 1);
114   connection->tl = tl;
115   connection->id = id;
116   connection->state = CAM_TL_CONNECTION_STATE_CLOSED;
117   connection->has_data = FALSE;
118
119   return connection;
120 }
121
122 static void
123 cam_tl_connection_destroy (CamTLConnection * connection)
124 {
125   if (connection->last_poll)
126     g_timer_destroy (connection->last_poll);
127
128   g_free (connection);
129 }
130
131 CamTL *
132 cam_tl_new (int fd)
133 {
134   CamTL *tl;
135
136   tl = g_new0 (CamTL, 1);
137   tl->fd = fd;
138   tl->connections = g_hash_table_new_full (g_direct_hash, g_direct_equal,
139       NULL, (GDestroyNotify) cam_tl_connection_destroy);
140
141   return tl;
142 }
143
144 void
145 cam_tl_destroy (CamTL * tl)
146 {
147   g_hash_table_destroy (tl->connections);
148
149   g_free (tl);
150 }
151
152 /* read data from the module without blocking indefinitely */
153 static CamReturn
154 cam_tl_read_timeout (CamTL * tl, struct timeval *timeout)
155 {
156   fd_set read_fd;
157   int sret;
158
159   FD_ZERO (&read_fd);
160   FD_SET (tl->fd, &read_fd);
161
162   sret = select (tl->fd + 1, &read_fd, NULL, NULL, timeout);
163   if (sret == 0) {
164     GST_DEBUG ("read timeout");
165     return CAM_RETURN_TRANSPORT_TIMEOUT;
166   }
167
168   tl->buffer_size = read (tl->fd, &tl->buffer, HOST_BUFFER_SIZE);
169   if (tl->buffer_size == -1) {
170     GST_ERROR ("error reading tpdu: %s", g_strerror (errno));
171     return CAM_RETURN_TRANSPORT_ERROR;
172   }
173
174   return CAM_RETURN_OK;
175 }
176
177 /* read data from the module using the default timeout */
178 static CamReturn
179 cam_tl_read (CamTL * tl)
180 {
181   struct timeval timeout;
182
183   timeout.tv_sec = READ_TIMEOUT_SEC;
184   timeout.tv_usec = READ_TIMEOUT_USEC;
185
186   return cam_tl_read_timeout (tl, &timeout);
187 }
188
189 /* get the number of bytes to allocate for a TPDU with a body of body_length
190  * bytes. Also get the offset from the beginning of the buffer that marks the
191  * position of the first byte of the TPDU body */
192 void
193 cam_tl_calc_buffer_size (CamTL * tl, guint body_length,
194     guint * buffer_size, guint * offset)
195 {
196   guint length_field_len;
197
198   /* the size of a TPDU is:
199    * 1 byte slot number
200    * 1 byte connection id 
201    * length_field_len bytes length field 
202    * 1 byte connection id
203    * body_length bytes body
204    */
205
206   /* get the length of the lenght_field block */
207   length_field_len = cam_calc_length_field_size (body_length);
208
209   *offset = 3 + length_field_len + 1;
210   *buffer_size = *offset + body_length;
211 }
212
213 /* write the header of a TPDU
214  * NOTE: this function assumes that the buffer is large enough to contain the
215  * complete TPDU (see cam_tl_calc_buffer_size ()) and that enough space has been
216  * left from the beginning of the buffer to write the TPDU header.
217  */
218 static CamReturn
219 cam_tl_connection_write_tpdu (CamTLConnection * connection,
220     guint8 tag, guint8 * buffer, guint buffer_size, guint body_length)
221 {
222   int sret;
223   CamTL *tl = connection->tl;
224   guint8 length_field_len;
225
226   /* slot number */
227   buffer[0] = connection->slot;
228   /* connection number */
229   buffer[1] = connection->id;
230   /* tag */
231   buffer[2] = tag;
232   /* length can take 1 to 4 bytes */
233   length_field_len = cam_write_length_field (&buffer[3], body_length);
234   buffer[3 + length_field_len] = connection->id;
235
236   GST_DEBUG ("writing TPDU %x (%s) connection %d (size:%d)",
237       buffer[2], tag_get_name (buffer[2]), connection->id, buffer_size);
238
239   //cam_gst_util_dump_mem (buffer, buffer_size);
240
241   sret = write (tl->fd, buffer, buffer_size);
242   if (sret == -1) {
243     GST_ERROR ("error witing TPDU (%d): %s", errno, g_strerror (errno));
244     return CAM_RETURN_TRANSPORT_ERROR;
245   }
246
247   tl->expected_tpdus += 1;
248
249   GST_DEBUG ("Success writing tpdu 0x%x (%s)", buffer[2],
250       tag_get_name (buffer[2]));
251
252   return CAM_RETURN_OK;
253 }
254
255 /* convenience function to write control TPDUs (TPDUs having a single-byte body)
256  */
257 static CamReturn
258 cam_tl_connection_write_control_tpdu (CamTLConnection * connection, guint8 tag)
259 {
260   guint8 tpdu[5];
261
262   /* TPDU layout (5 bytes):
263    *
264    * slot number (1 byte)
265    * connection id (1 byte)
266    * tag (1 byte)
267    * length (1 byte)
268    * connection id (1 byte)
269    */
270
271   return cam_tl_connection_write_tpdu (connection, tag, tpdu, 5, 1);
272 }
273
274 /* read the next TPDU from the CAM */
275 static CamReturn
276 cam_tl_read_tpdu_next (CamTL * tl, CamTLConnection ** out_connection)
277 {
278   CamReturn ret;
279   CamTLConnection *connection;
280   guint8 connection_id;
281   guint8 *tpdu;
282   guint8 length_field_len;
283   guint8 status;
284
285   ret = cam_tl_read (tl);
286   if (CAM_FAILED (ret))
287     return ret;
288
289   tpdu = tl->buffer;
290
291   /* must hold at least slot, connection_id, 1byte length_field, connection_id
292    */
293   if (tl->buffer_size < 4) {
294     GST_ERROR ("invalid TPDU length %d", tl->buffer_size);
295     return CAM_RETURN_TRANSPORT_ERROR;
296   }
297
298   /* LPDU slot */
299   /* slot = tpdu[0]; */
300   /* LPDU connection id */
301   connection_id = tpdu[1];
302
303   connection = g_hash_table_lookup (tl->connections,
304       GINT_TO_POINTER ((guint) connection_id));
305   if (connection == NULL) {
306     /* WHAT? */
307     GST_ERROR ("CAM sent a TPDU on an unknown connection: %d", connection_id);
308     return CAM_RETURN_TRANSPORT_ERROR;
309   }
310
311   /* read the length_field () */
312   length_field_len = cam_read_length_field (&tpdu[3], &tl->body_length);
313
314   if (tl->body_length + 3 > tl->buffer_size) {
315     GST_ERROR ("invalid TPDU length_field (%d) exceeds "
316         "the size of the buffer (%d)", tl->body_length, tl->buffer_size);
317     return CAM_RETURN_TRANSPORT_ERROR;
318   }
319
320   /* skip slot + connection id + tag + lenght_field () + connection id */
321   tl->body = tpdu + 4 + length_field_len;
322   /* do not count the connection id byte as part of the body */
323   tl->body_length -= 1;
324
325   if (tl->buffer[tl->buffer_size - 4] != TAG_SB) {
326     GST_ERROR ("no TAG_SB appended to TPDU");
327     return CAM_RETURN_TRANSPORT_ERROR;
328   }
329
330   status = tl->buffer[tl->buffer_size - 1];
331   if (status & 0x80) {
332     connection->has_data = TRUE;
333   } else {
334     connection->has_data = FALSE;
335   }
336
337   GST_DEBUG ("received TPDU %x (%s) more data %d", tpdu[2],
338       tag_get_name (tpdu[2]), connection->has_data);
339   tl->expected_tpdus -= 1;
340
341   *out_connection = connection;
342
343   return CAM_RETURN_OK;
344 }
345
346 /* create a connection with the module */
347 CamReturn
348 cam_tl_create_connection (CamTL * tl, guint8 slot,
349     CamTLConnection ** connection)
350 {
351   CamReturn ret;
352   CamTLConnection *conn = NULL;
353   guint count = 10;
354
355   if (tl->connection_ids == 255)
356     return CAM_RETURN_TRANSPORT_TOO_MANY_CONNECTIONS;
357
358   conn = cam_tl_connection_new (tl, ++tl->connection_ids);
359
360   /* Some CA devices take a long time to set themselves up,
361    * therefore retry every 250ms (for a maximum of 2.5s)
362    */
363   while (TRUE) {
364     /* send a TAG_CREATE_T_C TPDU */
365     ret = cam_tl_connection_write_control_tpdu (conn, TAG_CREATE_T_C);
366     if (!CAM_FAILED (ret))
367       break;
368     if (!count)
369       goto error;
370     GST_DEBUG ("Failed sending initial connection message .. but retrying");
371     count--;
372     /* Wait 250ms */
373     g_usleep (G_USEC_PER_SEC / 4);
374   }
375
376   g_hash_table_insert (tl->connections, GINT_TO_POINTER (conn->id), conn);
377
378   *connection = conn;
379
380   return CAM_RETURN_OK;
381
382 error:
383   if (conn)
384     cam_tl_connection_destroy (conn);
385
386   return ret;
387 }
388
389 CamReturn
390 cam_tl_connection_delete (CamTLConnection * connection)
391 {
392   CamReturn ret;
393
394   ret = cam_tl_connection_write_control_tpdu (connection, TAG_DELETE_T_C);
395   if (CAM_FAILED (ret))
396     return ret;
397
398   connection->state = CAM_TL_CONNECTION_STATE_IN_DELETION;
399
400   return CAM_RETURN_OK;
401 }
402
403 static CamReturn
404 handle_control_tpdu (CamTL * tl, CamTLConnection * connection)
405 {
406   if (tl->body_length != 0) {
407     GST_ERROR ("got control tpdu of invalid length: %d", tl->body_length);
408     return CAM_RETURN_TRANSPORT_ERROR;
409   }
410
411   switch (tl->buffer[2]) {
412       /* create transport connection reply */
413     case TAG_C_T_C_REPLY:
414       /* a connection might be closed before it's acknowledged */
415       if (connection->state != CAM_TL_CONNECTION_STATE_IN_DELETION) {
416         GST_DEBUG ("connection created %d", connection->id);
417         connection->state = CAM_TL_CONNECTION_STATE_OPEN;
418
419         if (tl->connection_created)
420           tl->connection_created (tl, connection);
421       }
422       break;
423       /* delete transport connection reply */
424     case TAG_D_T_C_REPLY:
425       connection->state = CAM_TL_CONNECTION_STATE_CLOSED;
426       GST_DEBUG ("connection closed %d", connection->id);
427
428       if (tl->connection_deleted)
429         tl->connection_deleted (tl, connection);
430
431       g_hash_table_remove (tl->connections,
432           GINT_TO_POINTER ((guint) connection->id));
433       break;
434   }
435
436   return CAM_RETURN_OK;
437 }
438
439 static CamReturn
440 handle_data_tpdu (CamTL * tl, CamTLConnection * connection)
441 {
442   if (tl->body_length == 0) {
443     /* FIXME: figure out why this seems to happen from time to time with the
444      * predator cam */
445     GST_WARNING ("Empty data TPDU received");
446     return CAM_RETURN_OK;
447   }
448
449   if (tl->connection_data)
450     return tl->connection_data (tl, connection, tl->body, tl->body_length);
451
452   return CAM_RETURN_OK;
453 }
454
455 static void
456 foreach_connection_get (gpointer key, gpointer value, gpointer user_data)
457 {
458   GList **lst = (GList **) user_data;
459
460   *lst = g_list_append (*lst, value);
461 }
462
463 CamReturn
464 cam_tl_connection_poll (CamTLConnection * connection, gboolean force)
465 {
466   CamReturn ret;
467
468   if (connection->last_poll == NULL) {
469     connection->last_poll = g_timer_new ();
470   } else if (!force &&
471       g_timer_elapsed (connection->last_poll, NULL) < POLL_INTERVAL) {
472     return CAM_RETURN_TRANSPORT_POLL;
473   }
474
475   GST_DEBUG ("polling connection %d", connection->id);
476   ret = cam_tl_connection_write_control_tpdu (connection, TAG_DATA_LAST);
477   if (CAM_FAILED (ret))
478     return ret;
479
480   g_timer_start (connection->last_poll);
481
482   return CAM_RETURN_OK;
483 }
484
485 /* read all the queued TPDUs */
486 CamReturn
487 cam_tl_read_all (CamTL * tl, gboolean poll)
488 {
489   CamReturn ret = CAM_RETURN_OK;
490   CamTLConnection *connection;
491   GList *connections = NULL;
492   GList *walk;
493   gboolean done = FALSE;
494
495   while (!done) {
496     while (tl->expected_tpdus) {
497       /* read the next TPDU from the connection */
498       ret = cam_tl_read_tpdu_next (tl, &connection);
499       if (CAM_FAILED (ret)) {
500         GST_ERROR ("error reading TPDU from module: %d", ret);
501         goto out;
502       }
503
504       switch (tl->buffer[2]) {
505         case TAG_C_T_C_REPLY:
506         case TAG_D_T_C_REPLY:
507           connection->empty_data = 0;
508           ret = handle_control_tpdu (tl, connection);
509           break;
510         case TAG_DATA_MORE:
511         case TAG_DATA_LAST:
512           connection->empty_data = 0;
513           ret = handle_data_tpdu (tl, connection);
514           break;
515         case TAG_SB:
516           /* this is handled by tpdu_next */
517           break;
518       }
519
520       if (CAM_FAILED (ret))
521         goto out;
522     }
523
524     done = TRUE;
525
526     connections = NULL;
527     g_hash_table_foreach (tl->connections,
528         foreach_connection_get, &connections);
529
530     for (walk = connections; walk; walk = walk->next) {
531       CamTLConnection *connection = CAM_TL_CONNECTION (walk->data);
532
533       if (connection->has_data == TRUE && connection->empty_data < 10) {
534         ret = cam_tl_connection_write_control_tpdu (connection, TAG_RCV);
535         if (CAM_FAILED (ret)) {
536           g_list_free (connections);
537           goto out;
538         }
539         /* increment the empty_data counter. If we get data, this will be reset
540          * to 0 */
541         connection->empty_data++;
542         done = FALSE;
543       } else if (poll) {
544         ret = cam_tl_connection_poll (connection, FALSE);
545         if (ret == CAM_RETURN_TRANSPORT_POLL)
546           continue;
547
548         if (CAM_FAILED (ret)) {
549           g_list_free (connections);
550           goto out;
551         }
552
553         done = FALSE;
554       }
555     }
556
557     g_list_free (connections);
558   }
559
560 out:
561   return ret;
562 }
563
564 CamReturn
565 cam_tl_connection_write (CamTLConnection * connection,
566     guint8 * buffer, guint buffer_size, guint body_length)
567 {
568   return cam_tl_connection_write_tpdu (connection,
569       TAG_DATA_LAST, buffer, buffer_size, 1 + body_length);
570 }