sml_transport.c
/*
* libsyncml - A syncml protocol implementation
* Copyright (C) 2005 Armin Bauer <armin.bauer@opensync.org>
* Copyright (C) 2007-2009 Michael Bell <michael.bell@opensync.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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 "syncml.h"
#include "syncml_internals.h"
#include "sml_error_internals.h"
#include "sml_transport_internals.h"
#ifdef ENABLE_HTTP
#include "transports/http_client_internals.h"
#include "transports/http_server_internals.h"
#endif
#ifdef ENABLE_OBEX
#include "transports/obex_client_internals.h"
#include "transports/obex_server_internals.h"
#endif
static SmlBool smlTransportIsServer(SmlTransport *tsp)
{
smlAssert(tsp);
if (tsp->type == SML_TRANSPORT_HTTP_SERVER)
return TRUE;
if (tsp->type == SML_TRANSPORT_OBEX_SERVER)
return TRUE;
return FALSE;
}
void smlTransportSetEventCallback(SmlTransport *tsp, SmlTransportEventCb callback, void *userdata)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, tsp, callback, userdata);
smlAssert(tsp);
tsp->event_callback = callback;
tsp->event_callback_userdata = userdata;
/* Do not return until all running callbacks finished.
* This is a busy wait and yes it can happen that new
* callbacks are started during sleep but the setter
* must get a warranty that the old callback is no
* longer in use.
*
* This is acceptable because the callback is only set
* two times. First it is set when the manager registers
* the transport and second it is set when the manager
* is freed. So this is a protection against sending an
* event to an already freed manager.
*/
while (g_atomic_int_get(&(tsp->event_callback_ref_count)) > 0)
{
usleep(50);
}
smlTrace(TRACE_EXIT, "%s", __func__);
}
SmlBool smlTransportSend(SmlTransport *tsp, SmlLink *link_, SmlTransportData *data, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, tsp, link_, data, error);
CHECK_ERROR_REF
smlAssert(tsp);
smlAssert(data);
if (tsp->cached_error != NULL)
{
// the most parameters should be NULL because the cached error
// has nothing to do with the actual send request
smlTransportReceiveEvent(tsp, NULL, SML_TRANSPORT_EVENT_ERROR, NULL, NULL);
goto error;
}
SmlTransportCommand *cmd = smlTryMalloc0(sizeof(SmlTransportCommand), error);
if (!cmd) {
smlTransportReceiveEvent(tsp, NULL, SML_TRANSPORT_EVENT_ERROR, NULL, *error);
goto error;
}
cmd->type = SML_TRANSPORT_CMD_SEND;
cmd->data = data;
if (link_) {
cmd->link = link_;
smlLinkRef(cmd->link);
}
smlTransportDataRef(cmd->data);
//Call the fin function
smlQueueSend(tsp->command_queue, cmd);
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return FALSE;
}
void smlTransportWorkerHandler(void *message, void *userdata)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, message, userdata);
smlAssert(message);
smlAssert(userdata);
SmlTransportCommand *cmd = message;
SmlTransport *tsp = userdata;
switch (cmd->type) {
case SML_TRANSPORT_CMD_SEND:
tsp->functions.send(tsp->transport_data, cmd->link ? cmd->link->link_data : NULL, cmd->data, cmd->error);
break;
case SML_TRANSPORT_CMD_CONNECT:
if (!tsp->functions.connect) {
smlTransportReceiveEvent(tsp, NULL, SML_TRANSPORT_EVENT_CONNECT_DONE, NULL, NULL);
smlTrace(TRACE_INTERNAL, "%s: No connect function", __func__);
break;
}
tsp->functions.connect(tsp->transport_data);
break;
case SML_TRANSPORT_CMD_DISCONNECT:
if (!tsp->functions.disconnect) {
smlTransportReceiveEvent(tsp, NULL, SML_TRANSPORT_EVENT_DISCONNECT_DONE, NULL, NULL);
smlTrace(TRACE_INTERNAL, "%s: No disconnect function", __func__);
break;
}
/* If there is a disconnect request
* but the connection is already down
* then an earlier disconnect happened
* (perhaps initiated by the remote peer)
* and we can ignore the disconnect.
*/
if (smlTransportIsServer(tsp) && !cmd->link->link_data) {
/* The SERVER is already disconnected. */
smlTrace(TRACE_INTERNAL,
"%s: The server link was already disconnected.",
__func__);
break;
}
if (!smlTransportIsServer(tsp) && !tsp->connected) {
/* The CLIENT is already disconnected. */
smlTrace(TRACE_INTERNAL,
"%s: The client was already disconnected.",
__func__);
break;
}
tsp->functions.disconnect(tsp->transport_data, cmd->link ? cmd->link->link_data : NULL);
break;
}
if (cmd->link)
smlLinkDeref(cmd->link);
if (cmd->data)
smlTransportDataDeref(cmd->data);
smlSafeFree((gpointer *)&cmd);
smlTrace(TRACE_EXIT, "%s", __func__);
return;
}
SmlBool smlTransportReceiveEvent(SmlTransport *tsp, SmlLink *link_, SmlTransportEventType type, SmlTransportData *data, SmlError *error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %i, %p, %p)", __func__, tsp, link_, type, data, error);
smlAssert(tsp);
SmlBool ret = TRUE;
if (tsp->event_callback == NULL)
{
smlTrace(TRACE_INTERNAL, "%s: no callback available", __func__);
if (type != SML_TRANSPORT_EVENT_ERROR &&
type != SML_TRANSPORT_EVENT_DISCONNECT_DONE)
{
/* Somebody forget to set the event callback.
* This is a heavy bug which cannot be compensated.
* The whole state management will fail.
*/
g_error("The transport layer of type %d " \
"must send a normal event %d " \
"but there is no event callback set.",
tsp->type, type);
/* abort if g_error is not fatal */
exit(1);
}
/* If there is a cached error then the transport layer
* uses the error event without an error as a trigger
* to send the original error.
*/
if (tsp->cached_error && error != NULL)
{
/* Wow, this is the second error and
* there is still no event callback.
*/
g_warning("The transport layer already caches an error " \
"because the event callback is not present until now." \
"The received error is ignored. %s",
smlErrorPrint(&error));
// do not free the error here because you are not the owner of the error
}
if (!tsp->cached_error && error) {
// we stop all communications here
smlTrace(TRACE_INTERNAL, "%s: init failed in transport protocol -> %s", __func__, smlErrorPrint(&error));
tsp->state = SML_TRANSPORT_ERROR;
tsp->cached_error = error;
smlErrorRef(&error);
}
} else {
smlTrace(TRACE_INTERNAL, "%s: callback available", __func__);
if (tsp->cached_error != NULL)
{
/* First we have to send the cached error. */
smlTrace(TRACE_INTERNAL, "%s: cached error detected - %s", __func__,
smlErrorPrint(&(tsp->cached_error)));
g_atomic_int_inc(&(tsp->event_callback_ref_count));
ret = tsp->event_callback(
tsp, NULL,
SML_TRANSPORT_EVENT_ERROR, NULL,
tsp->cached_error, tsp->event_callback_userdata);
smlTrace(TRACE_INTERNAL, "%s: %d event callbacks",
__func__, g_atomic_int_dec_and_test(&(tsp->event_callback_ref_count)));
/* Second we have to prepare the internal states. */
/* Third we have to send the original event. */
}
/* If a connect or disconnect is received without a link
* then this means that this is a new connection or a
* closed connection of a client. If such a constallation
* happens with a server then this is a fatal error.
*
* If a connect is reveived with a link
* then this must be a server and
* the link_data must be set.
*
* If a disconnect is received with a link
* then this must be a server and
* the link_data must not be present
* because the link is down now.
*/
if (type == SML_TRANSPORT_EVENT_CONNECT_DONE && !link_)
{
if (smlTransportIsServer(tsp))
g_error("A connect event without a link was received " \
"but the transport layer is a server.");
if (tsp->connected)
g_error("A connect event was received " \
"but the transport layer is already connected.");
smlTrace(TRACE_INTERNAL, "%s: connect + no link");
tsp->connected = TRUE;
}
if (type == SML_TRANSPORT_EVENT_DISCONNECT_DONE && !link_)
{
if (smlTransportIsServer(tsp))
g_error("A disconnect event without a link was received " \
"but the transport layer is a server.");
if (!tsp->connected)
g_error("A disconnect event was received " \
"but there is no connected transport.");
smlTrace(TRACE_INTERNAL, "%s: disconnect + no link");
tsp->connected = FALSE;
}
if (type == SML_TRANSPORT_EVENT_CONNECT_DONE && link_)
{
if (!smlTransportIsServer(tsp))
g_error("A connect event with a link was received " \
"but the transport layer is a server.");
if (!link_->link_data)
g_error("A connect event with a link was received " \
"but the link does not contain the required " \
"transport environment.");
smlTrace(TRACE_INTERNAL, "%s: connect + link");
g_mutex_lock(tsp->connections_mutex);
tsp->connections++;
g_mutex_unlock(tsp->connections_mutex);
}
if (type == SML_TRANSPORT_EVENT_DISCONNECT_DONE && link_)
{
if (!smlTransportIsServer(tsp))
g_error("A disconnect event with a link was received " \
"but the transport layer is not a server.");
if (link_->link_data)
g_error("A disconnect event with a link was received " \
"but the link still contains the " \
"transport environment.");
smlTrace(TRACE_INTERNAL, "%s: disconnect + link");
g_mutex_lock(tsp->connections_mutex);
tsp->connections--;
g_mutex_unlock(tsp->connections_mutex);
}
/* now execute the callback */
if (!(tsp->cached_error &&
type == SML_TRANSPORT_EVENT_ERROR &&
error == NULL))
{
/* send the event */
g_atomic_int_inc(&(tsp->event_callback_ref_count));
ret = tsp->event_callback(tsp, link_, type, data, error, tsp->event_callback_userdata);
smlTrace(TRACE_INTERNAL, "%s: %d event callbacks",
__func__, g_atomic_int_dec_and_test(&(tsp->event_callback_ref_count)));
}
if (tsp->cached_error) {
smlErrorDeref(&(tsp->cached_error));
tsp->cached_error = NULL;
}
} /* end of tsp->event_callback */
smlTrace(TRACE_EXIT, "%s: %i", __func__, ret);
return ret;
}
/* FIXME: DEPRECATED */
SmlBool smlTransportRunAsync(SmlTransport *tsp, SmlError **error)
{
smlTrace(TRACE_ERROR, "%s(%p,%p)", __func__, tsp, error);
CHECK_ERROR_REF
/* All transports run in asynchronous mode.
* Therefore this function should never be called.
* The functions always returns TRUE to avoid any complications.
*/
return TRUE;
}
static void _smlTransportStop(SmlTransport *tsp)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, tsp);
smlAssert(tsp->thread);
smlThreadStop(tsp->thread);
smlThreadFree(tsp->thread);
tsp->thread = NULL;
smlTrace(TRACE_EXIT, "%s", __func__);
}
/* FIXME: DEPRECATED */
void smlTransportStop(SmlTransport *tsp)
{
_smlTransportStop(tsp);
}
SmlBool smlTransportConnect(SmlTransport *tsp, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, tsp, error);
CHECK_ERROR_REF
smlAssert(tsp);
/* Only clients can actively connect and
* a client can only connect once.
*/
if (smlTransportIsServer(tsp))
{
smlErrorSet(error, SML_ERROR_GENERIC,
"Only a transport client can be actively connected.");
goto error;
}
if (tsp->connected)
{
smlErrorSet(error, SML_ERROR_GENERIC,
"A transport client can be connected only once.");
goto error;
}
/* put the command into the queue */
SmlTransportCommand *cmd = smlTryMalloc0(sizeof(SmlTransportCommand), error);
if (!cmd)
goto error;
cmd->type = SML_TRANSPORT_CMD_CONNECT;
//Call the connect function
smlQueueSend(tsp->command_queue, cmd);
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return FALSE;
}
SmlBool smlTransportDisconnect(SmlTransport *tsp, SmlLink *link_, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, tsp, link_, error);
CHECK_ERROR_REF
smlAssert(tsp);
/* The transport must (still) be connected. */
if (!tsp->connected)
{
smlErrorSet(error, SML_ERROR_GENERIC,
"The transport is not connected and so it cannot be disconnected.");
goto error;
}
/* If this is a client then there must not be a link
* because every client can have only one connection.
*/
if (link_ && !smlTransportIsServer(tsp))
{
smlErrorSet(error, SML_ERROR_GENERIC,
"A transport client has no link " \
"because there is only one connection.");
goto error;
}
/* If this is a server then there are two types of disconnects.
*
* If there is link then this is a shutdown for a connection.
* If a connection is closed then there must be link_data in
* the link because otherwise the connection is already closed.
*
* If there is no link then this is a shutdown for the server.
* This means that all links must be shut down.
*/
if (link_ && !link_->link_data)
{
/* If there is a very fast or aggressive client
* then it can happen that the client closes
* the connection and the server registers this
* event before the disconnect function was
* called.
*
* If this happens then the link_data is perhaps
* already empty and the disconnect event was
* already triggered.
*/
smlTrace(TRACE_EXIT,
"%s: A server connection should be closed " \
"but the connection is already closed.",
__func__);
return TRUE;
}
if (!link_ && smlTransportIsServer(tsp) && tsp->connections)
{
smlErrorSet(error, SML_ERROR_GENERIC,
"A server shutdown is requested " \
"but there are still open connections (%d).",
tsp->connections);
goto error;
}
/* check that the link is used at the correct transport layer
* this is also a check for the correct registration of the link
*/
if (link_)
{
if (tsp != link_->tsp)
{
smlErrorSet(error, SML_ERROR_GENERIC,
"The link %p is registered at another transport layer %p than this one %p.",
link_, link_->tsp, tsp);
goto error;
}
g_mutex_lock(tsp->links_mutex);
if (!g_hash_table_lookup(tsp->links, link_)) {
g_mutex_unlock(tsp->links_mutex);
smlErrorSet(error, SML_ERROR_GENERIC,
"The link %p is not registered at the transport layer %p",
link_, tsp);
goto error;
}
g_mutex_unlock(tsp->links_mutex);
}
/* send the command */
SmlTransportCommand *cmd = smlTryMalloc0(sizeof(SmlTransportCommand), error);
if (!cmd)
goto error;
cmd->type = SML_TRANSPORT_CMD_DISCONNECT;
if (link_) {
cmd->link = link_;
smlLinkRef(cmd->link);
}
//Call the disconnect function
smlQueueSend(tsp->command_queue, cmd);
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return FALSE;
}
SmlTransportType smlTransportGetType(SmlTransport *tsp)
{
smlAssert(tsp);
return tsp->type;
}
SmlTransportData *smlTransportDataNew(char *data, unsigned long size, SmlMimeType mimetype, SmlBool ownsData, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %d, %i, %i, %p)", __func__, data, size, mimetype, ownsData, error);
CHECK_ERROR_REF
//Append the data to the outgoing queue
SmlTransportData *cmd = smlTryMalloc0(sizeof(SmlTransportData), error);
if (!cmd)
goto error;
cmd->type = mimetype;
cmd->data = data;
cmd->size = size;
cmd->ownsData = ownsData;
cmd->refCount = 1;
cmd->needsAnswer = TRUE;
cmd->type_get = SML_MIMETYPE_UNKNOWN;
smlTrace(TRACE_EXIT, "%s: %p", __func__, cmd);
return cmd;
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return NULL;
}
SmlTransportData *smlTransportDataRef(SmlTransportData *data)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, data);
smlAssert(data);
g_atomic_int_inc(&(data->refCount));
smlTrace(TRACE_EXIT, "%s", __func__);
return data;
}
void smlTransportDataDeref(SmlTransportData *data)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, data);
smlAssert(data);
if (!g_atomic_int_dec_and_test(&(data->refCount))) {
smlTrace(TRACE_EXIT, "%s: refCount > 0", __func__);
return;
}
if (data->ownsData && data->data != NULL)
smlSafeCFree(&(data->data));
smlSafeFree((gpointer *)&data);
smlTrace(TRACE_EXIT, "%s: Freed", __func__);
}
void smlTransportSetError(SmlTransport *tsp, SmlLink *link_, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p(%p))", __func__, tsp, link_, error, error ? *error : NULL);
/* Do no use CHECK_ERROR_REF here.
* SmlError **error contains an error which should be send.
*/
smlAssert(tsp);
SmlError *locerror = NULL;
SmlTransportCommand *cmd = smlTryMalloc0(sizeof(SmlTransportCommand), &locerror);
if (!cmd)
return;
cmd->type = SML_TRANSPORT_CMD_SEND;
cmd->data = NULL;
if (link_) {
cmd->link = link_;
smlLinkRef(cmd->link);
}
if (error && *error) {
cmd->error = *error;
smlErrorRef(error);
}
//Call the fin function
smlQueueSend(tsp->command_queue, cmd);
smlTrace(TRACE_EXIT, "%s", __func__);
}
SmlLink *smlLinkNew(SmlTransport *tsp, void *link_data, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, tsp, link_data, error);
CHECK_ERROR_REF
smlAssert(link_data);
SmlLink *link_ = smlTryMalloc0(sizeof(SmlLink), error);
if (!link_)
goto error;
link_->tsp = tsp;
link_->link_data = link_data;
link_->refCount = 1;
/* register the link at the transport */
g_mutex_lock(tsp->links_mutex);
g_hash_table_insert(tsp->links, link_, GINT_TO_POINTER(1));
g_mutex_unlock(tsp->links_mutex);
smlTrace(TRACE_EXIT, "%s: %p", __func__, link_);
return link_;
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return NULL;
}
/* FIXME: DEPRECATED */
SmlLink *smlLinkFind(SmlTransport *tsp, void *link_data)
{
/* The function always returns NULL.
* The function is not removed to protect the intergrity of the library.
* Actually no user of this function is known.
*/
smlTrace(TRACE_INTERNAL, "%s(%p, %p)", __func__, tsp, link_data);
return NULL;
}
SmlLink *smlLinkRef(SmlLink *link_)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, link_);
smlAssert(link_);
g_atomic_int_inc(&(link_->refCount));
smlTrace(TRACE_EXIT, "%s", __func__);
return link_;
}
void smlLinkDeref(SmlLink *link_)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, link_);
smlAssert(link_);
if (!g_atomic_int_dec_and_test(&(link_->refCount))) {
smlTrace(TRACE_EXIT, "%s: refCount > 0", __func__);
return;
}
/* deregister the link from the transport */
g_mutex_lock(link_->tsp->links_mutex);
if (!g_hash_table_remove(link_->tsp->links, link_))
g_warning("The link %p was never registered.", link_);
g_mutex_unlock(link_->tsp->links_mutex);
/* free the memory */
smlSafeFree((gpointer *)&link_);
smlTrace(TRACE_EXIT, "%s: Freed", __func__);
}
SmlTransport *smlTransportNew(SmlTransportType type, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%i, %p)", __func__, type, error);
CHECK_ERROR_REF
/* Initialize GLib thread system. */
if (!g_thread_supported ()) g_thread_init (NULL);
g_type_init();
SmlTransport *tsp = smlTryMalloc0(sizeof(SmlTransport), error);
if (!tsp)
goto error;
tsp->type = type;
tsp->cached_error = NULL;
tsp->event_callback = NULL;
tsp->context = NULL;
switch (type) {
#ifdef ENABLE_HTTP
case SML_TRANSPORT_HTTP_SERVER:
if (!smlTransportHttpServerNew(tsp, error))
goto error_free_tsp;
break;
case SML_TRANSPORT_HTTP_CLIENT:
if (!smlTransportHttpClientNew(tsp, error))
goto error_free_tsp;
break;
#else
case SML_TRANSPORT_HTTP_SERVER:
case SML_TRANSPORT_HTTP_CLIENT:
smlErrorSet(error, SML_ERROR_GENERIC, "HTTP Transport not enabled in this build");
goto error_free_tsp;
#endif
#ifdef ENABLE_OBEX
case SML_TRANSPORT_OBEX_CLIENT:
if (!smlTransportObexClientNew(tsp, error))
goto error_free_tsp;
break;
case SML_TRANSPORT_OBEX_SERVER:
if (!smlTransportObexServerNew(tsp, error))
goto error_free_tsp;
break;
#else
case SML_TRANSPORT_OBEX_SERVER:
case SML_TRANSPORT_OBEX_CLIENT:
smlErrorSet(error, SML_ERROR_GENERIC, "OBEX Transport not enabled in this build");
goto error_free_tsp;
#endif
}
tsp->command_queue = smlQueueNew(error);
if (!tsp->command_queue)
goto error_free_tsp;
/* links must be tracked to detect server connections
* which are not cleaned up before server finalization
*
* connections must be tracked to detect open server connections
* which were not closed before server shutdown
*/
if (smlTransportIsServer(tsp))
{
tsp->links = g_hash_table_new(g_direct_hash, g_direct_equal);
tsp->links_mutex = g_mutex_new();
tsp->connections = 0;
tsp->connections_mutex = g_mutex_new();
} else {
tsp->links = NULL;
tsp->links_mutex = NULL;
tsp->connections = 0;
tsp->connections_mutex = NULL;
}
tsp->connected = FALSE;
smlTrace(TRACE_EXIT, "%s: %p", __func__, tsp);
return tsp;
error_free_tsp:
smlTransportFree(tsp);
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return NULL;
}
void smlTransportFree(SmlTransport *tsp)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, tsp);
smlAssert(tsp);
if (tsp->command_queue)
smlQueueFree(tsp->command_queue);
SmlError *error = NULL;
if (tsp->transport_data &&
!tsp->functions.finalize(tsp->transport_data, &error))
{
g_warning("The library libsyncml cannot free the transport. %s",
smlErrorPrint(&error));
smlErrorDeref(&error);
}
if (tsp->context)
g_main_context_unref(tsp->context);
tsp->context = NULL;
/* check for open connections */
if (tsp->connections)
{
g_warning("The transport layer of libsyncml is freed " \
"but not all connections were close (%d).",
tsp->connections);
}
if (tsp->connections_mutex)
g_mutex_free(tsp->connections_mutex);
/* check for forgotten connections */
if (tsp->links && g_hash_table_size(tsp->links))
g_warning("The transport layer of libsyncml is freed " \
"but not all connections were cleaned up (%d).",
g_hash_table_size(tsp->links));
if (tsp->links)
g_hash_table_unref(tsp->links);
if (tsp->links_mutex)
g_mutex_free(tsp->links_mutex);
/* check for forgotten errors */
if (tsp->cached_error) {
g_warning("The transport layer is cleaned up and an error is ignored. %s",
smlErrorPrint(&(tsp->cached_error)));
smlErrorDeref(&(tsp->cached_error));
}
/* free the memory */
smlSafeFree((gpointer *)&tsp);
smlTrace(TRACE_EXIT, "%s", __func__);
}
SmlBool smlTransportSetConfigOption(
SmlTransport *tsp,
const char *name,
const char *value,
SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %s, %s, %p)", __func__, tsp, VA_STRING(name), strcmp(name, "PASSWORD") ? VA_STRING(value) : "***sensitive***", error);
/* Do not change the assertion to simple return FALSE stuff
* because to many developers incl. me ignore returned FALSE
* if it is only configuration.
*/
CHECK_ERROR_REF
smlAssert(tsp);
smlAssert(tsp->state == SML_TRANSPORT_UNINITIALIZED);
smlAssert(tsp->functions.set_config_option);
if (!name)
{
smlErrorSet(error, SML_ERROR_INTERNAL_MISCONFIGURATION,
"The name of the configuration option is missing.");
goto error;
}
if (!tsp->functions.set_config_option(tsp, name, value, error))
goto error;
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error:
smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
return FALSE;
}
SmlBool smlTransportSetConnectionType(
SmlTransport *tsp,
SmlTransportConnectionType type,
SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %i, %p)", __func__, tsp, type, error);
/* Do not change the assertion to simple return FALSE stuff
* because to many developers incl. me ignore returned FALSE
* if it is only configuration.
*/
CHECK_ERROR_REF
smlAssert(tsp);
smlAssert(type != SML_TRANSPORT_CONNECTION_TYPE_UNKNOWN);
smlAssert(tsp->state == SML_TRANSPORT_UNINITIALIZED);
smlAssert(tsp->functions.set_connection_type);
if (tsp->functions.set_connection_type(tsp, type, error))
{
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
} else {
smlTrace(TRACE_EXIT_ERROR, "%s", __func__);
return FALSE;
}
}
SmlBool smlTransportInitialize(SmlTransport *tsp, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, tsp, error);
CHECK_ERROR_REF
smlAssert(tsp);
smlAssert(tsp->state == SML_TRANSPORT_UNINITIALIZED);
/* Intialize the context and thread for the g_main_loop. */
tsp->context = g_main_context_new();
if (!tsp->context) {
smlErrorSet(error, SML_ERROR_GENERIC,
"Cannot create new GMainContext for asynchronous transport.");
goto error;
}
tsp->thread = smlThreadNew(tsp->context, error);
if (!tsp->thread)
goto error_free_loop;
/* The transport always needs a context based loop for the
* command queue handling. Never attach this queue to
* another context because this can block the transport.
*/
/* start the queue - e.g. to handle errors */
smlQueueSetHandler(tsp->command_queue, (SmlQueueHandler)smlTransportWorkerHandler, tsp);
smlQueueAttach(tsp->command_queue, tsp->context);
if (tsp->functions.initialize && !tsp->functions.initialize(tsp, error))
goto error_detach;
/* Now start the GMainLoop.
* Do not start earlier to avoid confusion with libsoup
* because of registering a context at a running main loop. */
smlThreadStart(tsp->thread);
tsp->state = SML_TRANSPORT_INITIALIZED;
/* If this is a server then the server is running now. */
if (smlTransportIsServer(tsp))
tsp->connected = TRUE;
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error_detach:
smlQueueDetach(tsp->command_queue);
error_free_loop:
if (tsp->context) {
g_main_context_unref(tsp->context);
tsp->context = NULL;
}
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return FALSE;
}
SmlBool smlTransportSetResponseURI(
SmlTransport *tsp,
const char *uri,
SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %s, %p)", __func__, tsp, VA_STRING(uri), error);
/* Do not change the assertion to simple return FALSE stuff
* because to many developers incl. me ignore returned FALSE
* if it is only configuration.
*/
CHECK_ERROR_REF
smlAssert(tsp);
smlAssert(uri);
smlAssert(tsp->state == SML_TRANSPORT_INITIALIZED ||
tsp->state == SML_TRANSPORT_CONNECTED);
smlAssert(tsp->functions.set_response_uri);
if (tsp->functions.set_response_uri(tsp, uri, error))
{
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
} else {
smlTrace(TRACE_EXIT_ERROR, "%s", __func__);
return FALSE;
}
}
static SmlBool smlTransportDetachQueueCallback(
gpointer data,
SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s", __func__);
CHECK_ERROR_REF
smlAssert(data);
SmlQueue *queue = data;
smlQueueDetach(queue);
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
}
static SmlBool smlTransportDispatchQueueCallback(
gpointer data,
SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s", __func__);
CHECK_ERROR_REF
smlAssert(data);
SmlQueue *queue = data;
smlQueueDispatch(queue);
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
}
SmlBool smlTransportFinalize(SmlTransport *tsp, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, tsp, error);
CHECK_ERROR_REF
smlAssert(tsp);
smlAssert(tsp->functions.finalize);
if (tsp->connected && !smlTransportIsServer(tsp))
{
smlErrorSet(error, SML_ERROR_GENERIC,
"If a client transport is connected then it cannot be finalized.");
goto error;
}
if (tsp->state != SML_TRANSPORT_INITIALIZED &&
tsp->state != SML_TRANSPORT_ERROR) {
smlErrorSet(error, SML_ERROR_GENERIC, "Transport was not in the state \"Initialized\"");
goto error;
}
/* FIXME: Is there a situation where no thread is present? */
/* OBEX */
if (tsp->type != SML_TRANSPORT_HTTP_CLIENT &&
tsp->type != SML_TRANSPORT_HTTP_SERVER && tsp->thread)
_smlTransportStop(tsp);
/* stop the command queue */
if (tsp->thread) {
/* HTTP - thread safe destroy */
if (!smlThreadCallFunction(
tsp->thread,
smlTransportDetachQueueCallback,
tsp->command_queue,
error))
goto error;
} else {
/* OBEX */
smlQueueDetach(tsp->command_queue);
}
/* give all jobs a chance to finish cleanly */
unsigned int i = 0;
unsigned int queueLength = smlQueueLength(tsp->command_queue);
for (; i < queueLength; i++) {
if (tsp->thread) {
/* HTTP */
if (!smlThreadCallFunction(
tsp->thread,
smlTransportDispatchQueueCallback,
tsp->command_queue,
error))
goto error;
} else {
/* OBEX */
smlQueueDispatch(tsp->command_queue);
}
}
if (!tsp->functions.finalize(tsp->transport_data, error))
goto error;
/* HTTP */
if (tsp->thread)
_smlTransportStop(tsp);
tsp->transport_data = NULL;
tsp->state = SML_TRANSPORT_UNINITIALIZED;
/* If this is a server then the server is shutdown now. */
if (smlTransportIsServer(tsp))
tsp->connected = FALSE;
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return FALSE;
}
char * smlTransportGetResponseURI(SmlLink *link_, SmlSession *session, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, link_, session, error);
/* Do not change the assertion to simple return FALSE stuff
* because to many developers incl. me ignore returned FALSE
* if it is only configuration.
*/
CHECK_ERROR_REF
smlAssert(link_);
smlAssert(link_->tsp);
smlAssert(session);
if (link_->tsp->functions.get_response_uri)
{
char *result = link_->tsp->functions.get_response_uri(link_, session, error);
smlTrace(TRACE_EXIT, "%s - %s", __func__, VA_STRING(result));
return result;
} else {
smlTrace(TRACE_EXIT, "%s - unsupported feature", __func__);
return NULL;
}
}
smlTransportSetConfigOption
SmlBool smlTransportSetConfigOption(SmlTransport *tsp, const char *name, const char *value, SmlError **error)
Sets a configuration parameter.
Definition: sml_transport.c:883
smlErrorPrint
const char * smlErrorPrint(SmlError **error)
Returns the message of the error.
Definition: sml_error.c:299
smlTransportFinalize
SmlBool smlTransportFinalize(SmlTransport *tsp, SmlError **error)
Finalizes the transport.
Definition: sml_transport.c:1104
SmlError::type
SmlErrorType type
Definition: sml_error_internals.h:31
smlThreadCallFunction
SmlBool smlThreadCallFunction(SmlThread *thread, SmlThreadCallFunctionType func, gpointer data, SmlError **error)
Definition: sml_support.c:561
smlTransportSetConnectionType
SmlBool smlTransportSetConnectionType(SmlTransport *tsp, SmlTransportConnectionType type, SmlError **error)
Sets a configuration parameter.
Definition: sml_transport.c:931
smlQueueSend
void smlQueueSend(SmlQueue *queue, void *data)
Sends a message down a queue.
Definition: sml_queue.c:353
smlQueueSetHandler
void smlQueueSetHandler(SmlQueue *queue, SmlQueueHandler handler, void *userdata)
Sets the message handler for a queue.
Definition: sml_queue.c:384
SmlTransportCommand
Definition: sml_transport_internals.h:104
SmlTransport
Definition: sml_transport_internals.h:69
smlQueueNew
SmlQueue * smlQueueNew(SmlError **error)
Creates a new asynchronous queue.
Definition: sml_queue.c:81
smlTransportNew
SmlTransport * smlTransportNew(SmlTransportType type, SmlError **error)
Creates a new transport.
Definition: sml_transport.c:721
smlTransportSetResponseURI
SmlBool smlTransportSetResponseURI(SmlTransport *tsp, const char *uri, SmlError **error)
Sets the response URI after initialization.
Definition: sml_transport.c:1041
smlTransportFree
void smlTransportFree(SmlTransport *tsp)
Frees the given transport.
Definition: sml_transport.c:810
SmlTransportData
Definition: sml_transport_internals.h:112
SmlSession
Definition: sml_session_internals.h:27
SmlLink
Definition: sml_transport_internals.h:92
SmlQueue
Represents a Queue which can be used to receive messages.
Definition: sml_queue_internals.h:36
smlTransportInitialize
SmlBool smlTransportInitialize(SmlTransport *tsp, SmlError **error)
Initializes the transport with the given config.
Definition: sml_transport.c:969
smlTrace
void smlTrace(SmlTraceType type, const char *message,...)
Used for tracing the application.
Definition: sml_support.c:120
smlQueueAttach
void smlQueueAttach(SmlQueue *queue, GMainContext *context)
Sets the queue to use the gmainloop with the given context.
Definition: sml_queue.c:400
smlTryMalloc0
void * smlTryMalloc0(long n_bytes, SmlError **error)
Safely mallocs.
Definition: sml_support.c:335
smlErrorSet
void smlErrorSet(SmlError **error, SmlErrorType type, const char *format,...)
Sets the error.
Definition: sml_error.c:355
SmlError
Represent an error.
Definition: sml_error_internals.h:29