summaryrefslogtreecommitdiffstats
path: root/binding/bluetooth-map-bluez.c
blob: f957f5cddedc4f4dd1d263eb485ebf9ccd40b23e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
  <remote fetch="https://gerrit.automotivelinux.org/gerrit/" name="agl" pushurl="ssh://gerrit.automotivelinux.org:29418" review="https://gerrit.automotivelinux.org/gerrit/"/>
  <remote fetch="https://github.com/" name="github"/>
  <remote fetch="git://git.openembedded.org/" name="openembedded"/>
  <remote fetch="git://code.qt.io/" name="qt.io"/>
  <remote fetch="git://git.yoctoproject.org/" name="yocto"/>
  
  <default remote="agl" revision="refs/tags/flounder/6.0.3" sync-j="4"/>
  
  <project name="01org/meta-security-isafw" path="meta-security-isafw" remote="github" revision="489abdc65cefb566d696c8b218aa0b9b99a350ae" upstream="master"/>
  <project name="AGL/meta-agl" path="meta-agl"/>
  <project name="AGL/meta-agl-demo" path="meta-agl-demo"/>
  <project name="AGL/meta-agl-devel" path="meta-agl-devel"/>
  <project name="AGL/meta-agl-extra" path="meta-agl-extra"/>
  <project name="AGL/meta-renesas-rcar-gen3" path="meta-renesas-rcar-gen3"/>
  <project name="CogentEmbedded/meta-rcar" path="meta-rcar" remote="github" revision="b9be2cd1ed7e6fe3662cf99001a0fabc51652ecf" upstream="v3.9.0"/>
  <project name="advancedtelematic/meta-updater" path="meta-updater" remote="github" revision="ff555e8690eb47177ade42dc6912ae17a759cc45" upstream="rocko"/>
  <project name="advancedtelematic/meta-updater-qemux86-64" path="meta-updater-qemux86-64" remote="github" revision="697632ddd98ed7ae3dbd0bd84abb04079767bc56" upstream="rocko"/>
  <project name="boundarydevices/meta-boundary" path="meta-boundary" remote="github" revision="f96f41b2e5beda2b51acb702d082568898b36a68" upstream="rocko"/>
  <project name="kraj/meta-altera" path="meta-altera" remote="github" revision="14e08a419cb9d4017f40360c
/*
 * Copyright (C) 2019 Konsulko Group
 * Author: Matt Ranostay <matt.ranostay@konsulko.com>
 * 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 <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);

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") ||
			   !strcmp(method, "SetFolder")) {

			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") ||
			   !strcmp(method, "PushMessage")) {

			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 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",
				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) ||
			  !strcmp(access_type, BLUEZ_AT_MESSAGEACCESS))) {
		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 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",
				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;
}