diff options
author | Johan Hedberg <johan.hedberg@nokia.com> | 2006-11-16 15:03:00 +0000 |
---|---|---|
committer | Johan Hedberg <johan.hedberg@nokia.com> | 2006-11-16 15:03:00 +0000 |
commit | 6dd50a28822ea1f2cc577909758eef8927466fb7 (patch) | |
tree | 6ba40fcf760b51f89979882e39043d4e390b9aac /audio | |
parent | 65bb8c216b23e4ca01ecdaa24ffd2ae462b80da8 (diff) |
Implement listening rfcomm socket support
Diffstat (limited to 'audio')
-rw-r--r-- | audio/headset.c | 319 |
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); |