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 ("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
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