summaryrefslogtreecommitdiffstats
path: root/audio/headset.c
diff options
context:
space:
mode:
authorJohan Hedberg <johan.hedberg@nokia.com>2006-11-16 15:03:00 +0000
committerJohan Hedberg <johan.hedberg@nokia.com>2006-11-16 15:03:00 +0000
commit6dd50a28822ea1f2cc577909758eef8927466fb7 (patch)
tree6ba40fcf760b51f89979882e39043d4e390b9aac /audio/headset.c
parent65bb8c216b23e4ca01ecdaa24ffd2ae462b80da8 (diff)
Implement listening rfcomm socket support
Diffstat (limited to 'audio/headset.c')
-rw-r--r--audio/headset.c319
1 files changed, 317 insertions, 2 deletions
diff --git a/audio/headset.c b/audio/headset.c
index 25e3c0fb..adf4194a 100644
--- a/audio/headset.c
+++ b/audio/headset.c
@@ -51,6 +51,7 @@
#include "glib-ectomy.c"
#define HEADSET_PATH "/org/bluez/headset"
+static const char *hs_path = HEADSET_PATH;
struct pending_connect {
bdaddr_t bda;
@@ -66,6 +67,12 @@ struct hs_connection {
GIOChannel *sco;
};
+static gboolean connect_in_progress = FALSE;
+
+static uint8_t config_channel = 0;
+
+static uint32_t record_id = 0;
+
static char *on_init_bda = NULL;
static int started = 0;
@@ -76,6 +83,8 @@ static GMainLoop *main_loop = NULL;
static struct hs_connection *connected_hs = NULL;
+static GIOChannel *server_sk = NULL;
+
static DBusHandlerResult hs_connect(DBusConnection *conn, DBusMessage *msg,
const char *address);
static DBusHandlerResult hs_disconnect(DBusConnection *conn, DBusMessage *msg);
@@ -117,6 +126,8 @@ static void pending_connect_free(struct pending_connect *c, gboolean unref_io)
if (c->conn)
dbus_connection_unref(c->conn);
free(c);
+
+ connect_in_progress = FALSE;
}
static DBusHandlerResult error_reply(DBusConnection *conn, DBusMessage *msg,
@@ -202,6 +213,69 @@ failed:
return FALSE;
}
+static gboolean server_io_cb(GIOChannel *chan, GIOCondition cond, void *data)
+{
+ int srv_sk, cli_sk;
+ struct sockaddr_rc addr;
+ socklen_t size;
+
+ if (cond & G_IO_NVAL) {
+ g_io_channel_unref(chan);
+ return FALSE;
+ }
+
+ if (cond & (G_IO_HUP | G_IO_ERR)) {
+ error("Hangup or error on rfcomm server socket");
+ g_io_channel_close(chan);
+ server_sk = NULL;
+ return TRUE;
+ }
+
+ srv_sk = g_io_channel_unix_get_fd(chan);
+
+ size = sizeof(struct sockaddr_rc);
+ cli_sk = accept(srv_sk, (struct sockaddr *) &addr, &size);
+ if (cli_sk < 0) {
+ error("accept: %s (%d)", strerror(errno), errno);
+ return TRUE;
+ }
+
+ if (connected_hs || connect_in_progress) {
+ debug("Refusing new connection since one already exists");
+ close(cli_sk);
+ return TRUE;
+ }
+
+ connected_hs = malloc(sizeof(struct hs_connection));
+ if (!connected_hs) {
+ error("Allocating new hs connection struct failed!");
+ close(cli_sk);
+ return TRUE;
+ }
+
+ memset(connected_hs, 0, sizeof(struct hs_connection));
+
+ connected_hs->rfcomm = g_io_channel_unix_new(cli_sk);
+ if (!connected_hs->rfcomm) {
+ error("Allocating new GIOChannel failed!");
+ close(cli_sk);
+ free(connected_hs);
+ connected_hs = NULL;
+ return TRUE;
+ }
+
+ ba2str(&addr.rc_bdaddr, connected_hs->address);
+
+ debug("rfcomm_connect_cb: connected to %s", connected_hs->address);
+
+ g_io_add_watch(connected_hs->rfcomm,
+ G_IO_ERR | G_IO_HUP | G_IO_IN | G_IO_NVAL,
+ (GIOFunc) rfcomm_io_cb, connected_hs);
+
+ return TRUE;
+}
+
+
static gboolean rfcomm_connect_cb(GIOChannel *chan, GIOCondition cond, struct pending_connect *c)
{
int sk, ret, err;
@@ -328,6 +402,217 @@ static void sig_term(int sig)
g_main_quit(main_loop);
}
+static int server_socket(uint8_t *channel)
+{
+ int sock;
+ struct sockaddr_rc addr;
+ socklen_t sa_len;
+
+ sock = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+ if (sock < 0) {
+ error("server socket: %s (%d)", strerror(errno), errno);
+ return -1;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.rc_family = AF_BLUETOOTH;
+ bacpy(&addr.rc_bdaddr, BDADDR_ANY);
+ addr.rc_channel = 0;
+
+ if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ error("server bind: %s", strerror(errno), errno);
+ close(sock);
+ return -1;
+ }
+
+ if (listen(sock, 1) < 0) {
+ error("server listen: %s", strerror(errno), errno);
+ close(sock);
+ return -1;
+ }
+
+ sa_len = sizeof(struct sockaddr_rc);
+ getsockname(sock, (struct sockaddr *) &addr, &sa_len);
+ *channel = addr.rc_channel;
+
+ return sock;
+}
+
+static int create_ag_record(sdp_buf_t *buf, uint8_t ch)
+{
+ sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+ uuid_t root_uuid, svclass_uuid, ga_svclass_uuid, l2cap_uuid, rfcomm_uuid;
+ sdp_profile_desc_t profile;
+ sdp_list_t *aproto, *proto[2];
+ sdp_record_t record;
+ sdp_data_t *channel;
+ int ret;
+
+ memset(&record, 0, sizeof(sdp_record_t));
+
+ sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+ root = sdp_list_append(0, &root_uuid);
+ sdp_set_browse_groups(&record, root);
+
+ sdp_uuid16_create(&svclass_uuid, HEADSET_SVCLASS_ID);
+ svclass_id = sdp_list_append(0, &svclass_uuid);
+ sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID);
+ svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid);
+ sdp_set_service_classes(&record, svclass_id);
+
+ sdp_uuid16_create(&profile.uuid, HEADSET_PROFILE_ID);
+ profile.version = 0x0100;
+ pfseq = sdp_list_append(0, &profile);
+ sdp_set_profile_descs(&record, pfseq);
+
+ sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+ proto[0] = sdp_list_append(0, &l2cap_uuid);
+ apseq = sdp_list_append(0, proto[0]);
+
+ sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
+ proto[1] = sdp_list_append(0, &rfcomm_uuid);
+ channel = sdp_data_alloc(SDP_UINT8, &ch);
+ proto[1] = sdp_list_append(proto[1], channel);
+ apseq = sdp_list_append(apseq, proto[1]);
+
+ aproto = sdp_list_append(0, apseq);
+ sdp_set_access_protos(&record, aproto);
+
+ sdp_set_info_attr(&record, "Headset", 0, 0);
+
+ if (sdp_gen_record_pdu(&record, buf) < 0)
+ ret = -1;
+ else
+ ret = 0;
+
+ sdp_data_free(channel);
+ sdp_list_free(proto[0], 0);
+ sdp_list_free(proto[1], 0);
+ sdp_list_free(apseq, 0);
+ sdp_list_free(pfseq, 0);
+ sdp_list_free(aproto, 0);
+ sdp_list_free(root, 0);
+ sdp_list_free(svclass_id, 0);
+ sdp_list_free(record.attrlist, (sdp_free_func_t) sdp_data_free);
+ sdp_list_free(record.pattern, free);
+
+ return ret;
+}
+
+static uint32_t add_ag_record(uint8_t channel)
+{
+ DBusMessage *msg, *reply;
+ DBusError derr;
+ dbus_uint32_t rec_id;
+ sdp_buf_t buf;
+
+ msg = dbus_message_new_method_call("org.bluez", "/org/bluez",
+ "org.bluez.Manager", "AddServiceRecord");
+ if (!msg) {
+ error("Can't allocate new method call");
+ return 0;
+ }
+
+ if (create_ag_record(&buf, channel) < 0) {
+ error("Unable to allocate new service record");
+ dbus_message_unref(msg);
+ return 0;
+ }
+
+ dbus_message_append_args(msg, DBUS_TYPE_STRING, &hs_path,
+ DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &buf.data, buf.data_size,
+ DBUS_TYPE_INVALID);
+
+ dbus_error_init(&derr);
+ reply = dbus_connection_send_with_reply_and_block(connection, msg, -1, &derr);
+
+ free(buf.data);
+ dbus_message_unref(msg);
+
+ if (dbus_error_is_set(&derr) || dbus_set_error_from_message(&derr, reply)) {
+ error("Adding service record failed: %s", derr.message);
+ dbus_error_free(&derr);
+ return 0;
+ }
+
+ dbus_message_get_args(reply, &derr, DBUS_TYPE_UINT32, &rec_id,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_error_is_set(&derr)) {
+ error("Invalid arguments to AddServiceRecord reply: %s", derr.message);
+ dbus_message_unref(reply);
+ dbus_error_free(&derr);
+ return 0;
+ }
+
+ dbus_message_unref(reply);
+
+ return rec_id;
+}
+
+static int remove_ag_record(uint32_t rec_id)
+{
+ DBusMessage *msg, *reply;
+ DBusError derr;
+
+ msg = dbus_message_new_method_call("org.bluez", "/org/bluez",
+ "org.bluez.Manager", "RemoveServiceRecord");
+ if (!msg) {
+ error("Can't allocate new method call");
+ return 0;
+ }
+
+ dbus_message_append_args(msg, DBUS_TYPE_STRING, &hs_path,
+ DBUS_TYPE_UINT32, &rec_id,
+ DBUS_TYPE_INVALID);
+
+ dbus_error_init(&derr);
+ reply = dbus_connection_send_with_reply_and_block(connection, msg, -1, &derr);
+
+ dbus_message_unref(msg);
+
+ if (dbus_error_is_set(&derr)) {
+ error("Removing service record failed: %s", derr.message);
+ dbus_error_free(&derr);
+ return 0;
+ }
+
+ dbus_message_unref(reply);
+
+ return 0;
+}
+
+static void create_server_socket(void)
+{
+ uint8_t chan = config_channel;
+ int srv_sk;
+
+ srv_sk = server_socket(&chan);
+ if (srv_sk < 0) {
+ error("Unable to create server socket");
+ return;
+ }
+
+ record_id = add_ag_record(chan);
+
+ if (!record_id) {
+ error("Unable to register service record");
+ close(srv_sk);
+ return;
+ }
+
+ server_sk = g_io_channel_unix_new(srv_sk);
+ if (!server_sk) {
+ error("Unable to allocate new GIOChannel");
+ remove_ag_record(record_id);
+ return;
+ }
+
+ g_io_add_watch(server_sk, G_IO_IN | G_IO_ERR | G_IO_HUP,
+ (GIOFunc) server_io_cb, NULL);
+}
+
+
static DBusHandlerResult start_message(DBusConnection *conn,
DBusMessage *msg, void *data)
{
@@ -341,6 +626,9 @@ static DBusHandlerResult start_message(DBusConnection *conn,
return DBUS_HANDLER_RESULT_NEED_MEMORY;
}
+ if (!record_id)
+ create_server_socket();
+
dbus_connection_send(conn, reply, NULL);
dbus_message_unref(reply);
@@ -367,6 +655,25 @@ static DBusHandlerResult stop_message(DBusConnection *conn,
dbus_message_unref(reply);
+ if (connected_hs) {
+ if (connected_hs->sco)
+ g_io_channel_close(connected_hs->sco);
+ if (connected_hs->rfcomm)
+ g_io_channel_close(connected_hs->rfcomm);
+ free(connected_hs);
+ connected_hs = NULL;
+ }
+
+ if (!config_channel) {
+ remove_ag_record(record_id);
+ record_id = 0;
+ }
+
+ if (server_sk) {
+ g_io_channel_close(server_sk);
+ server_sk = NULL;
+ }
+
started = 0;
return DBUS_HANDLER_RESULT_HANDLED;
@@ -448,6 +755,9 @@ static void register_reply(DBusPendingCall *call, void *data)
dbus_message_unref(reply);
+ if (config_channel)
+ record_id = add_ag_record(config_channel);
+
if (on_init_bda)
hs_connect(NULL, NULL, on_init_bda);
}
@@ -458,7 +768,6 @@ int headset_dbus_init(char *bda)
DBusPendingCall *pending;
const char *name = "Headset service";
const char *description = "A service for headsets";
- const char *hs_path = HEADSET_PATH;
connection = init_dbus(NULL, NULL, NULL);
if (!connection)
@@ -717,6 +1026,8 @@ static DBusHandlerResult hs_connect(DBusConnection *conn, DBusMessage *msg,
return DBUS_HANDLER_RESULT_NEED_MEMORY;
}
+ connect_in_progress = TRUE;
+
memset(c, 0, sizeof(struct pending_connect));
msg = dbus_message_new_method_call("org.bluez", "/org/bluez/hci0",
@@ -750,12 +1061,16 @@ int main(int argc, char *argv[])
struct sigaction sa;
int opt, daemonize = 1;
- while ((opt = getopt(argc, argv, "n")) != EOF) {
+ while ((opt = getopt(argc, argv, "nc:")) != EOF) {
switch (opt) {
case 'n':
daemonize = 0;
break;
+ case 'c':
+ config_channel = strtol(optarg, NULL, 0);
+ break;
+
default:
printf("Usage: %s [-n] [bdaddr]\n", argv[0]);
exit(1);