summaryrefslogtreecommitdiff
path: root/health/mcap.c
diff options
context:
space:
mode:
Diffstat (limited to 'health/mcap.c')
-rw-r--r--health/mcap.c2195
1 files changed, 2195 insertions, 0 deletions
diff --git a/health/mcap.c b/health/mcap.c
new file mode 100644
index 0000000..48866d4
--- /dev/null
+++ b/health/mcap.c
@@ -0,0 +1,2195 @@
+/*
+ *
+ * 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_mcap_mdl_op(struct mcap_mdl_op_cb *op)
+{
+ if (op->destroy)
+ op->destroy(op->user_data);
+
+ if (op->mdl)
+ mcap_mdl_unref(op->mdl);
+
+ g_free(op);
+}
+
+static void free_mcl_priv_data(struct mcap_mcl *mcl)
+{
+ free_mcap_mdl_op(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);
+ mcap_mdl_unref(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->mi->mcl_disconnected_cb(mcl, mcl->mi->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 = mcap_mdl_ref(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)) {
+ mcap_mdl_unref(con->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, mcap_mdl_ref(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;
+ }
+
+ cmd = create_req(MCAP_MD_RECONNECT_MDL_REQ, mdl->mdlid);
+ if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_req), err)) {
+ g_free(cmd);
+ return FALSE;
+ }
+
+ mdl->state = MDL_WAITING;
+
+ con = g_new0(struct mcap_mdl_op_cb, 1);
+ con->mdl = mcap_mdl_ref(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 = mcap_mdl_ref(mdl);
+ con->cb.notify = delete_cb;
+ con->destroy = destroy;
+ con->user_data = user_data;
+
+ if (!send_delete_req(mcl, con, mdl->mdlid, err)) {
+ mcap_mdl_unref(con->mdl);
+ 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;
+ }
+
+ cmd = create_req(MCAP_MD_ABORT_MDL_REQ, mdl->mdlid);
+ if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_req), err)) {
+ g_free(cmd);
+ return FALSE;
+ }
+
+ con = g_new0(struct mcap_mdl_op_cb, 1);
+ con->mdl = mcap_mdl_ref(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) mcap_mdl_unref, 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->mi->mcls = g_slist_remove(mcl->mi->mcls, mcl);
+
+ if (mcl->ctrl & MCAP_CTRL_NOCACHE) {
+ mcl->mi->cached = g_slist_remove(mcl->mi->cached, mcl);
+ mcap_mcl_release(mcl);
+ mcap_mcl_unref(mcl);
+ return;
+ }
+
+ DBG("Caching MCL");
+
+ len = g_slist_length(mcl->mi->cached);
+ if (len == MAX_CACHED) {
+ /* Remove the latest cached mcl */
+ l = g_slist_last(mcl->mi->cached);
+ last = l->data;
+ mcl->mi->cached = g_slist_remove(mcl->mi->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->mi->mcl_uncached_cb(last, last->mi->user_data);
+ }
+ mcap_mcl_unref(last);
+ }
+
+ mcl->mi->cached = g_slist_prepend(mcl->mi->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->mi->cached = g_slist_remove(mcl->mi->cached, mcl);
+ mcl->mi->mcls = g_slist_prepend(mcl->mi->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->mi->cached = g_slist_remove(mcl->mi->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);
+ mcap_instance_unref(mcl->mi);
+ 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);
+
+ mcap_mdl_unref(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, mcap_mdl_ref(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;
+ abrt = NULL;
+ 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);
+ mcap_mdl_unref(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);
+ mcap_mdl_unref(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);
+ mcl->cb->mdl_deleted(mdl, mcl->cb->user_data);
+ mcap_mdl_unref(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 void check_mdl_del_err(struct mcap_mdl *mdl, mcap_rsp *rsp)
+{
+ if (rsp->rc != MCAP_ERROR_INVALID_MDL) {
+ restore_mdl(mdl, NULL);
+ return;
+ }
+
+ /* MDL does not exist in remote side, we can delete it */
+ mdl->mcl->mdls = g_slist_remove(mdl->mcl->mdls, mdl);
+ mcap_mdl_unref(mdl);
+}
+
+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)
+ check_mdl_del_err(mdl, rsp);
+ 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 (mcl->priv_data != op) {
+ /* Queued MCAP request in some callback. */
+ /* We should not delete the mcl private data */
+ free_mcap_mdl_op(op);
+ } else {
+ /* This is not a queued request. It's safe */
+ /* delete the mcl private data here. */
+ free_mcl_priv_data(mcl);
+ }
+}
+
+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->mi->mcl_disconnected_cb(mcl, mcl->mi->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_full(mdl->dc, G_PRIORITY_DEFAULT,
+ G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) mdl_event_cb,
+ mcap_mdl_ref(mdl),
+ (GDestroyNotify) mcap_mdl_unref);
+
+ cb(mdl, conn_err, user_data);
+}
+
+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 = mcap_mdl_ref(mdl);
+ con->cb.op = connect_cb;
+ con->destroy = destroy;
+ con->user_data = user_data;
+
+ mdl->dc = bt_io_connect(BT_IO_L2CAP, mcap_connect_mdl_cb, con,
+ (GDestroyNotify) free_mcap_mdl_op, err,
+ BT_IO_OPT_SOURCE_BDADDR, &mdl->mcl->mi->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->mi->sec,
+ BT_IO_OPT_MODE, mode,
+ BT_IO_OPT_INVALID);
+ if (!mdl->dc) {
+ DBG("MDL Connection error");
+ mdl->state = MDL_CLOSED;
+ mcap_mdl_unref(con->mdl);
+ 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->mi->mcl_disconnected_cb(mcl, mcl->mi->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->mi->mcl_uncached_cb(mcl, mcl->mi->user_data);
+ }
+ connect_cb(NULL, conn_err, data);
+ return;
+ }
+
+ ba2str(&mcl->addr, dstaddr);
+
+ aux = find_mcl(mcl->mi->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->mi->mcls = g_slist_prepend(mcl->mi->mcls,
+ mcap_mcl_ref(mcl));
+ }
+
+ mcl->wid = g_io_add_watch_full(mcl->cc, G_PRIORITY_DEFAULT,
+ G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) mcl_control_cb,
+ mcap_mcl_ref(mcl),
+ (GDestroyNotify) mcap_mcl_unref);
+ 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_full(mdl->dc, G_PRIORITY_DEFAULT,
+ G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) mdl_event_cb,
+ mcap_mdl_ref(mdl),
+ (GDestroyNotify) mcap_mdl_unref);
+
+ 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 *mi,
+ 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(mi->mcls, addr);
+ if (mcl) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_ALREADY_EXISTS,
+ "MCL is already connected.");
+ return FALSE;
+ }
+
+ mcl = find_mcl(mi->cached, addr);
+ if (!mcl) {
+ mcl = g_new0(struct mcap_mcl, 1);
+ mcl->mi = mcap_instance_ref(mi);
+ 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, &mi->src,
+ BT_IO_OPT_DEST_BDADDR, addr,
+ BT_IO_OPT_PSM, ccpsm,
+ BT_IO_OPT_MTU, MCAP_CC_MTU,
+ BT_IO_OPT_SEC_LEVEL, mi->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->mi->mcl_uncached_cb(mcl, mcl->mi->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 *mi = 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(mi->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->mi->mcls = g_slist_prepend(mcl->mi->mcls,
+ mcap_mcl_ref(mcl));
+
+ mcl->wid = g_io_add_watch_full(mcl->cc, G_PRIORITY_DEFAULT,
+ G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) mcl_control_cb,
+ mcap_mcl_ref(mcl),
+ (GDestroyNotify) mcap_mcl_unref);
+
+ /* Callback to report new MCL */
+ if (reconn)
+ mcl->mi->mcl_reconnected_cb(mcl, mcl->mi->user_data);
+ else
+ mcl->mi->mcl_connected_cb(mcl, mcl->mi->user_data);
+}
+
+static void connect_mcl_event_cb(GIOChannel *chan, GError *gerr,
+ gpointer user_data)
+{
+ struct mcap_instance *mi = 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(&mi->src, srcstr);
+ mcl = find_mcl(mi->mcls, &dst);
+ if (mcl) {
+ error("Control channel already created with %s on adapter %s",
+ address, srcstr);
+ goto drop;
+ }
+
+ mcl = find_mcl(mi->cached, &dst);
+ if (!mcl) {
+ mcl = g_new0(struct mcap_mcl, 1);
+ mcl->mi = mcap_instance_ref(mi);
+ 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 *mi;
+
+ 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;
+ }
+
+ mi = g_new0(struct mcap_instance, 1);
+
+ bacpy(&mi->src, src);
+
+ mi->sec = sec;
+ mi->mcl_connected_cb = mcl_connected;
+ mi->mcl_reconnected_cb = mcl_reconnected;
+ mi->mcl_disconnected_cb = mcl_disconnected;
+ mi->mcl_uncached_cb = mcl_uncached;
+ mi->mcl_sync_infoind_cb = mcl_sync_info_ind;
+ mi->user_data = user_data;
+ mi->csp_enabled = FALSE;
+
+ /* Listen incoming connections in control channel */
+ mi->ccio = bt_io_listen(BT_IO_L2CAP, connect_mcl_event_cb, NULL, mi,
+ NULL, gerr,
+ BT_IO_OPT_SOURCE_BDADDR, &mi->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 (!mi->ccio) {
+ error("%s", (*gerr)->message);
+ g_free(mi);
+ return NULL;
+ }
+
+ /* Listen incoming connections in data channels */
+ mi->dcio = bt_io_listen(BT_IO_L2CAP, connect_dc_event_cb, NULL, mi,
+ NULL, gerr,
+ BT_IO_OPT_SOURCE_BDADDR, &mi->src,
+ BT_IO_OPT_PSM, dcpsm,
+ BT_IO_OPT_MTU, MCAP_DC_MTU,
+ BT_IO_OPT_SEC_LEVEL, sec,
+ BT_IO_OPT_INVALID);
+ if (!mi->dcio) {
+ g_io_channel_shutdown(mi->ccio, TRUE, NULL);
+ g_io_channel_unref(mi->ccio);
+ mi->ccio = NULL;
+ error("%s", (*gerr)->message);
+ g_free(mi);
+ return NULL;
+ }
+
+ /* Initialize random seed to generate mdlids for this instance */
+ srand(time(NULL));
+
+ return mcap_instance_ref(mi);;
+}
+
+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;
+}
+
+struct mcap_instance *mcap_instance_ref(struct mcap_instance *mi)
+{
+ mi->ref++;
+
+ DBG("mcap_instance_ref(%p): ref=%d", mi, mi->ref);
+
+ return mi;
+}
+
+void mcap_instance_unref(struct mcap_instance *mi)
+{
+ mi->ref--;
+
+ DBG("mcap_instance_unref(%p): ref=%d", mi, mi->ref);
+
+ if (mi->ref > 0)
+ return;
+
+ mcap_release_instance(mi);
+ 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;
+}
+
+gboolean mcap_set_data_chan_mode(struct mcap_instance *mi, uint8_t mode,
+ GError **err)
+{
+ if (!(mi && mi->dcio)) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
+ "Invalid MCAP instance");
+ return FALSE;
+ }
+
+ return bt_io_set(mi->dcio, BT_IO_L2CAP, err, BT_IO_OPT_MODE, mode,
+ BT_IO_OPT_INVALID);
+}
+
+struct mcap_mdl *mcap_mdl_ref(struct mcap_mdl *mdl)
+{
+ mdl->ref++;
+
+ DBG("mcap_mdl_ref(%p): ref=%d", mdl, mdl->ref);
+
+ return mdl;
+}
+
+void mcap_mdl_unref(struct mcap_mdl *mdl)
+{
+ mdl->ref--;
+
+ DBG("mcap_mdl_unref(%p): ref=%d", mdl, mdl->ref);
+
+ if (mdl->ref > 0)
+ return;
+
+ free_mdl(mdl);
+}