diff options
-rw-r--r-- | README.md | 33 | ||||
-rw-r--r-- | binding/CMakeLists.txt | 4 | ||||
-rw-r--r-- | binding/bluetooth-map-api.c | 7 | ||||
-rw-r--r-- | binding/bluetooth-map-bmessage.c | 194 | ||||
-rw-r--r-- | binding/bluetooth-map-common.h | 4 |
5 files changed, 212 insertions, 30 deletions
@@ -59,30 +59,13 @@ Send a message (if supported) via MAP profile: <pre> { - "bmessage": - "BEGIN:BMSG\r\n - VERSION:1.0\r\n - STATUS:UNREAD\r\n - TYPE:SMS_GSM\r\n - FOLDER:telecom/msg/inbox\r\n - NOTIFICATION:1\r\n - BEGIN:VCARD\r\n - VERSION:2.1\r\n - FN;CHARSET=UTF-8:Satoshi Nakamoto\r\n - N;CHARSET=UTF-8:Satoshi\r\n - TEL:+13605551212\r\n - END:VCARD\r\n - BEGIN:BENV\r\n - BEGIN:BBODY\r\n - CHARSET:UTF-8\r\n - LANGUAGE:UNKNOWN\r\n - LENGTH:46\r\n - BEGIN:MSG\r\n - Meet at Victor 23 at 6p?\r\n - END:MSG\r\n - END:BBODY\r\n - END:BENV\r\n - END:BMSG\r\n - " + "status": "UNREAD", + "type": "SMS_GSM", + "folder": "telecom/msg/inbox", + "recipient": { + "fn": "Satoshi Nakamoto", + "n": "Satoshi" + }, + "message": "Meet at Victor 23 at 6p?" } </pre> diff --git a/binding/CMakeLists.txt b/binding/CMakeLists.txt index 6c137e9..97a28df 100644 --- a/binding/CMakeLists.txt +++ b/binding/CMakeLists.txt @@ -21,7 +21,9 @@ PROJECT_TARGET_ADD(bluetooth-map-binding) # Define project Targets - add_library(bluetooth-map-binding MODULE bluetooth-map-api.c bluetooth-map-bluez.c) + add_library(bluetooth-map-binding MODULE bluetooth-map-api.c + bluetooth-map-bluez.c + bluetooth-map-bmessage.c) # Binder exposes a unique public entry point SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES diff --git a/binding/bluetooth-map-api.c b/binding/bluetooth-map-api.c index c897838..df90ae5 100644 --- a/binding/bluetooth-map-api.c +++ b/binding/bluetooth-map-api.c @@ -402,10 +402,9 @@ static void map_notification_event(struct map_state *ns, gchar *filename) if (!g_file_get_contents(filename, &buf, NULL, NULL)) return; - jresp = json_object_new_object(); - json_object_object_add(jresp, "bmessage", json_object_new_string(buf)); - - afb_event_push(ns->notification_event, jresp); + jresp = bmessage_parse(buf); + if (jresp) + afb_event_push(ns->notification_event, jresp); g_free(buf); } diff --git a/binding/bluetooth-map-bmessage.c b/binding/bluetooth-map-bmessage.c new file mode 100644 index 0000000..146c7db --- /dev/null +++ b/binding/bluetooth-map-bmessage.c @@ -0,0 +1,194 @@ +/* + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> +#include <time.h> + +#include <glib.h> +#include <gio/gio.h> +#include <json-c/json.h> + +#define AFB_BINDING_VERSION 3 +#include <afb/afb-binding.h> + +static void populate_json_entry(gchar *msg, json_object *jresp) +{ + gchar **tmp = g_strsplit(msg, ":", 2); + gchar *key = g_ascii_strdown(tmp[0], -1), *ptr; + + // TODO: process CHARSET in fields + ptr = strstr(key, ";"); + if (ptr) + *ptr = '\0'; + + if (strlen(tmp[1])) + json_object_object_add(jresp, key, json_object_new_string(tmp[1])); + + g_free(key); + g_strfreev(tmp); +} + +static gboolean __expect(gchar ***msg, gchar *str, json_object *jresp, gboolean autoincrement) +{ + gboolean ret = FALSE; + gchar *tmp; + + if (!**msg) + return FALSE; + + tmp = **msg; + + if (jresp) { + ret = g_str_has_prefix(tmp, str); + populate_json_entry(tmp, jresp); + } else if (g_str_has_suffix(str, ":")) { + ret = g_str_has_prefix(tmp, str); + } else { + if (!g_strcmp0(tmp, str)) + ret = TRUE; + } + + if (autoincrement) + (*msg)++; + + return ret; +} + +#define expect(msg, str, jresp) { if (!__expect(msg, str, jresp, TRUE)) { return FALSE; }; } + +static gboolean parse_recipient_vcard(gchar ***msg, json_object *jresp) +{ + json_object *jobj; + + expect(msg, "VERSION:", NULL); + + jobj = json_object_new_object(); + json_object_object_add(jresp, "recipient", jobj); + + expect(msg, "FN", jobj); + expect(msg, "N", jobj); + expect(msg, "TEL:", jobj); + + return TRUE; +} + +static gboolean __bmessage_parse(gchar **msg, json_object *jresp) +{ + gboolean benv = FALSE, bmsg = FALSE, vcard = FALSE; + GString *message = g_string_new(NULL); + + // BMSG: Begin of valid message + expect(&msg, "BEGIN:BMSG", NULL); + + // BMSG: Both Android and iOS + expect(&msg, "VERSION:", NULL); + expect(&msg, "STATUS:", jresp); + expect(&msg, "TYPE:", jresp); + expect(&msg, "FOLDER:", jresp); + + // BMSG: iOS puts some non-standard stuff here + + while (*msg) { + if (!vcard && __expect(&msg, "BEGIN:VCARD", NULL, TRUE)) { + vcard = parse_recipient_vcard(&msg, jresp); + continue; + } + + // VCARD: any BENV is invalid if comes before the recipient data + if (!vcard) + continue; + + if (!benv && __expect(&msg, "BEGIN:BENV", NULL, TRUE)) { + benv = TRUE; + continue; + } + + // BENV: can't be in the message body yet + if (!benv) + continue; + + if (!bmsg && __expect(&msg, "BEGIN:MSG", NULL, TRUE)) { + bmsg = TRUE; + continue; + } + + // MSG: not in message yet + if (!bmsg) + continue; + + // MSG: Keep looking for END:MSG + if (!__expect(&msg, "END:MSG", NULL, FALSE)) { + g_string_append(message, *msg); + g_string_append(message, "\n"); + } + + // MSG: Keep looking for END:MSG (increment this time) + if (__expect(&msg, "END:MSG", NULL, TRUE)) + break; + } + + // BMSG: End of valid message + expect(&msg, "END:BBODY", NULL); + expect(&msg, "END:BENV", NULL); + expect(&msg, "END:BMSG", NULL); + + json_object_object_add(jresp, "message", json_object_new_string(message->str)); + g_string_free(message, TRUE); + + // If not BMSG then isn't a valid message + return bmsg; +} + +// NOTE: replaces \r with \0 to make it easier to parse +static void sanitize_msg(gchar **msg) +{ + while (*msg) { + gchar *tmp = *msg++; + if (g_str_has_suffix(tmp, "\r")) + *strstr(tmp, "\r") = '\0'; + } +} + +json_object *bmessage_parse(const gchar *bmessage) +{ + gchar **msg; + json_object *jresp = json_object_new_object(); + gboolean ret; + + if (!bmessage || !strlen(bmessage)) + return NULL; + + AFB_INFO("Parsing incoming bMessage of length %lu", strlen(bmessage)); + + msg = g_strsplit(bmessage, "\n", -1); + sanitize_msg(msg); + + ret = __bmessage_parse(msg, jresp); + if (!ret) { + json_object_put(jresp); + jresp = NULL; + } + + g_strfreev(msg); + + return jresp; +} diff --git a/binding/bluetooth-map-common.h b/binding/bluetooth-map-common.h index 12fe35a..ba68781 100644 --- a/binding/bluetooth-map-common.h +++ b/binding/bluetooth-map-common.h @@ -171,4 +171,8 @@ json_object *get_named_property(const struct property_info *pi, gboolean is_json_name, const char *name, json_object *jprop); +/* functions defined in bluetooth-map-bmessage.c */ + +json_object *bmessage_parse(const gchar *bmessage); + #endif /* BLUETOOTH_MAP_COMMON_H */ |