From 38cc843679289222b4a3f3c8fb215760ead109f9 Mon Sep 17 00:00:00 2001 From: Marcel Holtmann Date: Fri, 3 Oct 2008 09:48:18 +0200 Subject: [PATCH] obexd: Add private copy of osso-gwobex-0.60 --- obexd/gwobex/gw-obex.c | 367 +++++++++++++ obexd/gwobex/gw-obex.h | 601 +++++++++++++++++++++ obexd/gwobex/log.h | 38 ++ obexd/gwobex/obex-priv.c | 1084 ++++++++++++++++++++++++++++++++++++++ obexd/gwobex/obex-priv.h | 215 ++++++++ obexd/gwobex/obex-xfer.c | 507 ++++++++++++++++++ obexd/gwobex/obex-xfer.h | 87 +++ obexd/gwobex/utils.c | 185 +++++++ obexd/gwobex/utils.h | 60 +++ 9 files changed, 3144 insertions(+) create mode 100644 obexd/gwobex/gw-obex.c create mode 100644 obexd/gwobex/gw-obex.h create mode 100644 obexd/gwobex/log.h create mode 100644 obexd/gwobex/obex-priv.c create mode 100644 obexd/gwobex/obex-priv.h create mode 100644 obexd/gwobex/obex-xfer.c create mode 100644 obexd/gwobex/obex-xfer.h create mode 100644 obexd/gwobex/utils.c create mode 100644 obexd/gwobex/utils.h diff --git a/obexd/gwobex/gw-obex.c b/obexd/gwobex/gw-obex.c new file mode 100644 index 000000000..6549b0995 --- /dev/null +++ b/obexd/gwobex/gw-obex.c @@ -0,0 +1,367 @@ +/** + @file gw-obex.c + + @author Johan Hedberg + + 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 +#include +#include +#include +#include +#include +#include + +#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 000000000..c1be1032f --- /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 + + 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 +#include + +/** + * @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: + *
    + *
  • Data can be written (i.e. xfer_write will succeed)
  • + *
  • Data can be read (i.e. xfer_read will succees)
  • + *
  • An error ocured
  • + *
  • The transfer is finished
  • + *
+ * + * @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 000000000..41be01adc --- /dev/null +++ b/obexd/gwobex/log.h @@ -0,0 +1,38 @@ +/** + @file log.h + + @author Johan Hedberg + + 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 +# define debug(...) g_print(__VA_ARGS__) +# else +# include +# 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 000000000..792cb4ff1 --- /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 + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#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 000000000..051c67a6f --- /dev/null +++ b/obexd/gwobex/obex-priv.h @@ -0,0 +1,215 @@ +/** + @file obex-priv.h + + @author Johan Hedberg + + 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 +#include +#include +#include + +#include + +#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 000000000..1cce005a9 --- /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 + + 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 +#include +#include +#include + +#include + +#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 000000000..9771f0228 --- /dev/null +++ b/obexd/gwobex/obex-xfer.h @@ -0,0 +1,87 @@ +/** + @file obex-xfer.h + + @author Johan Hedberg + + 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 +#include +#include + +#include + +#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 000000000..02afa2e12 --- /dev/null +++ b/obexd/gwobex/utils.c @@ -0,0 +1,185 @@ +/** + @file utils.c + + @author Johan Hedberg + + 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 +#include +#include +#include +#include +#include +#include + +#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 000000000..de1ba0592 --- /dev/null +++ b/obexd/gwobex/utils.h @@ -0,0 +1,60 @@ +/** + @file utils.h + + @author Johan Hedberg + + 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 +#include +#include +#include +#include + +/** 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 */ -- 2.47.3