| /* |
| * |
| * OBEX Client |
| * |
| * Copyright (C) 2007-2010 Intel Corporation |
| * Copyright (C) 2007-2010 Marcel Holtmann <marcel@holtmann.org> |
| * |
| * |
| * 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 <config.h> |
| #endif |
| |
| #include <errno.h> |
| #include <string.h> |
| #include <stdio.h> |
| |
| #include <glib.h> |
| |
| #include "lib/bluetooth.h" |
| #include "lib/sdp.h" |
| |
| #include "gobex/gobex-apparam.h" |
| #include "gdbus/gdbus.h" |
| |
| #include "obexd/src/log.h" |
| |
| #include "transfer.h" |
| #include "session.h" |
| #include "driver.h" |
| #include "pbap.h" |
| |
| #define OBEX_PBAP_UUID \ |
| "\x79\x61\x35\xF0\xF0\xC5\x11\xD8\x09\x66\x08\x00\x20\x0C\x9A\x66" |
| #define OBEX_PBAP_UUID_LEN 16 |
| |
| #define FORMAT_VCARD21 0x0 |
| #define FORMAT_VCARD30 0x1 |
| |
| #define ORDER_INDEXED 0x0 |
| #define ORDER_ALPHANUMERIC 0x1 |
| #define ORDER_PHONETIC 0x2 |
| |
| #define ATTRIB_NAME 0x0 |
| #define ATTRIB_NUMBER 0x1 |
| #define ATTRIB_SOUND 0x2 |
| |
| #define DEFAULT_COUNT 65535 |
| #define DEFAULT_OFFSET 0 |
| |
| #define PULLPHONEBOOK 0x1 |
| #define GETPHONEBOOKSIZE 0x2 |
| |
| #define ORDER_TAG 0x01 |
| #define SEARCHVALUE_TAG 0x02 |
| #define SEARCHATTRIB_TAG 0x03 |
| #define MAXLISTCOUNT_TAG 0x04 |
| #define LISTSTARTOFFSET_TAG 0x05 |
| #define FILTER_TAG 0x06 |
| #define FORMAT_TAG 0X07 |
| #define PHONEBOOKSIZE_TAG 0X08 |
| #define NEWMISSEDCALLS_TAG 0X09 |
| #define PRIMARY_COUNTER_TAG 0X0A |
| #define SECONDARY_COUNTER_TAG 0X0B |
| #define DATABASEID_TAG 0X0D |
| #define SUPPORTED_FEATURES_TAG 0x10 |
| |
| #define DOWNLOAD_FEATURE 0x00000001 |
| #define BROWSE_FEATURE 0x00000002 |
| #define DATABASEID_FEATURE 0x00000004 |
| #define FOLDER_VERSION_FEATURE 0x00000008 |
| #define VCARD_SELECTING_FEATURE 0x00000010 |
| #define ENHANCED_CALLS_FEATURE 0x00000020 |
| #define UCI_FEATURE 0x00000040 |
| #define UID_FEATURE 0x00000080 |
| #define REFERENCING_FEATURE 0x00000100 |
| #define DEFAULT_IMAGE_FEATURE 0x00000200 |
| |
| static const char *filter_list[] = { |
| "VERSION", |
| "FN", |
| "N", |
| "PHOTO", |
| "BDAY", |
| "ADR", |
| "LABEL", |
| "TEL", |
| "EMAIL", |
| "MAILER", |
| "TZ", |
| "GEO", |
| "TITLE", |
| "ROLE", |
| "LOGO", |
| "AGENT", |
| "ORG", |
| "NOTE", |
| "REV", |
| "SOUND", |
| "URL", |
| "UID", |
| "KEY", |
| "NICKNAME", |
| "CATEGORIES", |
| "PROID", |
| "CLASS", |
| "SORT-STRING", |
| "X-IRMC-CALL-DATETIME", |
| "X-BT-SPEEDDIALKEY", |
| "X-BT-UCI", |
| "X-BT-UID", |
| NULL |
| }; |
| |
| #define FILTER_BIT_MAX 63 |
| #define FILTER_ALL 0xFFFFFFFFFFFFFFFFULL |
| |
| #define PBAP_INTERFACE "org.bluez.obex.PhonebookAccess1" |
| #define ERROR_INTERFACE "org.bluez.obex.Error" |
| #define PBAP_UUID "0000112f-0000-1000-8000-00805f9b34fb" |
| |
| struct pbap_data { |
| struct obc_session *session; |
| char *path; |
| uint16_t version; |
| uint32_t supported_features; |
| uint8_t databaseid[16]; |
| uint8_t primary[16]; |
| uint8_t secondary[16]; |
| }; |
| |
| struct pending_request { |
| struct pbap_data *pbap; |
| DBusMessage *msg; |
| }; |
| |
| static DBusConnection *conn = NULL; |
| |
| static struct pending_request *pending_request_new(struct pbap_data *pbap, |
| DBusMessage *message) |
| { |
| struct pending_request *p; |
| |
| p = g_new0(struct pending_request, 1); |
| p->pbap = pbap; |
| p->msg = dbus_message_ref(message); |
| |
| return p; |
| } |
| |
| static void pending_request_free(struct pending_request *p) |
| { |
| dbus_message_unref(p->msg); |
| g_free(p); |
| } |
| |
| static void listing_element(GMarkupParseContext *ctxt, |
| const char *element, |
| const char **names, |
| const char **values, |
| gpointer user_data, |
| GError **gerr) |
| { |
| DBusMessageIter *item = user_data, entry; |
| char **key; |
| const char *handle = NULL, *vcardname = NULL; |
| |
| if (g_str_equal(element, "card") != TRUE) |
| return; |
| |
| for (key = (char **) names; *key; key++, values++) { |
| if (g_str_equal(*key, "handle") == TRUE) |
| handle = *values; |
| else if (g_str_equal(*key, "name") == TRUE) |
| vcardname = *values; |
| } |
| |
| if (!handle || !vcardname) |
| return; |
| |
| dbus_message_iter_open_container(item, DBUS_TYPE_STRUCT, NULL, &entry); |
| dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &handle); |
| dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &vcardname); |
| dbus_message_iter_close_container(item, &entry); |
| } |
| |
| static const GMarkupParser listing_parser = { |
| listing_element, |
| NULL, |
| NULL, |
| NULL, |
| NULL |
| }; |
| |
| static char *build_phonebook_path(const char *location, const char *item) |
| { |
| char *path = NULL, *tmp, *tmp1; |
| gboolean internal = FALSE; |
| |
| if (!g_ascii_strcasecmp(location, "int") || |
| !g_ascii_strcasecmp(location, "internal")) { |
| path = g_strdup("/telecom"); |
| internal = TRUE; |
| } else if (!g_ascii_strncasecmp(location, "sim", 3)) { |
| if (strlen(location) == 3) |
| tmp = g_strdup("sim1"); |
| else |
| tmp = g_ascii_strup(location, 4); |
| |
| path = g_build_filename("/", tmp, "telecom", NULL); |
| g_free(tmp); |
| } else |
| return NULL; |
| |
| if (!g_ascii_strcasecmp(item, "pb") || |
| !g_ascii_strcasecmp(item, "ich") || |
| !g_ascii_strcasecmp(item, "och") || |
| !g_ascii_strcasecmp(item, "mch") || |
| !g_ascii_strcasecmp(item, "cch") || |
| (internal && !g_ascii_strcasecmp(item, "spd")) || |
| (internal && !g_ascii_strcasecmp(item, "fav"))) { |
| tmp = path; |
| tmp1 = g_ascii_strdown(item, -1); |
| path = g_build_filename(tmp, tmp1, NULL); |
| g_free(tmp); |
| g_free(tmp1); |
| } else { |
| g_free(path); |
| return NULL; |
| } |
| |
| return path; |
| } |
| |
| /* should only be called inside pbap_set_path */ |
| static void pbap_reset_path(struct pbap_data *pbap) |
| { |
| if (!pbap->path) |
| return; |
| |
| obc_session_setpath(pbap->session, pbap->path, NULL, NULL, NULL); |
| } |
| |
| static void pbap_setpath_cb(struct obc_session *session, |
| struct obc_transfer *transfer, |
| GError *err, void *user_data) |
| { |
| struct pending_request *request = user_data; |
| struct pbap_data *pbap = request->pbap; |
| |
| if (err != NULL) |
| pbap_reset_path(pbap); |
| else |
| g_dbus_emit_property_changed(conn, |
| obc_session_get_path(pbap->session), |
| PBAP_INTERFACE, "Folder"); |
| |
| if (err) { |
| DBusMessage *reply = g_dbus_create_error(request->msg, |
| ERROR_INTERFACE ".Failed", |
| "%s", err->message); |
| g_dbus_send_message(conn, reply); |
| } else |
| g_dbus_send_reply(conn, request->msg, DBUS_TYPE_INVALID); |
| |
| pending_request_free(request); |
| } |
| |
| static void read_version(struct pbap_data *pbap, GObexApparam *apparam) |
| { |
| const guint8 *data; |
| uint8_t value[16]; |
| gsize len; |
| |
| if (!(pbap->supported_features & FOLDER_VERSION_FEATURE)) |
| return; |
| |
| if (!g_obex_apparam_get_bytes(apparam, PRIMARY_COUNTER_TAG, &data, |
| &len)) { |
| len = sizeof(value); |
| memset(value, 0, len); |
| data = value; |
| } |
| |
| if (memcmp(pbap->primary, data, len)) { |
| memcpy(pbap->primary, data, len); |
| g_dbus_emit_property_changed(conn, |
| obc_session_get_path(pbap->session), |
| PBAP_INTERFACE, "PrimaryCounter"); |
| } |
| |
| if (!g_obex_apparam_get_bytes(apparam, SECONDARY_COUNTER_TAG, &data, |
| &len)) { |
| len = sizeof(value); |
| memset(value, 0, len); |
| data = value; |
| } |
| |
| if (memcmp(pbap->secondary, data, len)) { |
| memcpy(pbap->secondary, data, len); |
| g_dbus_emit_property_changed(conn, |
| obc_session_get_path(pbap->session), |
| PBAP_INTERFACE, "SecondaryCounter"); |
| } |
| } |
| |
| static void read_databaseid(struct pbap_data *pbap, GObexApparam *apparam) |
| { |
| const guint8 *data; |
| guint8 value[16]; |
| gsize len; |
| |
| if (!(pbap->supported_features & DATABASEID_FEATURE)) |
| return; |
| |
| if (!g_obex_apparam_get_bytes(apparam, DATABASEID_TAG, &data, &len)) { |
| len = sizeof(value); |
| memset(value, 0, len); |
| data = value; |
| } |
| |
| if (memcmp(data, pbap->databaseid, len)) { |
| memcpy(pbap->databaseid, data, len); |
| g_dbus_emit_property_changed(conn, |
| obc_session_get_path(pbap->session), |
| PBAP_INTERFACE, "DatabaseIdentifier"); |
| } |
| } |
| |
| static void read_return_apparam(struct obc_transfer *transfer, |
| struct pbap_data *pbap, |
| guint16 *phone_book_size, |
| guint8 *new_missed_calls) |
| { |
| GObexApparam *apparam; |
| |
| *phone_book_size = 0; |
| *new_missed_calls = 0; |
| |
| apparam = obc_transfer_get_apparam(transfer); |
| if (apparam == NULL) |
| return; |
| |
| g_obex_apparam_get_uint16(apparam, PHONEBOOKSIZE_TAG, |
| phone_book_size); |
| g_obex_apparam_get_uint8(apparam, NEWMISSEDCALLS_TAG, |
| new_missed_calls); |
| |
| read_version(pbap, apparam); |
| read_databaseid(pbap, apparam); |
| } |
| |
| static void phonebook_size_callback(struct obc_session *session, |
| struct obc_transfer *transfer, |
| GError *err, void *user_data) |
| { |
| struct pending_request *request = user_data; |
| DBusMessage *reply; |
| guint16 phone_book_size; |
| guint8 new_missed_calls; |
| |
| if (err) { |
| reply = g_dbus_create_error(request->msg, |
| ERROR_INTERFACE ".Failed", |
| "%s", err->message); |
| goto send; |
| } |
| |
| reply = dbus_message_new_method_return(request->msg); |
| |
| read_return_apparam(transfer, request->pbap, &phone_book_size, |
| &new_missed_calls); |
| |
| if (dbus_message_is_method_call(request->msg, PBAP_INTERFACE, |
| "GetSize")) |
| dbus_message_append_args(reply, |
| DBUS_TYPE_UINT16, &phone_book_size, |
| DBUS_TYPE_INVALID); |
| |
| send: |
| g_dbus_send_message(conn, reply); |
| pending_request_free(request); |
| } |
| |
| static void pull_vcard_listing_callback(struct obc_session *session, |
| struct obc_transfer *transfer, |
| GError *err, void *user_data) |
| { |
| struct pending_request *request = user_data; |
| GMarkupParseContext *ctxt; |
| DBusMessage *reply; |
| DBusMessageIter iter, array; |
| char *contents; |
| size_t size; |
| int perr; |
| |
| if (err) { |
| reply = g_dbus_create_error(request->msg, |
| ERROR_INTERFACE ".Failed", |
| "%s", err->message); |
| goto send; |
| } |
| |
| perr = obc_transfer_get_contents(transfer, &contents, &size); |
| if (perr < 0) { |
| reply = g_dbus_create_error(request->msg, |
| ERROR_INTERFACE ".Failed", |
| "Error reading contents: %s", |
| strerror(-perr)); |
| goto send; |
| } |
| |
| reply = dbus_message_new_method_return(request->msg); |
| |
| dbus_message_iter_init_append(reply, &iter); |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, |
| DBUS_STRUCT_BEGIN_CHAR_AS_STRING |
| DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_STRING_AS_STRING |
| DBUS_STRUCT_END_CHAR_AS_STRING, &array); |
| ctxt = g_markup_parse_context_new(&listing_parser, 0, &array, NULL); |
| g_markup_parse_context_parse(ctxt, contents, size, NULL); |
| g_markup_parse_context_free(ctxt); |
| dbus_message_iter_close_container(&iter, &array); |
| g_free(contents); |
| |
| send: |
| g_dbus_send_message(conn, reply); |
| pending_request_free(request); |
| } |
| |
| static GObexApparam *parse_format(GObexApparam *apparam, DBusMessageIter *iter) |
| { |
| const char *string; |
| guint8 format; |
| |
| if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) |
| return NULL; |
| |
| dbus_message_iter_get_basic(iter, &string); |
| |
| if (!string || g_str_equal(string, "")) |
| format = FORMAT_VCARD21; |
| else if (!g_ascii_strcasecmp(string, "vcard21")) |
| format = FORMAT_VCARD21; |
| else if (!g_ascii_strcasecmp(string, "vcard30")) |
| format = FORMAT_VCARD30; |
| else |
| return NULL; |
| |
| return g_obex_apparam_set_uint8(apparam, FORMAT_TAG, format); |
| } |
| |
| static GObexApparam *parse_order(GObexApparam *apparam, DBusMessageIter *iter) |
| { |
| const char *string; |
| guint8 order; |
| |
| if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) |
| return NULL; |
| |
| dbus_message_iter_get_basic(iter, &string); |
| |
| if (!string || g_str_equal(string, "")) |
| order = ORDER_INDEXED; |
| else if (!g_ascii_strcasecmp(string, "indexed")) |
| order = ORDER_INDEXED; |
| else if (!g_ascii_strcasecmp(string, "alphanumeric")) |
| order = ORDER_ALPHANUMERIC; |
| else if (!g_ascii_strcasecmp(string, "phonetic")) |
| order = ORDER_PHONETIC; |
| else |
| return NULL; |
| |
| return g_obex_apparam_set_uint8(apparam, ORDER_TAG, order); |
| } |
| |
| static GObexApparam *parse_offset(GObexApparam *apparam, DBusMessageIter *iter) |
| { |
| guint16 num; |
| |
| if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) |
| return NULL; |
| |
| dbus_message_iter_get_basic(iter, &num); |
| |
| return g_obex_apparam_set_uint16(apparam, LISTSTARTOFFSET_TAG, num); |
| } |
| |
| static GObexApparam *parse_max_count(GObexApparam *apparam, |
| DBusMessageIter *iter) |
| { |
| guint16 num; |
| |
| if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) |
| return NULL; |
| |
| dbus_message_iter_get_basic(iter, &num); |
| |
| return g_obex_apparam_set_uint16(apparam, MAXLISTCOUNT_TAG, num); |
| } |
| |
| static uint64_t get_filter_mask(const char *filterstr) |
| { |
| int i, bit = -1; |
| |
| if (!filterstr) |
| return 0; |
| |
| if (!g_ascii_strcasecmp(filterstr, "ALL")) |
| return FILTER_ALL; |
| |
| for (i = 0; filter_list[i] != NULL; i++) |
| if (!g_ascii_strcasecmp(filterstr, filter_list[i])) |
| return 1ULL << i; |
| |
| if (strlen(filterstr) < 4 || strlen(filterstr) > 5 |
| || g_ascii_strncasecmp(filterstr, "bit", 3) != 0) |
| return 0; |
| |
| sscanf(&filterstr[3], "%d", &bit); |
| if (bit >= 0 && bit <= FILTER_BIT_MAX) |
| return 1ULL << bit; |
| else |
| return 0; |
| } |
| |
| static int set_field(guint64 *filter, const char *filterstr) |
| { |
| guint64 mask; |
| |
| mask = get_filter_mask(filterstr); |
| |
| if (mask == 0) |
| return -EINVAL; |
| |
| *filter |= mask; |
| return 0; |
| } |
| |
| static GObexApparam *parse_fields(GObexApparam *apparam, DBusMessageIter *iter) |
| { |
| DBusMessageIter array; |
| guint64 filter = 0; |
| |
| if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) |
| return NULL; |
| |
| dbus_message_iter_recurse(iter, &array); |
| |
| while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) { |
| const char *string; |
| |
| dbus_message_iter_get_basic(&array, &string); |
| |
| if (set_field(&filter, string) < 0) |
| return NULL; |
| |
| dbus_message_iter_next(&array); |
| } |
| |
| return g_obex_apparam_set_uint64(apparam, FILTER_TAG, filter); |
| } |
| |
| static GObexApparam *parse_filters(GObexApparam *apparam, |
| DBusMessageIter *iter) |
| { |
| DBusMessageIter array; |
| |
| if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) |
| return NULL; |
| |
| dbus_message_iter_recurse(iter, &array); |
| |
| while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) { |
| const char *key; |
| DBusMessageIter value, entry; |
| |
| dbus_message_iter_recurse(&array, &entry); |
| dbus_message_iter_get_basic(&entry, &key); |
| |
| dbus_message_iter_next(&entry); |
| dbus_message_iter_recurse(&entry, &value); |
| |
| if (strcasecmp(key, "Format") == 0) { |
| if (parse_format(apparam, &value) == NULL) |
| return NULL; |
| } else if (strcasecmp(key, "Order") == 0) { |
| if (parse_order(apparam, &value) == NULL) |
| return NULL; |
| } else if (strcasecmp(key, "Offset") == 0) { |
| if (parse_offset(apparam, &value) == NULL) |
| return NULL; |
| } else if (strcasecmp(key, "MaxCount") == 0) { |
| if (parse_max_count(apparam, &value) == NULL) |
| return NULL; |
| } else if (strcasecmp(key, "Fields") == 0) { |
| if (parse_fields(apparam, &value) == NULL) |
| return NULL; |
| } |
| |
| dbus_message_iter_next(&array); |
| } |
| |
| return apparam; |
| } |
| |
| static DBusMessage *pull_phonebook(struct pbap_data *pbap, |
| DBusMessage *message, |
| guint8 type, |
| const char *targetfile, |
| GObexApparam *apparam) |
| { |
| struct pending_request *request; |
| struct obc_transfer *transfer; |
| char *name; |
| session_callback_t func; |
| DBusMessage *reply; |
| GError *err = NULL; |
| |
| name = g_strconcat(g_path_skip_root(pbap->path), ".vcf", NULL); |
| |
| transfer = obc_transfer_get("x-bt/phonebook", name, targetfile, &err); |
| if (transfer == NULL) { |
| g_obex_apparam_free(apparam); |
| goto fail; |
| } |
| |
| switch (type) { |
| case PULLPHONEBOOK: |
| func = NULL; |
| request = NULL; |
| break; |
| case GETPHONEBOOKSIZE: |
| func = phonebook_size_callback; |
| request = pending_request_new(pbap, message); |
| break; |
| default: |
| error("Unexpected type : 0x%2x", type); |
| return NULL; |
| } |
| |
| obc_transfer_set_apparam(transfer, apparam); |
| |
| if (!obc_session_queue(pbap->session, transfer, func, request, &err)) { |
| if (request != NULL) |
| pending_request_free(request); |
| |
| goto fail; |
| } |
| |
| g_free(name); |
| |
| if (targetfile == NULL) |
| return NULL; |
| |
| return obc_transfer_create_dbus_reply(transfer, message); |
| |
| fail: |
| g_free(name); |
| reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", |
| err->message); |
| g_error_free(err); |
| return reply; |
| } |
| |
| static DBusMessage *pull_vcard_listing(struct pbap_data *pbap, |
| DBusMessage *message, const char *name, |
| GObexApparam *apparam) |
| { |
| struct pending_request *request; |
| struct obc_transfer *transfer; |
| GError *err = NULL; |
| DBusMessage *reply; |
| |
| transfer = obc_transfer_get("x-bt/vcard-listing", name, NULL, &err); |
| if (transfer == NULL) { |
| g_obex_apparam_free(apparam); |
| goto fail; |
| } |
| |
| obc_transfer_set_apparam(transfer, apparam); |
| |
| request = pending_request_new(pbap, message); |
| if (obc_session_queue(pbap->session, transfer, |
| pull_vcard_listing_callback, request, &err)) |
| return NULL; |
| |
| pending_request_free(request); |
| |
| fail: |
| reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", |
| err->message); |
| g_error_free(err); |
| return reply; |
| } |
| |
| static DBusMessage *pbap_select(DBusConnection *connection, |
| DBusMessage *message, void *user_data) |
| { |
| struct pbap_data *pbap = user_data; |
| const char *item, *location; |
| char *path; |
| struct pending_request *request; |
| GError *err = NULL; |
| |
| if (dbus_message_get_args(message, NULL, |
| DBUS_TYPE_STRING, &location, |
| DBUS_TYPE_STRING, &item, |
| DBUS_TYPE_INVALID) == FALSE) |
| return g_dbus_create_error(message, |
| ERROR_INTERFACE ".InvalidArguments", NULL); |
| |
| path = build_phonebook_path(location, item); |
| if (path == NULL) |
| return g_dbus_create_error(message, |
| ERROR_INTERFACE ".InvalidArguments", |
| "Invalid path"); |
| |
| if (pbap->path != NULL && g_str_equal(pbap->path, path)) { |
| g_free(path); |
| return dbus_message_new_method_return(message); |
| } |
| |
| request = pending_request_new(pbap, message); |
| |
| obc_session_setpath(pbap->session, path, pbap_setpath_cb, request, |
| &err); |
| if (err != NULL) { |
| DBusMessage *reply; |
| reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", |
| "%s", err->message); |
| g_error_free(err); |
| g_free(path); |
| pending_request_free(request); |
| return reply; |
| } |
| |
| g_free(pbap->path); |
| pbap->path = path; |
| |
| return NULL; |
| } |
| |
| static DBusMessage *pbap_pull_all(DBusConnection *connection, |
| DBusMessage *message, void *user_data) |
| { |
| struct pbap_data *pbap = user_data; |
| const char *targetfile; |
| GObexApparam *apparam; |
| DBusMessageIter args; |
| |
| if (!pbap->path) |
| return g_dbus_create_error(message, |
| ERROR_INTERFACE ".Forbidden", |
| "Call Select first of all"); |
| |
| dbus_message_iter_init(message, &args); |
| |
| if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) |
| return g_dbus_create_error(message, |
| ERROR_INTERFACE ".InvalidArguments", NULL); |
| |
| dbus_message_iter_get_basic(&args, &targetfile); |
| dbus_message_iter_next(&args); |
| |
| apparam = g_obex_apparam_set_uint16(NULL, MAXLISTCOUNT_TAG, |
| DEFAULT_COUNT); |
| apparam = g_obex_apparam_set_uint16(apparam, LISTSTARTOFFSET_TAG, |
| DEFAULT_OFFSET); |
| |
| if (parse_filters(apparam, &args) == NULL) { |
| g_obex_apparam_free(apparam); |
| return g_dbus_create_error(message, |
| ERROR_INTERFACE ".InvalidArguments", NULL); |
| } |
| |
| return pull_phonebook(pbap, message, PULLPHONEBOOK, targetfile, |
| apparam); |
| } |
| |
| static DBusMessage *pull_vcard(struct pbap_data *pbap, DBusMessage *message, |
| const char *name, const char *targetfile, |
| GObexApparam *apparam) |
| { |
| struct obc_transfer *transfer; |
| DBusMessage *reply; |
| GError *err = NULL; |
| |
| transfer = obc_transfer_get("x-bt/vcard", name, targetfile, &err); |
| if (transfer == NULL) { |
| g_obex_apparam_free(apparam); |
| goto fail; |
| } |
| |
| obc_transfer_set_apparam(transfer, apparam); |
| |
| if (!obc_session_queue(pbap->session, transfer, NULL, NULL, &err)) |
| goto fail; |
| |
| return obc_transfer_create_dbus_reply(transfer, message); |
| |
| fail: |
| reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", |
| err->message); |
| g_error_free(err); |
| return reply; |
| } |
| |
| static DBusMessage *pbap_pull_vcard(DBusConnection *connection, |
| DBusMessage *message, void *user_data) |
| { |
| struct pbap_data *pbap = user_data; |
| GObexApparam *apparam; |
| const char *name, *targetfile; |
| DBusMessageIter args; |
| |
| if (!pbap->path) |
| return g_dbus_create_error(message, |
| ERROR_INTERFACE ".Forbidden", |
| "Call Select first of all"); |
| |
| dbus_message_iter_init(message, &args); |
| |
| if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) |
| return g_dbus_create_error(message, |
| ERROR_INTERFACE ".InvalidArguments", NULL); |
| |
| dbus_message_iter_get_basic(&args, &name); |
| dbus_message_iter_next(&args); |
| |
| if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) |
| return g_dbus_create_error(message, |
| ERROR_INTERFACE ".InvalidArguments", NULL); |
| |
| dbus_message_iter_get_basic(&args, &targetfile); |
| dbus_message_iter_next(&args); |
| |
| apparam = g_obex_apparam_set_uint16(NULL, MAXLISTCOUNT_TAG, |
| DEFAULT_COUNT); |
| apparam = g_obex_apparam_set_uint16(apparam, LISTSTARTOFFSET_TAG, |
| DEFAULT_OFFSET); |
| |
| if (parse_filters(apparam, &args) == NULL) { |
| g_obex_apparam_free(apparam); |
| return g_dbus_create_error(message, |
| ERROR_INTERFACE ".InvalidArguments", NULL); |
| } |
| |
| return pull_vcard(pbap, message, name, targetfile, apparam); |
| } |
| |
| static DBusMessage *pbap_list(DBusConnection *connection, |
| DBusMessage *message, void *user_data) |
| { |
| struct pbap_data *pbap = user_data; |
| GObexApparam *apparam; |
| DBusMessageIter args; |
| |
| if (!pbap->path) |
| return g_dbus_create_error(message, |
| ERROR_INTERFACE ".Forbidden", |
| "Call Select first of all"); |
| |
| dbus_message_iter_init(message, &args); |
| |
| apparam = g_obex_apparam_set_uint16(NULL, MAXLISTCOUNT_TAG, |
| DEFAULT_COUNT); |
| apparam = g_obex_apparam_set_uint16(apparam, LISTSTARTOFFSET_TAG, |
| DEFAULT_OFFSET); |
| |
| if (parse_filters(apparam, &args) == NULL) { |
| g_obex_apparam_free(apparam); |
| return g_dbus_create_error(message, |
| ERROR_INTERFACE ".InvalidArguments", NULL); |
| } |
| |
| return pull_vcard_listing(pbap, message, "", apparam); |
| } |
| |
| static GObexApparam *parse_attribute(GObexApparam *apparam, const char *field) |
| { |
| guint8 attrib; |
| |
| if (!field || g_str_equal(field, "")) |
| attrib = ATTRIB_NAME; |
| else if (!g_ascii_strcasecmp(field, "name")) |
| attrib = ATTRIB_NAME; |
| else if (!g_ascii_strcasecmp(field, "number")) |
| attrib = ATTRIB_NUMBER; |
| else if (!g_ascii_strcasecmp(field, "sound")) |
| attrib = ATTRIB_SOUND; |
| else |
| return NULL; |
| |
| return g_obex_apparam_set_uint8(apparam, SEARCHATTRIB_TAG, attrib); |
| } |
| |
| static DBusMessage *pbap_search(DBusConnection *connection, |
| DBusMessage *message, void *user_data) |
| { |
| struct pbap_data *pbap = user_data; |
| char *field, *value; |
| GObexApparam *apparam; |
| DBusMessageIter args; |
| |
| if (!pbap->path) |
| return g_dbus_create_error(message, |
| ERROR_INTERFACE ".Forbidden", |
| "Call Select first of all"); |
| |
| dbus_message_iter_init(message, &args); |
| |
| if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) |
| return g_dbus_create_error(message, |
| ERROR_INTERFACE ".InvalidArguments", NULL); |
| |
| dbus_message_iter_get_basic(&args, &field); |
| dbus_message_iter_next(&args); |
| |
| apparam = parse_attribute(NULL, field); |
| if (apparam == NULL) |
| return g_dbus_create_error(message, |
| ERROR_INTERFACE ".InvalidArguments", NULL); |
| |
| if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) |
| return g_dbus_create_error(message, |
| ERROR_INTERFACE ".InvalidArguments", NULL); |
| |
| dbus_message_iter_get_basic(&args, &value); |
| dbus_message_iter_next(&args); |
| |
| apparam = g_obex_apparam_set_uint16(apparam, MAXLISTCOUNT_TAG, |
| DEFAULT_COUNT); |
| apparam = g_obex_apparam_set_uint16(apparam, LISTSTARTOFFSET_TAG, |
| DEFAULT_OFFSET); |
| apparam = g_obex_apparam_set_string(apparam, SEARCHVALUE_TAG, value); |
| |
| if (parse_filters(apparam, &args) == NULL) { |
| g_obex_apparam_free(apparam); |
| return g_dbus_create_error(message, |
| ERROR_INTERFACE ".InvalidArguments", NULL); |
| } |
| |
| return pull_vcard_listing(pbap, message, "", apparam); |
| } |
| |
| static DBusMessage *pbap_get_size(DBusConnection *connection, |
| DBusMessage *message, void *user_data) |
| { |
| struct pbap_data *pbap = user_data; |
| GObexApparam *apparam; |
| DBusMessageIter args; |
| |
| if (!pbap->path) |
| return g_dbus_create_error(message, |
| ERROR_INTERFACE ".Forbidden", |
| "Call Select first of all"); |
| |
| dbus_message_iter_init(message, &args); |
| |
| apparam = g_obex_apparam_set_uint16(NULL, MAXLISTCOUNT_TAG, 0); |
| apparam = g_obex_apparam_set_uint16(apparam, LISTSTARTOFFSET_TAG, |
| DEFAULT_OFFSET); |
| |
| return pull_phonebook(pbap, message, GETPHONEBOOKSIZE, NULL, apparam); |
| } |
| |
| static char **get_filter_strs(uint64_t filter, int *size) |
| { |
| char **list, **item; |
| int i; |
| |
| list = g_malloc0(sizeof(char **) * (FILTER_BIT_MAX + 2)); |
| |
| item = list; |
| |
| for (i = 0; filter_list[i] != NULL; i++) |
| if (filter & (1ULL << i)) |
| *(item++) = g_strdup(filter_list[i]); |
| |
| for (; i <= FILTER_BIT_MAX; i++) |
| if (filter & (1ULL << i)) |
| *(item++) = g_strdup_printf("%s%d", "BIT", i); |
| |
| *item = NULL; |
| *size = item - list; |
| return list; |
| } |
| |
| static DBusMessage *pbap_list_filter_fields(DBusConnection *connection, |
| DBusMessage *message, void *user_data) |
| { |
| char **filters = NULL; |
| int size; |
| DBusMessage *reply; |
| |
| filters = get_filter_strs(FILTER_ALL, &size); |
| reply = dbus_message_new_method_return(message); |
| dbus_message_append_args(reply, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_STRING, &filters, size, |
| DBUS_TYPE_INVALID); |
| |
| g_strfreev(filters); |
| return reply; |
| } |
| |
| static DBusMessage *pbap_update_version(DBusConnection *connection, |
| DBusMessage *message, void *user_data) |
| { |
| struct pbap_data *pbap = user_data; |
| |
| if (!(pbap->supported_features & FOLDER_VERSION_FEATURE)) |
| return g_dbus_create_error(message, |
| ERROR_INTERFACE ".NotSupported", |
| "Operation is not supported"); |
| |
| return pbap_get_size(connection, message, user_data); |
| } |
| |
| static const GDBusMethodTable pbap_methods[] = { |
| { GDBUS_ASYNC_METHOD("Select", |
| GDBUS_ARGS({ "location", "s" }, { "phonebook", "s" }), |
| NULL, pbap_select) }, |
| { GDBUS_METHOD("PullAll", |
| GDBUS_ARGS({ "targetfile", "s" }, |
| { "filters", "a{sv}" }), |
| GDBUS_ARGS({ "transfer", "o" }, |
| { "properties", "a{sv}" }), |
| pbap_pull_all) }, |
| { GDBUS_METHOD("Pull", |
| GDBUS_ARGS({ "vcard", "s" }, { "targetfile", "s" }, |
| { "filters", "a{sv}" }), |
| GDBUS_ARGS({ "transfer", "o" }, |
| { "properties", "a{sv}" }), |
| pbap_pull_vcard) }, |
| { GDBUS_ASYNC_METHOD("List", |
| GDBUS_ARGS({ "filters", "a{sv}" }), |
| GDBUS_ARGS({ "vcard_listing", "a(ss)" }), |
| pbap_list) }, |
| { GDBUS_ASYNC_METHOD("Search", |
| GDBUS_ARGS({ "field", "s" }, { "value", "s" }, |
| { "filters", "a{sv}" }), |
| GDBUS_ARGS({ "vcard_listing", "a(ss)" }), |
| pbap_search) }, |
| { GDBUS_ASYNC_METHOD("GetSize", |
| NULL, GDBUS_ARGS({ "size", "q" }), |
| pbap_get_size) }, |
| { GDBUS_METHOD("ListFilterFields", |
| NULL, GDBUS_ARGS({ "fields", "as" }), |
| pbap_list_filter_fields) }, |
| { GDBUS_ASYNC_METHOD("UpdateVersion", NULL, NULL, |
| pbap_update_version) }, |
| { } |
| }; |
| |
| static gboolean folder_exists(const GDBusPropertyTable *property, void *data) |
| { |
| struct pbap_data *pbap = data; |
| |
| return pbap->path != NULL; |
| } |
| |
| static gboolean get_folder(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct pbap_data *pbap = data; |
| |
| if (!pbap->path) |
| return FALSE; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &pbap->path); |
| |
| return TRUE; |
| } |
| |
| static gboolean databaseid_exists(const GDBusPropertyTable *property, |
| void *data) |
| { |
| struct pbap_data *pbap = data; |
| |
| return pbap->supported_features & DATABASEID_FEATURE; |
| } |
| |
| static int u128_to_string(uint8_t *data, char *str, size_t len) |
| { |
| return snprintf(str, len, "%02X%02X%02X%02X%02X%02X%02X%02X" |
| "%02X%02X%02X%02X%02X%02X%02X%02X", |
| data[0], data[1], data[2], data[3], |
| data[3], data[5], data[6], data[7], |
| data[8], data[9], data[10], data[11], |
| data[12], data[13], data[14], data[15]); |
| } |
| |
| static gboolean get_databaseid(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct pbap_data *pbap = data; |
| char value[33]; |
| const char *pvalue = value; |
| |
| if (u128_to_string(pbap->databaseid, value, sizeof(value)) < 0) |
| return FALSE; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &pvalue); |
| |
| return TRUE; |
| } |
| |
| static gboolean version_exists(const GDBusPropertyTable *property, |
| void *data) |
| { |
| struct pbap_data *pbap = data; |
| |
| return pbap->supported_features & FOLDER_VERSION_FEATURE; |
| } |
| |
| static gboolean get_primary(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct pbap_data *pbap = data; |
| char value[33]; |
| const char *pvalue = value; |
| |
| if (u128_to_string(pbap->primary, value, sizeof(value)) < 0) |
| return FALSE; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &pvalue); |
| |
| return TRUE; |
| } |
| |
| static gboolean get_secondary(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct pbap_data *pbap = data; |
| char value[33]; |
| const char *pvalue = value; |
| |
| if (u128_to_string(pbap->secondary, value, sizeof(value)) < 0) |
| return FALSE; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &pvalue); |
| |
| return TRUE; |
| } |
| |
| static gboolean image_size_exists(const GDBusPropertyTable *property, |
| void *data) |
| { |
| struct pbap_data *pbap = data; |
| |
| return pbap->supported_features & DEFAULT_IMAGE_FEATURE; |
| } |
| |
| static gboolean get_image_size(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| dbus_bool_t value = TRUE; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); |
| |
| return TRUE; |
| } |
| |
| static const GDBusPropertyTable pbap_properties[] = { |
| { "Folder", "s", get_folder, NULL, folder_exists }, |
| { "DatabaseIdentifier", "s", get_databaseid, NULL, databaseid_exists }, |
| { "PrimaryCounter", "s", get_primary, NULL, version_exists }, |
| { "SecondaryCounter", "s", get_secondary, NULL, version_exists }, |
| { "FixedImageSize", "b", get_image_size, NULL, image_size_exists }, |
| { } |
| }; |
| |
| static void pbap_free(void *data) |
| { |
| struct pbap_data *pbap = data; |
| |
| obc_session_unref(pbap->session); |
| g_free(pbap->path); |
| g_free(pbap); |
| } |
| |
| static void parse_service_record(struct pbap_data *pbap) |
| { |
| const void *data; |
| |
| /* Version */ |
| data = obc_session_get_attribute(pbap->session, |
| SDP_ATTR_PFILE_DESC_LIST); |
| if (!data) |
| return; |
| |
| pbap->version = GPOINTER_TO_UINT(data); |
| |
| /* |
| * If the PbapSupportedFeatures attribute is not present |
| * 0x00000003 shall be assumed for a remote PSE. |
| */ |
| pbap->supported_features = 0x00000003; |
| |
| if (pbap->version < 0x0102) |
| return; |
| |
| /* Supported Feature Bits */ |
| data = obc_session_get_attribute(pbap->session, |
| SDP_ATTR_PBAP_SUPPORTED_FEATURES); |
| if (data) |
| pbap->supported_features = *(uint32_t *) data; |
| |
| } |
| |
| static void *pbap_supported_features(struct obc_session *session) |
| { |
| const void *data; |
| uint16_t version; |
| |
| /* Version */ |
| data = obc_session_get_attribute(session, SDP_ATTR_PFILE_DESC_LIST); |
| if (!data) |
| return NULL; |
| |
| version = GPOINTER_TO_UINT(data); |
| |
| if (version < 0x0102) |
| return NULL; |
| |
| /* Supported Feature Bits */ |
| data = obc_session_get_attribute(session, |
| SDP_ATTR_PBAP_SUPPORTED_FEATURES); |
| if (!data) |
| return NULL; |
| |
| return g_obex_apparam_set_uint32(NULL, SUPPORTED_FEATURES_TAG, |
| DOWNLOAD_FEATURE | |
| BROWSE_FEATURE | |
| DATABASEID_FEATURE | |
| FOLDER_VERSION_FEATURE | |
| VCARD_SELECTING_FEATURE | |
| ENHANCED_CALLS_FEATURE | |
| UCI_FEATURE | |
| UID_FEATURE | |
| REFERENCING_FEATURE | |
| DEFAULT_IMAGE_FEATURE); |
| } |
| |
| static int pbap_probe(struct obc_session *session) |
| { |
| struct pbap_data *pbap; |
| const char *path; |
| |
| path = obc_session_get_path(session); |
| |
| DBG("%s", path); |
| |
| pbap = g_try_new0(struct pbap_data, 1); |
| if (!pbap) |
| return -ENOMEM; |
| |
| pbap->session = obc_session_ref(session); |
| |
| parse_service_record(pbap); |
| |
| DBG("%s, version 0x%04x supported features 0x%08x", path, pbap->version, |
| pbap->supported_features); |
| |
| if (!g_dbus_register_interface(conn, path, PBAP_INTERFACE, pbap_methods, |
| NULL, pbap_properties, pbap, |
| pbap_free)) { |
| pbap_free(pbap); |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| static void pbap_remove(struct obc_session *session) |
| { |
| const char *path = obc_session_get_path(session); |
| |
| DBG("%s", path); |
| |
| g_dbus_unregister_interface(conn, path, PBAP_INTERFACE); |
| } |
| |
| static struct obc_driver pbap = { |
| .service = "PBAP", |
| .uuid = PBAP_UUID, |
| .target = OBEX_PBAP_UUID, |
| .target_len = OBEX_PBAP_UUID_LEN, |
| .supported_features = pbap_supported_features, |
| .probe = pbap_probe, |
| .remove = pbap_remove |
| }; |
| |
| int pbap_init(void) |
| { |
| int err; |
| |
| DBG(""); |
| |
| conn = dbus_bus_get(DBUS_BUS_SESSION, NULL); |
| if (!conn) |
| return -EIO; |
| |
| err = obc_driver_register(&pbap); |
| if (err < 0) { |
| dbus_connection_unref(conn); |
| conn = NULL; |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| void pbap_exit(void) |
| { |
| DBG(""); |
| |
| dbus_connection_unref(conn); |
| conn = NULL; |
| |
| obc_driver_unregister(&pbap); |
| } |