/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2010 Nokia Corporation * Copyright (C) 2010 Marcel Holtmann * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "plugin.h" #include "log.h" #include "adapter.h" #include "manager.h" #include "device.h" #include "event.h" #define MGMT_BUF_SIZE 1024 static int max_index = -1; static struct controller_info { gboolean valid; gboolean notified; uint8_t type; bdaddr_t bdaddr; uint8_t features[8]; uint8_t dev_class[3]; uint16_t manufacturer; uint8_t hci_ver; uint16_t hci_rev; gboolean enabled; gboolean connectable; gboolean discoverable; gboolean pairable; uint8_t sec_mode; GSList *connections; } *controllers = NULL; static int mgmt_sock = -1; static guint mgmt_watch = 0; static uint8_t mgmt_version = 0; static uint16_t mgmt_revision = 0; static void read_version_complete(int sk, void *buf, size_t len) { struct mgmt_hdr hdr; struct mgmt_rp_read_version *rp = buf; if (len < sizeof(*rp)) { error("Too small read version complete event"); return; } mgmt_revision = btohs(bt_get_unaligned(&rp->revision)); mgmt_version = rp->version; DBG("version %u revision %u", mgmt_version, mgmt_revision); memset(&hdr, 0, sizeof(hdr)); hdr.opcode = htobs(MGMT_OP_READ_INDEX_LIST); hdr.index = htobs(MGMT_INDEX_NONE); if (write(sk, &hdr, sizeof(hdr)) < 0) error("Unable to read controller index list: %s (%d)", strerror(errno), errno); } static void add_controller(uint16_t index) { if (index > max_index) { size_t size = sizeof(struct controller_info) * (index + 1); max_index = index; controllers = g_realloc(controllers, size); } memset(&controllers[index], 0, sizeof(struct controller_info)); controllers[index].valid = TRUE; DBG("Added controller %u", index); } static void read_info(int sk, uint16_t index) { struct mgmt_hdr hdr; memset(&hdr, 0, sizeof(hdr)); hdr.opcode = htobs(MGMT_OP_READ_INFO); hdr.index = htobs(index); if (write(sk, &hdr, sizeof(hdr)) < 0) error("Unable to send read_info command: %s (%d)", strerror(errno), errno); } static void get_connections(int sk, uint16_t index) { struct mgmt_hdr hdr; memset(&hdr, 0, sizeof(hdr)); hdr.opcode = htobs(MGMT_OP_GET_CONNECTIONS); hdr.index = htobs(index); if (write(sk, &hdr, sizeof(hdr)) < 0) error("Unable to send get_connections command: %s (%d)", strerror(errno), errno); } static void mgmt_index_added(int sk, uint16_t index) { add_controller(index); read_info(sk, index); } static void remove_controller(uint16_t index) { if (index > max_index) return; if (!controllers[index].valid) return; btd_manager_unregister_adapter(index); memset(&controllers[index], 0, sizeof(struct controller_info)); DBG("Removed controller %u", index); } static void mgmt_index_removed(int sk, uint16_t index) { remove_controller(index); } static int mgmt_set_mode(int index, uint16_t opcode, uint8_t val) { char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_mode)]; struct mgmt_hdr *hdr = (void *) buf; struct mgmt_mode *cp = (void *) &buf[sizeof(*hdr)]; memset(buf, 0, sizeof(buf)); hdr->opcode = htobs(opcode); hdr->index = htobs(index); hdr->len = htobs(sizeof(*cp)); cp->val = val; if (write(mgmt_sock, buf, sizeof(buf)) < 0) return -errno; return 0; } static int mgmt_set_connectable(int index, gboolean connectable) { DBG("index %d connectable %d", index, connectable); return mgmt_set_mode(index, MGMT_OP_SET_CONNECTABLE, connectable); } static int mgmt_set_discoverable(int index, gboolean discoverable) { DBG("index %d discoverable %d", index, discoverable); return mgmt_set_mode(index, MGMT_OP_SET_DISCOVERABLE, discoverable); } static int mgmt_set_pairable(int index, gboolean pairable) { DBG("index %d pairable %d", index, pairable); return mgmt_set_mode(index, MGMT_OP_SET_PAIRABLE, pairable); } static int mgmt_update_powered(int index, uint8_t powered) { struct controller_info *info; struct btd_adapter *adapter; gboolean pairable, discoverable; uint8_t on_mode; if (index > max_index) { error("Unexpected index %u", index); return -ENODEV; } info = &controllers[index]; info->enabled = powered; adapter = manager_find_adapter(&info->bdaddr); if (adapter == NULL) { DBG("Adapter not found"); return -ENODEV; } if (!powered) { info->connectable = FALSE; info->pairable = FALSE; info->discoverable = FALSE; btd_adapter_stop(adapter); return 0; } btd_adapter_start(adapter); btd_adapter_get_mode(adapter, NULL, &on_mode, &pairable); discoverable = (on_mode == MODE_DISCOVERABLE); if (on_mode == MODE_DISCOVERABLE && !info->discoverable) mgmt_set_discoverable(index, TRUE); else if (on_mode == MODE_CONNECTABLE && !info->connectable) mgmt_set_connectable(index, TRUE); else { uint8_t mode = 0; if (info->connectable) mode |= SCAN_PAGE; if (info->discoverable) mode |= SCAN_INQUIRY; adapter_mode_changed(adapter, mode); } if (info->pairable != pairable) mgmt_set_pairable(index, pairable); return 0; } static void mgmt_powered(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_mode *ev = buf; if (len < sizeof(*ev)) { error("Too small powered event"); return; } DBG("Controller %u powered %u", index, ev->val); mgmt_update_powered(index, ev->val); } static void mgmt_discoverable(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_mode *ev = buf; struct controller_info *info; struct btd_adapter *adapter; uint8_t mode; if (len < sizeof(*ev)) { error("Too small discoverable event"); return; } DBG("Controller %u discoverable %u", index, ev->val); if (index > max_index) { error("Unexpected index %u in discoverable event", index); return; } info = &controllers[index]; info->discoverable = ev->val ? TRUE : FALSE; adapter = manager_find_adapter(&info->bdaddr); if (!adapter) return; if (info->connectable) mode = SCAN_PAGE; else mode = 0; if (info->discoverable) mode |= SCAN_INQUIRY; adapter_mode_changed(adapter, mode); } static void mgmt_connectable(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_mode *ev = buf; struct controller_info *info; struct btd_adapter *adapter; uint8_t mode; if (len < sizeof(*ev)) { error("Too small connectable event"); return; } DBG("Controller %u connectable %u", index, ev->val); if (index > max_index) { error("Unexpected index %u in connectable event", index); return; } info = &controllers[index]; info->connectable = ev->val ? TRUE : FALSE; adapter = manager_find_adapter(&info->bdaddr); if (!adapter) return; if (info->discoverable) mode = SCAN_INQUIRY; else mode = 0; if (info->connectable) mode |= SCAN_PAGE; adapter_mode_changed(adapter, mode); } static void mgmt_pairable(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_mode *ev = buf; struct controller_info *info; struct btd_adapter *adapter; if (len < sizeof(*ev)) { error("Too small pairable event"); return; } DBG("Controller %u pairable %u", index, ev->val); if (index > max_index) { error("Unexpected index %u in pairable event", index); return; } info = &controllers[index]; info->pairable = ev->val ? TRUE : FALSE; adapter = manager_find_adapter(&info->bdaddr); if (!adapter) return; btd_adapter_pairable_changed(adapter, info->pairable); } static void mgmt_new_key(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_ev_new_key *ev = buf; struct controller_info *info; if (len != sizeof(*ev)) { error("new_key event size mismatch (%zu != %zu)", len, sizeof(*ev)); return; } DBG("Controller %u new key of type %u pin_len %u", index, ev->key.type, ev->key.pin_len); if (index > max_index) { error("Unexpected index %u in new_key event", index); return; } if (ev->key.pin_len > 16) { error("Invalid PIN length (%u) in new_key event", ev->key.pin_len); return; } info = &controllers[index]; btd_event_link_key_notify(&info->bdaddr, &ev->key.bdaddr, ev->key.val, ev->key.type, ev->key.pin_len); btd_event_bonding_complete(&info->bdaddr, &ev->key.bdaddr, 0); } static void mgmt_device_connected(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_ev_device_connected *ev = buf; struct controller_info *info; char addr[18]; if (len < sizeof(*ev)) { error("Too small device_connected event"); return; } ba2str(&ev->bdaddr, addr); DBG("hci%u device %s connected", index, addr); if (index > max_index) { error("Unexpected index %u in device_connected event", index); return; } info = &controllers[index]; btd_event_conn_complete(&info->bdaddr, &ev->bdaddr); } static void mgmt_device_disconnected(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_ev_device_disconnected *ev = buf; struct controller_info *info; char addr[18]; if (len < sizeof(*ev)) { error("Too small device_disconnected event"); return; } ba2str(&ev->bdaddr, addr); DBG("hci%u device %s disconnected", index, addr); if (index > max_index) { error("Unexpected index %u in device_disconnected event", index); return; } info = &controllers[index]; btd_event_disconn_complete(&info->bdaddr, &ev->bdaddr); } static void mgmt_connect_failed(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_ev_connect_failed *ev = buf; struct controller_info *info; char addr[18]; if (len < sizeof(*ev)) { error("Too small connect_failed event"); return; } ba2str(&ev->bdaddr, addr); DBG("hci%u %s status %u", index, addr, ev->status); if (index > max_index) { error("Unexpected index %u in connect_failed event", index); return; } info = &controllers[index]; btd_event_conn_failed(&info->bdaddr, &ev->bdaddr, ev->status); /* In the case of security mode 3 devices */ btd_event_bonding_complete(&info->bdaddr, &ev->bdaddr, ev->status); } static int mgmt_pincode_reply(int index, bdaddr_t *bdaddr, const char *pin) { char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_pin_code_reply)]; struct mgmt_hdr *hdr = (void *) buf; size_t buf_len; char addr[18]; ba2str(bdaddr, addr); DBG("index %d addr %s pin %s", index, addr, pin ? pin : ""); memset(buf, 0, sizeof(buf)); if (pin == NULL) { struct mgmt_cp_pin_code_neg_reply *cp; hdr->opcode = htobs(MGMT_OP_PIN_CODE_NEG_REPLY); hdr->len = htobs(sizeof(*cp)); hdr->index = htobs(index); cp = (void *) &buf[sizeof(*hdr)]; bacpy(&cp->bdaddr, bdaddr); buf_len = sizeof(*hdr) + sizeof(*cp); } else { struct mgmt_cp_pin_code_reply *cp; size_t pin_len; pin_len = strlen(pin); if (pin_len > 16) return -EINVAL; hdr->opcode = htobs(MGMT_OP_PIN_CODE_REPLY); hdr->len = htobs(sizeof(*cp)); hdr->index = htobs(index); cp = (void *) &buf[sizeof(*hdr)]; bacpy(&cp->bdaddr, bdaddr); cp->pin_len = pin_len; memcpy(cp->pin_code, pin, pin_len); buf_len = sizeof(*hdr) + sizeof(*cp); } if (write(mgmt_sock, buf, buf_len) < 0) return -errno; return 0; } static void mgmt_pin_code_request(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_ev_pin_code_request *ev = buf; struct controller_info *info; char addr[18]; int err; if (len < sizeof(*ev)) { error("Too small pin_code_request event"); return; } ba2str(&ev->bdaddr, addr); DBG("hci%u %s", index, addr); if (index > max_index) { error("Unexpected index %u in pin_code_request event", index); return; } info = &controllers[index]; err = btd_event_request_pin(&info->bdaddr, &ev->bdaddr); if (err < 0) { error("btd_event_request_pin: %s", strerror(-err)); mgmt_pincode_reply(index, &ev->bdaddr, NULL); } } static int mgmt_confirm_reply(int index, bdaddr_t *bdaddr, gboolean success) { char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_user_confirm_reply)]; struct mgmt_hdr *hdr = (void *) buf; struct mgmt_cp_user_confirm_reply *cp; char addr[18]; ba2str(bdaddr, addr); DBG("index %d addr %s success %d", index, addr, success); memset(buf, 0, sizeof(buf)); if (success) hdr->opcode = htobs(MGMT_OP_USER_CONFIRM_REPLY); else hdr->opcode = htobs(MGMT_OP_USER_CONFIRM_NEG_REPLY); hdr->len = htobs(sizeof(*cp)); hdr->index = htobs(index); cp = (void *) &buf[sizeof(*hdr)]; bacpy(&cp->bdaddr, bdaddr); if (write(mgmt_sock, buf, sizeof(buf)) < 0) return -errno; return 0; } static void mgmt_user_confirm_request(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_ev_user_confirm_request *ev = buf; struct controller_info *info; char addr[18]; int err; if (len < sizeof(*ev)) { error("Too small user_confirm_request event"); return; } ba2str(&ev->bdaddr, addr); DBG("hci%u %s", index, addr); if (index > max_index) { error("Unexpected index %u in user_confirm_request event", index); return; } info = &controllers[index]; err = btd_event_user_confirm(&info->bdaddr, &ev->bdaddr, btohl(ev->value)); if (err < 0) { error("btd_event_user_confirm: %s", strerror(-err)); mgmt_confirm_reply(index, &ev->bdaddr, FALSE); } } static void uuid_to_uuid128(uuid_t *uuid128, const uuid_t *uuid) { if (uuid->type == SDP_UUID16) sdp_uuid16_to_uuid128(uuid128, uuid); else if (uuid->type == SDP_UUID32) sdp_uuid32_to_uuid128(uuid128, uuid); else memcpy(uuid128, uuid, sizeof(*uuid)); } static int mgmt_add_uuid(int index, uuid_t *uuid, uint8_t svc_hint) { char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_add_uuid)]; struct mgmt_hdr *hdr = (void *) buf; struct mgmt_cp_add_uuid *cp = (void *) &buf[sizeof(*hdr)]; uuid_t uuid128; DBG("index %d", index); uuid_to_uuid128(&uuid128, uuid); memset(buf, 0, sizeof(buf)); hdr->opcode = htobs(MGMT_OP_ADD_UUID); hdr->len = htobs(sizeof(*cp)); hdr->index = htobs(index); memcpy(cp->uuid, uuid128.value.uuid128.data, 16); cp->svc_hint = svc_hint; if (write(mgmt_sock, buf, sizeof(buf)) < 0) return -errno; return 0; } static int mgmt_remove_uuid(int index, uuid_t *uuid) { char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_remove_uuid)]; struct mgmt_hdr *hdr = (void *) buf; struct mgmt_cp_remove_uuid *cp = (void *) &buf[sizeof(*hdr)]; uuid_t uuid128; DBG("index %d", index); uuid_to_uuid128(&uuid128, uuid); memset(buf, 0, sizeof(buf)); hdr->opcode = htobs(MGMT_OP_REMOVE_UUID); hdr->len = htobs(sizeof(*cp)); hdr->index = htobs(index); memcpy(cp->uuid, uuid128.value.uuid128.data, 16); if (write(mgmt_sock, buf, sizeof(buf)) < 0) return -errno; return 0; } static int clear_uuids(int index) { uuid_t uuid_any; memset(&uuid_any, 0, sizeof(uuid_any)); uuid_any.type = SDP_UUID128; return mgmt_remove_uuid(index, &uuid_any); } static void read_index_list_complete(int sk, void *buf, size_t len) { struct mgmt_rp_read_index_list *rp = buf; uint16_t num; int i; if (len < sizeof(*rp)) { error("Too small read index list complete event"); return; } num = btohs(bt_get_unaligned(&rp->num_controllers)); if (num * sizeof(uint16_t) + sizeof(*rp) != len) { error("Incorrect packet size for index list event"); return; } for (i = 0; i < num; i++) { uint16_t index; index = btohs(bt_get_unaligned(&rp->index[i])); add_controller(index); get_connections(sk, index); clear_uuids(index); } } static int mgmt_set_powered(int index, gboolean powered) { DBG("index %d powered %d", index, powered); return mgmt_set_mode(index, MGMT_OP_SET_POWERED, powered); } static void read_info_complete(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_rp_read_info *rp = buf; struct controller_info *info; struct btd_adapter *adapter; uint8_t mode; char addr[18]; if (len < sizeof(*rp)) { error("Too small read info complete event"); return; } if (index > max_index) { error("Unexpected index %u in read info complete", index); return; } mgmt_set_mode(index, MGMT_OP_SET_SERVICE_CACHE, 1); info = &controllers[index]; info->type = rp->type; info->enabled = rp->powered; info->connectable = rp->connectable; info->discoverable = rp->discoverable; info->pairable = rp->pairable; info->sec_mode = rp->sec_mode; bacpy(&info->bdaddr, &rp->bdaddr); memcpy(info->dev_class, rp->dev_class, 3); memcpy(info->features, rp->features, 8); info->manufacturer = btohs(bt_get_unaligned(&rp->manufacturer)); info->hci_ver = rp->hci_ver; info->hci_rev = btohs(bt_get_unaligned(&rp->hci_rev)); ba2str(&info->bdaddr, addr); DBG("hci%u type %u addr %s", index, info->type, addr); DBG("hci%u class 0x%02x%02x%02x", index, info->dev_class[2], info->dev_class[1], info->dev_class[0]); DBG("hci%u manufacturer %d HCI ver %d:%d", index, info->manufacturer, info->hci_ver, info->hci_rev); DBG("hci%u enabled %u discoverable %u pairable %u sec_mode %u", index, info->enabled, info->discoverable, info->pairable, info->sec_mode); adapter = btd_manager_register_adapter(index); if (adapter == NULL) { error("mgmtops: unable to register adapter"); return; } btd_adapter_get_mode(adapter, &mode, NULL, NULL); if (mode == MODE_OFF) { mgmt_set_powered(index, FALSE); return; } if (info->enabled) mgmt_update_powered(index, TRUE); else mgmt_set_powered(index, TRUE); btd_adapter_unref(adapter); } static void set_powered_complete(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_mode *rp = buf; if (len < sizeof(*rp)) { error("Too small set powered complete event"); return; } DBG("hci%d powered %u", index, rp->val); mgmt_update_powered(index, rp->val); } static void set_discoverable_complete(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_mode *rp = buf; struct controller_info *info; struct btd_adapter *adapter; uint8_t mode; if (len < sizeof(*rp)) { error("Too small set discoverable complete event"); return; } DBG("hci%d discoverable %u", index, rp->val); if (index > max_index) { error("Unexpected index %u in discoverable complete", index); return; } info = &controllers[index]; info->discoverable = rp->val ? TRUE : FALSE; adapter = manager_find_adapter(&info->bdaddr); if (!adapter) return; /* set_discoverable will always also change page scanning */ mode = SCAN_PAGE; if (info->discoverable) mode |= SCAN_INQUIRY; adapter_mode_changed(adapter, mode); } static void set_connectable_complete(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_mode *rp = buf; struct controller_info *info; struct btd_adapter *adapter; if (len < sizeof(*rp)) { error("Too small set connectable complete event"); return; } DBG("hci%d connectable %u", index, rp->val); if (index > max_index) { error("Unexpected index %u in connectable complete", index); return; } info = &controllers[index]; info->connectable = rp->val ? TRUE : FALSE; adapter = manager_find_adapter(&info->bdaddr); if (adapter) adapter_mode_changed(adapter, rp->val ? SCAN_PAGE : 0); } static void set_pairable_complete(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_mode *rp = buf; struct controller_info *info; struct btd_adapter *adapter; if (len < sizeof(*rp)) { error("Too small set pairable complete event"); return; } DBG("hci%d pairable %u", index, rp->val); if (index > max_index) { error("Unexpected index %u in pairable complete", index); return; } info = &controllers[index]; info->pairable = rp->val ? TRUE : FALSE; adapter = manager_find_adapter(&info->bdaddr); if (!adapter) return; btd_adapter_pairable_changed(adapter, info->pairable); } static void disconnect_complete(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_rp_disconnect *rp = buf; struct controller_info *info; char addr[18]; if (len < sizeof(*rp)) { error("Too small disconnect complete event"); return; } ba2str(&rp->bdaddr, addr); DBG("hci%d %s disconnected", index, addr); if (index > max_index) { error("Unexpected index %u in disconnect complete", index); return; } info = &controllers[index]; btd_event_disconn_complete(&info->bdaddr, &rp->bdaddr); btd_event_bonding_complete(&info->bdaddr, &rp->bdaddr, HCI_CONNECTION_TERMINATED); } static void pair_device_complete(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_rp_pair_device *rp = buf; struct controller_info *info; char addr[18]; if (len < sizeof(*rp)) { error("Too small pair_device complete event"); return; } ba2str(&rp->bdaddr, addr); DBG("hci%d %s pairing complete status %u", index, addr, rp->status); if (index > max_index) { error("Unexpected index %u in pair_device complete", index); return; } info = &controllers[index]; btd_event_bonding_complete(&info->bdaddr, &rp->bdaddr, rp->status); } static void get_connections_complete(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_rp_get_connections *rp = buf; struct controller_info *info; int i; if (len < sizeof(*rp)) { error("Too small get_connections complete event"); return; } if (len < (sizeof(*rp) + (rp->conn_count * sizeof(bdaddr_t)))) { error("Too small get_connections complete event"); return; } if (index > max_index) { error("Unexpected index %u in get_connections complete", index); return; } info = &controllers[index]; for (i = 0; i < rp->conn_count; i++) { bdaddr_t *bdaddr = g_memdup(&rp->conn[i], sizeof(bdaddr_t)); info->connections = g_slist_append(info->connections, bdaddr); } read_info(sk, index); } static void mgmt_cmd_complete(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_ev_cmd_complete *ev = buf; uint16_t opcode; DBG(""); if (len < sizeof(*ev)) { error("Too small management command complete event packet"); return; } opcode = btohs(bt_get_unaligned(&ev->opcode)); len -= sizeof(*ev); switch (opcode) { case MGMT_OP_READ_VERSION: read_version_complete(sk, ev->data, len); break; case MGMT_OP_READ_INDEX_LIST: read_index_list_complete(sk, ev->data, len); break; case MGMT_OP_READ_INFO: read_info_complete(sk, index, ev->data, len); break; case MGMT_OP_SET_POWERED: set_powered_complete(sk, index, ev->data, len); break; case MGMT_OP_SET_DISCOVERABLE: set_discoverable_complete(sk, index, ev->data, len); break; case MGMT_OP_SET_CONNECTABLE: set_connectable_complete(sk, index, ev->data, len); break; case MGMT_OP_SET_PAIRABLE: set_pairable_complete(sk, index, ev->data, len); break; case MGMT_OP_ADD_UUID: DBG("add_uuid complete"); break; case MGMT_OP_REMOVE_UUID: DBG("remove_uuid complete"); break; case MGMT_OP_SET_DEV_CLASS: DBG("set_dev_class complete"); break; case MGMT_OP_SET_SERVICE_CACHE: DBG("set_service_cache complete"); break; case MGMT_OP_LOAD_KEYS: DBG("load_keys complete"); break; case MGMT_OP_REMOVE_KEY: DBG("remove_key complete"); break; case MGMT_OP_DISCONNECT: DBG("disconnect complete"); disconnect_complete(sk, index, ev->data, len); break; case MGMT_OP_GET_CONNECTIONS: get_connections_complete(sk, index, ev->data, len); break; case MGMT_OP_PIN_CODE_REPLY: DBG("pin_code_reply complete"); break; case MGMT_OP_PIN_CODE_NEG_REPLY: DBG("pin_code_neg_reply complete"); break; case MGMT_OP_SET_IO_CAPABILITY: DBG("set_io_capability complete"); break; case MGMT_OP_PAIR_DEVICE: pair_device_complete(sk, index, ev->data, len); break; case MGMT_OP_USER_CONFIRM_REPLY: DBG("user_confirm_reply complete"); break; case MGMT_OP_USER_CONFIRM_NEG_REPLY: DBG("user_confirm_net_reply complete"); break; default: error("Unknown command complete for opcode %u", opcode); break; } } static void mgmt_cmd_status(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_ev_cmd_status *ev = buf; uint16_t opcode; if (len < sizeof(*ev)) { error("Too small management command status event packet"); return; } opcode = btohs(bt_get_unaligned(&ev->opcode)); DBG("status %u opcode %u (index %u)", ev->status, opcode, index); } static void mgmt_controller_error(int sk, uint16_t index, void *buf, size_t len) { struct mgmt_ev_controller_error *ev = buf; if (len < sizeof(*ev)) { error("Too small management controller error event packet"); return; } DBG("index %u error_code %u", index, ev->error_code); } static void mgmt_auth_failed(int sk, uint16_t index, void *buf, size_t len) { struct controller_info *info; struct mgmt_ev_auth_failed *ev = buf; if (len < sizeof(*ev)) { error("Too small mgmt_auth_failed event packet"); return; } DBG("hci%u auth failed status %u", index, ev->status); if (index > max_index) { error("Unexpected index %u in auth_failed event", index); return; } info = &controllers[index]; btd_event_bonding_complete(&info->bdaddr, &ev->bdaddr, ev->status); } static gboolean mgmt_event(GIOChannel *io, GIOCondition cond, gpointer user_data) { char buf[MGMT_BUF_SIZE]; struct mgmt_hdr *hdr = (void *) buf; int sk; ssize_t ret; uint16_t len, opcode, index; DBG("cond %d", cond); if (cond & G_IO_NVAL) return FALSE; sk = g_io_channel_unix_get_fd(io); if (cond & (G_IO_ERR | G_IO_HUP)) { error("Error on management socket"); return FALSE; } ret = read(sk, buf, sizeof(buf)); if (ret < 0) { error("Unable to read from management socket: %s (%d)", strerror(errno), errno); return TRUE; } DBG("Received %zd bytes from management socket", ret); if (ret < MGMT_HDR_SIZE) { error("Too small Management packet"); return TRUE; } opcode = btohs(bt_get_unaligned(&hdr->opcode)); len = btohs(bt_get_unaligned(&hdr->len)); index = btohs(bt_get_unaligned(&hdr->index)); if (ret != MGMT_HDR_SIZE + len) { error("Packet length mismatch. ret %zd len %u", ret, len); return TRUE; } switch (opcode) { case MGMT_EV_CMD_COMPLETE: mgmt_cmd_complete(sk, index, buf + MGMT_HDR_SIZE, len); break; case MGMT_EV_CMD_STATUS: mgmt_cmd_status(sk, index, buf + MGMT_HDR_SIZE, len); break; case MGMT_EV_CONTROLLER_ERROR: mgmt_controller_error(sk, index, buf + MGMT_HDR_SIZE, len); break; case MGMT_EV_INDEX_ADDED: mgmt_index_added(sk, index); break; case MGMT_EV_INDEX_REMOVED: mgmt_index_removed(sk, index); break; case MGMT_EV_POWERED: mgmt_powered(sk, index, buf + MGMT_HDR_SIZE, len); break; case MGMT_EV_DISCOVERABLE: mgmt_discoverable(sk, index, buf + MGMT_HDR_SIZE, len); break; case MGMT_EV_CONNECTABLE: mgmt_connectable(sk, index, buf + MGMT_HDR_SIZE, len); break; case MGMT_EV_PAIRABLE: mgmt_pairable(sk, index, buf + MGMT_HDR_SIZE, len); break; case MGMT_EV_NEW_KEY: mgmt_new_key(sk, index, buf + MGMT_HDR_SIZE, len); break; case MGMT_EV_DEVICE_CONNECTED: mgmt_device_connected(sk, index, buf + MGMT_HDR_SIZE, len); break; case MGMT_EV_DEVICE_DISCONNECTED: mgmt_device_disconnected(sk, index, buf + MGMT_HDR_SIZE, len); break; case MGMT_EV_CONNECT_FAILED: mgmt_connect_failed(sk, index, buf + MGMT_HDR_SIZE, len); break; case MGMT_EV_PIN_CODE_REQUEST: mgmt_pin_code_request(sk, index, buf + MGMT_HDR_SIZE, len); break; case MGMT_EV_USER_CONFIRM_REQUEST: mgmt_user_confirm_request(sk, index, buf + MGMT_HDR_SIZE, len); break; case MGMT_EV_AUTH_FAILED: mgmt_auth_failed(sk, index, buf + MGMT_HDR_SIZE, len); break; default: error("Unknown Management opcode %u (index %u)", opcode, index); break; } return TRUE; } static int mgmt_setup(void) { struct mgmt_hdr hdr; struct sockaddr_hci addr; GIOChannel *io; GIOCondition condition; int dd, err; dd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); if (dd < 0) return -errno; memset(&addr, 0, sizeof(addr)); addr.hci_family = AF_BLUETOOTH; addr.hci_dev = HCI_DEV_NONE; addr.hci_channel = HCI_CHANNEL_CONTROL; if (bind(dd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { err = -errno; goto fail; } memset(&hdr, 0, sizeof(hdr)); hdr.opcode = htobs(MGMT_OP_READ_VERSION); hdr.index = htobs(MGMT_INDEX_NONE); if (write(dd, &hdr, sizeof(hdr)) < 0) { err = -errno; goto fail; } io = g_io_channel_unix_new(dd); condition = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL; mgmt_watch = g_io_add_watch(io, condition, mgmt_event, NULL); g_io_channel_unref(io); mgmt_sock = dd; info("Bluetooth Management interface initialized"); return 0; fail: close(dd); return err; } static void mgmt_cleanup(void) { g_free(controllers); controllers = NULL; max_index = -1; if (mgmt_sock >= 0) { close(mgmt_sock); mgmt_sock = -1; } if (mgmt_watch > 0) { g_source_remove(mgmt_watch); mgmt_watch = 0; } } static int mgmt_set_dev_class(int index, uint8_t major, uint8_t minor) { char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_set_dev_class)]; struct mgmt_hdr *hdr = (void *) buf; struct mgmt_cp_set_dev_class *cp = (void *) &buf[sizeof(*hdr)]; DBG("index %d major %u minor %u", index, major, minor); memset(buf, 0, sizeof(buf)); hdr->opcode = htobs(MGMT_OP_SET_DEV_CLASS); hdr->len = htobs(sizeof(*cp)); hdr->index = htobs(index); cp->major = major; cp->minor = minor; if (write(mgmt_sock, buf, sizeof(buf)) < 0) return -errno; return 0; } static int mgmt_set_limited_discoverable(int index, gboolean limited) { DBG("index %d limited %d", index, limited); return -ENOSYS; } static int mgmt_start_inquiry(int index, uint8_t length, gboolean periodic) { DBG("index %d length %u periodic %d", index, length, periodic); return -ENOSYS; } static int mgmt_stop_inquiry(int index) { DBG("index %d", index); return -ENOSYS; } static int mgmt_start_scanning(int index) { DBG("index %d", index); return -ENOSYS; } static int mgmt_stop_scanning(int index) { DBG("index %d", index); return -ENOSYS; } static int mgmt_resolve_name(int index, bdaddr_t *bdaddr) { char addr[18]; ba2str(bdaddr, addr); DBG("index %d addr %s", index, addr); return -ENOSYS; } static int mgmt_set_name(int index, const char *name) { DBG("index %d, name %s", index, name); return -ENOSYS; } static int mgmt_cancel_resolve_name(int index, bdaddr_t *bdaddr) { char addr[18]; ba2str(bdaddr, addr); DBG("index %d addr %s", index, addr); return -ENOSYS; } static int mgmt_fast_connectable(int index, gboolean enable) { DBG("index %d enable %d", index, enable); return -ENOSYS; } static int mgmt_read_clock(int index, bdaddr_t *bdaddr, int which, int timeout, uint32_t *clock, uint16_t *accuracy) { char addr[18]; ba2str(bdaddr, addr); DBG("index %d addr %s which %d timeout %d", index, addr, which, timeout); return -ENOSYS; } static int mgmt_read_bdaddr(int index, bdaddr_t *bdaddr) { char addr[18]; struct controller_info *info = &controllers[index]; ba2str(&info->bdaddr, addr); DBG("index %d addr %s", index, addr); if (!info->valid) return -ENODEV; bacpy(bdaddr, &info->bdaddr); return 0; } static int mgmt_block_device(int index, bdaddr_t *bdaddr) { char addr[18]; ba2str(bdaddr, addr); DBG("index %d addr %s", index, addr); return -ENOSYS; } static int mgmt_unblock_device(int index, bdaddr_t *bdaddr) { char addr[18]; ba2str(bdaddr, addr); DBG("index %d addr %s", index, addr); return -ENOSYS; } static int mgmt_get_conn_list(int index, GSList **conns) { struct controller_info *info = &controllers[index]; DBG("index %d", index); *conns = info->connections; info->connections = NULL; return 0; } static int mgmt_read_local_version(int index, struct hci_version *ver) { struct controller_info *info = &controllers[index]; DBG("index %d", index); if (!info->valid) return -ENODEV; memset(ver, 0, sizeof(*ver)); ver->manufacturer = info->manufacturer; ver->hci_ver = info->hci_ver; ver->hci_rev = info->hci_rev; return 0; } static int mgmt_read_local_features(int index, uint8_t *features) { struct controller_info *info = &controllers[index]; DBG("index %d", index); if (!info->valid) return -ENODEV; memcpy(features, info->features, 8); return 0; } static int mgmt_disconnect(int index, bdaddr_t *bdaddr) { char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_disconnect)]; struct mgmt_hdr *hdr = (void *) buf; struct mgmt_cp_disconnect *cp = (void *) &buf[sizeof(*hdr)]; char addr[18]; ba2str(bdaddr, addr); DBG("index %d %s", index, addr); memset(buf, 0, sizeof(buf)); hdr->opcode = htobs(MGMT_OP_DISCONNECT); hdr->len = htobs(sizeof(*cp)); hdr->index = htobs(index); bacpy(&cp->bdaddr, bdaddr); if (write(mgmt_sock, buf, sizeof(buf)) < 0) error("write: %s (%d)", strerror(errno), errno); return 0; } static int mgmt_remove_bonding(int index, bdaddr_t *bdaddr) { char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_remove_key)]; struct mgmt_hdr *hdr = (void *) buf; struct mgmt_cp_remove_key *cp = (void *) &buf[sizeof(*hdr)]; char addr[18]; ba2str(bdaddr, addr); DBG("index %d addr %s", index, addr); memset(buf, 0, sizeof(buf)); hdr->opcode = htobs(MGMT_OP_REMOVE_KEY); hdr->len = htobs(sizeof(*cp)); hdr->index = htobs(index); bacpy(&cp->bdaddr, bdaddr); cp->disconnect = 1; if (write(mgmt_sock, buf, sizeof(buf)) < 0) return -errno; return 0; } static int mgmt_passkey_reply(int index, bdaddr_t *bdaddr, uint32_t passkey) { char addr[18]; ba2str(bdaddr, addr); DBG("index %d addr %s passkey %06u", index, addr, passkey); return -ENOSYS; } static int mgmt_enable_le(int index) { DBG("index %d", index); return -ENOSYS; } static int mgmt_encrypt_link(int index, bdaddr_t *dst, bt_hci_result_t cb, gpointer user_data) { char addr[18]; ba2str(dst, addr); DBG("index %d addr %s", index, addr); return -ENOSYS; } static int mgmt_set_did(int index, uint16_t vendor, uint16_t product, uint16_t version) { DBG("index %d vendor %u product %u version %u", index, vendor, product, version); return -ENOSYS; } static int mgmt_disable_cod_cache(int index) { DBG("index %d", index); return mgmt_set_mode(index, MGMT_OP_SET_SERVICE_CACHE, 0); } static int mgmt_restore_powered(int index) { DBG("index %d", index); return -ENOSYS; } static int mgmt_load_keys(int index, GSList *keys, gboolean debug_keys) { char *buf; struct mgmt_hdr *hdr; struct mgmt_cp_load_keys *cp; struct mgmt_key_info *key; size_t key_count, cp_size; GSList *l; int err; key_count = g_slist_length(keys); DBG("index %d keys %zu debug_keys %d", index, key_count, debug_keys); cp_size = sizeof(*cp) + (key_count * sizeof(*key)); buf = g_try_malloc0(sizeof(*hdr) + cp_size); if (buf == NULL) return -ENOMEM; memset(buf, 0, sizeof(buf)); hdr = (void *) buf; hdr->opcode = htobs(MGMT_OP_LOAD_KEYS); hdr->len = htobs(cp_size); hdr->index = htobs(index); cp = (void *) (buf + sizeof(*hdr)); cp->debug_keys = debug_keys; cp->key_count = htobs(key_count); for (l = keys, key = cp->keys; l != NULL; l = g_slist_next(l), key++) { struct link_key_info *info = l->data; bacpy(&key->bdaddr, &info->bdaddr); key->type = info->type; memcpy(key->val, info->key, 16); key->pin_len = info->pin_len; } if (write(mgmt_sock, buf, sizeof(*hdr) + cp_size) < 0) err = -errno; else err = 0; g_free(buf); return err; } static int mgmt_set_io_capability(int index, uint8_t io_capability) { char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_set_io_capability)]; struct mgmt_hdr *hdr = (void *) buf; struct mgmt_cp_set_io_capability *cp = (void *) &buf[sizeof(*hdr)]; DBG("hci%d io_capability 0x%02x", index, io_capability); memset(buf, 0, sizeof(buf)); hdr->opcode = htobs(MGMT_OP_SET_IO_CAPABILITY); hdr->len = htobs(sizeof(*cp)); hdr->index = htobs(index); cp->io_capability = io_capability; if (write(mgmt_sock, buf, sizeof(buf)) < 0) return -errno; return 0; } static int mgmt_create_bonding(int index, bdaddr_t *bdaddr, uint8_t io_cap) { char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_pair_device)]; struct mgmt_hdr *hdr = (void *) buf; struct mgmt_cp_pair_device *cp = (void *) &buf[sizeof(*hdr)]; char addr[18]; ba2str(bdaddr, addr); DBG("hci%d bdaddr %s io_cap 0x%02x", index, addr, io_cap); memset(buf, 0, sizeof(buf)); hdr->opcode = htobs(MGMT_OP_PAIR_DEVICE); hdr->len = htobs(sizeof(*cp)); hdr->index = htobs(index); bacpy(&cp->bdaddr, bdaddr); cp->io_cap = io_cap; if (write(mgmt_sock, &buf, sizeof(buf)) < 0) return -errno; return 0; } static int mgmt_cancel_bonding(int index, bdaddr_t *bdaddr) { char addr[18]; ba2str(bdaddr, addr); DBG("hci%d bdaddr %s", index, addr); return -ENOSYS; } static struct btd_adapter_ops mgmt_ops = { .setup = mgmt_setup, .cleanup = mgmt_cleanup, .set_powered = mgmt_set_powered, .set_discoverable = mgmt_set_discoverable, .set_pairable = mgmt_set_pairable, .set_limited_discoverable = mgmt_set_limited_discoverable, .start_inquiry = mgmt_start_inquiry, .stop_inquiry = mgmt_stop_inquiry, .start_scanning = mgmt_start_scanning, .stop_scanning = mgmt_stop_scanning, .resolve_name = mgmt_resolve_name, .cancel_resolve_name = mgmt_cancel_resolve_name, .set_name = mgmt_set_name, .set_dev_class = mgmt_set_dev_class, .set_fast_connectable = mgmt_fast_connectable, .read_clock = mgmt_read_clock, .read_bdaddr = mgmt_read_bdaddr, .block_device = mgmt_block_device, .unblock_device = mgmt_unblock_device, .get_conn_list = mgmt_get_conn_list, .read_local_version = mgmt_read_local_version, .read_local_features = mgmt_read_local_features, .disconnect = mgmt_disconnect, .remove_bonding = mgmt_remove_bonding, .pincode_reply = mgmt_pincode_reply, .confirm_reply = mgmt_confirm_reply, .passkey_reply = mgmt_passkey_reply, .enable_le = mgmt_enable_le, .encrypt_link = mgmt_encrypt_link, .set_did = mgmt_set_did, .add_uuid = mgmt_add_uuid, .remove_uuid = mgmt_remove_uuid, .disable_cod_cache = mgmt_disable_cod_cache, .restore_powered = mgmt_restore_powered, .load_keys = mgmt_load_keys, .set_io_capability = mgmt_set_io_capability, .create_bonding = mgmt_create_bonding, .cancel_bonding = mgmt_cancel_bonding, }; static int mgmt_init(void) { return btd_register_adapter_ops(&mgmt_ops, TRUE); } static void mgmt_exit(void) { btd_adapter_cleanup_ops(&mgmt_ops); } BLUETOOTH_PLUGIN_DEFINE(mgmtops, VERSION, BLUETOOTH_PLUGIN_PRIORITY_LOW, mgmt_init, mgmt_exit)