/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2007 Nokia Corporation * Copyright (C) 2004-2008 Marcel Holtmann * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "logging.h" #include "adapter.h" #include "device.h" #include "manager.h" #include "control.h" #include "avdtp.h" #include "glib-helper.h" #include "sink.h" #include #define AVDTP_PSM 25 #define MAX_SEID 0x3E #define AVDTP_DISCOVER 0x01 #define AVDTP_GET_CAPABILITIES 0x02 #define AVDTP_SET_CONFIGURATION 0x03 #define AVDTP_GET_CONFIGURATION 0x04 #define AVDTP_RECONFIGURE 0x05 #define AVDTP_OPEN 0x06 #define AVDTP_START 0x07 #define AVDTP_CLOSE 0x08 #define AVDTP_SUSPEND 0x09 #define AVDTP_ABORT 0x0A #define AVDTP_SECURITY_CONTROL 0x0B #define AVDTP_PKT_TYPE_SINGLE 0x00 #define AVDTP_PKT_TYPE_START 0x01 #define AVDTP_PKT_TYPE_CONTINUE 0x02 #define AVDTP_PKT_TYPE_END 0x03 #define AVDTP_MSG_TYPE_COMMAND 0x00 #define AVDTP_MSG_TYPE_ACCEPT 0x02 #define AVDTP_MSG_TYPE_REJECT 0x03 #define REQ_TIMEOUT 4000 #define DISCONNECT_TIMEOUT 5000 #define STREAM_TIMEOUT 20000 typedef enum { AVDTP_SESSION_STATE_DISCONNECTED, AVDTP_SESSION_STATE_CONNECTING, AVDTP_SESSION_STATE_CONNECTED } avdtp_session_state_t; #if __BYTE_ORDER == __LITTLE_ENDIAN struct avdtp_header { uint8_t message_type:2; uint8_t packet_type:2; uint8_t transaction:4; uint8_t signal_id:6; uint8_t rfa0:2; } __attribute__ ((packed)); struct seid_info { uint8_t rfa0:1; uint8_t inuse:1; uint8_t seid:6; uint8_t rfa2:3; uint8_t type:1; uint8_t media_type:4; } __attribute__ ((packed)); struct seid { uint8_t rfa0:2; uint8_t seid:6; } __attribute__ ((packed)); #elif __BYTE_ORDER == __BIG_ENDIAN struct avdtp_header { uint8_t transaction:4; uint8_t packet_type:2; uint8_t message_type:2; uint8_t rfa0:2; uint8_t signal_id:6; } __attribute__ ((packed)); struct seid_info { uint8_t seid:6; uint8_t inuse:1; uint8_t rfa0:1; uint8_t media_type:4; uint8_t type:1; uint8_t rfa2:3; } __attribute__ ((packed)); struct seid { uint8_t seid:6; uint8_t rfa0:2; } __attribute__ ((packed)); #else #error "Unknown byte order" #endif /* packets */ struct gen_req { struct avdtp_header header; } __attribute__ ((packed)); struct gen_resp { struct avdtp_header header; } __attribute__ ((packed)); struct discover_resp { struct avdtp_header header; struct seid_info seps[0]; } __attribute__ ((packed)); struct getcap_resp { struct avdtp_header header; uint8_t caps[0]; } __attribute__ ((packed)); struct start_req { struct avdtp_header header; struct seid first_seid; struct seid other_seids[0]; } __attribute__ ((packed)); struct suspend_req { struct avdtp_header header; struct seid first_seid; struct seid other_seids[0]; } __attribute__ ((packed)); struct seid_rej { struct avdtp_header header; uint8_t error; } __attribute__ ((packed)); struct conf_rej { struct avdtp_header header; uint8_t category; uint8_t error; } __attribute__ ((packed)); #if __BYTE_ORDER == __LITTLE_ENDIAN struct seid_req { struct avdtp_header header; uint8_t rfa0:2; uint8_t acp_seid:6; } __attribute__ ((packed)); struct setconf_req { struct avdtp_header header; uint8_t rfa0:2; uint8_t acp_seid:6; uint8_t rfa1:2; uint8_t int_seid:6; uint8_t caps[0]; } __attribute__ ((packed)); struct stream_rej { struct avdtp_header header; uint8_t rfa0:2; uint8_t acp_seid:6; uint8_t error; } __attribute__ ((packed)); struct reconf_req { struct avdtp_header header; uint8_t rfa0:2; uint8_t acp_seid:6; uint8_t serv_cap; uint8_t serv_cap_len; uint8_t caps[0]; } __attribute__ ((packed)); struct avdtp_general_rej { uint8_t message_type:2; uint8_t packet_type:2; uint8_t transaction:4; uint8_t rfa0; } __attribute__ ((packed)); #elif __BYTE_ORDER == __BIG_ENDIAN struct seid_req { struct avdtp_header header; uint8_t acp_seid:6; uint8_t rfa0:2; } __attribute__ ((packed)); struct setconf_req { struct avdtp_header header; uint8_t acp_seid:6; uint8_t rfa0:2; uint8_t int_seid:6; uint8_t rfa1:2; uint8_t caps[0]; } __attribute__ ((packed)); struct stream_rej { struct avdtp_header header; uint8_t acp_seid:6; uint8_t rfa0:2; uint8_t error; } __attribute__ ((packed)); struct reconf_req { struct avdtp_header header; uint8_t acp_seid:6; uint8_t rfa0:2; uint8_t serv_cap; uint8_t serv_cap_len; uint8_t caps[0]; } __attribute__ ((packed)); struct avdtp_general_rej { uint8_t transaction:4; uint8_t packet_type:2; uint8_t message_type:2; uint8_t rfa0; } __attribute__ ((packed)); #else #error "Unknown byte order" #endif struct pending_req { struct avdtp_header *msg; int msg_size; struct avdtp_stream *stream; /* Set if the request targeted a stream */ guint timeout; }; struct avdtp_remote_sep { uint8_t seid; uint8_t type; uint8_t media_type; struct avdtp_service_capability *codec; GSList *caps; /* of type struct avdtp_service_capability */ struct avdtp_stream *stream; }; struct avdtp_server { bdaddr_t src; GIOChannel *io; GSList *seps; }; struct avdtp_local_sep { avdtp_state_t state; struct avdtp_stream *stream; struct seid_info info; uint8_t codec; GSList *caps; struct avdtp_sep_ind *ind; struct avdtp_sep_cfm *cfm; void *user_data; struct avdtp_server *server; }; struct stream_callback { avdtp_stream_state_cb cb; void *user_data; unsigned int id; }; struct avdtp_stream { int sock; uint16_t imtu; uint16_t omtu; struct avdtp *session; struct avdtp_local_sep *lsep; uint8_t rseid; GSList *caps; GSList *callbacks; struct avdtp_service_capability *codec; guint io; /* Transport GSource ID */ guint timer; /* Waiting for other side to close or open the transport channel */ gboolean open_acp; /* If we are in ACT role for Open */ gboolean close_int; /* If we are in INT role for Close */ guint idle_timer; }; /* Structure describing an AVDTP connection between two devices */ struct avdtp { int ref; int free_lock; struct avdtp_server *server; bdaddr_t dst; avdtp_session_state_t last_state; avdtp_session_state_t state; guint io; int sock; GSList *seps; /* Elements of type struct avdtp_remote_sep * */ GSList *streams; /* Elements of type struct avdtp_stream * */ GSList *req_queue; /* Elements of type struct pending_req * */ GSList *prio_queue; /* Same as req_queue but is processed before it */ struct avdtp_stream *pending_open; uint16_t mtu; char *buf; avdtp_discover_cb_t discov_cb; void *user_data; struct pending_req *req; guint dc_timer; DBusPendingCall *pending_auth; }; static GSList *servers = NULL; static GSList *sessions = NULL; static int send_request(struct avdtp *session, gboolean priority, struct avdtp_stream *stream, void *buffer, int size); static gboolean avdtp_parse_resp(struct avdtp *session, struct avdtp_stream *stream, struct avdtp_header *header, int size); static gboolean avdtp_parse_rej(struct avdtp *session, struct avdtp_stream *stream, struct avdtp_header *header, int size); static int process_queue(struct avdtp *session); static void connection_lost(struct avdtp *session, int err); static void avdtp_sep_set_state(struct avdtp *session, struct avdtp_local_sep *sep, avdtp_state_t state); static struct avdtp_server *find_server(GSList *list, const bdaddr_t *src) { GSList *l; for (l = list; l; l = l->next) { struct avdtp_server *server = l->data; if (bacmp(&server->src, src) == 0) return server; } return NULL; } static const char *avdtp_statestr(avdtp_state_t state) { switch (state) { case AVDTP_STATE_IDLE: return "IDLE"; case AVDTP_STATE_CONFIGURED: return "CONFIGURED"; case AVDTP_STATE_OPEN: return "OPEN"; case AVDTP_STATE_STREAMING: return "STREAMING"; case AVDTP_STATE_CLOSING: return "CLOSING"; case AVDTP_STATE_ABORTING: return "ABORTING"; default: return ""; } } static gboolean avdtp_send(struct avdtp *session, void *data, int len) { int ret; if (session->sock < 0) { error("avdtp_send: session is closed"); return FALSE; } ret = send(session->sock, data, len, 0); if (ret < 0) ret = -errno; else if (ret != len) ret = -EIO; if (ret < 0) { error("avdtp_send: %s (%d)", strerror(-ret), -ret); return FALSE; } return TRUE; } static void pending_req_free(struct pending_req *req) { if (req->timeout) g_source_remove(req->timeout); g_free(req->msg); g_free(req); } static gboolean stream_close_timeout(gpointer user_data) { struct avdtp_stream *stream = user_data; debug("Timed out waiting for peer to close the transport channel"); stream->timer = 0; close(stream->sock); return FALSE; } static gboolean stream_open_timeout(gpointer user_data) { struct avdtp_stream *stream = user_data; debug("Timed out waiting for peer to open the transport channel"); stream->timer = 0; stream->session->pending_open = NULL; avdtp_abort(stream->session, stream); return FALSE; } static gboolean disconnect_timeout(gpointer user_data) { struct avdtp *session = user_data; assert(session->ref == 1); session->dc_timer = 0; connection_lost(session, -ETIMEDOUT); return FALSE; } static void remove_disconnect_timer(struct avdtp *session) { g_source_remove(session->dc_timer); session->dc_timer = 0; } static void set_disconnect_timer(struct avdtp *session) { if (session->dc_timer) remove_disconnect_timer(session); session->dc_timer = g_timeout_add(DISCONNECT_TIMEOUT, disconnect_timeout, session); } void avdtp_error_init(struct avdtp_error *err, uint8_t type, int id) { err->type = type; switch (type) { case AVDTP_ERROR_ERRNO: err->err.posix_errno = id; break; case AVDTP_ERROR_ERROR_CODE: err->err.error_code = id; break; } } avdtp_error_type_t avdtp_error_type(struct avdtp_error *err) { return err->type; } int avdtp_error_error_code(struct avdtp_error *err) { assert(err->type == AVDTP_ERROR_ERROR_CODE); return err->err.error_code; } int avdtp_error_posix_errno(struct avdtp_error *err) { assert(err->type == AVDTP_ERROR_ERRNO); return err->err.posix_errno; } static struct avdtp_stream *find_stream_by_rseid(struct avdtp *session, uint8_t rseid) { GSList *l; for (l = session->streams; l != NULL; l = g_slist_next(l)) { struct avdtp_stream *stream = l->data; if (stream->rseid == rseid) return stream; } return NULL; } static struct avdtp_remote_sep *find_remote_sep(GSList *seps, uint8_t seid) { GSList *l; for (l = seps; l != NULL; l = g_slist_next(l)) { struct avdtp_remote_sep *sep = l->data; if (sep->seid == seid) return sep; } return NULL; } static void stream_free(struct avdtp_stream *stream) { struct avdtp_remote_sep *rsep; stream->lsep->info.inuse = 0; stream->lsep->stream = NULL; rsep = find_remote_sep(stream->session->seps, stream->rseid); if (rsep) rsep->stream = NULL; if (stream->timer) g_source_remove(stream->timer); if (stream->sock >= 0) close(stream->sock); if (stream->io) g_source_remove(stream->io); g_slist_foreach(stream->callbacks, (GFunc) g_free, NULL); g_slist_free(stream->callbacks); g_slist_foreach(stream->caps, (GFunc) g_free, NULL); g_slist_free(stream->caps); g_free(stream); } static gboolean stream_timeout(struct avdtp_stream *stream) { struct avdtp *session = stream->session; avdtp_close(session, stream); stream->idle_timer = 0; return FALSE; } static gboolean transport_cb(GIOChannel *chan, GIOCondition cond, gpointer data) { struct avdtp_stream *stream = data; struct avdtp_local_sep *sep = stream->lsep; if (stream->close_int && sep->cfm && sep->cfm->close) sep->cfm->close(stream->session, sep, stream, NULL, sep->user_data); stream->io = 0; avdtp_sep_set_state(stream->session, sep, AVDTP_STATE_IDLE); return FALSE; } static void handle_transport_connect(struct avdtp *session, int sock, uint16_t imtu, uint16_t omtu) { struct avdtp_stream *stream = session->pending_open; struct avdtp_local_sep *sep = stream->lsep; GIOChannel *channel; session->pending_open = NULL; if (stream->timer) { g_source_remove(stream->timer); stream->timer = 0; } if (sock < 0) { if (!stream->open_acp && sep->cfm && sep->cfm->open) { struct avdtp_error err; avdtp_error_init(&err, AVDTP_ERROR_ERRNO, EIO); sep->cfm->open(session, sep, NULL, &err, sep->user_data); } return; } stream->sock = sock; stream->omtu = omtu; stream->imtu = imtu; if (!stream->open_acp && sep->cfm && sep->cfm->open) sep->cfm->open(session, sep, stream, NULL, sep->user_data); avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN); channel = g_io_channel_unix_new(stream->sock); stream->io = g_io_add_watch(channel, G_IO_ERR | G_IO_HUP | G_IO_NVAL, (GIOFunc) transport_cb, stream); g_io_channel_unref(channel); } static void avdtp_sep_set_state(struct avdtp *session, struct avdtp_local_sep *sep, avdtp_state_t state) { struct avdtp_stream *stream = sep->stream; avdtp_state_t old_state; struct avdtp_error err, *err_ptr = NULL; if (sep->state == state) { avdtp_error_init(&err, AVDTP_ERROR_ERRNO, EIO); debug("stream state change failed: %s", avdtp_strerror(&err)); err_ptr = &err; } else { err_ptr = NULL; debug("stream state changed: %s -> %s", avdtp_statestr(sep->state), avdtp_statestr(state)); } old_state = sep->state; sep->state = state; if (stream) { GSList *l; for (l = stream->callbacks; l != NULL; l = g_slist_next(l)) { struct stream_callback *cb = l->data; cb->cb(stream, old_state, state, err_ptr, cb->user_data); } } switch (state) { case AVDTP_STATE_OPEN: break; case AVDTP_STATE_STREAMING: case AVDTP_STATE_CLOSING: case AVDTP_STATE_ABORTING: if (stream->idle_timer) { g_source_remove(stream->idle_timer); stream->idle_timer = 0; } break; case AVDTP_STATE_IDLE: if (stream->idle_timer) { g_source_remove(stream->idle_timer); stream->idle_timer = 0; } session->streams = g_slist_remove(session->streams, stream); if (session->pending_open == stream) handle_transport_connect(session, -1, 0, 0); if (session->req && session->req->stream == stream) session->req->stream = NULL; stream_free(stream); if (session->ref == 1 && !session->streams) set_disconnect_timer(session); break; default: break; } } static void finalize_discovery(struct avdtp *session, int err) { struct avdtp_error avdtp_err; avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, -err); if (!session->discov_cb) return; session->discov_cb(session, session->seps, err ? &avdtp_err : NULL, session->user_data); session->discov_cb = NULL; session->user_data = NULL; } static void release_stream(struct avdtp_stream *stream, struct avdtp *session) { struct avdtp_local_sep *sep = stream->lsep; if (sep->cfm && sep->cfm->abort) sep->cfm->abort(session, sep, stream, NULL, sep->user_data); avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE); } static void connection_lost(struct avdtp *session, int err) { struct audio_device *dev; dev = manager_find_device(&session->dst, AUDIO_CONTROL_INTERFACE, FALSE); if (dev) avrcp_disconnect(dev); if (session->state == AVDTP_SESSION_STATE_CONNECTED) { char address[18]; ba2str(&session->dst, address); debug("Disconnected from %s", address); } else if (session->state == AVDTP_SESSION_STATE_CONNECTING) btd_cancel_authorization(&dev->src, &dev->dst); session->free_lock = 1; finalize_discovery(session, err); g_slist_foreach(session->streams, (GFunc) release_stream, session); session->streams = NULL; session->free_lock = 0; if (session->sock >= 0) { close(session->sock); session->sock = -1; } session->state = AVDTP_SESSION_STATE_DISCONNECTED; if (session->io) { g_source_remove(session->io); session->io = 0; } if (session->ref != 1) error("connection_lost: ref count not 1 after all callbacks"); else avdtp_unref(session); } void avdtp_unref(struct avdtp *session) { if (!session) return; if (!g_slist_find(sessions, session)) { error("avdtp_unref: trying to unref a unknown session"); return; } session->ref--; debug("avdtp_unref(%p): ref=%d", session, session->ref); if (session->ref == 1) { if (session->state == AVDTP_SESSION_STATE_CONNECTING) { close(session->sock); session->sock = -1; } if (session->sock >= 0) set_disconnect_timer(session); else if (!session->free_lock) /* Drop the local ref if we aren't connected */ session->ref--; } if (session->ref > 0) return; debug("avdtp_unref(%p): freeing session and removing from list", session); if (session->dc_timer) remove_disconnect_timer(session); sessions = g_slist_remove(sessions, session); if (session->req) pending_req_free(session->req); g_slist_foreach(session->seps, (GFunc) g_free, NULL); g_slist_free(session->seps); g_free(session->buf); g_free(session); } struct avdtp *avdtp_ref(struct avdtp *session) { session->ref++; debug("avdtp_ref(%p): ref=%d", session, session->ref); if (session->dc_timer) remove_disconnect_timer(session); return session; } static struct avdtp_local_sep *find_local_sep_by_seid(struct avdtp_server *server, uint8_t seid) { GSList *l; for (l = server->seps; l != NULL; l = g_slist_next(l)) { struct avdtp_local_sep *sep = l->data; if (sep->info.seid == seid) return sep; } return NULL; } static struct avdtp_local_sep *find_local_sep(struct avdtp_server *server, uint8_t type, uint8_t media_type, uint8_t codec) { GSList *l; for (l = server->seps; l != NULL; l = g_slist_next(l)) { struct avdtp_local_sep *sep = l->data; if (sep->info.inuse) continue; if (sep->info.type == type && sep->info.media_type == media_type && sep->codec == codec) return sep; } return NULL; } static GSList *caps_to_list(uint8_t *data, int size, struct avdtp_service_capability **codec) { GSList *caps; int processed; for (processed = 0, caps = NULL; processed + 2 < size;) { struct avdtp_service_capability *cap; uint8_t length, category; category = data[0]; length = data[1]; if (processed + 2 + length > size) { error("Invalid capability data in getcap resp"); break; } cap = g_malloc(sizeof(struct avdtp_service_capability) + length); memcpy(cap, data, 2 + length); processed += 2 + length; data += 2 + length; caps = g_slist_append(caps, cap); if (category == AVDTP_MEDIA_CODEC && length >= sizeof(struct avdtp_media_codec_capability)) *codec = cap; } return caps; } static void init_response(struct avdtp_header *rsp, struct avdtp_header *req, gboolean accept) { rsp->packet_type = AVDTP_PKT_TYPE_SINGLE; rsp->message_type = accept ? AVDTP_MSG_TYPE_ACCEPT : AVDTP_MSG_TYPE_REJECT; rsp->transaction = req->transaction; rsp->signal_id = req->signal_id; rsp->rfa0 = 0; } static gboolean avdtp_unknown_cmd(struct avdtp *session, struct avdtp_header *req, int size) { struct avdtp_general_rej rej; memset(&rej, 0, sizeof(rej)); rej.packet_type = AVDTP_PKT_TYPE_SINGLE; rej.message_type = AVDTP_MSG_TYPE_REJECT; rej.transaction = req->transaction; return avdtp_send(session, &rej, sizeof(rej)); } static gboolean avdtp_discover_cmd(struct avdtp *session, struct gen_req *req, int size) { GSList *l; struct discover_resp *rsp = (struct discover_resp *) session->buf; struct seid_info *info; int rsp_size; init_response(&rsp->header, &req->header, TRUE); rsp_size = sizeof(struct discover_resp); info = rsp->seps; for (l = session->server->seps; l != NULL; l = l->next) { struct avdtp_local_sep *sep = l->data; if (rsp_size + sizeof(struct seid_info) > session->mtu) break; memcpy(info, &sep->info, sizeof(struct seid_info)); rsp_size += sizeof(struct seid_info); info++; } return avdtp_send(session, session->buf, rsp_size); } static gboolean avdtp_getcap_cmd(struct avdtp *session, struct seid_req *req, int size) { GSList *l, *caps; struct avdtp_local_sep *sep = NULL; struct seid_rej rej; struct getcap_resp *rsp = (struct getcap_resp *) session->buf; int rsp_size; unsigned char *ptr; uint8_t err; if (size < sizeof(struct seid_req)) { error("Too short getcap request"); return FALSE; } sep = find_local_sep_by_seid(session->server, req->acp_seid); if (!sep) { err = AVDTP_BAD_ACP_SEID; goto failed; } if (!sep->ind->get_capability(session, sep, &caps, &err, sep->user_data)) goto failed; init_response(&rsp->header, &req->header, TRUE); rsp_size = sizeof(struct getcap_resp); ptr = rsp->caps; for (l = caps; l != NULL; l = g_slist_next(l)) { struct avdtp_service_capability *cap = l->data; if (rsp_size + cap->length + 2 > session->mtu) break; memcpy(ptr, cap, cap->length + 2); rsp_size += cap->length + 2; ptr += cap->length + 2; g_free(cap); } g_slist_free(caps); return avdtp_send(session, session->buf, rsp_size); failed: init_response(&rej.header, &req->header, FALSE); rej.error = AVDTP_BAD_ACP_SEID; return avdtp_send(session, &rej, sizeof(rej)); } static gboolean avdtp_setconf_cmd(struct avdtp *session, struct setconf_req *req, int size) { struct conf_rej rej; struct gen_resp *rsp = (struct gen_resp *) session->buf; struct avdtp_local_sep *sep; struct avdtp_stream *stream; uint8_t err, category = 0x00; struct audio_device *dev; bdaddr_t src, dst; if (size < sizeof(struct setconf_req)) { error("Too short getcap request"); return FALSE; } sep = find_local_sep_by_seid(session->server, req->acp_seid); if (!sep) { err = AVDTP_BAD_ACP_SEID; goto failed; } if (sep->stream) { err = AVDTP_SEP_IN_USE; goto failed; } avdtp_get_peers(session, &src, &dst); dev = manager_get_device(&src, &dst, NULL); if (!dev) { error("Unable to get a audio device object"); goto failed; } switch (sep->info.type) { case AVDTP_SEP_TYPE_SOURCE: if (!dev->sink) dev->sink = sink_init(dev); break; case AVDTP_SEP_TYPE_SINK: /* Do source_init() here when it's implemented */ break; } stream = g_new0(struct avdtp_stream, 1); stream->session = session; stream->lsep = sep; stream->rseid = req->int_seid; stream->caps = caps_to_list(req->caps, size - sizeof(struct setconf_req), &stream->codec); stream->sock = -1; if (sep->ind && sep->ind->set_configuration) { if (!sep->ind->set_configuration(session, sep, stream, stream->caps, &err, &category, sep->user_data)) { stream_free(stream); goto failed; } } init_response(&rsp->header, &req->header, TRUE); if (!avdtp_send(session, rsp, sizeof(struct setconf_req))) { stream_free(stream); return FALSE; } sep->stream = stream; session->streams = g_slist_append(session->streams, stream); avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED); return TRUE; failed: init_response(&rej.header, &req->header, FALSE); rej.error = err; rej.category = category; return avdtp_send(session, &rej, sizeof(rej)); } static gboolean avdtp_getconf_cmd(struct avdtp *session, struct seid_req *req, int size) { return avdtp_unknown_cmd(session, (void *) req, size); } static gboolean avdtp_reconf_cmd(struct avdtp *session, struct seid_req *req, int size) { return avdtp_unknown_cmd(session, (void *) req, size); } static gboolean avdtp_open_cmd(struct avdtp *session, struct seid_req *req, int size) { struct avdtp_local_sep *sep; struct avdtp_stream *stream; struct gen_resp *rsp = (struct gen_resp *) session->buf; struct seid_rej rej; uint8_t err; if (size < sizeof(struct seid_req)) { error("Too short abort request"); return FALSE; } sep = find_local_sep_by_seid(session->server, req->acp_seid); if (!sep) { err = AVDTP_BAD_ACP_SEID; goto failed; } if (sep->state != AVDTP_STATE_CONFIGURED) { err = AVDTP_BAD_STATE; goto failed; } stream = sep->stream; if (sep->ind && sep->ind->open) { if (!sep->ind->open(session, sep, stream, &err, sep->user_data)) goto failed; } init_response(&rsp->header, &req->header, TRUE); if (!avdtp_send(session, rsp, sizeof(struct gen_resp))) return FALSE; stream->open_acp = TRUE; session->pending_open = stream; avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN); stream->timer = g_timeout_add(REQ_TIMEOUT, stream_open_timeout, stream); return TRUE; failed: init_response(&rej.header, &req->header, FALSE); rej.error = err; return avdtp_send(session, &rej, sizeof(rej)); } static gboolean avdtp_start_cmd(struct avdtp *session, struct start_req *req, int size) { struct avdtp_local_sep *sep; struct avdtp_stream *stream; struct gen_resp *rsp = (struct gen_resp *) session->buf; struct stream_rej rej; struct seid *seid; uint8_t err, failed_seid; int seid_count, i; if (size < sizeof(struct start_req)) { error("Too short start request"); return FALSE; } seid_count = 1 + size - sizeof(struct start_req); seid = &req->first_seid; for (i = 0; i < seid_count; i++, seid++) { failed_seid = seid->seid; sep = find_local_sep_by_seid(session->server, req->first_seid.seid); if (!sep || !sep->stream) { err = AVDTP_BAD_ACP_SEID; goto failed; } stream = sep->stream; if (sep->state != AVDTP_STATE_OPEN) { err = AVDTP_BAD_STATE; goto failed; } if (sep->ind && sep->ind->start) { if (!sep->ind->start(session, sep, stream, &err, sep->user_data)) goto failed; } avdtp_sep_set_state(session, sep, AVDTP_STATE_STREAMING); } init_response(&rsp->header, &req->header, TRUE); return avdtp_send(session, rsp, sizeof(struct gen_resp)); failed: memset(&rej, 0, sizeof(rej)); init_response(&rej.header, &req->header, FALSE); rej.acp_seid = failed_seid; rej.error = err; return avdtp_send(session, &rej, sizeof(rej)); } static gboolean avdtp_close_cmd(struct avdtp *session, struct seid_req *req, int size) { struct avdtp_local_sep *sep; struct avdtp_stream *stream; struct gen_resp *rsp = (struct gen_resp *) session->buf; struct seid_rej rej; uint8_t err; if (size < sizeof(struct seid_req)) { error("Too short close request"); return FALSE; } sep = find_local_sep_by_seid(session->server, req->acp_seid); if (!sep || !sep->stream) { err = AVDTP_BAD_ACP_SEID; goto failed; } if (sep->state != AVDTP_STATE_OPEN && sep->state != AVDTP_STATE_STREAMING) { err = AVDTP_BAD_STATE; goto failed; } stream = sep->stream; if (sep->ind && sep->ind->close) { if (!sep->ind->close(session, sep, stream, &err, sep->user_data)) goto failed; } avdtp_sep_set_state(session, sep, AVDTP_STATE_CLOSING); init_response(&rsp->header, &req->header, TRUE); if (!avdtp_send(session, rsp, sizeof(struct gen_resp))) return FALSE; stream->timer = g_timeout_add(REQ_TIMEOUT, stream_close_timeout, stream); return TRUE; failed: init_response(&rej.header, &req->header, FALSE); rej.error = err; return avdtp_send(session, &rej, sizeof(rej)); } static gboolean avdtp_suspend_cmd(struct avdtp *session, struct suspend_req *req, int size) { struct avdtp_local_sep *sep; struct avdtp_stream *stream; struct gen_resp *rsp = (struct gen_resp *) session->buf; struct stream_rej rej; struct seid *seid; uint8_t err, failed_seid; int seid_count, i; if (size < sizeof(struct suspend_req)) { error("Too short suspend request"); return FALSE; } seid_count = 1 + size - sizeof(struct suspend_req); seid = &req->first_seid; for (i = 0; i < seid_count; i++, seid++) { failed_seid = seid->seid; sep = find_local_sep_by_seid(session->server, req->first_seid.seid); if (!sep || !sep->stream) { err = AVDTP_BAD_ACP_SEID; goto failed; } stream = sep->stream; if (sep->state != AVDTP_STATE_STREAMING) { err = AVDTP_BAD_STATE; goto failed; } if (sep->ind && sep->ind->suspend) { if (!sep->ind->suspend(session, sep, stream, &err, sep->user_data)) goto failed; } avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN); } init_response(&rsp->header, &req->header, TRUE); return avdtp_send(session, rsp, sizeof(struct gen_resp)); failed: memset(&rej, 0, sizeof(rej)); init_response(&rej.header, &req->header, FALSE); rej.acp_seid = failed_seid; rej.error = err; return avdtp_send(session, &rej, sizeof(rej)); } static gboolean avdtp_abort_cmd(struct avdtp *session, struct seid_req *req, int size) { struct avdtp_local_sep *sep; struct gen_resp *rsp = (struct gen_resp *) session->buf; struct seid_rej rej; uint8_t err; gboolean ret; if (size < sizeof(struct seid_req)) { error("Too short abort request"); return FALSE; } sep = find_local_sep_by_seid(session->server, req->acp_seid); if (!sep || !sep->stream) { err = AVDTP_BAD_ACP_SEID; goto failed; } if (sep->ind && sep->ind->abort) { if (!sep->ind->abort(session, sep, sep->stream, &err, sep->user_data)) goto failed; } init_response(&rsp->header, &req->header, TRUE); ret = avdtp_send(session, rsp, sizeof(struct gen_resp)); if (ret) avdtp_sep_set_state(session, sep, AVDTP_STATE_ABORTING); return ret; failed: init_response(&rej.header, &req->header, FALSE); rej.error = err; return avdtp_send(session, &rej, sizeof(rej)); } static gboolean avdtp_secctl_cmd(struct avdtp *session, struct seid_req *req, int size) { return avdtp_unknown_cmd(session, (void *) req, size); } static gboolean avdtp_parse_cmd(struct avdtp *session, struct avdtp_header *header, int size) { switch (header->signal_id) { case AVDTP_DISCOVER: debug("Received DISCOVER_CMD"); return avdtp_discover_cmd(session, (void *) header, size); case AVDTP_GET_CAPABILITIES: debug("Received GET_CAPABILITIES_CMD"); return avdtp_getcap_cmd(session, (void *) header, size); case AVDTP_SET_CONFIGURATION: debug("Received SET_CONFIGURATION_CMD"); return avdtp_setconf_cmd(session, (void *) header, size); case AVDTP_GET_CONFIGURATION: debug("Received GET_CONFIGURATION_CMD"); return avdtp_getconf_cmd(session, (void *) header, size); case AVDTP_RECONFIGURE: debug("Received RECONFIGURE_CMD"); return avdtp_reconf_cmd(session, (void *) header, size); case AVDTP_OPEN: debug("Received OPEN_CMD"); return avdtp_open_cmd(session, (void *) header, size); case AVDTP_START: debug("Received START_CMD"); return avdtp_start_cmd(session, (void *) header, size); case AVDTP_CLOSE: debug("Received CLOSE_CMD"); return avdtp_close_cmd(session, (void *) header, size); case AVDTP_SUSPEND: debug("Received SUSPEND_CMD"); return avdtp_suspend_cmd(session, (void *) header, size); case AVDTP_ABORT: debug("Received ABORT_CMD"); return avdtp_abort_cmd(session, (void *) header, size); case AVDTP_SECURITY_CONTROL: debug("Received SECURITY_CONTROL_CMD"); return avdtp_secctl_cmd(session, (void *) header, size); default: debug("Received unknown request id %u", header->signal_id); return avdtp_unknown_cmd(session, (void *) header, size); } } static void init_request(struct avdtp_header *header, int request_id) { static int transaction = 0; header->packet_type = AVDTP_PKT_TYPE_SINGLE; header->message_type = AVDTP_MSG_TYPE_COMMAND; header->transaction = transaction; header->signal_id = request_id; /* clear rfa bits */ header->rfa0 = 0; transaction = (transaction + 1) % 16; } static gboolean session_cb(GIOChannel *chan, GIOCondition cond, gpointer data) { struct avdtp *session = data; struct avdtp_header *header; gsize size; debug("session_cb"); if (cond & G_IO_NVAL) return FALSE; if (cond & (G_IO_HUP | G_IO_ERR)) goto failed; if (g_io_channel_read(chan, session->buf, session->mtu, &size) != G_IO_ERROR_NONE) { error("IO Channel read error"); goto failed; } if (size < sizeof(struct avdtp_header)) { error("Received too small packet (%d bytes)", size); goto failed; } header = (struct avdtp_header *) session->buf; if (header->message_type == AVDTP_MSG_TYPE_COMMAND) { if (!avdtp_parse_cmd(session, header, size)) { error("Unable to handle command. Disconnecting"); goto failed; } if (session->ref == 1 && !session->streams) set_disconnect_timer(session); if (session->streams && session->dc_timer) remove_disconnect_timer(session); return TRUE; } if (session->req == NULL) { error("No pending request, rejecting message"); return TRUE; } if (header->transaction != session->req->msg->transaction) { error("Transaction label doesn't match"); return TRUE; } if (header->signal_id != session->req->msg->signal_id) { error("Reponse signal doesn't match"); return TRUE; } g_source_remove(session->req->timeout); session->req->timeout = 0; switch(header->message_type) { case AVDTP_MSG_TYPE_ACCEPT: if (!avdtp_parse_resp(session, session->req->stream, header, size)) { error("Unable to parse accept response"); goto failed; } break; case AVDTP_MSG_TYPE_REJECT: if (!avdtp_parse_rej(session, session->req->stream, header, size)) { error("Unable to parse reject response"); goto failed; } break; default: error("Unknown message type"); break; } pending_req_free(session->req); session->req = NULL; process_queue(session); return TRUE; failed: connection_lost(session, -EIO); return FALSE; } static void l2cap_connect_cb(GIOChannel *chan, int err, const bdaddr_t *src, const bdaddr_t *dst, gpointer user_data) { struct avdtp *session = user_data; struct l2cap_options l2o; socklen_t len; int sk; char address[18]; if (!g_slist_find(sessions, session)) { debug("l2cap_connect_cb: session got removed"); return; } if (err < 0) { error("connect(): %s (%d)", strerror(-err), -err); goto failed; } sk = g_io_channel_unix_get_fd(chan); if (session->state == AVDTP_SESSION_STATE_DISCONNECTED) { session->sock = sk; session->state = AVDTP_SESSION_STATE_CONNECTING; } ba2str(&session->dst, address); debug("AVDTP: connected %s channel to %s", session->pending_open ? "transport" : "signaling", address); memset(&l2o, 0, sizeof(l2o)); len = sizeof(l2o); if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len) < 0) { err = errno; error("getsockopt(L2CAP_OPTIONS): %s (%d)", strerror(err), err); goto failed; } if (session->state == AVDTP_SESSION_STATE_CONNECTING) { struct audio_device *dev; session->mtu = l2o.imtu; session->buf = g_malloc0(session->mtu); session->state = AVDTP_SESSION_STATE_CONNECTED; session->io = g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, (GIOFunc) session_cb, session); dev = manager_find_device(&session->dst, AUDIO_CONTROL_INTERFACE, FALSE); if (dev) avrcp_connect(dev); } else if (session->pending_open) handle_transport_connect(session, sk, l2o.imtu, l2o.omtu); else { err = -EIO; goto failed; } process_queue(session); return; failed: if (session->pending_open) { avdtp_sep_set_state(session, session->pending_open->lsep, AVDTP_STATE_IDLE); session->pending_open = NULL; } else connection_lost(session, -err); return; } static int l2cap_connect(struct avdtp *session) { int err; err = bt_l2cap_connect(&session->server->src, &session->dst, AVDTP_PSM, 0, l2cap_connect_cb, session); if (err < 0) { error("Connect failed. %s(%d)", strerror(-err), -err); return err; } return 0; } static void queue_request(struct avdtp *session, struct pending_req *req, gboolean priority) { if (priority) session->prio_queue = g_slist_append(session->prio_queue, req); else session->req_queue = g_slist_append(session->req_queue, req); } static gboolean request_timeout(gpointer user_data) { struct avdtp *session = user_data; struct pending_req *req; struct seid_req sreq; struct avdtp_local_sep *lsep; struct avdtp_stream *stream; uint8_t seid; struct avdtp_error err; req = session->req; session->req = NULL; avdtp_error_init(&err, AVDTP_ERROR_ERRNO, ETIMEDOUT); seid = ((struct seid_req *) (req->msg))->acp_seid; stream = find_stream_by_rseid(session, seid); if (stream) lsep = stream->lsep; else lsep = NULL; switch (req->msg->signal_id) { case AVDTP_RECONFIGURE: error("Reconfigure request timed out"); if (lsep && lsep->cfm && lsep->cfm->reconfigure) lsep->cfm->reconfigure(session, lsep, stream, &err, lsep->user_data); break; case AVDTP_OPEN: error("Open request timed out"); if (lsep && lsep->cfm && lsep->cfm->open) lsep->cfm->open(session, lsep, stream, &err, lsep->user_data); break; case AVDTP_START: error("Start request timed out"); if (lsep && lsep->cfm && lsep->cfm->start) lsep->cfm->start(session, lsep, stream, &err, lsep->user_data); break; case AVDTP_SUSPEND: error("Suspend request timed out"); if (lsep && lsep->cfm && lsep->cfm->suspend) lsep->cfm->suspend(session, lsep, stream, &err, lsep->user_data); break; case AVDTP_CLOSE: error("Close request timed out"); if (lsep && lsep->cfm && lsep->cfm->close) lsep->cfm->close(session, lsep, stream, &err, lsep->user_data); break; case AVDTP_SET_CONFIGURATION: error("SetConfiguration request timed out"); if (lsep && lsep->cfm && lsep->cfm->set_configuration) lsep->cfm->set_configuration(session, lsep, stream, &err, lsep->user_data); goto failed; case AVDTP_DISCOVER: error("Discover request timed out"); goto failed; case AVDTP_GET_CAPABILITIES: error("GetCapabilities request timed out"); goto failed; case AVDTP_ABORT: error("Abort request timed out"); goto failed; } memset(&sreq, 0, sizeof(sreq)); init_request(&sreq.header, AVDTP_ABORT); sreq.acp_seid = seid; if (send_request(session, TRUE, stream, &sreq, sizeof(sreq)) < 0) { error("Unable to send abort request"); goto failed; } goto done; failed: connection_lost(session, -ETIMEDOUT); done: pending_req_free(req); return FALSE; } static int send_req(struct avdtp *session, gboolean priority, struct pending_req *req) { int err; if (session->state == AVDTP_SESSION_STATE_DISCONNECTED) { err = l2cap_connect(session); if (err < 0) goto failed; } if (session->state < AVDTP_SESSION_STATE_CONNECTED || session->req != NULL) { queue_request(session, req, priority); return 0; } /* FIXME: Should we retry to send if the buffer was not totally sent or in case of EINTR? */ if (!avdtp_send(session, req->msg, req->msg_size)) { err = -EIO; goto failed; } session->req = req; req->timeout = g_timeout_add(REQ_TIMEOUT, request_timeout, session); return 0; failed: g_free(req->msg); g_free(req); return err; } static int send_request(struct avdtp *session, gboolean priority, struct avdtp_stream *stream, void *buffer, int size) { struct pending_req *req; req = g_new0(struct pending_req, 1); req->msg = g_malloc(size); memcpy(req->msg, buffer, size); req->msg_size = size; req->stream = stream; return send_req(session, priority, req); } static gboolean avdtp_discover_resp(struct avdtp *session, struct discover_resp *resp, int size) { int sep_count, i, isize = sizeof(struct seid_info); sep_count = (size - sizeof(struct avdtp_header)) / isize; for (i = 0; i < sep_count; i++) { struct avdtp_remote_sep *sep; struct avdtp_stream *stream; struct seid_req req; int ret; debug("seid %d type %d media %d in use %d", resp->seps[i].seid, resp->seps[i].type, resp->seps[i].media_type, resp->seps[i].inuse); stream = find_stream_by_rseid(session, resp->seps[i].seid); sep = find_remote_sep(session->seps, resp->seps[i].seid); if (!sep) { if (resp->seps[i].inuse && !stream) continue; sep = g_new0(struct avdtp_remote_sep, 1); session->seps = g_slist_append(session->seps, sep); } sep->stream = stream; sep->seid = resp->seps[i].seid; sep->type = resp->seps[i].type; sep->media_type = resp->seps[i].media_type; memset(&req, 0, sizeof(req)); init_request(&req.header, AVDTP_GET_CAPABILITIES); req.acp_seid = sep->seid; ret = send_request(session, TRUE, NULL, &req, sizeof(req)); if (ret < 0) { finalize_discovery(session, ret); break; } } return TRUE; } static gboolean avdtp_get_capabilities_resp(struct avdtp *session, struct getcap_resp *resp, int size) { struct avdtp_remote_sep *sep; uint8_t seid; /* Check for minimum required packet size includes: * 1. getcap resp header * 2. media transport capability (2 bytes) * 3. media codec capability type + length (2 bytes) * 4. the actual media codec elements * */ if (size < (sizeof(struct getcap_resp) + 4 + sizeof(struct avdtp_media_codec_capability))) { error("Too short getcap resp packet"); return FALSE; } seid = ((struct seid_req *) session->req->msg)->acp_seid; sep = find_remote_sep(session->seps, seid); debug("seid %d type %d media %d", sep->seid, sep->type, sep->media_type); if (sep->caps) { g_slist_foreach(sep->caps, (GFunc) g_free, NULL); g_slist_free(sep->caps); sep->caps = NULL; sep->codec = NULL; } sep->caps = caps_to_list(resp->caps, size - sizeof(struct getcap_resp), &sep->codec); return TRUE; } static gboolean avdtp_set_configuration_resp(struct avdtp *session, struct avdtp_stream *stream, struct avdtp_header *resp, int size) { struct avdtp_local_sep *sep = stream->lsep; if (sep->cfm && sep->cfm->set_configuration) sep->cfm->set_configuration(session, sep, stream, NULL, sep->user_data); avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED); return TRUE; } static gboolean avdtp_reconfigure_resp(struct avdtp *session, struct avdtp_stream *stream, struct avdtp_header *resp, int size) { return TRUE; } static gboolean avdtp_open_resp(struct avdtp *session, struct avdtp_stream *stream, struct seid_rej *resp, int size) { struct avdtp_local_sep *sep = stream->lsep; if (l2cap_connect(session) < 0) { avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE); return FALSE; } session->pending_open = stream; return TRUE; } static gboolean avdtp_start_resp(struct avdtp *session, struct avdtp_stream *stream, struct seid_rej *resp, int size) { struct avdtp_local_sep *sep = stream->lsep; if (sep->cfm && sep->cfm->start) sep->cfm->start(session, sep, stream, NULL, sep->user_data); avdtp_sep_set_state(session, sep, AVDTP_STATE_STREAMING); return TRUE; } static gboolean avdtp_close_resp(struct avdtp *session, struct avdtp_stream *stream, struct seid_rej *resp, int size) { struct avdtp_local_sep *sep = stream->lsep; avdtp_sep_set_state(session, sep, AVDTP_STATE_CLOSING); close(stream->sock); stream->sock = -1; return TRUE; } static gboolean avdtp_suspend_resp(struct avdtp *session, struct avdtp_stream *stream, struct gen_resp *resp, int size) { struct avdtp_local_sep *sep = stream->lsep; avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN); stream->idle_timer = g_timeout_add(STREAM_TIMEOUT, (GSourceFunc) stream_timeout, stream); if (sep->cfm && sep->cfm->suspend) sep->cfm->suspend(session, sep, stream, NULL, sep->user_data); return TRUE; } static gboolean avdtp_abort_resp(struct avdtp *session, struct avdtp_stream *stream, struct seid_rej *resp, int size) { struct avdtp_local_sep *sep = stream->lsep; if (sep->cfm && sep->cfm->abort) sep->cfm->abort(session, sep, stream, NULL, sep->user_data); avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE); return TRUE; } static gboolean avdtp_parse_resp(struct avdtp *session, struct avdtp_stream *stream, struct avdtp_header *header, int size) { struct avdtp_header *next; if (session->prio_queue) next = ((struct pending_req *) (session->prio_queue->data))->msg; else if (session->req_queue) next = ((struct pending_req *) (session->req_queue->data))->msg; else next = NULL; switch (header->signal_id) { case AVDTP_DISCOVER: debug("DISCOVER request succeeded"); return avdtp_discover_resp(session, (void *) header, size); case AVDTP_GET_CAPABILITIES: debug("GET_CAPABILITIES request succeeded"); if (!avdtp_get_capabilities_resp(session, (void *) header, size)) return FALSE; if (!(next && next->signal_id == AVDTP_GET_CAPABILITIES)) finalize_discovery(session, 0); return TRUE; case AVDTP_SET_CONFIGURATION: debug("SET_CONFIGURATION request succeeded"); return avdtp_set_configuration_resp(session, stream, (void *) header, size); case AVDTP_RECONFIGURE: debug("RECONFIGURE request succeeded"); return avdtp_reconfigure_resp(session, stream, (void *) header, size); case AVDTP_OPEN: debug("OPEN request succeeded"); return avdtp_open_resp(session, stream, (void *) header, size); case AVDTP_SUSPEND: debug("SUSPEND request succeeded"); return avdtp_suspend_resp(session, stream, (void *) header, size); case AVDTP_START: debug("START request succeeded"); return avdtp_start_resp(session, stream, (void *) header, size); case AVDTP_CLOSE: debug("CLOSE request succeeded"); return avdtp_close_resp(session, stream, (void *) header, size); case AVDTP_ABORT: debug("ABORT request succeeded"); return avdtp_abort_resp(session, stream, (void *) header, size); } error("Unknown signal id in accept response: %u", header->signal_id); return TRUE; } static gboolean seid_rej_to_err(struct seid_rej *rej, int size, struct avdtp_error *err) { if (size < sizeof(struct seid_rej)) { error("Too small packet for seid_rej"); return FALSE; } avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error); return TRUE; } static gboolean conf_rej_to_err(struct conf_rej *rej, int size, struct avdtp_error *err, uint8_t *category) { if (size < sizeof(struct conf_rej)) { error("Too small packet for conf_rej"); return FALSE; } avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error); if (category) *category = rej->category; return TRUE; } static gboolean stream_rej_to_err(struct stream_rej *rej, int size, struct avdtp_error *err, uint8_t *acp_seid) { if (size < sizeof(struct conf_rej)) { error("Too small packet for stream_rej"); return FALSE; } avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error); if (acp_seid) *acp_seid = rej->acp_seid; return TRUE; } static gboolean avdtp_parse_rej(struct avdtp *session, struct avdtp_stream *stream, struct avdtp_header *header, int size) { struct avdtp_error err; uint8_t acp_seid, category; struct avdtp_local_sep *sep = stream ? stream->lsep : NULL; switch (header->signal_id) { case AVDTP_DISCOVER: if (!seid_rej_to_err((void *) header, size, &err)) return FALSE; error("DISCOVER request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); return TRUE; case AVDTP_GET_CAPABILITIES: if (!seid_rej_to_err((void *) header, size, &err)) return FALSE; error("GET_CAPABILITIES request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); return TRUE; case AVDTP_OPEN: if (!seid_rej_to_err((void *) header, size, &err)) return FALSE; error("OPEN request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); if (sep && sep->cfm && sep->cfm->open) sep->cfm->open(session, sep, stream, &err, sep->user_data); return TRUE; case AVDTP_SET_CONFIGURATION: if (!conf_rej_to_err((void *) header, size, &err, &category)) return FALSE; error("SET_CONFIGURATION request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); if (sep && sep->cfm && sep->cfm->set_configuration) sep->cfm->set_configuration(session, sep, stream, &err, sep->user_data); return TRUE; case AVDTP_RECONFIGURE: if (!conf_rej_to_err((void *) header, size, &err, &category)) return FALSE; error("RECONFIGURE request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); if (sep && sep->cfm && sep->cfm->reconfigure) sep->cfm->reconfigure(session, sep, stream, &err, sep->user_data); return TRUE; case AVDTP_START: if (!stream_rej_to_err((void *) header, size, &err, &acp_seid)) return FALSE; error("START request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); if (sep && sep->cfm && sep->cfm->start) sep->cfm->start(session, sep, stream, &err, sep->user_data); return TRUE; case AVDTP_SUSPEND: if (!stream_rej_to_err((void *) header, size, &err, &acp_seid)) return FALSE; error("SUSPEND request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); if (sep && sep->cfm && sep->cfm->suspend) sep->cfm->suspend(session, sep, stream, &err, sep->user_data); return TRUE; case AVDTP_CLOSE: if (!stream_rej_to_err((void *) header, size, &err, &acp_seid)) return FALSE; error("CLOSE request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); if (sep && sep->cfm && sep->cfm->close) sep->cfm->close(session, sep, stream, &err, sep->user_data); return TRUE; case AVDTP_ABORT: if (!stream_rej_to_err((void *) header, size, &err, &acp_seid)) return FALSE; error("ABORT request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); if (sep && sep->cfm && sep->cfm->abort) sep->cfm->abort(session, sep, stream, &err, sep->user_data); return TRUE; default: error("Unknown reject response signal id: %u", header->signal_id); return TRUE; } } static struct avdtp *find_session(const bdaddr_t *src, const bdaddr_t *dst) { GSList *l; for (l = sessions; l != NULL; l = g_slist_next(l)) { struct avdtp *s = l->data; if (bacmp(src, &s->server->src) || bacmp(dst, &s->dst)) continue; return s; } return NULL; } static struct avdtp *avdtp_get_internal(const bdaddr_t *src, const bdaddr_t *dst) { struct avdtp *session; assert(src != NULL); assert(dst != NULL); session = find_session(src, dst); if (session) { if (session->pending_auth) return NULL; else return session; } session = g_new0(struct avdtp, 1); session->sock = -1; session->server = find_server(servers, src); bacpy(&session->dst, dst); session->ref = 1; session->state = AVDTP_SESSION_STATE_DISCONNECTED; sessions = g_slist_append(sessions, session); return session; } struct avdtp *avdtp_get(bdaddr_t *src, bdaddr_t *dst) { struct avdtp *session; session = avdtp_get_internal(src, dst); if (!session) return NULL; return avdtp_ref(session); } gboolean avdtp_is_connected(const bdaddr_t *src, const bdaddr_t *dst) { struct avdtp *session; session = find_session(src, dst); if (!session) return FALSE; if (session->state != AVDTP_SESSION_STATE_DISCONNECTED) return TRUE; return FALSE; } gboolean avdtp_stream_has_capability(struct avdtp_stream *stream, struct avdtp_service_capability *cap) { GSList *l; struct avdtp_service_capability *stream_cap; for (l = stream->caps; l; l = g_slist_next(l)) { stream_cap = l->data; if (stream_cap->category == cap->category && stream_cap->length == cap->length) { if (!memcmp(stream_cap->data, cap->data, cap->length)) return TRUE; } } return FALSE; } gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream, GSList *caps) { GSList *l; for (l = caps; l; l = g_slist_next(l)) { struct avdtp_service_capability *cap = l->data; if (!avdtp_stream_has_capability(stream, cap)) return FALSE; } return TRUE; } gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock, uint16_t *imtu, uint16_t *omtu, GSList **caps) { if (stream->sock < 0) return FALSE; if (sock) *sock = stream->sock; if (omtu) *omtu = stream->omtu; if (imtu) *imtu = stream->imtu; if (caps) *caps = stream->caps; return TRUE; } static int process_queue(struct avdtp *session) { GSList **queue, *l; struct pending_req *req; if (session->req) return 0; if (session->prio_queue) queue = &session->prio_queue; else queue = &session->req_queue; if (!*queue) return 0; l = *queue; req = l->data; *queue = g_slist_remove(*queue, req); return send_req(session, FALSE, req); } struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep) { return sep->codec; } struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category, void *data, int length) { struct avdtp_service_capability *cap; if (category < AVDTP_MEDIA_TRANSPORT || category > AVDTP_MEDIA_CODEC) return NULL; cap = g_malloc(sizeof(struct avdtp_service_capability) + length); cap->category = category; cap->length = length; memcpy(cap->data, data, length); return cap; } int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb, void *user_data) { struct gen_req req; int ret; if (session->discov_cb) return -EBUSY; if (session->seps) { cb(session, session->seps, NULL, user_data); return 0; } memset(&req, 0, sizeof(req)); init_request(&req.header, AVDTP_DISCOVER); ret = send_request(session, FALSE, NULL, &req, sizeof(req)); if (ret == 0) { session->discov_cb = cb; session->user_data = user_data; } return ret; } int avdtp_get_seps(struct avdtp *session, uint8_t acp_type, uint8_t media_type, uint8_t codec, struct avdtp_local_sep **lsep, struct avdtp_remote_sep **rsep) { GSList *l; uint8_t int_type; int_type = acp_type == AVDTP_SEP_TYPE_SINK ? AVDTP_SEP_TYPE_SOURCE : AVDTP_SEP_TYPE_SINK; *lsep = find_local_sep(session->server, int_type, media_type, codec); if (!*lsep) return -EINVAL; for (l = session->seps; l != NULL; l = g_slist_next(l)) { struct avdtp_remote_sep *sep = l->data; struct avdtp_service_capability *cap; struct avdtp_media_codec_capability *codec_data; if (sep->type != acp_type) continue; if (sep->media_type != media_type) continue; if (!sep->codec) continue; cap = sep->codec; codec_data = (void *) cap->data; if (codec_data->media_codec_type != codec) continue; if (!sep->stream) { *rsep = sep; return 0; } } return -EINVAL; } gboolean avdtp_stream_remove_cb(struct avdtp *session, struct avdtp_stream *stream, unsigned int id) { GSList *l; struct stream_callback *cb; if (!stream) return FALSE; for (cb = NULL, l = stream->callbacks; l != NULL; l = l->next) { struct stream_callback *tmp = l->data; if (tmp && tmp->id == id) { cb = tmp; break; } } if (!cb) return FALSE; stream->callbacks = g_slist_remove(stream->callbacks, cb); g_free(cb); return TRUE; } unsigned int avdtp_stream_add_cb(struct avdtp *session, struct avdtp_stream *stream, avdtp_stream_state_cb cb, void *data) { struct stream_callback *stream_cb; static unsigned int id = 0; stream_cb = g_new(struct stream_callback, 1); stream_cb->cb = cb; stream_cb->user_data = data; stream_cb->id = ++id; stream->callbacks = g_slist_append(stream->callbacks, stream_cb);; return stream_cb->id; } int avdtp_get_configuration(struct avdtp *session, struct avdtp_stream *stream) { struct seid_req req; if (session->state < AVDTP_SESSION_STATE_CONNECTED) return -EINVAL; memset(&req, 0, sizeof(req)); init_request(&req.header, AVDTP_GET_CONFIGURATION); req.acp_seid = stream->rseid; return send_request(session, FALSE, stream, &req, sizeof(req)); } static void copy_capabilities(gpointer data, gpointer user_data) { struct avdtp_service_capability *src_cap = data; struct avdtp_service_capability *dst_cap; GSList **l = user_data; dst_cap = avdtp_service_cap_new(src_cap->category, src_cap->data, src_cap->length); *l = g_slist_append(*l, dst_cap); } int avdtp_set_configuration(struct avdtp *session, struct avdtp_remote_sep *rsep, struct avdtp_local_sep *lsep, GSList *caps, struct avdtp_stream **stream) { struct setconf_req *req; struct avdtp_stream *new_stream; unsigned char *ptr; int ret, caps_len; struct avdtp_service_capability *cap; GSList *l; if (session->state != AVDTP_SESSION_STATE_CONNECTED) return -ENOTCONN; if (!(lsep && rsep)) return -EINVAL; debug("avdtp_set_configuration(%p): int_seid=%u, acp_seid=%u", session, lsep->info.seid, rsep->seid); new_stream = g_new0(struct avdtp_stream, 1); new_stream->sock = -1; new_stream->session = session; new_stream->lsep = lsep; new_stream->rseid = rsep->seid; g_slist_foreach(caps, copy_capabilities, &new_stream->caps); /* Calculate total size of request */ for (l = caps, caps_len = 0; l != NULL; l = g_slist_next(l)) { cap = l->data; caps_len += cap->length + 2; } req = g_malloc0(sizeof(struct setconf_req) + caps_len); init_request(&req->header, AVDTP_SET_CONFIGURATION); req->int_seid = lsep->info.seid; req->acp_seid = rsep->seid; /* Copy the capabilities into the request */ for (l = caps, ptr = req->caps; l != NULL; l = g_slist_next(l)) { cap = l->data; memcpy(ptr, cap, cap->length + 2); ptr += cap->length + 2; } ret = send_request(session, FALSE, new_stream, req, sizeof(struct setconf_req) + caps_len); if (ret < 0) stream_free(new_stream); else { lsep->info.inuse = 1; lsep->stream = new_stream; rsep->stream = new_stream; session->streams = g_slist_append(session->streams, new_stream); if (stream) *stream = new_stream; } g_free(req); return ret; } int avdtp_reconfigure(struct avdtp *session, GSList *caps, struct avdtp_stream *stream) { struct reconf_req *req; unsigned char *ptr; int caps_len; GSList *l; struct avdtp_service_capability *cap; if (!g_slist_find(session->streams, stream)) return -EINVAL; if (stream->lsep->state != AVDTP_STATE_OPEN) return -EINVAL; /* Calculate total size of request */ for (l = caps, caps_len = 0; l != NULL; l = g_slist_next(l)) { cap = l->data; caps_len += cap->length + 2; } req = g_malloc0(sizeof(struct reconf_req) + caps_len); init_request(&req->header, AVDTP_RECONFIGURE); req->acp_seid = stream->rseid; /* Copy the capabilities into the request */ for (l = caps, ptr = req->caps; l != NULL; l = g_slist_next(l)) { cap = l->data; memcpy(ptr, cap, cap->length + 2); ptr += cap->length + 2; } return send_request(session, FALSE, stream, req, sizeof(*req) + caps_len); } int avdtp_open(struct avdtp *session, struct avdtp_stream *stream) { struct seid_req req; if (!g_slist_find(session->streams, stream)) return -EINVAL; if (stream->lsep->state > AVDTP_STATE_CONFIGURED) return -EINVAL; memset(&req, 0, sizeof(req)); init_request(&req.header, AVDTP_OPEN); req.acp_seid = stream->rseid; return send_request(session, FALSE, stream, &req, sizeof(req)); } int avdtp_start(struct avdtp *session, struct avdtp_stream *stream) { struct start_req req; if (!g_slist_find(session->streams, stream)) return -EINVAL; if (stream->lsep->state != AVDTP_STATE_OPEN) return -EINVAL; memset(&req, 0, sizeof(req)); init_request(&req.header, AVDTP_START); req.first_seid.seid = stream->rseid; return send_request(session, FALSE, stream, &req, sizeof(req)); } int avdtp_close(struct avdtp *session, struct avdtp_stream *stream) { struct seid_req req; int ret; if (!g_slist_find(session->streams, stream)) return -EINVAL; if (stream->lsep->state < AVDTP_STATE_OPEN) return -EINVAL; memset(&req, 0, sizeof(req)); init_request(&req.header, AVDTP_CLOSE); req.acp_seid = stream->rseid; ret = send_request(session, FALSE, stream, &req, sizeof(req)); if (ret == 0) stream->close_int = TRUE; return ret; } int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream) { struct seid_req req; if (!g_slist_find(session->streams, stream)) return -EINVAL; if (stream->lsep->state <= AVDTP_STATE_OPEN) return -EINVAL; memset(&req, 0, sizeof(req)); init_request(&req.header, AVDTP_SUSPEND); req.acp_seid = stream->rseid; return send_request(session, FALSE, stream, &req, sizeof(req)); } int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream) { struct seid_req req; int ret; if (!g_slist_find(session->streams, stream)) return -EINVAL; if (stream->lsep->state <= AVDTP_STATE_OPEN) return -EINVAL; memset(&req, 0, sizeof(req)); init_request(&req.header, AVDTP_ABORT); req.acp_seid = stream->rseid; ret = send_request(session, FALSE, stream, &req, sizeof(req)); if (ret == 0) avdtp_sep_set_state(session, stream->lsep, AVDTP_STATE_ABORTING); return 0; } struct avdtp_local_sep *avdtp_register_sep(const bdaddr_t *src, uint8_t type, uint8_t media_type, uint8_t codec_type, struct avdtp_sep_ind *ind, struct avdtp_sep_cfm *cfm, void *user_data) { struct avdtp_server *server; struct avdtp_local_sep *sep; server = find_server(servers, src); if (!server) return NULL; if (g_slist_length(server->seps) > MAX_SEID) return NULL; sep = g_new0(struct avdtp_local_sep, 1); sep->state = AVDTP_STATE_IDLE; sep->info.seid = g_slist_length(server->seps) + 1; sep->info.type = type; sep->info.media_type = media_type; sep->codec = codec_type; sep->ind = ind; sep->cfm = cfm; sep->user_data = user_data; sep->server = server; debug("SEP %p registered: type:%d codec:%d seid:%d", sep, sep->info.type, sep->codec, sep->info.seid); server->seps = g_slist_append(server->seps, sep); return sep; } int avdtp_unregister_sep(struct avdtp_local_sep *sep) { struct avdtp_server *server; if (!sep) return -EINVAL; if (sep->info.inuse) return -EBUSY; server = sep->server; server->seps = g_slist_remove(server->seps, sep); g_free(sep); return 0; } static void auth_cb(DBusError *derr, void *user_data) { struct avdtp *session = user_data; struct audio_device *dev; GIOChannel *io; if (derr && dbus_error_is_set(derr)) { error("Access denied: %s", derr->message); connection_lost(session, -EACCES); return; } session->buf = g_malloc0(session->mtu); set_disconnect_timer(session); session->state = AVDTP_SESSION_STATE_CONNECTED; dev = manager_find_device(&session->dst, AUDIO_CONTROL_INTERFACE, FALSE); if (dev) avrcp_connect(dev); g_source_remove(session->io); io = g_io_channel_unix_new(session->sock); session->io = g_io_add_watch(io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, (GIOFunc) session_cb, session); g_io_channel_unref(io); } static void avdtp_server_cb(GIOChannel *chan, int err, const bdaddr_t *src, const bdaddr_t *dst, gpointer data) { int sk; socklen_t size; struct l2cap_options l2o; struct avdtp *session; struct audio_device *dev; char address[18]; if (err < 0) { error("accept: %s (%d)", strerror(-err), -err); return; } sk = g_io_channel_unix_get_fd(chan); ba2str(dst, address); debug("AVDTP: incoming connect from %s", address); memset(&l2o, 0, sizeof(l2o)); size = sizeof(l2o); if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &size) < 0) { error("getsockopt(L2CAP_OPTIONS): %s (%d)", strerror(errno), errno); goto drop; } session = avdtp_get_internal(src, dst); if (session->pending_open && session->pending_open->open_acp) { handle_transport_connect(session, sk, l2o.imtu, l2o.omtu); return; } if (session->sock >= 0) { error("Refusing unexpected connect from %s", address); goto drop; } dev = manager_get_device(src, dst, NULL); if (!dev) { error("Unable to get audio device object for %s", address); goto drop; } session->mtu = l2o.imtu; session->sock = sk; session->io = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL, (GIOFunc) session_cb, session); err = btd_request_authorization(src, dst, ADVANCED_AUDIO_UUID, auth_cb, session); if (err < 0) { avdtp_unref(session); goto drop; } g_io_channel_unref(chan); return; drop: g_io_channel_close(chan); g_io_channel_unref(chan); } static GIOChannel *avdtp_server_socket(const bdaddr_t *src, gboolean master) { int lm = L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT; if (master) lm |= L2CAP_LM_MASTER; return bt_l2cap_listen(src, AVDTP_PSM, 0, lm, avdtp_server_cb, NULL); } const char *avdtp_strerror(struct avdtp_error *err) { if (err->type == AVDTP_ERROR_ERRNO) return strerror(err->err.posix_errno); switch(err->err.error_code) { case AVDTP_BAD_HEADER_FORMAT: return "Bad Header Format"; case AVDTP_BAD_LENGTH: return "Bad Packet Lenght"; case AVDTP_BAD_ACP_SEID: return "Bad Acceptor SEID"; case AVDTP_SEP_IN_USE: return "Stream End Point in Use"; case AVDTP_SEP_NOT_IN_USE: return "Stream End Point Not in Use"; case AVDTP_BAD_SERV_CATEGORY: return "Bad Service Category"; case AVDTP_BAD_PAYLOAD_FORMAT: return "Bad Payload format"; case AVDTP_NOT_SUPPORTED_COMMAND: return "Command Not Supported"; case AVDTP_INVALID_CAPABILITIES: return "Invalid Capabilities"; case AVDTP_BAD_RECOVERY_TYPE: return "Bad Recovery Type"; case AVDTP_BAD_MEDIA_TRANSPORT_FORMAT: return "Bad Media Transport Format"; case AVDTP_BAD_RECOVERY_FORMAT: return "Bad Recovery Format"; case AVDTP_BAD_ROHC_FORMAT: return "Bad Header Compression Format"; case AVDTP_BAD_CP_FORMAT: return "Bad Content Protetion Format"; case AVDTP_BAD_MULTIPLEXING_FORMAT: return "Bad Multiplexing Format"; case AVDTP_UNSUPPORTED_CONFIGURATION: return "Configuration not supported"; case AVDTP_BAD_STATE: return "Bad State"; default: return "Unknow error"; } } avdtp_state_t avdtp_sep_get_state(struct avdtp_local_sep *sep) { return sep->state; } void avdtp_get_peers(struct avdtp *session, bdaddr_t *src, bdaddr_t *dst) { if (src) bacpy(src, &session->server->src); if (dst) bacpy(dst, &session->dst); } int avdtp_init(const bdaddr_t *src, GKeyFile *config) { GError *err = NULL; gboolean tmp, master = TRUE; struct avdtp_server *server; if (config) { tmp = g_key_file_get_boolean(config, "General", "Master", &err); if (err) { debug("audio.conf: %s", err->message); g_error_free(err); } else master = tmp; } server = g_new0(struct avdtp_server, 1); if (!server) return -ENOMEM; server->io = avdtp_server_socket(src, master); if (!server->io) { g_free(server); return -1; } bacpy(&server->src, src); servers = g_slist_append(servers, server); return 0; } void avdtp_exit(const bdaddr_t *src) { struct avdtp_server *server; server = find_server(servers, src); if (!server) return; servers = g_slist_remove(servers, server); g_io_channel_close(server->io); g_io_channel_unref(server->io); g_free(server); } gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream) { return g_slist_find(session->streams, stream) ? TRUE : FALSE; }