sml_session.c
/*
* libsyncml - A syncml protocol implementation
* Copyright (C) 2005 Armin Bauer <armin.bauer@opensync.org>
* Copyright (C) 2007-2008 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_queue_internals.h"
#include "sml_session_internals.h"
#include "sml_command_internals.h"
#include "sml_elements_internals.h"
#include "sml_parse_internals.h"
#include "sml_transport_internals.h"
#include "objects/sml_auth_internals.h"
typedef enum SmlSessionCommandType {
SML_SESSION_COMMAND_START,
SML_SESSION_COMMAND_END,
SML_SESSION_STATUS,
SML_SESSION_FLUSH
} SmlSessionCommandType;
typedef struct SmlSessionCommand {
SmlSessionCommandType type;
SmlCommand *cmd;
SmlCommand *parent;
SmlStatus *status;
SmlStatusReplyCb callback;
void *callbackUserdata;
SmlBool final;
SmlBool end;
} SmlSessionCommand;
void smlSessionDispatchEvent(SmlSession *session, SmlSessionEventType type, SmlCommand *cmd, SmlCommand *parent, SmlStatus *headerreply, SmlError *error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %i, %p, %p, %p, %p)", __func__, session, type, cmd, parent, headerreply, error);
smlAssert(session);
smlAssert(session->eventCallback);
session->eventCallback(session, type, cmd, parent, headerreply, error, session->eventCallbackUserdata);
smlTrace(TRACE_EXIT, "%s", __func__);
}
static void _smlSessionFreePendingReplies(SmlSession *session)
{
while (session->pendingReplies) {
smlSafeFree((gpointer *)&(session->pendingReplies->data));
session->pendingReplies = g_list_delete_link(session->pendingReplies, session->pendingReplies);
}
}
static SmlBool _smlSessionFlushInternal(SmlSession *session, SmlBool final, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %i, %p)", __func__, session, final, error);
CHECK_ERROR_REF
smlAssert(session);
smlAssert(session->dataCallback);
char *data = NULL;
unsigned int size = 0;
SmlBool end = FALSE;
/* Add the latest header */
if (!smlAssemblerAddHeader(session->assembler, session, error))
goto error;
/* Get the data that is waiting in the assembler */
if (!smlAssemblerRun(session->assembler, &data, &size, &end, final, error))
goto error;
if (session->authenticate)
end = FALSE;
/* Flush and reset the command ID counter */
if (session->lastCommandID > 1)
{
smlAssemblerFlush(session->assembler);
} else {
session->lastCommandID = smlAssemblerFlush(session->assembler) - 1;
}
smlTrace(TRACE_INTERNAL, "%s: flushed assembler - last command id is %i now",
__func__, session->lastCommandID);
SmlTransportData *tspdata = smlTransportDataNew(data, size, session->type, TRUE, error);
if (!tspdata)
goto error_free_data;
data = NULL;
if (end && session->sessionType == SML_SESSION_TYPE_SERVER)
tspdata->needsAnswer = FALSE;
/* Send the flush event */
if (!session->authenticate)
smlSessionDispatchEvent(session, SML_SESSION_EVENT_FLUSH, NULL, NULL, NULL, NULL);
/* Send the data over the transport */
session->dataCallback(session, tspdata, session->dataCallbackUserdata);
smlTransportDataDeref(tspdata);
session->waiting = TRUE;
if (final)
session->sending = FALSE;
if (end && session->sessionType == SML_SESSION_TYPE_SERVER) {
smlTrace(TRACE_INTERNAL, "%s: Ending session now", __func__);
session->end = TRUE;
smlSessionRestoreTargetURI(session);
g_mutex_lock(session->reportEnd);
if (!session->reportedEnd) {
/* The end should only be reported once. */
session->reportedEnd = TRUE;
smlSessionDispatchEvent(session, SML_SESSION_EVENT_END,
NULL, NULL, NULL, NULL);
}
g_mutex_unlock(session->reportEnd);
}
smlTrace(TRACE_INTERNAL, "%s: incrementing lastMessageID %d (old)",
__func__, session->lastMessageID);
session->lastMessageID++;
session->hasCommand = FALSE;
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error_free_data:
smlSafeCFree(&data);
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return FALSE;
}
static void _check_reply(SmlSession *session, SmlStatus *status, void *userdata)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, status, userdata);
smlTrace(TRACE_EXIT, "%s", __func__);
}
static void _smlSessionFragmentStatus(SmlSession *session, SmlStatus *status, void *userdata)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, status, userdata);
//SmlError *error = NULL;
smlTrace(TRACE_INTERNAL, "%s: Got a status reply %i", __func__, status->type);
if (smlStatusGetCode(status) != SML_CHUNK_ACCEPTED) {
/* We need to send a error reply for the original command */
smlCommandUnref(session->frag_command);
session->frag_command = NULL;
}
smlTrace(TRACE_EXIT, "%s", __func__);
return;
/*error:
smlSessionDispatchEvent(session, SML_SESSION_EVENT_ERROR, NULL, NULL, NULL, error);
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&error));
smlErrorDeref(&error);
return;*/
}
static SmlCommand *_smlSessionFragmentSend(SmlSession *session, SmlCommand *orig_cmd, SmlCommand *parent, unsigned int space, unsigned int start, unsigned int complete_size, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %i, %i, %i, %p)", __func__, session, orig_cmd, parent, space, start, complete_size, error);
CHECK_ERROR_REF
/* This library must not send more that one item per change
* command. This is compliant with all specification and only a
* self limitation.
*/
smlAssert(g_list_length(orig_cmd->private.change.items) == 1);
SmlCommand *frag_cmd = NULL;
char *data = NULL;
unsigned int size = 0;
SmlItem *orig_item = g_list_nth_data(orig_cmd->private.change.items, 0);
if (!smlItemGetData(orig_item, &data, &size, error))
goto error;
// FIXME: should we add an assertion here to guarantee
// FIXME: that size and complete_size are equal
if (space < size - start) {
/* We need to create a new command. But we only send as much data as space
* is available */
frag_cmd = smlCommandNew(orig_cmd->type, error);
if (!frag_cmd)
goto error;
SmlItem *frag_item = smlItemNewForData(data + start, space, error);
if (!frag_item)
goto error_free_cmd;
frag_cmd->private.change.items = g_list_append (NULL, frag_item);
if (!frag_cmd->private.change.items)
{
smlItemUnref(frag_item);
goto error_free_cmd;
}
frag_item->moreData = TRUE;
frag_item->target = orig_item->target;
if (frag_item->target)
smlLocationRef(frag_item->target);
frag_item->source = orig_item->source;
if (frag_item->source)
smlLocationRef(frag_item->source);
frag_item->contenttype = g_strdup(orig_item->contenttype);
/* If this is the first sent part of the fragmented command
* then this command must include the size of the complete
* command.
*/
if (start == 0)
frag_cmd->size = complete_size;
session->frag_size += space;
} else {
/* We have enough room to send the original command.
* Nevertheless the original item must be replaced
* with a new item which contains the rest of the data.
* The original item must be freed.
*/
/* get original item and cleanup item list */
smlAssert(g_list_length(orig_cmd->private.change.items) == 1);
smlAssert(g_list_remove(orig_cmd->private.change.items, orig_item) == NULL);
orig_cmd->private.change.items = NULL;
/* setup new item */
SmlItem *frag_item = smlItemNewForData(data + start, size - start, error);
if (!frag_item) {
smlItemUnref(orig_item);
goto error;
}
orig_cmd->private.change.items = g_list_append(NULL, frag_item);
if (!orig_cmd->private.change.items)
{
smlItemUnref(orig_item);
smlItemUnref(frag_item);
goto error;
}
/* configure new item */
frag_item->contenttype = g_strdup(orig_item->contenttype);
frag_item->target = orig_item->target;
if (frag_item->target)
smlLocationRef(frag_item->target);
frag_item->source = orig_item->source;
if (frag_item->source)
smlLocationRef(frag_item->source);
/* cleanup original item */
smlItemUnref(orig_item);
/* setup fragmentation command */
frag_cmd = orig_cmd;
session->frag_size += size - start;
}
smlTrace(TRACE_EXIT, "%s: %p", __func__, frag_cmd);
return frag_cmd;
error_free_cmd:
smlCommandUnref(frag_cmd);
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return NULL;
}
static SmlAssemblerResult _smlSessionStartCommandInternal(SmlSession *session, SmlCommand *cmd, SmlCommand *parent, SmlStatusReplyCb callback, void *userdata, SmlBool addToCommandStack, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p, %p, %i, %p)", __func__, session, cmd, parent, callback, userdata, addToCommandStack, error);
smlAssert(session);
smlAssert(cmd);
CHECK_ERROR_REF
SmlPendingStatus *pending = NULL;
SmlBool fragmented = FALSE;
if (parent && !session->hasCommand) {
GList *c = NULL;
SmlCommand *oldcmd = NULL;
SmlCommand *oldparent = NULL;
GList * cmds = g_list_copy(session->commandStack);
for (c = cmds; c; c = c->next) {
oldcmd = c->data;
if (_smlSessionStartCommandInternal(session, oldcmd, oldparent, _check_reply, NULL, FALSE, error) != SML_ASSEMBLER_RESULT_OK)
goto error;
oldparent = oldcmd;
}
g_list_free(cmds);
}
/* Large object handling. Only possible of the session has a limit and
* we dont have a 1.0 session */
if ((cmd->type == SML_COMMAND_TYPE_ADD || cmd->type == SML_COMMAND_TYPE_REPLACE) &&
smlAssemblerGetRemoteMaxMsgSize(session->assembler) > 0 && session->version != SML_VERSION_10) {
smlTrace(TRACE_INTERNAL, "%s: Checking if command needs to be fragmented", __func__);
/* This library must not send more that one item per change
* command. This is compliant with all specification and only a
* self limitation.
*/
smlAssert(g_list_length(cmd->private.change.items) == 1);
char *data = NULL;
unsigned int size = 0;
SmlItem *item = g_list_nth_data(cmd->private.change.items, 0);
if (!smlItemGetData(item, &data, &size, error))
goto error;
/* If max obj size is not unlimited (0), and the size of the item is larger
* than the max obj size, we have to return an error */
int sendingmaxobjsize = smlSessionGetRemoteMaxObjSize(session);
if ((sendingmaxobjsize > 0) &&
(size > (unsigned int)sendingmaxobjsize))
{
smlErrorSet(error, SML_ERROR_SIZE_MISMATCH, "Item (%ub) is larger than the limit (%db)", size, smlSessionGetRemoteMaxObjSize(session));
goto error;
}
if (!session->frag_command)
cmd->size = size;
item->moreData = TRUE;
int space = 0;
if (!smlAssemblerGetSpace(session->assembler, &space, parent, cmd, error))
goto error;
cmd->size = 0;
item->moreData = FALSE;
/* Check if item data fits into the current message */
if (session->frag_command ||
(space > 0 &&
(unsigned int) space < size - session->frag_size))
{
smlTrace(TRACE_INTERNAL, "%s: Space %i, size %i. Fragmenting. Already added: %i", __func__, space, size - session->frag_size, session->frag_size);
/* We need to fragment */
/* Store the command for processing */
if (!session->frag_command) {
session->frag_size = 0;
session->frag_command = cmd;
smlCommandRef(cmd);
session->frag_callback = callback;
session->frag_userdata = userdata;
}
if (!(cmd = _smlSessionFragmentSend(session, session->frag_command, parent, space, session->frag_size, size, error)))
goto error;
callback = _smlSessionFragmentStatus;
userdata = NULL;
fragmented = TRUE;
/* Sometime cmd is frag_command. So theoretically it can
* happen that the item is replaced. Therefore the item
* reference must be determined again.
*/
item = g_list_nth_data(cmd->private.change.items, 0);
if (item->moreData == FALSE) {
smlTrace(TRACE_INTERNAL, "%s: This is the last chunk", __func__);
callback = session->frag_callback;
userdata = session->frag_userdata;
fragmented = FALSE;
session->frag_size = 0;
smlCommandUnref(session->frag_command);
session->frag_command = NULL;
}
smlTrace(TRACE_INTERNAL, "%s: Fragmented. Added %i already", __func__, session->frag_size);
}
}
/* We now increment the session ID */
session->lastCommandID++;
cmd->cmdID = session->lastCommandID;
smlTrace(TRACE_INTERNAL, "%s: last command id is %i", __func__, session->lastCommandID);
/* Now we can try to add the command to the assembler */
switch (smlAssemblerStartCommand(session->assembler, parent, cmd, error)) {
case SML_ASSEMBLER_RESULT_OK:
/* We successfully added the command */
cmd->pushedBack = FALSE;
break;
case SML_ASSEMBLER_RESULT_MISMATCH:
if (cmd->pushedBack) {
smlErrorSet(error, SML_ERROR_GENERIC, "Command is too large");
goto error;
}
cmd->pushedBack = TRUE;
/* We werent able to add the command. So we have to flush the already
* added statuses/commands and then add it again later */
if (!_smlSessionFlushInternal(session, FALSE, error)) {
session->lastCommandID--;
goto error;
}
smlTrace(TRACE_EXIT, "%s: Mismatch", __func__);
return SML_ASSEMBLER_RESULT_MISMATCH;
case SML_ASSEMBLER_RESULT_ERROR:
session->lastCommandID--;
goto error;
}
/* If the user requested a reply to the command we generate
* the pending status so that we can dispatch the incoming status
*/
if (callback) {
pending = smlTryMalloc0(sizeof(SmlPendingStatus), error);
if (!pending) {
session->lastCommandID--;
goto error;
}
pending->callback = callback;
pending->userdata = userdata;
pending->cmdID = cmd->cmdID;
pending->msgID = session->lastMessageID;
smlTrace(TRACE_INTERNAL, "%s: Appending pending status with cmdID %i and msgID %i", __func__, pending->cmdID, pending->msgID);
session->pendingReplies = g_list_append(session->pendingReplies, pending);
} else
cmd->noResp = TRUE;
/* We will get a status and a result to a get command
* so we register a second callback for the result */
if (cmd->type == SML_COMMAND_TYPE_GET) {
pending = smlTryMalloc0(sizeof(SmlPendingStatus), error);
if (!pending) {
session->lastCommandID--;
goto error;
}
pending->callback = callback;
pending->userdata = userdata;
pending->cmdID = cmd->cmdID;
pending->msgID = session->lastMessageID;
smlTrace(TRACE_INTERNAL, "%s: Appending pending status for a result with cmdID %i and msgID %i", __func__, pending->cmdID, pending->msgID);
session->pendingReplies = g_list_append(session->pendingReplies, pending);
}
if (fragmented) {
/* We werent able to add the command. So we have to flush the already
* added statuses/commands and then add it again later */
if (!_smlSessionFlushInternal(session, FALSE, error)) {
session->lastCommandID--;
goto error;
}
smlCommandUnref(cmd);
smlTrace(TRACE_EXIT, "%s: Mismatch but fragmented", __func__);
return SML_ASSEMBLER_RESULT_MISMATCH;
}
if (addToCommandStack)
session->commandStack = g_list_append(session->commandStack, cmd);
session->hasCommand = TRUE;
smlTrace(TRACE_EXIT, "%s", __func__);
return SML_ASSEMBLER_RESULT_OK;
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return SML_ASSEMBLER_RESULT_ERROR;
}
static SmlBool _smlSessionEndCommandInternal(SmlSession *session, SmlCommand *parent, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, parent, error);
smlAssert(session);
CHECK_ERROR_REF
if (!smlAssemblerEndCommand(session->assembler, parent, error))
goto error;
session->commandStack = g_list_delete_link(session->commandStack, g_list_last(session->commandStack));
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return FALSE;
}
static void _smlSessionCommandFree(SmlSessionCommand *sesscmd)
{
if (sesscmd->cmd)
smlCommandUnref(sesscmd->cmd);
if (sesscmd->parent)
smlCommandUnref(sesscmd->parent);
if (sesscmd->status)
smlStatusUnref(sesscmd->status);
smlSafeFree((gpointer *)&sesscmd);
}
static void _smlSessionCommandHandler(void *message, void *userdata)
{
smlTrace(TRACE_ENTRY, "%s", __func__);
smlAssert(message);
smlAssert(userdata);
SmlSessionCommand *sesscmd = message;
SmlSession *session = userdata;
SmlError *error = NULL;
//SmlPendingStatus *pending = NULL;
smlTrace(TRACE_INTERNAL, "%s: session cmd type %i", __func__, sesscmd->type);
/* check if the header is ready */
if (!session->assmHasHeader)
{
if (!smlAssemblerAddHeader(session->assembler, session, &error))
goto error;
session->assmHasHeader = TRUE;
}
SmlCommand *parent = sesscmd->parent;
SmlCommand *cmd = sesscmd->cmd;
SmlStatus *status = sesscmd->status;
switch (sesscmd->type) {
case SML_SESSION_FLUSH:
if (sesscmd->end) {
session->end = TRUE;
smlTrace(TRACE_INTERNAL, "%s: End on flush was set", __func__);
}
if (!_smlSessionFlushInternal(session, sesscmd->final, &error))
goto error;
break;
case SML_SESSION_COMMAND_END:
if (!_smlSessionEndCommandInternal(session, parent, &error))
goto error;
break;
case SML_SESSION_COMMAND_START:
switch (_smlSessionStartCommandInternal(session, cmd, parent, sesscmd->callback, sesscmd->callbackUserdata, TRUE, &error)) {
case SML_ASSEMBLER_RESULT_OK:
/* We successfully added the command */
break;
case SML_ASSEMBLER_RESULT_MISMATCH:
smlQueuePushHead(session->command_queue, message);
return;
case SML_ASSEMBLER_RESULT_ERROR:
goto error;
}
break;
case SML_SESSION_STATUS:;
unsigned int cmdRef, msgRef = 0;
if (!smlAssemblerGetNextCmdRef(session->assembler, &cmdRef, &msgRef)) {
smlErrorSet(&error, SML_ERROR_GENERIC, "No more statuses were needed");
goto error;
}
if (status->cmdRef != cmdRef || status->msgRef != msgRef) {
//Put it on the end of the queue
smlQueueSendPrio(session->command_queue, message);
smlTrace(TRACE_EXIT,
"%s - next needed command status %d for message %d is not this one (cmd %d, msg %d)",
__func__, cmdRef, msgRef, status->cmdRef, status->msgRef);
return;
}
/* We ignore if the added status violates the size limitation if
* a incoming buffer for a large object is open. the problem is that
* the status for the chunk has to go into the next message AND the status
* MUST be in the same order as the original commands. so the only solution is
* to send all statuses and ignore the size. */
/* Now we can try to add the status to the assembler */
smlTrace(TRACE_INTERNAL, "%s - try to add status", __func__);
switch (smlAssemblerAddStatusFull(session->assembler, status, session->incomingBuffer ? TRUE : FALSE, &error)) {
case SML_ASSEMBLER_RESULT_OK:
/* We successfully added the status */
session->active = TRUE;
break;
case SML_ASSEMBLER_RESULT_MISMATCH:
/* We werent able to add the command. So we have to flush the already
* added statuses/commands and then add it again later */
if (!_smlSessionFlushInternal(session, FALSE, &error)) {
session->lastCommandID--;
goto error;
}
/* Push the message back into the queue */
smlQueuePushHeadPrio(session->command_queue, message);
smlTrace(TRACE_EXIT, "%s - status cannot be added, so push back", __func__);
return;
break;
case SML_ASSEMBLER_RESULT_ERROR:
goto error;
}
break;
}
_smlSessionCommandFree(sesscmd);
smlTrace(TRACE_EXIT, "%s", __func__);
return;
error:
smlSessionDispatchEvent(session, SML_SESSION_EVENT_ERROR, NULL, NULL, NULL, error);
_smlSessionCommandFree(sesscmd);
return;
}
SmlBool smlSessionReceiveHeader(SmlSession *session, SmlHeader *header, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, header, error);
smlAssert(session);
smlAssert(header);
CHECK_ERROR_REF
/* check and set the counters */
if (header->messageID <= session->lastReceivedMessageID) {
smlErrorSet(error, SML_ERROR_GENERIC, "Message ID not incremented");
goto error;
}
session->lastReceivedMessageID = header->messageID;
session->lastCommandID++;
/* check and set the MaxMsgSize */
if (0 < header->maxmsgsize) {
/* If MaxMsgSize is 0 then it is not set. Therefore
* a MaxMsgSize of 0 is ignored to not override an
* already configured MaxMsgSize from an earlier
* message.
*/
smlAssemblerSetRemoteMaxMsgSize(session->assembler, header->maxmsgsize);
}
if (0 < header->maxmsgsize && header->maxmsgsize < session->localMaxMsgSize)
{
/* The localMaxMsgSize is only set if it is already
* configured. If there is no local limit then it is not
* necessary to configure it only because the remote
* peer has a limit.
*/
smlSessionSetLocalMaxMsgSize(session, header->maxmsgsize);
}
/* Reserve the status for header reply. It will always be cmdRef 0
* and it will always be the first command in the message */
if (!smlAssemblerReserveStatus(session->assembler, 0, header->messageID, 1, error))
goto error;
/* If RespURI is send in the header
* then the URI in the transport layer must be adjusted. */
if (header->responseURI) {
if (!session->orgTarget) {
/* If the original target is already set
* then it MUST NOT be set again
* because this would destroy the real original
* target and an earlier RespURI would be used
* as original target.
*/
session->orgTarget = session->target;
session->target = NULL;
}
if (session->target) {
/* This is not the original target.
* Perhaps this is from an earlier message.
* Nevertheless only the most actual target
* is required.
*/
smlLocationUnref(session->target);
}
/* build the new target and publish it*/
session->target = smlLocationNew(
header->responseURI,
smlLocationGetName(session->orgTarget),
error);
if (!session->target)
goto error;
smlSessionDispatchEvent(session, SML_SESSION_EVENT_RESPONSE_URI, NULL, NULL, NULL, NULL);
smlTrace(TRACE_INTERNAL, "%s: RespURI: %s, Target: %s", __func__,
VA_STRING(smlLocationGetURI(session->target)),
VA_STRING(smlLocationGetURI(session->orgTarget)));
} else {
/* Sometimes a client changes the source reference
* after the SAN was sent because the SAN does not
* include the correct IMEI.
*
* If the header includes a new SourceRef/LocURI
* then we have to change the TargetRef/LocURI
* of the session.
*/
const char *header_source = smlLocationGetURI(header->source);
const char *session_target = smlLocationGetURI(session->target);
if (header_source &&
session_target &&
strcmp(header_source, session_target))
{
/* session target needs an update */
smlTrace(TRACE_INTERNAL,
"%s: update session target to header source %s",
__func__, header_source);
smlLocationSetURI(session->target, header_source);
}
}
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return FALSE;
}
SmlBool smlSessionDispatchStatus(SmlSession *session, SmlStatus *status, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, status, error);
smlAssert(status);
CHECK_ERROR_REF
if (status->cmdRef == 0) {
smlAssert(status->type == SML_COMMAND_TYPE_HEADER);
smlAssert(status->data);
SmlErrorType errorCode = atoi(status->data);
if (session->sessionType == SML_SESSION_TYPE_SERVER)
{
/* If this is an OMA DS server then the session
* authorization is done by the header callback.
* This is usually handled by functions named
* smlAuth*.
*
* If the OMA DS client code would be active
* then the remote client only have to send an
* error code 212 to force the server to accept
* the connection. So this is highly critical.
*/
SmlCommand *cmd = smlCommandNew(SML_COMMAND_TYPE_HEADER, error);
smlSessionDispatchEvent(session, SML_SESSION_EVENT_HEADER_REPLY, cmd, NULL, status, NULL);
smlCommandUnref(cmd);
}
else if (errorCode == SML_AUTH_ACCEPTED)
{
/* authentication succeeded
* we can remove the credentials from this session
* we do it now for better security
*/
smlTrace(TRACE_INTERNAL, "%s - authentication succeeded", __func__);
if (session->cred != NULL)
{
smlCredUnref(session->cred);
session->cred = NULL;
}
if (!session->established)
{
session->established = TRUE;
smlSessionDispatchEvent(
session, SML_SESSION_EVENT_ESTABLISHED,
NULL, NULL, NULL, NULL);
}
}
else if (errorCode == SML_ERROR_AUTH_REQUIRED)
{
/* If the requested authentication method violates the
* local security policy then the authentication must
* fail. Otherwise the password of the user can be
* compromized.
*/
smlTrace(TRACE_INTERNAL, "%s - authentication required", __func__);
session->authenticate = TRUE;
session->established = FALSE;
smlAssemblerRestoreCommands(session->assembler);
smlTrace(TRACE_INTERNAL, "%s - restored commands from previous message", __func__);
smlAssert(status->chal);
smlAssert(session->cred);
if (session->cred->type == SML_AUTH_TYPE_MD5 &&
status->chal->type != SML_AUTH_TYPE_MD5)
{
smlErrorSet(error, SML_ERROR_AUTH_REJECTED, "The remote peer tries to enforce an authentication method which violates the local security policy (syncml:auth-md5 is required).");
goto error;
}
smlTrace(TRACE_INTERNAL, "%s - authentication type conforms to security policy", __func__);
/* build the authentication string */
session->cred->data = smlAuthGetCredString(
status->chal->type,
session->cred->username,
session->cred->password,
status->chal->nonce_b64,
error);
if (!session->cred->data)
goto error;
smlTrace(TRACE_INTERNAL, "%s - credential string set", __func__);
} else {
if (!session->established)
{
session->established = TRUE;
smlSessionDispatchEvent(
session, SML_SESSION_EVENT_ESTABLISHED,
NULL, NULL, NULL, NULL);
}
SmlCommand *cmd = smlCommandNew(SML_COMMAND_TYPE_HEADER, error);
smlSessionDispatchEvent(session, SML_SESSION_EVENT_HEADER_REPLY, cmd, NULL, status, NULL);
smlCommandUnref(cmd);
}
goto out;
}
smlTrace(TRACE_INTERNAL, "%s - no header status", __func__);
/* Check if a handler for this object at this path has been installed.
* Please note that sometimes (e.g. GET command) a second handler
* was installed. So we cannot abort at the first found pending reply.
* We have to check every handler.
*/
GList *o = NULL;
SmlBool foundPending = FALSE;
for (o = session->pendingReplies; o; o = o ? o->next : NULL ) {
SmlPendingStatus *pending = o->data;
smlTrace(TRACE_INTERNAL, "%s: check cmd %i of msg %i", __func__, pending->cmdID, pending->msgID);
if (pending->cmdID == status->cmdRef && pending->msgID == status->msgRef) {
smlTrace(TRACE_INTERNAL, "%s - Found pending status %s of command %d in message %d",
__func__, VA_STRING(status->data), status->cmdRef, status->msgRef);
if (session->authenticate)
{
/* modify the pending reply
* If there is a required authentication
* then all commands must be send again.
* Please note that the simple change of the
* message ID only works because we never touch
* command ID.
*/
smlTrace(TRACE_INTERNAL, "%s - update message ID to %d",
__func__, session->lastMessageID);
pending->msgID++;
} else {
/* normal handling of the first pendingReply (the status one) */
smlTrace(TRACE_INTERNAL, "%s - normal status", __func__);
session->pendingReplies = g_list_remove(session->pendingReplies, pending);
smlAssert(pending->callback);
pending->callback(session, status, pending->userdata);
smlSafeFree((gpointer *)&pending);
/* We have to abort the for loop here
* because we MUST NOT delete a potential
* second pending reply. The GET command
* registers to pending replies - one for
* the status and one for the results.
*/
o = NULL;
}
foundPending = TRUE;
}
}
if (foundPending)
goto out;
smlTrace(TRACE_INTERNAL, "%s - no pending reply found", __func__);
/* We have to catch at this point if the status reply is the reply
* to our server alerted sync. Since this initial alert is not sent
* over a session, the status can of course not be wanted */
if (status->type == SML_COMMAND_TYPE_ALERT && !status->anchor) {
smlTrace(TRACE_INTERNAL, "%s: Handling status for server alerted sync", __func__);
SmlErrorType errorCode = atoi(status->data);
if ((199 < errorCode && errorCode < 300) ||
errorCode == 508)
{
/* If the status is 2xx or 508 then it is no error. */
goto out;
} else {
/* There is an error and this must be signaled.
* If there is no callback available
* then this is a normal error.
*/
smlErrorSet(error, errorCode, "The SAN alert was rejected.");
goto error;
}
}
smlErrorSet(error, SML_ERROR_GENERIC,
"Received unwanted status %s of command %d in message %d",
status->data, status->cmdRef, status->msgRef);
goto error;
out:
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return FALSE;
}
SmlBool smlSessionCheck(SmlSession *session)
{
smlAssert(session);
if (session->waiting)
{
smlTrace(TRACE_INTERNAL, "%s: waiting", __func__);
return FALSE;
}
SmlBool result;
if (!smlAssemblerIsStatusMissing(session->assembler))
{
result = smlQueueCheck(session->command_queue);
smlTrace(TRACE_INTERNAL, "%s: no status is missing - %i", __func__, result);
}
else
{
result = smlQueueCheckPrio(session->command_queue);
smlTrace(TRACE_INTERNAL, "%s: status is missing - %i", __func__, result);
}
return result;
}
SmlBool smlSessionTryLock(SmlSession *session)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, session);
smlAssert(session);
smlAssert(session->dispatch_mutex);
SmlBool ret = g_mutex_trylock(session->dispatch_mutex);
smlTrace(TRACE_EXIT, "%s - %d", __func__, ret);
return ret;
}
void smlSessionLock(SmlSession *session)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, session);
smlAssert(session);
smlAssert(session->dispatch_mutex);
g_mutex_lock(session->dispatch_mutex);
smlTrace(TRACE_EXIT, "%s", __func__);
}
void smlSessionUnlock(SmlSession *session)
{
smlTrace(TRACE_ENTRY, "%s", __func__);
smlAssert(session);
smlAssert(session->dispatch_mutex);
g_mutex_unlock(session->dispatch_mutex);
smlTrace(TRACE_EXIT, "%s", __func__);
}
void smlSessionDispatch(SmlSession *session)
{
smlTrace(TRACE_ENTRY, "%s", __func__);
smlAssert(session);
if (session->waiting)
{
smlTrace(TRACE_EXIT, "%s - session is waiting", __func__);
return;
}
/* If we are still missing statuses, we just dispatch the statuses
* If we are complete on the status side, we dispatch the commands
*/
if (!smlAssemblerIsStatusMissing(session->assembler))
{
smlTrace(TRACE_INTERNAL, "%s: dispatch commands", __func__);
smlQueueDispatch(session->command_queue);
}
else
{
smlTrace(TRACE_INTERNAL, "%s: dispatch status", __func__);
smlQueueDispatchPrio(session->command_queue);
}
smlTrace(TRACE_EXIT, "%s - done", __func__);
}
SmlBool smlSessionReceiveBody(SmlSession *session, SmlParser *parser, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, parser, error);
smlAssert(session);
smlAssert(parser);
CHECK_ERROR_REF
SmlStatus *status = NULL;
SmlCommand *cmd = NULL;
SmlParserResult result = SML_PARSER_RESULT_ERROR;
SmlBool isSan = TRUE;
if (session->sessionType == SML_SESSION_TYPE_SERVER)
isSan = FALSE;
/* The session received some stuff and so it is no longer waiting.
* It is important to set the state here because if it is set later
* then there is the risk that some dispatched command sends the
* next message via a direct flush. In this case a late state change
* could lead to a race condition because the correct state WAITING
* would be overwritten with the old value.
*/
session->waiting = FALSE;
session->sending = FALSE;
parse_status:
/* Now lets go through the available statuses (if any) */
while (1) {
if (!smlParserGetStatus(parser, &status, error))
goto error_free_status;
if (!status)
break;
/* There is at minimum one status. This means that
* this is not the first message and there is an
* header reply.
*/
isSan = FALSE;
/* If there is a status then it is necessary to check
* the session environment before the user has a chance
* to do something with the received information.
*
* The background is that libsyncml does not handle the
* SANs in a SmlSession. Therefore the SmlSession is not
* aware of an already used MsgRef 1.
*
* If a status is received and lastMessageID is still 1
* then a reply to a SAN is assumed and the lastMessageID
* is fixed accordingly.
*/
if (session->lastMessageID == 1)
{
/* This should be an answer to a SAN. */
smlTrace(TRACE_INTERNAL, "%s - reply to SAN detected", __func__);
session->lastMessageID = 2;
}
/* Call the callback*/
if (!smlSessionDispatchStatus(session, status, error))
goto error_free_status;
smlStatusUnref(status);
}
/* Go through the remaining commands */
while (1) {
result = smlParserGetCommand(parser, &cmd, error);
if (result == SML_PARSER_RESULT_ERROR)
goto error_free_cmd;
if (result == SML_PARSER_RESULT_OTHER)
break;
if (cmd)
cmd->msgID = session->lastReceivedMessageID;
/* If the handler of the header signals that an authentication
* is still required then we must send status 407 to all
* commands. The client must send all commands with the
* next message again.
*/
if (!session->established && !isSan) {
session->lastCommandID++;
/* Reserve the place for the status in the assembler */
if (!smlAssemblerReserveStatus(session->assembler, cmd->cmdID, session->lastReceivedMessageID, session->lastCommandID, error))
goto error_free_cmd;
/* send status 407 */
SmlStatus *reply = smlCommandNewReply(
cmd, SML_ERROR_AUTH_REQUIRED, error);
if (!reply)
goto error_free_cmd;
if (!smlSessionSendReply(session, reply, error))
goto error_free_cmd;
smlStatusUnref(reply);
smlCommandUnref(cmd);
continue;
}
switch (result) {
case SML_PARSER_RESULT_OPEN:
session->lastCommandID++;
/* Reserve the place for the status in the assembler */
if (!smlAssemblerReserveStatus(session->assembler, cmd->cmdID, session->lastReceivedMessageID, session->lastCommandID, error))
goto error_free_cmd;
/* Store the parent */
if (session->parentCommand) {
smlErrorSet(error, SML_ERROR_GENERIC, "Already had a parent");
goto error_free_cmd;
}
/* If we are a server, we just mimick the behaviour of the client regarding
* large object handling and support of number of changes. This way we dont
* need to parse the devinf */
if (cmd->type == SML_COMMAND_TYPE_SYNC) {
smlTrace(TRACE_INTERNAL, "%s: maxObjSize: %i, numbOfChanges: %i, %i",
__func__,
cmd->private.sync.maxObjSize,
cmd->private.sync.hasNumChanged,
cmd->private.sync.numChanged);
/* If the sync has the maxObjSize set (>= 0), we set the requested max obj size.
* If the max obj size was set before (with the alert, we overwrite it */
if (cmd->private.sync.maxObjSize >= 0)
smlSessionSetRemoteMaxObjSize(session, cmd->private.sync.maxObjSize);
/* If the DevInf of the client includes SupportNumberOfChanges
* but the client does not send NumberOfChanges
* then the server still MUST send NumberOfChanges.
* The client only MAY send NumberOfChanges.
*
* So even if NumberOfChanges are missing then never touch
* the requested and configured NumberOfChanges property.
*
* Please see OMA DS Protocol v1.2
* Section 9 Two-Way Sync
* Section 9.1 Client Modifications to Server
* Section 9.2 Server Modifications to Client
*/
}
session->parentCommand = cmd;
cmd->msgID = session->lastReceivedMessageID;
if (!session->end) {
smlSessionDispatchEvent(session, SML_SESSION_EVENT_COMMAND_START, cmd, NULL, NULL, NULL);
} else {
smlTrace(TRACE_INTERNAL, "%s: Replying with 407 since the session has ended",
__func__);
SmlStatus *reply = smlCommandNewReply(cmd, SML_ERROR_AUTH_REQUIRED, error);
if (!reply)
goto error_free_cmd;
if (!smlSessionSendReply(session, reply, error))
goto error_free_cmd;
smlStatusUnref(reply);
}
break;
case SML_PARSER_RESULT_NORMAL:
session->lastCommandID++;
/* Reserve the place for the status in the assembler */
if (!smlAssemblerReserveStatus(session->assembler, cmd->cmdID, session->lastReceivedMessageID, session->lastCommandID, error))
goto error_free_cmd;
/* Here we catch Alerts for the next message */
if (cmd->type == SML_COMMAND_TYPE_ALERT) {
if (cmd->private.alert.type == SML_ALERT_NEXT_MESSAGE) {
SmlStatus *reply = smlCommandNewReply(cmd, SML_NO_ERROR, error);
if (!reply)
goto error_free_cmd;
if (!smlSessionSendReply(session, reply, error))
goto error_free_cmd;
smlStatusUnref(reply);
smlCommandUnref(cmd);
break;
} else {
smlTrace(TRACE_INTERNAL, "%s: Alert maxObjSize: %i",
__func__, cmd->private.alert.maxObjSize);
if (cmd->private.alert.maxObjSize >= 0)
smlAssemblerSetRemoteMaxObjSize(session->assembler, cmd->private.alert.maxObjSize);
}
}
/* Here we catch changes with moreData set */
if (cmd->type == SML_COMMAND_TYPE_ADD || cmd->type == SML_COMMAND_TYPE_REPLACE) {
SmlItem *lastItem = g_list_nth_data(cmd->private.change.items,
g_list_length(cmd->private.change.items) - 1);
if (session->incomingBuffer) {
/* There is a command in the incoming buffer.
* This requires that all items of the received
* command are merged into this incoming buffer.
* Please remember that one command can contain
* several item each with a large object inside.
*
* If the last item of the command has no moreData
* flag then the command is dispatched.
*/
smlTrace(TRACE_INTERNAL, "%s: Appending to incoming buffer", __func__);
/* The size of an item must not be set in the
* different chunks except the first one.
*/
if (cmd->size) {
smlErrorSet(error, SML_ERROR_GENERIC,
"Only the first chunk can have the size set");
goto error_free_cmd;
}
/* Copy the item data of the first new item.
* This includes the moreData flag.
* This is a chunk of the previous item in the buffer.
*/
char *data = NULL;
unsigned int size = 0;
SmlItem *item = g_list_nth_data(cmd->private.change.items, 0);
if (!item)
goto error_free_cmd;
if (!smlItemGetData(item, &data, &size, error))
goto error_free_cmd;
lastItem = g_list_nth_data(session->incomingBuffer->private.change.items, g_list_length(session->incomingBuffer->private.change.items) - 1);
if (!smlItemAddData(lastItem, data, size, error))
goto error_free_cmd;
lastItem->moreData = item->moreData;
smlTrace(TRACE_INTERNAL,
"%s: Appended %i to buffer. Buffer size: %i. Required: %i",
__func__, size,
xmlBufferLength(lastItem->buffer),
lastItem->size);
/* move all other items from the new to the buffered command */
guint i;
for (i=1; i < g_list_length(cmd->private.change.items); i++)
{
item = g_list_nth_data(cmd->private.change.items, i);
session->incomingBuffer->private.change.items =
g_list_append(
session->incomingBuffer->private.change.items,
item);
}
smlItemUnref(g_list_nth_data(cmd->private.change.items, 0));
g_list_free(cmd->private.change.items);
cmd->private.change.items = NULL;
/* If the last item of the command is complete
* then the command must be prepared for dispatching.
*/
lastItem = g_list_nth_data(session->incomingBuffer->private.change.items, g_list_length(session->incomingBuffer->private.change.items) - 1);
if (!lastItem->moreData) {
smlTrace(TRACE_INTERNAL,
"%s: Command buffer complete. Dispatching.",
__func__);
/* check if the item size is correct */
if (!smlItemCheck(lastItem)) {
/* We have a size mismatch */
SmlStatus *reply = smlCommandNewReply(cmd, SML_ERROR_SIZE_MISMATCH, error);
if (!reply)
goto error_free_cmd;
if (!smlSessionSendReply(session, reply, error))
goto error_free_cmd;
smlStatusUnref(reply);
smlErrorSet(error, SML_ERROR_GENERIC,
"Chunked item size mismatch");
goto error_free_cmd;
}
/* The original command is no longer needed.
* The incoming buffer cannot be used
* because several status informations like
* the message ID are required for correct
* status replies.
*/
cmd->private.change.items =
session->incomingBuffer->private.change.items;
session->incomingBuffer->private.change.items = NULL;
smlCommandUnref(session->incomingBuffer);
session->incomingBuffer = NULL;
}
} else if (lastItem->moreData) {
/* There is no buffered command yet.
* The new command is copied to the buffer.
* This is done by reference copy and
* an incrementation of the reference counter.
*/
if (!cmd->size) {
smlErrorSet(error, SML_ERROR_GENERIC, "First MoreData item didn't have size set");
goto error_free_cmd;
}
session->incomingBuffer = cmd;
smlCommandRef(cmd);
smlTrace(TRACE_INTERNAL, "%s: New Buffer started. Buffered item size: %i. Required: %i",
__func__,
xmlBufferLength(lastItem->buffer),
lastItem->size);
char *bin = smlPrintBinary((char *) xmlBufferContent(lastItem->buffer), xmlBufferLength(lastItem->buffer));
smlTrace(TRACE_INTERNAL, "%s: Content so far: %s\n",
__func__, VA_STRING(bin));
smlSafeCFree(&bin);
}
if (lastItem->moreData == TRUE) {
smlTrace(TRACE_INTERNAL, "%s: Got item with moreData %i",
__func__, cmd->msgID);
SmlStatus *reply = smlCommandNewReply(cmd, SML_CHUNK_ACCEPTED, error);
if (!reply)
goto error_free_cmd;
if (!smlSessionSendReply(session, reply, error))
goto error_free_cmd;
smlStatusUnref(reply);
smlCommandUnref(cmd);
break;
}
}
/* if the command is a result, we treat it as a status */
if (cmd->type == SML_COMMAND_TYPE_RESULTS) {
/* Call the callback*/
if (!smlSessionDispatchStatus(session, cmd->private.results.status, error))
goto error_free_status;
} else {
/* Dispatch the command */
cmd->msgID = session->lastReceivedMessageID;
if (!session->end) {
if (!session->parentCommand) {
smlSessionDispatchEvent(session, SML_SESSION_EVENT_COMMAND_START, cmd, NULL, NULL, NULL);
} else {
smlSessionDispatchEvent(session, SML_SESSION_EVENT_CHILD_COMMAND, cmd, session->parentCommand, NULL, NULL);
}
} else {
smlTrace(TRACE_INTERNAL, "%s: Replying with 407 since the session has ended2",
__func__);
SmlStatus *reply = smlCommandNewReply(cmd, SML_ERROR_AUTH_REQUIRED, error);
if (!reply)
goto error_free_cmd;
if (!smlSessionSendReply(session, reply, error))
goto error_free_cmd;
smlStatusUnref(reply);
}
}
smlCommandUnref(cmd);
break;
case SML_PARSER_RESULT_CLOSE:
/* If it is a sync then it is necessary to signal it twice
* because DsSession has an enabled write_lock.
* If you add other command types here then please check
* twice that the callback are ready to be called twice
* (usually this requires some kind of statefulness).
*/
if (session->parentCommand->type == SML_COMMAND_TYPE_SYNC) {
smlSessionDispatchEvent(
session,
SML_SESSION_EVENT_COMMAND_END,
session->parentCommand,
NULL, NULL, NULL);
}
/* Remove the parent */
smlCommandUnref(session->parentCommand);
session->parentCommand = NULL;
break;
case SML_PARSER_RESULT_STATUS:
goto parse_status;
default:
goto error_free_cmd;
}
}
/* Check that the message closes correctly
* A final tag is only send if the SyncML package is finished.
* A package can consists of several messages if MaxMsgSize
* is smaller then the exchange data.
*/
SmlBool final = FALSE;
SmlBool end = FALSE;
if (!smlParserEnd(parser, &final, &end, error))
goto error_free_cmd;
/* Dispatch the final event before we enter the following loop. Otherwise if upper
layers depend on the final event we might end in a deadlock. */
if (final && !session->authenticate) {
/* If the session is handling an authentication
* then this must be transparent for the user.
* This means that the user should not see
* the FINAL event.
*
* Example: If the user flushs on FINAL then a FINAL
* signal could crash the complete client
* logic.
*/
/* FIXME: Why do we activate session->sending here? */
session->sending = TRUE;
smlSessionDispatchEvent(session, SML_SESSION_EVENT_FINAL, NULL, NULL, NULL, NULL);
}
/* We have to wait at this point until no status are pending anymore
* if there is an incoming buffer for large objs open,
* the other side expects to see the status for the open buffer of course. so we have to
* wait here for all open statuses, since they have to be added in the correct order and
* the status for the open buffer is obviously last */
while (1) {
unsigned int cmdRef, msgRef;
if (!smlAssemblerGetNextCmdRef(session->assembler, &cmdRef, &msgRef))
break;
smlTrace(TRACE_INTERNAL, "%s: command %d of message %d found",
__func__, cmdRef, msgRef);
/* yes, we do some kind busy wait here */
usleep(10000);
/* if there is nothing in the queue (num == 0) then this means
* that all status and commands are dispatched but the result of one or
* more triggered activities is still not available
* (therefore smlAssemblerGetNextCmdRef returns a command).
*
* smlQueueLengthPrio returns missing status count
*/
unsigned int num = smlQueueLengthPrio(session->command_queue);
smlTrace(TRACE_INTERNAL, "%s: Now dispatching %i statuses. Session waiting: %i, prio: %i",
__func__, num, session->waiting, smlQueueCheckPrio(session->command_queue));
/* If the session does not wait for the remote peer (session->waiting)
* and there is a missing status in the queue
* then the message is dispatched again.
* The message is dispatched at maximimum once per missing status (i < num).
* If smlSessionDispatch blocks because the dispatch_mutex is already in use
* and the situation changes (e.g. session->waiting is true)
* then this is no problem because the function is safe against
* mutliple calls (the SmlManager already requires this).
*/
unsigned int i = 0;
while (!session->waiting && smlQueueCheckPrio(session->command_queue) && i < num) {
smlSessionDispatch(session);
i++;
}
/* should we really call dispatch here or wait? */
}
/* A lot of checks are performed before the while loop is executed.
* This is necessary because this while loop is only present for one
* reason, a NEXT MESSAGE request from the remote peer if the original
* message data does not fit into one message (MaxMsgSize to small).
* The following stuff is checked:
*
* - the session must be in normal mode
* - not sending or waiting
* - no changed message counters
* - there must be a waiting command (not a waiting status!)
*
* Usually this happens if an image is too large and must be splitted
* (MaxMsgSize < MaxObjSize). Do not touch this stuff except that you
* really test it with an image which must be send and it is correctly
* splitted.
*/
unsigned int receivedMsg = session->lastReceivedMessageID;
unsigned int lastMsg = session->lastMessageID;
while (!session->waiting && !session->sending &&
smlQueueCheck(session->command_queue) &&
smlQueueLength(session->command_queue) != smlQueueLengthPrio(session->command_queue) &&
receivedMsg == session->lastReceivedMessageID &&
lastMsg == session->lastMessageID)
{
/* If the session does not wait for the remote peer (session->waiting)
* and there is a command in the queue
* then the command is dispatched.
* If the command is too large then the session is automatically
* flushed. If all commands were dispatched then the queue check
* fails and the normal mechanism for sending messages flushes
* the session or do some other actions.
*/
smlQueueDispatch(session->command_queue);
}
smlTrace(TRACE_INTERNAL, "%s: Final %i, waiting %i, sending %i",
__func__, final, session->waiting, session->sending);
if (session->authenticate) {
/* Do nothing. Simply send the message again.
* Please note that final and end MUST be ignored.
*/
smlTrace(TRACE_INTERNAL, "%s: ongoing authentication", __func__);
}
else if (final) {
/* Final means that the received message is the last message
* of this SyncML package. When a final is received then the
* library should not flush the session automatically because
* the user of the library usually starts some special action.
*
* This can be for example a new package or some kind of
* background action which results in informations expected by
* the remote peer in the next message.
* Example: OMA DS client (remote peer) sends a SYNC package
* and expects that the OMA DS server sends the server
* modifications at minimum after the last client
* modification.
*
* So again the library user must flush the session (e.g. handle
* the SML_MANAGER_SESSION_FINAL event).
*/
if (session->pendingReplies) {
/* If final is reached then there should be no
* pendingReplies anymore. The problem is that some
* commands (especially SYNC) results in new commands.
* Nevertheless we have to check for old pendingReplies
* and this we try to do here.
*
* Yes, this is some kind of bug detection.
*/
GList *o = NULL;
for (o = session->pendingReplies; o; o = o->next) {
SmlPendingStatus *pending = o->data;
/* This is an asynchronous library but the
* message numbering and sending is a serial
* operation. So pendingReplies should only
* exist for the last message. Anything else
* should be a bug.
* The problem is that a race condition can
* appear here. If the user of the library
* gets the FINAL event and flushs the session
* immediately then the flush function will
* send the message and increment the counter
* lastMessageID. So lastMessageID is in this
* case one number too high. The solution is to
* detect this event by the state of the
* session via the status variable waiting.
*/
if ((!session->waiting && pending->msgID < session->lastMessageID) ||
(session->waiting && pending->msgID < session->lastMessageID - 1))
{
smlErrorSet(error, SML_ERROR_GENERIC, "Didn't receive a reply for pending status (actual message %i, received message %i, cmdID %i, msgID %i)", session->lastMessageID, session->lastReceivedMessageID, pending->cmdID, pending->msgID);
goto error;
}
}
}
/* The incomingBuffer is used to store the last unhandled item.
* The incoming buffer can be filled after a message is
* completely handled if an object is too large for a message.
* Usually this happens if you add images to your contacts ;)
* The remote peer sends the moreData element in this case.
*
* Nevertheless if final is received then the package is
* finished and so there cannot be any more data which is not
* send by the client.
*
* Summary: there is a bug in this library or in the remote peer.
*/
if (session->incomingBuffer) {
smlErrorSet(error, SML_ERROR_GENERIC, "Incoming buffer was open when final arrived");
smlCommandUnref(session->incomingBuffer);
session->incomingBuffer = NULL;
goto error;
}
} else if (!session->waiting && !session->sending) {
/* If we don't wait already for an answer from the remote peer
* (perhaps because a user of this library directly called
* smlSessionFlush which causes a send operation of the
* transport layer) and if we are waiting for or already
* receiving a package (in terms of OMA DS specifications)
* then we should send the collected stuff to the remote peer.
*/
smlTrace(TRACE_INTERNAL, "%s: This is not the final message. Empty? %i",
__func__, smlAssemblerIsEmpty(session->assembler));
/* The received message was not a final message. So this is
* only a reaction to a part from a package. Sometimes such
* replies only contain status but a message must include a
* command.
*/
if (smlAssemblerIsEmpty(session->assembler)) {
/* The assembler is empty. This means there is no
* command included which is a requirement.
* The solution is simple - an alert called next
* message is added. this special alert does not need
* a source or target because it only signals that the
* remote peer should continue its work.
*/
SmlCommand *alert = smlCommandNewAlert(SML_ALERT_NEXT_MESSAGE, NULL, NULL, NULL, NULL, NULL, error);
if (!alert)
goto error;
if (_smlSessionStartCommandInternal(session, alert, NULL, NULL, NULL, TRUE, error) != SML_ASSEMBLER_RESULT_OK)
goto error;
if (!_smlSessionEndCommandInternal(session, NULL, error))
goto error;
smlCommandUnref(alert);
}
/* Anything is fine and nobody else want to send a reply
* to the received message. So the library flushs now.
*/
if (!_smlSessionFlushInternal(session, FALSE, error))
goto error;
}
if (session->authenticate) {
/* Do nothing. Simply send the message again. */
smlTrace(TRACE_INTERNAL, "%s: ongoing authentication", __func__);
/* session->end signals true because an authentication request
* is signaled via status/error 407 as answer to all commands.
*/
session->end = FALSE;
smlTrace(TRACE_INTERNAL, "%s: flushing server finally",
__func__);
if (!_smlSessionFlushInternal(session, TRUE, error))
goto error;
/* reset authenticate only after flush
* because we must surpress the FLUSH event.
*/
session->authenticate = FALSE;
} else if (end) {
/* end means the last message does not contain a command which
* is clearly illegal in SyncML except that the remote party
* thinks that the device synchronization or management action
* is successfully completed.
*
* The library can close the session now.
*/
smlTrace(TRACE_INTERNAL, "%s: Ending session now", __func__);
session->end = TRUE;
/* If the OMA DS or DM session ends and a correct FINAL was
* received then clients should simply end and servers should do
* a final flush according to OMA DS spec.
*
* This only makes sense after a received FINAL because
* otherwise the session was already flushed in some way. This
* can happen if the user of the library takes action because of
* an event.
*/
if (final)
{
smlTrace(TRACE_INTERNAL, "%s: session end on final",
__func__);
if (session->sessionType == SML_SESSION_TYPE_SERVER)
{
smlTrace(TRACE_INTERNAL, "%s: flushing server finally",
__func__);
if (!_smlSessionFlushInternal(session, TRUE, error))
goto error;
}
} else {
if (!session->waiting)
{
/* The library does not send a message
* after it received the last message
* and neither the remote peer nor the
* library send a final. This means
* there is an active package. This is
* an error of the library or the remote
* peer.
*/
smlErrorSet(error, SML_ERROR_BAD_REQUEST,
"The message from the remote peer does not contain a command.");
goto error;
}
}
/* Send the library user the signal that the session ends now
* and every use of this session will result in an undefined
* behaviour.
* If this is a server which received a final
* then FlushInternal already signalled the session end.
*/
if (!final || session->sessionType != SML_SESSION_TYPE_SERVER)
{
g_mutex_lock(session->reportEnd);
if (!session->reportedEnd) {
/* The end should only be reported once. */
session->reportedEnd = TRUE;
smlSessionRestoreTargetURI(session);
smlSessionDispatchEvent(session, SML_SESSION_EVENT_END,
NULL, NULL, NULL, NULL);
}
g_mutex_unlock(session->reportEnd);
}
}
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error_free_cmd:
if (cmd)
smlCommandUnref(cmd);
error_free_status:
if (status)
smlStatusUnref(status);
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return FALSE;
}
void smlSessionSetEventCallback(SmlSession *session, SmlSessionEventCallback callback, void *userdata)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, callback, userdata);
smlAssert(session);
session->eventCallback = callback;
session->eventCallbackUserdata = userdata;
smlTrace(TRACE_EXIT, "%s", __func__);
}
void smlSessionSetDataCallback(SmlSession *session, SmlSessionDataCallback callback, void *userdata)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, callback, userdata);
smlAssert(session);
session->dataCallback = callback;
session->dataCallbackUserdata = userdata;
smlTrace(TRACE_EXIT, "%s", __func__);
}
const char *smlSessionGetSessionID(SmlSession *session)
{
smlAssert(session);
return session->sessionID;
}
void smlSessionSetSessionID(SmlSession *session, const char *sessionID)
{
smlAssert(session);
if (session->sessionID)
smlSafeCFree(&(session->sessionID));
session->sessionID = g_strdup(sessionID);
}
SmlProtocolVersion smlSessionGetVersion(SmlSession *session)
{
smlAssert(session);
return session->version;
}
SmlLocation *smlSessionGetSource(SmlSession *session)
{
smlAssert(session);
return session->source;
}
SmlLocation *smlSessionGetTarget(SmlSession *session)
{
smlAssert(session);
return session->target;
}
void smlSessionRegisterCred(SmlSession *session, SmlCred *cred)
{
smlAssert(session);
smlAssert(session->sessionType == SML_SESSION_TYPE_CLIENT);
smlAssert(cred);
if (session->cred)
smlCredUnref(session->cred);
session->cred = cred;
if (cred->username)
smlLocationSetName(session->source, cred->username);
smlCredRef(session->cred);
}
SmlSession *smlSessionNew(SmlSessionType sessionType, SmlMimeType mimetype, SmlProtocolVersion version, SmlProtocolType protocol, SmlLocation *target, SmlLocation *source, const char *sessionID, unsigned int messageID, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%i, %i, %i, %i, %p, %p, %s, %i, %p)", __func__, sessionType, mimetype, version, protocol, target, source, VA_STRING(sessionID), messageID, error);
CHECK_ERROR_REF
/* Initialize GLib thread system. */
if (!g_thread_supported ()) g_thread_init (NULL);
/* Malloc the new session object */
SmlSession *session = smlTryMalloc0(sizeof(SmlSession), error);
if (!session)
goto error;
session->dispatch_mutex = g_mutex_new();
session->refCount = 1;
session->eventCallback = NULL;
session->version = version;
session->protocol = protocol;
session->type = mimetype;
session->sessionID = g_strdup(sessionID);
if (messageID)
session->lastMessageID = messageID;
else
session->lastMessageID = 1;
session->end = FALSE;
session->reportEnd = g_mutex_new();
session->reportedEnd = FALSE;
session->sessionType = sessionType;
session->lastCommandID = 0;
if (sessionType == SML_SESSION_TYPE_CLIENT)
session->sending = TRUE;
else
session->sending = FALSE;
session->source = source;
smlLocationRef(source);
session->target = target;
smlLocationRef(target);
session->cred = NULL;
session->chal = NULL;
/* Create the assembler that will parse messages send
* from this session */
session->assembler = smlAssemblerNew(mimetype, 0, error);
if (!session->assembler)
goto error_free_session;
smlAssemblerSetOption(session->assembler, "USE_STRTABLE", "1");
if (!smlAssemblerStart(session->assembler, session, error))
goto error_free_session;
/* Do not call smlAssemblerAddHeader here. It is nice to know
* if the header is is too large for the maximum message size
* but there is sometimes not enough information to pass all
* sanity checks of the assembler at this time (e.g. missing
* local MaxMsgSize).
*/
session->assmHasHeader = FALSE;
session->command_queue = smlQueueNew(error);
if (!session->command_queue)
goto error_free_session;
smlQueueSetHandler(session->command_queue, (SmlQueueHandler)_smlSessionCommandHandler, session);
smlTrace(TRACE_EXIT, "%s: %p", __func__, session);
return session;
error_free_session:
smlSessionUnref(session);
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return NULL;
}
SmlSession *smlSessionRef(SmlSession *session)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, session);
smlAssert(session);
g_atomic_int_inc(&(session->refCount));
smlTrace(TRACE_EXIT, "%s: New refcount: %i", __func__, session->refCount);
return session;
}
void smlSessionUnref(SmlSession *session)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, session);
smlAssert(session);
if (g_atomic_int_dec_and_test(&(session->refCount))) {
smlTrace(TRACE_INTERNAL, "%s: Refcount == 0!", __func__);
if (session->target)
smlLocationUnref(session->target);
if (session->responseURI)
smlSafeCFree(&(session->responseURI));
if (session->source)
smlLocationUnref(session->source);
if (session->cred)
smlCredUnref(session->cred);
if (session->chal)
smlChalUnref(session->chal);
if (session->sessionID)
smlSafeCFree(&(session->sessionID));
if (session->command_queue) {
SmlSessionCommand *sesscmd = NULL;
while ((sesscmd = smlQueueTryPop(session->command_queue)))
_smlSessionCommandFree(sesscmd);
smlQueueFree(session->command_queue);
}
_smlSessionFreePendingReplies(session);
if (session->assembler)
smlAssemblerFree(session->assembler);
if (session->incomingBuffer) {
smlTrace(TRACE_INTERNAL, "%s: There is an unhandled incoming buffer.", __func__);
smlCommandUnref(session->incomingBuffer);
}
if (session->parentCommand)
smlCommandUnref(session->parentCommand);
smlTrace(TRACE_INTERNAL, "%s: cleanup dispatch mutex", __func__);
if (!g_mutex_trylock(session->dispatch_mutex))
g_warning("%s: The session is still locked for dispatching - forcing unlock/free.",
__func__);
g_mutex_unlock(session->dispatch_mutex);
g_mutex_free(session->dispatch_mutex);
smlTrace(TRACE_INTERNAL, "%s: cleanup reportEnd mutex", __func__);
/* It is no bug if reportEnd is not used. */
if (!g_mutex_trylock(session->reportEnd))
g_warning("%s: The reportEnd is still locked to set reportEnd - forcing unlock/free.",
__func__);
g_mutex_unlock(session->reportEnd);
g_mutex_free(session->reportEnd);
smlSafeFree((gpointer *)&session);
}
smlTrace(TRACE_EXIT, "%s", __func__);
}
SmlBool smlSessionFlush(SmlSession *session, SmlBool final, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %i, %p)", __func__, session, final, error);
smlAssert(session);
CHECK_ERROR_REF
SmlSessionCommand *sesscmd = smlTryMalloc0(sizeof(SmlSessionCommand), error);
if (!sesscmd)
goto error;
sesscmd->type = SML_SESSION_FLUSH;
sesscmd->final = final;
smlTrace(TRACE_INTERNAL, "%s: sending flush (%i) command %p via queue %p",
__func__, SML_SESSION_FLUSH, sesscmd, session->command_queue);
smlQueueSend(session->command_queue, sesscmd);
// session is still active even if the other side sent only status
session->end = FALSE;
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return FALSE;
}
SmlBool smlSessionEnd(SmlSession *session, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, session, error);
smlAssert(session);
CHECK_ERROR_REF
g_mutex_lock(session->reportEnd);
if (session->reportedEnd)
{
/* If the end was already reported
* then this function should not be called.
* This can happen because of race conditions
* during the event handling. So this event
* is not necessarily a bug.
*/
g_mutex_unlock(session->reportEnd);
smlTrace(TRACE_EXIT, "%s - session end already signalled.", __func__);
return TRUE;
}
session->reportedEnd = TRUE;
/* If the session received all status and has no command ready
* to send then it makes no sense to flush. Such situations can
* appear if the user opens a connection to a SyncML server but
* after the connect the user notices that there is nothing to
* synchronize.
*
* The only exception is a server which received a map from the
* client as answer for a sync command. The server must send an
* empty message to the client. If the client sends no map
* then this is handled by the function ReceiveBody.
*/
if (smlSessionCheck(session) ||
(!session->end && session->lastCommandID))
{
/* The session end is reported by the internal
* flush function in this situation.
*/
session->reportedEnd = FALSE;
SmlSessionCommand *sesscmd = smlTryMalloc0(sizeof(SmlSessionCommand), error);
if (!sesscmd)
goto error;
sesscmd->type = SML_SESSION_FLUSH;
sesscmd->final = TRUE;
sesscmd->end = TRUE;
smlTrace(TRACE_INTERNAL, "%s: sending command %p", sesscmd);
smlQueueSend(session->command_queue, sesscmd);
} else {
/* If there is nothing to synchronize
* then simply signal the end.
*/
smlTrace(TRACE_INTERNAL, "%s: no status/command ... only sending event", __func__);
smlSessionRestoreTargetURI(session);
smlSessionDispatchEvent(session, SML_SESSION_EVENT_END, NULL, NULL, NULL, NULL);
}
g_mutex_unlock(session->reportEnd);
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error:
g_mutex_unlock(session->reportEnd);
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return FALSE;
}
/* This is for the client side of ResponseURI support. */
void smlSessionRestoreTargetURI(SmlSession *session)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, session);
smlAssert(session);
if (session->orgTarget) {
/* Restore original target if RespURI was used.
* This is unimportant because every new session
* must use a new link (connection).
*/
smlLocationUnref(session->target);
session->target = session->orgTarget;
session->orgTarget = NULL;
/* Do not try to reset the ResponseURI via the session
* event because the original Target URI is very often
* no URL which causes an error in the transport layer.
*/
}
smlTrace(TRACE_EXIT, "%s", __func__);
}
/* This is for the server side of ResponseURI support. */
SmlBool smlSessionSetResponseURI(
SmlSession *session,
const char *responseURI,
SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %s, %p)", __func__, session, VA_STRING(responseURI), error);
CHECK_ERROR_REF
if (session->responseURI) {
g_warning("The response URI (%s) of a session (%s) should never be overwritten (%s).",
session->responseURI, session->sessionID, responseURI);
smlSafeCFree(&(session->responseURI));
}
session->responseURI = g_strdup(responseURI);
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
}
void smlSessionUseStringTable(SmlSession *session, SmlBool useStringtable)
{
smlTrace(TRACE_ENTRY, "%s(%p, %i)", __func__, session, useStringtable);
smlAssert(session);
smlAssemblerSetOption(session->assembler, "USE_STRTABLE", useStringtable ? "1" : "0");
smlTrace(TRACE_EXIT, "%s", __func__);
}
void smlSessionUseNumberOfChanges(SmlSession *session, SmlBool support)
{
smlTrace(TRACE_ENTRY, "%s(%p, %i)", __func__, session, support);
smlAssert(session);
smlAssemblerSetOption(session->assembler, "USE_NUMBEROFCHANGES", support ? "1" : "0");
smlTrace(TRACE_EXIT, "%s", __func__);
}
void smlSessionUseLargeObjects(SmlSession *session, SmlBool support)
{
smlTrace(TRACE_ENTRY, "%s(%p, %i)", __func__, session, support);
smlAssert(session);
smlAssemblerSetOption(session->assembler, "USE_LARGEOBJECTS", support ? "1" : "0");
smlTrace(TRACE_EXIT, "%s", __func__);
}
void smlSessionUseOnlyReplace(SmlSession *session, SmlBool onlyReplace)
{
smlTrace(TRACE_ENTRY, "%s(%p, %i)", __func__, session, onlyReplace);
smlAssert(session);
smlAssemblerSetOption(session->assembler, "ONLY_REPLACE", onlyReplace ? "1" : "0");
session->onlyReplace = onlyReplace;
smlTrace(TRACE_EXIT, "%s", __func__);
}
/* FIXME: DEPRECATED */
void smlSessionSetSendingLimit(SmlSession *session, unsigned int limit)
{
smlSessionSetRemoteMaxMsgSize(session, limit);
}
/* FIXME: DEPRECATED */
unsigned int smlSessionGetSendingLimit(SmlSession *session)
{
return smlSessionGetRemoteMaxMsgSize(session);
}
/* FIXME: DEPRECATED */
void smlSessionSetReceivingLimit(SmlSession *session, unsigned int limit)
{
smlSessionSetLocalMaxMsgSize(session, limit);
}
/* FIXME: DEPRECATED */
void smlSessionSetSendingMaxMsgSize(SmlSession *session, unsigned int size)
{
smlSessionSetRemoteMaxMsgSize(session, size);
}
/* FIXME: DEPRECATED */
unsigned int smlSessionGetSendingMaxMsgSize(SmlSession *session)
{
return smlSessionGetRemoteMaxMsgSize(session);
}
/* FIXME: DEPRECATED */
void smlSessionSetReceivingMaxMsgSize(SmlSession *session, unsigned int size)
{
smlSessionSetLocalMaxMsgSize(session, size);
}
void smlSessionSetRemoteMaxMsgSize(SmlSession *session, unsigned int size)
{
smlTrace(TRACE_ENTRY, "%s(%p, %u)", __func__, session, size);
smlAssert(session);
smlAssemblerSetRemoteMaxMsgSize(session->assembler, size);
smlTrace(TRACE_EXIT, "%s", __func__);
}
unsigned int smlSessionGetRemoteMaxMsgSize(SmlSession *session)
{
smlAssert(session);
return smlAssemblerGetRemoteMaxMsgSize(session->assembler);
}
void smlSessionSetLocalMaxMsgSize(SmlSession *session, unsigned int size)
{
smlTrace(TRACE_ENTRY, "%s(%p, %u)", __func__, session, size);
smlAssert(session);
if (! smlSessionGetRemoteMaxMsgSize(session) > 0 ||
smlSessionGetRemoteMaxMsgSize(session) > size)
{
session->localMaxMsgSize = size;
} else {
session->localMaxMsgSize = smlSessionGetRemoteMaxMsgSize(session);
}
smlTrace(TRACE_EXIT, "%s", __func__);
}
unsigned int smlSessionGetLocalMaxMsgSize(SmlSession *session)
{
smlAssert(session);
return session->localMaxMsgSize;
}
/* FIXME: DEPRECATED */
void smlSessionSetSendingMaxObjSize(SmlSession *session, int limit)
{
smlSessionSetRemoteMaxObjSize(session, limit);
}
/* FIXME: DEPRECATED */
void smlSessionSetRequestedMaxObjSize(SmlSession *session, int limit)
{
smlSessionSetRemoteMaxObjSize(session, limit);
}
/* FIXME: DEPRECATED */
int smlSessionGetSendingMaxObjSize(SmlSession *session)
{
return smlSessionGetRemoteMaxObjSize(session);
}
/* FIXME: DEPRECATED */
void smlSessionSetReceivingMaxObjSize(SmlSession *session, unsigned int limit)
{
smlSessionSetLocalMaxObjSize(session, limit);
}
/* FIXME: DEPRECATED */
unsigned int smlSessionGetReceivingMaxObjSize(SmlSession *session)
{
return smlSessionGetLocalMaxObjSize(session);
}
void smlSessionSetRemoteMaxObjSize(SmlSession *session, unsigned int limit)
{
smlTrace(TRACE_ENTRY, "%s(%p, %i)", __func__, session, limit);
smlAssert(session);
smlAssemblerSetRemoteMaxObjSize(session->assembler, limit);
smlTrace(TRACE_EXIT, "%s", __func__);
}
unsigned int smlSessionGetRemoteMaxObjSize(SmlSession *session)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, session);
smlAssert(session);
int result = smlAssemblerGetRemoteMaxObjSize(session->assembler);
smlTrace(TRACE_EXIT, "%s => %i", __func__, result);
return result;
}
void smlSessionSetLocalMaxObjSize(SmlSession *session, unsigned int limit)
{
smlTrace(TRACE_ENTRY, "%s(%p, %u)", __func__, session, limit);
smlAssert(session);
int remoteLimit = smlSessionGetRemoteMaxObjSize(session);
if (remoteLimit > 0)
{
/* The remote peer already sent MaxObjSize */
if ((unsigned int)remoteLimit > limit && limit > 0)
{
/* If the limit of the remote peer is higher
* than the local limit then the local limit
* must be used.
*/
session->localMaxObjSize = limit;
} else {
/* If the remote limit is not higher than
* the local limit or of the local limit is
* not set then the remote limit is used as
* the local limit.
*/
session->localMaxObjSize = remoteLimit;
}
} else {
/* If there is no remote limit then the local limit is used. */
session->localMaxObjSize = limit;
}
smlTrace(TRACE_EXIT, "%s => %d", __func__, session->localMaxObjSize);
}
unsigned int smlSessionGetLocalMaxObjSize(SmlSession *session)
{
smlAssert(session);
return session->localMaxObjSize;
}
SmlBool smlSessionSendCommand(SmlSession *session, SmlCommand *cmd, SmlCommand *parent, SmlStatusReplyCb callback, void *userdata, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p, %p, %p)", __func__, session, cmd, parent, callback, userdata, error);
smlAssert(session);
smlAssert(cmd);
CHECK_ERROR_REF
if (!smlSessionStartCommand(session, cmd, parent, callback, userdata, error))
goto error;
if (!smlSessionEndCommand(session, parent, error))
goto error;
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return FALSE;
}
SmlBool smlSessionStartCommand(SmlSession *session, SmlCommand *cmd, SmlCommand *parent, SmlStatusReplyCb callback, void *userdata, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p, %p, %p)", __func__, session, cmd, parent, callback, userdata, error);
smlAssert(session);
smlAssert(cmd);
CHECK_ERROR_REF
SmlSessionCommand *sesscmd = smlTryMalloc0(sizeof(SmlSessionCommand), error);
if (!sesscmd)
goto error;
sesscmd->type = SML_SESSION_COMMAND_START;
sesscmd->cmd = cmd;
smlCommandRef(cmd);
if (parent) {
sesscmd->parent = parent;
smlCommandRef(parent);
}
sesscmd->callback = callback;
sesscmd->callbackUserdata = userdata;
smlQueueSend(session->command_queue, sesscmd);
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return FALSE;
}
SmlBool smlSessionEndCommand(SmlSession *session, SmlCommand *parent, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, parent, error);
smlAssert(session);
CHECK_ERROR_REF
SmlSessionCommand *sesscmd = smlTryMalloc0(sizeof(SmlSessionCommand), error);
if (!sesscmd)
goto error;
sesscmd->type = SML_SESSION_COMMAND_END;
if (parent) {
sesscmd->parent = parent;
smlCommandRef(parent);
}
smlQueueSend(session->command_queue, sesscmd);
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return FALSE;
}
SmlBool smlSessionSendReply(SmlSession *session, SmlStatus *status, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, status, error);
smlAssert(session);
smlAssert(status);
CHECK_ERROR_REF
SmlSessionCommand *sesscmd = smlTryMalloc0(sizeof(SmlSessionCommand), error);
if (!sesscmd)
goto error;
sesscmd->type = SML_SESSION_STATUS;
sesscmd->status = status;
smlStatusRef(status);
smlQueueSendPrio(session->command_queue, sesscmd);
smlTrace(TRACE_EXIT, "%s", __func__);
return TRUE;
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return FALSE;
}
smlAssemblerSetOption
void smlAssemblerSetOption(SmlAssembler *assm, const char *optionname, const char *value)
Sets a option on the assembler.
Definition: sml_parse.c:896
smlAssemblerAddHeader
SmlBool smlAssemblerAddHeader(SmlAssembler *assm, SmlSession *session, SmlError **error)
Assembles the header.
Definition: sml_parse.c:762
smlSessionSendReply
SmlBool smlSessionSendReply(SmlSession *session, SmlStatus *status, SmlError **error)
Sends a reply to a command.
Definition: sml_session.c:2536
SmlCred
Definition: sml_elements_internals.h:87
smlErrorPrint
const char * smlErrorPrint(SmlError **error)
Returns the message of the error.
Definition: sml_error.c:299
SmlTransportData::needsAnswer
SmlBool needsAnswer
Definition: sml_transport_internals.h:121
SmlError::type
SmlErrorType type
Definition: sml_error_internals.h:31
smlSessionSetLocalMaxObjSize
void smlSessionSetLocalMaxObjSize(SmlSession *session, unsigned int limit)
Definition: sml_session.c:2363
smlSessionFlush
SmlBool smlSessionFlush(SmlSession *session, SmlBool final, SmlError **error)
Flushes a session.
Definition: sml_session.c:2010
smlAssemblerStartCommand
SmlAssemblerResult smlAssemblerStartCommand(SmlAssembler *assm, SmlCommand *parent, SmlCommand *cmd, SmlError **error)
Starts a parent command.
Definition: sml_parse.c:659
smlAssemblerAddStatusFull
SmlAssemblerResult smlAssemblerAddStatusFull(SmlAssembler *assm, SmlStatus *status, SmlBool force, SmlError **error)
Assembles a status.
Definition: sml_parse.c:839
smlSessionEndCommand
SmlBool smlSessionEndCommand(SmlSession *session, SmlCommand *parent, SmlError **error)
ends a command
Definition: sml_session.c:2495
SmlStatus
Definition: sml_command_internals.h:94
smlSessionGetRemoteMaxObjSize
unsigned int smlSessionGetRemoteMaxObjSize(SmlSession *session)
Definition: sml_session.c:2339
smlQueueSend
void smlQueueSend(SmlQueue *queue, void *data)
Sends a message down a queue.
Definition: sml_queue.c:353
SmlHeader
Definition: sml_elements_internals.h:73
SmlParser
Definition: sml_parse_internals.h:25
smlQueueSetHandler
void smlQueueSetHandler(SmlQueue *queue, SmlQueueHandler handler, void *userdata)
Sets the message handler for a queue.
Definition: sml_queue.c:384
smlAssemblerReserveStatus
SmlAssemblerResult smlAssemblerReserveStatus(SmlAssembler *assm, unsigned int cmdRef, unsigned int msgRef, unsigned int cmdID, SmlError **error)
Reserves a place for a status with this assembler.
Definition: sml_parse.c:806
smlPrintBinary
char * smlPrintBinary(const char *data, int len)
Used for printing binary data.
Definition: sml_support.c:252
smlSessionSetRemoteMaxObjSize
void smlSessionSetRemoteMaxObjSize(SmlSession *session, unsigned int limit)
Definition: sml_session.c:2325
smlQueueNew
SmlQueue * smlQueueNew(SmlError **error)
Creates a new asynchronous queue.
Definition: sml_queue.c:81
smlParserEnd
SmlBool smlParserEnd(SmlParser *parser, SmlBool *final, SmlBool *end, SmlError **error)
Ends the parsing.
Definition: sml_parse.c:314
SmlItem
Definition: sml_elements_internals.h:46
SmlPendingStatus
Definition: sml_session_internals.h:99
smlAssemblerRun
SmlBool smlAssemblerRun(SmlAssembler *assm, char **data, unsigned int *size, SmlBool *end, SmlBool final, SmlError **error)
Assembles the complete message and returns the result.
Definition: sml_parse.c:456
smlSessionEnd
SmlBool smlSessionEnd(SmlSession *session, SmlError **error)
Ends the session.
Definition: sml_session.c:2057
smlAssemblerFlush
unsigned int smlAssemblerFlush(SmlAssembler *assm)
Flushes the already parsed commands.
Definition: sml_parse.c:514
smlAssemblerStart
SmlBool smlAssemblerStart(SmlAssembler *assm, SmlSession *session, SmlError **error)
Starts a new message.
Definition: sml_parse.c:428
SmlTransportData
Definition: sml_transport_internals.h:112
smlAssemblerIsStatusMissing
SmlBool smlAssemblerIsStatusMissing(SmlAssembler *assm)
Checks if there are reserved statuses missing.
Definition: sml_parse.c:1053
SmlSession
Definition: sml_session_internals.h:27
SmlCommand
Definition: sml_command_internals.h:73
smlSessionSendCommand
SmlBool smlSessionSendCommand(SmlSession *session, SmlCommand *cmd, SmlCommand *parent, SmlStatusReplyCb callback, void *userdata, SmlError **error)
Sends a command with a parent.
Definition: sml_session.c:2422
smlItemGetData
SmlBool smlItemGetData(SmlItem *item, char **data, unsigned int *size, SmlError **error)
Definition: sml_elements.c:495
smlItemCheck
SmlBool smlItemCheck(SmlItem *item)
Definition: sml_elements.c:444
smlTrace
void smlTrace(SmlTraceType type, const char *message,...)
Used for tracing the application.
Definition: sml_support.c:120
smlAssemblerFree
void smlAssemblerFree(SmlAssembler *assm)
Frees a assembler.
Definition: sml_parse.c:402
smlAssemblerEndCommand
SmlBool smlAssemblerEndCommand(SmlAssembler *assm, SmlCommand *parent, SmlError **error)
Ends a parent command.
Definition: sml_parse.c:722
SmlLocation
Definition: sml_elements_internals.h:40
smlSessionStartCommand
SmlBool smlSessionStartCommand(SmlSession *session, SmlCommand *cmd, SmlCommand *parent, SmlStatusReplyCb callback, void *userdata, SmlError **error)
starts a command with a parent
Definition: sml_session.c:2454
smlParserGetStatus
SmlBool smlParserGetStatus(SmlParser *parser, SmlStatus **status, SmlError **error)
Parses the next status.
Definition: sml_parse.c:284
smlAssemblerNew
SmlAssembler * smlAssemblerNew(SmlMimeType type, unsigned int limit, SmlError **error)
Creates a new assembler.
Definition: sml_parse.c:352
SmlSessionCommand
Definition: sml_session.c:49
smlSessionNew
SmlSession * smlSessionNew(SmlSessionType sessionType, SmlMimeType mimetype, SmlProtocolVersion version, SmlProtocolType protocol, SmlLocation *target, SmlLocation *source, const char *sessionID, unsigned int messageID, SmlError **error)
Creates a new session.
Definition: sml_session.c:1832
smlParserGetCommand
SmlParserResult smlParserGetCommand(SmlParser *parser, SmlCommand **cmd, SmlError **error)
Parses the next command.
Definition: sml_parse.c:250
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
smlAssemblerGetSpace
SmlBool smlAssemblerGetSpace(SmlAssembler *assm, int *space, SmlCommand *parent, SmlCommand *cmd, SmlError **error)
Gets the available space in the assembler.
Definition: sml_parse.c:545
SmlError
Represent an error.
Definition: sml_error_internals.h:29
SmlCommand::pushedBack
SmlBool pushedBack
Definition: sml_command_internals.h:91
smlQueueSendPrio
void smlQueueSendPrio(SmlQueue *queue, void *data)
Sends a message down a queue.
Definition: sml_queue.c:323