| /* |
| * |
| * MCAP for BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <netinet/in.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <unistd.h> |
| |
| #include <glib.h> |
| |
| #include <bluetooth/bluetooth.h> |
| #include <bluetooth/l2cap.h> |
| |
| #include "btio/btio.h" |
| #include "src/log.h" |
| #include "src/error.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 int 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 int 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) |
| { |
| struct mcap_mcl *mcl; |
| |
| for (; list; list = list->next) { |
| mcl = list->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 successful */ |
| 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); |
| g_free(c); |
| 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, ¬ify); |
| 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, ¬ify); |
| |
| 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; |
| int 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, ¬ify); |
| 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, ¬ify); |
| } |
| |
| 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(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(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, &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, &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(const 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(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(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, 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, 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, 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); |
| } |