| // SPDX-License-Identifier: LGPL-2.1-or-later |
| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. |
| * |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <errno.h> |
| |
| #include "bluetooth/bluetooth.h" |
| #include "bluetooth/mgmt.h" |
| #include "bluetooth/hci.h" |
| |
| #include "src/shared/io.h" |
| #include "src/shared/queue.h" |
| #include "src/shared/util.h" |
| #include "src/shared/mgmt.h" |
| #include "src/shared/timeout.h" |
| |
| #define DBG(_mgmt, _format, arg...) \ |
| mgmt_log(_mgmt, "%s:%s() " _format, __FILE__, __func__, ## arg) |
| |
| struct mgmt { |
| int ref_count; |
| int fd; |
| bool close_on_unref; |
| struct io *io; |
| bool writer_active; |
| struct queue *request_queue; |
| struct queue *reply_queue; |
| struct queue *pending_list; |
| struct queue *notify_list; |
| unsigned int next_request_id; |
| unsigned int next_notify_id; |
| bool need_notify_cleanup; |
| bool in_notify; |
| void *buf; |
| uint16_t len; |
| uint16_t mtu; |
| mgmt_debug_func_t debug_callback; |
| mgmt_destroy_func_t debug_destroy; |
| void *debug_data; |
| }; |
| |
| struct mgmt_request { |
| struct mgmt *mgmt; |
| unsigned int id; |
| uint16_t opcode; |
| uint16_t index; |
| void *buf; |
| uint16_t len; |
| mgmt_request_func_t callback; |
| mgmt_destroy_func_t destroy; |
| void *user_data; |
| int timeout; |
| unsigned int timeout_id; |
| }; |
| |
| struct mgmt_notify { |
| unsigned int id; |
| uint16_t event; |
| uint16_t index; |
| bool removed; |
| mgmt_notify_func_t callback; |
| mgmt_destroy_func_t destroy; |
| void *user_data; |
| }; |
| |
| struct mgmt_tlv_list { |
| struct queue *tlv_queue; |
| uint16_t size; |
| }; |
| |
| struct arg_table { |
| const char *name; |
| enum mgmt_io_capability value; |
| }; |
| |
| static const struct arg_table iocap_arguments[] = { |
| { "DisplayOnly", MGMT_IO_CAPABILITY_DISPLAYONLY }, |
| { "DisplayYesNo", MGMT_IO_CAPABILITY_DISPLAYYESNO }, |
| { "KeyboardOnly", MGMT_IO_CAPABILITY_KEYBOARDONLY }, |
| { "NoInputNoOutput", MGMT_IO_CAPABILITY_NOINPUTNOOUTPUT }, |
| { "KeyboardDisplay", MGMT_IO_CAPABILITY_KEYBOARDDISPLAY }, |
| { NULL, 0} |
| }; |
| |
| static void destroy_request(void *data) |
| { |
| struct mgmt_request *request = data; |
| |
| if (request->destroy) |
| request->destroy(request->user_data); |
| |
| if (request->timeout_id) |
| timeout_remove(request->timeout_id); |
| |
| free(request->buf); |
| free(request); |
| } |
| |
| static bool match_request_id(const void *a, const void *b) |
| { |
| const struct mgmt_request *request = a; |
| unsigned int id = PTR_TO_UINT(b); |
| |
| return request->id == id; |
| } |
| |
| static bool match_request_index(const void *a, const void *b) |
| { |
| const struct mgmt_request *request = a; |
| uint16_t index = PTR_TO_UINT(b); |
| |
| return request->index == index; |
| } |
| |
| static void destroy_notify(void *data) |
| { |
| struct mgmt_notify *notify = data; |
| |
| if (notify->destroy) |
| notify->destroy(notify->user_data); |
| |
| free(notify); |
| } |
| |
| static bool match_notify_id(const void *a, const void *b) |
| { |
| const struct mgmt_notify *notify = a; |
| unsigned int id = PTR_TO_UINT(b); |
| |
| return notify->id == id; |
| } |
| |
| static bool match_notify_index(const void *a, const void *b) |
| { |
| const struct mgmt_notify *notify = a; |
| uint16_t index = PTR_TO_UINT(b); |
| |
| return notify->index == index; |
| } |
| |
| static bool match_notify_removed(const void *a, const void *b) |
| { |
| const struct mgmt_notify *notify = a; |
| |
| return notify->removed; |
| } |
| |
| static void mark_notify_removed(void *data , void *user_data) |
| { |
| struct mgmt_notify *notify = data; |
| uint16_t index = PTR_TO_UINT(user_data); |
| |
| if (notify->index == index || index == MGMT_INDEX_NONE) |
| notify->removed = true; |
| } |
| |
| static void write_watch_destroy(void *user_data) |
| { |
| struct mgmt *mgmt = user_data; |
| |
| mgmt->writer_active = false; |
| } |
| |
| static bool request_timeout(void *data) |
| { |
| struct mgmt_request *request = data; |
| |
| if (!request) |
| return false; |
| |
| request->timeout_id = 0; |
| |
| queue_remove_if(request->mgmt->pending_list, NULL, request); |
| |
| if (request->callback) |
| request->callback(MGMT_STATUS_TIMEOUT, 0, NULL, |
| request->user_data); |
| |
| destroy_request(request); |
| |
| return false; |
| } |
| |
| static void mgmt_log(struct mgmt *mgmt, const char *format, ...) |
| { |
| va_list ap; |
| |
| if (!mgmt || !format || !mgmt->debug_callback) |
| return; |
| |
| va_start(ap, format); |
| util_debug_va(mgmt->debug_callback, mgmt->debug_data, format, ap); |
| va_end(ap); |
| } |
| |
| static bool send_request(struct mgmt *mgmt, struct mgmt_request *request) |
| { |
| struct iovec iov; |
| ssize_t ret; |
| |
| iov.iov_base = request->buf; |
| iov.iov_len = request->len; |
| |
| ret = io_send(mgmt->io, &iov, 1); |
| if (ret < 0) { |
| DBG(mgmt, "write failed: %s", strerror(-ret)); |
| |
| if (request->callback) |
| request->callback(MGMT_STATUS_FAILED, 0, NULL, |
| request->user_data); |
| destroy_request(request); |
| return false; |
| } |
| |
| if (request->timeout) |
| request->timeout_id = timeout_add_seconds(request->timeout, |
| request_timeout, |
| request, |
| NULL); |
| |
| DBG(mgmt, "[0x%04x] command 0x%04x", request->index, request->opcode); |
| |
| queue_push_tail(mgmt->pending_list, request); |
| |
| return true; |
| } |
| |
| static bool can_write_data(struct io *io, void *user_data) |
| { |
| struct mgmt *mgmt = user_data; |
| struct mgmt_request *request; |
| bool can_write; |
| |
| request = queue_pop_head(mgmt->reply_queue); |
| if (!request) { |
| /* only reply commands can jump the queue */ |
| if (!queue_isempty(mgmt->pending_list)) |
| return false; |
| |
| request = queue_pop_head(mgmt->request_queue); |
| if (!request) |
| return false; |
| |
| can_write = false; |
| } else { |
| /* allow multiple replies to jump the queue */ |
| can_write = !queue_isempty(mgmt->reply_queue); |
| } |
| |
| if (!send_request(mgmt, request)) |
| return true; |
| |
| return can_write; |
| } |
| |
| static void wakeup_writer(struct mgmt *mgmt) |
| { |
| if (!queue_isempty(mgmt->pending_list)) { |
| /* only queued reply commands trigger wakeup */ |
| if (queue_isempty(mgmt->reply_queue)) |
| return; |
| } |
| |
| if (mgmt->writer_active) |
| return; |
| |
| mgmt->writer_active = true; |
| |
| io_set_write_handler(mgmt->io, can_write_data, mgmt, |
| write_watch_destroy); |
| } |
| |
| struct opcode_index { |
| uint16_t opcode; |
| uint16_t index; |
| }; |
| |
| static bool match_request_opcode_index(const void *a, const void *b) |
| { |
| const struct mgmt_request *request = a; |
| const struct opcode_index *match = b; |
| |
| return request->opcode == match->opcode && |
| request->index == match->index; |
| } |
| |
| static void request_complete(struct mgmt *mgmt, uint8_t status, |
| uint16_t opcode, uint16_t index, |
| uint16_t length, const void *param) |
| { |
| struct opcode_index match = { .opcode = opcode, .index = index }; |
| struct mgmt_request *request; |
| |
| request = queue_remove_if(mgmt->pending_list, |
| match_request_opcode_index, &match); |
| if (!request) { |
| DBG(mgmt, "Unable to find request for opcode 0x%04x", opcode); |
| |
| /* Attempt to remove with no opcode */ |
| request = queue_remove_if(mgmt->pending_list, |
| match_request_index, |
| UINT_TO_PTR(index)); |
| } |
| |
| if (request) { |
| if (request->callback) |
| request->callback(status, length, param, |
| request->user_data); |
| |
| destroy_request(request); |
| } |
| |
| wakeup_writer(mgmt); |
| } |
| |
| struct event_index { |
| uint16_t event; |
| uint16_t index; |
| uint16_t length; |
| const void *param; |
| }; |
| |
| static void notify_handler(void *data, void *user_data) |
| { |
| struct mgmt_notify *notify = data; |
| struct event_index *match = user_data; |
| |
| if (notify->removed) |
| return; |
| |
| if (notify->event != match->event) |
| return; |
| |
| if (notify->index != match->index && notify->index != MGMT_INDEX_NONE) |
| return; |
| |
| if (notify->callback) |
| notify->callback(match->index, match->length, match->param, |
| notify->user_data); |
| } |
| |
| static void process_notify(struct mgmt *mgmt, uint16_t event, uint16_t index, |
| uint16_t length, const void *param) |
| { |
| struct event_index match = { .event = event, .index = index, |
| .length = length, .param = param }; |
| |
| mgmt->in_notify = true; |
| |
| queue_foreach(mgmt->notify_list, notify_handler, &match); |
| |
| mgmt->in_notify = false; |
| |
| if (mgmt->need_notify_cleanup) { |
| queue_remove_all(mgmt->notify_list, match_notify_removed, |
| NULL, destroy_notify); |
| mgmt->need_notify_cleanup = false; |
| } |
| } |
| |
| static bool can_read_data(struct io *io, void *user_data) |
| { |
| struct mgmt *mgmt = user_data; |
| struct mgmt_hdr *hdr; |
| struct mgmt_ev_cmd_complete *cc; |
| struct mgmt_ev_cmd_status *cs; |
| ssize_t bytes_read; |
| uint16_t opcode, event, index, length; |
| |
| bytes_read = read(mgmt->fd, mgmt->buf, mgmt->len); |
| if (bytes_read < 0) |
| return false; |
| |
| if (bytes_read < MGMT_HDR_SIZE) |
| return true; |
| |
| hdr = mgmt->buf; |
| event = btohs(hdr->opcode); |
| index = btohs(hdr->index); |
| length = btohs(hdr->len); |
| |
| if (bytes_read < length + MGMT_HDR_SIZE) |
| return true; |
| |
| mgmt_ref(mgmt); |
| |
| switch (event) { |
| case MGMT_EV_CMD_COMPLETE: |
| cc = mgmt->buf + MGMT_HDR_SIZE; |
| opcode = btohs(cc->opcode); |
| |
| DBG(mgmt, "[0x%04x] command 0x%04x complete: 0x%02x", |
| index, opcode, cc->status); |
| |
| request_complete(mgmt, cc->status, opcode, index, length - 3, |
| mgmt->buf + MGMT_HDR_SIZE + 3); |
| break; |
| case MGMT_EV_CMD_STATUS: |
| cs = mgmt->buf + MGMT_HDR_SIZE; |
| opcode = btohs(cs->opcode); |
| |
| DBG(mgmt, "[0x%04x] command 0x%02x status: 0x%02x", |
| index, opcode, cs->status); |
| |
| request_complete(mgmt, cs->status, opcode, index, 0, NULL); |
| break; |
| default: |
| DBG(mgmt, "[0x%04x] event 0x%04x", index, event); |
| |
| process_notify(mgmt, event, index, length, |
| mgmt->buf + MGMT_HDR_SIZE); |
| break; |
| } |
| |
| mgmt_unref(mgmt); |
| |
| return true; |
| } |
| |
| static void mgmt_set_mtu(struct mgmt *mgmt) |
| { |
| socklen_t len = 0; |
| |
| /* Check if kernel support BT_SNDMTU to read the current MTU set */ |
| if (getsockopt(mgmt->fd, SOL_BLUETOOTH, BT_SNDMTU, &mgmt->mtu, |
| &len) < 0) { |
| /* If BT_SNDMTU is not supported then MTU cannot be changed and |
| * MTU is fixed to HCI_MAX_ACL_SIZE. |
| */ |
| mgmt->mtu = HCI_MAX_ACL_SIZE; |
| return; |
| } |
| |
| if (mgmt->mtu < UINT16_MAX) { |
| uint16_t mtu = UINT16_MAX; |
| |
| /* Try increasing the MTU since some commands may go |
| * over HCI_MAX_ACL_SIZE (1024) |
| */ |
| if (!setsockopt(mgmt->fd, SOL_BLUETOOTH, BT_SNDMTU, &mtu, |
| sizeof(mtu))) |
| mgmt->mtu = mtu; |
| } |
| } |
| |
| struct mgmt *mgmt_new(int fd) |
| { |
| struct mgmt *mgmt; |
| |
| if (fd < 0) |
| return NULL; |
| |
| mgmt = new0(struct mgmt, 1); |
| mgmt->fd = fd; |
| mgmt->close_on_unref = false; |
| |
| mgmt->len = 512; |
| mgmt->buf = malloc(mgmt->len); |
| if (!mgmt->buf) { |
| free(mgmt); |
| return NULL; |
| } |
| |
| mgmt->io = io_new(fd); |
| if (!mgmt->io) { |
| free(mgmt->buf); |
| free(mgmt); |
| return NULL; |
| } |
| |
| mgmt->request_queue = queue_new(); |
| mgmt->reply_queue = queue_new(); |
| mgmt->pending_list = queue_new(); |
| mgmt->notify_list = queue_new(); |
| |
| if (!io_set_read_handler(mgmt->io, can_read_data, mgmt, NULL)) { |
| queue_destroy(mgmt->notify_list, NULL); |
| queue_destroy(mgmt->pending_list, NULL); |
| queue_destroy(mgmt->reply_queue, NULL); |
| queue_destroy(mgmt->request_queue, NULL); |
| io_destroy(mgmt->io); |
| free(mgmt->buf); |
| free(mgmt); |
| return NULL; |
| } |
| |
| mgmt->writer_active = false; |
| |
| mgmt_set_mtu(mgmt); |
| |
| return mgmt_ref(mgmt); |
| } |
| |
| struct mgmt *mgmt_new_default(void) |
| { |
| struct mgmt *mgmt; |
| union { |
| struct sockaddr common; |
| struct sockaddr_hci hci; |
| } addr; |
| int fd; |
| |
| fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, |
| BTPROTO_HCI); |
| if (fd < 0) |
| return NULL; |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.hci.hci_family = AF_BLUETOOTH; |
| addr.hci.hci_dev = HCI_DEV_NONE; |
| addr.hci.hci_channel = HCI_CHANNEL_CONTROL; |
| |
| if (bind(fd, &addr.common, sizeof(addr.hci)) < 0) { |
| close(fd); |
| return NULL; |
| } |
| |
| mgmt = mgmt_new(fd); |
| if (!mgmt) { |
| close(fd); |
| return NULL; |
| } |
| |
| mgmt->close_on_unref = true; |
| |
| return mgmt; |
| } |
| |
| struct mgmt *mgmt_ref(struct mgmt *mgmt) |
| { |
| if (!mgmt) |
| return NULL; |
| |
| __sync_fetch_and_add(&mgmt->ref_count, 1); |
| |
| return mgmt; |
| } |
| |
| void mgmt_unref(struct mgmt *mgmt) |
| { |
| if (!mgmt) |
| return; |
| |
| if (__sync_sub_and_fetch(&mgmt->ref_count, 1)) |
| return; |
| |
| mgmt_unregister_all(mgmt); |
| mgmt_cancel_all(mgmt); |
| |
| queue_destroy(mgmt->reply_queue, NULL); |
| queue_destroy(mgmt->request_queue, NULL); |
| |
| io_set_write_handler(mgmt->io, NULL, NULL, NULL); |
| io_set_read_handler(mgmt->io, NULL, NULL, NULL); |
| |
| io_destroy(mgmt->io); |
| mgmt->io = NULL; |
| |
| if (mgmt->close_on_unref) |
| close(mgmt->fd); |
| |
| if (mgmt->debug_destroy) |
| mgmt->debug_destroy(mgmt->debug_data); |
| |
| free(mgmt->buf); |
| mgmt->buf = NULL; |
| |
| if (!mgmt->in_notify) { |
| queue_destroy(mgmt->notify_list, NULL); |
| queue_destroy(mgmt->pending_list, NULL); |
| free(mgmt); |
| return; |
| } |
| } |
| |
| bool mgmt_set_debug(struct mgmt *mgmt, mgmt_debug_func_t callback, |
| void *user_data, mgmt_destroy_func_t destroy) |
| { |
| if (!mgmt) |
| return false; |
| |
| if (mgmt->debug_destroy) |
| mgmt->debug_destroy(mgmt->debug_data); |
| |
| mgmt->debug_callback = callback; |
| mgmt->debug_destroy = destroy; |
| mgmt->debug_data = user_data; |
| |
| return true; |
| } |
| |
| bool mgmt_set_close_on_unref(struct mgmt *mgmt, bool do_close) |
| { |
| if (!mgmt) |
| return false; |
| |
| mgmt->close_on_unref = do_close; |
| |
| return true; |
| } |
| |
| static struct mgmt_request *create_request(struct mgmt *mgmt, uint16_t opcode, |
| uint16_t index, uint16_t length, |
| const void *param, mgmt_request_func_t callback, |
| void *user_data, mgmt_destroy_func_t destroy, |
| int timeout) |
| { |
| struct mgmt_request *request; |
| struct mgmt_hdr *hdr; |
| |
| if (!opcode) |
| return NULL; |
| |
| if (length > 0 && !param) |
| return NULL; |
| |
| if (length > mgmt->mtu) { |
| printf("length %u > %u mgmt->mtu", length, mgmt->mtu); |
| return NULL; |
| } |
| |
| request = new0(struct mgmt_request, 1); |
| request->len = length + MGMT_HDR_SIZE; |
| request->buf = malloc(request->len); |
| if (!request->buf) { |
| free(request); |
| return NULL; |
| } |
| |
| if (length > 0) |
| memcpy(request->buf + MGMT_HDR_SIZE, param, length); |
| |
| hdr = request->buf; |
| hdr->opcode = htobs(opcode); |
| hdr->index = htobs(index); |
| hdr->len = htobs(length); |
| |
| /* Use a weak reference so requests don't prevent mgmt_unref to |
| * cancel requests and free in case of the last reference is dropped by |
| * the user. |
| */ |
| request->mgmt = mgmt; |
| request->opcode = opcode; |
| request->index = index; |
| |
| request->callback = callback; |
| request->destroy = destroy; |
| request->user_data = user_data; |
| request->timeout = timeout; |
| |
| return request; |
| } |
| |
| struct mgmt_tlv_list *mgmt_tlv_list_new(void) |
| { |
| struct mgmt_tlv_list *tlv_list = new0(struct mgmt_tlv_list, 1); |
| |
| tlv_list->tlv_queue = queue_new(); |
| tlv_list->size = 0; |
| |
| return tlv_list; |
| } |
| |
| static struct mgmt_tlv *mgmt_tlv_new(uint16_t type, uint8_t length, |
| void *value) |
| { |
| struct mgmt_tlv *entry = malloc(sizeof(*entry) + length); |
| |
| if (!entry) |
| return NULL; |
| |
| entry->type = htobs(type); |
| entry->length = length; |
| memcpy(entry->value, value, length); |
| |
| return entry; |
| } |
| |
| static void mgmt_tlv_free(struct mgmt_tlv *entry) |
| { |
| free(entry); |
| } |
| |
| void mgmt_tlv_list_free(struct mgmt_tlv_list *tlv_list) |
| { |
| queue_destroy(tlv_list->tlv_queue, free); |
| free(tlv_list); |
| } |
| |
| uint16_t mgmt_tlv_list_size(struct mgmt_tlv_list *tlv_list) |
| { |
| return tlv_list->size; |
| } |
| |
| bool mgmt_tlv_add(struct mgmt_tlv_list *tlv_list, uint16_t type, uint8_t length, |
| void *value) |
| { |
| struct mgmt_tlv *entry = mgmt_tlv_new(type, length, value); |
| |
| if (!entry) |
| return false; |
| |
| if (!queue_push_tail(tlv_list->tlv_queue, entry)) { |
| mgmt_tlv_free(entry); |
| return false; |
| } |
| |
| tlv_list->size += sizeof(*entry) + entry->length; |
| return true; |
| } |
| |
| static void mgmt_tlv_to_buf(void *data, void *user_data) |
| { |
| struct mgmt_tlv *entry = data; |
| uint8_t **buf_ptr = user_data; |
| size_t entry_size = sizeof(*entry) + entry->length; |
| |
| memcpy(*buf_ptr, entry, entry_size); |
| *buf_ptr += entry_size; |
| } |
| |
| struct mgmt_tlv_list *mgmt_tlv_list_load_from_buf(const uint8_t *buf, |
| uint16_t len) |
| { |
| struct mgmt_tlv_list *tlv_list; |
| const uint8_t *cur = buf; |
| |
| if (!len || !buf) |
| return NULL; |
| |
| tlv_list = mgmt_tlv_list_new(); |
| |
| while (cur < buf + len) { |
| struct mgmt_tlv *entry = (struct mgmt_tlv *)cur; |
| |
| cur += sizeof(*entry) + entry->length; |
| if (cur > buf + len) |
| goto failed; |
| |
| if (!mgmt_tlv_add(tlv_list, entry->type, entry->length, |
| entry->value)) { |
| goto failed; |
| } |
| } |
| |
| return tlv_list; |
| failed: |
| mgmt_tlv_list_free(tlv_list); |
| |
| return NULL; |
| } |
| |
| void mgmt_tlv_list_foreach(struct mgmt_tlv_list *tlv_list, |
| mgmt_tlv_list_foreach_func_t callback, |
| void *user_data) |
| { |
| queue_foreach(tlv_list->tlv_queue, callback, user_data); |
| } |
| |
| unsigned int mgmt_send_tlv(struct mgmt *mgmt, uint16_t opcode, uint16_t index, |
| struct mgmt_tlv_list *tlv_list, |
| mgmt_request_func_t callback, |
| void *user_data, mgmt_destroy_func_t destroy) |
| { |
| uint8_t *buf, *buf_ptr; |
| unsigned int ret; |
| |
| if (!tlv_list) |
| return 0; |
| |
| buf = malloc(tlv_list->size); |
| |
| if (!buf) |
| return 0; |
| |
| buf_ptr = buf; |
| |
| queue_foreach(tlv_list->tlv_queue, mgmt_tlv_to_buf, &buf_ptr); |
| |
| ret = mgmt_send(mgmt, opcode, index, tlv_list->size, (void *)buf, |
| callback, user_data, destroy); |
| free(buf); |
| return ret; |
| } |
| |
| unsigned int mgmt_send_timeout(struct mgmt *mgmt, uint16_t opcode, |
| uint16_t index, uint16_t length, |
| const void *param, mgmt_request_func_t callback, |
| void *user_data, mgmt_destroy_func_t destroy, |
| int timeout) |
| { |
| struct mgmt_request *request; |
| |
| if (!mgmt) |
| return 0; |
| |
| request = create_request(mgmt, opcode, index, length, param, |
| callback, user_data, destroy, timeout); |
| if (!request) |
| return 0; |
| |
| if (mgmt->next_request_id < 1) |
| mgmt->next_request_id = 1; |
| |
| request->id = mgmt->next_request_id++; |
| |
| if (!queue_push_tail(mgmt->request_queue, request)) { |
| free(request->buf); |
| free(request); |
| return 0; |
| } |
| |
| wakeup_writer(mgmt); |
| |
| return request->id; |
| } |
| |
| unsigned int mgmt_send(struct mgmt *mgmt, uint16_t opcode, uint16_t index, |
| uint16_t length, const void *param, |
| mgmt_request_func_t callback, |
| void *user_data, mgmt_destroy_func_t destroy) |
| { |
| return mgmt_send_timeout(mgmt, opcode, index, length, param, callback, |
| user_data, destroy, 0); |
| } |
| |
| unsigned int mgmt_send_nowait(struct mgmt *mgmt, uint16_t opcode, uint16_t index, |
| uint16_t length, const void *param, |
| mgmt_request_func_t callback, |
| void *user_data, mgmt_destroy_func_t destroy) |
| { |
| struct mgmt_request *request; |
| |
| if (!mgmt) |
| return 0; |
| |
| request = create_request(mgmt, opcode, index, length, param, |
| callback, user_data, destroy, |
| 0); |
| if (!request) |
| return 0; |
| |
| if (mgmt->next_request_id < 1) |
| mgmt->next_request_id = 1; |
| |
| request->id = mgmt->next_request_id++; |
| |
| if (!send_request(mgmt, request)) |
| return 0; |
| |
| return request->id; |
| } |
| |
| unsigned int mgmt_reply_timeout(struct mgmt *mgmt, uint16_t opcode, |
| uint16_t index, uint16_t length, |
| const void *param, mgmt_request_func_t callback, |
| void *user_data, mgmt_destroy_func_t destroy, |
| int timeout) |
| { |
| struct mgmt_request *request; |
| |
| if (!mgmt) |
| return 0; |
| |
| request = create_request(mgmt, opcode, index, length, param, |
| callback, user_data, destroy, timeout); |
| if (!request) |
| return 0; |
| |
| if (mgmt->next_request_id < 1) |
| mgmt->next_request_id = 1; |
| |
| request->id = mgmt->next_request_id++; |
| |
| if (!queue_push_tail(mgmt->reply_queue, request)) { |
| free(request->buf); |
| free(request); |
| return 0; |
| } |
| |
| wakeup_writer(mgmt); |
| |
| return request->id; |
| } |
| |
| unsigned int mgmt_reply(struct mgmt *mgmt, uint16_t opcode, uint16_t index, |
| uint16_t length, const void *param, |
| mgmt_request_func_t callback, |
| void *user_data, mgmt_destroy_func_t destroy) |
| { |
| return mgmt_reply_timeout(mgmt, opcode, index, length, param, callback, |
| user_data, destroy, 0); |
| } |
| |
| bool mgmt_cancel(struct mgmt *mgmt, unsigned int id) |
| { |
| struct mgmt_request *request; |
| |
| if (!mgmt || !id) |
| return false; |
| |
| request = queue_remove_if(mgmt->request_queue, match_request_id, |
| UINT_TO_PTR(id)); |
| if (request) |
| goto done; |
| |
| request = queue_remove_if(mgmt->reply_queue, match_request_id, |
| UINT_TO_PTR(id)); |
| if (request) |
| goto done; |
| |
| request = queue_remove_if(mgmt->pending_list, match_request_id, |
| UINT_TO_PTR(id)); |
| if (!request) |
| return false; |
| |
| done: |
| destroy_request(request); |
| |
| wakeup_writer(mgmt); |
| |
| return true; |
| } |
| |
| bool mgmt_cancel_index(struct mgmt *mgmt, uint16_t index) |
| { |
| if (!mgmt) |
| return false; |
| |
| queue_remove_all(mgmt->request_queue, match_request_index, |
| UINT_TO_PTR(index), destroy_request); |
| queue_remove_all(mgmt->reply_queue, match_request_index, |
| UINT_TO_PTR(index), destroy_request); |
| queue_remove_all(mgmt->pending_list, match_request_index, |
| UINT_TO_PTR(index), destroy_request); |
| |
| return true; |
| } |
| |
| bool mgmt_cancel_all(struct mgmt *mgmt) |
| { |
| if (!mgmt) |
| return false; |
| |
| queue_remove_all(mgmt->pending_list, NULL, NULL, destroy_request); |
| queue_remove_all(mgmt->reply_queue, NULL, NULL, destroy_request); |
| queue_remove_all(mgmt->request_queue, NULL, NULL, destroy_request); |
| |
| return true; |
| } |
| |
| unsigned int mgmt_register(struct mgmt *mgmt, uint16_t event, uint16_t index, |
| mgmt_notify_func_t callback, |
| void *user_data, mgmt_destroy_func_t destroy) |
| { |
| struct mgmt_notify *notify; |
| |
| if (!mgmt || !event) |
| return 0; |
| |
| notify = new0(struct mgmt_notify, 1); |
| notify->event = event; |
| notify->index = index; |
| |
| notify->callback = callback; |
| notify->destroy = destroy; |
| notify->user_data = user_data; |
| |
| if (mgmt->next_notify_id < 1) |
| mgmt->next_notify_id = 1; |
| |
| notify->id = mgmt->next_notify_id++; |
| |
| if (!queue_push_tail(mgmt->notify_list, notify)) { |
| free(notify); |
| return 0; |
| } |
| |
| return notify->id; |
| } |
| |
| bool mgmt_unregister(struct mgmt *mgmt, unsigned int id) |
| { |
| struct mgmt_notify *notify; |
| |
| if (!mgmt || !id) |
| return false; |
| |
| notify = queue_remove_if(mgmt->notify_list, match_notify_id, |
| UINT_TO_PTR(id)); |
| if (!notify) |
| return false; |
| |
| if (!mgmt->in_notify) { |
| destroy_notify(notify); |
| return true; |
| } |
| |
| notify->removed = true; |
| mgmt->need_notify_cleanup = true; |
| |
| return true; |
| } |
| |
| bool mgmt_unregister_index(struct mgmt *mgmt, uint16_t index) |
| { |
| if (!mgmt) |
| return false; |
| |
| if (mgmt->in_notify) { |
| queue_foreach(mgmt->notify_list, mark_notify_removed, |
| UINT_TO_PTR(index)); |
| mgmt->need_notify_cleanup = true; |
| } else |
| queue_remove_all(mgmt->notify_list, match_notify_index, |
| UINT_TO_PTR(index), destroy_notify); |
| |
| return true; |
| } |
| |
| bool mgmt_unregister_all(struct mgmt *mgmt) |
| { |
| if (!mgmt) |
| return false; |
| |
| if (mgmt->in_notify) { |
| queue_foreach(mgmt->notify_list, mark_notify_removed, |
| UINT_TO_PTR(MGMT_INDEX_NONE)); |
| mgmt->need_notify_cleanup = true; |
| } else |
| queue_remove_all(mgmt->notify_list, NULL, NULL, destroy_notify); |
| |
| return true; |
| } |
| |
| uint16_t mgmt_get_mtu(struct mgmt *mgmt) |
| { |
| if (!mgmt) |
| return 0; |
| |
| return mgmt->mtu; |
| } |
| |
| char *mgmt_iocap_generator(const char *text, int state) |
| { |
| static int index, len; |
| const char *arg; |
| |
| if (!state) { |
| index = 0; |
| len = strlen(text); |
| } |
| |
| while ((arg = iocap_arguments[index].name)) { |
| index++; |
| |
| if (!strncmp(arg, text, len)) |
| return strdup(arg); |
| } |
| |
| return NULL; |
| } |
| |
| enum mgmt_io_capability mgmt_parse_io_capability(const char *capability) |
| { |
| const char *arg; |
| int index = 0; |
| |
| if (!strcmp(capability, "")) |
| return MGMT_IO_CAPABILITY_KEYBOARDDISPLAY; |
| |
| while ((arg = iocap_arguments[index].name)) { |
| if (!strncmp(arg, capability, strlen(capability))) |
| return iocap_arguments[index].value; |
| |
| index++; |
| } |
| |
| return MGMT_IO_CAPABILITY_INVALID; |
| } |