summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md68
-rw-r--r--binding/CMakeLists.txt1
-rw-r--r--binding/bluetooth-pbap-binding.c27
-rw-r--r--binding/bluetooth-pbap-common.h23
-rw-r--r--binding/bluetooth-vcard-parser.c246
5 files changed, 332 insertions, 33 deletions
diff --git a/README.md b/README.md
index cc89cef..5d72482 100644
--- a/README.md
+++ b/README.md
@@ -21,9 +21,24 @@ Bluetooth PBAP service reports respective vCard phonebook data from BlueZ via co
Returns all vCards that are accessible from respective connected device in concatenated output:
<pre>
- "response": {
- "vcards": "BEGIN:VCARD\r\nVERSION:3.0\r\nFN:Art McGee\r\nN:Art\r\nTEL: +13305551212\r\nUID:27e\r\nEND:VCARD\r\n"
- }
+
+"response": {
+ "vcards": [
+ {
+ "fn": "Art McGee",
+ "photo": {
+ "mimetype": "image/jpeg",
+ "data": 'BASE64 IMAGE BLOB HERE'
+ },
+ "telephone": [
+ {
+ "CELL": "+13305551212"
+ }
+ ]
+ },
+ ...
+ ]
+}
</pre>
### search Verb
@@ -31,14 +46,14 @@ Returns all vCards that are accessible from respective connected device in conca
Example of a request for vCard search using **number** parameter (i.e. *{"number":"+15035551212"}*) results:
<pre>
- "response": {
- "results": [
- {
- "handle": "27e.vcf",
- "name": "Art McGee"
- }
- ]
- } },
+"response": {
+ "results": [
+ {
+ "handle": "27e.vcf",
+ "name": "Art McGee"
+ }
+ ]
+} },
</pre>
### entry Verb
@@ -55,11 +70,7 @@ Client must pass one of the following values to the **list** parameter in reques
Also there is a **handle** parameter that must be in form of vCard path (e.g. 27e.vcf).
-<pre>
- "response": {
- "vcard":"BEGIN:VCARD\r\nVERSION:3.0\r\nFN:Art McGee\r\nN:;Art\r\nTEL;TYPE=CELL:+13305551212\r\nUID:27e\r\nEND:VCARD\r\n"
- }
-</pre>
+Response is the same as noted in the **contacts** verb
### history Verb
@@ -75,9 +86,28 @@ Client must pass one of the following values to the list parameter in request:
Sample request for a combined list (i.e. *{"list":"cch"}*) and its respective response:
<pre>
- "response": {
- "vcards":"BEGIN:VCARD\r\nVERSION:3.0\r\nFN:Art \r\nN:\r\nTEL:3305551212\r\nX-IRMC-CALL-DATETIME;DIALED:20190103T202524\r\nEND:VCARD\r\nBEGIN:VCARD\r\nVERSION:3.0\r\nFN:Art McGee\r\nN:;Art\r\nTEL;TYPE=CELL:+15035551212\r\nUID:27e\r\nX-IRMC-CALL-DATETIME;RECEIVED:20181207T065311\r\nEND:VCARD\r\nBEGIN:VCARD\r\nVERSION:3.0\r\n"
- }
+"response": {
+ "vcards": [
+ {
+ "fn": "Art McGee"
+ "type": "DIALED",
+ "timestamp": "20190509T193422",
+ "telephone": "+13305551212"
+ },
+ {
+ "fn": "UNKNOWN CALLER",
+ "type": "MISSED",
+ "timestamp": "20190426T014109",
+ "telephone": "+15035551212"
+ },
+ {
+ "fn": "Satoshi Nakamoto"
+ "type": "RECEIVED",
+ "timestamp": "20190421T090123",
+ "telephone": "+13605551212"
+ }
+ ]
+}
</pre>
## Events
diff --git a/binding/CMakeLists.txt b/binding/CMakeLists.txt
index 2477384..3ed0035 100644
--- a/binding/CMakeLists.txt
+++ b/binding/CMakeLists.txt
@@ -23,6 +23,7 @@ PROJECT_TARGET_ADD(bluetooth-pbap-binding)
# Define project Targets
add_library(bluetooth-pbap-binding MODULE
bluetooth-pbap-binding.c
+ bluetooth-vcard-parser.c
gdbus/freedesktop_dbus_properties_interface.c
gdbus/obex_client1_interface.c
gdbus/obex_phonebookaccess1_interface.c
diff --git a/binding/bluetooth-pbap-binding.c b/binding/bluetooth-pbap-binding.c
index 76ee9ad..8dfba3d 100644
--- a/binding/bluetooth-pbap-binding.c
+++ b/binding/bluetooth-pbap-binding.c
@@ -35,6 +35,8 @@
#include "obex_phonebookaccess1_interface.h"
#include "freedesktop_dbus_properties_interface.h"
+#include "bluetooth-pbap-common.h"
+
static GDBusObjectManager *obj_manager;
static OrgBluezObexClient1 *client;
static OrgBluezObexSession1 *session;
@@ -152,10 +154,9 @@ static void on_interface_proxy_properties_changed(
}
}
-static json_object *get_vcard_xfer(gchar *filename)
+static char *get_vcard_xfer(gchar *filename)
{
FILE *fp;
- json_object *vcard_str;
gchar *vcard_data;
size_t size, n;
@@ -170,12 +171,10 @@ static json_object *get_vcard_xfer(gchar *filename)
return NULL;
}
- vcard_str = json_object_new_string(vcard_data);
- free(vcard_data);
fclose(fp);
unlink(filename);
- return vcard_str;
+ return vcard_data;
}
static void get_filename(gchar *filename)
@@ -224,8 +223,8 @@ static gchar *pull_vcard(const gchar *handle)
static json_object *get_vcard(const gchar *handle)
{
- json_object *vcard_str = NULL, *vcard = NULL;
- gchar *tpath, *filename;
+ json_object *vcard;
+ gchar *tpath, *filename, *vcard_str = NULL;
tpath = pull_vcard(handle);
@@ -240,10 +239,9 @@ static json_object *get_vcard(const gchar *handle)
g_free(filename);
g_mutex_unlock(&xfer_complete_mutex);
- if (vcard_str) {
- vcard = json_object_new_object();
- json_object_object_add(vcard, "vcard", vcard_str);
- }
+ vcard = json_object_new_object();
+ json_object_object_add(vcard, "vcards", parse_vcards(vcard_str));
+ g_free(vcard_str);
return vcard;
}
@@ -276,8 +274,8 @@ static gchar *pull_vcards(int max_entries)
static json_object *get_vcards(int max_entries)
{
- json_object *vcards_str, *vcards;
- gchar *tpath, *filename;
+ json_object *vcards;
+ gchar *tpath, *filename, *vcards_str = NULL;
tpath = pull_vcards(max_entries);
g_mutex_lock(&xfer_complete_mutex);
@@ -290,7 +288,8 @@ static json_object *get_vcards(int max_entries)
g_mutex_unlock(&xfer_complete_mutex);
vcards = json_object_new_object();
- json_object_object_add(vcards, "vcards", vcards_str);
+ json_object_object_add(vcards, "vcards", parse_vcards(vcards_str));
+ g_free(vcards_str);
return vcards;
}
diff --git a/binding/bluetooth-pbap-common.h b/binding/bluetooth-pbap-common.h
new file mode 100644
index 0000000..8fdbdc7
--- /dev/null
+++ b/binding/bluetooth-pbap-common.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2019 Konsulko Group
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef BLUETOOTH_PBAP_COMMON_H
+#define BLUETOOTH_PBAP_COMMON_H
+
+json_object *parse_vcards(const char *message);
+
+#endif /* BLUETOOTH_PBAP_COMMON_H */
diff --git a/binding/bluetooth-vcard-parser.c b/binding/bluetooth-vcard-parser.c
new file mode 100644
index 0000000..927443a
--- /dev/null
+++ b/binding/bluetooth-vcard-parser.c
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2019 Konsulko Group
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <string.h>
+#include <glib.h>
+
+#include <json-c/json.h>
+
+#define CRLF "\r\n"
+#define SUPPORTED_VERSION "3.0"
+
+enum {
+ VC_FORMATTED_NAME,
+ VC_TELEPHONE,
+ VC_DATETIME,
+ VC_PHOTO,
+ VC_FIELD_COUNT,
+};
+
+static const gchar *VC_PREFIXES[VC_FIELD_COUNT] = {
+ "FN",
+ "TEL",
+ "X-IRMC-CALL-DATETIME",
+ "PHOTO",
+};
+
+#define vc_len(name) strlen(VC_PREFIXES[name])
+
+#define vc_strstr(ptr, name) \
+ ({ \
+ void *_ptr = strstr(ptr, name); \
+ if (_ptr) _ptr += strlen(name); \
+ _ptr; \
+ })
+
+static void add_fn(json_object *resp, gchar *msg)
+{
+ gchar *name = msg + vc_len(VC_FORMATTED_NAME) + 1;
+
+ if (!name)
+ name = "UNKNOWN";
+
+ json_object_object_add(resp, "fn", json_object_new_string(name));
+}
+
+static void add_phone(json_object *array, gchar *msg)
+{
+ gchar *ptr = strstr(msg, ":");
+ json_object *dict, *number;
+
+ if (!ptr || !strlen(ptr + 1))
+ return;
+
+ number = json_object_new_string(ptr + 1);
+
+ ptr = vc_strstr(msg, "TYPE=");
+ if (!ptr)
+ ptr = "UNKNOWN";
+ else
+ ptr = strsep(&ptr, ",;:");
+
+ dict = json_object_new_object();
+ json_object_object_add(dict, ptr, number);
+ json_object_array_add(array, dict);
+}
+
+static void add_datetime(json_object *resp, gchar *msg)
+{
+ gchar *ptr;
+
+ msg += vc_len(VC_DATETIME) + 1;
+
+ ptr = strsep(&msg, ":");
+ if (!ptr)
+ return;
+ json_object_object_add(resp, "type", json_object_new_string(ptr));
+
+ ptr = strsep(&msg, "");
+ if (!ptr)
+ return;
+ json_object_object_add(resp, "timestamp", json_object_new_string(ptr));
+}
+
+static gchar **extract_photo(json_object *resp, gchar **msg)
+{
+ GString *data;
+ gchar *ptr;
+ json_object *obj;
+
+ // TODO: handle other mimetypes
+ if (!strstr(*msg, "TYPE=JPEG"))
+ return msg;
+
+ ptr = strstr(*msg, ":");
+ if (!ptr)
+ return msg;
+
+ data = g_string_new(NULL);
+ g_string_append(data, ptr + 1);
+
+ if (!*msg++)
+ return msg;
+
+ for (; *msg; msg++) {
+ if (!strlen(*msg))
+ break;
+ if (!g_ascii_isspace(**msg))
+ break;
+ g_string_append(data, g_strchug(*msg));
+ }
+
+ obj = json_object_new_object();
+ json_object_object_add(obj, "mimetype", json_object_new_string("image/jpeg"));
+ json_object_object_add(obj, "data", json_object_new_string(data->str));
+ json_object_object_add(resp, "photo", obj);
+
+ g_string_free(data, TRUE);
+
+ return msg;
+}
+
+static int tag_index(gchar *msg)
+{
+ gchar *ptr;
+ int i;
+
+ for (i = 0; i < VC_FIELD_COUNT; i++) {
+ if (!g_str_has_prefix(msg, VC_PREFIXES[i]))
+ continue;
+
+ ptr = vc_strstr(msg, VC_PREFIXES[i]);
+ if (*ptr == ':' || *ptr == ';')
+ return i;
+
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static void process_vcard(json_object *resp, gchar **start, gchar **end)
+{
+ json_object *vcard = json_object_new_object();
+ json_object *phone = NULL;
+ gboolean history = FALSE;
+
+ for (; start != end; start++) {
+ int idx = tag_index(*start);
+
+ switch (idx) {
+ case VC_FORMATTED_NAME:
+ add_fn(vcard, *start);
+ break;
+ case VC_TELEPHONE:
+ if (!phone)
+ phone = json_object_new_array();
+ add_phone(phone, *start);
+ break;
+ case VC_DATETIME:
+ history = TRUE;
+ add_datetime(vcard, *start);
+ break;
+ case VC_PHOTO:
+ start = extract_photo(vcard, start);
+ break;
+ default:
+ continue;
+ }
+ }
+
+ if (phone) {
+ if (!json_object_array_length(phone)) {
+ // No phone numbers, so discard
+ json_object_put(phone);
+ json_object_put(vcard);
+ return;
+ }
+
+ if (!history)
+ json_object_object_add(vcard, "telephone", phone);
+ else {
+ json_object *number = json_object_array_get_idx(phone, 0);
+
+ json_object_object_foreach(number, unused, val) {
+ json_object_object_add(vcard, "telephone", val);
+ (void) unused;
+ }
+ }
+ }
+ json_object_array_add(resp, vcard);
+}
+
+json_object *parse_vcards(const char *message)
+{
+ gchar **lines, **tmp, **ptr = NULL;
+ json_object *resp;
+
+ if (!message || !strlen(message))
+ return NULL;
+
+ resp = json_object_new_array();
+ lines = g_strsplit(message, CRLF, -1);
+
+ for (tmp = lines; *tmp; tmp++) {
+ // BEGIN:VCARD
+ if (!g_strcmp0(*tmp, "BEGIN:VCARD")) {
+ ptr = tmp + 1;
+ continue;
+ }
+
+ if (!ptr)
+ continue;
+
+ // VERSION should be the line after BEGIN:VCARD
+ if (tmp == ptr && g_strcmp0(*tmp, "VERSION:" SUPPORTED_VERSION)) {
+ ptr = NULL;
+ continue;
+ }
+
+ // END:VCARD
+ if (!g_strcmp0(*tmp, "END:VCARD")) {
+ process_vcard(resp, ptr, tmp);
+ ptr = NULL;
+ }
+ }
+
+ g_strfreev(lines);
+
+ return resp;
+}