text: /* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
   * Use of this source code is governed by a BSD-style license that can be
   * found in the LICENSE file.
   */
  
  #include <dbus/dbus.h>
  #include <errno.h>
  #include <stdlib.h>
  #include <string.h>
  #include <syslog.h>
  #include <unistd.h>
  
  #include "cras_bt_constants.h"
  #include "cras_bt_device.h"
  #include "cras_bt_profile.h"
  #include "cras_dbus_util.h"
  #include "utlist.h"
  
  #define PROFILE_INTROSPECT_XML                                                 \
  	DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                              \
  	"<node>\n"                                                             \
  	"  <interface name=\"org.bluez.Profile1\">\n"                          \
  	"    <method name=\"Release\">\n"                                      \
  	"    </method>\n"                                                      \
  	"    <method name=\"NewConnection\">\n"                                \
  	"      <arg name=\"device\" type=\"o\" direction=\"in\">\n"            \
  	"      <arg name=\"fd\" type=\"h\" direction=\"in\">\n"                \
  	"      <arg name=\"fd_properties\" type=\"a{sv}\" direction=\"in\">\n" \
  	"    </method>\n"                                                      \
  	"    <method name=\"RequestDisconnection\">\n"                         \
  	"      <arg name=\"device\" type=\"o\" direction=\"in\">\n"            \
  	"    </method>\n"                                                      \
  	"    <method name=\"Cancel\">\n"                                       \
  	"    </method>\n"                                                      \
  	"  <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n"           \
  	"    <method name=\"Introspect\">\n"                                   \
  	"      <arg name=\"data\" type=\"s\" direction=\"out\"/>\n"            \
  	"    </method>\n"                                                      \
  	"  </interface>\n"                                                     \
  	"</node>\n"
  
  /* Profiles */
  static struct cras_bt_profile *profiles;
  
  static DBusHandlerResult cras_bt_profile_handle_release(DBusConnection *conn,
  							DBusMessage *message,
  							void *arg)
  {
  	DBusMessage *reply;
  	const char *profile_path;
  	struct cras_bt_profile *profile;
  
  	profile_path = dbus_message_get_path(message);
  
  	profile = cras_bt_profile_get(profile_path);
  	if (!profile)
  		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  
  	syslog(LOG_ERR, "Profile %s released by bluetoothd", profile->name);
  	profile->release(profile);
  
  	reply = dbus_message_new_method_return(message);
  	if (!reply)
  		return DBUS_HANDLER_RESULT_NEED_MEMORY;
  	if (!dbus_connection_send(conn, reply, NULL)) {
  		dbus_message_unref(reply);
  		return DBUS_HANDLER_RESULT_NEED_MEMORY;
  	}
  
  	dbus_message_unref(reply);
  
  	return DBUS_HANDLER_RESULT_HANDLED;
  }
  
  static DBusHandlerResult
  cras_bt_profile_handle_new_connection(DBusConnection *conn,
  				      DBusMessage *message, void *arg)
  {
  	DBusMessageIter message_iter;
  	DBusMessage *reply;
  	const char *profile_path, *object_path;
  	int fd = -1;
  	int err;
  	struct cras_bt_profile *profile;
  	struct cras_bt_device *device;
  
  	profile_path = dbus_message_get_path(message);
  
  	dbus_message_iter_init(message, &message_iter);
  	dbus_message_iter_get_basic(&message_iter, &object_path);
  	dbus_message_iter_next(&message_iter);
  
  	if (dbus_message_iter_get_arg_type(&message_iter) !=
  	    DBUS_TYPE_UNIX_FD) {
  		syslog(LOG_ERR, "Argument not a valid unix file descriptor");
  		goto invalid;
  	}
  
  	dbus_message_iter_get_basic(&message_iter, &fd);
  	dbus_message_iter_next(&message_iter);
  	if (fd < 0)
  		goto invalid;
  
  	profile = cras_bt_profile_get(profile_path);
  	if (!profile)
  		goto invalid;
  
  	device = cras_bt_device_get(object_path);
  	if (!device) {
  		syslog(LOG_ERR, "Device %s not found at %s new connection",
  		       object_path, profile_path);
  		device = cras_bt_device_create(conn, object_path);
  	}
  
  	err = profile->new_connection(conn, profile, device, fd);
  	if (err) {
  		syslog(LOG_INFO, "%s new connection rejected", profile->name);
  		close(fd);
  		reply = dbus_message_new_error(
  			message, "org.chromium.Cras.Error.RejectNewConnection",
  			"Possibly another headset already in use");
  		if (!dbus_connection_send(conn, reply, NULL))
  			return DBUS_HANDLER_RESULT_NEED_MEMORY;
  
  		dbus_message_unref(reply);
  		return DBUS_HANDLER_RESULT_HANDLED;
  	}
  
  	reply = dbus_message_new_method_return(message);
  	if (!reply)
  		return DBUS_HANDLER_RESULT_NEED_MEMORY;
  	if (!dbus_connection_send(conn, reply, NULL)) {
  		dbus_message_unref(reply);
  		return DBUS_HANDLER_RESULT_NEED_MEMORY;
  	}
  
  	dbus_message_unref(reply);
  	return DBUS_HANDLER_RESULT_HANDLED;
  
  invalid:
  	if (fd >= 0)
  		close(fd);
  	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  }
  
  static DBusHandlerResult
  cras_bt_profile_handle_request_disconnection(DBusConnection *conn,
  					     DBusMessage *message, void *arg)
  {
  	DBusMessageIter message_iter;
  	DBusMessage *reply;
  	const char *prpofile_path, *object_path;
  	struct cras_bt_profile *profile;
  	struct cras_bt_device *device;
  
  	prpofile_path = dbus_message_get_path(message);
  
  	dbus_message_iter_init(message, &message_iter);
  	dbus_message_iter_get_basic(&message_iter, &object_path);
  	dbus_message_iter_next(&message_iter);
  
  	profile = cras_bt_profile_get(prpofile_path);
  	if (!profile)
  		goto invalid;
  
  	device = cras_bt_device_get(object_path);
  	if (!device)
  		goto invalid;
  
  	profile->request_disconnection(profile, device);
  
  	reply = dbus_message_new_method_return(message);
  	if (!reply)
  		return DBUS_HANDLER_RESULT_NEED_MEMORY;
  	if (!dbus_connection_send(conn, reply, NULL)) {
  		dbus_message_unref(reply);
  		return DBUS_HANDLER_RESULT_NEED_MEMORY;
  	}
  
  	dbus_message_unref(reply);
  
  	return DBUS_HANDLER_RESULT_HANDLED;
  
  invalid:
  	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  }
  
  static DBusHandlerResult cras_bt_profile_handle_cancel(DBusConnection *conn,
  						       DBusMessage *message,
  						       void *arg)
  {
  	DBusMessage *reply;
  	const char *profile_path;
  	struct cras_bt_profile *profile;
  
  	profile_path = dbus_message_get_path(message);
  
  	profile = cras_bt_profile_get(profile_path);
  	if (!profile)
  		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  
  	profile->cancel(profile);
  
  	reply = dbus_message_new_method_return(message);
  	if (!reply)
  		return DBUS_HANDLER_RESULT_NEED_MEMORY;
  	if (!dbus_connection_send(conn, reply, NULL)) {
  		dbus_message_unref(reply);
  		return DBUS_HANDLER_RESULT_NEED_MEMORY;
  	}
  
  	dbus_message_unref(reply);
  
  	return DBUS_HANDLER_RESULT_HANDLED;
  }
  
  static DBusHandlerResult cras_bt_handle_profile_messages(DBusConnection *conn,
  							 DBusMessage *message,
  							 void *arg)
  {
  	if (dbus_message_is_method_call(message, DBUS_INTERFACE_INTROSPECTABLE,
  					"Introspect")) {
  		DBusMessage *reply;
  		const char *xml = PROFILE_INTROSPECT_XML;
  
  		reply = dbus_message_new_method_return(message);
  		if (!reply)
  			return DBUS_HANDLER_RESULT_NEED_MEMORY;
  		if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &xml,
  					      DBUS_TYPE_INVALID)) {
  			dbus_message_unref(reply);
  			return DBUS_HANDLER_RESULT_NEED_MEMORY;
  		}
  		if (!dbus_connection_send(conn, reply, NULL)) {
  			dbus_message_unref(reply);
  			return DBUS_HANDLER_RESULT_NEED_MEMORY;
  		}
  
  		dbus_message_unref(reply);
  		return DBUS_HANDLER_RESULT_HANDLED;
  	} else if (dbus_message_is_method_call(message, BLUEZ_INTERFACE_PROFILE,
  					       "Release")) {
  		return cras_bt_profile_handle_release(conn, message, arg);
  	} else if (dbus_message_is_method_call(message, BLUEZ_INTERFACE_PROFILE,
  					       "NewConnection")) {
  		return cras_bt_profile_handle_new_connection(conn, message,
  							     arg);
  	} else if (dbus_message_is_method_call(message, BLUEZ_INTERFACE_PROFILE,
  					       "RequestDisconnection")) {
  		return cras_bt_profile_handle_request_disconnection(
  			conn, message, arg);
  	} else if (dbus_message_is_method_call(message, BLUEZ_INTERFACE_PROFILE,
  					       "Cancel")) {
  		return cras_bt_profile_handle_cancel(conn, message, arg);
  	} else {
  		syslog(LOG_ERR, "Unknown Profile message");
  	}
  
  	return DBUS_HANDLER_RESULT_HANDLED;
  }
  
  static void cras_bt_on_register_profile(DBusPendingCall *pending_call,
  					void *data)
  {
  	DBusMessage *reply;
  
  	reply = dbus_pending_call_steal_reply(pending_call);
  	dbus_pending_call_unref(pending_call);
  
  	if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR)
  		syslog(LOG_ERR, "RegisterProfile returned error: %s",
  		       dbus_message_get_error_name(reply));
  	dbus_message_unref(reply);
  }
  
  int cras_bt_register_profile(DBusConnection *conn,
  			     struct cras_bt_profile *profile)
  {
  	DBusMessage *method_call;
  	DBusMessageIter message_iter;
  	DBusMessageIter properties_array_iter;
  	DBusPendingCall *pending_call;
  
  	method_call = dbus_message_new_method_call(BLUEZ_SERVICE,
  						   PROFILE_MANAGER_OBJ_PATH,
  						   BLUEZ_PROFILE_MGMT_INTERFACE,
  						   "RegisterProfile");
  
  	if (!method_call)
  		return -ENOMEM;
  
  	dbus_message_iter_init_append(method_call, &message_iter);
  	dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_OBJECT_PATH,
  				       &profile->object_path);
  	dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_STRING,
  				       &profile->uuid);
  
  	dbus_message_iter_open_container(
  		&message_iter, DBUS_TYPE_ARRAY,
  		DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
  			DBUS_TYPE_VARIANT_AS_STRING
  				DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
  		&properties_array_iter);
  
  	if (!append_key_value(&properties_array_iter, "Name", DBUS_TYPE_STRING,
  			      DBUS_TYPE_STRING_AS_STRING, &profile->name)) {
  		dbus_message_unref(method_call);
  		return -ENOMEM;
  	}
  
  	if (profile->record &&
  	    !append_key_value(&properties_array_iter, "ServiceRecord",
  			      DBUS_TYPE_STRING, DBUS_TYPE_STRING_AS_STRING,
  			      &profile->record)) {
  		dbus_message_unref(method_call);
  		return -ENOMEM;
  	}
  
  	if (!append_key_value(&properties_array_iter, "Version",
  			      DBUS_TYPE_UINT16, DBUS_TYPE_UINT16_AS_STRING,
  			      &profile->version)) {
  		dbus_message_unref(method_call);
  		return -ENOMEM;
  	}
  
  	if (profile->role &&
  	    !append_key_value(&properties_array_iter, "Role", DBUS_TYPE_STRING,
  			      DBUS_TYPE_STRING_AS_STRING, &profile->role)) {
  		dbus_message_unref(method_call);
  		return -ENOMEM;
  	}
  
  	if (profile->features &&
  	    !append_key_value(&properties_array_iter, "Features",
  			      DBUS_TYPE_UINT16, DBUS_TYPE_UINT16_AS_STRING,
  			      &profile->features)) {
  		dbus_message_unref(method_call);
  		return -ENOMEM;
  	}
  
  	dbus_message_iter_close_container(&message_iter,
  					  &properties_array_iter);
  
  	if (!dbus_connection_send_with_reply(conn, method_call, &pending_call,
  					     DBUS_TIMEOUT_USE_DEFAULT)) {
  		dbus_message_unref(method_call);
  		return -ENOMEM;
  	}
  
  	dbus_message_unref(method_call);
  	if (!pending_call)
  		return -EIO;
  
  	if (!dbus_pending_call_set_notify(
  		    pending_call, cras_bt_on_register_profile, NULL, NULL)) {
  		dbus_pending_call_cancel(pending_call);
  		dbus_pending_call_unref(pending_call);
  		syslog(LOG_ERR, "register profile fail on set notify");
  		return -ENOMEM;
  	}
  
  	return 0;
  }
  
  int cras_bt_register_profiles(DBusConnection *conn)
  {
  	struct cras_bt_profile *profile;
  	int err;
  
  	DL_FOREACH (profiles, profile) {
  		err = cras_bt_register_profile(conn, profile);
  		if (err)
  			return err;
  	}
  
  	return 0;
  }
  
  int cras_bt_add_profile(DBusConnection *conn, struct cras_bt_profile *profile)
  {
  	static const DBusObjectPathVTable profile_vtable = {
  		NULL, cras_bt_handle_profile_messages, NULL, NULL, NULL, NULL
  	};
  
  	DBusError dbus_error;
  
  	dbus_error_init(&dbus_error);
  
  	if (!dbus_connection_register_object_path(
  		    conn, profile->object_path, &profile_vtable, &dbus_error)) {
  		syslog(LOG_ERR, "Could not register BT profile %s: %s",
  		       profile->object_path, dbus_error.message);
  		dbus_error_free(&dbus_error);
  		return -ENOMEM;
  	}
  
  	DL_APPEND(profiles, profile);
  
  	return 0;
  }
  
  void cras_bt_profile_reset()
  {
  	struct cras_bt_profile *profile;
  
  	DL_FOREACH (profiles, profile)
  		profile->release(profile);
  }
  
  struct cras_bt_profile *cras_bt_profile_get(const char *path)
  {
  	struct cras_bt_profile *profile;
  	DL_FOREACH (profiles, profile) {
  		if (strcmp(profile->object_path, path) == 0)
  			return profile;
  	}
  
  	return NULL;
  }
  
  void cras_bt_profile_on_device_disconnected(struct cras_bt_device *device)
  {
  	struct cras_bt_profile *profile;
  	DL_FOREACH (profiles, profile)
  		profile->request_disconnection(profile, device);
  }
  