summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatt Ranostay <matt.ranostay@konsulko.com>2019-06-14 17:56:41 -0700
committerMatt Ranostay <matt.ranostay@konsulko.com>2019-06-19 08:53:00 -0700
commit30b84ed96f8fc03fc5a73bbe97a86d76899ab8cc (patch)
tree3967da514a77ba4f1c4e186111ef44bc1197d2f3
parenta9584f072945bfda5d7741d43cb870dcb291fd70 (diff)
binding: bluetooth-map: add message access support
Add support for retrieving messages, and listing handles of messages located in folders. SPEC-2512 Change-Id: Ia345488b51f5cac1e1c2fd508305d7ffda794251 Signed-off-by: Matt Ranostay <matt.ranostay@konsulko.com>
-rw-r--r--README.md67
-rw-r--r--binding/CMakeLists.txt3
-rw-r--r--binding/bluetooth-map-api.c209
-rw-r--r--binding/bluetooth-map-api.h8
-rw-r--r--binding/bluetooth-map-bluez.c6
-rw-r--r--binding/bluetooth-map-common.h1
-rw-r--r--binding/bluetooth-map-util.c1029
7 files changed, 1312 insertions, 11 deletions
diff --git a/README.md b/README.md
index 3d4d92b..d51e00e 100644
--- a/README.md
+++ b/README.md
@@ -7,14 +7,16 @@ 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.
+Message composition and message access 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 |
+| message | display message | see **message verb** section |
+| list_messages | list messages in folder | see **list_messages** section |
| subscribe | subscribe to MAP service events | *Request:* {"value": "notification"} |
| unsubscribe | unsubscribe to MAP service events | *Request:* {"value": "notification"} |
@@ -36,6 +38,67 @@ Send a message (if supported) via MAP profile:
}
</pre>
+### message verb
+
+Request a message via the respective handle:
+
+<pre>
+{
+ "handle": "message288230376151711769"
+}
+</pre>
+
+Response:
+
+<pre>
+{
+ "response": {
+ "status": "UNREAD",
+ "type": "SMS_GSM",
+ "folder": "telecom/msg/INBOX",
+ "sender": {
+ "tel": "+13605551212"
+ },
+ "message": "Meet at Victor 23 at 6p?"
+},
+
+</pre>
+
+### list_messages verb
+
+Request a folder listing of messages:
+
+<pre>
+{
+ "folder":"INBOX"
+}
+</pre>
+
+Response which the message handle as the key and includes its properties:
+
+<pre>
+{
+ "response": {
+ "message288230376151711769": {
+ "folder": "/telecom/msg/INBOX",
+ "subject": "Meet at Victor 23 at 6p?",
+ "timestamp": "20190614T093341",
+ "sender": "",
+ "senderaddress": "+13605551212",
+ "recipient": "",
+ "recipientaddress": "",
+ "type": "sms-gsm",
+ "size": 24,
+ "status": "complete",
+ "priority": false,
+ "read": false,
+ "sent": false,
+ "protected": false
+ },
+ ...
+},
+</pre>
+
### notification event
<pre>
diff --git a/binding/CMakeLists.txt b/binding/CMakeLists.txt
index 97a28df..7012e33 100644
--- a/binding/CMakeLists.txt
+++ b/binding/CMakeLists.txt
@@ -23,7 +23,8 @@ PROJECT_TARGET_ADD(bluetooth-map-binding)
# Define project Targets
add_library(bluetooth-map-binding MODULE bluetooth-map-api.c
bluetooth-map-bluez.c
- bluetooth-map-bmessage.c)
+ bluetooth-map-bmessage.c
+ bluetooth-map-util.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 a52df61..3d75bf4 100644
--- a/binding/bluetooth-map-api.c
+++ b/binding/bluetooth-map-api.c
@@ -339,7 +339,7 @@ static void compose(afb_req_t request)
g_file_set_contents(name, message->str, -1, NULL);
g_string_free(message, TRUE);
- params = g_variant_new("(&s)", "telecom/msg/OUTBOX");
+ 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);
@@ -375,6 +375,158 @@ err_msg_invalid:
g_free(session);
}
+
+static void get_message_callback(void *user_data,
+ GVariant *result, GError **error)
+{
+ struct call_work *cw = user_data;
+ struct map_state *ns = cw->ns;
+
+ 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;
+ }
+
+ if (!result) {
+ goto out_free;
+ }
+
+ g_variant_unref(result);
+
+ return;
+
+out_free:
+ afb_req_unref(cw->request);
+ call_work_destroy(cw);
+}
+
+static void message(afb_req_t request)
+{
+ struct map_state *ns = map_get_userdata(request);
+ GError *error = NULL;
+ GVariant *params;
+ const char *handle;
+ gchar *path, *filename;
+ struct call_work *cw;
+
+ handle = afb_req_value(request, "handle");
+ if (!handle) {
+ afb_req_fail_f(request, "failed", "no handle value passed");
+ return;
+ }
+
+ call_work_lock(ns);
+ if (!ns || !ns->session_path) {
+ afb_req_fail_f(request, "failed", "no valid OBEX session");
+ call_work_unlock(ns);
+ return;
+ }
+ path = g_strconcat(ns->session_path, "/", handle, NULL);
+ call_work_unlock(ns);
+
+ cw = call_work_create(ns, BLUEZ_AT_MESSAGE, NULL,
+ "get_message", "Get", &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);
+
+ filename = g_strconcat("/tmp/obex-message-", handle, NULL);
+ params = g_variant_new("(&sb)", filename, g_variant_new_boolean(FALSE));
+ cw->cpw = bluez_call_async(ns, BLUEZ_AT_MESSAGE, path,
+ "Get", params, &error,
+ get_message_callback, cw);
+ g_free(filename);
+
+ 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(path);
+}
+
+static void list_msgs(afb_req_t request)
+{
+ struct map_state *ns = map_get_userdata(request);
+ GVariant *params, *reply;
+ GVariantIter *iter = NULL, *iter2 = NULL;
+ const char *folder;
+ const gchar *path = NULL;
+ json_object *jresp;
+ gchar *session;
+
+ folder = afb_req_value(request, "folder");
+ if (!folder) {
+ afb_req_fail_f(request, "failed", "no folder value passed");
+ return;
+ }
+
+ call_work_lock(ns);
+ session = g_strdup(ns->session_path);
+
+ params = g_variant_new("(&s)", "/telecom/msg");
+ reply = bluez_call(ns, BLUEZ_AT_MESSAGEACCESS, session, "SetFolder", params, NULL);
+ if (!reply) {
+ afb_req_fail_f(request, "failed", "cannot switch to telecom/msg");
+ goto out_free;
+ }
+ g_variant_unref(reply);
+
+ params = g_variant_new("(&sa{sv})", folder, NULL);
+ reply = bluez_call(ns, BLUEZ_AT_MESSAGEACCESS, session, "ListMessages", params, NULL);
+ if (!reply) {
+ afb_req_fail_f(request, "failed",
+ "Cannot list messages in telecom/msg/%s", folder);
+ goto out_free;
+ }
+
+ jresp = json_object_new_object();
+
+ g_variant_get(reply, "(a{oa{sv}})", &iter);
+ while (g_variant_iter_loop(iter, "{oa{sv}}", &path, &iter2)) {
+ const char *key = NULL;
+ GVariant *val = NULL;
+ json_object *msg = json_object_new_object();
+
+ while (g_variant_iter_loop(iter2, "{sv}", &key, &val)) {
+ GError *error = NULL;
+ gboolean is_config, ret;
+
+ ret = message_property_dbus2json(msg, key, val,
+ &is_config, &error);
+ g_variant_unref(val);
+ if (!ret) {
+ AFB_DEBUG("%s property %s - %s",
+ path,
+ key, error->message);
+ g_clear_error(&error);
+ }
+ }
+ json_object_object_add(jresp, path + strlen(session) + 1, msg);
+ }
+
+ afb_req_success_f(request, jresp, "Bluetooth MAP folder listing");
+
+out_free:
+ call_work_unlock(ns);
+ g_free(session);
+}
+
static void subscribe(afb_req_t request)
{
map_subscribe_unsubscribe(request, FALSE);
@@ -423,6 +575,35 @@ static gboolean map_notification_check(GVariant *var)
return FALSE;
}
+static void map_message_response(struct map_state *ns, gchar *filename)
+{
+ struct call_work *cw;
+ json_object *jresp;
+ gchar *buf;
+
+ if (!g_file_get_contents(filename, &buf, NULL, NULL))
+ return;
+
+ jresp = bmessage_parse(buf);
+
+ call_work_lock(ns);
+
+ cw = call_work_lookup_unlocked(ns, BLUEZ_AT_MESSAGE, NULL, "get_message");
+ if (!cw) {
+ if (jresp)
+ json_object_put(jresp);
+ return;
+ }
+
+
+ afb_req_success_f(cw->request, jresp, "Bluetooth MAP message result");
+
+ call_work_destroy_unlocked(cw);
+
+ call_work_unlock(ns);
+ g_free(buf);
+}
+
static void bluez_map_signal_callback(
GDBusConnection *connection,
const gchar *sender_name,
@@ -477,14 +658,21 @@ static void bluez_map_signal_callback(
while (g_variant_iter_next(array1, "{&sv}", &name, &val)) {
struct xfer_tuple *data;
+ gchar *filename;
if (g_strcmp0(name, "Filename"))
continue;
call_work_lock(ns);
data = g_try_malloc0(sizeof(*data));
- data->type = XFER_NOTIFICATION;
- data->value = g_strdup(g_variant_get_string(val, NULL));
+ filename = g_strdup(g_variant_get_string(val, NULL));
+
+ // TODO: figure a better way that isn't based on name
+ if (strstr(filename, "obex-message-"))
+ data->type = XFER_MESSAGE;
+ else
+ data->type = XFER_NOTIFICATION;
+ data->value = filename;
g_hash_table_insert(ns->xfer_queue, g_strdup(path), data);
call_work_unlock(ns);
break;
@@ -538,6 +726,13 @@ static void bluez_map_signal_callback(
afb_req_unref(request);
g_free(val);
break;
+ } else if (val->type == XFER_MESSAGE) {
+ if (!g_strcmp0(status, "complete"))
+ map_message_response(ns, (gchar *) val->value);
+
+ g_free(val->value);
+ g_free(val);
+ break;
}
}
}
@@ -830,9 +1025,11 @@ 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" },
+ { .verb = "compose", .callback = compose, .info = "Compose message" },
+ { .verb = "message", .callback = message, .info = "Retrieve message" },
+ { .verb = "list_messages", .callback = list_msgs, .info = "List messages" },
+ { .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 d5b20a1..5e373ba 100644
--- a/binding/bluetooth-map-api.h
+++ b/binding/bluetooth-map-api.h
@@ -116,6 +116,14 @@ static inline gboolean transfer_property_dbus2json(json_object *jprop,
jprop, key, var, is_config, error);
}
+static inline gboolean message_property_dbus2json(json_object *jprop,
+ const gchar *key, GVariant *var, gboolean *is_config,
+ GError **error)
+{
+ return bluez_property_dbus2json(BLUEZ_AT_MESSAGE,
+ jprop, key, var, is_config, error);
+}
+
static inline json_object *object_properties(struct map_state *ns,
GError **error)
{
diff --git a/binding/bluetooth-map-bluez.c b/binding/bluetooth-map-bluez.c
index f957f5c..4f1b850 100644
--- a/binding/bluetooth-map-bluez.c
+++ b/binding/bluetooth-map-bluez.c
@@ -38,8 +38,6 @@
#include "bluetooth-map-api.h"
#include "bluetooth-map-common.h"
-G_DEFINE_QUARK(bluetooth-map-error-quark, nb_error);
-
static const struct property_info session_props[] = {
{ .name = "Source", .fmt = "s", },
{ .name = "Destination", .fmt = "s", },
@@ -257,6 +255,7 @@ bluez_call_async(struct map_state *ns,
if (!type_arg && (!strcmp(access_type, BLUEZ_AT_SESSION) ||
!strcmp(access_type, BLUEZ_AT_TRANSFER) ||
+ !strcmp(access_type, BLUEZ_AT_MESSAGE) ||
!strcmp(access_type, BLUEZ_AT_MESSAGEACCESS))) {
g_set_error(error, NB_ERROR, NB_ERROR_MISSING_ARGUMENT,
"missing %s argument",
@@ -270,6 +269,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_MESSAGE)) {
+ path = type_arg;
+ interface = BLUEZ_OBEX_MESSAGE_INTERFACE;
} else if (!strcmp(access_type, BLUEZ_AT_MESSAGEACCESS)) {
path = type_arg;
interface = BLUEZ_OBEX_MESSAGEACCESS_INTERFACE;
diff --git a/binding/bluetooth-map-common.h b/binding/bluetooth-map-common.h
index 8b2f469..f154c91 100644
--- a/binding/bluetooth-map-common.h
+++ b/binding/bluetooth-map-common.h
@@ -60,6 +60,7 @@ struct map_state {
enum xfer_types {
XFER_NOTIFICATION,
XFER_SENTMSG,
+ XFER_MESSAGE,
};
struct xfer_tuple {
diff --git a/binding/bluetooth-map-util.c b/binding/bluetooth-map-util.c
new file mode 100644
index 0000000..420c7d2
--- /dev/null
+++ b/binding/bluetooth-map-util.c
@@ -0,0 +1,1029 @@
+/*
+ * Copyright 2018 Konsulko Group
+ * Author: Pantelis Antoniou <pantelis.antoniou@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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <pthread.h>
+#include <semaphore.h>
+
+#include <glib.h>
+#include <stdlib.h>
+#include <gio/gio.h>
+#include <glib-object.h>
+
+#include <json-c/json.h>
+
+#define AFB_BINDING_VERSION 3
+#include <afb/afb-binding.h>
+
+#include "bluetooth-map-api.h"
+#include "bluetooth-map-common.h"
+
+G_DEFINE_QUARK(bluetooth-map-error-quark, nb_error)
+
+/* convert dbus key to lower case */
+gboolean auto_lowercase_keys = TRUE;
+
+void signal_init_done(struct init_data *id, int rc)
+{
+ g_mutex_lock(&id->mutex);
+ id->init_done = TRUE;
+ id->rc = rc;
+ g_cond_signal(&id->cond);
+ g_mutex_unlock(&id->mutex);
+}
+
+int str2boolean(const char *value)
+{
+ if (!strcmp(value, "1") || !g_ascii_strcasecmp(value, "true") ||
+ !g_ascii_strcasecmp(value, "on") || !g_ascii_strcasecmp(value, "enabled") ||
+ !g_ascii_strcasecmp(value, "yes"))
+ return TRUE;
+ if (!strcmp(value, "0") || !g_ascii_strcasecmp(value, "false") ||
+ !g_ascii_strcasecmp(value, "off") || !g_ascii_strcasecmp(value, "disabled") ||
+ !g_ascii_strcasecmp(value, "no"))
+ return FALSE;
+ return -1;
+}
+
+json_object *json_object_copy(json_object *jval)
+{
+ json_object *jobj;
+ size_t len;
+ int i;
+
+ /* handle NULL */
+ if (!jval)
+ return NULL;
+
+ switch (json_object_get_type(jval)) {
+ case json_type_object:
+ jobj = json_object_new_object();
+ json_object_object_foreach(jval, key, jval2)
+ json_object_object_add(jobj, key,
+ json_object_copy(jval2));
+
+ return jobj;
+
+ case json_type_array:
+ jobj = json_object_new_array();
+ len = json_object_array_length(jval);
+ for (i = 0; i < len; i++)
+ json_object_array_add(jobj,
+ json_object_copy(
+ json_object_array_get_idx(jval, i)));
+ return jobj;
+
+ case json_type_null:
+ return NULL;
+
+ case json_type_boolean:
+ return json_object_new_boolean(
+ json_object_get_boolean(jval));
+
+ case json_type_double:
+ return json_object_new_double(
+ json_object_get_double(jval));
+
+ case json_type_int:
+ return json_object_new_int64(
+ json_object_get_int64(jval));
+
+ case json_type_string:
+ return json_object_new_string(
+ json_object_get_string(jval));
+ }
+
+ g_assert(0);
+ /* should never happen */
+ return NULL;
+}
+
+gchar *key_dbus_to_json(const gchar *key, gboolean auto_lower)
+{
+ gchar *lower, *s;
+
+ lower = g_strdup(key);
+ g_assert(lower);
+
+ if (!auto_lower)
+ return lower;
+
+ /* convert to lower case */
+ for (s = lower; *s; s++)
+ *s = g_ascii_tolower(*s);
+
+ return lower;
+}
+
+json_object *simple_gvariant_to_json(GVariant *var, json_object *parent,
+ gboolean recurse)
+{
+ json_object *obj = NULL, *item;
+ gint32 i32;
+ gint64 i64;
+ guint32 ui32;
+ guint64 ui64;
+ GVariantIter iter;
+ GVariant *key, *value;
+ gchar *json_key;
+ gsize nitems;
+ gboolean is_dict;
+
+ obj = NULL;
+
+ /* AFB_DEBUG("g_variant_classify(var)=%c", g_variant_classify(var)); */
+
+ /* we only handle simple types */
+ switch (g_variant_classify(var)) {
+ case G_VARIANT_CLASS_BOOLEAN:
+ obj = json_object_new_boolean(g_variant_get_boolean(var));
+ break;
+ case G_VARIANT_CLASS_INT16:
+ obj = json_object_new_int(g_variant_get_int16(var));
+ break;
+ case G_VARIANT_CLASS_INT32:
+ i32 = g_variant_get_int32(var);
+ obj = json_object_new_int(i32);
+ break;
+ case G_VARIANT_CLASS_INT64:
+ i64 = g_variant_get_int64(var);
+ if (i64 >= -(1L << 31) && i64 < (1L << 31))
+ obj = json_object_new_int((int)i64);
+ else
+ obj = json_object_new_int64(i64);
+ break;
+ case G_VARIANT_CLASS_BYTE:
+ obj = json_object_new_int((int)g_variant_get_byte(var));
+ break;
+ case G_VARIANT_CLASS_UINT16:
+ obj = json_object_new_int((int)g_variant_get_uint16(var));
+ break;
+ case G_VARIANT_CLASS_UINT32:
+ ui32 = g_variant_get_uint32(var);
+ if (ui32 < (1U << 31))
+ obj = json_object_new_int(ui32);
+ else
+ obj = json_object_new_int64(ui32);
+ break;
+ case G_VARIANT_CLASS_UINT64:
+ ui64 = g_variant_get_uint64(var);
+ if (ui64 < (1U << 31))
+ obj = json_object_new_int((int)ui64);
+ else if (ui64 < (1LLU << 63))
+ obj = json_object_new_int64(ui64);
+ else {
+ AFB_WARNING("U64 value %llu clamped to %llu",
+ (unsigned long long)ui64,
+ (unsigned long long)((1LLU << 63) - 1));
+ obj = json_object_new_int64((1LLU << 63) - 1);
+ }
+ break;
+ case G_VARIANT_CLASS_DOUBLE:
+ obj = json_object_new_double(g_variant_get_double(var));
+ break;
+ case G_VARIANT_CLASS_OBJECT_PATH:
+ case G_VARIANT_CLASS_STRING:
+ obj = json_object_new_string(g_variant_get_string(var, NULL));
+ break;
+
+ case G_VARIANT_CLASS_ARRAY:
+
+ if (!recurse)
+ break;
+
+ /* detect dictionaries which are arrays of dict entries */
+ g_variant_iter_init(&iter, var);
+
+ nitems = g_variant_iter_n_children(&iter);
+ /* remove completely empty arrays */
+ if (nitems == 0)
+ break;
+
+ is_dict = nitems > 0;
+ while (is_dict && (value = g_variant_iter_next_value(&iter))) {
+ is_dict = g_variant_classify(value) == G_VARIANT_CLASS_DICT_ENTRY;
+ g_variant_unref(value);
+ }
+
+ if (is_dict)
+ obj = json_object_new_object();
+ else
+ obj = json_object_new_array();
+
+ g_variant_iter_init(&iter, var);
+ while ((value = g_variant_iter_next_value(&iter))) {
+
+ item = simple_gvariant_to_json(value, obj, TRUE);
+ if (!is_dict && item)
+ json_object_array_add(obj, item);
+
+ g_variant_unref(value);
+ }
+ break;
+
+ case G_VARIANT_CLASS_DICT_ENTRY:
+
+ if (!recurse)
+ break;
+
+ if (!parent) {
+ AFB_WARNING("#### dict new object without a parent");
+ break;
+ }
+
+ g_variant_iter_init(&iter, var);
+ while ((key = g_variant_iter_next_value(&iter))) {
+
+ value = g_variant_iter_next_value(&iter);
+ if (!value) {
+ AFB_WARNING("Out of values with a key");
+ g_variant_unref(key);
+ break;
+ }
+
+ json_key = key_dbus_to_json(
+ g_variant_get_string(key, NULL),
+ auto_lowercase_keys);
+
+ /* only handle dict values with string keys */
+ if (g_variant_classify(key) == G_VARIANT_CLASS_STRING) {
+ item = simple_gvariant_to_json(value, obj, TRUE);
+ if (item)
+ json_object_object_add(parent, json_key, item);
+
+ } else
+ AFB_WARNING("Can't handle non-string key");
+
+ g_free(json_key);
+
+ g_variant_unref(value);
+ g_variant_unref(key);
+ }
+ break;
+
+ case G_VARIANT_CLASS_VARIANT:
+
+ /* NOTE: recurse allowed because we only allow a single encapsulated variant */
+
+ g_variant_iter_init(&iter, var);
+ nitems = g_variant_iter_n_children(&iter);
+ if (nitems != 1) {
+ AFB_WARNING("Can't handle variants with more than one children (%lu)", nitems);
+ break;
+ }
+
+ while ((value = g_variant_iter_next_value(&iter))) {
+ obj = simple_gvariant_to_json(value, parent, TRUE);
+ g_variant_unref(value);
+ break;
+ }
+ break;
+
+ default:
+ AFB_WARNING("############ class is %c", g_variant_classify(var));
+ obj = NULL;
+ break;
+ }
+
+ return obj;
+}
+
+gchar *property_name_dbus2json(const struct property_info *pi,
+ gboolean is_config)
+{
+ gchar *json_name;
+ gchar *cfgname;
+
+ if (pi->json_name)
+ json_name = g_strdup(pi->json_name);
+ else
+ json_name = key_dbus_to_json(pi->name, auto_lowercase_keys);
+
+ if (!json_name)
+ return NULL;
+
+ if (!is_config)
+ return json_name;
+
+ cfgname = g_strdup_printf("%s.%configuration",
+ json_name,
+ auto_lowercase_keys ? 'c' : 'C');
+ g_free(json_name);
+ return cfgname;
+}
+
+json_object *property_dbus2json(
+ const struct property_info **pip,
+ const gchar *key, GVariant *var,
+ gboolean *is_config)
+{
+ const struct property_info *pi = *pip, *pi2, *pi_sub;
+ GVariantIter iter, iter2;
+ json_object *obj = NULL, *obji;
+ const char *fmt;
+ GVariant *value, *dict_value, *dict_key;
+ const gchar *sub_key;
+ gchar *json_key;
+ gboolean is_subconfig;
+
+ if (key) {
+ pi = property_by_dbus_name(pi, key, is_config);
+ if (!pi)
+ return NULL;
+ *pip = pi;
+ }
+
+ fmt = pi->fmt;
+
+ obj = simple_gvariant_to_json(var, NULL, FALSE);
+ if (obj) {
+ /* TODO check fmt for matching type */
+ return obj;
+ }
+
+ switch (*fmt) {
+ case 'a': /* array */
+ obj = json_object_new_array();
+
+ g_variant_iter_init(&iter, var);
+ while ((value = g_variant_iter_next_value(&iter))) {
+ pi2 = pi;
+ obji = property_dbus2json(&pi2, NULL, value,
+ &is_subconfig);
+ if (obji)
+ json_object_array_add(obj, obji);
+
+ g_variant_unref(value);
+ }
+ break;
+ case '{':
+ /* we only support {sX} */
+
+ /* there must be a sub property entry */
+ g_assert(pi->sub);
+
+ obj = json_object_new_object();
+
+ g_variant_iter_init(&iter, var);
+ while ((value = g_variant_iter_next_value(&iter))) {
+
+ if (g_variant_classify(value) != G_VARIANT_CLASS_DICT_ENTRY) {
+ AFB_WARNING("Expecting dict got '%c'", g_variant_classify(value));
+ g_variant_unref(value);
+ break;
+ }
+
+ g_variant_iter_init(&iter2, value);
+ while ((dict_key = g_variant_iter_next_value(&iter2))) {
+ if (g_variant_classify(dict_key) != G_VARIANT_CLASS_STRING) {
+ AFB_WARNING("Can't handle non-string dict keys '%c'",
+ g_variant_classify(dict_key));
+ g_variant_unref(dict_key);
+ g_variant_unref(value);
+ continue;
+ }
+
+ dict_value = g_variant_iter_next_value(&iter2);
+ if (!dict_value) {
+ AFB_WARNING("Out of values with a dict_key");
+ g_variant_unref(dict_key);
+ g_variant_unref(value);
+ break;
+ }
+
+ sub_key = g_variant_get_string(dict_key, NULL);
+
+ pi_sub = pi->sub;
+ while (pi_sub->name) {
+ if (!g_strcmp0(sub_key, pi_sub->name))
+ break;
+ pi_sub++;
+ }
+
+ if (pi_sub->name) {
+ pi2 = pi_sub;
+ obji = property_dbus2json(&pi2,
+ sub_key, dict_value,
+ &is_subconfig);
+ if (obji) {
+ json_key = property_name_dbus2json(pi2, FALSE);
+ json_object_object_add(obj, json_key, obji);
+ g_free(json_key);
+ }
+ } else
+ AFB_INFO("Unhandled %s/%s property", key, sub_key);
+
+ g_variant_unref(dict_value);
+ g_variant_unref(dict_key);
+ }
+
+ g_variant_unref(value);
+ }
+
+ break;
+ }
+
+ if (!obj)
+ AFB_INFO("# %s not a type we can handle", key ? key : "<NULL>");
+
+ return obj;
+}
+
+const struct property_info *property_by_dbus_name(
+ const struct property_info *pi,
+ const gchar *dbus_name,
+ gboolean *is_config)
+{
+ const struct property_info *pit;
+ const gchar *suffix;
+ gchar *tmpname;
+ size_t len;
+
+ /* direct match first */
+ pit = pi;
+ while (pit->name) {
+ if (!g_strcmp0(dbus_name, pit->name)) {
+ if (is_config)
+ *is_config = FALSE;
+ return pit;
+ }
+ pit++;
+ }
+
+ /* try to see if a matching config property exists */
+ suffix = strrchr(dbus_name, '.');
+ if (!suffix || g_ascii_strcasecmp(suffix, ".Configuration"))
+ return NULL;
+
+ /* it's a (possible) .config property */
+ len = suffix - dbus_name;
+ tmpname = alloca(len + 1);
+ memcpy(tmpname, dbus_name, len);
+ tmpname[len] = '\0';
+
+ /* match with config */
+ pit = pi;
+ while (pit->name) {
+ if (!(pit->flags & PI_CONFIG)) {
+ pit++;
+ continue;
+ }
+ if (!g_strcmp0(tmpname, pit->name)) {
+ if (is_config)
+ *is_config = TRUE;
+ return pit;
+ }
+ pit++;
+ }
+
+ return NULL;
+}
+
+const struct property_info *property_by_json_name(
+ const struct property_info *pi,
+ const gchar *json_name,
+ gboolean *is_config)
+{
+ const struct property_info *pit;
+ gchar *this_json_name;
+ const gchar *suffix;
+ gchar *tmpname;
+ size_t len;
+
+ /* direct match */
+ pit = pi;
+ while (pit->name) {
+ this_json_name = property_name_dbus2json(pit, FALSE);
+ if (!g_strcmp0(this_json_name, json_name)) {
+ g_free(this_json_name);
+ if (is_config)
+ *is_config = FALSE;
+ return pit;
+ }
+ g_free(this_json_name);
+ pit++;
+ }
+
+ /* try to see if a matching config property exists */
+ suffix = strrchr(json_name, '.');
+ if (!suffix || g_ascii_strcasecmp(suffix, ".configuration"))
+ return NULL;
+
+ /* it's a (possible) .config property */
+ len = suffix - json_name;
+ tmpname = alloca(len + 1);
+ memcpy(tmpname, json_name, len);
+ tmpname[len] = '\0';
+
+ /* match with config */
+ pit = pi;
+ while (pit->name) {
+ if (!(pit->flags & PI_CONFIG)) {
+ pit++;
+ continue;
+ }
+ this_json_name = property_name_dbus2json(pit, FALSE);
+ if (!g_strcmp0(this_json_name, tmpname)) {
+ g_free(this_json_name);
+ if (is_config)
+ *is_config = TRUE;
+ return pit;
+ }
+ g_free(this_json_name);
+ pit++;
+ }
+
+ return NULL;
+}
+
+const struct property_info *property_by_name(
+ const struct property_info *pi,
+ gboolean is_json_name, const gchar *name,
+ gboolean *is_config)
+{
+ return is_json_name ?
+ property_by_json_name(pi, name, is_config) :
+ property_by_dbus_name(pi, name, is_config);
+}
+
+gchar *property_get_json_name(const struct property_info *pi,
+ const gchar *name)
+{
+ gboolean is_config;
+
+ pi = property_by_dbus_name(pi, name, &is_config);
+ if (!pi)
+ return NULL;
+ return property_name_dbus2json(pi, is_config);
+}
+
+gchar *configuration_dbus_name(const gchar *dbus_name)
+{
+ return g_strdup_printf("%s.Configuration", dbus_name);
+}
+
+gchar *configuration_json_name(const gchar *json_name)
+{
+ return g_strdup_printf("%s.configuration", json_name);
+}
+
+gchar *property_get_name(const struct property_info *pi,
+ const gchar *json_name)
+{
+ gboolean is_config;
+
+ pi = property_by_json_name(pi, json_name, &is_config);
+ if (!pi)
+ return NULL;
+
+ return !is_config ? g_strdup(pi->name) :
+ configuration_dbus_name(pi->name);
+}
+
+gboolean root_property_dbus2json(
+ json_object *jparent,
+ const struct property_info *pi,
+ const gchar *key, GVariant *var,
+ gboolean *is_config)
+{
+ json_object *obj;
+ gchar *json_key;
+
+ obj = property_dbus2json(&pi, key, var, is_config);
+ if (!obj)
+ return FALSE;
+
+ switch (json_object_get_type(jparent)) {
+ case json_type_object:
+ json_key = property_name_dbus2json(pi, *is_config);
+ json_object_object_add(jparent, json_key, obj);
+ g_free(json_key);
+ break;
+ case json_type_array:
+ json_object_array_add(jparent, obj);
+ break;
+ default:
+ json_object_put(obj);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static const GVariantType *type_from_fmt(const char *fmt)
+{
+ switch (*fmt) {
+ case 'b': /* gboolean */
+ return G_VARIANT_TYPE_BOOLEAN;
+ case 'y': /* guchar */
+ return G_VARIANT_TYPE_BYTE;
+ case 'n': /* gint16 */
+ return G_VARIANT_TYPE_INT16;
+ case 'q': /* guint16 */
+ return G_VARIANT_TYPE_UINT16;
+ case 'h':
+ return G_VARIANT_TYPE_HANDLE;
+ case 'i': /* gint32 */
+ return G_VARIANT_TYPE_INT32;
+ case 'u': /* guint32 */
+ return G_VARIANT_TYPE_UINT32;
+ case 'x': /* gint64 */
+ return G_VARIANT_TYPE_INT64;
+ case 't': /* gint64 */
+ return G_VARIANT_TYPE_UINT64;
+ case 'd': /* double */
+ return G_VARIANT_TYPE_DOUBLE;
+ case 's': /* string */
+ return G_VARIANT_TYPE_STRING;
+ case 'o': /* object */
+ return G_VARIANT_TYPE_OBJECT_PATH;
+ case 'g': /* signature */
+ return G_VARIANT_TYPE_SIGNATURE;
+ case 'v': /* variant */
+ return G_VARIANT_TYPE_VARIANT;
+ }
+ /* nothing complex */
+ return NULL;
+}
+
+GVariant *property_json_to_gvariant(
+ const struct property_info *pi,
+ const char *fmt,
+ const struct property_info *pi_parent,
+ json_object *jval,
+ GError **error)
+{
+ const struct property_info *pi_sub;
+ GVariant *arg, *item;
+ GVariantBuilder builder;
+ json_object *jitem;
+ json_bool b;
+ gchar *dbus_name;
+ int64_t i64;
+ double d;
+ const char *jvalstr, *str;
+ char c;
+ int i;
+ size_t len;
+ gboolean is_config;
+
+ if (!fmt)
+ fmt = pi->fmt;
+
+ if (!jval) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "can't encode json NULL type");
+ return NULL;
+ }
+
+ jvalstr = json_object_to_json_string(jval);
+
+ arg = NULL;
+
+ b = FALSE;
+ i64 = 0;
+ d = 0.0;
+ str = NULL;
+
+ /* conversion and type check */
+ c = *fmt++;
+ if (c == 'a') {
+ if (!json_object_is_type(jval, json_type_array)) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property \"%s\" (not an array)",
+ jvalstr);
+ return NULL;
+ }
+
+ len = json_object_array_length(jval);
+ /* special case for empty array */
+ if (!len) {
+ arg = g_variant_new_array(type_from_fmt(fmt), NULL, 0);
+ if (!arg)
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Can't create empty array on \"%s\"",
+ jvalstr);
+ return arg;
+ }
+
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+ for (i = 0; i < len; i++) {
+ jitem = json_object_array_get_idx(jval, i);
+ item = property_json_to_gvariant(pi, fmt, NULL, jitem, error);
+ if (!item) {
+ g_variant_builder_clear(&builder);
+ return NULL;
+ }
+ g_variant_builder_add_value(&builder, item);
+ }
+
+ arg = g_variant_builder_end(&builder);
+
+ if (!arg)
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Can't handle array on \"%s\"",
+ jvalstr);
+
+ return arg;
+ }
+ if (c == '{') {
+ g_assert(pi->sub);
+
+ c = *fmt++;
+ /* we only handle string keys */
+ if (c != 's') {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Can't handle non-string keys on \"%s\"",
+ jvalstr);
+ return NULL;
+ }
+ c = *fmt++;
+
+ /* this is arguably wrong */
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
+ pi_sub = pi->sub;
+ json_object_object_foreach(jval, key_o, jval_o) {
+ pi_sub = property_by_json_name(pi->sub, key_o, &is_config);
+ if (!pi_sub) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Unknown sub-property %s in \"%s\"",
+ key_o, json_object_to_json_string(jval_o));
+ return NULL;
+ }
+ item = property_json_to_gvariant(pi_sub, NULL, pi, jval_o, error);
+ if (!item)
+ return NULL;
+
+ dbus_name = property_get_name(pi->sub, key_o);
+ g_assert(dbus_name); /* can't fail; but check */
+
+ g_variant_builder_add(&builder, pi->fmt, dbus_name, item);
+
+ g_free(dbus_name);
+ }
+
+ arg = g_variant_builder_end(&builder);
+
+ if (!arg)
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Can't handle object on \"%s\"",
+ jvalstr);
+ return arg;
+ }
+
+ switch (c) {
+ case 'b': /* gboolean */
+ if (!json_object_is_type(jval, json_type_boolean)) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property \"%s\" (not a boolean)",
+ jvalstr);
+ return NULL;
+ }
+ b = json_object_get_boolean(jval);
+ break;
+ case 'y': /* guchar */
+ case 'n': /* gint16 */
+ case 'q': /* guint16 */
+ case 'h':
+ case 'i': /* gint32 */
+ case 'u': /* guint32 */
+ case 'x': /* gint64 */
+ case 't': /* gint64 */
+ if (!json_object_is_type(jval, json_type_int)) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property \"%s\" (not an integer)",
+ jvalstr);
+ return NULL;
+ }
+ /* note unsigned 64 bit values shall be truncated */
+ i64 = json_object_get_int64(jval);
+ break;
+
+ case 'd': /* double */
+ if (!json_object_is_type(jval, json_type_double)) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property \"%s\" (not a double)",
+ jvalstr);
+ return NULL;
+ }
+ d = json_object_get_double(jval);
+ break;
+ case 's': /* string */
+ case 'o': /* object */
+ case 'g': /* signature */
+ if (!json_object_is_type(jval, json_type_string)) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property \"%s\" (not a string)",
+ jvalstr);
+ return NULL;
+ }
+ str = json_object_get_string(jval);
+ break;
+ case 'v': /* variant */
+ AFB_WARNING("Can't handle variant yet");
+ break;
+ }
+
+ /* build gvariant */
+ switch (c) {
+ case 'b': /* gboolean */
+ arg = g_variant_new_boolean(b);
+ break;
+ case 'y': /* guchar */
+ if (i64 < 0 || i64 > 255) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property %s (out of byte range)",
+ jvalstr);
+ return FALSE;
+ }
+ arg = g_variant_new_byte((guchar)i64);
+ break;
+ case 'n': /* gint16 */
+ if (i64 < -(1LL << 15) || i64 >= (1LL << 15)) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property %s (out of int16 range)",
+ jvalstr);
+ return FALSE;
+ }
+ arg = g_variant_new_int16((gint16)i64);
+ break;
+ case 'q': /* guint16 */
+ if (i64 < 0 || i64 >= (1LL << 16)) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property %s (out of uint16 range)",
+ jvalstr);
+ return FALSE;
+ }
+ arg = g_variant_new_uint16((guint16)i64);
+ break;
+ case 'h':
+ case 'i': /* gint32 */
+ if (i64 < -(1LL << 31) || i64 >= (1LL << 31)) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property %s (out of int32 range)",
+ jvalstr);
+ return FALSE;
+ }
+ arg = g_variant_new_int32((gint32)i64);
+ break;
+ case 'u': /* guint32 */
+ if (i64 < 0 || i64 >= (1LL << 32)) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property %s (out of uint32 range)",
+ jvalstr);
+ return FALSE;
+ }
+ arg = g_variant_new_uint32((guint32)i64);
+ break;
+ case 'x': /* gint64 */
+ arg = g_variant_new_int64(i64);
+ break;
+ case 't': /* gint64 */
+ arg = g_variant_new_uint64(i64);
+ break;
+ case 'd': /* double */
+ arg = g_variant_new_double(d);
+ break;
+ case 's': /* string */
+ arg = g_variant_new_string(str);
+ break;
+ case 'o': /* object */
+ arg = g_variant_new_object_path(str);
+ break;
+ case 'g': /* signature */
+ arg = g_variant_new_signature(str);
+ break;
+ case 'v': /* variant */
+ AFB_WARNING("Can't handle variant yet");
+ break;
+ }
+
+ return arg;
+}
+
+json_object *get_property_collect(json_object *jreqprop, json_object *jprop,
+ GError **error)
+{
+ size_t len;
+ int i;
+ json_object *jkey, *jval, *jobj = NULL, *jobjval;
+ const char *key;
+
+
+ /* printf("jreqprop=%s\n", json_object_to_json_string_ext(jreqprop,
+ JSON_C_TO_STRING_SPACED));
+ printf("jprop=%s\n", json_object_to_json_string_ext(jprop,
+ JSON_C_TO_STRING_SPACED)); */
+
+ /* get is an array of strings (or an object for subtype */
+ g_assert(json_object_get_type(jreqprop) == json_type_array);
+
+ len = json_object_array_length(jreqprop);
+ if (len == 0)
+ return NULL;
+
+ for (i = 0; i < len; i++) {
+ jkey = json_object_array_get_idx(jreqprop, i);
+
+ /* string key */
+ if (json_object_is_type(jkey, json_type_string)) {
+ key = json_object_get_string(jkey);
+ if (!json_object_object_get_ex(jprop, key, &jval)) {
+ g_set_error(error,
+ NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "can't find key %s", key);
+ json_object_put(jobj);
+ return NULL;
+ }
+
+ if (!jobj)
+ jobj = json_object_new_object();
+
+ json_object_object_add(jobj, key,
+ json_object_copy(jval));
+
+ } else if (json_object_is_type(jkey, json_type_object)) {
+ /* recursing into an object */
+
+ json_object_object_foreach(jkey, key_o, jval_o) {
+ if (!json_object_object_get_ex(jprop, key_o,
+ &jval)) {
+ g_set_error(error, NB_ERROR,
+ NB_ERROR_BAD_PROPERTY,
+ "can't find key %s", key_o);
+ json_object_put(jobj);
+ return NULL;
+ }
+
+ /* jval_o is on jreqprop */
+ /* jval is on jprop */
+
+ jobjval = get_property_collect(jval_o, jval,
+ error);
+
+ if (!jobjval && error && *error) {
+ json_object_put(jobj);
+ return NULL;
+ }
+
+ if (jobjval) {
+ if (!jobj)
+ jobj = json_object_new_object();
+
+ json_object_object_add(jobj, key_o, jobjval);
+ }
+ }
+ }
+ }
+
+ /* if (jobj)
+ printf("jobj=%s\n", json_object_to_json_string_ext(jobj,
+ JSON_C_TO_STRING_SPACED)); */
+
+ return jobj;
+}
+
+json_object *get_named_property(const struct property_info *pi,
+ gboolean is_json_name, const char *name, json_object *jprop)
+{
+ json_object *jret = NULL;
+ gchar *json_name = NULL;
+
+ if (!is_json_name) {
+ json_name = property_get_json_name(pi, name);
+ if (!json_name)
+ return NULL;
+ name = json_name;
+ }
+
+ json_object_object_foreach(jprop, key, jval) {
+ if (!g_strcmp0(key, name)) {
+ jret = json_object_copy(jval);
+ break;
+ }
+ }
+
+ g_free(json_name);
+
+ return jret;
+}