From 91e1d0697da98971ab6375bfd745ed158b7b7185 Mon Sep 17 00:00:00 2001 From: Matt Ranostay Date: Thu, 4 Apr 2019 17:21:59 -0700 Subject: binding: bluetooth-map: add initial MAP binding This patchset brings initial Bluetooth MAP (Message Access Profile) support. Bug-AGL: SPEC-2351 Change-Id: I76b974978f72869f593526c4f6926bb5c27c48a9 Signed-off-by: Matt Ranostay --- binding/bluetooth-map-bluez.c | 481 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 481 insertions(+) create mode 100644 binding/bluetooth-map-bluez.c (limited to 'binding/bluetooth-map-bluez.c') diff --git a/binding/bluetooth-map-bluez.c b/binding/bluetooth-map-bluez.c new file mode 100644 index 0000000..6d70fab --- /dev/null +++ b/binding/bluetooth-map-bluez.c @@ -0,0 +1,481 @@ +/* + * Copyright (C) 2019 Konsulko Group + * Author: Matt Ranostay + * Author: Pantelis Antoniou + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define AFB_BINDING_VERSION 3 +#include + +#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", }, + { .name = "Channel", .fmt = "y", }, + { .name = "Target", .fmt = "s", }, + { .name = "Root", .fmt = "s", }, + {}, +}; + +static const struct property_info transfer_props[] = { + { .name = "Status", .fmt = "s", }, + { .name = "Session", .fmt = "o", }, + { .name = "Name", .fmt = "s", }, + { .name = "Type", .fmt = "s", }, + { .name = "Time", .fmt = "t", }, + { .name = "Size", .fmt = "t", }, + { .name = "Transferred", .fmt = "t", }, + { .name = "Filename", .fmt = "s", }, + {}, +}; + +static const struct property_info message_props[] = { + { .name = "Folder", .fmt = "s", }, + { .name = "Subject", .fmt = "s", }, + { .name = "Timestamp", .fmt = "s", }, + { .name = "Sender", .fmt = "s", }, + { .name = "SenderAddress", .fmt = "s", }, + { .name = "ReplyTo", .fmt = "s", }, + { .name = "Recipient", .fmt = "s", }, + { .name = "RecipientAddress", .fmt = "s", }, + { .name = "Type", .fmt = "s", }, + { .name = "Size", .fmt = "t", }, + { .name = "Status", .fmt = "s", }, + { .name = "Priority", .fmt = "b", }, + { .name = "Read", .fmt = "b", }, + { .name = "Deleted", .fmt = "b", }, + { .name = "Sent", .fmt = "b", }, + { .name = "Protected", .fmt = "b", }, + {}, +}; + +const struct property_info *bluez_get_property_info( + const char *access_type, GError **error) +{ + const struct property_info *pi = NULL; + + if (!strcmp(access_type, BLUEZ_AT_SESSION)) + pi = session_props; + else if (!strcmp(access_type, BLUEZ_AT_TRANSFER)) + pi = transfer_props; + else if (!strcmp(access_type, BLUEZ_AT_MESSAGE)) + pi = message_props; + else + g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT, + "illegal %s argument", access_type); + return pi; +} + +gboolean bluez_property_dbus2json(const char *access_type, + json_object *jprop, const gchar *key, GVariant *var, + gboolean *is_config, + GError **error) +{ + const struct property_info *pi; + gboolean ret; + + *is_config = FALSE; + pi = bluez_get_property_info(access_type, error); + if (!pi) + return FALSE; + + ret = root_property_dbus2json(jprop, pi, key, var, is_config); + if (!ret) + g_set_error(error, NB_ERROR, NB_ERROR_UNKNOWN_PROPERTY, + "unknown %s property %s", + access_type, key); + + return ret; +} + +void bluez_decode_call_error(struct map_state *ns, + const char *access_type, const char *type_arg, + const char *method, + GError **error) +{ + if (!error || !*error) + return; + + if (strstr((*error)->message, + "org.freedesktop.DBus.Error.UnknownObject")) { + + if (!strcmp(method, "Set") || + !strcmp(method, "Get") || + !strcmp(method, "GetAll")) { + + g_clear_error(error); + g_set_error(error, NB_ERROR, + NB_ERROR_UNKNOWN_PROPERTY, + "unknown %s property on %s", + access_type, type_arg); + + } else if (!strcmp(method, "CreateSession") || + !strcmp(method, "RemoveSession")) { + + g_clear_error(error); + g_set_error(error, NB_ERROR, + NB_ERROR_UNKNOWN_SERVICE, + "unknown service %s", + type_arg); + + } else if (!strcmp(method, "Cancel") || + !strcmp(method, "Suspend") || + !strcmp(method, "Resume")) { + + g_clear_error(error); + g_set_error(error, NB_ERROR, + NB_ERROR_UNKNOWN_TRANSFER, + "unknown transfer %s", + type_arg); + + } + } +} + +GVariant *bluez_call(struct map_state *ns, + const char *access_type, const char *path, + const char *method, GVariant *params, GError **error) +{ + const char *interface; + GVariant *reply; + + if (!path && (!strcmp(access_type, BLUEZ_AT_SESSION) || + !strcmp(access_type, BLUEZ_AT_TRANSFER) || + !strcmp(access_type, BLUEZ_AT_MESSAGE))) { + g_set_error(error, NB_ERROR, NB_ERROR_MISSING_ARGUMENT, + "missing %s argument", + access_type); + return NULL; + } + + if (!strcmp(access_type, BLUEZ_AT_CLIENT)) { + path = BLUEZ_OBEX_PATH; + interface = BLUEZ_OBEX_CLIENT_INTERFACE; + } else if (!strcmp(access_type, BLUEZ_AT_SESSION)) { + interface = BLUEZ_OBEX_SESSION_INTERFACE; + } else if (!strcmp(access_type, BLUEZ_AT_TRANSFER)) { + interface = BLUEZ_OBEX_TRANSFER_INTERFACE; + } else if (!strcmp(access_type, BLUEZ_AT_MESSAGE)) { + interface = BLUEZ_OBEX_MESSAGE_INTERFACE; + } else { + g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT, + "illegal %s argument", + access_type); + return NULL; + } + + reply = g_dbus_connection_call_sync(ns->conn, + BLUEZ_OBEX_SERVICE, path, interface, method, params, + NULL, G_DBUS_CALL_FLAGS_NONE, DBUS_REPLY_TIMEOUT, + NULL, error); + bluez_decode_call_error(ns, access_type, path, method, + error); + if (!reply && error) { + if (*error) + g_dbus_error_strip_remote_error(*error); + AFB_ERROR("Error calling %s%s%s %s method %s", + access_type, + path ? "/" : "", + path ? path : "", + method, + error && *error ? (*error)->message : + "unspecified"); + } + + return reply; +} + +static void bluez_call_async_ready(GObject *source_object, + GAsyncResult *res, gpointer user_data) +{ + struct bluez_pending_work *cpw = user_data; + struct map_state *ns = cpw->ns; + GVariant *result; + GError *error = NULL; + + result = g_dbus_connection_call_finish(ns->conn, res, &error); + + cpw->callback(cpw->user_data, result, &error); + + g_clear_error(&error); + g_cancellable_reset(cpw->cancel); + g_free(cpw); +} + +void bluez_cancel_call(struct map_state *ns, + struct bluez_pending_work *cpw) +{ + g_cancellable_cancel(cpw->cancel); +} + +struct bluez_pending_work * +bluez_call_async(struct map_state *ns, + const char *access_type, const char *type_arg, + const char *method, GVariant *params, GError **error, + void (*callback)(void *user_data, GVariant *result, GError **error), + void *user_data) +{ + const char *path; + const char *interface; + struct bluez_pending_work *cpw; + + if (!type_arg && (!strcmp(access_type, BLUEZ_AT_SESSION) || + !strcmp(access_type, BLUEZ_AT_TRANSFER))) { + g_set_error(error, NB_ERROR, NB_ERROR_MISSING_ARGUMENT, + "missing %s argument", + access_type); + return NULL; + } + + if (!strcmp(access_type, BLUEZ_AT_SESSION)) { + path = type_arg; + interface = BLUEZ_OBEX_SESSION_INTERFACE; + } else if (!strcmp(access_type, BLUEZ_AT_TRANSFER)) { + path = type_arg; + interface = BLUEZ_OBEX_TRANSFER_INTERFACE; + } else { + g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT, + "illegal %s argument", + access_type); + return NULL; + } + + cpw = g_malloc(sizeof(*cpw)); + if (!cpw) { + g_set_error(error, NB_ERROR, NB_ERROR_OUT_OF_MEMORY, + "out of memory"); + return NULL; + } + cpw->ns = ns; + cpw->user_data = user_data; + cpw->cancel = g_cancellable_new(); + if (!cpw->cancel) { + g_free(cpw); + g_set_error(error, NB_ERROR, NB_ERROR_OUT_OF_MEMORY, + "out of memory"); + return NULL; + } + cpw->callback = callback; + + g_dbus_connection_call(ns->conn, + BLUEZ_OBEX_SERVICE, path, interface, method, params, + NULL, /* reply type */ + G_DBUS_CALL_FLAGS_NONE, DBUS_REPLY_TIMEOUT, + cpw->cancel, /* cancellable? */ + bluez_call_async_ready, + cpw); + + return cpw; +} + +json_object *bluez_get_properties(struct map_state *ns, + const char *access_type, const char *path, + GError **error) +{ + const struct property_info *pi = NULL; + const char *method = NULL; + GVariant *reply = NULL, *var = NULL; + GVariantIter *array; + const char *interface, *interface2; + const gchar *key = NULL; + json_object *jprop = NULL, *jresp = NULL; + gboolean is_config; + + if (!strcmp(access_type, BLUEZ_AT_SESSION) || + !strcmp(access_type, BLUEZ_AT_TRANSFER) || + !strcmp(access_type, BLUEZ_AT_MESSAGE)) { + + pi = bluez_get_property_info(access_type, error); + if (!pi) + return NULL; + + interface = FREEDESKTOP_PROPERTIES; + method = "GetAll"; + } else if (!strcmp(access_type, BLUEZ_AT_OBJECT)) { + interface = FREEDESKTOP_OBJECTMANAGER; + method = "GetManagedObjects"; + } else { + return FALSE; + } + + if (!strcmp(access_type, BLUEZ_AT_SESSION)) + interface2 = BLUEZ_OBEX_SESSION_INTERFACE; + else if (!strcmp(access_type, BLUEZ_AT_TRANSFER)) + interface2 = BLUEZ_OBEX_TRANSFER_INTERFACE; + else if (!strcmp(access_type, BLUEZ_AT_MESSAGE)) + interface2 = BLUEZ_OBEX_MESSAGE_INTERFACE; + else if (!strcmp(access_type, BLUEZ_AT_OBJECT)) + interface2 = NULL; + else + return FALSE; + + if (!method) { + g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT, + "illegal %s argument", + access_type); + return NULL; + } + + reply = g_dbus_connection_call_sync(ns->conn, + BLUEZ_OBEX_SERVICE, path, interface, method, + interface2 ? g_variant_new("(s)", interface2) : NULL, + NULL, G_DBUS_CALL_FLAGS_NONE, DBUS_REPLY_TIMEOUT, + NULL, error); + + if (!reply) + return NULL; + + if (!strcmp(access_type, BLUEZ_AT_SESSION) || + !strcmp(access_type, BLUEZ_AT_TRANSFER) || + !strcmp(access_type, BLUEZ_AT_MESSAGE)) { + jprop = json_object_new_object(); + g_variant_get(reply, "(a{sv})", &array); + while (g_variant_iter_loop(array, "{sv}", &key, &var)) { + root_property_dbus2json(jprop, pi, + key, var, &is_config); + } + g_variant_iter_free(array); + g_variant_unref(reply); + jresp = jprop; + } else if (!strcmp(access_type, BLUEZ_AT_OBJECT)) { + /* TODO: maybe not needed */ + + g_variant_iter_free(array); + g_variant_unref(reply); + } + + if (!jresp) { + g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT, + "No %s", access_type); + } + + return jresp; +} + +json_object *bluez_get_property(struct map_state *ns, + const char *access_type, const char *path, + gboolean is_json_name, const char *name, GError **error) +{ + const struct property_info *pi; + json_object *jprop, *jval; + + pi = bluez_get_property_info(access_type, error); + if (!pi) + return NULL; + + jprop = bluez_get_properties(ns, access_type, path, error); + if (!jprop) + return NULL; + + jval = get_named_property(pi, is_json_name, name, jprop); + json_object_put(jprop); + + if (!jval) + g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY, + "Bad property %s on %s%s%s", name, + access_type, + path ? "/" : "", + path ? path : ""); + return jval; +} + +/* NOTE: jval is consumed */ +gboolean bluez_set_property(struct map_state *ns, + const char *access_type, const char *path, + gboolean is_json_name, const char *name, json_object *jval, + GError **error) +{ + const struct property_info *pi; + GVariant *reply, *arg; + const char *interface; + gboolean is_config; + gchar *propname; + + g_assert(path); + + /* get start of properties */ + pi = bluez_get_property_info(access_type, error); + if (!pi) + return FALSE; + + /* get actual property */ + pi = property_by_name(pi, is_json_name, name, &is_config); + if (!pi) { + g_set_error(error, NB_ERROR, NB_ERROR_UNKNOWN_PROPERTY, + "unknown property with name %s", name); + json_object_put(jval); + return FALSE; + } + + /* convert to gvariant */ + arg = property_json_to_gvariant(pi, NULL, NULL, jval, error); + + /* we don't need this anymore */ + json_object_put(jval); + jval = NULL; + + /* no variant? error */ + if (!arg) + return FALSE; + + if (!is_config) + propname = g_strdup(pi->name); + else + propname = configuration_dbus_name(pi->name); + + if (!strcmp(access_type, BLUEZ_AT_SESSION)) + interface = BLUEZ_OBEX_SESSION_INTERFACE; + else if (!strcmp(access_type, BLUEZ_AT_TRANSFER)) + interface = BLUEZ_OBEX_TRANSFER_INTERFACE; + else if (!strcmp(access_type, BLUEZ_AT_MESSAGE)) + interface = BLUEZ_OBEX_MESSAGE_INTERFACE; + else + return FALSE; + + reply = g_dbus_connection_call_sync(ns->conn, + BLUEZ_OBEX_SERVICE, path, FREEDESKTOP_PROPERTIES, "Set", + g_variant_new("(ssv)", interface, propname, arg), + NULL, G_DBUS_CALL_FLAGS_NONE, DBUS_REPLY_TIMEOUT, + NULL, error); + + g_free(propname); + + if (!reply) + return FALSE; + + g_variant_unref(reply); + + return TRUE; +} -- cgit 1.2.3-korg