| /* |
| * neard - Near Field Communication manager |
| * |
| * Copyright (C) 2013 Intel Corporation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program 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 General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation. |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdint.h> |
| #include <errno.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <sys/socket.h> |
| |
| #include <near/nfc_copy.h> |
| #include <near/dbus.h> |
| #include <near/log.h> |
| #include <near/device.h> |
| |
| #include <glib.h> |
| |
| #include <gdbus.h> |
| |
| #include "p2p.h" |
| |
| |
| #define NFC_NEARD_PHDC_IFACE NFC_SERVICE ".PHDC" |
| #define NFC_NEARD_PHDC_PATH "/" |
| |
| /* Phdc Agent */ |
| #define PHDC_MANAGER_IFACE "org.neard.PHDC.Manager" |
| #define DEFAULT_PHDC_AGENT_PATH "/" |
| #define DEFAULT_PHDC_SERVICE "urn:nfc:sn:phdc" |
| |
| /* |
| * Client role |
| * TODO: Extend the role to Agent |
| */ |
| #define ROLE_MANAGER_TEXT "Manager" |
| #define ROLE_AGENT_TEXT "Agent" |
| |
| enum near_role_id { |
| ROLE_UNKNOWN = 0, |
| ROLE_MANAGER = 1, |
| ROLE_AGENT = 2, |
| }; |
| |
| #define AGENT_NEWCONNECTION "NewConnection" |
| #define AGENT_DISCONNECT "Disconnection" |
| #define AGENT_RELEASE "Release" |
| |
| struct near_phdc_data { |
| char *sender; /* dbus sender internal */ |
| enum near_role_id role; /* Manager or Agent */ |
| char *path; /* dbus manager path */ |
| struct near_p2p_driver *p2p_driver; /* associated p2p driver */ |
| guint watch; /* dbus watch */ |
| }; |
| |
| static DBusConnection *phdc_conn; |
| static GHashTable *mgr_list = NULL; /* Existing managers list */ |
| |
| static DBusMessage *error_invalid_arguments(DBusMessage *msg) |
| { |
| return g_dbus_create_error(msg, NFC_ERROR_INTERFACE |
| ".InvalidArguments", "Invalid arguments"); |
| } |
| |
| static DBusMessage *error_not_found(DBusMessage *msg) |
| { |
| return g_dbus_create_error(msg, NFC_ERROR_INTERFACE |
| ".NotFound", "Not found"); |
| } |
| |
| static DBusMessage *error_permission_denied(DBusMessage *msg) |
| { |
| return g_dbus_create_error(msg, NFC_ERROR_INTERFACE |
| ".PermissionDenied", "PermissionDenied"); |
| } |
| |
| static DBusMessage *error_failed(DBusMessage *msg, int errnum) |
| { |
| const char *str = strerror(errnum); |
| |
| return g_dbus_create_error(msg, NFC_ERROR_INTERFACE |
| ".Failed", "%s", str); |
| } |
| |
| /* Search for the specific path */ |
| static void *search_mgr_list_by_path(const char *path) |
| { |
| struct near_phdc_data *tmp; |
| GHashTableIter it; |
| gpointer key; |
| |
| DBG("Look for mgr path %s", path); |
| g_hash_table_iter_init(&it, mgr_list); |
| while (g_hash_table_iter_next(&it, &key, (gpointer *)&tmp)) |
| if (g_str_equal(tmp->path, path)) |
| return (void *)tmp; |
| return NULL; |
| } |
| |
| /* add the new phdc manager if the associated service is not already there */ |
| static int manager_add_to_list(struct near_phdc_data *mgr) |
| { |
| DBG(" mgr service name %s", mgr->p2p_driver->service_name); |
| |
| if (g_hash_table_lookup(mgr_list, mgr->p2p_driver->service_name)) { |
| near_error("[%s] already present", |
| mgr->p2p_driver->service_name); |
| return -EALREADY; |
| } |
| |
| g_hash_table_insert(mgr_list, mgr->p2p_driver->service_name, mgr); |
| |
| return 0; |
| } |
| |
| static void mgr_agent_release(gpointer key, gpointer data, gpointer user_data) |
| { |
| struct near_phdc_data *mgr_data = data; |
| DBusMessage *message; |
| |
| DBG("%s %s", mgr_data->sender, mgr_data->path); |
| |
| message = dbus_message_new_method_call(mgr_data->sender, mgr_data->path, |
| PHDC_MANAGER_IFACE, AGENT_RELEASE); |
| if (!message) |
| return; |
| |
| dbus_message_set_no_reply(message, TRUE); |
| |
| g_dbus_send_message(phdc_conn, message); |
| } |
| |
| static void free_mgr_data(gpointer data) |
| { |
| struct near_phdc_data *mgr_data = data; |
| |
| DBG("%p", data); |
| |
| /* free memory */ |
| if (mgr_data->watch > 0) |
| g_dbus_remove_watch(phdc_conn, mgr_data->watch); |
| |
| if (mgr_data->p2p_driver) { |
| g_free(mgr_data->p2p_driver->name); |
| g_free(mgr_data->p2p_driver->service_name); |
| g_free(mgr_data->p2p_driver); |
| } |
| |
| g_free(mgr_data->path); |
| g_free(mgr_data->sender); |
| |
| g_free(mgr_data); |
| } |
| |
| /* |
| * This function is called when a new client (Phdc Agent) connects on the |
| * same p2p service as the one we previously registered. We have to find the |
| * right Phdc Manager (with the service name) to send it the file descriptor. |
| */ |
| static bool phdc_p2p_newclient(char *service_name, int agent_fd, gpointer data) |
| { |
| DBusMessage *msg; |
| DBusMessageIter args; |
| struct near_phdc_data *mgr; |
| |
| DBG(""); |
| |
| if ((!agent_fd) || (!service_name)) |
| return false; |
| |
| DBG("service name: %s fd: %d", service_name, agent_fd); |
| |
| /* Look for existing service name */ |
| mgr = g_hash_table_lookup(mgr_list, service_name); |
| if (!mgr) |
| return false; |
| |
| mgr->p2p_driver->user_data = mgr; |
| |
| /* Call the pdhc manager */ |
| msg = dbus_message_new_method_call(mgr->sender, mgr->path, |
| PHDC_MANAGER_IFACE, |
| AGENT_NEWCONNECTION); |
| if (!msg) { |
| near_error("msg NULL"); |
| return false; |
| } |
| |
| /* Add args */ |
| dbus_message_iter_init_append(msg, &args); |
| |
| if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UNIX_FD, |
| &agent_fd)) { |
| near_error("out of memory"); |
| return false; |
| } |
| |
| dbus_message_set_no_reply(msg, TRUE); |
| if (g_dbus_send_message(phdc_conn, msg) == FALSE) { |
| near_error("Dbus send failed"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static void phdc_p2p_close(int agent_fd, int err, gpointer data) |
| { |
| DBusMessage *msg; |
| DBusMessageIter args; |
| struct near_phdc_data *mgr; |
| |
| mgr = (struct near_phdc_data *)data; |
| |
| DBG("fd: %d err: %d mgr:%p", agent_fd, err, mgr); |
| if (!mgr) { |
| near_error("mgr is null"); |
| return; |
| } |
| |
| msg = dbus_message_new_method_call(mgr->sender, mgr->path, |
| PHDC_MANAGER_IFACE, |
| AGENT_DISCONNECT); |
| if (!msg) { |
| near_error("msg NULL"); |
| return; |
| } |
| |
| dbus_message_iter_init_append(msg, &args); |
| |
| if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UNIX_FD, |
| &agent_fd)) { |
| near_error("out of memory"); |
| return; |
| } |
| |
| dbus_message_iter_init_append(msg, &args); |
| if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_INT32, &err)) { |
| near_error("out of memory"); |
| return; |
| } |
| |
| dbus_message_set_no_reply(msg, TRUE); |
| |
| if (g_dbus_send_message(phdc_conn, msg) == FALSE) |
| near_error("Dbus send failed"); |
| } |
| |
| /* Called when the external Phdc manager ends or disconnect */ |
| static void phdc_manager_disconnect(DBusConnection *conn, void *user_data) |
| { |
| struct near_phdc_data *phdc_mgr = user_data; |
| |
| phdc_mgr->watch = 0; |
| |
| DBG("PHDC manager %s disconnected", phdc_mgr->sender); |
| /* Stop the associated p2p driver */ |
| near_p2p_unregister(phdc_mgr->p2p_driver); |
| |
| g_hash_table_remove(mgr_list, phdc_mgr->p2p_driver->service_name); |
| } |
| |
| /* |
| * Parse the data dictionary sent, to fill the phdc_mgr and p2p driver struct. |
| */ |
| static int parse_dictionary(DBusMessage *msg, void *data, |
| struct near_phdc_data *phdc_mgr, |
| struct near_p2p_driver *p2p) |
| { |
| DBusMessageIter array, dict; |
| int err; |
| |
| /* p2p should exist */ |
| if (!p2p) |
| return -EINVAL; |
| |
| if (dbus_message_iter_init(msg, &array) == FALSE) |
| return -EINVAL; |
| |
| if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_ARRAY) |
| return -EINVAL; |
| |
| dbus_message_iter_recurse(&array, &dict); |
| |
| err = -ENOMEM; |
| while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { |
| DBusMessageIter entry, value; |
| const char *key; |
| |
| dbus_message_iter_recurse(&dict, &entry); |
| dbus_message_iter_get_basic(&entry, &key); |
| |
| dbus_message_iter_next(&entry); |
| dbus_message_iter_recurse(&entry, &value); |
| |
| /* get p2p driver service name */ |
| if (g_str_equal(key, "ServiceName")) { |
| dbus_message_iter_get_basic(&value, &data); |
| g_free(p2p->service_name); |
| |
| p2p->service_name = g_strdup(data); |
| if (!p2p->service_name) |
| goto error; |
| } |
| |
| if (g_str_equal(key, "Path")) { |
| dbus_message_iter_get_basic(&value, &data); |
| g_free(phdc_mgr->path); |
| phdc_mgr->path = g_strdup(data); |
| if (!phdc_mgr->path) |
| goto error; |
| } else if (g_str_equal(key, "Role")) { |
| dbus_message_iter_get_basic(&value, &data); |
| /* Manager or Agent only */ |
| if (g_strcmp0(data, ROLE_MANAGER_TEXT) == 0) |
| phdc_mgr->role = ROLE_MANAGER; |
| else if (g_strcmp0(data, ROLE_AGENT_TEXT) == 0) |
| phdc_mgr->role = ROLE_AGENT; |
| else { |
| err = -EINVAL; |
| goto error; |
| } |
| } |
| |
| dbus_message_iter_next(&dict); |
| } |
| |
| return 0; |
| |
| error: |
| g_free(p2p->service_name); |
| p2p->service_name = NULL; |
| |
| |
| g_free(phdc_mgr->path); |
| phdc_mgr->path = NULL; |
| |
| return err; |
| } |
| |
| /* |
| * A Phdc Manager requests to be added to the manager list. |
| * - parse the parameters |
| * |
| * Initial version: the PHDC manager calls dbus_register_phdc_manager, |
| * sending a simple path and a service name |
| * TODO: check for DBUS_TYPE_UNIX_FD ((int) 'h') |
| */ |
| static DBusMessage *dbus_register_phdc_agent(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct near_phdc_data *phdc_mgr; |
| int err; |
| |
| DBG("conn %p", conn); |
| |
| /* Allocate the phdc_mgr struct */ |
| phdc_mgr = g_try_malloc0(sizeof(struct near_phdc_data)); |
| if (!phdc_mgr) { |
| err = -ENOMEM; |
| goto error; |
| } |
| |
| /* Allocate a default p2p_driver */ |
| phdc_mgr->p2p_driver = g_try_malloc0(sizeof(struct near_p2p_driver)); |
| if (!phdc_mgr->p2p_driver) { |
| err = -ENOMEM; |
| goto error; |
| } |
| |
| /* Get the the sender name */ |
| phdc_mgr->sender = g_strdup(dbus_message_get_sender(msg)); |
| |
| DBG("%s", phdc_mgr->sender); |
| |
| /* default p2p values */ |
| phdc_mgr->p2p_driver->fallback_service_name = NULL; |
| phdc_mgr->p2p_driver->sock_type = SOCK_STREAM; |
| phdc_mgr->p2p_driver->single_connection = FALSE; |
| phdc_mgr->p2p_driver->new_client = phdc_p2p_newclient; |
| phdc_mgr->p2p_driver->close = phdc_p2p_close; |
| |
| /* look for dict values and fill the struct */ |
| err = parse_dictionary(msg, data, phdc_mgr, phdc_mgr->p2p_driver); |
| if (err < 0) |
| goto error; |
| |
| /* TODO: At this time, there's no support for Role == Agent */ |
| if (phdc_mgr->role == ROLE_AGENT) { |
| err = -ENOTSUP; |
| goto error; |
| } |
| |
| /* No correct role ? */ |
| if (phdc_mgr->role == ROLE_UNKNOWN) { |
| err = -EINVAL; |
| goto error; |
| } |
| |
| /* No path ? */ |
| if (!phdc_mgr->path) { |
| err = -EINVAL; |
| goto error; |
| } |
| |
| /* defaulting the p2p driver */ |
| if (!phdc_mgr->p2p_driver->service_name) |
| phdc_mgr->p2p_driver->service_name = |
| g_strdup(DEFAULT_PHDC_SERVICE); |
| |
| /* p2p internal name */ |
| phdc_mgr->p2p_driver->name = g_strdup_printf("{%s-%s}", |
| (phdc_mgr->role == ROLE_MANAGER ? ROLE_MANAGER_TEXT : |
| ROLE_AGENT_TEXT), |
| phdc_mgr->p2p_driver->service_name); |
| |
| /* if one pointer is null, memory failed ! */ |
| if ((!phdc_mgr->p2p_driver->name) || |
| (!phdc_mgr->p2p_driver->service_name)) { |
| err = -ENOMEM; |
| goto error; |
| } |
| |
| /* Watch the Phdc Manager */ |
| phdc_mgr->watch = g_dbus_add_disconnect_watch(phdc_conn, |
| phdc_mgr->sender, |
| phdc_manager_disconnect, |
| phdc_mgr, NULL); |
| /* Add to the existing Manager list */ |
| err = manager_add_to_list(phdc_mgr); |
| if (err < 0) |
| goto error; |
| |
| /* and register the p2p driver for the specified service */ |
| err = near_p2p_register(phdc_mgr->p2p_driver); |
| if (err < 0) |
| goto error; |
| |
| return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); |
| |
| error: |
| /* free memory */ |
| if (phdc_mgr) |
| free_mgr_data(phdc_mgr); |
| |
| return error_failed(msg, -err); |
| } |
| |
| /* |
| * Phdc Manager requests to be removed from the existing list of managers. |
| */ |
| static DBusMessage *dbus_unregister_phdc_agent(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct near_phdc_data *mgr; |
| DBusMessageIter iter; |
| const char *path, *role, *sender; |
| |
| DBG("conn %p", conn); |
| |
| if (!dbus_message_iter_init(msg, &iter)) |
| return error_invalid_arguments(msg); |
| |
| if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH) |
| return error_invalid_arguments(msg); |
| |
| sender = dbus_message_get_sender(msg); |
| |
| dbus_message_iter_get_basic(&iter, &path); |
| dbus_message_iter_next(&iter); |
| |
| if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) |
| return error_invalid_arguments(msg); |
| |
| dbus_message_iter_get_basic(&iter, &role); |
| |
| /* look for specific path */ |
| mgr = search_mgr_list_by_path(path); |
| if (!mgr) |
| return error_not_found(msg); |
| |
| DBG("%s", mgr->sender); |
| |
| if (strncmp(sender, mgr->sender, strlen(mgr->sender))) |
| return error_permission_denied(msg); |
| |
| /* remove it */ |
| near_p2p_unregister(mgr->p2p_driver); |
| |
| g_hash_table_remove(mgr_list, mgr->p2p_driver->service_name); |
| |
| return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); |
| } |
| |
| static const GDBusMethodTable phdc_methods[] = { |
| { GDBUS_METHOD("RegisterAgent", |
| GDBUS_ARGS({"", "a{sv}"}), |
| NULL, dbus_register_phdc_agent) }, |
| |
| { GDBUS_METHOD("UnregisterAgent", |
| GDBUS_ARGS({ "path", "o" }, { "type", "s"}), |
| NULL, dbus_unregister_phdc_agent) }, |
| { }, |
| }; |
| |
| /* Initialize the PHDC plugin - Expose our dbus entry points */ |
| int phdc_init(void) |
| { |
| gboolean err; |
| |
| DBG(""); |
| |
| /* save the dbus connection */ |
| phdc_conn = near_dbus_get_connection(); |
| |
| mgr_list = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, |
| free_mgr_data); |
| |
| /* register dbus interface */ |
| err = g_dbus_register_interface(phdc_conn, "/org/neard", |
| NFC_NEARD_PHDC_IFACE, |
| phdc_methods, |
| NULL, NULL, NULL, NULL); |
| |
| return err; |
| } |
| |
| /* Called when exiting neard */ |
| void phdc_exit(void) |
| { |
| DBG(""); |
| |
| /* Notify listeners...*/ |
| g_hash_table_foreach(mgr_list, mgr_agent_release, NULL); |
| |
| g_dbus_unregister_interface(phdc_conn, "/org/neard", |
| NFC_NEARD_PHDC_IFACE); |
| /* Clean before leaving */ |
| g_hash_table_remove_all(mgr_list); |
| g_hash_table_destroy(mgr_list); |
| } |