mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-03 16:09:39 +00:00
570 lines
15 KiB
C
570 lines
15 KiB
C
/*
|
|
* camtransport.c - GStreamer CAM (EN50221) transport layer
|
|
* Copyright (C) 2007 Alessandro Decina
|
|
*
|
|
* Authors:
|
|
* Alessandro Decina <alessandro@nnva.org>
|
|
*
|
|
* 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., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "camtransport.h"
|
|
#include <sys/select.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
#define GST_CAT_DEFAULT cam_debug_cat
|
|
#define READ_TIMEOUT_SEC 2
|
|
#define READ_TIMEOUT_USEC 0
|
|
|
|
#define POLL_INTERVAL 0.300
|
|
|
|
/* Layer tags */
|
|
#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
|
|
|
|
/* Session tags */
|
|
#define TAG_SESSION_NUMBER 0x90
|
|
#define TAG_OPEN_SESSION_REQUEST 0x91
|
|
#define TAG_OPEN_SESSION_RESPONSE 0x92
|
|
#define TAG_CREATE_SESSION 0x93
|
|
#define TAG_CREATE_SESSION_RESPONSE 0x94
|
|
#define TAG_CLOSE_SESSION_REQUEST 0x95
|
|
#define TAG_CLOSE_SESSION_RESPONSE 0x96
|
|
|
|
|
|
typedef struct
|
|
{
|
|
guint tagid;
|
|
const gchar *description;
|
|
} CamTagMessage;
|
|
|
|
static CamTagMessage debugmessage[] = {
|
|
{TAG_SB, "SB"},
|
|
{TAG_RCV, "RCV"},
|
|
{TAG_CREATE_T_C, "CREATE_T_C"},
|
|
{TAG_C_T_C_REPLY, "CREATE_T_C_REPLY"},
|
|
{TAG_DELETE_T_C, "DELETE_T_C"},
|
|
{TAG_D_T_C_REPLY, "DELETE_T_C_REPLY"},
|
|
{TAG_REQUEST_T_C, "REQUEST_T_C"},
|
|
{TAG_NEW_T_C, "NEW_T_C"},
|
|
{TAG_T_C_ERROR, "T_C_ERROR"},
|
|
{TAG_SESSION_NUMBER, "SESSION_NUMBER"},
|
|
{TAG_OPEN_SESSION_REQUEST, "OPEN_SESSION_REQUEST"},
|
|
{TAG_OPEN_SESSION_RESPONSE, "OPEN_SESSION_RESPONSE"},
|
|
{TAG_CREATE_SESSION, "CREATE_SESSION"},
|
|
{TAG_CREATE_SESSION_RESPONSE, "CREATE_SESSION_RESPONSE"},
|
|
{TAG_CLOSE_SESSION_REQUEST, "CLOSE_SESSION_REQUEST"},
|
|
{TAG_CLOSE_SESSION_RESPONSE, "CLOSE_SESSION_RESPONSE"},
|
|
{TAG_DATA_MORE, "DATA_MORE"},
|
|
{TAG_DATA_LAST, "DATA_LAST"}
|
|
};
|
|
|
|
static inline const gchar *
|
|
tag_get_name (guint tagid)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (debugmessage); i++)
|
|
if (debugmessage[i].tagid == tagid)
|
|
return debugmessage[i].description;
|
|
return "UNKNOWN";
|
|
}
|
|
|
|
/* 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);
|
|
|
|
static 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 (%s) connection %d (size:%d)",
|
|
buffer[2], tag_get_name (buffer[2]), connection->id, buffer_size);
|
|
|
|
//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;
|
|
|
|
GST_DEBUG ("Sucess writing tpdu 0x%x (%s)", buffer[2],
|
|
tag_get_name (buffer[2]));
|
|
|
|
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 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 (%s) more data %d", tpdu[2],
|
|
tag_get_name (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;
|
|
guint count = 10;
|
|
|
|
if (tl->connection_ids == 255)
|
|
return CAM_RETURN_TRANSPORT_TOO_MANY_CONNECTIONS;
|
|
|
|
conn = cam_tl_connection_new (tl, ++tl->connection_ids);
|
|
|
|
/* Some CA devices take a long time to set themselves up,
|
|
* therefore retry every 250ms (for a maximum of 2.5s)
|
|
*/
|
|
while (TRUE) {
|
|
/* send a TAG_CREATE_T_C TPDU */
|
|
ret = cam_tl_connection_write_control_tpdu (conn, TAG_CREATE_T_C);
|
|
if (!CAM_FAILED (ret))
|
|
break;
|
|
if (!count)
|
|
goto error;
|
|
GST_DEBUG ("Failed sending initial connection message .. but retrying");
|
|
count--;
|
|
/* Wait 250ms */
|
|
g_usleep (G_USEC_PER_SEC / 4);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static 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);
|
|
}
|