bluez/health/mcap.c
2010-10-12 11:10:28 +01:00

2124 lines
47 KiB
C

/*
*
* MCAP for BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
*
* Authors:
* Santiago Carot-Nemesio <sancane at gmail.com>
* Jose Antonio Santos-Cadenas <santoscadenas at gmail.com>
*
* 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
*
*/
#include "log.h"
#include "error.h"
#include <netinet/in.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include "btio.h"
#include <bluetooth/bluetooth.h>
#include <bluetooth/l2cap.h>
#include "mcap.h"
#include "mcap_lib.h"
#include "mcap_internal.h"
#define RESPONSE_TIMER 6 /* seconds */
#define MAX_CACHED 10 /* 10 devices */
#define MCAP_ERROR g_quark_from_static_string("mcap-error-quark")
#define RELEASE_TIMER(__mcl) do { \
if (__mcl->tid) { \
g_source_remove(__mcl->tid); \
__mcl->tid = 0; \
} \
} while(0)
struct connect_mcl {
struct mcap_mcl *mcl; /* MCL for this operation */
mcap_mcl_connect_cb connect_cb; /* Connect callback */
GDestroyNotify destroy; /* Destroy callback */
gpointer user_data; /* Callback user data */
};
typedef union {
mcap_mdl_operation_cb op;
mcap_mdl_operation_conf_cb op_conf;
mcap_mdl_notify_cb notify;
} mcap_cb_type;
struct mcap_mdl_op_cb {
struct mcap_mdl *mdl; /* MDL for this operation */
mcap_cb_type cb; /* Operation callback */
GDestroyNotify destroy; /* Destroy callback */
gpointer user_data; /* Callback user data */
};
/* MCAP finite state machine functions */
static void proc_req_connected(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t l);
static void proc_req_pending(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t l);
static void proc_req_active(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t l);
static void (*proc_req[])(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len) = {
proc_req_connected,
proc_req_pending,
proc_req_active
};
static void mcap_cache_mcl(struct mcap_mcl *mcl);
static void default_mdl_connected_cb(struct mcap_mdl *mdl, gpointer data)
{
DBG("MCAP Unmanaged mdl connection");
}
static void default_mdl_closed_cb(struct mcap_mdl *mdl, gpointer data)
{
DBG("MCAP Unmanaged mdl closed");
}
static void default_mdl_deleted_cb(struct mcap_mdl *mdl, gpointer data)
{
DBG("MCAP Unmanaged mdl deleted");
}
static void default_mdl_aborted_cb(struct mcap_mdl *mdl, gpointer data)
{
DBG("MCAP Unmanaged mdl aborted");
}
static uint8_t default_mdl_conn_req_cb(struct mcap_mcl *mcl,
uint8_t mdepid, uint16_t mdlid,
uint8_t *conf, gpointer data)
{
DBG("MCAP mdl remote connection aborted");
/* Due to this callback isn't managed this request won't be supported */
return MCAP_REQUEST_NOT_SUPPORTED;
}
static uint8_t default_mdl_reconn_req_cb(struct mcap_mdl *mdl,
gpointer data)
{
DBG("MCAP mdl remote reconnection aborted");
/* Due to this callback isn't managed this request won't be supported */
return MCAP_REQUEST_NOT_SUPPORTED;
}
static void set_default_cb(struct mcap_mcl *mcl)
{
if (!mcl->cb)
mcl->cb = g_new0(struct mcap_mdl_cb, 1);
mcl->cb->mdl_connected = default_mdl_connected_cb;
mcl->cb->mdl_closed = default_mdl_closed_cb;
mcl->cb->mdl_deleted = default_mdl_deleted_cb;
mcl->cb->mdl_aborted = default_mdl_aborted_cb;
mcl->cb->mdl_conn_req = default_mdl_conn_req_cb;
mcl->cb->mdl_reconn_req = default_mdl_reconn_req_cb;
}
static char *error2str(uint8_t rc)
{
switch (rc) {
case MCAP_SUCCESS:
return "Success";
case MCAP_INVALID_OP_CODE:
return "Invalid Op Code";
case MCAP_INVALID_PARAM_VALUE:
return "Invalid Parameter Value";
case MCAP_INVALID_MDEP:
return "Invalid MDEP";
case MCAP_MDEP_BUSY:
return "MDEP Busy";
case MCAP_INVALID_MDL:
return "Invalid MDL";
case MCAP_MDL_BUSY:
return "MDL Busy";
case MCAP_INVALID_OPERATION:
return "Invalid Operation";
case MCAP_RESOURCE_UNAVAILABLE:
return "Resource Unavailable";
case MCAP_UNSPECIFIED_ERROR:
return "Unspecified Error";
case MCAP_REQUEST_NOT_SUPPORTED:
return "Request Not Supported";
case MCAP_CONFIGURATION_REJECTED:
return "Configuration Rejected";
default:
return "Unknown Response Code";
}
}
static gboolean mcap_send_std_opcode(struct mcap_mcl *mcl, void *cmd,
uint32_t size, GError **err)
{
if (mcl->state == MCL_IDLE) {
g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
"MCL is not connected");
return FALSE;
}
if (mcl->req != MCL_AVAILABLE) {
g_set_error(err, MCAP_ERROR, MCAP_ERROR_RESOURCE_UNAVAILABLE,
"Pending request");
return FALSE;
}
if (!(mcl->ctrl & MCAP_CTRL_STD_OP)) {
g_set_error(err, MCAP_ERROR, MCAP_ERROR_REQUEST_NOT_SUPPORTED,
"Remote does not support standard opcodes");
return FALSE;
}
if (mcl->state == MCL_PENDING) {
g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_OPERATION,
"Not Std Op. Codes can be sent in PENDING State");
return FALSE;
}
if (mcap_send_data(g_io_channel_unix_get_fd(mcl->cc), cmd, size) < 0) {
g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
"Command can't be sent, write error");
return FALSE;
}
mcl->lcmd = cmd;
mcl->req = MCL_WAITING_RSP;
return TRUE;
}
static void update_mcl_state(struct mcap_mcl *mcl)
{
GSList *l;
struct mcap_mdl *mdl;
if (mcl->state == MCL_PENDING)
return;
for (l = mcl->mdls; l; l = l->next) {
mdl = l->data;
if (mdl->state == MDL_CONNECTED) {
mcl->state = MCL_ACTIVE;
return;
}
}
mcl->state = MCL_CONNECTED;
}
static void shutdown_mdl(struct mcap_mdl *mdl)
{
mdl->state = MDL_CLOSED;
if (mdl->wid) {
g_source_remove(mdl->wid);
mdl->wid = 0;
}
if (mdl->dc) {
g_io_channel_shutdown(mdl->dc, TRUE, NULL);
g_io_channel_unref(mdl->dc);
mdl->dc = NULL;
}
}
static void free_mdl(struct mcap_mdl *mdl)
{
if (!mdl)
return;
mcap_mcl_unref(mdl->mcl);
g_free(mdl);
}
static gint cmp_mdl_state(gconstpointer a, gconstpointer b)
{
const struct mcap_mdl *mdl = a;
const MDLState *st = b;
if (mdl->state == *st)
return 0;
else if (mdl->state < *st)
return -1;
else
return 1;
}
static void free_mcl_priv_data(struct mcap_mcl *mcl)
{
struct mcap_mdl_op_cb *op = mcl->priv_data;
if (op->destroy)
op->destroy(op->user_data);
g_free(mcl->priv_data);
mcl->priv_data = NULL;
}
static void mcap_notify_error(struct mcap_mcl *mcl, GError *err)
{
struct mcap_mdl_op_cb *con = mcl->priv_data;
struct mcap_mdl *mdl;
MDLState st;
GSList *l;
if (!con || !mcl->lcmd)
return;
switch (mcl->lcmd[0]) {
case MCAP_MD_CREATE_MDL_REQ:
st = MDL_WAITING;
l = g_slist_find_custom(mcl->mdls, &st, cmp_mdl_state);
mdl = l->data;
mcl->mdls = g_slist_remove(mcl->mdls, mdl);
free_mdl(mdl);
update_mcl_state(mcl);
con->cb.op_conf(NULL, 0, err, con->user_data);
break;
case MCAP_MD_ABORT_MDL_REQ:
st = MDL_WAITING;
l = g_slist_find_custom(mcl->mdls, &st, cmp_mdl_state);
shutdown_mdl(l->data);
update_mcl_state(mcl);
con->cb.notify(err, con->user_data);
break;
case MCAP_MD_DELETE_MDL_REQ:
for (l = mcl->mdls; l; l = l->next) {
mdl = l->data;
if (mdl->state == MDL_DELETING)
mdl->state = (mdl->dc) ? MDL_CONNECTED :
MDL_CLOSED;
}
update_mcl_state(mcl);
con->cb.notify(err, con->user_data);
break;
case MCAP_MD_RECONNECT_MDL_REQ:
st = MDL_WAITING;
l = g_slist_find_custom(mcl->mdls, &st, cmp_mdl_state);
shutdown_mdl(l->data);
update_mcl_state(mcl);
con->cb.op(NULL, err, con->user_data);
break;
}
free_mcl_priv_data(mcl);
g_free(mcl->lcmd);
mcl->lcmd = NULL;
}
int mcap_send_data(int sock, const void *buf, uint32_t size)
{
const uint8_t *buf_b = buf;
uint32_t sent = 0;
while (sent < size) {
int n = write(sock, buf_b + sent, size - sent);
if (n < 0)
return -1;
sent += n;
}
return 0;
}
static int mcap_send_cmd(struct mcap_mcl *mcl, uint8_t oc, uint8_t rc,
uint16_t mdl, uint8_t *data, size_t len)
{
mcap_rsp *cmd;
int sock, sent;
if (mcl->cc == NULL)
return -1;
sock = g_io_channel_unix_get_fd(mcl->cc);
cmd = g_malloc(sizeof(mcap_rsp) + len);
cmd->op = oc;
cmd->rc = rc;
cmd->mdl = htons(mdl);
if (data && len > 0)
memcpy(cmd->data, data, len);
sent = mcap_send_data(sock, cmd, sizeof(mcap_rsp) + len);
g_free(cmd);
return sent;
}
static struct mcap_mdl *get_mdl(struct mcap_mcl *mcl, uint16_t mdlid)
{
GSList *l;
struct mcap_mdl *mdl;
for (l = mcl->mdls; l; l = l->next) {
mdl = l->data;
if (mdlid == mdl->mdlid)
return mdl;
}
return NULL;
}
static uint16_t generate_mdlid(struct mcap_mcl *mcl)
{
uint16_t mdlid = mcl->next_mdl;
struct mcap_mdl *mdl;
do {
mdl = get_mdl(mcl, mdlid);
if (!mdl) {
mcl->next_mdl = (mdlid % MCAP_MDLID_FINAL) + 1;
return mdlid;
} else
mdlid = (mdlid % MCAP_MDLID_FINAL) + 1;
} while (mdlid != mcl->next_mdl);
/* No more mdlids availables */
return 0;
}
static mcap_md_req *create_req(uint8_t op, uint16_t mdl_id)
{
mcap_md_req *req_cmd;
req_cmd = g_new0(mcap_md_req, 1);
req_cmd->op = op;
req_cmd->mdl = htons(mdl_id);
return req_cmd;
}
static mcap_md_create_mdl_req *create_mdl_req(uint16_t mdl_id, uint8_t mdep,
uint8_t conf)
{
mcap_md_create_mdl_req *req_mdl;
req_mdl = g_new0(mcap_md_create_mdl_req, 1);
req_mdl->op = MCAP_MD_CREATE_MDL_REQ;
req_mdl->mdl = htons(mdl_id);
req_mdl->mdep = mdep;
req_mdl->conf = conf;
return req_mdl;
}
static gint compare_mdl(gconstpointer a, gconstpointer b)
{
const struct mcap_mdl *mdla = a;
const struct mcap_mdl *mdlb = b;
if (mdla->mdlid == mdlb->mdlid)
return 0;
else if (mdla->mdlid < mdlb->mdlid)
return -1;
else
return 1;
}
static gboolean wait_response_timer(gpointer data)
{
struct mcap_mcl *mcl = data;
GError *gerr = NULL;
RELEASE_TIMER(mcl);
g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_FAILED,
"Timeout waiting response");
mcap_notify_error(mcl, gerr);
g_error_free(gerr);
mcl->ms->mcl_disconnected_cb(mcl, mcl->ms->user_data);
mcap_cache_mcl(mcl);
return FALSE;
}
gboolean mcap_create_mdl(struct mcap_mcl *mcl,
uint8_t mdepid,
uint8_t conf,
mcap_mdl_operation_conf_cb connect_cb,
gpointer user_data,
GDestroyNotify destroy,
GError **err)
{
struct mcap_mdl *mdl;
struct mcap_mdl_op_cb *con;
mcap_md_create_mdl_req *cmd;
uint16_t id;
id = generate_mdlid(mcl);
if (!id) {
g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
"Not more mdlids available");
return FALSE;
}
mdl = g_new0(struct mcap_mdl, 1);
mdl->mcl = mcap_mcl_ref(mcl);
mdl->mdlid = id;
mdl->mdep_id = mdepid;
mdl->state = MDL_WAITING;
con = g_new0(struct mcap_mdl_op_cb, 1);
con->mdl = mdl;
con->cb.op_conf = connect_cb;
con->destroy = destroy;
con->user_data = user_data;
cmd = create_mdl_req(id, mdepid, conf);
if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_create_mdl_req),
err)) {
free_mdl(mdl);
g_free(con);
g_free(cmd);
return FALSE;
}
mcl->state = MCL_ACTIVE;
mcl->priv_data = con;
mcl->mdls = g_slist_insert_sorted(mcl->mdls, mdl, compare_mdl);
mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer,
mcl);
return TRUE;
}
gboolean mcap_reconnect_mdl(struct mcap_mdl *mdl,
mcap_mdl_operation_cb reconnect_cb,
gpointer user_data,
GDestroyNotify destroy,
GError **err)
{
struct mcap_mdl_op_cb *con;
struct mcap_mcl *mcl = mdl->mcl;
mcap_md_req *cmd;
if (mdl->state != MDL_CLOSED) {
g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
"MDL is not closed");
return FALSE;
}
con = g_new0(struct mcap_mdl_op_cb, 1);
cmd = create_req(MCAP_MD_RECONNECT_MDL_REQ, mdl->mdlid);
if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_req), err)) {
g_free(con);
g_free(cmd);
return FALSE;
}
mdl->state = MDL_WAITING;
con->mdl = mdl;
con->cb.op = reconnect_cb;
con->destroy = destroy;
con->user_data = user_data;
mcl->state = MCL_ACTIVE;
mcl->priv_data = con;
mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer,
mcl);
return TRUE;
}
static gboolean send_delete_req(struct mcap_mcl *mcl,
struct mcap_mdl_op_cb *con,
uint16_t mdlid,
GError **err)
{
mcap_md_req *cmd;
cmd = create_req(MCAP_MD_DELETE_MDL_REQ, mdlid);
if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_req), err)) {
g_free(cmd);
return FALSE;
}
mcl->priv_data = con;
mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer,
mcl);
return TRUE;
}
gboolean mcap_delete_all_mdls(struct mcap_mcl *mcl,
mcap_mdl_notify_cb delete_cb,
gpointer user_data,
GDestroyNotify destroy,
GError **err)
{
GSList *l;
struct mcap_mdl *mdl;
struct mcap_mdl_op_cb *con;
DBG("MCL in state: %d", mcl->state);
if (!mcl->mdls) {
g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
"There are not MDLs created");
return FALSE;
}
for (l = mcl->mdls; l; l = l->next) {
mdl = l->data;
if (mdl->state != MDL_WAITING)
mdl->state = MDL_DELETING;
}
con = g_new0(struct mcap_mdl_op_cb, 1);
con->mdl = NULL;
con->cb.notify = delete_cb;
con->destroy = destroy;
con->user_data = user_data;
if (!send_delete_req(mcl, con, MCAP_ALL_MDLIDS, err)) {
g_free(con);
return FALSE;
}
return TRUE;
}
gboolean mcap_delete_mdl(struct mcap_mdl *mdl, mcap_mdl_notify_cb delete_cb,
gpointer user_data,
GDestroyNotify destroy,
GError **err)
{
struct mcap_mcl *mcl= mdl->mcl;
struct mcap_mdl_op_cb *con;
GSList *l;
l = g_slist_find(mcl->mdls, mdl);
if (!l) {
g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_MDL,
"%s" , error2str(MCAP_INVALID_MDEP));
return FALSE;
}
if (mdl->state == MDL_WAITING) {
g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
"Mdl is not created");
return FALSE;
}
mdl->state = MDL_DELETING;
con = g_new0(struct mcap_mdl_op_cb, 1);
con->mdl = mdl;
con->cb.notify = delete_cb;
con->destroy = destroy;
con->user_data = user_data;
if (!send_delete_req(mcl, con, mdl->mdlid, err)) {
g_free(con);
return FALSE;
}
return TRUE;
}
gboolean mcap_mdl_abort(struct mcap_mdl *mdl, mcap_mdl_notify_cb abort_cb,
gpointer user_data,
GDestroyNotify destroy,
GError **err)
{
struct mcap_mdl_op_cb *con;
struct mcap_mcl *mcl = mdl->mcl;
mcap_md_req *cmd;
if (mdl->state != MDL_WAITING) {
g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
"Mdl in invalid state");
return FALSE;
}
con = g_new0(struct mcap_mdl_op_cb, 1);
cmd = create_req(MCAP_MD_ABORT_MDL_REQ, mdl->mdlid);
if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_req), err)) {
g_free(con);
g_free(cmd);
return FALSE;
}
con->mdl = mdl;
con->cb.notify = abort_cb;
con->destroy = destroy;
con->user_data = user_data;
mcl->priv_data = con;
mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer,
mcl);
return TRUE;
}
static struct mcap_mcl *find_mcl(GSList *list, const bdaddr_t *addr)
{
GSList *l;
struct mcap_mcl *mcl;
for (l = list; l; l = l->next) {
mcl = l->data;
if (!bacmp(&mcl->addr, addr))
return mcl;
}
return NULL;
}
int mcap_mdl_get_fd(struct mcap_mdl *mdl)
{
if (!mdl || mdl->state != MDL_CONNECTED)
return -ENOTCONN;
return g_io_channel_unix_get_fd(mdl->dc);
}
uint16_t mcap_mdl_get_mdlid(struct mcap_mdl *mdl)
{
if (!mdl)
return MCAP_MDLID_RESERVED;
return mdl->mdlid;
}
static void close_mcl(struct mcap_mcl *mcl, gboolean cache_requested)
{
gboolean save = ((!(mcl->ctrl & MCAP_CTRL_FREE)) && cache_requested);
RELEASE_TIMER(mcl);
if (mcl->cc) {
g_io_channel_shutdown(mcl->cc, TRUE, NULL);
g_io_channel_unref(mcl->cc);
mcl->cc = NULL;
}
if (mcl->wid) {
g_source_remove(mcl->wid);
mcl->wid = 0;
}
if (mcl->lcmd) {
g_free(mcl->lcmd);
mcl->lcmd = NULL;
}
if (mcl->priv_data)
free_mcl_priv_data(mcl);
g_slist_foreach(mcl->mdls, (GFunc) shutdown_mdl, NULL);
mcap_sync_stop(mcl);
mcl->state = MCL_IDLE;
if (save)
return;
g_slist_foreach(mcl->mdls, (GFunc) free_mdl, NULL);
g_slist_free(mcl->mdls);
mcl->mdls = NULL;
}
static void mcap_mcl_shutdown(struct mcap_mcl *mcl)
{
close_mcl(mcl, TRUE);
}
static void mcap_mcl_release(struct mcap_mcl *mcl)
{
close_mcl(mcl, FALSE);
}
static void mcap_cache_mcl(struct mcap_mcl *mcl)
{
GSList *l;
struct mcap_mcl *last;
int len;
if (mcl->ctrl & MCAP_CTRL_CACHED)
return;
mcl->ms->mcls = g_slist_remove(mcl->ms->mcls, mcl);
if (mcl->ctrl & MCAP_CTRL_NOCACHE) {
mcl->ms->cached = g_slist_remove(mcl->ms->cached, mcl);
mcap_mcl_release(mcl);
mcap_mcl_unref(mcl);
return;
}
DBG("Caching MCL");
len = g_slist_length(mcl->ms->cached);
if (len == MAX_CACHED) {
/* Remove the latest cached mcl */
l = g_slist_last(mcl->ms->cached);
last = l->data;
mcl->ms->cached = g_slist_remove(mcl->ms->cached, last);
last->ctrl &= ~MCAP_CTRL_CACHED;
if (last->ctrl & MCAP_CTRL_CONN) {
/* We have to release this MCL if */
/* connection is not succesful */
last->ctrl |= MCAP_CTRL_FREE;
} else {
mcap_mcl_release(last);
last->ms->mcl_uncached_cb(last, last->ms->user_data);
}
mcap_mcl_unref(last);
}
mcl->ms->cached = g_slist_prepend(mcl->ms->cached, mcl);
mcl->ctrl |= MCAP_CTRL_CACHED;
mcap_mcl_shutdown(mcl);
}
static void mcap_uncache_mcl(struct mcap_mcl *mcl)
{
if (!(mcl->ctrl & MCAP_CTRL_CACHED))
return;
DBG("Got MCL from cache");
mcl->ms->cached = g_slist_remove(mcl->ms->cached, mcl);
mcl->ms->mcls = g_slist_prepend(mcl->ms->mcls, mcl);
mcl->ctrl &= ~MCAP_CTRL_CACHED;
mcl->ctrl &= ~MCAP_CTRL_FREE;
}
void mcap_close_mcl(struct mcap_mcl *mcl, gboolean cache)
{
if (!mcl)
return;
if (mcl->ctrl & MCAP_CTRL_FREE) {
mcap_mcl_release(mcl);
return;
}
if (!cache)
mcl->ctrl |= MCAP_CTRL_NOCACHE;
if (mcl->cc) {
g_io_channel_shutdown(mcl->cc, TRUE, NULL);
g_io_channel_unref(mcl->cc);
mcl->cc = NULL;
mcl->state = MCL_IDLE;
} else if ((mcl->ctrl & MCAP_CTRL_CACHED) &&
(mcl->ctrl & MCAP_CTRL_NOCACHE)) {
mcl->ctrl &= ~MCAP_CTRL_CACHED;
mcl->ms->cached = g_slist_remove(mcl->ms->cached, mcl);
mcap_mcl_release(mcl);
mcap_mcl_unref(mcl);
}
}
struct mcap_mcl *mcap_mcl_ref(struct mcap_mcl *mcl)
{
mcl->ref++;
DBG("mcap_mcl_ref(%p): ref=%d", mcl, mcl->ref);
return mcl;
}
void mcap_mcl_unref(struct mcap_mcl *mcl)
{
mcl->ref--;
DBG("mcap_mcl_unref(%p): ref=%d", mcl, mcl->ref);
if (mcl->ref > 0)
return;
mcap_mcl_release(mcl);
g_free(mcl->cb);
g_free(mcl);
}
static gboolean parse_set_opts(struct mcap_mdl_cb *mdl_cb, GError **err,
McapMclCb cb1, va_list args)
{
McapMclCb cb = cb1;
struct mcap_mdl_cb *c;
c = g_new0(struct mcap_mdl_cb, 1);
while (cb != MCAP_MDL_CB_INVALID) {
switch (cb) {
case MCAP_MDL_CB_CONNECTED:
c->mdl_connected = va_arg(args, mcap_mdl_event_cb);
break;
case MCAP_MDL_CB_CLOSED:
c->mdl_closed = va_arg(args, mcap_mdl_event_cb);
break;
case MCAP_MDL_CB_DELETED:
c->mdl_deleted = va_arg(args, mcap_mdl_event_cb);
break;
case MCAP_MDL_CB_ABORTED:
c->mdl_aborted = va_arg(args, mcap_mdl_event_cb);
break;
case MCAP_MDL_CB_REMOTE_CONN_REQ:
c->mdl_conn_req = va_arg(args,
mcap_remote_mdl_conn_req_cb);
break;
case MCAP_MDL_CB_REMOTE_RECONN_REQ:
c->mdl_reconn_req = va_arg(args,
mcap_remote_mdl_reconn_req_cb);
break;
default:
g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
"Unknown option %d", cb);
return FALSE;
}
cb = va_arg(args, int);
}
/* Set new callbacks */
if (c->mdl_connected)
mdl_cb->mdl_connected = c->mdl_connected;
if (c->mdl_closed)
mdl_cb->mdl_closed = c->mdl_closed;
if (c->mdl_deleted)
mdl_cb->mdl_deleted = c->mdl_deleted;
if (c->mdl_aborted)
mdl_cb->mdl_aborted = c->mdl_aborted;
if (c->mdl_conn_req)
mdl_cb->mdl_conn_req = c->mdl_conn_req;
if (c->mdl_reconn_req)
mdl_cb->mdl_reconn_req = c->mdl_reconn_req;
g_free(c);
return TRUE;
}
gboolean mcap_mcl_set_cb(struct mcap_mcl *mcl, gpointer user_data,
GError **gerr, McapMclCb cb1, ...)
{
va_list args;
gboolean ret;
va_start(args, cb1);
ret = parse_set_opts(mcl->cb, gerr, cb1, args);
va_end(args);
if (!ret)
return FALSE;
mcl->cb->user_data = user_data;
return TRUE;
}
void mcap_mcl_get_addr(struct mcap_mcl *mcl, bdaddr_t *addr)
{
bacpy(addr, &mcl->addr);
}
static void mcap_del_mdl(gpointer elem, gpointer user_data)
{
struct mcap_mdl *mdl = elem;
gboolean notify = *(gboolean *) user_data;
shutdown_mdl(mdl);
if (notify)
mdl->mcl->cb->mdl_deleted(mdl, mdl->mcl->cb->user_data);
free_mdl(mdl);
}
static gboolean check_cmd_req_length(struct mcap_mcl *mcl, void *cmd,
uint32_t rlen, uint32_t explen, uint8_t rspcod)
{
mcap_md_req *req;
uint16_t mdl_id;
if (rlen != explen) {
if (rlen >= sizeof(mcap_md_req)) {
req = cmd;
mdl_id = ntohs(req->mdl);
} else {
/* We can't get mdlid */
mdl_id = MCAP_MDLID_RESERVED;
}
mcap_send_cmd(mcl, rspcod, MCAP_INVALID_PARAM_VALUE, mdl_id,
NULL, 0);
return FALSE;
}
return TRUE;
}
static void process_md_create_mdl_req(struct mcap_mcl *mcl, void *cmd,
uint32_t len)
{
mcap_md_create_mdl_req *req;
struct mcap_mdl *mdl;
uint16_t mdl_id;
uint8_t mdep_id;
uint8_t cfga, conf;
uint8_t rsp;
if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_create_mdl_req),
MCAP_MD_CREATE_MDL_RSP))
return;
req = cmd;
mdl_id = ntohs(req->mdl);
if (mdl_id < MCAP_MDLID_INITIAL || mdl_id > MCAP_MDLID_FINAL) {
mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_INVALID_MDL,
mdl_id, NULL, 0);
return;
}
mdep_id = req->mdep;
if (mdep_id > MCAP_MDEPID_FINAL) {
mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_INVALID_MDEP,
mdl_id, NULL, 0);
return;
}
mdl = get_mdl(mcl, mdl_id);
if (mdl && (mdl->state == MDL_WAITING || mdl->state == MDL_DELETING )) {
/* Creation request arrives for a MDL that is being managed
* at current moment */
mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_MDL_BUSY,
mdl_id, NULL, 0);
return;
}
cfga = conf = req->conf;
/* Callback to upper layer */
rsp = mcl->cb->mdl_conn_req(mcl, mdep_id, mdl_id, &conf,
mcl->cb->user_data);
if (mcl->state == MCL_IDLE) {
/* MCL has been closed int the callback */
return;
}
if (cfga != 0 && cfga != conf) {
/* Remote device set default configuration but upper profile */
/* has changed it. Protocol Error: force closing the MCL by */
/* remote device using UNSPECIFIED_ERROR response */
mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP,
MCAP_UNSPECIFIED_ERROR, mdl_id, NULL, 0);
return;
}
if (rsp != MCAP_SUCCESS) {
mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, rsp, mdl_id,
NULL, 0);
return;
}
if (!mdl) {
mdl = g_new0(struct mcap_mdl, 1);
mdl->mcl = mcap_mcl_ref(mcl);
mdl->mdlid = mdl_id;
mcl->mdls = g_slist_insert_sorted(mcl->mdls, mdl, compare_mdl);
} else if (mdl->state == MDL_CONNECTED) {
/* MCAP specification says that we should close the MCL if
* it is open when we receive a MD_CREATE_MDL_REQ */
shutdown_mdl(mdl);
}
mdl->mdep_id = mdep_id;
mdl->state = MDL_WAITING;
mcl->state = MCL_PENDING;
mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_SUCCESS, mdl_id,
&conf, 1);
}
static void process_md_reconnect_mdl_req(struct mcap_mcl *mcl, void *cmd,
uint32_t len)
{
mcap_md_req *req;
struct mcap_mdl *mdl;
uint16_t mdl_id;
uint8_t rsp;
if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_req),
MCAP_MD_RECONNECT_MDL_RSP))
return;
req = cmd;
mdl_id = ntohs(req->mdl);
mdl = get_mdl(mcl, mdl_id);
if (!mdl) {
mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, MCAP_INVALID_MDL,
mdl_id, NULL, 0);
return;
} else if (mdl->state == MDL_WAITING || mdl->state == MDL_DELETING ) {
/* Creation request arrives for a MDL that is being managed
* at current moment */
mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, MCAP_MDL_BUSY,
mdl_id, NULL, 0);
return;
}
/* Callback to upper layer */
rsp = mcl->cb->mdl_reconn_req(mdl, mcl->cb->user_data);
if (mcl->state == MCL_IDLE)
return;
if (rsp != MCAP_SUCCESS) {
mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, rsp, mdl_id,
NULL, 0);
return;
}
if (mdl->state == MDL_CONNECTED)
shutdown_mdl(mdl);
mdl->state = MDL_WAITING;
mcl->state = MCL_PENDING;
mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, MCAP_SUCCESS, mdl_id,
NULL, 0);
}
static void process_md_abort_mdl_req(struct mcap_mcl *mcl, void *cmd,
uint32_t len)
{
mcap_md_req *req;
GSList *l;
struct mcap_mdl *mdl, *abrt;
uint16_t mdl_id;
if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_req),
MCAP_MD_ABORT_MDL_RSP))
return;
req = cmd;
mdl_id = ntohs(req->mdl);
mcl->state = MCL_CONNECTED;
for (l = mcl->mdls; l; l = l->next) {
mdl = l->data;
if (mdl_id == mdl->mdlid && mdl->state == MDL_WAITING) {
abrt = mdl;
if (mcl->state != MCL_CONNECTED)
break;
continue;
}
if (mdl->state == MDL_CONNECTED && mcl->state != MCL_ACTIVE)
mcl->state = MCL_ACTIVE;
if (abrt && mcl->state == MCL_ACTIVE)
break;
}
if (!abrt) {
mcap_send_cmd(mcl, MCAP_MD_ABORT_MDL_RSP, MCAP_INVALID_MDL,
mdl_id, NULL, 0);
return;
}
mcl->cb->mdl_aborted(abrt, mcl->cb->user_data);
abrt->state = MDL_CLOSED;
mcap_send_cmd(mcl, MCAP_MD_ABORT_MDL_RSP, MCAP_SUCCESS, mdl_id,
NULL, 0);
}
static void process_md_delete_mdl_req(struct mcap_mcl *mcl, void *cmd,
uint32_t len)
{
mcap_md_req *req;
struct mcap_mdl *mdl, *aux;
uint16_t mdlid;
gboolean notify;
GSList *l;
if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_req),
MCAP_MD_DELETE_MDL_RSP))
return;
req = cmd;
mdlid = ntohs(req->mdl);
if (mdlid == MCAP_ALL_MDLIDS) {
notify = FALSE;
g_slist_foreach(mcl->mdls, mcap_del_mdl, &notify);
g_slist_free(mcl->mdls);
mcl->mdls = NULL;
mcl->state = MCL_CONNECTED;
/* NULL mdl means ALL_MDLS */
mcl->cb->mdl_deleted(NULL, mcl->cb->user_data);
goto resp;
}
if (mdlid < MCAP_MDLID_INITIAL || mdlid > MCAP_MDLID_FINAL) {
mcap_send_cmd(mcl, MCAP_MD_DELETE_MDL_RSP, MCAP_INVALID_MDL,
mdlid, NULL, 0);
return;
}
for (l = mcl->mdls, mdl = NULL; l; l = l->next) {
aux = l->data;
if (aux->mdlid == mdlid) {
mdl = aux;
break;
}
}
if (!mdl || mdl->state == MDL_WAITING) {
mcap_send_cmd(mcl, MCAP_MD_DELETE_MDL_RSP, MCAP_INVALID_MDL,
mdlid, NULL, 0);
return;
}
mcl->mdls = g_slist_remove(mcl->mdls, mdl);
update_mcl_state(mcl);
notify = TRUE;
mcap_del_mdl(mdl, &notify);
resp:
mcap_send_cmd(mcl, MCAP_MD_DELETE_MDL_RSP, MCAP_SUCCESS, mdlid,
NULL, 0);
}
static void invalid_req_state(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
{
uint16_t mdlr;
error("Invalid cmd received (op code = %d) in state %d", cmd[0],
mcl->state);
/* Get previously mdlid sent to generate an appropriate
* response if it is possible */
mdlr = len < sizeof(mcap_md_req) ? MCAP_MDLID_RESERVED :
ntohs(((mcap_md_req *) cmd)->mdl);
mcap_send_cmd(mcl, cmd[0]+1, MCAP_INVALID_OPERATION, mdlr, NULL, 0);
}
/* Function used to process commands depending of MCL state */
static void proc_req_connected(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
{
switch (cmd[0]) {
case MCAP_MD_CREATE_MDL_REQ:
process_md_create_mdl_req(mcl, cmd, len);
break;
case MCAP_MD_RECONNECT_MDL_REQ:
process_md_reconnect_mdl_req(mcl, cmd, len);
break;
case MCAP_MD_DELETE_MDL_REQ:
process_md_delete_mdl_req(mcl, cmd, len);
break;
default:
invalid_req_state(mcl, cmd, len);
}
}
static void proc_req_pending(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
{
if (cmd[0] == MCAP_MD_ABORT_MDL_REQ)
process_md_abort_mdl_req(mcl, cmd, len);
else
invalid_req_state(mcl, cmd, len);
}
static void proc_req_active(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
{
switch (cmd[0]) {
case MCAP_MD_CREATE_MDL_REQ:
process_md_create_mdl_req(mcl, cmd, len);
break;
case MCAP_MD_RECONNECT_MDL_REQ:
process_md_reconnect_mdl_req(mcl, cmd, len);
break;
case MCAP_MD_DELETE_MDL_REQ:
process_md_delete_mdl_req(mcl, cmd, len);
break;
default:
invalid_req_state(mcl, cmd, len);
}
}
/* Function used to process replies */
static gboolean check_err_rsp(struct mcap_mcl *mcl, mcap_rsp *rsp,
uint32_t rlen, uint32_t len, GError **gerr)
{
mcap_md_req *cmdlast = (mcap_md_req *) mcl->lcmd;
gint err = MCAP_ERROR_FAILED;
gboolean close = FALSE;
char *msg;
if (rsp->op == MCAP_ERROR_RSP) {
msg = "MCAP_ERROR_RSP received";
close = FALSE;
goto fail;
}
/* Check if the response matches with the last request */
if (rlen < sizeof(mcap_rsp) || (mcl->lcmd[0] + 1) != rsp->op) {
msg = "Protocol error";
close = FALSE;
goto fail;
}
if (rlen < len) {
msg = "Protocol error";
close = FALSE;
goto fail;
}
if (rsp->mdl != cmdlast->mdl) {
msg = "MDLID received doesn't match with MDLID sent";
close = TRUE;
goto fail;
}
if (rsp->rc == MCAP_REQUEST_NOT_SUPPORTED) {
msg = "Remote does not support opcodes";
mcl->ctrl &= ~MCAP_CTRL_STD_OP;
goto fail;
}
if (rsp->rc == MCAP_UNSPECIFIED_ERROR) {
msg = "Unspecified error";
close = TRUE;
goto fail;
}
if (rsp->rc != MCAP_SUCCESS) {
msg = error2str(rsp->rc);
err = rsp->rc;
goto fail;
}
return FALSE;
fail:
g_set_error(gerr, MCAP_ERROR, err, "%s", msg);
return close;
}
static gboolean process_md_create_mdl_rsp(struct mcap_mcl *mcl,
mcap_rsp *rsp, uint32_t len)
{
mcap_md_create_mdl_req *cmdlast = (mcap_md_create_mdl_req *) mcl->lcmd;
struct mcap_mdl_op_cb *conn = mcl->priv_data;
mcap_mdl_operation_conf_cb connect_cb = conn->cb.op_conf;
gpointer user_data = conn->user_data;
struct mcap_mdl *mdl = conn->mdl;
uint8_t conf = cmdlast->conf;
gboolean close;
GError *gerr = NULL;
close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp) + 1, &gerr);
g_free(mcl->lcmd);
mcl->lcmd = NULL;
mcl->req = MCL_AVAILABLE;
if (gerr)
goto fail;
/* Check if preferences changed */
if (conf != 0x00 && rsp->data[0] != conf) {
g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_FAILED,
"Configuration changed");
close = TRUE;
goto fail;
}
connect_cb(mdl, rsp->data[0], gerr, user_data);
return close;
fail:
connect_cb(NULL, 0, gerr, user_data);
mcl->mdls = g_slist_remove(mcl->mdls, mdl);
free_mdl(mdl);
g_error_free(gerr);
update_mcl_state(mcl);
return close;
}
static gboolean process_md_reconnect_mdl_rsp(struct mcap_mcl *mcl,
mcap_rsp *rsp, uint32_t len)
{
struct mcap_mdl_op_cb *reconn = mcl->priv_data;
mcap_mdl_operation_cb reconn_cb = reconn->cb.op;
gpointer user_data = reconn->user_data;
struct mcap_mdl *mdl = reconn->mdl;
GError *gerr = NULL;
gboolean close;
close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp), &gerr);
g_free(mcl->lcmd);
mcl->lcmd = NULL;
mcl->req = MCL_AVAILABLE;
reconn_cb(mdl, gerr, user_data);
if (!gerr)
return close;
g_error_free(gerr);
shutdown_mdl(mdl);
update_mcl_state(mcl);
if (rsp->rc != MCAP_INVALID_MDL)
return close;
/* Remove cached mdlid */
mcl->mdls = g_slist_remove(mcl->mdls, mdl);
mcl->cb->mdl_deleted(mdl, mcl->cb->user_data);
free_mdl(mdl);
return close;
}
static gboolean process_md_abort_mdl_rsp(struct mcap_mcl *mcl,
mcap_rsp *rsp, uint32_t len)
{
struct mcap_mdl_op_cb *abrt = mcl->priv_data;
mcap_mdl_notify_cb abrt_cb = abrt->cb.notify;
gpointer user_data = abrt->user_data;
struct mcap_mdl *mdl = abrt->mdl;
GError *gerr = NULL;
gboolean close;
close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp), &gerr);
g_free(mcl->lcmd);
mcl->lcmd = NULL;
mcl->req = MCL_AVAILABLE;
abrt_cb(gerr, user_data);
shutdown_mdl(mdl);
if (len >= sizeof(mcap_rsp) && rsp->rc == MCAP_INVALID_MDL) {
mcl->mdls = g_slist_remove(mcl->mdls, mdl);
free_mdl(mdl);
}
if (gerr)
g_error_free(gerr);
update_mcl_state(mcl);
return close;
}
static void restore_mdl(gpointer elem, gpointer data)
{
struct mcap_mdl *mdl = elem;
if (mdl->state == MDL_DELETING) {
if (mdl->dc)
mdl->state = MDL_CONNECTED;
else
mdl->state = MDL_CLOSED;
} else if (mdl->state == MDL_CLOSED)
mdl->mcl->cb->mdl_closed(mdl, mdl->mcl->cb->user_data);
}
static gboolean process_md_delete_mdl_rsp(struct mcap_mcl *mcl, mcap_rsp *rsp,
uint32_t len)
{
struct mcap_mdl_op_cb *del = mcl->priv_data;
struct mcap_mdl *mdl = del->mdl;
mcap_mdl_notify_cb deleted_cb = del->cb.notify;
gpointer user_data = del->user_data;
mcap_md_req *cmdlast = (mcap_md_req *) mcl->lcmd;
uint16_t mdlid = ntohs(cmdlast->mdl);
GError *gerr = NULL;
gboolean close;
gboolean notify = FALSE;
close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp), &gerr);
g_free(mcl->lcmd);
mcl->lcmd = NULL;
mcl->req = MCL_AVAILABLE;
if (gerr) {
if (mdl)
restore_mdl(mdl, NULL);
else
g_slist_foreach(mcl->mdls, restore_mdl, NULL);
deleted_cb(gerr, user_data);
g_error_free(gerr);
return close;
}
if (mdlid == MCAP_ALL_MDLIDS) {
g_slist_foreach(mcl->mdls, mcap_del_mdl, &notify);
g_slist_free(mcl->mdls);
mcl->mdls = NULL;
mcl->state = MCL_CONNECTED;
} else {
mcl->mdls = g_slist_remove(mcl->mdls, mdl);
update_mcl_state(mcl);
mcap_del_mdl(mdl, &notify);
}
deleted_cb(gerr, user_data);
return close;
}
static void post_process_rsp(struct mcap_mcl *mcl, struct mcap_mdl_op_cb *op)
{
if (op->destroy)
op->destroy(op->user_data);
if (mcl->priv_data != op) {
/* Queued MCAP request in some callback. */
/* We sould not delete the mcl private data */
g_free(op);
} else {
/* This is not queued requets. It's safe */
/* delete the mcl private data here. */
g_free(mcl->priv_data);
mcl->priv_data = NULL;
}
}
static void proc_response(struct mcap_mcl *mcl, void *buf, uint32_t len)
{
struct mcap_mdl_op_cb *op = mcl->priv_data;
mcap_rsp *rsp = buf;
gboolean close;
RELEASE_TIMER(mcl);
switch (mcl->lcmd[0] + 1) {
case MCAP_MD_CREATE_MDL_RSP:
close = process_md_create_mdl_rsp(mcl, rsp, len);
post_process_rsp(mcl, op);
break;
case MCAP_MD_RECONNECT_MDL_RSP:
close = process_md_reconnect_mdl_rsp(mcl, rsp, len);
post_process_rsp(mcl, op);
break;
case MCAP_MD_ABORT_MDL_RSP:
close = process_md_abort_mdl_rsp(mcl, rsp, len);
post_process_rsp(mcl, op);
break;
case MCAP_MD_DELETE_MDL_RSP:
close = process_md_delete_mdl_rsp(mcl, rsp, len);
post_process_rsp(mcl, op);
break;
default:
DBG("Unknown cmd response received (op code = %d)", rsp->op);
close = TRUE;
break;
}
if (close) {
mcl->ms->mcl_disconnected_cb(mcl, mcl->ms->user_data);
mcap_cache_mcl(mcl);
}
}
static void proc_cmd(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
{
GError *gerr = NULL;
if (cmd[0] > MCAP_MD_SYNC_INFO_IND ||
(cmd[0] > MCAP_MD_DELETE_MDL_RSP &&
cmd[0] < MCAP_MD_SYNC_CAP_REQ)) {
error("Unknown cmd received (op code = %d)", cmd[0]);
mcap_send_cmd(mcl, MCAP_ERROR_RSP, MCAP_INVALID_OP_CODE,
MCAP_MDLID_RESERVED, NULL, 0);
return;
}
if (cmd[0] >= MCAP_MD_SYNC_CAP_REQ &&
cmd[0] <= MCAP_MD_SYNC_INFO_IND) {
proc_sync_cmd(mcl, cmd, len);
return;
}
if (!(mcl->ctrl & MCAP_CTRL_STD_OP)) {
/* In case the remote device doesn't work correctly */
error("Remote device does not support opcodes, cmd ignored");
return;
}
if (mcl->req == MCL_WAITING_RSP) {
if (cmd[0] & 0x01) {
/* Request arrived when a response is expected */
if (mcl->role == MCL_INITIATOR)
/* ignore */
return;
/* Initiator will ignore our last request */
RELEASE_TIMER(mcl);
mcl->req = MCL_AVAILABLE;
g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_REQ_IGNORED,
"Initiator sent a request with more priority");
mcap_notify_error(mcl, gerr);
proc_req[mcl->state](mcl, cmd, len);
return;
}
proc_response(mcl, cmd, len);
} else if (cmd[0] & 0x01)
proc_req[mcl->state](mcl, cmd, len);
}
static gboolean mdl_event_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
{
struct mcap_mdl *mdl = data;
gboolean notify;
DBG("Close MDL %d", mdl->mdlid);
notify = (mdl->state == MDL_CONNECTED);
shutdown_mdl(mdl);
update_mcl_state(mdl->mcl);
if (notify) {
/*Callback to upper layer */
mdl->mcl->cb->mdl_closed(mdl, mdl->mcl->cb->user_data);
}
return FALSE;
}
static void mcap_connect_mdl_cb(GIOChannel *chan, GError *conn_err,
gpointer data)
{
struct mcap_mdl_op_cb *con = data;
struct mcap_mdl *mdl = con->mdl;
mcap_mdl_operation_cb cb = con->cb.op;
gpointer user_data = con->user_data;
DBG("mdl connect callback");
if (conn_err) {
DBG("ERROR: mdl connect callback");
mdl->state = MDL_CLOSED;
g_io_channel_unref(mdl->dc);
mdl->dc = NULL;
cb(mdl, conn_err, user_data);
return;
}
mdl->state = MDL_CONNECTED;
mdl->wid = g_io_add_watch(mdl->dc, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
(GIOFunc) mdl_event_cb, mdl);
cb(mdl, conn_err, user_data);
}
static void mdl_io_destroy(gpointer data)
{
struct mcap_mdl_op_cb *con = data;
if (con->destroy)
con->destroy(con->user_data);
g_free(con);
}
gboolean mcap_connect_mdl(struct mcap_mdl *mdl, uint8_t mode,
uint16_t dcpsm,
mcap_mdl_operation_cb connect_cb,
gpointer user_data,
GDestroyNotify destroy,
GError **err)
{
struct mcap_mdl_op_cb *con;
if (mdl->state != MDL_WAITING) {
g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_MDL,
"%s", error2str(MCAP_INVALID_MDL));
return FALSE;
}
if ((mode != L2CAP_MODE_ERTM) && (mode != L2CAP_MODE_STREAMING)) {
g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
"Invalid MDL configuration");
return FALSE;
}
con = g_new0(struct mcap_mdl_op_cb, 1);
con->mdl = mdl;
con->cb.op = connect_cb;
con->destroy = destroy;
con->user_data = user_data;
/* TODO: Check if BtIOType is ERTM or Streaming before continue */
mdl->dc = bt_io_connect(BT_IO_L2CAP, mcap_connect_mdl_cb, con,
mdl_io_destroy, err,
BT_IO_OPT_SOURCE_BDADDR, &mdl->mcl->ms->src,
BT_IO_OPT_DEST_BDADDR, &mdl->mcl->addr,
BT_IO_OPT_PSM, dcpsm,
BT_IO_OPT_MTU, MCAP_DC_MTU,
BT_IO_OPT_SEC_LEVEL, mdl->mcl->ms->sec,
BT_IO_OPT_MODE, mode,
BT_IO_OPT_INVALID);
if (!mdl->dc) {
DBG("MDL Connection error");
mdl->state = MDL_CLOSED;
g_free(con);
return FALSE;
}
return TRUE;
}
static gboolean mcl_control_cb(GIOChannel *chan, GIOCondition cond,
gpointer data)
{
GError *gerr = NULL;
struct mcap_mcl *mcl = data;
int sk, len;
uint8_t buf[MCAP_CC_MTU];
if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
goto fail;
sk = g_io_channel_unix_get_fd(chan);
len = read(sk, buf, sizeof(buf));
if (len < 0)
goto fail;
proc_cmd(mcl, buf, (uint32_t) len);
return TRUE;
fail:
if (mcl->state != MCL_IDLE) {
if (mcl->req == MCL_WAITING_RSP) {
/* notify error in pending callback */
g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_MCL_CLOSED,
"MCL closed");
mcap_notify_error(mcl, gerr);
g_error_free(gerr);
}
mcl->ms->mcl_disconnected_cb(mcl, mcl->ms->user_data);
}
mcap_cache_mcl(mcl);
return FALSE;
}
static void mcap_connect_mcl_cb(GIOChannel *chan, GError *conn_err,
gpointer user_data)
{
char dstaddr[18];
struct connect_mcl *con = user_data;
struct mcap_mcl *aux, *mcl = con->mcl;
mcap_mcl_connect_cb connect_cb = con->connect_cb;
gpointer data = con->user_data;
GError *gerr = NULL;
mcl->ctrl &= ~MCAP_CTRL_CONN;
if (conn_err) {
if (mcl->ctrl & MCAP_CTRL_FREE) {
mcap_mcl_release(mcl);
mcl->ms->mcl_uncached_cb(mcl, mcl->ms->user_data);
}
connect_cb(NULL, conn_err, data);
return;
}
ba2str(&mcl->addr, dstaddr);
aux = find_mcl(mcl->ms->mcls, &mcl->addr);
if (aux) {
/* Double MCL connection case */
error("MCL error: Device %s is already connected", dstaddr);
g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_ALREADY_EXISTS,
"MCL %s is already connected", dstaddr);
connect_cb(NULL, gerr, data);
g_error_free(gerr);
return;
}
mcl->state = MCL_CONNECTED;
mcl->role = MCL_INITIATOR;
mcl->req = MCL_AVAILABLE;
mcl->ctrl |= MCAP_CTRL_STD_OP;
mcap_sync_init(mcl);
if (mcl->ctrl & MCAP_CTRL_CACHED)
mcap_uncache_mcl(mcl);
else {
mcl->ctrl &= ~MCAP_CTRL_FREE;
mcl->ms->mcls = g_slist_prepend(mcl->ms->mcls,
mcap_mcl_ref(mcl));
}
mcl->wid = g_io_add_watch(mcl->cc,
G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
(GIOFunc) mcl_control_cb, mcl);
connect_cb(mcl, gerr, data);
}
static void set_mdl_properties(GIOChannel *chan, struct mcap_mdl *mdl)
{
struct mcap_mcl *mcl = mdl->mcl;
mdl->state = MDL_CONNECTED;
mdl->dc = g_io_channel_ref(chan);
mdl->wid = g_io_add_watch(mdl->dc, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
(GIOFunc) mdl_event_cb, mdl);
mcl->state = MCL_ACTIVE;
mcl->cb->mdl_connected(mdl, mcl->cb->user_data);
}
static void mcl_io_destroy(gpointer data)
{
struct connect_mcl *con = data;
mcap_mcl_unref(con->mcl);
if (con->destroy)
con->destroy(con->user_data);
g_free(con);
}
gboolean mcap_create_mcl(struct mcap_instance *ms,
const bdaddr_t *addr,
uint16_t ccpsm,
mcap_mcl_connect_cb connect_cb,
gpointer user_data,
GDestroyNotify destroy,
GError **err)
{
struct mcap_mcl *mcl;
struct connect_mcl *con;
mcl = find_mcl(ms->mcls, addr);
if (mcl) {
g_set_error(err, MCAP_ERROR, MCAP_ERROR_ALREADY_EXISTS,
"MCL is already connected.");
return FALSE;
}
mcl = find_mcl(ms->cached, addr);
if (!mcl) {
mcl = g_new0(struct mcap_mcl, 1);
mcl->ms = ms;
mcl->state = MCL_IDLE;
bacpy(&mcl->addr, addr);
set_default_cb(mcl);
mcl->next_mdl = (rand() % MCAP_MDLID_FINAL) + 1;
}
mcl->ctrl |= MCAP_CTRL_CONN;
con = g_new0(struct connect_mcl, 1);
con->mcl = mcap_mcl_ref(mcl);
con->connect_cb = connect_cb;
con->destroy = destroy;
con->user_data = user_data;
mcl->cc = bt_io_connect(BT_IO_L2CAP, mcap_connect_mcl_cb, con,
mcl_io_destroy, err,
BT_IO_OPT_SOURCE_BDADDR, &ms->src,
BT_IO_OPT_DEST_BDADDR, addr,
BT_IO_OPT_PSM, ccpsm,
BT_IO_OPT_MTU, MCAP_CC_MTU,
BT_IO_OPT_SEC_LEVEL, ms->sec,
BT_IO_OPT_MODE, L2CAP_MODE_ERTM,
BT_IO_OPT_INVALID);
if (!mcl->cc) {
mcl->ctrl &= ~MCAP_CTRL_CONN;
if (mcl->ctrl & MCAP_CTRL_FREE) {
mcap_mcl_release(mcl);
mcl->ms->mcl_uncached_cb(mcl, mcl->ms->user_data);
}
mcap_mcl_unref(con->mcl);
g_free(con);
return FALSE;
}
return TRUE;
}
static void connect_dc_event_cb(GIOChannel *chan, GError *gerr,
gpointer user_data)
{
struct mcap_instance *ms = user_data;
struct mcap_mcl *mcl;
struct mcap_mdl *mdl;
GError *err = NULL;
bdaddr_t dst;
GSList *l;
if (gerr)
return;
bt_io_get(chan, BT_IO_L2CAP, &err,
BT_IO_OPT_DEST_BDADDR, &dst,
BT_IO_OPT_INVALID);
if (err) {
error("%s", err->message);
g_error_free(err);
goto drop;
}
mcl = find_mcl(ms->mcls, &dst);
if (!mcl || mcl->state != MCL_PENDING)
goto drop;
for (l = mcl->mdls; l; l = l->next) {
mdl = l->data;
if (mdl->state == MDL_WAITING) {
set_mdl_properties(chan, mdl);
return;
}
}
drop:
g_io_channel_shutdown(chan, TRUE, NULL);
}
static void set_mcl_conf(GIOChannel *chan, struct mcap_mcl *mcl)
{
gboolean reconn;
mcl->state = MCL_CONNECTED;
mcl->role = MCL_ACCEPTOR;
mcl->req = MCL_AVAILABLE;
mcl->cc = g_io_channel_ref(chan);
mcl->ctrl |= MCAP_CTRL_STD_OP;
mcap_sync_init(mcl);
reconn = (mcl->ctrl & MCAP_CTRL_CACHED);
if (reconn)
mcap_uncache_mcl(mcl);
else
mcl->ms->mcls = g_slist_prepend(mcl->ms->mcls,
mcap_mcl_ref(mcl));
mcl->wid = g_io_add_watch(mcl->cc,
G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
(GIOFunc) mcl_control_cb, mcl);
/* Callback to report new MCL */
if (reconn)
mcl->ms->mcl_reconnected_cb(mcl, mcl->ms->user_data);
else
mcl->ms->mcl_connected_cb(mcl, mcl->ms->user_data);
}
static void connect_mcl_event_cb(GIOChannel *chan, GError *gerr,
gpointer user_data)
{
struct mcap_instance *ms = user_data;
struct mcap_mcl *mcl;
bdaddr_t dst;
char address[18], srcstr[18];
GError *err = NULL;
if (gerr)
return;
bt_io_get(chan, BT_IO_L2CAP, &err,
BT_IO_OPT_DEST_BDADDR, &dst,
BT_IO_OPT_DEST, address,
BT_IO_OPT_INVALID);
if (err) {
error("%s", err->message);
g_error_free(err);
goto drop;
}
ba2str(&ms->src, srcstr);
mcl = find_mcl(ms->mcls, &dst);
if (mcl) {
error("Control channel already created with %s on adapter %s",
address, srcstr);
goto drop;
}
mcl = find_mcl(ms->cached, &dst);
if (!mcl) {
mcl = g_new0(struct mcap_mcl, 1);
mcl->ms = ms;
bacpy(&mcl->addr, &dst);
set_default_cb(mcl);
mcl->next_mdl = (rand() % MCAP_MDLID_FINAL) + 1;
}
set_mcl_conf(chan, mcl);
return;
drop:
g_io_channel_shutdown(chan, TRUE, NULL);
}
struct mcap_instance *mcap_create_instance(bdaddr_t *src,
BtIOSecLevel sec,
uint16_t ccpsm,
uint16_t dcpsm,
mcap_mcl_event_cb mcl_connected,
mcap_mcl_event_cb mcl_reconnected,
mcap_mcl_event_cb mcl_disconnected,
mcap_mcl_event_cb mcl_uncached,
mcap_info_ind_event_cb mcl_sync_info_ind,
gpointer user_data,
GError **gerr)
{
struct mcap_instance *ms;
if (sec < BT_IO_SEC_MEDIUM) {
g_set_error(gerr, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
"Security level can't be minor of %d",
BT_IO_SEC_MEDIUM);
return NULL;
}
if (!(mcl_connected && mcl_reconnected &&
mcl_disconnected && mcl_uncached)) {
g_set_error(gerr, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
"The callbacks can't be null");
return NULL;
}
ms = g_new0(struct mcap_instance, 1);
bacpy(&ms->src, src);
ms->sec = sec;
ms->mcl_connected_cb = mcl_connected;
ms->mcl_reconnected_cb = mcl_reconnected;
ms->mcl_disconnected_cb = mcl_disconnected;
ms->mcl_uncached_cb = mcl_uncached;
ms->mcl_sync_infoind_cb = mcl_sync_info_ind;
ms->user_data = user_data;
ms->csp_enabled = FALSE;
/* Listen incoming connections in control channel */
ms->ccio = bt_io_listen(BT_IO_L2CAP, connect_mcl_event_cb, NULL, ms,
NULL, gerr,
BT_IO_OPT_SOURCE_BDADDR, &ms->src,
BT_IO_OPT_PSM, ccpsm,
BT_IO_OPT_MTU, MCAP_CC_MTU,
BT_IO_OPT_SEC_LEVEL, sec,
BT_IO_OPT_MODE, L2CAP_MODE_ERTM,
BT_IO_OPT_INVALID);
if (!ms->ccio) {
error("%s", (*gerr)->message);
g_free(ms);
return NULL;
}
/* Listen incoming connections in data channels */
ms->dcio = bt_io_listen(BT_IO_L2CAP, connect_dc_event_cb, NULL, ms,
NULL, gerr,
BT_IO_OPT_SOURCE_BDADDR, &ms->src,
BT_IO_OPT_PSM, dcpsm,
BT_IO_OPT_MTU, MCAP_DC_MTU,
BT_IO_OPT_SEC_LEVEL, sec,
BT_IO_OPT_INVALID);
if (!ms->dcio) {
g_io_channel_shutdown(ms->ccio, TRUE, NULL);
g_io_channel_unref(ms->ccio);
ms->ccio = NULL;
error("%s", (*gerr)->message);
g_free(ms);
return NULL;
}
/* Initialize random seed to generate mdlids for this instance */
srand(time(NULL));
return ms;
}
void mcap_release_instance(struct mcap_instance *mi)
{
GSList *l;
if (!mi)
return;
if (mi->ccio) {
g_io_channel_shutdown(mi->ccio, TRUE, NULL);
g_io_channel_unref(mi->ccio);
mi->ccio = NULL;
}
if (mi->dcio) {
g_io_channel_shutdown(mi->dcio, TRUE, NULL);
g_io_channel_unref(mi->dcio);
mi->dcio = NULL;
}
for (l = mi->mcls; l; l = l->next) {
mcap_mcl_release(l->data);
mcap_mcl_unref(l->data);
}
g_slist_free(mi->mcls);
mi->mcls = NULL;
for (l = mi->cached; l; l = l->next) {
mcap_mcl_release(l->data);
mcap_mcl_unref(l->data);
}
g_slist_free(mi->cached);
mi->cached = NULL;
g_free(mi);
}
uint16_t mcap_get_ctrl_psm(struct mcap_instance *mi, GError **err)
{
uint16_t lpsm;
if (!(mi && mi->ccio)) {
g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
"Invalid MCAP instance");
return 0;
}
if (!bt_io_get(mi->ccio, BT_IO_L2CAP, err,
BT_IO_OPT_PSM, &lpsm,
BT_IO_OPT_INVALID))
return 0;
return lpsm;
}
uint16_t mcap_get_data_psm(struct mcap_instance *mi, GError **err)
{
uint16_t lpsm;
if (!(mi && mi->dcio)) {
g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
"Invalid MCAP instance");
return 0;
}
if (!bt_io_get(mi->dcio, BT_IO_L2CAP, err,
BT_IO_OPT_PSM, &lpsm,
BT_IO_OPT_INVALID))
return 0;
return lpsm;
}