Diff between 41f2c9e5d81995c2c1dc54a79f553a8ef2a0694a and 38cc843679289222b4a3f3c8fb215760ead109f9

Changed Files

File Additions Deletions Status
obexd/gwobex/gw-obex.c +367 -0 added
obexd/gwobex/gw-obex.h +601 -0 added
obexd/gwobex/log.h +38 -0 added
obexd/gwobex/obex-priv.c +1084 -0 added
obexd/gwobex/obex-priv.h +215 -0 added
obexd/gwobex/obex-xfer.c +507 -0 added
obexd/gwobex/obex-xfer.h +87 -0 added
obexd/gwobex/utils.c +185 -0 added
obexd/gwobex/utils.h +60 -0 added

Full Patch

diff --git a/obexd/gwobex/gw-obex.c b/obexd/gwobex/gw-obex.c
new file mode 100644
index 0000000..6549b09
--- /dev/null
+++ b/obexd/gwobex/gw-obex.c
@@ -0,0 +1,367 @@
+/**
+  @file gw-obex.c
+
+  @author Johan Hedberg <johan.hedberg@nokia.com>
+
+  Copyright (C) 2004-2006 Nokia Corporation. All rights reserved.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License, version 2.1, as published by the Free Software Foundation.
+
+  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 <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <glib.h>
+
+#include "log.h"
+#include "gw-obex.h"
+#include "utils.h"
+#include "obex-xfer.h"
+#include "obex-priv.h"
+
+
+gboolean gw_obex_get_file(GwObex *ctx,
+                          const gchar *local,
+                          const gchar *remote,
+                          const gchar *type,
+                          gint *error) {
+    gboolean ret;
+    GW_OBEX_LOCK(ctx);
+    CHECK_DISCONNECT(FALSE, error, ctx);
+    ret = gw_obex_get(ctx, local, remote, type, NULL, NULL, -1, FALSE);
+    if (ret == FALSE)
+        gw_obex_get_error(ctx, error);
+    GW_OBEX_UNLOCK(ctx);
+    return ret;
+}
+
+gboolean gw_obex_get_fd(GwObex      *ctx,
+                        gint         fd,
+                        const gchar *remote,
+                        const gchar *type,
+                        gint        *error) {
+    gboolean ret;
+    GW_OBEX_LOCK(ctx);
+    CHECK_DISCONNECT(FALSE, error, ctx);
+    ret = gw_obex_get(ctx, NULL, remote, type, NULL, NULL, fd, FALSE);
+    if (ret == FALSE)
+        gw_obex_get_error(ctx, error);
+    GW_OBEX_UNLOCK(ctx);
+    return ret;
+}
+
+gboolean gw_obex_put_fd(GwObex      *ctx,
+                        gint         fd,
+                        const gchar *remote,
+                        const gchar *type,
+                        gint        *error) {
+    gboolean ret;
+    GW_OBEX_LOCK(ctx);
+    CHECK_DISCONNECT(FALSE, error, ctx);
+    ret = gw_obex_put(ctx, NULL, remote, type, NULL, 0, -1, fd, FALSE);
+    if (ret == FALSE)
+        gw_obex_get_error(ctx, error);
+    GW_OBEX_UNLOCK(ctx);
+    return ret;
+}
+
+gboolean gw_obex_put_file(GwObex      *ctx,
+                          const gchar *local,
+                          const gchar *remote,
+                          const gchar *type,
+                          gint        *error) {
+    gboolean ret;
+    GW_OBEX_LOCK(ctx);
+    CHECK_DISCONNECT(FALSE, error, ctx);
+    ret = gw_obex_put(ctx, local, remote, type, NULL, 0, -1, -1, FALSE);
+    if (ret == FALSE)
+        gw_obex_get_error(ctx, error);
+    GW_OBEX_UNLOCK(ctx);
+    return ret;
+}
+
+gboolean gw_obex_get_buf(GwObex       *ctx,
+                         const gchar  *remote,
+                         const gchar  *type,
+                         gchar       **buf,
+                         gint         *buf_size,
+                         gint         *error) {
+    gboolean ret;
+    GW_OBEX_LOCK(ctx);
+    CHECK_DISCONNECT(FALSE, error, ctx);
+    ret = gw_obex_get(ctx, NULL, remote, type, buf, buf_size, -1, FALSE);
+    if (ret == FALSE)
+        gw_obex_get_error(ctx, error);
+    GW_OBEX_UNLOCK(ctx);
+    return ret;
+}
+
+gboolean gw_obex_put_buf(GwObex      *ctx,
+                         const gchar *remote,
+                         const gchar *type,
+                         const gchar *buf,
+                         gint         buf_size,
+                         gint         time,
+                         gint        *error) {
+    gboolean ret;
+    GW_OBEX_LOCK(ctx);
+    CHECK_DISCONNECT(FALSE, error, ctx);
+    ret = gw_obex_put(ctx, NULL, remote, type, buf, buf_size, time, -1, FALSE);
+    if (ret == FALSE)
+        gw_obex_get_error(ctx, error);
+    GW_OBEX_UNLOCK(ctx);
+    return ret;
+}
+
+gboolean gw_obex_chdir(GwObex *ctx, const gchar *dir, gint *error) {
+    gboolean ret;
+    GW_OBEX_LOCK(ctx);
+    CHECK_DISCONNECT(FALSE, error, ctx);
+    ret = gw_obex_setpath(ctx, dir ? dir : "", 0);
+    if (ret == FALSE)
+        gw_obex_get_error(ctx, error);
+    GW_OBEX_UNLOCK(ctx);
+    return ret;
+}
+
+gboolean gw_obex_mkdir(GwObex *ctx, const gchar *dir, gint *error) {
+    GW_OBEX_LOCK(ctx);
+    CHECK_DISCONNECT(FALSE, error, ctx);
+    if (!gw_obex_setpath(ctx, dir ? dir : "", SETPATH_CREATE)) {
+        gw_obex_get_error(ctx, error);
+        GW_OBEX_UNLOCK(ctx);
+        return FALSE;
+    }
+    (void) gw_obex_setpath(ctx, "..", 0);
+    GW_OBEX_UNLOCK(ctx);
+    return TRUE;
+}
+
+gboolean gw_obex_read_dir(GwObex *ctx, const gchar *dir,
+                          gchar **buf, gint *buf_size, gint *error) {
+    gboolean ret;
+    GW_OBEX_LOCK(ctx);
+    CHECK_DISCONNECT(FALSE, error, ctx);
+    ret = gw_obex_get(ctx, NULL, dir ? dir : "", LST_TYPE, buf, buf_size, -1, FALSE);
+    if (ret == FALSE)
+        gw_obex_get_error(ctx, error);
+    else if (*buf_size > 0) {
+        /* Hack for some OBEX implementations which send nul's
+         * at the end of the listing */
+        int i;
+
+        for (i = *buf_size - 1; i > 0; i--) {
+            if ((*buf)[i] == '\0')
+                (*buf_size)--;
+            else
+                break;
+        }
+    }
+    GW_OBEX_UNLOCK(ctx);
+    return ret;
+}
+
+gboolean gw_obex_delete(GwObex *ctx, const gchar *name, gint *error) {
+    gboolean ret;
+    GW_OBEX_LOCK(ctx);
+    CHECK_DISCONNECT(FALSE, error, ctx);
+    ret = gw_obex_put(ctx, NULL, name, NULL, NULL, 0, -1, -1, FALSE);
+    if (ret == FALSE)
+        gw_obex_get_error(ctx, error);
+    GW_OBEX_UNLOCK(ctx);
+    return ret;
+}
+
+gboolean gw_obex_move(GwObex *ctx, const gchar *src, const gchar *dst, gint *error) {
+    gboolean ret;
+    GW_OBEX_LOCK(ctx);
+    CHECK_DISCONNECT(FALSE, error, ctx);
+    ret = gw_obex_action_op(ctx, src, dst, OBEX_ACTION_MOVE);
+    if (ret == FALSE)
+        gw_obex_get_error(ctx, error);
+    GW_OBEX_UNLOCK(ctx);
+    return ret;
+}
+
+gboolean gw_obex_copy(GwObex *ctx, const gchar *src, const gchar *dst, gint *error) {
+    gboolean ret;
+    GW_OBEX_LOCK(ctx);
+    CHECK_DISCONNECT(FALSE, error, ctx);
+    ret = gw_obex_action_op(ctx, src, dst, OBEX_ACTION_COPY);
+    if (ret == FALSE)
+        gw_obex_get_error(ctx, error);
+    GW_OBEX_UNLOCK(ctx);
+    return ret;
+}
+
+gboolean gw_obex_get_capability(GwObex *ctx, gchar **cap, gint *cap_len, gint *error) {
+    gboolean ret;
+    GW_OBEX_LOCK(ctx);
+    CHECK_DISCONNECT(FALSE, error, ctx);
+    ret = gw_obex_get(ctx, NULL, NULL, CAP_TYPE, cap, cap_len, -1, FALSE);
+    if (ret == FALSE) {
+        *cap = NULL;
+        *cap_len = 0;
+        gw_obex_get_error(ctx, error);
+    }
+    GW_OBEX_UNLOCK(ctx);
+    return ret;
+}
+
+void gw_obex_set_disconnect_callback(GwObex *ctx, gw_obex_disconnect_cb_t callback, gpointer data) {
+    GW_OBEX_LOCK(ctx);
+    ctx->dc_cb = callback;
+    ctx->dc_data = data;
+    GW_OBEX_UNLOCK(ctx);
+}
+
+void gw_obex_set_progress_callback(GwObex *ctx, gw_obex_progress_cb_t callback, gpointer data) {
+    GW_OBEX_LOCK(ctx);
+    ctx->pr_cb = callback;
+    ctx->pr_data = data;
+    GW_OBEX_UNLOCK(ctx);
+}
+
+void gw_obex_set_cancel_callback(GwObex *ctx, gw_obex_cancel_cb_t callback, gpointer data) {
+    GW_OBEX_LOCK(ctx);
+    ctx->cancel_cb = callback;
+    ctx->cancel_data = data;
+    GW_OBEX_UNLOCK(ctx);
+}
+
+void gw_obex_close(GwObex *ctx) {
+    GW_OBEX_LOCK(ctx);
+    if (ctx->xfer) {
+        GwObexXfer *xfer = ctx->xfer;
+        GW_OBEX_UNLOCK(ctx);
+        gw_obex_xfer_close(ctx->xfer, NULL);
+        GW_OBEX_LOCK(ctx);
+        /* In the async case the caller of put/get_async owns the xfer object */
+        if (!xfer->async)
+            _gw_obex_xfer_free(xfer);
+        ctx->xfer = NULL;
+    }
+    if (ctx->conn_fd >= 0) {
+        if (!gw_obex_disconnect(ctx))
+            debug("OBEX Disconnect command failed\n");
+        OBEX_TransportDisconnect(ctx->handle);
+        close(ctx->conn_fd);
+        ctx->conn_fd = -1;
+    }
+    if (ctx->handle) {
+        OBEX_Cleanup(ctx->handle);
+        ctx->handle = NULL;
+    }
+    if (ctx->gio) {
+        g_io_channel_unref(ctx->gio);
+        ctx->gio = NULL;
+    }
+    if (ctx->gio_source) {
+        g_source_destroy(ctx->gio_source);
+        ctx->gio_source = NULL;
+    }
+    GW_OBEX_UNLOCK(ctx);
+#ifdef G_THREADS_ENABLED
+    g_mutex_free(ctx->mutex);
+    ctx->mutex = NULL;
+#endif
+    g_free(ctx);
+}
+
+GwObex *gw_obex_setup_fd(int fd, const gchar *uuid, gint uuid_len,
+                         GMainContext *context, gint *error) {
+    obex_t *handle;
+    GwObex *ctx;
+
+    if (!gw_obex_transport_setup(fd, &handle)) {
+        debug("gw_obex_open() failed\n");
+        if (error)
+            *error = GW_OBEX_ERROR_CONNECT_FAILED;
+        return NULL;
+    }
+
+    debug("Transport connection opened.\n");
+
+    ctx = make_context(handle);
+
+#ifdef G_THREADS_ENABLED
+    if (!g_thread_supported())
+        g_thread_init(NULL);
+    ctx->mutex = g_mutex_new();
+#endif
+
+    OBEX_SetCustomData(handle, ctx);
+
+    debug("Connecting to OBEX service\n");
+    if (!gw_obex_connect(ctx, uuid, uuid_len)) {
+        debug("Unable to connect to OBEX service\n");
+#ifdef G_THREADS_ENABLED
+        g_mutex_free(ctx->mutex);
+        ctx->mutex = NULL;
+#endif
+        g_free(ctx);
+        OBEX_Cleanup(handle);
+        if (error)
+            *error = GW_OBEX_ERROR_NO_SERVICE;
+        return NULL;
+    }
+
+    debug("Connected (Connection ID: %#x)\n", ctx->conid);
+
+    ctx->gio = g_io_channel_unix_new(ctx->conn_fd);
+    ctx->gio_source = g_io_create_watch (ctx->gio,
+                                         G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL);
+    g_source_set_callback(ctx->gio_source, (GSourceFunc)gw_obex_cb, ctx, NULL);
+    (void) g_source_attach(ctx->gio_source, context);
+    g_source_unref(ctx->gio_source);
+
+    ctx->main_ctx = context;
+
+    return ctx;
+}
+
+GwObex *gw_obex_setup_dev(const char *dev, const gchar *uuid, gint uuid_len,
+                          GMainContext *context, gint *error) {
+    GwObex *ctx;
+    int fd;
+
+    fd = open(dev, O_RDWR | O_NOCTTY | O_SYNC);
+    if (fd < 0) {
+        debug("open(\"%s\"): %s\n", dev, strerror(errno));
+        if (error)
+            *error = GW_OBEX_ERROR_CONNECT_FAILED;
+        return NULL;
+    }
+
+    if (!fd_raw_mode(fd)) {
+        debug("setting raw mode failed\n");
+        close(fd);
+        if (error)
+            *error = GW_OBEX_ERROR_CONNECT_FAILED;
+        return NULL;
+    }
+
+    ctx = gw_obex_setup_fd(fd, uuid, uuid_len, context, error);
+    if (ctx == NULL) {
+        close(fd);
+    }
+
+    return ctx;
+}
+
diff --git a/obexd/gwobex/gw-obex.h b/obexd/gwobex/gw-obex.h
new file mode 100644
index 0000000..c1be103
--- /dev/null
+++ b/obexd/gwobex/gw-obex.h
@@ -0,0 +1,601 @@
+/**
+  @file gw-obex.h
+
+  OSSO GW OBEX Connectivity Library and API
+
+  @author Johan Hedberg <johan.hedberg@nokia.com>
+
+  Copyright (C) 2004-2005 Nokia Corporation. All rights reserved.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License, version 2.1, as published by the Free Software Foundation.
+
+  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.
+
+*/
+#ifndef _GW_OBEX_H_
+#define _GW_OBEX_H_
+
+#include <glib.h>
+#include <openobex/obex.h>
+
+/**
+ * @name GW OBEX Error Codes
+ * The error codes returned by many of the functions refer either to an OBEX
+ * Protocol error or to a GW OBEX error. If the error code is less that 256, it
+ * refers to an OBEX error, othervice it refers to a GW_OBEX_ERROR_* error.
+ * @{
+*/
+
+/** Transport connection was disconnected */
+#define GW_OBEX_ERROR_DISCONNECT        256
+
+/** Operation was aborted */
+#define GW_OBEX_ERROR_ABORT             257
+
+/** GW OBEX internal error */
+#define GW_OBEX_ERROR_INTERNAL          258
+
+/** Unable to connecto to the specified service (UUID) */
+#define GW_OBEX_ERROR_NO_SERVICE        259
+
+/** Unable to create connection */
+#define GW_OBEX_ERROR_CONNECT_FAILED    260
+
+/** Timeout while waiting for data from the remote device */
+#define GW_OBEX_ERROR_TIMEOUT           261
+
+/** Remote device returned invalid/corrupted data */
+#define GW_OBEX_ERROR_INVALID_DATA      262
+
+/** Invalid parameters given to gwobex */
+#define GW_OBEX_ERROR_INVALID_PARAMS    263
+
+/** Local access error (e.g. read/write/open failed for local file) */
+#define GW_OBEX_ERROR_LOCAL_ACCESS      264
+
+/** Another operation is in progress */
+#define GW_OBEX_ERROR_BUSY              265
+
+/** No data currently available */
+#define GW_OBEX_ERROR_NO_DATA           266
+
+
+/** @} */
+
+/** Value used if target length for put or get is not known */
+#define GW_OBEX_UNKNOWN_LENGTH -1
+
+/** Standard folder browsing service UUID (give this as a parameter to
+ *  gw_obex_setup_* to connect to folder browsing service */
+#define OBEX_FTP_UUID \
+    "\xF9\xEC\x7B\xC4\x95\x3C\x11\xD2\x98\x4E\x52\x54\x00\xDC\x9E\x09"
+/** Length of OBEX_FTP_UUID */
+#define OBEX_FTP_UUID_LEN 16
+
+/** Phone Book Access Profile UUID */
+#define OBEX_PBAP_UUID \
+    "\x79\x61\x35\xF0\xF0\xC5\x11\xD8\x09\x66\x08\x00\x20\x0C\x9A\x66"
+/** Length of OBEX_PBAP_UUID */
+#define OBEX_PBAP_UUID_LEN 16
+
+/** Struct containing the context of a gwobex connection */
+typedef struct gw_obex GwObex;
+
+/** Objecct transfer handle */
+typedef struct gw_obex_xfer GwObexXfer;
+
+/** Callback type for ongoing transfers
+ * @param ctx  GwObexXfer pointer for the transfer
+ * @param data Optional pointer to user data
+ */
+typedef void (*gw_obex_xfer_cb_t) (GwObexXfer *xfer,
+                                   gpointer data);
+
+/** Callback type for transport connection loss
+ * @param ctx  GwObex pointer for the connection
+ * @param data Optional pointer to user data
+ */
+typedef void (*gw_obex_disconnect_cb_t) (GwObex *ctx,
+                                         gpointer data);
+
+/** Callback type for progress information
+ * Only used for the synchronous transfer functions.
+ * @param ctx       GwObex pointer for the connection
+ * @param obex_cmd  eg. OBEX_CMD_PUT
+ * @param current   Bytes transfered
+ * @param target    Total length (or GW_OBEX_UNKNOWN_LENGTH)
+ * @param data      Optional pointer to user data
+ */
+typedef void (*gw_obex_progress_cb_t) (GwObex *ctx, gint obex_cmd,
+                                       gint current, gint target,
+                                       gpointer data);
+
+/** Callback type for checking if the operation should be canceled.
+ * Only used for the synchronous functions.
+ * In the GNOME VFS case the callback function should be
+ * gnome_vfs_cancellation_check().
+ * @param data Optional pointer to user data
+ * @returns TRUE if the operation should be canceled, FALSE othervice
+ */
+typedef gboolean (*gw_obex_cancel_cb_t) (gpointer data);
+
+
+/**
+ * @name Functions for connecting and disconnecting
+ * With these functions you can create and and disconnect connections. You can
+ * either connect using a filename (e.g. "/dev/rfcomm0") or using a file
+ * descriptor (e.g. a RFCOMM socket).
+ * @{
+ */
+
+/** Open connection using a local device node and setup parameters.
+ * This function should be called before calling any other functions. The
+ * pointer returned by this function should be passed to the other functions.
+ *
+ * @param device   The local device which should be opened for the connection
+ * @param uuid     UUID of service to connect to. NULL for the default service
+ *                 (INBOX).
+ * @param uuid_len Length (in bytes) of UUID
+ * @param context  GMainContext to attach to (or NULL for the default one)
+ * @param error    Place to store error code on failure (NULL if not interested)
+ *
+ * @returns A pointer, NULL on failure
+ *  This pointer should be passed to the other obex_* functions.
+ **/
+GwObex *gw_obex_setup_dev(const gchar *device,
+                          const gchar *uuid,
+                          gint uuid_len,
+                          GMainContext *context,
+                          gint *error);
+
+
+/** Setup OBEX connection using an opened file descriptor
+ * This function should be called before calling any other functions. The
+ * pointer returned by this function should be passed to the other functions.
+ *
+ * @param fd       Opened file descriptor to use for the connection
+ * @param uuid     UUID of service to connect to. NULL for the default service
+ *                 (INBOX).
+ * @param uuid_len Length (in bytes) of UUID
+ * @param context  GMainContext to attach to (or NULL for the default one)
+ * @param error    Place to store error code on failure (NULL if not interested)
+ *
+ * @returns A pointer, NULL on failure
+ *  This pointer should be passed to the other obex_* functions.
+ **/
+GwObex *gw_obex_setup_fd(int fd,
+                         const gchar *uuid,
+                         gint uuid_len,
+                         GMainContext *context,
+                         gint *error);
+
+
+/** Close GW OBEX connection and free all memory associated with it.
+ *
+ * @param ctx Pointer returned by gw_obex_setup().
+ *  Cannot be used anymore after this calling this function.
+ */
+void gw_obex_close(GwObex *ctx);
+
+/** @} */
+
+/**
+ * @name Registering callback functions
+ * With these functions you can register your own callback functions
+ * to gwobex to receive indications about special events.
+ * @{
+ */
+
+/** Set function to be called when a disconnection happens.
+ *  You may (and probably should) call gw_obex_close() if this function is
+ *  called.
+ * @param ctx      Pointer returned by gw_obex_setup()
+ * @param callback Function to call
+ * @param data     Optional data to pass to the callback function
+ */
+void gw_obex_set_disconnect_callback(GwObex *ctx,
+                                     gw_obex_disconnect_cb_t callback,
+                                     gpointer data);
+
+
+/** Set function to be called when progress for a put or get operation happens.
+ * Only used for the synchronous transfer functions.
+ *
+ * @param ctx      Pointer returned by gw_obex_setup()
+ * @param callback Function to call
+ * @param data     Optional data to pass to the callback function
+ */
+void gw_obex_set_progress_callback(GwObex *ctx,
+                                   gw_obex_progress_cb_t callback,
+                                   gpointer data);
+
+
+/** Set function to be called to check if the current operation should be
+ * canceled. In the GNOME VFS case the callback function should be
+ * gnome_vfs_cancellation_check(). The callback function should return TRUE if
+ * the operation should be canceled and FALSE othervice.
+ *
+ * Only used for the synchronous transfer functions.
+ * 
+ * @param ctx      Pointer returned by gw_obex_setup()
+ * @param callback Function to call
+ * @param data     Pointer to pass to the callback function
+ */
+void gw_obex_set_cancel_callback(GwObex *ctx,
+                                 gw_obex_cancel_cb_t callback,
+                                 gpointer data);
+
+/** @} */
+
+/**
+ * @name Functions for performing synchronous remote operations
+ * Once you have setup a connection using one of the gw_obex_setup_* functions,
+ * you can perform different remote transactions using these functions.
+ * @{
+ */
+
+/** Get the capability object from the connected remote device.
+ *
+ * @param ctx     Pointer returned by gw_obex_setup()
+ * @param cap     Place to store the fetched object.
+ *                 g_free() when not needed anymore.
+ * @param cap_len Place to store the size of the fetched object
+ * @param error   Place to store a possible error code
+ *   (NULL if not interested)
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_get_capability(GwObex *ctx,
+                                gchar **cap,
+                                gint *cap_len,
+                                gint *error);
+
+
+/** Get a file from the remote device.
+ *
+ * @param ctx    Pointer returned by gw_obex_setup()
+ * @param local  Local filename (null terminated UTF-8)
+ * @param remote Remote filename (null terminated UTF-8)
+ * @param type   MIME-type of the object
+ * @param error  Place to store error code on failure
+ *               (NULL if not interested)
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_get_file(GwObex *ctx,
+                          const gchar *local,
+                          const gchar *remote,
+                          const gchar *type,
+                          gint *error);
+
+
+/** Send a file to the remote device.
+ *
+ * @param ctx    Pointer returned by gw_obex_setup()
+ * @param local  Local filename (null terminated UTF-8)
+ * @param remote Remote filename (null terminated UTF-8)
+ * @param type   MIME-type of the object
+ * @param error  Place to store error code on failure
+ *               (NULL if not interested)
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_put_file(GwObex *ctx,
+                          const gchar *local,
+                          const gchar *remote,
+                          const gchar *type,
+                          gint *error);
+
+
+/** Get a file from the remote device and write it to a file descriptor
+ *
+ * @param ctx    Pointer returned by gw_obex_setup()
+ * @param fd     File descriptor to write the file into
+ * @param remote Remote filename (null terminated UTF-8)
+ * @param type   MIME-type of the object
+ * @param error  Place to store error code on failure
+ *               (NULL if not interested)
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_get_fd(GwObex *ctx, gint fd,
+                        const gchar *remote,
+                        const gchar *type,
+                        gint *error);
+
+/** Read data from a file descriptor and send it to the remote device
+ *
+ * @param ctx    Pointer returned by gw_obex_setup()
+ * @param fd     File descriptor to read the data from
+ * @param remote Remote filename (null terminated UTF-8)
+ * @param type   MIME-type of the object
+ * @param error  Place to store error code on failure
+ *               (NULL if not interested)
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_put_fd(GwObex *ctx, gint fd,
+                        const gchar *remote,
+                        const gchar *type,
+                        gint *error);
+
+/** Get an object from the remote device and store it in a memory buffer.
+ * Either remote filename or type must be supplied (or both).
+ *
+ * @param ctx      Pointer returned by gw_obex_setup()
+ * @param remote   Remote filename (null terminated UTF-8)
+ * @param type     MIME-type of the object
+ * @param buf      Buffer to store the object in.
+ *                  g_free() when not needed anymore.
+ * @param buf_size Place to store length of fetched object
+ * @param error    Place to store error code on failure
+ *                 (NULL if not interested)
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_get_buf(GwObex *ctx, const gchar *remote, const gchar *type,
+                         gchar **buf, gint *buf_size, gint *error);
+
+
+/** Send a object located in a memory buffer to the remote device.
+ * Either remote filename or type must be supplied (or both)
+ *
+ * @param ctx      Pointer returned by gw_obex_setup()
+ * @param remote   Remote filename (null terminated UTF-8)
+ * @param type     MIME-type of the object
+ * @param buf      Buffer containing the object
+ * @param buf_size Buffer (object) size
+ * @param time     Last modification time of object (or -1 if not known)
+ * @param error    Place to store error code on failure
+ *                 (NULL if not interested)
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_put_buf(GwObex *ctx, const gchar *remote, const gchar *type,
+                         const gchar *buf, gint buf_size, gint time, gint *error);
+
+
+/** Change directory (relative to the current one).
+ *
+ * @param ctx   Pointer returned by gw_obex_setup()
+ * @param dir   New directory to change to (null terminated UTF-8),
+ *              ".." to go up, NULL to go to the root folder
+ * @param error Place to store error code on failure (NULL if not interested)
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_chdir(GwObex *ctx, const gchar *dir, gint *error);
+
+
+/** Create a new directory.
+ *
+ * @param ctx   Pointer returned by gw_obex_setup()
+ * @param dir   Directory to create (null terminated UTF-8)
+ * @param error Place to store error code on failure (NULL if not interested)
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_mkdir(GwObex *ctx, const gchar *dir, gint *error);
+
+
+/** Get folder listing for the specified directory.
+ *
+ * @param ctx      Pointer returned by gw_obex_setup()
+ * @param dir      Directory to list (null terminated UTF-8),
+ *                 NULL to list current directory
+ * @param buf      Place to store the folder-listing object
+ * @param buf_size Place to store the size for the retrieved object
+ * @param error    Place to store error code on failure (NULL if not interested)
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_read_dir(GwObex *ctx, const gchar *dir,
+                          gchar **buf, gint *buf_size, gint *error);
+
+
+/** Remove a file from the remote device.
+ *
+ * @param ctx   Pointer returned by gw_obex_setup()
+ * @param name  Filename to remove (null terminated UTF-8)
+ * @param error Place to store error code on failure (NULL if not interested)
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_delete(GwObex *ctx, const gchar *name, gint *error);
+
+
+/** Move/Rename a file on the remote device.
+ *
+ * @param ctx   Pointer returned by gw_obex_setup()
+ * @param src   Source filename (null terminated UTF-8)
+ * @param dst   Destination filename (null terminated UTF-8)
+ * @param error Place to store error code on failure (NULL if not interested)
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_move(GwObex *ctx, const gchar *src, const gchar *dst,
+                      gint *error);
+
+
+/** Copy a file on the remote device.
+ *
+ * @param ctx   Pointer returned by gw_obex_setup()
+ * @param src   Source filename (null terminated UTF-8)
+ * @param dst   Destination filename (null terminated UTF-8)
+ * @param error Place to store error code on failure (NULL if not interested)
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_copy(GwObex *ctx, const gchar *src, const gchar *dst,
+                      gint *error);
+
+/** @} */
+
+/**
+ * @name Functions for performing transfers in an asynchronous manner
+ * With these functions you can do transfers in smaller steps. The steps
+ * are split up in a open, read/write, close manner.
+ * @{
+ */
+
+/** Start a PUT operation asynchronously
+ *
+ * @param ctx   Pointer returned by gw_obex_setup()
+ * @param name  Name of the object (null terminated UTF-8)
+ * @param type  Type of the object (null terminated UTF-8), or NULL
+ * @param size  Size of the object (GW_OBEX_UNKNOWN_LENGTH if not known)
+ * @param time  Last modification time of the object (-1 if not known)
+ * @param error Place to store error code on failure (NULL if not interested)
+ *
+ * @returns a new GwObexXfer object on success, NULL on failure
+ */
+GwObexXfer *gw_obex_put_async(GwObex *ctx, const char *name, const char *type,
+                              gint size, time_t time, gint *error);
+
+
+/** Start a GET operation asynchronously
+ *
+ * @param ctx   Pointer returned by gw_obex_setup()
+ * @param name  Name of the object (null terminated UTF-8)
+ * @param type  Type of the object (null terminated UTF-8), or NULL
+ * @param error Place to store error code on failure (NULL if not interested)
+ *
+ * @returns a new GwObexXfer object on success, NULL on failure
+ */
+GwObexXfer *gw_obex_get_async(GwObex *ctx, const char *name, const char *type, gint *error);
+
+
+/** Set a callback function for a GwObexXfer object
+ * The callback function will be called in the following situations:
+ * <ul>
+ *  <li>Data can be written (i.e. xfer_write will succeed)</li>
+ *  <li>Data can be read (i.e. xfer_read will succees)</li>
+ *  <li>An error ocured</li>
+ *  <li>The transfer is finished</li>
+ * </ul>
+ *
+ * @param xfer      Pointer returned by gw_obex_put_async or gw_obex_get_async
+ * @param cb        Pointer to the callback function
+ * @param user_data Optional user data which will be passed to the callback function
+ *
+ * @returns a new GwObexXfer object on success, NULL on failure
+ */
+void gw_obex_xfer_set_callback(GwObexXfer *xfer, gw_obex_xfer_cb_t cb, gpointer user_data);
+
+
+/** Get the last modification time of the object being transfered
+ *
+ * @param xfer Pointer returned by gw_obex_put_async or gw_obex_get_async
+ *
+ * @returns The modification time or -1 if it is not known.
+ */
+time_t gw_obex_xfer_object_time(GwObexXfer *xfer);
+
+
+/** Get the size of the object being transfered
+ *
+ * @param xfer Pointer returned by gw_obex_put_async or gw_obex_get_async
+ *
+ * @returns The size or GW_OBEX_UNKNOWN_LENGTH if it is not known.
+ */
+gint gw_obex_xfer_object_size(GwObexXfer *xfer);
+
+
+/** Supply more data to a transfer
+ *
+ * @param xfer          Pointer returned by gw_obex_put_async or gw_obex_get_async
+ * @param buf           Buffer containing the data
+ * @param buf_size      Size of the buffer
+ * @param bytes_written Return value for the number of bytes that were written
+ * @param error Place to store error code on failure (NULL if not interested)
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_xfer_write(GwObexXfer *xfer, const char *buf, gint buf_size,
+                            gint *bytes_written, gint *error);
+
+/** Read data from a transfer
+ *
+ * The function will report EOF by returning success with zero bytes read.
+ *
+ * @param xfer          Pointer returned by gw_obex_put_async or gw_obex_get_async
+ * @param buf           Buffer where the data should be stored
+ * @param buf_size      Size of the buffer
+ * @param bytes_read    Return value for the number of bytes that were read
+ * @param error Place to store error code on failure (NULL if not interested)
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_xfer_read(GwObexXfer *xfer, char *buf, gint buf_size,
+                           gint *bytes_read, gint *error);
+
+
+/** Force all data remaining in buffers to be sent
+ *
+ * @param xfer  Pointer returned by gw_obex_put_async or gw_obex_get_async
+ * @param error Place to store error code on failure (NULL if not interested)
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_xfer_flush(GwObexXfer *xfer, gint *error);
+
+
+/** Close an ongoing transfer
+ *
+ * You still need to call gw_obex_xfer_free after this to free the actual
+ * memory allocated for the GwObexXfer object.
+ *
+ * @param xfer  Pointer returned by gw_obex_put_async or gw_obex_get_async
+ * @param error Place to store error code on failure (NULL if not interested)
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_xfer_close(GwObexXfer *xfer, gint *error);
+
+
+/** Abort an ongoing transfer
+ *
+ * You still need to call gw_obex_xfer_free after this to free the actual
+ * memory allocated for the GwObexXfer object. xfer_close and xfer_abort are
+ * mutually exclusive (only call one of them for a transfer).
+ *
+ * @param xfer  Pointer returned by gw_obex_put_async or gw_obex_get_async
+ * @param error Place to store error code on failure (NULL if not interested)
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_xfer_abort(GwObexXfer *xfer, gint *error);
+
+
+/** Free the data allocated for a GwObexXfer object
+ *
+ * @param xfer  Pointer returned by gw_obex_put_async or gw_obex_get_async
+ */
+void gw_obex_xfer_free(struct gw_obex_xfer *xfer);
+
+
+/** Set blocking behaviour for a GwObexXfer object when calling xfer_read and xfer_write
+ *
+ * When blocking is enabled xfer_read will return only after it has been able
+ * to read some data (i.e. GW_OBEX_ERROR_NO_DATA will not be returned). For xfer_write
+ * blocking guarantees that *some* data will be written.
+ *
+ * @param xfer  Pointer returned by gw_obex_put_async or gw_obex_get_async
+ * @param block TRUE to enable blocking behaviour
+ */
+void gw_obex_xfer_set_blocking(GwObexXfer *xfer, gboolean block);
+
+/** @} */
+
+#endif /* _GW_OBEX_H_ */
+
diff --git a/obexd/gwobex/log.h b/obexd/gwobex/log.h
new file mode 100644
index 0000000..41be01a
--- /dev/null
+++ b/obexd/gwobex/log.h
@@ -0,0 +1,38 @@
+/**
+  @file log.h
+
+  @author Johan Hedberg <johan.hedberg@nokia.com>
+
+  Copyright (C) 2004 Nokia Corporation. All rights reserved.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License, version 2.1, as published by the Free Software Foundation.
+
+  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.
+
+*/
+#ifndef _LOG_H_
+#define _LOG_H_
+
+#ifdef DEBUG
+# ifdef DEBUG_STDOUT
+#  include <glib.h>
+#  define debug(...) g_print(__VA_ARGS__)
+# else
+#  include <syslog.h>
+#  define debug(fmt, arg...) syslog(LOG_DEBUG, "gwobex: " fmt, ## arg)
+# endif
+#else
+# define debug(...) ((void)(0))
+#endif
+
+#endif /* _LOG_H_ */
diff --git a/obexd/gwobex/obex-priv.c b/obexd/gwobex/obex-priv.c
new file mode 100644
index 0000000..792cb4f
--- /dev/null
+++ b/obexd/gwobex/obex-priv.c
@@ -0,0 +1,1084 @@
+/**
+  @file obex-priv.c
+
+  Private functions for the GW OBEX Library
+
+  @author Johan Hedberg <johan.hedberg@nokia.com>
+
+  Copyright (C) 2004-2006 Nokia Corporation. All rights reserved.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License, version 2.1, as published by the Free Software Foundation.
+
+  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 <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <glib.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <openobex/obex.h>
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "log.h"
+#include "gw-obex.h"
+#include "utils.h"
+#include "obex-xfer.h"
+#include "obex-priv.h"
+
+#define MAX_TIMEOUTS 20
+#define MAX_TIMEOUTS_FIRST 60
+
+static gboolean file_is_dir(const char *filename) {
+    struct stat st;
+
+    if (stat(filename, &st) < 0)
+        return FALSE;
+
+    if (S_ISDIR(st.st_mode))
+        return TRUE;
+
+    return FALSE;
+}
+
+static void idle_callback(obex_t *handle, obex_object_t *object, int mode,
+                    int event, int obex_cmd, int obex_rsp) {
+    debug("idle_callback() called\n");
+    sleep(1);
+}
+
+static gboolean gw_obex_request_async(GwObex *ctx, obex_object_t *object) {
+    ctx->done = FALSE;
+
+    /* If this is a put, the first request can only be sent after we
+     * have some data in the outgoing buffer */
+    if (ctx->obex_op == OBEX_CMD_PUT) {
+        ctx->xfer->object = object;
+        return TRUE;
+    }
+
+    if (OBEX_Request(ctx->handle, object) < 0) {
+        debug("OBEX_Request() failed\n");
+        ctx->error = GW_OBEX_ERROR_INTERNAL;
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static gboolean gw_obex_request_sync(GwObex *ctx, obex_object_t *object) {
+    int timeouts = 0, first = 1;
+
+    /* Set sensible start values */
+    ctx->done       = FALSE;
+
+    if (OBEX_Request(ctx->handle, object) < 0) {
+        debug("OBEX_Request() failed\n");
+        ctx->error = GW_OBEX_ERROR_INTERNAL;
+        return FALSE;
+    }
+
+    while (TRUE) {
+        int ret, max_timeouts;
+
+        ret = OBEX_HandleInput(ctx->handle, 1);
+
+        if (ctx->done)
+            break;
+
+        if (ctx->cancel_cb && ctx->cancel_cb(ctx->cancel_data)) {
+            debug("cancel_cb() returned TRUE, aborting.\n");
+            if (ctx->xfer->abort)
+                continue;
+            if (!gw_obex_xfer_do_abort(ctx->xfer))
+                break;
+            /* Must continue to receive the abort reply */
+            continue;
+        }
+
+        if (ret < 0) {
+            debug("OBEX_HandleInput() failed\n");
+            ctx->error = GW_OBEX_ERROR_INTERNAL;
+            return FALSE;
+        }
+
+        /* Timeout */
+        if (ret == 0)
+            timeouts++;
+        else {
+            timeouts = 0;
+            if (first)
+                first = 0;
+        }
+
+        max_timeouts = first ? MAX_TIMEOUTS_FIRST : MAX_TIMEOUTS;
+
+        if (timeouts > max_timeouts) {
+            debug("OBEX_HandleInput(): timeout\n");
+            ctx->error = GW_OBEX_ERROR_TIMEOUT;
+            obex_link_error(ctx);
+            return FALSE;
+        }
+
+        debug("gw_obex_request_sync(): looping\n");
+    }
+
+    gw_obex_set_error(ctx);
+
+    if (ctx->error == OBEX_RSP_SUCCESS) {
+        /* It is possible that a EV_PROGRESS doesn't arrive after all data has
+         * been transfered. Call pr_cb here to ensure app gets 100% progress */
+        if (ctx->report_progress && ctx->pr_cb)
+            ctx->pr_cb(ctx, ctx->obex_op, ctx->xfer->counter, ctx->xfer->counter, ctx->pr_data);
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+#ifdef DEBUG
+static const char *optostr(uint8_t op) {
+    switch (op) {
+        case OBEX_CMD_CONNECT:
+            return "Connect";
+        case OBEX_CMD_DISCONNECT:
+            return "Disconnect";
+        case OBEX_CMD_PUT:
+            return "Put";
+        case OBEX_CMD_GET:
+            return "Get";
+        case OBEX_CMD_SETPATH:
+            return "SetPath";
+        case OBEX_CMD_ABORT:
+            return "Abort";
+        case OBEX_CMD_ACTION:
+            return "Action";
+        default:
+            return "(unknown)";
+    }
+}
+#endif
+
+static void obex_connect_done(GwObex *ctx, obex_object_t *object, int obex_rsp) {
+    obex_headerdata_t hv;
+    uint8_t hi;
+    unsigned int hlen;
+    uint8_t *ptr;
+
+    if (OBEX_ObjectGetNonHdrData(object, &ptr)
+            != sizeof(obex_connect_hdr_t)) {
+        debug("Invalid packet content.\n");
+    }
+    else {
+        obex_connect_hdr_t *nonhdrdata = (obex_connect_hdr_t *)ptr;
+        uint16_t mtu = g_ntohs(nonhdrdata->mtu);
+        int new_size;
+        debug("Version: 0x%02x. Flags: 0x%02x  OBEX packet length: %d\n",
+                nonhdrdata->version, nonhdrdata->flags, mtu);
+        /* Leave space for headers */
+        new_size = mtu - 200;
+        if (new_size < ctx->tx_max) {
+            debug("Resizing stream chunks to %d\n", new_size);
+            ctx->tx_max = new_size;
+        }
+    }
+
+    while (OBEX_ObjectGetNextHeader(ctx->handle, object, &hi, &hv, &hlen)) {
+        switch (hi) {
+#ifdef DEBUG
+            case OBEX_HDR_WHO:
+                {
+                    char *str;
+                    str = bytestr(hv.bs, hlen);
+                    debug("WHO header (UUID): %s\n", str);
+                    g_free(str);
+                }
+                break;
+#endif
+            case OBEX_HDR_CONNECTION:
+                ctx->conid = hv.bq4;
+                debug("got Conection ID: %#x\n", hv.bq4);
+                break;
+            default:
+                debug("Skipped header %02x\n", hi);
+                break;
+        }
+    }
+}
+
+#ifdef DEBUG
+static void show_headers(obex_t *handle, obex_object_t *object) {
+    obex_headerdata_t hv;
+    uint8_t hi;
+    unsigned int hlen;
+
+    while (OBEX_ObjectGetNextHeader(handle, object, &hi, &hv, &hlen)) {
+        char *str;
+
+        switch (hi) {
+            case OBEX_HDR_WHO:
+                debug("OBEX_HDR_WHO\n");
+                break;
+            case OBEX_HDR_CONNECTION:
+                debug("OBEX_HDR_CONNECTION: %#x\n", hv.bq4);
+                break;
+            case OBEX_HDR_LENGTH:
+                debug("OBEX_HDR_LENGTH: %d\n", hv.bq4);
+                break;
+            case OBEX_HDR_NAME:
+                str = g_utf16_to_utf8((gunichar2 *)hv.bs, hlen, NULL, NULL, NULL);
+                if (str) {
+                    debug("OBEX_HDR_NAME: %s\n", str);
+                    g_free(str);
+                }
+                break;
+            case OBEX_HDR_AUTHCHAL:
+                str = bytestr(hv.bs, hlen);
+                debug("OBEX_HDR_AUTHCHAL: %s\n", str);
+                g_free(str);
+                break;
+            case OBEX_HDR_TIME:
+		str = g_new0(char, hlen + 1);
+		memcpy(str, hv.bs, hlen);
+                debug("OBEX_HDR_TIME: %s\n", str);
+                g_free(str);
+                break;
+            case OBEX_HDR_TYPE:
+                debug("OBEX_HDR_TYPE: %s\n", hv.bs);
+                break;
+            default:
+                debug("Skipped header 0x%02x\n", hi);
+                break;
+        }
+    }
+
+    OBEX_ObjectReParseHeaders(handle, object);
+}
+#else
+static inline void show_headers(obex_t *handle, obex_object_t *object) {}
+#endif
+
+static void obex_abort_done(GwObex *ctx, obex_object_t *object,
+                            int obex_cmd, int obex_rsp) {
+    ctx->done = TRUE;
+    if (ctx->xfer)
+        ctx->xfer->do_cb = TRUE;
+
+    if (obex_rsp != OBEX_RSP_SUCCESS)
+        debug("ABORT of %s command (0x%02x) failed: %s (0x%02x)\n",
+                optostr((uint8_t)obex_cmd), (uint8_t)obex_cmd,
+                OBEX_ResponseToString(obex_rsp), (uint8_t)obex_rsp);
+    else
+        debug("ABORT of %s command (0x%02x) succeeded.\n",
+                optostr((uint8_t)obex_cmd), (uint8_t)obex_cmd);
+}
+
+static void obex_request_done(GwObex *ctx, obex_object_t *object,
+                              int obex_cmd, int obex_rsp) {
+    ctx->done = TRUE;
+    if (ctx->xfer)
+        ctx->xfer->do_cb = TRUE;
+
+    ctx->obex_rsp = obex_rsp;
+
+    if (obex_rsp != OBEX_RSP_SUCCESS) {
+        debug("%s command (0x%02x) failed: %s (0x%02x)\n",
+                optostr((uint8_t)obex_cmd), (uint8_t)obex_cmd,
+                OBEX_ResponseToString(obex_rsp), (uint8_t)obex_rsp);
+#ifdef DEBUG
+        if (obex_rsp == OBEX_RSP_UNAUTHORIZED) {
+            debug("Showing headers..\n");
+            show_headers(ctx->handle, object);
+        }
+#endif
+        return;
+    }
+
+    debug("%s command (0x%02x) succeeded.\n", optostr((uint8_t)obex_cmd),
+            (uint8_t)obex_cmd);
+
+    switch (obex_cmd) {
+        case OBEX_CMD_CONNECT:
+            obex_connect_done(ctx, object, obex_rsp);
+            break;
+        default:
+            break;
+    }
+}
+
+static void get_target_size_and_time(obex_t *handle, obex_object_t *object,
+                                     gint *size, time_t *time) {
+    obex_headerdata_t hv;
+    uint8_t hi;
+    unsigned int hlen;
+
+    *size = GW_OBEX_UNKNOWN_LENGTH;
+    *time = -1;
+
+    while (OBEX_ObjectGetNextHeader(handle, object, &hi, &hv, &hlen)) {
+        switch (hi) {
+            case OBEX_HDR_LENGTH:
+                *size = hv.bq4; //(gint) g_ntohl(hv.bq4);
+                break;
+            case OBEX_HDR_TIME:
+                *time = parse_iso8601((char *)hv.bs, hlen);
+                break;
+            default:
+                break;
+        }
+    }
+
+    OBEX_ObjectReParseHeaders(handle, object);
+}
+
+static void obex_readstream(GwObex *ctx, obex_object_t *object) {
+    struct gw_obex_xfer *xfer = ctx->xfer;
+    const uint8_t *buf;
+    int actual;
+
+    if (!xfer) {
+        debug("Incomming data even though no xfer active!\n");
+        /* Flush incomming stream */
+        actual = OBEX_ObjectReadStream(ctx->handle, object, &buf);
+        if (actual > 0)
+            debug("Ignored %d bytes\n", actual);
+        return;
+    }
+
+    if (ctx->xfer->counter == 0) {
+        get_target_size_and_time(ctx->handle, object,
+                                 &xfer->target_size, &xfer->modtime);
+        show_headers(ctx->handle, object);
+    }
+
+    actual = OBEX_ObjectReadStream(ctx->handle, object, &buf);
+    if (actual > 0) {
+        xfer->counter += actual;
+
+        debug("obex_readstream: got %d bytes (%zd in total)\n", actual, xfer->counter);
+
+        if (xfer->async) {
+            gint free_space = xfer->buf_size - (xfer->data_start + xfer->data_length);
+            if (actual > free_space) {
+                /* This should never happen */
+                debug("Out of buffer space: actual=%d, free=%d\n", actual, free_space);
+                return;
+            }
+
+            memcpy(&xfer->buf[xfer->data_start], buf, actual);
+            xfer->data_length += actual;
+
+            debug("OBEX_SuspendRequest at %s:%d (%s)\n", __FILE__, __LINE__, __PRETTY_FUNCTION__);
+            OBEX_SuspendRequest(ctx->handle, object);
+
+            xfer->do_cb = TRUE;
+        }
+        else if (xfer->stream_fd >= 0) {
+            int written = 0;
+
+            while (written < actual) {
+                int ret;
+
+                ret = write(xfer->stream_fd, buf + written, actual - written);
+                if (ret < 0 && errno == EINTR)
+                    continue;
+
+                if (ret < 0) {
+                    debug("Could not write: %s (%d)", g_strerror(errno), errno);
+                    break;
+                }
+
+                written += ret;
+            }
+        }
+        else {
+            xfer->buf = g_realloc(xfer->buf, xfer->counter);
+            memcpy(&xfer->buf[xfer->buf_size], buf, actual);
+            xfer->buf_size = xfer->counter;
+        }
+    }
+    else
+        debug("Error or no data on OBEX stream\n");
+}
+
+static void obex_writestream(GwObex *ctx, obex_object_t *object) {
+    struct gw_obex_xfer *xfer = ctx->xfer;
+    obex_headerdata_t hv;
+    int actual = -1;
+
+    if (!xfer) {
+        debug("Request to provide data even though no active xfer!");
+        hv.bs = NULL;
+        OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_BODY,
+                hv, 0, OBEX_FL_STREAM_DATAEND);
+        return;
+    }
+
+    if (xfer->async) {
+        if (xfer->data_length > 0) {
+            gint send_size = xfer->data_length > ctx->tx_max ? ctx->tx_max : xfer->data_length;
+
+            hv.bs = &xfer->buf[xfer->data_start];
+            OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_BODY,
+                    hv, send_size, OBEX_FL_STREAM_DATA);
+            actual = send_size;
+            xfer->data_length -= send_size;
+            if (xfer->data_length == 0)
+                xfer->data_start = 0;
+            else
+                xfer->data_start += send_size;
+
+            xfer->do_cb = TRUE;
+	    if (!xfer->close) {
+                debug("OBEX_SuspendRequest at %s:%d (%s)\n", __FILE__, __LINE__, __PRETTY_FUNCTION__);
+                OBEX_SuspendRequest(ctx->handle, object);
+            }
+        }
+        else {
+            hv.bs = NULL;
+            OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_BODY,
+                    hv, 0, OBEX_FL_STREAM_DATAEND);
+        }
+    }
+    else if (xfer->stream_fd >= 0) {
+        actual = read(xfer->stream_fd, xfer->buf, ctx->tx_max);
+        hv.bs = xfer->buf;
+#ifdef TEST_ABORT
+        if (xfer->counter > 4000)
+            actual = -1;
+#endif
+        if (actual > 0)
+            OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_BODY,
+                    hv, actual, OBEX_FL_STREAM_DATA);
+        else if (actual == 0) /* EOF */
+            OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_BODY,
+                    hv, 0, OBEX_FL_STREAM_DATAEND);
+        else { /* error reading file */
+            debug("read(): %s\n", strerror(errno));
+            gw_obex_xfer_do_abort(xfer);
+        }
+    }
+    else {
+        if (xfer->counter < xfer->buf_size) {
+            if (xfer->buf_size > xfer->counter + ctx->tx_max)
+                actual = ctx->tx_max;
+            else
+                actual = xfer->buf_size - xfer->counter;
+            hv.bs = &xfer->buf[xfer->counter];
+            OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_BODY,
+                    hv, actual, OBEX_FL_STREAM_DATA);
+        }
+        else {
+            hv.bs = NULL;
+            OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_BODY,
+                    hv, 0, OBEX_FL_STREAM_DATAEND);
+        }
+    }
+
+    if (actual > 0)
+        xfer->counter += actual;
+}
+
+static void obex_event_handler(obex_t *handle, obex_object_t *object, int mode,
+                               int event, int obex_cmd, int obex_rsp) {
+    GwObex *ctx = OBEX_GetCustomData(handle);
+    switch (event) {
+        case OBEX_EV_ABORT:
+            debug("OBEX_EV_ABORT\n");
+            obex_abort_done(ctx, object, obex_cmd, obex_rsp);
+            break;
+        case OBEX_EV_PROGRESS:
+            debug("OBEX_EV_PROGRESS\n");
+            if (ctx->report_progress && ctx->pr_cb)
+                ctx->pr_cb(ctx, ctx->obex_op, ctx->xfer->counter, ctx->xfer->target_size, ctx->pr_data);
+            break;
+        case OBEX_EV_REQDONE:
+            debug("OBEX_EV_REQDONE\n");
+            obex_request_done(ctx, object, obex_cmd, obex_rsp);
+            break;
+        case OBEX_EV_REQ:
+            debug("OBEX_EV_REQ: %s (0x%02x)\n",
+                    optostr((uint8_t)obex_cmd), (uint8_t)obex_cmd);
+            OBEX_ObjectSetRsp(object, OBEX_RSP_NOT_IMPLEMENTED,
+                    OBEX_RSP_NOT_IMPLEMENTED);
+            break;
+        case OBEX_EV_REQHINT:
+            debug("OBEX_EV_REQHINT: %s (0x%02x)\n",
+                    optostr((uint8_t)obex_cmd), (uint8_t)obex_cmd);
+            OBEX_ObjectSetRsp(object, OBEX_RSP_NOT_IMPLEMENTED,
+                    OBEX_RSP_NOT_IMPLEMENTED);
+            break;
+        case OBEX_EV_LINKERR:
+            debug("OBEX_EV_LINKERR\n");
+            obex_link_error(ctx);
+            break;
+        case OBEX_EV_STREAMEMPTY:
+            debug("OBEX_EV_STREAMEMPTY\n");
+            obex_writestream(ctx, object);
+            break;
+        case OBEX_EV_STREAMAVAIL:
+            debug("OBEX_EV_STREAMAVAIL\n");
+            obex_readstream(ctx, object);
+            break;
+        case OBEX_EV_PARSEERR:
+            debug("OBEX_EV_PARSEERR\n");
+            break;
+        default:
+            debug("Unknown event %d\n", event);
+            break;
+    }
+}
+
+gboolean gw_obex_set_error(GwObex *ctx) {
+    ctx->error = 0;
+
+    if (!ctx->done)
+        return FALSE;
+
+    if (ctx->xfer && ctx->xfer->abort)
+        ctx->error = GW_OBEX_ERROR_ABORT;
+    else if (ctx->conn_fd < 0 || ctx->link_err)
+        ctx->error = GW_OBEX_ERROR_DISCONNECT;
+    else
+        ctx->error = (gint)ctx->obex_rsp;
+
+    if (ctx->error == OBEX_RSP_SUCCESS)
+        return FALSE;
+
+    return TRUE;
+}
+
+void obex_link_error(GwObex *ctx) {
+    if (ctx->link_err)
+        return;
+    ctx->link_err = TRUE;
+    OBEX_SetUserCallBack(ctx->handle, idle_callback, NULL);
+    ctx->done = TRUE;
+    ctx->conid = CONID_INVALID;
+    if (ctx->conn_fd >= 0) {
+        OBEX_TransportDisconnect(ctx->handle);
+        close(ctx->conn_fd);
+        ctx->conn_fd = -1;
+    }
+    if (ctx->gio) {
+        g_io_channel_unref(ctx->gio);
+        ctx->gio = NULL;
+    }
+    if (ctx->gio_source) {
+        g_source_destroy(ctx->gio_source);
+        ctx->gio_source = NULL;
+    }
+    if (ctx->xfer) {
+        /* Check that buffer is owned by us */
+        if (!(ctx->obex_op == OBEX_CMD_PUT && ctx->xfer->stream_fd < 0))
+            g_free(ctx->xfer->buf);
+        ctx->xfer->buf = NULL;
+        ctx->xfer->buf_size = 0;
+	ctx->xfer->do_cb = TRUE;
+    }
+}
+
+gboolean gw_obex_transport_setup(int fd, obex_t **handle) {
+    *handle = OBEX_Init(OBEX_TRANS_FD, obex_event_handler, 0);
+    if (*handle == NULL) {
+        debug("OBEX_Init() failed\n");
+        return FALSE;
+    }
+
+    (void) OBEX_SetTransportMTU(*handle, GW_OBEX_RX_MTU, GW_OBEX_TX_MTU);
+
+    if (FdOBEX_TransportSetup(*handle, fd, fd, 0) < 0) {
+        debug("FdOBEX_TransportSetup() failed\n");
+        OBEX_Cleanup(*handle);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+void gw_obex_get_error(GwObex *ctx, gint *error) {
+    if (error)
+        *error = ctx->error;
+    ctx->error = OBEX_RSP_SUCCESS;
+}
+
+gboolean gw_obex_cb(GIOChannel *chan, GIOCondition cond, gpointer data) {
+    GwObex *ctx = (GwObex *)data;
+
+    debug("gw_obex_cb(): entered\n");
+
+    GW_OBEX_LOCK(ctx);
+
+    if (ctx->conn_fd < 0 || (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))) {
+        debug("gw_obex_cb: error or connection closed\n");
+        obex_link_error(ctx);
+        GW_OBEX_UNLOCK(ctx);
+        if (ctx->xfer && ctx->xfer->cb)
+            ctx->xfer->cb(ctx->xfer, ctx->xfer->cb_data);
+        if (ctx->dc_cb)
+            ctx->dc_cb(ctx, ctx->dc_data);
+        return FALSE;
+    }
+
+    debug("Calling OBEX_HandleInput\n");
+    OBEX_HandleInput(ctx->handle, 0);
+    debug("Returned from OBEX_HandleInput\n");
+
+    if (ctx->xfer && ctx->xfer->cb && ctx->xfer->do_cb) {
+        ctx->xfer->do_cb = FALSE;
+        GW_OBEX_UNLOCK(ctx);
+        ctx->xfer->cb(ctx->xfer, ctx->xfer->cb_data);
+        GW_OBEX_LOCK(ctx);
+    }
+
+    GW_OBEX_UNLOCK(ctx);
+
+    return TRUE;
+}
+
+gboolean gw_obex_disconnect(GwObex *ctx) {
+    obex_object_t *object;
+
+    g_assert(!ctx->xfer);
+
+    if (!ctx->done) {
+        ctx->error = GW_OBEX_ERROR_BUSY;
+        return FALSE;
+    }
+
+    object = OBEX_ObjectNew(ctx->handle, OBEX_CMD_DISCONNECT);
+
+    if (ctx->conid != CONID_INVALID) {
+        obex_headerdata_t hv;
+        hv.bq4 = ctx->conid;
+        OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_CONNECTION, hv, 4, 0);
+    }
+
+    return gw_obex_request_sync(ctx, object);
+}
+
+gboolean gw_obex_connect(GwObex *ctx, const char *target, size_t target_len) {
+    gboolean ret;
+    obex_object_t *object;
+
+    g_assert(ctx->done && !ctx->xfer);
+
+    ctx->obex_op = OBEX_CMD_CONNECT;
+
+    object = OBEX_ObjectNew(ctx->handle, OBEX_CMD_CONNECT);
+    if (target) {
+        obex_headerdata_t hv;
+        hv.bs = (const unsigned char *)target;
+        OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_TARGET, hv, target_len, OBEX_FL_FIT_ONE_PACKET);
+    }
+
+    ret = gw_obex_request_sync(ctx, object);
+    ctx->obex_op = OBEX_CMD_NONE;
+    return ret;
+}
+
+GwObex *make_context(obex_t *handle) {
+    GwObex *context;
+
+    context = g_new0(GwObex, 1);
+
+    context->handle      = handle;
+    context->conn_fd     = OBEX_GetFD(handle);
+    context->conid       = CONID_INVALID;
+    context->tx_max      = GW_OBEX_TX_MTU - 200;
+    context->rx_max      = GW_OBEX_RX_MTU;
+    context->obex_op     = OBEX_CMD_NONE;
+    context->obex_rsp    = OBEX_RSP_SUCCESS;
+    context->done        = TRUE;
+
+    return context;
+}
+
+gboolean gw_obex_action_op(GwObex *ctx, const gchar *src, const gchar *dst,
+                           uint8_t action) {
+    gboolean ret = FALSE;
+    obex_object_t *object;
+    obex_headerdata_t hv;
+    gunichar2 *uname;
+    glong uname_len;
+
+    g_assert(src && dst);
+
+    if (!ctx->done || ctx->xfer) {
+        ctx->error = GW_OBEX_ERROR_BUSY;
+        return FALSE;
+    }
+
+    ctx->obex_op = OBEX_CMD_ACTION;
+
+    object = OBEX_ObjectNew(ctx->handle, OBEX_CMD_ACTION);
+
+    if (ctx->conid != CONID_INVALID) {
+        hv.bq4 = ctx->conid;
+        OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_CONNECTION, hv, 4, 0);
+    }
+
+    hv.bq1 = action;
+    OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_ACTION_ID, hv, 1, 0);
+
+    uname_len = get_uname(&uname, src);
+    if (uname_len < 0) {
+        OBEX_ObjectDelete(ctx->handle, object);
+        goto out;
+    }
+    hv.bs = (unsigned char *)uname;
+    OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_NAME, hv, uname_len, 0);
+    g_free(uname);
+
+    uname_len = get_uname(&uname, dst);
+    if (uname_len < 0) {
+        OBEX_ObjectDelete(ctx->handle, object);
+        goto out;
+    }
+    hv.bs = (unsigned char *)uname;
+    OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_DESTNAME, hv, uname_len, 0);
+    g_free(uname);
+
+    ret = gw_obex_request_sync(ctx, object);
+
+out:
+    ctx->obex_op = OBEX_CMD_NONE;
+    return ret;
+}
+
+gboolean gw_obex_setpath(GwObex *ctx, const gchar *path, int flags) {
+    gboolean ret = FALSE;
+    obex_headerdata_t hv;
+    obex_object_t *object;
+    obex_setpath_hdr_t nonhdrdata;
+    gunichar2 *uname;
+    glong uname_len;
+
+    if (!ctx->done || ctx->xfer) {
+        ctx->error = GW_OBEX_ERROR_BUSY;
+        return FALSE;
+    }
+
+    ctx->obex_op = OBEX_CMD_SETPATH;
+
+    nonhdrdata.flags = 0x02;
+    nonhdrdata.constants = 0;
+
+    if (strcmp(path, "..") == 0) {
+        /* move up one directory */
+        nonhdrdata.flags = 0x03;
+        uname_len = -1;
+    }
+    else {
+        /* normal directory change */
+        uname_len = get_uname(&uname, path);
+        if (uname_len < 0) {
+            ctx->error = GW_OBEX_ERROR_INVALID_PARAMS;
+            goto out;
+        }
+    }
+
+    if (flags & SETPATH_CREATE)
+        nonhdrdata.flags &= ~0x02;
+
+    object = OBEX_ObjectNew(ctx->handle, OBEX_CMD_SETPATH);
+    OBEX_ObjectSetNonHdrData(object, (uint8_t*)&nonhdrdata, 2);
+
+    if (ctx->conid != CONID_INVALID) {
+        hv.bq4 = ctx->conid;
+        OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_CONNECTION, hv, 4, 0);
+    }
+
+    if (uname_len >= 0) {
+        hv.bs = (unsigned char *) (uname ? (char *)uname : "");
+        OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_NAME, hv, uname_len, 0);
+        g_free(uname);
+    }
+
+    ret = gw_obex_request_sync(ctx, object);
+
+out:
+    ctx->obex_op = OBEX_CMD_NONE;
+    return ret;
+}
+
+gboolean gw_obex_get(GwObex *ctx,
+                     const gchar *local, const gchar *remote, const gchar *type,
+                     gchar **buf, gint *buf_size, int stream_fd,
+                     gboolean async) {
+    gboolean ret = FALSE;
+    obex_headerdata_t hv;
+    obex_object_t *object;
+
+    g_assert(local || buf || stream_fd > 0 || async);
+    g_assert(remote || type);
+
+    if (!ctx->done || ctx->xfer) {
+        ctx->error = GW_OBEX_ERROR_BUSY;
+        return ret;
+    }
+
+    ctx->obex_op = OBEX_CMD_GET;
+
+    ctx->xfer = gw_obex_xfer_new(ctx, async, stream_fd);
+
+    object = OBEX_ObjectNew(ctx->handle, OBEX_CMD_GET);
+
+    if (ctx->conid != CONID_INVALID) {
+        hv.bq4 = ctx->conid;
+        OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_CONNECTION, hv, 4, 0);
+    }
+
+    if (type) {
+        hv.bs = (unsigned char *)type;
+        OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_TYPE, hv, strlen(type) + 1, 0);
+    }
+
+    if (remote) {
+        gunichar2 *uname;
+        glong uname_len;
+
+        uname_len = get_uname(&uname, remote);
+        if (uname_len < 0) {
+            OBEX_ObjectDelete(ctx->handle, object);
+            ctx->error = GW_OBEX_ERROR_INVALID_PARAMS;
+            goto out;
+        }
+
+        /* OpenOBEX is buggy and won't append the header unless hv.bs != NULL */
+        hv.bs = (unsigned char *) (uname ? (char *)uname : "");
+
+        OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_NAME, hv, uname_len, 0);
+        g_free(uname);
+    }
+
+    if (local) {
+        ctx->xfer->stream_fd = open(local, O_WRONLY | O_CREAT, 0600);
+        if (ctx->xfer->stream_fd < 0) {
+            if (errno == ENOENT || errno == ENODEV)
+                ctx->error = GW_OBEX_ERROR_INVALID_PARAMS;
+            else
+                ctx->error = GW_OBEX_ERROR_LOCAL_ACCESS;
+            debug("open(%s): %s", local, strerror(errno));
+            OBEX_ObjectDelete(ctx->handle, object);
+            goto out;
+        }
+    }
+
+    OBEX_ObjectReadStream(ctx->handle, object, NULL);
+
+    if (async) {
+        ret = gw_obex_request_async(ctx, object);
+        if (ret)
+            return ret;
+    }
+    else {
+        ctx->report_progress = TRUE;
+        ret = gw_obex_request_sync(ctx, object);
+    }
+
+    if (ctx->xfer->stream_fd >= 0 && stream_fd < 0)
+        close(ctx->xfer->stream_fd);
+
+    if (ret == FALSE) {
+        if (local)
+            unlink(local);
+    }
+    else {
+        if (local) {
+            debug("%s stored in %s\n", remote ? remote : type, local);
+            if (ctx->xfer->modtime != -1) {
+                struct utimbuf ubuf;
+                ubuf.actime = time(NULL);
+                ubuf.modtime = ctx->xfer->modtime;
+                if (utime(local, &ubuf) < 0)
+                    debug("utime(%s): %s\n", local, g_strerror(errno));
+            }
+        }
+        if (buf) {
+            *buf = (gchar *)ctx->xfer->buf;
+            *buf_size = ctx->xfer->buf_size;
+            /* Make sure gw_obex_xfer_free doesn't free the buffer */
+            ctx->xfer->buf = NULL;
+        }
+    }
+
+out:
+    _gw_obex_xfer_free(ctx->xfer);
+    ctx->xfer = NULL;
+
+    ctx->report_progress = FALSE;
+    ctx->obex_op = OBEX_CMD_NONE;
+
+    return ret;
+}
+
+gboolean gw_obex_put(GwObex *ctx,
+                     const gchar *local, const gchar *remote, const gchar *type,
+                     const gchar *buf, gint object_size, time_t object_time,
+                     int stream_fd, gboolean async) {
+    gboolean ret = FALSE;
+    obex_headerdata_t hv;
+    obex_object_t *object;
+    gunichar2 *uname = NULL;
+    glong uname_len = 0;
+
+    g_assert(remote || type);
+
+    if (!ctx->done || ctx->xfer) {
+        ctx->error = GW_OBEX_ERROR_BUSY;
+        return FALSE;
+    }
+
+    ctx->obex_op = OBEX_CMD_PUT;
+
+    if (remote) {
+        uname_len = get_uname(&uname, remote);
+        if (uname_len < 0) {
+            ctx->error = GW_OBEX_ERROR_INVALID_PARAMS;
+            goto out;
+        }
+    }
+
+    ctx->xfer = gw_obex_xfer_new(ctx, async, stream_fd);
+
+    if (local) {
+        if (file_is_dir(local)) {
+            debug("Trying to PUT a directory\n");
+            ctx->error = GW_OBEX_ERROR_INVALID_PARAMS;
+            goto out;
+        }
+
+        ctx->xfer->stream_fd = open(local, O_RDONLY);
+        if (ctx->xfer->stream_fd < 0) {
+            if (errno == ENOENT || errno == ENODEV)
+                ctx->error = GW_OBEX_ERROR_INVALID_PARAMS;
+            else
+                ctx->error = GW_OBEX_ERROR_LOCAL_ACCESS;
+            debug("open(%s): %s", local, strerror(errno));
+            goto out;
+        }
+        ctx->xfer->buf = g_malloc(ctx->tx_max);
+        ctx->xfer->buf_size = ctx->tx_max;
+        debug("Sending %s to %s\n", local, remote ? remote : type);
+    }
+    else if (buf) {
+        ctx->xfer->buf = (unsigned char *)buf;
+        ctx->xfer->buf_size = object_size;
+        debug("Sending to %s\n", remote ? remote : type);
+    }
+    else if (stream_fd < 0 && !async) { /* Delete */
+        ctx->report_progress = FALSE;
+        debug("Deleting %s\n", remote ? remote : type);
+    }
+
+    object = OBEX_ObjectNew(ctx->handle, OBEX_CMD_PUT);
+
+    if (ctx->conid != CONID_INVALID) {
+        hv.bq4 = ctx->conid;
+        OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_CONNECTION, hv, 4, 0);
+    }
+
+    if (uname) {
+        hv.bs = (unsigned char *)uname;
+        OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_NAME, hv, uname_len, 0);
+        g_free(uname);
+        uname = NULL;
+    }
+
+    if (type) {
+        hv.bs = (unsigned char *)type;
+        OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_TYPE, hv, strlen(type) + 1, 0);
+    }
+
+    /* Try to figure out modification time if none was given */
+    if (ctx->xfer->stream_fd >= 0) {
+        struct stat stats;
+        if (fstat(ctx->xfer->stream_fd, &stats) == 0) {
+            object_size = stats.st_size;
+            if (object_time < 0)
+                object_time = stats.st_mtime;
+        }
+    }
+
+    /* Add a time header if possible */
+    if (object_time >= 0) {
+        char tstr[17];
+        int len;
+
+        len = make_iso8601(object_time, tstr, sizeof(tstr));
+
+        if (len >= 0) {
+            debug("Adding time header: %s\n", tstr);
+            hv.bs = (unsigned char *)tstr;
+            OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_TIME, hv, len, 0);
+        }
+    }
+
+    /* Add a length header if possible */
+    if (object_size > 0) {
+        ctx->xfer->target_size = object_size;
+        debug("Adding size header: %d\n", object_size);
+        hv.bq4 = (uint32_t)object_size;
+        OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_LENGTH, hv, 4, 0);
+    }
+    else
+        ctx->xfer->target_size = GW_OBEX_UNKNOWN_LENGTH;
+
+    if (ctx->xfer->stream_fd >= 0 || buf || async) {
+        hv.bs = NULL;
+        OBEX_ObjectAddHeader(ctx->handle, object, OBEX_HDR_BODY, hv, 0, OBEX_FL_STREAM_START);
+    }
+
+    if (async) {
+        ret = gw_obex_request_async(ctx, object);
+        if (ret)
+            return ret;
+    }
+    else {
+        ctx->report_progress = TRUE;
+        ret = gw_obex_request_sync(ctx, object);
+    }
+
+out:
+    g_free(uname);
+
+    if (ctx->xfer->stream_fd >= 0 && stream_fd < 0)
+        close(ctx->xfer->stream_fd);
+
+    if (buf)
+        ctx->xfer->buf = NULL;
+
+    _gw_obex_xfer_free(ctx->xfer);
+    ctx->xfer = NULL;
+
+    ctx->report_progress = FALSE;
+    ctx->obex_op = OBEX_CMD_NONE;
+
+    return ret;
+}
+
diff --git a/obexd/gwobex/obex-priv.h b/obexd/gwobex/obex-priv.h
new file mode 100644
index 0000000..051c67a
--- /dev/null
+++ b/obexd/gwobex/obex-priv.h
@@ -0,0 +1,215 @@
+/**
+  @file obex-priv.h
+
+  @author Johan Hedberg <johan.hedberg@nokia.com>
+
+  Copyright (C) 2004-2006 Nokia Corporation. All rights reserved.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License, version 2.1, as published by the Free Software Foundation.
+
+  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.
+
+*/
+#ifndef _OBEX_PRIV_H_
+#define _OBEX_PRIV_H_
+
+#include <stdio.h>
+#include <stdint.h>
+#include <time.h>
+#include <glib.h>
+
+#include <openobex/obex.h>
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "gw-obex.h"
+#include "obex-xfer.h"
+
+#define CHECK_DISCONNECT(ret,err,ctx) do { \
+                                          if ((ctx)->conn_fd < 0) { \
+                                              if (err) \
+                                                  *(err) = GW_OBEX_ERROR_DISCONNECT; \
+                                              GW_OBEX_UNLOCK(ctx); \
+                                              return (ret); \
+                                          } \
+                                      } while (0)
+
+#ifndef OBEX_CMD_ACTION
+# define OBEX_CMD_ACTION      0x06
+
+# define OBEX_HDR_ACTION_ID   0x94
+# define OBEX_HDR_DESTNAME    0x15
+# define OBEX_HDR_PERMISSIONS 0xD6
+
+# define OBEX_ACTION_COPY     0x00
+# define OBEX_ACTION_MOVE     0x01
+# define OBEX_ACTION_SETPERM  0x02
+
+#endif /* OBEX_CMD_ACTION */
+
+#define CONID_INVALID 0xFFFFFFFF
+
+#define OBEX_CMD_NONE 0x10
+
+#define GW_OBEX_RX_MTU 4096
+#define GW_OBEX_TX_MTU 32767
+
+#define SETPATH_CREATE  0x0001
+
+#define CAP_TYPE "x-obex/capability"
+#define OBP_TYPE "x-obex/object-profile"
+#define LST_TYPE "x-obex/folder-listing"
+
+#ifdef G_THREADS_ENABLED
+# ifdef DEBUG
+#  define GW_OBEX_LOCK(ctx) do { \
+      debug("Attempting GW_OBEX_LOCK at %s:%d (%s)...", __FILE__, __LINE__, __PRETTY_FUNCTION__); \
+      fflush(stdout); \
+      g_mutex_lock((ctx)->mutex); \
+      debug("got it!\n"); \
+   } while (0)
+#  define GW_OBEX_UNLOCK(ctx) do { \
+      debug("Unlocking GW_OBEX_LOCK at %s:%d (%s)\n", __FILE__, __LINE__, __PRETTY_FUNCTION__); \
+      g_mutex_unlock((ctx)->mutex); \
+   } while (0)
+# else
+#  define GW_OBEX_LOCK(ctx)   g_mutex_lock((ctx)->mutex)
+#  define GW_OBEX_UNLOCK(ctx) g_mutex_unlock((ctx)->mutex)
+# endif
+#else
+# define GW_OBEX_LOCK(ctx)   ((void)(0))
+# define GW_OBEX_UNLOCK(ctx) ((void)(0))
+#endif
+
+typedef struct obex_setpath_hdr {
+    uint8_t  flags;
+    uint8_t constants;
+} __attribute__ ((packed)) obex_setpath_hdr_t;
+
+typedef struct obex_connect_hdr {
+    uint8_t  version;
+    uint8_t  flags;
+    uint16_t mtu;
+} __attribute__ ((packed)) obex_connect_hdr_t;
+
+struct gw_obex {
+#ifdef G_THREADS_ENABLED
+    /* To get rid of race conditions in multithreaded apps */
+    GMutex                  *mutex;
+#endif
+
+    /* Main OpenOBEX handle */
+    obex_t                  *handle;
+
+    /* Exception callback and associated data */
+    gw_obex_disconnect_cb_t  dc_cb;
+    gpointer                 dc_data;
+
+    /* Progress callback and associated data */
+    gw_obex_progress_cb_t    pr_cb;
+    gpointer                 pr_data;
+
+    /* Whether calling pr_cb is necessary or not */
+    gboolean                 report_progress;
+
+    /* Cancel callback and associated data */
+    gw_obex_cancel_cb_t      cancel_cb;
+    gpointer                 cancel_data;
+
+    /* For checking if the current operation is finished */
+    gboolean                 done;
+
+    /* TRUE if a link error has hapened */
+    gboolean                 link_err;
+
+    /* FD for the transport connection */
+    int                      conn_fd;
+
+    GMainContext            *main_ctx;
+
+    /* The transport connection's GIOChannel */
+    GIOChannel               *gio;
+
+    /* The transport connection's GSource */
+    GSource                  *gio_source;
+
+    /* OBEX Connection ID */
+    uint32_t                 conid;
+
+    /* The last OBEX response code */
+    uint8_t                  obex_rsp;
+
+    /* The current OBEX operation */
+    uint8_t                  obex_op;
+
+    /* This is set if some operation fails */
+    gint                     error;
+
+    /* Bytes to read at a time when doing a put */
+    uint16_t                 tx_max;
+
+    /* How many bytes to allocate for incomming object data */
+    uint16_t                 rx_max;
+
+    /* Current object transfer handle */
+    struct gw_obex_xfer     *xfer;
+};
+
+GwObex *make_context(obex_t *handle);
+
+gboolean gw_obex_set_error(GwObex *ctx);
+
+void gw_obex_get_error(GwObex *ctx, gint *error);
+
+void obex_link_error(GwObex *ctx);
+
+gboolean gw_obex_cb(GIOChannel *chan, GIOCondition cond, gpointer data);
+
+gboolean gw_obex_connect(GwObex *ctx, const char *target, size_t target_len);
+
+gboolean gw_obex_disconnect(GwObex *ctx);
+
+gboolean gw_obex_transport_setup(int fd, obex_t **handle);
+
+gboolean gw_obex_action_op(GwObex *ctx, const gchar *src, const gchar *dst,
+                           uint8_t action);
+
+gboolean gw_obex_setpath(GwObex *ctx, const gchar *path, int flags);
+
+/** Get an object from the server
+ * @param ctx Pointer returned by gw_obex_setup()
+ * @param local Local filename which contains the object
+ * @param remote Remote filename to store the object in
+ * @param type MIME-type of the object (NULL if not known)
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_get(GwObex *ctx,
+                     const gchar *local, const gchar *remote, const gchar *type,
+                     gchar **buf, gint *buf_size, int stream_fd,
+                     gboolean async);
+
+/** Send an object to the server
+ * @param ctx Pointer returned by gw_obex_setup()
+ * @param local Local filename to store the objec in
+ * @param remote Remote filename which contains the object
+ * @param type MIME-type of the object (NULL if not known)
+ * @returns TRUE on success, FALSE on failure
+ */
+gboolean gw_obex_put(GwObex *ctx,
+                     const gchar *local, const gchar *remote, const gchar *type,
+                     const gchar *buf, gint buf_size, time_t object_time,
+                     int stream_fd, gboolean async);
+
+#endif /* _OBEX_PRIV_H_ */
diff --git a/obexd/gwobex/obex-xfer.c b/obexd/gwobex/obex-xfer.c
new file mode 100644
index 0000000..1cce005
--- /dev/null
+++ b/obexd/gwobex/obex-xfer.c
@@ -0,0 +1,507 @@
+/**
+  @file obex-xfer.c
+
+  Object transfer related functions for the GW OBEX Library
+
+  @author Johan Hedberg <johan.hedberg@nokia.com>
+
+  Copyright (C) 2004-2006 Nokia Corporation. All rights reserved.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License, version 2.1 as published by the Free Software Foundation.
+
+  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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openobex/obex.h>
+
+#include "obex-priv.h"
+#include "obex-xfer.h"
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "log.h"
+#include "obex-priv.h"
+#include "obex-xfer.h"
+#include "gw-obex.h"
+
+static gboolean handle_input(GwObex *ctx, gint *err) {
+    gboolean ret = TRUE;
+    int r;
+
+    r = OBEX_HandleInput(ctx->handle, 10);
+
+    if (r < 0) {
+        debug("OBEX_HandleInput() failed\n");
+        obex_link_error(ctx);
+        if (err)
+            *err = GW_OBEX_ERROR_INTERNAL;
+        ret = FALSE;
+    }
+    else if (r == 0) { /* Timeout */
+        debug("OBEX_HandleInput(): timeout\n");
+        if (err)
+            *err = GW_OBEX_ERROR_TIMEOUT;
+        ret = FALSE;
+    }
+
+    return ret;
+}
+
+struct gw_obex_xfer *gw_obex_xfer_new(struct gw_obex *ctx, gboolean async, int stream_fd) {
+    struct gw_obex_xfer *xfer;
+    size_t buf_size = (ctx->obex_op == OBEX_CMD_GET) ? ctx->rx_max : ctx->tx_max;
+
+    xfer = g_new0(struct gw_obex_xfer, 1);
+
+    xfer->ctx         = ctx;
+    xfer->async       = async;
+    xfer->stream_fd   = stream_fd;
+    xfer->target_size = GW_OBEX_UNKNOWN_LENGTH;
+    xfer->modtime     = -1;
+
+    if (async || (stream_fd >= 0 && ctx->obex_op == OBEX_CMD_PUT)) {
+        xfer->buf = g_malloc(buf_size);
+        xfer->buf_size = buf_size;
+    }
+
+    if (async && ctx->obex_op == OBEX_CMD_PUT)
+        xfer->do_cb = TRUE;
+
+    return xfer;
+}
+
+gboolean gw_obex_xfer_do_abort(struct gw_obex_xfer *xfer) {
+    debug("gw_obex_xfer_do_abort()\n");
+
+    if (xfer->ctx->conn_fd < 0 || xfer->ctx->xfer == NULL || xfer->ctx->done)
+        return FALSE;
+
+    if (xfer->abort)
+        return TRUE;
+
+    xfer->abort = TRUE;
+
+#ifdef USE_NICE_ABORT
+    debug("Performing nice abort\n");
+    if (OBEX_CancelRequest(xfer->ctx->handle, TRUE) != 0)
+        return FALSE;
+    return TRUE;
+#else
+    debug("Performing abort through disconnection (without ABORT command)\n");
+    xfer->ctx->done = TRUE;
+    OBEX_CancelRequest(xfer->ctx->handle, FALSE);
+    obex_link_error(xfer->ctx);
+    return FALSE;
+#endif
+}
+
+GwObexXfer *gw_obex_put_async(GwObex *ctx, const char *name, const char *type,
+                              gint size, time_t time, gint *error) {
+    gboolean ret;
+    GW_OBEX_LOCK(ctx);
+    CHECK_DISCONNECT(NULL, error, ctx);
+    ret = gw_obex_put(ctx, NULL, name, type, NULL, size, time, -1, TRUE);
+    if (ret == FALSE)
+        gw_obex_get_error(ctx, error);
+    GW_OBEX_UNLOCK(ctx);
+    return ret ? ctx->xfer : NULL;
+}
+
+GwObexXfer *gw_obex_get_async(GwObex *ctx, const char *name, const char *type, gint *error) {
+    gboolean ret;
+    GW_OBEX_LOCK(ctx);
+    CHECK_DISCONNECT(NULL, error, ctx);
+    ret = gw_obex_get(ctx, NULL, name, type, NULL, NULL, -1, TRUE);
+    if (ret == FALSE)
+        gw_obex_get_error(ctx, error);
+    GW_OBEX_UNLOCK(ctx);
+    return ret ? ctx->xfer : NULL;
+}
+
+static gboolean gw_obex_put_idle(GwObexXfer *xfer) {
+    struct gw_obex *ctx = xfer->ctx;
+
+    g_source_destroy(xfer->idle_source);
+    xfer->idle_source = NULL;
+
+    if (!ctx)
+        return FALSE;
+
+    GW_OBEX_LOCK(ctx);
+
+    if (xfer->cb && xfer->do_cb) {
+        xfer->do_cb = FALSE;
+        GW_OBEX_UNLOCK(ctx);
+        xfer->cb(xfer, xfer->cb_data);
+        GW_OBEX_LOCK(ctx);
+    }
+
+    GW_OBEX_UNLOCK(ctx);
+
+    return FALSE;
+}
+
+void gw_obex_xfer_set_callback(GwObexXfer *xfer, gw_obex_xfer_cb_t cb, gpointer user_data) {
+    GwObex *ctx = xfer->ctx;
+
+    GW_OBEX_LOCK(ctx);
+
+    xfer->cb = cb;
+    xfer->cb_data = user_data;
+
+    if (xfer->do_cb && xfer->idle_source == NULL) {
+        xfer->idle_source = g_idle_source_new();
+        g_source_set_callback(xfer->idle_source, (GSourceFunc)gw_obex_put_idle, xfer, NULL);
+        (void) g_source_attach(xfer->idle_source, ctx->main_ctx);
+        g_source_unref(xfer->idle_source);
+    }
+
+    GW_OBEX_UNLOCK(ctx);
+}
+
+time_t gw_obex_xfer_object_time(GwObexXfer *xfer) {
+    return xfer->modtime;
+}
+
+gint gw_obex_xfer_object_size(GwObexXfer *xfer) {
+    return xfer->target_size;
+}
+
+gboolean gw_obex_xfer_write(GwObexXfer *xfer, const char *buf, gint buf_size,
+		            gint *bytes_written, gint *err) {
+    GwObex *ctx = xfer->ctx;
+    gboolean ret = TRUE;
+    gint free_space;
+
+    debug("gw_obex_xfer_write(buf_size=%d): entered\n", buf_size);
+
+    if (!ctx) {
+        if (err)
+            *err = GW_OBEX_ERROR_INVALID_PARAMS;
+	return FALSE;
+    }
+
+    GW_OBEX_LOCK(ctx);
+
+    if (ctx->obex_op != OBEX_CMD_PUT) {
+        ret = FALSE;
+        if (err)
+            *err = GW_OBEX_ERROR_INVALID_PARAMS;
+        goto out;
+    }
+
+    if (gw_obex_set_error(ctx)) {
+        gw_obex_get_error(ctx, err);
+        ret = FALSE;
+        goto out;
+    }
+
+    free_space = xfer->buf_size - (xfer->data_start + xfer->data_length);
+
+    *bytes_written = buf_size > free_space ? free_space : buf_size;
+
+    memcpy(&xfer->buf[xfer->data_start + xfer->data_length], buf, *bytes_written);
+
+    xfer->data_length += *bytes_written;
+    free_space -= *bytes_written;
+
+    if (xfer->object) {
+        if (OBEX_Request(ctx->handle, xfer->object) < 0) {
+            debug("OBEX_Request() failed\n");
+            xfer->data_length -= *bytes_written;
+            ret = FALSE;
+            goto out;
+        }
+
+        xfer->object = NULL;
+
+        /* Recalculate free space */
+        free_space = xfer->buf_size - (xfer->data_start + xfer->data_length);
+    }
+
+    if (xfer->data_length >= ctx->tx_max || !free_space) {
+        gint old_length = xfer->data_length;
+
+        debug("OBEX_ResumeRequest at %s:%d (%s)\n", __FILE__, __LINE__, __PRETTY_FUNCTION__);
+        OBEX_ResumeRequest(ctx->handle);
+
+        if (!xfer->block)
+            goto out;
+
+        /* Call OBEX_HandleInput if the xfer is blocking and no data could be sent */
+        while (old_length == xfer->data_length) {
+            if (gw_obex_set_error(ctx)) {
+                gw_obex_get_error(ctx, err);
+                ret = FALSE;
+                goto out;
+            }
+
+            if (!handle_input(ctx, err)) {
+                ret = FALSE;
+                goto out;
+            }
+        }
+    }
+
+out:
+    if (xfer->cb && xfer->do_cb && xfer->idle_source == NULL) {
+        xfer->idle_source = g_idle_source_new();
+        g_source_set_callback(xfer->idle_source, (GSourceFunc)gw_obex_put_idle, xfer, NULL);
+        (void) g_source_attach(xfer->idle_source, ctx->main_ctx);
+        g_source_unref(xfer->idle_source);
+    }
+
+    GW_OBEX_UNLOCK(ctx);
+    if (ret)
+        debug("gw_obex_xfer_write(): returning, %d bytes written\n", *bytes_written);
+    else
+        debug("gw_obex_xfer_write(): returning, failed (%d)\n", err ? *err : 0);
+    return ret;
+}
+
+gboolean gw_obex_xfer_read(GwObexXfer *xfer, char *buf, gint buf_size,
+		           gint *bytes_read, gint *err) {
+    GwObex *ctx = xfer->ctx;
+    gboolean ret = TRUE;
+
+    debug("gw_obex_xfer_read(buf_size=%d): entered\n", buf_size);
+
+    if (!ctx) {
+        if (err)
+            *err = GW_OBEX_ERROR_INVALID_PARAMS;
+	return FALSE;
+    }
+
+    GW_OBEX_LOCK(ctx);
+
+    if (ctx->obex_op != OBEX_CMD_GET) {
+        ret = FALSE;
+        if (err)
+            *err = GW_OBEX_ERROR_INVALID_PARAMS;
+        goto out;
+    }
+
+    while (TRUE) {
+        if (gw_obex_set_error(ctx)) {
+            gw_obex_get_error(ctx, err);
+            ret = FALSE;
+            goto out;
+        }
+
+        if (xfer->data_length)
+            break;
+
+        if (ctx->done) {
+            *bytes_read = 0;
+            goto out;
+        }
+
+        if (xfer->block) {
+            if (!handle_input(ctx, err)) {
+                ret = FALSE;
+                goto out;
+            }
+        }
+        else {
+            ret = FALSE;
+            if (err)
+                *err = GW_OBEX_ERROR_NO_DATA;
+            goto out;
+        }
+    }
+
+    *bytes_read = buf_size < xfer->data_length ? buf_size : xfer->data_length;
+
+    memcpy(buf, &xfer->buf[xfer->data_start], *bytes_read);
+
+    xfer->data_length -= *bytes_read;
+
+    if (xfer->data_length)
+        xfer->data_start += *bytes_read;
+    else {
+        xfer->data_start = 0;
+        debug("OBEX_ResumeRequest at %s:%d (%s)\n", __FILE__, __LINE__, __PRETTY_FUNCTION__);
+        OBEX_ResumeRequest(ctx->handle);
+    }
+
+out:
+    GW_OBEX_UNLOCK(ctx);
+    if (ret)
+        debug("gw_obex_xfer_read(): returning, %d bytes read\n", *bytes_read);
+    else
+        debug("gw_obex_xfer_read(): returning, failed (%d)\n", err ? *err : 0);
+    return ret;
+}
+
+gboolean gw_obex_xfer_flush(GwObexXfer *xfer, gint *err) {
+    gboolean ret = TRUE;
+    struct gw_obex *ctx = xfer->ctx;
+
+    if (!ctx) {
+        if (err)
+            *err = GW_OBEX_ERROR_INVALID_PARAMS;
+	return FALSE;
+    }
+
+    GW_OBEX_LOCK(ctx);
+
+    if (ctx->obex_op != OBEX_CMD_PUT)
+        goto out;
+
+    if (gw_obex_set_error(ctx)) {
+        gw_obex_get_error(ctx, err);
+        ret = FALSE;
+        goto out;
+    }
+
+    while (xfer->data_length) {
+        debug("OBEX_ResumeRequest at %s:%d (%s)\n", __FILE__, __LINE__, __PRETTY_FUNCTION__);
+        OBEX_ResumeRequest(ctx->handle);
+
+        if (gw_obex_set_error(ctx)) {
+            gw_obex_get_error(ctx, err);
+            ret = FALSE;
+            goto out;
+        }
+
+        if (xfer->data_length) {
+            if (!handle_input(ctx, err)) {
+                ret = FALSE;
+                goto out;
+            }
+        }
+    }
+
+out:
+    GW_OBEX_UNLOCK(ctx);
+    return ret;
+}
+
+void _gw_obex_xfer_free(struct gw_obex_xfer *xfer) {
+    g_free(xfer->buf);
+    g_free(xfer);
+}
+
+void gw_obex_xfer_free(struct gw_obex_xfer *xfer) {
+    if (xfer->ctx)
+        gw_obex_xfer_close(xfer, NULL);
+    _gw_obex_xfer_free(xfer);
+}
+
+gboolean gw_obex_xfer_close(GwObexXfer *xfer, gint *err) {
+    gboolean ret = TRUE;
+    struct gw_obex *ctx = xfer->ctx;
+
+    /* If previous close() failed, just signal success so caller can continue */
+    if (!ctx)
+        return TRUE;
+
+    GW_OBEX_LOCK(ctx);
+
+    xfer->close = TRUE;
+
+    if (ctx->obex_op == OBEX_CMD_GET && !ctx->done)
+        gw_obex_xfer_do_abort(xfer);
+
+    if (ctx->obex_op == OBEX_CMD_PUT) {
+        if (xfer->object) {
+            if (OBEX_Request(ctx->handle, xfer->object) < 0) {
+                debug("OBEX_Request() failed\n");
+                ctx->done = TRUE;
+            }
+            xfer->object = NULL;
+        }
+        else {
+            debug("OBEX_ResumeRequest at %s:%d (%s)\n", __FILE__, __LINE__, __PRETTY_FUNCTION__);
+            OBEX_ResumeRequest(ctx->handle);
+        }
+    }
+
+    while (!ctx->done) {
+        if (!handle_input(ctx, err)) {
+            ret = FALSE;
+            break;
+        }
+    }
+
+    /* Check for error but ignore ERROR_ABORT since we can still do a proper
+     * xfer_close() in that case */
+    if (gw_obex_set_error(ctx) && ctx->error != GW_OBEX_ERROR_ABORT) {
+        gw_obex_get_error(ctx, err);
+        ret = FALSE;
+    }
+
+    /* Remove the idle function related to this transfer (if there is one) */
+    if (xfer->idle_source) {
+        g_source_destroy(xfer->idle_source);
+        xfer->idle_source = NULL;
+    }
+
+    /* Disassociate from the GwObex object */
+    ctx->xfer = NULL;
+    xfer->ctx = NULL;
+
+    GW_OBEX_UNLOCK(ctx);
+
+    return ret;
+}
+
+gboolean gw_obex_xfer_abort(GwObexXfer *xfer, gint *err) {
+    GwObex *ctx = xfer->ctx;
+    gboolean ret = TRUE;
+
+    /* If previous call failed just signal success so caller can continue */
+    if (!ctx)
+        return TRUE;
+
+    GW_OBEX_LOCK(ctx);
+
+    /* Return if abort has already been sent */
+    if (xfer->abort)
+        goto out;
+
+    /* Return if actual request hasn't been sent */
+    if (xfer->object) {
+        OBEX_ObjectDelete(ctx->handle, xfer->object);
+        xfer->object = NULL;
+        ctx->done = TRUE;
+        goto out;
+    }
+
+    if (!gw_obex_xfer_do_abort(xfer)) {
+        ret = FALSE;
+        if (err)
+            *err = GW_OBEX_ERROR_INTERNAL;
+        goto out;
+    }
+
+out:
+    GW_OBEX_UNLOCK(ctx);
+
+    gw_obex_xfer_close(xfer, err);
+
+    return ret;
+}
+
+void gw_obex_xfer_set_blocking(GwObexXfer *xfer, gboolean block) {
+    GW_OBEX_LOCK(xfer->ctx);
+    xfer->block = block;
+    GW_OBEX_UNLOCK(xfer->ctx);
+}
diff --git a/obexd/gwobex/obex-xfer.h b/obexd/gwobex/obex-xfer.h
new file mode 100644
index 0000000..9771f02
--- /dev/null
+++ b/obexd/gwobex/obex-xfer.h
@@ -0,0 +1,87 @@
+/**
+  @file obex-xfer.h
+
+  @author Johan Hedberg <johan.hedberg@nokia.com>
+
+  Copyright (C) 2004-2006 Nokia Corporation. All rights reserved.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License, version 2.1, as published by the Free Software Foundation.
+
+  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.
+
+*/
+#ifndef _OBEX_XFER_H_
+#define _OBEX_XFER_H_
+
+#include <stdint.h>
+#include <time.h>
+#include <glib.h>
+
+#include <openobex/obex.h>
+
+#include "gw-obex.h"
+#include "obex-priv.h"
+
+struct gw_obex_xfer {
+    /* Pointer to parent gw_obex struct */
+    struct gw_obex *ctx;
+
+    /* Used only for async PUT transfers */
+    obex_object_t           *object;
+
+    /* Sync or async transfer */
+    gboolean                 async;
+
+    /* If read and write operations should block for an async transfer */
+    gboolean                 block;
+
+    /* When doing a get or put for a local file */
+    int                      stream_fd;
+
+    /* TRUE if the current operation was aborted */
+    gboolean                 abort;
+
+    /* Transfer should be closed when no more data to send */
+    gboolean                 close;
+
+    /* Temporary buffer when doing a put or get */
+    unsigned char           *buf;
+    size_t                   buf_size;
+
+    /* These two elements are only used for async transfers */
+    size_t                   data_start;
+    size_t                   data_length;
+
+    /* Bytes read or written for the current get/put operation */
+    size_t                   counter;
+
+    /* Target length of the current get/put operation */
+    gint                     target_size;
+
+    /* Modification time of last file transfered */
+    time_t                   modtime;
+
+    gboolean                 do_cb;
+    gw_obex_xfer_cb_t        cb;
+    gpointer                 cb_data;
+
+    GSource                 *idle_source;
+};
+
+struct gw_obex_xfer *gw_obex_xfer_new(struct gw_obex *ctx, gboolean async, int stream_fd);
+
+void _gw_obex_xfer_free(struct gw_obex_xfer *xfer);
+
+gboolean gw_obex_xfer_do_abort(struct gw_obex_xfer *xfer);
+
+#endif /* _OBEX_XFER_H_ */
diff --git a/obexd/gwobex/utils.c b/obexd/gwobex/utils.c
new file mode 100644
index 0000000..02afa2e
--- /dev/null
+++ b/obexd/gwobex/utils.c
@@ -0,0 +1,185 @@
+/**
+  @file utils.c
+
+  @author Johan Hedberg <johan.hedberg@nokia.com>
+
+  Copyright (C) 2004-2006 Nokia Corporation. All rights reserved.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License, version 2.1, as published by the Free Software Foundation.
+
+  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 <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <glib.h>
+#include <termios.h>
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "log.h"
+#include "utils.h"
+
+#ifdef DEBUG
+char *bytestr(const uint8_t *uuid, int len) {
+    int i;
+    char *str = g_malloc((len << 1) + 1);
+
+    for (i = 0; i < len; i++)
+        sprintf(str + (2*i), "%02X", uuid[i]);
+
+    return str;
+}
+#endif
+
+gboolean fd_raw_mode(int fd) {
+    struct termios mode;
+
+    memset(&mode, 0, sizeof (mode));
+    if (tcgetattr(fd, &mode) < 0) {
+        debug("tcgetattr(%d, &mode): %s", fd, strerror(errno));
+        return FALSE;
+    }
+
+    mode.c_iflag = 0;
+    mode.c_oflag &= ~OPOST;
+    mode.c_lflag &= ~(ISIG | ICANON | ECHO
+#ifdef XCASE
+            | XCASE
+#endif
+            );
+    mode.c_cc[VMIN] = 1;
+    mode.c_cc[VTIME] = 0;
+
+    if (tcsetattr(fd, TCSADRAIN, &mode) < 0) {
+        debug("tcsetattr(%d, TCSADRAIN, &mode): %s", fd, strerror(errno));
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+glong get_uname(gunichar2 **uname, const gchar *name) {
+    glong uname_len;
+
+    if (*name == '\0') {
+        *uname = NULL;
+        return 0;
+    }
+
+    *uname = g_utf8_to_utf16(name, -1, NULL, &uname_len, NULL);
+
+    if (*uname == NULL)
+        uname_len = -1;
+    else {
+        int i;
+        /* g_utf8_to_utf16 produces host-byteorder UTF-16,
+         * but OBEX requires network byteorder (big endian) */
+        for (i = 0; i < uname_len; i++)
+            (*uname)[i] = g_htons((*uname)[i]);
+        uname_len = (uname_len + 1) << 1;
+    }
+
+    return uname_len;
+}
+
+int make_iso8601(time_t time, char *str, int len) {
+    struct tm tm;
+#if defined(HAVE_TIMEZONE) && defined(USE_LOCALTIME)
+    time_t tz_offset = 0;
+
+    tz_offset = -timezone;
+    if (daylight > 0)
+        tz_offset += 3600;
+    time += tz_offset;
+#endif
+
+    if (gmtime_r(&time, &tm) == NULL)
+        return -1;
+
+    tm.tm_year += 1900;
+    tm.tm_mon++;
+
+    return snprintf(str, len,
+#ifdef USE_LOCALTIME
+                    "%04u%02u%02uT%02u%02u%02u",
+#else
+                    "%04u%02u%02uT%02u%02u%02uZ",
+#endif
+                    tm.tm_year, tm.tm_mon, tm.tm_mday,
+                    tm.tm_hour, tm.tm_min, tm.tm_sec);
+}
+
+/* From Imendio's GnomeVFS OBEX module (om-utils.c) */
+time_t parse_iso8601(const gchar *str, int len) {
+    gchar    *tstr;
+    struct tm tm;
+    gint      nr;
+    gchar     tz;
+    time_t    time;
+    time_t    tz_offset = 0;
+
+    memset (&tm, 0, sizeof (struct tm));
+
+    /* According to spec the time doesn't have to be null terminated */
+    if (str[len - 1] != '\0') {
+        tstr = g_malloc(len + 1);
+        strncpy(tstr, str, len);
+        tstr[len] = '\0';
+    }
+    else
+        tstr = g_strdup(str);
+
+    nr = sscanf (tstr, "%04u%02u%02uT%02u%02u%02u%c",
+            &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
+            &tm.tm_hour, &tm.tm_min, &tm.tm_sec,
+            &tz);
+
+    g_free(tstr);
+
+    /* Fixup the tm values */
+    tm.tm_year -= 1900;       /* Year since 1900 */
+    tm.tm_mon--;              /* Months since January, values 0-11 */
+    tm.tm_isdst = -1;         /* Daylight savings information not avail */
+
+    if (nr < 6) {
+        /* Invalid time format */
+        return -1;
+    }
+
+    time = mktime (&tm);
+
+#if defined(HAVE_TM_GMTOFF)
+    tz_offset = tm.tm_gmtoff;
+#elif defined(HAVE_TIMEZONE)
+    tz_offset = -timezone;
+    if (tm.tm_isdst > 0) {
+        tz_offset += 3600;
+    }
+#endif
+
+    if (nr == 7) { /* Date/Time was in localtime (to remote device)
+                    * already. Since we don't know anything about the
+                    * timezone on that one we won't try to apply UTC offset
+                    */
+        time += tz_offset;
+    }
+
+    return time;
+}
+
diff --git a/obexd/gwobex/utils.h b/obexd/gwobex/utils.h
new file mode 100644
index 0000000..de1ba05
--- /dev/null
+++ b/obexd/gwobex/utils.h
@@ -0,0 +1,60 @@
+/**
+  @file utils.h
+
+  @author Johan Hedberg <johan.hedberg@nokia.com>
+
+  Copyright (C) 2004-2006 Nokia Corporation. All rights reserved.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License, version 2.1, as published by the Free Software Foundation.
+
+  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.
+
+*/
+#ifndef _UTILS_H_
+#define _UTILS_H_
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <time.h>
+
+/** Create ISO8601 time format string from time_t
+ * @param time Time to convert
+ * @param str  Pointer where result is stored
+ * @param len  Maximum amount of chars written
+ * @returns length of created string.
+ */
+int make_iso8601(time_t time, char *str, int len);
+
+/** Convert a time string in ISO8601 format to time_t
+ * @param str Time string in ISO8601 format
+ * @param len Length of string
+ * @returns time as time_t format
+ */
+time_t parse_iso8601(const gchar *str, int len);
+
+#ifdef DEBUG
+char *bytestr(const uint8_t *uuid, int len);
+#endif
+
+/** Convert an UTF-8 string to UTF-16 (Network byte order)
+ * @param uname, Place to store the new UTF-16 string
+ * @param name, Original UTF-8 string
+ * @returns Size in bytes allocated for the UTF-16 string (uname)
+ */
+glong get_uname(gunichar2 **uname, const gchar *name);
+
+gboolean fd_raw_mode(int fd);
+
+#endif /* _UTILS_H */