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>
41 #include "gobex/gobex.h"
42 #include "gobex/gobex-apparam.h"
44 #include "obexd/src/obexd.h"
45 #include "obexd/src/plugin.h"
46 #include "obexd/src/log.h"
47 #include "obexd/src/obex.h"
48 #include "obexd/src/service.h"
49 #include "obexd/src/manager.h"
50 #include "obexd/src/mimetype.h"
51 #include "phonebook.h"
52 #include "filesystem.h"
54 #define PHONEBOOK_TYPE "x-bt/phonebook"
55 #define VCARDLISTING_TYPE "x-bt/vcard-listing"
56 #define VCARDENTRY_TYPE "x-bt/vcard"
58 #define ORDER_TAG 0x01
59 #define SEARCHVALUE_TAG 0x02
60 #define SEARCHATTRIB_TAG 0x03
61 #define MAXLISTCOUNT_TAG 0x04
62 #define LISTSTARTOFFSET_TAG 0x05
63 #define FILTER_TAG 0x06
64 #define FORMAT_TAG 0X07
65 #define PHONEBOOKSIZE_TAG 0X08
66 #define NEWMISSEDCALLS_TAG 0X09
68 #ifdef __TIZEN_PATCH__
69 #define PBAP_MAXLISTCOUNT_MAX_VALUE 65535
70 #endif /* __TIZEN_PATCH__ */
87 struct apparam_field *params;
91 struct pbap_object *obj;
92 #ifdef __TIZEN_PATCH__
99 GObexApparam *apparam;
100 gboolean firstpacket;
102 struct pbap_session *session;
106 static const uint8_t PBAP_TARGET[TARGET_SIZE] = {
107 0x79, 0x61, 0x35, 0xF0, 0xF0, 0xC5, 0x11, 0xD8,
108 0x09, 0x66, 0x08, 0x00, 0x20, 0x0C, 0x9A, 0x66 };
110 typedef int (*cache_entry_find_f) (const struct cache_entry *entry,
113 static void cache_entry_free(void *data)
115 struct cache_entry *entry = data;
119 g_free(entry->sound);
124 static gboolean entry_name_find(const struct cache_entry *entry,
133 if (strlen(value) == 0)
136 name = g_utf8_strdown(entry->name, -1);
137 ret = (g_strstr_len(name, -1, value) ? TRUE : FALSE);
143 static gboolean entry_sound_find(const struct cache_entry *entry,
149 return (g_strstr_len(entry->sound, -1, value) ? TRUE : FALSE);
152 static gboolean entry_tel_find(const struct cache_entry *entry,
158 return (g_strstr_len(entry->tel, -1, value) ? TRUE : FALSE);
161 static const char *cache_find(struct cache *cache, uint32_t handle)
165 for (l = cache->entries; l; l = l->next) {
166 struct cache_entry *entry = l->data;
168 if (entry->handle == handle)
175 static void cache_clear(struct cache *cache)
177 g_slist_free_full(cache->entries, cache_entry_free);
178 cache->entries = NULL;
181 static void phonebook_size_result(const char *buffer, size_t bufsize,
182 int vcards, int missed,
183 gboolean lastpart, void *user_data)
185 struct pbap_session *pbap = user_data;
186 uint16_t phonebooksize;
188 if (pbap->obj->request) {
189 phonebook_req_finalize(pbap->obj->request);
190 pbap->obj->request = NULL;
196 DBG("vcards %d", vcards);
198 phonebooksize = vcards;
200 pbap->obj->apparam = g_obex_apparam_set_uint16(NULL, PHONEBOOKSIZE_TAG,
202 #ifndef __TIZEN_PATCH__
205 if (pbap->params->required_missedcall_call_header == TRUE) {
206 #endif /* __TIZEN_PATCH__ */
207 DBG("missed %d", missed);
209 pbap->obj->apparam = g_obex_apparam_set_uint16(
215 obex_object_set_io_flags(pbap->obj, G_IO_IN, 0);
218 static void query_result(const char *buffer, size_t bufsize, int vcards,
219 int missed, gboolean lastpart, void *user_data)
221 struct pbap_session *pbap = user_data;
225 if (pbap->obj->request && lastpart) {
226 phonebook_req_finalize(pbap->obj->request);
227 pbap->obj->request = NULL;
230 pbap->obj->lastpart = lastpart;
233 obex_object_set_io_flags(pbap->obj, G_IO_ERR, -ENOENT);
237 if (!pbap->obj->buffer)
238 pbap->obj->buffer = g_string_new_len(buffer, bufsize);
240 pbap->obj->buffer = g_string_append_len(pbap->obj->buffer,
242 #ifndef __TIZEN_PATCH__
245 if (pbap->params->required_missedcall_call_header == TRUE) {
246 #endif /* _TIZEN_PATCH__ */
247 DBG("missed %d", missed);
249 pbap->obj->firstpacket = TRUE;
251 pbap->obj->apparam = g_obex_apparam_set_uint16(
257 obex_object_set_io_flags(pbap->obj, G_IO_IN, 0);
260 static void cache_entry_notify(const char *id, uint32_t handle,
261 const char *name, const char *sound,
262 const char *tel, void *user_data)
264 struct pbap_session *pbap = user_data;
265 struct cache_entry *entry = g_new0(struct cache_entry, 1);
266 struct cache *cache = &pbap->cache;
268 if (handle != PHONEBOOK_INVALID_HANDLE)
269 entry->handle = handle;
271 entry->handle = ++pbap->cache.index;
273 entry->id = g_strdup(id);
274 entry->name = g_strdup(name);
275 entry->sound = g_strdup(sound);
276 entry->tel = g_strdup(tel);
278 cache->entries = g_slist_append(cache->entries, entry);
281 static int alpha_sort(gconstpointer a, gconstpointer b)
283 const struct cache_entry *e1 = a;
284 const struct cache_entry *e2 = b;
286 return g_strcmp0(e1->name, e2->name);
289 static int indexed_sort(gconstpointer a, gconstpointer b)
291 const struct cache_entry *e1 = a;
292 const struct cache_entry *e2 = b;
294 return (e1->handle - e2->handle);
297 static int phonetical_sort(gconstpointer a, gconstpointer b)
299 const struct cache_entry *e1 = a;
300 const struct cache_entry *e2 = b;
302 /* SOUND attribute is optional. Use Indexed sort if not present. */
303 if (!e1->sound || !e2->sound)
304 return indexed_sort(a, b);
306 return g_strcmp0(e1->sound, e2->sound);
309 static GSList *sort_entries(GSList *l, uint8_t order, uint8_t search_attrib,
312 GSList *sorted = NULL;
313 cache_entry_find_f find;
318 * Default sorter is "Indexed". Some backends doesn't inform the index,
319 * for this case a sequential internal index is assigned.
321 * 0x01 = alphanumeric
329 sort = phonetical_sort;
337 * This implementation checks if the given field CONTAINS the
338 * search value(case insensitive). Name is the default field
339 * when the attribute is not provided.
341 switch (search_attrib) {
344 find = entry_tel_find;
348 find = entry_sound_find;
351 find = entry_name_find;
355 searchval = value ? g_utf8_strdown(value, -1) : NULL;
356 for (; l; l = l->next) {
357 struct cache_entry *entry = l->data;
359 if (searchval && !find(entry, (const char *) searchval))
362 sorted = g_slist_insert_sorted(sorted, entry, sort);
370 static int generate_response(void *user_data)
372 struct pbap_session *pbap = user_data;
375 uint16_t max = pbap->params->maxlistcount;
380 /* Ignore all other parameter and return PhoneBookSize */
381 uint16_t size = g_slist_length(pbap->cache.entries);
383 pbap->obj->apparam = g_obex_apparam_set_uint16(
387 #ifdef __TIZEN_PATCH__
388 if (pbap->params->required_missedcall_call_header == TRUE) {
389 //DBG("missed %d", missed);
390 pbap->obj->apparam = g_obex_apparam_set_uint16(
393 pbap->params->new_missed_calls);
395 #endif /* __TIZEN_PATCH__ */
399 #ifdef __TIZEN_PATCH__
400 if (pbap->params->required_missedcall_call_header == TRUE) {
401 pbap->obj->firstpacket = TRUE;
402 pbap->obj->apparam = g_obex_apparam_set_uint16(
405 pbap->params->new_missed_calls);
407 #endif /* __TIZEN_PATCH__ */
410 * Don't free the sorted list content: this list contains
411 * only the reference for the "real" cache entry.
413 sorted = sort_entries(pbap->cache.entries, pbap->params->order,
414 pbap->params->searchattrib,
415 (const char *) pbap->params->searchval);
417 /* Computing offset considering first entry of the phonebook */
418 l = g_slist_nth(sorted, pbap->params->liststartoffset);
420 pbap->obj->buffer = g_string_new(VCARD_LISTING_BEGIN);
421 for (; l && max; l = l->next, max--) {
422 const struct cache_entry *entry = l->data;
423 char *escaped_name = g_markup_escape_text(entry->name, -1);
425 g_string_append_printf(pbap->obj->buffer,
426 VCARD_LISTING_ELEMENT, entry->handle, escaped_name);
428 g_free(escaped_name);
431 pbap->obj->buffer = g_string_append(pbap->obj->buffer,
433 g_slist_free(sorted);
438 #ifndef __TIZEN_PATCH__
439 static void cache_ready_notify(void *user_data)
441 static void cache_ready_notify(void *user_data, unsigned int new_missed_call)
442 #endif /* __TIZEN_PATCH__ */
444 struct pbap_session *pbap = user_data;
447 #ifdef __TIZEN_PATCH__
448 pbap->params->new_missed_calls = new_missed_call;
449 #endif /* __TIZEN_PATCH__ */
450 phonebook_req_finalize(pbap->obj->request);
451 pbap->obj->request = NULL;
453 pbap->cache.valid = TRUE;
455 generate_response(pbap);
456 obex_object_set_io_flags(pbap->obj, G_IO_IN, 0);
459 static void cache_entry_done(void *user_data)
461 struct pbap_session *pbap = user_data;
467 pbap->cache.valid = TRUE;
469 id = cache_find(&pbap->cache, pbap->find_handle);
471 DBG("Entry %d not found on cache", pbap->find_handle);
472 obex_object_set_io_flags(pbap->obj, G_IO_ERR, -ENOENT);
476 phonebook_req_finalize(pbap->obj->request);
477 pbap->obj->request = phonebook_get_entry(pbap->folder, id,
478 pbap->params, query_result, pbap, &ret);
480 obex_object_set_io_flags(pbap->obj, G_IO_ERR, ret);
483 #ifdef __TIZEN_PATCH__
484 static void cache_clear_notify(void *user_data)
486 struct pbap_session *pbap = user_data;
491 pbap->cache.valid = FALSE;
492 pbap->cache.index = 0;
493 cache_clear(&pbap->cache);
497 static struct apparam_field *parse_aparam(const uint8_t *buffer, uint32_t hlen)
499 GObexApparam *apparam;
500 struct apparam_field *param;
501 #ifdef __TIZEN_PATCH__
502 gboolean bmaxlistCount = FALSE;
503 #endif /* __TIZEN_PATCH__ */
505 apparam = g_obex_apparam_decode(buffer, hlen);
509 param = g_new0(struct apparam_field, 1);
512 * As per spec when client doesn't include MAXLISTCOUNT_TAG then it
513 * should be assume as Maximum value in vcardlisting 65535
515 param->maxlistcount = UINT16_MAX;
517 g_obex_apparam_get_uint8(apparam, ORDER_TAG, ¶m->order);
518 g_obex_apparam_get_uint8(apparam, SEARCHATTRIB_TAG,
519 ¶m->searchattrib);
520 g_obex_apparam_get_uint8(apparam, FORMAT_TAG, ¶m->format);
521 #ifdef __TIZEN_PATCH__
522 bmaxlistCount = g_obex_apparam_get_uint16(apparam, MAXLISTCOUNT_TAG,
523 ¶m->maxlistcount);
525 g_obex_apparam_get_uint16(apparam, MAXLISTCOUNT_TAG,
526 ¶m->maxlistcount);
527 #endif /* __TIZEN_PATCH__ */
528 g_obex_apparam_get_uint16(apparam, LISTSTARTOFFSET_TAG,
529 ¶m->liststartoffset);
530 g_obex_apparam_get_uint64(apparam, FILTER_TAG, ¶m->filter);
531 param->searchval = g_obex_apparam_get_string(apparam, SEARCHVALUE_TAG);
533 #ifdef __TIZEN_PATCH__
534 if(bmaxlistCount == FALSE) {
535 param->maxlistcount = PBAP_MAXLISTCOUNT_MAX_VALUE;
538 /* Keep the MaxlistCount value as send in request from client */
540 #endif /* __TIZEN_PATCH__ */
542 DBG("o %x sa %x sv %s fil %" G_GINT64_MODIFIER "x for %x max %x off %x",
543 param->order, param->searchattrib, param->searchval,
544 param->filter, param->format, param->maxlistcount,
545 param->liststartoffset);
547 g_obex_apparam_free(apparam);
552 static void *pbap_connect(struct obex_session *os, int *err)
554 struct pbap_session *pbap;
556 manager_register_session(os);
558 pbap = g_new0(struct pbap_session, 1);
559 pbap->folder = g_strdup("/");
560 pbap->find_handle = PHONEBOOK_INVALID_HANDLE;
562 #ifdef __TIZEN_PATCH__
565 error = phonebook_connect(&pbap->backend_data);
578 #ifdef __TIZEN_PATCH__
586 static int pbap_get(struct obex_session *os, void *user_data)
588 struct pbap_session *pbap = user_data;
589 const char *type = obex_get_type(os);
590 const char *name = obex_get_name(os);
591 struct apparam_field *params;
592 const uint8_t *buffer;
597 DBG("name %s type %s pbap %p", name, type, pbap);
602 rsize = obex_get_apparam(os, &buffer);
604 if (g_ascii_strcasecmp(type, VCARDENTRY_TYPE) != 0)
610 params = parse_aparam(buffer, rsize);
615 g_free(pbap->params->searchval);
616 g_free(pbap->params);
619 pbap->params = params;
621 if (g_ascii_strcasecmp(type, PHONEBOOK_TYPE) == 0) {
622 /* Always contains the absolute path */
623 if (g_path_is_absolute(name))
624 path = g_strdup(name);
626 path = g_build_filename("/", name, NULL);
628 } else if (g_ascii_strcasecmp(type, VCARDLISTING_TYPE) == 0) {
629 /* Always relative */
630 if (!name || strlen(name) == 0)
632 path = g_strdup(pbap->folder);
634 /* Current folder + relative path */
635 path = g_build_filename(pbap->folder, name, NULL);
637 } else if (g_ascii_strcasecmp(type, VCARDENTRY_TYPE) == 0) {
639 path = g_strdup(name);
646 ret = obex_get_stream_start(os, path);
653 static int pbap_setpath(struct obex_session *os, void *user_data)
655 struct pbap_session *pbap = user_data;
657 const uint8_t *nonhdr;
661 if (obex_get_non_header_data(os, &nonhdr) != 2) {
662 error("Set path failed: flag and constants not found!");
666 name = obex_get_name(os);
668 DBG("name %s folder %s nonhdr 0x%x%x", name, pbap->folder,
669 nonhdr[0], nonhdr[1]);
671 fullname = phonebook_set_folder(pbap->folder, name, nonhdr[0], &err);
675 g_free(pbap->folder);
676 pbap->folder = fullname;
679 * FIXME: Define a criteria to mark the cache as invalid
681 pbap->cache.valid = FALSE;
682 pbap->cache.index = 0;
683 cache_clear(&pbap->cache);
688 static void pbap_disconnect(struct obex_session *os, void *user_data)
690 struct pbap_session *pbap = user_data;
692 manager_unregister_session(os);
694 #ifdef __TIZEN_PATCH__
698 phonebook_disconnect(pbap->backend_data);
702 pbap->obj->session = NULL;
705 g_free(pbap->params->searchval);
706 g_free(pbap->params);
709 cache_clear(&pbap->cache);
710 g_free(pbap->folder);
714 static int pbap_chkput(struct obex_session *os, void *user_data)
716 /* Rejects all PUTs */
720 static struct obex_service_driver pbap = {
721 .name = "Phonebook Access server",
722 .service = OBEX_PBAP,
723 .target = PBAP_TARGET,
724 .target_size = TARGET_SIZE,
725 .connect = pbap_connect,
727 .setpath = pbap_setpath,
728 .disconnect = pbap_disconnect,
729 .chkput = pbap_chkput
732 static struct pbap_object *vobject_create(struct pbap_session *pbap,
735 struct pbap_object *obj;
737 obj = g_new0(struct pbap_object, 1);
740 obj->request = request;
745 static void *vobject_pull_open(const char *name, int oflag, mode_t mode,
746 void *context, size_t *size, int *err)
748 struct pbap_session *pbap = context;
753 DBG("name %s context %p maxlistcount %d", name, context,
754 pbap->params->maxlistcount);
756 if (oflag != O_RDONLY) {
765 #ifdef __TIZEN_PATCH__
766 if (strcmp(name, "/telecom/mch.vcf") == 0)
767 pbap->params->required_missedcall_call_header = TRUE;
769 DBG("[%s] - required_missedcall_call_header [%d] ",
770 name,pbap->params->required_missedcall_call_header);
771 #endif /* __TIZEN_PATCH__ */
773 if (pbap->params->maxlistcount == 0)
774 cb = phonebook_size_result;
778 request = phonebook_pull(name, pbap->params, cb, pbap, &ret);
783 /* reading first part of results from backend */
784 ret = phonebook_pull_read(request);
791 return vobject_create(pbap, request);
800 static int vobject_close(void *object)
802 struct pbap_object *obj = object;
807 obj->session->obj = NULL;
810 g_string_free(obj->buffer, TRUE);
813 g_obex_apparam_free(obj->apparam);
816 phonebook_req_finalize(obj->request);
823 static void *vobject_list_open(const char *name, int oflag, mode_t mode,
824 void *context, size_t *size, int *err)
826 struct pbap_session *pbap = context;
827 struct pbap_object *obj = NULL;
836 DBG("name %s context %p valid %d", name, context, pbap->cache.valid);
838 if (oflag != O_RDONLY) {
842 #ifdef __TIZEN_PATCH__
843 if (strcmp(name, "/telecom/mch") == 0)
844 pbap->params->required_missedcall_call_header = TRUE;
846 DBG("[%s] - required_missedcall_call_header [%d] ",
847 name,pbap->params->required_missedcall_call_header);
848 #endif /* __TIZEN_PATCH__ */
850 /* PullvCardListing always get the contacts from the cache */
852 if (pbap->cache.valid) {
853 obj = vobject_create(pbap, NULL);
854 ret = generate_response(pbap);
856 request = phonebook_create_cache(name, cache_entry_notify,
857 cache_ready_notify, pbap, &ret);
859 obj = vobject_create(pbap, request);
860 #ifdef __TIZEN_PATCH__
861 phonebook_set_cache_notification(pbap->backend_data,
862 cache_clear_notify, pbap);
883 static void *vobject_vcard_open(const char *name, int oflag, mode_t mode,
884 void *context, size_t *size, int *err)
886 struct pbap_session *pbap = context;
889 #ifdef __TIZEN_PATCH__
896 DBG("name %s context %p valid %d", name, context, pbap->cache.valid);
898 if (oflag != O_RDONLY) {
903 if (name == NULL || sscanf(name, "%u.vcf", &handle) != 1) {
908 if (pbap->cache.valid == FALSE) {
909 pbap->find_handle = handle;
910 #ifdef __TIZEN_PATCH__
911 request = phonebook_create_cache(pbap->folder,
912 (phonebook_entry_cb)cache_entry_notify,
913 (phonebook_cache_ready_cb)cache_entry_done, pbap, &ret);
915 request = phonebook_create_cache(pbap->folder,
916 cache_entry_notify, cache_entry_done, pbap, &ret);
918 #ifdef __TIZEN_PATCH__
919 phonebook_set_cache_notification(pbap->backend_data,
920 cache_clear_notify, pbap);
925 id = cache_find(&pbap->cache, handle);
930 DBG("pbap session: Folder[%s] Filter[%llu] Format[%d] ret[%d]",
931 pbap->folder, pbap->params->filter, pbap->params->format, ret);
933 request = phonebook_get_entry(pbap->folder, id, pbap->params,
934 query_result, pbap, &ret);
944 return vobject_create(pbap, request);
954 static ssize_t vobject_pull_get_next_header(void *object, void *buf, size_t mtu,
957 struct pbap_object *obj = object;
958 struct pbap_session *pbap = obj->session;
960 if (!obj->buffer && !obj->apparam)
963 *hi = G_OBEX_HDR_APPARAM;
965 if (pbap->params->maxlistcount == 0 || obj->firstpacket) {
966 obj->firstpacket = FALSE;
968 return g_obex_apparam_encode(obj->apparam, buf, mtu);
974 static ssize_t vobject_pull_read(void *object, void *buf, size_t count)
976 struct pbap_object *obj = object;
977 struct pbap_session *pbap = obj->session;
980 DBG("buffer %p maxlistcount %d", obj->buffer,
981 pbap->params->maxlistcount);
984 if (pbap->params->maxlistcount == 0)
990 len = string_read(obj->buffer, buf, count);
991 if (len == 0 && !obj->lastpart) {
992 /* in case when buffer is empty and we know that more
993 * data is still available in backend, requesting new
994 * data part via phonebook_pull_read and returning
995 * -EAGAIN to suspend request for now */
996 ret = phonebook_pull_read(obj->request);
1006 static ssize_t vobject_list_get_next_header(void *object, void *buf, size_t mtu,
1009 struct pbap_object *obj = object;
1010 struct pbap_session *pbap = obj->session;
1012 /* Backend still busy reading contacts */
1013 if (!pbap->cache.valid)
1016 *hi = G_OBEX_HDR_APPARAM;
1017 #ifndef __TIZEN_PATCH__
1018 if (pbap->params->maxlistcount == 0)
1019 return g_obex_apparam_encode(obj->apparam, buf, mtu);
1023 if (obj->apparam != NULL)
1024 return g_obex_apparam_encode(obj->apparam, buf, mtu);
1027 #endif /* __TIZEN_PATCH__ */
1030 static ssize_t vobject_list_read(void *object, void *buf, size_t count)
1032 struct pbap_object *obj = object;
1033 struct pbap_session *pbap = obj->session;
1035 DBG("valid %d maxlistcount %d", pbap->cache.valid,
1036 pbap->params->maxlistcount);
1038 if (pbap->params->maxlistcount == 0)
1041 return string_read(obj->buffer, buf, count);
1044 static ssize_t vobject_vcard_read(void *object, void *buf, size_t count)
1046 struct pbap_object *obj = object;
1048 DBG("buffer %p", obj->buffer);
1053 return string_read(obj->buffer, buf, count);
1056 static struct obex_mime_type_driver mime_pull = {
1057 .target = PBAP_TARGET,
1058 .target_size = TARGET_SIZE,
1059 .mimetype = "x-bt/phonebook",
1060 .open = vobject_pull_open,
1061 .close = vobject_close,
1062 .read = vobject_pull_read,
1063 .get_next_header = vobject_pull_get_next_header,
1066 static struct obex_mime_type_driver mime_list = {
1067 .target = PBAP_TARGET,
1068 .target_size = TARGET_SIZE,
1069 .mimetype = "x-bt/vcard-listing",
1070 .open = vobject_list_open,
1071 .close = vobject_close,
1072 .read = vobject_list_read,
1073 .get_next_header = vobject_list_get_next_header,
1076 static struct obex_mime_type_driver mime_vcard = {
1077 .target = PBAP_TARGET,
1078 .target_size = TARGET_SIZE,
1079 .mimetype = "x-bt/vcard",
1080 .open = vobject_vcard_open,
1081 .close = vobject_close,
1082 .read = vobject_vcard_read,
1085 static int pbap_init(void)
1089 err = phonebook_init();
1093 err = obex_mime_type_driver_register(&mime_pull);
1095 goto fail_mime_pull;
1097 err = obex_mime_type_driver_register(&mime_list);
1099 goto fail_mime_list;
1101 err = obex_mime_type_driver_register(&mime_vcard);
1103 goto fail_mime_vcard;
1105 err = obex_service_driver_register(&pbap);
1112 obex_mime_type_driver_unregister(&mime_vcard);
1114 obex_mime_type_driver_unregister(&mime_list);
1116 obex_mime_type_driver_unregister(&mime_pull);
1123 static void pbap_exit(void)
1125 obex_service_driver_unregister(&pbap);
1126 obex_mime_type_driver_unregister(&mime_pull);
1127 obex_mime_type_driver_unregister(&mime_list);
1128 obex_mime_type_driver_unregister(&mime_vcard);
1132 OBEX_PLUGIN_DEFINE(pbap, pbap_init, pbap_exit)