| /* |
| * |
| * OBEX Server |
| * |
| * Copyright (C) 2009-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 <stdio.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <glib.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <arpa/inet.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| |
| #include "gobex/gobex.h" |
| #include "gobex/gobex-apparam.h" |
| |
| #include "obexd/src/obexd.h" |
| #include "obexd/src/plugin.h" |
| #include "obexd/src/log.h" |
| #include "obexd/src/obex.h" |
| #include "obexd/src/service.h" |
| #include "obexd/src/manager.h" |
| #include "obexd/src/mimetype.h" |
| #include "phonebook.h" |
| #include "filesystem.h" |
| |
| #define PHONEBOOK_TYPE "x-bt/phonebook" |
| #define VCARDLISTING_TYPE "x-bt/vcard-listing" |
| #define VCARDENTRY_TYPE "x-bt/vcard" |
| |
| #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 |
| |
| struct cache { |
| gboolean valid; |
| uint32_t index; |
| GSList *entries; |
| }; |
| |
| struct cache_entry { |
| uint32_t handle; |
| char *id; |
| char *name; |
| char *sound; |
| char *tel; |
| }; |
| |
| struct pbap_session { |
| struct apparam_field *params; |
| char *folder; |
| uint32_t find_handle; |
| struct cache cache; |
| struct pbap_object *obj; |
| }; |
| |
| struct pbap_object { |
| GString *buffer; |
| GObexApparam *apparam; |
| gboolean firstpacket; |
| gboolean lastpart; |
| struct pbap_session *session; |
| void *request; |
| }; |
| |
| static const uint8_t PBAP_TARGET[TARGET_SIZE] = { |
| 0x79, 0x61, 0x35, 0xF0, 0xF0, 0xC5, 0x11, 0xD8, |
| 0x09, 0x66, 0x08, 0x00, 0x20, 0x0C, 0x9A, 0x66 }; |
| |
| typedef int (*cache_entry_find_f) (const struct cache_entry *entry, |
| const char *value); |
| |
| static void cache_entry_free(void *data) |
| { |
| struct cache_entry *entry = data; |
| |
| g_free(entry->id); |
| g_free(entry->name); |
| g_free(entry->sound); |
| g_free(entry->tel); |
| g_free(entry); |
| } |
| |
| static gboolean entry_name_find(const struct cache_entry *entry, |
| const char *value) |
| { |
| char *name; |
| gboolean ret; |
| |
| if (!entry->name) |
| return FALSE; |
| |
| if (strlen(value) == 0) |
| return TRUE; |
| |
| name = g_utf8_strdown(entry->name, -1); |
| ret = (g_strstr_len(name, -1, value) ? TRUE : FALSE); |
| g_free(name); |
| |
| return ret; |
| } |
| |
| static gboolean entry_sound_find(const struct cache_entry *entry, |
| const char *value) |
| { |
| if (!entry->sound) |
| return FALSE; |
| |
| return (g_strstr_len(entry->sound, -1, value) ? TRUE : FALSE); |
| } |
| |
| static gboolean entry_tel_find(const struct cache_entry *entry, |
| const char *value) |
| { |
| if (!entry->tel) |
| return FALSE; |
| |
| return (g_strstr_len(entry->tel, -1, value) ? TRUE : FALSE); |
| } |
| |
| static const char *cache_find(struct cache *cache, uint32_t handle) |
| { |
| GSList *l; |
| |
| for (l = cache->entries; l; l = l->next) { |
| struct cache_entry *entry = l->data; |
| |
| if (entry->handle == handle) |
| return entry->id; |
| } |
| |
| return NULL; |
| } |
| |
| static void cache_clear(struct cache *cache) |
| { |
| g_slist_free_full(cache->entries, cache_entry_free); |
| cache->entries = NULL; |
| } |
| |
| static void phonebook_size_result(const char *buffer, size_t bufsize, |
| int vcards, int missed, |
| gboolean lastpart, void *user_data) |
| { |
| struct pbap_session *pbap = user_data; |
| uint16_t phonebooksize; |
| |
| if (pbap->obj->request) { |
| phonebook_req_finalize(pbap->obj->request); |
| pbap->obj->request = NULL; |
| } |
| |
| if (vcards < 0) |
| vcards = 0; |
| |
| DBG("vcards %d", vcards); |
| |
| phonebooksize = vcards; |
| |
| pbap->obj->apparam = g_obex_apparam_set_uint16(NULL, PHONEBOOKSIZE_TAG, |
| phonebooksize); |
| |
| pbap->obj->firstpacket = TRUE; |
| |
| if (missed > 0) { |
| DBG("missed %d", missed); |
| |
| pbap->obj->apparam = g_obex_apparam_set_uint16( |
| pbap->obj->apparam, |
| NEWMISSEDCALLS_TAG, |
| missed); |
| } |
| |
| obex_object_set_io_flags(pbap->obj, G_IO_IN, 0); |
| } |
| |
| static void query_result(const char *buffer, size_t bufsize, int vcards, |
| int missed, gboolean lastpart, void *user_data) |
| { |
| struct pbap_session *pbap = user_data; |
| |
| DBG(""); |
| |
| if (pbap->obj->request && lastpart) { |
| phonebook_req_finalize(pbap->obj->request); |
| pbap->obj->request = NULL; |
| } |
| |
| pbap->obj->lastpart = lastpart; |
| |
| if (vcards < 0) { |
| obex_object_set_io_flags(pbap->obj, G_IO_ERR, -ENOENT); |
| return; |
| } |
| |
| if (!pbap->obj->buffer) |
| pbap->obj->buffer = g_string_new_len(buffer, bufsize); |
| else |
| pbap->obj->buffer = g_string_append_len(pbap->obj->buffer, |
| buffer, bufsize); |
| |
| if (missed > 0) { |
| DBG("missed %d", missed); |
| |
| pbap->obj->firstpacket = TRUE; |
| |
| pbap->obj->apparam = g_obex_apparam_set_uint16( |
| pbap->obj->apparam, |
| NEWMISSEDCALLS_TAG, |
| missed); |
| } |
| |
| obex_object_set_io_flags(pbap->obj, G_IO_IN, 0); |
| } |
| |
| static void cache_entry_notify(const char *id, uint32_t handle, |
| const char *name, const char *sound, |
| const char *tel, void *user_data) |
| { |
| struct pbap_session *pbap = user_data; |
| struct cache_entry *entry = g_new0(struct cache_entry, 1); |
| struct cache *cache = &pbap->cache; |
| |
| if (handle != PHONEBOOK_INVALID_HANDLE) |
| entry->handle = handle; |
| else |
| entry->handle = ++pbap->cache.index; |
| |
| entry->id = g_strdup(id); |
| entry->name = g_strdup(name); |
| entry->sound = g_strdup(sound); |
| entry->tel = g_strdup(tel); |
| |
| cache->entries = g_slist_append(cache->entries, entry); |
| } |
| |
| static int alpha_sort(gconstpointer a, gconstpointer b) |
| { |
| const struct cache_entry *e1 = a; |
| const struct cache_entry *e2 = b; |
| |
| return g_strcmp0(e1->name, e2->name); |
| } |
| |
| static int indexed_sort(gconstpointer a, gconstpointer b) |
| { |
| const struct cache_entry *e1 = a; |
| const struct cache_entry *e2 = b; |
| |
| return (e1->handle - e2->handle); |
| } |
| |
| static int phonetical_sort(gconstpointer a, gconstpointer b) |
| { |
| const struct cache_entry *e1 = a; |
| const struct cache_entry *e2 = b; |
| |
| /* SOUND attribute is optional. Use Indexed sort if not present. */ |
| if (!e1->sound || !e2->sound) |
| return indexed_sort(a, b); |
| |
| return g_strcmp0(e1->sound, e2->sound); |
| } |
| |
| static GSList *sort_entries(GSList *l, uint8_t order, uint8_t search_attrib, |
| const char *value) |
| { |
| GSList *sorted = NULL; |
| cache_entry_find_f find; |
| GCompareFunc sort; |
| char *searchval; |
| |
| /* |
| * Default sorter is "Indexed". Some backends doesn't inform the index, |
| * for this case a sequential internal index is assigned. |
| * 0x00 = indexed |
| * 0x01 = alphanumeric |
| * 0x02 = phonetic |
| */ |
| switch (order) { |
| case 0x01: |
| sort = alpha_sort; |
| break; |
| case 0x02: |
| sort = phonetical_sort; |
| break; |
| default: |
| sort = indexed_sort; |
| break; |
| } |
| |
| /* |
| * This implementation checks if the given field CONTAINS the |
| * search value(case insensitive). Name is the default field |
| * when the attribute is not provided. |
| */ |
| switch (search_attrib) { |
| /* Number */ |
| case 1: |
| find = entry_tel_find; |
| break; |
| /* Sound */ |
| case 2: |
| find = entry_sound_find; |
| break; |
| default: |
| find = entry_name_find; |
| break; |
| } |
| |
| searchval = value ? g_utf8_strdown(value, -1) : NULL; |
| for (; l; l = l->next) { |
| struct cache_entry *entry = l->data; |
| |
| if (searchval && !find(entry, (const char *) searchval)) |
| continue; |
| |
| sorted = g_slist_insert_sorted(sorted, entry, sort); |
| } |
| |
| g_free(searchval); |
| |
| return sorted; |
| } |
| |
| static int generate_response(void *user_data) |
| { |
| struct pbap_session *pbap = user_data; |
| GSList *sorted; |
| GSList *l; |
| uint16_t max = pbap->params->maxlistcount; |
| |
| DBG(""); |
| |
| if (max == 0) { |
| /* Ignore all other parameter and return PhoneBookSize */ |
| uint16_t size = g_slist_length(pbap->cache.entries); |
| |
| pbap->obj->apparam = g_obex_apparam_set_uint16( |
| pbap->obj->apparam, |
| PHONEBOOKSIZE_TAG, |
| size); |
| |
| return 0; |
| } |
| |
| /* |
| * Don't free the sorted list content: this list contains |
| * only the reference for the "real" cache entry. |
| */ |
| sorted = sort_entries(pbap->cache.entries, pbap->params->order, |
| pbap->params->searchattrib, |
| (const char *) pbap->params->searchval); |
| |
| /* Computing offset considering first entry of the phonebook */ |
| l = g_slist_nth(sorted, pbap->params->liststartoffset); |
| |
| pbap->obj->buffer = g_string_new(VCARD_LISTING_BEGIN); |
| for (; l && max; l = l->next, max--) { |
| const struct cache_entry *entry = l->data; |
| char *escaped_name = g_markup_escape_text(entry->name, -1); |
| |
| g_string_append_printf(pbap->obj->buffer, |
| VCARD_LISTING_ELEMENT, entry->handle, escaped_name); |
| |
| g_free(escaped_name); |
| } |
| |
| pbap->obj->buffer = g_string_append(pbap->obj->buffer, |
| VCARD_LISTING_END); |
| g_slist_free(sorted); |
| |
| return 0; |
| } |
| |
| static void cache_ready_notify(void *user_data) |
| { |
| struct pbap_session *pbap = user_data; |
| |
| DBG(""); |
| |
| phonebook_req_finalize(pbap->obj->request); |
| pbap->obj->request = NULL; |
| |
| pbap->cache.valid = TRUE; |
| |
| generate_response(pbap); |
| obex_object_set_io_flags(pbap->obj, G_IO_IN, 0); |
| } |
| |
| static void cache_entry_done(void *user_data) |
| { |
| struct pbap_session *pbap = user_data; |
| const char *id; |
| int ret; |
| |
| DBG(""); |
| |
| pbap->cache.valid = TRUE; |
| |
| id = cache_find(&pbap->cache, pbap->find_handle); |
| if (id == NULL) { |
| DBG("Entry %d not found on cache", pbap->find_handle); |
| obex_object_set_io_flags(pbap->obj, G_IO_ERR, -ENOENT); |
| return; |
| } |
| |
| phonebook_req_finalize(pbap->obj->request); |
| pbap->obj->request = phonebook_get_entry(pbap->folder, id, |
| pbap->params, query_result, pbap, &ret); |
| if (ret < 0) |
| obex_object_set_io_flags(pbap->obj, G_IO_ERR, ret); |
| } |
| |
| static struct apparam_field *parse_aparam(const uint8_t *buffer, uint32_t hlen) |
| { |
| GObexApparam *apparam; |
| struct apparam_field *param; |
| |
| apparam = g_obex_apparam_decode(buffer, hlen); |
| if (apparam == NULL) |
| return NULL; |
| |
| param = g_new0(struct apparam_field, 1); |
| |
| /* |
| * As per spec when client doesn't include MAXLISTCOUNT_TAG then it |
| * should be assume as Maximum value in vcardlisting 65535 |
| */ |
| param->maxlistcount = UINT16_MAX; |
| |
| g_obex_apparam_get_uint8(apparam, ORDER_TAG, ¶m->order); |
| g_obex_apparam_get_uint8(apparam, SEARCHATTRIB_TAG, |
| ¶m->searchattrib); |
| g_obex_apparam_get_uint8(apparam, FORMAT_TAG, ¶m->format); |
| g_obex_apparam_get_uint16(apparam, MAXLISTCOUNT_TAG, |
| ¶m->maxlistcount); |
| g_obex_apparam_get_uint16(apparam, LISTSTARTOFFSET_TAG, |
| ¶m->liststartoffset); |
| g_obex_apparam_get_uint64(apparam, FILTER_TAG, ¶m->filter); |
| param->searchval = g_obex_apparam_get_string(apparam, SEARCHVALUE_TAG); |
| |
| DBG("o %x sa %x sv %s fil %" G_GINT64_MODIFIER "x for %x max %x off %x", |
| param->order, param->searchattrib, param->searchval, |
| param->filter, param->format, param->maxlistcount, |
| param->liststartoffset); |
| |
| g_obex_apparam_free(apparam); |
| |
| return param; |
| } |
| |
| static void *pbap_connect(struct obex_session *os, int *err) |
| { |
| struct pbap_session *pbap; |
| |
| manager_register_session(os); |
| |
| pbap = g_new0(struct pbap_session, 1); |
| pbap->folder = g_strdup("/"); |
| pbap->find_handle = PHONEBOOK_INVALID_HANDLE; |
| |
| if (err) |
| *err = 0; |
| |
| return pbap; |
| } |
| |
| static int pbap_get(struct obex_session *os, void *user_data) |
| { |
| struct pbap_session *pbap = user_data; |
| const char *type = obex_get_type(os); |
| const char *name = obex_get_name(os); |
| struct apparam_field *params; |
| const uint8_t *buffer; |
| char *path; |
| ssize_t rsize; |
| int ret; |
| |
| DBG("name %s type %s pbap %p", name, type, pbap); |
| |
| if (type == NULL) |
| return -EBADR; |
| |
| rsize = obex_get_apparam(os, &buffer); |
| if (rsize < 0) { |
| if (g_ascii_strcasecmp(type, VCARDENTRY_TYPE) != 0) |
| return -EBADR; |
| |
| rsize = 0; |
| } |
| |
| params = parse_aparam(buffer, rsize); |
| if (params == NULL) |
| return -EBADR; |
| |
| if (pbap->params) { |
| g_free(pbap->params->searchval); |
| g_free(pbap->params); |
| } |
| |
| pbap->params = params; |
| |
| if (g_ascii_strcasecmp(type, PHONEBOOK_TYPE) == 0) { |
| /* Always contains the absolute path */ |
| if (g_path_is_absolute(name)) |
| path = g_strdup(name); |
| else |
| path = g_build_filename("/", name, NULL); |
| |
| } else if (g_ascii_strcasecmp(type, VCARDLISTING_TYPE) == 0) { |
| /* Always relative */ |
| if (!name || strlen(name) == 0) { |
| /* Current folder */ |
| path = g_strdup(pbap->folder); |
| } else { |
| /* Current folder + relative path */ |
| path = g_build_filename(pbap->folder, name, NULL); |
| |
| /* clear cache */ |
| pbap->cache.valid = FALSE; |
| pbap->cache.index = 0; |
| cache_clear(&pbap->cache); |
| } |
| } else if (g_ascii_strcasecmp(type, VCARDENTRY_TYPE) == 0) { |
| /* File name only */ |
| path = g_strdup(name); |
| } else |
| return -EBADR; |
| |
| if (path == NULL) |
| return -EBADR; |
| |
| ret = obex_get_stream_start(os, path); |
| |
| g_free(path); |
| |
| return ret; |
| } |
| |
| static int pbap_setpath(struct obex_session *os, void *user_data) |
| { |
| struct pbap_session *pbap = user_data; |
| const char *name; |
| const uint8_t *nonhdr; |
| char *fullname; |
| int err; |
| |
| if (obex_get_non_header_data(os, &nonhdr) != 2) { |
| error("Set path failed: flag and constants not found!"); |
| return -EBADMSG; |
| } |
| |
| name = obex_get_name(os); |
| |
| DBG("name %s folder %s nonhdr 0x%x%x", name, pbap->folder, |
| nonhdr[0], nonhdr[1]); |
| |
| fullname = phonebook_set_folder(pbap->folder, name, nonhdr[0], &err); |
| if (err < 0) |
| return err; |
| |
| g_free(pbap->folder); |
| pbap->folder = fullname; |
| |
| /* |
| * FIXME: Define a criteria to mark the cache as invalid |
| */ |
| pbap->cache.valid = FALSE; |
| pbap->cache.index = 0; |
| cache_clear(&pbap->cache); |
| |
| return 0; |
| } |
| |
| static void pbap_disconnect(struct obex_session *os, void *user_data) |
| { |
| struct pbap_session *pbap = user_data; |
| |
| manager_unregister_session(os); |
| |
| if (pbap->obj) |
| pbap->obj->session = NULL; |
| |
| if (pbap->params) { |
| g_free(pbap->params->searchval); |
| g_free(pbap->params); |
| } |
| |
| cache_clear(&pbap->cache); |
| g_free(pbap->folder); |
| g_free(pbap); |
| } |
| |
| static int pbap_chkput(struct obex_session *os, void *user_data) |
| { |
| /* Rejects all PUTs */ |
| return -EBADR; |
| } |
| |
| static struct obex_service_driver pbap = { |
| .name = "Phonebook Access server", |
| .service = OBEX_PBAP, |
| .target = PBAP_TARGET, |
| .target_size = TARGET_SIZE, |
| .connect = pbap_connect, |
| .get = pbap_get, |
| .setpath = pbap_setpath, |
| .disconnect = pbap_disconnect, |
| .chkput = pbap_chkput |
| }; |
| |
| static struct pbap_object *vobject_create(struct pbap_session *pbap, |
| void *request) |
| { |
| struct pbap_object *obj; |
| |
| obj = g_new0(struct pbap_object, 1); |
| obj->session = pbap; |
| pbap->obj = obj; |
| obj->request = request; |
| |
| return obj; |
| } |
| |
| static void *vobject_pull_open(const char *name, int oflag, mode_t mode, |
| void *context, size_t *size, int *err) |
| { |
| struct pbap_session *pbap = context; |
| phonebook_cb cb; |
| int ret; |
| void *request; |
| |
| DBG("name %s context %p maxlistcount %d", name, context, |
| pbap->params->maxlistcount); |
| |
| if (oflag != O_RDONLY) { |
| ret = -EPERM; |
| goto fail; |
| } |
| |
| if (name == NULL) { |
| ret = -EBADR; |
| goto fail; |
| } |
| |
| if (pbap->params->maxlistcount == 0) |
| cb = phonebook_size_result; |
| else |
| cb = query_result; |
| |
| request = phonebook_pull(name, pbap->params, cb, pbap, &ret); |
| |
| if (ret < 0) |
| goto fail; |
| |
| /* reading first part of results from backend */ |
| ret = phonebook_pull_read(request); |
| if (ret < 0) |
| goto fail; |
| |
| if (err) |
| *err = 0; |
| |
| return vobject_create(pbap, request); |
| |
| fail: |
| if (err) |
| *err = ret; |
| |
| return NULL; |
| } |
| |
| static int vobject_close(void *object) |
| { |
| struct pbap_object *obj = object; |
| |
| DBG(""); |
| |
| if (obj->session) |
| obj->session->obj = NULL; |
| |
| if (obj->buffer) |
| g_string_free(obj->buffer, TRUE); |
| |
| if (obj->apparam) |
| g_obex_apparam_free(obj->apparam); |
| |
| if (obj->request) |
| phonebook_req_finalize(obj->request); |
| |
| g_free(obj); |
| |
| return 0; |
| } |
| |
| static void *vobject_list_open(const char *name, int oflag, mode_t mode, |
| void *context, size_t *size, int *err) |
| { |
| struct pbap_session *pbap = context; |
| struct pbap_object *obj = NULL; |
| int ret; |
| void *request; |
| |
| if (name == NULL) { |
| ret = -EBADR; |
| goto fail; |
| } |
| |
| DBG("name %s context %p valid %d", name, context, pbap->cache.valid); |
| |
| if (oflag != O_RDONLY) { |
| ret = -EPERM; |
| goto fail; |
| } |
| |
| /* PullvCardListing always get the contacts from the cache */ |
| |
| if (pbap->cache.valid) { |
| obj = vobject_create(pbap, NULL); |
| ret = generate_response(pbap); |
| } else { |
| request = phonebook_create_cache(name, cache_entry_notify, |
| cache_ready_notify, pbap, &ret); |
| if (ret == 0) |
| obj = vobject_create(pbap, request); |
| } |
| if (ret < 0) |
| goto fail; |
| |
| if (err) |
| *err = 0; |
| |
| return obj; |
| |
| fail: |
| if (obj) |
| vobject_close(obj); |
| |
| if (err) |
| *err = ret; |
| |
| return NULL; |
| } |
| |
| static void *vobject_vcard_open(const char *name, int oflag, mode_t mode, |
| void *context, size_t *size, int *err) |
| { |
| struct pbap_session *pbap = context; |
| const char *id; |
| uint32_t handle; |
| int ret; |
| void *request; |
| |
| DBG("name %s context %p valid %d", name, context, pbap->cache.valid); |
| |
| if (oflag != O_RDONLY) { |
| ret = -EPERM; |
| goto fail; |
| } |
| |
| if (name == NULL || sscanf(name, "%u.vcf", &handle) != 1) { |
| ret = -EBADR; |
| goto fail; |
| } |
| |
| if (pbap->cache.valid == FALSE) { |
| pbap->find_handle = handle; |
| request = phonebook_create_cache(pbap->folder, |
| cache_entry_notify, cache_entry_done, pbap, &ret); |
| goto done; |
| } |
| |
| id = cache_find(&pbap->cache, handle); |
| if (!id) { |
| ret = -ENOENT; |
| goto fail; |
| } |
| |
| request = phonebook_get_entry(pbap->folder, id, pbap->params, |
| query_result, pbap, &ret); |
| |
| done: |
| if (ret < 0) |
| goto fail; |
| |
| if (err) |
| *err = 0; |
| |
| return vobject_create(pbap, request); |
| |
| fail: |
| if (err) |
| *err = ret; |
| |
| return NULL; |
| } |
| |
| static ssize_t vobject_pull_get_next_header(void *object, void *buf, size_t mtu, |
| uint8_t *hi) |
| { |
| struct pbap_object *obj = object; |
| |
| if (!obj->buffer && !obj->apparam) |
| return -EAGAIN; |
| |
| *hi = G_OBEX_HDR_APPARAM; |
| |
| if (obj->firstpacket) { |
| obj->firstpacket = FALSE; |
| |
| return g_obex_apparam_encode(obj->apparam, buf, mtu); |
| } |
| |
| return 0; |
| } |
| |
| static ssize_t vobject_pull_read(void *object, void *buf, size_t count) |
| { |
| struct pbap_object *obj = object; |
| struct pbap_session *pbap = obj->session; |
| int len, ret; |
| |
| DBG("buffer %p maxlistcount %d", obj->buffer, |
| pbap->params->maxlistcount); |
| |
| if (!obj->buffer) { |
| if (pbap->params->maxlistcount == 0) |
| return -ENOSTR; |
| |
| return -EAGAIN; |
| } |
| |
| len = string_read(obj->buffer, buf, count); |
| if (len == 0 && !obj->lastpart) { |
| /* in case when buffer is empty and we know that more |
| * data is still available in backend, requesting new |
| * data part via phonebook_pull_read and returning |
| * -EAGAIN to suspend request for now */ |
| ret = phonebook_pull_read(obj->request); |
| if (ret) |
| return -EPERM; |
| |
| return -EAGAIN; |
| } |
| |
| return len; |
| } |
| |
| static ssize_t vobject_list_get_next_header(void *object, void *buf, size_t mtu, |
| uint8_t *hi) |
| { |
| struct pbap_object *obj = object; |
| struct pbap_session *pbap = obj->session; |
| |
| /* Backend still busy reading contacts */ |
| if (!pbap->cache.valid) |
| return -EAGAIN; |
| |
| *hi = G_OBEX_HDR_APPARAM; |
| |
| if (pbap->params->maxlistcount == 0) |
| return g_obex_apparam_encode(obj->apparam, buf, mtu); |
| |
| return 0; |
| } |
| |
| static ssize_t vobject_list_read(void *object, void *buf, size_t count) |
| { |
| struct pbap_object *obj = object; |
| struct pbap_session *pbap = obj->session; |
| |
| DBG("valid %d maxlistcount %d", pbap->cache.valid, |
| pbap->params->maxlistcount); |
| |
| if (pbap->params->maxlistcount == 0) |
| return -ENOSTR; |
| |
| return string_read(obj->buffer, buf, count); |
| } |
| |
| static ssize_t vobject_vcard_read(void *object, void *buf, size_t count) |
| { |
| struct pbap_object *obj = object; |
| |
| DBG("buffer %p", obj->buffer); |
| |
| if (!obj->buffer) |
| return -EAGAIN; |
| |
| return string_read(obj->buffer, buf, count); |
| } |
| |
| static struct obex_mime_type_driver mime_pull = { |
| .target = PBAP_TARGET, |
| .target_size = TARGET_SIZE, |
| .mimetype = "x-bt/phonebook", |
| .open = vobject_pull_open, |
| .close = vobject_close, |
| .read = vobject_pull_read, |
| .get_next_header = vobject_pull_get_next_header, |
| }; |
| |
| static struct obex_mime_type_driver mime_list = { |
| .target = PBAP_TARGET, |
| .target_size = TARGET_SIZE, |
| .mimetype = "x-bt/vcard-listing", |
| .open = vobject_list_open, |
| .close = vobject_close, |
| .read = vobject_list_read, |
| .get_next_header = vobject_list_get_next_header, |
| }; |
| |
| static struct obex_mime_type_driver mime_vcard = { |
| .target = PBAP_TARGET, |
| .target_size = TARGET_SIZE, |
| .mimetype = "x-bt/vcard", |
| .open = vobject_vcard_open, |
| .close = vobject_close, |
| .read = vobject_vcard_read, |
| }; |
| |
| static int pbap_init(void) |
| { |
| int err; |
| |
| err = phonebook_init(); |
| if (err < 0) |
| return err; |
| |
| err = obex_mime_type_driver_register(&mime_pull); |
| if (err < 0) |
| goto fail_mime_pull; |
| |
| err = obex_mime_type_driver_register(&mime_list); |
| if (err < 0) |
| goto fail_mime_list; |
| |
| err = obex_mime_type_driver_register(&mime_vcard); |
| if (err < 0) |
| goto fail_mime_vcard; |
| |
| err = obex_service_driver_register(&pbap); |
| if (err < 0) |
| goto fail_pbap_reg; |
| |
| return 0; |
| |
| fail_pbap_reg: |
| obex_mime_type_driver_unregister(&mime_vcard); |
| fail_mime_vcard: |
| obex_mime_type_driver_unregister(&mime_list); |
| fail_mime_list: |
| obex_mime_type_driver_unregister(&mime_pull); |
| fail_mime_pull: |
| phonebook_exit(); |
| |
| return err; |
| } |
| |
| static void pbap_exit(void) |
| { |
| obex_service_driver_unregister(&pbap); |
| obex_mime_type_driver_unregister(&mime_pull); |
| obex_mime_type_driver_unregister(&mime_list); |
| obex_mime_type_driver_unregister(&mime_vcard); |
| phonebook_exit(); |
| } |
| |
| OBEX_PLUGIN_DEFINE(pbap, pbap_init, pbap_exit) |