/* * camtransport.c - GStreamer CAM (EN50221) transport layer * Copyright (C) 2007 Alessandro Decina * * Authors: * Alessandro Decina * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "camtransport.h" #include #include #include #include #include #define GST_CAT_DEFAULT cam_debug_cat #define READ_TIMEOUT_SEC 2 #define READ_TIMEOUT_USEC 0 #define POLL_INTERVAL 0.300 #define TAG_SB 0x80 #define TAG_RCV 0x81 #define TAG_CREATE_T_C 0x82 #define TAG_C_T_C_REPLY 0x83 #define TAG_DELETE_T_C 0x84 #define TAG_D_T_C_REPLY 0x85 #define TAG_REQUEST_T_C 0x86 #define TAG_NEW_T_C 0x87 #define TAG_T_C_ERROR 0x88 #define TAG_DATA_MORE 0xA1 #define TAG_DATA_LAST 0xA0 /* utility struct used to store the state of the connections in cam_tl_read_next */ typedef struct { GList *active; GList *idle; } CamTLConnectionsStatus; void cam_gst_util_dump_mem (const guchar * mem, guint size); CamTLConnection * cam_tl_connection_new (CamTL * tl, guint8 id) { CamTLConnection *connection; connection = g_new0 (CamTLConnection, 1); connection->tl = tl; connection->id = id; connection->state = CAM_TL_CONNECTION_STATE_CLOSED; connection->has_data = FALSE; return connection; } static void cam_tl_connection_destroy (CamTLConnection * connection) { if (connection->last_poll) g_timer_destroy (connection->last_poll); g_free (connection); } CamTL * cam_tl_new (int fd) { CamTL *tl; tl = g_new0 (CamTL, 1); tl->fd = fd; tl->connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) cam_tl_connection_destroy); return tl; } void cam_tl_destroy (CamTL * tl) { g_hash_table_destroy (tl->connections); g_free (tl); } /* read data from the module without blocking indefinitely */ static CamReturn cam_tl_read_timeout (CamTL * tl, struct timeval *timeout) { fd_set read_fd; int sret; FD_ZERO (&read_fd); FD_SET (tl->fd, &read_fd); sret = select (tl->fd + 1, &read_fd, NULL, NULL, timeout); if (sret == 0) { GST_DEBUG ("read timeout"); return CAM_RETURN_TRANSPORT_TIMEOUT; } tl->buffer_size = read (tl->fd, &tl->buffer, HOST_BUFFER_SIZE); if (tl->buffer_size == -1) { GST_ERROR ("error reading tpdu: %s", g_strerror (errno)); return CAM_RETURN_TRANSPORT_ERROR; } return CAM_RETURN_OK; } /* read data from the module using the default timeout */ static CamReturn cam_tl_read (CamTL * tl) { struct timeval timeout; timeout.tv_sec = READ_TIMEOUT_SEC; timeout.tv_usec = READ_TIMEOUT_USEC; return cam_tl_read_timeout (tl, &timeout); } /* get the number of bytes to allocate for a TPDU with a body of body_length * bytes. Also get the offset from the beginning of the buffer that marks the * position of the first byte of the TPDU body */ void cam_tl_calc_buffer_size (CamTL * tl, guint body_length, guint * buffer_size, guint * offset) { guint length_field_len; /* the size of a TPDU is: * 1 byte slot number * 1 byte connection id * length_field_len bytes length field * 1 byte connection id * body_length bytes body */ /* get the length of the lenght_field block */ length_field_len = cam_calc_length_field_size (body_length); *offset = 3 + length_field_len + 1; *buffer_size = *offset + body_length; } /* write the header of a TPDU * NOTE: this function assumes that the buffer is large enough to contain the * complete TPDU (see cam_tl_calc_buffer_size ()) and that enough space has been * left from the beginning of the buffer to write the TPDU header. */ static CamReturn cam_tl_connection_write_tpdu (CamTLConnection * connection, guint8 tag, guint8 * buffer, guint buffer_size, guint body_length) { int sret; CamTL *tl = connection->tl; guint8 length_field_len; /* slot number */ buffer[0] = connection->slot; /* connection number */ buffer[1] = connection->id; /* tag */ buffer[2] = tag; /* length can take 1 to 4 bytes */ length_field_len = cam_write_length_field (&buffer[3], body_length); buffer[3 + length_field_len] = connection->id; GST_DEBUG ("writing TPDU %x connection %d", buffer[2], connection->id); //cam_gst_util_dump_mem (buffer, buffer_size); sret = write (tl->fd, buffer, buffer_size); if (sret == -1) { GST_ERROR ("error witing TPDU (%d): %s", errno, g_strerror (errno)); return CAM_RETURN_TRANSPORT_ERROR; } tl->expected_tpdus += 1; return CAM_RETURN_OK; } /* convenience function to write control TPDUs (TPDUs having a single-byte body) */ static CamReturn cam_tl_connection_write_control_tpdu (CamTLConnection * connection, guint8 tag) { guint8 tpdu[5]; /* TPDU layout (5 bytes): * * slot number (1 byte) * connection id (1 byte) * tag (1 byte) * length (1 byte) * connection id (1 byte) */ return cam_tl_connection_write_tpdu (connection, tag, tpdu, 5, 1); } /* read the next TPDU from the CAM */ static CamReturn cam_tl_read_tpdu_next (CamTL * tl, CamTLConnection ** out_connection) { CamReturn ret; CamTLConnection *connection; guint8 slot; guint8 connection_id; guint8 *tpdu; guint8 length_field_len; guint8 status; ret = cam_tl_read (tl); if (CAM_FAILED (ret)) return ret; tpdu = tl->buffer; /* must hold at least slot, connection_id, 1byte length_field, connection_id */ if (tl->buffer_size < 4) { GST_ERROR ("invalid TPDU length %d", tl->buffer_size); return CAM_RETURN_TRANSPORT_ERROR; } /* LPDU slot */ slot = tpdu[0]; /* LPDU connection id */ connection_id = tpdu[1]; connection = g_hash_table_lookup (tl->connections, GINT_TO_POINTER ((guint) connection_id)); if (connection == NULL) { /* WHAT? */ GST_ERROR ("CAM sent a TPDU on an unknown connection: %d", connection_id); return CAM_RETURN_TRANSPORT_ERROR; } /* read the length_field () */ length_field_len = cam_read_length_field (&tpdu[3], &tl->body_length); if (tl->body_length + 3 > tl->buffer_size) { GST_ERROR ("invalid TPDU length_field (%d) exceeds " "the size of the buffer (%d)", tl->body_length, tl->buffer_size); return CAM_RETURN_TRANSPORT_ERROR; } /* skip slot + connection id + tag + lenght_field () + connection id */ tl->body = tpdu + 4 + length_field_len; /* do not count the connection id byte as part of the body */ tl->body_length -= 1; if (tl->buffer[tl->buffer_size - 4] != TAG_SB) { GST_ERROR ("no TAG_SB appended to TPDU"); return CAM_RETURN_TRANSPORT_ERROR; } status = tl->buffer[tl->buffer_size - 1]; if (status & 0x80) { connection->has_data = TRUE; } else { connection->has_data = FALSE; } GST_DEBUG ("received TPDU %x more data %d", tpdu[2], connection->has_data); tl->expected_tpdus -= 1; *out_connection = connection; return CAM_RETURN_OK; } /* create a connection with the module */ CamReturn cam_tl_create_connection (CamTL * tl, guint8 slot, CamTLConnection ** connection) { CamReturn ret; CamTLConnection *conn = NULL; if (tl->connection_ids == 255) return CAM_RETURN_TRANSPORT_TOO_MANY_CONNECTIONS; conn = cam_tl_connection_new (tl, ++tl->connection_ids); /* send a TAG_CREATE_T_C TPDU */ ret = cam_tl_connection_write_control_tpdu (conn, TAG_CREATE_T_C); if (CAM_FAILED (ret)) goto error; g_hash_table_insert (tl->connections, GINT_TO_POINTER (conn->id), conn); *connection = conn; return CAM_RETURN_OK; error: if (conn) cam_tl_connection_destroy (conn); return ret; } CamReturn cam_tl_connection_delete (CamTLConnection * connection) { CamReturn ret; ret = cam_tl_connection_write_control_tpdu (connection, TAG_DELETE_T_C); if (CAM_FAILED (ret)) return ret; connection->state = CAM_TL_CONNECTION_STATE_IN_DELETION; return CAM_RETURN_OK; } CamReturn handle_control_tpdu (CamTL * tl, CamTLConnection * connection) { if (tl->body_length != 0) { GST_ERROR ("got control tpdu of invalid length: %d", tl->body_length); return CAM_RETURN_TRANSPORT_ERROR; } switch (tl->buffer[2]) { /* create transport connection reply */ case TAG_C_T_C_REPLY: /* a connection might be closed before it's acknowledged */ if (connection->state != CAM_TL_CONNECTION_STATE_IN_DELETION) { GST_DEBUG ("connection created %d", connection->id); connection->state = CAM_TL_CONNECTION_STATE_OPEN; if (tl->connection_created) tl->connection_created (tl, connection); } break; /* delete transport connection reply */ case TAG_D_T_C_REPLY: connection->state = CAM_TL_CONNECTION_STATE_CLOSED; GST_DEBUG ("connection closed %d", connection->id); if (tl->connection_deleted) tl->connection_deleted (tl, connection); g_hash_table_remove (tl->connections, GINT_TO_POINTER ((guint) connection->id)); break; } return CAM_RETURN_OK; } CamReturn handle_data_tpdu (CamTL * tl, CamTLConnection * connection) { if (tl->body_length == 0) { /* FIXME: figure out why this seems to happen from time to time with the * predator cam */ GST_WARNING ("Empty data TPDU received"); return CAM_RETURN_OK; } if (tl->connection_data) return tl->connection_data (tl, connection, tl->body, tl->body_length); return CAM_RETURN_OK; } static void foreach_connection_get (gpointer key, gpointer value, gpointer user_data) { GList **lst = (GList **) user_data; *lst = g_list_append (*lst, value); } CamReturn cam_tl_connection_poll (CamTLConnection * connection, gboolean force) { CamReturn ret; if (connection->last_poll == NULL) { connection->last_poll = g_timer_new (); } else if (!force && g_timer_elapsed (connection->last_poll, NULL) < POLL_INTERVAL) { return CAM_RETURN_TRANSPORT_POLL; } GST_DEBUG ("polling connection %d", connection->id); ret = cam_tl_connection_write_control_tpdu (connection, TAG_DATA_LAST); if (CAM_FAILED (ret)) return ret; g_timer_start (connection->last_poll); return CAM_RETURN_OK; } /* read all the queued TPDUs */ CamReturn cam_tl_read_all (CamTL * tl, gboolean poll) { CamReturn ret = CAM_RETURN_OK; CamTLConnection *connection; GList *connections = NULL; GList *walk; gboolean done = FALSE; while (!done) { while (tl->expected_tpdus) { /* read the next TPDU from the connection */ ret = cam_tl_read_tpdu_next (tl, &connection); if (CAM_FAILED (ret)) { GST_ERROR ("error reading TPDU from module: %d", ret); goto out; } switch (tl->buffer[2]) { case TAG_C_T_C_REPLY: case TAG_D_T_C_REPLY: connection->empty_data = 0; ret = handle_control_tpdu (tl, connection); break; case TAG_DATA_MORE: case TAG_DATA_LAST: connection->empty_data = 0; ret = handle_data_tpdu (tl, connection); break; case TAG_SB: /* this is handled by tpdu_next */ break; } if (CAM_FAILED (ret)) goto out; } done = TRUE; connections = NULL; g_hash_table_foreach (tl->connections, foreach_connection_get, &connections); for (walk = connections; walk; walk = walk->next) { CamTLConnection *connection = CAM_TL_CONNECTION (walk->data); if (connection->has_data == TRUE && connection->empty_data < 10) { ret = cam_tl_connection_write_control_tpdu (connection, TAG_RCV); if (CAM_FAILED (ret)) { g_list_free (connections); goto out; } /* increment the empty_data counter. If we get data, this will be reset * to 0 */ connection->empty_data++; done = FALSE; } else if (poll) { ret = cam_tl_connection_poll (connection, FALSE); if (ret == CAM_RETURN_TRANSPORT_POLL) continue; if (CAM_FAILED (ret)) { g_list_free (connections); goto out; } done = FALSE; } } g_list_free (connections); } out: return ret; } CamReturn cam_tl_connection_write (CamTLConnection * connection, guint8 * buffer, guint buffer_size, guint body_length) { return cam_tl_connection_write_tpdu (connection, TAG_DATA_LAST, buffer, buffer_size, 1 + body_length); }