aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatt Ranostay <matt.ranostay@konsulko.com>2019-05-03 02:43:24 -0700
committerMatt Ranostay <matt.ranostay@konsulko.com>2019-05-06 23:29:12 -0700
commit4fd90fb81a9ab329800c41473ba948af16bbda8e (patch)
tree1360aa53882536fe92bceb126a1211ab3910792a
parent2ebcd4022682f10f1084f0cf1abe77f19cdcd575 (diff)
binding: bluetooth-map: add message composition support
Add message composition suppport for sending SMS/MMS messages in bMessage format. Bug-AGL: SPEC-2351 Change-Id: I5709c6619f44863e949ae61363f7d5fe54adcbfc Signed-off-by: Matt Ranostay <matt.ranostay@konsulko.com>
-rw-r--r--README.md40
-rw-r--r--binding/bluetooth-map-api.c311
-rw-r--r--binding/bluetooth-map-api.h2
-rw-r--r--binding/bluetooth-map-bluez.c14
-rw-r--r--binding/bluetooth-map-common.h13
5 files changed, 362 insertions, 18 deletions
diff --git a/README.md b/README.md
index c382e18..f84bccc 100644
--- a/README.md
+++ b/README.md
@@ -5,10 +5,16 @@
Bluetooth MAP (Message Access Profile) service uses the respective profile support from BlueZ
to enable message notifications from SMS/email/etc.
+### Important Notes
+
+Message composition isn't available on iOS devices due to respective security polices, and in
+turn isn't supported in Apple's implementation of the (MAP) Message Access Profile.
+
## Verbs
| Name | Description | JSON Response |
|-------------------|------------------------------------------|-------------------------------------------|
+| compose | send message | see **compose verb** section |
| subscribe | subscribe to MAP service events | *Request:* {"value": "notification"} |
| unsubscribe | unsubscribe to MAP service events | *Request:* {"value": "notification"} |
@@ -19,6 +25,36 @@ to enable message notifications from SMS/email/etc.
| notification | report notification message | see **notification event** section |
+### compose verb
+
+Send a message (if supported) via MAP profile:
+
+<pre>
+{
+ "bmessage":
+ "BEGIN:BMSG\r\n
+ VERSION:1.0\r\n
+ TYPE:SMS_GSM\r\n
+ FOLDER:telecom/msg/outbox\r\n
+ BEGIN:BENV\r\n
+ BEGIN:VCARD\r\n
+ VERSION:2.1\r\n
+ TEL:+13605551212\r\n
+ END:VCARD\r\n
+ BEGIN:BBODY\r\n
+ CHARSET:UTF-8\r\n
+ LANGUAGE:UNKNOWN\r\n
+ LENGTH:48\r\n
+ BEGIN:MSG\r\n
+ Sounds good. See you then.\r\n
+ END:MSG\r\n
+ END:BBODY\r\n
+ END:BENV\r\n
+ END:BMSG\r\n
+ "
+}
+</pre>
+
### notification event
<pre>
@@ -34,7 +70,7 @@ to enable message notifications from SMS/email/etc.
VERSION:2.1\r\n
FN;CHARSET=UTF-8:Satoshi Nakamoto\r\n
N;CHARSET=UTF-8:Satoshi\r\n
- TEL:\r\n
+ TEL:+13605551212\r\n
END:VCARD\r\n
BEGIN:BENV\r\n
BEGIN:BBODY\r\n
@@ -42,7 +78,7 @@ to enable message notifications from SMS/email/etc.
LANGUAGE:UNKNOWN\r\n
LENGTH:46\r\n
BEGIN:MSG\r\n
- Meet at Victor 23 at 6p?
+ Meet at Victor 23 at 6p?\r\n
END:MSG\r\n
END:BBODY\r\n
END:BENV\r\n
diff --git a/binding/bluetooth-map-api.c b/binding/bluetooth-map-api.c
index 4ede612..898487f 100644
--- a/binding/bluetooth-map-api.c
+++ b/binding/bluetooth-map-api.c
@@ -62,6 +62,166 @@ void call_work_unlock(struct map_state *ns)
g_mutex_unlock(&ns->cw_mutex);
}
+struct call_work *call_work_lookup_unlocked(
+ struct map_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method)
+{
+ struct call_work *cw;
+ GSList *list;
+
+ /* we can only allow a single pending call */
+ for (list = ns->cw_pending; list; list = g_slist_next(list)) {
+ cw = list->data;
+ if (!g_strcmp0(access_type, cw->access_type) &&
+ !g_strcmp0(type_arg, cw->type_arg) &&
+ !g_strcmp0(method, cw->method))
+ return cw;
+ }
+ return NULL;
+}
+
+struct call_work *call_work_lookup(
+ struct map_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method)
+{
+ struct call_work *cw;
+
+ g_mutex_lock(&ns->cw_mutex);
+ cw = call_work_lookup_unlocked(ns, access_type, type_arg, method);
+ g_mutex_unlock(&ns->cw_mutex);
+
+ return cw;
+}
+
+int call_work_pending_id(
+ struct map_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method)
+{
+ struct call_work *cw;
+ int id = -1;
+
+ g_mutex_lock(&ns->cw_mutex);
+ cw = call_work_lookup_unlocked(ns, access_type, type_arg, method);
+ if (cw)
+ id = cw->id;
+ g_mutex_unlock(&ns->cw_mutex);
+
+ return id;
+}
+
+struct call_work *call_work_lookup_by_id_unlocked(
+ struct map_state *ns, int id)
+{
+ struct call_work *cw;
+ GSList *list;
+
+ /* we can only allow a single pending call */
+ for (list = ns->cw_pending; list; list = g_slist_next(list)) {
+ cw = list->data;
+ if (cw->id == id)
+ return cw;
+ }
+ return NULL;
+}
+
+struct call_work *call_work_lookup_by_id(
+ struct map_state *ns, int id)
+{
+ struct call_work *cw;
+
+ g_mutex_lock(&ns->cw_mutex);
+ cw = call_work_lookup_by_id_unlocked(ns, id);
+ g_mutex_unlock(&ns->cw_mutex);
+
+ return cw;
+}
+
+struct call_work *call_work_create_unlocked(struct map_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method, const char *bluez_method,
+ GError **error)
+{
+
+ struct call_work *cw = NULL;
+
+ cw = call_work_lookup_unlocked(ns, access_type, type_arg, method);
+ if (cw) {
+ g_set_error(error, NB_ERROR, NB_ERROR_CALL_IN_PROGRESS,
+ "another call in progress (%s/%s/%s)",
+ access_type, type_arg, method);
+ return NULL;
+ }
+
+ /* no other pending; allocate */
+ cw = g_malloc0(sizeof(*cw));
+ cw->ns = ns;
+ do {
+ cw->id = ns->next_cw_id;
+ if (++ns->next_cw_id < 0)
+ ns->next_cw_id = 1;
+ } while (call_work_lookup_by_id_unlocked(ns, cw->id));
+
+ cw->access_type = g_strdup(access_type);
+ cw->type_arg = g_strdup(type_arg);
+ cw->method = g_strdup(method);
+ cw->bluez_method = g_strdup(bluez_method);
+
+ ns->cw_pending = g_slist_prepend(ns->cw_pending, cw);
+
+ return cw;
+}
+
+struct call_work *call_work_create(struct map_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method, const char *bluez_method,
+ GError **error)
+{
+
+ struct call_work *cw;
+
+ g_mutex_lock(&ns->cw_mutex);
+ cw = call_work_create_unlocked(ns,
+ access_type, type_arg, method, bluez_method,
+ error);
+ g_mutex_unlock(&ns->cw_mutex);
+
+ return cw;
+}
+
+void call_work_destroy_unlocked(struct call_work *cw)
+{
+ struct map_state *ns = cw->ns;
+ struct call_work *cw2;
+
+ /* verify that it's something we know about */
+ cw2 = call_work_lookup_by_id_unlocked(ns, cw->id);
+ if (cw2 != cw) {
+ AFB_ERROR("Bad call work to destroy");
+ return;
+ }
+
+ /* remove it */
+ ns->cw_pending = g_slist_remove(ns->cw_pending, cw);
+
+ g_free(cw->access_type);
+ g_free(cw->type_arg);
+ g_free(cw->method);
+ g_free(cw->bluez_method);
+}
+
+
+void call_work_destroy(struct call_work *cw)
+{
+ struct map_state *ns = cw->ns;
+
+ g_mutex_lock(&ns->cw_mutex);
+ call_work_destroy_unlocked(cw);
+ g_mutex_unlock(&ns->cw_mutex);
+}
+
static afb_event_t get_event_from_value(struct map_state *ns,
const char *value)
{
@@ -112,6 +272,109 @@ static void map_subscribe_unsubscribe(afb_req_t request,
value);
}
+static void push_message_callback(void *user_data,
+ GVariant *result, GError **error)
+{
+ struct call_work *cw = user_data;
+ struct map_state *ns = cw->ns;
+ struct xfer_tuple *data;
+ gchar *path = NULL;
+
+ bluez_decode_call_error(ns,
+ cw->access_type, cw->type_arg, cw->method,
+ error);
+
+ if (error && *error) {
+ afb_req_fail_f(cw->request, "failed", "OBEX error: %s",
+ (*error)->message);
+ goto out_free;
+ } else if (!result) {
+ goto out_free;
+ }
+
+ g_variant_get(result, "(&oa{sv})", &path, NULL);
+
+ afb_req_addref(cw->request);
+
+ data = g_try_malloc0(sizeof(*data));
+ data->type = XFER_SENTMSG;
+ data->value = cw->request;
+
+ g_hash_table_insert(ns->xfer_queue, g_strdup(path), data);
+
+ g_variant_unref(result);
+out_free:
+ afb_req_unref(cw->request);
+ call_work_destroy(cw);
+}
+
+
+
+static void compose(afb_req_t request)
+{
+ struct map_state *ns = map_get_userdata(request);
+ const char *message = afb_req_value(request, "bmessage");
+ GError *error = NULL;
+ GVariant *params, *reply;
+ gchar *name, *session;
+ struct call_work *cw;
+ int fd;
+
+ call_work_lock(ns);
+ if (!ns || !ns->session_path) {
+ afb_req_fail_f(request, "failed", "no valid OBEX session");
+ call_work_unlock(ns);
+ return;
+ }
+ session = g_strdup(ns->session_path);
+ call_work_unlock(ns);
+
+ if (!message) {
+ afb_req_fail_f(request, "failed", "no valid message provided");
+ goto err_msg_invalid;
+ }
+
+ fd = g_file_open_tmp("obex-clientXXXXXX", &name, NULL);
+ close(fd);
+
+ g_file_set_contents(name, message, -1, NULL);
+
+ params = g_variant_new("(&s)", "telecom/msg/OUTBOX");
+ reply = bluez_call(ns, BLUEZ_AT_MESSAGEACCESS, session, "SetFolder", params, NULL);
+ if (reply) g_variant_unref(reply);
+
+ cw = call_work_create(ns, BLUEZ_AT_MESSAGEACCESS, session,
+ "queue_message", "PushMessage", &error);
+ if (!cw) {
+ afb_req_fail_f(request, "failed", "can't queue work %s",
+ error->message);
+ g_error_free(error);
+ goto err_queue_free;
+ }
+
+ cw->request = request;
+ afb_req_addref(request);
+
+ params = g_variant_new("(&s&sa{sv})", name, "", NULL);
+ cw->cpw = bluez_call_async(ns, BLUEZ_AT_MESSAGEACCESS, session,
+ "PushMessage", params, &error,
+ push_message_callback, cw);
+
+ if (!cw->cpw) {
+ afb_req_fail_f(request, "failed", "connection error %s",
+ error->message);
+ call_work_destroy(cw);
+ g_error_free(error);
+ /* fall-thru */
+ }
+
+err_queue_free:
+ g_free(name);
+
+err_msg_invalid:
+ g_free(session);
+}
+
static void subscribe(afb_req_t request)
{
map_subscribe_unsubscribe(request, FALSE);
@@ -185,12 +448,16 @@ static void bluez_map_signal_callback(
array1 = g_variant_iter_new(var);
while (g_variant_iter_next(array1, "{&sv}", &name, &val)) {
+ struct xfer_tuple *data;
+
if (g_strcmp0(name, "Filename"))
continue;
call_work_lock(ns);
- g_hash_table_insert(ns->xfer_queue, g_strdup(path),
- g_strdup(g_variant_get_string(val, NULL)));
+ data = g_try_malloc0(sizeof(*data));
+ data->type = XFER_NOTIFICATION;
+ data->value = g_strdup(g_variant_get_string(val, NULL));
+ g_hash_table_insert(ns->xfer_queue, g_strdup(path), data);
call_work_unlock(ns);
break;
}
@@ -204,27 +471,46 @@ static void bluez_map_signal_callback(
return;
while (g_variant_iter_next(array, "{&sv}", &key, &var)) {
- gchar *filename;
+ struct xfer_tuple *val;
+ const gchar *status;
// only check Status field
if (g_strcmp0(key, "Status"))
continue;
- // only need the "complete" Status
- if (g_strcmp0(g_variant_get_string(var, NULL), "complete"))
- return;
+ // only need the "complete" or "error" Status
+ status = g_variant_get_string(var, NULL);
+ if (g_strcmp0(status, "complete") && g_strcmp0(status, "error"))
+ break;
call_work_lock(ns);
- filename = (gchar *) g_hash_table_lookup(ns->xfer_queue, object_path);
- if (filename) {
+ val = g_hash_table_lookup(ns->xfer_queue, object_path);
+ if (val)
g_hash_table_remove(ns->xfer_queue, object_path);
- call_work_unlock(ns);
+ call_work_unlock(ns);
- map_notification_event(ns, filename);
- g_free(filename);
+ if (!val)
+ break;
+
+ if (val->type == XFER_NOTIFICATION) {
+ if (!g_strcmp0(status, "complete"))
+ map_notification_event(ns, (gchar *) val->value);
+
+ g_free(val->value);
+ g_free(val);
+ break;
+ } else if (val->type == XFER_SENTMSG) {
+ afb_req_t request = (afb_req_t) val->value;
+
+ if (!g_strcmp0(status, "complete"))
+ afb_req_success_f(request, NULL, "OBEX - transfer %s completed", object_path);
+ else
+ afb_req_fail_f(request, "failed", "OBEX - transfer %s failed", object_path);
+
+ afb_req_unref(request);
+ g_free(val);
break;
}
- call_work_unlock(ns);
}
}
}
@@ -516,6 +802,7 @@ static void onevent(afb_api_t api, const char *event, struct json_object *object
}
static const afb_verb_t binding_verbs[] = {
+ { .verb = "compose", .callback = compose, .info = "Compose message" },
{ .verb = "subscribe", .callback = subscribe, .info = "Subscribe to events" },
{ .verb = "unsubscribe",.callback = unsubscribe,.info = "Unsubscribe to events" },
{}
diff --git a/binding/bluetooth-map-api.h b/binding/bluetooth-map-api.h
index 906c360..d5b20a1 100644
--- a/binding/bluetooth-map-api.h
+++ b/binding/bluetooth-map-api.h
@@ -30,6 +30,7 @@
#define BLUEZ_OBEX_SESSION_INTERFACE BLUEZ_OBEX_SERVICE ".Session1"
#define BLUEZ_OBEX_TRANSFER_INTERFACE BLUEZ_OBEX_SERVICE ".Transfer1"
#define BLUEZ_OBEX_MESSAGE_INTERFACE BLUEZ_OBEX_SERVICE ".Message1"
+#define BLUEZ_OBEX_MESSAGEACCESS_INTERFACE BLUEZ_OBEX_SERVICE ".MessageAccess1"
#define BLUEZ_OBJECT_PATH "/"
#define BLUEZ_OBEX_PATH "/org/bluez/obex"
@@ -49,6 +50,7 @@
#define BLUEZ_AT_SESSION "session"
#define BLUEZ_AT_TRANSFER "transfer"
#define BLUEZ_AT_MESSAGE "message"
+#define BLUEZ_AT_MESSAGEACCESS "message-access"
struct map_state;
diff --git a/binding/bluetooth-map-bluez.c b/binding/bluetooth-map-bluez.c
index 6d70fab..f957f5c 100644
--- a/binding/bluetooth-map-bluez.c
+++ b/binding/bluetooth-map-bluez.c
@@ -142,7 +142,8 @@ void bluez_decode_call_error(struct map_state *ns,
access_type, type_arg);
} else if (!strcmp(method, "CreateSession") ||
- !strcmp(method, "RemoveSession")) {
+ !strcmp(method, "RemoveSession") ||
+ !strcmp(method, "SetFolder")) {
g_clear_error(error);
g_set_error(error, NB_ERROR,
@@ -152,7 +153,8 @@ void bluez_decode_call_error(struct map_state *ns,
} else if (!strcmp(method, "Cancel") ||
!strcmp(method, "Suspend") ||
- !strcmp(method, "Resume")) {
+ !strcmp(method, "Resume") ||
+ !strcmp(method, "PushMessage")) {
g_clear_error(error);
g_set_error(error, NB_ERROR,
@@ -189,6 +191,8 @@ GVariant *bluez_call(struct map_state *ns,
interface = BLUEZ_OBEX_TRANSFER_INTERFACE;
} else if (!strcmp(access_type, BLUEZ_AT_MESSAGE)) {
interface = BLUEZ_OBEX_MESSAGE_INTERFACE;
+ } else if (!strcmp(access_type, BLUEZ_AT_MESSAGEACCESS)) {
+ interface = BLUEZ_OBEX_MESSAGEACCESS_INTERFACE;
} else {
g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT,
"illegal %s argument",
@@ -252,7 +256,8 @@ bluez_call_async(struct map_state *ns,
struct bluez_pending_work *cpw;
if (!type_arg && (!strcmp(access_type, BLUEZ_AT_SESSION) ||
- !strcmp(access_type, BLUEZ_AT_TRANSFER))) {
+ !strcmp(access_type, BLUEZ_AT_TRANSFER) ||
+ !strcmp(access_type, BLUEZ_AT_MESSAGEACCESS))) {
g_set_error(error, NB_ERROR, NB_ERROR_MISSING_ARGUMENT,
"missing %s argument",
access_type);
@@ -265,6 +270,9 @@ bluez_call_async(struct map_state *ns,
} else if (!strcmp(access_type, BLUEZ_AT_TRANSFER)) {
path = type_arg;
interface = BLUEZ_OBEX_TRANSFER_INTERFACE;
+ } else if (!strcmp(access_type, BLUEZ_AT_MESSAGEACCESS)) {
+ path = type_arg;
+ interface = BLUEZ_OBEX_MESSAGEACCESS_INTERFACE;
} else {
g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT,
"illegal %s argument",
diff --git a/binding/bluetooth-map-common.h b/binding/bluetooth-map-common.h
index 4b43c7e..12fe35a 100644
--- a/binding/bluetooth-map-common.h
+++ b/binding/bluetooth-map-common.h
@@ -57,6 +57,16 @@ struct map_state {
GHashTable *xfer_queue;
};
+enum xfer_types {
+ XFER_NOTIFICATION,
+ XFER_SENTMSG,
+};
+
+struct xfer_tuple {
+ enum xfer_types type;
+ gpointer value;
+};
+
struct init_data {
GCond cond;
GMutex mutex;
@@ -67,11 +77,12 @@ struct init_data {
};
struct call_work {
- struct bluetooth_state *ns;
+ struct map_state *ns;
int id;
gchar *access_type;
gchar *type_arg;
gchar *method;
+ gchar *bluez_method;
struct bluez_pending_work *cpw;
afb_req_t request;
GDBusMethodInvocation *invocation;