blob: 1628b672787cc9f26a6089a712e5fbb00db2158d [file] [log] [blame]
/*
*
* Embedded Linux library
*
* Copyright (C) 2011-2014 Intel Corporation. All rights reserved.
* Copyright (C) 2017 Codecoup. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "ell.h"
#include "private.h"
struct l_dbus_client {
struct l_dbus *dbus;
unsigned int watch;
unsigned int added_watch;
unsigned int removed_watch;
char *service;
uint32_t objects_call;
l_dbus_watch_func_t connect_cb;
void *connect_cb_data;
l_dbus_destroy_func_t connect_cb_data_destroy;
l_dbus_watch_func_t disconnect_cb;
void *disconnect_cb_data;
l_dbus_destroy_func_t disconnect_cb_data_destroy;
l_dbus_client_ready_func_t ready_cb;
void *ready_cb_data;
l_dbus_destroy_func_t ready_cb_data_destroy;
l_dbus_client_proxy_func_t proxy_added_cb;
l_dbus_client_proxy_func_t proxy_removed_cb;
l_dbus_client_property_function_t properties_changed_cb;
void *proxy_cb_data;
l_dbus_destroy_func_t proxy_cb_data_destroy;
struct l_queue *proxies;
};
struct proxy_property {
char *name;
struct l_dbus_message *msg;
};
struct l_dbus_proxy {
struct l_dbus_client *client;
char *interface;
char *path;
uint32_t properties_watch;
bool ready;
struct l_queue *properties;
struct l_queue *pending_calls;
};
LIB_EXPORT const char *l_dbus_proxy_get_path(struct l_dbus_proxy *proxy)
{
if (unlikely(!proxy))
return NULL;
return proxy->path;
}
LIB_EXPORT const char *l_dbus_proxy_get_interface(struct l_dbus_proxy *proxy)
{
if (unlikely(!proxy))
return NULL;
return proxy->interface;
}
static bool property_match_by_name(const void *a, const void *b)
{
const struct proxy_property *prop = a;
const char *name = b;
return !strcmp(prop->name, name);
}
static struct proxy_property *find_property(struct l_dbus_proxy *proxy,
const char *name)
{
return l_queue_find(proxy->properties, property_match_by_name, name);
}
static struct proxy_property *get_property(struct l_dbus_proxy *proxy,
const char *name)
{
struct proxy_property *prop;
prop = find_property(proxy, name);
if (prop)
return prop;
prop = l_new(struct proxy_property, 1);
prop->name = l_strdup(name);
l_queue_push_tail(proxy->properties, prop);
return prop;
}
LIB_EXPORT bool l_dbus_proxy_get_property(struct l_dbus_proxy *proxy,
const char *name,
const char *signature, ...)
{
struct proxy_property *prop;
va_list args;
bool res;
if (unlikely(!proxy))
return false;
prop = find_property(proxy, name);
if (!prop)
return false;
va_start(args, signature);
res = l_dbus_message_get_arguments_valist(prop->msg, signature, args);
va_end(args);
return res;
}
static void property_free(void *data)
{
struct proxy_property *prop = data;
if (prop->msg)
l_dbus_message_unref(prop->msg);
l_free(prop->name);
l_free(prop);
}
static void cancel_pending_calls(struct l_dbus_proxy *proxy)
{
const struct l_queue_entry *entry;
for (entry = l_queue_get_entries(proxy->pending_calls); entry;
entry = entry->next) {
uint32_t call_id = L_PTR_TO_UINT(entry->data);
l_dbus_cancel(proxy->client->dbus, call_id);
}
}
static void dbus_proxy_destroy(struct l_dbus_proxy *proxy)
{
if (unlikely(!proxy))
return;
if (proxy->properties_watch)
l_dbus_remove_signal_watch(proxy->client->dbus,
proxy->properties_watch);
cancel_pending_calls(proxy);
l_queue_destroy(proxy->pending_calls, NULL);
l_queue_destroy(proxy->properties, property_free);
l_free(proxy->interface);
l_free(proxy->path);
l_free(proxy);
}
struct method_call_request
{
struct l_dbus_proxy *proxy;
uint32_t call_id;
l_dbus_message_func_t setup;
l_dbus_client_proxy_result_func_t result;
void *user_data;
l_dbus_destroy_func_t destroy;
};
static void method_call_request_free(void *user_data)
{
struct method_call_request *req = user_data;
l_queue_remove(req->proxy->pending_calls, L_UINT_TO_PTR(req->call_id));
if (req->destroy)
req->destroy(req->user_data);
l_free(req);
}
static void method_call_setup(struct l_dbus_message *message, void *user_data)
{
struct method_call_request *req = user_data;
if (req->setup)
req->setup(message, req->user_data);
else
l_dbus_message_set_arguments(message, "");
}
static void method_call_reply(struct l_dbus_message *message, void *user_data)
{
struct method_call_request *req = user_data;
if (req->result)
req->result(req->proxy, message, req->user_data);
}
LIB_EXPORT bool l_dbus_proxy_set_property(struct l_dbus_proxy *proxy,
l_dbus_client_proxy_result_func_t result,
void *user_data, l_dbus_destroy_func_t destroy,
const char *name, const char *signature, ...)
{
struct l_dbus_client *client = proxy->client;
struct l_dbus_message_builder *builder;
struct method_call_request *req;
struct l_dbus_message *message;
struct proxy_property *prop;
va_list args;
if (unlikely(!proxy))
return false;
prop = find_property(proxy, name);
if (!prop)
return false;
if (strcmp(l_dbus_message_get_signature(prop->msg), signature))
return false;
message = l_dbus_message_new_method_call(client->dbus, client->service,
proxy->path,
L_DBUS_INTERFACE_PROPERTIES,
"Set");
if (!message)
return false;
builder = l_dbus_message_builder_new(message);
if (!builder) {
l_dbus_message_unref(message);
return false;
}
l_dbus_message_builder_append_basic(builder, 's', proxy->interface);
l_dbus_message_builder_append_basic(builder, 's', name);
l_dbus_message_builder_enter_variant(builder, signature);
va_start(args, signature);
l_dbus_message_builder_append_from_valist(builder, signature, args);
va_end(args);
l_dbus_message_builder_leave_variant(builder);
l_dbus_message_builder_finalize(builder);
l_dbus_message_builder_destroy(builder);
req = l_new(struct method_call_request, 1);
req->proxy = proxy;
req->result = result;
req->user_data = user_data;
req->destroy = destroy;
req->call_id = l_dbus_send_with_reply(client->dbus, message,
method_call_reply, req,
method_call_request_free);
if (!req->call_id) {
l_free(req);
return false;
}
l_queue_push_tail(proxy->pending_calls, L_UINT_TO_PTR(req->call_id));
return true;
}
LIB_EXPORT uint32_t l_dbus_proxy_method_call(struct l_dbus_proxy *proxy,
const char *method,
l_dbus_message_func_t setup,
l_dbus_client_proxy_result_func_t reply,
void *user_data,
l_dbus_destroy_func_t destroy)
{
struct method_call_request *req;
req = l_new(struct method_call_request, 1);
req->proxy = proxy;
req->setup = setup;
req->result = reply;
req->user_data = user_data;
req->destroy = destroy;
req->call_id = l_dbus_method_call(proxy->client->dbus,
proxy->client->service,
proxy->path, proxy->interface,
method, method_call_setup,
method_call_reply, req,
method_call_request_free);
if (!req->call_id) {
l_free(req);
return 0;
}
l_queue_push_tail(proxy->pending_calls, L_UINT_TO_PTR(req->call_id));
return req->call_id;
}
static void proxy_update_property(struct l_dbus_proxy *proxy,
const char *name,
struct l_dbus_message_iter *property)
{
struct l_dbus_message_builder *builder;
struct proxy_property *prop = get_property(proxy, name);
l_dbus_message_unref(prop->msg);
if (!property) {
prop->msg = NULL;
goto done;
}
prop->msg = l_dbus_message_new_signal(proxy->client->dbus, proxy->path,
proxy->interface, name);
if (!prop->msg)
return;
builder = l_dbus_message_builder_new(prop->msg);
l_dbus_message_builder_append_from_iter(builder, property);
l_dbus_message_builder_finalize(builder);
l_dbus_message_builder_destroy(builder);
done:
if (proxy->client->properties_changed_cb && proxy->ready)
proxy->client->properties_changed_cb(proxy, name, prop->msg,
proxy->client->proxy_cb_data);
}
static void proxy_invalidate_properties(struct l_dbus_proxy *proxy,
struct l_dbus_message_iter* props)
{
const char *name;
while (l_dbus_message_iter_next_entry(props, &name))
proxy_update_property(proxy, name, NULL);
}
static void proxy_update_properties(struct l_dbus_proxy *proxy,
struct l_dbus_message_iter* props)
{
struct l_dbus_message_iter variant;
const char *name;
while (l_dbus_message_iter_next_entry(props, &name, &variant))
proxy_update_property(proxy, name, &variant);
}
static void properties_changed_callback(struct l_dbus_message *message,
void *user_data)
{
struct l_dbus_proxy *proxy = user_data;
const char *interface;
struct l_dbus_message_iter changed;
struct l_dbus_message_iter invalidated;
if (!l_dbus_message_get_arguments(message, "sa{sv}as", &interface,
&changed, &invalidated))
return;
proxy_update_properties(proxy, &changed);
proxy_invalidate_properties(proxy, &invalidated);
}
static struct l_dbus_proxy *dbus_proxy_new(struct l_dbus_client *client,
const char *path, const char *interface)
{
struct l_dbus_proxy *proxy = l_new(struct l_dbus_proxy, 1);
proxy->properties_watch = l_dbus_add_signal_watch(client->dbus,
client->service, path,
L_DBUS_INTERFACE_PROPERTIES,
"PropertiesChanged",
L_DBUS_MATCH_ARGUMENT(0),
interface, L_DBUS_MATCH_NONE,
properties_changed_callback,
proxy);
if (!proxy->properties_watch) {
l_free(proxy);
return NULL;
}
proxy->client = client;
proxy->interface = l_strdup(interface);
proxy->path = l_strdup(path);
proxy->properties = l_queue_new();
proxy->pending_calls = l_queue_new();;
l_queue_push_tail(client->proxies, proxy);
return proxy;
}
static bool is_ignorable(const char *interface)
{
static const struct {
const char *interface;
} interfaces_to_ignore[] = {
{ L_DBUS_INTERFACE_OBJECT_MANAGER },
{ L_DBUS_INTERFACE_INTROSPECTABLE },
{ L_DBUS_INTERFACE_PROPERTIES },
};
size_t i;
for (i = 0; i < L_ARRAY_SIZE(interfaces_to_ignore); i++)
if (!strcmp(interfaces_to_ignore[i].interface, interface))
return true;
return false;
}
static struct l_dbus_proxy *find_proxy(struct l_dbus_client *client,
const char *path, const char *interface)
{
const struct l_queue_entry *entry;
for (entry = l_queue_get_entries(client->proxies); entry;
entry = entry->next) {
struct l_dbus_proxy *proxy = entry->data;
if (!strcmp(proxy->interface, interface) &&
!strcmp(proxy->path, path))
return proxy;
}
return NULL;
}
static void parse_interface(struct l_dbus_client *client, const char *path,
const char *interface,
struct l_dbus_message_iter *properties)
{
struct l_dbus_proxy *proxy;
if (is_ignorable(interface))
return;
proxy = find_proxy(client, path, interface);
if (!proxy)
proxy = dbus_proxy_new(client, path, interface);
if (!proxy)
return;
proxy_update_properties(proxy, properties);
if (!proxy->ready) {
proxy->ready = true;
if (client->proxy_added_cb)
client->proxy_added_cb(proxy, client->proxy_cb_data);
}
}
static void parse_object(struct l_dbus_client *client, const char *path,
struct l_dbus_message_iter *object)
{
const char *interface;
struct l_dbus_message_iter properties;
if (!path)
return;
while (l_dbus_message_iter_next_entry(object, &interface, &properties))
parse_interface(client, path, interface, &properties);
}
static void interfaces_added_callback(struct l_dbus_message *message,
void *user_data)
{
struct l_dbus_client *client = user_data;
struct l_dbus_message_iter object;
const char *path;
if (!l_dbus_message_get_arguments(message, "oa{sa{sv}}", &path,
&object))
return;
parse_object(client, path, &object);
}
static void interfaces_removed_callback(struct l_dbus_message *message,
void *user_data)
{
struct l_dbus_client *client = user_data;
struct l_dbus_message_iter interfaces;
const char *interface;
const char *path;
if (!l_dbus_message_get_arguments(message, "oas", &path, &interfaces))
return;
while (l_dbus_message_iter_next_entry(&interfaces, &interface)) {
struct l_dbus_proxy *proxy;
proxy = find_proxy(client, path, interface);
if (!proxy)
continue;
l_queue_remove(proxy->client->proxies, proxy);
if (client->proxy_removed_cb)
client->proxy_removed_cb(proxy, client->proxy_cb_data);
dbus_proxy_destroy(proxy);
}
}
static void get_managed_objects_reply(struct l_dbus_message *message,
void *user_data)
{
struct l_dbus_client *client = user_data;
struct l_dbus_message_iter objects;
struct l_dbus_message_iter object;
const char *path;
client->objects_call = 0;
if (l_dbus_message_is_error(message))
return;
l_dbus_message_get_arguments(message, "a{oa{sa{sv}}}", &objects);
while (l_dbus_message_iter_next_entry(&objects, &path, &object))
parse_object(client, path, &object);
client->added_watch = l_dbus_add_signal_watch(client->dbus,
client->service, "/",
L_DBUS_INTERFACE_OBJECT_MANAGER,
"InterfacesAdded",
L_DBUS_MATCH_NONE,
interfaces_added_callback,
client);
client->removed_watch = l_dbus_add_signal_watch(client->dbus,
client->service, "/",
L_DBUS_INTERFACE_OBJECT_MANAGER,
"InterfacesRemoved",
L_DBUS_MATCH_NONE,
interfaces_removed_callback,
client);
if (client->ready_cb)
client->ready_cb(client, client->ready_cb_data);
}
static void service_appeared_callback(struct l_dbus *dbus, void *user_data)
{
struct l_dbus_client *client = user_data;
/* TODO should we allow to set different root? */
client->objects_call = l_dbus_method_call(dbus, client->service, "/",
L_DBUS_INTERFACE_OBJECT_MANAGER,
"GetManagedObjects", NULL,
get_managed_objects_reply,
client, NULL);
if (client->connect_cb)
client->connect_cb(client->dbus, client->connect_cb_data);
}
static void service_disappeared_callback(struct l_dbus *dbus, void *user_data)
{
struct l_dbus_client *client = user_data;
if (client->disconnect_cb)
client->disconnect_cb(client->dbus, client->disconnect_cb_data);
l_queue_clear(client->proxies,
(l_queue_destroy_func_t)dbus_proxy_destroy);
}
LIB_EXPORT struct l_dbus_client *l_dbus_client_new(struct l_dbus *dbus,
const char *service, const char *path)
{
struct l_dbus_client *client = l_new(struct l_dbus_client, 1);
client->dbus = dbus;
client->watch = l_dbus_add_service_watch(dbus, service,
service_appeared_callback,
service_disappeared_callback,
client, NULL);
if (!client->watch) {
l_free(client);
return NULL;
}
client->service = l_strdup(service);
client->proxies = l_queue_new();
return client;
}
LIB_EXPORT void l_dbus_client_destroy(struct l_dbus_client *client)
{
if (unlikely(!client))
return;
if (client->watch)
l_dbus_remove_signal_watch(client->dbus, client->watch);
if (client->added_watch)
l_dbus_remove_signal_watch(client->dbus, client->added_watch);
if (client->removed_watch)
l_dbus_remove_signal_watch(client->dbus, client->removed_watch);
if (client->connect_cb_data_destroy)
client->connect_cb_data_destroy(client->connect_cb_data);
if (client->disconnect_cb_data_destroy)
client->disconnect_cb_data_destroy(client->disconnect_cb_data);
if (client->ready_cb_data_destroy)
client->ready_cb_data_destroy(client->ready_cb_data);
if (client->proxy_cb_data_destroy)
client->proxy_cb_data_destroy(client->proxy_cb_data);
if (client->objects_call)
l_dbus_cancel(client->dbus, client->objects_call)
;
l_queue_destroy(client->proxies,
(l_queue_destroy_func_t)dbus_proxy_destroy);
l_free(client->service);
l_free(client);
}
LIB_EXPORT bool l_dbus_client_set_connect_handler(struct l_dbus_client *client,
l_dbus_watch_func_t function,
void *user_data,
l_dbus_destroy_func_t destroy)
{
if (unlikely(!client))
return false;
if (client->connect_cb_data_destroy)
client->connect_cb_data_destroy(client->connect_cb_data);
client->connect_cb = function;
client->connect_cb_data = user_data;
client->connect_cb_data_destroy = destroy;
return true;
}
LIB_EXPORT bool l_dbus_client_set_disconnect_handler(struct l_dbus_client *client,
l_dbus_watch_func_t function,
void *user_data,
l_dbus_destroy_func_t destroy)
{
if (unlikely(!client))
return false;
if(client->disconnect_cb_data_destroy)
client->disconnect_cb_data_destroy(client->disconnect_cb_data);
client->disconnect_cb = function;
client->disconnect_cb_data = user_data;
client->disconnect_cb_data_destroy = destroy;
return true;
}
LIB_EXPORT bool l_dbus_client_set_ready_handler(struct l_dbus_client *client,
l_dbus_client_ready_func_t function,
void *user_data,
l_dbus_destroy_func_t destroy)
{
if (unlikely(!client))
return false;
if (client->ready_cb_data_destroy)
client->ready_cb_data_destroy(client->ready_cb_data);
client->ready_cb = function;
client->ready_cb_data = user_data;
client->ready_cb_data_destroy = destroy;
return true;
}
LIB_EXPORT bool l_dbus_client_set_proxy_handlers(struct l_dbus_client *client,
l_dbus_client_proxy_func_t proxy_added,
l_dbus_client_proxy_func_t proxy_removed,
l_dbus_client_property_function_t property_changed,
void *user_data, l_dbus_destroy_func_t destroy)
{
if (unlikely(!client))
return false;
if (client->proxy_cb_data_destroy)
client->proxy_cb_data_destroy(client->proxy_cb_data);
client->proxy_added_cb = proxy_added;
client->proxy_removed_cb = proxy_removed;
client->properties_changed_cb = property_changed;
client->proxy_cb_data = user_data;
client->proxy_cb_data_destroy = destroy;
return true;
}