2 * Copyright (C) 2018 Konsulko Group
3 * Author: Matt Porter <mporter@konsulko.com>
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
21 #include <json-c/json.h>
29 #define AFB_BINDING_VERSION 3
30 #include <afb/afb-binding.h>
32 #include "obex_client1_interface.h"
33 #include "obex_session1_interface.h"
34 #include "obex_transfer1_interface.h"
35 #include "obex_phonebookaccess1_interface.h"
36 #include "freedesktop_dbus_properties_interface.h"
38 #include "bluetooth-pbap-common.h"
40 static GDBusObjectManager *obj_manager;
41 static OrgBluezObexClient1 *client;
42 static OrgBluezObexSession1 *session;
43 static OrgBluezObexPhonebookAccess1 *phonebook;
44 static GHashTable *xfer_queue;
45 static GMutex xfer_queue_mutex;
46 static GHashTable *xfer_complete;
47 static GMutex xfer_complete_mutex;
48 static GCond xfer_complete_cond;
49 static GMutex connected_mutex;
50 static gboolean connected = FALSE;
51 static gchar *connected_address = NULL;
52 static afb_event_t status_event;
54 #define PBAP_UUID "0000112f-0000-1000-8000-00805f9b34fb"
56 #define INTERNAL "int"
62 #define COMBINED "cch"
63 #define INCOMING "ich"
64 #define OUTGOING "och"
68 static int update_or_insert(const char *key, const char *value)
70 json_object *query = json_object_new_object();
73 json_object_object_add(query, "key", json_object_new_string(key));
74 json_object_object_add(query, "value", json_object_new_string(value));
75 json_object_get(query);
77 ret = afb_service_call_sync("persistence", "update", query, NULL, NULL, NULL);
79 AFB_DEBUG("Updating persistence value '%s'", key);
80 json_object_put(query);
84 ret = afb_service_call_sync("persistence", "write", query, NULL, NULL, NULL);
86 AFB_DEBUG("Create persistence value '%s'", key);
93 static int read_cached_value(const char *key, const char **data)
95 json_object *response, *query;
98 query = json_object_new_object();
99 json_object_object_add(query, "key", json_object_new_string(key));
101 ret = afb_service_call_sync("persistence", "read", query, &response, NULL, NULL);
103 json_object *val = NULL;
105 json_object_object_get_ex(response, "value", &val);
109 *data = g_strdup(json_object_get_string(val));
112 json_object_get(response);
117 static void on_interface_proxy_properties_changed(
118 GDBusObjectManagerClient *manager,
119 GDBusObjectProxy *object_proxy,
120 GDBusProxy *interface_proxy,
121 GVariant *changed_properties,
122 const gchar *const *invalidated_properties,
130 const gchar *path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object_proxy));
132 if ((filename = g_hash_table_lookup(xfer_queue, path))) {
133 g_variant_iter_init(&iter, changed_properties);
134 while (g_variant_iter_next(&iter, "{&sv}", &key, &value)) {
135 const gchar *val = NULL;
137 if (g_strcmp0(key, "Status"))
140 val = g_variant_get_string(value, NULL);
142 if (g_strcmp0(val, "complete") && g_strcmp0(val, "error"))
145 g_mutex_lock(&xfer_complete_mutex);
146 g_hash_table_insert(xfer_complete, g_strdup(path),
147 g_strdup(!g_strcmp0(val, "complete") ? filename : ""));
148 g_cond_signal(&xfer_complete_cond);
149 g_mutex_unlock(&xfer_complete_mutex);
150 g_mutex_lock(&xfer_queue_mutex);
151 g_hash_table_remove(xfer_queue, path);
152 g_mutex_unlock(&xfer_queue_mutex);
157 static char *get_vcard_xfer(gchar *filename)
163 fp = fopen(filename, "ro");
164 fseek(fp, 0L, SEEK_END);
166 vcard_data = calloc(1, size);
167 fseek(fp, 0L, SEEK_SET);
168 n = fread(vcard_data, 1, size, fp);
170 AFB_ERROR("Read only %ld/%ld bytes from %s", n, size, filename);
180 static void get_filename(gchar *filename)
187 gettimeofday(&tv, NULL);
189 ms = lrint((double)tv.tv_usec/1000.0);
194 tm_info = localtime(&tv.tv_sec);
196 strftime(buffer, 26, "%Y%m%d%H%M%S", tm_info);
198 sprintf(filename, "/tmp/vcard-%s%03ld.dat", buffer, ms);
201 static gchar *pull_vcard(const gchar *handle)
204 GVariant *filter, *properties;
208 b = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
209 g_variant_builder_add(b, "{sv}", "Format", g_variant_new_string("vcard30"));
210 filter = g_variant_builder_end(b);
212 get_filename(filename);
213 org_bluez_obex_phonebook_access1_call_pull_sync(
214 phonebook, handle, filename, filter, &tpath, &properties, NULL, NULL);
216 g_variant_builder_unref(b);
217 g_mutex_lock(&xfer_queue_mutex);
218 g_hash_table_insert(xfer_queue, g_strdup(tpath), g_strdup(filename));
219 g_mutex_unlock(&xfer_queue_mutex);
224 static json_object *get_vcard(const gchar *handle)
227 gchar *tpath, *filename, *vcard_str = NULL;
229 tpath = pull_vcard(handle);
231 g_mutex_lock(&xfer_complete_mutex);
232 while (!(filename = g_hash_table_lookup(xfer_complete, tpath)))
233 g_cond_wait(&xfer_complete_cond, &xfer_complete_mutex);
235 if (strlen(filename) > 0)
236 vcard_str = get_vcard_xfer(filename);
238 g_hash_table_remove(xfer_complete, tpath);
240 g_mutex_unlock(&xfer_complete_mutex);
242 vcard = json_object_new_object();
243 json_object_object_add(vcard, "vcards", parse_vcards(vcard_str));
249 static gchar *pull_vcards(int max_entries)
252 GVariant *filter, *properties;
256 b = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
257 g_variant_builder_add(b, "{sv}", "Format", g_variant_new_string("vcard30"));
258 g_variant_builder_add(b, "{sv}", "Order", g_variant_new_string("indexed"));
259 g_variant_builder_add(b, "{sv}", "Offset", g_variant_new_uint16(0));
260 if (max_entries >= 0)
261 g_variant_builder_add(b, "{sv}", "MaxCount", g_variant_new_uint16((guint16)max_entries));
262 filter = g_variant_builder_end(b);
264 get_filename(filename);
265 org_bluez_obex_phonebook_access1_call_pull_all_sync(
266 phonebook, filename, filter, &tpath, &properties, NULL, NULL);
267 g_variant_builder_unref(b);
268 g_mutex_lock(&xfer_queue_mutex);
269 g_hash_table_insert(xfer_queue, g_strdup(tpath), g_strdup(filename));
270 g_mutex_unlock(&xfer_queue_mutex);
275 static json_object *get_vcards(int max_entries)
278 gchar *tpath, *filename, *vcards_str = NULL;
280 tpath = pull_vcards(max_entries);
281 g_mutex_lock(&xfer_complete_mutex);
282 while (!(filename = g_hash_table_lookup(xfer_complete, tpath)))
283 g_cond_wait(&xfer_complete_cond, &xfer_complete_mutex);
285 vcards_str = get_vcard_xfer(filename);
287 g_hash_table_remove(xfer_complete, tpath);
288 g_mutex_unlock(&xfer_complete_mutex);
290 vcards = json_object_new_object();
291 json_object_object_add(vcards, "vcards", parse_vcards(vcards_str));
297 static gboolean parse_list_parameter(afb_req_t request, gchar **list)
299 struct json_object *list_obj, *query;
300 const gchar *list_str;
302 query = afb_req_json(request);
304 if (json_object_object_get_ex(query, "list", &list_obj) == TRUE) {
305 if (json_object_is_type(list_obj, json_type_string)) {
306 list_str = json_object_get_string(list_obj);
307 if (!g_strcmp0(list_str, COMBINED)) {
309 } else if (!g_strcmp0(list_str, INCOMING)) {
311 } else if (!g_strcmp0(list_str, OUTGOING)) {
313 } else if (!g_strcmp0(list_str, MISSED)) {
315 } else if (!g_strcmp0(list_str, CONTACTS)) {
318 afb_req_fail(request, "invalid list", NULL);
322 afb_req_fail(request, "list not string", NULL);
326 afb_req_fail(request, "no list", NULL);
333 static gboolean parse_max_entries_parameter(afb_req_t request, int *max_entries)
335 struct json_object *max_obj, *query;
337 query = afb_req_json(request);
339 if (json_object_object_get_ex(query, "max_entries", &max_obj) == TRUE) {
340 if (json_object_is_type(max_obj, json_type_int)) {
341 *max_entries = json_object_get_int(max_obj);
342 if ((*max_entries < 0) || (*max_entries > (2e16-1))) {
343 afb_req_fail(request, "max_entries out of range", NULL);
347 afb_req_fail(request, "max_entries not integer", NULL);
355 void contacts(afb_req_t request)
357 struct json_object *jresp;
358 const char *cached = NULL;
361 afb_req_fail(request, "not connected", NULL);
365 if (!read_cached_value(connected_address, &cached)) {
366 jresp = json_tokener_parse(cached);
368 afb_req_fail(request, "no imported contacts", NULL);
371 afb_req_success(request, jresp, "contacts");
374 void import(afb_req_t request)
376 struct json_object *jresp;
377 int max_entries = -1;
380 g_mutex_lock(&connected_mutex);
382 afb_req_fail(request, "not connected", NULL);
383 g_mutex_unlock(&connected_mutex);
387 address = g_strdup(connected_address);
388 g_mutex_unlock(&connected_mutex);
390 if (!parse_max_entries_parameter(request, &max_entries))
393 if (!org_bluez_obex_phonebook_access1_call_select_sync(
394 phonebook, INTERNAL, CONTACTS, NULL, NULL)) {
395 afb_req_fail(request, "cannot import contacts", NULL);
399 jresp = get_vcards(max_entries);
400 update_or_insert(address,
401 json_object_to_json_string_ext(jresp, JSON_C_TO_STRING_PLAIN));
403 afb_req_success(request, jresp, "contacts");
409 void entry(afb_req_t request)
411 struct json_object *handle_obj, *jresp, *query;
416 afb_req_fail(request, "not connected", NULL);
420 query = afb_req_json(request);
422 if (json_object_object_get_ex(query, "handle", &handle_obj) == TRUE) {
423 if (json_object_is_type(handle_obj, json_type_string)) {
424 handle = json_object_get_string(handle_obj);
426 afb_req_fail(request, "handle not string", NULL);
430 afb_req_fail(request, "no handle", NULL);
434 if (!parse_list_parameter(request, &list))
437 org_bluez_obex_phonebook_access1_call_select_sync(
438 phonebook, INTERNAL, list, NULL, NULL);
439 jresp = get_vcard(handle);
442 afb_req_fail(request, "invalid handle", NULL);
446 afb_req_success(request, jresp, "list entry");
449 void history(afb_req_t request)
451 struct json_object *jresp;
453 int max_entries = -1;
456 afb_req_fail(request, "not connected", NULL);
460 if (!parse_list_parameter(request, &list))
463 if (!parse_max_entries_parameter(request, &max_entries))
466 org_bluez_obex_phonebook_access1_call_select_sync(
467 phonebook, INTERNAL, list, NULL, NULL);
468 jresp = get_vcards(max_entries);
470 afb_req_success(request, jresp, "call history");
473 static void search(afb_req_t request)
475 struct json_object *query, *val, *results_array, *response;
476 const char *number = NULL;
479 GVariant *entry, *filter, *results;
481 int max_entries = -1;
484 afb_req_fail(request, "not connected", NULL);
488 query = afb_req_json(request);
490 json_object_object_get_ex(query, "number", &val);
491 if (json_object_is_type(val, json_type_string)) {
492 number = json_object_get_string(val);
495 afb_req_fail(request, "no number", NULL);
499 if (!parse_max_entries_parameter(request, &max_entries))
502 org_bluez_obex_phonebook_access1_call_select_sync(
503 phonebook, INTERNAL, CONTACTS, NULL, NULL);
505 b = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
506 g_variant_builder_add(b, "{sv}", "Order", g_variant_new_string("indexed"));
507 g_variant_builder_add(b, "{sv}", "Offset", g_variant_new_uint16(0));
508 g_variant_builder_add(b, "{sv}", "Format", g_variant_new_string("vcard30"));
509 if (max_entries >= 0)
510 g_variant_builder_add(b, "{sv}", "MaxCount", g_variant_new_uint16((guint16)max_entries));
511 filter = g_variant_builder_end(b);
513 org_bluez_obex_phonebook_access1_call_search_sync(
514 phonebook, "number", number, filter, &results, NULL, NULL);
516 g_variant_builder_unref(b);
518 results_array = json_object_new_array();
519 g_variant_iter_init(&iter, results);
520 while ((entry = g_variant_iter_next_value(&iter))) {
521 g_variant_get(entry, "(ss)", &card, &name);
522 g_variant_unref(entry);
523 json_object *result_obj = json_object_new_object();
524 json_object *card_str = json_object_new_string(card);
525 json_object *name_str = json_object_new_string(name);
526 json_object_object_add(result_obj, "handle", card_str);
527 json_object_object_add(result_obj, "name", name_str);
528 json_object_array_add(results_array, result_obj);
532 response = json_object_new_object();
533 json_object_object_add(response, "results", results_array);
535 afb_req_success(request, response, NULL);
538 static void status(afb_req_t request)
540 struct json_object *response, *status;
542 response = json_object_new_object();
543 g_mutex_lock(&connected_mutex);
544 status = json_object_new_boolean(connected);
545 g_mutex_unlock(&connected_mutex);
546 json_object_object_add(response, "connected", status);
548 afb_req_success(request, response, NULL);
551 static void subscribe(afb_req_t request)
553 const char *value = afb_req_value(request, "value");
556 afb_req_fail(request, "failed", "No event");
560 if (!g_strcmp0(value, "status")) {
561 afb_req_subscribe(request, status_event);
562 afb_req_success(request, NULL, NULL);
564 struct json_object *event, *status;
565 event = json_object_new_object();
566 g_mutex_lock(&connected_mutex);
567 status = json_object_new_boolean(connected);
568 g_mutex_unlock(&connected_mutex);
569 json_object_object_add(event, "connected", status);
570 afb_event_push(status_event, event);
572 afb_req_fail(request, "failed", "Invalid event");
576 static void unsubscribe(afb_req_t request)
578 const char *value = afb_req_value(request, "value");
580 if (!g_strcmp0(value, "status")) {
581 afb_req_unsubscribe(request, status_event);
583 afb_req_fail(request, "failed", "Invalid event");
588 afb_req_success(request, NULL, NULL);
591 static gboolean init_session(const gchar *address)
598 xfer_queue = g_hash_table_new(g_str_hash, g_str_equal);
599 xfer_complete = g_hash_table_new(g_str_hash, g_str_equal);
601 obj_manager = object_manager_client_new_for_bus_sync(
603 G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
604 "org.bluez.obex", "/", NULL, NULL);
606 if (obj_manager == NULL) {
607 AFB_ERROR("Failed to create object manager");
611 g_signal_connect(obj_manager,
612 "interface-proxy-properties-changed",
613 G_CALLBACK (on_interface_proxy_properties_changed),
616 client = org_bluez_obex_client1_proxy_new_for_bus_sync(
617 G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE,
618 "org.bluez.obex", "/org/bluez/obex", NULL, NULL);
620 if (client == NULL) {
621 AFB_ERROR("Failed to create client proxy");
625 b = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
626 g_variant_builder_add(b, "{sv}", "Target", g_variant_new_string("pbap"));
627 args = g_variant_builder_end(b);
629 org_bluez_obex_client1_call_create_session_sync(
630 client, address, args, &spath, NULL, NULL);
632 session = org_bluez_obex_session1_proxy_new_for_bus_sync(
633 G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE,
634 "org.bluez.obex", spath, NULL, NULL);
636 target = org_bluez_obex_session1_get_target(session);
637 if (g_strcmp0(target, PBAP_UUID) != 0) {
638 AFB_ERROR("Device does not support PBAP");
642 phonebook = org_bluez_obex_phonebook_access1_proxy_new_for_bus_sync(
643 G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE,
644 "org.bluez.obex", spath, NULL, NULL);
649 static gboolean is_pbap_dev_and_init(struct json_object *dev)
651 struct json_object *jresp, *props = NULL, *val = NULL;
654 json_object_object_get_ex(dev, "properties", &props);
658 json_object_object_get_ex(props, "connected", &val);
659 if (!val || !json_object_get_boolean(val))
662 json_object_object_get_ex(props, "uuids", &val);
663 for (i = 0; i < json_object_array_length(val); i++) {
664 const char *uuid = json_object_get_string(json_object_array_get_idx(val, i));
665 const char *address = NULL;
666 struct json_object *val1 = NULL;
668 if (g_strcmp0(PBAP_UUID, uuid))
671 json_object_object_get_ex(props, "address", &val1);
672 address = json_object_get_string(val1);
677 if (init_session(address)) {
678 jresp = json_object_new_object();
680 g_mutex_lock(&connected_mutex);
681 if (connected_address)
682 g_free(connected_address);
685 json_object_object_add(jresp, "connected",
686 json_object_new_boolean(connected));
688 json_object_object_get_ex(dev, "device", &val1);
689 connected_address = g_strdup(json_object_get_string(val1));
691 json_object_object_add(jresp, "device",
692 json_object_new_string(connected_address));
694 g_mutex_unlock(&connected_mutex);
696 afb_event_push(status_event, jresp);
698 AFB_NOTICE("PBAP device connected: %s", connected_address);
708 static void discovery_result_cb(void *closure, struct json_object *result,
709 const char *error, const char *info,
713 struct json_object *dev, *tmp;
716 if (!json_object_object_get_ex(result, "devices", &tmp))
718 type = json_object_get_type(tmp);
720 if (type != json_type_array)
723 for (i = 0; i < json_object_array_length(tmp); i++) {
724 dev = json_object_array_get_idx(tmp, i);
725 if (is_pbap_dev_and_init(dev))
730 static void init_bt(afb_api_t api)
732 struct json_object *args;
734 args = json_object_new_object();
735 json_object_object_add(args , "value", json_object_new_string("device_changes"));
736 afb_api_call_sync(api, "Bluetooth-Manager", "subscribe", args, NULL, NULL, NULL);
738 args = json_object_new_object();
739 afb_api_call(api, "Bluetooth-Manager", "managed_objects", args, discovery_result_cb, NULL);
742 static const afb_verb_t binding_verbs[] = {
743 { .verb = "contacts", .callback = contacts, .info = "List contacts" },
744 { .verb = "import", .callback = import, .info = "Import contacts" },
745 { .verb = "entry", .callback = entry, .info = "List call entry" },
746 { .verb = "history", .callback = history, .info = "List call history" },
747 { .verb = "search", .callback = search, .info = "Search for entry" },
748 { .verb = "status", .callback = status, .info = "Get status" },
749 { .verb = "subscribe", .callback = subscribe, .info = "Subscribe to events" },
750 { .verb = "unsubscribe",.callback = unsubscribe, .info = "Unsubscribe to events" },
754 static void *main_loop_thread(void *unused)
756 GMainLoop *loop = g_main_loop_new(NULL, FALSE);
757 g_main_loop_run(loop);
762 static int init(afb_api_t api)
764 AFB_NOTICE("PBAP binding init");
769 status_event = afb_daemon_make_event("status");
771 ret = afb_daemon_require_api("Bluetooth-Manager", 1);
773 AFB_ERROR("unable to initialize bluetooth binding");
777 /* Start the main loop thread */
778 pthread_create(&tid, NULL, main_loop_thread, NULL);
786 static void process_connection_event(afb_api_t api, struct json_object *object)
788 struct json_object *jresp, *val = NULL, *props = NULL;
789 const char *action, *device;
791 json_object_object_get_ex(object, "action", &val);
794 action = json_object_get_string(val);
795 if (g_strcmp0("changed", action))
798 json_object_object_get_ex(object, "properties", &props);
802 json_object_object_get_ex(props, "connected", &val);
806 if (json_object_get_boolean(val)) {
807 struct json_object *args = json_object_new_object();
808 afb_api_call(api, "Bluetooth-Manager", "managed_objects",
809 args, discovery_result_cb, NULL);
813 json_object_object_get_ex(object, "device", &val);
817 device = json_object_get_string(val);
818 jresp = json_object_new_object();
820 g_mutex_lock(&connected_mutex);
822 json_object_object_add(jresp, "connected", json_object_new_boolean(connected));
823 json_object_object_add(jresp, "device", json_object_new_string(device));
824 g_mutex_unlock(&connected_mutex);
826 afb_event_push(status_event, jresp);
828 AFB_NOTICE("PBAP device disconnected: %s", device);
831 static void onevent(afb_api_t api, const char *event, struct json_object *object)
833 if (!g_ascii_strcasecmp(event, "Bluetooth-Manager/device_changes"))
834 process_connection_event(api, object);
836 AFB_ERROR("Unsupported event: %s\n", event);
839 const afb_binding_t afbBindingV3 = {
840 .api = "bluetooth-pbap",
841 .verbs = binding_verbs,