5 * Copyright (C) 2009-2010 Intel Corporation
6 * Copyright (C) 2007-2010 Marcel Holtmann <marcel@holtmann.org>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
35 #include <arpa/inet.h>
36 #include <sys/types.h>
48 #include "phonebook.h"
50 #include "filesystem.h"
53 #define PHONEBOOK_TYPE "x-bt/phonebook"
54 #define VCARDLISTING_TYPE "x-bt/vcard-listing"
55 #define VCARDENTRY_TYPE "x-bt/vcard"
57 #define ORDER_TAG 0x01
58 #define SEARCHVALUE_TAG 0x02
59 #define SEARCHATTRIB_TAG 0x03
60 #define MAXLISTCOUNT_TAG 0x04
61 #define LISTSTARTOFFSET_TAG 0x05
62 #define FILTER_TAG 0x06
63 #define FORMAT_TAG 0X07
64 #define PHONEBOOKSIZE_TAG 0X08
65 #define NEWMISSEDCALLS_TAG 0X09
67 /* The following length is in the unit of byte */
69 #define SEARCHATTRIB_LEN 1
70 #define MAXLISTCOUNT_LEN 2
71 #define LISTSTARTOFFSET_LEN 2
74 #define PHONEBOOKSIZE_LEN 2
75 #define NEWMISSEDCALLS_LEN 1
77 #define PBAP_CHANNEL 15
79 #define PBAP_RECORD "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> \
81 <attribute id=\"0x0001\"> \
83 <uuid value=\"0x112f\"/> \
87 <attribute id=\"0x0004\"> \
90 <uuid value=\"0x0100\"/> \
93 <uuid value=\"0x0003\"/> \
94 <uint8 value=\"%u\" name=\"channel\"/> \
97 <uuid value=\"0x0008\"/> \
102 <attribute id=\"0x0009\"> \
105 <uuid value=\"0x1130\"/> \
106 <uint16 value=\"0x0100\" name=\"version\"/> \
111 <attribute id=\"0x0100\"> \
112 <text value=\"%s\" name=\"name\"/> \
115 <attribute id=\"0x0314\"> \
116 <uint8 value=\"0x01\"/> \
120 struct aparam_header {
124 } __attribute__ ((packed));
140 struct pbap_session {
141 struct apparam_field *params;
143 uint32_t find_handle;
145 struct pbap_object *obj;
151 gboolean firstpacket;
153 struct pbap_session *session;
157 static const uint8_t PBAP_TARGET[TARGET_SIZE] = {
158 0x79, 0x61, 0x35, 0xF0, 0xF0, 0xC5, 0x11, 0xD8,
159 0x09, 0x66, 0x08, 0x00, 0x20, 0x0C, 0x9A, 0x66 };
161 typedef int (*cache_entry_find_f) (const struct cache_entry *entry,
164 static void cache_entry_free(void *data)
166 struct cache_entry *entry = data;
170 g_free(entry->sound);
175 static gboolean entry_name_find(const struct cache_entry *entry,
184 if (strlen(value) == 0)
187 name = g_utf8_strdown(entry->name, -1);
188 ret = (g_strstr_len(name, -1, value) ? TRUE : FALSE);
194 static gboolean entry_sound_find(const struct cache_entry *entry,
200 return (g_strstr_len(entry->sound, -1, value) ? TRUE : FALSE);
203 static gboolean entry_tel_find(const struct cache_entry *entry,
209 return (g_strstr_len(entry->tel, -1, value) ? TRUE : FALSE);
212 static const char *cache_find(struct cache *cache, uint32_t handle)
216 for (l = cache->entries; l; l = l->next) {
217 struct cache_entry *entry = l->data;
219 if (entry->handle == handle)
226 static void cache_clear(struct cache *cache)
228 g_slist_free_full(cache->entries, cache_entry_free);
229 cache->entries = NULL;
232 static GByteArray *append_aparam_header(GByteArray *buf, uint8_t tag,
235 /* largest aparam is for phonebooksize (4 bytes) */
236 uint8_t aparam[sizeof(struct aparam_header) + PHONEBOOKSIZE_LEN];
237 struct aparam_header *hdr = (struct aparam_header *) aparam;
240 case PHONEBOOKSIZE_TAG:
241 hdr->tag = PHONEBOOKSIZE_TAG;
242 hdr->len = PHONEBOOKSIZE_LEN;
243 memcpy(hdr->val, val, PHONEBOOKSIZE_LEN);
245 return g_byte_array_append(buf, aparam,
246 sizeof(struct aparam_header) + PHONEBOOKSIZE_LEN);
247 case NEWMISSEDCALLS_TAG:
248 hdr->tag = NEWMISSEDCALLS_TAG;
249 hdr->len = NEWMISSEDCALLS_LEN;
250 memcpy(hdr->val, val, NEWMISSEDCALLS_LEN);
252 return g_byte_array_append(buf, aparam,
253 sizeof(struct aparam_header) + NEWMISSEDCALLS_LEN);
259 static void phonebook_size_result(const char *buffer, size_t bufsize,
260 int vcards, int missed,
261 gboolean lastpart, void *user_data)
263 struct pbap_session *pbap = user_data;
264 uint16_t phonebooksize;
266 if (pbap->obj->request) {
267 phonebook_req_finalize(pbap->obj->request);
268 pbap->obj->request = NULL;
274 DBG("vcards %d", vcards);
276 phonebooksize = htons(vcards);
278 pbap->obj->aparams = g_byte_array_new();
279 pbap->obj->aparams = append_aparam_header(pbap->obj->aparams,
280 PHONEBOOKSIZE_TAG, &phonebooksize);
283 DBG("missed %d", missed);
285 pbap->obj->aparams = append_aparam_header(pbap->obj->aparams,
286 NEWMISSEDCALLS_TAG, &missed);
289 obex_object_set_io_flags(pbap->obj, G_IO_IN, 0);
292 static void query_result(const char *buffer, size_t bufsize, int vcards,
293 int missed, gboolean lastpart, void *user_data)
295 struct pbap_session *pbap = user_data;
299 if (pbap->obj->request && lastpart) {
300 phonebook_req_finalize(pbap->obj->request);
301 pbap->obj->request = NULL;
304 pbap->obj->lastpart = lastpart;
307 obex_object_set_io_flags(pbap->obj, G_IO_ERR, -ENOENT);
311 if (!pbap->obj->buffer)
312 pbap->obj->buffer = g_string_new_len(buffer, bufsize);
314 pbap->obj->buffer = g_string_append_len(pbap->obj->buffer,
318 DBG("missed %d", missed);
320 pbap->obj->firstpacket = TRUE;
322 pbap->obj->aparams = g_byte_array_new();
323 pbap->obj->aparams = append_aparam_header(pbap->obj->aparams,
324 NEWMISSEDCALLS_TAG, &missed);
327 obex_object_set_io_flags(pbap->obj, G_IO_IN, 0);
330 static void cache_entry_notify(const char *id, uint32_t handle,
331 const char *name, const char *sound,
332 const char *tel, void *user_data)
334 struct pbap_session *pbap = user_data;
335 struct cache_entry *entry = g_new0(struct cache_entry, 1);
336 struct cache *cache = &pbap->cache;
338 if (handle != PHONEBOOK_INVALID_HANDLE)
339 entry->handle = handle;
341 entry->handle = ++pbap->cache.index;
343 entry->id = g_strdup(id);
344 entry->name = g_strdup(name);
345 entry->sound = g_strdup(sound);
346 entry->tel = g_strdup(tel);
348 cache->entries = g_slist_append(cache->entries, entry);
351 static int alpha_sort(gconstpointer a, gconstpointer b)
353 const struct cache_entry *e1 = a;
354 const struct cache_entry *e2 = b;
356 return g_strcmp0(e1->name, e2->name);
359 static int indexed_sort(gconstpointer a, gconstpointer b)
361 const struct cache_entry *e1 = a;
362 const struct cache_entry *e2 = b;
364 return (e1->handle - e2->handle);
367 static int phonetical_sort(gconstpointer a, gconstpointer b)
369 const struct cache_entry *e1 = a;
370 const struct cache_entry *e2 = b;
372 /* SOUND attribute is optional. Use Indexed sort if not present. */
373 if (!e1->sound || !e2->sound)
374 return indexed_sort(a, b);
376 return g_strcmp0(e1->sound, e2->sound);
379 static GSList *sort_entries(GSList *l, uint8_t order, uint8_t search_attrib,
382 GSList *sorted = NULL;
383 cache_entry_find_f find;
388 * Default sorter is "Indexed". Some backends doesn't inform the index,
389 * for this case a sequential internal index is assigned.
391 * 0x01 = alphanumeric
399 sort = phonetical_sort;
407 * This implementation checks if the given field CONTAINS the
408 * search value(case insensitive). Name is the default field
409 * when the attribute is not provided.
411 switch (search_attrib) {
414 find = entry_tel_find;
418 find = entry_sound_find;
421 find = entry_name_find;
425 searchval = value ? g_utf8_strdown(value, -1) : NULL;
426 for (; l; l = l->next) {
427 struct cache_entry *entry = l->data;
429 if (searchval && !find(entry, (const char *) searchval))
432 sorted = g_slist_insert_sorted(sorted, entry, sort);
440 static int generate_response(void *user_data)
442 struct pbap_session *pbap = user_data;
445 uint16_t max = pbap->params->maxlistcount;
450 /* Ignore all other parameter and return PhoneBookSize */
451 uint16_t size = htons(g_slist_length(pbap->cache.entries));
453 pbap->obj->aparams = g_byte_array_new();
454 pbap->obj->aparams = append_aparam_header(pbap->obj->aparams,
455 PHONEBOOKSIZE_TAG, &size);
461 * Don't free the sorted list content: this list contains
462 * only the reference for the "real" cache entry.
464 sorted = sort_entries(pbap->cache.entries, pbap->params->order,
465 pbap->params->searchattrib,
466 (const char *) pbap->params->searchval);
468 /* Computing offset considering first entry of the phonebook */
469 l = g_slist_nth(sorted, pbap->params->liststartoffset);
471 pbap->obj->buffer = g_string_new(VCARD_LISTING_BEGIN);
472 for (; l && max; l = l->next, max--) {
473 const struct cache_entry *entry = l->data;
474 char *escaped_name = g_markup_escape_text(entry->name, -1);
476 g_string_append_printf(pbap->obj->buffer,
477 VCARD_LISTING_ELEMENT, entry->handle, escaped_name);
479 g_free(escaped_name);
482 pbap->obj->buffer = g_string_append(pbap->obj->buffer,
484 g_slist_free(sorted);
489 static void cache_ready_notify(void *user_data)
491 struct pbap_session *pbap = user_data;
495 phonebook_req_finalize(pbap->obj->request);
496 pbap->obj->request = NULL;
498 pbap->cache.valid = TRUE;
500 generate_response(pbap);
501 obex_object_set_io_flags(pbap->obj, G_IO_IN, 0);
504 static void cache_entry_done(void *user_data)
506 struct pbap_session *pbap = user_data;
512 pbap->cache.valid = TRUE;
514 id = cache_find(&pbap->cache, pbap->find_handle);
516 DBG("Entry %d not found on cache", pbap->find_handle);
517 obex_object_set_io_flags(pbap->obj, G_IO_ERR, -ENOENT);
521 phonebook_req_finalize(pbap->obj->request);
522 pbap->obj->request = phonebook_get_entry(pbap->folder, id,
523 pbap->params, query_result, pbap, &ret);
525 obex_object_set_io_flags(pbap->obj, G_IO_ERR, ret);
528 static struct apparam_field *parse_aparam(const uint8_t *buffer, uint32_t hlen)
530 struct apparam_field *param;
531 struct aparam_header *hdr;
536 param = g_new0(struct apparam_field, 1);
539 hdr = (void *) buffer + len;
543 if (hdr->len != ORDER_LEN)
546 param->order = hdr->val[0];
549 case SEARCHATTRIB_TAG:
550 if (hdr->len != SEARCHATTRIB_LEN)
553 param->searchattrib = hdr->val[0];
555 case SEARCHVALUE_TAG:
559 param->searchval = g_try_malloc0(hdr->len + 1);
560 if (param->searchval)
561 memcpy(param->searchval, hdr->val, hdr->len);
564 if (hdr->len != FILTER_LEN)
567 memcpy(&val64, hdr->val, sizeof(val64));
568 param->filter = GUINT64_FROM_BE(val64);
572 if (hdr->len != FORMAT_LEN)
575 param->format = hdr->val[0];
577 case MAXLISTCOUNT_TAG:
578 if (hdr->len != MAXLISTCOUNT_LEN)
581 memcpy(&val16, hdr->val, sizeof(val16));
582 param->maxlistcount = GUINT16_FROM_BE(val16);
584 case LISTSTARTOFFSET_TAG:
585 if (hdr->len != LISTSTARTOFFSET_LEN)
588 memcpy(&val16, hdr->val, sizeof(val16));
589 param->liststartoffset = GUINT16_FROM_BE(val16);
595 len += hdr->len + sizeof(struct aparam_header);
598 DBG("o %x sa %x sv %s fil %" G_GINT64_MODIFIER "x for %x max %x off %x",
599 param->order, param->searchattrib, param->searchval,
600 param->filter, param->format, param->maxlistcount,
601 param->liststartoffset);
611 static void *pbap_connect(struct obex_session *os, int *err)
613 struct pbap_session *pbap;
615 manager_register_session(os);
617 pbap = g_new0(struct pbap_session, 1);
618 pbap->folder = g_strdup("/");
619 pbap->find_handle = PHONEBOOK_INVALID_HANDLE;
627 static int pbap_get(struct obex_session *os, void *user_data)
629 struct pbap_session *pbap = user_data;
630 const char *type = obex_get_type(os);
631 const char *name = obex_get_name(os);
632 struct apparam_field *params;
633 const uint8_t *buffer;
638 DBG("name %s type %s pbap %p", name, type, pbap);
643 rsize = obex_get_apparam(os, &buffer);
645 if (g_ascii_strcasecmp(type, VCARDENTRY_TYPE) != 0)
651 params = parse_aparam(buffer, rsize);
656 g_free(pbap->params->searchval);
657 g_free(pbap->params);
660 pbap->params = params;
662 if (g_ascii_strcasecmp(type, PHONEBOOK_TYPE) == 0) {
663 /* Always contains the absolute path */
664 if (g_path_is_absolute(name))
665 path = g_strdup(name);
667 path = g_build_filename("/", name, NULL);
669 } else if (g_ascii_strcasecmp(type, VCARDLISTING_TYPE) == 0) {
670 /* Always relative */
671 if (!name || strlen(name) == 0)
673 path = g_strdup(pbap->folder);
675 /* Current folder + relative path */
676 path = g_build_filename(pbap->folder, name, NULL);
678 } else if (g_ascii_strcasecmp(type, VCARDENTRY_TYPE) == 0) {
680 path = g_strdup(name);
687 ret = obex_get_stream_start(os, path);
694 static int pbap_setpath(struct obex_session *os, void *user_data)
696 struct pbap_session *pbap = user_data;
698 const uint8_t *nonhdr;
702 if (obex_get_non_header_data(os, &nonhdr) != 2) {
703 error("Set path failed: flag and constants not found!");
707 name = obex_get_name(os);
709 DBG("name %s folder %s nonhdr 0x%x%x", name, pbap->folder,
710 nonhdr[0], nonhdr[1]);
712 fullname = phonebook_set_folder(pbap->folder, name, nonhdr[0], &err);
716 g_free(pbap->folder);
717 pbap->folder = fullname;
720 * FIXME: Define a criteria to mark the cache as invalid
722 pbap->cache.valid = FALSE;
723 pbap->cache.index = 0;
724 cache_clear(&pbap->cache);
729 static void pbap_disconnect(struct obex_session *os, void *user_data)
731 struct pbap_session *pbap = user_data;
733 manager_unregister_session(os);
736 pbap->obj->session = NULL;
739 g_free(pbap->params->searchval);
740 g_free(pbap->params);
743 cache_clear(&pbap->cache);
744 g_free(pbap->folder);
748 static int pbap_chkput(struct obex_session *os, void *user_data)
750 /* Rejects all PUTs */
754 static struct obex_service_driver pbap = {
755 .name = "Phonebook Access server",
756 .service = OBEX_PBAP,
757 .channel = PBAP_CHANNEL,
759 .record = PBAP_RECORD,
760 .target = PBAP_TARGET,
761 .target_size = TARGET_SIZE,
762 .connect = pbap_connect,
764 .setpath = pbap_setpath,
765 .disconnect = pbap_disconnect,
766 .chkput = pbap_chkput
769 static struct pbap_object *vobject_create(struct pbap_session *pbap,
772 struct pbap_object *obj;
774 obj = g_new0(struct pbap_object, 1);
777 obj->request = request;
782 static void *vobject_pull_open(const char *name, int oflag, mode_t mode,
783 void *context, size_t *size, int *err)
785 struct pbap_session *pbap = context;
790 DBG("name %s context %p maxlistcount %d", name, context,
791 pbap->params->maxlistcount);
793 if (oflag != O_RDONLY) {
803 if (pbap->params->maxlistcount == 0)
804 cb = phonebook_size_result;
808 request = phonebook_pull(name, pbap->params, cb, pbap, &ret);
813 /* reading first part of results from backend */
814 ret = phonebook_pull_read(request);
821 return vobject_create(pbap, request);
830 static int vobject_close(void *object)
832 struct pbap_object *obj = object;
837 obj->session->obj = NULL;
840 g_string_free(obj->buffer, TRUE);
843 g_byte_array_free(obj->aparams, TRUE);
846 phonebook_req_finalize(obj->request);
853 static void *vobject_list_open(const char *name, int oflag, mode_t mode,
854 void *context, size_t *size, int *err)
856 struct pbap_session *pbap = context;
857 struct pbap_object *obj = NULL;
861 DBG("name %s context %p valid %d", name, context, pbap->cache.valid);
863 if (oflag != O_RDONLY) {
873 /* PullvCardListing always get the contacts from the cache */
875 if (pbap->cache.valid) {
876 obj = vobject_create(pbap, NULL);
877 ret = generate_response(pbap);
879 request = phonebook_create_cache(name, cache_entry_notify,
880 cache_ready_notify, pbap, &ret);
882 obj = vobject_create(pbap, request);
902 static void *vobject_vcard_open(const char *name, int oflag, mode_t mode,
903 void *context, size_t *size, int *err)
905 struct pbap_session *pbap = context;
911 DBG("name %s context %p valid %d", name, context, pbap->cache.valid);
913 if (oflag != O_RDONLY) {
918 if (name == NULL || sscanf(name, "%u.vcf", &handle) != 1) {
923 if (pbap->cache.valid == FALSE) {
924 pbap->find_handle = handle;
925 request = phonebook_create_cache(pbap->folder,
926 cache_entry_notify, cache_entry_done, pbap, &ret);
930 id = cache_find(&pbap->cache, handle);
936 request = phonebook_get_entry(pbap->folder, id, pbap->params,
937 query_result, pbap, &ret);
946 return vobject_create(pbap, request);
955 static ssize_t array_read(GByteArray *array, void *buf, size_t count)
962 len = MIN(array->len, count);
963 memcpy(buf, array->data, len);
964 g_byte_array_remove_range(array, 0, len);
969 static ssize_t vobject_pull_get_next_header(void *object, void *buf, size_t mtu,
972 struct pbap_object *obj = object;
973 struct pbap_session *pbap = obj->session;
975 if (!obj->buffer && !obj->aparams)
978 *hi = G_OBEX_HDR_APPARAM;
980 if (pbap->params->maxlistcount == 0 || obj->firstpacket) {
981 obj->firstpacket = FALSE;
983 return array_read(obj->aparams, buf, mtu);
989 static ssize_t vobject_pull_read(void *object, void *buf, size_t count)
991 struct pbap_object *obj = object;
992 struct pbap_session *pbap = obj->session;
995 DBG("buffer %p maxlistcount %d", obj->buffer,
996 pbap->params->maxlistcount);
999 if (pbap->params->maxlistcount == 0)
1005 len = string_read(obj->buffer, buf, count);
1006 if (len == 0 && !obj->lastpart) {
1007 /* in case when buffer is empty and we know that more
1008 * data is still available in backend, requesting new
1009 * data part via phonebook_pull_read and returning
1010 * -EAGAIN to suspend request for now */
1011 ret = phonebook_pull_read(obj->request);
1021 static ssize_t vobject_list_get_next_header(void *object, void *buf, size_t mtu,
1024 struct pbap_object *obj = object;
1025 struct pbap_session *pbap = obj->session;
1027 /* Backend still busy reading contacts */
1028 if (!pbap->cache.valid)
1031 *hi = G_OBEX_HDR_APPARAM;
1033 if (pbap->params->maxlistcount == 0)
1034 return array_read(obj->aparams, buf, mtu);
1039 static ssize_t vobject_list_read(void *object, void *buf, size_t count)
1041 struct pbap_object *obj = object;
1042 struct pbap_session *pbap = obj->session;
1044 DBG("valid %d maxlistcount %d", pbap->cache.valid,
1045 pbap->params->maxlistcount);
1047 if (pbap->params->maxlistcount == 0)
1050 return string_read(obj->buffer, buf, count);
1053 static ssize_t vobject_vcard_read(void *object, void *buf, size_t count)
1055 struct pbap_object *obj = object;
1057 DBG("buffer %p", obj->buffer);
1062 return string_read(obj->buffer, buf, count);
1065 static struct obex_mime_type_driver mime_pull = {
1066 .target = PBAP_TARGET,
1067 .target_size = TARGET_SIZE,
1068 .mimetype = "x-bt/phonebook",
1069 .open = vobject_pull_open,
1070 .close = vobject_close,
1071 .read = vobject_pull_read,
1072 .get_next_header = vobject_pull_get_next_header,
1075 static struct obex_mime_type_driver mime_list = {
1076 .target = PBAP_TARGET,
1077 .target_size = TARGET_SIZE,
1078 .mimetype = "x-bt/vcard-listing",
1079 .open = vobject_list_open,
1080 .close = vobject_close,
1081 .read = vobject_list_read,
1082 .get_next_header = vobject_list_get_next_header,
1085 static struct obex_mime_type_driver mime_vcard = {
1086 .target = PBAP_TARGET,
1087 .target_size = TARGET_SIZE,
1088 .mimetype = "x-bt/vcard",
1089 .open = vobject_vcard_open,
1090 .close = vobject_close,
1091 .read = vobject_vcard_read,
1094 static int pbap_init(void)
1098 err = phonebook_init();
1102 err = obex_mime_type_driver_register(&mime_pull);
1104 goto fail_mime_pull;
1106 err = obex_mime_type_driver_register(&mime_list);
1108 goto fail_mime_list;
1110 err = obex_mime_type_driver_register(&mime_vcard);
1112 goto fail_mime_vcard;
1114 err = obex_service_driver_register(&pbap);
1121 obex_mime_type_driver_unregister(&mime_vcard);
1123 obex_mime_type_driver_unregister(&mime_list);
1125 obex_mime_type_driver_unregister(&mime_pull);
1132 static void pbap_exit(void)
1134 obex_service_driver_unregister(&pbap);
1135 obex_mime_type_driver_unregister(&mime_pull);
1136 obex_mime_type_driver_unregister(&mime_list);
1137 obex_mime_type_driver_unregister(&mime_vcard);
1141 OBEX_PLUGIN_DEFINE(pbap, pbap_init, pbap_exit)