static void cmd_included(int argcp, char **argvp) { int start = 0x0001; int end = 0xffff; if (conn_state != STATE_CONNECTED) { resp_error(err_BAD_STATE); return; } if (argcp > 1) { start = strtohandle(argvp[1]); if (start < 0) { resp_error(err_BAD_PARAM); return; } end = start; } if (argcp > 2) { end = strtohandle(argvp[2]); if (end < 0) { resp_error(err_BAD_PARAM); return; } } gatt_find_included(attrib, start, end, included_cb, NULL); }
static void cmd_read_hnd(int argcp, char **argvp) { int handle; if (conn_state != STATE_CONNECTED) { resp_error(err_BAD_STATE); return; } if (argcp < 2) { resp_error(err_BAD_PARAM);; return; } handle = strtohandle(argvp[1]); if (handle < 0) { resp_error(err_BAD_PARAM);; return; } if (argcp == 2) { gatt_read_char(attrib, handle, char_read_cb, attrib); } else { char *end; int offset = strtol(argvp[2], &end, 0); if (*end != '\0') { resp_error(err_BAD_PARAM);; return; } gatt_read_char_single(attrib, handle, offset, char_read_single_cb, attrib); } }
static void cmd_char_desc(int argcp, char **argvp) { if (conn_state != STATE_CONNECTED) { resp_error(err_BAD_STATE); return; } if (argcp > 1) { start = strtohandle(argvp[1]); if (start < 0) { resp_error(err_BAD_PARAM); return; } } else start = 0x0001; if (argcp > 2) { end = strtohandle(argvp[2]); if (end < 0) { resp_error(err_BAD_PARAM); return; } } else end = 0xffff; gatt_discover_char_desc(attrib, start, end, char_desc_cb, NULL); }
static void cmd_gatts(int argcp, char **argvp) { if (argcp == 2) { GAttribResultFunc cb_func = NULL; uint8_t *opdu = NULL; size_t olen = gatt_attr_data_from_string(argvp[1], &opdu); DBG("GATTS %p %d %zd", opdu, opt_mtu, olen); /* Check if it is a value gatt server request */ if (opdu && (opt_mtu == 0 || olen <= opt_mtu) && !(opdu[0] == BT_ATT_OP_MTU_REQ || opdu[0] == BT_ATT_OP_FIND_INFO_REQ || opdu[0] == BT_ATT_OP_FIND_BY_TYPE_VAL_REQ || opdu[0] == BT_ATT_OP_READ_BY_TYPE_REQ || opdu[0] == BT_ATT_OP_READ_REQ || opdu[0] == BT_ATT_OP_READ_BLOB_REQ || opdu[0] == BT_ATT_OP_READ_MULT_REQ || opdu[0] == BT_ATT_OP_READ_BY_GRP_TYPE_REQ || opdu[0] == BT_ATT_OP_WRITE_REQ || opdu[0] == BT_ATT_OP_PREP_WRITE_REQ || opdu[0] == BT_ATT_OP_EXEC_WRITE_REQ)) { if (opdu[0] == BT_ATT_OP_HANDLE_VAL_IND) { cb_func = on_indicate_complete; } g_attrib_send(attrib, 0, opdu, olen, cb_func, NULL, NULL); } else { resp_error(err_BAD_PARAM);; } g_free(opdu); } else { resp_error(err_BAD_PARAM);; } }
static void exchange_mtu_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { uint16_t mtu; if (status != 0) { resp_error(err_COMM_ERR); // Todo: status return; } if (!dec_mtu_resp(pdu, plen, &mtu)) { resp_error(err_PROTO_ERR); return; } mtu = MIN(mtu, opt_mtu); /* Set new value for MTU in client */ if (g_attrib_set_mtu(attrib, mtu)) { opt_mtu = mtu; cmd_status(0, NULL); } else { printf("# Error exchanging MTU\n"); resp_error(err_COMM_ERR); } }
static void cmd_mtu(int argcp, char **argvp) { if (conn_state != STATE_CONNECTED) { resp_error(err_BAD_STATE); return; } assert(!opt_psm); if (argcp < 2) { resp_error(err_BAD_PARAM); return; } if (opt_mtu) { resp_error(err_BAD_STATE); /* Can only set once per connection */ return; } errno = 0; opt_mtu = strtoll(argvp[1], NULL, 16); if (errno != 0 || opt_mtu < ATT_DEFAULT_LE_MTU) { resp_error(err_BAD_PARAM); return; } gatt_exchange_mtu(attrib, opt_mtu, exchange_mtu_cb, NULL); }
static void cmd_connect(int argcp, char **argvp) { GError *gerr=NULL; if (conn_state != STATE_DISCONNECTED) return; if (argcp > 1) { g_free(opt_dst); opt_dst = g_strdup(argvp[1]); g_free(opt_dst_type); if (argcp > 2) opt_dst_type = g_strdup(argvp[2]); else opt_dst_type = g_strdup("public"); } if (opt_dst == NULL) { resp_error(err_BAD_PARAM); return; } set_state(STATE_CONNECTING); iochannel = gatt_connect(opt_src, opt_dst, opt_dst_type, opt_sec_level, opt_psm, opt_mtu, connect_cb,&gerr); if (iochannel == NULL) set_state(STATE_DISCONNECTED); else g_io_add_watch(iochannel, G_IO_HUP, channel_watcher, NULL); }
static void char_read_single_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { uint8_t value[plen]; ssize_t vlen; if (status != 0) { resp_error_comm(status); return; } if ((pdu != NULL) && (plen >= 1)) { if (pdu[0] == ATT_OP_READ_RESP) vlen = dec_read_resp(pdu, plen, value, sizeof(value)); else vlen = dec_read_blob_resp(pdu, plen, value, sizeof(value)); } else { vlen = -EINVAL; } if (vlen < 0) { resp_error(err_PROTO_ERR); return; } resp_begin(rsp_READ); send_data(value, vlen); resp_end(); }
static void char_write_req_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { if (status != 0) { resp_error(err_COMM_ERR); // Todo: status return; } if (!dec_write_resp(pdu, plen) && !dec_exec_write_resp(pdu, plen)) { resp_error(err_PROTO_ERR); return; } resp_begin(rsp_WRITE); resp_end(); }
static void cmd_discoverable(int argcp, char **argvp) { struct mgmt_cp_set_discoverable cp = {0, 0}; uint16_t opcode = MGMT_OP_SET_DISCOVERABLE; if (argcp < 2) { resp_mgmt(err_BAD_PARAM); return; } if (!on_or_off(argvp[1], &cp.val)) { resp_mgmt(err_BAD_PARAM); return; } if (!mgmt_master) { resp_error(err_NO_MGMT); return; } if (mgmt_send(mgmt_master, opcode, opt_src_idx, sizeof(cp), &cp, set_mode_complete, NULL, NULL) == 0) { DBG("mgmt_send(MGMT_OP_SET_DISCOVERABLE) failed"); resp_mgmt(err_PROTO_ERR); return; } }
static bool set_mode_with_cb(uint16_t opcode, char *p_mode, mgmt_request_func_t callback) { struct mgmt_mode cp; uintptr_t user_data; memset(&cp, 0, sizeof(cp)); if (!on_or_off(p_mode, &cp.val)) return false; /* Save the configured value in the user_data */ if (cp.val) user_data = 1; else user_data = 0; if (!mgmt_master) { resp_error(err_NO_MGMT); return true; } if (mgmt_send(mgmt_master, opcode, opt_src_idx, sizeof(cp), &cp, callback, (void *)user_data, NULL) == 0) { resp_mgmt(err_SUCCESS); } return true; }
static void parse_line(char *line_read) { gchar **argvp; int argcp; int i; line_read = g_strstrip(line_read); if (*line_read == '\0') goto done; g_shell_parse_argv(line_read, &argcp, &argvp, NULL); for (i = 0; commands[i].cmd; i++) if (strcasecmp(commands[i].cmd, argvp[0]) == 0) break; if (commands[i].cmd) commands[i].func(argcp, argvp); else resp_error(err_BAD_CMD); g_strfreev(argvp); done: free(line_read); }
static void connect_cb(GIOChannel *io, GError *err, gpointer user_data) { uint16_t mtu; uint16_t cid; GError *gerr = NULL; DBG("io = %p, err = %p", io, err); if (err) { DBG("err = %s", err->message); set_state(STATE_DISCONNECTED); resp_error(err_CONN_FAIL); printf("# Connect error: %s\n", err->message); return; } bt_io_get(io, &gerr, BT_IO_OPT_IMTU, &mtu, BT_IO_OPT_CID, &cid, BT_IO_OPT_INVALID); if (gerr) { printf("# Can't detect MTU, using default"); g_error_free(gerr); mtu = ATT_DEFAULT_LE_MTU; } else if (cid == ATT_CID) mtu = ATT_DEFAULT_LE_MTU; attrib = g_attrib_new(iochannel, mtu); g_attrib_register(attrib, ATT_OP_HANDLE_NOTIFY, GATTRIB_ALL_HANDLES, events_handler, attrib, NULL); g_attrib_register(attrib, ATT_OP_HANDLE_IND, GATTRIB_ALL_HANDLES, events_handler, attrib, NULL); g_attrib_register(attrib, ATT_OP_FIND_INFO_REQ, GATTRIB_ALL_HANDLES, req_gatts, NULL, NULL); g_attrib_register(attrib, ATT_OP_FIND_BY_TYPE_REQ, GATTRIB_ALL_HANDLES, req_gatts, NULL, NULL); g_attrib_register(attrib, ATT_OP_READ_BY_TYPE_REQ, GATTRIB_ALL_HANDLES, req_gatts, NULL, NULL); g_attrib_register(attrib, ATT_OP_READ_REQ, GATTRIB_ALL_HANDLES, req_gatts, NULL, NULL); g_attrib_register(attrib, ATT_OP_READ_BLOB_REQ, GATTRIB_ALL_HANDLES, req_gatts, NULL, NULL); g_attrib_register(attrib, ATT_OP_READ_MULTI_REQ, GATTRIB_ALL_HANDLES, req_gatts, NULL, NULL); g_attrib_register(attrib, ATT_OP_READ_BY_GROUP_REQ, GATTRIB_ALL_HANDLES, req_gatts, NULL, NULL); g_attrib_register(attrib, ATT_OP_WRITE_REQ, GATTRIB_ALL_HANDLES, req_gatts, NULL, NULL); g_attrib_register(attrib, ATT_OP_WRITE_CMD, GATTRIB_ALL_HANDLES, req_gatts, NULL, NULL); g_attrib_register(attrib, ATT_OP_SIGNED_WRITE_CMD, GATTRIB_ALL_HANDLES, req_gatts, NULL, NULL); g_attrib_register(attrib, ATT_OP_PREP_WRITE_REQ, GATTRIB_ALL_HANDLES, req_gatts, NULL, NULL); g_attrib_register(attrib, ATT_OP_EXEC_WRITE_REQ, GATTRIB_ALL_HANDLES, req_gatts, NULL, NULL); set_state(STATE_CONNECTED); }
static void char_desc_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { struct att_data_list *list; guint8 format; uint16_t handle = 0xffff; int i; if (status != 0) { resp_error(err_COMM_ERR); // Todo: status return; } list = dec_find_info_resp(pdu, plen, &format); if (list == NULL) { resp_error(err_NOT_FOUND); // Todo: what does this mean? return; } resp_begin(rsp_DESCRIPTORS); for (i = 0; i < list->num; i++) { char uuidstr[MAX_LEN_UUID_STR]; uint8_t *value; bt_uuid_t uuid; value = list->data[i]; handle = att_get_u16(value); if (format == 0x01) uuid = att_get_uuid16(&value[2]); else uuid = att_get_uuid128(&value[2]); bt_uuid_to_string(&uuid, uuidstr, MAX_LEN_UUID_STR); send_uint(tag_HANDLE, handle); send_str (tag_UUID, uuidstr); } resp_end(); att_data_list_free(list); if (handle != 0xffff && handle < end) gatt_discover_char_desc(attrib, handle + 1, end, char_desc_cb, NULL); }
static void cmd_sec_level(int argcp, char **argvp) { GError *gerr = NULL; BtIOSecLevel sec_level; if (argcp < 2) { resp_error(err_BAD_PARAM);; return; } if (strcasecmp(argvp[1], "medium") == 0) sec_level = BT_IO_SEC_MEDIUM; else if (strcasecmp(argvp[1], "high") == 0) sec_level = BT_IO_SEC_HIGH; else if (strcasecmp(argvp[1], "low") == 0) sec_level = BT_IO_SEC_LOW; else { resp_error(err_BAD_PARAM);; return; } g_free(opt_sec_level); opt_sec_level = g_strdup(argvp[1]); if (conn_state != STATE_CONNECTED) return; assert(!opt_psm); bt_io_set(iochannel, &gerr, BT_IO_OPT_SEC_LEVEL, sec_level, BT_IO_OPT_INVALID); if (gerr) { printf("# Error: %s\n", gerr->message); resp_error(err_PROTO_ERR); g_error_free(gerr); } else { /* Tell bluepy the security level * has been changed successfuly */ cmd_status(0, NULL); } }
static void cmd_read_uuid(int argcp, char **argvp) { struct characteristic_data *char_data; int start = 0x0001; int end = 0xffff; bt_uuid_t uuid; if (conn_state != STATE_CONNECTED) { resp_error(err_BAD_STATE); return; } if (argcp < 2 || bt_string_to_uuid(&uuid, argvp[1]) < 0) { resp_error(err_BAD_PARAM); return; } if (argcp > 2) { start = strtohandle(argvp[2]); if (start < 0) { resp_error(err_BAD_PARAM); return; } } if (argcp > 3) { end = strtohandle(argvp[3]); if (end < 0) { resp_error(err_BAD_PARAM); return; } } char_data = g_new(struct characteristic_data, 1); char_data->orig_start = start; char_data->start = start; char_data->end = end; char_data->uuid = uuid; gatt_read_char_by_uuid(attrib, start, end, &char_data->uuid, char_read_by_uuid_cb, char_data); }
int main(int argc, char *argv[]) { GIOChannel *pchan; gint events; const char *hci = "hci0"; opt_sec_level = g_strdup("low"); if (argc > 1) hci = argv[1]; if (parse_dev_src(hci, &opt_src, &opt_src_idx)) { fprintf(stderr,"%s: expected optional argument 'hciX' valid and up\n", argv[0]); resp_error(err_BAD_HCI); return EXIT_FAILURE; } DBG("Using controller hci%d addr:%s", opt_src_idx, opt_src); opt_dst = NULL; opt_dst_type = g_strdup("public"); DBG(__FILE__ " built at " __TIME__ " on " __DATE__); mgmt_setup(); event_loop = g_main_loop_new(NULL, FALSE); pchan = g_io_channel_unix_new(fileno(stdin)); g_io_channel_set_close_on_unref(pchan, TRUE); events = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL; g_io_add_watch(pchan, events, prompt_read, NULL); DBG("Starting loop"); g_main_loop_run(event_loop); DBG("Exiting loop"); cmd_disconnect(0, NULL); fflush(stdout); g_io_channel_unref(pchan); g_main_loop_unref(event_loop); g_free(opt_src); g_free(opt_dst); g_free(opt_dst_type); g_free(opt_sec_level); mgmt_unregister_index(mgmt_master, opt_src_idx); mgmt_cancel_index(mgmt_master, opt_src_idx); mgmt_unref(mgmt_master); mgmt_master = NULL; return EXIT_SUCCESS; }
static void cmd_primary(int argcp, char **argvp) { bt_uuid_t uuid; if (conn_state != STATE_CONNECTED) { resp_error(err_BAD_STATE); return; } if (argcp == 1) { gatt_discover_primary(attrib, NULL, primary_all_cb, NULL); return; } if (bt_string_to_uuid(&uuid, argvp[1]) < 0) { resp_error(err_BAD_PARAM); return; } gatt_discover_primary(attrib, &uuid, primary_by_uuid_cb, NULL); }
static void char_read_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { uint8_t value[plen]; ssize_t vlen; if (status != 0) { resp_error(err_COMM_ERR); // Todo: status return; } vlen = dec_read_resp(pdu, plen, value, sizeof(value)); if (vlen < 0) { resp_error(err_COMM_ERR); return; } resp_begin(rsp_READ); send_data(value, vlen); resp_end(); }
static void cmd_char_write_common(int argcp, char **argvp, int with_response) { uint8_t *value; size_t plen; int handle; if (conn_state != STATE_CONNECTED) { resp_error(err_BAD_STATE); return; } if (argcp < 3) { resp_error(err_BAD_PARAM); return; } handle = strtohandle(argvp[1]); if (handle <= 0) { resp_error(err_BAD_PARAM); return; } plen = gatt_attr_data_from_string(argvp[2], &value); if (plen == 0) { resp_error(err_BAD_PARAM); return; } if (with_response) gatt_write_char(attrib, handle, value, plen, char_write_req_cb, NULL); else { gatt_write_char(attrib, handle, value, plen, NULL, NULL); resp_begin(rsp_WRITE); resp_end(); } g_free(value); }
static void cmd_char(int argcp, char **argvp) { int start = 0x0001; int end = 0xffff; if (conn_state != STATE_CONNECTED) { resp_error(err_BAD_STATE); return; } if (argcp > 1) { start = strtohandle(argvp[1]); if (start < 0) { resp_error(err_BAD_PARAM); return; } } if (argcp > 2) { end = strtohandle(argvp[2]); if (end < 0) { resp_error(err_BAD_PARAM); return; } } if (argcp > 3) { bt_uuid_t uuid; if (bt_string_to_uuid(&uuid, argvp[3]) < 0) { resp_error(err_BAD_PARAM); return; } gatt_discover_char(attrib, start, end, &uuid, char_cb, NULL); return; } gatt_discover_char(attrib, start, end, NULL, char_cb, NULL); }
static void cmd_read_hnd(int argcp, char **argvp) { int handle; if (conn_state != STATE_CONNECTED) { resp_error(err_BAD_STATE); return; } if (argcp < 2) { resp_error(err_BAD_PARAM); return; } handle = strtohandle(argvp[1]); if (handle < 0) { resp_error(err_BAD_PARAM); return; } gatt_read_char(attrib, handle, char_read_cb, attrib); }
static void connect_cb(GIOChannel *io, GError *err, gpointer user_data) { if (err) { set_state(STATE_DISCONNECTED); resp_error(err_CONN_FAIL); printf("# Connect error: %s\n", err->message); return; } attrib = g_attrib_new(iochannel); g_attrib_register(attrib, ATT_OP_HANDLE_NOTIFY, GATTRIB_ALL_HANDLES, events_handler, attrib, NULL); g_attrib_register(attrib, ATT_OP_HANDLE_IND, GATTRIB_ALL_HANDLES, events_handler, attrib, NULL); set_state(STATE_CONNECTED); }
static void cmd_settings(int argcp, char **argvp) { if (1 < argcp) { resp_mgmt(err_BAD_PARAM); } if (!mgmt_master) { resp_error(err_NO_MGMT); return; } if (mgmt_send(mgmt_master, MGMT_OP_READ_INFO, opt_src_idx, 0, NULL, read_info_complete, NULL, NULL) == 0) { DBG("mgmt_send(MGMT_OP_READ_INFO) failed"); resp_mgmt(err_PROTO_ERR); } }
static void primary_by_uuid_cb(GSList *ranges, guint8 status, gpointer user_data) { GSList *l; if (status) { resp_error(err_COMM_ERR); // Todo: status return; } resp_begin(rsp_DISCOVERY); for (l = ranges; l; l = l->next) { struct att_range *range = l->data; send_uint(tag_RANGE_START, range->start); send_uint(tag_RANGE_END, range->end); } resp_end(); }
static void primary_all_cb(GSList *services, guint8 status, gpointer user_data) { GSList *l; if (status) { resp_error(err_COMM_ERR); // Todo: status return; } resp_begin(rsp_DISCOVERY); for (l = services; l; l = l->next) { struct gatt_primary *prim = l->data; send_uint(tag_RANGE_START, prim->range.start); send_uint(tag_RANGE_END, prim->range.end); send_str(tag_UUID, prim->uuid); } resp_end(); }
static void included_cb(GSList *includes, guint8 status, gpointer user_data) { GSList *l; if (status) { resp_error(err_COMM_ERR); // Todo: status return; } resp_begin(rsp_DISCOVERY); for (l = includes; l; l = l->next) { struct gatt_included *incl = l->data; send_uint(tag_HANDLE, incl->handle); send_uint(tag_RANGE_START, incl->range.start); send_uint(tag_RANGE_END, incl->range.end); send_str(tag_UUID, incl->uuid); } resp_end(); }
static void char_cb(GSList *characteristics, guint8 status, gpointer user_data) { GSList *l; if (status) { resp_error(err_COMM_ERR); // Todo: status return; } resp_begin(rsp_DISCOVERY); for (l = characteristics; l; l = l->next) { struct gatt_char *chars = l->data; send_uint(tag_HANDLE, chars->handle); send_uint(tag_PROPERTIES, chars->properties); send_uint(tag_VALUE_HANDLE, chars->value_handle); send_str(tag_UUID, chars->uuid); } resp_end(); }
static void char_read_by_uuid_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { struct characteristic_data *char_data = user_data; struct att_data_list *list; int i; if (status == ATT_ECODE_ATTR_NOT_FOUND && char_data->start != char_data->orig_start) { printf("# TODO case in char_read_by_uuid_cb\n"); goto done; } if (status != 0) { resp_error(err_COMM_ERR); // Todo: status goto done; } list = dec_read_by_type_resp(pdu, plen); resp_begin(rsp_READ); if (list == NULL) goto nolist; for (i = 0; i < list->num; i++) { uint8_t *value = list->data[i]; int j; char_data->start = att_get_u16(value) + 1; send_uint(tag_HANDLE, att_get_u16(value)); send_data(value+2, list->len-2); // All the same length?? } att_data_list_free(list); nolist: resp_end(); done: g_free(char_data); }
// Unlike Bluez, we follow BT 4.0 spec which renammed Device Discovery by Scan static void scan(bool start) { // mgmt_cp_start_discovery and mgmt_cp_stop_discovery are the same struct mgmt_cp_start_discovery cp = { (1 << BDADDR_LE_PUBLIC) | (1 << BDADDR_LE_RANDOM) }; uint16_t opcode = start? MGMT_OP_START_DISCOVERY : MGMT_OP_STOP_DISCOVERY; DBG("Scan %s", start? "start" : "stop"); if (!mgmt_master) { resp_error(err_NO_MGMT); return; } if (mgmt_send(mgmt_master, opcode, opt_src_idx, sizeof(cp), &cp, scan_cb, NULL, NULL) == 0) { DBG("mgmt_send(MGMT_OP_%s_DISCOVERY) failed", start? "START" : "STOP"); resp_mgmt(err_PROTO_ERR); return; } }