diff options
author | Johan Hedberg <johan.hedberg@nokia.com> | 2007-08-11 11:05:24 +0000 |
---|---|---|
committer | Johan Hedberg <johan.hedberg@nokia.com> | 2007-08-11 11:05:24 +0000 |
commit | 6763ebb3c231740c66a235f94d56e8d8cc213d90 (patch) | |
tree | 527ad7a778289b70ac64b2d4e49512eae6d634e2 /audio/avdtp.c | |
parent | 46e860574f3d6d70d961e38270522764191cea20 (diff) |
Integrate A2DP work from Johan's and Luiz's GIT trees
Diffstat (limited to 'audio/avdtp.c')
-rw-r--r-- | audio/avdtp.c | 2174 |
1 files changed, 2174 insertions, 0 deletions
diff --git a/audio/avdtp.c b/audio/avdtp.c index 2f4ab59e..8615a18a 100644 --- a/audio/avdtp.c +++ b/audio/avdtp.c @@ -24,3 +24,2177 @@ #ifdef HAVE_CONFIG_H #include <config.h> #endif + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <assert.h> +#include <signal.h> +#include <netinet/in.h> + +#include <glib.h> + +#include "avdtp.h" +#include "logging.h" +#include "dbus.h" + +#include <bluetooth/l2cap.h> + +#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 2000 +#define DISCONNECT_TIMEOUT 5000 + +typedef enum { + AVDTP_ERROR_ERRNO, + AVDTP_ERROR_ERROR_CODE +} avdtp_error_type_t; + +typedef enum { + AVDTP_SESSION_STATE_DISCONNECTED, + AVDTP_SESSION_STATE_CONNECTING, + AVDTP_SESSION_STATE_CONNECTED +} avdtp_session_state_t; + +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)); + +typedef 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)) seid_info_t; + +/* packets */ + +struct discover_req { + 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 seid_req { + struct avdtp_header header; + uint8_t rfa0:2; + uint8_t acp_seid:6; +} __attribute__ ((packed)); + +struct seid_rej { + struct avdtp_header header; + uint8_t error; +} __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 setconf_resp { + struct avdtp_header header; +} __attribute__ ((packed)); + +struct conf_rej { + struct avdtp_header header; + uint8_t category; + uint8_t error; +} __attribute__ ((packed)); + +struct stream_rej { + struct avdtp_header header; + uint8_t rfa0; + uint8_t acp_seid:6; + uint8_t error_code; +} __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 reconf_resp { + struct avdtp_header header; +} __attribute__ ((packed)); + +struct abort_resp { + struct avdtp_header header; +} __attribute__ ((packed)); + +struct stream_pause_resp { + struct avdtp_header header; + uint8_t rfa0:2; + uint8_t acp_seid:6; + uint8_t error; +} __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)); + +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_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 *data; +}; + +struct avdtp_stream { + int sock; + uint16_t mtu; + struct avdtp *session; + struct avdtp_local_sep *lsep; + struct avdtp_remote_sep *rsep; + GSList *caps; + avdtp_stream_state_cb cb; + void *user_data; + guint io; /* Transport GSource ID */ + guint close_timer; /* Waiting for other side to close transport */ + gboolean open_acp; /* If we are in ACT role for Open */ + gboolean close_int; /* If we are in INT role for Close */ +}; + +/* Structure describing an AVDTP connection between two devices */ +struct avdtp { + int ref; + + bdaddr_t src; + 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; +}; + +struct avdtp_error { + avdtp_error_type_t type; + union { + uint8_t error_code; + int posix_errno; + } err; +}; + +static uint8_t free_seid = 1; +static GSList *local_seps = NULL; + +static GIOChannel *avdtp_server = 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 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 "<unknown state>"; + } +} + +static gboolean avdtp_send(struct avdtp *session, void *data, int len) +{ + int ret; + + 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); +} + +#if 0 +static gboolean stream_close_timeout(gpointer user_data) +{ + struct avdtp_stream *stream = user_data; + + close(stream->sock); + + return FALSE; +} +#endif + +static gboolean disconnect_timeout(gpointer user_data) +{ + struct avdtp *session = user_data; + + assert(session->ref == 1); + + sessions = g_slist_remove(sessions, session); + + session->dc_timer = 0; + + avdtp_unref(session); + + 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); +} + +static 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; + } +} + +static void stream_free(struct avdtp_stream *stream) +{ + stream->lsep->info.inuse = 0; + stream->lsep->stream = NULL; + stream->rsep->stream = NULL; + if (stream->caps) { + g_slist_foreach(stream->caps, (GFunc) g_free, NULL); + g_slist_free(stream->caps); + } + g_free(stream); +} + +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; + + if (sep->state == state) + return; + + debug("stream state changed: %s -> %s", avdtp_statestr(sep->state), + avdtp_statestr(state)); + + old_state = sep->state; + sep->state = state; + + if (stream && stream->cb) + stream->cb(stream, old_state, state, NULL, + stream->user_data); + + if (state == AVDTP_STATE_IDLE) { + session->streams = g_slist_remove(session->streams, stream); + stream_free(stream); + } +} + +static void finalize_discovery(struct avdtp *session, int err) +{ + if (!session->discov_cb) + return; + + session->discov_cb(session, session->seps, err, + session->user_data); + + session->discov_cb = NULL; + session->user_data = NULL; +} + +static void release_stream(struct avdtp_stream *stream, struct avdtp *session) +{ + if (stream->sock >= 0) + close(stream->sock); + if (stream->io) + g_source_remove(stream->io); + avdtp_sep_set_state(session, stream->lsep, AVDTP_STATE_IDLE); +} + +static void connection_lost(struct avdtp *session) +{ + if (session->state == AVDTP_SESSION_STATE_CONNECTED) { + char address[18]; + + ba2str(&session->dst, address); + debug("Disconnected from %s", address); + } + + if (session->discov_cb) + finalize_discovery(session, -ECONNABORTED); + + 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; + } + + g_slist_foreach(session->streams, (GFunc) release_stream, session); + session->streams = NULL; +} + +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: ref=%d", session->ref); + + if (session->ref == 1) { + if (session->dc_timer) + remove_disconnect_timer(session); + if (session->sock >= 0) + set_disconnect_timer(session); + return; + } + + if (session->ref > 0) + return; + + if (session->dc_timer) + remove_disconnect_timer(session); + + connection_lost(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: ref=%d", session->ref); + if (session->dc_timer) + remove_disconnect_timer(session); + return session; +} + +static struct avdtp_local_sep *find_local_sep_by_seid(uint8_t seid) +{ + GSList *l; + + for (l = local_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(uint8_t type, uint8_t media_type, + uint8_t codec) +{ + GSList *l; + + for (l = local_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 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 discover_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 = local_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(req->acp_seid); + if (!sep) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + if (!sep->ind->get_capability(sep, &caps, &err)) + 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 setconf_resp *rsp = (struct setconf_resp *) session->buf; + struct avdtp_local_sep *lsep; + gboolean ret; + + if (size < sizeof(struct setconf_req)) { + error("Too short getcap request"); + return FALSE; + } + + lsep = find_local_sep_by_seid(req->acp_seid); + if (!lsep || !lsep->stream) { + init_response(&rej.header, &req->header, FALSE); + rej.error = AVDTP_BAD_ACP_SEID; + return avdtp_send(session, &rej, sizeof(rej)); + } + + init_response(&rsp->header, &req->header, TRUE); + + ret = avdtp_send(session, rsp, sizeof(struct setconf_req)); + + if (ret) + avdtp_sep_set_state(session, lsep, AVDTP_STATE_CONFIGURED); + + return ret; +} + +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) +{ + return avdtp_unknown_cmd(session, (void *) req, size); +} + +static gboolean avdtp_start_cmd(struct avdtp *session, struct seid_req *req, + int size) +{ + return avdtp_unknown_cmd(session, (void *) req, size); +} + +static gboolean avdtp_close_cmd(struct avdtp *session, struct seid_req *req, + int size) +{ + return avdtp_unknown_cmd(session, (void *) req, size); +} + +static gboolean avdtp_suspend_cmd(struct avdtp *session, struct seid_req *req, + int size) +{ + return avdtp_unknown_cmd(session, (void *) req, size); +} + +static gboolean avdtp_abort_cmd(struct avdtp *session, struct seid_req *req, + int size) +{ + struct avdtp_local_sep *sep; + struct abort_resp *rsp = (struct abort_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(req->acp_seid); + if (!sep || !sep->stream) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + if (sep->ind && sep->ind->abort) { + if (!sep->ind->abort(sep, sep->stream, &err)) + goto failed; + } + + init_response(&rsp->header, &req->header, TRUE); + ret = avdtp_send(session, rsp, sizeof(struct abort_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 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(sep, stream); + + avdtp_sep_set_state(stream->session, sep, AVDTP_STATE_IDLE); + + return FALSE; +} + +static void handle_transport_connect(struct avdtp *session, int sock, + uint16_t mtu) +{ + struct avdtp_stream *stream = session->pending_open; + struct avdtp_local_sep *sep = stream->lsep; + GIOChannel *channel; + + session->pending_open = NULL; + + stream->sock = sock; + stream->mtu = mtu; + + if (sep->cfm && sep->cfm->open) + sep->cfm->open(sep, stream); + + 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); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN); +} + +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); + + 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); + avdtp_unref(session); + return FALSE; +} + +static gboolean l2cap_connect_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct avdtp *session = data; + struct l2cap_options l2o; + socklen_t len; + int ret, err, sk; + char address[18]; + + if (cond & G_IO_NVAL) + return FALSE; + + if (!g_slist_find(sessions, session)) { + debug("l2cap_connect_cb: session got removed"); + return FALSE; + } + + if (cond & (G_IO_ERR | G_IO_HUP)) { + err = EIO; + goto failed; + } + + sk = g_io_channel_unix_get_fd(chan); + len = sizeof(ret); + if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &ret, &len) < 0) { + err = errno; + error("getsockopt(SO_ERROR): %s (%d)", strerror(err), err); + goto failed; + } + + if (ret != 0) { + err = ret; + error("connect(): %s (%d)", strerror(err), err); + goto failed; + } + + 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) { + session->sock = sk; + 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); + } + else if (session->pending_open) + handle_transport_connect(session, sk, l2o.imtu); + + process_queue(session); + + return FALSE; + +failed: + if (session->pending_open) { + avdtp_sep_set_state(session, session->pending_open->lsep, + AVDTP_STATE_IDLE); + session->pending_open = NULL; + } else { + finalize_discovery(session, -err); + connection_lost(session); + avdtp_unref(session); + } + + return FALSE; +} + +static int l2cap_connect(struct avdtp *session) +{ + struct sockaddr_l2 l2a; + GIOChannel *io; + int sk; + + memset(&l2a, 0, sizeof(l2a)); + l2a.l2_family = AF_BLUETOOTH; + bacpy(&l2a.l2_bdaddr, &session->src); + + sk = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (sk < 0) { + error("Cannot create L2CAP socket. %s(%d)", strerror(errno), + errno); + return -errno; + } + + if (bind(sk, (struct sockaddr *) &l2a, sizeof(l2a)) < 0) { + error("Bind failed. %s (%d)", strerror(errno), errno); + return -errno; + } + + memset(&l2a, 0, sizeof(l2a)); + l2a.l2_family = AF_BLUETOOTH; + bacpy(&l2a.l2_bdaddr, &session->dst); + l2a.l2_psm = htobs(AVDTP_PSM); + + if (set_nonblocking(sk) < 0) { + error("Set non blocking: %s (%d)", strerror(errno), errno); + return -errno; + } + + io = g_io_channel_unix_new(sk); + g_io_channel_set_close_on_unref(io, FALSE); + + if (connect(sk, (struct sockaddr *) &l2a, sizeof(l2a)) < 0) { + if (!(errno == EAGAIN || errno == EINPROGRESS)) { + error("Connect failed. %s(%d)", strerror(errno), + errno); + finalize_discovery(session, errno); + g_io_channel_close(io); + g_io_channel_unref(io); + return -errno; + } + g_io_add_watch(io, G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + (GIOFunc) l2cap_connect_cb, session); + + if (session->state == AVDTP_SESSION_STATE_DISCONNECTED) + session->state = AVDTP_SESSION_STATE_CONNECTING; + } else + l2cap_connect_cb(io, G_IO_OUT, session); + + g_io_channel_unref(io); + + 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 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 gboolean request_timeout(gpointer user_data) +{ + struct avdtp *session = user_data; + struct pending_req *req; + struct seid_req sreq; + struct avdtp_remote_sep *sep; + struct avdtp_stream *stream; + uint8_t seid; + + error("Request timed out"); + + req = session->req; + session->req = NULL; + + switch (req->msg->signal_id) { + case AVDTP_DISCOVER: + case AVDTP_GET_CAPABILITIES: + case AVDTP_SET_CONFIGURATION: + case AVDTP_ABORT: + goto failed; + } + + seid = ((struct seid_req *) (req->msg))->acp_seid; + + sep = find_remote_sep(session->seps, seid); + if (!sep) { + error("Unable to find matching remote SEID %u", seid); + goto failed; + } + + stream = sep->stream; + + memset(&sreq, 0, sizeof(sreq)); + init_request(&sreq.header, AVDTP_ABORT); + sreq.acp_seid = seid; + + if (!send_request(session, TRUE, stream, &sreq, sizeof(sreq))) { + error("Unable to send abort request"); + goto failed; + } + + goto done; + +failed: + connection_lost(session); + avdtp_unref(session); +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? */ + err = avdtp_send(session, req->msg, req->msg_size); + if (err < 0) + 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 seid_req req; + int ret; + + /* Skip SEP's which are in use */ + if (resp->seps[i].inuse) + continue; + + sep = find_remote_sep(session->seps, resp->seps[i].seid); + if (!sep) { + sep = g_new0(struct avdtp_remote_sep, 1); + session->seps = g_slist_append(session->seps, sep); + } + else if (sep && sep->stream) + continue; + + 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) +{ + int processed; + struct avdtp_remote_sep *sep; + unsigned char *ptr; + 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); + + if (sep->caps) { + g_slist_foreach(sep->caps, (GFunc) g_free, NULL); + g_slist_free(sep->caps); + sep->caps = NULL; + sep->codec = NULL; + } + + ptr = resp->caps; + processed = sizeof(struct getcap_resp); + + while (processed + 2 < size) { + struct avdtp_service_capability *cap; + uint8_t length, category; + + category = ptr[0]; + length = ptr[1]; + + if (processed + 2 + length > size) { + error("Invalid capability data in getcap resp"); + return FALSE; + } + + cap = g_malloc(sizeof(struct avdtp_service_capability) + + length); + memcpy(cap, ptr, 2 + length); + + processed += 2 + length; + ptr += 2 + length; + + sep->caps = g_slist_append(sep->caps, cap); + + if (category == AVDTP_MEDIA_CODEC && + length >= + sizeof(struct avdtp_media_codec_capability)) + sep->codec = cap; + + } + + 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(sep, stream); + + 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); + + 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(sep, stream); + + 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 stream_pause_resp *resp, + int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + if (sep->cfm && sep->cfm->suspend) + sep->cfm->suspend(sep, stream); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN); + + 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->suspend) + sep->cfm->suspend(sep, stream); + + 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_code); + + 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; + + 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); + 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); + 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); + 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); + 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); + 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); + 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); + return TRUE; + default: + error("Unknown reject response signal id: %u", + header->signal_id); + return TRUE; + } +} + +static struct avdtp *find_session(bdaddr_t *src, bdaddr_t *dst) +{ + GSList *l; + + for (l = sessions; l != NULL; l = g_slist_next(l)) { + struct avdtp *s = l->data; + + if (bacmp(src, &s->src) || bacmp(dst, &s->dst)) + continue; + + return s; + } + + return NULL; +} + +struct avdtp *avdtp_get(bdaddr_t *src, bdaddr_t *dst) +{ + struct avdtp *session; + + assert(src != NULL); + assert(dst != NULL); + + session = find_session(src, dst); + if (session) + return avdtp_ref(session); + + session = g_new0(struct avdtp, 1); + + session->sock = -1; + bacpy(&session->src, src); + bacpy(&session->dst, dst); + session->ref = 1; + session->state = AVDTP_SESSION_STATE_DISCONNECTED; + + sessions = g_slist_append(sessions, session); + + return avdtp_ref(session); +} + +gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock, + uint16_t *mtu, GSList **caps) +{ + if (stream->sock < 0) + return FALSE; + + if (sock) + *sock = stream->sock; + + if (mtu) + *mtu = stream->mtu; + + 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; + + 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 discover_req req; + int ret; + + if (session->discov_cb) + return -EBUSY; + + 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(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; +} + +void avdtp_stream_set_cb(struct avdtp *session, struct avdtp_stream *stream, + avdtp_stream_state_cb cb, void *data) +{ + stream->cb = cb; + stream->user_data = data; +} + +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->rsep->seid; + + return send_request(session, FALSE, stream, &req, sizeof(req)); +} + +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; + + new_stream = g_new0(struct avdtp_stream, 1); + + new_stream->session = session; + new_stream->lsep = lsep; + new_stream->rsep = rsep; + new_stream->caps = 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->acp_seid = lsep->info.seid; + req->int_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, 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_GET_CONFIGURATION); + req.acp_seid = stream->rsep->seid; + + return send_request(session, FALSE, NULL, &req, sizeof(req)); +} + +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->rsep->seid; + + return send_request(session, FALSE, stream, &req, sizeof(req)); +} + +int avdtp_start(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_START); + req.acp_seid = stream->rsep->seid; + + 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->rsep->seid; + + 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; + 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_SUSPEND); + req.acp_seid = stream->rsep->seid; + + ret = send_request(session, FALSE, stream, &req, sizeof(req)); + if (ret == 0) + avdtp_sep_set_state(session, stream->lsep, + AVDTP_STATE_OPEN); + + return ret; +} + +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->rsep->seid; + + 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(uint8_t type, uint8_t media_type, + struct avdtp_sep_ind *ind, + struct avdtp_sep_cfm *cfm) +{ + struct avdtp_local_sep *sep; + + if (free_seid > MAX_SEID) + return NULL; + + sep = g_new0(struct avdtp_local_sep, 1); + + sep->state = AVDTP_STATE_IDLE; + sep->info.seid = free_seid++; + sep->info.type = type; + sep->info.media_type = media_type; + sep->ind = ind; + sep->cfm = cfm; + + local_seps = g_slist_append(local_seps, sep); + + return sep; +} + +int avdtp_unregister_sep(struct avdtp_local_sep *sep) +{ + if (!sep) + return -EINVAL; + + if (sep->info.inuse) + return -EBUSY; + + local_seps = g_slist_remove(local_seps, sep); + + g_free(sep); + + return 0; +} + +static gboolean avdtp_server_cb(GIOChannel *chan, GIOCondition cond, void *data) +{ + int srv_sk, cli_sk; + socklen_t size; + struct sockaddr_l2 addr; + struct l2cap_options l2o; + bdaddr_t src, dst; + struct avdtp *session; + GIOChannel *cli_io; + char address[18]; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_HUP | G_IO_ERR)) { + error("Hangup or error on AVDTP server socket"); + g_io_channel_close(chan); + raise(SIGTERM); + return FALSE; + } + + srv_sk = g_io_channel_unix_get_fd(chan); + + size = sizeof(struct sockaddr_l2); + cli_sk = accept(srv_sk, (struct sockaddr *) &addr, &size); + if (cli_sk < 0) { + error("AVDTP accept: %s (%d)", strerror(errno), errno); + return TRUE; + } + + bacpy(&dst, &addr.l2_bdaddr); + + ba2str(&dst, address); + debug("AVDTP: incoming connect from %s", address); + + size = sizeof(struct sockaddr_l2); + if (getsockname(srv_sk, (struct sockaddr *) &addr, &size) < 0) { + error("getsockname: %s (%d)", strerror(errno), errno); + close(cli_sk); + return TRUE; + } + + bacpy(&src, &addr.l2_bdaddr); + + memset(&l2o, 0, sizeof(l2o)); + size = sizeof(l2o); + if (getsockopt(cli_sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &size) < 0) { + error("getsockopt(L2CAP_OPTIONS): %s (%d)", strerror(errno), + errno); + close(cli_sk); + return TRUE; + } + + session = avdtp_get(&src, &dst); + + if (session->pending_open && session->pending_open->open_acp) { + handle_transport_connect(session, cli_sk, l2o.imtu); + return TRUE; + } + + if (session->sock >= 0) { + error("Refusing unexpected connect from %s", address); + close(cli_sk); + return TRUE; + } + + if (session->ref == 1) + set_disconnect_timer(session); + + session->mtu = l2o.imtu; + session->buf = g_malloc0(session->mtu); + session->sock = cli_sk; + session->state = AVDTP_SESSION_STATE_CONNECTED; + + cli_io = g_io_channel_unix_new(session->sock); + session->io = g_io_add_watch(cli_io, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) session_cb, session); + g_io_channel_unref(cli_io); + + return TRUE; +} + +static GIOChannel *avdtp_server_socket(void) +{ + int sock, lm; + struct sockaddr_l2 addr; + GIOChannel *io; + + sock = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (sock < 0) { + error("AVDTP server socket: %s (%d)", strerror(errno), errno); + return NULL; + } + + lm = L2CAP_LM_SECURE; + if (setsockopt(sock, SOL_L2CAP, L2CAP_LM, &lm, sizeof(lm)) < 0) { + error("AVDTP server setsockopt: %s (%d)", strerror(errno), errno); + close(sock); + return NULL; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, BDADDR_ANY); + addr.l2_psm = htobs(AVDTP_PSM); + + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + error("AVDTP server bind: %s", strerror(errno), errno); + close(sock); + return NULL; + } + + if (listen(sock, 4) < 0) { + error("AVDTP server listen: %s", strerror(errno), errno); + close(sock); + return NULL; + } + + io = g_io_channel_unix_new(sock); + if (!io) { + error("Unable to allocate new io channel"); + close(sock); + return NULL; + } + + return io; +} + +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"; + } +} + +int avdtp_init(void) +{ + if (avdtp_server) + return 0; + + avdtp_server = avdtp_server_socket(); + if (!avdtp_server) + return -1; + + g_io_add_watch(avdtp_server, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + (GIOFunc) avdtp_server_cb, NULL); + + return 0; +} + +void avdtp_exit(void) +{ + if (!avdtp_server) + return; + + g_io_channel_close(avdtp_server); + g_io_channel_unref(avdtp_server); + avdtp_server = NULL; +} |