diff --git a/obexd/client/main.c b/obexd/client/main.c
index 39f65bb..ecb377d 100644
--- a/obexd/client/main.c
+++ b/obexd/client/main.c
struct send_data {
DBusConnection *connection;
DBusMessage *message;
+ gchar *sender;
gchar *agent;
- gchar *file;
+ GPtrArray *files;
};
static void create_callback(struct session_data *session, void *user_data)
{
struct send_data *data = user_data;
+ int i;
- g_dbus_send_reply(data->connection, data->message, DBUS_TYPE_INVALID);
+ if (session->obex == NULL) {
+ DBusMessage *error = g_dbus_create_error(data->message,
+ "org.openobex.Error.Failed", NULL);
+ g_dbus_send_message(data->connection, error);
- dbus_message_unref(data->message);
- dbus_connection_unref(data->connection);
+ dbus_message_unref(data->message);
+ dbus_connection_unref(data->connection);
+ return;
+ }
- session_set_agent(session, data->agent);
+ g_dbus_send_reply(data->connection, data->message, DBUS_TYPE_INVALID);
+
+ session_set_agent(session, data->sender, data->agent);
+ g_free(data->sender);
g_free(data->agent);
- session_send(session, data->file);
- g_free(data->file);
+ for (i = 0; i < data->files->len; i++) {
+ if (session_send(session,
+ g_ptr_array_index(data->files, i)) < 0)
+ break;
+ }
+
+ g_ptr_array_free(data->files, TRUE);
+ dbus_message_unref(data->message);
+ dbus_connection_unref(data->connection);
g_free(data);
}
DBusMessage *message, void *user_data)
{
DBusMessageIter iter, array;
+ GPtrArray *files;
struct send_data *data;
- const char *agent, *dest = NULL, *file = NULL;
+ const char *agent, *dest = NULL;
+
+ files = g_ptr_array_new();
+ if (files == NULL)
+ return g_dbus_create_error(message,
+ "org.openobex.Error.NoMemory", NULL);
dbus_message_iter_init(message, &iter);
dbus_message_iter_recurse(&iter, &array);
dbus_message_iter_recurse(&iter, &array);
while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) {
- if (file == NULL)
- dbus_message_iter_get_basic(&array, &file);
+ char *value;
+
+ dbus_message_iter_get_basic(&array, &value);
+ g_ptr_array_add(files, value);
dbus_message_iter_next(&array);
}
dbus_message_iter_next(&iter);
dbus_message_iter_get_basic(&iter, &agent);
- if (dest == NULL)
+ if (dest == NULL || files->len == 0) {
+ g_ptr_array_free(files, TRUE);
return g_dbus_create_error(message,
"org.openobex.Error.InvalidArguments", NULL);
+ }
data = g_try_malloc0(sizeof(*data));
- if (data == NULL)
+ if (data == NULL) {
+ g_ptr_array_free(files, TRUE);
return g_dbus_create_error(message,
"org.openobex.Error.NoMemory", NULL);
+ }
data->connection = dbus_connection_ref(connection);
data->message = dbus_message_ref(message);
+ data->sender = g_strdup(dbus_message_get_sender(message));
data->agent = g_strdup(agent);
- data->file = g_strdup(file);
+ data->files = files;
if (session_create(NULL, dest, NULL, create_callback, data) == 0)
return NULL;
+ g_ptr_array_free(data->files, TRUE);
dbus_message_unref(message);
dbus_connection_unref(connection);
+ g_free(data->sender);
g_free(data->agent);
- g_free(data->file);
g_free(data);
return g_dbus_create_error(message, "org.openobex.Error.Failed", NULL);
diff --git a/obexd/client/session.c b/obexd/client/session.c
index 28993e0..10cf28f 100644
--- a/obexd/client/session.c
+++ b/obexd/client/session.c
#endif
#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/stat.h>
#include <glib.h>
+#include <gdbus.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
#include "session.h"
+#define AGENT_INTERFACE "org.openobex.Agent"
+
+#define TRANSFER_INTERFACE "org.openobex.Transfer"
+#define TRANSFER_BASEPATH "/org/openobex"
+
+static guint64 counter = 0;
+
+struct callback_data {
+ struct session_data *session;
+ sdp_session_t *sdp;
+ uint16_t uuid;
+ session_callback_t func;
+ void *data;
+};
+
+static struct session_data *session_ref(struct session_data *session)
+{
+ g_atomic_int_inc(&session->refcount);
+
+ return session;
+}
+
+static void session_unref(struct session_data *session)
+{
+ if (g_atomic_int_dec_and_test(&session->refcount) == TRUE) {
+ DBusMessage *message;
+
+ message = dbus_message_new_method_call(session->agent_name,
+ session->agent_path, AGENT_INTERFACE, "Release");
+
+ dbus_message_set_no_reply(message, TRUE);
+
+ g_dbus_send_message(session->conn, message);
+
+ if (session->pending != NULL)
+ g_ptr_array_free(session->pending, TRUE);
+
+ if (session->obex != NULL) {
+ if (session->xfer != NULL) {
+ gw_obex_xfer_close(session->xfer, NULL);
+ gw_obex_xfer_free(session->xfer);
+ }
+
+ gw_obex_close(session->obex);
+ }
+
+ if (session->sock > 2)
+ close(session->sock);
+
+ if (session->conn) {
+ if (session->path)
+ g_dbus_unregister_interface(session->conn,
+ session->path, TRANSFER_INTERFACE);
+
+ dbus_connection_unref(session->conn);
+ }
+
+ g_free(session->path);
+ g_free(session->name);
+ g_free(session->target);
+ g_free(session->filename);
+ g_free(session->agent_name);
+ g_free(session->agent_path);
+ g_free(session);
+ }
+}
+
+static gboolean rfcomm_callback(GIOChannel *io, GIOCondition cond,
+ gpointer user_data)
+{
+ struct callback_data *callback = user_data;
+ GwObex *obex;
+ int fd;
+
+ if (cond & (G_IO_NVAL | G_IO_ERR))
+ goto done;
+
+ fd = g_io_channel_unix_get_fd(io);
+
+ obex = gw_obex_setup_fd(fd, NULL, 0, NULL, NULL);
+
+ callback->session->sock = fd;
+ callback->session->obex = obex;
+
+ callback->session->pending = g_ptr_array_new();
+
+done:
+ callback->func(callback->session, callback->data);
+
+ session_unref(callback->session);
+
+ g_free(callback);
+
+ return FALSE;
+}
+
+static int rfcomm_connect(const bdaddr_t *src,
+ const bdaddr_t *dst, uint8_t channel,
+ GIOFunc function, gpointer user_data)
+{
+ GIOChannel *io;
+ struct sockaddr_rc addr;
+ int sk;
+
+ sk = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+ if (sk < 0)
+ return -EIO;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.rc_family = AF_BLUETOOTH;
+ bacpy(&addr.rc_bdaddr, src);
+
+ if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ close(sk);
+ return -EIO;
+ }
+
+ io = g_io_channel_unix_new(sk);
+ if (io == NULL) {
+ close(sk);
+ return -ENOMEM;
+ }
+
+ if (g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK,
+ NULL) != G_IO_STATUS_NORMAL) {
+ g_io_channel_unref(io);
+ close(sk);
+ return -EPERM;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.rc_family = AF_BLUETOOTH;
+ bacpy(&addr.rc_bdaddr, dst);
+ addr.rc_channel = channel;
+
+ if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ if (errno != EAGAIN && errno != EINPROGRESS) {
+ g_io_channel_unref(io);
+ close(sk);
+ return -EIO;
+ }
+ }
+
+ g_io_add_watch(io, G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ function, user_data);
+
+ g_io_channel_unref(io);
+
+ return 0;
+}
+
+static void search_callback(uint8_t type, uint16_t status,
+ uint8_t *rsp, size_t size, void *user_data)
+{
+ struct callback_data *callback = user_data;
+ sdp_list_t *recs = NULL;
+ int scanned, seqlen = 0, bytesleft = size;
+ uint8_t dataType, channel = 0;
+
+ if (status || type != SDP_SVC_SEARCH_ATTR_RSP)
+ goto failed;
+
+ scanned = sdp_extract_seqtype(rsp, bytesleft, &dataType, &seqlen);
+ if (!scanned || !seqlen)
+ goto failed;
+
+ rsp += scanned;
+ bytesleft -= scanned;
+ do {
+ sdp_record_t *rec;
+ sdp_list_t *protos;
+ int recsize, ch = -1;
+
+ recsize = 0;
+ rec = sdp_extract_pdu(rsp, bytesleft, &recsize);
+ if (!rec)
+ break;
+
+ if (!recsize) {
+ sdp_record_free(rec);
+ break;
+ }
+
+ if (!sdp_get_access_protos(rec, &protos)) {
+ ch = sdp_get_proto_port(protos, RFCOMM_UUID);
+ sdp_list_foreach(protos,
+ (sdp_list_func_t) sdp_list_free, NULL);
+ sdp_list_free(protos, NULL);
+ protos = NULL;
+ }
+
+ if (ch > 0) {
+ channel = ch;
+ break;
+ }
+
+ scanned += recsize;
+ rsp += recsize;
+ bytesleft -= recsize;
+
+ recs = sdp_list_append(recs, rec);
+ } while (scanned < size && bytesleft > 0);
+
+ if (channel == 0)
+ goto failed;
+
+ sdp_close(callback->sdp);
+
+ rfcomm_connect(&callback->session->src, &callback->session->dst,
+ channel, rfcomm_callback, callback);
+
+ return;
+
+failed:
+ sdp_close(callback->sdp);
+
+ if (recs)
+ sdp_list_free(recs, (sdp_free_func_t) sdp_record_free);
+
+ callback->func(callback->session, callback->data);
+ session_unref(callback->session);
+ g_free(callback);
+}
+
+static gboolean process_callback(GIOChannel *io, GIOCondition cond,
+ gpointer user_data)
+{
+ struct callback_data *callback = user_data;
+
+ if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
+ return FALSE;
+
+ if (sdp_process(callback->sdp) < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean service_callback(GIOChannel *io, GIOCondition cond,
+ gpointer user_data)
+{
+ struct callback_data *callback = user_data;
+ sdp_list_t *search, *attrid;
+ uint32_t range = 0x0000ffff;
+ uuid_t uuid;
+
+ if (cond & (G_IO_NVAL | G_IO_ERR))
+ goto failed;
+
+ if (sdp_set_notify(callback->sdp, search_callback, callback) < 0)
+ goto failed;
+
+ sdp_uuid16_create(&uuid, callback->uuid);
+
+ search = sdp_list_append(NULL, &uuid);
+ attrid = sdp_list_append(NULL, &range);
+
+ if (sdp_service_search_attr_async(callback->sdp,
+ search, SDP_ATTR_REQ_RANGE, attrid) < 0) {
+ sdp_list_free(attrid, NULL);
+ sdp_list_free(search, NULL);
+ goto failed;
+ }
+
+ sdp_list_free(attrid, NULL);
+ sdp_list_free(search, NULL);
+
+ g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+ process_callback, callback);
+
+ return FALSE;
+
+failed:
+ sdp_close(callback->sdp);
+
+ callback->func(callback->session, callback->data);
+ session_unref(callback->session);
+ g_free(callback);
+ return FALSE;
+}
+
+static sdp_session_t *service_connect(const bdaddr_t *src, const bdaddr_t *dst,
+ GIOFunc function, gpointer user_data)
+{
+ sdp_session_t *sdp;
+ GIOChannel *io;
+
+ sdp = sdp_connect(src, dst, SDP_NON_BLOCKING);
+ if (sdp == NULL)
+ return NULL;
+
+ io = g_io_channel_unix_new(sdp_get_socket(sdp));
+ if (io == NULL) {
+ sdp_close(sdp);
+ return NULL;
+ }
+
+ g_io_add_watch(io, G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+ function, user_data);
+
+ g_io_channel_unref(io);
+
+ return sdp;
+}
+
int session_create(const char *source,
const char *destination, const char *target,
- session_callback callback, void *user_data)
+ session_callback_t function, void *user_data)
{
struct session_data *session;
+ struct callback_data *callback;
+ int err;
if (destination == NULL)
return -EINVAL;
if (session == NULL)
return -ENOMEM;
+ session->refcount = 1;
+ session->sock = -1;
+
+ session->conn = dbus_bus_get(DBUS_BUS_SESSION, NULL);
+ if (session->conn == NULL) {
+ session_unref(session);
+ return -ENOMEM;
+ }
+
if (source == NULL)
bacpy(&session->src, BDADDR_ANY);
else
if (target != NULL)
session->target = g_strdup(target);
- callback(session, user_data);
+ callback = g_try_malloc0(sizeof(*callback));
+ if (callback == NULL) {
+ session_unref(session);
+ return -ENOMEM;
+ }
+
+ callback->session = session;
+ callback->func = function;
+ callback->data = user_data;
- g_free(session->agent);
- g_free(session->target);
- g_free(session);
+ if (session->channel > 0) {
+ err = rfcomm_connect(&session->src, &session->dst,
+ session->channel, rfcomm_callback, callback);
+ } else {
+ callback->uuid = OBEX_OBJPUSH_SVCLASS_ID;
+ callback->sdp = service_connect(&session->src, &session->dst,
+ service_callback, callback);
+ err = (callback->sdp == NULL) ? -ENOMEM : 0;
+ }
+
+ if (err < 0) {
+ session_unref(session);
+ g_free(callback);
+ return -EINVAL;
+ }
return 0;
}
-int session_set_agent(struct session_data *session, const char *agent)
+int session_set_agent(struct session_data *session, const char *name,
+ const char *path)
{
if (session == NULL)
return -EINVAL;
- if (session->agent != NULL)
+ if (session->agent_name != NULL || session->agent_path != NULL)
return -EALREADY;
- printf("Using agent at %s\n", agent);
-
- session->agent = g_strdup(agent);
+ session->agent_name = g_strdup(name);
+ session->agent_path = g_strdup(path);
return 0;
}
+static void append_entry(DBusMessageIter *dict,
+ const char *key, int type, void *val)
+{
+ DBusMessageIter entry, value;
+ const char *signature;
+
+ dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY,
+ NULL, &entry);
+
+ dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
+
+ switch (type) {
+ case DBUS_TYPE_STRING:
+ signature = DBUS_TYPE_STRING_AS_STRING;
+ break;
+ case DBUS_TYPE_UINT64:
+ signature = DBUS_TYPE_UINT64_AS_STRING;
+ break;
+ default:
+ signature = DBUS_TYPE_VARIANT_AS_STRING;
+ break;
+ }
+
+ dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
+ signature, &value);
+ dbus_message_iter_append_basic(&value, type, val);
+ dbus_message_iter_close_container(&entry, &value);
+
+ dbus_message_iter_close_container(dict, &entry);
+}
+
+static DBusMessage *get_properties(DBusConnection *connection,
+ DBusMessage *message, void *user_data)
+{
+ struct session_data *session = user_data;
+ DBusMessage *reply;
+ DBusMessageIter iter, dict;
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ append_entry(&dict, "Name", DBUS_TYPE_STRING, &session->name);
+ append_entry(&dict, "Size", DBUS_TYPE_UINT64, &session->size);
+ append_entry(&dict, "Filename", DBUS_TYPE_STRING, &session->filename);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static GDBusMethodTable transfer_methods[] = {
+ { "GetProperties", "", "a{sv}", get_properties },
+ { }
+};
+
+static void xfer_progress(GwObexXfer *xfer, gpointer user_data)
+{
+ struct session_data *session = user_data;
+ DBusMessage *message;
+ ssize_t len;
+ gint written;
+
+ len = read(session->fd, session->buffer + session->filled,
+ sizeof(session->buffer) - session->filled);
+ if (len <= 0) {
+ message = dbus_message_new_method_call(session->agent_name,
+ session->agent_path, AGENT_INTERFACE, "Complete");
+
+ dbus_message_append_args(message,
+ DBUS_TYPE_OBJECT_PATH, &session->path,
+ DBUS_TYPE_INVALID);
+
+ g_dbus_send_message(session->conn, message);
+
+ if (session->pending->len > 0) {
+ gchar *filename;
+ filename = g_ptr_array_index(session->pending, 0);
+ g_ptr_array_remove(session->pending, filename);
+
+ gw_obex_xfer_close(session->xfer, NULL);
+ gw_obex_xfer_free(session->xfer);
+ session->xfer = NULL;
+
+ g_free(session->filename);
+ session->filename = NULL;
+
+ g_free(session->name);
+ session->name = NULL;
+
+ if (session->path) {
+ g_dbus_unregister_interface(session->conn,
+ session->path, TRANSFER_INTERFACE);
+ g_free(session->path);
+ session->path = NULL;
+ }
+
+ session_send(session, filename);
+ g_free(filename);
+ }
+
+ session_unref(session);
+ return;
+ }
+
+ gw_obex_xfer_write(xfer, session->buffer, session->filled + len,
+ &written, NULL);
+
+ session->filled = (session->filled + len) - written;
+
+ memmove(session->buffer + written, session->buffer, session->filled);
+
+ session->transferred += written;
+
+ message = dbus_message_new_method_call(session->agent_name,
+ session->agent_path, AGENT_INTERFACE, "Progress");
+
+ dbus_message_set_no_reply(message, TRUE);
+
+ dbus_message_append_args(message, DBUS_TYPE_OBJECT_PATH, &session->path,
+ DBUS_TYPE_UINT64, &session->transferred,
+ DBUS_TYPE_INVALID);
+
+ g_dbus_send_message(session->conn, message);
+}
+
int session_send(struct session_data *session, const char *filename)
{
- printf("Sending file %s\n", filename);
+ GwObexXfer *xfer;
+ DBusMessage *message;
+ guint64 transferred = 0;
+ struct stat st;
+ int fd;
+
+ if (session->obex == NULL)
+ return -ENOTCONN;
+
+ if (session->xfer != NULL) {
+ g_ptr_array_add(session->pending, g_strdup(filename));
+ return 0;
+ }
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ return -EIO;
+
+ if (fstat(fd, &st) < 0) {
+ close(fd);
+ return -EIO;
+ }
+
+ session->fd = fd;
+ session->size = st.st_size;
+ session->transferred = 0;
+ session->filename = g_strdup(filename);
+
+ session->name = g_path_get_basename(filename);
+ session->path = g_strdup_printf("%s/transfer%ld",
+ TRANSFER_BASEPATH, counter++);
+
+ if (g_dbus_register_interface(session->conn, session->path,
+ TRANSFER_INTERFACE,
+ transfer_methods, NULL, NULL,
+ session, NULL) == FALSE)
+ return -EIO;
+
+ session_ref(session);
+
+ message = dbus_message_new_method_call(session->agent_name,
+ session->agent_path, AGENT_INTERFACE, "Request");
+
+ dbus_message_append_args(message, DBUS_TYPE_OBJECT_PATH, &session->path,
+ DBUS_TYPE_INVALID);
+
+ g_dbus_send_message(session->conn, message);
+
+ xfer = gw_obex_put_async(session->obex, session->name, NULL,
+ session->size, -1, NULL);
+
+ message = dbus_message_new_method_call(session->agent_name,
+ session->agent_path, AGENT_INTERFACE, "Progress");
+
+ dbus_message_set_no_reply(message, TRUE);
+
+ dbus_message_append_args(message, DBUS_TYPE_OBJECT_PATH, &session->path,
+ DBUS_TYPE_UINT64, &transferred,
+ DBUS_TYPE_INVALID);
+
+ g_dbus_send_message(session->conn, message);
+
+ gw_obex_xfer_set_callback(xfer, xfer_progress, session);
+
+ session->xfer = xfer;
return 0;
}
diff --git a/obexd/client/session.h b/obexd/client/session.h
index 3f4edfd..d74706b 100644
--- a/obexd/client/session.h
+++ b/obexd/client/session.h
*
*/
+#include <glib.h>
+#include <gdbus.h>
+
#include <bluetooth/bluetooth.h>
+#include <gw-obex.h>
struct session_data {
+ gint refcount;
bdaddr_t src;
bdaddr_t dst;
- char *target;
- char *agent;
+ uint8_t channel;
+ gchar *target;
+ gchar *name;
+ gchar *path;
+ int sock;
+ int fd;
+ DBusConnection *conn;
+ GwObex *obex;
+ GwObexXfer *xfer;
+ char buffer[4096];
+ int filled;
+ uint64_t size;
+ uint64_t transferred;
+ gchar *filename;
+ gchar *agent_name;
+ gchar *agent_path;
+ GPtrArray *pending;
};
-typedef void (*session_callback) (struct session_data *session,
+typedef void (*session_callback_t) (struct session_data *session,
void *user_data);
int session_create(const char *source,
const char *destination, const char *target,
- session_callback callback, void *user_data);
-int session_set_agent(struct session_data *session, const char *agent);
+ session_callback_t function, void *user_data);
+int session_set_agent(struct session_data *session, const char *name,
+ const char *path);
int session_send(struct session_data *session, const char *filename);