2 * camtransport.c - GStreamer CAM (EN50221) transport layer
3 * Copyright (C) 2007 Alessandro Decina
6 * Alessandro Decina <alessandro.d@gmail.com>
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.
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.
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.
24 #include "camtransport.h"
25 #include <sys/select.h>
27 #include <sys/types.h>
31 #define GST_CAT_DEFAULT cam_debug_cat
32 #define READ_TIMEOUT_SEC 2
33 #define READ_TIMEOUT_USEC 0
35 #define POLL_INTERVAL 0.300
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
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
63 const gchar *description;
66 static const CamTagMessage debugmessage[] = {
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"}
87 static inline const gchar *
88 tag_get_name (guint tagid)
92 for (i = 0; i < G_N_ELEMENTS (debugmessage); i++)
93 if (debugmessage[i].tagid == tagid)
94 return debugmessage[i].description;
98 /* utility struct used to store the state of the connections in cam_tl_read_next
104 } CamTLConnectionsStatus;
106 void cam_gst_util_dump_mem (const guchar * mem, guint size);
108 static CamTLConnection *
109 cam_tl_connection_new (CamTL * tl, guint8 id)
111 CamTLConnection *connection;
113 connection = g_new0 (CamTLConnection, 1);
116 connection->state = CAM_TL_CONNECTION_STATE_CLOSED;
117 connection->has_data = FALSE;
123 cam_tl_connection_destroy (CamTLConnection * connection)
125 if (connection->last_poll)
126 g_timer_destroy (connection->last_poll);
136 tl = g_new0 (CamTL, 1);
138 tl->connections = g_hash_table_new_full (g_direct_hash, g_direct_equal,
139 NULL, (GDestroyNotify) cam_tl_connection_destroy);
145 cam_tl_destroy (CamTL * tl)
147 g_hash_table_destroy (tl->connections);
152 /* read data from the module without blocking indefinitely */
154 cam_tl_read_timeout (CamTL * tl, struct timeval *timeout)
160 FD_SET (tl->fd, &read_fd);
162 sret = select (tl->fd + 1, &read_fd, NULL, NULL, timeout);
164 GST_DEBUG ("read timeout");
165 return CAM_RETURN_TRANSPORT_TIMEOUT;
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;
174 return CAM_RETURN_OK;
177 /* read data from the module using the default timeout */
179 cam_tl_read (CamTL * tl)
181 struct timeval timeout;
183 timeout.tv_sec = READ_TIMEOUT_SEC;
184 timeout.tv_usec = READ_TIMEOUT_USEC;
186 return cam_tl_read_timeout (tl, &timeout);
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 */
193 cam_tl_calc_buffer_size (CamTL * tl, guint body_length,
194 guint * buffer_size, guint * offset)
196 guint length_field_len;
198 /* the size of a TPDU is:
200 * 1 byte connection id
201 * length_field_len bytes length field
202 * 1 byte connection id
203 * body_length bytes body
206 /* get the length of the lenght_field block */
207 length_field_len = cam_calc_length_field_size (body_length);
209 *offset = 3 + length_field_len + 1;
210 *buffer_size = *offset + body_length;
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.
219 cam_tl_connection_write_tpdu (CamTLConnection * connection,
220 guint8 tag, guint8 * buffer, guint buffer_size, guint body_length)
223 CamTL *tl = connection->tl;
224 guint8 length_field_len;
227 buffer[0] = connection->slot;
228 /* connection number */
229 buffer[1] = connection->id;
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;
236 GST_DEBUG ("writing TPDU %x (%s) connection %d (size:%d)",
237 buffer[2], tag_get_name (buffer[2]), connection->id, buffer_size);
239 //cam_gst_util_dump_mem (buffer, buffer_size);
241 sret = write (tl->fd, buffer, buffer_size);
243 GST_ERROR ("error witing TPDU (%d): %s", errno, g_strerror (errno));
244 return CAM_RETURN_TRANSPORT_ERROR;
247 tl->expected_tpdus += 1;
249 GST_DEBUG ("Success writing tpdu 0x%x (%s)", buffer[2],
250 tag_get_name (buffer[2]));
252 return CAM_RETURN_OK;
255 /* convenience function to write control TPDUs (TPDUs having a single-byte body)
258 cam_tl_connection_write_control_tpdu (CamTLConnection * connection, guint8 tag)
262 /* TPDU layout (5 bytes):
264 * slot number (1 byte)
265 * connection id (1 byte)
268 * connection id (1 byte)
271 return cam_tl_connection_write_tpdu (connection, tag, tpdu, 5, 1);
274 /* read the next TPDU from the CAM */
276 cam_tl_read_tpdu_next (CamTL * tl, CamTLConnection ** out_connection)
279 CamTLConnection *connection;
280 guint8 connection_id;
282 guint8 length_field_len;
285 ret = cam_tl_read (tl);
286 if (CAM_FAILED (ret))
291 /* must hold at least slot, connection_id, 1byte length_field, connection_id
293 if (tl->buffer_size < 4) {
294 GST_ERROR ("invalid TPDU length %d", tl->buffer_size);
295 return CAM_RETURN_TRANSPORT_ERROR;
299 /* slot = tpdu[0]; */
300 /* LPDU connection id */
301 connection_id = tpdu[1];
303 connection = g_hash_table_lookup (tl->connections,
304 GINT_TO_POINTER ((guint) connection_id));
305 if (connection == NULL) {
307 GST_ERROR ("CAM sent a TPDU on an unknown connection: %d", connection_id);
308 return CAM_RETURN_TRANSPORT_ERROR;
311 /* read the length_field () */
312 length_field_len = cam_read_length_field (&tpdu[3], &tl->body_length);
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;
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;
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;
330 status = tl->buffer[tl->buffer_size - 1];
332 connection->has_data = TRUE;
334 connection->has_data = FALSE;
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;
341 *out_connection = connection;
343 return CAM_RETURN_OK;
346 /* create a connection with the module */
348 cam_tl_create_connection (CamTL * tl, guint8 slot,
349 CamTLConnection ** connection)
352 CamTLConnection *conn = NULL;
355 if (tl->connection_ids == 255)
356 return CAM_RETURN_TRANSPORT_TOO_MANY_CONNECTIONS;
358 conn = cam_tl_connection_new (tl, ++tl->connection_ids);
360 /* Some CA devices take a long time to set themselves up,
361 * therefore retry every 250ms (for a maximum of 2.5s)
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))
370 GST_DEBUG ("Failed sending initial connection message .. but retrying");
373 g_usleep (G_USEC_PER_SEC / 4);
376 g_hash_table_insert (tl->connections, GINT_TO_POINTER (conn->id), conn);
380 return CAM_RETURN_OK;
384 cam_tl_connection_destroy (conn);
390 cam_tl_connection_delete (CamTLConnection * connection)
394 ret = cam_tl_connection_write_control_tpdu (connection, TAG_DELETE_T_C);
395 if (CAM_FAILED (ret))
398 connection->state = CAM_TL_CONNECTION_STATE_IN_DELETION;
400 return CAM_RETURN_OK;
404 handle_control_tpdu (CamTL * tl, CamTLConnection * connection)
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;
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;
419 if (tl->connection_created)
420 tl->connection_created (tl, connection);
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);
428 if (tl->connection_deleted)
429 tl->connection_deleted (tl, connection);
431 g_hash_table_remove (tl->connections,
432 GINT_TO_POINTER ((guint) connection->id));
436 return CAM_RETURN_OK;
440 handle_data_tpdu (CamTL * tl, CamTLConnection * connection)
442 if (tl->body_length == 0) {
443 /* FIXME: figure out why this seems to happen from time to time with the
445 GST_WARNING ("Empty data TPDU received");
446 return CAM_RETURN_OK;
449 if (tl->connection_data)
450 return tl->connection_data (tl, connection, tl->body, tl->body_length);
452 return CAM_RETURN_OK;
456 foreach_connection_get (gpointer key, gpointer value, gpointer user_data)
458 GList **lst = (GList **) user_data;
460 *lst = g_list_append (*lst, value);
464 cam_tl_connection_poll (CamTLConnection * connection, gboolean force)
468 if (connection->last_poll == NULL) {
469 connection->last_poll = g_timer_new ();
471 g_timer_elapsed (connection->last_poll, NULL) < POLL_INTERVAL) {
472 return CAM_RETURN_TRANSPORT_POLL;
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))
480 g_timer_start (connection->last_poll);
482 return CAM_RETURN_OK;
485 /* read all the queued TPDUs */
487 cam_tl_read_all (CamTL * tl, gboolean poll)
489 CamReturn ret = CAM_RETURN_OK;
490 CamTLConnection *connection;
491 GList *connections = NULL;
493 gboolean done = FALSE;
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);
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);
512 connection->empty_data = 0;
513 ret = handle_data_tpdu (tl, connection);
516 /* this is handled by tpdu_next */
520 if (CAM_FAILED (ret))
527 g_hash_table_foreach (tl->connections,
528 foreach_connection_get, &connections);
530 for (walk = connections; walk; walk = walk->next) {
531 CamTLConnection *connection = CAM_TL_CONNECTION (walk->data);
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);
539 /* increment the empty_data counter. If we get data, this will be reset
541 connection->empty_data++;
544 ret = cam_tl_connection_poll (connection, FALSE);
545 if (ret == CAM_RETURN_TRANSPORT_POLL)
548 if (CAM_FAILED (ret)) {
549 g_list_free (connections);
557 g_list_free (connections);
565 cam_tl_connection_write (CamTLConnection * connection,
566 guint8 * buffer, guint buffer_size, guint body_length)
568 return cam_tl_connection_write_tpdu (connection,
569 TAG_DATA_LAST, buffer, buffer_size, 1 + body_length);