Return-Path: From: Forrest Zhao To: linux-bluetooth@vger.kernel.org Cc: forrest.zhao@gmail.com, patrick.ohly@intel.com, Forrest Zhao Subject: [PATCH] Implements the OBEX server/SyncML binding for syncEvolution (http://syncevolution.org/). Date: Wed, 21 Oct 2009 11:34:22 +0800 Message-Id: <1256096062-8294-1-git-send-email-forrest.zhao@intel.com> Sender: linux-bluetooth-owner@vger.kernel.org List-ID: OBEX/SyncML binding spec is at http://www.openmobilealliance.net/ Technical/release_program/docs/Common/V1_2_1-20070813-A/OMA-TS-SyncML_OBEXBinding-V1_2-20070221-A.pdf --- This is the refined patch according to Patrick's comments. Makefile.am | 2 +- src/main.c | 17 ++- src/manager.c | 33 +++++ src/obex.c | 47 +++++-- src/obex.h | 10 ++ src/syncevolution.c | 362 +++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 458 insertions(+), 13 deletions(-) create mode 100644 src/syncevolution.c diff --git a/Makefile.am b/Makefile.am index 63f7478..49817ee 100644 --- a/Makefile.am +++ b/Makefile.am @@ -31,7 +31,7 @@ src_obexd_SOURCES = $(gdbus_sources) \ src/main.c src/obexd.h src/plugin.h src/plugin.c \ src/logging.h src/logging.c src/btio.h src/btio.c \ src/dbus.h src/manager.c src/obex.h src/obex.c \ - src/opp.c src/ftp.c src/pbap.c \ + src/opp.c src/ftp.c src/pbap.c src/syncevolution.c \ src/bluetooth.h src/bluetooth.c \ src/phonebook.h src/phonebook.c diff --git a/src/main.c b/src/main.c index abd2bcc..520973f 100644 --- a/src/main.c +++ b/src/main.c @@ -53,6 +53,7 @@ #define OPP_CHANNEL 9 #define FTP_CHANNEL 10 #define PBAP_CHANNEL 15 +#define SYNCEVOLUTION_CHANNEL 16 #define PCSUITE_CHANNEL 24 #define DEFAULT_ROOT_PATH "/tmp" @@ -172,6 +173,7 @@ static gboolean option_ftp = FALSE; static gboolean option_pbap = FALSE; static gboolean option_pcsuite = FALSE; static gboolean option_symlinks = FALSE; +static gboolean option_syncevolution = FALSE; static GOptionEntry options[] = { { "nodaemon", 'n', G_OPTION_FLAG_REVERSE, @@ -199,6 +201,8 @@ static GOptionEntry options[] = { "Enable Phonebook Access server" }, { "pcsuite", 's', 0, G_OPTION_ARG_NONE, &option_pcsuite, "Enable PC Suite Services server" }, + { "syncevolution", 'e', 0, G_OPTION_ARG_NONE, &option_syncevolution, + "Enable OBEX server for SyncEvolution" }, { NULL }, }; @@ -337,9 +341,9 @@ int main(int argc, char *argv[]) log_option |= LOG_PERROR; if (option_opp == FALSE && option_ftp == FALSE && - option_pbap == FALSE) { + option_pbap == FALSE && option_syncevolution == FALSE) { fprintf(stderr, "No server selected (use either " - "--opp or --ftp or both)\n"); + "--opp, --ftp, --pbap or --syncevolution)\n"); exit(EXIT_FAILURE); } @@ -408,6 +412,15 @@ int main(int argc, char *argv[]) option_capability); } + /* FIXME: move "SyncEvolution" from ServiceName to ProviderName */ + if (option_syncevolution == TRUE) { + services |= OBEX_SYNCEVOLUTION; + bluetooth_init(OBEX_SYNCEVOLUTION, "OBEX server for SyncML," + " using SyncEvolution", NULL, + SYNCEVOLUTION_CHANNEL, TRUE, FALSE, FALSE, + NULL); + } + if (option_devnode) devnode_setup(); diff --git a/src/manager.c b/src/manager.c index 24ed3db..c36ecfd 100644 --- a/src/manager.c +++ b/src/manager.c @@ -216,6 +216,35 @@ static const gchar *pcsuite_record = \ "; +static const gchar *syncevolution_record = +" \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +"; + #define TRANSFER_INTERFACE OPENOBEX_SERVICE ".Transfer" #define SESSION_INTERFACE OPENOBEX_SERVICE ".Session" @@ -525,6 +554,10 @@ static gchar *create_xml_record(const char *name, case OBEX_PCSUITE: xml = g_markup_printf_escaped(pcsuite_record, channel, name); break; + case OBEX_SYNCEVOLUTION: + xml = g_markup_printf_escaped(syncevolution_record, channel, + name); + break; default: xml = NULL; break; diff --git a/src/obex.c b/src/obex.c index 9ec9c5b..61fc5c5 100644 --- a/src/obex.c +++ b/src/obex.c @@ -53,6 +53,7 @@ #define DEFAULT_TX_MTU 32767 #define TARGET_SIZE 16 +#define SYNCML_TARGET_SIZE 11 static const guint8 FTP_TARGET[TARGET_SIZE] = { 0xF9, 0xEC, 0x7B, 0xC4, 0x95, 0x3C, 0x11, 0xD2, @@ -62,6 +63,10 @@ static const guint8 PBAP_TARGET[TARGET_SIZE] = { 0x79, 0x61, 0x35, 0xF0, 0xF0, 0xC5, 0x11, 0xD8, 0x09, 0x66, 0x08, 0x00, 0x20, 0x0C, 0x9A, 0x66 }; +static const guint8 SYNCML_TARGET[SYNCML_TARGET_SIZE] = { + 0x53, 0x59, 0x4E, 0x43, 0x4D, 0x4C, 0x2D, 0x53, + 0x59, 0x4E, 0x43 }; + /* Connection ID */ static guint32 cid = 0x0000; @@ -91,6 +96,11 @@ struct obex_commands pbap = { .setpath = pbap_setpath, }; +struct obex_commands synce = { + .put = synce_put, + .get = synce_get, +}; + static void os_reset_session(struct obex_session *os) { if (os->fd > 0) { @@ -119,6 +129,7 @@ static void os_reset_session(struct obex_session *os) os->offset = 0; os->size = OBJECT_SIZE_DELETE; os->finished = 0; + os->reply_received = 0; } static void os_session_mark_aborted(struct obex_session *os) @@ -145,6 +156,9 @@ static void obex_session_free(struct obex_session *os) if (os->target && !memcmp(os->target, PBAP_TARGET, TARGET_SIZE)) pbap_phonebook_context_destroy(os); + else if (os->target && !memcmp(os->target, SYNCML_TARGET, + SYNCML_TARGET_SIZE)) + synce_destroy(os); g_free(os); } @@ -202,7 +216,7 @@ static void cmd_connect(struct obex_session *os, obex_connect_hdr_t *nonhdr; obex_headerdata_t hd; uint8_t *buffer; - guint hlen, newsize; + guint hlen, newsize, target_size; guint16 mtu; guint8 hi; @@ -227,8 +241,20 @@ static void cmd_connect(struct obex_session *os, os->cid = ++cid; while (OBEX_ObjectGetNextHeader(obex, obj, &hi, &hd, &hlen)) { - if (hi != OBEX_HDR_TARGET || hlen != TARGET_SIZE) + if (hi != OBEX_HDR_TARGET || (hlen != TARGET_SIZE && + hlen != SYNCML_TARGET_SIZE)) continue; + target_size = hlen; + + if (memcmp(hd.bs, SYNCML_TARGET, SYNCML_TARGET_SIZE) == 0 && + os->server->services & OBEX_SYNCEVOLUTION) { + if (!synce_connect(os)) + break; + + os->target = SYNCML_TARGET; + os->cmds = &synce; + break; + } if (memcmp(hd.bs, FTP_TARGET, TARGET_SIZE) == 0 && os->server->services & @@ -270,7 +296,7 @@ static void cmd_connect(struct obex_session *os, /* Append received UUID in WHO header */ hd.bs = os->target; OBEX_ObjectAddHeader(obex, obj, - OBEX_HDR_WHO, hd, TARGET_SIZE, + OBEX_HDR_WHO, hd, target_size, OBEX_FL_FIT_ONE_PACKET); hd.bq4 = cid; OBEX_ObjectAddHeader(obex, obj, @@ -337,7 +363,7 @@ static void cmd_get(struct obex_session *os, obex_t *obex, obex_object_t *obj) os->name = NULL; } - if (os->buf) { + if (os->buf && memcmp(os->target, SYNCML_TARGET, SYNCML_TARGET_SIZE)) { g_free(os->buf); os->buf = NULL; } @@ -599,15 +625,15 @@ static gint obex_read_stream(struct obex_session *os, obex_t *obex, } if (os->fd < 0 && size > 0) { - if (os->buf) { + if (os->buf && memcmp(os->target, SYNCML_TARGET, + SYNCML_TARGET_SIZE)) { error("Got more data but there is still a pending buffer"); return -EIO; } - os->buf = g_malloc0(os->rx_mtu); - memcpy(os->buf, buffer, size); - os->offset = size; - + os->buf = g_realloc(os->buf, os->offset + size); + memcpy(os->buf + os->offset, buffer, size); + os->offset += size; debug("Stored %u bytes into temporary buffer", size); return 0; @@ -710,7 +736,8 @@ static gboolean check_put(obex_t *obex, obex_object_t *obj) OBEX_ObjectReParseHeaders(obex, obj); - if (!os->name) { + if (!os->name && memcmp(os->target, SYNCML_TARGET, + SYNCML_TARGET_SIZE)) { OBEX_ObjectSetRsp(obj, OBEX_RSP_BAD_REQUEST, OBEX_RSP_BAD_REQUEST); g_free(os->type); diff --git a/src/obex.h b/src/obex.h index 94a273e..b9af4ed 100644 --- a/src/obex.h +++ b/src/obex.h @@ -27,6 +27,7 @@ #include #endif +#include #include #include "phonebook.h" @@ -36,6 +37,7 @@ #define OBEX_BIP (1 << 3) #define OBEX_PBAP (1 << 4) #define OBEX_PCSUITE (1 << 5) +#define OBEX_SYNCEVOLUTION (1 << 6) #define OBJECT_SIZE_UNKNOWN -1 #define OBJECT_SIZE_DELETE -2 @@ -86,6 +88,9 @@ struct obex_session { obex_t *obex; struct phonebook_context *pbctx; gboolean finished; + DBusConnection *dbus_conn; + gchar *conn_obj; + gboolean reply_received; }; gint obex_session_start(GIOChannel *io, struct server *server); @@ -106,6 +111,11 @@ gboolean pbap_phonebook_context_create(struct obex_session *session); void pbap_phonebook_context_destroy(struct obex_session *session); struct obex_session *pbap_get_session(struct phonebook_context *context); +gboolean synce_connect(struct obex_session *os); +void synce_put(obex_t *obex, obex_object_t *obj); +void synce_get(obex_t *obex, obex_object_t *obj); +void synce_destroy(struct obex_session *os); + gint os_prepare_get(struct obex_session *os, gchar *file, guint32 *size); gint os_prepare_put(struct obex_session *os); diff --git a/src/syncevolution.c b/src/syncevolution.c new file mode 100644 index 0000000..c65fce9 --- /dev/null +++ b/src/syncevolution.c @@ -0,0 +1,362 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2008 Intel Corporation + * Copyright (C) 2007-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include + +#include +#include + +#include "logging.h" +#include "obex.h" +#include "obexd.h" +#include "dbus.h" +#include "btio.h" + +#define SYNCE_BUS_NAME "org.syncevolution" +#define SYNCE_PATH "/org/syncevolution/Server" +#define SYNCE_SERVER_INTERFACE "org.syncevolution.Server" +#define SYNCE_CONN_INTERFACE "org.syncevolution.Connection" + +static char match_string[256]; + +struct cb_data { + struct obex_session *os; + obex_object_t *obj; +} process_cb_data; + +static GSList *os_list = NULL; + +static void dbus_message_iter_append_dict_entry(DBusMessageIter *dict, + const char *key, int type, void *val) +{ + DBusMessageIter entry; + + dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, + NULL, &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &val); + dbus_message_iter_close_container(dict, &entry); +} + +static void handle_connection_reply_signal(DBusMessage *msg, + const char *obj_path, void *data) +{ + struct obex_session *os = data; + DBusMessageIter iter, array_iter; + gchar *value; + gint length = 0; + + if (strcmp(os->conn_obj, obj_path)) + return; + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in Reply signal"); + } + + dbus_message_iter_recurse(&iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &value, &length); + + if (length) { + os->buf = g_try_malloc(length); + memcpy(os->buf, value, length); + os->size = length; + os->reply_received = TRUE; + os->finished = TRUE; + OBEX_ResumeRequest(os->obex); + } +} + +static void handle_connection_abort_signal(DBusMessage *msg, + const char *obj_path, void *data) +{ + struct obex_session *os = data; + + OBEX_TransportDisconnect(os->obex); +} + +static DBusHandlerResult signal_filter(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *path = dbus_message_get_path(msg); + struct obex_session *os = NULL; + GSList *l; + + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + for (l = os_list; l != NULL; l = l->next) { + struct obex_session *tmp_os = l->data; + if (!strcmp(tmp_os->conn_obj, path)) { + os = tmp_os; + break; + } + } + if (!os) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (dbus_message_is_signal(msg, SYNCE_CONN_INTERFACE, "Reply")) { + debug("Reply signal is received."); + handle_connection_reply_signal(msg, path, os); + } else if (dbus_message_is_signal(msg, SYNCE_CONN_INTERFACE, "Abort")) { + debug("Abort signal is received."); + handle_connection_abort_signal(msg, path, os); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static void connect_cb(DBusPendingCall *call, void *user_data) +{ + static gboolean signal_filter_added = FALSE; + struct obex_session *os = user_data; + DBusConnection *conn = os->dbus_conn; + DBusMessage *reply; + gchar *path; + + reply = dbus_pending_call_steal_reply(call); + + dbus_message_get_args(reply, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + info("Got conn object %s from syncevolution", path); + os->conn_obj = g_strdup(path); + + /* add signal filter */ + if (!signal_filter_added) { + if (!dbus_connection_add_filter(conn, signal_filter, + os, NULL)) { + error("Can't add signal filter"); + dbus_message_unref(reply); + return; + } + signal_filter_added = TRUE; + } + os_list = g_slist_append(os_list, os); + snprintf(match_string, sizeof(match_string), "type=signal,interface=%s," + "path=%s", SYNCE_CONN_INTERFACE, os->conn_obj); + dbus_bus_add_match(conn, match_string, NULL); + dbus_connection_flush(conn); + + dbus_message_unref(reply); +} + +static void process_cb(DBusPendingCall *call, void *user_data) +{ + DBusMessage *reply; + DBusError derr; + + info("At the begin of process_cb()."); + reply = dbus_pending_call_steal_reply(call); + dbus_error_init(&derr); + if (dbus_set_error_from_message(&derr, reply)) { + error("process_cb(): syncevolution replied with an error:" + " %s, %s", derr.name, derr.message); + dbus_error_free(&derr); + } + dbus_message_unref(reply); +} + +gboolean synce_connect(struct obex_session *os) +{ + DBusConnection *conn = NULL; + GError *err = NULL; + char address[18]; + guint8 channel; + DBusMessage *msg; + DBusMessageIter iter, dict; + gchar id[36]; + gchar transport[36]; + gchar transport_description[24]; + gboolean authenticate = FALSE; + char *session = ""; + DBusPendingCall *call; + + conn = dbus_bus_get(DBUS_BUS_SESSION, NULL); + if (conn == NULL) + return FALSE; + + bt_io_get(os->io, BT_IO_RFCOMM, &err, + BT_IO_OPT_DEST, address, + BT_IO_OPT_CHANNEL, &channel, + BT_IO_OPT_INVALID); + + msg = dbus_message_new_method_call(SYNCE_BUS_NAME, SYNCE_PATH, + SYNCE_SERVER_INTERFACE, "Connect"); + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_STRING_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + snprintf(id, sizeof(id), "%s+%u", address, channel); + dbus_message_iter_append_dict_entry(&dict, "id", DBUS_TYPE_STRING, id); + + snprintf(transport, sizeof(transport), "%s.obexd", + OPENOBEX_SERVICE); + dbus_message_iter_append_dict_entry(&dict, "transport", + DBUS_TYPE_STRING, transport); + + snprintf(transport_description, sizeof(transport_description), + "version %s", VERSION); + dbus_message_iter_append_dict_entry(&dict, "transport_description", + DBUS_TYPE_STRING, transport_description); + dbus_message_iter_close_container(&iter, &dict); + dbus_message_append_args(msg, DBUS_TYPE_BOOLEAN, &authenticate, + DBUS_TYPE_STRING, &session, DBUS_TYPE_INVALID); + + if (!dbus_connection_send_with_reply(conn, msg, &call, -1)) { + error("D-Bus call to %s failed.", SYNCE_SERVER_INTERFACE); + dbus_message_unref(msg); + return FALSE; + } + + dbus_pending_call_set_notify(call, connect_cb, os, NULL); + + os->dbus_conn = conn; + + dbus_pending_call_unref(call); + dbus_message_unref(msg); + return TRUE; +} + +void synce_put(obex_t *obex, obex_object_t *obj) +{ + struct obex_session *os; + DBusMessage *msg; + DBusMessageIter iter, array_iter; + DBusPendingCall *call; + + os = OBEX_GetUserData(obex); + if (os == NULL) + return; + + if (!os->conn_obj) { + OBEX_ObjectSetRsp(obj, OBEX_RSP_SERVICE_UNAVAILABLE, + OBEX_RSP_SERVICE_UNAVAILABLE); + return; + } + + msg = dbus_message_new_method_call(SYNCE_BUS_NAME, os->conn_obj, + SYNCE_CONN_INTERFACE, "Process"); + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_BYTE_AS_STRING, &array_iter); + dbus_message_iter_append_fixed_array(&array_iter, DBUS_TYPE_BYTE, + &os->buf, os->offset); + dbus_message_iter_close_container(&iter, &array_iter); + + dbus_message_append_args(msg, DBUS_TYPE_STRING, &os->type, + DBUS_TYPE_INVALID); + + if (!dbus_connection_send_with_reply(os->dbus_conn, msg, &call, -1)) { + error("D-Bus call to %s failed.", SYNCE_CONN_INTERFACE); + dbus_message_unref(msg); + OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN); + return; + } + + dbus_pending_call_set_notify(call, process_cb, os, NULL); + + dbus_message_unref(msg); + dbus_pending_call_unref(call); + + OBEX_ObjectSetRsp(obj, OBEX_RSP_CONTINUE, OBEX_RSP_SUCCESS); + return; +} + +void synce_get(obex_t *obex, obex_object_t *obj) +{ + obex_headerdata_t hd; + struct obex_session *os; + + os = OBEX_GetUserData(obex); + if (os == NULL) + return; + + if (!os->reply_received) { + debug("in synce_get() OBEX_SuspendRequest() is called"); + OBEX_SuspendRequest(obex, obj); + } + + hd.bs = NULL; + OBEX_ObjectAddHeader(obex, obj, OBEX_HDR_BODY, hd, 0, + OBEX_FL_STREAM_START); + + OBEX_ObjectSetRsp(obj, OBEX_RSP_CONTINUE, OBEX_RSP_SUCCESS); + return; +} + +static void close_cb(DBusPendingCall *call, void *user_data) +{ + DBusMessage *reply; + DBusError derr; + + reply = dbus_pending_call_steal_reply(call); + dbus_error_init(&derr); + if (dbus_set_error_from_message(&derr, reply)) { + error("close_cb(): syncevolution replied with an error:" + " %s, %s", derr.name, derr.message); + dbus_error_free(&derr); + } + + dbus_message_unref(reply); +} + +void synce_destroy(struct obex_session *os) +{ + DBusMessage *msg; + gboolean normal = TRUE; + gchar *error = "none"; + DBusPendingCall *call; + + debug("At the begin of synce_destroy()."); + if (os->conn_obj) { + msg = dbus_message_new_method_call(SYNCE_BUS_NAME, os->conn_obj, + SYNCE_CONN_INTERFACE, "Close"); + dbus_message_append_args(msg, DBUS_TYPE_BOOLEAN, &normal, + DBUS_TYPE_STRING, &error, DBUS_TYPE_INVALID); + dbus_connection_send_with_reply(os->dbus_conn, msg, &call, -1); + dbus_pending_call_set_notify(call, close_cb, NULL, NULL); + dbus_message_unref(msg); + dbus_pending_call_unref(call); + + snprintf(match_string, sizeof(match_string), + "type=signal,interface=%s,path=%s", + SYNCE_CONN_INTERFACE, os->conn_obj); + dbus_bus_remove_match(os->dbus_conn, match_string, NULL); + g_free(os->conn_obj); + os->conn_obj = NULL; + } + dbus_connection_unref(os->dbus_conn); + os_list = g_slist_remove(os_list, os); +} -- 1.5.4.5