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 *
tag_get_name(guint tagid)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 *
cam_tl_connection_new(CamTL * tl,guint8 id)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
cam_tl_connection_destroy(CamTLConnection * connection)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 *
cam_tl_new(int fd)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
cam_tl_destroy(CamTL * tl)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
cam_tl_read_timeout(CamTL * tl,struct timeval * timeout)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
cam_tl_read(CamTL * tl)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
cam_tl_calc_buffer_size(CamTL * tl,guint body_length,guint * buffer_size,guint * offset)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
cam_tl_connection_write_tpdu(CamTLConnection * connection,guint8 tag,guint8 * buffer,guint buffer_size,guint body_length)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 ("Sucess 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
cam_tl_connection_write_control_tpdu(CamTLConnection * connection,guint8 tag)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
cam_tl_read_tpdu_next(CamTL * tl,CamTLConnection ** out_connection)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
cam_tl_create_connection(CamTL * tl,guint8 slot,CamTLConnection ** connection)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
cam_tl_connection_delete(CamTLConnection * connection)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
handle_control_tpdu(CamTL * tl,CamTLConnection * connection)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
handle_data_tpdu(CamTL * tl,CamTLConnection * connection)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
foreach_connection_get(gpointer key,gpointer value,gpointer user_data)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
cam_tl_connection_poll(CamTLConnection * connection,gboolean force)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
cam_tl_read_all(CamTL * tl,gboolean poll)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
cam_tl_connection_write(CamTLConnection * connection,guint8 * buffer,guint buffer_size,guint body_length)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 }
571