binding: bluetooth-pbap: add vcard parser framework 25/21325/4
authorMatt Ranostay <matt.ranostay@konsulko.com>
Tue, 14 May 2019 07:31:03 +0000 (00:31 -0700)
committerMatt Ranostay <matt.ranostay@konsulko.com>
Thu, 16 May 2019 09:20:38 +0000 (02:20 -0700)
Parse the vCard information within the binding itself, and
reduce service consumers processing and dependencies.

Bug-AGL: SPEC-2392
Change-Id: Ie7654c4ba6d269cb8224e61021e19739614afb4e
Signed-off-by: Matt Ranostay <matt.ranostay@konsulko.com>
README.md
binding/CMakeLists.txt
binding/bluetooth-pbap-binding.c
binding/bluetooth-pbap-common.h [new file with mode: 0644]
binding/bluetooth-vcard-parser.c [new file with mode: 0644]

index cc89cef..5d72482 100644 (file)
--- 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
index 2477384..3ed0035 100644 (file)
@@ -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
index 76ee9ad..8dfba3d 100644 (file)
@@ -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 (file)
index 0000000..8fdbdc7
--- /dev/null
@@ -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 (file)
index 0000000..927443a
--- /dev/null
@@ -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;
+}