diff options
author | Magnus Feuer <mfeuer@jaguarlandrover.com> | 2015-06-06 10:36:06 -0700 |
---|---|---|
committer | Magnus Feuer <mfeuer@jaguarlandrover.com> | 2015-06-09 13:53:55 -0700 |
commit | a13fd36530c1287ac4a3371cb44841de74333a25 (patch) | |
tree | 01df9d03094b4582c1157ac73585a47ab50fb0b4 /deps/bt | |
parent | e23ca6ea6aae0c1525c4e783ebbff3781f1e5de4 (diff) | |
download | rvi_core-a13fd36530c1287ac4a3371cb44841de74333a25.tar.gz |
Bumped to 0.4.0. Added bt deps.
Diffstat (limited to 'deps/bt')
47 files changed, 21341 insertions, 0 deletions
diff --git a/deps/bt/.gitignore b/deps/bt/.gitignore new file mode 100644 index 0000000..b25c15b --- /dev/null +++ b/deps/bt/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/deps/bt/README.md b/deps/bt/README.md new file mode 100644 index 0000000..3c32f92 --- /dev/null +++ b/deps/bt/README.md @@ -0,0 +1,5 @@ +Bluetooth API for Erlang +======================== + +Currently mac os x only. + diff --git a/deps/bt/c_src/.gitignore b/deps/bt/c_src/.gitignore new file mode 100644 index 0000000..6142305 --- /dev/null +++ b/deps/bt/c_src/.gitignore @@ -0,0 +1,2 @@ +*.o +*.d diff --git a/deps/bt/c_src/bt_drv.h b/deps/bt/c_src/bt_drv.h new file mode 100644 index 0000000..623f77e --- /dev/null +++ b/deps/bt/c_src/bt_drv.h @@ -0,0 +1,96 @@ +#ifndef __BT_DRV_H__ +#define __BT_DRV_H__ + +#include <stdint.h> + +#include "bt_sub.h" + +#define CMD_PING 1 +#define CMD_RECENT_DEVICES 2 +#define CMD_PAIRED_DEVICES 3 +#define CMD_FAVORITE_DEVICES 4 +#define CMD_INQUIRY_START 5 +#define CMD_INQUIRY_STOP 6 +#define CMD_REMOTE_NAME 7 +#define CMD_CONNECT 8 +#define CMD_DISCONNECT 9 +#define CMD_DEVICE_INFO 10 +#define CMD_SERVICE_INFO 11 +#define CMD_SERVICE_QUERY 12 +#define CMD_SERVICE_ADD 13 +#define CMD_SERVICE_DEL 14 +#define CMD_SERVICE_RFCOMM 15 +#define CMD_LOCAL_INFO 16 +#define CMD_DEBUG 17 + +/* RCCOMM Channels */ +#define CMD_RFCOMM_OPEN 20 +#define CMD_RFCOMM_CLOSE 21 +#define CMD_RFCOMM_LISTEN 22 +#define CMD_RFCOMM_SEND 23 +#define CMD_RFCOMM_ACCEPT 24 +#define CMD_RFCOMM_MTU 25 +#define CMD_RFCOMM_ADDRESS 26 +#define CMD_RFCOMM_CHANNEL 27 + +/* L2CAP */ +#define CMD_L2CAP_OPEN 30 +#define CMD_L2CAP_CLOSE 31 +#define CMD_L2CAP_LISTEN 32 +#define CMD_L2CAP_SEND 33 +#define CMD_L2CAP_ACCEPT 34 +#define CMD_L2CAP_MTU 35 +#define CMD_L2CAP_ADDRESS 36 +#define CMD_L2CAP_PSM 37 + +/* device info codes */ +#define NFO_DEVICE_NAME 1 /* string */ +#define NFO_DEVICE_CLASS 2 /* uint32 */ +#define NFO_DEVICE_CLOCK 3 /* uint16 */ +#define NFO_DEVICE_INQUIRY 4 /* date */ +#define NFO_DEVICE_ACCESS 5 /* date */ +#define NFO_DEVICE_UPDATE 6 /* date */ +#define NFO_DEVICE_IS_FAVORITE 7 /* Boolean */ +#define NFO_DEVICE_IS_PAIRED 8 /* Boolean */ +#define NFO_DEVICE_IS_CONNECTED 9 /* Boolean */ + +/* local info codes */ +#define NFO_LOCAL_NAME 1 /* string */ +#define NFO_LOCAL_CLASS 2 /* uint32 */ +#define NFO_LOCAL_ADDRESS 3 /* addr */ +#define NFO_LOCAL_DISCOVERABLE 4 /* Boolean */ +#define NFO_LOCAL_POWER_STATE 5 /* on | off */ +/* add more */ + +#define REPLY_OK 1 +#define REPLY_ERROR 2 +#define REPLY_EVENT 3 + +/* extension data types */ +#define ADDR 100 /* bluetooth address 6 bytes */ +#define DATE 101 /* uint32 seconds since 1970 unix-time */ + +typedef struct _bt_ctx_t +{ + size_t pbuf_len; // number of bytes in pbuf + uint8_t pbuf[4]; // packet length bytes + const uint8_t* ptr; // data ptr + size_t len; // length of data + size_t remain; // remaining bytes to read + uint8_t* packet; // the data packet being built + subscription_list_t list; + void* drv_data; // Extra, per-driver specific data. +} bt_ctx_t; + +#define LISTEN_QUEUE_LENGTH 8 /* max connections can only be 7 ? */ + +typedef struct { + bt_ctx_t* ctx; // access to subscription list + int qh; + int qt; + void* qelem[LISTEN_QUEUE_LENGTH]; + subscription_list_t wait; +} listen_queue_t; + +#endif + diff --git a/deps/bt/c_src/bt_linux_drv.c b/deps/bt/c_src/bt_linux_drv.c new file mode 100644 index 0000000..9dc54b9 --- /dev/null +++ b/deps/bt/c_src/bt_linux_drv.c @@ -0,0 +1,1088 @@ +// +// Work in progress (linux) bluetooth driver +// + +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <string.h> +#include <memory.h> +#include <poll.h> +#include <errno.h> +#include <fcntl.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/l2cap.h> +#include <bluetooth/rfcomm.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> + +#include "dthread/include/dlog.h" +#include "dthread/include/ddata.h" +#include "bt_drv.h" +#include "bt_poll.h" + +#define RFCOMM_CHANNEL_ID_IS_VALID(x) (((x) >= 1) && ((x) <= 30)) +#define PSM_IS_VALID(x) (((x) >= 0) && ((x) <= 0xffff)) + +#define PTR2INT(x) ((int)((long)(x))) +#define INT2PTR(x) ((void*)((long)(x))) + +#define alloc_type(type) calloc(1, sizeof(type)) + +typedef struct _drv_data_t { + int dev_id; + int mgmt_sock; + int dev_sock; + int inquiry_pid; + int inquiry_fd; +} drv_data_t; + +int set_nonblock(int fd) +{ + int flags; +#if defined(O_NONBLOCK) + if (-1 == (flags = fcntl(fd, F_GETFL, 0))) + flags = 0; + return fcntl(fd, F_SETFL, flags | O_NONBLOCK); +#else + flags = 1; + return ioctl(fd, FIOBIO, &flags); +#endif +} + + +#define ERR_SHORT 1 +#define ERR_LONG 2 + + + +static int mgmt_open(void) +{ + struct sockaddr_hci addr; + int sk; + + sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (sk < 0) { + fprintf(stderr, "socket: %s\n", strerror(errno)); + return sk; + } + + memset(&addr, 0, sizeof(addr)); + addr.hci_family = AF_BLUETOOTH; + addr.hci_dev = HCI_DEV_NONE; + addr.hci_channel = HCI_CHANNEL_CONTROL; + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + fprintf(stderr, "bind: %s\n", strerror(errno)); + close(sk); + return -1; + } + + return sk; +} + + +static inline int get_address(ddata_t* data, bdaddr_t* val) +{ + if (ddata_r_avail(data) >= sizeof(bdaddr_t)) { + // For some reason, the address is reversed when it + // hits bt_linux_drv. + val->b[0] = *(data->rd+5); + val->b[1] = *(data->rd+4); + val->b[2] = *(data->rd+3); + val->b[3] = *(data->rd+2); + val->b[4] = *(data->rd+1); + val->b[5] = *(data->rd+0); + + // Was: memcpy(val, data->rd, sizeof(bdaddr_t)); + data->rd += sizeof(bdaddr_t); + return 1; + } + return 0; +} + +static inline void ddata_put_addr(ddata_t* data, bdaddr_t* addr) +{ + uint8_t* ptr = ddata_alloc(data, sizeof(bdaddr_t)+1); + *ptr++ = ADDR; + *ptr++ = addr->b[5]; + *ptr++ = addr->b[4]; + *ptr++ = addr->b[3]; + *ptr++ = addr->b[2]; + *ptr++ = addr->b[1]; + *ptr++ = addr->b[0]; +} + + +static void bt_local_info(drv_data_t* lctx, ddata_t* data_in, ddata_t* data_out) +{ + uint8_t op_code; + + ddata_put_tag(data_out, LIST); + + while(ddata_get_uint8(data_in, &op_code)) { + switch(op_code) { + case NFO_LOCAL_NAME: { + struct hci_dev_info di; + + DEBUGF("local_info: local name"); + + hci_devinfo(lctx->dev_id, &di); + + ddata_put_string(data_out, di.name); + break; + } + + case NFO_LOCAL_CLASS: { + uint8_t cls_str[4]; + uint32_t cls; + + memset(cls_str, 0, sizeof(cls_str)); + // Send in the lowest three bytes of the uint32 to + // retrieve the three byte class + if (hci_read_class_of_dev(lctx->dev_sock, cls_str+1, 0) == -1) { + DEBUGF("hci_read_class_of_dev() failed: %s", strerror(errno)); + break; + } + + cls = *((int*)cls_str); + + DEBUGF("local_info: local class: %X", cls); + ddata_put_uint32(data_out, cls); + break; + } + + case NFO_LOCAL_ADDRESS: { + struct hci_dev_info di; + DEBUGF("local_info: local address"); + hci_devinfo(lctx->dev_id, &di); + ddata_put_addr(data_out, &(di.bdaddr)); + break; + } + + case NFO_LOCAL_DISCOVERABLE: { + // Boolean value; + // if (IOBluetoothLocalDeviceGetDiscoverable(&value) == kIOReturnSuccess) + DEBUGF("local_info: discoverable"); + ddata_put_boolean(data_out, 1); + break; + } + + case NFO_LOCAL_POWER_STATE: { + /* BluetoothHCIPowerState powerState = */ + /* [[IOBluetoothHostController defaultController] powerState]; */ + /* if (powerState == kBluetoothHCIPowerStateON) */ + /* ddata_put_atom(data_out, "on"); */ + /* else if (powerState == kBluetoothHCIPowerStateOFF) */ + /* ddata_put_atom(data_out, "off"); */ + /* else if (powerState == kBluetoothHCIPowerStateUnintialized) */ + /* ddata_put_atom(data_out, "unintialized"); */ + /* else */ + DEBUGF("local_info: power_state"); + ddata_put_atom(data_out, "on"); + + break; + } + default: + break; + } + } + ddata_put_tag(data_out, LIST_END); +} + + +static void launch_inquiry_proc(drv_data_t *lctx, uint32_t max_seconds) { + inquiry_info *ii = NULL; + int32_t max_rsp; + int32_t num_rsp; + int dev_id, sock, len, flags; + int i; + int pfd[2]; + int child_pid; + + pipe(pfd); + if ((child_pid = fork()) > 0) { + close(pfd[1]); + lctx->inquiry_pid = child_pid; + lctx->inquiry_fd = pfd[0]; + return ; + } + + // We are child + close(pfd[0]); + + // Open a new socket to the adapter + // just so that we don't mess up something in the stack + // by accessing the same fd from two different processes. + dev_id = hci_get_route(NULL); + sock = hci_open_dev( dev_id ); + + if (dev_id < 0 || sock < 0) { + num_rsp = -1; + write(pfd[1], &num_rsp, sizeof(num_rsp)); + close(pfd[1]); + close(sock); + exit(0); + + } + + len = (int32_t) ((float) max_seconds / 1.28); // 1.28 second per length unit + max_rsp = 255; + flags = IREQ_CACHE_FLUSH; + + ii = (inquiry_info*)malloc(max_rsp * sizeof(inquiry_info)); + + num_rsp = hci_inquiry(dev_id, len, max_rsp, NULL, &ii, flags); + + if( num_rsp < 0 ) { + num_rsp = 0; + write(pfd[1], &num_rsp, sizeof(num_rsp)); + close(pfd[1]); + free( ii ); + close( sock ); + exit(0); + } + + bdaddr_t resp[num_rsp]; + + // Retrieve address and remote name + for (i = 0; i < num_rsp; i++) + resp[i] = (ii+i)->bdaddr; + + // Write number of responses that we have. + printf("Num_resp: %d\n", num_rsp); + write(pfd[1], &num_rsp, sizeof(num_rsp)); + write(pfd[1], resp, sizeof(resp)); + + close(pfd[1]); + free( ii ); + close( sock ); + exit(0); +} + + + +/* type: 1=short-atom 2=string-long 3=both (encapsule in LIST ot TUPLE) */ +static void ddata_put_io_error(ddata_t* data, int errnum, int type) +{ + if (errnum == 0) { + if (type&1) ddata_put_atom(data, "Success"); + if (type&2) ddata_put_string(data, "OK"); + } + else { + if (type&1) ddata_put_atom(data, "Error"); // fixme + if (type&2) ddata_put_string(data, strerror(errnum)); + } +} + + +static void cleanup(subscription_t* s) +{ + DEBUGF("cleanup: %s", format_subscription(s)); + drv_data_t* lctx = (drv_data_t*) s->handle; + + switch(s->type) { + case INQUIRY: { + bt_poll_del(lctx->inquiry_fd); + close(lctx->inquiry_fd); + + kill(lctx->inquiry_pid, SIGHUP); + lctx->inquiry_pid = -1; + lctx->inquiry_fd = -1; + wait(0); + break; + } + + case REMOTE_NAME: break; + case CONNECT: break; + case SDP_QUERY: break; + case SDP: break; + case RFCOMM: { + bt_poll_del(PTR2INT(s->handle)); + close(PTR2INT(s->handle)); + break; + } + case RFCOMM_LISTEN: { + bt_poll_del(PTR2INT(s->handle)); + close(PTR2INT(s->handle)); + break; + } + case L2CAP: break; + case L2CAP_LISTEN: break; + default: // warn? + break; + } +} + +// setup "standard" reply buf, with initial 32 but size +static void mesg_setup(ddata_t* data, uint8_t* buf, size_t size) +{ + ddata_init(data, buf, size, 0); + ddata_put_UINT32(data, 0); +} + +/* Send OK reply */ +static void reply_ok(uint32_t cmdid) +{ + uint8_t buf[16]; + ddata_t data; + + mesg_setup(&data, buf, sizeof(buf)); + ddata_put_tag(&data, REPLY_OK); + ddata_put_UINT32(&data, cmdid); + ddata_send(&data, 1); + ddata_final(&data); +} + +/* Send ERROR reply */ +static void reply_error(uint32_t cmdid, int err) +{ + uint8_t buf[128]; + ddata_t data; + + mesg_setup(&data, buf, sizeof(buf)); + ddata_put_tag(&data, REPLY_ERROR); + ddata_put_UINT32(&data, cmdid); + ddata_put_io_error(&data, err, ERR_SHORT); + ddata_send(&data, 1); + ddata_final(&data); +} + +// simple event +static void send_event(uint32_t sid, const char* evtname) +{ + uint8_t buf[64]; + ddata_t data; + + mesg_setup(&data, buf, sizeof(buf)); + ddata_put_tag(&data, REPLY_EVENT); + ddata_put_UINT32(&data, sid); + ddata_put_atom(&data, evtname); + ddata_send(&data, 1); + ddata_final(&data); +} + + +// CALLBACK +static void rfcomm_running(struct pollfd* pfd, void* arg) +{ + subscription_t* s = arg; + uint8_t buf[1024]; + uint8_t bt_data[800]; + int32_t bt_data_len; + ddata_t data; + + + if (pfd->revents & POLLIN) { // input ready + DEBUGF("rfcomm_running: %d has input", PTR2INT(s->handle)); + + bt_data_len = read(pfd->fd, bt_data, sizeof(bt_data)); + + ddata_init(&data, buf, sizeof(buf), 0); + ddata_put_UINT32(&data, 0); + ddata_put_tag(&data, REPLY_EVENT); + ddata_put_UINT32(&data, s->id); + ddata_put_tag(&data, TUPLE); + ddata_put_atom(&data, "data"); + ddata_put_binary(&data, bt_data, bt_data_len); + ddata_put_tag(&data, TUPLE_END); + ddata_send(&data, 1); + ddata_final(&data); + } + + if (pfd->revents & POLLHUP) { // close + DEBUGF("rfcomm_running: %d Hangup", PTR2INT(s->handle)); + ddata_init(&data, buf, sizeof(buf), 0); + ddata_put_UINT32(&data, 0); + ddata_put_tag(&data, REPLY_EVENT); + ddata_put_UINT32(&data, s->id); + ddata_put_atom(&data, "closed"); + ddata_send(&data, 1); + ddata_final(&data); + + release_subscription(s); + bt_poll_del(pfd->fd); + shutdown(pfd->fd, 2); + close(pfd->fd); + } + if (pfd->revents & POLLOUT) { // output ready + DEBUGF("rfcomm_running: %d may output", PTR2INT(s->handle)); + // FIXME: Send additional pending data. + } +} + +// CALLBACK +static void rfcomm_connected(struct pollfd* pfd, void* arg) +{ + subscription_t* s = arg; + (void) pfd; + DEBUGF("rfcomm_connected: %d", PTR2INT(s->handle)); + reply_ok(s->cmdid); + // FIXME: check error + s->cmdid = 0; + bt_poll_set_cb(PTR2INT(s->handle), rfcomm_running); + bt_poll_set_events(PTR2INT(s->handle), POLLIN); +} + + + +// CALLBACK +static void rfcomm_accept(struct pollfd* pfd, void* arg) +{ + subscription_t *accept_s = (subscription_t*) arg; + + // We have a pending client connection + struct sockaddr_rc rem_addr = { 0 }; + int client; + socklen_t alen = sizeof(rem_addr); + uint8_t buf[64]; + ddata_t data; + + client = accept(pfd->fd, (struct sockaddr *)&rem_addr, &alen); + + /* send EVENT id {accept,Address,Channel} */ + + bt_poll_add(client, POLLIN | POLLHUP, rfcomm_running, accept_s); + accept_s->handle = INT2PTR(client); + + ddata_init(&data, buf, sizeof(buf), 0); + ddata_put_UINT32(&data, 0); + ddata_put_tag(&data, REPLY_EVENT); + ddata_put_UINT32(&data, accept_s->id); + ddata_put_tag(&data, TUPLE); + ddata_put_atom(&data, "accept"); + ddata_put_addr(&data, &rem_addr.rc_bdaddr); + ddata_put_uint8(&data, PTR2INT(accept_s->handle)); + ddata_put_tag(&data, TUPLE_END); + ddata_send(&data, 1); + ddata_final(&data); +} + + +// +static void hci_inquiry_result(struct pollfd* pfd, void* arg) +{ + subscription_t* s = arg; + uint8_t out_buf[2048]; + ddata_t data_out; + + DEBUGF("hci_inquiry_result: %d", PTR2INT(s->handle)); + int32_t count; + + + // Read count, which may be -1. + read(pfd->fd, &count, sizeof(count)); + + DEBUGF("hci_inquiry_res: %d addresses\n", count); + + ddata_init(&data_out, out_buf, sizeof(out_buf), 0); + + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, s->cmdid); + ddata_put_tag(&data_out, LIST); + + while(count-- > 0) { + bdaddr_t resp; + read(pfd->fd, &resp, sizeof(resp)); + ddata_put_addr(&data_out, &resp); + } + + ddata_put_tag(&data_out, LIST_END); + + bt_poll_del(pfd->fd); + + close(pfd->fd); + + // Wait for child process to die. + wait(0); + + ddata_send(&data_out, 1); + ddata_final(&data_out); + + s->cmdid = 0; + + // only for Inquiry objects + release_subscription(s); +} + + +void bt_command(bt_ctx_t* ctx, const uint8_t* src, uint32_t src_len) +{ + uint8_t op = 0; + uint32_t cmdid = 0; + int bt_error = 0; + uint8_t out_buf[2048]; + ddata_t data_in; + ddata_t data_out; + drv_data_t* lctx = (drv_data_t*) ctx->drv_data; + + // dump subscription list + if (dlog_debug_level == DLOG_DEBUG) { + subscription_link_t* p = ctx->list.first; + fprintf(stderr, "ctx.list = {"); + while(p) { + fprintf(stderr, " %s,", format_subscription(p->s)); + p = p->next; + } + fprintf(stderr, "}\r\n"); + } + + ddata_r_init(&data_in, (uint8_t*)src, src_len, 0); + mesg_setup(&data_out, out_buf, sizeof(out_buf)); + + if (!ddata_get_uint8(&data_in, &op)) + goto badarg; + if (!ddata_get_uint32(&data_in, &cmdid)) + goto badarg; + + switch (op) { + case CMD_PING: { + DEBUGF("CMD_PING cmdid=%d", cmdid); + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + ddata_put_string(&data_out, "pong"); + goto reply; + } + case CMD_DEBUG: { // <<level>> + int level; + DEBUGF("CMD_DEBUG cmdid=%d", cmdid); + if (!ddata_get_int32(&data_in, &level)) + goto badarg; + dlog_set_debug(level); + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + goto reply; + } + + + case CMD_RFCOMM_OPEN: { /* id:32 bt-address(6) channel-id:8 */ + uint32_t sid; + bdaddr_t bt_addr; + struct sockaddr_rc addr; + int sock; + int r; + uint8_t channel_id; + subscription_t* s; + char addrstr[40]; + + DEBUGF("CMD_RFCOMM_OPEN cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if(!get_address(&data_in, &bt_addr)) + goto badarg; + if(!ddata_get_uint8(&data_in, &channel_id)) + goto badarg; + if (!RFCOMM_CHANNEL_ID_IS_VALID(channel_id)) + goto badarg; + if (ddata_r_avail(&data_in) != 0) + goto badarg; + + if ((sock = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) + goto bt_error; + + addr.rc_family = AF_BLUETOOTH; + addr.rc_channel = channel_id; + bacpy(&addr.rc_bdaddr, &bt_addr); + ba2str(&bt_addr, addrstr ); + DEBUGF("Will connect to %s channel %d", addrstr, channel_id); + if (set_nonblock(sock) < 0) { + bt_error = errno; + close(sock); + goto bt_error; + } + if ((r = connect(sock, (struct sockaddr*)&addr, sizeof(addr))) < 0) { + if (errno != EINPROGRESS) { + bt_error = errno; + close(sock); + goto bt_error; + } + } + + if ((s = new_subscription(RFCOMM,sid,cmdid,0,cleanup)) == NULL) { + close(sock); + goto mem_error; + } + if (r < 0) // inprogress + bt_poll_add(sock, POLLOUT, rfcomm_connected, s); + else + bt_poll_add(sock, POLLIN, rfcomm_running, s); + s->handle = INT2PTR(sock); + insert_last(&ctx->list, s); + break; + } + + + case CMD_RFCOMM_LISTEN: { /* id:32, channel:8 */ + uint32_t sid = 0; + uint8_t channel = 0; + subscription_t* listen_sub = 0; +// listen_queue_t* lq = 0; + int listen_desc = 0; + struct sockaddr_rc loc_addr = { 0 }; + int dev_id; + struct hci_dev_info dev_info; + char buf[32]; + + DEBUGF("CMD_RFCOMM_LISTEN cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + + if(!ddata_get_uint8(&data_in, &channel)) + goto badarg; + + // Fixme auto-allocated channel ID. + if ((channel == 0) || !RFCOMM_CHANNEL_ID_IS_VALID(channel)) + goto badarg; + + if (ddata_r_avail(&data_in) != 0) + goto badarg; + + + if ((listen_sub = new_subscription(RFCOMM_LISTEN, + sid, + cmdid, + NULL, + cleanup)) == NULL) + goto mem_error; + + dev_id = hci_get_route(NULL); + hci_devinfo(dev_id, &dev_info); + ba2str( &dev_info.bdaddr, buf ); + + DEBUGF("Listening on device %s\n", buf); + + // allocate socket + listen_desc = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + + // bind socket to port 1 of the first available + // local bluetooth adapter + loc_addr.rc_family = AF_BLUETOOTH; + loc_addr.rc_bdaddr = *BDADDR_ANY; + loc_addr.rc_channel = (uint8_t) channel; + + if (bind(listen_desc, + (struct sockaddr *) &loc_addr, + sizeof(loc_addr)) == -1) { + DEBUGF("bind(%d) failed: %s", channel, strerror(errno)); + goto error; + } + + // put socket into listening mode + if (listen(listen_desc, 1) == -1) { + DEBUGF("listen(%d) failed: %s", channel, strerror(errno)); + goto error; + } + + listen_sub->handle = INT2PTR(listen_desc); + listen_sub->opaque = INT2PTR(channel); + insert_last(&ctx->list, listen_sub); + + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + goto reply; + } + + + case CMD_RFCOMM_ACCEPT: { /* id:32 listen_id:32 */ + uint32_t sid = 0; + uint32_t listen_id = 0; +// listen_queue_t* lq = 0; + subscription_t* listen = 0; + subscription_t* s = 0; + + DEBUGF("CMD_RFCOMM_ACCEPT cmdid=%d", cmdid); + + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + + if (!ddata_get_uint32(&data_in, &listen_id)) + goto badarg; + + if (ddata_r_avail(&data_in) != 0) + goto badarg; + + if (find_subscription(&ctx->list,RFCOMM, sid) != NULL) { + DEBUGF("subscription %d already exists", sid); + goto badarg; + } + + if ((listen = find_subscription(&ctx->list, + RFCOMM_LISTEN,listen_id))==NULL) { + DEBUGF("listen subscription %d does not exists", listen_id); + goto badarg; + } + + if ((s = new_subscription(RFCOMM,sid,cmdid,NULL,cleanup)) == NULL) + goto mem_error; + + // s->accept = listen; // mark that we are accepting + + bt_poll_add(PTR2INT(listen->handle), POLLIN, rfcomm_accept, s); + s->handle = listen->opaque; + insert_last(&ctx->list, s); + + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + ddata_send(&data_out, 1); + goto done; + } + + case CMD_RFCOMM_CLOSE: { /* arguments: id:32 */ + uint32_t sid; + subscription_link_t* link; + + DEBUGF("CMD_RFCOMM_CLOSE cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if (ddata_r_avail(&data_in) != 0) + goto badarg; + + if ((link = find_subscription_link(&ctx->list,RFCOMM,sid)) != NULL) { + subscription_t* s = link->s; + int sock; + s->cmdid = cmdid; + sock = PTR2INT(s->handle); + if (sock >= 0) { + DEBUGF("RFCOMM_CLOSE: channel=%d", sock); + bt_poll_del(sock); + close(sock); + unlink_subscription(link); + goto done; + } + /* else if (s->accept != NULL) { */ + /* listen_queue_t* lq = (listen_queue_t*)((s->accept)->opaque); */ + /* remove_subscription(&lq->wait,RFCOMM,sid); */ + /* unlink_subscription(link); */ + /* goto ok; */ + /* } */ + } + else if ((link = find_subscription_link(&ctx->list,RFCOMM_LISTEN,sid)) != NULL) { + /* subscription_t* listen = link->s; */ + /* listen_queue_t* lq = (listen_queue_t*)listen->opaque; */ + /* subscription_link_t* link1; */ + /* /\* remove all waiters *\/ */ + /* while((link1=lq->wait.first) != NULL) { */ + /* send_event(link1->s->id, "closed"); */ + /* unlink_subscription(link1); */ + /* } */ + /* unlink_subscription(link); */ + goto ok; + } + goto error; + } + + + case CMD_RFCOMM_SEND: { /* id:32, data/rest */ + uint32_t sid; + subscription_t* s; + int r; + int len; + int sock; + + DEBUGF("CMD_RFCOMM_SEND cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if ((s = find_subscription(&ctx->list,RFCOMM,sid)) == NULL) + goto badarg; + if ((sock = PTR2INT(s->handle)) < 0) + goto badarg; + s->cmdid = cmdid; + + if ((len = ddata_r_avail(&data_in)) > 0) { + if (s->out == NULL) + s->out = ddata_new(data_in.rd, len); + else + ddata_add(s->out, data_in.rd, len); + r = write(sock, s->out->rd, ddata_r_avail(s->out)); + if (r != -1) + DEBUGF("CMD_RFCOMM_SEND wrote=%d", r); + else + DEBUGF("CMD_RFCOMM_SEND failed: %s", strerror(errno)); + + if ((r >= 0) || ((r < 0) && (errno == EINPROGRESS))) { + if (r < 0) r = 0; + s->out->rd += r; + if (ddata_r_avail(s->out) > 0) + bt_poll_set_events(PTR2INT(s->handle), POLLIN|POLLOUT); + else { + ddata_reset(s->out); + s->cmdid = 0; + goto ok; + } + } + else { + s->cmdid = 0; + goto error; + } + } + else { + s->cmdid = 0; + goto ok; + } + goto done; + } + + + case CMD_INQUIRY_START: { /* arguments: id:32 secs:32 */ + uint32_t sid; + uint32_t secs; + + subscription_t* s; + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + + if (!ddata_get_uint32(&data_in, &secs)) + goto badarg; + + DEBUGF("CMD_INQUIRY_START: %d", sid); + if (lctx->inquiry_pid != -1) { + DEBUGF("CMD_INQUIRY_START: Already in progress"); + goto error; + } + + // hci_inquiry() is very, very blocking. We need to run it in a separate + // process in order to maintain asynchronicity. + // Will set ctx->drv_data->inquriry_pid and inquiry_fd + launch_inquiry_proc(lctx, secs); + DEBUGF("CMD_INQUIRY_START: process launched"); + + if ((s = new_subscription(INQUIRY,sid,0,lctx, cleanup)) == NULL) + goto mem_error; + + + bt_poll_add(lctx->inquiry_fd, POLLIN, hci_inquiry_result, ctx); + insert_last(&ctx->list, s); + DEBUGF("CMD_INQUIRY_START: Done"); + + goto ok; + } + + case CMD_INQUIRY_STOP: { /* arguments: id:32 secs:32 */ + uint32_t sid; + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + + // Will call cleanup, which will handle correct shutdown + // of inquiry process. + remove_subscription(&ctx->list,INQUIRY,sid); + + goto ok; + } + + case CMD_RECENT_DEVICES: { + DEBUGF("CMD_RECENT_DEVICES cmdid=%d: Not supported under linux", cmdid); + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + ddata_put_tag(&data_out, LIST); + ddata_put_tag(&data_out, LIST_END); + goto reply; + } + + case CMD_PAIRED_DEVICES: { + DEBUGF("CMD_PAIRED_DEVICES cmdid=%d: Not supported under linux", cmdid); + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + ddata_put_tag(&data_out, LIST); + ddata_put_tag(&data_out, LIST_END); + goto reply; + } + + case CMD_FAVORITE_DEVICES: { + DEBUGF("CMD_FAVORITE_DEVICES cmdid=%d: Not supported under linux", cmdid); + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + ddata_put_tag(&data_out, LIST); + ddata_put_tag(&data_out, LIST_END); + goto reply; + } + + case CMD_DEVICE_INFO: { + bdaddr_t bt_addr; + DEBUGF("CMD_DEVICE_INFO cmdid=%d", cmdid); + + if(!get_address(&data_in, &bt_addr)) + goto badarg; + + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + ddata_put_tag(&data_out, LIST); + ddata_put_tag(&data_out, LIST_END); + + goto reply; + } + + case CMD_LOCAL_INFO: { + DEBUGF("CMD_LOCAL_INFO cmdid=%d", cmdid); + + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + + bt_local_info(lctx, &data_in, &data_out); + + goto reply; + } + + default: + DEBUGF("CMD_UNKNOWN = %d cmdid=%d", op, cmdid); + goto badarg; + } + + goto done; + +ok: + if (cmdid == 0) + goto done; + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + goto reply; + +mem_error: + if (cmdid == 0) + goto done; + bt_error = ENOMEM; + goto bt_error; + +badarg: + if (cmdid == 0) + goto done; + bt_error = EINVAL; + goto bt_error; + +error: + bt_error = errno; +bt_error: +/* reset, just in case something was inserted */ + ddata_reset(&data_out); + ddata_put_UINT32(&data_out, 0); + ddata_put_tag(&data_out, REPLY_ERROR); + ddata_put_UINT32(&data_out, cmdid); + ddata_put_io_error(&data_out, bt_error, ERR_SHORT); +reply: + ddata_send(&data_out, 1); +done: + ddata_final(&data_out); +} + +// CALLBACK +void read_callback(struct pollfd* pfd, void* data) +{ + bt_ctx_t* ctx = (bt_ctx_t*) data; + int fd = pfd->fd; + int n; + + DEBUGF("read_callback: %d", pfd->fd); + if (pfd->revents & POLLHUP) { + DEBUGF("hangup"); + goto closed; + } + + if (ctx->pbuf_len < sizeof(ctx->pbuf)) { + int r = sizeof(ctx->pbuf) - ctx->pbuf_len; + if ((n = read(fd, ctx->pbuf+ctx->pbuf_len, r)) < 0) + goto error; + if (n == 0) + goto closed; + ctx->pbuf_len += n; + DEBUGF("READ: %d pbuf_len=%d", n, ctx->pbuf_len); + if (ctx->pbuf_len == sizeof(ctx->pbuf)) { + ctx->len = (ctx->pbuf[0]<<24) + (ctx->pbuf[1]<<16) + + (ctx->pbuf[2]<<8) + ctx->pbuf[3]; + DEBUGF("READ: %d packet len=%d", n, ctx->len); + if (ctx->len > 0) { + ctx->remain = ctx->len; + ctx->packet = (uint8_t*) malloc(ctx->len); + ctx->ptr = ctx->packet; + } + else { + ctx->remain = 0; + ctx->pbuf_len = 0; + } + } + } + else { + if ((n = read(fd, (void*)ctx->ptr, ctx->remain)) < 0) + goto error; + if (n == 0) + goto closed; + DEBUGF("READ: %d packet bytes", n); + ctx->remain -= n; + DEBUGF("PACKET: remain=%d", ctx->remain); + ctx->ptr += n; + if (ctx->remain == 0) { + bt_command(ctx, ctx->packet, ctx->len); + free(ctx->packet); + ctx->packet = NULL; + ctx->len = 0; + ctx->pbuf_len = 0; + } + } + return; + +error: + DEBUGF("pipe read error",0); + exit(1); + +closed: + DEBUGF("eof clean-up",0); + exit(0); +} + +static void main_loop(bt_ctx_t* ctx) +{ + int dev_id = 0; + int sock = 0; + drv_data_t local_context; + + // FIXME: How the hell do we handle more than one BT adapter. + dev_id = hci_get_route(NULL); + sock = hci_open_dev(dev_id); + if (sock == -1) { + fprintf(stderr, "Failed to open BT device %d: %s\n", dev_id, strerror(errno)); + exit(0); + } + + // Setup local context +// local_context.mgmt_sock = mgmt_open(); + local_context.mgmt_sock = -1; + local_context.dev_id = dev_id; + local_context.dev_sock = sock; + local_context.inquiry_fd = -1; + local_context.inquiry_pid = -1; + + ctx->drv_data = &local_context; + + while(1) { + int r = bt_poll(-1); + if (r < 0) { + DEBUGF("poll error %s", strerror(errno)); + } + } +} + +int main(int argc, char** argv) +{ + bt_ctx_t bt_info; + (void) argc; + (void) argv; + + dlog_init(); + dlog_set_debug(DLOG_DEFAULT); + memset(&bt_info, 0, sizeof(bt_ctx_t)); + + bt_poll_add(0, POLLIN | POLLERR | POLLHUP, read_callback, &bt_info); + main_loop(&bt_info); + + dlog_finish(); + DEBUGF("terminate",0); + exit(0); +} diff --git a/deps/bt/c_src/bt_macos_drv.c b/deps/bt/c_src/bt_macos_drv.c new file mode 100644 index 0000000..81c9a3e --- /dev/null +++ b/deps/bt/c_src/bt_macos_drv.c @@ -0,0 +1,3066 @@ +/* + * BT driver for Erlang + * + */ +#include <AvailabilityMacros.h> +#include <sys/types.h> +#include <string.h> +#include <memory.h> +#include <CoreFoundation/CoreFoundation.h> +#include <IOBluetooth/IOBluetoothUtilities.h> +#include <IOBluetooth/IOBluetoothUserLib.h> +#include <IOBluetooth/objc/IOBluetoothDevice.h> +#include <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h> +#include <IOBluetooth/objc/IOBluetoothDeviceInquiry.h> +#include <IOBluetooth/objc/IOBluetoothRFCOMMChannel.h> +#include <IOBluetooth/objc/IOBluetoothL2CAPChannel.h> +#include <IOBluetooth/objc/IOBluetoothSDPDataElement.h> +#include <IOBluetooth/objc/IOBluetoothSDPUUID.h> +#include <IOBluetooth/objc/IOBluetoothHostController.h> + +#include "dthread/include/dlog.h" +#include "dthread/include/ddata.h" +#include "bt_drv.h" + +#define alloc_type(type) calloc(1, sizeof(type)) + +// Object structure +// +----------+-------------------------------------------v +// | | +----------+ +----------+ +----------+ +// | ctx.list | <-> | sub_link | <-> | sub_link | <-> | sub_link | +// +----------+ +----+-----+ +-----+----+ +----------+ +// | | +// +----v-------+ +-----v------+ +// | subscriber1| | subscriber2| +// | INQUIRY | | RFCOMM | +// +----+-------+ +-------+----+ +// | handle | handle +// +----------------v---------+ +-----v--------------------+ +// | IOBluetoothDeviceInquiry | | IOBluetoothRFCOMMChannel | +// +-------+------------------+ +-----+--------------------+ +// | | +// +-------v---------+ +-----v--------------------+ +// | InquiryDelegate | | RFCOMMChannelDelegate | +// +-------+---------+ +----------+---------------+ +// | | +// v v +// subscriber1 ! subscriber2 ! +// + +// ----------------------------------------------------------------------- +// Delgate classes +// ----------------------------------------------------------------------- + +@interface InquiryDelegate : NSObject <IOBluetoothDeviceInquiryDelegate> { + subscription_t* mSub; +} +- (instancetype)initWithSub:(subscription_t*)s; +- (void) dealloc; +@end + +@interface RemoteNameDelegate : NSObject <IOBluetoothDeviceInquiryDelegate> { + subscription_t* mSub; +} +- (instancetype)initWithSub:(subscription_t*) s; +- (void) dealloc; +@end + +@interface ConnectDelegate : NSObject <IOBluetoothDeviceInquiryDelegate> { + subscription_t* mSub; +} +- (instancetype)initWithSub:(subscription_t*) s; +- (void) dealloc; +@end + +@interface SDPQueryDelegate : NSObject <IOBluetoothDeviceInquiryDelegate> { + subscription_t* mSub; + IOBluetoothSDPUUID* mUUID; +} +- (instancetype)initWithSub:(subscription_t*) s andUUID:(IOBluetoothSDPUUID*)uuid; +- (void) dealloc; +@end + +@interface RFCOMMChannelDelegate : NSObject <IOBluetoothRFCOMMChannelDelegate> { + subscription_t* mSub; + bt_ctx_t* mCtx; +} +- (instancetype)initWithSub:(subscription_t*) s andCtx:(bt_ctx_t*) ctx; +- (void) dealloc; +@end + +@interface L2CAPChannelDelegate : NSObject <IOBluetoothL2CAPChannelDelegate> { + subscription_t* mSub; + bt_ctx_t* mCtx; +} +- (instancetype)initWithSub:(subscription_t*) s andCtx:(bt_ctx_t*) ctx; +- (void) dealloc; +@end + +static inline void put_date(ddata_t* data, NSDate* date) +{ + uint8_t* ptr = ddata_alloc(data, 5); + NSTimeInterval at = 0; + uint32_t secs; + + if (date != NULL) + at = [date timeIntervalSince1970]; + secs = (uint32_t) at; + *ptr++ = DATE; + DDATA_PUT_UINT32(ptr, secs); +} + +static inline void put_ns_string(ddata_t* data, NSString* str) +{ + char buffer[1024]; + if ([str getCString:buffer maxLength:1024 encoding:NSUTF8StringEncoding]) + ddata_put_string(data, buffer); + else + ddata_put_string(data, "?"); +} + +static inline void ddata_put_addr(ddata_t* data, const BluetoothDeviceAddress* addr) +{ + uint8_t* ptr = ddata_alloc(data, sizeof(BluetoothDeviceAddress)+1); + *ptr++ = ADDR; + memcpy(ptr, addr, sizeof(BluetoothDeviceAddress)); +} + +static inline int get_address(ddata_t* data, BluetoothDeviceAddress* val) +{ + if (ddata_r_avail(data) >= sizeof(BluetoothDeviceAddress)) { + memcpy(val, data->rd, sizeof(BluetoothDeviceAddress)); + data->rd += sizeof(BluetoothDeviceAddress); + return 1; + } + return 0; +} + + +#define ERR_SHORT 1 +#define ERR_LONG 2 + +/* type: 1=short-atom 2=string-long 3=both (encapsule in LIST ot TUPLE) */ +static void ddata_put_io_error(ddata_t* data, IOReturn error, int type) +{ + switch(error) { + case kIOReturnSuccess: + if (type&1) ddata_put_atom(data, "Success"); + if (type&2) ddata_put_string(data, "OK"); + break; + case kIOReturnError: + if (type&1) ddata_put_atom(data, "Error"); + if (type&2) ddata_put_string(data, "general error"); + break; + case kIOReturnNoMemory: + if (type&1) ddata_put_atom(data, "NoMemory"); + if (type&2) ddata_put_string(data, "can't allocate memory"); + break; + case kIOReturnNoResources: + if (type&1) ddata_put_atom(data, "NoResources"); + if (type&2) ddata_put_string(data, "resource shortage "); + break; + case kIOReturnIPCError: + if (type&1) ddata_put_atom(data, "IPCError"); + if (type&2) ddata_put_string(data, "error during IPC "); + break; + case kIOReturnNoDevice: + if (type&1) ddata_put_atom(data, "NoDevice"); + if (type&2) ddata_put_string(data, "no such device "); + break; + case kIOReturnNotPrivileged: + if (type&1) ddata_put_atom(data, "NotPrivileged"); + if (type&2) ddata_put_string(data, "privilege violation "); + break; + case kIOReturnBadArgument: + if (type&1) ddata_put_atom(data, "BadArgument"); + if (type&2) ddata_put_string(data, "invalid argument "); + break; + case kIOReturnLockedRead: + if (type&1) ddata_put_atom(data, "LockedRead"); + if (type&2) ddata_put_string(data, "device read locked "); + break; + case kIOReturnLockedWrite: + if (type&1) ddata_put_atom(data, "LockedWrite"); + if (type&2) ddata_put_string(data, "device write locked "); + break; + case kIOReturnExclusiveAccess: + if (type&1) ddata_put_atom(data, "ExclusiveAccess"); + if (type&2) ddata_put_string(data, "exclusive access and device already open"); + break; + case kIOReturnBadMessageID: + if (type&1) ddata_put_atom(data, "BadMessageID"); + if (type&2) ddata_put_string(data, "sent/received messages had different msg_id"); + break; + case kIOReturnUnsupported: + if (type&1) ddata_put_atom(data, "Unsupported"); + if (type&2) ddata_put_string(data, "unsupported function"); + break; + case kIOReturnVMError: + if (type&1) ddata_put_atom(data, "VMError"); + if (type&2) ddata_put_string(data, "misc. VM failure "); + break; + case kIOReturnInternalError: + if (type&1) ddata_put_atom(data, "InternalError"); + if (type&2) ddata_put_string(data, "internal error "); + break; + case kIOReturnIOError: + if (type&1) ddata_put_atom(data, "IOError"); + if (type&2) ddata_put_string(data, "General I/O error "); + break; + case kIOReturnCannotLock: + if (type&1) ddata_put_atom(data, "CannotLock"); + if (type&2) ddata_put_string(data, "can't acquire lock"); + break; + case kIOReturnNotOpen: + if (type&1) ddata_put_atom(data, "NotOpen"); + if (type&2) ddata_put_string(data, "device not open "); + break; + case kIOReturnNotReadable: + if (type&1) ddata_put_atom(data, "NotReadable"); + if (type&2) ddata_put_string(data, "read not supported "); + break; + case kIOReturnNotWritable: + if (type&1) ddata_put_atom(data, "NotWritable"); + if (type&2) ddata_put_string(data, "write not supported"); + break; + case kIOReturnNotAligned: + if (type&1) ddata_put_atom(data, "NotAligned"); + if (type&2) ddata_put_string(data, "alignment error"); + break; + case kIOReturnBadMedia: + if (type&1) ddata_put_atom(data, "BadMedia"); + if (type&2) ddata_put_string(data, "Media Error"); + break; + case kIOReturnStillOpen: + if (type&1) ddata_put_atom(data, "StillOpen"); + if (type&2) ddata_put_string(data, "device(s) still open"); + break; + case kIOReturnRLDError: + if (type&1) ddata_put_atom(data, "RLDError"); + if (type&2) ddata_put_string(data, "rld failure"); + break; + case kIOReturnDMAError: + if (type&1) ddata_put_atom(data, "DMAError"); + if (type&2) ddata_put_string(data, "DMA failure"); + break; + case kIOReturnBusy: + if (type&1) ddata_put_atom(data, "Busy"); + if (type&2) ddata_put_string(data, "Device Busy"); + break; + case kIOReturnTimeout: + if (type&1) ddata_put_atom(data, "Timeout"); + if (type&2) ddata_put_string(data, "I/O Timeout"); + break; + case kIOReturnOffline: + if (type&1) ddata_put_atom(data, "Offline"); + if (type&2) ddata_put_string(data, "device offline"); + break; + case kIOReturnNotReady: + if (type&1) ddata_put_atom(data, "NotReady"); + if (type&2) ddata_put_string(data, "not ready"); + break; + case kIOReturnNotAttached: + if (type&1) ddata_put_atom(data, "NotAttached"); + if (type&2) ddata_put_string(data, "device not attached"); + break; + case kIOReturnNoChannels: + if (type&1) ddata_put_atom(data, "NoChannels"); + if (type&2) ddata_put_string(data, "no DMA channels left"); + break; + case kIOReturnNoSpace: + if (type&1) ddata_put_atom(data, "NoSpace"); + if (type&2) ddata_put_string(data, "no space for data"); + break; + case kIOReturnPortExists: + if (type&1) ddata_put_atom(data, "PortExists"); + if (type&2) ddata_put_string(data, "port already exists"); + break; + case kIOReturnCannotWire: + if (type&1) ddata_put_atom(data, "CannotWire"); + if (type&2) ddata_put_string(data, "can't wire down physical memory"); + break; + case kIOReturnNoInterrupt: + if (type&1) ddata_put_atom(data, "NoInterrupt"); + if (type&2) ddata_put_string(data, "no interrupt attached"); + break; + case kIOReturnNoFrames: + if (type&1) ddata_put_atom(data, "NoFrames"); + if (type&2) ddata_put_string(data, "no DMA frames enqueued"); + break; + case kIOReturnMessageTooLarge: + if (type&1) ddata_put_atom(data, "MessageTooLarge"); + if (type&2) ddata_put_string(data, "oversized msg received"); + break; + case kIOReturnNotPermitted: + if (type&1) ddata_put_atom(data, "NotPermitted"); + if (type&2) ddata_put_string(data, "not permitted"); + break; + case kIOReturnNoPower: + if (type&1) ddata_put_atom(data, "NoPower"); + if (type&2) ddata_put_string(data, "no power to device"); + break; + case kIOReturnNoMedia: + if (type&1) ddata_put_atom(data, "NoMedia"); + if (type&2) ddata_put_string(data, "media not present"); + break; + case kIOReturnUnformattedMedia: + if (type&1) ddata_put_atom(data, "UnformattedMedia"); + if (type&2) ddata_put_string(data, "media not formatted"); + break; + case kIOReturnUnsupportedMode: + if (type&1) ddata_put_atom(data, "UnsupportedMode"); + if (type&2) ddata_put_string(data, "no such mode"); + break; + case kIOReturnUnderrun: + if (type&1) ddata_put_atom(data, "Underrun"); + if (type&2) ddata_put_string(data, "data underrun"); + break; + case kIOReturnOverrun: + if (type&1) ddata_put_atom(data, "Overrun"); + if (type&2) ddata_put_string(data, "data overrun"); + break; + case kIOReturnDeviceError: + if (type&1) ddata_put_atom(data, "DeviceError"); + if (type&2) ddata_put_string(data, "the device is not working properly!"); + break; + case kIOReturnNoCompletion: + if (type&1) ddata_put_atom(data, "NoCompletion"); + if (type&2) ddata_put_string(data, "a completion routine is required"); + break; + case kIOReturnAborted: + if (type&1) ddata_put_atom(data, "Aborted"); + if (type&2) ddata_put_string(data, "operation aborted"); + break; + case kIOReturnNoBandwidth: + if (type&1) ddata_put_atom(data, "NoBandwidth"); + if (type&2) ddata_put_string(data, "bus bandwidth would be exceeded"); + break; + case kIOReturnNotResponding: + if (type&1) ddata_put_atom(data, "NotResponding"); + if (type&2) ddata_put_string(data, "device not responding"); + break; + case kIOReturnIsoTooOld: + if (type&1) ddata_put_atom(data, "IsoTooOld"); + if (type&2) ddata_put_string(data, "isochronous I/O request for distant past!"); + break; + case kIOReturnIsoTooNew: + if (type&1) ddata_put_atom(data, "IsoTooNew"); + if (type&2) ddata_put_string(data, "isochronous I/O request for distant future"); + break; + case kIOReturnNotFound: + if (type&1) ddata_put_atom(data, "NotFound"); + if (type&2) ddata_put_string(data, "data was not found"); + break; + case kIOReturnInvalid: + if (type&1) ddata_put_atom(data, "Invalid"); + if (type&2) ddata_put_string(data, "should never be seen"); + break; + /* Add HCI error codes */ + default: + DEBUGF("ERROR: code=%d, sub=%d, system=%d", + err_get_code(error), + err_get_sub(error), + err_get_system(error)); + if (type&1) ddata_put_atom(data, "Unknown"); + if (type&2) ddata_put_string(data, "do not know about this error"); + break; + } +} + +static void cleanup(subscription_t* s) +{ + DEBUGF("cleanup: %s", format_subscription(s)); + if (s->handle != NULL) { + switch(s->type) { + case INQUIRY: { + IOBluetoothDeviceInquiry* obj = s->handle; + if (obj) { + InquiryDelegate* delegate = [obj delegate]; + [delegate release]; + [obj release]; + } + break; + } + + case REMOTE_NAME: { + IOBluetoothDeviceInquiry* obj = s->handle; + if (obj) { + RemoteNameDelegate* delegate = [obj delegate]; + [delegate release]; + [obj release]; + } + break; + } + + case CONNECT: { + IOBluetoothDeviceInquiry* obj = s->handle; + if (obj) { + ConnectDelegate* delegate = [obj delegate]; + [delegate release]; + [obj release]; + } + break; + } + + case SDP_QUERY: { + IOBluetoothDeviceInquiry* obj = s->handle; + if (obj) { + SDPQueryDelegate* delegate = [obj delegate]; + [delegate release]; + [obj release]; + } + break; + } + + case SDP: { + IOBluetoothSDPServiceRecord* serviceRecord = s->handle; + [serviceRecord removeServiceRecord]; + [serviceRecord release]; + break; + } + case RFCOMM: { + IOBluetoothRFCOMMChannel* obj = s->handle; + if (obj) { + RFCOMMChannelDelegate* delegate = [obj delegate]; + [delegate release]; + [obj release]; + } + break; + } + case RFCOMM_LISTEN: { + IOBluetoothUserNotification* obj = s->handle; + if (obj) + [obj unregister]; + // check all acceptors here? + [obj release]; + break; + } + case L2CAP: { + IOBluetoothL2CAPChannel* obj = s->handle; + if (obj) { + L2CAPChannelDelegate* delegate = [obj delegate]; + [delegate release]; + [obj release]; + } + break; + } + case L2CAP_LISTEN: { + IOBluetoothUserNotification* obj = s->handle; + if (obj) + [obj unregister]; + [obj release]; + break; + } + default: // warn? + break; + } + } +} + + +/* Send OK reply */ +void bt_ok(uint32_t cmdid) +{ + uint8_t buf[16]; + ddata_t data; + + ddata_init(&data, buf, sizeof(buf), 0); + ddata_put_UINT32(&data, 0); + ddata_put_tag(&data, REPLY_OK); + ddata_put_UINT32(&data, cmdid); + ddata_send(&data, 1); + ddata_final(&data); +} + +/* Send ERROR reply */ +void bt_error(uint32_t cmdid, IOReturn err) +{ + uint8_t buf[128]; + ddata_t data; + + ddata_init(&data, buf, sizeof(buf), 0); + ddata_put_UINT32(&data, 0); + ddata_put_tag(&data, REPLY_ERROR); + ddata_put_UINT32(&data, cmdid); + ddata_put_io_error(&data, err, ERR_SHORT); + ddata_send(&data, 1); + ddata_final(&data); +} + +// simple event +void bt_event(uint32_t sid, const char* evtname) +{ + uint8_t buf[64]; + ddata_t data; + ddata_init(&data, buf, sizeof(buf), 0); + ddata_put_UINT32(&data, 0); + ddata_put_tag(&data, REPLY_EVENT); + ddata_put_UINT32(&data, sid); + ddata_put_atom(&data, evtname); + ddata_send(&data, 1); + ddata_final(&data); +} + +/* + * Given array of devices reply with + * OK LIST ADDR <addr> ... LIST_END + */ +void bt_device_list(NSArray* devices, ddata_t* data_out) +{ + NSUInteger i, n; + + if (devices == NULL) + n = 0; + else + n = [devices count]; + /* reply:8 cmdid:32 LIST ADDR <addr> ADDR <addr> LIST_END */ + DEBUGF("bt_device_list n=%d", n); + + ddata_put_tag(data_out, LIST); + for (i = 0; i < n; i++) { + DEBUGF("bt_device_list i=%d, n=%d", n); + IOBluetoothDevice* device = (IOBluetoothDevice*) + [devices objectAtIndex:i]; + const BluetoothDeviceAddress* bt_addr = [device getAddress]; + ddata_put_addr(data_out, bt_addr); + i++; + } + ddata_put_tag(data_out, LIST_END); +} + +/* + * Given a IOBluetoothSDPDataElementRef produce a SDP attribute value element + * Format a binary version of SDP Element + */ + +int ddata_put_sdp_elem(IOBluetoothSDPDataElement* value, ddata_t* data_out, int level) +{ + BluetoothSDPDataElementTypeDescriptor sdp_type; + BluetoothSDPDataElementSizeDescriptor sdp_size; + uint32_t byte_size; + + sdp_type = [value getTypeDescriptor]; + sdp_size = [value getSizeDescriptor]; + byte_size = [value getSize]; + DEBUGF(" %d: sdp:tag=%d,size=%d, avail=%d", + level,sdp_type<<3|(sdp_size&7),byte_size, + ddata_r_avail(data_out)); + + switch(sdp_type) { + case kBluetoothSDPDataElementTypeNil: /* only a tag byte */ + ddata_put_UINT8(data_out, ((sdp_type<<3)|(sdp_size&0x7))); + break; + case kBluetoothSDPDataElementTypeUnsignedInt: + case kBluetoothSDPDataElementTypeSignedInt: { + ddata_put_UINT8(data_out, ((sdp_type<<3)|(sdp_size&0x7))); + switch(sdp_size) { + case 0: { + NSNumber* num = [value getNumberValue]; + ddata_put_UINT8(data_out, num.unsignedCharValue); + break; + } + case 1: { + NSNumber* num = [value getNumberValue]; + ddata_put_UINT16(data_out, num.unsignedShortValue); + break; + } + case 2: { + NSNumber* num = [value getNumberValue]; + ddata_put_UINT32(data_out, num.unsignedLongValue); + break; + } + case 3: { + NSNumber* num = [value getNumberValue]; + ddata_put_UINT64(data_out, num.unsignedLongLongValue); + break; + } + case 4: { + NSData* data = [value getDataValue]; + NSUInteger len = [data length]; + const void* ptr = [data bytes]; + ddata_add(data_out, (uint8_t*)ptr, len); + break; + } + default: + return -1; + } + break; + } + + case kBluetoothSDPDataElementTypeUUID: { + IOBluetoothSDPUUID* uuid = [value getUUIDValue]; + NSUInteger len = [uuid length]; + const void* ptr = [uuid bytes]; + ddata_put_UINT8(data_out, ((sdp_type<<3)|(sdp_size&0x7))); + switch(sdp_size) { + case 1: + ddata_add(data_out, (uint8_t*)ptr, len); + break; + case 2: + ddata_add(data_out, (uint8_t*)ptr, len); + break; + case 4: + ddata_add(data_out, (uint8_t*)ptr, len); + break; + default: + return -1; + } + break; + } + + case kBluetoothSDPDataElementTypeURL: + case kBluetoothSDPDataElementTypeString: { + NSString* str = [value getStringValue]; + NSUInteger size = [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + NSUInteger len = [str length]; + NSUInteger ulen; + NSRange range = NSMakeRange(0, len); + uint8_t* ptr; + + if (size < 256) { + ddata_put_UINT8(data_out, ((sdp_type<<3)|5)); + ptr = ddata_alloc(data_out, 1+size); + DDATA_PUT_UINT8(ptr, size); ptr += 1; + [str getBytes:(void*)ptr maxLength:size + usedLength:&ulen encoding:NSUTF8StringEncoding + options:0 range:range remainingRange:NULL]; + } + else if (size < 65536) { + ddata_put_UINT8(data_out, ((sdp_type<<3)|6)); + ptr = ddata_alloc(data_out, 2+size); + DDATA_PUT_UINT16(ptr, size); ptr += 2; + [str getBytes:(void*)ptr maxLength:size + usedLength:&ulen encoding:NSUTF8StringEncoding + options:0 range:range remainingRange:NULL]; + } + else { + ddata_put_UINT8(data_out, ((sdp_type<<3)|7)); + ptr = ddata_alloc(data_out, 4+size); + DDATA_PUT_UINT32(ptr, size); ptr += 4; + [str getBytes:(void*)ptr maxLength:size + usedLength:&ulen encoding:NSUTF8StringEncoding + options:0 range:range remainingRange:NULL]; + } + break; + } + + case kBluetoothSDPDataElementTypeBoolean: { + NSNumber* num = [value getNumberValue]; + ddata_put_UINT8(data_out, ((sdp_type<<3)|(sdp_size&0x7))); + ddata_put_UINT8(data_out, num.unsignedCharValue); + break; + } + + case kBluetoothSDPDataElementTypeDataElementSequence: + case kBluetoothSDPDataElementTypeDataElementAlternative: { + NSArray* array = [value getArrayValue]; + NSUInteger n = array.count; + NSUInteger i; + size_t bin_size; + size_t used_size; + intptr_t patch_offset; + + // always use 4 byte header so we can patch + ddata_put_UINT8(data_out, ((sdp_type<<3)|(7))); + patch_offset = ddata_used(data_out); /* keep offset */ + ddata_put_UINT32(data_out, 0); /* patch this later */ + + used_size = ddata_used(data_out); // size before + for (i = 0; i < n; i++) { + IOBluetoothSDPDataElement* elem = + (IOBluetoothSDPDataElement*) [array objectAtIndex:i]; + if (ddata_put_sdp_elem(elem, data_out, level+1) < 0) + return -1; + } + bin_size = (ddata_used(data_out) - used_size); // size after + DDATA_PUT_UINT32(data_out->base+patch_offset, bin_size); + break; + } + default: + return -1; + } + return 0; +} + +/* + * Ouput a SDP service record + * LIST ServiceAttributes LIST_END + */ +void ddata_put_sdp_service(IOBluetoothSDPServiceRecord* serv, ddata_t* data_out) +{ + NSDictionary* dict; + NSNumber* key; + + ddata_put_tag(data_out, LIST); + dict = [serv attributes]; + + for (key in dict) { + uint16_t aid; + size_t bin_size; + size_t used_size; + intptr_t patch_offset; + uint8_t type; + IOBluetoothSDPDataElement* value = [dict objectForKey:key]; + + ddata_put_UINT8(data_out, BINARY); + patch_offset = ddata_used(data_out); /* keep offset */ + ddata_put_UINT32(data_out, 0); /* patch this later */ + + // FIXME: do not assume key is a number? + aid = key.unsignedShortValue; + type=((kBluetoothSDPDataElementTypeUnsignedInt << 3) | 1); + ddata_put_UINT8(data_out, type); + ddata_put_UINT16(data_out, aid); + DEBUGF(" type:%d id=%d avail=%d", type, aid, ddata_r_avail(data_out)); + used_size = ddata_used(data_out); // size before + ddata_put_sdp_elem(value, data_out, 0); + bin_size = 3 + (ddata_used(data_out) - used_size); // size after + DDATA_PUT_UINT32(data_out->base+patch_offset, bin_size); + } + ddata_put_tag(data_out, LIST_END); +} + +/* read a UUID from data_in + * if data_in == 0 then all records are returned + * data_in == 2 UUID16 is assumed and searched for + * data_in == 4 UUID32 is assumed and searched for + * data_in == 16 UUID is assumed and searched + */ + +int get_uuid(ddata_t* data_in, IOBluetoothSDPUUID** uuid) +{ + switch(ddata_r_avail(data_in)) { + case 2: { + BluetoothSDPUUID16 uuid16; + ddata_get_uint16(data_in, &uuid16); + *uuid = [IOBluetoothSDPUUID uuid16:uuid16]; + break; + } + case 4: { + BluetoothSDPUUID32 uuid32; + ddata_get_uint32(data_in, &uuid32); + *uuid = [IOBluetoothSDPUUID uuid32:uuid32]; + break; + } + case 16: { + *uuid = [IOBluetoothSDPUUID uuidWithBytes:data_in->rd length:16]; + break; + } + case 0: + *uuid = NULL; + break; + default: + return 0; + } + return 1; +} + + +void bt_sdp_info(IOBluetoothDevice* device, IOBluetoothSDPUUID* uuid, + ddata_t* data_out) +{ + if (uuid != NULL) { + IOBluetoothSDPServiceRecord* serv = + [device getServiceRecordForUUID:uuid]; + if (serv != NULL) + ddata_put_sdp_service(serv, data_out); + [uuid release]; + } + else { + NSArray* services; + + ddata_put_tag(data_out, LIST); + + services = [device services]; + if (services != NULL) { + NSUInteger i, n; + + n = [services count]; + DEBUGF("bt_sdp_info: %lu services found", n); + for (i = 0; i < n; i++) { + IOBluetoothSDPServiceRecord* serv = [services objectAtIndex:i]; + DEBUGF("bt_sdp_info: service %lu %p", i, serv); + ddata_put_sdp_service(serv, data_out); + } + DEBUGF("bt_sdp_info: %lu services done", n); + } + ddata_put_tag(data_out, LIST_END); + } +} + +/* construct a NSDictionary that makes up a Bluetooth service entry + * the input format MUST be on form + * LIST + * BINARY <sdp-attribute> <sdp-value> + * BINARY <sdp-attribute> <spd-value> + * BINARY <sdp-attribute> <sdp-value> + * LIST_END + * + */ +NSDictionary* bt_type_dict(int sdp_type) +{ + id keys[1]; + id objects[1]; + + keys[0] = [NSString stringWithUTF8String:"DataElementType"]; + objects[0] = [NSNumber numberWithInt:sdp_type]; + + return [NSDictionary dictionaryWithObjects:objects forKeys:keys count:1]; +} + +NSDictionary* bt_data_dict(int sdp_type, void* value) +{ + id keys[2]; + id objects[2]; + + keys[0] = [NSString stringWithUTF8String:"DataElementType"]; + objects[0] = [NSNumber numberWithInt:sdp_type]; + + keys[1] = [NSString stringWithUTF8String:"DataElementValue"]; + objects[1] = value; + + return [NSDictionary dictionaryWithObjects:objects forKeys:keys count:2]; +} + +NSDictionary* bt_value_dict(int sdp_type, int sdp_size, void* value) +{ + id keys[3]; + id objects[3]; + + if (value == NULL) + return NULL; + + keys[0] = [NSString stringWithUTF8String:"DataElementSize"]; + objects[0] = [NSNumber numberWithInt:sdp_size]; + + keys[1] = [NSString stringWithUTF8String:"DataElementType"]; + objects[1] = [NSNumber numberWithInt:sdp_type]; + + keys[2] = [NSString stringWithUTF8String:"DataElementValue"]; + objects[2] = value; + + return [NSDictionary dictionaryWithObjects:objects forKeys:keys count:3]; +} + + +int bt_dynamic_len(ddata_t* data_in, int sdp_size, NSUInteger* length) +{ + switch(sdp_size) { + case 5: { + uint8_t len; + if (!ddata_get_uint8(data_in, &len)) return 0; + *length = (NSUInteger) len; + return 1; + } + case 6: { + uint16_t len; + if (!ddata_get_uint16(data_in, &len)) return 0; + *length = (NSUInteger) len; + return 1; + } + case 7: { + uint32_t len; + if (!ddata_get_uint32(data_in, &len)) return 0; + *length = (NSUInteger) len; + return 1; + } + default: + return 0; + } +} + +int bt_fixed_len(int sdp_size, NSUInteger* length) +{ + switch(sdp_size) { + case 0: *length=1; return 1; + case 1: *length=2; return 1; + case 2: *length=4; return 1; + case 3: *length=8; return 1; + case 4: *length=16; return 1; + default: return 0; + } +} + + +NSObject* bt_get_sdp_value(ddata_t* data_in) +{ + uint8_t sdp_tag; + int sdp_type; + int sdp_size; + + ddata_get_uint8(data_in, &sdp_tag); + sdp_type = (sdp_tag >> 3); + sdp_size = sdp_tag & 0x7; + + switch(sdp_type) { + case kBluetoothSDPDataElementTypeNil: + return bt_type_dict(sdp_type); + + case kBluetoothSDPDataElementTypeUnsignedInt: + case kBluetoothSDPDataElementTypeSignedInt: { + void* value; + switch(sdp_size) { + case 0: { + uint8_t uval; + int val; + ddata_get_uint8(data_in, &uval); + val = uval; + value = [NSNumber numberWithInt:val]; + return bt_value_dict(sdp_type,1,value); + } + case 1: { + uint16_t uval; + int val; + if (!ddata_get_uint16(data_in, &uval)) return NULL; + val = uval; + value = [NSNumber numberWithInt:val]; + return bt_value_dict(sdp_type,2,value); + } + case 2: { + uint32_t uval; + int val; + if (!ddata_get_uint32(data_in, &uval)) return NULL; + val = uval; + value = [NSNumber numberWithInt:val]; + if (sdp_type == kBluetoothSDPDataElementTypeUnsignedInt) + return value; /* Special case ... */ + else + return bt_value_dict(sdp_type,4,value); + } + + case 3: { + value = [NSData dataWithBytes:data_in->rd length:8]; + ddata_forward(data_in, 8); + return bt_data_dict(sdp_type,value); + } + case 4: { + value = [NSData dataWithBytes:data_in->rd length:16]; + ddata_forward(data_in, 16); + return bt_data_dict(sdp_type,value); + } + default: + return NULL; + } + } + + + case kBluetoothSDPDataElementTypeUUID: { + NSData* data; + NSUInteger len; + if (!bt_fixed_len(sdp_size, &len)) return NULL; + if ((len == 8) || (len==1)) return NULL; + data = [NSData dataWithBytes:data_in->rd length:len]; + ddata_forward(data_in, len); + return (void*) data; + } + + case kBluetoothSDPDataElementTypeURL: + case kBluetoothSDPDataElementTypeString: { + NSUInteger len; + void* value; + + if (!bt_dynamic_len(data_in, sdp_size, &len)) return NULL; + + value = [[NSString alloc]initWithBytes:(const void*)data_in->rd + length:len + encoding:NSUTF8StringEncoding]; + ddata_forward(data_in, len); + if (sdp_type == kBluetoothSDPDataElementTypeURL) + return bt_value_dict(sdp_type, sdp_size, value); + else + return value; + } + + case kBluetoothSDPDataElementTypeBoolean: { + uint8_t bval; + int val; + void* value; + + ddata_get_uint8(data_in, &bval); + val = bval; + value = [NSNumber numberWithInt:val]; + return bt_value_dict(sdp_type,1,value); + } + + case kBluetoothSDPDataElementTypeDataElementSequence: + case kBluetoothSDPDataElementTypeDataElementAlternative: { + NSMutableArray* array; + NSUInteger len; + uint8_t* end_ptr; + + if (!bt_dynamic_len(data_in, sdp_size, &len)) return NULL; + + end_ptr = data_in->rd + len; + + array = [[NSMutableArray alloc] init]; + while(data_in->rd < end_ptr) { + id value = bt_get_sdp_value(data_in); + if (value == NULL) { + [array release]; + return NULL; + } + [array addObject:value]; + } + if (sdp_type==kBluetoothSDPDataElementTypeDataElementAlternative) + return bt_data_dict(sdp_type, array); + else + return array; + } + + default: + return NULL; + } +} + +NSDictionary* bt_make_sdp_dict(ddata_t* data_in) +{ + uint8_t tag; + NSMutableDictionary* servDict; + void* value; + + if (!ddata_get_uint8(data_in, &tag) || (tag != LIST)) + return NULL; + if ((servDict = [[NSMutableDictionary alloc] initWithCapacity:10]) == NULL) + return NULL; + + DEBUGF("Service add: found LIST tag", 0); + + while(ddata_get_uint8(data_in, &tag)) { + if (tag == LIST_END) { + DEBUGF("Service add: found LIST_END tag", 0); + return servDict; + } + else if (tag == BINARY) { + uint32_t bin_sz; + uint8_t sdp_tag; + uint8_t sdp_type; + uint8_t sdp_size; + uint16_t attributeID; + NSString* key; + char attr[5]; + + if (!ddata_get_uint32(data_in, &bin_sz)) goto error; + DEBUGF("Service add: found BINARY size=%d", bin_sz); + if (!ddata_get_uint8(data_in, &sdp_tag)) goto error; + sdp_type = (sdp_tag >> 3); + sdp_size = sdp_tag & 0x7; + if (sdp_type != kBluetoothSDPDataElementTypeUnsignedInt) goto error; + if (sdp_size != 1) goto error; + if (!ddata_get_uint16(data_in, &attributeID)) goto error; + + /* Generate the attribute ID as Hex String Key 4 digits */ + snprintf(attr, 5, "%04X", attributeID); + DEBUGF("Service add: found attribute %s", attr); + key = [[NSString alloc]initWithBytes:(const void*)attr + length:4 encoding:NSUTF8StringEncoding]; + value = bt_get_sdp_value(data_in); + [servDict setObject:value forKey:key]; + } + else + goto error; + } + +error: + return NULL; +} + +void bt_local_info(ddata_t* data_in, ddata_t* data_out) +{ + uint8_t op_code; + + ddata_put_tag(data_out, LIST); + + while(ddata_get_uint8(data_in, &op_code)) { + switch(op_code) { + case NFO_LOCAL_NAME: { + NSString* name = + [[IOBluetoothHostController defaultController] nameAsString]; + put_ns_string(data_out, name); + break; + } + + case NFO_LOCAL_CLASS: { + BluetoothClassOfDevice value = + [[IOBluetoothHostController defaultController] classOfDevice]; + ddata_put_uint32(data_out, value); + break; + } + + case NFO_LOCAL_ADDRESS: { + BluetoothDeviceAddress addr; + NSString* addrStr = + [[IOBluetoothHostController defaultController] addressAsString]; + IOBluetoothNSStringToDeviceAddress(addrStr, &addr); + ddata_put_addr(data_out, &addr); + break; + } + + case NFO_LOCAL_DISCOVERABLE: { + // Boolean value; + // if (IOBluetoothLocalDeviceGetDiscoverable(&value) == kIOReturnSuccess) + ddata_put_boolean(data_out, true); + break; + } + + case NFO_LOCAL_POWER_STATE: { + BluetoothHCIPowerState powerState = + [[IOBluetoothHostController defaultController] powerState]; + if (powerState == kBluetoothHCIPowerStateON) + ddata_put_atom(data_out, "on"); + else if (powerState == kBluetoothHCIPowerStateOFF) + ddata_put_atom(data_out, "off"); + else if (powerState == kBluetoothHCIPowerStateUnintialized) + ddata_put_atom(data_out, "unintialized"); + else + ddata_put_atom(data_out, "unknown"); + break; + } + default: + break; + } + } + ddata_put_tag(data_out, LIST_END); +} + + +void bt_device_info(IOBluetoothDevice* device, ddata_t* data_in, ddata_t* data_out) +{ + uint8_t op_code; + + ddata_put_tag(data_out, LIST); + + while(ddata_get_uint8(data_in, &op_code)) { + switch(op_code) { + case NFO_DEVICE_NAME: { + NSString* name = [device name]; + put_ns_string(data_out, name); + break; + } + + case NFO_DEVICE_CLASS: { + BluetoothClassOfDevice value = [device classOfDevice]; + ddata_put_uint32(data_out, value); + break; + } + + case NFO_DEVICE_CLOCK: { + BluetoothClockOffset value = [device getClockOffset]; + ddata_put_uint16(data_out, value); + break; + } + + case NFO_DEVICE_IS_FAVORITE: { + Boolean value = [device isFavorite]; + ddata_put_boolean(data_out, value); + break; + + } + + case NFO_DEVICE_IS_PAIRED: { + Boolean value = [device isPaired]; + ddata_put_boolean(data_out, value); + break; + } + + case NFO_DEVICE_IS_CONNECTED: { + Boolean value = [device isConnected]; + ddata_put_boolean(data_out, value); + break; + } + + case NFO_DEVICE_INQUIRY: { + NSDate* date = [device getLastInquiryUpdate]; + put_date(data_out, date); + break; + } + case NFO_DEVICE_ACCESS: { + NSDate* date = [device recentAccessDate]; + put_date(data_out, date); + break; + } + case NFO_DEVICE_UPDATE: { + NSDate* date = [device getLastServicesUpdate]; + put_date(data_out, date); + break; + } + default: + break; + } + } + ddata_put_tag(data_out, LIST_END); + +} + +// ----------------------------------------------------------------------- +// InquiryDelegate +// ----------------------------------------------------------------------- + +@implementation InquiryDelegate + +- (instancetype)initWithSub:(subscription_t*)s +{ + if (self = [super init]) { + mSub = s; // weak! + } + return self; +} + +- (void) dealloc +{ + DEBUGF("InquiryDelegate: dealloc"); + [super dealloc]; +} + +- (void) deviceInquiryStarted:(IOBluetoothDeviceInquiry*)sender +{ + (void) sender; + uint8_t buf[64]; + ddata_t data; + + DEBUGF("InquiryDelegate: started"); + + ddata_init(&data, buf, sizeof(buf), 0); + ddata_put_UINT32(&data, 0); + ddata_put_tag(&data, REPLY_EVENT); + ddata_put_UINT32(&data, mSub->id); + ddata_put_atom(&data, "started"); + + ddata_send(&data, 1); + ddata_final(&data); +} + +- (void) deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender +device:(IOBluetoothDevice*)device +{ + (void) sender; + const BluetoothDeviceAddress* bt_addr = [device getAddress]; + uint8_t buf[64]; + ddata_t data; + + DEBUGF("InquiryDelegate: device found"); + + ddata_init(&data, buf, sizeof(buf), 0); + ddata_put_UINT32(&data, 0); + ddata_put_tag(&data, REPLY_EVENT); + ddata_put_UINT32(&data, mSub->id); + ddata_put_tag(&data, TUPLE); + ddata_put_atom(&data, "device"); + ddata_put_addr(&data, bt_addr); + ddata_put_tag(&data, TUPLE_END); + + ddata_send(&data, 1); + ddata_final(&data); +} + +- (void) deviceInquiryUpdatingDeviceNamesStarted:(IOBluetoothDeviceInquiry*)sender devicesRemaining:(uint32_t)devicesRemaining +{ + (void) sender; + (void) devicesRemaining; + + DEBUGF("InquiryDelegate: %d name started: devicesRemaining=%d", + mSub->id, devicesRemaining); +} + + +- (void) deviceInquiryDeviceNameUpdated:(IOBluetoothDeviceInquiry*)sender device:(IOBluetoothDevice*)device devicesRemaining:(uint32_t)devicesRemaining +{ + (void) sender; + (void) device; + (void) devicesRemaining; + + DEBUGF("InquiryDelegate: %d device name updated: deviceRemaining=%d", + mSub->id, devicesRemaining); +} + +- (void) deviceInquiryComplete:(IOBluetoothDeviceInquiry*)sender error:(IOReturn)error aborted:(BOOL)aborted +{ + (void) sender; + (void) error; + + DEBUGF("InquiryDelegate: %d stopped",mSub->id); + + if (!aborted) { /* no need to send event on user abort */ + uint8_t buf[64]; + ddata_t data; + + ddata_init(&data, buf, sizeof(buf), 0); + ddata_put_UINT32(&data, 0); + ddata_put_tag(&data, REPLY_EVENT); + ddata_put_UINT32(&data, mSub->id); + ddata_put_atom(&data, "stopped"); + ddata_send(&data, 1); + ddata_final(&data); + } +} + +@end + +// ----------------------------------------------------------------------- +// RemoteNameDelegate +// ----------------------------------------------------------------------- + +@implementation RemoteNameDelegate + +- (instancetype)initWithSub:(subscription_t*) s +{ + if (self = [super init]) { + mSub = s; // weak + } + return self; +} + +- (void) dealloc +{ + DEBUGF("RemoteNameDelegate dealloc"); + [super dealloc]; +} + +- (void)remoteNameRequestComplete:(IOBluetoothDevice *)device status:(IOReturn)status +{ + if (status == kIOReturnSuccess) { + uint8_t buf[265]; + ddata_t data; + NSString* name = [device name]; + + ddata_init(&data, buf, sizeof(buf), 0); + ddata_put_UINT32(&data, 0); + + ddata_put_tag(&data, REPLY_OK); + ddata_put_UINT32(&data, mSub->cmdid); + put_ns_string(&data, name); + ddata_send(&data, 1); + ddata_final(&data); + } + else + bt_error(mSub->cmdid, status); + // only for Inquiry objects + release_subscription(mSub); +} + +@end + +// ----------------------------------------------------------------------- +// SDPQueryDelegate +// ----------------------------------------------------------------------- + +@implementation SDPQueryDelegate + +- (instancetype)initWithSub:(subscription_t*) s andUUID:(IOBluetoothSDPUUID*) uuid +{ + DEBUGF("SDPQueryDelegate: alloc"); + if (self = [super init]) { + mSub = s; // weak + mUUID = [uuid retain]; + } + return self; +} + +- (void) dealloc +{ + DEBUGF("SDPQueryDelegate: dealloc"); + [mUUID release]; + [super dealloc]; +} + +- (void)sdpQueryComplete:(IOBluetoothDevice *)device status:(IOReturn)status +{ + uint8_t out_buf[2048]; + ddata_t data_out; + + if (status == kIOReturnSuccess) { + ddata_init(&data_out, out_buf, sizeof(out_buf), 0); + ddata_put_UINT32(&data_out, 0); + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, mSub->cmdid); + bt_sdp_info(device, mUUID, &data_out); + ddata_send(&data_out, 1); + ddata_final(&data_out); + } + else + bt_error(mSub->cmdid, status); + // only for Inquiry objects + release_subscription(mSub); +} +@end + +// ----------------------------------------------------------------------- +// Connectelegate +// ----------------------------------------------------------------------- + +@implementation ConnectDelegate + +- (instancetype)initWithSub:(subscription_t*) s +{ + if (self = [super init]) { + mSub = s; // weak + } + return self; +} + +- (void) dealloc +{ + DEBUGF("Connectelegate: dealloc"); + [super dealloc]; +} + + +- (void)connectionComplete:(IOBluetoothDevice *)device status:(IOReturn)status +{ + (void) device; + if (status == kIOReturnSuccess) + bt_ok(mSub->cmdid); + else + bt_error(mSub->cmdid, status); + release_subscription(mSub); +} +@end + +// ----------------------------------------------------------------------- +// RFCOMMChannelDelegate +// ----------------------------------------------------------------------- + +@implementation RFCOMMChannelDelegate + +- (instancetype)initWithSub:(subscription_t*) s andCtx:(bt_ctx_t*) ctx +{ + if (self = [super init]) { + mSub = s; // weak + mCtx = ctx; + } + return self; +} + +- (void) dealloc +{ + DEBUGF("RFCOMMChannelDelegate: dealloc"); + [super dealloc]; +} + +- (void)rfcommChannelData:(IOBluetoothRFCOMMChannel*)rfcommChannel data:(void *)dataPointer length:(size_t)dataLength +{ + uint8_t buf[128]; + ddata_t data; + (void) rfcommChannel; + + DEBUGF("RFCOMMChannelDelegate: Data size=%d", dataLength); + ddata_init(&data, buf, sizeof(buf), 0); + ddata_put_UINT32(&data, 0); + ddata_put_tag(&data, REPLY_EVENT); + ddata_put_UINT32(&data, mSub->id); + ddata_put_tag(&data, TUPLE); + ddata_put_atom(&data, "data"); + ddata_put_binary(&data, dataPointer, dataLength); + ddata_put_tag(&data, TUPLE_END); + ddata_send(&data, 1); + ddata_final(&data); +} + +- (void)rfcommChannelOpenComplete:(IOBluetoothRFCOMMChannel*)rfcommChannel status:(IOReturn)error +{ + (void) rfcommChannel; + (void) error; + DEBUGF("RFCOMMChannelDelegate: OpenComplete error=%d", error); + bt_ok(mSub->cmdid); + mSub->cmdid = 0; +} + +- (void)rfcommChannelClosed:(IOBluetoothRFCOMMChannel*)rfcommChannel +{ + (void) rfcommChannel; + DEBUGF("RFCOMMChannelDelegate: Closed"); + if (mSub->cmdid == 0) { + uint8_t buf[64]; + ddata_t data; + ddata_init(&data, buf, sizeof(buf), 0); + ddata_put_UINT32(&data, 0); + ddata_put_tag(&data, REPLY_EVENT); + ddata_put_UINT32(&data, mSub->id); + ddata_put_atom(&data, "closed"); + ddata_send(&data, 1); + ddata_final(&data); + } + else { + bt_ok(mSub->cmdid); + mSub->cmdid = 0; + } + remove_subscription(&mCtx->list,RFCOMM,mSub->id); +} + +- (void)rfcommChannelControlSignalsChanged:(IOBluetoothRFCOMMChannel*)rfcommChannel +{ + (void) rfcommChannel; + DEBUGF("RFCOMMChannelDelegate: ControlSignalsChanged"); +} + +- (void)rfcommChannelFlowControlChanged:(IOBluetoothRFCOMMChannel*)rfcommChannel +{ + if ([rfcommChannel isTransmissionPaused]) + DEBUGF("RFCOMMChannelDelegate: FlowControlChanged OFF"); + else + DEBUGF("RFCOMMChannelDelegate: FlowControlChanged ON"); +} + +- (void)rfcommChannelWriteComplete:(IOBluetoothRFCOMMChannel*)rfcommChannel refcon:(void*)refcon status:(IOReturn)error +{ + ddata_t* out = (ddata_t*) refcon; + uint32_t len; + (void) error; + + // FIXME: handle error! + + DEBUGF("RFCOMMChannelDelegate: WriteComplete"); + if ((len=ddata_r_avail(out)) == 0) { + ddata_free(out); + bt_ok(mSub->cmdid); + mSub->cmdid = 0; + } + else { + BluetoothRFCOMMMTU mtu = [rfcommChannel getMTU]; + uint8_t* ptr = out->wr; + IOReturn err; + if (len > mtu) len = mtu; + ddata_forward(out, len); /* move to next block */ + err = [rfcommChannel writeAsync:ptr length:len refcon:out]; + if (err != kIOReturnSuccess) { + bt_error(mSub->cmdid, err); + mSub->cmdid = 0; + ddata_free(out); + } + } +} + +- (void)rfcommChannelQueueSpaceAvailable:(IOBluetoothRFCOMMChannel*)rfcommChannel +{ + (void) rfcommChannel; + // FIXME: probably a good place to fill output data here ? + DEBUGF("RFCOMMChannelDelegate: QueueSpaceAvailable"); +} + +@end + +// ----------------------------------------------------------------------- +// L2CAPChannelDelegate +// ----------------------------------------------------------------------- + +@implementation L2CAPChannelDelegate + +- (instancetype)initWithSub:(subscription_t*) s andCtx:(bt_ctx_t*) ctx +{ + if (self = [super init]) { + mSub = s; // weak + mCtx = ctx; + } + return self; +} + +- (void) dealloc +{ + DEBUGF("L2CAPDelegate: dealloc"); + [super dealloc]; +} + +- (void)l2capChannelData:(IOBluetoothL2CAPChannel*)l2capChannel data:(void *)dataPointer length:(size_t)dataLength +{ + uint8_t buf[128]; + ddata_t data; + (void) l2capChannel; + + DEBUGF("L2CAPChannelDelegate: Data size=%d", dataLength); + ddata_init(&data, buf, sizeof(buf), 0); + ddata_put_UINT32(&data, 0); + ddata_put_tag(&data, REPLY_EVENT); + ddata_put_UINT32(&data, mSub->id); + ddata_put_tag(&data, TUPLE); + ddata_put_atom(&data, "data"); + ddata_put_binary(&data, dataPointer, dataLength); + ddata_put_tag(&data, TUPLE_END); + ddata_send(&data, 1); + ddata_final(&data); +} + +- (void)l2capChannelOpenComplete:(IOBluetoothL2CAPChannel*)l2capChannel status:(IOReturn)error +{ + (void) l2capChannel; + (void) error; // fixme check this + DEBUGF("L2CAPChannelDelegate: OpenComplete"); + bt_ok(mSub->cmdid); + mSub->cmdid = 0; +} + +- (void)l2capChannelClosed:(IOBluetoothL2CAPChannel*)l2capChannel +{ + (void) l2capChannel; + DEBUGF("L2CAPChannelDelegate: Closed this"); + if (mSub->cmdid == 0) { + uint8_t buf[64]; + ddata_t data; + + ddata_init(&data, buf, sizeof(buf), 0); + ddata_put_UINT32(&data, 0); + ddata_put_tag(&data, REPLY_EVENT); + ddata_put_UINT32(&data, mSub->id); + ddata_put_atom(&data, "closed"); + ddata_send(&data, 1); + ddata_final(&data); + } + else { + bt_ok(mSub->cmdid); + } + remove_subscription(&mCtx->list,L2CAP,mSub->id); +} + +- (void)l2capChannelReconfigured:(IOBluetoothL2CAPChannel*)l2capChannel +{ + (void) l2capChannel; + DEBUGF("L2CAPChannelDelegate: Reconfigured"); +} + +- (void)l2capChannelWriteComplete:(IOBluetoothL2CAPChannel*)l2capChannel refcon:(void*)refcon status:(IOReturn)error +{ + ddata_t* out = (ddata_t*) refcon; + uint32_t len; + (void)error; + + DEBUGF("L2CAPChannelDelegate: WriteComplete"); + if ((len=ddata_r_avail(out)) == 0) { + ddata_free(out); + bt_ok(mSub->cmdid); + mSub->cmdid = 0; + } + else { + BluetoothL2CAPMTU mtu = [l2capChannel outgoingMTU]; + uint8_t* ptr = out->wr; + IOReturn err; + if (len > mtu) len = mtu; + ddata_forward(out, len); /* move to next block */ + err = [l2capChannel writeAsync:ptr length:len refcon:out]; + if (err != kIOReturnSuccess) { + bt_error(mSub->cmdid, err); + mSub->cmdid=0; + ddata_free(out); + } + } +} + +- (void)l2capChannelQueueSpaceAvailable:(IOBluetoothL2CAPChannel*)l2capChannel +{ + (void) l2capChannel; + DEBUGF("L2CAPChannelDelegate: QueueSpaceAvailable"); +} + +@end + +// ----------------------------------------------------------------------- + + +/* check if we have a listner setup channel event listner */ +void rfcomm_accept(subscription_t* listen) +{ + listen_queue_t* lq = (listen_queue_t*) listen->opaque; + bt_ctx_t* ctx = lq->ctx; + + if (listen->type != RFCOMM_LISTEN) { + DEBUGF("RFCOMM: not a listen subscription", 0); + } + else if (lq->qh == lq->qt) { + DEBUGF("RFCOMM: listen queue empty"); + } + else { + subscription_link_t* link; + + if ((link = lq->wait.last) == NULL) { // peek + DEBUGF("RFCOMM: no acceptor"); + } + else { + RFCOMMChannelDelegate* delegate; + subscription_t* s = link->s; + + delegate = [[RFCOMMChannelDelegate alloc] initWithSub:s andCtx:ctx]; + + /* loop until we find a working channel */ + while (lq->qh != lq->qt) { + IOBluetoothRFCOMMChannel* channel = + (IOBluetoothRFCOMMChannel*) lq->qelem[lq->qt]; + IOReturn err; + + lq->qelem[lq->qt] = NULL; + lq->qt = (lq->qt+1) % LISTEN_QUEUE_LENGTH; + + err = [channel setDelegate:delegate]; + + if (err != kIOReturnSuccess) { + DEBUGF("RFCOMM: accept error %d:", err); + [channel closeChannel]; + } + else { + const BluetoothDeviceAddress* addr; + BluetoothRFCOMMChannelID channel_id; + IOBluetoothDevice* device; + uint8_t buf[64]; + ddata_t data; + + if ((device = [channel getDevice]) == NULL) { + DEBUGF("RFCOMM: accept no device"); + [channel closeChannel]; + [channel release]; + continue; + } + if ((addr = [device getAddress]) == NULL) { + DEBUGF("RFCOMM: accept no address"); + [channel closeChannel]; + [channel release]; + continue; + } + s->handle = channel; + s->accept = NULL; + channel_id = [channel getChannelID]; + + DEBUGF("RFCOMM: accept on %d", channel_id); + + unlink_subscription(link); + + /* send EVENT id {accept,Address,Channel} */ + ddata_init(&data, buf, sizeof(buf), 0); + ddata_put_UINT32(&data, 0); + ddata_put_tag(&data, REPLY_EVENT); + ddata_put_UINT32(&data, s->id); + ddata_put_tag(&data, TUPLE); + ddata_put_atom(&data, "accept"); + ddata_put_addr(&data, addr); + ddata_put_uint8(&data, channel_id); + ddata_put_tag(&data, TUPLE_END); + ddata_send(&data, 1); + ddata_final(&data); + return; + } + } + [delegate release]; // no working channel found + } + } +} + +// special open callback ! +void cb_rfcomm_open(void * userRefCon, IOBluetoothUserNotificationRef inRef, IOBluetoothObjectRef objectRef ) +{ + IOBluetoothRFCOMMChannel* channel = (IOBluetoothRFCOMMChannel*) objectRef; + subscription_t* listen = (subscription_t*) userRefCon; + listen_queue_t* lq = (listen_queue_t*) listen->opaque; + int qh_next; + (void) inRef; + + DEBUGF("RFCOMM: OpenNotification", 0); + qh_next = (lq->qh+1) % LISTEN_QUEUE_LENGTH; + if (qh_next == lq->qt) { + DEBUGF("RFCOMM: listen queue full"); + /* If queue is full possibly delete the oldest/newest ? */ + // close it? + return; + } + else { + lq->qelem[lq->qh] = channel; + lq->qh = qh_next; + [channel retain]; + rfcomm_accept(listen); + } +} + +/* check if we have a listner setup channel event listner */ +void l2cap_accept(subscription_t* listen) +{ + listen_queue_t* lq = (listen_queue_t*) listen->opaque; + bt_ctx_t* ctx = lq->ctx; + + if (listen->type != L2CAP_LISTEN) { + DEBUGF("L2CAP: not a listen subscription", 0); + } + else if (lq->qh == lq->qt) { + DEBUGF("L2CAP: listen queue empty", 0); + } + else { + subscription_link_t* link; + + if ((link = lq->wait.last) == NULL) { // peek + DEBUGF("L2CAP: no acceptor"); + } + else { + L2CAPChannelDelegate* delegate; + subscription_t* s = link->s; + + delegate = [[L2CAPChannelDelegate alloc] initWithSub:s andCtx:ctx]; + + /* loop until we find a working channel */ + while (lq->qh != lq->qt) { + IOBluetoothL2CAPChannel* channel = + (IOBluetoothL2CAPChannel*) lq->qelem[lq->qt]; + IOReturn err; + + lq->qelem[lq->qt] = NULL; + lq->qt = (lq->qt+1) % LISTEN_QUEUE_LENGTH; + + err = [channel setDelegate:delegate]; + + if (err != kIOReturnSuccess) { + DEBUGF("L2CAP: accept error %d:", err); + [channel closeChannel]; + [channel release]; + } + else { + const BluetoothDeviceAddress* addr; + BluetoothL2CAPPSM psm; + IOBluetoothDevice* device; + uint8_t buf[64]; + ddata_t data; + + if ((device = [channel device]) == NULL) { + DEBUGF("L2CAP: accept no device", 0); + [channel closeChannel]; + [channel release]; + continue; + } + psm = [channel PSM]; + if ((addr = [device getAddress]) == NULL) { + DEBUGF("L2CAP: accept no address", 0); + [channel closeChannel]; + [channel release]; + continue; + } + DEBUGF("L2CAP: accept psm=%d", psm); + s->handle = channel; + s->accept = NULL; + unlink_subscription(link); + + /* send EVENT id {accept,Address,Psm} */ + ddata_init(&data, buf, sizeof(buf), 0); + ddata_put_UINT32(&data, 0); + ddata_put_tag(&data, REPLY_EVENT); + ddata_put_UINT32(&data, s->id); + ddata_put_tag(&data, TUPLE); + ddata_put_atom(&data, "accept"); + ddata_put_addr(&data, addr); + ddata_put_uint16(&data, psm); + ddata_put_tag(&data, TUPLE_END); + ddata_send(&data, 1); + ddata_final(&data); + return; + } + } + [delegate release]; // no working channel found + } + } +} + +void cb_l2cap_open(void * userRefCon, IOBluetoothUserNotificationRef inRef, IOBluetoothObjectRef objectRef ) +{ + IOBluetoothL2CAPChannel* l2capChannel = + (IOBluetoothL2CAPChannel*) objectRef; + subscription_t* listen = (subscription_t*) userRefCon; + listen_queue_t* lq = (listen_queue_t*) listen->opaque; + int qh_next; + (void) inRef; + + DEBUGF("L2CAP: OpenNotification", 0); + qh_next = (lq->qh+1) % LISTEN_QUEUE_LENGTH; + if (qh_next == lq->qt) { + DEBUGF("L2CAP: listen queue full", 0); + /* If queue is full possibly delete the oldest/newest ? */ + // close? + return; + } + else { + lq->qelem[lq->qh] = l2capChannel; + lq->qh = qh_next; + [l2capChannel retain]; + l2cap_accept(listen); + } +} + + +void bt_command(bt_ctx_t* ctx, const uint8_t* src, uint32_t src_len) +{ + uint8_t op = 0; + uint32_t cmdid = 0; + IOReturn bt_error = kIOReturnSuccess; + uint8_t out_buf[2048]; + ddata_t data_in; + ddata_t data_out; + + // dump subscription list + if (dlog_debug_level == DLOG_DEBUG) { + subscription_link_t* p = ctx->list.first; + fprintf(stderr, "ctx.list = {"); + while(p) { + fprintf(stderr, " %s,", format_subscription(p->s)); + p = p->next; + } + fprintf(stderr, "}\r\n"); + } + + ddata_r_init(&data_in, (uint8_t*)src, src_len, 0); + ddata_init(&data_out, out_buf, sizeof(out_buf), 0); + ddata_put_UINT32(&data_out, 0); + + if (!ddata_get_uint8(&data_in, &op)) + goto badarg; + if (!ddata_get_uint32(&data_in, &cmdid)) + goto badarg; + + switch (op) { + case CMD_PING: { + DEBUGF("CMD_PING cmdid=%d", cmdid); + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + ddata_put_string(&data_out, "pong"); + goto reply; + } + + case CMD_DEBUG: { // <<level>> + int level; + DEBUGF("CMD_DEBUG cmdid=%d", cmdid); + if (!ddata_get_int32(&data_in, &level)) + goto badarg; + dlog_set_debug(level); + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + goto reply; + } + + case CMD_PAIRED_DEVICES: { + NSArray* devices = [IOBluetoothDevice pairedDevices]; + DEBUGF("CMD_PAIRED_DEVICE cmdid=%d", cmdid); + + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + bt_device_list(devices, &data_out); + [devices release]; + goto reply; + } + + case CMD_FAVORITE_DEVICES: { + NSArray* devices = [IOBluetoothDevice favoriteDevices]; + + DEBUGF("CMD_FAVORITE_DEVICE cmdid=%d", cmdid); + + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + bt_device_list(devices, &data_out); + [devices release]; + goto reply; + } + + case CMD_RECENT_DEVICES: { + NSArray* devices = [IOBluetoothDevice recentDevices:0]; + + DEBUGF("CMD_RECENT_DEVICES cmdid=%d", cmdid); + + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + bt_device_list(devices, &data_out); + [devices release]; + goto reply; + } + + case CMD_INQUIRY_START: { /* arguments: id:32 secs:32 */ + /* FIXME: check that no inquery is running !!! */ + uint32_t sid; + uint32_t secs; + InquiryDelegate* delegate; + IOBluetoothDeviceInquiry* inquiry; + subscription_t* s; + + /* NOTE: The behavior when running multiple inquery is + * random (buggy, undefined?) use one at the time as + * a work around + */ + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if (!ddata_get_uint32(&data_in, &secs)) + goto badarg; + + DEBUGF("CMD_INQUIRY_START cmdid=%d id=%u secs=%u", + cmdid, sid, secs); + + if ((s = new_subscription(INQUIRY,sid,0,NULL,cleanup)) == NULL) + goto mem_error; + + delegate = [[InquiryDelegate alloc] initWithSub:s]; + inquiry = [[IOBluetoothDeviceInquiry alloc] initWithDelegate:delegate]; + + s->handle = inquiry; + inquiry.inquiryLength = secs; + inquiry.updateNewDeviceNames = FALSE; + + bt_error=[inquiry start]; + if (bt_error == kIOReturnSuccess) { + insert_last(&ctx->list, s); + goto ok; + } + else { + release_subscription(s); + [inquiry release]; + [delegate release]; + goto error; + } + break; + } + + case CMD_INQUIRY_STOP: { /* arguments: id:32 */ + uint32_t sid; + IOBluetoothDeviceInquiry* inquiry; + subscription_t* s; + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + + DEBUGF("CMD_INQUIRY_STOP cmdid=%d, sid=%u", cmdid, sid); + + if ((s = find_subscription(&ctx->list,INQUIRY,sid)) == NULL) + goto badarg; + inquiry = (IOBluetoothDeviceInquiry*)(s->handle); + [inquiry stop]; + remove_subscription(&ctx->list,INQUIRY,sid); + goto ok; + } + + case CMD_CONNECT: { /* arg = bt-address(6) [timeout:32 [auth:8]] */ + BluetoothDeviceAddress bt_addr; + IOBluetoothDevice* device; + BluetoothHCIPageTimeout timeout = 0; + Boolean auth = FALSE; + Boolean opts = FALSE; + uint32_t milli; + ConnectDelegate* delegate; + subscription_t* s; + + DEBUGF("CMD_CONNECT cmdid=%d", cmdid); + + if(!get_address(&data_in, &bt_addr)) + goto badarg; + if (ddata_get_uint32(&data_in, &milli)) { + opts = TRUE; + if (milli == 0) + timeout = 0; + else { + uint32_t t; + if (milli > 40960) + milli = 40960; + t = ((milli*1600) / 1000); + timeout = t; + } + if (!ddata_get_boolean(&data_in, &auth)) + auth = FALSE; + if (ddata_r_avail(&data_in) != 0) + goto badarg; + } + if ((s = new_subscription(CONNECT,0,cmdid,NULL,cleanup)) == NULL) + goto mem_error; + if ((device = [IOBluetoothDevice deviceWithAddress:&bt_addr]) == NULL) + goto badarg; + delegate = [[ConnectDelegate alloc] initWithSub:s]; + if (opts) + bt_error = [device openConnection:delegate + withPageTimeout:timeout + authenticationRequired: auth]; + else + bt_error = [device openConnection:delegate]; + if (bt_error != kIOReturnSuccess) { + [delegate release]; + release_subscription(s); + goto error; + } + /* callback will do the rest */ + break; + } + + case CMD_DISCONNECT: { /* arg = bt-address(6) */ + BluetoothDeviceAddress bt_addr; + IOBluetoothDevice* device; + + DEBUGF("CMD_DISCONNECT cmdid=%d", cmdid); + + if(!get_address(&data_in, &bt_addr)) + goto badarg; + if (ddata_r_avail(&data_in) != 0) + goto badarg; + if ((device = [IOBluetoothDevice deviceWithAddress:&bt_addr]) == NULL) + goto badarg; + bt_error = [device closeConnection]; + if (bt_error != kIOReturnSuccess) + goto error; + goto ok; + } + + case CMD_REMOTE_NAME: { /* arg = bt-address(6) timeout:32 */ + /* FIXME: check that no inquery is running !!! */ + BluetoothDeviceAddress bt_addr; + IOBluetoothDevice* device; + Boolean opts = FALSE; + uint32_t milli; + BluetoothHCIPageTimeout timeout; + RemoteNameDelegate* delegate; + subscription_t* s; + + DEBUGF("CMD_REMOTE_NAME cmdid=%d", cmdid); + + if(!get_address(&data_in, &bt_addr)) + goto badarg; + + if (ddata_get_uint32(&data_in, &milli)) { + opts = TRUE; + + if (milli == 0) + timeout = 0; + else { + uint32_t t; + if (milli > 40960) + milli = 40960; + t = ((milli*1600) / 1000); + timeout = t; + } + } + + if (ddata_r_avail(&data_in) != 0) + goto badarg; + if ((s = new_subscription(REMOTE_NAME,0,cmdid,NULL,cleanup)) == NULL) + goto mem_error; + if ((device = [IOBluetoothDevice deviceWithAddress:&bt_addr]) == NULL) + goto badarg; + delegate = [[RemoteNameDelegate alloc] initWithSub:s]; + if (opts && (timeout != 0)) + bt_error = [device remoteNameRequest:delegate withPageTimeout:timeout]; + else + bt_error = [device remoteNameRequest:delegate]; + if (bt_error != kIOReturnSuccess) { + [delegate release]; + release_subscription(s); + goto error; + } + /* callback will do the rest */ + break; + } + + case CMD_DEVICE_INFO: { /* arg = bt-address(6) info-items */ + BluetoothDeviceAddress bt_addr; + IOBluetoothDevice* device; + + DEBUGF("CMD_DEVICE_INFO cmdid=%d", cmdid); + + if(!get_address(&data_in, &bt_addr)) + goto badarg; + if ((device = [IOBluetoothDevice deviceWithAddress:&bt_addr]) == NULL) + goto badarg; + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + bt_device_info(device, &data_in, &data_out); + goto reply; + } + + case CMD_LOCAL_INFO: { /* arg = info-items */ + DEBUGF("CMD_LOCAL_INFO cmdid=%d", cmdid); + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + bt_local_info(&data_in, &data_out); + goto reply; + } + + case CMD_SERVICE_INFO: { /* addr:48 [uuid:16|uuid:32|uuid:128] */ + BluetoothDeviceAddress bt_addr; + IOBluetoothDevice* device; + IOBluetoothSDPUUID* uuid; + uint32_t n; + + DEBUGF("CMD_SERVICE_INFO cmdid=%d", cmdid); + + if(!get_address(&data_in, &bt_addr)) + goto badarg; + if ((device = [IOBluetoothDevice deviceWithAddress:&bt_addr]) == NULL) + goto badarg; + n = ddata_r_avail(&data_in); + DEBUGF("SERVICE_INFO: avail=%d", n); + if (get_uuid(&data_in, &uuid)) { + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + bt_sdp_info(device, uuid, &data_out); + goto reply; + } + goto badarg; + } + + case CMD_SERVICE_QUERY: { /* addr:48 [uuid:16|uuid:32|uuid:128]*/ + BluetoothDeviceAddress bt_addr; + IOBluetoothDevice* device; + IOBluetoothSDPUUID* uuid; + SDPQueryDelegate* delegate; + subscription_t* s; + + DEBUGF("CMD_SERVICE_QUERY cmdid=%d", cmdid); + + if(!get_address(&data_in, &bt_addr)) + goto badarg; + if(!get_uuid(&data_in, &uuid)) + goto badarg; + if ((device = [IOBluetoothDevice deviceWithAddress:&bt_addr]) == NULL) + goto badarg; + if (ddata_r_avail(&data_in) != 0) + goto badarg; + + if ((s = new_subscription(SDP_QUERY,0,cmdid,NULL,cleanup)) == NULL) + goto mem_error; + delegate = [[SDPQueryDelegate alloc] initWithSub:s andUUID:uuid]; + // FIXME: add uuid match + bt_error = [device performSDPQuery:delegate]; + if (bt_error != kIOReturnSuccess) { + [delegate release]; + release_subscription(s); + goto error; + } + goto done; + } + + case CMD_SERVICE_DEL: { /* id:32 */ + uint32_t sid; + subscription_link_t* link; + + DEBUGF("CMD_SERVICE_DEL cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if (ddata_r_avail(&data_in) != 0) + goto badarg; + DEBUGF("SERVICE_CLOSE: id=%d", sid); + if ((link=find_subscription_link(&ctx->list,SDP,sid)) != NULL) { + unlink_subscription(link); + goto ok; + } + goto badarg; + } + + case CMD_SERVICE_ADD: { /* id:32 sdp-service-data/binary */ + uint32_t sid; + NSDictionary* serviceDict; + IOBluetoothSDPServiceRecord* serviceRecord; + + DEBUGF("CMD_SERVICE_ADD cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + + if ((serviceDict = bt_make_sdp_dict(&data_in)) == NULL) + goto badarg; +// #ifdef debug + /* DEBUG print the plist to file for debugging */ + { + id plist = serviceDict; // Assume this property list exists. + NSData *xmlData; + NSError *error; + NSString *path = [NSString stringWithUTF8String:"Service.plist"]; + xmlData = [NSPropertyListSerialization + dataWithPropertyList:plist + format:NSPropertyListXMLFormat_v1_0 + options:0 + error:&error]; + if(xmlData) { + [xmlData writeToFile:path atomically:YES]; + } + else { + NSLog(@"xmlData error %@", error); + [error release]; + } + } +// #endif + // fixme: add "LocalAttributes" + // "Persistent" boolean()/integer(), + // "TargetApplication" + serviceRecord = [IOBluetoothSDPServiceRecord + publishedServiceRecordWithDictionary:serviceDict]; + if (serviceRecord == NULL) { + bt_error = kIOReturnNoMemory; + goto error; + } + else { + subscription_t* s; + BluetoothSDPServiceRecordHandle serviceRecordHandle; + + if ((s = new_subscription(SDP,sid,cmdid,serviceRecord,cleanup)) + == NULL) + goto mem_error; + if (insert_last(&ctx->list, s) < 0) + goto mem_error; + bt_error = [serviceRecord + getServiceRecordHandle:&serviceRecordHandle]; + if (bt_error != kIOReturnSuccess) + goto error; + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + ddata_put_uint32(&data_out, serviceRecordHandle); + goto reply; + } + } + + case CMD_SERVICE_RFCOMM: { /* id:32 */ + uint32_t sid; + subscription_t* s; + + DEBUGF("CMD_SERVICE_RFCOMM cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if (ddata_r_avail(&data_in) != 0) + goto badarg; + if ((s = find_subscription(&ctx->list,SDP,sid)) != NULL) { + BluetoothRFCOMMChannelID channelID; + IOBluetoothSDPServiceRecord* serviceRecord = + (IOBluetoothSDPServiceRecord*) s->handle; + bt_error = [serviceRecord getRFCOMMChannelID:&channelID]; + if (bt_error == kIOReturnSuccess) { + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + ddata_put_uint8(&data_out, channelID); + goto reply; + } + goto error; + } + goto badarg; + } + + case CMD_RFCOMM_OPEN: { /* id:32 bt-address(6) channel-id:8 */ + uint32_t sid; + BluetoothDeviceAddress bt_addr; + IOBluetoothDevice* device; + IOBluetoothRFCOMMChannel* channel; + BluetoothRFCOMMChannelID channel_id; + RFCOMMChannelDelegate* delegate; + subscription_t* s; + + DEBUGF("CMD_RFCOMM_OPEN cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if(!get_address(&data_in, &bt_addr)) + goto badarg; + if(!ddata_get_uint8(&data_in, &channel_id)) + goto badarg; + if (!RFCOMM_CHANNEL_ID_IS_VALID(channel_id)) + goto badarg; + if (ddata_r_avail(&data_in) != 0) + goto badarg; + if ((device = [IOBluetoothDevice deviceWithAddress:&bt_addr]) == NULL) + goto badarg; + + if ((s = new_subscription(RFCOMM,sid,cmdid,NULL,cleanup)) == NULL) + goto mem_error; + delegate = [[RFCOMMChannelDelegate alloc] initWithSub:s andCtx:ctx]; + bt_error = [device openRFCOMMChannelAsync:&channel + withChannelID:channel_id + delegate:delegate]; + if (bt_error == kIOReturnSuccess) { + s->handle = channel; + insert_last(&ctx->list, s); + goto done; + } + else { + [delegate release]; + release_subscription(s); + goto error; + } + break; + } + + case CMD_RFCOMM_CLOSE: { /* arguments: id:32 */ + uint32_t sid; + subscription_link_t* link; + + DEBUGF("CMD_RFCOMM_CLOSE cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if (ddata_r_avail(&data_in) != 0) + goto badarg; + + if ((link = find_subscription_link(&ctx->list,RFCOMM,sid)) != NULL) { + IOBluetoothRFCOMMChannel* rfcommChannel; + subscription_t* s = link->s; + s->cmdid = cmdid; + rfcommChannel = (IOBluetoothRFCOMMChannel*) (s->handle); + if (rfcommChannel != NULL) { + DEBUGF("RFCOMM_CLOSE: channel=%p", rfcommChannel); + bt_error = [rfcommChannel closeChannel]; + if (bt_error == kIOReturnSuccess) + goto done; + } + else if (s->accept != NULL) { + listen_queue_t* lq = (listen_queue_t*)((s->accept)->opaque); + remove_subscription(&lq->wait,RFCOMM,sid); + unlink_subscription(link); + goto ok; + } + } + else if ((link = find_subscription_link(&ctx->list,RFCOMM_LISTEN,sid)) != NULL) { + subscription_t* listen = link->s; + listen_queue_t* lq = (listen_queue_t*)listen->opaque; + subscription_link_t* link1; + + /* remove all waiters */ + while((link1=lq->wait.first) != NULL) { + bt_event(link1->s->id, "closed"); + unlink_subscription(link1); + } + unlink_subscription(link); + goto ok; + } + goto error; + } + + case CMD_RFCOMM_LISTEN: { /* id:32, channel:8 */ + uint32_t sid; + BluetoothRFCOMMChannelID channel_id; + IOBluetoothUserNotificationRef ref; + subscription_t* listen; + listen_queue_t* lq; + + DEBUGF("CMD_RFCOMM_LISTEN cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if(!ddata_get_uint8(&data_in, &channel_id)) + goto badarg; + if ((channel_id != 0) && !RFCOMM_CHANNEL_ID_IS_VALID(channel_id)) + goto badarg; + if (ddata_r_avail(&data_in) != 0) + goto badarg; + + if ((listen = new_subscription(RFCOMM_LISTEN,sid,cmdid,NULL,cleanup)) + == NULL) + goto mem_error; + if ((lq = alloc_type(listen_queue_t)) == NULL) { + release_subscription(listen); + goto mem_error; + } + lq->ctx = ctx; + listen->opaque = (void*) lq; + + if (channel_id == 0) + ref = IOBluetoothRegisterForRFCOMMChannelOpenNotifications(cb_rfcomm_open, (void*)listen); + else + ref = IOBluetoothRegisterForFilteredRFCOMMChannelOpenNotifications( + cb_rfcomm_open, (void*) listen, channel_id, + kIOBluetoothUserNotificationChannelDirectionIncoming); + if (ref == NULL) { + release_subscription(listen); + goto mem_error; + } + listen->handle = ref; + insert_last(&ctx->list, listen); + goto ok; + } + + case CMD_RFCOMM_ACCEPT: { /* id:32 listen_id:32 */ + uint32_t sid; + uint32_t listen_id; + listen_queue_t* lq; + subscription_t* listen; + subscription_t* s; + + DEBUGF("CMD_RFCOMM_ACCEPT cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if (!ddata_get_uint32(&data_in, &listen_id)) + goto badarg; + if (ddata_r_avail(&data_in) != 0) + goto badarg; + + if (find_subscription(&ctx->list,RFCOMM,sid) != NULL) { + DEBUGF("subscription %d already exists", sid); + goto badarg; + } + if ((listen = find_subscription(&ctx->list, + RFCOMM_LISTEN,listen_id))==NULL) { + DEBUGF("listen subscription %d does not exists", listen_id); + goto badarg; + } + if ((s = new_subscription(RFCOMM,sid,cmdid,NULL,cleanup)) == NULL) + goto mem_error; + s->accept = listen; // mark that we are accepting + lq = (listen_queue_t*) listen->opaque; + insert_last(&lq->wait, s); + insert_last(&ctx->list, s); + + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + ddata_send(&data_out, 1); + rfcomm_accept(listen); + goto done; + } + + case CMD_RFCOMM_SEND: { /* id:32, data/rest */ + uint32_t sid; + subscription_t* s; + IOBluetoothRFCOMMChannel* rfcommChannel; + uint32_t len; + ddata_t* out; + uint8_t* ptr; + BluetoothRFCOMMMTU mtu; + + DEBUGF("CMD_RFCOMM_SEND cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if ((s = find_subscription(&ctx->list,RFCOMM,sid)) == NULL) + goto badarg; + rfcommChannel = (IOBluetoothRFCOMMChannel*)(s->handle); + if (rfcommChannel == NULL) + goto badarg; + s->cmdid = cmdid; + /* we may have to retain the data while sending !!! + * create a Data and pace the sending MTU wise... + */ + out = ddata_new(data_in.rd, ddata_r_avail(&data_in)); + mtu = [rfcommChannel getMTU]; + if ((len = ddata_r_avail(out)) > mtu) + len = mtu; + ptr = out->wr; + ddata_forward(out, len); /* move to next block */ + bt_error = [rfcommChannel writeAsync:ptr length:len refcon:out]; + if (bt_error != kIOReturnSuccess) + goto error; + /* event call back will do the rest ? */ + goto done; + } + + case CMD_RFCOMM_MTU: { /* id:32 */ + uint32_t sid; + subscription_t* s; + IOBluetoothRFCOMMChannel* rfcommChannel; + BluetoothRFCOMMMTU mtu; + + DEBUGF("CMD_RFCOMM_MTU cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if ((s = find_subscription(&ctx->list,RFCOMM,sid)) == NULL) + goto badarg; + rfcommChannel = (IOBluetoothRFCOMMChannel*) (s->handle); + if (rfcommChannel == NULL) + goto badarg; + mtu = [rfcommChannel getMTU]; + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + ddata_put_uint16(&data_out, mtu); + goto reply; + } + + case CMD_RFCOMM_ADDRESS: { /* id:32 */ + uint32_t sid; + subscription_t* s; + IOBluetoothRFCOMMChannel* rfcommChannel; + const BluetoothDeviceAddress* addr; + IOBluetoothDevice* device; + + DEBUGF("CMD_RFCOMM_ADDRESS cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if ((s = find_subscription(&ctx->list,RFCOMM,sid)) == NULL) + goto badarg; + rfcommChannel = (IOBluetoothRFCOMMChannel*)(s->handle); + if (rfcommChannel == NULL) + goto badarg; + if ((device = [rfcommChannel getDevice]) == NULL) + goto badarg; + if ((addr = [device getAddress]) == NULL) + goto badarg; + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + ddata_put_addr(&data_out, addr); + goto reply; + } + + case CMD_RFCOMM_CHANNEL: { /* id:32 */ + uint32_t sid; + subscription_t* s; + IOBluetoothRFCOMMChannel* rfcommChannel; + BluetoothRFCOMMChannelID channel_id; + + DEBUGF("CMD_RFCOMM_CHANNEL cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if ((s = find_subscription(&ctx->list,RFCOMM,sid)) == NULL) + goto badarg; + rfcommChannel = (IOBluetoothRFCOMMChannel*)(s->handle); + channel_id = [rfcommChannel getChannelID]; + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + ddata_put_uint8(&data_out, channel_id); + goto reply; + } + + case CMD_L2CAP_OPEN: { /* id:32 bt-address(6) psm:16 */ + uint32_t sid; + BluetoothDeviceAddress bt_addr; + IOBluetoothDevice* device; + IOBluetoothL2CAPChannel* channel; + BluetoothL2CAPPSM psm; + subscription_t* s; + L2CAPChannelDelegate* delegate; + + DEBUGF("CMD_L2CAP_OPEN cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if(!get_address(&data_in, &bt_addr)) + goto badarg; + if(!ddata_get_uint16(&data_in, &psm)) + goto badarg; + if (ddata_r_avail(&data_in) != 0) + goto badarg; + + if ((device = [IOBluetoothDevice deviceWithAddress:&bt_addr]) == NULL) + goto badarg; + + DEBUGF("L2CAP_OPEN; %d psm=%d", sid, psm); + + if ((s = new_subscription(L2CAP,sid,cmdid,NULL,cleanup)) == NULL) + goto mem_error; + delegate = [[L2CAPChannelDelegate alloc] initWithSub:s andCtx:ctx]; + bt_error = [device openL2CAPChannelAsync:&channel + withPSM:psm + delegate:delegate]; + if (bt_error == kIOReturnSuccess) { + s->handle = channel; + insert_last(&ctx->list, s); + goto done; + } + else { + [delegate release]; + release_subscription(s); + goto error; + } + break; + } + + case CMD_L2CAP_CLOSE: { /* arguments: id:32 */ + uint32_t sid; + subscription_link_t* link; + + DEBUGF("CMD_L2CAP_CLOSE cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if (ddata_r_avail(&data_in) != 0) + goto badarg; + DEBUGF("L2CAP_CLOSE: id=%d", sid); + if ((link = find_subscription_link(&ctx->list,L2CAP,sid)) != NULL) { + IOBluetoothL2CAPChannel* l2capChannel; + subscription_t* s = link->s; + s->cmdid = cmdid; + l2capChannel = (IOBluetoothL2CAPChannel*)(s->handle); + if (l2capChannel != NULL) { + DEBUGF("L2CAP_CLOSE: channel=%p", l2capChannel); + bt_error = [l2capChannel closeChannel]; + if (bt_error == kIOReturnSuccess) + goto done; + + } + else if (s->accept != NULL) { + listen_queue_t* lq = (listen_queue_t*)(s->accept)->opaque; + remove_subscription(&lq->wait,L2CAP,sid); + unlink_subscription(link); + goto ok; + } + } + else if ((link = find_subscription_link(&ctx->list,L2CAP_LISTEN,sid)) + != NULL) { + subscription_t* listen = link->s; + listen_queue_t* lq = (listen_queue_t*)listen->opaque; + subscription_link_t* link1; + + // move this code to free subscription ? + /* remove all waiters */ + while((link1=lq->wait.first) != NULL) { + uint8_t buf[64]; + ddata_t data; + + ddata_init(&data, buf, sizeof(buf), 0); + ddata_put_UINT32(&data, 0); + ddata_put_tag(&data, REPLY_EVENT); + ddata_put_UINT32(&data, link1->s->id); + ddata_put_atom(&data, "closed"); + ddata_send(&data, 1); + ddata_final(&data); + unlink_subscription(link1); + } + unlink_subscription(link); + goto ok; + } + goto error; + } + + case CMD_L2CAP_LISTEN: { /* id:32, psm:16 */ + uint32_t sid; + BluetoothL2CAPPSM psm; + IOBluetoothUserNotificationRef ref; + subscription_t* listen; + listen_queue_t* lq; + + DEBUGF("CMD_L2CAP_LISTEN cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if(!ddata_get_uint16(&data_in, &psm)) + goto badarg; + if (ddata_r_avail(&data_in) != 0) + goto badarg; + + if ((listen = new_subscription(L2CAP_LISTEN,sid,cmdid,NULL,cleanup)) + == NULL) + goto mem_error; + if ((lq = alloc_type(listen_queue_t)) == NULL) { + release_subscription(listen); + goto mem_error; + } + lq->ctx = ctx; + listen->opaque = (void*) lq; + if (psm == 0) + ref = IOBluetoothRegisterForL2CAPChannelOpenNotifications(cb_l2cap_open, (void*) listen); + else + ref = IOBluetoothRegisterForFilteredL2CAPChannelOpenNotifications( + cb_l2cap_open, (void*) listen, psm, + kIOBluetoothUserNotificationChannelDirectionIncoming); + if (ref == NULL) { + release_subscription(listen); + goto mem_error; + } + listen->handle = ref; + insert_last(&ctx->list, listen); + goto ok; + } + + case CMD_L2CAP_SEND: { /* id:32, data/rest */ + uint32_t sid; + subscription_t* s; + IOBluetoothL2CAPChannel* l2capChannel; + uint32_t len; + ddata_t* out; + uint8_t* ptr; + BluetoothL2CAPMTU mtu; + + DEBUGF("CMD_L2CAP_SEND cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if ((s = find_subscription(&ctx->list,L2CAP,sid)) == NULL) + goto badarg; + l2capChannel = (IOBluetoothL2CAPChannel*)(s->handle); + if (l2capChannel == NULL) + goto badarg; + s->cmdid = cmdid; + /* we may have to retain the data while sending !!! + * create a Data and pace the sending MTU wise... + */ + out = ddata_new(data_in.rd, ddata_r_avail(&data_in)); + mtu = [l2capChannel outgoingMTU]; + if ((len = ddata_r_avail(out)) > mtu) + len = mtu; + ptr = out->wr; + ddata_forward(out, len); /* move to next block */ + bt_error = [l2capChannel writeAsync:ptr length:len refcon:out]; + if (bt_error != kIOReturnSuccess) + goto error; + /* event call back will do the rest ? */ + goto done; + } + + case CMD_L2CAP_ACCEPT: { /* id:32 listen_id:32 */ + uint32_t sid; + uint32_t listen_id; + listen_queue_t* lq; + subscription_t* listen; + subscription_t* s; + + DEBUGF("CMD_L2CAP_ACCEPT cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if (!ddata_get_uint32(&data_in, &listen_id)) + goto badarg; + if (ddata_r_avail(&data_in) != 0) + goto badarg; + + if (find_subscription(&ctx->list,L2CAP,sid) != NULL) { + DEBUGF("subscription %d already exists", sid); + goto badarg; + } + if ((listen = find_subscription(&ctx->list,L2CAP_LISTEN,listen_id)) + ==NULL) { + DEBUGF("listen subscription %d does not exists", listen_id); + goto badarg; + } + if ((s = new_subscription(L2CAP,sid,cmdid,NULL,cleanup)) == NULL) + goto mem_error; + s->accept = listen; // mark that we are accepting + lq = (listen_queue_t*) listen->opaque; + insert_last(&lq->wait, s); + insert_last(&ctx->list, s); + + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + ddata_send(&data_out, 1); + l2cap_accept(listen); + goto done; + } + + case CMD_L2CAP_MTU: {/* id:32 */ + uint32_t sid; + subscription_t* s; + IOBluetoothL2CAPChannel* l2capChannel; + BluetoothL2CAPMTU mtu; + + DEBUGF("CMD_L2CAP_MTU cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if ((s = find_subscription(&ctx->list,L2CAP,sid)) == NULL) + goto badarg; + l2capChannel = (IOBluetoothL2CAPChannel*)(s->handle); + if (l2capChannel == NULL) + goto badarg; + mtu = [l2capChannel outgoingMTU]; + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + ddata_put_uint16(&data_out, mtu); + goto reply; + } + + case CMD_L2CAP_ADDRESS: { /* id:32 */ + uint32_t sid; + subscription_t* s; + IOBluetoothL2CAPChannel* l2capChannel; + const BluetoothDeviceAddress* addr; + IOBluetoothDevice* device; + + DEBUGF("CMD_L2CAP_ADDRESS cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if ((s = find_subscription(&ctx->list,L2CAP,sid)) == NULL) + goto badarg; + l2capChannel = (IOBluetoothL2CAPChannel*) (s->handle); + if (l2capChannel == NULL) + goto badarg; + if ((device = [l2capChannel device]) == NULL) + goto badarg; + if ((addr = [device getAddress]) == NULL) + goto badarg; + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + ddata_put_addr(&data_out, addr); + goto reply; + } + + case CMD_L2CAP_PSM: {/* id:32 */ + uint32_t sid; + subscription_t* s; + IOBluetoothL2CAPChannel* l2capChannel; + BluetoothL2CAPPSM psm; + + DEBUGF("CMD_L2CAP_PSM cmdid=%d", cmdid); + + if (!ddata_get_uint32(&data_in, &sid)) + goto badarg; + if ((s = find_subscription(&ctx->list,L2CAP,sid)) == NULL) + goto badarg; + l2capChannel = (IOBluetoothL2CAPChannel*) (s->handle); + psm = [l2capChannel PSM]; + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + ddata_put_uint16(&data_out, psm); + goto reply; + } + + default: + DEBUGF("CMD_UNKNOWN = %d cmdid=%d", op, cmdid); + goto badarg; + } + + goto done; + +ok: + if (cmdid == 0) + goto done; + ddata_put_tag(&data_out, REPLY_OK); + ddata_put_UINT32(&data_out, cmdid); + goto reply; + +mem_error: + if (cmdid == 0) + goto done; + bt_error = kIOReturnNoMemory; + goto error; + +badarg: + if (cmdid == 0) + goto done; + bt_error = kIOReturnBadArgument; + +error: +/* reset, just in case something was inserted */ + ddata_reset(&data_out); + ddata_put_UINT32(&data_out, 0); + ddata_put_tag(&data_out, REPLY_ERROR); + ddata_put_UINT32(&data_out, cmdid); + ddata_put_io_error(&data_out, bt_error, ERR_SHORT); +reply: + ddata_send(&data_out, 1); +done: + ddata_final(&data_out); +} + +typedef CFSocketNativeHandle PipeNativeHandle; +typedef CFSocketCallBack PipeCallBack; +typedef CFSocketCallBackType PipeCallBackType; +typedef CFSocketContext PipeContext; +typedef CFSocketRef PipeRef; + +#define kPipeNoCallBack kCGSocketNoCallBack +#define kPipeReadCallBack kCFSocketReadCallBack +#define kPipeDataCallBack kCFSocketDataCallBack +#define kPipeWriteCallBack kCFSocketWriteCallBack + +PipeRef PipeCreateWithNative(CFAllocatorRef allocator, + PipeNativeHandle pipe, + CFOptionFlags callBackTypes, + PipeCallBack callback, const PipeContext *context) +{ + return CFSocketCreateWithNative(allocator, pipe, + callBackTypes, + callback, + context); +} + +CFRunLoopSourceRef PipeCreateRunLoopSource(CFAllocatorRef allocator, + PipeRef pipe, CFIndex order) +{ + return CFSocketCreateRunLoopSource(allocator, pipe, order); +} + + +/* + * Handle input commands from Erlang (or other program) + */ + +void pipe_callback(PipeRef pipe, PipeCallBackType type, CFDataRef address, + const void *data, void *info) +{ + bt_ctx_t* ctx = (bt_ctx_t*) info; + (void) address; + (void) data; + + // DEBUGF("PIPE: callback type=%d", type); + if (type == kPipeReadCallBack) { + int fd = CFSocketGetNative(pipe); + int n; + + if (ctx->pbuf_len < sizeof(ctx->pbuf)) { + int r = sizeof(ctx->pbuf) - ctx->pbuf_len; + if ((n = read(fd, ctx->pbuf+ctx->pbuf_len, r)) < 0) + goto error; + if (n == 0) + goto closed; + ctx->pbuf_len += n; + // DEBUGF("READ: %d pbuf_len=%d", n, ctx->pbuf_len); + if (ctx->pbuf_len == sizeof(ctx->pbuf)) { + ctx->len = (ctx->pbuf[0]<<24) + (ctx->pbuf[1]<<16) + + (ctx->pbuf[2]<<8) + ctx->pbuf[3]; + // DEBUGF("READ: %d packet len=%d", n, ctx->len); + if (ctx->len > 0) { + ctx->remain = ctx->len; + ctx->packet = (uint8_t*) malloc(ctx->len); + ctx->ptr = ctx->packet; + } + else { + ctx->remain = 0; + ctx->pbuf_len = 0; + } + } + } + else { + if ((n = read(fd, (void*)ctx->ptr, ctx->remain)) < 0) + goto error; + if (n == 0) + goto closed; + // DEBUGF("READ: %d packet bytes", n); + ctx->remain -= n; + // DEBUGF("PACKET: remain=%d", ctx->remain); + ctx->ptr += n; + if (ctx->remain == 0) { + bt_command(ctx, ctx->packet, ctx->len); + free(ctx->packet); + ctx->packet = NULL; + ctx->len = 0; + ctx->pbuf_len = 0; + } + } + } + // DEBUGF("PIPE: callback done",0); + return; + +error: + DEBUGF("pipe read error",0); + exit(1); + +closed: + DEBUGF("eof clean-up",0); + CFRunLoopStop(CFRunLoopGetCurrent()); +} + + + +int main(int argc, char** argv) +{ + PipeRef pipe_in; + PipeRef pipe_out; + PipeNativeHandle in_fd = 0; + PipeNativeHandle out_fd = 1; + CFRunLoopSourceRef pipe_ref; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + (void) argc; + (void) argv; +// The global context (fixme - remove this) + bt_ctx_t bt_info; + PipeContext in_ctx; + PipeContext out_ctx; + + dlog_init(); + + memset(&bt_info, 0, sizeof(bt_ctx_t)); + memset(&in_ctx, 0, sizeof(PipeContext)); + memset(&out_ctx, 0, sizeof(PipeContext)); + + in_ctx.info = &bt_info; + out_ctx.info = &bt_info; + + // kCFSocketWriteCallBack + + pipe_in = PipeCreateWithNative(0, in_fd, + kCFSocketReadCallBack, + pipe_callback, + &in_ctx); + + pipe_out = PipeCreateWithNative(0, out_fd, + kCFSocketWriteCallBack, + pipe_callback, + &out_ctx); + + pipe_ref = PipeCreateRunLoopSource(0, pipe_in, 0); + + CFRunLoopAddSource(CFRunLoopGetCurrent(), pipe_ref, kCFRunLoopDefaultMode); + + CFRunLoopRun(); + + [pool release]; + + dlog_finish(); + DEBUGF("terminate",0); + exit(0); +} diff --git a/deps/bt/c_src/bt_poll.c b/deps/bt/c_src/bt_poll.c new file mode 100644 index 0000000..306af01 --- /dev/null +++ b/deps/bt/c_src/bt_poll.c @@ -0,0 +1,142 @@ +// +// Utility to handle multiple poll and callbacks +// + +#include <stdio.h> +#include <stdlib.h> + +#include "dthread/include/dlog.h" +#include "bt_poll.h" + +static int poll_sz = 0; // allocated size +static int poll_n = 0; // used len +static int poll_fdmapsz = 0; // size of fdmap +static struct pollfd* poll_fds = NULL; +static event_cb_t* poll_cb = NULL; +static void** poll_data = NULL; +static int* poll_fdmap = NULL; // fd -> poll_fds index + +static inline int fd_to_pollix(int fd) +{ + if ((fd < 0) || (fd >= poll_fdmapsz)) + return -1; + return poll_fdmap[fd]; +} + +// change pollfd data +int bt_poll_set_events(int fd, short events) +{ + int i; + if ((i = fd_to_pollix(fd)) < 0) return -1; + poll_fds[i].events = events; + return 0; +} + +int bt_poll_set_cb(int fd, event_cb_t cb) +{ + int i; + if ((i = fd_to_pollix(fd)) < 0) return -1; + poll_cb[i] = cb; + return 0; +} + +int bt_poll_set_data(int fd, void* data) +{ + int i; + if ((i = fd_to_pollix(fd)) < 0) return -1; + poll_data[i] = data; + return 0; +} + +int bt_poll_add(int fd, short events, + void (*cb)(struct pollfd* pfd,void* data), + void* data) +{ + int i = poll_n; + if (i == poll_sz) { + size_t new_sz = 2*poll_sz+1; + poll_fds = realloc(poll_fds, new_sz*sizeof(struct pollfd)); + if (poll_fds == NULL) return -1; + poll_cb = realloc(poll_cb, new_sz*sizeof(event_cb_t)); + if (poll_cb == NULL) return -1; + poll_data = realloc(poll_data, new_sz*sizeof(void*)); + if (poll_data == NULL) return -1; + poll_sz = new_sz; + } + if (fd >= poll_fdmapsz) { + int new_sz = fd+64; // add some extra + int j; + poll_fdmap = realloc(poll_fdmap, new_sz*sizeof(int)); + if (poll_fdmap == NULL) return -1; + for (j = poll_fdmapsz; j < new_sz; j++) + poll_fdmap[j] = -1; + poll_fdmapsz = new_sz; + } + poll_fdmap[fd] = i; + poll_cb[i] = cb; + poll_data[i] = data; + poll_fds[i].fd = fd; + poll_fds[i].events = events; + poll_n++; + return 0; +} + +static void bt_poll_swap(int i, int j) +{ + if (i != j) { + int fdi = poll_fds[i].fd; + int fdj = poll_fds[j].fd; + struct pollfd fds = poll_fds[i]; + event_cb_t cb = poll_cb[i]; + void* data = poll_data[i]; + + poll_fds[i] = poll_fds[j]; + poll_cb[i] = poll_cb[j]; + poll_data[i] = poll_data[j]; + + poll_fds[j] = fds; + poll_cb[j] = cb; + poll_data[j] = data; + // remap indices + poll_fdmap[fdi] = j; + poll_fdmap[fdj] = i; + } +} + +// find and remove the pollfd +int bt_poll_del(int fd) +{ + int i; + + if ((i = fd_to_pollix(fd)) < 0) return -1; + bt_poll_swap(i, poll_n-1); + poll_n--; + poll_fdmap[fd] = -1; // not mapped any more + return 0; +} + +int bt_poll(int timeout) +{ + int r,r0; + + r = r0 = poll(poll_fds, poll_n, timeout); + if (r > 0) { + int i = 0; + while((r > 0) && (i < (int)poll_n)) { + if ((poll_fds[i].revents & poll_fds[i].events) != 0) { + int j = poll_n-1; + // swap i and the last item, this makes the poll set less + // sensitive to starvation, the bt_poll_del may also be + // called in the callback without wory, note that + // you can not del any fd in a callback and expect to + // get a poll on all fds in the same loop (yet) + bt_poll_swap(i, j); + (*poll_cb[j])(&poll_fds[j], poll_data[j]); + r--; + } + else + i++; + } + } + return r0; +} diff --git a/deps/bt/c_src/bt_poll.h b/deps/bt/c_src/bt_poll.h new file mode 100644 index 0000000..bd92479 --- /dev/null +++ b/deps/bt/c_src/bt_poll.h @@ -0,0 +1,17 @@ +#ifndef __BT_POLL_H__ +#define __BT_POLL_H__ + +#include <poll.h> + +typedef void (*event_cb_t)(struct pollfd*, void*); + +extern int bt_poll_add(int fd, short events, event_cb_t cb, void* data); +extern int bt_poll_del(int fd); + +extern int bt_poll_set_events(int fd, short events); +extern int bt_poll_set_cb(int fd, event_cb_t cb); +extern int bt_poll_set_data(int fd, void* data); + +extern int bt_poll(int timeout); + +#endif diff --git a/deps/bt/c_src/bt_sub.c b/deps/bt/c_src/bt_sub.c new file mode 100644 index 0000000..2da8e02 --- /dev/null +++ b/deps/bt/c_src/bt_sub.c @@ -0,0 +1,236 @@ +// +// Subscription handling +// + +#include <stdio.h> +#include <string.h> + +#include "dthread/include/dlog.h" +#include "bt_sub.h" + +#define alloc_type(type) calloc(1, sizeof(type)) + +static char* format_subscription_type(subscription_type_t type) +{ + switch(type) { + case INQUIRY: return "INQUIRY"; + case REMOTE_NAME: return "REMOTE_NAME"; + case CONNECT: return "CONNECT"; + case SDP_QUERY: return "SPD_QUERY"; + case SDP: return "SDP"; + case RFCOMM: return "RFCOMM"; + case RFCOMM_LISTEN: return "RFCOMM_LISTEN"; + case L2CAP: return "LPCAP"; + case L2CAP_LISTEN: return "LPCAP_LISTEN"; + default: return "????"; + } +} + +char* format_subscription(subscription_t* s) +{ + static char format_buf[128]; + + snprintf(format_buf, sizeof(format_buf), + "%s id=%u,handle=%p ref=%u", + format_subscription_type(s->type), + s->id, s->handle, s->ref); + return format_buf; +} + + +subscription_t* new_subscription(subscription_type_t type, + uint32_t id, uint32_t cmdid, + void* handle, + void (*cleanup)(subscription_t* s)) +{ + subscription_t* s = alloc_type(subscription_t); + s->type = type; + s->id = id; + s->cmdid = cmdid; + s->handle = handle; + s->opaque = NULL; + s->ref = 0; + s->accept = NULL; + s->cleanup = cleanup; + return s; +} + +subscription_list_t* new_subscription_list() +{ + subscription_list_t* list = alloc_type(subscription_list_t); + return list; +} + +subscription_t* retain_subscription(subscription_t* s) +{ + if (s == NULL) { + DEBUGF("retain_subscription: NULL"); + } + else { + DEBUGF("retain_subscription: %s", format_subscription(s)); + s->ref++; + } + return s; +} + +subscription_link_t* new_subscription_link(subscription_list_t* list, + subscription_t* s) +{ + subscription_link_t* link = alloc_type(subscription_link_t); + if (link) { + link->list = list; + link->s = retain_subscription(s); + } + return link; +} + +subscription_t* release_subscription(subscription_t* s) +{ + DEBUGF("release_subscription: %s", format_subscription(s)); + if (s->ref <= 1) { + if (s->cleanup) + (*s->cleanup)(s); + if (s->opaque != NULL) + free(s->opaque); + free(s); + return NULL; + } + else { + s->ref--; + return s; + } +} + +subscription_link_t* insert_after_link(subscription_link_t* link, + subscription_link_t* after_link) +{ + subscription_list_t* list = after_link->list; + link->next = after_link->next; + link->prev = after_link; + link->list = list; + after_link->next = link; + if (link->next == NULL) + list->last = link; + else + link->next->prev = link; + list->length++; + return link; +} + +subscription_link_t* insert_before_link(subscription_link_t* link, + subscription_link_t* before_link) +{ + if (before_link->prev != NULL) + return insert_after_link(link, before_link->prev); + else { + subscription_list_t* list = before_link->list; + link->next = before_link; + link->list = list; + before_link->prev = link; + list->first = link; + list->length++; + } + return link; +} + +int insert_link_first(subscription_link_t* link) +{ + if (link != NULL) { + subscription_list_t* list = link->list; + link->next = list->first; + link->prev = NULL; + if (list->first != NULL) + list->first->prev = link; + else + list->last = link; + list->first = link; + list->length++; + return 0; + } + return -1; +} + +int insert_first(subscription_list_t* list, subscription_t* s) +{ + subscription_link_t* link = new_subscription_link(list,s); + return insert_link_first(link); +} + +int insert_link_last(subscription_link_t* link) +{ + if (link != NULL) { + subscription_list_t* list = link->list; + link->next = NULL; + link->prev = list->last; + if (list->last) + list->last->next = link; + else + list->first = link; + list->last = link; + list->length++; + return 0; + } + return -1; +} + + +int insert_last(subscription_list_t* list, subscription_t*s) +{ + subscription_link_t* link = new_subscription_link(list,s); + return insert_link_last(link); +} + +subscription_link_t* find_subscription_link(subscription_list_t* list, + subscription_type_t type, + uint32_t sid) +{ + subscription_link_t* p = list->first; + DEBUGF("find_subscription_link: %s id=%u", + format_subscription_type(type), sid); + while(p) { + if ((p->s->id == sid) && (p->s->type == type)) + return p; + p = p->next; + } + return NULL; +} + +subscription_t* find_subscription(subscription_list_t* list, + subscription_type_t type, + uint32_t sid) +{ + subscription_link_t* p = find_subscription_link(list, type, sid); + if (p != NULL) + return p->s; + return NULL; +} + +void unlink_subscription(subscription_link_t* link) +{ + subscription_list_t* list = link->list; + DEBUGF("unlink_subscription: %s", format_subscription(link->s)); + if (link->prev != NULL) + link->prev->next = link->next; + else + list->first = link->next; + + if (link->next != NULL) + link->next->prev = link->prev; + else + list->last = link->prev; + list->length--; + release_subscription(link->s); + free(link); +} + +int remove_subscription(subscription_list_t* list, + subscription_type_t type, uint32_t sid) +{ + subscription_link_t* link; + + if ((link = find_subscription_link(list, type, sid)) != NULL) { + unlink_subscription(link); + return 1; + } + return 0; +} diff --git a/deps/bt/c_src/bt_sub.h b/deps/bt/c_src/bt_sub.h new file mode 100644 index 0000000..75aed8a --- /dev/null +++ b/deps/bt/c_src/bt_sub.h @@ -0,0 +1,82 @@ +#ifndef __BT_SUB_H__ +#define __BT_SUB_H__ + +#include <stdlib.h> +#include <stdint.h> + +#include "dthread/include/ddata.h" + +typedef enum { + INQUIRY, + REMOTE_NAME, + CONNECT, + SDP_QUERY, + SDP, + RFCOMM, + RFCOMM_LISTEN, + L2CAP, + L2CAP_LISTEN +} subscription_type_t; + +typedef struct _subscription_t +{ + uint32_t ref; // ref count + subscription_type_t type; // type of subscription + uint32_t id; // subscription id + uint32_t cmdid; // current async cmdid (if any) + void* handle; // Bluetooth object handle + void* opaque; // subscription data + ddata_t* out; // send buffer + void (*cleanup)(struct _subscription_t* s); // clean up callback + struct _subscription_t* accept; // if on accept list +} subscription_t; + +typedef struct _subscription_link_t +{ + struct _subscription_list_t* list; + struct _subscription_link_t* next; + struct _subscription_link_t* prev; + subscription_t* s; +} subscription_link_t; + +typedef struct _subscription_list_t +{ + subscription_link_t* first; + subscription_link_t* last; + size_t length; +} subscription_list_t; + +extern char* format_subscription(subscription_t* s); +extern subscription_t* new_subscription(subscription_type_t type, + uint32_t id, uint32_t cmdid, + void* handle, + void (*cleanup)(subscription_t* s)); +extern subscription_list_t* new_subscription_list(void); +extern subscription_t* retain_subscription(subscription_t* s); +extern subscription_link_t* new_subscription_link(subscription_list_t* list, + subscription_t* s); +extern void free_subscription(subscription_t* s); +extern subscription_t* release_subscription(subscription_t* s); +extern subscription_link_t* insert_after_link(subscription_link_t* link, + subscription_link_t* after_link); +extern subscription_link_t* insert_before_link(subscription_link_t* link, + subscription_link_t* before_link); +extern int insert_link_first(subscription_link_t* link); +extern int insert_first(subscription_list_t* list, subscription_t* s); +extern int insert_link_last(subscription_link_t* link); +extern int insert_last(subscription_list_t* list, subscription_t*s); +extern subscription_link_t* find_subscription_link(subscription_list_t* list, + subscription_type_t type, + uint32_t sid); +extern subscription_t* find_subscription(subscription_list_t* list, + subscription_type_t type, + uint32_t sid); +extern void unlink_subscription(subscription_link_t* link); +extern int remove_subscription(subscription_list_t* list, + subscription_type_t type, uint32_t sid); + + + + + +#endif diff --git a/deps/bt/c_src/hci_drv.c b/deps/bt/c_src/hci_drv.c new file mode 100644 index 0000000..e3e9359 --- /dev/null +++ b/deps/bt/c_src/hci_drv.c @@ -0,0 +1,1009 @@ +/**** BEGIN COPYRIGHT ******************************************************** + * + * Copyright (C) 2015 Rogvall Invest AB, <tony@rogvall.se> + * + * This software is licensed as described in the file COPYRIGHT, which + * you should have received as part of this distribution. The terms + * are also available at http://www.rogvall.se/docs/copyright.txt. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYRIGHT file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + **** END COPYRIGHT **********************************************************/ + +#include <stdio.h> +#include <stdarg.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <asm/types.h> +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> + +#include "erl_driver.h" + +#define ATOM(NAME) am_ ## NAME +#define INIT_ATOM(NAME) am_ ## NAME = driver_mk_atom(#NAME) + +// Hack to handle R15 driver used with pre R15 driver +#if ERL_DRV_EXTENDED_MAJOR_VERSION == 1 +typedef int ErlDrvSizeT; +typedef int ErlDrvSSizeT; +#endif + +#if (ERL_DRV_EXTENDED_MAJOR_VERSION > 2) || ((ERL_DRV_EXTENDED_MAJOR_VERSION == 2) && (ERL_DRV_EXTENDED_MINOR_VERSION >= 1)) +#define SEND_TERM(ctx, to, message, len) erl_drv_send_term((ctx)->dport,(to),(message),(len)) +#else +#define SEND_TERM(ctx, to, message, len) driver_send_term((ctx)->port,(to),(message),(len)) +#endif + +#define PORT_CONTROL_BINARY + +#define INT_EVENT(ptr) ((int)((long)(ptr))) + +typedef struct _hci_drv_ctx_t { + ErlDrvPort port; + ErlDrvTermData dport; + ErlDrvTermData owner; + ErlDrvEvent fd; // socket + int protocol; // bt protocol + int active; + int is_selecting; + int is_sending; + int dev_id; // bound device id + void* hcibuf; + size_t hcibuf_len; +} hci_drv_ctx_t; + + +#define CMD_ACTIVE 1 +#define CMD_DEBUG 2 +#define CMD_BIND 3 +#define CMD_GETFILTER 4 +#define CMD_SETFILTER 5 + +#define CMD_HCIDEVUP 201 +#define CMD_HCIDEVDOWN 202 +#define CMD_HCIDEVRESET 203 +#define CMD_HCIDEVRESTAT 204 +#define CMD_HCIGETDEVLIST 210 +#define CMD_HCIGETDEVINFO 211 +#define CMD_HCIGETCONNLIST 212 +#define CMD_HCIGETCONNINFO 213 +#define CMD_HCIGETAUTHINFO 215 +#define CMD_HCISETRAW 220 +#define CMD_HCISETSCAN 221 +#define CMD_HCISETAUTH 222 +#define CMD_HCISETENCRYPT 223 +#define CMD_HCISETPTYPE 224 +#define CMD_HCISETLINKPOL 225 +#define CMD_HCISETLINKMODE 226 +#define CMD_HCISETACLMTU 227 +#define CMD_HCISETSCOMTU 228 +#define CMD_HCIBLOCKADDR 230 +#define CMD_HCIUNBLOCKADDR 231 +#define CMD_HCIINQUIRY 240 + +#define CTL_OK 0 +#define CTL_INT 1 +#define CTL_PAIR 2 +#define CTL_BIN 3 +#define CTL_LIST 4 +#define CTL_ERR 255 +#define CTL_STRERR 254 + +#define MIN_HCI_BUFSIZE (32*1024) +#define MAX_HCI_BUFSIZE (512*1024) + +#define MAX_CONN 10 + +#define MAX_VSIZE 16 + +ErlDrvTermData am_ok; +ErlDrvTermData am_error; +ErlDrvTermData am_undefined; +ErlDrvTermData am_true; +ErlDrvTermData am_false; + +#define push_atom(atm) do { \ + message[i++] = ERL_DRV_ATOM; \ + message[i++] = (atm); \ + } while(0) + +#define push_port(prt) do { \ + message[i++] = ERL_DRV_PORT; \ + message[i++] = (prt); \ + } while(0) + +#define push_pid(pid) do { \ + message[i++] = ERL_DRV_PID; \ + message[i++] = (pid); \ + } while(0) + +#define push_bin(buf,len) do { \ + message[i++] = ERL_DRV_BUF2BINARY; \ + message[i++] = (ErlDrvTermData)(buf); \ + message[i++] = (ErlDrvTermData)(len); \ + } while(0) + +#define push_nil() do { \ + message[i++] = ERL_DRV_NIL; \ + } while(0) + +#define push_string(str) do { \ + message[i++] = ERL_DRV_STRING; \ + message[i++] = (ErlDrvTermData) (str); \ + message[i++] = strlen(str); \ + } while(0) + +#define push_int(val) do { \ + message[i++] = ERL_DRV_INT; \ + message[i++] = (val); \ + } while(0) + +#define push_tuple(n) do { \ + message[i++] = ERL_DRV_TUPLE; \ + message[i++] = (n); \ + } while(0) + +#define push_list(n) do { \ + message[i++] = ERL_DRV_LIST; \ + message[i++] = (n); \ + } while(0) + + +ErlDrvEntry hci_drv_entry; + +static inline uint32_t get_uint32(uint8_t* ptr) +{ + uint32_t value = (ptr[0]<<24) | (ptr[1]<<16) | (ptr[2]<<8) | (ptr[3]<<0); + return value; +} + +static inline int32_t get_int32(uint8_t* ptr) +{ + uint32_t value = (ptr[0]<<24) | (ptr[1]<<16) | (ptr[2]<<8) | (ptr[3]<<0); + return (int32_t) value; +} + +static inline uint16_t get_uint16(uint8_t* ptr) +{ + uint16_t value = (ptr[0]<<8) | (ptr[1]<<0); + return value; +} + +static inline uint8_t get_uint8(uint8_t* ptr) +{ + return ptr[0]; +} + +static inline int8_t get_int8(uint8_t* ptr) +{ + return (int8_t) ptr[0]; +} + +static inline void put_uint8(uint8_t* ptr, uint8_t v) +{ + ptr[0] = v; +} + +static inline void put_uint16(uint8_t* ptr, uint16_t v) +{ + ptr[0] = v>>8; + ptr[1] = v; +} + +static inline void put_uint32(uint8_t* ptr, uint32_t v) +{ + ptr[0] = v>>24; + ptr[1] = v>>16; + ptr[2] = v>>8; + ptr[3] = v; +} + +static uint8_t* put_hci_dev_stats(uint8_t* ptr, struct hci_dev_stats* sp) +{ + put_uint32(ptr, sp->err_rx); ptr += sizeof(sp->err_rx); + put_uint32(ptr, sp->err_tx); ptr += sizeof(sp->err_tx); + put_uint32(ptr, sp->cmd_tx); ptr += sizeof(sp->cmd_tx); + put_uint32(ptr, sp->evt_rx); ptr += sizeof(sp->evt_rx); + put_uint32(ptr, sp->acl_tx); ptr += sizeof(sp->acl_tx); + put_uint32(ptr, sp->acl_rx); ptr += sizeof(sp->acl_rx); + put_uint32(ptr, sp->sco_tx); ptr += sizeof(sp->sco_tx); + put_uint32(ptr, sp->sco_rx); ptr += sizeof(sp->sco_rx); + put_uint32(ptr, sp->byte_tx); ptr += sizeof(sp->byte_tx); + put_uint32(ptr, sp->byte_rx); ptr += sizeof(sp->byte_rx); + return ptr; +}; + +static uint8_t* put_hci_dev_info(uint8_t* ptr, struct hci_dev_info* di) +{ + put_uint16(ptr, di->dev_id); ptr += sizeof(di->dev_id); + memcpy(ptr, di->name, sizeof(di->name)); ptr += sizeof(di->name); + memcpy(ptr, &di->bdaddr, sizeof(di->bdaddr)); ptr += sizeof(di->bdaddr); + put_uint32(ptr, di->flags); ptr += sizeof(di->flags); + put_uint8(ptr, di->type); ptr += sizeof(di->type); + memcpy(ptr, di->features, sizeof(di->features)); ptr += sizeof(di->features); + put_uint32(ptr, di->pkt_type); ptr += sizeof(di->pkt_type); + put_uint32(ptr, di->link_policy); ptr += sizeof(di->link_policy); + put_uint32(ptr, di->link_mode); ptr += sizeof(di->link_mode); + + put_uint16(ptr, di->acl_mtu); ptr += sizeof(di->acl_mtu); + put_uint16(ptr, di->acl_pkts); ptr += sizeof(di->acl_pkts); + put_uint16(ptr, di->sco_mtu); ptr += sizeof(di->sco_mtu); + put_uint16(ptr, di->sco_pkts); ptr += sizeof(di->sco_pkts); + + return put_hci_dev_stats(ptr, &di->stat); +} + +static uint8_t* put_hci_conn_info(uint8_t* ptr, struct hci_conn_info* ci) +{ + put_uint16(ptr, ci->handle); + ptr += sizeof(ci->handle); + memcpy(ptr, &ci->bdaddr, sizeof(ci->bdaddr)); + ptr += sizeof(ci->bdaddr); + put_uint8(ptr, ci->type); + ptr += sizeof(ci->type); + put_uint8(ptr, ci->out); + ptr += sizeof(ci->out); + put_uint16(ptr, ci->state); + ptr += sizeof(ci->state); + put_uint32(ptr, ci->link_mode); + ptr += sizeof(ci->link_mode); + return ptr; +} + +static uint8_t* put_hci_filter(uint8_t* ptr, struct hci_filter* fp) +{ + put_uint32(ptr, fp->type_mask); ptr += sizeof(fp->type_mask); + put_uint32(ptr, fp->event_mask[0]); ptr += sizeof(fp->event_mask[0]); + put_uint32(ptr, fp->event_mask[1]); ptr += sizeof(fp->event_mask[1]); + put_uint16(ptr, fp->opcode); ptr += sizeof(fp->opcode); + return ptr; +} + +static uint8_t* get_hci_filter(uint8_t* ptr, struct hci_filter* fp) +{ + + fp->type_mask = get_uint32(ptr); ptr += sizeof(fp->type_mask); + fp->event_mask[0] = get_uint32(ptr); ptr += sizeof(fp->event_mask[0]); + fp->event_mask[1] = get_uint32(ptr); ptr += sizeof(fp->event_mask[1]); + fp->opcode = get_uint16(ptr); ptr += sizeof(fp->opcode); + return ptr; +} + +static int hci_drv_init(void); +static void hci_drv_finish(void); +static void hci_drv_stop(ErlDrvData); +static void hci_drv_output(ErlDrvData,char*,ErlDrvSizeT); +#if 0 +static void hci_drv_outputv(ErlDrvData, ErlIOVec*); +#endif +static void hci_drv_ready_input(ErlDrvData, ErlDrvEvent); +static void hci_drv_ready_output(ErlDrvData data, ErlDrvEvent event); +static ErlDrvData hci_drv_start(ErlDrvPort, char* command); +static ErlDrvSSizeT hci_drv_ctl(ErlDrvData,unsigned int,char*,ErlDrvSizeT,char**,ErlDrvSizeT); +static void hci_drv_timeout(ErlDrvData); +static void hci_drv_stop_select(ErlDrvEvent event, void* arg); + +#define DLOG_DEBUG 7 +#define DLOG_INFO 6 +#define DLOG_NOTICE 5 +#define DLOG_WARNING 4 +#define DLOG_ERROR 3 +#define DLOG_CRITICAL 2 +#define DLOG_ALERT 1 +#define DLOG_EMERGENCY 0 +#define DLOG_NONE -1 + +#ifndef DLOG_DEFAULT +#define DLOG_DEFAULT DLOG_NONE +#endif + +#define DLOG(level,file,line,args...) do { \ + if (((level) == DLOG_EMERGENCY) || \ + ((debug_level >= 0) && ((level) <= debug_level))) { \ + emit_log((level),(file),(line),args); \ + } \ + } while(0) + +#define DEBUGF(args...) DLOG(DLOG_DEBUG,__FILE__,__LINE__,args) +#define INFOF(args...) DLOG(DLOG_INFO,__FILE__,__LINE__,args) +#define NOTICEF(args...) DLOG(DLOG_NOTICE,__FILE__,__LINE__,args) +#define WARNINGF(args...) DLOG(DLOG_WARNING,__FILE__,__LINE__,args) +#define ERRORF(args...) DLOG(DLOG_ERROR,__FILE__,__LINE__,args) +#define CRITICALF(args...) DLOG(DLOG_CRITICAL,__FILE__,__LINE__,args) +#define ALERTF(args...) DLOG(DLOG_ALERT,__FILE__,__LINE__,args) +#define EMERGENCYF(args...) DLOG(DLOG_EMERGENCY,__FILE__,__LINE__,args) + +static int debug_level = DLOG_DEFAULT; + +static void emit_log(int level, char* file, int line, ...) +{ + va_list ap; + char* fmt; + + if ((level == DLOG_EMERGENCY) || + ((debug_level >= 0) && (level <= debug_level))) { + int save_errno = errno; + va_start(ap, line); + fmt = va_arg(ap, char*); + fprintf(stderr, "%s:%d: ", file, line); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\r\n"); + va_end(ap); + errno = save_errno; + } +} + +/* general control reply function */ +static ErlDrvSSizeT ctl_reply(int rep, void* buf, ErlDrvSizeT len, + char** rbuf, ErlDrvSizeT rsize) +{ + char* ptr; + + if ((len+1) > rsize) { +#ifdef PORT_CONTROL_BINARY + ErlDrvBinary* bin = driver_alloc_binary(len+1); + if (bin == NULL) + return -1; + ptr = bin->orig_bytes; + *rbuf = (char*) bin; +#else + if ((ptr = driver_alloc(len+1)) == NULL) + return -1; + *rbuf = ptr; +#endif + } + else + ptr = *rbuf; + *ptr++ = rep; + memcpy(ptr, buf, len); + return len+1; +} + +static void* hci_drv_realloc_buffer(hci_drv_ctx_t* ctx, size_t len) +{ + if (ctx->hcibuf_len < len) { + ctx->hcibuf = driver_realloc(ctx->hcibuf, len); + ctx->hcibuf_len = len; + } + return ctx->hcibuf; +} + +static int hci_drv_init(void) +{ + INIT_ATOM(ok); + INIT_ATOM(error); + INIT_ATOM(undefined); + INIT_ATOM(true); + INIT_ATOM(false); + return 0; +} + +static void hci_drv_finish(void) +{ +} + +static void hci_drv_stop(ErlDrvData d) +{ + hci_drv_ctx_t* ctx = (hci_drv_ctx_t*) d; + + if (ctx) { + if (ctx->is_selecting) + driver_select(ctx->port, ctx->fd, ERL_DRV_READ, 0); + if (ctx->is_sending) + driver_select(ctx->port, ctx->fd, ERL_DRV_WRITE, 0); + driver_select(ctx->port, ctx->fd, ERL_DRV_USE, 0); + driver_free(ctx); + } +} + +// #define USE_WRITEV + +static void hci_drv_output(ErlDrvData d, char* buf,ErlDrvSizeT len) +{ + hci_drv_ctx_t* ctx = (hci_drv_ctx_t*) d; + int n; + + if ((n = driver_sizeq(ctx->port)) > 0) { + driver_enq(ctx->port, buf, len); + DEBUGF("hci_drv_tput: put on queue pending=%d", n+len); + } + else { // try send directly + // data format must be + // <<HCI_COMMAND_PKT,Opcode:16,Plen:8,Param:Plen/binary>> +#ifdef USE_WRITEV + struct iovec iv[3]; + int ivn; + // try write with writev !!! + iv[0].iov_base = buf; + iv[0].iov_len = 1; + iv[1].iov_base = buf+1; + iv[1].iov_len = HCI_COMMAND_HDR_SIZE; + iv[2].iov_base = buf+4; + iv[2].iov_len = buf[3]; + ivn = buf[3] ? 3 : 2; + DEBUGF("writev = %d, len=%d", 1+HCI_COMMAND_HDR_SIZE+buf[3], len); + + n = writev(INT_EVENT(ctx->fd), iv, ivn); +#else + n = write(INT_EVENT(ctx->fd), buf, len); +#endif + if (n < 0) { + ERRORF("write error=%s", strerror(errno)); + if ((errno == EAGAIN) || (errno = ENOBUFS)) { + DEBUGF("hci_drv: put %d bytes on queue", n); + driver_enq(ctx->port, buf, len); + driver_select(ctx->port, ctx->fd, ERL_DRV_WRITE, 1); + ctx->is_sending = 1; + } + } + else if (n < (int)len) { + driver_enq(ctx->port, buf+n, len+n); + driver_select(ctx->port, ctx->fd, ERL_DRV_WRITE, 1); + ctx->is_sending = 1; + } + else { + DEBUGF("hci_drv: wrote %d bytes", n); + } + } +} + +// hci socket triggered process data +static void hci_drv_ready_input(ErlDrvData d, ErlDrvEvent event) +{ + hci_drv_ctx_t* ctx = (hci_drv_ctx_t*) d; + uint8_t buf[HCI_MAX_EVENT_SIZE]; + int n; + (void) event; + + if ((n = read(INT_EVENT(ctx->fd), buf, sizeof(buf))) < 0) { + if ((errno == EAGAIN) || (errno == EINTR)) + return; + DEBUGF("hci_drv_ready_input: read error %d, %s", + errno, strerror(errno)); + return; + } + DEBUGF("hci_drv_ready_input: got %d bytes", n); + if (n > 0) + driver_output(ctx->port, (char*) buf, n); +} + +static void hci_drv_ready_output(ErlDrvData d, ErlDrvEvent event) +{ + hci_drv_ctx_t* ctx = (hci_drv_ctx_t*) d; + int vsize; + SysIOVec* iovp; + int n; + + DEBUGF("hci_drv_ready_output called"); + + if (ctx->fd != event) { + DEBUGF("hci_drv_ready_output bad event"); + return; + } + + if ((iovp = driver_peekq(ctx->port, &vsize)) == NULL) { + driver_select(ctx->port, ctx->fd, ERL_DRV_WRITE, 0); + ctx->is_sending = 0; + return; + } + vsize = vsize > MAX_VSIZE ? MAX_VSIZE : vsize; + + DEBUGF("hci_drv_ready_output: try send vsize=%d", vsize); + n = writev(INT_EVENT(ctx->fd), (const struct iovec*) iovp, vsize); + if (n < 0) { + if ((errno == EAGAIN) || (errno == ENOBUFS)) + return; + ERRORF("write error=%s", strerror(errno)); + return; + } + DEBUGF("hci_drv_ready_output: sent %d bytes", n); + if (driver_deq(ctx->port, n) == 0) { + driver_select(ctx->port, ctx->fd, ERL_DRV_WRITE, 0); + ctx->is_sending = 0; + } +} + + +static ErlDrvSSizeT hci_drv_ctl(ErlDrvData d,unsigned int cmd,char* buf0, + ErlDrvSizeT len,char** rbuf,ErlDrvSizeT rsize) +{ + uint8_t* buf = (uint8_t*) buf0; + hci_drv_ctx_t* ctx = (hci_drv_ctx_t*) d; + + DEBUGF("hci_drv_ctl cmd=%d", cmd); + + switch(cmd) { + case CMD_ACTIVE: { + int active; + + if (len != 4) + goto badarg; + active = get_int32(buf); + if (active) { + if (!ctx->is_selecting) { + driver_select(ctx->port, ctx->fd, ERL_DRV_READ, 1); + DEBUGF("selecting %d for read\n", ctx->fd); + } + ctx->is_selecting = 1; + ctx->active = active; + } + else { + if (ctx->is_selecting) { + driver_select(ctx->port, ctx->fd, ERL_DRV_READ, 0); + DEBUGF("deselecting %d for read\n", ctx->fd); + } + ctx->is_selecting = 0; + ctx->active = 0; + } + goto ok; + break; + } + + case CMD_DEBUG: { + if (len != 4) + goto badarg; + debug_level = get_int32(buf); + goto ok; + } + + case CMD_BIND: { + struct sockaddr_hci a; + int dev_id; + + if (len != 4) + goto badarg; + if ((dev_id = get_int32(buf)) < 0) { + errno = ENODEV; + goto error; + } + memset(&a, 0, sizeof(a)); + a.hci_family = AF_BLUETOOTH; + a.hci_dev = dev_id; + + if (bind(INT_EVENT(ctx->fd), (struct sockaddr *) &a, sizeof(a)) < 0) + goto error; + ctx->dev_id = dev_id; + goto ok; + } + + case CMD_GETFILTER: { + struct hci_filter filt; + socklen_t flen; + uint8_t lbuf[sizeof(struct hci_filter)]; + uint8_t* lptr; + + flen = sizeof(filt); + if (getsockopt(INT_EVENT(ctx->fd), SOL_HCI, HCI_FILTER, &filt, &flen) < 0) + goto error; + lptr = put_hci_filter(lbuf, &filt); + return ctl_reply(CTL_BIN, lbuf, lptr-lbuf, rbuf, rsize); + } + + case CMD_SETFILTER: { + struct hci_filter filt; + + if (len != 14) + goto badarg; + (void) get_hci_filter(buf, &filt); + if (setsockopt(INT_EVENT(ctx->fd), SOL_HCI, HCI_FILTER, &filt, + sizeof(filt)) < 0) + goto error; + goto ok; + } + + case CMD_HCIDEVUP: { + int dev_id; + if (len != 4) + goto badarg; + dev_id = get_int32(buf); + if (ioctl(INT_EVENT(ctx->fd), HCIDEVUP, dev_id) < 0) + goto error; + goto ok; + } + + case CMD_HCIDEVDOWN: { + int dev_id; + if (len != 4) + goto badarg; + dev_id = get_int32(buf); + if (ioctl(INT_EVENT(ctx->fd), HCIDEVDOWN, dev_id) < 0) + goto error; + goto ok; + } + + case CMD_HCIDEVRESET: { + int dev_id; + if (len != 4) + goto badarg; + dev_id = get_int32(buf); + if (ioctl(INT_EVENT(ctx->fd), HCIDEVRESET, dev_id) < 0) + goto error; + goto ok; + } + + case CMD_HCIDEVRESTAT: { + int dev_id; + if (len != 4) + goto badarg; + dev_id = get_int32(buf); + if (ioctl(INT_EVENT(ctx->fd), HCIDEVRESTAT, dev_id) < 0) + goto error; + goto ok; + } + + case CMD_HCIGETDEVLIST: { + struct hci_dev_list_req *dl; + size_t sz = HCI_MAX_DEV*sizeof(struct hci_dev_req) + + sizeof(struct hci_dev_list_req); + uint8_t lbuf[HCI_MAX_DEV*sizeof(struct hci_dev_req)]; + uint8_t* lptr; + int i; + + if ((dl = hci_drv_realloc_buffer(ctx, sz)) == NULL) + goto error; + memset(dl, 0, sz); + dl->dev_num = HCI_MAX_DEV; + + if (ioctl(INT_EVENT(ctx->fd), HCIGETDEVLIST, (void *) dl) < 0) + goto error; + + lptr = lbuf; + for (i = 0; i < dl->dev_num; i++) { + put_uint16(lptr, dl->dev_req[i].dev_id); + lptr += sizeof(dl->dev_req[i].dev_id); + put_uint32(lptr, dl->dev_req[i].dev_opt); + lptr += sizeof(dl->dev_req[i].dev_opt); + } + return ctl_reply(CTL_BIN, lbuf, lptr-lbuf, rbuf, rsize); + } + + case CMD_HCIGETDEVINFO: { + size_t sz = sizeof(struct hci_dev_info); + uint8_t lbuf[sizeof(struct hci_dev_info)]; + uint8_t* lptr; + struct hci_dev_info* di; + int dev_id; + + if (len != 4) + goto badarg; + dev_id = get_int32(buf); + + if ((di = hci_drv_realloc_buffer(ctx, sz)) == NULL) + goto error; + memset(di, 0, sz); + + di->dev_id = dev_id; + if (ioctl(INT_EVENT(ctx->fd), HCIGETDEVINFO, (void *) di) < 0) + goto error; + lptr = put_hci_dev_info(lbuf, di); + return ctl_reply(CTL_BIN, lbuf, lptr-lbuf, rbuf, rsize); + } + + case CMD_HCIGETCONNLIST: { + struct hci_conn_list_req *cl; + size_t sz = sizeof(struct hci_conn_list_req) + + MAX_CONN*sizeof(struct hci_conn_info); + uint8_t lbuf[MAX_CONN*sizeof(struct hci_conn_info)]; + uint8_t* lptr; + int dev_id; + int i; + + if (len != 4) + goto badarg; + dev_id = get_int32(buf); + + if ((cl = hci_drv_realloc_buffer(ctx, sz)) == NULL) + goto error; + memset(cl, 0, sz); + cl->dev_id = dev_id; + cl->conn_num = MAX_CONN; + + if (ioctl(INT_EVENT(ctx->fd), HCIGETCONNLIST, (void *)cl) < 0) + goto error; + + lptr = lbuf; + for (i=0; i< cl->conn_num; i++) + lptr = put_hci_conn_info(lptr, &cl->conn_info[i]); + return ctl_reply(CTL_BIN, lbuf, lptr-lbuf, rbuf, rsize); + } + + case CMD_HCIGETCONNINFO: { + struct hci_conn_info_req *ci; + size_t sz = sizeof(struct hci_conn_info_req) + + sizeof(struct hci_conn_info); + uint8_t lbuf[sizeof(struct hci_conn_info)]; + uint8_t* lptr; + + if (len != 7) + goto badarg; + + if ((ci = hci_drv_realloc_buffer(ctx, sz)) == NULL) + goto error; + memset(ci, 0, sz); + memcpy(&ci->bdaddr, buf, sizeof(bdaddr_t)); + ci->type = buf[6]; + + if (ioctl(INT_EVENT(ctx->fd), HCIGETCONNINFO, (void *)ci) < 0) + goto error; + lptr = put_hci_conn_info(lbuf, &ci->conn_info[0]); + return ctl_reply(CTL_BIN, lbuf, lptr-lbuf, rbuf, rsize); + } + + case CMD_HCIGETAUTHINFO: { + struct hci_auth_info_req ai; + if (len != 6) + goto badarg; + memcpy(&ai.bdaddr, buf, sizeof(ai.bdaddr)); + if (ioctl(INT_EVENT(ctx->fd), HCIGETAUTHINFO, (void *)&ai) < 0) + goto error; + return ctl_reply(CTL_BIN, &ai.type, 1, rbuf, rsize); + } + + case CMD_HCISETRAW: { // guessing... + int dev_id; + if (len != 4) + goto badarg; + dev_id = get_int32(buf); + if (ioctl(INT_EVENT(ctx->fd), HCISETRAW, dev_id) < 0) + goto error; + goto ok; + } + + case CMD_HCISETSCAN: { + struct hci_dev_req dr; + if (len != 8) + goto badarg; + dr.dev_id = get_int32(buf); + dr.dev_opt = get_uint32(buf+4); + if (ioctl(INT_EVENT(ctx->fd), HCISETSCAN, (void*) &dr) < 0) + goto error; + goto ok; + } + + case CMD_HCISETAUTH: { + struct hci_dev_req dr; + if (len != 5) + goto badarg; + dr.dev_id = get_int32(buf); + dr.dev_opt = buf[4] ? AUTH_ENABLED : AUTH_DISABLED; + if (ioctl(INT_EVENT(ctx->fd), HCISETAUTH, (void*) &dr) < 0) + goto error; + goto ok; + } + + case CMD_HCISETENCRYPT: { + struct hci_dev_req dr; + if (len != 5) + goto badarg; + dr.dev_id = get_int32(buf); + dr.dev_opt = buf[4] ? ENCRYPT_P2P : ENCRYPT_DISABLED; + if (ioctl(INT_EVENT(ctx->fd), HCISETAUTH, (void*) &dr) < 0) + goto error; + goto ok; + } + + case CMD_HCISETPTYPE: { + struct hci_dev_req dr; + if (len != 8) + goto badarg; + dr.dev_id = get_int32(buf); + dr.dev_opt = get_uint32(buf+4); + if (ioctl(INT_EVENT(ctx->fd), HCISETPTYPE, (void*) &dr) < 0) + goto error; + goto ok; + } + + case CMD_HCISETLINKPOL: { + struct hci_dev_req dr; + if (len != 8) + goto badarg; + dr.dev_id = get_int32(buf); + dr.dev_opt = get_uint32(buf+4); + if (ioctl(INT_EVENT(ctx->fd), HCISETLINKPOL, (void*) &dr) < 0) + goto error; + goto ok; + } + + case CMD_HCISETLINKMODE: { + struct hci_dev_req dr; + if (len != 8) + goto badarg; + dr.dev_id = get_int32(buf); + dr.dev_opt = get_uint32(buf+4); + if (ioctl(INT_EVENT(ctx->fd), HCISETLINKMODE, (void*) &dr) < 0) + goto error; + goto ok; + } + + case CMD_HCISETACLMTU: { + struct hci_dev_req dr; + uint16_t mtu, mpkt; + + if (len != 8) + goto badarg; + dr.dev_id = get_int32(buf); + mtu = get_uint16(buf+4); + mpkt = get_uint16(buf+6); + dr.dev_opt = htobl(htobs(mpkt) | (htobs(mtu) << 16)); + if (ioctl(INT_EVENT(ctx->fd), HCISETACLMTU, (void*) &dr) < 0) + goto error; + goto ok; + } + + case CMD_HCISETSCOMTU: { + struct hci_dev_req dr; + uint16_t mtu, mpkt; + + if (len != 8) + goto badarg; + dr.dev_id = get_int32(buf); + mtu = get_uint16(buf+4); + mpkt = get_uint16(buf+6); + dr.dev_opt = htobl(htobs(mpkt) | (htobs(mtu) << 16)); + if (ioctl(INT_EVENT(ctx->fd), HCISETSCOMTU, (void*) &dr) < 0) + goto error; + goto ok; + } + + case CMD_HCIBLOCKADDR: { // bound + bdaddr_t bdaddr; + if (len != 6) + goto badarg; + memcpy(&bdaddr, buf, 6); + if (ioctl(INT_EVENT(ctx->fd), HCIBLOCKADDR, (void*) &bdaddr) < 0) + goto error; + goto ok; + } + + case CMD_HCIUNBLOCKADDR: { // bound, bdaddr={0,0,0,0,0,0} => all + bdaddr_t bdaddr; + if (len != 6) + goto badarg; + memcpy(&bdaddr, buf, 6); + if (ioctl(INT_EVENT(ctx->fd), HCIUNBLOCKADDR, (void*) &bdaddr) < 0) + goto error; + goto ok; + } + + case CMD_HCIINQUIRY: { + // blocking! must be in a thread or use the async api + uint8_t ibuf[sizeof(struct hci_inquiry_req)+sizeof(inquiry_info)*255]; + struct hci_inquiry_req* ir; + + if (len != 9) + goto badarg; + ir = (struct hci_inquiry_req *) ibuf; + ir->length = get_uint8(buf); + ir->num_rsp = get_uint8(buf+1); + ir->lap[0] = get_uint8(buf+2); + ir->lap[1] = get_uint8(buf+3); + ir->lap[2] = get_uint8(buf+4); + ir->flags = get_uint32(buf+5); + ir->dev_id = ctx->dev_id; + + if (ioctl(INT_EVENT(ctx->fd), HCIINQUIRY, (unsigned long) ibuf) < 0) + goto error; + + // return size is sizoef(inquiry_info) * ir->num_rsp + return ctl_reply(CTL_BIN, ibuf + sizeof(struct hci_inquiry_req), + sizeof(inquiry_info) * ir->num_rsp, rbuf, rsize); + } + + default: + return -1; + } + +ok: + return ctl_reply(CTL_OK, NULL, 0, rbuf, rsize); +badarg: + errno = EINVAL; +error: { + char* err_str = erl_errno_id(errno); + return ctl_reply(CTL_ERR, err_str, strlen(err_str), rbuf, rsize); +} +} + + +static void hci_drv_timeout(ErlDrvData d) +{ + (void) d; + fprintf(stderr, "hci_drv_timeout called!!!\r\n"); +} + +static void hci_drv_stop_select(ErlDrvEvent event, void* arg) +{ + (void) arg; + DEBUGF("eth_drv: stop_select event=%d", INT_EVENT(event)); + close(INT_EVENT(event)); +} + + +static ErlDrvData hci_drv_start(ErlDrvPort port, char* command) +{ + (void) command; + hci_drv_ctx_t* ctx; + int fd; + int protocol; + char* ptr; + // char* arg; + + ptr = command; + while(*ptr && (*ptr != ' ')) ptr++; // skip command + while(*ptr && (*ptr == ' ')) ptr++; // and blanks + // arg = ptr; + // while(*ptr && (*ptr >= '0') && (*ptr <= '9')) ptr++; + // if ((arg == ptr) || (*ptr != '\0')) { + // errno = EINVAL; + // return ERL_DRV_ERROR_ERRNO; + // } + // protocol = atoi(arg); + protocol = BTPROTO_HCI; + if ((fd = socket(PF_BLUETOOTH, + SOCK_NONBLOCK| SOCK_RAW | SOCK_CLOEXEC, + protocol)) < 0) + return ERL_DRV_ERROR_ERRNO; + + // flags = fcntl(fd, F_GETFL, 0); + // fcntl(fd, F_SETFL, flags | O_NONBLOCK); + + if (!(ctx = driver_alloc(sizeof(hci_drv_ctx_t)))) + return ERL_DRV_ERROR_ERRNO; + memset(ctx, 0, sizeof(hci_drv_ctx_t)); + ctx->port = port; + ctx->dport = driver_mk_port(port); + ctx->owner = driver_caller(port); + ctx->protocol = protocol; + ctx->fd = (ErlDrvEvent)((long)fd); + ctx->active = 0; + ctx->is_selecting = 0; + ctx->is_sending = 0; + + // create i/o buffer + hci_drv_realloc_buffer(ctx, MIN_HCI_BUFSIZE); +#ifdef PORT_CONTROL_BINARY + set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY); +#endif + return (ErlDrvData) ctx; +} + +DRIVER_INIT(hci_drv) +{ + ErlDrvEntry* ptr = &hci_drv_entry; + + ptr->driver_name = "hci_drv"; + ptr->init = hci_drv_init; + ptr->start = hci_drv_start; + ptr->stop = hci_drv_stop; + ptr->output = hci_drv_output; + ptr->ready_input = hci_drv_ready_input; + ptr->ready_output = hci_drv_ready_output; + ptr->finish = hci_drv_finish; + ptr->control = hci_drv_ctl; + ptr->timeout = hci_drv_timeout; +#if 0 + ptr->outputv = hci_drv_outputv; +#endif + ptr->ready_async = 0; + ptr->flush = 0; + ptr->call = 0; + ptr->event = 0; + ptr->extended_marker = ERL_DRV_EXTENDED_MARKER; + ptr->major_version = ERL_DRV_EXTENDED_MAJOR_VERSION; + ptr->minor_version = ERL_DRV_EXTENDED_MINOR_VERSION; + ptr->driver_flags = ERL_DRV_FLAG_USE_PORT_LOCKING; + ptr->process_exit = 0; + ptr->stop_select = hci_drv_stop_select; + + return (ErlDrvEntry*) ptr; +} diff --git a/deps/bt/ebin/.gitignore b/deps/bt/ebin/.gitignore new file mode 100644 index 0000000..120fe3a --- /dev/null +++ b/deps/bt/ebin/.gitignore @@ -0,0 +1,2 @@ +*.beam +*.app diff --git a/deps/bt/include/bt.hrl b/deps/bt/include/bt.hrl new file mode 100644 index 0000000..4e8878a --- /dev/null +++ b/deps/bt/include/bt.hrl @@ -0,0 +1,67 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- + +%% +%% Some none-guard macros +%% +-ifndef(BT_HRL). +-define(BT_HRL, true). + +-include("uuid.hrl"). + +%% Bluetooth base uuid = ?BT_UUID(0) +-define(BT_UUID32(N), + ?UUID(N,16#0000,16#1000,16#8000,16#00805f9b34fb)). + +%% Speical UUID match for 16 bit BT uuid +-define(BT_UUID16(N), + <<16#0000:16,N:16,16#0000:16,16#1000:16,16#8000:16,16#00805f9b34fb:48>>). + + +-define(is_bt_address(A), + is_tuple((A)), size((A)) == 6, + ((element(1,(A)) bor element(2,(A)) bor element(3,(A)) bor + element(4,(A)) bor element(5,(A)) bor element(6,(A))) band + -16#100 == 0)). + +-define(never, {{0,0,0},{0,0,0}}). + +-define(L2CAP_PSM_None, 16#0000). + +%% Range < 0x1000 reserved. +-define(L2CAP_PSM_ReservedStart, 16#0001). +-define(L2CAP_PSM_ReservedEnd, 16#1000). + +-define(L2CAP_PSM_SDP, 16#0001). +-define(L2CAP_PSM_RFCOMM, 16#0003). +-define(L2CAP_PSM_TCS_BIN, 16#0005). %% Telephony Control Specifictation / TCS Binary + +-define(L2CAP_PSM_TCS_BIN_Cordless, 16#0007). %% Telephony Control Specifictation / TCS Binary +-define(L2CAP_PSM_BNEP, 16#000F). %% Bluetooth Network Encapsulation Protocol +-define(L2CAP_PSM_HIDControl, 16#0011). %% HID profile - control interface +-define(L2CAP_PSM_HIDInterrupt, 16#0013). %% HID profile - interrupt interface +-define(L2CAP_PSM_AVCTP, 16#0017). %% Audio/Video Control Transport Protocol +-define(L2CAP_PSM_AVDTP, 16#0019). %% Audio/Video Distribution Transport Protocol +-define(L2CAP_PSM_UID_C_Plane, 16#001D). %% Unrestricted Digital Information Profile (UDI) + +%% Range 0x1001-0xFFFF dynamically assigned. +-define(L2CAP_PSM_DynamicStart, 16#1001). +-define(L2CAP_PSM_DynamicEnd, 16#FFFF). + +-endif. + + diff --git a/deps/bt/include/hci_drv.hrl b/deps/bt/include/hci_drv.hrl new file mode 100644 index 0000000..c13c50c --- /dev/null +++ b/deps/bt/include/hci_drv.hrl @@ -0,0 +1,260 @@ + +-ifndef(__HCI_DRV_HRL__). +-define(__HCI_DRV_HRL__, true). + +-type bdaddr_t() :: binary(). %% 6 bytes +-type uint8_t() :: 0..16#ff. +-type uint16_t() :: 0..16#ffff. +-type uint32_t() :: 0..16#ffffffff. +-type uint64_t() :: 0..16#ffffffffffffffff. + +%% HCI Packet types +-define(HCI_COMMAND_PKT, 16#01). +-define(HCI_ACLDATA_PKT, 16#02). +-define(HCI_SCODATA_PKT, 16#03). +-define(HCI_EVENT_PKT, 16#04). +-define(HCI_VENDOR_PKT, 16#ff). + +%% Command opcode pack/unpack +-define(cmd_opcode_pack(OGF, OCF), (((OCF) band 16#03ff)bor((OGF) bsl 10))). +-define(cmd_opcode_ogf(OP), ((OP) bsr 10)). +-define(cmd_opcode_ocf(OP), ((OP) band 16#03ff)). + +%% ACL handle and flags pack/unpack +-define(acl_handle_pack(H, F), (((H) band 16#0fff) bor ((F) bsl 12))). +-define(acl_handle(H), ((H) band 16#0fff)). +-define(acl_flags(H), ((H) bsr 12)). + +-record(hci_dev_stats, + { + err_rx::uint32_t(), + err_tx::uint32_t(), + cmd_tx::uint32_t(), + evt_rx::uint32_t(), + acl_tx::uint32_t(), + acl_rx::uint32_t(), + sco_tx::uint32_t(), + sco_rx::uint32_t(), + byte_rx::uint32_t(), + byte_tx::uint32_t() + }). + +%% HCI device flags +-define(HCI_UP, 0). +-define(HCI_INIT, 1). +-define(HCI_RUNNING, 2). +-define(HCI_PSCAN, 3). +-define(HCI_ISCAN, 4). +-define(HCI_AUTH, 5). +-define(HCI_ENCRYPT, 6). +-define(HCI_INQUIRY, 7). +-define(HCI_RAW, 8). + +%% LE address type +-define(LE_PUBLIC_ADDRESS, 16#00). +-define(LE_RANDOM_ADDRESS, 16#01). + +%% HCI Packet types +-define(HCI_2DH1, 16#0002). +-define(HCI_3DH1, 16#0004). +-define(HCI_DM1, 16#0008). +-define(HCI_DH1, 16#0010). +-define(HCI_2DH3, 16#0100). +-define(HCI_3DH3, 16#0200). +-define(HCI_DM3, 16#0400). +-define(HCI_DH3, 16#0800). +-define(HCI_2DH5, 16#1000). +-define(HCI_3DH5, 16#2000). +-define(HCI_DM5, 16#4000). +-define(HCI_DH5, 16#8000). + +-define(HCI_HV1, 16#0020). +-define(HCI_HV2, 16#0040). +-define(HCI_HV3, 16#0080). + +-define(HCI_EV3, 16#0008). +-define(HCI_EV4, 16#0010). +-define(HCI_EV5, 16#0020). +-define(HCI_2EV3, 16#0040). +-define(HCI_3EV3, 16#0080). +-define(HCI_2EV5, 16#0100). +-define(HCI_3EV5, 16#0200). + +-define(SCO_PTYPE_MASK, (?HCI_HV1 bor ?HCI_HV2 bor ?HCI_HV3)). +-define(ACL_PTYPE_MASK, (?HCI_DM1 bor ?HCI_DH1 bor ?HCI_DM3 bor ?HCI_DH3 bor ?HCI_DM5 bor ?HCI_DH5)). + +%% HCI controller types +-define(HCI_BREDR, 16#00). +-define(HCI_AMP, 16#01). + + +%% HCI bus types +-define(HCI_VIRTUAL, 0). +-define(HCI_USB, 1). +-define(HCI_PCCARD, 2). +-define(HCI_UART, 3). +-define(HCI_RS232, 4). +-define(HCI_PCI, 5). +-define(HCI_SDIO, 6). + +%% Link policies +-define(HCI_LP_RSWITCH, 16#0001). +-define(HCI_LP_HOLD, 16#0002). +-define(HCI_LP_SNIFF, 16#0004). +-define(HCI_LP_PARK, 16#0008). + +%% Link mode +-define(HCI_LM_ACCEPT, 16#8000). +-define(HCI_LM_MASTER, 16#0001). +-define(HCI_LM_AUTH, 16#0002). +-define(HCI_LM_ENCRYPT, 16#0004). +-define(HCI_LM_TRUSTED, 16#0008). +-define(HCI_LM_RELIABLE, 16#0010). +-define(HCI_LM_SECURE, 16#0020). + +%% Link Key types +-define(HCI_LK_COMBINATION, 16#00). +-define(HCI_LK_LOCAL_UNIT, 16#01). +-define(HCI_LK_REMOTE_UNIT, 16#02). +-define(HCI_LK_DEBUG_COMBINATION, 16#03). +-define(HCI_LK_UNAUTH_COMBINATION, 16#04). +-define(HCI_LK_AUTH_COMBINATION, 16#05). +-define(HCI_LK_CHANGED_COMBINATION, 16#06). +-define(HCI_LK_INVALID, 16#FF). + + +%% ACL flags +-define(ACL_START_NO_FLUSH, 16#00). +-define(ACL_CONT, 16#01). +-define(ACL_START, 16#02). +-define(ACL_ACTIVE_BCAST, 16#04). +-define(ACL_PICO_BCAST, 16#08). + +%% Baseband links +-define(SCO_LINK, 16#00). +-define(ACL_LINK, 16#01). +-define(ESCO_LINK, 16#02). + +%% LMP features +-define(LMP_3SLOT, 16#01). +-define(LMP_5SLOT, 16#02). +-define(LMP_ENCRYPT, 16#04). +-define(LMP_SOFFSET, 16#08). +-define(LMP_TACCURACY, 16#10). +-define(LMP_RSWITCH, 16#20). +-define(LMP_HOLD, 16#40). +-define(LMP_SNIFF, 16#80). + +-define(LMP_PARK, 16#01). +-define(LMP_RSSI, 16#02). +-define(LMP_QUALITY, 16#04). +-define(LMP_SCO, 16#08). +-define(LMP_HV2, 16#10). +-define(LMP_HV3, 16#20). +-define(LMP_ULAW, 16#40). +-define(LMP_ALAW, 16#80). + +-define(LMP_CVSD, 16#01). +-define(LMP_PSCHEME, 16#02). +-define(LMP_PCONTROL, 16#04). +-define(LMP_TRSP_SCO, 16#08). +-define(LMP_BCAST_ENC, 16#80). + +-define(LMP_EDR_ACL_2M, 16#02). +-define(LMP_EDR_ACL_3M, 16#04). +-define(LMP_ENH_ISCAN, 16#08). +-define(LMP_ILACE_ISCAN, 16#10). +-define(LMP_ILACE_PSCAN, 16#20). +-define(LMP_RSSI_INQ, 16#40). +-define(LMP_ESCO, 16#80). + +-define(LMP_EV4, 16#01). +-define(LMP_EV5, 16#02). +-define(LMP_AFH_CAP_SLV, 16#08). +-define(LMP_AFH_CLS_SLV, 16#10). +-define(LMP_NO_BREDR, 16#20). +-define(LMP_LE, 16#40). +-define(LMP_EDR_3SLOT, 16#80). + +-define(LMP_EDR_5SLOT, 16#01). +-define(LMP_SNIFF_SUBR, 16#02). +-define(LMP_PAUSE_ENC, 16#04). +-define(LMP_AFH_CAP_MST, 16#08). +-define(LMP_AFH_CLS_MST, 16#10). +-define(LMP_EDR_ESCO_2M, 16#20). +-define(LMP_EDR_ESCO_3M, 16#40). +-define(LMP_EDR_3S_ESCO, 16#80). + +-define(LMP_EXT_INQ, 16#01). +-define(LMP_LE_BREDR, 16#02). +-define(LMP_SIMPLE_PAIR, 16#08). +-define(LMP_ENCAPS_PDU, 16#10). +-define(LMP_ERR_DAT_REP, 16#20). +-define(LMP_NFLUSH_PKTS, 16#40). + +-define(LMP_LSTO, 16#01). +-define(LMP_INQ_TX_PWR, 16#02). +-define(LMP_EPC, 16#04). +-define(LMP_EXT_FEAT, 16#80). + +%% Extended LMP features +-define(LMP_HOST_SSP, 16#01). +-define(LMP_HOST_LE, 16#02). +-define(LMP_HOST_LE_BREDR, 16#04). + +%% Scan flags used by HCISETSCAN/WRITE_SCAN_ENABLE +-define(SCAN_DISABLED, 16#00). +-define(SCAN_INQUIRY, 16#01). +-define(SCAN_PAGE, 16#02). + + +-record(hci_dev_info, + { + dev_id::uint16_t(), + name::binary(), %% 8 bytes + bdaddr::bdaddr_t(), %% 6 bytes [2 pad bytes] + flags::uint32_t(), + type::uint8_t(), + features::binary(), %% 64 bits + + pkt_type::uint32_t(), + link_policy::uint32_t(), + link_mode::uint32_t(), + acl_mtu::uint16_t(), + acl_pkts::uint16_t(), + sco_mtu::uint16_t(), + sco_pkts::uint16_t(), + stat::#hci_dev_stats{} + }). + +-record(hci_conn_info, + { + handle::uint16_t(), + bdaddr::bdaddr_t(), + type::uint8_t(), + out::uint8_t(), + state::uint16_t(), + link_mode::uint32_t() + }). + +-record(hci_filter, + { + type_mask :: uint32_t(), + event_mask :: uint64_t(), %% encoded as 2*uint32_t() + opcode :: uint16_t() + }). + + +-define(HCI_FLT_TYPE_BITS, 31). +-define(HCI_FLT_EVENT_BITS, 63). +-define(HCI_FLT_OGF_BITS, 63). +-define(HCI_FLT_OCF_BITS, 127). + +-type hci_socket_t() :: port(). +-type hci_devid_t() :: integer(). +-type posix() :: atom(). +-type level_t() :: boolean() | debug | info | notice | warning | error | + critical | alert | emergency | none. + + +-endif. diff --git a/deps/bt/include/obex.hrl b/deps/bt/include/obex.hrl new file mode 100644 index 0000000..d92019d --- /dev/null +++ b/deps/bt/include/obex.hrl @@ -0,0 +1,146 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- + +-ifndef(OBEX_HRL). +-define(OBEX_HRL, true). + +-define(OBEX_VERSION, 16#10). + +%% 0x - Request codes +-define(OBEX_CMD_CONNECT, 16#00). +-define(OBEX_CMD_DISCONNECT, 16#01). +-define(OBEX_CMD_PUT, 16#02). +-define(OBEX_CMD_GET, 16#03). +-define(OBEX_CMD_COMMAND, 16#04). +-define(OBEX_CMD_SETPATH, 16#05). + +%% 1x +-define(OBEX_RSP_CONTINUE, 16#10). +-define(OBEX_RSP_SWITCH_PRO, 16#11). + +%% 2x +-define(OBEX_RSP_SUCCESS, 16#20). +-define(OBEX_RSP_CREATED, 16#21). +-define(OBEX_RSP_ACCEPTED, 16#22). +-define(OBEX_RSP_NON_AUTHORITATIVE, 16#23). +-define(OBEX_RSP_NO_CONTENT, 16#24). +-define(OBEX_RSP_RESET_CONTENT, 16#25). +-define(OBEX_RSP_PARTIAL_CONTENT, 16#26). + +%% 3x +-define(OBEX_RSP_MULTIPLE_CHOICES, 16#30). +-define(OBEX_RSP_MOVED_PERMANENTLY, 16#31). +-define(OBEX_RSP_MOVED_TEMPORARILY, 16#32). +-define(OBEX_RSP_SEE_OTHER, 16#33). +-define(OBEX_RSP_NOT_MODIFIED, 16#34). +-define(OBEX_RSP_USE_PROXY, 16#35). + +%% 4x +-define(OBEX_RSP_BAD_REQUEST, 16#40). +-define(OBEX_RSP_UNAUTHORIZED, 16#41). +-define(OBEX_RSP_PAYMENT_REQUIRED, 16#42). +-define(OBEX_RSP_FORBIDDEN, 16#43). +-define(OBEX_RSP_NOT_FOUND, 16#44). +-define(OBEX_RSP_METHOD_NOT_ALLOWED, 16#45). +-define(OBEX_RSP_NOT_ACCEPTABLE, 16#46). +-define(OBEX_RSP_PROXY_AUTH, 16#47). +-define(OBEX_RSP_REQUEST_TIMEOUT, 16#48). +-define(OBEX_RSP_CONFLICT, 16#49). +-define(OBEX_RSP_GONE, 16#4A). +-define(OBEX_RSP_LENGTH_REQUIRED, 16#4B). +-define(OBEX_RSP_PRECONDITION_FAILED, 16#4C). +-define(OBEX_RSP_ENTITY_TOO_LARGE, 16#4D). +-define(OBEX_RSP_URL_TOO_LARGE, 16#4E). +-define(OBEX_RSP_UNSUPPORTED_MEDIA, 16#4F). + +%% 5x +-define(OBEX_RSP_INTERNAL_SERVER_ERROR, 16#50). +-define(OBEX_RSP_NOT_IMPLEMENTED, 16#51). +-define(OBEX_RSP_BAD_GATEWAY, 16#52). +-define(OBEX_RSP_SERVICE_UNAVAILABLE, 16#53). +-define(OBEX_RSP_GATEWAY_TIMEOUT, 16#54). +-define(OBEX_RSP_HTTP_VERSION_NOT_SUPPORTED, 16#55). + +%% 6x +-define(OBEX_RSP_DATABASE_FULL, 16#60). +-define(OBEX_RSP_DATABASE_LOCKED, 16#61). + +%% 7x - command/response +-define(OBEX_CMD_ABORT, 16#7f). + +-define(TARGET_FOLDER_BROWSING, + ?UUID(16#F9EC7BC4,16#953C,16#11D2,16#984E,16#525400DC9E09)). +-define(TARGET_SYNCML_DM, "SYNCML-DM"). +-define(TARGET_SYNCML_SYNC, "SYNCML-SYNC"). + +%% NOKIA SYNCML-SYNC ???? +-define(UUID_SYNCML_SYNC, + ?UUID(16#00005601,16#0000,16#1000,16#8000,16#0002EE000002)). + +%% Min, Max and default transport MTU +-define(OBEX_DEFAULT_MTU, 1024). +-define(OBEX_MINIMUM_MTU, 255). +-define(OBEX_MAXIMUM_MTU, 32768). + +-define(OBEX_HDR_MASK, 16#C0). +-define(OBEX_V0, 2#00). +-define(OBEX_Vn, 2#01). +-define(OBEX_I1, 2#10). +-define(OBEX_I4, 2#11). + +%% Standard headers + +%% null terminated values +-define(OBEX_HDR_NAME, 16#01). %% Name of the object. +-define(OBEX_HDR_DESCRIPTION, 16#05). %% Description of object + +-define(OBEX_HDR_RESERVED_RANGE_START, 16#10). +-define(OBEX_HDR_RESERVED_RANGE_END, 16#2F). + +-define(OBEX_HDR_USER_DEFINED_RANGE_START, 16#30). +-define(OBEX_HDR_USER_DEFINED_RANGE_END, 16#3F). + +%% bytes sequence values +-define(OBEX_HDR_TYPE, 16#42). %% Type of the object +-define(OBEX_HDR_TIME, 16#44). %% Last modification time of (ISO8601) +-define(OBEX_HDR_TARGET, 16#46). %% Identifies the target for the object +-define(OBEX_HDR_HTTP, 16#47). %% Data part of the object +-define(OBEX_HDR_BODY, 16#48). %% Data part of the object +-define(OBEX_HDR_BODY_END, 16#49). %% Last data part of the object +-define(OBEX_HDR_WHO, 16#4a). %% Identifies the sender of the object +-define(OBEX_HDR_APPARAM, 16#4c). %% Application parameters +-define(OBEX_HDR_AUTHCHAL, 16#4d). %% Authentication challenge +-define(OBEX_HDR_AUTHRESP, 16#4e). %% Authentication response +-define(OBEX_HDR_OBJCLASS, 16#4f). %% OBEX Object class of object + +%% 4 bytes values +-define(OBEX_HDR_COUNT, 16#c0). %% Number of objects (used by connect) +-define(OBEX_HDR_CONNECTION_ID, 16#cb). %% Connection identifier +-define(OBEX_HDR_LENGTH, 16#c3). %% Total lenght of object +-define(OBEX_HDR_TIME2, 16#C4). %% Deprecated use HDR_TIME instead + +%% vCard charsets +-define(OBEX_CHARSET_STRING_ISO88591, "CHARSET=ISO-8859-1"). +-define(OBEX_CHARSET_STRING_UTF8, "UTF-8"). + +%% vEvent encodings +-define(OBEX_ENCODING_STRING_QUOTED_PRINTABLE, "QUOTED-PRINTABLE"). +-define(OBEX_ENCODING_STRING_BASE64, "BASE-64"). +-define(OBEX_ENCODING_STRING_8BIT, "8BIT"). + + +-endif. diff --git a/deps/bt/include/sdp.hrl b/deps/bt/include/sdp.hrl new file mode 100644 index 0000000..830c2d7 --- /dev/null +++ b/deps/bt/include/sdp.hrl @@ -0,0 +1,262 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +-ifndef(SDP_HRL). +-define(SDP_HRL, true). + +%% ATTRIBUTES +-define(ATTR_ServiceRecordHandle, 16#0000). +-define(ATTR_ServiceClassIDList, 16#0001). +-define(ATTR_ServiceRecordState, 16#0002). +-define(ATTR_ServiceID, 16#0003). +-define(ATTR_ProtocolDescriptorList, 16#0004). +-define(ATTR_BrowseGroupList, 16#0005). +-define(ATTR_LanguageBaseAttributeIDList, 16#0006). +-define(ATTR_ServiceInfoTimeToLive, 16#0007). +-define(ATTR_ServiceAvailability, 16#0008). +-define(ATTR_BluetoothProfileDescriptorList, 16#0009). +-define(ATTR_DocumentationURL, 16#000A). +-define(ATTR_ClientExecutableURL, 16#000B). +-define(ATTR_IconURL, 16#000C). +-define(ATTR_AdditionalProtocolsDescriptorList, 16#000D). + +%% ATTRIBUTE +-define(ATTR_ServiceName, 16#0000). +-define(ATTR_ServiceDescription, 16#0001). +-define(ATTR_ProviderName, 16#0002). + +%% SDP +-define(ATTR_SDP_VersionNumberList, 16#0200). +-define(ATTR_SDP_ServiceDatabaseState, 16#0201). + +%% PAN +-define(ATTR_PAN_IPSubnet, 16#0200). + +%% BIP +-define(ATTR_BIP_GoepL2capPsm, 16#0200). +-define(ATTR_BIP_SupportedCapabilities, 16#0310). +-define(ATTR_BIP_SupportedFeatures, 16#0311). +-define(ATTR_BIP_SupportedFunctions, 16#0312). +-define(ATTR_BIP_TotalImagingDataCapacity, 16#0313). + +%% AVRCP +-define(ATTR_ACRCP_SupportedFeatures, 16#0311). + +%% BPP - basic printing profile, Direct Printing(1119), ReferencePrinting(1119), +%% DirectPrintingReferenceObjectsService(1120),ReflectedUI(1121),PrintingStatus(1123) + +%% UUID Def's + +%% PROTOCOLS +-define(UUID_SDP, <<16#0001:16>>). +-define(UUID_UDP, <<16#0002:16>>). +-define(UUID_RFCOMM, <<16#0003:16>>). +-define(UUID_TCP, <<16#0004:16>>). +-define(UUID_TCS_BIN, <<16#0005:16>>). +-define(UUID_TCS_AT, <<16#0006:16>>). +-define(UUID_OBEX, <<16#0008:16>>). +-define(UUID_IP, <<16#0009:16>>). +-define(UUID_FTP, <<16#000A:16>>). +-define(UUID_HTTP, <<16#000C:16>>). +-define(UUID_WSP, <<16#000E:16>>). +-define(UUID_BNEP, <<16#000F:16>>). +-define(UUID_UPNP, <<16#0010:16>>). +-define(UUID_HIDP, <<16#0011:16>>). +-define(UUID_HCRP_CTRL, <<16#0012:16>>). +-define(UUID_HCRP_DATA, <<16#0014:16>>). +-define(UUID_HCRP_NOTE, <<16#0016:16>>). +-define(UUID_AVCTP, <<16#0017:16>>). +-define(UUID_AVDTP, <<16#0019:16>>). +-define(UUID_CMPT, <<16#001B:16>>). +-define(UUID_UDI, <<16#001D:16>>). +-define(UUID_MCAP_CTRL, <<16#001E:16>>). +-define(UUID_MCAP_DATA, <<16#001F:16>>). +-define(UUID_L2CAP, <<16#0100:16>>). + +%% SERVICE Classes +-define(UUID_ServiceDiscoveryServer, <<16#1000:16>>). +-define(UUID_BrowseGroupDescriptor, <<16#1001:16>>). +-define(UUID_PublicBrowseGroup, <<16#1002:16>>). +-define(UUID_SerialPort, <<16#1101:16>>). +-define(UUID_LANAccessUsingPPP, <<16#1102:16>>). +-define(UUID_DialupNetworking, <<16#1103:16>>). +-define(UUID_IrMCSync, <<16#1104:16>>). +-define(UUID_OBEXObjectPush, <<16#1105:16>>). +-define(UUID_OBEXFileTransfer, <<16#1106:16>>). +-define(UUID_IrMCSyncCommand, <<16#1107:16>>). +-define(UUID_Headset, <<16#1108:16>>). +-define(UUID_CordlessTelephony, <<16#1109:16>>). +-define(UUID_AudioSource, <<16#110A:16>>). +-define(UUID_AudioSink, <<16#110B:16>>). +-define(UUID_AVRemoteControlTarget, <<16#110C:16>>). +-define(UUID_AdvancedAudioDistribution, <<16#110D:16>>). +-define(UUID_AVRemoteControl, <<16#110E:16>>). +-define(UUID_VideoConferencing, <<16#110F:16>>). %% fixme RemoteControlController? +-define(UUID_Intercom, <<16#1110:16>>). +-define(UUID_Fax, <<16#1111:16>>). +-define(UUID_HeadsetAudioGateway, <<16#1112:16>>). +-define(UUID_WAP, <<16#1113:16>>). +-define(UUID_WAPClient, <<16#1114:16>>). +-define(UUID_PANU, <<16#1115:16>>). +-define(UUID_NAP, <<16#1116:16>>). +-define(UUID_GN, <<16#1117:16>>). +-define(UUID_DirectPrinting, <<16#1118:16>>). +-define(UUID_ReferencePrinting, <<16#1119:16>>). +-define(UUID_Imaging, <<16#111A:16>>). +-define(UUID_ImagingResponder, <<16#111B:16>>). +-define(UUID_ImagingAutomaticArchive, <<16#111C:16>>). +-define(UUID_ImagingReferencedObjects, <<16#111D:16>>). +-define(UUID_Handsfree, <<16#111E:16>>). +-define(UUID_HandsfreeAudioGateway, <<16#111F:16>>). +-define(UUID_DirectPrintingReferenceObjectsService, <<16#1120:16>>). +-define(UUID_ReflectedUI, <<16#1121:16>>). +-define(UUID_BasicPrinting, <<16#1122:16>>). +-define(UUID_PrintingStatus, <<16#1123:16>>). +-define(UUID_HumanInterfaceDeviceService, <<16#1124:16>>). +-define(UUID_HardcopyCableReplacement, <<16#1125:16>>). +-define(UUID_HCR_Print, <<16#1126:16>>). +-define(UUID_HCR_Scan, <<16#1127:16>>). +-define(UUID_CommonISDNAccess, <<16#1128:16>>). +-define(UUID_VideoConferencingGW, <<16#1129:16>>). +-define(UUID_UDI_MT, <<16#112A:16>>). +-define(UUID_UDI_TA, <<16#112B:16>>). +-define(UUID_Audio_Video, <<16#112C:16>>). +-define(UUID_SIM_Access, <<16#112D:16>>). +-define(UUID_PhonebookAccess_PCE, <<16#112E:16>>). %% Phonebook Access Profile +-define(UUID_PhonebookAccess_PSE, <<16#112F:16>>). %%Phonebook Access Profile +-define(UUID_PhonebookAccess, <<16#1130:16>>). %% Phonebook Access Profile +-define(UUID_Headset_HS, <<16#1131:16>>). %% Headset Profile (HSP) +-define(UUID_Message_Access_Server, <<16#1132>>). %% Message Access Profile (MAP) +-define(UUID_Message_Notification_Server, <<16#1133>>). %% Message Access Profile (MAP) +-define(UUID_Message_Access_Profile, <<16#1134>>). %% Message Access Profile (MAP) +-define(UUID_GNSS, <<16#1135>>). %% Global Navigation Satellite System Profile (GNSS) +-define(UUID_GNSS_Server, <<16#1136>>). %% Global Navigation Satellite System Profile (GNSS) +-define(UUID_3D_Display, <<16#1137>>). %% 3D Synchronization Profile (3DSP) +-define(UUID_3D_Glasses, <<16#1138>>). %% 3D Synchronization Profile (3DSP) +-define(UUID_3D_Synchronization, <<16#1139>>). %% 3D Synchronization Profile (3DSP) +-define(UUID_MPS_Profile, <<16#113A>>). %% Multi-Profile Specification (MPS) +-define(UUID_MPS_SC, <<16#113B>>). %% Multi-Profile Specification (MPS) +-define(UUID_CTN_Access_Service, <<16#113C>>). %% Calendar, Task, and Notes (CTN) +-define(UUID_CTN_Notification_Service, <<16#113D>>). %% Calendar Tasks and Notes (CTN) +-define(UUID_CTN_Profile, <<16#113E>>). %% Calendar Tasks and Notes (CTN) Profile +-define(UUID_PnPInformation, <<16#1200:16>>). +-define(UUID_GenericNetworking, <<16#1201:16>>). +-define(UUID_GenericFileTransfer, <<16#1202:16>>). +-define(UUID_GenericAudio, <<16#1203:16>>). +-define(UUID_GenericTelephony, <<16#1204:16>>). +-define(UUID_UPNP_Service, <<16#1205:16>>). +-define(UUID_UPNP_IP_Service, <<16#1206:16>>). +-define(UUID_ESDP_UPNP_IP_PAN, <<16#1300:16>>). +-define(UUID_ESDP_UPNP_IP_LAP, <<16#1301:16>>). +-define(UUID_ESDP_UPNP_L2CAP, <<16#1302:16>>). +-define(UUID_VideoSource, <<16#1303:16>>). +-define(UUID_VideoSink, <<16#1304:16>>). +-define(UUID_VideoDistribution, <<16#1305:16>>). +-define(UUID_HDP, <<16#1400:16>>). %% Health Device Profile +-define(UUID_HDP_Source, <<16#1401:16>>). %% Health Device Profile (HDP) +-define(UUID_HDP_Sink, <<16#1402:16>>). %% Health Device Profile (HDP) + +-define(UUID_SyncMLServer, + <<16#00000001:32,16#0000:16,16#1000:16,16#8000:16,16#0002EE000002:48>>). +-define(UUID_SyncMLClient, + <<16#00000002:32,16#0000:16,16#1000:16,16#8000:16,16#0002EE000002:48>>). +-define(UUID_SyncMLDMServer, + <<16#00000003:32,16#0000:16,16#1000:16,16#8000:16,16#0002EE000002:48>>). +-define(UUID_SyncMLDMClient, + <<16#00000004:32,16#0000:16,16#1000:16,16#8000:16,16#0002EE000002:48>>). + +%% NOKIA +-define(UUID_NokiaSyncMLServer, + ?UUID(16#00005601,16#0000,16#1000,16#8000,16#0002EE000001)). +-define(UUID_NokiaObexPcSuiteServices, + ?UUID(16#00005005,16#0000,16#1000,16#8000,16#0002EE000001)). + +%% 00005557-0000-1000-8000-0002EE000001 What is this? E60! + +%% SONY-ERICSSON +-define(UUID_Z520a_SyncMLClient, + <<16#00001111:32,16#0000:16,16#0000:16,16#0000:16,16#000222000000:48>>). + + +-define(LANGUAGE(L1,L2), (((L1) bsl 8) + (L2))). + +-define(ENCODING_UTF8, 106). + +%% +%% SDP Request/Response codes +%% +-define(SDP_ErrorResponse, 16#01). +-define(SDP_ServiceSearchRequest, 16#02). +-define(SDP_ServiceSearchResponse, 16#03). +-define(SDP_ServiceAttributeRequest, 16#04). +-define(SDP_ServiceAttributeResponse, 16#05). +-define(SDP_ServiceSearchAttributeRequest, 16#06). +-define(SDP_ServiceSearchAttributeResponse, 16#07). + +-define(SDP_ErrorInvalidVersion, 16#0001). +-define(SDP_ErrorInvalidHandle, 16#0002). +-define(SDP_ErrorInvalidSyntax, 16#0003). +-define(SDP_ErrorInvaludPduSize, 16#0004). +-define(SDP_ErrorInvalidContinuation, 16#0005). +-define(SDP_ErrorInsufficientResources, 16#0006). + + +-record(sdpErrorResponse, + { + errorCode, + errorInfo + }). + +-record(sdpServiceSearchRequest, + { + serviceSearchPattern, + maximumServiceRecordCount + }). + +-record(sdpServiceSearchResponse, + { + totalServiceRecordCount, + currentServiceRecordCount, + serviceRecordHandleList + }). + +-record(sdpServiceAttributeRequest, + { + serviceRecordHandle, + maximumAttributeByteCount, + attributeIDList + }). + +-record(sdpServiceAttributeResponse, + { + attributeListByteCount, + attributeList + }). + +-record(sdpServiceSearchAttributeRequest, + { + serviceSearchPattern, + maximumAttributeByteCount, + attributeIDList + }). + +-record(sdpServiceSearchAttributeResponse, + { + attributeListByteCount, + attributeList + }). + +-endif. diff --git a/deps/bt/include/uuid.hrl b/deps/bt/include/uuid.hrl new file mode 100644 index 0000000..b7f4bd0 --- /dev/null +++ b/deps/bt/include/uuid.hrl @@ -0,0 +1,52 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2007 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +%% +%% Some UUID macros +%% +-ifndef(UUID_HRL). +-define(UUID_HRL, true). + +-define(is_uuid(UUID), is_binary(UUID), size(UUID) == 16). + +-define(is_ieee_802(A), + is_tuple((A)), size((A)) == 6, + ((element(1,(A)) bor element(2,(A)) bor element(3,(A)) bor + element(4,(A)) bor element(5,(A)) bor element(6,(A))) band + -16#100 == 0)). + +%% UUID all components +-define(UUIDx(Ver,TimeLow,TimeMid,TimeHi,Var,Clock,Node), + <<(TimeLow):32,(TimeMid):16,(Ver):4,(TimeHi):12, + (Var):2,(Clock):14,(Node):48>>). + +%% UUID major components - for formating etc +-define(UUID(TimeLow,TimeMid,TimeHigh,Clock,Node), + <<TimeLow:32,TimeMid:16,TimeHigh:16, Clock:16, Node:48>>). + +-define(NameSpace_DNS, + ?UUID(16#6ba7b810, 16#9dad, 16#11d1, 16#80b4, 16#00c04fd430c8)). + +-define(NameSpace_URL, + ?UUID(16#6ba7b811, 16#9dad, 16#11d1, 16#80b4, 16#00c04fd430c8)). + +-define(NameSpace_OID, + ?UUID(16#6ba7b812, 16#9dad, 16#11d1, 16#80b4, 16#00c04fd430c8)). + +-define(NameSpace_X500, + ?UUID(16#6ba7b814, 16#9dad, 16#11d1, 16#80b4, 16#00c04fd430c8)). + +-endif. diff --git a/deps/bt/priv/.gitignore b/deps/bt/priv/.gitignore new file mode 100644 index 0000000..4b2ef59 --- /dev/null +++ b/deps/bt/priv/.gitignore @@ -0,0 +1,2 @@ +bt +*.so diff --git a/deps/bt/priv/hci.def b/deps/bt/priv/hci.def new file mode 100644 index 0000000..f63e1e6 --- /dev/null +++ b/deps/bt/priv/hci.def @@ -0,0 +1,2250 @@ +%% -*- erlang -*- + +{define, 'HCI_MAX_DEV', 16}. + +{define, 'HCI_MAX_ACL_SIZE', 1024}. +{define, 'HCI_MAX_SCO_SIZE', 255}. +{define, 'HCI_MAX_EVENT_SIZE', 260}. +{define, 'HCI_MAX_FRAME_SIZE', "(?HCI_MAX_ACL_SIZE + 4)"}. + +%% HCI dev events +{define, 'HCI_DEV_REG', 1}. +{define, 'HCI_DEV_UNREG', 2}. +{define, 'HCI_DEV_UP', 3}. +{define, 'HCI_DEV_DOWN', 4}. +{define, 'HCI_DEV_SUSPEND', 5}. +{define, 'HCI_DEV_RESUME', 6}. + +%% HCI Error codes +{define, 'HCI_UNKNOWN_COMMAND', 16#01}. +{define, 'HCI_NO_CONNECTION', 16#02}. +{define, 'HCI_HARDWARE_FAILURE', 16#03}. +{define, 'HCI_PAGE_TIMEOUT', 16#04}. +{define, 'HCI_AUTHENTICATION_FAILURE', 16#05}. +{define, 'HCI_PIN_OR_KEY_MISSING', 16#06}. +{define, 'HCI_MEMORY_FULL', 16#07}. +{define, 'HCI_CONNECTION_TIMEOUT', 16#08}. +{define, 'HCI_MAX_NUMBER_OF_CONNECTIONS', 16#09}. +{define, 'HCI_MAX_NUMBER_OF_SCO_CONNECTIONS', 16#0a}. +{define, 'HCI_ACL_CONNECTION_EXISTS', 16#0b}. +{define, 'HCI_COMMAND_DISALLOWED', 16#0c}. +{define, 'HCI_REJECTED_LIMITED_RESOURCES', 16#0d}. +{define, 'HCI_REJECTED_SECURITY', 16#0e}. +{define, 'HCI_REJECTED_PERSONAL', 16#0f}. +{define, 'HCI_HOST_TIMEOUT', 16#10}. +{define, 'HCI_UNSUPPORTED_FEATURE', 16#11}. +{define, 'HCI_INVALID_PARAMETERS', 16#12}. +{define, 'HCI_OE_USER_ENDED_CONNECTION', 16#13}. +{define, 'HCI_OE_LOW_RESOURCES', 16#14}. +{define, 'HCI_OE_POWER_OFF', 16#15}. +{define, 'HCI_CONNECTION_TERMINATED', 16#16}. +{define, 'HCI_REPEATED_ATTEMPTS', 16#17}. +{define, 'HCI_PAIRING_NOT_ALLOWED', 16#18}. +{define, 'HCI_UNKNOWN_LMP_PDU', 16#19}. +{define, 'HCI_UNSUPPORTED_REMOTE_FEATURE', 16#1a}. +{define, 'HCI_SCO_OFFSET_REJECTED', 16#1b}. +{define, 'HCI_SCO_INTERVAL_REJECTED', 16#1c}. +{define, 'HCI_AIR_MODE_REJECTED', 16#1d}. +{define, 'HCI_INVALID_LMP_PARAMETERS', 16#1e}. +{define, 'HCI_UNSPECIFIED_ERROR', 16#1f}. +{define, 'HCI_UNSUPPORTED_LMP_PARAMETER_VALUE', 16#20}. +{define, 'HCI_ROLE_CHANGE_NOT_ALLOWED', 16#21}. +{define, 'HCI_LMP_RESPONSE_TIMEOUT', 16#22}. +{define, 'HCI_LMP_ERROR_TRANSACTION_COLLISION', 16#23}. +{define, 'HCI_LMP_PDU_NOT_ALLOWED', 16#24}. +{define, 'HCI_ENCRYPTION_MODE_NOT_ACCEPTED', 16#25}. +{define, 'HCI_UNIT_LINK_KEY_USED', 16#26}. +{define, 'HCI_QOS_NOT_SUPPORTED', 16#27}. +{define, 'HCI_INSTANT_PASSED', 16#28}. +{define, 'HCI_PAIRING_NOT_SUPPORTED', 16#29}. +{define, 'HCI_TRANSACTION_COLLISION', 16#2a}. +{define, 'HCI_QOS_UNACCEPTABLE_PARAMETER', 16#2c}. +{define, 'HCI_QOS_REJECTED', 16#2d}. +{define, 'HCI_CLASSIFICATION_NOT_SUPPORTED', 16#2e}. +{define, 'HCI_INSUFFICIENT_SECURITY', 16#2f}. +{define, 'HCI_PARAMETER_OUT_OF_RANGE', 16#30}. +{define, 'HCI_ROLE_SWITCH_PENDING', 16#32}. +{define, 'HCI_SLOT_VIOLATION', 16#34}. +{define, 'HCI_ROLE_SWITCH_FAILED', 16#35}. +{define, 'HCI_EIR_TOO_LARGE', 16#36}. +{define, 'HCI_SIMPLE_PAIRING_NOT_SUPPORTED', 16#37}. +{define, 'HCI_HOST_BUSY_PAIRING', 16#38}. + +%% Types +{type, bdaddr_t, {uint8_t,6}}. +{type, hci_qos_t, {uint8_t,17}}. +%% {type, dev_class_t, {uint8_t,3}}. +%% {type, lap_t, {uint8_t,3}}. + + +%% ----- HCI Commands ----- + +%% Link Control +{define, 'OGF_LINK_CTL', 16#01}. + +{define, 'OCF_INQUIRY', 16#0001}. +{struct,inquiry_cp, + [ + {{uint8_t,3}, lap}, + {uint8_t, length}, %% 1.28s units + {uint8_t, num_rsp} + ]}. +{define, 'INQUIRY_CP_SIZE', 5}. + +{struct, status_bdaddr_rp, + [ + {uint8_t, status}, + {bdaddr_t, bdaddr} +]}. +{define, 'STATUS_BDADDR_RP_SIZE', 7}. + +{define, 'OCF_INQUIRY_CANCEL', 16#0002}. + +{define, 'OCF_PERIODIC_INQUIRY', 16#0003}. +{struct, periodic_inquiry_cp, + [ + {uint16_t, max_period}, %% 1.28s units + {uint16_t, min_period}, %% 1.28s units + {{uint8_t,3}, lap}, + {uint8_t, length}, %% 1.28s units + {uint8_t, num_rsp} +]}. +{define, 'PERIODIC_INQUIRY_CP_SIZE', 9}. + +{define, 'OCF_EXIT_PERIODIC_INQUIRY', 16#0004}. + +{define, 'OCF_CREATE_CONN', 16#0005}. +{struct, create_conn_cp, + [ + {bdaddr_t, bdaddr}, + {uint16_t, pkt_type}, + {uint8_t, pscan_rep_mode}, + {uint8_t, pscan_mode}, + {uint16_t, clock_offset}, + {uint8_t, role_switch} +]}. +{define, 'CREATE_CONN_CP_SIZE', 13}. + +{define, 'OCF_DISCONNECT', 16#0006}. +{struct, disconnect_cp, + [ + {uint16_t, handle}, + {uint8_t, reason} +]}. +{define, 'DISCONNECT_CP_SIZE', 3}. + +{define, 'OCF_ADD_SCO', 16#0007}. +{struct, add_sco_cp, + [ + {uint16_t, handle}, + {uint16_t, pkt_type} +]}. +{define, 'ADD_SCO_CP_SIZE', 4}. + +{define, 'OCF_CREATE_CONN_CANCEL', 16#0008}. +{struct, create_conn_cancel_cp, + [ + {bdaddr_t, bdaddr} +]}. +{define, 'CREATE_CONN_CANCEL_CP_SIZE', 6}. + +{define, 'OCF_ACCEPT_CONN_REQ', 16#0009}. +{struct, accept_conn_req_cp, + [ + {bdaddr_t, bdaddr}, + {uint8_t, role} +]}. +{define, 'ACCEPT_CONN_REQ_CP_SIZE', 7}. + +{define, 'OCF_REJECT_CONN_REQ', 16#000A}. +{struct, reject_conn_req_cp, + [ + {bdaddr_t, bdaddr}, + {uint8_t, reason} +]}. +{define, 'REJECT_CONN_REQ_CP_SIZE', 7}. + +{define, 'OCF_LINK_KEY_REPLY', 16#000B}. +{struct, link_key_reply_cp, + [ + {bdaddr_t, bdaddr}, + {{uint8_t,16}, link_key} +]}. +{define, 'LINK_KEY_REPLY_CP_SIZE', 22}. + +{define, 'OCF_LINK_KEY_NEG_REPLY', 16#000C}. +{struct, link_key_neg_reply_cp, + [ + {bdaddr_t, bdaddr} + ]}. +{define, 'LINK_KEY_NEG_REPLY_CP_SIZE', 6}. + +{define, 'OCF_PIN_CODE_REPLY', 16#000D}. +{struct, pin_code_reply_cp, + [ + {bdaddr_t, bdaddr}, + {uint8_t, pin_len}, + {{uint8_t,16}, pin_code} +]}. +{define, 'PIN_CODE_REPLY_CP_SIZE', 23}. + +{define, 'OCF_PIN_CODE_NEG_REPLY', 16#000E}. + +{define, 'OCF_SET_CONN_PTYPE', 16#000F}. +{struct, set_conn_ptype_cp, + [ + {uint16_t, handle}, + {uint16_t, pkt_type} +]}. +{define, 'SET_CONN_PTYPE_CP_SIZE', 4}. + +{define, 'OCF_AUTH_REQUESTED', 16#0011}. +{struct, auth_requested_cp, + [ + {uint16_t, handle} +]}. +{define, 'AUTH_REQUESTED_CP_SIZE', 2}. + +{define, 'OCF_SET_CONN_ENCRYPT', 16#0013}. +{struct, set_conn_encrypt_cp, + [ + {uint16_t, handle}, + {uint8_t, encrypt} +]}. +{define, 'SET_CONN_ENCRYPT_CP_SIZE', 3}. + +{define, 'OCF_CHANGE_CONN_LINK_KEY', 16#0015}. +{struct, change_conn_link_key_cp, + [ + {uint16_t, handle} +]}. +{define, 'CHANGE_CONN_LINK_KEY_CP_SIZE', 2}. + +{define, 'OCF_MASTER_LINK_KEY', 16#0017}. +{struct, master_link_key_cp, + [ + {uint8_t, key_flag} +]}. +{define, 'MASTER_LINK_KEY_CP_SIZE', 1}. + +{define, 'OCF_REMOTE_NAME_REQ', 16#0019}. +{struct, remote_name_req_cp, + [ + {bdaddr_t, bdaddr}, + {uint8_t, pscan_rep_mode}, + {uint8_t, pscan_mode}, + {uint16_t, clock_offset} +]}. +{define, 'REMOTE_NAME_REQ_CP_SIZE', 10}. + +{define, 'OCF_REMOTE_NAME_REQ_CANCEL', 16#001A}. +{struct, remote_name_req_cancel_cp, + [ + {bdaddr_t, bdaddr} +]}. +{define, 'REMOTE_NAME_REQ_CANCEL_CP_SIZE', 6}. + +{define, 'OCF_READ_REMOTE_FEATURES', 16#001B}. +{struct, read_remote_features_cp, + [ + {uint16_t, handle} +]}. +{define, 'READ_REMOTE_FEATURES_CP_SIZE', 2}. + +{define, 'OCF_READ_REMOTE_EXT_FEATURES', 16#001C}. +{struct, read_remote_ext_features_cp, + [ + {uint16_t, handle}, + {uint8_t, page_num} +]}. +{define, 'READ_REMOTE_EXT_FEATURES_CP_SIZE', 3}. + +{define, 'OCF_READ_REMOTE_VERSION', 16#001D}. +{struct, read_remote_version_cp, + [ + {uint16_t, handle} +]}. +{define, 'READ_REMOTE_VERSION_CP_SIZE', 2}. + +{define, 'OCF_READ_CLOCK_OFFSET', 16#001F}. +{struct, read_clock_offset_cp, + [ + {uint16_t, handle} +]}. +{define, 'READ_CLOCK_OFFSET_CP_SIZE', 2}. + +{define, 'OCF_READ_LMP_HANDLE', 16#0020}. + +{define, 'OCF_SETUP_SYNC_CONN', 16#0028}. +{struct, setup_sync_conn_cp, + [ + {uint16_t, handle}, + {uint32_t, tx_bandwith}, + {uint32_t, rx_bandwith}, + {uint16_t, max_latency}, + {uint16_t, voice_setting}, + {uint8_t, retrans_effort}, + {uint16_t, pkt_type} +]}. +{define, 'SETUP_SYNC_CONN_CP_SIZE', 17}. + +{define, 'OCF_ACCEPT_SYNC_CONN_REQ', 16#0029}. +{struct, accept_sync_conn_req_cp, + [ + {bdaddr_t, bdaddr}, + {uint32_t, tx_bandwith}, + {uint32_t, rx_bandwith}, + {uint16_t, max_latency}, + {uint16_t, voice_setting}, + {uint8_t, retrans_effort}, + {uint16_t, pkt_type} +]}. +{define, 'ACCEPT_SYNC_CONN_REQ_CP_SIZE', 21}. + +{define, 'OCF_REJECT_SYNC_CONN_REQ', 16#002A}. +{struct, reject_sync_conn_req_cp, + [ + {bdaddr_t, bdaddr}, + {uint8_t, reason} +]}. +{define, 'REJECT_SYNC_CONN_REQ_CP_SIZE', 7}. + +{define, 'OCF_IO_CAPABILITY_REPLY', 16#002B}. +{struct, io_capability_reply_cp, + [ + {bdaddr_t, bdaddr}, + {uint8_t, capability}, + {uint8_t, oob_data}, + {uint8_t, authentication} +]}. +{define, 'IO_CAPABILITY_REPLY_CP_SIZE', 9}. + +{define, 'OCF_USER_CONFIRM_REPLY', 16#002C}. +{struct, user_confirm_reply_cp, + [ + {bdaddr_t, bdaddr} +]}. +{define, 'USER_CONFIRM_REPLY_CP_SIZE', 6}. + +{define, 'OCF_USER_CONFIRM_NEG_REPLY', 16#002D}. + +{define, 'OCF_USER_PASSKEY_REPLY', 16#002E}. +{struct, user_passkey_reply_cp, + [ + {bdaddr_t, bdaddr}, + {uint32_t, passkey} +]}. +{define, 'USER_PASSKEY_REPLY_CP_SIZE', 10}. + +{define, 'OCF_USER_PASSKEY_NEG_REPLY', 16#002F}. + +{define, 'OCF_REMOTE_OOB_DATA_REPLY', 16#0030}. +{struct, remote_oob_data_reply_cp, + [ + {bdaddr_t, bdaddr}, + {{uint8_t,16}, hash}, + {{uint8_t,16}, randomizer} +]}. +{define, 'REMOTE_OOB_DATA_REPLY_CP_SIZE', 38}. + +{define, 'OCF_REMOTE_OOB_DATA_NEG_REPLY', 16#0033}. + +{define, 'OCF_IO_CAPABILITY_NEG_REPLY', 16#0034}. +{struct, io_capability_neg_reply_cp, + [ + {bdaddr_t, bdaddr}, + {uint8_t, reason} +]}. +{define, 'IO_CAPABILITY_NEG_REPLY_CP_SIZE', 7}. + +{define, 'OCF_CREATE_PHYSICAL_LINK', 16#0035}. +{struct, create_physical_link_cp, + [ + {uint8_t, handle}, + {uint8_t, key_length}, + {uint8_t, key_type}, + {{uint8_t,32}, key} +]}. +{define, 'CREATE_PHYSICAL_LINK_CP_SIZE', 35}. + +{define, 'OCF_ACCEPT_PHYSICAL_LINK', 16#0036}. + +{define, 'OCF_DISCONNECT_PHYSICAL_LINK', 16#0037}. +{struct, disconnect_physical_link_cp, + [ + {uint8_t, handle}, + {uint8_t, reason} +]}. +{define, 'DISCONNECT_PHYSICAL_LINK_CP_SIZE', 2}. + +{define, 'OCF_CREATE_LOGICAL_LINK', 16#0038}. +{struct, create_logical_link_cp, + [ + {uint8_t, handle}, + {{uint8_t,16}, tx_flow}, + {{uint8_t,16}, rx_flow} +]}. +{define, 'CREATE_LOGICAL_LINK_CP_SIZE', 33}. + +{define, 'OCF_ACCEPT_LOGICAL_LINK', 16#0039}. + +{define, 'OCF_DISCONNECT_LOGICAL_LINK', 16#003A}. +{struct, disconnect_logical_link_cp, + [ + {uint16_t, handle} +]}. +{define, 'DISCONNECT_LOGICAL_LINK_CP_SIZE', 2}. + +{define, 'OCF_LOGICAL_LINK_CANCEL', 16#003B}. +{struct, cancel_logical_link_cp, + [ + {uint8_t, handle}, + {uint8_t, tx_flow_id} +]}. +{define, 'LOGICAL_LINK_CANCEL_CP_SIZE', 2}. +{struct, cancel_logical_link_rp, + [ + {uint8_t, status}, + {uint8_t, handle}, + {uint8_t, tx_flow_id} +]}. +{define, 'LOGICAL_LINK_CANCEL_RP_SIZE', 3}. + +{define, 'OCF_FLOW_SPEC_MODIFY', 16#003C}. + +%% Link Policy +{define, 'OGF_LINK_POLICY', 16#02}. + +{define, 'OCF_HOLD_MODE', 16#0001}. +{struct, hold_mode_cp, + [ + {uint16_t, handle}, + {uint16_t, max_interval}, + {uint16_t, min_interval} +]}. +{define, 'HOLD_MODE_CP_SIZE', 6}. + +{define, 'OCF_SNIFF_MODE', 16#0003}. +{struct, sniff_mode_cp, + [ + {uint16_t, handle}, + {uint16_t, max_interval}, + {uint16_t, min_interval}, + {uint16_t, attempt}, + {uint16_t, timeout} +]}. +{define, 'SNIFF_MODE_CP_SIZE', 10}. + +{define, 'OCF_EXIT_SNIFF_MODE', 16#0004}. +{struct, exit_sniff_mode_cp, + [ + {uint16_t, handle} +]}. +{define, 'EXIT_SNIFF_MODE_CP_SIZE', 2}. + +{define, 'OCF_PARK_MODE', 16#0005}. +{struct, park_mode_cp, + [ + {uint16_t, handle}, + {uint16_t, max_interval}, + {uint16_t, min_interval} +]}. +{define, 'PARK_MODE_CP_SIZE', 6}. + +{define, 'OCF_EXIT_PARK_MODE', 16#0006}. +{struct, exit_park_mode_cp, + [ + {uint16_t, handle} +]}. +{define, 'EXIT_PARK_MODE_CP_SIZE', 2}. + +{define, 'OCF_QOS_SETUP', 16#0007}. +{struct, hci_qos, + [ + {uint8_t, service_type}, %% 1 = best effort + {uint32_t, token_rate}, %% Byte per seconds + {uint32_t, peak_bandwidth}, %% Byte per seconds + {uint32_t, latency}, %% Microseconds + {uint32_t, delay_variation} %% Microseconds +]}. +{define, 'HCI_QOS_CP_SIZE', 17}. +{struct, qos_setup_cp, + [ + {uint16_t, handle}, + {uint8_t, flags}, %% Reserved + {hci_qos_t, qos} +]}. +{define, 'QOS_SETUP_CP_SIZE', "(3 + ?HCI_QOS_CP_SIZE)"}. + +{define, 'OCF_ROLE_DISCOVERY', 16#0009}. +{struct, role_discovery_cp, + [ + {uint16_t, handle} +]}. +{define, 'ROLE_DISCOVERY_CP_SIZE', 2}. +{struct, role_discovery_rp, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint8_t, role} +]}. +{define, 'ROLE_DISCOVERY_RP_SIZE', 4}. + +{define, 'OCF_SWITCH_ROLE', 16#000B}. +{struct, switch_role_cp, + [ + {bdaddr_t, bdaddr}, + {uint8_t, role} +]}. +{define, 'SWITCH_ROLE_CP_SIZE', 7}. + +{define, 'OCF_READ_LINK_POLICY', 16#000C}. +{struct, read_link_policy_cp, + [ + {uint16_t, handle} +]}. +{define, 'READ_LINK_POLICY_CP_SIZE', 2}. +{struct, read_link_policy_rp, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint16_t, policy} +]}. +{define, 'READ_LINK_POLICY_RP_SIZE', 5}. + +{define, 'OCF_WRITE_LINK_POLICY', 16#000D}. +{struct, write_link_policy_cp, + [ + {uint16_t, handle}, + {uint16_t, policy} +]}. +{define, 'WRITE_LINK_POLICY_CP_SIZE', 4}. +{struct, write_link_policy_rp, + [ + {uint8_t, status}, + {uint16_t, handle} +]}. +{define, 'WRITE_LINK_POLICY_RP_SIZE', 3}. + +{define, 'OCF_READ_DEFAULT_LINK_POLICY', 16#000E}. + +{define, 'OCF_WRITE_DEFAULT_LINK_POLICY', 16#000F}. + +{define, 'OCF_FLOW_SPECIFICATION', 16#0010}. + +{define, 'OCF_SNIFF_SUBRATING', 16#0011}. +{struct, sniff_subrating_cp, + [ + {uint16_t, handle}, + {uint16_t, max_latency}, + {uint16_t, min_remote_timeout}, + {uint16_t, min_local_timeout} +]}. +{define, 'SNIFF_SUBRATING_CP_SIZE', 8}. + +%% Host Controller and Baseband +{define, 'OGF_HOST_CTL', 16#03}. + +{define, 'OCF_SET_EVENT_MASK', 16#0001}. +{struct, set_event_mask_cp, + [ + {{uint8_t,8}, mask} +]}. +{define, 'SET_EVENT_MASK_CP_SIZE', 8}. + +{define, 'OCF_RESET', 16#0003}. + +{define, 'OCF_SET_EVENT_FLT', 16#0005}. +{struct, set_event_flt_cp, + [ + {uint8_t, flt_type}, + {uint8_t, cond_type}, + {{uint8_t,0}, condition} +]}. +{define, 'SET_EVENT_FLT_CP_SIZE', 2}. + +%% Filter types +{define, 'FLT_CLEAR_ALL', 16#00}. +{define, 'FLT_INQ_RESULT', 16#01}. +{define, 'FLT_CONN_SETUP', 16#02}. +%% INQ_RESULT Condition types +{define, 'INQ_RESULT_RETURN_ALL', 16#00}. +{define, 'INQ_RESULT_RETURN_CLASS', 16#01}. +{define, 'INQ_RESULT_RETURN_BDADDR', 16#02}. +%% CONN_SETUP Condition types +{define, 'CONN_SETUP_ALLOW_ALL', 16#00}. +{define, 'CONN_SETUP_ALLOW_CLASS', 16#01}. +{define, 'CONN_SETUP_ALLOW_BDADDR', 16#02}. +%% CONN_SETUP Conditions +{define, 'CONN_SETUP_AUTO_OFF', 16#01}. +{define, 'CONN_SETUP_AUTO_ON', 16#02}. + +{define, 'OCF_FLUSH', 16#0008}. + +{define, 'OCF_READ_PIN_TYPE', 16#0009}. +{struct, read_pin_type_rp, + [ + {uint8_t, status}, + {uint8_t, pin_type} +]}. +{define, 'READ_PIN_TYPE_RP_SIZE', 2}. + +{define, 'OCF_WRITE_PIN_TYPE', 16#000A}. +{struct, write_pin_type_cp, + [ + {uint8_t, pin_type} +]}. +{define, 'WRITE_PIN_TYPE_CP_SIZE', 1}. + +{define, 'OCF_CREATE_NEW_UNIT_KEY', 16#000B}. + +{define, 'OCF_READ_STORED_LINK_KEY', 16#000D}. +{struct, read_stored_link_key_cp, + [ + {bdaddr_t, bdaddr}, + {uint8_t, read_all} +]}. +{define, 'READ_STORED_LINK_KEY_CP_SIZE', 7}. +{struct, read_stored_link_key_rp, + [ + {uint8_t, status}, + {uint16_t, max_keys}, + {uint16_t, num_keys} +]}. +{define, 'READ_STORED_LINK_KEY_RP_SIZE', 5}. + +{define, 'OCF_WRITE_STORED_LINK_KEY', 16#0011}. +{struct, write_stored_link_key_cp, + [ + {uint8_t, num_keys} + %% variable length part +]}. +{define, 'WRITE_STORED_LINK_KEY_CP_SIZE', 1}. +{struct, write_stored_link_key_rp, + [ + {uint8_t, status}, + {uint8_t, num_keys} +]}. +{define, 'READ_WRITE_LINK_KEY_RP_SIZE', 2}. + +{define, 'OCF_DELETE_STORED_LINK_KEY', 16#0012}. +{struct, delete_stored_link_key_cp, + [ + {bdaddr_t, bdaddr}, + {uint8_t, delete_all} +]}. +{define, 'DELETE_STORED_LINK_KEY_CP_SIZE', 7}. +{struct, delete_stored_link_key_rp, + [ + {uint8_t, status}, + {uint16_t, num_keys} +]}. +{define, 'DELETE_STORED_LINK_KEY_RP_SIZE', 3}. + +{define, 'HCI_MAX_NAME_LENGTH', 248}. + +{define, 'OCF_CHANGE_LOCAL_NAME', 16#0013}. +{struct, change_local_name_cp, + [ + {{char,"?HCI_MAX_NAME_LENGTH"}, name} +]}. +{define, 'CHANGE_LOCAL_NAME_CP_SIZE', 248}. + +{define, 'OCF_READ_LOCAL_NAME', 16#0014}. +{struct, read_local_name_rp, + [ + {uint8_t, status}, + {{char, "?HCI_MAX_NAME_LENGTH"}, name} +]}. +{define, 'READ_LOCAL_NAME_RP_SIZE', 249}. + +{define, 'OCF_READ_CONN_ACCEPT_TIMEOUT', 16#0015}. +{struct, read_conn_accept_timeout_rp, + [ + {uint8_t, status}, + {uint16_t, timeout} +]}. +{define, 'READ_CONN_ACCEPT_TIMEOUT_RP_SIZE', 3}. + +{define, 'OCF_WRITE_CONN_ACCEPT_TIMEOUT', 16#0016}. +{struct, write_conn_accept_timeout_cp, + [ + {uint16_t, timeout} +]}. +{define, 'WRITE_CONN_ACCEPT_TIMEOUT_CP_SIZE', 2}. + +{define, 'OCF_READ_PAGE_TIMEOUT', 16#0017}. +{struct, read_page_timeout_rp, + [ + {uint8_t, status}, + {uint16_t, timeout} +]}. +{define, 'READ_PAGE_TIMEOUT_RP_SIZE', 3}. + +{define, 'OCF_WRITE_PAGE_TIMEOUT', 16#0018}. +{struct, write_page_timeout_cp, + [ + {uint16_t, timeout} +]}. +{define, 'WRITE_PAGE_TIMEOUT_CP_SIZE', 2}. + +{define, 'OCF_READ_SCAN_ENABLE', 16#0019}. +{struct, read_scan_enable_rp, + [ + {uint8_t, status}, + {uint8_t, enable} +]}. +{define, 'READ_SCAN_ENABLE_RP_SIZE', 2}. + +{define, 'OCF_WRITE_SCAN_ENABLE', 16#001A}. +%% {define, 'SCAN_DISABLED', 16#00}. +%% {define, 'SCAN_INQUIRY', 16#01}. +%% {define, 'SCAN_PAGE', 16#02}. + +{define, 'OCF_READ_PAGE_ACTIVITY', 16#001B}. +{struct, read_page_activity_rp, + [ + {uint8_t, status}, + {uint16_t, interval}, + {uint16_t, window} +]}. +{define, 'READ_PAGE_ACTIVITY_RP_SIZE', 5}. + +{define, 'OCF_WRITE_PAGE_ACTIVITY', 16#001C}. +{struct, write_page_activity_cp, + [ + {uint16_t, interval}, + {uint16_t, window} +]}. +{define, 'WRITE_PAGE_ACTIVITY_CP_SIZE', 4}. + +{define, 'OCF_READ_INQ_ACTIVITY', 16#001D}. +{struct, read_inq_activity_rp, + [ + {uint8_t, status}, + {uint16_t, interval}, + {uint16_t, window} +]}. +{define, 'READ_INQ_ACTIVITY_RP_SIZE', 5}. + +{define, 'OCF_WRITE_INQ_ACTIVITY', 16#001E}. +{struct, write_inq_activity_cp, + [ + {uint16_t, interval}, + {uint16_t, window} +]}. +{define, 'WRITE_INQ_ACTIVITY_CP_SIZE', 4}. + +{define, 'OCF_READ_AUTH_ENABLE', 16#001F}. + +{define, 'OCF_WRITE_AUTH_ENABLE', 16#0020}. + {define, 'AUTH_DISABLED', 16#00}. + {define, 'AUTH_ENABLED', 16#01}. + +{define, 'OCF_READ_ENCRYPT_MODE', 16#0021}. + +{define, 'OCF_WRITE_ENCRYPT_MODE', 16#0022}. + {define, 'ENCRYPT_DISABLED', 16#00}. + {define, 'ENCRYPT_P2P', 16#01}. + {define, 'ENCRYPT_BOTH', 16#02}. + +{define, 'OCF_READ_CLASS_OF_DEV', 16#0023}. +{struct, read_class_of_dev_rp, + [ + {uint8_t, status}, + {{uint8_t,3}, dev_class} +]}. +{define, 'READ_CLASS_OF_DEV_RP_SIZE', 4}. + +{define, 'OCF_WRITE_CLASS_OF_DEV', 16#0024}. +{struct, write_class_of_dev_cp, + [ + {{uint8_t,3}, dev_class} +]}. +{define, 'WRITE_CLASS_OF_DEV_CP_SIZE', 3}. + +{define, 'OCF_READ_VOICE_SETTING', 16#0025}. +{struct, read_voice_setting_rp, + [ + {uint8_t, status}, + {uint16_t, voice_setting} +]}. +{define, 'READ_VOICE_SETTING_RP_SIZE', 3}. + +{define, 'OCF_WRITE_VOICE_SETTING', 16#0026}. +{struct, write_voice_setting_cp, + [ + {uint16_t, voice_setting} +]}. +{define, 'WRITE_VOICE_SETTING_CP_SIZE', 2}. + +{define, 'OCF_READ_AUTOMATIC_FLUSH_TIMEOUT', 16#0027}. + +{define, 'OCF_WRITE_AUTOMATIC_FLUSH_TIMEOUT', 16#0028}. + +{define, 'OCF_READ_NUM_BROADCAST_RETRANS', 16#0029}. + +{define, 'OCF_WRITE_NUM_BROADCAST_RETRANS', 16#002A}. + +{define, 'OCF_READ_HOLD_MODE_ACTIVITY', 16#002B}. + +{define, 'OCF_WRITE_HOLD_MODE_ACTIVITY', 16#002C}. + +{define, 'OCF_READ_TRANSMIT_POWER_LEVEL', 16#002D}. +{struct, read_transmit_power_level_cp, + [ + {uint16_t, handle}, + {uint8_t, type} +]}. +{define, 'READ_TRANSMIT_POWER_LEVEL_CP_SIZE', 3}. +{struct, read_transmit_power_level_rp, + [ + {uint8_t, status}, + {uint16_t, handle}, + {int8_t, level} +]}. +{define, 'READ_TRANSMIT_POWER_LEVEL_RP_SIZE', 4}. + +{define, 'OCF_READ_SYNC_FLOW_ENABLE', 16#002E}. + +{define, 'OCF_WRITE_SYNC_FLOW_ENABLE', 16#002F}. + +{define, 'OCF_SET_CONTROLLER_TO_HOST_FC', 16#0031}. + +{define, 'OCF_HOST_BUFFER_SIZE', 16#0033}. +{struct, host_buffer_size_cp, + [ + {uint16_t, acl_mtu}, + {uint8_t, sco_mtu}, + {uint16_t, acl_max_pkt}, + {uint16_t, sco_max_pkt} +]}. +{define, 'HOST_BUFFER_SIZE_CP_SIZE', 7}. + +{define, 'OCF_HOST_NUM_COMP_PKTS', 16#0035}. +{struct, host_num_comp_pkts_cp, + [ + {uint8_t, num_hndl} + %% variable length part + %% {list, num_hndl, uint16_t, hndls} +]}. +{define, 'HOST_NUM_COMP_PKTS_CP_SIZE', 1}. + +{define, 'OCF_READ_LINK_SUPERVISION_TIMEOUT', 16#0036}. +{struct, read_link_supervision_timeout_rp, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint16_t, timeout} +]}. +{define, 'READ_LINK_SUPERVISION_TIMEOUT_RP_SIZE', 5}. + +{define, 'OCF_WRITE_LINK_SUPERVISION_TIMEOUT', 16#0037}. +{struct, write_link_supervision_timeout_cp, + [ + {uint16_t, handle}, + {uint16_t, timeout} +]}. +{define, 'WRITE_LINK_SUPERVISION_TIMEOUT_CP_SIZE', 4}. +{struct, write_link_supervision_timeout_rp, + [ + {uint8_t, status}, + {uint16_t, handle} +]}. +{define, 'WRITE_LINK_SUPERVISION_TIMEOUT_RP_SIZE', 3}. + +{define, 'OCF_READ_NUM_SUPPORTED_IAC', 16#0038}. + +{define, 'MAX_IAC_LAP', 16#40}. +{define, 'OCF_READ_CURRENT_IAC_LAP', 16#0039}. +{struct, read_current_iac_lap_rp, + [ + {uint8_t, status}, + {uint8_t, num_current_iac}, + %% {list,num_current_iac,{binary,3},"?MAX_IAC_LAP",lap} + {{uint8_t,"3*?MAX_IAC_LAP"}, lap} +]}. +{define, 'READ_CURRENT_IAC_LAP_RP_SIZE', "2+3*?MAX_IAC_LAP"}. + +{define, 'OCF_WRITE_CURRENT_IAC_LAP', 16#003A}. +{struct, write_current_iac_lap_cp, + [ + {uint8_t, num_current_iac}, + %% {list,num_current_iac,{binary,3},"?MAX_IAC_LAP",lap} + {{uint8_t,"3*?MAX_IAC_LAP"}, lap} +]}. +{define, 'WRITE_CURRENT_IAC_LAP_CP_SIZE', "1+3*?MAX_IAC_LAP"}. + +{define, 'OCF_READ_PAGE_SCAN_PERIOD_MODE', 16#003B}. + +{define, 'OCF_WRITE_PAGE_SCAN_PERIOD_MODE', 16#003C}. + +{define, 'OCF_READ_PAGE_SCAN_MODE', 16#003D}. + +{define, 'OCF_WRITE_PAGE_SCAN_MODE', 16#003E}. + +{define, 'OCF_SET_AFH_CLASSIFICATION', 16#003F}. +{struct, set_afh_classification_cp, + [ + {{uint8_t,10}, map} +]}. +{define, 'SET_AFH_CLASSIFICATION_CP_SIZE', 10}. +{struct, set_afh_classification_rp, + [ + {uint8_t, status} +]}. +{define, 'SET_AFH_CLASSIFICATION_RP_SIZE', 1}. + +{define, 'OCF_READ_INQUIRY_SCAN_TYPE', 16#0042}. +{struct, read_inquiry_scan_type_rp, + [ + {uint8_t, status}, + {uint8_t, type} +]}. +{define, 'READ_INQUIRY_SCAN_TYPE_RP_SIZE', 2}. + +{define, 'OCF_WRITE_INQUIRY_SCAN_TYPE', 16#0043}. +{struct, write_inquiry_scan_type_cp, + [ + {uint8_t, type} +]}. +{define, 'WRITE_INQUIRY_SCAN_TYPE_CP_SIZE', 1}. +{struct, write_inquiry_scan_type_rp, + [ + {uint8_t, status} +]}. +{define, 'WRITE_INQUIRY_SCAN_TYPE_RP_SIZE', 1}. + +{define, 'OCF_READ_INQUIRY_MODE', 16#0044}. +{struct, read_inquiry_mode_rp, + [ + {uint8_t, status}, + {uint8_t, mode} +]}. +{define, 'READ_INQUIRY_MODE_RP_SIZE', 2}. + +{define, 'OCF_WRITE_INQUIRY_MODE', 16#0045}. +{struct, write_inquiry_mode_cp, + [ + {uint8_t, mode} +]}. +{define, 'WRITE_INQUIRY_MODE_CP_SIZE', 1}. +{struct, write_inquiry_mode_rp, + [ + {uint8_t, status} +]}. +{define, 'WRITE_INQUIRY_MODE_RP_SIZE', 1}. + +{define, 'OCF_READ_PAGE_SCAN_TYPE', 16#0046}. + +{define, 'OCF_WRITE_PAGE_SCAN_TYPE', 16#0047}. +{define, 'PAGE_SCAN_TYPE_STANDARD', 16#00}. +{define, 'PAGE_SCAN_TYPE_INTERLACED', 16#01}. + +{define, 'OCF_READ_AFH_MODE', 16#0048}. +{struct, read_afh_mode_rp, + [ + {uint8_t, status}, + {uint8_t, mode} +]}. +{define, 'READ_AFH_MODE_RP_SIZE', 2}. + +{define, 'OCF_WRITE_AFH_MODE', 16#0049}. +{struct, write_afh_mode_cp, + [ + {uint8_t, mode} +]}. +{define, 'WRITE_AFH_MODE_CP_SIZE', 1}. +{struct, write_afh_mode_rp, + [ + {uint8_t, status} +]}. +{define, 'WRITE_AFH_MODE_RP_SIZE', 1}. + +{define, 'HCI_MAX_EIR_LENGTH', 240}. + +{define, 'OCF_READ_EXT_INQUIRY_RESPONSE', 16#0051}. +{struct, read_ext_inquiry_response_rp, + [ + {uint8_t, status}, + {uint8_t, fec}, + {{uint8_t,"?HCI_MAX_EIR_LENGTH"}, data} +]}. +{define, 'READ_EXT_INQUIRY_RESPONSE_RP_SIZE', 242}. + +{define, 'OCF_WRITE_EXT_INQUIRY_RESPONSE', 16#0052}. +{struct, write_ext_inquiry_response_cp, + [ + {uint8_t, fec}, + {{uint8_t,"?HCI_MAX_EIR_LENGTH"}, data} +]}. +{define, 'WRITE_EXT_INQUIRY_RESPONSE_CP_SIZE', 241}. +{struct, write_ext_inquiry_response_rp, + [ + {uint8_t, status} +]}. +{define, 'WRITE_EXT_INQUIRY_RESPONSE_RP_SIZE', 1}. + +{define, 'OCF_REFRESH_ENCRYPTION_KEY', 16#0053}. +{struct, refresh_encryption_key_cp, + [ + {uint16_t, handle} +]}. +{define, 'REFRESH_ENCRYPTION_KEY_CP_SIZE', 2}. +{struct, refresh_encryption_key_rp, + [ + {uint8_t, status} +]}. +{define, 'REFRESH_ENCRYPTION_KEY_RP_SIZE', 1}. + +{define, 'OCF_READ_SIMPLE_PAIRING_MODE', 16#0055}. +{struct, read_simple_pairing_mode_rp, + [ + {uint8_t, status}, + {uint8_t, mode} +]}. +{define, 'READ_SIMPLE_PAIRING_MODE_RP_SIZE', 2}. + +{define, 'OCF_WRITE_SIMPLE_PAIRING_MODE', 16#0056}. +{struct, write_simple_pairing_mode_cp, + [ + {uint8_t, mode} +]}. +{define, 'WRITE_SIMPLE_PAIRING_MODE_CP_SIZE', 1}. +{struct, write_simple_pairing_mode_rp, + [ + {uint8_t, status} +]}. +{define, 'WRITE_SIMPLE_PAIRING_MODE_RP_SIZE', 1}. + +{define, 'OCF_READ_LOCAL_OOB_DATA', 16#0057}. +{struct, read_local_oob_data_rp, + [ + {uint8_t, status}, + {{uint8_t,16}, hash}, + {{uint8_t,16}, randomizer} +]}. +{define, 'READ_LOCAL_OOB_DATA_RP_SIZE', 33}. + +{define, 'OCF_READ_INQ_RESPONSE_TX_POWER_LEVEL', 16#0058}. +{struct, read_inq_response_tx_power_level_rp, + [ + {uint8_t, status}, + {int8_t, level} +]}. +{define, 'READ_INQ_RESPONSE_TX_POWER_LEVEL_RP_SIZE', 2}. + +{define, 'OCF_READ_INQUIRY_TRANSMIT_POWER_LEVEL', 16#0058}. +{struct, read_inquiry_transmit_power_level_rp, + [ + {uint8_t, status}, + {int8_t, level} +]}. +{define, 'READ_INQUIRY_TRANSMIT_POWER_LEVEL_RP_SIZE', 2}. + +{define, 'OCF_WRITE_INQUIRY_TRANSMIT_POWER_LEVEL', 16#0059}. +{struct, write_inquiry_transmit_power_level_cp, + [ + {int8_t, level} +]}. +{define, 'WRITE_INQUIRY_TRANSMIT_POWER_LEVEL_CP_SIZE', 1}. +{struct, write_inquiry_transmit_power_level_rp, + [ + {uint8_t, status} +]}. +{define, 'WRITE_INQUIRY_TRANSMIT_POWER_LEVEL_RP_SIZE', 1}. + +{define, 'OCF_READ_DEFAULT_ERROR_DATA_REPORTING', 16#005A}. +{struct, read_default_error_data_reporting_rp, + [ + {uint8_t, status}, + {uint8_t, reporting} +]}. +{define, 'READ_DEFAULT_ERROR_DATA_REPORTING_RP_SIZE', 2}. + +{define, 'OCF_WRITE_DEFAULT_ERROR_DATA_REPORTING', 16#005B}. +{struct, write_default_error_data_reporting_cp, + [ + {uint8_t, reporting} +]}. +{define, 'WRITE_DEFAULT_ERROR_DATA_REPORTING_CP_SIZE', 1}. +{struct, write_default_error_data_reporting_rp, + [ + {uint8_t, status} +]}. +{define, 'WRITE_DEFAULT_ERROR_DATA_REPORTING_RP_SIZE', 1}. + +{define, 'OCF_ENHANCED_FLUSH', 16#005F}. +{struct, enhanced_flush_cp, + [ + {uint16_t, handle}, + {uint8_t, type} +]}. +{define, 'ENHANCED_FLUSH_CP_SIZE', 3}. + +{define, 'OCF_SEND_KEYPRESS_NOTIFY', 16#0060}. +{struct, send_keypress_notify_cp, + [ + {bdaddr_t, bdaddr}, + {uint8_t, type} +]}. +{define, 'SEND_KEYPRESS_NOTIFY_CP_SIZE', 7}. +{struct, send_keypress_notify_rp, + [ + {uint8_t, status} +]}. +{define, 'SEND_KEYPRESS_NOTIFY_RP_SIZE', 1}. + +{define, 'OCF_READ_LOGICAL_LINK_ACCEPT_TIMEOUT', 16#0061}. +{struct, read_log_link_accept_timeout_rp, + [ + {uint8_t, status}, + {uint16_t, timeout} +]}. +{define, 'READ_LOGICAL_LINK_ACCEPT_TIMEOUT_RP_SIZE', 3}. + +{define, 'OCF_WRITE_LOGICAL_LINK_ACCEPT_TIMEOUT', 16#0062}. +{struct, write_log_link_accept_timeout_cp, + [ + {uint16_t, timeout} +]}. +{define, 'WRITE_LOGICAL_LINK_ACCEPT_TIMEOUT_CP_SIZE', 2}. + +{define, 'OCF_SET_EVENT_MASK_PAGE_2', 16#0063}. + +{define, 'OCF_READ_LOCATION_DATA', 16#0064}. + +{define, 'OCF_WRITE_LOCATION_DATA', 16#0065}. + +{define, 'OCF_READ_FLOW_CONTROL_MODE', 16#0066}. + +{define, 'OCF_WRITE_FLOW_CONTROL_MODE', 16#0067}. + +{define, 'OCF_READ_ENHANCED_TRANSMIT_POWER_LEVEL', 16#0068}. +{struct, read_enhanced_transmit_power_level_rp, + [ + {uint8_t, status}, + {uint16_t, handle}, + {int8_t, level_gfsk}, + {int8_t, level_dqpsk}, + {int8_t, level_8dpsk} +]}. +{define, 'READ_ENHANCED_TRANSMIT_POWER_LEVEL_RP_SIZE', 6}. + +{define, 'OCF_READ_BEST_EFFORT_FLUSH_TIMEOUT', 16#0069}. +{struct, read_best_effort_flush_timeout_rp, + [ + {uint8_t, status}, + {uint32_t, timeout} +]}. +{define, 'READ_BEST_EFFORT_FLUSH_TIMEOUT_RP_SIZE', 5}. + +{define, 'OCF_WRITE_BEST_EFFORT_FLUSH_TIMEOUT', 16#006A}. +{struct, write_best_effort_flush_timeout_cp, + [ + {uint16_t, handle}, + {uint32_t, timeout} +]}. +{define, 'WRITE_BEST_EFFORT_FLUSH_TIMEOUT_CP_SIZE', 6}. +{struct, write_best_effort_flush_timeout_rp, + [ + {uint8_t, status} +]}. +{define, 'WRITE_BEST_EFFORT_FLUSH_TIMEOUT_RP_SIZE', 1}. + +{define, 'OCF_READ_LE_HOST_SUPPORTED', 16#006C}. +{struct, read_le_host_supported_rp, + [ + {uint8_t, status}, + {uint8_t, le}, + {uint8_t, simul} +]}. +{define, 'READ_LE_HOST_SUPPORTED_RP_SIZE', 3}. + +{define, 'OCF_WRITE_LE_HOST_SUPPORTED', 16#006D}. +{struct, write_le_host_supported_cp, + [ + {uint8_t, le}, + {uint8_t, simul} +]}. +{define, 'WRITE_LE_HOST_SUPPORTED_CP_SIZE', 2}. + +%% Informational Parameters +{define, 'OGF_INFO_PARAM', 16#04}. + +{define, 'OCF_READ_LOCAL_VERSION', 16#0001}. +{struct, read_local_version_rp, + [ + {uint8_t, status}, + {uint8_t, hci_ver}, + {uint16_t, hci_rev}, + {uint8_t, lmp_ver}, + {uint16_t, manufacturer}, + {uint16_t, lmp_subver} +]}. +{define, 'READ_LOCAL_VERSION_RP_SIZE', 9}. + +{define, 'OCF_READ_LOCAL_COMMANDS', 16#0002}. +{struct, read_local_commands_rp, + [ + {uint8_t, status}, + {{uint8_t,64}, commands} +]}. +{define, 'READ_LOCAL_COMMANDS_RP_SIZE', 65}. + +{define, 'OCF_READ_LOCAL_FEATURES', 16#0003}. +{struct, read_local_features_rp, + [ + {uint8_t, status}, + {{uint8_t,8}, features} +]}. +{define, 'READ_LOCAL_FEATURES_RP_SIZE', 9}. + +{define, 'OCF_READ_LOCAL_EXT_FEATURES', 16#0004}. +{struct, read_local_ext_features_cp, + [ + {uint8_t, page_num} +]}. +{define, 'READ_LOCAL_EXT_FEATURES_CP_SIZE', 1}. +{struct, read_local_ext_features_rp, + [ + {uint8_t, status}, + {uint8_t, page_num}, + {uint8_t, max_page_num}, + {{uint8_t,8}, features} +]}. +{define, 'READ_LOCAL_EXT_FEATURES_RP_SIZE', 11}. + +{define, 'OCF_READ_BUFFER_SIZE', 16#0005}. +{struct, read_buffer_size_rp, + [ + {uint8_t, status}, + {uint16_t, acl_mtu}, + {uint8_t, sco_mtu}, + {uint16_t, acl_max_pkt}, + {uint16_t, sco_max_pkt} +]}. +{define, 'READ_BUFFER_SIZE_RP_SIZE', 8}. + +{define, 'OCF_READ_COUNTRY_CODE', 16#0007}. + +{define, 'OCF_READ_BD_ADDR', 16#0009}. +{struct, read_bd_addr_rp, + [ + {uint8_t, status}, + {bdaddr_t, bdaddr} +]}. +{define, 'READ_BD_ADDR_RP_SIZE', 7}. + +%% Status params +{define, 'OGF_STATUS_PARAM', 16#05}. + +{define, 'OCF_READ_FAILED_CONTACT_COUNTER', 16#0001}. +{struct, read_failed_contact_counter_rp, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint8_t, counter} +]}. +{define, 'READ_FAILED_CONTACT_COUNTER_RP_SIZE', 4}. + +{define, 'OCF_RESET_FAILED_CONTACT_COUNTER', 16#0002}. +{struct, reset_failed_contact_counter_rp, + [ + {uint8_t, status}, + {uint16_t, handle} +]}. +{define, 'RESET_FAILED_CONTACT_COUNTER_RP_SIZE', 4}. + +{define, 'OCF_READ_LINK_QUALITY', 16#0003}. +{struct, read_link_quality_rp, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint8_t, link_quality} +]}. +{define, 'READ_LINK_QUALITY_RP_SIZE', 4}. + +{define, 'OCF_READ_RSSI', 16#0005}. +{struct, read_rssi_rp, + [ + {uint8_t, status}, + {uint16_t, handle}, + {int8_t, rssi} +]}. +{define, 'READ_RSSI_RP_SIZE', 4}. + +{define, 'OCF_READ_AFH_MAP', 16#0006}. +{struct, read_afh_map_rp, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint8_t, mode}, + {{uint8_t,10}, map} +]}. +{define, 'READ_AFH_MAP_RP_SIZE', 14}. + +{define, 'OCF_READ_CLOCK', 16#0007}. +{struct, read_clock_cp, + [ + {uint16_t, handle}, + {uint8_t, which_clock} +]}. +{define, 'READ_CLOCK_CP_SIZE', 3}. +{struct, read_clock_rp, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint32_t, clock}, + {uint16_t, accuracy} +]}. +{define, 'READ_CLOCK_RP_SIZE', 9}. + +{define, 'OCF_READ_LOCAL_AMP_INFO', 16#0009}. +{struct, read_local_amp_info_rp, + [ + {uint8_t, status}, + {uint8_t, amp_status}, + {uint32_t, total_bandwidth}, + {uint32_t, max_guaranteed_bandwidth}, + {uint32_t, min_latency}, + {uint32_t, max_pdu_size}, + {uint8_t, controller_type}, + {uint16_t, pal_caps}, + {uint16_t, max_amp_assoc_length}, + {uint32_t, max_flush_timeout}, + {uint32_t, best_effort_flush_timeout} +]}. +{define, 'READ_LOCAL_AMP_INFO_RP_SIZE', 31}. + +{define, 'OCF_READ_LOCAL_AMP_ASSOC', 16#000A}. +{struct, read_local_amp_assoc_cp, + [ + {uint8_t, handle}, + {uint16_t, len_so_far}, + {uint16_t, max_len} +]}. + +{struct, read_local_amp_assoc_rp, + [ + {uint8_t, status}, + {uint8_t, handle}, + {uint16_t, rem_len}, + {{uint8_t,0}, frag} +]}. + +{define, 'OCF_WRITE_REMOTE_AMP_ASSOC', 16#000B}. +{struct, write_remote_amp_assoc_cp, + [ + {uint8_t, handle}, + {uint16_t, length_so_far}, + {uint16_t, assoc_length}, + {{char, "?HCI_MAX_NAME_LENGTH"}, fragment} +]}. +{define, 'WRITE_REMOTE_AMP_ASSOC_CP_SIZE', 253}. +{struct, write_remote_amp_assoc_rp, + [ + {uint8_t, status}, + {uint8_t, handle} +]}. +{define, 'WRITE_REMOTE_AMP_ASSOC_RP_SIZE', 2}. + +%% Testing commands +{define, 'OGF_TESTING_CMD', 16#3e}. + +{define, 'OCF_READ_LOOPBACK_MODE', 16#0001}. + +{define, 'OCF_WRITE_LOOPBACK_MODE', 16#0002}. + +{define, 'OCF_ENABLE_DEVICE_UNDER_TEST_MODE', 16#0003}. + +{define, 'OCF_WRITE_SIMPLE_PAIRING_DEBUG_MODE', 16#0004}. +{struct, write_simple_pairing_debug_mode_cp, + [ + {uint8_t, mode} +]}. +{define, 'WRITE_SIMPLE_PAIRING_DEBUG_MODE_CP_SIZE', 1}. +{struct, write_simple_pairing_debug_mode_rp, + [ + {uint8_t, status} +]}. +{define, 'WRITE_SIMPLE_PAIRING_DEBUG_MODE_RP_SIZE', 1}. + +%% LE commands +{define, 'OGF_LE_CTL', 16#08}. + +{define, 'OCF_LE_SET_EVENT_MASK', 16#0001}. +{struct, le_set_event_mask_cp, + [ + {{uint8_t,8}, mask} +]}. +{define, 'LE_SET_EVENT_MASK_CP_SIZE', 8}. + +{define, 'OCF_LE_READ_BUFFER_SIZE', 16#0002}. +{struct, le_read_buffer_size_rp, + [ + {uint8_t, status}, + {uint16_t, pkt_len}, + {uint8_t, max_pkt} +]}. +{define, 'LE_READ_BUFFER_SIZE_RP_SIZE', 4}. + +{define, 'OCF_LE_READ_LOCAL_SUPPORTED_FEATURES', 16#0003}. +{struct, le_read_local_supported_features_rp, + [ + {uint8_t, status}, + {{uint8_t,8}, features} +]}. +{define, 'LE_READ_LOCAL_SUPPORTED_FEATURES_RP_SIZE', 9}. + +{define, 'OCF_LE_SET_RANDOM_ADDRESS', 16#0005}. +{struct, le_set_random_address_cp, + [ + {bdaddr_t, bdaddr} +]}. +{define, 'LE_SET_RANDOM_ADDRESS_CP_SIZE', 6}. + +{define, 'OCF_LE_SET_ADVERTISING_PARAMETERS', 16#0006}. +{struct, le_set_advertising_parameters_cp, + [ + {uint16_t, min_interval}, + {uint16_t, max_interval}, + {uint8_t, advtype}, + {uint8_t, own_bdaddr_type}, + {uint8_t, direct_bdaddr_type}, + {bdaddr_t, direct_bdaddr}, + {uint8_t, chan_map}, + {uint8_t, filter} +]}. +{define, 'LE_SET_ADVERTISING_PARAMETERS_CP_SIZE', 15}. + +{define, 'OCF_LE_READ_ADVERTISING_CHANNEL_TX_POWER', 16#0007}. +{struct, le_read_advertising_channel_tx_power_rp, + [ + {uint8_t, status}, + {uint8_t, level} +]}. +{define, 'LE_READ_ADVERTISING_CHANNEL_TX_POWER_RP_SIZE', 2}. + +{define, 'OCF_LE_SET_ADVERTISING_DATA', 16#0008}. +{struct, le_set_advertising_data_cp, + [ + {uint8_t, length}, + {{uint8_t,31}, data} +]}. +{define, 'LE_SET_ADVERTISING_DATA_CP_SIZE', 32}. + +{define, 'OCF_LE_SET_SCAN_RESPONSE_DATA', 16#0009}. +{struct, le_set_scan_response_data_cp, + [ + {uint8_t, length}, + {{uint8_t,31}, data} +]}. +{define, 'LE_SET_SCAN_RESPONSE_DATA_CP_SIZE', 32}. + +{define, 'OCF_LE_SET_ADVERTISE_ENABLE', 16#000A}. +{struct, le_set_advertise_enable_cp, + [ + {uint8_t, enable} +]}. +{define, 'LE_SET_ADVERTISE_ENABLE_CP_SIZE', 1}. + +{define, 'OCF_LE_SET_SCAN_PARAMETERS', 16#000B}. +{struct, le_set_scan_parameters_cp, + [ + {uint8_t, type}, + {uint16_t, interval}, + {uint16_t, window}, + {uint8_t, own_bdaddr_type}, + {uint8_t, filter} +]}. +{define, 'LE_SET_SCAN_PARAMETERS_CP_SIZE', 7}. + +{define, 'OCF_LE_SET_SCAN_ENABLE', 16#000C}. +{struct, le_set_scan_enable_cp, + [ + {uint8_t, enable}, + {uint8_t, filter_dup} +]}. +{define, 'LE_SET_SCAN_ENABLE_CP_SIZE', 2}. + +{define, 'OCF_LE_CREATE_CONN', 16#000D}. +{struct, le_create_connection_cp, + [ + {uint16_t, interval}, + {uint16_t, window}, + {uint8_t, initiator_filter}, + {uint8_t, peer_bdaddr_type}, + {bdaddr_t, peer_bdaddr}, + {uint8_t, own_bdaddr_type}, + {uint16_t, min_interval}, + {uint16_t, max_interval}, + {uint16_t, latency}, + {uint16_t, supervision_timeout}, + {uint16_t, min_ce_length}, + {uint16_t, max_ce_length} +]}. +{define, 'LE_CREATE_CONN_CP_SIZE', 25}. + +{define, 'OCF_LE_CREATE_CONN_CANCEL', 16#000E}. + +{define, 'OCF_LE_READ_WHITE_LIST_SIZE', 16#000F}. +{struct, le_read_white_list_size_rp, + [ + {uint8_t, status}, + {uint8_t, size} +]}. +{define, 'LE_READ_WHITE_LIST_SIZE_RP_SIZE', 2}. + +{define, 'OCF_LE_CLEAR_WHITE_LIST', 16#0010}. + +{define, 'OCF_LE_ADD_DEVICE_TO_WHITE_LIST', 16#0011}. +{struct, le_add_device_to_white_list_cp, + [ + {uint8_t, bdaddr_type}, + {bdaddr_t, bdaddr} +]}. +{define, 'LE_ADD_DEVICE_TO_WHITE_LIST_CP_SIZE', 7}. + +{define, 'OCF_LE_REMOVE_DEVICE_FROM_WHITE_LIST', 16#0012}. +{struct, le_remove_device_from_white_list_cp, + [ + {uint8_t, bdaddr_type}, + {bdaddr_t, bdaddr} +]}. +{define, 'LE_REMOVE_DEVICE_FROM_WHITE_LIST_CP_SIZE', 7}. + +{define, 'OCF_LE_CONN_UPDATE', 16#0013}. +{struct, le_connection_update_cp, + [ + {uint16_t, handle}, + {uint16_t, min_interval}, + {uint16_t, max_interval}, + {uint16_t, latency}, + {uint16_t, supervision_timeout}, + {uint16_t, min_ce_length}, + {uint16_t, max_ce_length} +]}. +{define, 'LE_CONN_UPDATE_CP_SIZE', 14}. + +{define, 'OCF_LE_SET_HOST_CHANNEL_CLASSIFICATION', 16#0014}. +{struct, le_set_host_channel_classification_cp, + [ + {{uint8_t,5}, map} +]}. +{define, 'LE_SET_HOST_CHANNEL_CLASSIFICATION_CP_SIZE', 5}. + +{define, 'OCF_LE_READ_CHANNEL_MAP', 16#0015}. +{struct, le_read_channel_map_cp, + [ + {uint16_t, handle} +]}. +{define, 'LE_READ_CHANNEL_MAP_CP_SIZE', 2}. +{struct, le_read_channel_map_rp, + [ + {uint8_t, status}, + {uint16_t, handle}, + {{uint8_t,5}, map} +]}. +{define, 'LE_READ_CHANNEL_MAP_RP_SIZE', 8}. + +{define, 'OCF_LE_READ_REMOTE_USED_FEATURES', 16#0016}. +{struct, le_read_remote_used_features_cp, + [ + {uint16_t, handle} +]}. +{define, 'LE_READ_REMOTE_USED_FEATURES_CP_SIZE', 2}. + +{define, 'OCF_LE_ENCRYPT', 16#0017}. +{struct, le_encrypt_cp, + [ + {{uint8_t,16}, key}, + {{uint8_t,16}, plaintext} +]}. +{define, 'LE_ENCRYPT_CP_SIZE', 32}. +{struct, le_encrypt_rp, + [ + {uint8_t, status}, + {{uint8_t,16}, data} +]}. +{define, 'LE_ENCRYPT_RP_SIZE', 17}. + +{define, 'OCF_LE_RAND', 16#0018}. +{struct, le_rand_rp, + [ + {uint8_t, status}, + {uint64_t, random} +]}. +{define, 'LE_RAND_RP_SIZE', 9}. + +{define, 'OCF_LE_START_ENCRYPTION', 16#0019}. +{struct, le_start_encryption_cp, + [ + {uint16_t, handle}, + {uint64_t, random}, + {uint16_t, diversifier}, + {{uint8_t,16}, key} +]}. +{define, 'LE_START_ENCRYPTION_CP_SIZE', 28}. + +{define, 'OCF_LE_LTK_REPLY', 16#001A}. +{struct, le_ltk_reply_cp, + [ + {uint16_t, handle}, + {{uint8_t,16}, key} +]}. +{define, 'LE_LTK_REPLY_CP_SIZE', 18}. +{struct, le_ltk_reply_rp, + [ + {uint8_t, status}, + {uint16_t, handle} +]}. +{define, 'LE_LTK_REPLY_RP_SIZE', 3}. + +{define, 'OCF_LE_LTK_NEG_REPLY', 16#001B}. +{struct, le_ltk_neg_reply_cp, + [ + {uint16_t, handle} +]}. +{define, 'LE_LTK_NEG_REPLY_CP_SIZE', 2}. +{struct, le_ltk_neg_reply_rp, + [ + {uint8_t, status}, + {uint16_t, handle} +]}. +{define, 'LE_LTK_NEG_REPLY_RP_SIZE', 3}. + +{define, 'OCF_LE_READ_SUPPORTED_STATES', 16#001C}. +{struct, le_read_supported_states_rp, + [ + {uint8_t, status}, + {uint64_t, states} +]}. +{define, 'LE_READ_SUPPORTED_STATES_RP_SIZE', 9}. + +{define, 'OCF_LE_RECEIVER_TEST', 16#001D}. +{struct, le_receiver_test_cp, + [ + {uint8_t, frequency} +]}. +{define, 'LE_RECEIVER_TEST_CP_SIZE', 1}. + +{define, 'OCF_LE_TRANSMITTER_TEST', 16#001E}. +{struct, le_transmitter_test_cp, + [ + {uint8_t, frequency}, + {uint8_t, length}, + {uint8_t, payload} +]}. +{define, 'LE_TRANSMITTER_TEST_CP_SIZE', 3}. + +{define, 'OCF_LE_TEST_END', 16#001F}. +{struct, le_test_end_rp, + [ + {uint8_t, status}, + {uint16_t, num_pkts} +]}. +{define, 'LE_TEST_END_RP_SIZE', 3}. + +%% Vendor specific commands +{define, 'OGF_VENDOR_CMD', 16#3f}. + +%% ---- HCI Events ---- + +{define, 'EVT_INQUIRY_COMPLETE', 16#01}. + +{define, 'EVT_INQUIRY_RESULT', 16#02}. +{struct, inquiry_info, + [ + {bdaddr_t, bdaddr}, + {uint8_t, pscan_rep_mode}, + {uint8_t, pscan_period_mode}, + {uint8_t, pscan_mode}, + {{uint8_t,3}, dev_class}, + {uint16_t, clock_offset} +]}. +{define, 'INQUIRY_INFO_SIZE', 14}. + +{define, 'EVT_CONN_COMPLETE', 16#03}. +{struct, evt_conn_complete, + [ + {uint8_t, status}, + {uint16_t, handle}, + {bdaddr_t, bdaddr}, + {uint8_t, link_type}, + {uint8_t, encr_mode} +]}. +{define, 'EVT_CONN_COMPLETE_SIZE', 13}. %% should be 11? + +{define, 'EVT_CONN_REQUEST', 16#04}. +{struct, evt_conn_request, + [ + {bdaddr_t, bdaddr}, + {{uint8_t,3}, dev_class}, + {uint8_t, link_type} +]}. +{define, 'EVT_CONN_REQUEST_SIZE', 10}. + +{define, 'EVT_DISCONN_COMPLETE', 16#05}. +{struct, evt_disconn_complete, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint8_t, reason} +]}. +{define, 'EVT_DISCONN_COMPLETE_SIZE', 4}. + +{define, 'EVT_AUTH_COMPLETE', 16#06}. +{struct, evt_auth_complete, + [ + {uint8_t, status}, + {uint16_t, handle} +]}. +{define, 'EVT_AUTH_COMPLETE_SIZE', 3}. + +{define, 'EVT_REMOTE_NAME_REQ_COMPLETE', 16#07}. +{struct, evt_remote_name_req_complete, + [ + {uint8_t, status}, + {bdaddr_t, bdaddr}, + {{char,"?HCI_MAX_NAME_LENGTH"}, name} +]}. +{define, 'EVT_REMOTE_NAME_REQ_COMPLETE_SIZE', 255}. + +{define, 'EVT_ENCRYPT_CHANGE', 16#08}. +{struct, evt_encrypt_change, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint8_t, encrypt} +]}. +{define, 'EVT_ENCRYPT_CHANGE_SIZE', 5}. + +{define, 'EVT_CHANGE_CONN_LINK_KEY_COMPLETE', 16#09}. +{struct, evt_change_conn_link_key_complete, + [ + {uint8_t, status}, + {uint16_t, handle} +]}. +{define, 'EVT_CHANGE_CONN_LINK_KEY_COMPLETE_SIZE', 3}. + +{define, 'EVT_MASTER_LINK_KEY_COMPLETE', 16#0A}. +{struct, evt_master_link_key_complete, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint8_t, key_flag} +]}. +{define, 'EVT_MASTER_LINK_KEY_COMPLETE_SIZE', 4}. + +{define, 'EVT_READ_REMOTE_FEATURES_COMPLETE', 16#0B}. +{struct, evt_read_remote_features_complete, + [ + {uint8_t, status}, + {uint16_t, handle}, + {{uint8_t,8}, features} +]}. +{define, 'EVT_READ_REMOTE_FEATURES_COMPLETE_SIZE', 11}. + +{define, 'EVT_READ_REMOTE_VERSION_COMPLETE', 16#0C}. +{struct, evt_read_remote_version_complete, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint8_t, lmp_ver}, + {uint16_t, manufacturer}, + {uint16_t, lmp_subver} +]}. +{define, 'EVT_READ_REMOTE_VERSION_COMPLETE_SIZE', 8}. + +{define, 'EVT_QOS_SETUP_COMPLETE', 16#0D}. +{struct, evt_qos_setup_complete, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint8_t, flags}, %% Reserved + {hci_qos_t, qos} +]}. +{define, 'EVT_QOS_SETUP_COMPLETE_SIZE', "(4 + ?HCI_QOS_CP_SIZE)"}. + +{define, 'EVT_CMD_COMPLETE', 16#0E}. +{struct, evt_cmd_complete, + [ + {uint8_t, ncmd}, + {uint16_t, opcode} +]}. +{define, 'EVT_CMD_COMPLETE_SIZE', 3}. + +{define, 'EVT_CMD_STATUS', 16#0F}. +{struct, evt_cmd_status, + [ + {uint8_t, status}, + {uint8_t, ncmd}, + {uint16_t, opcode} +]}. +{define, 'EVT_CMD_STATUS_SIZE', 4}. + +{define, 'EVT_HARDWARE_ERROR', 16#10}. +{struct, evt_hardware_error, + [ + {uint8_t, code} +]}. +{define, 'EVT_HARDWARE_ERROR_SIZE', 1}. + +{define, 'EVT_FLUSH_OCCURRED', 16#11}. +{struct, evt_flush_occured, + [ + {uint16_t, handle} +]}. +{define, 'EVT_FLUSH_OCCURRED_SIZE', 2}. + +{define, 'EVT_ROLE_CHANGE', 16#12}. +{struct, evt_role_change, + [ + {uint8_t, status}, + {bdaddr_t, bdaddr}, + {uint8_t, role} +]}. +{define, 'EVT_ROLE_CHANGE_SIZE', 8}. + +{define, 'EVT_NUM_COMP_PKTS', 16#13}. +{struct, evt_num_comp_pkts, + [ + {uint8_t, num_hndl} + %% variable length part + %% {list, num_hndl, uint16_t, hndls} +]}. +{define, 'EVT_NUM_COMP_PKTS_SIZE', 1}. + +{define, 'EVT_MODE_CHANGE', 16#14}. +{struct, evt_mode_change, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint8_t, mode}, + {uint16_t, interval} +]}. +{define, 'EVT_MODE_CHANGE_SIZE', 6}. + +{define, 'EVT_RETURN_LINK_KEYS', 16#15}. +{struct, evt_return_link_keys, + [ + {uint8_t, num_keys} + %% variable length part + %% {list, num_keys, {binary,16}, keys} +]}. +{define, 'EVT_RETURN_LINK_KEYS_SIZE', 1}. + +{define, 'EVT_PIN_CODE_REQ', 16#16}. +{struct, evt_pin_code_req, + [ + {bdaddr_t, bdaddr} +]}. +{define, 'EVT_PIN_CODE_REQ_SIZE', 6}. + +{define, 'EVT_LINK_KEY_REQ', 16#17}. +{struct, evt_link_key_req, + [ + {bdaddr_t, bdaddr} +]}. +{define, 'EVT_LINK_KEY_REQ_SIZE', 6}. + +{define, 'EVT_LINK_KEY_NOTIFY', 16#18}. +{struct, evt_link_key_notify, + [ + {bdaddr_t, bdaddr}, + {{uint8_t,16}, link_key}, + {uint8_t, key_type} +]}. +{define, 'EVT_LINK_KEY_NOTIFY_SIZE', 23}. + +{define, 'EVT_LOOPBACK_COMMAND', 16#19}. + +{define, 'EVT_DATA_BUFFER_OVERFLOW', 16#1A}. +{struct, evt_data_buffer_overflow, + [ + {uint8_t, link_type} +]}. +{define, 'EVT_DATA_BUFFER_OVERFLOW_SIZE', 1}. + +{define, 'EVT_MAX_SLOTS_CHANGE', 16#1B}. +{struct, evt_max_slots_change, + [ + {uint16_t, handle}, + {uint8_t, max_slots} +]}. +{define, 'EVT_MAX_SLOTS_CHANGE_SIZE', 3}. + +{define, 'EVT_READ_CLOCK_OFFSET_COMPLETE', 16#1C}. +{struct, evt_read_clock_offset_complete, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint16_t, clock_offset} +]}. +{define, 'EVT_READ_CLOCK_OFFSET_COMPLETE_SIZE', 5}. + +{define, 'EVT_CONN_PTYPE_CHANGED', 16#1D}. +{struct, evt_conn_ptype_changed, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint16_t, ptype} +]}. +{define, 'EVT_CONN_PTYPE_CHANGED_SIZE', 5}. + +{define, 'EVT_QOS_VIOLATION', 16#1E}. +{struct, evt_qos_violation, + [ + {uint16_t, handle} +]}. +{define, 'EVT_QOS_VIOLATION_SIZE', 2}. + +{define, 'EVT_PSCAN_REP_MODE_CHANGE', 16#20}. +{struct, evt_pscan_rep_mode_change, + [ + {bdaddr_t, bdaddr}, + {uint8_t, pscan_rep_mode} +]}. +{define, 'EVT_PSCAN_REP_MODE_CHANGE_SIZE', 7}. + +{define, 'EVT_FLOW_SPEC_COMPLETE', 16#21}. +{struct, evt_flow_spec_complete, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint8_t, flags}, + {uint8_t, direction}, + {hci_qos_t, qos} +]}. +{define, 'EVT_FLOW_SPEC_COMPLETE_SIZE', "(5 + ?HCI_QOS_CP_SIZE)"}. + +{define, 'EVT_INQUIRY_RESULT_WITH_RSSI', 16#22}. +{struct, inquiry_info_with_rssi, + [ + {bdaddr_t, bdaddr}, + {uint8_t, pscan_rep_mode}, + {uint8_t, pscan_period_mode}, + {{uint8_t,3}, dev_class}, + {uint16_t, clock_offset}, + {int8_t, rssi} +]}. +{define, 'INQUIRY_INFO_WITH_RSSI_SIZE', 14}. +{struct, inquiry_info_with_rssi_and_pscan_mode, + [ + {bdaddr_t, bdaddr}, + {uint8_t, pscan_rep_mode}, + {uint8_t, pscan_period_mode}, + {uint8_t, pscan_mode}, + {{uint8_t,3}, dev_class}, + {uint16_t, clock_offset}, + {int8_t, rssi} +]}. +{define, 'INQUIRY_INFO_WITH_RSSI_AND_PSCAN_MODE_SIZE', 15}. + +{define, 'EVT_READ_REMOTE_EXT_FEATURES_COMPLETE', 16#23}. +{struct, evt_read_remote_ext_features_complete, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint8_t, page_num}, + {uint8_t, max_page_num}, + {{uint8_t,8}, features} +]}. +{define, 'EVT_READ_REMOTE_EXT_FEATURES_COMPLETE_SIZE', 13}. + +{define, 'EVT_SYNC_CONN_COMPLETE', 16#2C}. +{struct, evt_sync_conn_complete, + [ + {uint8_t, status}, + {uint16_t, handle}, + {bdaddr_t, bdaddr}, + {uint8_t, link_type}, + {uint8_t, trans_interval}, + {uint8_t, retrans_window}, + {uint16_t, rx_pkt_len}, + {uint16_t, tx_pkt_len}, + {uint8_t, air_mode} +]}. +{define, 'EVT_SYNC_CONN_COMPLETE_SIZE', 17}. + +{define, 'EVT_SYNC_CONN_CHANGED', 16#2D}. +{struct, evt_sync_conn_changed, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint8_t, trans_interval}, + {uint8_t, retrans_window}, + {uint16_t, rx_pkt_len}, + {uint16_t, tx_pkt_len} +]}. +{define, 'EVT_SYNC_CONN_CHANGED_SIZE', 9}. + +{define, 'EVT_SNIFF_SUBRATING', 16#2E}. +{struct, evt_sniff_subrating, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint16_t, max_tx_latency}, + {uint16_t, max_rx_latency}, + {uint16_t, min_remote_timeout}, + {uint16_t, min_local_timeout} +]}. +{define, 'EVT_SNIFF_SUBRATING_SIZE', 11}. + +{define, 'EVT_EXTENDED_INQUIRY_RESULT', 16#2F}. +{struct, extended_inquiry_info, + [ + {bdaddr_t, bdaddr}, + {uint8_t, pscan_rep_mode}, + {uint8_t, pscan_period_mode}, + {{uint8_t,3}, dev_class}, + {uint16_t, clock_offset}, + {int8_t, rssi}, + {{uint8_t,"?HCI_MAX_EIR_LENGTH"}, data} +]}. +{define, 'EXTENDED_INQUIRY_INFO_SIZE', 254}. + +{define, 'EVT_ENCRYPTION_KEY_REFRESH_COMPLETE', 16#30}. +{struct, evt_encryption_key_refresh_complete, + [ + {uint8_t, status}, + {uint16_t, handle} +]}. +{define, 'EVT_ENCRYPTION_KEY_REFRESH_COMPLETE_SIZE', 3}. + +{define, 'EVT_IO_CAPABILITY_REQUEST', 16#31}. +{struct, evt_io_capability_request, + [ + {bdaddr_t, bdaddr} +]}. +{define, 'EVT_IO_CAPABILITY_REQUEST_SIZE', 6}. + +{define, 'EVT_IO_CAPABILITY_RESPONSE', 16#32}. +{struct, evt_io_capability_response, + [ + {bdaddr_t, bdaddr}, + {uint8_t, capability}, + {uint8_t, oob_data}, + {uint8_t, authentication} +]}. +{define, 'EVT_IO_CAPABILITY_RESPONSE_SIZE', 9}. + +{define, 'EVT_USER_CONFIRM_REQUEST', 16#33}. +{struct, evt_user_confirm_request, + [ + {bdaddr_t, bdaddr}, + {uint32_t, passkey} +]}. +{define, 'EVT_USER_CONFIRM_REQUEST_SIZE', 10}. + +{define, 'EVT_USER_PASSKEY_REQUEST', 16#34}. +{struct, evt_user_passkey_request, + [ + {bdaddr_t, bdaddr} +]}. +{define, 'EVT_USER_PASSKEY_REQUEST_SIZE', 6}. + +{define, 'EVT_REMOTE_OOB_DATA_REQUEST', 16#35}. +{struct, evt_remote_oob_data_request, + [ + {bdaddr_t, bdaddr} +]}. +{define, 'EVT_REMOTE_OOB_DATA_REQUEST_SIZE', 6}. + +{define, 'EVT_SIMPLE_PAIRING_COMPLETE', 16#36}. +{struct, evt_simple_pairing_complete, + [ + {uint8_t, status}, + {bdaddr_t, bdaddr} +]}. +{define, 'EVT_SIMPLE_PAIRING_COMPLETE_SIZE', 7}. + +{define, 'EVT_LINK_SUPERVISION_TIMEOUT_CHANGED', 16#38}. +{struct, evt_link_supervision_timeout_changed, + [ + {uint16_t, handle}, + {uint16_t, timeout} +]}. +{define, 'EVT_LINK_SUPERVISION_TIMEOUT_CHANGED_SIZE', 4}. + +{define, 'EVT_ENHANCED_FLUSH_COMPLETE', 16#39}. +{struct, evt_enhanced_flush_complete, + [ + {uint16_t, handle} +]}. +{define, 'EVT_ENHANCED_FLUSH_COMPLETE_SIZE', 2}. + +{define, 'EVT_USER_PASSKEY_NOTIFY', 16#3B}. +{struct, evt_user_passkey_notify, + [ + {bdaddr_t, bdaddr}, + {uint32_t, passkey}, + {uint8_t, entered} +]}. +{define, 'EVT_USER_PASSKEY_NOTIFY_SIZE', 11}. + +{define, 'EVT_KEYPRESS_NOTIFY', 16#3C}. +{struct, evt_keypress_notify, + [ + {bdaddr_t, bdaddr}, + {uint8_t, type} +]}. +{define, 'EVT_KEYPRESS_NOTIFY_SIZE', 7}. + +{define, 'EVT_REMOTE_HOST_FEATURES_NOTIFY', 16#3D}. +{struct, evt_remote_host_features_notify, + [ + {bdaddr_t, bdaddr}, + {{uint8_t,8}, features} +]}. +{define, 'EVT_REMOTE_HOST_FEATURES_NOTIFY_SIZE', 14}. + +{define, 'EVT_LE_META_EVENT', 16#3E}. +{struct, evt_le_meta_event, + [ + {uint8_t, subevent}, + {{uint8_t,0}, data} +]}. +{define, 'EVT_LE_META_EVENT_SIZE', 1}. + +{define, 'EVT_LE_CONN_COMPLETE', 16#01}. +{struct, evt_le_connection_complete, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint8_t, role}, + {uint8_t, peer_bdaddr_type}, + {bdaddr_t, peer_bdaddr}, + {uint16_t, interval}, + {uint16_t, latency}, + {uint16_t, supervision_timeout}, + {uint8_t, master_clock_accuracy} +]}. +{define, 'EVT_LE_CONN_COMPLETE_SIZE', 18}. + +{define, 'EVT_LE_ADVERTISING_REPORT', 16#02}. +{struct, le_advertising_info, + [ + {uint8_t, evt_type}, + {uint8_t, bdaddr_type}, + {bdaddr_t, bdaddr}, + {uint8_t, length}, + {{uint8_t,0}, data} +]}. +{define, 'LE_ADVERTISING_INFO_SIZE', 9}. + +{define, 'EVT_LE_CONN_UPDATE_COMPLETE', 16#03}. +{struct, evt_le_connection_update_complete, + [ + {uint8_t, status}, + {uint16_t, handle}, + {uint16_t, interval}, + {uint16_t, latency}, + {uint16_t, supervision_timeout} +]}. +{define, 'EVT_LE_CONN_UPDATE_COMPLETE_SIZE', 9}. + +{define, 'EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE', 16#04}. +{struct, evt_le_read_remote_used_features_complete, + [ + {uint8_t, status}, + {uint16_t, handle}, + {{uint8_t,8}, features} +]}. +{define, 'EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE_SIZE', 11}. + +{define, 'EVT_LE_LTK_REQUEST', 16#05}. +{struct, evt_le_long_term_key_request, + [ + {uint16_t, handle}, + {uint64_t, random}, + {uint16_t, diversifier} +]}. +{define, 'EVT_LE_LTK_REQUEST_SIZE', 12}. + +{define, 'EVT_PHYSICAL_LINK_COMPLETE', 16#40}. +{struct, evt_physical_link_complete, + [ + {uint8_t, status}, + {uint8_t, handle} +]}. +{define, 'EVT_PHYSICAL_LINK_COMPLETE_SIZE', 2}. + +{define, 'EVT_CHANNEL_SELECTED', 16#41}. + +{define, 'EVT_DISCONNECT_PHYSICAL_LINK_COMPLETE', 16#42}. +{struct, evt_disconn_physical_link_complete, + [ + {uint8_t, status}, + {uint8_t, handle}, + {uint8_t, reason} +]}. +{define, 'EVT_DISCONNECT_PHYSICAL_LINK_COMPLETE_SIZE', 3}. + +{define, 'EVT_PHYSICAL_LINK_LOSS_EARLY_WARNING', 16#43}. +{struct, evt_physical_link_loss_warning, + [ + {uint8_t, handle}, + {uint8_t, reason} +]}. +{define, 'EVT_PHYSICAL_LINK_LOSS_WARNING_SIZE', 2}. + +{define, 'EVT_PHYSICAL_LINK_RECOVERY', 16#44}. +{struct, evt_physical_link_recovery, + [ + {uint8_t, handle} +]}. +{define, 'EVT_PHYSICAL_LINK_RECOVERY_SIZE', 1}. + +{define, 'EVT_LOGICAL_LINK_COMPLETE', 16#45}. +{struct, evt_logical_link_complete, + [ + {uint8_t, status}, + {uint16_t, log_handle}, + {uint8_t, handle}, + {uint8_t, tx_flow_id} +]}. +{define, 'EVT_LOGICAL_LINK_COMPLETE_SIZE', 5}. + +{define, 'EVT_DISCONNECT_LOGICAL_LINK_COMPLETE', 16#46}. + +{define, 'EVT_FLOW_SPEC_MODIFY_COMPLETE', 16#47}. +{struct, evt_flow_spec_modify_complete, + [ + {uint8_t, status}, + {uint16_t, handle} +]}. +{define, 'EVT_FLOW_SPEC_MODIFY_COMPLETE_SIZE', 3}. + +{define, 'EVT_NUMBER_COMPLETED_BLOCKS', 16#48}. + +{define, 'EVT_AMP_STATUS_CHANGE', 16#4D}. +{struct, evt_amp_status_change, + [ + {uint8_t, status}, + {uint8_t, amp_status} +]}. +{define, 'EVT_AMP_STATUS_CHANGE_SIZE', 2}. + +{define, 'EVT_TESTING', 16#FE}. + +{define, 'EVT_VENDOR', 16#FF}. + +%% Internal events generated by BlueZ stack +{define, 'EVT_STACK_INTERNAL', 16#FD}. +{struct, evt_stack_internal, + [ + {uint16_t, type}, + {{uint8_t,0}, data} +]}. +{define, 'EVT_STACK_INTERNAL_SIZE', 2}. + +{define, 'EVT_SI_DEVICE', 16#01}. +{struct, evt_si_device, + [ + {uint16_t, event}, + {uint16_t, dev_id} +]}. +{define, 'EVT_SI_DEVICE_SIZE', 4}. + diff --git a/deps/bt/rebar.config b/deps/bt/rebar.config new file mode 100644 index 0000000..b9f2f2a --- /dev/null +++ b/deps/bt/rebar.config @@ -0,0 +1,37 @@ +%% -*- erlang -*- +%% Config file for bt-application +%% +%% +{deps, [ {dthread, ".*", {git, "https://github.com/tonyrog/dthread.git"}}]}. +{erl_opts, [debug_info, fail_on_warning]}. + +{port_env, [ + {"CFLAGS", "$CFLAGS -DDLOG_DEFAULT=DLOG_INFO -Wall -Wextra -Wswitch-default -Wswitch-enum -fno-common -DNO_ERL_DRIVER -I $REBAR_DEPS_DIR"}, + {"(darwin)", "CFLAGS", "$CFLAGS -ObjC"}, + {"(darwin)", "LDFLAGS", "$LDFLAGS -framework IOBluetooth -framework CoreFoundation -framework Foundation $REBAR_DEPS_DIR/dthread/c_src/dlog.o"}, + + {"(linux)", "LDFLAGS", "$LDFLAGS -lbluetooth $REBAR_DEPS_DIR/dthread/c_src/dlog.o"} + ]}. + +%% first version is a regular port program +{port_specs, [ + {"(darwin)", "priv/bt", [ "c_src/bt_macos_drv.c", + "c_src/bt_sub.c" + ]}, + {"(linux)", "priv/bt", [ "c_src/bt_linux_drv.c", + "c_src/bt_sub.c", + "c_src/bt_poll.c" + ]}, + + {"(linux)", "priv/hci_drv.so", + [ "c_src/hci_drv.c" ]} + + ]}. + +{erl_first_files, + [ + "src/bt_make_hci_api.erl" + ]}. + + + diff --git a/deps/bt/src/bt.app.src b/deps/bt/src/bt.app.src new file mode 100644 index 0000000..bbc29a0 --- /dev/null +++ b/deps/bt/src/bt.app.src @@ -0,0 +1,11 @@ + +{application, bt, + [{description, "Bluetooth driver"}, + {vsn, git}, + {registered, [bt_drv]}, + {mod,{bt_app,[]}}, + {env, []}, + {applications,[kernel,stdlib]} + ]}. + + diff --git a/deps/bt/src/bt.erl b/deps/bt/src/bt.erl new file mode 100644 index 0000000..06beb35 --- /dev/null +++ b/deps/bt/src/bt.erl @@ -0,0 +1,355 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +%%% File : bt.erl +%%% Author : Tony Rogvall <tony@iMac.local> +%%% Description : Bluetooth utilities +%%% Created : 31 Jan 2006 by Tony Rogvall <tony@iMac.local> + +-module(bt). + +-export([start/0, stop/0]). +-export([i/0, i/1]). +-export([li/0]). +-export([s/1, s/2]). +-export([debug/0, debug/1]). +-export([scan/1, scan/3]). +-export([getaddr/1, getaddr_by_name/1]). +-export([format_address/1]). +-export([service_info/1, service_info/2]). +-export([rfcomm_channel/2]). +-export([decode_service/1]). + +-import(lists, [foreach/2, map/2]). + +-include("../include/bt.hrl"). +-include("../include/uuid.hrl"). +-include("../include/sdp.hrl"). + +start() -> + application:start(bt). + +stop() -> %% restart= + bt_drv:stop(). + + +debug() -> + bt_drv:debug(debug). + +debug(true) -> + bt_drv:debug(debug); +debug(false) -> + bt_drv:debug(none); +debug(Level) -> + bt_drv:debug(Level). + +%% +%% Dump device information +%% +i() -> + {ok,Devices} = bt_drv:devices(), + foreach(fun(A) -> i(A) end, Devices). + +i(paired) -> + {ok, Devices} = bt_drv:paired_devices(), + foreach(fun(A) -> i(A) end, Devices); +i(favorite) -> + {ok, Devices} = bt_drv:favorite_devices(), + foreach(fun(A) -> i(A) end, Devices); +i(BtAddr) -> + {ok,A} = getaddr(BtAddr), + case bt_drv:device_info(A, [inquiry,update]) of + {ok, [{inquiry,?never},{update,_}]} -> + io:format("Address: ~s\n", [format_address(A)]); + {ok, [{inquiry,_InQuiry},{update,Update}]}-> + io:format("Address: ~s\n", [format_address(A)]), + {ok,DevInfo} = bt_drv:device_info(A,[name, + is_paired, + is_favorite, + is_connected, + class]), + foreach(fun({class,Value}) -> + {Service,Major,Minor} = bt_drv:decode_class(Value), + io:format(" major: ~p\n", [Major]), + io:format(" minor: ~p\n", [Minor]), + io:format(" service: ~p\n", [Service]); + ({name,Name}) -> + io:format(" name: ~s\n", [Name]); + ({What,Value}) -> + io:format(" ~p: ~p\n", [What,Value]) + end, DevInfo), + if Update == ?never -> + ok; + true -> + {ok,SdpInfo} = bt_drv:service_info(A), + io:format(" Profiles:"), + foreach( + fun(Service) -> + As = map(fun(A1) -> + bt_sdp:decode(A1) + end, Service), + case lists:keysearch(256, 1, As) of + false -> ok; + {value,{_,{text,Name}}} -> + io:format(" ~s,", [Name]) + end + end, SdpInfo), + io:format("\n") + end; + Error -> Error + end. + +%% +%% Dump information about the local bluetooth adapter +%% +li() -> + case bt_drv:local_info([name,class,address,discoverable,power_state]) of + {ok,Info} -> + io:format("Local Adapter:\n",[]), + foreach(fun({class,Value}) -> + {Service,Major,Minor} = bt_drv:decode_class(Value), + io:format(" major: ~p\n", [Major]), + io:format(" minor: ~p\n", [Minor]), + io:format(" service: ~p\n", [Service]); + ({name,Name}) -> + io:format(" name: ~s\n", [Name]); + ({address,Addr}) -> + io:format(" addr: ~s\n", [format_address(Addr)]); + ({What,Value}) -> + io:format(" ~p: ~p\n", [What,Value]) + end, Info); + Error -> + Error + end. + +%% +%% Dump service information +%% s(Addr [UUID]) +%% +%% Addr is either the Bluetooth address (as string or tuple) or +%% the name of the device (a bit slow) +%% UUID is UUID16 | UUID32 | UUID128 | Symbolic-Name +%% +s(Addr) -> + case bt_drv:service_info(Addr,<<>>) of + {ok, Info} when is_list(Info) -> + foreach( + fun(Service) when is_list(Service) -> + s_serv(Service), + io:format("\n") + end, Info); + Error -> + Error + end. + +s(Addr, UUID) when is_binary(UUID), size(UUID) > 0 -> + case bt_drv:service_info(Addr,UUID) of + {ok, Service} -> + s_serv(Service); + Error -> + Error + end; +s(Addr, Name) when is_list(Name) -> + UUID = bt_sdp:string_to_uuid(Name), + case bt_drv:service_info(Addr,UUID) of + {ok, Service} -> + s_serv(Service); + Error -> + Error + end. + +s_serv(Attributes=[A1|_]) when is_binary(A1) -> + Attrs0 = map(fun(A) -> bt_sdp:decode(A) end, Attributes), + LangList = [{_,_,Base}|_] = get_language_base(Attrs0, 256), + UUID = case lists:keysearch(?ATTR_ServiceClassIDList, 1, Attrs0) of + false -> <<>>; + {value,{_,{sequence,[{uuid,UUID0}|_]}}} -> UUID0 + end, + %% Fixme: present ServiceName,ServiceDescription,ProviderName with language base + %% and delete them before printing further data. + ServiceNames = s_attr(?ATTR_ServiceName, LangList, Attrs0, []), + ServiceDescs = s_attr(?ATTR_ServiceDescription, LangList, Attrs0, []), + ProviderNames = s_attr(?ATTR_ProviderName, LangList, Attrs0, []), + + case ServiceNames of + [{_,{text,Name}}|_] -> io:format("Service Name: ~s\n", [Name]); + [] -> io:format("Service Name: None\n", []) + end, + + case ServiceDescs of + [{_,{text,Desc}}|_] -> io:format(" Service Description: ~s\n", [Desc]); + [] -> ok + end, + + case ProviderNames of + [{_,{text,Prov}}|_] -> io:format(" Provider Name: ~s\n", [Prov]); + [] -> ok + end, + Attrs1 = Attrs0 -- (ServiceNames++ServiceDescs++ProviderNames), + Attrs = lists:keydelete(?ATTR_LanguageBaseAttributeIDList, 1, Attrs1), + foreach( + fun ({ID,Value}) -> + AttrName = bt_sdp:attribute_to_string(ID,Base,UUID), + io:format(" ~s: ~s\n", [AttrName, bt_sdp:value_to_string(Value)]) + end, Attrs). + +s_attr(Attr, [{_Lang,_Env,Base}|Ls], As, Acc) -> + case lists:keysearch(Attr+Base, 1, As) of + false -> + s_attr(Attr, Ls, As, Acc); + {value,Value} -> + s_attr(Attr, Ls, As, [Value|Acc]) + end; +s_attr(_Attr, [], _As, Acc) -> + lists:reverse(Acc). + + +get_language_base(Attrs,Default) -> + case get_languages(Attrs) of + [] -> [{25966,106,Default}]; + Ls -> Ls + end. + +get_languages(Attrs) -> + case lists:keysearch(?ATTR_LanguageBaseAttributeIDList, 1, Attrs) of + {value,{_,{sequence,Seq}}} -> get_lang3(Seq); + _ -> [] + end. + +get_lang3([{uint16,Lang},{uint16,Encoding},{uint16,Base}|Ls]) -> + [{Lang,Encoding,Base} | get_lang3(Ls)]; +get_lang3([]) -> + []. + + +%% +%% Decode all services on a device +%% + +service_info(Addr) -> + case bt_drv:service_info(Addr,<<>>) of + {ok, ServiceList} when is_list(hd(ServiceList)) -> + map(fun(Attributes) -> + decode_service(Attributes) + end, ServiceList); + Error -> + Error + end. +%% +%% +%% +service_info(Addr, Service) when is_list(Service) -> + service_info(Addr, bt_sdp:string_to_uuid(Service)); +service_info(Addr, UUID) when is_binary(UUID), size(UUID) > 0 -> + case bt_drv:service_info(Addr,UUID) of + {ok, Service} when is_binary(hd(Service)) -> + decode_service(Service); + Error -> + Error + end. +%% +%% Extract rfcomm channel for the given service +%% +rfcomm_channel(Addr, Service) when is_list(Service) -> + rfcomm_channel(Addr, bt_sdp:string_to_uuid(Service)); +rfcomm_channel(Addr, UUID) when is_binary(UUID), size(UUID) > 0 -> + case bt_drv:service_info(Addr,UUID) of + {ok, Service} when is_binary(hd(Service)) -> + As = decode_service(Service), + case lists:keysearch(?ATTR_ProtocolDescriptorList, 1, As) of + {value,{_, {sequence,[{sequence,["L2CAP"]}, + {sequence,["RFCOMM",Channel]}|_]}}} -> + {ok,Channel}; + _ -> + {error, no_channel} + end; + Error -> Error + end. + +%% +%% Decode a binary encoded SDP list: +%% [ binary(<attribute><value>) ] in SDP format +%% + +decode_service(Attributes) when is_binary(hd(Attributes)) -> + Attrs = map(fun(A) -> bt_sdp:decode(A) end, Attributes), + map(fun({ID,Value}) -> + {ID, bt_sdp:decode_sdp_value(Value)} + end, Attrs). + +getaddr(Name) -> + bt_util:getaddr(Name). + +getaddr_by_name(Name) -> + bt_util:getaddr_by_name(Name). + +%% +%% Inquiry scan: +%% Note that the Fun can not make meaningfull remote calls +%% while inquiry is running. +%% +%% +scan(Timeout) -> + scan(Timeout, fun(Addr,Acc) -> {continue,[Addr|Acc]} end, []). + +scan(Timeout, Fun, Acc) -> + case bt_drv:inquiry_start(Timeout) of + {ok,Ref} -> + receive + {bt,Ref,started} -> + scan_loop(Ref, Fun, Acc) + end; + Error -> + Error + end. + +scan_loop(Ref, Fun, Acc) -> + receive + {bt,Ref,{device,Addr}} -> + case Fun(Addr,Acc) of + {continue,Acc1} -> + scan_loop(Ref, Fun, Acc1); + {stop,Acc1} -> + bt_drv:inquiry_stop(Ref), + {ok,Acc1} + end; + {bt,Ref, stopped} -> + bt_drv:inquiry_stop(Ref), + {ok,Acc} + end. + +%% +%% Format bluetooth address into a hex string +%% +format_address(A) when ?is_bt_address(A) -> + case os:type() of + {unix,darwin} -> + format_address_(A, $-); + _ -> + format_address_(A, $:) + end; +format_address(<<F,E,D,C,B,A>>) -> %% binary is reverse format + format_address({A,B,C,D,E,F}). + +format_address_({A,B,C,D,E,F}, S) -> + [hexh(A),hexl(A),S,hexh(B),hexl(B),S,hexh(C),hexl(C),S, + hexh(D),hexl(D),S,hexh(E),hexl(E),S,hexh(F),hexl(F)]. + +hexl(A) -> hex1(A band 16#f). +hexh(A) -> hex1((A bsr 4) band 16#f). + +hex1(A) when A < 10 -> A+$0; +hex1(A) -> (A-10)+$a. diff --git a/deps/bt/src/bt_app.erl b/deps/bt/src/bt_app.erl new file mode 100644 index 0000000..7862c56 --- /dev/null +++ b/deps/bt/src/bt_app.erl @@ -0,0 +1,33 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +%%% File : bt_app.erl +%%% Author : Tony Rogvall <tony@pbook.synap.se> +%%% Description : +%%% Created : 2 Feb 2006 by Tony Rogvall <tony@pbook.synap.se> + +-module(bt_app). + +-behaviour(application). +-export([start/2,stop/1]). + +%% start +start(_Type, _StartArgs) -> + bt_sup:start_link(). + +%% stop FIXME +stop(_State) -> + ok. diff --git a/deps/bt/src/bt_drv.erl b/deps/bt/src/bt_drv.erl new file mode 100644 index 0000000..6f44ac9 --- /dev/null +++ b/deps/bt/src/bt_drv.erl @@ -0,0 +1,1398 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +%%%------------------------------------------------------------------- +%%% File : bt_drv.erl +%%% Author : Tony Rogvall <tony@iMac.local> +%%% Description : Bluetooth driver control +%%% +%%% Created : 28 Jan 2006 by Tony Rogvall <tony@iMac.local> +%%%------------------------------------------------------------------- +-module(bt_drv). + +-behaviour(gen_server). + +-include("../include/bt.hrl"). + +%% API +-export([start_link/0, start/0, stop/0]). +-export([ping/0]). +-export([register/2, unregister/1]). +-export([devices/0]). +-export([recent_devices/0]). +-export([paired_devices/0]). +-export([favorite_devices/0]). +-export([device_info/2]). +-export([local_info/1]). +-export([service_info/1, service_info/2]). +-export([service_query/1, service_query/2]). +-export([service_add/1, service_add_persist/1]). +-export([service_del/1]). +-export([service_rfcomm/1]). +-export([connect/1, connect/2, connect/3, disconnect/1]). +-export([remote_name/1, remote_name/2]). + +-export([inquiry_start/1, + inquiry_stop/1, + inquiry_flush/1]). + +-export([rfcomm_open/2, + rfcomm_close/1, + rfcomm_send/2, + rfcomm_listen/1, + rfcomm_accept/1, + rfcomm_accept/2, + rfcomm_accept/3, + rfcomm_mtu/1, + rfcomm_channel/1, + rfcomm_address/1]). + +-export([l2cap_open/2, + l2cap_close/1, + l2cap_send/2, + l2cap_listen/1, + l2cap_accept/1, + l2cap_mtu/1, + l2cap_psm/1, + l2cap_address/1]). + +-export([debug/1]). + +%% utils +-export([decode_class/1, + encode/1, decode/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +%% -define(debug, true). + +-ifdef(debug). +-define(dbg(Fmt,As), io:format("~s:~w:" Fmt "\n", [?FILE,?LINE | As])). +-else. +-define(dbg(Fmt,As), ok). +-endif. + +-define(SERVER, bt_drv). + +%% -record(session, +%% { +%% id, +%% owner, +%% tag, +%% data +%% }). + +%% event subscriptions +-record(subscription, + { + id, %% subscription id (32 bit) + subscriber, %% event subsriber + monitor, %% monitor on subscriber + ref, %% event reference + tag, %% event tag + data %% subscription data + }). + +-record(wait, + { + from, %% gen_server From + id, %% command id + data %% reply style + }). + +-record(state, + { + bt_port, + cmd_id = 1, %% 1..16#ffffffff command id + evt_id = 1, %% 1..16#ffffffff event id + wait = [], %% #wait + subscription = [], %% #subscription + sessions = [], %% #session + reg %% table of registrations + }). + +-define(CMD_PING, 1). +-define(CMD_RECENT_DEVICES, 2). +-define(CMD_PAIRED_DEVICES, 3). +-define(CMD_FAVORITE_DEVICES, 4). +-define(CMD_INQUIRY_START, 5). +-define(CMD_INQUIRY_STOP, 6). +-define(CMD_REMOTE_NAME, 7). +-define(CMD_CONNECT, 8). +-define(CMD_DISCONNECT, 9). + +-define(CMD_DEVICE_INFO, 10). +-define(CMD_SERVICE_INFO, 11). +-define(CMD_SERVICE_QUERY, 12). +-define(CMD_SERVICE_ADD, 13). +-define(CMD_SERVICE_DEL, 14). +-define(CMD_SERVICE_RFCOMM, 15). +-define(CMD_LOCAL_INFO, 16). +-define(CMD_DEBUG, 17). + +%% RCCOMM Channels +-define(CMD_RFCOMM_OPEN, 20). +-define(CMD_RFCOMM_CLOSE, 21). +-define(CMD_RFCOMM_LISTEN, 22). +-define(CMD_RFCOMM_SEND, 23). +-define(CMD_RFCOMM_ACCEPT, 24). +-define(CMD_RFCOMM_MTU, 25). +-define(CMD_RFCOMM_ADDRESS, 26). +-define(CMD_RFCOMM_CHANNEL, 27). + + +%% L2CAP +-define(CMD_L2CAP_OPEN, 30). +-define(CMD_L2CAP_CLOSE, 31). +-define(CMD_L2CAP_LISTEN, 32). +-define(CMD_L2CAP_SEND, 33). +-define(CMD_L2CAP_ACCEPT, 34). +-define(CMD_L2CAP_MTU, 35). +-define(CMD_L2CAP_ADDRESS, 36). +-define(CMD_L2CAP_PSM, 37). + + +-define(REPLY_OK, 1). +-define(REPLY_ERROR, 2). +-define(REPLY_EVENT, 3). + +-define(BOOLEAN, 0). +-define(UINT8, 1). +-define(UINT16, 2). +-define(UINT32, 3). +-define(UINT64, 4). +-define(STRING1, 5). +-define(LIST, 6). +-define(LIST_END, 7). +-define(TUPLE, 8). +-define(TUPLE_END, 9). +-define(ATOM, 10). +-define(BINARY, 11). +-define(INT8, 12). +-define(INT16, 13). +-define(INT32, 14). +-define(INT64, 15). +-define(FLOAT32, 16). +-define(FLOAT64, 17). +-define(STRING4, 18). + +-define(ADDR, 100). +-define(DATE, 101). + + +%% device info codes +-define(NFO_DEVICE_NAME, 1). +-define(NFO_DEVICE_CLASS, 2). +-define(NFO_DEVICE_CLOCK, 3). +-define(NFO_DEVICE_INQUIRY, 4). +-define(NFO_DEVICE_ACCESS, 5). +-define(NFO_DEVICE_UPDATE, 6). +-define(NFO_DEVICE_IS_FAVORITE, 7). +-define(NFO_DEVICE_IS_PAIRED, 8). +-define(NFO_DEVICE_IS_CONNECTED, 9). + +%% local info +-define(NFO_LOCAL_NAME, 1). +-define(NFO_LOCAL_CLASS, 2). +-define(NFO_LOCAL_ADDRESS, 3). +-define(NFO_LOCAL_DISCOVERABLE, 4). +-define(NFO_LOCAL_POWER_STATE, 5). + +%% Service Classes +-define(CLASS_LIMITED_DISCOVERABLE, 2#00000000001). +-define(CLASS_POSITIONING, 2#00000001000). +-define(CLASS_NETWORKING, 2#00000010000). +-define(CLASS_RENDERING, 2#00000100000). +-define(CLASS_CAPTURING, 2#00001000000). +-define(CLASS_OBJECT_TRANSFER, 2#00010000000). +-define(CLASS_AUDIO, 2#00100000000). +-define(CLASS_TELEPHONY, 2#01000000000). +-define(CLASS_INFORMATION, 2#10000000000). + +%% Miscellaneous +-define(CLASS_MAJOR_MISCELLANEOUS, 16#00). +%% Desktop, Notebook, PDA, Organizers, etc... +-define(CLASS_MAJOR_COMPUTER, 16#01). +%% Cellular, Cordless, Payphone, Modem, etc... +-define(CLASS_MAJOR_PHONE, 16#02). +%% LAN Access Point +-define(CLASS_MAJOR_LAN_ACCESSPOINT, 16#03). +%% Headset, Speaker, Stereo, etc... +-define(CLASS_MAJOR_AUDIO, 16#04). +%% Mouse, Joystick, Keyboards, etc... +-define(CLASS_MAJOR_PERIPHERAL, 16#05). +%% Printing, scanner, camera, display, etc... +-define(CLASS_MAJOR_IMAGING, 16#06). + +%% FIXME: Add minor per major .. + +-define(NEXT_ID(N), + if (N)==16#ffffffff -> 1; + true -> (N)+1 + end). + +-define(DLOG_DEBUG, 7). +-define(DLOG_INFO, 6). +-define(DLOG_NOTICE, 5). +-define(DLOG_WARNING, 4). +-define(DLOG_ERROR, 3). +-define(DLOG_CRITICAL, 2). +-define(DLOG_ALERT, 1). +-define(DLOG_EMERGENCY, 0). +-define(DLOG_NONE, -1). + +%%==================================================================== +%% API +%%==================================================================== + +ping() -> + gen_server:call(?SERVER, ping). + +debug(Level) when is_atom(Level) -> + gen_server:call(?SERVER, {debug, level(Level)}). + +register(Type, MFA={M,F,As}) when is_atom(M),is_atom(F),is_list(As) -> + ets:insert(btreg, {Type,MFA}). + +unregister(Type) -> + ets:delete(btreg, Type). + +devices() -> + {ok,D1} = recent_devices(), + {ok,D2} = paired_devices(), + {ok,D3} = favorite_devices(), + {ok,ordsets:to_list(ordsets:union([ordsets:from_list(D1), + ordsets:from_list(D2), + ordsets:from_list(D3)]))}. + +recent_devices() -> + gen_server:call(?SERVER, recent_devices). + +paired_devices() -> + gen_server:call(?SERVER, paired_devices). + +favorite_devices() -> + gen_server:call(?SERVER, favorite_devices). + +device_info(Address, Info) -> + {ok,Addr} = bt_util:getaddr(Address), + case gen_server:call(?SERVER, {device_info, Addr, Info}) of + {ok,InfoReply} -> + {ok, map2(fun(A,B) -> {A,B} end, Info, InfoReply)}; + Error -> + Error + end. + +local_info(Info) -> + case gen_server:call(?SERVER, {local_info, Info}) of + {ok,InfoReply} -> + {ok, map2(fun(A,B) -> {A,B} end, Info, InfoReply)}; + Error -> + Error + end. + + +service_info(Address) -> + service_info(Address,<<>>). + +service_info(Address, UUID) -> + case bt_util:getaddr(Address) of + {ok,Addr} -> + gen_server:call(?SERVER, {service_info, Addr, UUID}); + Error -> Error + end. + +service_query(Address) -> + service_query(Address, << >>). + +service_query(Address, UUID) -> + case bt_util:getaddr(Address) of + {ok,Addr} -> + gen_server:call(?SERVER, {service_query, Addr, UUID}, 20000); + Error -> Error + end. + +%% add a "bound" service +service_add(ServiceRecord) when is_list(ServiceRecord) -> + Service = lists:map(fun({ID,Value}) -> bt_sdp:encode(ID,Value) end, + ServiceRecord), + gen_server:call(?SERVER, {service_add,self(),Service}, 10000). + +%% add persistent service (i.e no owner) +service_add_persist(ServiceRecord) when is_list(ServiceRecord) -> + Service = lists:map(fun({ID,Value}) -> bt_sdp:encode(ID,Value) end, + ServiceRecord), + gen_server:call(?SERVER, {service_add,undefined,Service}, 10000). + +service_del(Handle) when is_integer(Handle) -> + gen_server:call(?SERVER, {service_del, Handle}, 10000). + +service_rfcomm(Handle) when is_integer(Handle) -> + gen_server:call(?SERVER, {service_rfcomm, Handle}, 10000). + + +connect(Address) -> + {ok,Addr} = bt_util:getaddr(Address), + gen_server:call(?SERVER, {connect, Addr}, 20000). + +connect(Address,Timeout) -> + {ok,Addr} = bt_util:getaddr(Address), + gen_server:call(?SERVER, {connect, Addr, Timeout, false}, 20000). + +connect(Address,Timeout,Auth) -> + {ok,Addr} = bt_util:getaddr(Address), + gen_server:call(?SERVER, {connect, Addr, Timeout, Auth}, 20000). + +disconnect(Address) -> + {ok,Addr} = bt_util:getaddr(Address), + gen_server:call(?SERVER, {disconnect, Addr}, 10000). + +remote_name(Address) -> + remote_name(Address, 0). + +remote_name(Address, Timeout) when is_integer(Timeout), Timeout >= 0 -> + gen_server:call(?SERVER, {remote_name, Address, Timeout}, 20000). + +%% +%% start bluetooth scan return {ok, Ref} or {error, Reason} +%% events sent to caller are: +%% {bt, Ref, started} +%% {bt, Ref, {device, Address}} +%% {bt, Ref, stopped} +%% +inquiry_start(Secs) -> + gen_server:call(?SERVER, {inquiry_start,self(),Secs}). + +%% +%% Stop bluetooth scanning (early) possibly +%% +inquiry_stop(Ref) -> + Res = gen_server:call(?SERVER, {inquiry_stop,Ref}), + inquiry_flush(Ref), + Res. + +%% remove (only) stopped messages! +inquiry_flush(Ref) -> + receive + {bt,Ref,stopped} -> + ok + after 0 -> + ok + end. + +rfcomm_open(Address, Channel) -> + {ok,Addr} = bt_util:getaddr(Address), + gen_server:call(?SERVER, {rfcomm_open, self(), Addr, Channel}, 20000). + +rfcomm_close(Ref) -> + gen_server:call(?SERVER, {rfcomm_close, Ref}). + +rfcomm_send(Ref, Data) -> + gen_server:call(?SERVER, {rfcomm_send, Ref, Data}). + +rfcomm_listen(Channel) -> + gen_server:call(?SERVER, {rfcomm_listen,self(),Channel}). + +rfcomm_accept(ListenRef) -> + rfcomm_accept(ListenRef, infinity). + +rfcomm_accept(ListenRef, Timeout) -> + rfcomm_accept(ListenRef, Timeout, self()). + +rfcomm_accept(ListenRef, Timeout, CtlPid) -> + gen_server:call(?SERVER, {rfcomm_accept, CtlPid, ListenRef, Timeout}). + +rfcomm_mtu(Ref) -> + gen_server:call(?SERVER, {rfcomm_mtu, Ref}). + +rfcomm_channel(Ref) -> + gen_server:call(?SERVER, {rfcomm_channel, Ref}). + +rfcomm_address(Ref) -> + gen_server:call(?SERVER, {rfcomm_address, Ref}). + +%% L2CAP +l2cap_open(Address, Psm) -> + {ok,Addr} = bt_util:getaddr(Address), + gen_server:call(?SERVER, {l2cap_open, self(), Addr, Psm}, 20000). + +l2cap_close(Ref) -> + gen_server:call(?SERVER, {l2cap_close, Ref}). + +l2cap_send(Ref, Data) -> + gen_server:call(?SERVER, {l2cap_send, Ref, Data}). + +l2cap_listen(Psm) -> + gen_server:call(?SERVER, {l2cap_listen,self(),Psm}). + +l2cap_accept(ListenRef) -> + l2cap_accept(ListenRef, infinity). + +l2cap_accept(ListenRef, Timeout) -> + gen_server:call(?SERVER, {l2cap_accept,self(),ListenRef,Timeout}). + +l2cap_mtu(Ref) -> + gen_server:call(?SERVER, {l2cap_mtu, Ref}). + +l2cap_psm(Ref) -> + gen_server:call(?SERVER, {l2cap_psm, Ref}). + +l2cap_address(Ref) -> + gen_server:call(?SERVER, {l2cap_address, Ref}). + + +%%-------------------------------------------------------------------- +%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} +%% Description: Starts the server +%%-------------------------------------------------------------------- +start() -> + gen_server:start({local, ?SERVER}, ?MODULE, [], []). + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +stop() -> + gen_server:call(?SERVER, stop). + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% Description: Initiates the server +%%-------------------------------------------------------------------- +init([]) -> + PD = case code:priv_dir(bt) of + { error, bad_name } -> "./priv/"; + Res -> Res + end, + + Driver = filename:join([PD,"bt"]), + Port = open_port({spawn, Driver}, [{packet,4},binary,eof]), + Reg = ets:new(btreg, [public, set, named_table]), + {ok, #state{ bt_port = Port, reg = Reg }}. + +%%-------------------------------------------------------------------- +%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, State} +%% Description: Handling call messages +%%-------------------------------------------------------------------- +handle_call(ping, From, State) -> + CmdId = State#state.cmd_id, + State1 = bt_command(From, State, ?CMD_PING, CmdId, []), + {noreply, State1}; +handle_call({debug,Level}, From, State) -> + CmdId = State#state.cmd_id, + State1 = bt_command(From, State, ?CMD_DEBUG, CmdId, <<Level:32/signed>>), + {noreply, State1}; +handle_call(recent_devices, From, State) -> + CmdId = State#state.cmd_id, + State1 = bt_command(From, State, ?CMD_RECENT_DEVICES, CmdId, []), + {noreply, State1}; +handle_call(paired_devices, From, State) -> + CmdId = State#state.cmd_id, + State1 = bt_command(From, State, ?CMD_PAIRED_DEVICES, CmdId, []), + {noreply, State1}; +handle_call(favorite_devices, From, State) -> + CmdId = State#state.cmd_id, + State1 = bt_command(From, State, ?CMD_FAVORITE_DEVICES, CmdId, []), + {noreply, State1}; +handle_call({device_info,Address,Info}, From, State) -> + case is_address(Address) of + true -> + AddrArg = tuple_to_list(Address), + case catch encode_info(Info) of + {'EXIT', _} -> + {reply, {error, einval}, State}; + InfoArg -> + CmdId = State#state.cmd_id, + State1 = bt_command(From, State, ?CMD_DEVICE_INFO, CmdId, + [AddrArg, InfoArg]), + {noreply, State1} + end; + false -> + {reply, {error, einval}, State} + end; +handle_call({local_info,Info}, From, State) -> + case catch encode_linfo(Info) of + {'EXIT', _} -> + {reply, {error, einval}, State}; + InfoArg -> + CmdId = State#state.cmd_id, + State1 = bt_command(From, State, ?CMD_LOCAL_INFO, CmdId, + [InfoArg]), + {noreply, State1} + end; +handle_call({service_info,Address,UUID}, From, State) -> + case is_address(Address) andalso is_binary(UUID) of + true -> + AddrArg = tuple_to_list(Address), + CmdId = State#state.cmd_id, + State1 = bt_command(From, State, ?CMD_SERVICE_INFO, CmdId, + [AddrArg, UUID]), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; +handle_call({service_query,Address,UUID}, From, State) -> + case is_address(Address) andalso is_binary(UUID) of + true -> + AddrArg = tuple_to_list(Address), + CmdId = State#state.cmd_id, + State1 = bt_command(From, State, ?CMD_SERVICE_QUERY, CmdId, + [AddrArg, UUID]), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; + +handle_call({service_add,Owner,Service}, From, State) -> + case lists:all(fun(B) -> is_binary(B) end, Service) of + true -> + CmdId = State#state.cmd_id, + EvtId = State#state.evt_id, + Args = [<< EvtId:32 >>,encode(Service)], + EvtRef = make_ref(), + Mon = if is_pid(Owner) ->erlang:monitor(process, Owner); + true -> undefined + end, + SList = [#subscription { ref=EvtRef, + id=EvtId, + subscriber=Owner, + monitor=Mon, + tag = sdp, + data = sdp + } | State#state.subscription], + State1 = bt_command(From, + State#state { evt_id = ?NEXT_ID(EvtId), + subscription = SList + }, + ?CMD_SERVICE_ADD, CmdId, Args, + {reply, EvtRef}), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; +handle_call({service_del, Handle}, From, State) -> + if is_integer(Handle) -> + case lists:keysearch(Handle, #subscription.data,State#state.subscription) of + {value, S} when S#subscription.tag == sdp -> + SList = State#state.subscription -- [S], + unmon(S#subscription.monitor), + CmdId = State#state.cmd_id, + State1 = bt_command(From, + State#state { subscription = SList }, + ?CMD_SERVICE_DEL, CmdId, + <<(S#subscription.id):32>>), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; + true -> + {reply, {error, einval}, State} + end; +handle_call({service_rfcomm, Handle}, From, State) -> + if is_integer(Handle) -> + case lists:keysearch(Handle, #subscription.data,State#state.subscription) of + {value, S} when S#subscription.tag == sdp -> + CmdId = State#state.cmd_id, + State1 = bt_command(From,State,?CMD_SERVICE_RFCOMM,CmdId,[<<(S#subscription.id):32>>]), + {noreply, State1}; + _ -> + {reply, {error, einval}, State} + end; + true -> + {reply, {error, einval}, State} + end; +handle_call({inquiry_start,Pid,Timeout},From,State) -> + case is_pid(Pid) andalso is_timeout(Timeout) of + true -> + CmdId = State#state.cmd_id, + EvtId = State#state.evt_id, + EvtRef = make_ref(), + Mon = erlang:monitor(process, Pid), + SList = [#subscription { ref=EvtRef, + id=EvtId, + subscriber=Pid, + monitor=Mon, + tag = bt, + data = inquiry + } | State#state.subscription], + State1 = bt_command(From, + State#state { evt_id = ?NEXT_ID(EvtId), + subscription = SList + }, + ?CMD_INQUIRY_START, CmdId, + <<EvtId:32, Timeout:32>>, + {reply, EvtRef}), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; +handle_call({inquiry_stop,Ref}, From, State) -> + case lists:keysearch(Ref,#subscription.ref,State#state.subscription) of + false -> + {reply, {error, einval}, State}; + {value,S} -> + unmon(S#subscription.monitor), + SList = State#state.subscription -- [S], + State1 = bt_command(From, + State#state { subscription = SList }, + ?CMD_INQUIRY_STOP, + State#state.cmd_id, + <<(S#subscription.id):32>>), + {noreply, State1} + end; +handle_call({remote_name,Address,Timeout},From,State) -> + case is_address(Address) andalso + is_timeout(Timeout) of + true -> + AddrArg = tuple_to_list(Address), + CmdId = State#state.cmd_id, + State1 = bt_command(From, State, ?CMD_REMOTE_NAME, CmdId, + [AddrArg, <<Timeout:32>>]), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; +handle_call({connect, Address},From,State) -> + case is_address(Address) of + true -> + AddrArg = tuple_to_list(Address), + CmdId = State#state.cmd_id, + State1 = bt_command(From, State, ?CMD_CONNECT, CmdId, [AddrArg]), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; +handle_call({connect, Address, Timeout, Auth},From,State) -> + case is_address(Address) andalso + is_timeout(Timeout) andalso + (Auth == true orelse Auth == false) of + true -> + AddrArg = tuple_to_list(Address), + AuthArg = if Auth == true -> 1; Auth == false -> 0 end, + CmdId = State#state.cmd_id, + State1 = bt_command(From, State, ?CMD_CONNECT, CmdId, + [AddrArg, <<Timeout:32, AuthArg:8>>]), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; +handle_call({disconnect, Address},From,State) -> + case is_address(Address) of + true -> + AddrArg = tuple_to_list(Address), + CmdId = State#state.cmd_id, + State1 = bt_command(From, State, ?CMD_DISCONNECT, CmdId, [AddrArg]), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; +%% RFCOMM +handle_call({rfcomm_open, Owner, Address, Channel}, From, State) -> + case is_pid(Owner) andalso + is_address(Address) andalso + is_channel(Channel) of + true -> + CmdId = State#state.cmd_id, + EvtId = State#state.evt_id, + AddrArg = tuple_to_list(Address), + Args = [<< EvtId:32 >>,AddrArg,<<Channel:8>>], + EvtRef = make_ref(), + Mon = erlang:monitor(process, Owner), + SList = [#subscription { ref=EvtRef, + id=EvtId, + subscriber=Owner, + monitor=Mon, + tag = rfcomm, + data = rfcomm + } | State#state.subscription], + State1 = bt_command(From, + State#state { evt_id = ?NEXT_ID(EvtId), + subscription = SList + }, + ?CMD_RFCOMM_OPEN, CmdId, Args, + {reply, EvtRef}), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; + +handle_call({rfcomm_listen, Owner, Channel}, From, State) -> + case is_pid(Owner) andalso + (is_channel(Channel) orelse (Channel==0)) of + true -> + CmdId = State#state.cmd_id, + EvtId = State#state.evt_id, + Args = [<< EvtId:32 >>,<<Channel:8>>], + EvtRef = make_ref(), + Mon = erlang:monitor(process, Owner), + SList = [#subscription { ref=EvtRef, + id=EvtId, + subscriber=Owner, + monitor=Mon, + tag = rfcomm_listen, + data = rfcomm_listen + } | State#state.subscription], + State1 = bt_command(From, + State#state { evt_id = ?NEXT_ID(EvtId), + subscription = SList + }, + ?CMD_RFCOMM_LISTEN, CmdId, Args, + {reply, EvtRef}), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; + +handle_call({rfcomm_accept,Owner,Ref,_Timeout}, From, State) -> + case lists:keysearch(Ref,#subscription.ref,State#state.subscription) of + {value,S} when S#subscription.tag == rfcomm_listen -> + CmdId = State#state.cmd_id, + EvtId = State#state.evt_id, + Args = [<<EvtId:32, (S#subscription.id):32 >>], + EvtRef = make_ref(), + Mon = erlang:monitor(process, Owner), + SList = [#subscription { ref=EvtRef, + id=EvtId, + subscriber=Owner, + monitor=Mon, + tag = rfcomm, + data = rfcomm + } | State#state.subscription], + State1 = bt_command(From, + State#state { evt_id = ?NEXT_ID(EvtId), + subscription = SList + }, + ?CMD_RFCOMM_ACCEPT, CmdId, Args, + {reply, EvtRef}), + {noreply, State1}; + _ -> + {reply, {error, einval}, State} + end; + +handle_call({rfcomm_close, Ref}, From, State) -> + case lists:keysearch(Ref,#subscription.ref,State#state.subscription) of + {value,S} when S#subscription.tag == rfcomm; S#subscription.tag == rfcomm_listen -> + unmon(S#subscription.monitor), + SList = State#state.subscription -- [S], + State1 = bt_command(From, + State#state { subscription = SList }, + ?CMD_RFCOMM_CLOSE, + State#state.cmd_id, + <<(S#subscription.id):32>>), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; +handle_call({rfcomm_send, Ref, Data}, From, State) -> + case lists:keysearch(Ref,#subscription.ref,State#state.subscription) of + {value,S} when S#subscription.tag == rfcomm -> + State1 = bt_command(From, + State, + ?CMD_RFCOMM_SEND, + State#state.cmd_id, + [ <<(S#subscription.id):32 >> , Data]), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; +handle_call({rfcomm_channel, Ref}, From, State) -> + case lists:keysearch(Ref,#subscription.ref,State#state.subscription) of + {value,S} when S#subscription.tag == rfcomm -> + State1 = bt_command(From, + State, + ?CMD_RFCOMM_CHANNEL, + State#state.cmd_id, + [ <<(S#subscription.id):32 >>]), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; +handle_call({rfcomm_mtu, Ref}, From, State) -> + case lists:keysearch(Ref,#subscription.ref,State#state.subscription) of + {value,S} when S#subscription.tag == rfcomm -> + State1 = bt_command(From, + State, + ?CMD_RFCOMM_MTU, + State#state.cmd_id, + [ <<(S#subscription.id):32 >>]), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; +handle_call({rfcomm_address, Ref}, From, State) -> + case lists:keysearch(Ref,#subscription.ref,State#state.subscription) of + {value,S} when S#subscription.tag == rfcomm -> + State1 = bt_command(From, + State, + ?CMD_RFCOMM_ADDRESS, + State#state.cmd_id, + [ <<(S#subscription.id):32 >>]), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; +%% L2CAP +handle_call({l2cap_open, Owner, Address, Psm}, From, State) -> + case is_pid(Owner) andalso + is_address(Address) andalso + is_psm(Psm) of + true -> + CmdId = State#state.cmd_id, + EvtId = State#state.evt_id, + AddrArg = tuple_to_list(Address), + Args = [<< EvtId:32 >>,AddrArg,<<Psm:16>>], + EvtRef = make_ref(), + Mon = erlang:monitor(process, Owner), + SList = [#subscription { ref=EvtRef, + id=EvtId, + subscriber=Owner, + monitor=Mon, + tag = l2cap, + data = l2cap + } | State#state.subscription], + State1 = bt_command(From, + State#state { evt_id = ?NEXT_ID(EvtId), + subscription = SList + }, + ?CMD_L2CAP_OPEN, CmdId, Args, + {reply, EvtRef}), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; + +handle_call({l2cap_listen, Owner, Psm}, From, State) -> + case is_pid(Owner) andalso + (is_psm(Psm) orelse (Psm=:=0)) of + true -> + CmdId = State#state.cmd_id, + EvtId = State#state.evt_id, + Args = [<< EvtId:32 >>,<<Psm:16>>], + EvtRef = make_ref(), + Mon = erlang:monitor(process, Owner), + SList = [#subscription { ref=EvtRef, + id=EvtId, + subscriber=Owner, + monitor=Mon, + tag = l2cap_listen, + data = l2cap_listen + } | State#state.subscription], + State1 = bt_command(From, + State#state { evt_id = ?NEXT_ID(EvtId), + subscription = SList + }, + ?CMD_L2CAP_LISTEN, CmdId, Args, + {reply, EvtRef}), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; + +handle_call({l2cap_accept,Owner,Ref,_Timeout}, From, State) -> + case lists:keysearch(Ref,#subscription.ref,State#state.subscription) of + {value,S} when S#subscription.tag == l2cap_listen -> + CmdId = State#state.cmd_id, + EvtId = State#state.evt_id, + Args = [<<EvtId:32, (S#subscription.id):32 >>], + EvtRef = make_ref(), + Mon = erlang:monitor(process, Owner), + SList = [#subscription { ref=EvtRef, + id=EvtId, + subscriber=Owner, + monitor=Mon, + tag = l2cap, + data = l2cap + } | State#state.subscription], + State1 = bt_command(From, + State#state { evt_id = ?NEXT_ID(EvtId), + subscription = SList + }, + ?CMD_L2CAP_ACCEPT, CmdId, Args, + {reply, EvtRef}), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; + +handle_call({l2cap_close, Ref}, From, State) -> + case lists:keysearch(Ref,#subscription.ref,State#state.subscription) of + {value,S} when S#subscription.tag == l2cap; S#subscription.tag == l2cap_listen -> + unmon(S#subscription.monitor), + SList = State#state.subscription -- [S], + State1 = bt_command(From, + State#state { subscription = SList }, + ?CMD_L2CAP_CLOSE, + State#state.cmd_id, + <<(S#subscription.id):32>>), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; +handle_call({l2cap_send, Ref, Data}, From, State) -> + case lists:keysearch(Ref,#subscription.ref,State#state.subscription) of + {value,S} when S#subscription.tag == l2cap -> + State1 = bt_command(From, + State, + ?CMD_L2CAP_SEND, + State#state.cmd_id, + [ <<(S#subscription.id):32 >> , Data]), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; +handle_call({l2cap_psm, Ref}, From, State) -> + case lists:keysearch(Ref,#subscription.ref,State#state.subscription) of + {value,S} when S#subscription.tag == l2cap -> + State1 = bt_command(From, + State, + ?CMD_L2CAP_PSM, + State#state.cmd_id, + [ <<(S#subscription.id):32 >>]), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; +handle_call({l2cap_mtu, Ref}, From, State) -> + case lists:keysearch(Ref,#subscription.ref,State#state.subscription) of + {value,S} when S#subscription.tag == l2cap -> + State1 = bt_command(From, + State, + ?CMD_L2CAP_MTU, + State#state.cmd_id, + [ <<(S#subscription.id):32 >>]), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; +handle_call({l2cap_address, Ref}, From, State) -> + case lists:keysearch(Ref,#subscription.ref,State#state.subscription) of + {value,S} when S#subscription.tag == l2cap -> + State1 = bt_command(From, + State, + ?CMD_L2CAP_ADDRESS, + State#state.cmd_id, + [ <<(S#subscription.id):32 >>]), + {noreply, State1}; + false -> + {reply, {error, einval}, State} + end; +%% +handle_call(stop, _From, State) -> + erlang:port_close(State#state.bt_port), + {stop, normal, ok, State}; +handle_call(_Request, _From, State) -> + {reply, {error, bad_call}, State}. + +%%-------------------------------------------------------------------- +%% Function: handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling cast messages +%%-------------------------------------------------------------------- +handle_cast(_Msg, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling all non call/cast messages +%%-------------------------------------------------------------------- +handle_info({Port,{data,Data}},State) when Port == State#state.bt_port -> + ?dbg("Port: data=~p", [Data]), + case Data of + <<?REPLY_OK, CmdId:32, ReplyData/binary>> -> + ?dbg("Got: OK cmdid=~w, data=~p", [CmdId,ReplyData]), + State1 = case decode(ReplyData) of + false -> + bt_reply(CmdId, ok, State); + {value,Decoded} -> + bt_reply(CmdId, {ok,Decoded}, State) + end, + {noreply, State1}; + <<?REPLY_ERROR, CmdId:32, ReplyData/binary>> -> + ?dbg("Got: ERROR cmdid=~w, data=~p", [CmdId,ReplyData]), + State1 = case decode(ReplyData) of + false -> + bt_reply(CmdId, error, State); + {value,Decoded} -> + bt_reply(CmdId, {error,Decoded}, State) + end, + {noreply, State1}; + <<?REPLY_EVENT, EvtId:32, EventData/binary>> -> + ?dbg("Got: EVENT evtid=~w, data=~p", [EvtId,EventData]), + case lists:keysearch(EvtId,#subscription.id, + State#state.subscription) of + false -> + ?dbg("no receipient for evtid=~p data=~p", + [EvtId, decode(EventData)]), + {noreply, State}; + {value,S} -> + case decode(EventData) of + false -> + ?dbg("bad event data ~p", [EventData]), + {noreply, State}; + {value, Decoded} -> + S#subscription.subscriber ! + {S#subscription.tag,S#subscription.ref,Decoded}, + if Decoded == closed -> + unmon(S#subscription.monitor), + SList = State#state.subscription -- [S], + State1 = State#state { subscription = SList }, + {noreply, State1}; + true -> + {noreply, State} + end + end + end; + _Other -> + ?dbg("Unexpected data=~999p", [_Other]), + {noreply, State} + end; +handle_info({Port,eof}, State) when Port == State#state.bt_port -> + ?dbg("bt_drv closed",[]), + erlang:port_close(Port), + {stop, closed, State}; +handle_info({'DOWN', Ref, process, _Pid, _Reason}, State) -> + case lists:keysearch(Ref, #subscription.monitor,State#state.subscription) of + false -> + ?dbg("handle_info: Down process ~p not subsribing\n", [_Pid]), + {noreply, State}; + {value, S} -> + SList = State#state.subscription -- [S], + State1 = + if S#subscription.data == inquiry -> + bt_command(undefined, + State#state { subscription = SList }, + ?CMD_INQUIRY_STOP, 0, + <<(S#subscription.id):32>>); + S#subscription.data == rfcomm -> + bt_command(undefined, + State#state { subscription = SList }, + ?CMD_RFCOMM_CLOSE, 0, + <<(S#subscription.id):32>>); + S#subscription.data == rfcomm_listen -> + bt_command(undefined, + State#state { subscription = SList }, + ?CMD_RFCOMM_CLOSE, 0, + <<(S#subscription.id):32>>); + S#subscription.data == l2cap -> + bt_command(undefined, + State#state { subscription = SList }, + ?CMD_L2CAP_CLOSE, 0, + <<(S#subscription.id):32>>); + S#subscription.data == l2cap_listen -> + bt_command(undefined, + State#state { subscription = SList }, + ?CMD_L2CAP_CLOSE, 0, + <<(S#subscription.id):32>>); + true -> + State#state { subscription = SList } + end, + {noreply, State1} + end; +handle_info(Info, State) -> + io:format("handle_info: got ~p\n", [Info]), + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: terminate(Reason, State) -> void() +%% Description: This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any necessary +%% cleaning up. When it returns, the gen_server terminates with Reason. +%% The return value is ignored. +%%-------------------------------------------------------------------- +terminate(_Reason, _State) -> + ok. + +%%-------------------------------------------------------------------- +%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} +%% Description: Convert process state when code is changed +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +bt_command(From, State, OpCode, CmdId, Data) -> + bt_command(From, State, OpCode, CmdId, Data, reply). + +bt_command(From, State, OpCode, CmdId, Data, ReplyData) -> + ?dbg("Command: ~w ~w ~w", [OpCode, CmdId, Data]), + erlang:port_command(State#state.bt_port, [OpCode, <<CmdId:32>>, Data]), + if CmdId == 0 -> %% no reply expected + State; + true -> + Wait = [#wait { from=From, id=CmdId, data=ReplyData} | + State#state.wait], + State#state { cmd_id = ?NEXT_ID(CmdId), wait = Wait } + end. + +bt_reply(0, _Reply, State) -> %% global reply + ?dbg("bt_drv: error: ~p", [_Reply]), + %% maybe send this error to all, and terminate ? + State; +bt_reply(CmdId, Reply, State) -> + case lists:keysearch(CmdId, #wait.id, State#state.wait) of + {value,W = #wait { data = reply }} -> + gen_server:reply(W#wait.from, Reply), + Wait = State#state.wait -- [W], + State#state { wait = Wait }; + + {value,W = #wait { data={reply,EvtRef} }} -> + case Reply of + ok -> + gen_server:reply(W#wait.from, {ok,EvtRef}), + Wait = State#state.wait -- [W], + State#state { wait = Wait }; + Other -> + ?dbg("other reply: ~p", [Other]), + Wait = State#state.wait -- [W], + case lists:keysearch(EvtRef, #subscription.ref, + State#state.subscription) of + false -> + gen_server:reply(W#wait.from, Other), + State#state { wait = Wait }; + {value,S} when S#subscription.data == sdp -> + %% special case + case Other of + {ok,Handle} -> + S1 = S#subscription { data = Handle }, %% switch to handle + gen_server:reply(W#wait.from, Other), + SList = lists:keyreplace(EvtRef, #subscription.ref, + State#state.subscription, S1), + State#state { wait = Wait, subscription = SList }; + _ -> + gen_server:reply(W#wait.from, Other), + unmon(S#subscription.monitor), + SList = State#state.subscription -- [S], + State#state { wait = Wait, subscription = SList } + end; + {value,S} -> + gen_server:reply(W#wait.from, Other), + unmon(S#subscription.monitor), + SList = State#state.subscription -- [S], + State#state { wait = Wait, subscription = SList } + end + end; + false -> + io:format("Did not find waiter for cmdid ~w\n", [CmdId]), + State + end. + +%% unmon - ignore if reference is undefined +unmon(undefined) -> ok; +unmon(Ref) -> erlang:demonitor(Ref, [flush]). + +encode_info([What | Info]) -> + case What of + name -> [?NFO_DEVICE_NAME | encode_info(Info)]; + class -> [?NFO_DEVICE_CLASS | encode_info(Info)]; + clock -> [?NFO_DEVICE_CLOCK | encode_info(Info)]; + inquiry -> [?NFO_DEVICE_INQUIRY | encode_info(Info)]; + access -> [?NFO_DEVICE_ACCESS | encode_info(Info)]; + update -> [?NFO_DEVICE_UPDATE | encode_info(Info)]; + is_favorite -> [?NFO_DEVICE_IS_FAVORITE | encode_info(Info)]; + is_paired -> [?NFO_DEVICE_IS_PAIRED | encode_info(Info)]; + is_connected -> [?NFO_DEVICE_IS_CONNECTED | encode_info(Info)] + end; +encode_info([]) -> []. + +encode_linfo([What | Info]) -> + case What of + name -> [?NFO_LOCAL_NAME | encode_linfo(Info)]; + class -> [?NFO_LOCAL_CLASS | encode_linfo(Info)]; + address -> [?NFO_LOCAL_ADDRESS | encode_linfo(Info)]; + discoverable -> [?NFO_LOCAL_DISCOVERABLE | encode_linfo(Info)]; + power_state -> [?NFO_LOCAL_POWER_STATE | encode_linfo(Info)] + end; +encode_linfo([]) -> []. + + +decode_class(Class) -> + <<Service:11, Major:5, Minor:6,_:2>> = <<Class:24>>, + {Service,Major,Minor}. + +%% encode into Data format +encode(Term) -> + list_to_binary([enc(Term)]). + +enc(true) -> + <<?BOOLEAN, 1>>; +enc(false) -> + <<?BOOLEAN, 0>>; +enc(X) when is_atom(X) -> + Val = atom_to_list(X), + [<<?ATOM, (length(Val)):8>>, Val]; +enc(X) when is_integer(X) -> + if X =< 16#ffffffff -> <<?UINT32, X:32>> ; + X >= -16#8000000 -> <<?INT32, X:32>> ; + X > 0 -> <<?UINT64, X:64>>; + true -> (<<?INT64, X:64>>) + end; +enc(X) when is_float(X) -> + <<?FLOAT64, X:64>>; +enc(X) when is_list(X) -> + case is_string(X) of + true -> + Len = length(X), + if Len =< 255 -> + [<<?STRING1, Len:8>>, X]; + true -> + [<<?STRING4, Len:32>>, X] + end; + false -> + [?LIST, lists:map(fun(E) -> enc(E) end, X), ?LIST_END] + end; +enc(X) when is_tuple(X) -> + [?TUPLE, lists:map(fun(E) -> enc(E) end, tuple_to_list(X)), ?TUPLE_END]; +enc(X) when is_binary(X) -> + [<<?BINARY,(size(X)):32>>, X]. + +is_string([X|Xs]) when X >= 0, X =< 255 -> is_string(Xs); +is_string([]) -> true; +is_string(_) -> false. + + +%% decode reply data +decode(<<>>) -> false; +decode(Data) -> {value, decode(Data, [])}. + +decode(<<>>, [Hd]) -> Hd; +decode(Data, Stack) -> + case Data of + <<?LIST, Rest/binary>> -> + ?dbg("LIST",[]), + decode(Rest, [list|Stack]); + <<?TUPLE, Rest/binary>> -> + ?dbg("TUPLE",[]), + decode(Rest, [tuple|Stack]); + <<?BOOLEAN, B, Rest/binary>> -> + ?dbg("BOOLEAN:~w",[B]), + decode(Rest, [B =/= 0 | Stack]); + <<?UINT8, I:8, Rest/binary>> -> + ?dbg("UINT8:~w",[I]), + decode(Rest, [I|Stack]); + <<?UINT16, I:16, Rest/binary>> -> + ?dbg("UINT16:~w",[I]), + decode(Rest, [I|Stack]); + <<?UINT32, I:32, Rest/binary>> -> + ?dbg("UINT32:~w",[I]), + decode(Rest, [I|Stack]); + <<?UINT64, I:64, Rest/binary>> -> + ?dbg("UINT64:~w",[I]), + decode(Rest, [I|Stack]); + <<?INT8, I:8/signed, Rest/binary>> -> + ?dbg("INT8:~w",[I]), + decode(Rest, [I|Stack]); + <<?INT16, I:16/signed, Rest/binary>> -> + ?dbg("INT16:~w",[I]), + decode(Rest, [I|Stack]); + <<?INT32, I:32/signed, Rest/binary>> -> + ?dbg("INT32:~w",[I]), + decode(Rest, [I|Stack]); + <<?INT64, I:64/signed, Rest/binary>> -> + ?dbg("INT64:~w",[I]), + decode(Rest, [I|Stack]); + <<?FLOAT32, F:32/float, Rest/binary>> -> + ?dbg("FLOAT32:~w",[F]), + decode(Rest, [F|Stack]); + <<?FLOAT64, F:64/float, Rest/binary>> -> + ?dbg("FLOAT64:~w",[F]), + decode(Rest, [F|Stack]); + <<?STRING1, Len:8, String:Len/binary, Rest/binary>> -> + ?dbg("STRING1: len=~w, ~w",[Len,String]), + decode(Rest, [binary_to_list(String) | Stack]); + <<?STRING4, Len:32, String:Len/binary, Rest/binary>> -> + ?dbg("STRING4: len=~w, ~w",[Len,String]), + decode(Rest, [binary_to_list(String) | Stack]); + <<?BINARY, Len:32, Bin:Len/binary, Rest/binary>> -> + ?dbg("BINARY: len=~w, ~w",[Len,Bin]), + decode(Rest, [Bin | Stack]); + <<?ATOM, Len:8, Atom:Len/binary, Rest/binary>> -> + ?dbg("ATOM: len=~w, ~w",[Len,Atom]), + decode(Rest, [list_to_atom(binary_to_list(Atom)) | Stack]); + <<?LIST_END, Rest/binary>> -> + ?dbg("LIST_END",[]), + {L,[_|Stack1]} = lists:splitwith(fun(X) -> X =/= list end, Stack), + decode(Rest, [lists:reverse(L) | Stack1]); + <<?TUPLE_END, Rest/binary>> -> + ?dbg("TUPLE_END",[]), + {L,[_|Stack1]}=lists:splitwith(fun(X) -> X =/= tuple end, Stack), + decode(Rest, [list_to_tuple(lists:reverse(L)) | Stack1]); + %% EXTENSIONS + <<?DATE, Date:32, Rest/binary>> -> + ?dbg("DATE: ~w",[Date]), + DateTime = if Date == 0 -> {{0,0,0},{0,0,0}}; + true -> + Now = {Date div 1000000, Date rem 1000000, 0}, + calendar:now_to_datetime(Now) + end, + decode(Rest,[DateTime|Stack]); + <<?ADDR, Addr:6/binary, Rest/binary>> -> + ?dbg("ADDR: ~w",[Addr]), + decode(Rest, [list_to_tuple(binary_to_list(Addr)) | Stack]) + end. + +map2(Fun, [A|As], [B|Bs]) -> + [ Fun(A, B) | map2(Fun, As, Bs)]; +map2(_Fun, [], []) -> + []. + +is_address(Addr) -> + if ?is_bt_address(Addr) -> true; + true -> false + end. + +is_channel(C) when is_integer(C), C >= 1, C =< 30 -> + true; +is_channel(_) -> false. + +is_psm(C) when is_integer(C), C >= 0, C =< 16#ffff -> + true; +is_psm(_) -> false. + + +is_timeout(T) when is_integer(T), T>=0 -> + true; +is_timeout(_) -> false. + +%% convert symbolic to numeric level +level(true) -> ?DLOG_DEBUG; +level(false) -> ?DLOG_NONE; +level(debug) -> ?DLOG_DEBUG; +level(info) -> ?DLOG_INFO; +level(notice) -> ?DLOG_NOTICE; +level(warning) -> ?DLOG_WARNING; +level(error) -> ?DLOG_ERROR; +level(critical) -> ?DLOG_CRITICAL; +level(alert) -> ?DLOG_ALERT; +level(emergency) -> ?DLOG_EMERGENCY; +level(none) -> ?DLOG_NONE. diff --git a/deps/bt/src/bt_erl_server.erl b/deps/bt/src/bt_erl_server.erl new file mode 100644 index 0000000..f879c92 --- /dev/null +++ b/deps/bt/src/bt_erl_server.erl @@ -0,0 +1,108 @@ +%%% @author Tony Rogvall <tony@rogvall.se> +%%% @copyright (C) 2014, Tony Rogvall +%%% @doc +%%% Bluetooth distribution +%%% @end +%%% Created : 3 Dec 2014 by Tony Rogvall <tony@rogvall.se> + +-module(bt_erl_server). + +-export([sdp_info/2]). +-compile(export_all). + +-include("../include/sdp.hrl"). + +-define(UUID_ErlangNode, + <<16#0f1662dc:32,16#0c1e:16,16#4e18:16,16#acd7:16,16#da61165bcd29:48>>). + +sdp_info(NodeName,Channel) -> + Base1 = 16#0100, + [ + {?ATTR_ServiceRecordHandle, {uint32,0}}, %% Should not be needed ? + {?ATTR_ServiceClassIDList, {sequence,[{uuid,?UUID_ErlangNode}]}}, + {?ATTR_ProtocolDescriptorList, + {sequence,[{sequence,[{uuid,?UUID_L2CAP}]}, + {sequence,[{uuid,?UUID_RFCOMM},{uint8,Channel}]}]}}, + {?ATTR_BrowseGroupList, + {sequence,[{uuid,?UUID_PublicBrowseGroup}]}}, + {?ATTR_LanguageBaseAttributeIDList, + {sequence, [{uint16, ?LANGUAGE($e,$n)}, + {uint16, ?ENCODING_UTF8}, + {uint16, Base1}]}}, + {?ATTR_ServiceName+Base1, {text,NodeName}}]. + + +start(NodeName) -> + start(NodeName,1). +start(NodeName, ChannelHint) -> + spawn(fun() -> init(NodeName,ChannelHint) end). + +init(NodeName, ChannelHint) -> + case bt_drv:service_add(sdp_info(NodeName,ChannelHint)) of + {ok,Handle} -> + case bt_drv:service_rfcomm(Handle) of + {ok,RealChannel} -> + io:format("Registered server: ~p rfcomm=~p\n", + [Handle,RealChannel]), + init_rfcomm(RealChannel); + Error -> + Error + end; + Error -> + Error + end. + +init_rfcomm(Channel) -> + process_flag(trap_exit, true), + case rfcomm:listen(Channel) of + {ok,ListenRef} -> + listen_loop(ListenRef); + Error -> + Error + end. + +listen_loop(ListenRef) -> + Pid = spawn_link(?MODULE, accept, [self(), ListenRef]), + receive + {accept,Pid,ok} -> + listen_loop(ListenRef); + {accept,Pid,{error,Reason}} -> + io:format("Accept error ~p\n", [Reason]), + listen_loop(ListenRef) + end. + +accept(Listen, ListenRef) -> + case rfcomm:accept(ListenRef) of + {ok,ARef} -> + receive + {rfcomm,ARef,{accept,Address,Channel}} -> + Listen ! {accept, self(), ok}, + io:format("connection from ~w:~w\n", [Address,Channel]), + main(ARef,Address,Channel); + {rfcomm,ARef,{data,_Data}} -> + io:format("error: got data ~p\n", [_Data]), + rfcomm:close(ARef), + Listen ! {accept, self(), {error,data}}, + error; + {rfcomm,ARef,closed} -> + rfcomm:close(ARef), + Listen ! {accept, self(), {error,closed}}, + closed + end; + Error -> + Error + end. + + +main(Ref,Address,Channel) -> + receive + {rfcomm,Ref,{data,Data}} -> + rfcomm:send(Ref, ["Echo:",Data]), + main(Ref,Address,Channel); + {rfcomm,Ref,closed} -> + rfcomm:close(Ref), + normal; + Other -> + io:format("bt_erl_server: main other ~w\n", [Other]), + main(Ref,Address,Channel) + end. diff --git a/deps/bt/src/bt_iset.erl b/deps/bt/src/bt_iset.erl new file mode 100644 index 0000000..32c7137 --- /dev/null +++ b/deps/bt/src/bt_iset.erl @@ -0,0 +1,457 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +%%% File : iset.erl +%%% Author : Tony Rogvall <tony@iMac.local> +%%% Description : Integer Set operations +%%% Created : 26 Jan 2006 by Tony Rogvall <tony@iMac.local> + +-module(bt_iset). + +%% +%% bt_iset: +%% +%% representation: +%% List of interval and points +%% +%% Points +%% A sorted list of values and/or closed intervals where +%% intervals are represented as pairs with as {Low,High}. +%% It may also be the empty list if no specific points are +%% given even though Max and Min has values. +%% +%% operations: +%% new() -> ISet +%% from_list([Integer|{RangeStart,RangeStop}]) -> ISet +%% to_list(ISet) -> List +%% +%% add_element(Element, ISet) -> ISet U [Element] +%% del_element(Element, ISet) -> ISet - [Element] +%% intersection(ISet1, ISet2) -> (ISet1 ^ ISet2) +%% subtract(ISet1, ISet2) -> (ISet1 - ISet2) +%% union(ISet1, ISet2) -> (ISet1 U ISet2) +%% is_subset(ISet1, ISet2) -> boolean() +%% is_psubset(ISet1,ISet2) -> boolean() +%% size(ISet) -> integer() +%% is_element(Element, ISet) -> boolean() +%% first(ISet) -> integer() | 'EXIT' +%% last(ISet) -> integer() | 'EXIT' +%% format(ISet) +%% + +-export([new/0]). +-export([add_element/2, del_element/2]). +-export([add_elements/2, del_elements/2]). +-export([from_list/1, to_list/1]). +-export([is_element/2, size/1, is_subset/2, is_psubset/2]). +-export([first/1, last/1]). +-export([format/1]). +-export([union/2, intersection/2, subtract/2]). +-export([sum/2, product/2, negate/1]). +-export([map/2, fold/3, filter/2]). + +-import(lists, [reverse/1]). + +%% +%% Create a new iset +%% +new() -> []. + +%% +%% is_element(N, Domain) +%% +%% returns: +%% true if N is a member of Domain +%% false otherwise +%% +is_element(N, [N | _]) -> true; +is_element(N, [V | _]) when is_integer(V), N < V -> false; +is_element(N, [V | L]) when is_integer(V), N > V -> is_element(N,L); +is_element(N, [{Low,High}|_]) when N >= Low, N =< High -> true; +is_element(N, [{Low,_High} | _]) when N < Low -> false; +is_element(N, [{_Low,High} | L]) when N > High -> is_element(N, L); +is_element(_N, []) -> false. + +%% +%% Calculate size of domain +%% +%% returns: +%% 0 if empty set +%% N number of members +%% +size(L) -> + size(L, 0). + +size([V | L], N) when is_integer(V) -> size(L, N+1); +size([{Low, High}|L], N) -> size(L, N+(High-Low)+1); +size([], N) -> N. + +%% +%% Get maximum and minimum value +%% +first([{V,_} | _]) -> V; +first([V | _]) -> V. + +last(D) -> + case lists:reverse(D) of + [{_,V} | _] -> V; + [V | _] -> V + end. + +%% +%% Map over iset +%% +map(_F, []) -> + []; +map(F, [D|Ds]) when is_integer(D) -> + [F(D) | map(F,Ds)]; +map(F, [{L,H}|Ds]) -> + map_range(F, L, H, Ds). + +map_range(F, I, N, Ds) when I > N -> + map(F, Ds); +map_range(F, I, N, Ds) -> + [F(I) | map_range(F,I+1,N,Ds)]. + +fold(F, Acc, [{A,B}|Es]) -> + fold(F,fold_range(F, Acc, A, B),Es); +fold(F, Acc, [A|Es]) -> + fold(F, F(A,Acc), Es); +fold(_F, Acc, []) -> + Acc. + +fold_range(_Fun, Acc, I, N) when I > N -> + Acc; +fold_range(Fun, Acc, I, N) -> + fold_range(Fun, Fun(I,Acc), I+1, N). + + +filter(F, ISet) -> + filter(F, ISet, new()). + +filter(F, [{A,B}|Es], ISet) -> + filter(F,Es,filter_range(F,A,B,ISet)); +filter(F, [A|Es], ISet) -> + case F(A) of + true -> + filter(F, Es, add_element(A,ISet)); + false -> + filter(F, Es, ISet) + end; +filter(_F, [], ISet) -> + ISet. + +filter_range(_F, I, N, ISet) when I > N -> + ISet; +filter_range(F, I, N, ISet) -> + case F(I) of + true -> filter_range(F, I+1, N, add_element(I, ISet)); + false -> filter_range(F, I+1, N, ISet) + end. + +%% +%% Utilities: +%% +%% value(A,B) make A-B a domain value +%% min(A,B) return the minimum of A and B +%% max(A,B) return the maximum of A and B +%% min_max(A,B) returns {Min,Max} +%% +-define(low(X), if is_tuple((X)) -> element(1,(X)); + true -> (X) + end). +-define(high(X), if is_tuple((X)) -> element(2,(X)); + true -> (X) + end). + +value(N,N) -> N; +value(Low, High) -> {Low,High}. + +min_max(A,B) when A < B -> {A,B}; +min_max(A,B) -> {B,A}. + +%% +%% Check if D1 is a subset D2 +%% +is_subset(D1,D2) -> + case intersection(D1, D2) of + D1 -> true; + _ -> false + end. + +%% +%% Check if D1 is a proper subset of D2 +%% +is_psubset(D1, D1) -> false; +is_psubset(D1, D2) -> is_subset(D1,D2). + + +%% +%% from_list(List, ISet) +%% +%% description: +%% Updates a domain given a list of integers and +%% intervals and simplifies them as much as possible +%% +%% returns: +%% Domain +%% +from_list(List) -> + add_elements(List, new()). + +to_list(ISet) -> + ISet. + +add_elements([E|List], ISet) -> + add_elements(List, add_element(E,ISet)); +add_elements([], ISet) -> + ISet. + +%% +%% add_element(Element, ISet) -> ISet' +%% +add_element(E={A,B},ISet) when is_integer(A),is_integer(B),A<B -> + union([E], ISet); +add_element({A,A},ISet) when is_integer(A) -> + union([A], ISet); +add_element(A,ISet) when is_integer(A) -> + union([A], ISet). + +%% +%% delete(Points, Domain) +%% +%% description: +%% Remove values from a domain given a list of integers and +%% intervals and simplifies them as much as possible +%% +%% returns: +%% Domain +%% + +del_elements([E|List], ISet) -> + del_elements(List, del_element(E,ISet)); +del_elements([], ISet) -> ISet. + +del_element(E={A,B},ISet) when is_integer(A),is_integer(B),A<B -> + subtract(ISet, [E]); +del_element({A,A},ISet) when is_integer(A) -> + subtract(ISet, [A]); +del_element(A,ISet) when is_integer(A) -> + subtract(ISet, [A]). + +%% +%% union(Domain1, Domain2) +%% +%% description: +%% Create a union of two domains +%% +%% +union(D,D) -> D; +union(D1,D2) -> + union(D1,D2,new()). + +union(D, [], U) -> cons(U,D); +union([],D, U) -> cons(U,D); +union(S1=[D1 | D1s], S2=[D2 | D2s], U) -> + Min1 = ?low(D1), + Max1 = ?high(D1), + Min2 = ?low(D2), + Max2 = ?high(D2), + if Min2 == Max1+1 -> + union(D1s,[value(Min1,Max2) | D2s], U); + Min1 == Max2+1 -> + union([value(Min2,Max1)|D1s], D2s, U); + Min2 > Max1 -> + union(D1s, S2, [D1|U]); + Min1 > Max2 -> + union(S1, D2s, [D2|U]); + Max1 > Max2 -> + union([value(min(Min1,Min2),Max1)|D1s], D2s, U); + true -> + union(D1s,[value(min(Min1,Min2),Max2)|D2s], U) + end. + +%% +%% intersection(ISet1, ISet2) +%% +%% description: +%% Create the intersection between two domains +%% +%% + +intersection([], _) -> []; +intersection(_, []) -> []; +intersection(D1, D2) -> + intersection(D1, D2, new()). + +intersection(_D, [], I) -> reverse(I); +intersection([], _D, I) -> reverse(I); +intersection(S1=[D1|D1s], S2=[D2|D2s], I) -> + Min1 = ?low(D1), + Max1 = ?high(D1), + Min2 = ?low(D2), + Max2 = ?high(D2), + if Min2 > Max1 -> + intersection(D1s, S2, I); + Min1 > Max2 -> + intersection(S1, D2s, I); + Max1<Max2 -> + intersection(D1s,S2, + [value(max(Min1,Min2),Max1)|I]); + true -> + intersection(S1, D2s, + [value(max(Min1,Min2),Max2)|I]) + end. + +%% +%% subtract(ISet1,ISet2) +%% +%% returns: +%% The difference between Iset1 and ISet2, by removeing the +%% values in ISet1 from ISet2 i.e D1 - D2. +%% +%% + +subtract(D, []) -> D; +subtract([], _) -> []; +subtract(D1, D2) -> + subtract(D1,D2,new()). + +subtract(D1s,[],Diff) -> cons(Diff,D1s); +subtract(D1s,[D2|D2s],Diff) -> + Min2 = ?low(D2), + Max2 = ?high(D2), + ddiff(D1s, Min2, Max2, D2, D2s, Diff). + +ddiff([], _Min2, _Max2, _D2, _D2s, Diff) -> + reverse(Diff); +ddiff([D1 | D1s], Min2, Max2, D2, D2s, Diff) -> + Min1 = ?low(D1), + Max1 = ?high(D1), + ddiffer(Min1, Max1, Min2, Max2, D1, D2, D1s,D2s,Diff). + +ddiffer(Min1,Max1,Min2,Max2,D1,D2,D1s,D2s,Diff) -> + if Min2 > Max1 -> + ddiff(D1s, Min2, Max2, D2, D2s, [D1 | Diff]); + Min1 > Max2 -> + subtract([D1 | D1s], D2s, Diff); + Min1<Min2 -> + D = value(Min1, Min2-1), + NewD1 = value(Min2, Max1), + ddiffer(Min2, Max1, Min2, Max2, + NewD1, D2, D1s, D2s, [D|Diff]); + Max1 > Max2 -> + NewD1 = value(Max2+1, Max1), + ddiffer(Max2+1, Max1, Min2, Max2, + NewD1, D2, D1s, D2s, Diff); + true -> + ddiff(D1s, Min2, Max2, D2, D2s, Diff) + end. + +%% +%% Arithmetic in universe +%% +%% negate(A) Integer negate values in domain A +%% sum(A,B) Integer addition of values in domains A and B +%% multiply(A,B) Integer multiplication of values in domains A and B +%% divide(A,B) Integer division of values in domain A with values in B +%% + +%% +%% Negate domain +%% returns: +%% { x | x = -a, a in A} +%% +negate(A) -> + negate(A, new()). + +negate([{I,J} | As], Neg) -> + negate(As, [value(-J,-I)|Neg]); +negate([I | As], Neg) -> + negate(As, [-I|Neg]); +negate([], Neg) -> + Neg. + +%% +%% Sum of domains +%% returns: +%% { x | x = a+b, a in A, b in B } +%% +sum(A, B) -> + sum(A,B,new()). + +sum([A|As], Bs, Sum) -> + MinA = ?low(A), + MaxA = ?high(A), + sum(As,Bs,union(sum1(Bs,MinA,MaxA,new()), Sum)); +sum([], _, Sum) -> + Sum. + +sum1([B|Bs],MinA,MaxA,Sum) -> + MinB = ?low(B), + MaxB = ?high(B), + sum1(Bs, MinA, MaxA, union([value(MinA+MinB,MaxA+MaxB)], Sum)); +sum1([],_,_,Sum) -> + Sum. + +%% +%% Product of domains +%% returns: +%% { x | x = a*b, a in A, b in B } +%% +product(A, B) -> + product(A,B,new()). + +product([A|As], Bs, Prod) -> + product(As,Bs,union(prod(Bs,A,new()), Prod)); +product([], _, Prod) -> + Prod. + +prod([B|Bs],A,Prod) -> + MinA = ?low(A), + MaxA = ?high(A), + MinB = ?low(B), + MaxB = ?high(B), + P1 = MinA*MinB, + P2 = MinA*MaxB, + P3 = MaxA*MinB, + P4 = MaxA*MaxB, + {Min1,Max1} = min_max(P1,P2), + {Min2,Max2} = min_max(P3,P4), + N = add_element({min(Min1,Min2),max(Max1,Max2)}, new()), + prod(Bs, A, union(N, Prod)); +prod([],_, Prod) -> + Prod. + + +cons([E | L1], L2) -> + cons(L1, [E | L2]); +cons([], L) -> + L. + +%% +%% format(D) +%% +%% output a domain +%% +format([]) -> "{}"; +format([D]) -> ["{", format1(D), "}"]; +format([D|Ds]) -> + ["{", format1(D), lists:map(fun(D1) -> [",",format1(D1)] end, Ds), "}"]. + +format1({Low,High}) -> [$[,integer_to_list(Low),"..",integer_to_list(High),$]]; +format1(Value) -> [integer_to_list(Value)]. + diff --git a/deps/bt/src/bt_make_hci_api.erl b/deps/bt/src/bt_make_hci_api.erl new file mode 100644 index 0000000..e108494 --- /dev/null +++ b/deps/bt/src/bt_make_hci_api.erl @@ -0,0 +1,408 @@ +%%% @author Tony Rogvall <tony@up13> +%%% @copyright (C) 2015, Tony Rogvall +%%% @doc +%%% Create header/erlang file from def file +%%% @end +%%% Created : 3 Apr 2015 by Tony Rogvall <tony@up13> + +-module(bt_make_hci_api). + +-compile(export_all). + +%% +%% the def file content consist of: +%% +%% {define, Name::atom(), Value::integer() | string()} +%% +%% <NAME>_SIZE are really not needed since the size may be +%% computed from the struct? +%% OGF_<Group?-Name> +%% OCF_<Command-Name> +%% +%% A command OCG_<COMMAND-NAME> is also in company with +%% command structure: <command_name>_cp or <command_name>_rp +%% +%% {type, name(), type()} type definition +%% +%% {enum, [name()]} | {enum, [{name(),value()}]} +%% +%% {struct,name(),[{type(),name()}]} +%% {struct,name(),[{type(),size(),name()}]} +%% +%% <name>_cp is command struct +%% <name>_rp is reply struct +%% +%% +%% +%% Generate include file hrl +%% Generate source file erl +%% +main() -> + {ok,Defs0} = file:consult(filename:join(code:priv_dir(bt), "hci.def")), + {ok,Erl} = file:open("hci_api.erl", [write]), + {ok,Hrl} = file:open("hci_api.hrl", [write]), + write_erl_header(Erl), + write_hrl_header(Hrl), + R = try write(Hrl, Erl, Defs0, Defs0, "", "", [], [], []) of + Result -> Result + catch + error:Reason -> + io:format("crash: ~p\n", [erlang:get_stacktrace()]), + {error,Reason} + end, + file:close(Erl), + write_hrl_footer(Hrl), + file:close(Hrl), + R. + +write_erl_header(Erl) -> + io:format(Erl, "%% -*- erlang -*-\n", []), + io:format(Erl, "-module(hci_api).\n", []), + io:format(Erl, "-compile(export_all).\n", []), + io:format(Erl, "-include(\"hci_api.hrl\").\n", []), + io:format(Erl, "\n", []), + io:format(Erl, "~s\n\n", [" +cname(<<0,_/binary>>) -> []; +cname(<<C,Cs/binary>>) -> [C|cname(Cs)]; +cname(<<>>) -> []. +"]), + ok. + +write_hrl_header(Hrl) -> + io:format(Hrl, "%% -*- erlang -*-\n", []), + io:format(Hrl, "-ifndef(__HCI_HRL__).\n", []), + io:format(Hrl, "-define(__HCI_HRL__,true).\n", []), + io:format(Hrl, "\n\n", []), + ok. + +write_hrl_footer(Erl) -> + io:format(Erl, "-endif.\n", []), + ok. + +%% +%% Generate erlang files +%% +write(Hrl,Erl,[Def|Defs],Defs0,OGF0,OCF0,EVTs,Funcs,Ds) -> + case write_def(Hrl,Erl,Def,Defs0) of + {ogf,OGF} -> write(Hrl,Erl,Defs,Defs0,OGF,OCF0,EVTs,Funcs,Ds); + {ocf,OCF} -> write(Hrl,Erl,Defs,Defs0,OGF0,OCF,EVTs, + [{OGF0,OCF}|Funcs],Ds); + {evt,EVT} -> write(Hrl,Erl,Defs,Defs0,OGF0,OCF0,[EVT|EVTs],Funcs,Ds); + {evt_decode,Name} -> + D = [{Evt,Name} || Evt <- EVTs], + Ds1 = lists:reverse(D, Ds), + write(Hrl,Erl,Defs,Defs0,OGF0,OCF0,[],Funcs,Ds1); + _ -> write(Hrl,Erl,Defs,Defs0,OGF0,OCF0,EVTs,Funcs,Ds) + end; +write(_Hrl,Erl,[],Defs0,_OGF0,_OCF0,_EVTs,Funcs,Ds) -> + %% do "normal" events + io:format(Erl, + "decode(Evt,_Data) ->\n" + " case Evt of\n", []), + lists:foreach( + fun({"EVT_TESTING", _Name}) -> ok; + ({"EVT_VENDOR", _Name}) -> ok; + ({"EVT_SI_"++_, _Name}) -> ok; + ({Evt="EVT_INQUIRY_COMPLETE", _Name}) -> + write_clause(Erl, Evt, inquiry_info); + ({Evt="EVT_INQUIRY_RESULT", _Name}) -> + write_clause(Erl, Evt, inquiry_info); + ({Evt="EVT_LE_META_EVENT",Name}) -> + write_clause(Erl, Evt, Name); + ({"EVT_LE"++_,_Name}) -> ok; + ({Evt,Name}) -> + write_clause(Erl, Evt, Name) + end, Ds), + io:format(Erl, + " _ -> erlang:error(bad_event)\n", []), + io:format(Erl, + " end.\n\n", []), + %% do "LE" events + io:format(Erl, + "decode_le(Evt,_Data) ->\n" + " case Evt of\n", []), + lists:foreach( + fun({"EVT_LE_META_EVENT",_Name}) -> ok; + ({Evt="EVT_LE"++_,Name}) -> + write_clause(Erl, Evt, Name); + ({_, _}) -> ok + end, Ds), + io:format(Erl, + " _ -> erlang:error(bad_event)\n", []), + io:format(Erl, + " end.\n\n", []), + %% Do hci calls + write_calls(Erl, Funcs, Defs0), + ok. + +write_clause(Erl, Evt, Name) -> + io:format(Erl, " ?~s -> decode_~s(_Data);\n", + [Evt,Name]). + +%% Generate send function +write_send(Erl,OGF,OCF,Defs) -> + "ocf_"++ FuncName = string:to_lower(atom_to_list(OCF)), + Cp = list_to_atom(FuncName++"_cp"), + case find_struct(Cp, Defs) of + false -> + io:format(Erl, "send_~s(Socket) ->\n", + [FuncName]), + io:format(Erl, " hci_socket:send(Socket,?~s,?~s,<<>>).\n\n", + [OGF,OCF]); + {struct,Cp,Fields} -> + FieldNames = field_names(Fields), + MacroArgs = [ var_name(F) || F <- FieldNames ], + %% generate the argument structure + io:format(Erl, + "send_~s(Socket,~s) ->\n", + [FuncName, join(MacroArgs,",")]), + io:format(Erl, + " hci_socket:send(Socket,?~s,?~s,<<?~s_bin(~s)>>).\n\n", + [OGF,OCF,Cp,join(MacroArgs,",")]) + end. + +%% For each {OGF, OCF} generate a send_<ocf> +%% and also generate a call_<ocf> +write_calls(Erl,[{OGF,OCF}|Funcs],Defs) -> + write_call(Erl, OGF, OCF, Defs), + write_calls(Erl,Funcs,Defs); +write_calls(_Erl,[],_Defs) -> + ok. + +%% Emit call function +write_call(Erl,OGF,OCF,Defs) -> + "ocf_"++ FuncName = string:to_lower(atom_to_list(OCF)), + Cp = list_to_atom(FuncName++"_cp"), + Rp = list_to_atom(FuncName++"_rp"), + Decoder = case find_struct(Rp, Defs) of + false -> "undefined"; + {struct,Rp,_} -> "fun decode_"++FuncName++"_rp/1" + end, + case find_struct(Cp, Defs) of + false -> + io:format(Erl, "~s(Socket) ->\n", + [FuncName]), + io:format(Erl, " hci_socket:call(Socket,?~s,?~s,<<>>,~s).\n\n", + [OGF,OCF,Decoder]); + {struct,Cp,Fields} -> + FieldNames = field_names(Fields), + MacroArgs = [ var_name(F) || F <- FieldNames ], + %% generate the argument structure + io:format(Erl, + "~s(Socket,~s) ->\n", + [FuncName, join(MacroArgs,",")]), + io:format(Erl, + " hci_socket:call(Socket,?~s,?~s,<<?~s_bin(~s)>>,~s).\n\n", + [OGF,OCF,Cp,join(MacroArgs,","),Decoder]) + end. + +%% +%% Write definition to header file. +%% Write decode functions to erlang file. +%% +write_def(Hrl, Erl, Def, Defs0) -> + io:format("Def = ~p\n", [Def]), + case Def of + {define,Name,Value} when is_integer(Value) -> + NameString = atom_to_list(Name), + io:format(Hrl, "-define(~s, ~w).\n", [NameString, Value]), + case NameString of + "OGF_"++_ -> {ogf,Name}; + "OCF_"++_ -> {ocf,Name}; + "EVT_"++_ -> + case lists:suffix("_SIZE", NameString) of + true -> ok; + false -> {evt,NameString} + end; + _ -> ok + end; + {define,Name,Value} when is_list(Value) -> + NameString = atom_to_list(Name), + io:format(Hrl, "-define(~s, ~s).\n", [NameString, Value]), + case NameString of + "OGF_"++_ -> {ogf,Name}; + "OCF_"++_ -> {ocf,Name}; + "EVT_"++_ -> + case lists:suffix("_SIZE", NameString) of + true -> ok; + false -> {evt,NameString} + end; + _ -> ok + end; + + {type, _Name, _Type} -> %% used internally + ok; + + {enum,Es} -> + lists:foreach( + fun({Name,Value}) when is_integer(Value) -> + io:format(Hrl, "-define(~s, ~w).\n", [Name, Value]) + end, enum_es(Es)); + + {struct,Name,Fields} -> + NameString = atom_to_list(Name), + %% generate a record + io:format(Hrl, "-record(~s, {\n ", [Name]), + FieldNames = field_names(Fields), + StrFieldNames = [atom_to_list(Nm) || Nm <- FieldNames], + io:format(Hrl, "~s", [join(StrFieldNames, ",\n ")]), + io:format(Hrl, "\n}).\n", []), + %% generate a binary match sequence + MacroArgs = [ var_name(F) || F <- FieldNames ], + BinBody = field_match_list(Fields, Defs0), + io:format(Hrl, "-define(~s_bin(~s),~s).\n", + [Name, join(MacroArgs,","), + join(BinBody,",")]), + case lists:reverse(NameString) of + "pc_"++_ -> %% <name>_cp async command + ok; + "pr_" ++ _ -> %% reply data structure + decode_function(Erl, Name, Fields, MacroArgs); + "ofni_yriuqni" -> + decode_function(Erl, Name, Fields, MacroArgs); + _ -> + case lists:prefix("evt_", NameString) of + true -> + decode_function(Erl, Name, Fields, MacroArgs), + {evt_decode, Name}; + false -> + ok + end + end + end. + +decode_function(Erl, Name, Fields, MacroArgs) -> + FsList = format_field_assign_list(Fields, MacroArgs), + %% generate a decode function + io:format(Erl, "decode_~s(_Data) ->\n", [Name]), + io:format(Erl, + " case _Data of\n" + " <<?~s_bin(~s)>> ->\n" + " #~s { ~s }\n" + " end.\n\n", + [Name, + join(MacroArgs,","), + Name, + FsList]). + + +format_field_assign_list([F], [Arg]) -> + format_field_assign(F, Arg); +format_field_assign_list([F|Fs], [Arg|As]) -> + [format_field_assign(F, Arg), ",", + format_field_assign_list(Fs,As)]; +format_field_assign_list([], []) -> + []. + +format_field_assign({char,_,Name}, Arg) -> + [atom_to_list(Name)," = cname(", Arg, ")"]; +format_field_assign({_Type,Name}, Arg) -> + [atom_to_list(Name)," = ", Arg]; +format_field_assign({_Type,_,Name}, Arg) -> + [atom_to_list(Name)," = ", Arg]. + + +var_name(Name) when is_atom(Name) -> + var_name_(atom_to_list(Name)); +var_name(Name) when is_list(Name) -> + var_name_(Name). + +var_name_([H|T]) -> + [string:to_upper(H) | T]. + +remove_suffix(Suffix, List) -> + RSuffix = lists:reverse(Suffix), + RList = lists:reverse(List), + case lists:prefix(RSuffix, RList) of + true -> + lists:reverse(lists:nthtail(length(RSuffix), RList)); + false -> + List + end. + +find_define(Name,[Def={define,Name,_}|_Defs]) -> + Def; +find_define(Name,[_|Defs]) -> + find_define(Name,Defs); +find_define(_, []) -> + false. + +find_struct(Name,[Def={struct,Name,_}|_Defs]) -> + Def; +find_struct(Name,[_|Defs]) -> + find_struct(Name,Defs); +find_struct(_, []) -> + false. + +find_type(char,_) -> true; +find_type(uint8_t,_) -> true; +find_type(int8_t,_) -> true; +find_type(uint16_t,_) -> true; +find_type(uint32_t,_) -> true; +find_type(uint64_t,_) -> true; +find_type(Name,[TypeDef={type,Name,_}|_Defs]) -> + TypeDef; +find_type(Name,[_|Defs]) -> + find_type(Name,Defs); +find_type(_, []) -> + false. + +field_match_list([{Type,Name}|Fs], Defs) -> + [ field_match(Type,Name,Defs) | field_match_list(Fs,Defs)]; +field_match_list([], _Defs) -> + []. + +field_match(Type,Name,Defs) when is_atom(Type) -> + case find_type(Type,Defs) of + true -> %% basic type + var_name(Name)++type_unit(Type); + {type,_,Type1} -> + field_match(Type1,Name,Defs) + end; +field_match({Type,Size},Name,Defs) when is_atom(Type) -> + case find_type(Type,Defs) of + true -> + var_name(Name)++":"++ + if is_integer(Size) -> + integer_to_list(Size); + is_list(Size) -> + "("++Size++")" + end ++ "/" ++ unit(Type)++"-binary"; + {type,_,Type1} -> + field_match({Type1,Size}, Name, Defs) + end. +%% more cases are neede here at some point + +type_unit(char) -> ":1/unsigned-unit:8"; +type_unit(uint8_t) -> ":1/unsigned-unit:8"; +type_unit(int8_t) -> ":1/signed-unit:8"; +type_unit(uint16_t) -> ":1/little-unsigned-unit:16"; +type_unit(uint32_t) -> ":1/little-unsigned-unit:32"; +type_unit(uint64_t) -> ":1/little-unsigned-unit:64". + +%% element unit +unit(char) -> "unit:8"; +unit(uint8_t) -> "unit:8"; +unit(int8_t) -> "unit:8"; +unit(uint16_t) -> "unit:16"; +unit(uint32_t) -> "unit:32"; +unit(uint64_t) -> "unit:64". + + +field_names(Fs) -> + [ Name || {_Type, Name} <- Fs]. + +join([], _Sep) -> []; +join([A], _Sep) -> [A]; +join([A|As], Sep) -> [A,Sep|join(As,Sep)]. + + +enum_es(Enums) -> + enum_es(Enums, 0). + +enum_es([Name|Enums], I) when is_atom(Name) -> + [{Name,I} | enum_es(Enums, I+1)]; +enum_es([{Name,Value}|Enums], I) -> + [{Name,Value}|enum_es(Enums, erlang:max(Value,I)+1)]; +enum_es([], _) -> + []. diff --git a/deps/bt/src/bt_sdp.erl b/deps/bt/src/bt_sdp.erl new file mode 100644 index 0000000..3d77dae --- /dev/null +++ b/deps/bt/src/bt_sdp.erl @@ -0,0 +1,1108 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +%%% File : bt_sdp.erl +%%% Author : Tony Rogvall <tony@PBook.local> +%%% Description : SDP functions +%%% Created : 28 May 2006 by Tony Rogvall <tony@PBook.local> + +-module(bt_sdp). + +-export([attribute_to_string/1, attribute_to_string/2, attribute_to_string/3]). +-export([string_to_attribute/1, string_to_attribute/2]). +-export([value_to_string/1]). +-export([uuid_to_string/1, string_to_uuid/1]). + +-export([is_value/1, encode_value/1, decode_value/1, decode_value/2]). +-export([fold_value/3]). +-export([encode/2, decode/1]). +-export([uuid_128/1]). +-export([decode_sdp_value/1]). + +%% Native Client SDP/L2CAP API +-export([service_search/2, + service_attribute/3, + service_search_attribute/3 + ]). +-export([encode_pdu/2,encode_pdu/3, + decode_pdu/1]). + +-import(lists, [reverse/1, map/2]). + +-include("../include/bt.hrl"). +-include("../include/sdp.hrl"). + + +-define(SDP_NIL, 0). +-define(SDP_UNSIGNED, 1). +-define(SDP_SIGNED, 2). +-define(SDP_UUID, 3). +-define(SDP_TEXT, 4). +-define(SDP_BOOLEAN, 5). +-define(SDP_SEQUENCE, 6). +-define(SDP_ALTERNATIVE, 7). +-define(SDP_URL, 8). + + +%% L2CAP implementation of SDP query (will be) +service_search(Address, UUIDList) -> + SearchPattern = cvt_uuid_list(UUIDList), + PDU = #sdpServiceSearchRequest { serviceSearchPattern=SearchPattern, + maximumServiceRecordCount=16#ffff + }, + sdp_request(Address, PDU). + +service_attribute(Address, Handle, AttributeList) -> + AttributeIDList= cvt_attribute_id_list(AttributeList), + PDU = #sdpServiceAttributeRequest { serviceRecordHandle=Handle, + maximumAttributeByteCount=16#ffff, + attributeIDList=AttributeIDList }, + sdp_request(Address, PDU). + +service_search_attribute(Address, UUIDList, AttributeList) -> + SearchPattern = cvt_uuid_list(UUIDList), + AttributeIDList= cvt_attribute_id_list(AttributeList), + PDU=#sdpServiceSearchAttributeRequest { serviceSearchPattern=SearchPattern, + maximumAttributeByteCount=16#ffff, + attributeIDList=AttributeIDList }, + sdp_request(Address, PDU). + + +sdp_request(Address, PDU) -> + case l2cap:open(Address, ?L2CAP_PSM_SDP) of + {ok,L2CAP} -> + Result = sdp_l2cap_request(L2CAP,<<>>,[],PDU), + l2cap:close(L2CAP), + Result; + Error -> Error + end. + +sdp_l2cap_request(L2CAP,Continuation,Acc,Request) -> + Transaction = next_transaction_id(), + io:format("Transaction ~w\n", [Transaction]), + Bin = encode_pdu(Transaction,Continuation,Request), + l2cap:send(L2CAP, Bin), + receive + {l2cap, L2CAP, {data,Data}} -> + case decode_pdu(Data,Acc) of + {ok,{Transaction,<<>>,_Acc1, + #sdpErrorResponse { errorCode=ErrorCode, + errorInfo=ErrorInfo }}} -> + {error,{ErrorCode,ErrorInfo}}; + {ok,{Transaction,<<>>,_Acc1,Response}} -> + {ok,Response}; + {ok,{Transaction,Continuation1,Acc1,_Response}} -> + io:format("get more\n",[]), + sdp_l2cap_request(L2CAP,Continuation1,Acc1,Request); + Error -> + Error + end; + {l2cap, L2CAP, closed} -> + {error, closed} + end. + +%% +%% Encode PDU +%% +encode_pdu(Transaction,PDU) -> + encode_pdu(Transaction,<<>>,PDU). + +encode_pdu(Transaction,Continuation, + #sdpServiceSearchRequest + { serviceSearchPattern=ServiceSearchPattern, + maximumServiceRecordCount=MaximumServiceRecordCount + }) -> + EServiceSearchPattern = encode_value(ServiceSearchPattern), + ParameterLen = size(EServiceSearchPattern)+2+1+size(Continuation), + <<?SDP_ServiceSearchRequest, + Transaction:16/unsigned-integer, + ParameterLen:16/unsigned-integer, + EServiceSearchPattern/binary, + MaximumServiceRecordCount:16/unsigned-integer, + (size(Continuation)):8/unsigned-integer, Continuation/binary>>; +encode_pdu(Transaction,Continuation, + #sdpServiceSearchResponse + { totalServiceRecordCount=TotalServiceRecordCount, + currentServiceRecordCount=CurrentServiceRecordCount, + serviceRecordHandleList=Handles + }) -> + ParameterLen = 2+2+4*length(Handles), + ServiceRecordHandleList = + list_to_binary(map(fun(H) -> <<H:32/unsigned-integer>> end, Handles)), + <<?SDP_ServiceSearchResponse, + Transaction:16/unsigned-integer, + ParameterLen:16/unsigned-integer, + TotalServiceRecordCount:16/unsigned-integer, + CurrentServiceRecordCount:16/unsigned-integer, + ServiceRecordHandleList/binary, + (size(Continuation)):8, Continuation/binary>>; +encode_pdu(Transaction,Continuation, + #sdpServiceAttributeRequest + { serviceRecordHandle=Handle, + maximumAttributeByteCount=MaximumAttributeByteCount, + attributeIDList=AttributeIDList + }) -> + EAttributeIDList = encode_value(AttributeIDList), + ParameterLen = 4+2+size(EAttributeIDList)+1+size(Continuation), + <<?SDP_ServiceAttributeRequest, + Transaction:16/unsigned-integer, + ParameterLen:16/unsigned-integer, + Handle:32/unsigned-integer, + MaximumAttributeByteCount:16/unsigned-integer, + EAttributeIDList/binary, + (size(Continuation)):8/unsigned-integer, Continuation/binary>>; +encode_pdu(Transaction,Continuation, + #sdpServiceAttributeResponse + { attributeListByteCount=AttributeListByteCount, + attributeList=AttributeList }) -> + ParameterLen = 2 + size(AttributeList)+1+size(Continuation), + <<?SDP_ServiceAttributeResponse, + Transaction:16/unsigned-integer, + ParameterLen:16/unsigned-integer, + AttributeListByteCount:16/unsigned-integer, + AttributeList/binary, + (size(Continuation)):8, Continuation/binary>>; +encode_pdu(Transaction,Continuation, + #sdpServiceSearchAttributeRequest + { serviceSearchPattern=ServiceSearchPattern, + maximumAttributeByteCount=MaximumAttributeByteCount, + attributeIDList=AttributeIDList + }) -> + EServiceSearchPattern = encode_value(ServiceSearchPattern), + EAttributeIDList = encode_value(AttributeIDList), + ParameterLen = size(EServiceSearchPattern)+2+ + size(EAttributeIDList)+1+size(Continuation), + <<?SDP_ServiceSearchAttributeRequest, + Transaction:16/unsigned-integer, + ParameterLen:16/unsigned-integer, + EServiceSearchPattern/binary, + MaximumAttributeByteCount:16/unsigned-integer, + EAttributeIDList/binary, + (size(Continuation)):8/unsigned-integer, Continuation/binary>>; +encode_pdu(Transaction,Continuation, + #sdpServiceSearchAttributeResponse + { attributeListByteCount=AttributeListByteCount, + attributeList=AttributeList }) -> + ParameterLen = 2+size(AttributeList)+1+size(Continuation), + <<?SDP_ServiceSearchAttributeResponse, + Transaction:16/unsigned-integer, + ParameterLen:16/unsigned-integer, + AttributeListByteCount:16/unsigned-integer, + AttributeList/binary, + (size(Continuation)):8, Continuation/binary>>; +encode_pdu(Transaction,_Continuation, + #sdpErrorResponse { errorCode=ECode, + errorInfo=ErrorInfo }) -> + ErrorCode = + case ECode of + invalid_version -> ?SDP_ErrorInvalidVersion; + invalid_handle -> ?SDP_ErrorInvalidHandle; + invalid_syntax -> ?SDP_ErrorInvalidSyntax; + invalid_pdu_size -> ?SDP_ErrorInvaludPduSize; + invalid_continuation -> ?SDP_ErrorInvalidContinuation; + insufficient_resources ->?SDP_ErrorInsufficientResources; + _ when is_integer(ECode) -> ECode + end, + ParameterLen = 2+size(ErrorInfo), + <<?SDP_ErrorResponse, + Transaction:16/unsigned-integer, + ParameterLen:16/unsigned-integer, + ErrorCode:16/unsigned-integer, + ErrorInfo/binary>>; + +encode_pdu(_Transaction,_Continuation,PDU) -> + %% add response pdus. + erlang:error({bad_pdu, PDU}). + +decode_pdu(Bin) -> + decode_pdu(Bin, []). + +decode_pdu(<<ID, + Transaction:16/unsigned-integer, + ParameterLen:16/unsigned-integer, + Parameters:ParameterLen/binary>>, Acc) -> + case catch decode_pdu(ID,Transaction,Parameters,Acc) of + {'EXIT',_} -> + {error,invalid_request_syntax}; + Result -> Result + end; +decode_pdu(PDU, _Acc) -> + erlang:error({bad_pdu,PDU}). + +%% decode pdu return {Transaction,Continuation,Pdu} +decode_pdu(?SDP_ServiceSearchRequest,Transaction,Parameters,_Acc) -> + PatternSize = sizeof(Parameters,0), + + <<Patterns:PatternSize/binary, + MaximumServiceRecordCount:16/unsigned-integer, + CSize:8, Continuation:CSize/binary>> = Parameters, + + {ok,{Transaction,Continuation + #sdpServiceSearchRequest + { serviceSearchPattern=decode(Patterns), + maximumServiceRecordCount=MaximumServiceRecordCount}}}; + +decode_pdu(?SDP_ServiceSearchResponse,Transaction, + <<TotalServiceRecordCount:16/unsigned-integer, + CurrentServiceRecordCount:16/unsigned-integer, + ServiceRecordHandleList:CurrentServiceRecordCount/binary-unit:32, + CSize:8, Continuation:CSize/binary>>, Acc) -> + HandleList = uint32_list(ServiceRecordHandleList), + if Continuation == <<>> -> + {ok,{Transaction,Continuation,[], + #sdpServiceSearchResponse + { totalServiceRecordCount=TotalServiceRecordCount, + currentServiceRecordCount=CurrentServiceRecordCount, + serviceRecordHandleList=Acc++HandleList}}}; + true -> + {ok,{Transaction,Continuation,Acc++HandleList, + #sdpServiceSearchResponse + { totalServiceRecordCount=TotalServiceRecordCount, + currentServiceRecordCount=CurrentServiceRecordCount, + serviceRecordHandleList=HandleList}}} + end; + +decode_pdu(?SDP_ServiceAttributeRequest,Transaction,Parameters,_Acc) -> + AttributeIDSize = sizeof(Parameters,6), + <<Handle:32/unsigned-integer, + MaximumAttributeByteCount:16/unsigned-integer, + AttributeIDList:AttributeIDSize/binary, + CSize:8, Continuation:CSize/binary>> = Parameters, + {ok,{Transaction,Continuation,[], + #sdpServiceAttributeRequest + { serviceRecordHandle=Handle, + maximumAttributeByteCount=MaximumAttributeByteCount, + attributeIDList = decode(AttributeIDList) }}}; + +decode_pdu(?SDP_ServiceAttributeResponse,Transaction, + <<AttributeListByteCount:16/unsigned-integer, + AttributeList:AttributeListByteCount/binary, + CSize:8, Continuation:CSize/binary>>, Acc) -> + if Continuation == <<>> -> + TotalAttributeList = list_to_binary([Acc,AttributeList]), + AList = decode_value(TotalAttributeList), + IAttributeList=attribute_id_list(AList), + {ok,{Transaction,Continuation,[], + #sdpServiceAttributeResponse + { attributeListByteCount=size(TotalAttributeList), + attributeList=IAttributeList}}}; + true -> + {ok,{Transaction,Continuation,Acc++[AttributeList], + #sdpServiceAttributeResponse + { attributeListByteCount=AttributeListByteCount, + attributeList=AttributeList}}} + end; +decode_pdu(?SDP_ServiceSearchAttributeRequest,Transaction, Parameters, _Acc) -> + PatternSize = sizeof(Parameters,0), + AttributeIDSize = sizeof(Parameters,PatternSize+2), + <<Pattern:PatternSize/binary, + MaximumAttributeByteCount:16/unsigned-integer, + AttributeIDList:AttributeIDSize/binary, + CSize:8, Continuation:CSize/binary>> = Parameters, + + {ok,{Transaction,Continuation,[], + #sdpServiceSearchAttributeRequest + { serviceSearchPattern=decode(Pattern), + maximumAttributeByteCount=MaximumAttributeByteCount, + attributeIDList = decode(AttributeIDList) }}}; + +decode_pdu(?SDP_ServiceSearchAttributeResponse,Transaction, + <<AttributeListByteCount:16/unsigned-integer, + AttributeList:AttributeListByteCount/binary, + CSize:8, Continuation:CSize/binary>> , Acc) -> + if Continuation == <<>> -> + TotalAttributeList = list_to_binary([Acc,AttributeList]), + AList = decode_value(TotalAttributeList), + RAttributeList = service_id_list(AList), + {ok,{Transaction,Continuation,[], + #sdpServiceSearchAttributeResponse + { attributeListByteCount=size(TotalAttributeList), + attributeList=RAttributeList}}}; + true -> + {ok,{Transaction,Continuation,Acc++[AttributeList], + #sdpServiceSearchAttributeResponse + { attributeListByteCount=AttributeListByteCount, + attributeList=AttributeList}}} + end; +decode_pdu(?SDP_ErrorResponse,Transaction, + <<ErrorCode:16/unsigned-integer, + ErrorInfo/binary>>, _Acc) -> + ECode = + case ErrorCode of + ?SDP_ErrorInvalidVersion -> invalid_version; + ?SDP_ErrorInvalidHandle -> invalid_handle; + ?SDP_ErrorInvalidSyntax -> invalid_syntax; + ?SDP_ErrorInvaludPduSize -> invalid_pdu_size; + ?SDP_ErrorInvalidContinuation -> invalid_continuation; + ?SDP_ErrorInsufficientResources -> insufficient_resources; + _ -> ErrorCode + end, + {ok,{Transaction,<<>>,[], + #sdpErrorResponse { errorCode=ECode, + errorInfo=ErrorInfo }}}; +decode_pdu(_ID, _Transaction, _Parameters, _Acc) -> + {error, invalid_pdu}. + + + + +uint32_list(<<X:32/unsigned-integer, T/binary>>) -> + [X | uint32_list(T)]; +uint32_list(<<>>) -> + []. + +%% convert search attribute response into attribute list +service_id_list({sequence,List}) -> + map(fun(H) -> attribute_id_list(H) end, List); +service_id_list(_) -> + []. + + +%% convert attribute response into attribute list +attribute_id_list({sequence,List}) -> + attribute_id_list1(List); +attribute_id_list(_) -> []. + +attribute_id_list1([{uint16,ID},Elem|T]) -> + [{ID,Elem} | attribute_id_list1(T)]; +attribute_id_list1([{uint16,ID}]) -> + [{ID,nil}]; +attribute_id_list1([]) -> + []; +attribute_id_list1(_) -> + []. + + + +cvt_uuid_list(UUIDList) -> + {sequence, + map( + fun(UUID) when is_binary(UUID) -> + {uuid,UUID}; + (UUID) when is_list(UUID); is_atom(UUID) -> + {uuid,string_to_uuid(UUID)} + end, + UUIDList)}. + +cvt_attribute_id_list(AttributeList) -> + {sequence, + map( + fun({A1,A2}) -> + A11 = if is_list(A1); is_atom(A1) -> + string_to_attribute(A1); + is_integer(A1) -> + A1 band 16#ffff + end, + A22 = if is_list(A2); is_atom(A2) -> + string_to_attribute(A2); + is_integer(A2) -> + A2 band 16#ffff + end, + {uint32, (A11 bsl 16) + A22}; + (A) when is_list(A); is_atom(A) -> + {uint16, string_to_attribute(A)}; + (A) when is_integer(A) -> + {uint16, A band 16#ffff} + end, AttributeList)}. + + +next_transaction_id() -> + N = case get('$sdp_transaction_id') of + undefined -> 1; + T -> T + end, + put('$sdp_transaction_id', N+1), + N. + + + +encode(Attribute, Value) -> + <<?SDP_UNSIGNED:5, 1:3, Attribute:16, (encode_value(Value))/binary>>. + +decode(Bin = <<?SDP_UNSIGNED:5,1:3,Attribute:16,_/binary>>) -> + {Attribute, decode_value(Bin, 3)}. + + +%% +%% return size of tag & value (including length bytes if present) +%% +sizeof(Bin, Offs) -> + case Bin of + <<_:Offs/binary, ?SDP_NIL:5, 0:3, _/binary>> -> 1; + <<_:Offs/binary, _:5, 0:3, _/binary>> -> 2; + <<_:Offs/binary, _:5, 1:3, _/binary>> -> 3; + <<_:Offs/binary, _:5, 2:3, _/binary>> -> 5; + <<_:Offs/binary, _:5, 3:3, _/binary>> -> 9; + <<_:Offs/binary, _:5, 4:3, _/binary>> -> 17; + <<_:Offs/binary, _:5, 5:3, Len:8, _/binary>> -> Len+2; + <<_:Offs/binary, _:5, 6:3, Len:16, _/binary>> -> Len+3; + <<_:Offs/binary, _:5, 7:3, Len:32, _/binary>> -> Len+5 + end. + +decode_value(Bin) -> + decode_value(Bin, 0). + +decode_value(Bin, Offs) -> + case Bin of + <<_:Offs/binary,?SDP_NIL:5, 0:3,_/binary>> -> nil; + %% type=1 unsigned integer + <<_:Offs/binary,?SDP_UNSIGNED:5, 0:3, X:8/unsigned,_/binary>> -> + {uint8,X}; + <<_:Offs/binary,?SDP_UNSIGNED:5, 1:3, X:16/unsigned,_/binary>> -> + {uint16,X}; + <<_:Offs/binary,?SDP_UNSIGNED:5, 2:3, X:32/unsigned,_/binary>> -> + {uint32,X}; + <<_:Offs/binary,?SDP_UNSIGNED:5, 3:3, X:64/unsigned,_/binary>> -> + {uint64,X}; + <<_:Offs/binary,?SDP_UNSIGNED:5, 4:3, X:128/unsigned,_/binary>> -> + {uint128,X}; + %% type=2 signed integer + <<_:Offs/binary,?SDP_SIGNED:5, 0:3, X:8/signed,_/binary>> -> + {int8,X}; + <<_:Offs/binary,?SDP_SIGNED:5, 1:3, X:16/signed,_/binary>> -> + {int16,X}; + <<_:Offs/binary,?SDP_SIGNED:5, 2:3, X:32/signed,_/binary>> -> + {int32,X}; + <<_:Offs/binary,?SDP_SIGNED:5, 3:3, X:64/signed,_/binary>> -> + {int64,X}; + <<_:Offs/binary,?SDP_SIGNED:5, 4:3, X:128/signed,_/binary>> -> + {int128,X}; + %% type=3 UUID + <<_:Offs/binary,?SDP_UUID:5, 1:3, X:2/binary,_/binary>> -> + {uuid,X}; + <<_:Offs/binary,?SDP_UUID:5, 2:3, X:4/binary,_/binary>> -> + {uuid,X}; + <<_:Offs/binary,?SDP_UUID:5, 4:3, X:16/binary,_/binary>> -> + {uuid,X}; + %% type=4 Text string + <<_:Offs/binary,?SDP_TEXT:5, 5:3, Len:8, X:Len/binary,_/binary>> -> + {text,binary_to_list(X)}; + <<_:Offs/binary,?SDP_TEXT:5, 6:3, Len:16, X:Len/binary,_/binary>> -> + {text,binary_to_list(X)}; + <<_:Offs/binary,?SDP_TEXT:5, 7:3, Len:32, X:Len/binary,_/binary>> -> + {text,binary_to_list(X)}; + %% type=5 Boolean + <<_:Offs/binary,?SDP_BOOLEAN:5, 0:3, 0,_/binary>> -> + {boolean,false}; + <<_:Offs/binary,?SDP_BOOLEAN:5, 0:3, _,_/binary>> -> + {boolean,true}; + %% type=6 Sequence + <<_:Offs/binary,?SDP_SEQUENCE:5, 5:3, Len:8, _/binary>> -> + {sequence, decode_sequence(Bin,Offs+2, Len, [])}; + <<_:Offs/binary,?SDP_SEQUENCE:5, 6:3, Len:16, _/binary>> -> + {sequence, decode_sequence(Bin,Offs+3, Len, [])}; + <<_:Offs/binary,?SDP_SEQUENCE:5, 7:3, Len:32, _/binary>> -> + {sequence, decode_sequence(Bin,Offs+5, Len, [])}; + %% type=7 Alternative + <<_:Offs/binary,?SDP_ALTERNATIVE:5, 5:3, Len:8, _/binary>> -> + {alternative, decode_sequence(Bin,Offs+2, Len, [])}; + <<_:Offs/binary,?SDP_ALTERNATIVE:5, 6:3, Len:16, _/binary>> -> + {alternative, decode_sequence(Bin,Offs+3, Len, [])}; + <<_:Offs/binary,?SDP_ALTERNATIVE:5, 7:3, Len:32, _/binary>> -> + {alternative, decode_sequence(Bin,Offs+5, Len, [])}; + %% type=8 URL + <<_:Offs/binary,?SDP_URL:5, 5:3, Len:8, X:Len/binary,_/binary>> -> + {url,binary_to_list(X)}; + <<_:Offs/binary,?SDP_URL:5, 6:3, Len:16, X:Len/binary,_/binary>> -> + {url,binary_to_list(X)}; + <<_:Offs/binary,?SDP_URL:5, 7:3, Len:32, X:Len/binary,_/binary>> -> + {url,binary_to_list(X)}; + %% 9-31 reserved + <<_:Offs/binary,Code:5, _:3, _/binary>> when Code >= 9 -> + Sz = sizeof(Bin, Offs), + <<_:Offs/binary,_,Reserved:Sz/binary,_/binary>> = Bin, + %% The reserved includes the tag byte + {reserved, Code, Reserved} + end. + +decode_sequence(Bin,Offset,Len,Acc) when Len > 0 -> + Elem = decode_value(Bin, Offset), + Sz = sizeof(Bin, Offset), + decode_sequence(Bin,Offset+Sz,Len-Sz,[Elem|Acc]); +decode_sequence(_Bin,_Offset,0,Acc) -> + reverse(Acc). + +encode_value(nil) -> <<?SDP_NIL:5, 0:3>>; + +encode_value({uint8,X}) -> <<?SDP_UNSIGNED:5, 0:3, X:8>>; +encode_value({uint16,X}) -> <<?SDP_UNSIGNED:5, 1:3, X:16>>; +encode_value({uint32,X}) -> <<?SDP_UNSIGNED:5, 2:3, X:32>>; +encode_value({uint64,X}) -> <<?SDP_UNSIGNED:5, 3:3, X:64>>; +encode_value({uint128,X}) -> <<?SDP_UNSIGNED:5, 4:3, X:128>>; +encode_value({uint,X}) when X>=0 -> + if X<16#100 -> <<?SDP_UNSIGNED:5, 0:3, X:8>>; + X<16#10000 -> <<?SDP_UNSIGNED:5, 1:3, X:16>>; + X<16#100000000 -> <<?SDP_UNSIGNED:5, 2:3, X:32>>; + X<16#10000000000000000 -> <<?SDP_UNSIGNED:5, 3:3, X:64>>; + true -> (<<?SDP_UNSIGNED:5, 4:3, X:128>>) + end; +encode_value({int8,X}) -> <<?SDP_SIGNED:5, 0:3, X:8>>; +encode_value({int16,X}) -> <<?SDP_SIGNED:5, 1:3, X:16>>; +encode_value({int32,X}) -> <<?SDP_SIGNED:5, 2:3, X:32>>; +encode_value({int64,X}) -> <<?SDP_SIGNED:5, 3:3, X:64>>; +encode_value({int128,X}) -> <<?SDP_SIGNED:5, 4:3, X:128>>; +encode_value({int,X}) -> + if X >= -16#100, X<16#100 -> + <<?SDP_SIGNED:5, 0:3, X:8>>; + X >= -16#10000, X<16#10000 -> + <<?SDP_SIGNED:5, 1:3, X:16>>; + X >= -16#100000000, X<16#100000000 -> + <<?SDP_SIGNED:5, 2:3, X:32>>; + X >= -16#10000000000000000, X<16#10000000000000000 -> + <<?SDP_SIGNED:5,3:3,X:64>>; + true -> (<<?SDP_SIGNED:5, 4:3, X:128>>) + end; +encode_value({uuid,UUID}) when is_binary(UUID) -> + case size(UUID) of + 2 -> <<?SDP_UUID:5, 1:3, UUID/binary>>; + 4 -> <<?SDP_UUID:5, 2:3, UUID/binary>>; + 16 -> (<<?SDP_UUID:5, 4:3, UUID/binary>>) + end; +encode_value({text,Text}) when is_list(Text) -> + Bin = list_to_binary(Text), + Len = size(Bin), + if Len =< 16#100 -> + <<?SDP_TEXT:5, 5:3, Len:8, Bin/binary>>; + Len =< 16#10000 -> + <<?SDP_TEXT:5, 6:3, Len:16, Bin/binary>>; + true -> + (<<?SDP_TEXT:5, 6:3, Len:32, Bin/binary>>) + end; +encode_value({boolean,Bool}) -> + case Bool of + true -> <<?SDP_BOOLEAN:5, 0:3, 1>>; + false -> (<<?SDP_BOOLEAN:5, 0:3, 0>>) + end; +encode_value({url,URL}) when is_list(URL) -> + Bin = list_to_binary(URL), + Len = size(Bin), + if Len =< 16#100 -> + <<?SDP_URL:5, 5:3, Len:8, Bin/binary>>; + Len =< 16#10000 -> + <<?SDP_URL:5, 6:3, Len:16, Bin/binary>>; + true -> + (<<?SDP_URL:5, 6:3, Len:32, Bin/binary>>) + end; +encode_value({sequence, List}) -> + Bin = list_to_binary(map(fun(E) -> encode_value(E) end, List)), + Len = size(Bin), + if Len =< 16#100 -> + <<?SDP_SEQUENCE:5, 5:3, Len:8, Bin/binary>>; + Len =< 16#10000 -> + <<?SDP_SEQUENCE:5, 6:3, Len:16, Bin/binary>>; + true -> + (<<?SDP_SEQUENCE:5, 6:3, Len:32, Bin/binary>>) + end; +encode_value({alternative, List}) -> + Bin = list_to_binary(map(fun(E) -> encode_value(E) end, List)), + Len = size(Bin), + if Len =< 16#100 -> + <<?SDP_ALTERNATIVE:5, 5:3, Len:8, Bin/binary>>; + Len =< 16#10000 -> + <<?SDP_ALTERNATIVE:5, 6:3, Len:16, Bin/binary>>; + true -> + (<<?SDP_ALTERNATIVE:5, 6:3, Len:32, Bin/binary>>) + end; +encode_value({reserved,_Code,Bin}) when is_binary(Bin) -> + Bin. + +fold_value(Fun, Acc, Value={alternative,List}) -> + fold_value(Fun, Fun(Value,Acc), List); +fold_value(Fun, Acc, Value={sequence,List}) -> + fold_value(Fun, Fun(Value,Acc), List); +fold_value(Fun, Acc,[H|T]) -> + fold_value(Fun, fold_value(H, Fun, Acc), T); +fold_value(_Fun, Acc, []) -> + Acc; +fold_value(Fun, Acc, Value) -> + Fun(Value, Acc). + + + +%% make a "decode" of binary sdp value +decode_sdp_value({uuid,Bin}) when is_binary(Bin) -> uuid_to_string(Bin); +decode_sdp_value({uint8,Int}) when is_integer(Int) -> Int; +decode_sdp_value({uint16,Int}) when is_integer(Int) -> Int; +decode_sdp_value({uint32,Int}) when is_integer(Int) -> Int; +decode_sdp_value({uint64,Int}) when is_integer(Int) -> Int; +decode_sdp_value({uint128,Int}) when is_integer(Int) -> Int; +decode_sdp_value({int8,Int}) when is_integer(Int) -> Int; +decode_sdp_value({int16,Int}) when is_integer(Int) -> Int; +decode_sdp_value({int32,Int}) when is_integer(Int) -> Int; +decode_sdp_value({int64,Int}) when is_integer(Int) -> Int; +decode_sdp_value({int128,Int}) when is_integer(Int) -> Int; +decode_sdp_value({boolean,Bool}) -> Bool; +decode_sdp_value({text,Text}) -> Text; +decode_sdp_value({url,Url}) -> Url; +decode_sdp_value({sequence,Seq}) -> + {sequence,map(fun decode_sdp_value/1, Seq)}; +decode_sdp_value({alternative,Alt}) -> + {alternative,map(fun decode_sdp_value/1, Alt)}. + +is_value(nil) -> true; +is_value({uint8,X}) when is_integer(X), X>=0 -> true; +is_value({uint16,X}) when is_integer(X), X>=0 -> true; +is_value({uint32,X}) when is_integer(X), X>=0 -> true; +is_value({uint64,X}) when is_integer(X), X>=0 -> true; +is_value({uint128,X}) when is_integer(X), X>=0 -> true; +is_value({uint,X}) when is_integer(X), X>=0 -> true; +is_value({int8,X}) when is_integer(X) -> true; +is_value({int16,X}) when is_integer(X) -> true; +is_value({int32,X}) when is_integer(X) -> true; +is_value({int64,X}) when is_integer(X) -> true; +is_value({int128,X}) when is_integer(X) -> true; +is_value({int,X}) when is_integer(X) -> true; +is_value({uuid,UUID}) when is_binary(UUID) -> + case size(UUID) of + 2 -> true; + 4 -> true; + 16 -> true; + _ -> false + end; +is_value({text,Text}) when is_list(Text) -> + lists:all(fun(X) -> is_integer(X) end, Text); +is_value({boolean,true}) -> true; +is_value({boolean,false}) -> true; +is_value({url,URL}) when is_list(URL) -> + case catch list_to_binary(URL) of + {'EXIT', _} -> false; + _ -> true + end; +is_value({sequence, List}) -> + lists:all(fun is_value/1, List); +is_value({alternative, List}) -> + lists:all(fun is_value/1, List); +is_value({reserved,_Code,Bin}) when is_binary(Bin) -> + Bin; +is_value(_) -> false. + + + + +value_to_string({uuid,Bin}) -> uuid_to_string(Bin); +value_to_string({uint8,Int}) -> integer_to_list(Int); +value_to_string({uint16,Int}) -> integer_to_list(Int); +value_to_string({uint32,Int}) -> integer_to_list(Int); +value_to_string({uint64,Int}) -> integer_to_list(Int); +value_to_string({uint128,Int}) -> integer_to_list(Int); +value_to_string({int8,Int}) -> integer_to_list(Int); +value_to_string({int16,Int}) -> integer_to_list(Int); +value_to_string({int32,Int}) -> integer_to_list(Int); +value_to_string({int64,Int}) -> integer_to_list(Int); +value_to_string({int128,Int}) -> integer_to_list(Int); +value_to_string({boolean,Bool}) -> atom_to_list(Bool); +value_to_string({sequence,Seq}) -> + map_fmt(fun value_to_string/1, ",", Seq); +value_to_string({alternative,Alt}) -> + map_fmt(fun value_to_string/1, "|", Alt); +value_to_string({url,URL}) -> URL; +value_to_string({text,Text}) -> [$\",Text, $\"]. + +map_fmt(Fun, _Sep, [H]) -> Fun(H); +map_fmt(Fun, Sep, [H|T]) -> [Fun(H),Sep | map_fmt(Fun, Sep, T)]; +map_fmt(_Fun, _Sep, []) -> []. + +%% convert to full uuid +uuid_128(<<UUID:16>>) -> ?BT_UUID16(UUID); +uuid_128(<<UUID:32>>) -> ?BT_UUID32(UUID); +uuid_128(<<>>) -> <<>>; +uuid_128(UUID) when ?is_uuid(UUID) -> UUID. + +%% Try convert UUID into symboic name +uuid_to_string(UUID) -> + case uuid_128(UUID) of + UUID128 = ?BT_UUID16(UUID16) -> + case <<UUID16:16>> of + %% PROTOCOLS + ?UUID_SDP -> "SDP"; + ?UUID_UDP -> "UDP"; + ?UUID_RFCOMM -> "RFCOMM"; + ?UUID_TCP -> "TCP"; + ?UUID_TCS_BIN -> "TCS_BIN"; + ?UUID_TCS_AT -> "TCS_AT"; + ?UUID_OBEX -> "OBEX"; + ?UUID_IP -> "IP"; + ?UUID_FTP -> "FTP"; + ?UUID_HTTP -> "HTTP"; + ?UUID_WSP -> "WSP"; + ?UUID_BNEP -> "BNEP"; + ?UUID_UPNP -> "UPNP"; + ?UUID_HIDP -> "HIDP"; + ?UUID_HCRP_CTRL -> "HCRP_CTRL"; + ?UUID_HCRP_DATA -> "HCRP_DATA"; + ?UUID_HCRP_NOTE -> "HRCP_NOTE"; + ?UUID_AVCTP -> "AVCTP"; + ?UUID_AVDTP -> "AVDTP"; + ?UUID_CMPT -> "CMPT"; + ?UUID_UDI -> "UDI"; + ?UUID_MCAP_CTRL -> "MCAP_CTRL"; + ?UUID_MCAP_DATA -> "MCAP_DATA"; + ?UUID_L2CAP -> "L2CAP"; + + %% SERVICE Classes + ?UUID_ServiceDiscoveryServer -> "ServiceDiscoveryServer"; + ?UUID_BrowseGroupDescriptor -> "BrowseGroupDescriptor"; + ?UUID_PublicBrowseGroup -> "PublicBrowseGroup"; + ?UUID_SerialPort -> "SerialPort"; + ?UUID_LANAccessUsingPPP -> "LANAccessUsingPPP"; + ?UUID_DialupNetworking -> "DialupNetworking"; + ?UUID_IrMCSync -> "IrMCSync"; + ?UUID_OBEXObjectPush -> "OBEXObjectPush"; + ?UUID_OBEXFileTransfer -> "OBEXFileTransfer"; + ?UUID_IrMCSyncCommand -> "IrMCSyncCommand"; + ?UUID_Headset -> "Headset"; + ?UUID_CordlessTelephony -> "CordlessTelephony"; + ?UUID_AudioSource -> "AudioSource"; + ?UUID_AudioSink -> "AudioSink"; + ?UUID_AVRemoteControlTarget -> "AVRemoteControlTarget"; + ?UUID_AdvancedAudioDistribution -> "AdvancedAudioDistribution"; + ?UUID_AVRemoteControl -> "AVRemoteControl"; + ?UUID_VideoConferencing -> "VideoConferencing"; + ?UUID_Intercom -> "Intercom"; + ?UUID_Fax -> "Fax"; + ?UUID_HeadsetAudioGateway -> "HeadsetAudioGateway"; + ?UUID_WAP -> "WAP"; + ?UUID_WAPClient -> "WAPClient"; + ?UUID_PANU -> "PANU"; + ?UUID_NAP -> "NAP"; + ?UUID_GN -> "GN"; + ?UUID_DirectPrinting -> "DirectPrinting"; + ?UUID_ReferencePrinting -> "ReferencePrinting"; + ?UUID_Imaging -> "Imaging"; + ?UUID_ImagingResponder -> "ImagingResponder"; + ?UUID_ImagingAutomaticArchive -> "ImagingAutomaticArchive"; + ?UUID_ImagingReferencedObjects -> "ImagingReferencedObjects"; + ?UUID_Handsfree -> "Handsfree"; + ?UUID_HandsfreeAudioGateway -> "HandsfreeAudioGateway"; + ?UUID_DirectPrintingReferenceObjectsService -> "DirectPrintingReferenceObjectsService"; + ?UUID_ReflectedUI -> "ReflectedUI"; + ?UUID_BasicPrinting -> "BasicPrinting"; + ?UUID_PrintingStatus -> "PrintingStatus"; + ?UUID_HumanInterfaceDeviceService -> "HumanInterfaceDeviceService"; + ?UUID_HardcopyCableReplacement -> "HardcopyCableReplacement"; + ?UUID_HCR_Print -> "HCR_Print"; + ?UUID_HCR_Scan -> "HCR_Scan"; + ?UUID_CommonISDNAccess -> "CommonISDNAccess"; + ?UUID_VideoConferencingGW -> "VideoConferencingGW"; + ?UUID_UDI_MT -> "UDI_MT"; + ?UUID_UDI_TA -> "UDI_TA"; + ?UUID_Audio_Video -> "Audio/Video"; + ?UUID_SIM_Access -> "SIM_Access"; + ?UUID_PhonebookAccess_PCE -> "PhonebookAccess-PCE"; + ?UUID_PhonebookAccess_PSE -> "PhonebookAccess-PSE"; + + ?UUID_PhonebookAccess -> "PhonebookAccessProfile"; + ?UUID_Headset_HS -> "Headset Profile"; + ?UUID_Message_Access_Server -> "MessageAccessProfile"; + ?UUID_Message_Notification_Server -> "MessageNotificationServer"; + ?UUID_Message_Access_Profile -> "MessageAccessProfile"; + ?UUID_GNSS -> "GNSSProfile"; + ?UUID_GNSS_Server -> "GNSSSever"; + ?UUID_3D_Display -> "3D-Display"; + ?UUID_3D_Glasses -> "3D-Glasses"; + ?UUID_3D_Synchronization -> "3D-Synchronization"; + ?UUID_MPS_Profile -> "MPS"; + ?UUID_MPS_SC -> "MPS-SC"; + ?UUID_CTN_Access_Service -> "CTNAccessService"; + ?UUID_CTN_Notification_Service ->"CTNNotificationService"; + ?UUID_CTN_Profile -> "CTNProfile"; + ?UUID_PnPInformation -> "PnPInformation"; + ?UUID_GenericNetworking -> "GenericNetworking"; + ?UUID_GenericFileTransfer -> "GenericFileTransfer"; + ?UUID_GenericAudio -> "GenericAudio"; + ?UUID_GenericTelephony -> "GenericTelephony"; + ?UUID_UPNP_Service -> "UPNP_Service"; + ?UUID_UPNP_IP_Service -> "UPNP_IP_Service"; + ?UUID_ESDP_UPNP_IP_PAN -> "ESDP_UPNP_IP_PAN"; + ?UUID_ESDP_UPNP_IP_LAP -> "ESDP_UPNP_IP_LAP"; + ?UUID_ESDP_UPNP_L2CAP -> "ESDP_UPNP_L2CAP"; + ?UUID_VideoSource -> "VideoSource"; + ?UUID_VideoSink -> "VideoSink"; + ?UUID_VideoDistribution -> "VideoDistribution"; + ?UUID_HDP -> "HDP"; + ?UUID_HDP_Source -> "HDP-Source"; + ?UUID_HDP_Sink -> "HDP-Sink"; + _ -> bt_util:uuid_to_string(UUID128) + end; + ?UUID_SyncMLServer -> "SyncMLServer"; + ?UUID_SyncMLClient -> "SyncMLClient"; + ?UUID_SyncMLDMServer -> "SyncMLDMServer"; + ?UUID_SyncMLDMClient -> "SyncMLDMClient"; + ?UUID_NokiaSyncMLServer -> "NokiaSyncMLServer"; + ?UUID_NokiaObexPcSuiteServices -> "NokiaObexPcSuiteService"; + UUID128 -> bt_util:uuid_to_string(UUID128) + end. + + + +%% Try convert symboic name into UUID +string_to_uuid(Name) when is_list(Name) -> + case tolower(Name) of + %% PROTOCOLS + "sdp" -> ?UUID_SDP; + "udp" -> ?UUID_UDP; + "rfcomm" -> ?UUID_RFCOMM; + "tcp" -> ?UUID_TCP; + "tcs_bin" -> ?UUID_TCS_BIN; + "tcs_at" -> ?UUID_TCS_AT; + "obex" -> ?UUID_OBEX; + "ip" -> ?UUID_IP; + "ftp" -> ?UUID_FTP; + "http" -> ?UUID_HTTP; + "wsp" -> ?UUID_WSP; + "bnep" -> ?UUID_BNEP; + "upnp" -> ?UUID_UPNP; + "hidp" -> ?UUID_HIDP; + "hardcopycontrolchannel" -> ?UUID_HCRP_CTRL; + "hardcopydatachannel" -> ?UUID_HCRP_DATA; + "hardcopynotification" -> ?UUID_HCRP_NOTE; + "avctp" -> ?UUID_AVCTP; + "avdtp" -> ?UUID_AVDTP; + "cmpt" -> ?UUID_CMPT; + "udi" -> ?UUID_UDI; + "l2cap" -> ?UUID_L2CAP; + %% SERVICE Classes + "servicediscoveryserver" -> ?UUID_ServiceDiscoveryServer; + "browsegroupdescriptor" -> ?UUID_BrowseGroupDescriptor; + "publicbrowsegroup" -> ?UUID_PublicBrowseGroup; + "serialport" -> ?UUID_SerialPort; + "lanaccessusingppp" -> ?UUID_LANAccessUsingPPP; + "dialupnetworking" -> ?UUID_DialupNetworking; + "irmcsync" -> ?UUID_IrMCSync; + "obexobjectpush" -> ?UUID_OBEXObjectPush; + "obexfiletransfer" -> ?UUID_OBEXFileTransfer; + "irmcsynccommand" -> ?UUID_IrMCSyncCommand; + "headset" -> ?UUID_Headset; + "cordlesstelephony" -> ?UUID_CordlessTelephony; + "audiosource" -> ?UUID_AudioSource; + "audiosink" -> ?UUID_AudioSink; + "avremotecontroltarget" -> ?UUID_AVRemoteControlTarget; + "advancedaudiodistribution" -> ?UUID_AdvancedAudioDistribution; + "avremotecontrol" -> ?UUID_AVRemoteControl; + "videoconferencing" -> ?UUID_VideoConferencing; + "intercom" -> ?UUID_Intercom; + "fax" -> ?UUID_Fax; + "headsetaudiogateway" -> ?UUID_HeadsetAudioGateway; + "wap" -> ?UUID_WAP; + "wapclient" -> ?UUID_WAPClient; + "panu" -> ?UUID_PANU; + "nap" -> ?UUID_NAP; + "gn" -> ?UUID_GN; + "directprinting" -> ?UUID_DirectPrinting; + "referenceprinting" -> ?UUID_ReferencePrinting; + "imaging" -> ?UUID_Imaging; + "imagingresponder" -> ?UUID_ImagingResponder; + "imagingautomaticarchive" -> ?UUID_ImagingAutomaticArchive; + "imagingreferencedobjects" -> ?UUID_ImagingReferencedObjects; + "handsfree" -> ?UUID_Handsfree; + "handsfreeaudiogateway" -> ?UUID_HandsfreeAudioGateway; + "directprintingreferenceobjectsservice" -> ?UUID_DirectPrintingReferenceObjectsService; + "reflectedui" -> ?UUID_ReflectedUI; + "basicprinting" -> ?UUID_BasicPrinting; + "printingstatus" -> ?UUID_PrintingStatus; + "humaninterfacedeviceservice" -> ?UUID_HumanInterfaceDeviceService; + "hardcopycablereplacement" -> ?UUID_HardcopyCableReplacement; + "hcr_print" -> ?UUID_HCR_Print; + "hcr_scan" -> ?UUID_HCR_Scan; + "commonisdnaccess" -> ?UUID_CommonISDNAccess; + "videoconferencinggw" -> ?UUID_VideoConferencingGW; + "udi_mt" -> ?UUID_UDI_MT; + "udi_ta" -> ?UUID_UDI_TA; + "audio/video" -> ?UUID_Audio_Video; + "sim_access" -> ?UUID_SIM_Access; + "phonebookaccess-pce" -> ?UUID_PhonebookAccess_PCE; + "phonebookaccess-pse" -> ?UUID_PhonebookAccess_PSE; + "pnpinformation" -> ?UUID_PnPInformation; + "genericnetworking" -> ?UUID_GenericNetworking; + "genericfiletransfer" -> ?UUID_GenericFileTransfer; + "genericaudio" -> ?UUID_GenericAudio; + "generictelephony" -> ?UUID_GenericTelephony; + "upnp_service" -> ?UUID_UPNP_Service; + "upnp_ip_service" -> ?UUID_UPNP_IP_Service; + "esdp_upnp_ip_pan" -> ?UUID_ESDP_UPNP_IP_PAN; + "esdp_upnp_ip_lap" -> ?UUID_ESDP_UPNP_IP_LAP; + "esdp_upnp_l2cap" -> ?UUID_ESDP_UPNP_L2CAP; + "videosource" -> ?UUID_VideoSource; + "videosink" -> ?UUID_VideoSink; + "videodistribution" -> ?UUID_VideoDistribution; + + %% SyncML + "syncmlserver" -> ?UUID_SyncMLServer; + "syncmlclient" -> ?UUID_SyncMLClient; + "syncmldmserver" -> ?UUID_SyncMLDMServer; + "syncmldmclient" -> ?UUID_SyncMLDMClient; + _ -> bt_util:string_to_uuid(Name) + end; +string_to_uuid(Name) when is_atom(Name) -> + string_to_uuid(atom_to_list(Name)). + + +string_to_attribute(Name) -> + string_to_attribute(Name, 16#0100). + +string_to_attribute(Name,LanguageBase) when is_list(Name) -> + case tolower(Name) of + "servicerecordhandle" -> ?ATTR_ServiceRecordHandle; + "serviceclassidlist" -> ?ATTR_ServiceClassIDList; + "servicerecordstate" -> ?ATTR_ServiceRecordState; + "serviceid" -> ?ATTR_ServiceID; + "protocoldescriptorlist" -> ?ATTR_ProtocolDescriptorList; + "browsegrouplist" -> ?ATTR_BrowseGroupList; + "languagebaseattributeidlist" -> + ?ATTR_LanguageBaseAttributeIDList; + "serviceinfotimetolive" -> ?ATTR_ServiceInfoTimeToLive; + "serviceavailability" -> ?ATTR_ServiceAvailability; + "bluetoothprofiledescriptorlist" -> + ?ATTR_BluetoothProfileDescriptorList; + "documentationurl" -> ?ATTR_DocumentationURL; + "clientexecutableurl" -> ?ATTR_ClientExecutableURL; + "iconurl" -> ?ATTR_IconURL; + "additionalprotocolsdescriptorlist" -> + ?ATTR_AdditionalProtocolsDescriptorList; + %% + "servicename" -> ?ATTR_ServiceName+LanguageBase; + "servicedescription" -> ?ATTR_ServiceDescription+LanguageBase; + "providername" -> ?ATTR_ProviderName+LanguageBase; + + %% SDP + "servicedatabasestate" -> 16#0201; + %% PAN + "ipsubnet" -> 16#0200; + %% OTHER + "externalnetwork" -> 16#0301; + "network" -> 16#0301; + "supporteddatastoreslist" -> 16#0301; + "faxclass1support" -> 16#0302; + "faxclass2support" -> 16#0303; + "remoteaudiovolumecontrol" -> 16#0302; + "supporterformatslist" -> 16#0303; + %% Check hex + _ -> + case catch erlang:list_to_integer(Name, 16) of + {'EXIT',_} -> + erlang:error(badarg); + ID -> ID band 16#ffff + end + end; +string_to_attribute(Name,LanguageBase) when is_atom(Name) -> + string_to_attribute(atom_to_list(Name),LanguageBase). + + +attribute_to_string(ID) -> + attribute_to_string(ID,16#0100). + +attribute_to_string(ID,LanguageBase) -> + attribute_to_string(ID,LanguageBase,<<>>). + +attribute_to_string(ID,LanguageBase,UUID) -> + case uuid_128(UUID) of + ?BT_UUID16(UUID16) -> + case UUID16-LanguageBase of + 16#0000 -> "ServiceName"; + 16#0001 -> "ServiceDescription"; + 16#0002 -> "ProviderName"; + _ -> attr16(ID,<<UUID16:16>>) + end; + UUID128 -> + attr(ID, LanguageBase,UUID128) + end. + +attr(ID,_LanuageBase,_UUID) -> + attr16_id(ID). + +%% +%% Universal Attributes +%% +attr16(?ATTR_ServiceRecordHandle,_UUID) -> "ServiceRecordHandle"; +attr16(?ATTR_ServiceClassIDList,_UUID) -> "ServiceClassIDList"; +attr16(?ATTR_ServiceRecordState,_UUID) -> "ServiceRecordState"; +attr16(?ATTR_ServiceID,_UUID) -> "ServiceID"; +attr16(?ATTR_ProtocolDescriptorList,_UUID) -> "ProtocolDescriptorList"; +attr16(?ATTR_BrowseGroupList,_UUID) -> "BrowseGroupList"; +attr16(?ATTR_LanguageBaseAttributeIDList,_UUID) -> "LanguageBaseAttributeIDList"; +attr16(?ATTR_ServiceInfoTimeToLive,_UUID) -> "ServiceInfoTimeToLive"; +attr16(?ATTR_ServiceAvailability,_UUID) -> "ServiceAvailability"; +attr16(?ATTR_BluetoothProfileDescriptorList,_UUID) -> "BluetoothProfileDescriptorList"; +attr16(?ATTR_DocumentationURL,_UUID) -> "DocumentationURL"; +attr16(?ATTR_ClientExecutableURL,_UUID) -> "ClientExecutableURL"; +attr16(?ATTR_IconURL,_UUID) -> "IconURL"; +attr16(?ATTR_AdditionalProtocolsDescriptorList,_UUID) ->"AdditionalProtocolsDescriptorList"; + +attr16(16#0200, ?UUID_BrowseGroupDescriptor) -> "GroupID"; + +%% SDP 1000 +attr16(ID,?UUID_ServiceDiscoveryServer) -> attr16_sdp(ID); + +%% AVRCP +attr16(ID,?UUID_AVRemoteControlTarget) -> attr16_avrcp(ID); +attr16(ID,?UUID_AVRemoteControl) -> attr16_avrcp(ID); +attr16(ID,?UUID_VideoConferencing) -> attr16_avrcp(ID); + +%% PAN +attr16(ID,?UUID_PANU) -> attr16_pan(ID); +attr16(ID,?UUID_NAP) -> attr16_pan(ID); +attr16(ID,?UUID_GN) -> attr16_pan(ID); + +%% BIP 0x111B, 0x111C, 0x111D +attr16(ID,?UUID_ImagingResponder) -> attr16_bip(ID); +attr16(ID,?UUID_ImagingAutomaticArchive) -> attr16_bip(ID); +attr16(ID,?UUID_ImagingReferencedObjects) -> attr16_bip(ID); + +attr16(ID,?UUID_PnPInformation) -> attr16_pn(ID); + +attr16(ID,_UUID) -> attr16_id(ID). + + +attr16_sdp(?ATTR_SDP_VersionNumberList) -> "VersionNumberList"; +attr16_sdp(?ATTR_SDP_ServiceDatabaseState) -> "ServiceDatabaseState"; +attr16_sdp(ID) -> attr16_id(ID). + +attr16_bip(?ATTR_BIP_GoepL2capPsm) -> "GoepL2capPsm"; +attr16_bip(?ATTR_BIP_SupportedCapabilities) -> "SupportedCapabilities"; +attr16_bip(?ATTR_BIP_SupportedFeatures) -> "SupportedFeatures"; +attr16_bip(?ATTR_BIP_SupportedFunctions) -> "SupportedFunctions"; +attr16_bip(?ATTR_BIP_TotalImagingDataCapacity) -> "TotalImagingDataCapacity"; +attr16_bip(ID) -> attr16_id(ID). + +attr16_pan(16#0200) -> "IpSubnet"; +attr16_pan(16#030A) -> "SecurityDescription"; +attr16_pan(16#030B) -> "NetAccessType"; +attr16_pan(16#030C) -> "MaxNetAccessrate"; +attr16_pan(16#030D) -> "IPv4Subnet"; +attr16_pan(16#030E) -> "IPv6Subnet"; +attr16_pan(ID) -> attr16_id(ID). + +attr16_avrcp(16#0311) -> "SupportedFeatures"; +attr16_avrcp(ID) -> attr16_id(ID). + +attr16_pn(16#0200) -> "SpecificationID"; +attr16_pn(16#0201) -> "VendorID"; +attr16_pn(16#0202) -> "ProductID"; +attr16_pn(16#0203) -> "Version"; +attr16_pn(16#0204) -> "PrimaryRecord"; +attr16_pn(16#0205) -> "VendorIDSource"; +attr16_pn(ID) -> attr16_id(ID). + +%% convert to 0xABCD format +attr16_id(ID) -> + "0x"++tl(integer_to_list((ID band 16#ffff) bor 16#10000, 16)). + +tolower([H|T]) when H>=$A, H=<$Z -> + [(H-$A)+$a | tolower(T)]; +tolower([H|T]) -> + [H|tolower(T)]; +tolower([]) -> + []. diff --git a/deps/bt/src/bt_sdp_srv.erl b/deps/bt/src/bt_sdp_srv.erl new file mode 100644 index 0000000..05530c4 --- /dev/null +++ b/deps/bt/src/bt_sdp_srv.erl @@ -0,0 +1,608 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +%%%------------------------------------------------------------------- +%%% File : bt_sdp_srv.erl +%%% Author : Tony Rogvall <tony@PBook.local> +%%% Description : BLuetooth SDP server +%%% +%%% Created : 31 Jul 2006 by Tony Rogvall <tony@PBook.local> +%%%------------------------------------------------------------------- +-module(bt_sdp_srv). + +-behaviour(gen_server). + +-include("../include/bt.hrl"). +-include("../include/sdp.hrl"). + +-compile(export_all). + +%% API +-export([start/0, start_link/0, stop/0]). + +-export([service_add/1, + service_add_persist/1, + service_del/1]). + +-export([rfcomm_channel/1, + service_search/1, + service_attribute/2, + service_search_attribute/2]). + + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-import(lists, [reverse/1, map/2, foldl/3, filter/2]). + +-record(serviceRecord, + { + handle, %% generated 32 bit handle + monitor, %% creator monitor (or undefined for persist) + uuid_set=[], %% list of uuid included in this record + rfcomm_list=[], %% list of used rfcomm channels + psm_list=[], %% list of used psm (dynamic) + attributes=[] + }). + +-record(state, + { + %% rfcomm free list (iset) + rfcomm = [], + %% dynamic psm free list (iset) + psm = [], + records = [] + }). + +-define(SERVER, bt_sdp_srv). +%%==================================================================== +%% API +%%==================================================================== +%%-------------------------------------------------------------------- +%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} +%% Description: Starts the server +%%-------------------------------------------------------------------- +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +start() -> + gen_server:start({local, ?SERVER}, ?MODULE, [], []). + +stop() -> + gen_server:call(?SERVER, stop). + +service_add(ServiceRecord) when is_list(ServiceRecord) -> + gen_server:call(?SERVER, {service_add,self(),ServiceRecord}). + +service_add_persist(ServiceRecord) when is_list(ServiceRecord) -> + gen_server:call(?SERVER, {service_add,undefined,ServiceRecord}). + +service_del(Handle) when is_integer(Handle) -> + gen_server:call(?SERVER, {service_del, Handle}). + +rfcomm_channel(UUID) when is_binary(UUID) -> + gen_server:call(?SERVER, {rfcomm_channel, UUID}); +rfcomm_channel(Handle) when is_integer(Handle) -> + gen_server:call(?SERVER, {rfcomm_channel, Handle}). + +service_search(UUIDList) when is_list(UUIDList) -> + gen_server:call(?SERVER, {service_search, uuid_set(UUIDList)}). + +service_attribute(Handle, AttributeList) + when is_integer(Handle), is_list(AttributeList) -> + gen_server:call(?SERVER, {service_attribute,Handle, + attribute_iset(AttributeList)}). + +service_search_attribute(UUIDList,AttributeList) + when is_list(UUIDList), is_list(AttributeList) -> + gen_server:call(?SERVER, {service_search_attribute, + uuid_set(UUIDList), + attribute_iset(AttributeList)}). + + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% Description: Initiates the server +%%-------------------------------------------------------------------- +init([]) -> + {A,B,C} = now(), + random:seed(A,B,C), + SDP = #serviceRecord { handle = 0, + monitor = undefined, + uuid_set=uuid_set( + [?UUID_ServiceDiscoveryServer, + ?UUID_L2CAP]), + rfcomm_list=[], + psm_list=[], + attributes= + [{?ATTR_ServiceRecordHandle,{uint32,0}}, + {?ATTR_ServiceClassIDList, + {sequence,[{uuid,?UUID_ServiceDiscoveryServer}]}}, + {?ATTR_ServiceRecordState,{uint32,0}}, + {?ATTR_ProtocolDescriptorList, + {sequence, + [{sequence,[{uuid,?UUID_L2CAP}]}]}}, + {?ATTR_BrowseGroupList, + {sequence,[{uuid,?UUID_ServiceDiscoveryServer}]}}, + {?ATTR_LanguageBaseAttributeIDList, + {sequence, [{uint16, ?LANGUAGE($e,$n)}, + {uint16, ?ENCODING_UTF8}, + {uint16, 16#0100}]}}, + {?ATTR_ServiceInfoTimeToLive, + {uint32, 1200}}, + {?ATTR_ServiceAvailability, + {uint8, 255}}, + {?ATTR_ServiceName+16#0100, {text,"SDP Server"}} + ]}, + {ok, + #state { rfcomm = bt_iset:from_list([{1,30}]), + psm = bt_iset:from_list([{?L2CAP_PSM_DynamicStart, + ?L2CAP_PSM_DynamicEnd}]), + records = [SDP] + }}. + +%%-------------------------------------------------------------------- +%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, State} +%% Description: Handling call messages +%%-------------------------------------------------------------------- + +handle_call({service_add,Pid,ServiceRecord}, _From, State) -> + case is_service_record(ServiceRecord) of + true -> + case add_service(ServiceRecord, Pid, State) of + {ok,{Handle,State1}} -> + {reply,{ok,Handle},State1}; + {Error,State1} -> + {reply,Error,State1} + end; + false -> + {reply,{error,einval},State} + end; +handle_call({service_del,Handle}, _From, State) -> + {reply, ok, remove_service(Handle, State)}; +handle_call(stop, _From, State) -> + {stop, normal, ok, State}; + +handle_call({rfcomm_channel,UUID},_From,State) when is_binary(UUID) -> + case match_service(ordsets:from_list([UUID]), State#state.records) of + [] -> + {reply, {error, not_found}, State}; + [R] -> + {reply,{ok,get_rfcomm_channels(R#serviceRecord.attributes)},State}; + _Rs -> + {reply, {error,multiple_match},State} + end; +handle_call({rfcomm_channel,Handle},_From,State) when is_integer(Handle) -> + case lists:keysearch(Handle,#serviceRecord.handle,State#state.records) of + false -> + {reply, {error, not_found}, State}; + {value,R} -> + {reply,{ok,get_rfcomm_channels(R#serviceRecord.attributes)},State} + end; +handle_call({service_search, UUIDSet}, _From, State) -> + Rs = match_service(UUIDSet, State#state.records), + {reply, {ok,map(fun(R) -> R#serviceRecord.handle end,Rs)}, State}; +handle_call({service_attribute,Handle,AttributeISet}, _From, State) -> + case lists:keysearch(Handle,#serviceRecord.handle,State#state.records) of + false -> + {reply,{error,bad_handle},State}; + {value,R} -> + Is = filter(fun({ID,_}) -> + bt_iset:is_element(ID, AttributeISet) + end, R#serviceRecord.attributes), + {reply,{ok,Is},State} + end; +handle_call({service_search_attribute,UUIDSet,AttributeISet}, _From, State) -> + Rs = match_service(UUIDSet, State#state.records), + IRs = + map(fun(R) -> + filter(fun({ID,_}) -> + bt_iset:is_element(ID, AttributeISet) + end, R#serviceRecord.attributes) + end, Rs), + {reply,{ok,IRs},State}; +handle_call(_Request, _From, State) -> + {reply, {error,bad_call}, State}. + +%%-------------------------------------------------------------------- +%% Function: handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling cast messages +%%-------------------------------------------------------------------- +handle_cast(_Msg, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling all non call/cast messages +%%-------------------------------------------------------------------- +handle_info({'DOWN',Ref,_Pid,process,_Reason}, State) -> + State1 = remove_service(Ref, State), + {noreply, State1}; +handle_info(_Info, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: terminate(Reason, State) -> void() +%% Description: This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any necessary +%% cleaning up. When it returns, the gen_server terminates with Reason. +%% The return value is ignored. +%%-------------------------------------------------------------------- +terminate(_Reason, _State) -> + ok. + +%%-------------------------------------------------------------------- +%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} +%% Description: Convert process state when code is changed +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +add_monitor(undefined) -> + undefined; +add_monitor(Pid) when is_pid(Pid) -> + erlang:monitor(process, Pid). + +del_monitor(undefined) -> + undefined; +del_monitor(Mon) when is_reference(Mon) -> + erlang:demonitor(Mon). + +new_handle(State) -> + Handle = random:uniform(16#100000000)-1, + case lists:keysearch(Handle,#serviceRecord.handle,State#state.records) of + false -> + Handle; + {value,_} -> + new_handle(State) + end. + +%% match all services that have the UUIDSet as a subset. +match_service(UUIDSet, Records) -> + lists:filter(fun(R) -> + ordsets:is_subset(UUIDSet, R#serviceRecord.uuid_set) + end, Records). + +%% +%% Add a service +%% +add_service(ServiceRecord, Pid, State) -> + %% Collect all UUID's for search operations + UUIDSet = + foldl(fun({_ID,Value}, Acc) -> + bt_sdp:fold_value( + fun({uuid,UUID},Acc1) when is_binary(UUID) -> + UUID128 = bt_sdp:uuid_128(UUID), + ordsets:add_element(UUID128,Acc1); + ({uuid,UUID},Acc1) when + is_list(UUID); is_atom(UUID) -> + UUID1 = bt_sdp:string_to_uuid(UUID), + UUID128 = bt_sdp:uuid_128(UUID1), + ordsets:add_element(UUID128,Acc1); + (_, Acc1) -> Acc1 + end, Acc, Value) + end, ordsets:new(), ServiceRecord), + RequestCn = get_rfcomm_channels(ServiceRecord), + RequestPsm = get_l2cap_psms(ServiceRecord), + case avail(RequestCn, State#state.rfcomm) andalso + avail(RequestPsm, State#state.psm) of + true -> + {CnMap,RfComm} = allocate(RequestCn, State#state.rfcomm), + ServiceRecord1 = set_rfcomm_channels(ServiceRecord, CnMap), + + {PsmMap,Psm} = allocate(RequestPsm, State#state.psm), + ServiceRecord2 = set_l2cap_psms(ServiceRecord1, PsmMap), + + Handle = new_handle(State), + ServiceRecord3 = set_attribute({?ATTR_ServiceRecordHandle, + {uint32,Handle}}, + ServiceRecord2), + R = #serviceRecord { handle = Handle, + monitor = add_monitor(Pid), + uuid_set = UUIDSet, + rfcomm_list = map(fun({_,CN}) -> CN end, CnMap), + psm_list = map(fun({_,PSM}) -> PSM end, PsmMap), + attributes = ServiceRecord3 }, + Rs = [R | State#state.records], + {ok,{Handle,State#state { records = Rs, + rfcomm = RfComm, + psm = Psm }}}; + false -> + {{error,insufficient_resources},State} + end. + + +remove_service(Handle, State) when is_integer(Handle) -> + remove_service(Handle, #serviceRecord.handle, State); +remove_service(Monitor, State) when is_reference(Monitor) -> + remove_service(Monitor, #serviceRecord.monitor, State). + +remove_service(Key, Pos, State) -> + case lists:keysearch(Key, Pos, State#state.records) of + false -> + State; + {value,R} -> + del_monitor(R#serviceRecord.monitor), + RFComm = bt_iset:add_elements(R#serviceRecord.rfcomm_list, + State#state.rfcomm), + PSM = bt_iset:add_elements(R#serviceRecord.psm_list, + State#state.psm), + Rs = lists:keydelete(Key, Pos, State#state.records), + State#state { rfcomm=RFComm, psm=PSM, records = Rs } + end. + +%% find attribute +lookup_attribute(ID, ServiceRecord) -> + lists:keysearch(ID,1,ServiceRecord). + +%% add or replace an attribute +set_attribute(Attribute={ID,_}, ServiceRecord) -> + case lists:keysearch(ID, 1, ServiceRecord) of + false -> + [Attribute | ServiceRecord]; + {value,_} -> + lists:keyreplace(ID,1,ServiceRecord,Attribute) + end. + +del_attribute(ID, ServiceRecord) -> + lists:keydelete(ID, 1, ServiceRecord). + +is_service_record([{ID,Elem}|IDList]) when ID>=0,ID=<16#ffff -> + case bt_sdp:is_value(Elem) of + true -> + is_service_record(IDList); + false -> false + end; +is_service_record([_|_]) -> false; +is_service_record([]) -> true. + + + + + +avail(Channels, FreeSet) -> + Sz = bt_iset:size(FreeSet), + Len = length(Channels), + if Sz < Len -> + false; + true -> true + end. + +allocate(Channels,FreeSet) -> + %% make dynamic channals (0) last in list + RSortedChannels = reverse(lists:sort(Channels)), + Sz = bt_iset:size(FreeSet), + Len = length(RSortedChannels), + if Sz < Len -> + io:format("Warning: can not allocate all channels\n"); + true -> + ok + end, + allocate(RSortedChannels,[],FreeSet). + +allocate([0|Cns],Map,FreeSet) -> + case bt_iset:size(FreeSet) of + 0 -> Map; + _ -> + Cn1 = bt_iset:first(FreeSet), + allocate(Cns,[{0,Cn1}|Map],bt_iset:del_element(Cn1,FreeSet)) + end; +allocate([Cn|Cns],Map,FreeSet) -> + case bt_iset:is_element(Cn,FreeSet) of + true -> + allocate(Cns,[{Cn,Cn}|Map],bt_iset:del_element(Cn,FreeSet)); + false -> + case bt_iset:size(FreeSet) of + 0 -> Map; + _ -> + Cn1 = bt_iset:first(FreeSet), + allocate(Cns,[{Cn,Cn1}|Map],bt_iset:del_element(Cn1,FreeSet)) + end + end; +allocate([], Map, FreeSet) -> + {Map,FreeSet}. + + + +get_rfcomm_channels(ServiceRecord) -> + case lookup_attribute(?ATTR_ProtocolDescriptorList,ServiceRecord) of + false -> []; + {value,{_,Value}} -> + get_cn(Value) + end. + + +set_rfcomm_channels(ServiceRecord, CnMap) -> + case lookup_attribute(?ATTR_ProtocolDescriptorList,ServiceRecord) of + false -> + ServiceRecord; + {value,{_,Value}} -> + case set_cn(Value, CnMap) of + {Value1,[]} -> + set_attribute({?ATTR_ProtocolDescriptorList,Value1}, + ServiceRecord); + {Value1,CnMap1} -> + io:format("Warning: unable to assign cn's=~p to ~p\n", + [CnMap1, Value1]), + set_attribute({?ATTR_ProtocolDescriptorList,Value1}, + ServiceRecord) + end + end. + + +get_l2cap_psms(ServiceRecord) -> + case lookup_attribute(?ATTR_ProtocolDescriptorList,ServiceRecord) of + false -> []; + {value,{_,Value}} -> + get_psm(Value) + end. + +set_l2cap_psms(ServiceRecord, PsmMap) -> + case lookup_attribute(?ATTR_ProtocolDescriptorList,ServiceRecord) of + false -> + ServiceRecord; + {value,{_,Value}} -> + case set_psm(Value, PsmMap) of + {Value1,[]} -> + set_attribute({?ATTR_ProtocolDescriptorList,Value1}, + ServiceRecord); + {Value1,PsmMap1} -> + io:format("Warning: unable to assign psm's=~p to ~p\n", + [PsmMap1, Value1]), + set_attribute({?ATTR_ProtocolDescriptorList,Value1}, + ServiceRecord) + end + end. + + + +%% locate rfcomm channels (0 = allocate) +get_cn({alternative,List}) -> + lists:flatmap(fun(E) -> get_cn(E) end, List); +get_cn({sequence,[{sequence,[{uuid,?UUID_L2CAP}|_]}, + {sequence,[{uuid,?UUID_RFCOMM}, + {uint8,Channel}|_]} | _]}) -> + [Channel]; +get_cn({sequence,[{sequence,[{uuid,?UUID_L2CAP}|_]}, + {sequence,[{uuid,?UUID_RFCOMM}]} | _]}) -> + %% need allocation + [0]; +get_cn(_) -> + []. + + +%% assign rfcomm channels +set_cn({alternative,List}, CnMap) -> + {List1, CnMap1} = set_cn_list(List,[],CnMap), + {{alternative,List1},CnMap1}; +set_cn(Value= + {sequence,[{sequence,L2CAP}, %% assume L2CAP + {sequence,[{uuid,?UUID_RFCOMM}, + {uint8,Cn}|RFParam]} | T]}, CnMap) -> + case lists:keysearch(Cn,1,CnMap) of + {value,{Cn,Cn1}} -> + {{sequence,[{sequence,L2CAP}, + {sequence,[{uuid,?UUID_RFCOMM}, + {uint8,Cn1}|RFParam]} | T]}, + lists:keydelete(Cn,1,CnMap)}; + false -> + {Value, CnMap} + end; +set_cn(Value={sequence,[{sequence,L2CAP}, %% assume L2CAP + {sequence,[{uuid,?UUID_RFCOMM}]} | T]},CnMap) -> + case lists:keysearch(0,1,CnMap) of + {value,{0,Cn1}} -> + {{sequence,[{sequence,L2CAP}, + {sequence,[{uuid,?UUID_RFCOMM}, + {uint8,Cn1}]} | T]}, + lists:keydelete(0,1,CnMap)}; + false -> + {Value,CnMap} + end; +set_cn(Value,CnMap) -> + {Value,CnMap}. + +set_cn_list([V|Vs],Acc,CnMap) -> + {V1, CnMap1} = set_cn(V, CnMap), + set_cn_list(Vs,[V1|Acc],CnMap1); +set_cn_list([],Acc,CnMap) -> + {reverse(Acc), CnMap}. + + +%% locate l2cap psm (0 = allocate) +get_psm({alternative,List}) -> + lists:flatmap(fun(E) -> get_psm(E) end, List); +get_psm({sequence,[{sequence,[{uuid,?UUID_L2CAP},{uint16,PSM}|_]} | _]}) -> + [PSM]; +get_psm(_) -> + []. + + +%% assign psm +set_psm({alternative,List}, PsmMap) -> + {List1, PsmMap1} = set_psm_list(List,[],PsmMap), + {{alternative,List1},PsmMap1}; +set_psm(Value={sequence,[{sequence,[{uuid,?UUID_L2CAP}, + {uint16,PSM}|L2CAP]} | T]},PsmMap) -> + case lists:keysearch(PSM,1,PsmMap) of + {value,{PSM,PSM1}} -> + {{sequence,[{sequence,[{uuid,?UUID_L2CAP}, + {uint16,PSM1}|L2CAP]} | T]}, + lists:keydelete(PSM,1,PsmMap)}; + false -> + {Value, PsmMap} + end; +set_psm(Value,PsmMap) -> + {Value,PsmMap}. + +set_psm_list([V|Vs],Acc,PsmMap) -> + {V1, PsmMap1} = set_psm(V, PsmMap), + set_psm_list(Vs,[V1|Acc],PsmMap1); +set_psm_list([],Acc,PsmMap) -> + {reverse(Acc), PsmMap}. + + +%% convert a list of UUID (Mix of binary|string|atom) into +%% a list of 128 full UUID +uuid_set(UUIDList) -> + ordsets:from_list( + map( + fun(UUID) when is_binary(UUID) -> + bt_sdp:uuid_128(UUID); + (UUID) when is_list(UUID); is_atom(UUID) -> + bt_sdp:uuid_128(bt_sdp:string_to_uuid(UUID)) + end, UUIDList)). + +attribute_iset(AttributeList) -> + bt_iset:from_list( + map( + fun({A1,A2}) -> + {if is_list(A1); is_atom(A1) -> + bt_sdp:string_to_attribute(A1); + is_integer(A1) -> + A1 band 16#ffff + end, + if is_list(A2); is_atom(A2) -> + bt_sdp:string_to_attribute(A2); + is_integer(A2) -> + A2 band 16#ffff + end}; + (A) when is_list(A); is_atom(A) -> + bt_sdp:string_to_attribute(A); + (A) when is_integer(A) -> + A band 16#ffff + end, AttributeList)). diff --git a/deps/bt/src/bt_sup.erl b/deps/bt/src/bt_sup.erl new file mode 100644 index 0000000..c23cbdc --- /dev/null +++ b/deps/bt/src/bt_sup.erl @@ -0,0 +1,47 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +%%% File : bt_sup.erl +%%% Author : Tony Rogvall <tony@pbook.synap.se> +%%% Description : bt supervisor +%%% Created : 2 Feb 2006 by Tony Rogvall <tony@pbook.synap.se> + +-module(bt_sup). + +-behaviour(supervisor). + +%% External exports +-export([start_link/0]). + +%% supervisor callbacks +-export([init/1]). + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%%%---------------------------------------------------------------------- +%%% Callback functions from supervisor +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%%---------------------------------------------------------------------- +init([]) -> + BtDrv = {bt_drv, {bt_drv, start_link, []}, + permanent, 5000, worker, [bt_drv]}, + {ok,{{one_for_all,0,300}, [BtDrv]}}. diff --git a/deps/bt/src/bt_util.erl b/deps/bt/src/bt_util.erl new file mode 100644 index 0000000..e4a2821 --- /dev/null +++ b/deps/bt/src/bt_util.erl @@ -0,0 +1,101 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +%%% File : bt.erl +%%% Author : Tony Rogvall <tony@iMac.local> +%%% Description : Bluetooth utilities +%%% Created : 31 Jan 2006 by Tony Rogvall <tony@iMac.local> + +-module(bt_util). + +-export([getaddr/1]). +-export([getaddr_by_name/1]). +-export([uuid_to_string/1]). +-export([string_to_uuid/1]). + +-include("../include/bt.hrl"). +%% +%% getaddr(Name) -> {ok,Addr} | {error, Reason} +%% +%% Convert address into a bluetooth address +%% either {A,B,C,D,E,F} +%% or "AA-BB-CC-DD-EE-FF" (hex) +%% or "AA:BB:CC:DD:EE:FF" (hex) +%% or "Name" in case the name is resolve +%% +getaddr(Addr) when ?is_bt_address(Addr) -> + {ok, Addr}; +getaddr(Addr) when is_list(Addr) -> + case string:tokens(Addr, ":-") of + [As,Bs,Cs,Ds,Es,Fs] -> + Res = (catch lists:map(fun(Hx) -> erlang:list_to_integer(Hx,16) end, + [As,Bs,Cs,Ds,Es,Fs])), + case Res of + {'EXIT',_} -> + getaddr_by_name(Addr); + [A,B,C,D,E,F] -> + {ok,{A,B,C,D,E,F}} + end; + _ -> + getaddr_by_name(Addr) + end; +getaddr(Addr) when is_list(Addr) -> + getaddr_by_name(Addr); +getaddr(_) -> + {error, einval}. + + +%% +%% getaddr_by_name(Name) -> {ok,Addr} | {error, Reason} +%% Find address by name (may be wastlty improved) +%% +getaddr_by_name(Name) -> + {ok,Devices} = bt_drv:devices(), + getaddr_by_name(Devices, Name). + +getaddr_by_name([A|As], Name) -> + case bt_drv:device_info(A, [name]) of + {ok,[{name,Name}]} -> + {ok, A}; + _ -> + getaddr_by_name(As, Name) + end; +getaddr_by_name([], _Name) -> + {error, einval}. + +%% convert uuid to string format +uuid_to_string(UUID) when ?is_uuid(UUID) -> + ?UUID(TLow,TMid,THigh,Clock,Node) = UUID, + Fmt = + io_lib:format("~8.16.0B-~4.16.0B-~4.16.0B-~4.16.0B-~12.16.0B", + [TLow,TMid,THigh,Clock,Node]), + lists:flatten(Fmt). + +%% convert uuid string format to bina +string_to_uuid([X1,X2,X3,X4,X5,X6,X7,X8,$-, + Y1,Y2,Y3,Y4,$-, + Z1,Z2,Z3,Z4,$-, + C1,C2,C3,C4,$-, + N1,N2,N3,N4,N5,N6,N7,N8,N9,N10,N11,N12]) -> + TimeLow = erlang:list_to_integer([X1,X2,X3,X4,X5,X6,X7,X8],16), + TimeMid = erlang:list_to_integer([Y1,Y2,Y3,Y4],16), + TimeHigh = erlang:list_to_integer([Z1,Z2,Z3,Z4],16), + Clock = erlang:list_to_integer([C1,C2,C3,C4],16), + Node = erlang:list_to_integer([N1,N2,N3,N4,N5,N6,N7,N8, + N9,N10,N11,N12],16), + ?UUID(TimeLow,TimeMid,TimeHigh,Clock,Node); +string_to_uuid(_) -> + erlang:error(bad_arg). diff --git a/deps/bt/src/hci.erl b/deps/bt/src/hci.erl new file mode 100644 index 0000000..8e12125 --- /dev/null +++ b/deps/bt/src/hci.erl @@ -0,0 +1,307 @@ +%%% @author Tony Rogvall <tony@up13> +%%% @copyright (C) 2015, Tony Rogvall +%%% @doc +%%% HCI commands +%%% @end +%%% Created : 18 Apr 2015 by Tony Rogvall <tony@up13> + +-module(hci). + +-compile(export_all). + +-include("../include/hci_drv.hrl"). +-include("hci_api.hrl"). + +-define(DEFAULT_TIMEOUT, 5000). +-define(INQUIRY_TIMEOUT, 10000). + +open(DevID) -> + Hci = hci_drv:open(), + case hci_drv:bind(Hci, DevID) of + ok -> + hci_drv:activate(Hci), + {ok,Hci}; + Error -> Error + end. + +open() -> + Hci = hci_drv:open(), + case hci_drv:get_dev_list(Hci) of + {ok,[]} -> {error, enoent}; + {ok,[{DevID,_}|_]} -> + case hci_drv:bind(Hci, DevID) of + ok -> + hci_drv:activate(Hci), + {ok,Hci}; + Error -> Error + end; + Error -> + Error + end. + +close(Hci) -> + hci_drv:close(Hci). + +%% dump information about bluetooth devices +i() -> + Hci = hci_drv:open(), + case hci_drv:get_dev_list(Hci) of + {ok,Devs} -> + lists:foreach( + fun({DevID,_DevFlags}) -> + case hci_drv:get_dev_info(Hci, DevID) of + {ok, Info} -> + io:format("~s\n", + [hci_util:format_hci_dev_info(Info)]); + Error -> + io:format("error: ~p\n", [Error]) + end + end, Devs); + Error -> + Error + end. + +scan() -> + scan(?INQUIRY_TIMEOUT). + +scan(Timeout) -> + {ok,Hci} = open(), + case hci_drv:inquiry(Hci, Timeout, 10, 0, 0) of + {ok,Is} -> + lists:foreach( + fun(I) -> + io:format("~s: dev_class=~w, clock_offset=~w\n", + [bt:format_address(I#inquiry_info.bdaddr), + I#inquiry_info.dev_class, + I#inquiry_info.clock_offset]), + io:format(" pscan_rep_mode:~w\n" + " pscan_period_mode:~w\n" + " pscan_mode:~w\n", + [ I#inquiry_info.pscan_rep_mode, + I#inquiry_info.pscan_period_mode, + I#inquiry_info.pscan_mode]), + case read_remote_name(Hci, I, ?DEFAULT_TIMEOUT) of + {ok,Name} -> + io:format(" Name: ~s\n", [Name]); + _Error -> + ok + end + end, Is); + Error -> + Error + end. + + +create_connection(Hci, Bdaddr, Pkt_type, Clock_offset, Role_switch, Timeout) -> + Pscan_rep_mode = 16#02, + Pscan_mode = 0, + case call(Hci,?OGF_LINK_CTL,?OCF_CREATE_CONN, + <<?create_conn_cp_bin(Bdaddr,Pkt_type,Pscan_rep_mode,Pscan_mode,Clock_offset,Role_switch)>>, + ?EVT_CONN_COMPLETE, Timeout) of + {ok, <<?evt_conn_complete_bin(0,Handle,_Bdaddr,_Link_type,_Encr_mode)>>} -> + {ok, Handle}; + {ok, <<?evt_conn_complete_bin(Status,_Handle,_Bdaddr,_Link_type,_Encr_mode)>>} -> + %% fixme: decode status + {error, Status}; + Error -> + Error + end. + +disconnect(Hci, Handle, Reason, Timeout) -> + case call(Hci, ?OGF_LINK_CTL, ?OCF_DISCONNECT, + <<?disconnect_cp_bin(Handle,Reason)>>, + ?EVT_DISCONN_COMPLETE, Timeout) of + {ok, <<?evt_disconn_complete_bin(0,Handle,Reason)>>} -> + ok; + {ok, <<?evt_disconn_complete_bin(_Status,Handle,Reason)>>} -> + {error,eio}; + Error -> + Error + end. + +read_remote_name(Bdaddr) -> + with_socket(fun(Hci) -> read_remote_name(Hci,Bdaddr,?DEFAULT_TIMEOUT) end). + + +read_remote_name(Hci, + #inquiry_info { + bdaddr=Bdaddr, + pscan_rep_mode=Pscan_rep_mode, + pscan_mode=Pscan_mode, + clock_offset=Clock_offset }, Timeout) -> + read_remote_name(Hci,Bdaddr,Pscan_rep_mode,Pscan_mode, + Clock_offset bor 16#8000,Timeout); +read_remote_name(Hci, Bdaddr, Timeout) when is_binary(Bdaddr) -> + read_remote_name(Hci,Bdaddr,16#02,16#00,16#0000,Timeout); +read_remote_name(Hci, Bdaddr, Timeout) when is_list(Bdaddr) -> + case bt:getaddr(Bdaddr) of + {ok,{A,B,C,D,E,F}} -> + read_remote_name(Hci, <<F,E,D,C,B,A>>, Timeout); + Error -> + Error + end. + +read_remote_name(Hci,Bdaddr,Pscan_rep_mode,Pscan_mode,Clock_offset,Timeout) -> + case call(Hci,?OGF_LINK_CTL,?OCF_REMOTE_NAME_REQ, + <<?remote_name_req_cp_bin(Bdaddr,Pscan_rep_mode,Pscan_mode, + Clock_offset)>>, + ?EVT_REMOTE_NAME_REQ_COMPLETE,Timeout) of + {ok, <<?evt_remote_name_req_complete_bin(0,_Bdaddr,Name)>>} -> + {ok,hci_util:c_string(Name)}; + {ok, <<?evt_remote_name_req_complete_bin(_Status,_Bdaddr,_Name)>>} -> + {error, eio}; + Error -> Error + end. + +read_local_name() -> + with_socket(fun(Hci) -> read_local_name(Hci, ?DEFAULT_TIMEOUT) end). + +read_local_name(Hci, Timeout) -> + case call(Hci,?OGF_HOST_CTL,?OCF_READ_LOCAL_NAME,<<>>,0,Timeout) of + {ok, <<?read_local_name_rp_bin(0,Name)>>} -> + {ok, hci_util:c_string(Name)}; + {ok, <<?read_local_name_rp_bin(_Status,_Name)>>} -> + {error, eio}; + Error -> + Error + end. + + +%% sending data and calling procedures over HCI socket + +send(Hci,Opcode,Data) -> + Pkt = <<?HCI_COMMAND_PKT,Opcode:16/little, + (byte_size(Data)):8,Data/binary>>, + R = hci_drv:send(Hci,Pkt), + io:format("send ~p = ~p\n", [Pkt,R]), + R. + +send(Hci, OGF, OCF, Data) -> + send(Hci, ?cmd_opcode_pack(OGF,OCF), Data). + +call(Hci,OGF,OCF,Data,Event,Timeout) -> + Opcode = ?cmd_opcode_pack(OGF,OCF), + {ok,OldFilter} = hci_drv:get_filter(Hci), + %% io:format("call: saved_filter = ~p\n", [OldFilter]), + NewFilter = hci_drv:make_filter(Opcode, + [?HCI_EVENT_PKT], + [?EVT_CMD_STATUS, + ?EVT_CMD_COMPLETE, + ?EVT_LE_META_EVENT, + Event]), + %% io:format("call: new_filter = ~p\n", [NewFilter]), + case hci_drv:set_filter(Hci, NewFilter) of + ok -> + true = send(Hci,Opcode,Data), + Reply = wait(Hci,Opcode,Event,10,Timeout), + hci_drv:set_filter(Hci, OldFilter), + Reply; + Error -> + Error + end. + +wait(Hci,Opcode,Event,Try,Timeout) -> + TRef = start_timer(Timeout), + Result = wait_(Hci,Opcode,Event,Try,TRef), + case Result of + timeout -> + cancel_timer(TRef), + {error, timedout}; + Result -> + Result + end. + +wait_(_Hci,_Opcode,_Event,0,TRef) -> + cancel_timer(TRef), + {error, timedout}; + +wait_(Hci,Opcode,Event,Try,TRef) -> + receive + {Hci,Data0={data,<<_:8,Evt:8,Plen:8,Packet:Plen/binary,_/binary>>}} -> + io:format("Got data ~p\n", [Data0]), + case Evt of + ?EVT_CMD_STATUS -> + io:format("got cmd_status\n", []), + case Packet of + <<?evt_cmd_status_bin(0,_Ncmd,Opcode),R/binary>> -> + if Evt =/= Event -> + wait_(Hci,Opcode,Event,Try-1,TRef); + true -> + {ok,R} + end; + <<?evt_cmd_status_bin(_Status,_Ncmd,Opcode),_R/binary>> -> + {error, eio}; %% other status? + <<?evt_cmd_status_bin(_S,_Ncmd,_Opcode),_R/binary>> -> + wait_(Hci,Opcode,Event,Try-1,TRef) + end; + ?EVT_CMD_COMPLETE -> + io:format("got cmd_complete\n", []), + case Packet of + <<?evt_cmd_complete_bin(_Ncmd,Opcode),R/binary>> -> + {ok,R}; + <<?evt_cmd_complete_bin(_Ncmd,_Opcode),_R/binary>> -> + wait_(Hci,Opcode,Event,Try-1,TRef) + end; + + ?EVT_REMOTE_NAME_REQ_COMPLETE when Evt =:= Event -> + io:format("got remote_name_req_complete\n", []), + case Packet of + <<?evt_remote_name_req_complete_bin(_Status,_Bdaddr,_Name)>> -> + %% fixme: check Bdaddr! + {ok,Packet} + end; + ?EVT_LE_META_EVENT -> + io:format("got evt_le_meta_event\n", []), + case Packet of + <<?evt_le_meta_event_bin(SEvt,_D1),LePacket/binary>> -> + if SEvt =:= Event -> + {ok,LePacket}; + true -> + wait_(Hci,Opcode,Event,Try-1,TRef) + end + end; + _ -> + io:format("got event ~p\n", [Evt]), + {ok,Packet} + end; + + {timeout,TRef,_} -> + timeout + end. + +rev_bin(Bin) -> + list_to_binary(lists:reverse(binary_to_list(Bin))). + +start_timer(0) -> undefined; +start_timer(Timeout) -> erlang:start_timer(Timeout, self(), timeout). + +cancel_timer(undefined) -> false; +cancel_timer(TRef) -> + erlang:cancel_timer(TRef), + receive {timeout,TRef,_} -> ok + after 0 -> ok + end. + +with_socket(Fun) -> + {ok,S} = open(), + try Fun(S) of + Result -> + Result + catch + error:Error -> + io:format("hci: with_socket crash: ~p\n", [Error]), + lists:foreach(fun(Item) -> print_item(Item) end, + erlang:get_stacktrace()), + {error,Error} + after + close(S) + end. + +print_item({Module,Function,ArityArgs,Location}) -> + File = proplists:get_value(file,Location,""), + Line = proplists:get_value(line,Location,0), + {Arity,Args} = + if is_list(ArityArgs) -> {length(ArityArgs),ArityArgs}; + is_integer(ArityArgs) -> {ArityArgs, []} + end, + io:format(" ~s:~w: ~s:~s/~w ~p\n", [File,Line,Module,Function,Arity,Args]). diff --git a/deps/bt/src/hci_api.erl b/deps/bt/src/hci_api.erl new file mode 100644 index 0000000..a923a71 --- /dev/null +++ b/deps/bt/src/hci_api.erl @@ -0,0 +1,1462 @@ +%% -*- erlang -*- +-module(hci_api). +-compile(export_all). +-include("hci_api.hrl"). + + +cname(<<0,_/binary>>) -> []; +cname(<<C,Cs/binary>>) -> [C|cname(Cs)]; +cname(<<>>) -> []. + + +decode_status_bdaddr_rp(_Data) -> + case _Data of + <<?status_bdaddr_rp_bin(Status,Bdaddr)>> -> + #status_bdaddr_rp { status = Status,bdaddr = Bdaddr } + end. + +decode_cancel_logical_link_rp(_Data) -> + case _Data of + <<?cancel_logical_link_rp_bin(Status,Handle,Tx_flow_id)>> -> + #cancel_logical_link_rp { status = Status,handle = Handle,tx_flow_id = Tx_flow_id } + end. + +decode_role_discovery_rp(_Data) -> + case _Data of + <<?role_discovery_rp_bin(Status,Handle,Role)>> -> + #role_discovery_rp { status = Status,handle = Handle,role = Role } + end. + +decode_read_link_policy_rp(_Data) -> + case _Data of + <<?read_link_policy_rp_bin(Status,Handle,Policy)>> -> + #read_link_policy_rp { status = Status,handle = Handle,policy = Policy } + end. + +decode_write_link_policy_rp(_Data) -> + case _Data of + <<?write_link_policy_rp_bin(Status,Handle)>> -> + #write_link_policy_rp { status = Status,handle = Handle } + end. + +decode_read_pin_type_rp(_Data) -> + case _Data of + <<?read_pin_type_rp_bin(Status,Pin_type)>> -> + #read_pin_type_rp { status = Status,pin_type = Pin_type } + end. + +decode_read_stored_link_key_rp(_Data) -> + case _Data of + <<?read_stored_link_key_rp_bin(Status,Max_keys,Num_keys)>> -> + #read_stored_link_key_rp { status = Status,max_keys = Max_keys,num_keys = Num_keys } + end. + +decode_write_stored_link_key_rp(_Data) -> + case _Data of + <<?write_stored_link_key_rp_bin(Status,Num_keys)>> -> + #write_stored_link_key_rp { status = Status,num_keys = Num_keys } + end. + +decode_delete_stored_link_key_rp(_Data) -> + case _Data of + <<?delete_stored_link_key_rp_bin(Status,Num_keys)>> -> + #delete_stored_link_key_rp { status = Status,num_keys = Num_keys } + end. + +decode_read_local_name_rp(_Data) -> + case _Data of + <<?read_local_name_rp_bin(Status,Name)>> -> + #read_local_name_rp { status = Status,name = Name } + end. + +decode_read_conn_accept_timeout_rp(_Data) -> + case _Data of + <<?read_conn_accept_timeout_rp_bin(Status,Timeout)>> -> + #read_conn_accept_timeout_rp { status = Status,timeout = Timeout } + end. + +decode_read_page_timeout_rp(_Data) -> + case _Data of + <<?read_page_timeout_rp_bin(Status,Timeout)>> -> + #read_page_timeout_rp { status = Status,timeout = Timeout } + end. + +decode_read_scan_enable_rp(_Data) -> + case _Data of + <<?read_scan_enable_rp_bin(Status,Enable)>> -> + #read_scan_enable_rp { status = Status,enable = Enable } + end. + +decode_read_page_activity_rp(_Data) -> + case _Data of + <<?read_page_activity_rp_bin(Status,Interval,Window)>> -> + #read_page_activity_rp { status = Status,interval = Interval,window = Window } + end. + +decode_read_inq_activity_rp(_Data) -> + case _Data of + <<?read_inq_activity_rp_bin(Status,Interval,Window)>> -> + #read_inq_activity_rp { status = Status,interval = Interval,window = Window } + end. + +decode_read_class_of_dev_rp(_Data) -> + case _Data of + <<?read_class_of_dev_rp_bin(Status,Dev_class)>> -> + #read_class_of_dev_rp { status = Status,dev_class = Dev_class } + end. + +decode_read_voice_setting_rp(_Data) -> + case _Data of + <<?read_voice_setting_rp_bin(Status,Voice_setting)>> -> + #read_voice_setting_rp { status = Status,voice_setting = Voice_setting } + end. + +decode_read_transmit_power_level_rp(_Data) -> + case _Data of + <<?read_transmit_power_level_rp_bin(Status,Handle,Level)>> -> + #read_transmit_power_level_rp { status = Status,handle = Handle,level = Level } + end. + +decode_read_link_supervision_timeout_rp(_Data) -> + case _Data of + <<?read_link_supervision_timeout_rp_bin(Status,Handle,Timeout)>> -> + #read_link_supervision_timeout_rp { status = Status,handle = Handle,timeout = Timeout } + end. + +decode_write_link_supervision_timeout_rp(_Data) -> + case _Data of + <<?write_link_supervision_timeout_rp_bin(Status,Handle)>> -> + #write_link_supervision_timeout_rp { status = Status,handle = Handle } + end. + +decode_read_current_iac_lap_rp(_Data) -> + case _Data of + <<?read_current_iac_lap_rp_bin(Status,Num_current_iac,Lap)>> -> + #read_current_iac_lap_rp { status = Status,num_current_iac = Num_current_iac,lap = Lap } + end. + +decode_set_afh_classification_rp(_Data) -> + case _Data of + <<?set_afh_classification_rp_bin(Status)>> -> + #set_afh_classification_rp { status = Status } + end. + +decode_read_inquiry_scan_type_rp(_Data) -> + case _Data of + <<?read_inquiry_scan_type_rp_bin(Status,Type)>> -> + #read_inquiry_scan_type_rp { status = Status,type = Type } + end. + +decode_write_inquiry_scan_type_rp(_Data) -> + case _Data of + <<?write_inquiry_scan_type_rp_bin(Status)>> -> + #write_inquiry_scan_type_rp { status = Status } + end. + +decode_read_inquiry_mode_rp(_Data) -> + case _Data of + <<?read_inquiry_mode_rp_bin(Status,Mode)>> -> + #read_inquiry_mode_rp { status = Status,mode = Mode } + end. + +decode_write_inquiry_mode_rp(_Data) -> + case _Data of + <<?write_inquiry_mode_rp_bin(Status)>> -> + #write_inquiry_mode_rp { status = Status } + end. + +decode_read_afh_mode_rp(_Data) -> + case _Data of + <<?read_afh_mode_rp_bin(Status,Mode)>> -> + #read_afh_mode_rp { status = Status,mode = Mode } + end. + +decode_write_afh_mode_rp(_Data) -> + case _Data of + <<?write_afh_mode_rp_bin(Status)>> -> + #write_afh_mode_rp { status = Status } + end. + +decode_read_ext_inquiry_response_rp(_Data) -> + case _Data of + <<?read_ext_inquiry_response_rp_bin(Status,Fec,Data)>> -> + #read_ext_inquiry_response_rp { status = Status,fec = Fec,data = Data } + end. + +decode_write_ext_inquiry_response_rp(_Data) -> + case _Data of + <<?write_ext_inquiry_response_rp_bin(Status)>> -> + #write_ext_inquiry_response_rp { status = Status } + end. + +decode_refresh_encryption_key_rp(_Data) -> + case _Data of + <<?refresh_encryption_key_rp_bin(Status)>> -> + #refresh_encryption_key_rp { status = Status } + end. + +decode_read_simple_pairing_mode_rp(_Data) -> + case _Data of + <<?read_simple_pairing_mode_rp_bin(Status,Mode)>> -> + #read_simple_pairing_mode_rp { status = Status,mode = Mode } + end. + +decode_write_simple_pairing_mode_rp(_Data) -> + case _Data of + <<?write_simple_pairing_mode_rp_bin(Status)>> -> + #write_simple_pairing_mode_rp { status = Status } + end. + +decode_read_local_oob_data_rp(_Data) -> + case _Data of + <<?read_local_oob_data_rp_bin(Status,Hash,Randomizer)>> -> + #read_local_oob_data_rp { status = Status,hash = Hash,randomizer = Randomizer } + end. + +decode_read_inq_response_tx_power_level_rp(_Data) -> + case _Data of + <<?read_inq_response_tx_power_level_rp_bin(Status,Level)>> -> + #read_inq_response_tx_power_level_rp { status = Status,level = Level } + end. + +decode_read_inquiry_transmit_power_level_rp(_Data) -> + case _Data of + <<?read_inquiry_transmit_power_level_rp_bin(Status,Level)>> -> + #read_inquiry_transmit_power_level_rp { status = Status,level = Level } + end. + +decode_write_inquiry_transmit_power_level_rp(_Data) -> + case _Data of + <<?write_inquiry_transmit_power_level_rp_bin(Status)>> -> + #write_inquiry_transmit_power_level_rp { status = Status } + end. + +decode_read_default_error_data_reporting_rp(_Data) -> + case _Data of + <<?read_default_error_data_reporting_rp_bin(Status,Reporting)>> -> + #read_default_error_data_reporting_rp { status = Status,reporting = Reporting } + end. + +decode_write_default_error_data_reporting_rp(_Data) -> + case _Data of + <<?write_default_error_data_reporting_rp_bin(Status)>> -> + #write_default_error_data_reporting_rp { status = Status } + end. + +decode_send_keypress_notify_rp(_Data) -> + case _Data of + <<?send_keypress_notify_rp_bin(Status)>> -> + #send_keypress_notify_rp { status = Status } + end. + +decode_read_log_link_accept_timeout_rp(_Data) -> + case _Data of + <<?read_log_link_accept_timeout_rp_bin(Status,Timeout)>> -> + #read_log_link_accept_timeout_rp { status = Status,timeout = Timeout } + end. + +decode_read_enhanced_transmit_power_level_rp(_Data) -> + case _Data of + <<?read_enhanced_transmit_power_level_rp_bin(Status,Handle,Level_gfsk,Level_dqpsk,Level_8dpsk)>> -> + #read_enhanced_transmit_power_level_rp { status = Status,handle = Handle,level_gfsk = Level_gfsk,level_dqpsk = Level_dqpsk,level_8dpsk = Level_8dpsk } + end. + +decode_read_best_effort_flush_timeout_rp(_Data) -> + case _Data of + <<?read_best_effort_flush_timeout_rp_bin(Status,Timeout)>> -> + #read_best_effort_flush_timeout_rp { status = Status,timeout = Timeout } + end. + +decode_write_best_effort_flush_timeout_rp(_Data) -> + case _Data of + <<?write_best_effort_flush_timeout_rp_bin(Status)>> -> + #write_best_effort_flush_timeout_rp { status = Status } + end. + +decode_read_le_host_supported_rp(_Data) -> + case _Data of + <<?read_le_host_supported_rp_bin(Status,Le,Simul)>> -> + #read_le_host_supported_rp { status = Status,le = Le,simul = Simul } + end. + +decode_read_local_version_rp(_Data) -> + case _Data of + <<?read_local_version_rp_bin(Status,Hci_ver,Hci_rev,Lmp_ver,Manufacturer,Lmp_subver)>> -> + #read_local_version_rp { status = Status,hci_ver = Hci_ver,hci_rev = Hci_rev,lmp_ver = Lmp_ver,manufacturer = Manufacturer,lmp_subver = Lmp_subver } + end. + +decode_read_local_commands_rp(_Data) -> + case _Data of + <<?read_local_commands_rp_bin(Status,Commands)>> -> + #read_local_commands_rp { status = Status,commands = Commands } + end. + +decode_read_local_features_rp(_Data) -> + case _Data of + <<?read_local_features_rp_bin(Status,Features)>> -> + #read_local_features_rp { status = Status,features = Features } + end. + +decode_read_local_ext_features_rp(_Data) -> + case _Data of + <<?read_local_ext_features_rp_bin(Status,Page_num,Max_page_num,Features)>> -> + #read_local_ext_features_rp { status = Status,page_num = Page_num,max_page_num = Max_page_num,features = Features } + end. + +decode_read_buffer_size_rp(_Data) -> + case _Data of + <<?read_buffer_size_rp_bin(Status,Acl_mtu,Sco_mtu,Acl_max_pkt,Sco_max_pkt)>> -> + #read_buffer_size_rp { status = Status,acl_mtu = Acl_mtu,sco_mtu = Sco_mtu,acl_max_pkt = Acl_max_pkt,sco_max_pkt = Sco_max_pkt } + end. + +decode_read_bd_addr_rp(_Data) -> + case _Data of + <<?read_bd_addr_rp_bin(Status,Bdaddr)>> -> + #read_bd_addr_rp { status = Status,bdaddr = Bdaddr } + end. + +decode_read_failed_contact_counter_rp(_Data) -> + case _Data of + <<?read_failed_contact_counter_rp_bin(Status,Handle,Counter)>> -> + #read_failed_contact_counter_rp { status = Status,handle = Handle,counter = Counter } + end. + +decode_reset_failed_contact_counter_rp(_Data) -> + case _Data of + <<?reset_failed_contact_counter_rp_bin(Status,Handle)>> -> + #reset_failed_contact_counter_rp { status = Status,handle = Handle } + end. + +decode_read_link_quality_rp(_Data) -> + case _Data of + <<?read_link_quality_rp_bin(Status,Handle,Link_quality)>> -> + #read_link_quality_rp { status = Status,handle = Handle,link_quality = Link_quality } + end. + +decode_read_rssi_rp(_Data) -> + case _Data of + <<?read_rssi_rp_bin(Status,Handle,Rssi)>> -> + #read_rssi_rp { status = Status,handle = Handle,rssi = Rssi } + end. + +decode_read_afh_map_rp(_Data) -> + case _Data of + <<?read_afh_map_rp_bin(Status,Handle,Mode,Map)>> -> + #read_afh_map_rp { status = Status,handle = Handle,mode = Mode,map = Map } + end. + +decode_read_clock_rp(_Data) -> + case _Data of + <<?read_clock_rp_bin(Status,Handle,Clock,Accuracy)>> -> + #read_clock_rp { status = Status,handle = Handle,clock = Clock,accuracy = Accuracy } + end. + +decode_read_local_amp_info_rp(_Data) -> + case _Data of + <<?read_local_amp_info_rp_bin(Status,Amp_status,Total_bandwidth,Max_guaranteed_bandwidth,Min_latency,Max_pdu_size,Controller_type,Pal_caps,Max_amp_assoc_length,Max_flush_timeout,Best_effort_flush_timeout)>> -> + #read_local_amp_info_rp { status = Status,amp_status = Amp_status,total_bandwidth = Total_bandwidth,max_guaranteed_bandwidth = Max_guaranteed_bandwidth,min_latency = Min_latency,max_pdu_size = Max_pdu_size,controller_type = Controller_type,pal_caps = Pal_caps,max_amp_assoc_length = Max_amp_assoc_length,max_flush_timeout = Max_flush_timeout,best_effort_flush_timeout = Best_effort_flush_timeout } + end. + +decode_read_local_amp_assoc_rp(_Data) -> + case _Data of + <<?read_local_amp_assoc_rp_bin(Status,Handle,Rem_len,Frag)>> -> + #read_local_amp_assoc_rp { status = Status,handle = Handle,rem_len = Rem_len,frag = Frag } + end. + +decode_write_remote_amp_assoc_rp(_Data) -> + case _Data of + <<?write_remote_amp_assoc_rp_bin(Status,Handle)>> -> + #write_remote_amp_assoc_rp { status = Status,handle = Handle } + end. + +decode_write_simple_pairing_debug_mode_rp(_Data) -> + case _Data of + <<?write_simple_pairing_debug_mode_rp_bin(Status)>> -> + #write_simple_pairing_debug_mode_rp { status = Status } + end. + +decode_le_read_buffer_size_rp(_Data) -> + case _Data of + <<?le_read_buffer_size_rp_bin(Status,Pkt_len,Max_pkt)>> -> + #le_read_buffer_size_rp { status = Status,pkt_len = Pkt_len,max_pkt = Max_pkt } + end. + +decode_le_read_local_supported_features_rp(_Data) -> + case _Data of + <<?le_read_local_supported_features_rp_bin(Status,Features)>> -> + #le_read_local_supported_features_rp { status = Status,features = Features } + end. + +decode_le_read_advertising_channel_tx_power_rp(_Data) -> + case _Data of + <<?le_read_advertising_channel_tx_power_rp_bin(Status,Level)>> -> + #le_read_advertising_channel_tx_power_rp { status = Status,level = Level } + end. + +decode_le_read_white_list_size_rp(_Data) -> + case _Data of + <<?le_read_white_list_size_rp_bin(Status,Size)>> -> + #le_read_white_list_size_rp { status = Status,size = Size } + end. + +decode_le_read_channel_map_rp(_Data) -> + case _Data of + <<?le_read_channel_map_rp_bin(Status,Handle,Map)>> -> + #le_read_channel_map_rp { status = Status,handle = Handle,map = Map } + end. + +decode_le_encrypt_rp(_Data) -> + case _Data of + <<?le_encrypt_rp_bin(Status,Data)>> -> + #le_encrypt_rp { status = Status,data = Data } + end. + +decode_le_rand_rp(_Data) -> + case _Data of + <<?le_rand_rp_bin(Status,Random)>> -> + #le_rand_rp { status = Status,random = Random } + end. + +decode_le_ltk_reply_rp(_Data) -> + case _Data of + <<?le_ltk_reply_rp_bin(Status,Handle)>> -> + #le_ltk_reply_rp { status = Status,handle = Handle } + end. + +decode_le_ltk_neg_reply_rp(_Data) -> + case _Data of + <<?le_ltk_neg_reply_rp_bin(Status,Handle)>> -> + #le_ltk_neg_reply_rp { status = Status,handle = Handle } + end. + +decode_le_read_supported_states_rp(_Data) -> + case _Data of + <<?le_read_supported_states_rp_bin(Status,States)>> -> + #le_read_supported_states_rp { status = Status,states = States } + end. + +decode_le_test_end_rp(_Data) -> + case _Data of + <<?le_test_end_rp_bin(Status,Num_pkts)>> -> + #le_test_end_rp { status = Status,num_pkts = Num_pkts } + end. + +decode_inquiry_info(_Data) -> + case _Data of + <<?inquiry_info_bin(Bdaddr,Pscan_rep_mode,Pscan_period_mode,Pscan_mode,Dev_class,Clock_offset)>> -> + #inquiry_info { bdaddr = Bdaddr,pscan_rep_mode = Pscan_rep_mode,pscan_period_mode = Pscan_period_mode,pscan_mode = Pscan_mode,dev_class = Dev_class,clock_offset = Clock_offset } + end. + +decode_evt_conn_complete(_Data) -> + case _Data of + <<?evt_conn_complete_bin(Status,Handle,Bdaddr,Link_type,Encr_mode)>> -> + #evt_conn_complete { status = Status,handle = Handle,bdaddr = Bdaddr,link_type = Link_type,encr_mode = Encr_mode } + end. + +decode_evt_conn_request(_Data) -> + case _Data of + <<?evt_conn_request_bin(Bdaddr,Dev_class,Link_type)>> -> + #evt_conn_request { bdaddr = Bdaddr,dev_class = Dev_class,link_type = Link_type } + end. + +decode_evt_disconn_complete(_Data) -> + case _Data of + <<?evt_disconn_complete_bin(Status,Handle,Reason)>> -> + #evt_disconn_complete { status = Status,handle = Handle,reason = Reason } + end. + +decode_evt_auth_complete(_Data) -> + case _Data of + <<?evt_auth_complete_bin(Status,Handle)>> -> + #evt_auth_complete { status = Status,handle = Handle } + end. + +decode_evt_remote_name_req_complete(_Data) -> + case _Data of + <<?evt_remote_name_req_complete_bin(Status,Bdaddr,Name)>> -> + #evt_remote_name_req_complete { status = Status,bdaddr = Bdaddr,name = Name } + end. + +decode_evt_encrypt_change(_Data) -> + case _Data of + <<?evt_encrypt_change_bin(Status,Handle,Encrypt)>> -> + #evt_encrypt_change { status = Status,handle = Handle,encrypt = Encrypt } + end. + +decode_evt_change_conn_link_key_complete(_Data) -> + case _Data of + <<?evt_change_conn_link_key_complete_bin(Status,Handle)>> -> + #evt_change_conn_link_key_complete { status = Status,handle = Handle } + end. + +decode_evt_master_link_key_complete(_Data) -> + case _Data of + <<?evt_master_link_key_complete_bin(Status,Handle,Key_flag)>> -> + #evt_master_link_key_complete { status = Status,handle = Handle,key_flag = Key_flag } + end. + +decode_evt_read_remote_features_complete(_Data) -> + case _Data of + <<?evt_read_remote_features_complete_bin(Status,Handle,Features)>> -> + #evt_read_remote_features_complete { status = Status,handle = Handle,features = Features } + end. + +decode_evt_read_remote_version_complete(_Data) -> + case _Data of + <<?evt_read_remote_version_complete_bin(Status,Handle,Lmp_ver,Manufacturer,Lmp_subver)>> -> + #evt_read_remote_version_complete { status = Status,handle = Handle,lmp_ver = Lmp_ver,manufacturer = Manufacturer,lmp_subver = Lmp_subver } + end. + +decode_evt_qos_setup_complete(_Data) -> + case _Data of + <<?evt_qos_setup_complete_bin(Status,Handle,Flags,Qos)>> -> + #evt_qos_setup_complete { status = Status,handle = Handle,flags = Flags,qos = Qos } + end. + +decode_evt_cmd_complete(_Data) -> + case _Data of + <<?evt_cmd_complete_bin(Ncmd,Opcode)>> -> + #evt_cmd_complete { ncmd = Ncmd,opcode = Opcode } + end. + +decode_evt_cmd_status(_Data) -> + case _Data of + <<?evt_cmd_status_bin(Status,Ncmd,Opcode)>> -> + #evt_cmd_status { status = Status,ncmd = Ncmd,opcode = Opcode } + end. + +decode_evt_hardware_error(_Data) -> + case _Data of + <<?evt_hardware_error_bin(Code)>> -> + #evt_hardware_error { code = Code } + end. + +decode_evt_flush_occured(_Data) -> + case _Data of + <<?evt_flush_occured_bin(Handle)>> -> + #evt_flush_occured { handle = Handle } + end. + +decode_evt_role_change(_Data) -> + case _Data of + <<?evt_role_change_bin(Status,Bdaddr,Role)>> -> + #evt_role_change { status = Status,bdaddr = Bdaddr,role = Role } + end. + +decode_evt_num_comp_pkts(_Data) -> + case _Data of + <<?evt_num_comp_pkts_bin(Num_hndl)>> -> + #evt_num_comp_pkts { num_hndl = Num_hndl } + end. + +decode_evt_mode_change(_Data) -> + case _Data of + <<?evt_mode_change_bin(Status,Handle,Mode,Interval)>> -> + #evt_mode_change { status = Status,handle = Handle,mode = Mode,interval = Interval } + end. + +decode_evt_return_link_keys(_Data) -> + case _Data of + <<?evt_return_link_keys_bin(Num_keys)>> -> + #evt_return_link_keys { num_keys = Num_keys } + end. + +decode_evt_pin_code_req(_Data) -> + case _Data of + <<?evt_pin_code_req_bin(Bdaddr)>> -> + #evt_pin_code_req { bdaddr = Bdaddr } + end. + +decode_evt_link_key_req(_Data) -> + case _Data of + <<?evt_link_key_req_bin(Bdaddr)>> -> + #evt_link_key_req { bdaddr = Bdaddr } + end. + +decode_evt_link_key_notify(_Data) -> + case _Data of + <<?evt_link_key_notify_bin(Bdaddr,Link_key,Key_type)>> -> + #evt_link_key_notify { bdaddr = Bdaddr,link_key = Link_key,key_type = Key_type } + end. + +decode_evt_data_buffer_overflow(_Data) -> + case _Data of + <<?evt_data_buffer_overflow_bin(Link_type)>> -> + #evt_data_buffer_overflow { link_type = Link_type } + end. + +decode_evt_max_slots_change(_Data) -> + case _Data of + <<?evt_max_slots_change_bin(Handle,Max_slots)>> -> + #evt_max_slots_change { handle = Handle,max_slots = Max_slots } + end. + +decode_evt_read_clock_offset_complete(_Data) -> + case _Data of + <<?evt_read_clock_offset_complete_bin(Status,Handle,Clock_offset)>> -> + #evt_read_clock_offset_complete { status = Status,handle = Handle,clock_offset = Clock_offset } + end. + +decode_evt_conn_ptype_changed(_Data) -> + case _Data of + <<?evt_conn_ptype_changed_bin(Status,Handle,Ptype)>> -> + #evt_conn_ptype_changed { status = Status,handle = Handle,ptype = Ptype } + end. + +decode_evt_qos_violation(_Data) -> + case _Data of + <<?evt_qos_violation_bin(Handle)>> -> + #evt_qos_violation { handle = Handle } + end. + +decode_evt_pscan_rep_mode_change(_Data) -> + case _Data of + <<?evt_pscan_rep_mode_change_bin(Bdaddr,Pscan_rep_mode)>> -> + #evt_pscan_rep_mode_change { bdaddr = Bdaddr,pscan_rep_mode = Pscan_rep_mode } + end. + +decode_evt_flow_spec_complete(_Data) -> + case _Data of + <<?evt_flow_spec_complete_bin(Status,Handle,Flags,Direction,Qos)>> -> + #evt_flow_spec_complete { status = Status,handle = Handle,flags = Flags,direction = Direction,qos = Qos } + end. + +decode_evt_read_remote_ext_features_complete(_Data) -> + case _Data of + <<?evt_read_remote_ext_features_complete_bin(Status,Handle,Page_num,Max_page_num,Features)>> -> + #evt_read_remote_ext_features_complete { status = Status,handle = Handle,page_num = Page_num,max_page_num = Max_page_num,features = Features } + end. + +decode_evt_sync_conn_complete(_Data) -> + case _Data of + <<?evt_sync_conn_complete_bin(Status,Handle,Bdaddr,Link_type,Trans_interval,Retrans_window,Rx_pkt_len,Tx_pkt_len,Air_mode)>> -> + #evt_sync_conn_complete { status = Status,handle = Handle,bdaddr = Bdaddr,link_type = Link_type,trans_interval = Trans_interval,retrans_window = Retrans_window,rx_pkt_len = Rx_pkt_len,tx_pkt_len = Tx_pkt_len,air_mode = Air_mode } + end. + +decode_evt_sync_conn_changed(_Data) -> + case _Data of + <<?evt_sync_conn_changed_bin(Status,Handle,Trans_interval,Retrans_window,Rx_pkt_len,Tx_pkt_len)>> -> + #evt_sync_conn_changed { status = Status,handle = Handle,trans_interval = Trans_interval,retrans_window = Retrans_window,rx_pkt_len = Rx_pkt_len,tx_pkt_len = Tx_pkt_len } + end. + +decode_evt_sniff_subrating(_Data) -> + case _Data of + <<?evt_sniff_subrating_bin(Status,Handle,Max_tx_latency,Max_rx_latency,Min_remote_timeout,Min_local_timeout)>> -> + #evt_sniff_subrating { status = Status,handle = Handle,max_tx_latency = Max_tx_latency,max_rx_latency = Max_rx_latency,min_remote_timeout = Min_remote_timeout,min_local_timeout = Min_local_timeout } + end. + +decode_evt_encryption_key_refresh_complete(_Data) -> + case _Data of + <<?evt_encryption_key_refresh_complete_bin(Status,Handle)>> -> + #evt_encryption_key_refresh_complete { status = Status,handle = Handle } + end. + +decode_evt_io_capability_request(_Data) -> + case _Data of + <<?evt_io_capability_request_bin(Bdaddr)>> -> + #evt_io_capability_request { bdaddr = Bdaddr } + end. + +decode_evt_io_capability_response(_Data) -> + case _Data of + <<?evt_io_capability_response_bin(Bdaddr,Capability,Oob_data,Authentication)>> -> + #evt_io_capability_response { bdaddr = Bdaddr,capability = Capability,oob_data = Oob_data,authentication = Authentication } + end. + +decode_evt_user_confirm_request(_Data) -> + case _Data of + <<?evt_user_confirm_request_bin(Bdaddr,Passkey)>> -> + #evt_user_confirm_request { bdaddr = Bdaddr,passkey = Passkey } + end. + +decode_evt_user_passkey_request(_Data) -> + case _Data of + <<?evt_user_passkey_request_bin(Bdaddr)>> -> + #evt_user_passkey_request { bdaddr = Bdaddr } + end. + +decode_evt_remote_oob_data_request(_Data) -> + case _Data of + <<?evt_remote_oob_data_request_bin(Bdaddr)>> -> + #evt_remote_oob_data_request { bdaddr = Bdaddr } + end. + +decode_evt_simple_pairing_complete(_Data) -> + case _Data of + <<?evt_simple_pairing_complete_bin(Status,Bdaddr)>> -> + #evt_simple_pairing_complete { status = Status,bdaddr = Bdaddr } + end. + +decode_evt_link_supervision_timeout_changed(_Data) -> + case _Data of + <<?evt_link_supervision_timeout_changed_bin(Handle,Timeout)>> -> + #evt_link_supervision_timeout_changed { handle = Handle,timeout = Timeout } + end. + +decode_evt_enhanced_flush_complete(_Data) -> + case _Data of + <<?evt_enhanced_flush_complete_bin(Handle)>> -> + #evt_enhanced_flush_complete { handle = Handle } + end. + +decode_evt_user_passkey_notify(_Data) -> + case _Data of + <<?evt_user_passkey_notify_bin(Bdaddr,Passkey,Entered)>> -> + #evt_user_passkey_notify { bdaddr = Bdaddr,passkey = Passkey,entered = Entered } + end. + +decode_evt_keypress_notify(_Data) -> + case _Data of + <<?evt_keypress_notify_bin(Bdaddr,Type)>> -> + #evt_keypress_notify { bdaddr = Bdaddr,type = Type } + end. + +decode_evt_remote_host_features_notify(_Data) -> + case _Data of + <<?evt_remote_host_features_notify_bin(Bdaddr,Features)>> -> + #evt_remote_host_features_notify { bdaddr = Bdaddr,features = Features } + end. + +decode_evt_le_meta_event(_Data) -> + case _Data of + <<?evt_le_meta_event_bin(Subevent,Data)>> -> + #evt_le_meta_event { subevent = Subevent,data = Data } + end. + +decode_evt_le_connection_complete(_Data) -> + case _Data of + <<?evt_le_connection_complete_bin(Status,Handle,Role,Peer_bdaddr_type,Peer_bdaddr,Interval,Latency,Supervision_timeout,Master_clock_accuracy)>> -> + #evt_le_connection_complete { status = Status,handle = Handle,role = Role,peer_bdaddr_type = Peer_bdaddr_type,peer_bdaddr = Peer_bdaddr,interval = Interval,latency = Latency,supervision_timeout = Supervision_timeout,master_clock_accuracy = Master_clock_accuracy } + end. + +decode_evt_le_connection_update_complete(_Data) -> + case _Data of + <<?evt_le_connection_update_complete_bin(Status,Handle,Interval,Latency,Supervision_timeout)>> -> + #evt_le_connection_update_complete { status = Status,handle = Handle,interval = Interval,latency = Latency,supervision_timeout = Supervision_timeout } + end. + +decode_evt_le_read_remote_used_features_complete(_Data) -> + case _Data of + <<?evt_le_read_remote_used_features_complete_bin(Status,Handle,Features)>> -> + #evt_le_read_remote_used_features_complete { status = Status,handle = Handle,features = Features } + end. + +decode_evt_le_long_term_key_request(_Data) -> + case _Data of + <<?evt_le_long_term_key_request_bin(Handle,Random,Diversifier)>> -> + #evt_le_long_term_key_request { handle = Handle,random = Random,diversifier = Diversifier } + end. + +decode_evt_physical_link_complete(_Data) -> + case _Data of + <<?evt_physical_link_complete_bin(Status,Handle)>> -> + #evt_physical_link_complete { status = Status,handle = Handle } + end. + +decode_evt_disconn_physical_link_complete(_Data) -> + case _Data of + <<?evt_disconn_physical_link_complete_bin(Status,Handle,Reason)>> -> + #evt_disconn_physical_link_complete { status = Status,handle = Handle,reason = Reason } + end. + +decode_evt_physical_link_loss_warning(_Data) -> + case _Data of + <<?evt_physical_link_loss_warning_bin(Handle,Reason)>> -> + #evt_physical_link_loss_warning { handle = Handle,reason = Reason } + end. + +decode_evt_physical_link_recovery(_Data) -> + case _Data of + <<?evt_physical_link_recovery_bin(Handle)>> -> + #evt_physical_link_recovery { handle = Handle } + end. + +decode_evt_logical_link_complete(_Data) -> + case _Data of + <<?evt_logical_link_complete_bin(Status,Log_handle,Handle,Tx_flow_id)>> -> + #evt_logical_link_complete { status = Status,log_handle = Log_handle,handle = Handle,tx_flow_id = Tx_flow_id } + end. + +decode_evt_flow_spec_modify_complete(_Data) -> + case _Data of + <<?evt_flow_spec_modify_complete_bin(Status,Handle)>> -> + #evt_flow_spec_modify_complete { status = Status,handle = Handle } + end. + +decode_evt_amp_status_change(_Data) -> + case _Data of + <<?evt_amp_status_change_bin(Status,Amp_status)>> -> + #evt_amp_status_change { status = Status,amp_status = Amp_status } + end. + +decode_evt_stack_internal(_Data) -> + case _Data of + <<?evt_stack_internal_bin(Type,Data)>> -> + #evt_stack_internal { type = Type,data = Data } + end. + +decode_evt_si_device(_Data) -> + case _Data of + <<?evt_si_device_bin(Event,Dev_id)>> -> + #evt_si_device { event = Event,dev_id = Dev_id } + end. + +decode(Evt,_Data) -> + case Evt of + ?EVT_STACK_INTERNAL -> decode_evt_stack_internal(_Data); + ?EVT_NUMBER_COMPLETED_BLOCKS -> decode_evt_amp_status_change(_Data); + ?EVT_AMP_STATUS_CHANGE -> decode_evt_amp_status_change(_Data); + ?EVT_DISCONNECT_LOGICAL_LINK_COMPLETE -> decode_evt_flow_spec_modify_complete(_Data); + ?EVT_FLOW_SPEC_MODIFY_COMPLETE -> decode_evt_flow_spec_modify_complete(_Data); + ?EVT_LOGICAL_LINK_COMPLETE -> decode_evt_logical_link_complete(_Data); + ?EVT_PHYSICAL_LINK_RECOVERY -> decode_evt_physical_link_recovery(_Data); + ?EVT_PHYSICAL_LINK_LOSS_EARLY_WARNING -> decode_evt_physical_link_loss_warning(_Data); + ?EVT_CHANNEL_SELECTED -> decode_evt_disconn_physical_link_complete(_Data); + ?EVT_DISCONNECT_PHYSICAL_LINK_COMPLETE -> decode_evt_disconn_physical_link_complete(_Data); + ?EVT_PHYSICAL_LINK_COMPLETE -> decode_evt_physical_link_complete(_Data); + ?EVT_LE_META_EVENT -> decode_evt_le_meta_event(_Data); + ?EVT_REMOTE_HOST_FEATURES_NOTIFY -> decode_evt_remote_host_features_notify(_Data); + ?EVT_KEYPRESS_NOTIFY -> decode_evt_keypress_notify(_Data); + ?EVT_USER_PASSKEY_NOTIFY -> decode_evt_user_passkey_notify(_Data); + ?EVT_ENHANCED_FLUSH_COMPLETE -> decode_evt_enhanced_flush_complete(_Data); + ?EVT_LINK_SUPERVISION_TIMEOUT_CHANGED -> decode_evt_link_supervision_timeout_changed(_Data); + ?EVT_SIMPLE_PAIRING_COMPLETE -> decode_evt_simple_pairing_complete(_Data); + ?EVT_REMOTE_OOB_DATA_REQUEST -> decode_evt_remote_oob_data_request(_Data); + ?EVT_USER_PASSKEY_REQUEST -> decode_evt_user_passkey_request(_Data); + ?EVT_USER_CONFIRM_REQUEST -> decode_evt_user_confirm_request(_Data); + ?EVT_IO_CAPABILITY_RESPONSE -> decode_evt_io_capability_response(_Data); + ?EVT_IO_CAPABILITY_REQUEST -> decode_evt_io_capability_request(_Data); + ?EVT_EXTENDED_INQUIRY_RESULT -> decode_evt_encryption_key_refresh_complete(_Data); + ?EVT_ENCRYPTION_KEY_REFRESH_COMPLETE -> decode_evt_encryption_key_refresh_complete(_Data); + ?EVT_SNIFF_SUBRATING -> decode_evt_sniff_subrating(_Data); + ?EVT_SYNC_CONN_CHANGED -> decode_evt_sync_conn_changed(_Data); + ?EVT_SYNC_CONN_COMPLETE -> decode_evt_sync_conn_complete(_Data); + ?EVT_INQUIRY_RESULT_WITH_RSSI -> decode_evt_read_remote_ext_features_complete(_Data); + ?EVT_READ_REMOTE_EXT_FEATURES_COMPLETE -> decode_evt_read_remote_ext_features_complete(_Data); + ?EVT_FLOW_SPEC_COMPLETE -> decode_evt_flow_spec_complete(_Data); + ?EVT_PSCAN_REP_MODE_CHANGE -> decode_evt_pscan_rep_mode_change(_Data); + ?EVT_QOS_VIOLATION -> decode_evt_qos_violation(_Data); + ?EVT_CONN_PTYPE_CHANGED -> decode_evt_conn_ptype_changed(_Data); + ?EVT_READ_CLOCK_OFFSET_COMPLETE -> decode_evt_read_clock_offset_complete(_Data); + ?EVT_MAX_SLOTS_CHANGE -> decode_evt_max_slots_change(_Data); + ?EVT_LOOPBACK_COMMAND -> decode_evt_data_buffer_overflow(_Data); + ?EVT_DATA_BUFFER_OVERFLOW -> decode_evt_data_buffer_overflow(_Data); + ?EVT_LINK_KEY_NOTIFY -> decode_evt_link_key_notify(_Data); + ?EVT_LINK_KEY_REQ -> decode_evt_link_key_req(_Data); + ?EVT_PIN_CODE_REQ -> decode_evt_pin_code_req(_Data); + ?EVT_RETURN_LINK_KEYS -> decode_evt_return_link_keys(_Data); + ?EVT_MODE_CHANGE -> decode_evt_mode_change(_Data); + ?EVT_NUM_COMP_PKTS -> decode_evt_num_comp_pkts(_Data); + ?EVT_ROLE_CHANGE -> decode_evt_role_change(_Data); + ?EVT_FLUSH_OCCURRED -> decode_evt_flush_occured(_Data); + ?EVT_HARDWARE_ERROR -> decode_evt_hardware_error(_Data); + ?EVT_CMD_STATUS -> decode_evt_cmd_status(_Data); + ?EVT_CMD_COMPLETE -> decode_evt_cmd_complete(_Data); + ?EVT_QOS_SETUP_COMPLETE -> decode_evt_qos_setup_complete(_Data); + ?EVT_READ_REMOTE_VERSION_COMPLETE -> decode_evt_read_remote_version_complete(_Data); + ?EVT_READ_REMOTE_FEATURES_COMPLETE -> decode_evt_read_remote_features_complete(_Data); + ?EVT_MASTER_LINK_KEY_COMPLETE -> decode_evt_master_link_key_complete(_Data); + ?EVT_CHANGE_CONN_LINK_KEY_COMPLETE -> decode_evt_change_conn_link_key_complete(_Data); + ?EVT_ENCRYPT_CHANGE -> decode_evt_encrypt_change(_Data); + ?EVT_REMOTE_NAME_REQ_COMPLETE -> decode_evt_remote_name_req_complete(_Data); + ?EVT_AUTH_COMPLETE -> decode_evt_auth_complete(_Data); + ?EVT_DISCONN_COMPLETE -> decode_evt_disconn_complete(_Data); + ?EVT_CONN_REQUEST -> decode_evt_conn_request(_Data); + ?EVT_INQUIRY_COMPLETE -> decode_inquiry_info(_Data); + ?EVT_INQUIRY_RESULT -> decode_inquiry_info(_Data); + ?EVT_CONN_COMPLETE -> decode_evt_conn_complete(_Data); + _ -> erlang:error(bad_event) + end. + +decode_le(Evt,_Data) -> + case Evt of + ?EVT_LE_LTK_REQUEST -> decode_evt_le_long_term_key_request(_Data); + ?EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE -> decode_evt_le_read_remote_used_features_complete(_Data); + ?EVT_LE_ADVERTISING_REPORT -> decode_evt_le_connection_update_complete(_Data); + ?EVT_LE_CONN_UPDATE_COMPLETE -> decode_evt_le_connection_update_complete(_Data); + ?EVT_LE_CONN_COMPLETE -> decode_evt_le_connection_complete(_Data); + _ -> erlang:error(bad_event) + end. + +le_test_end(Socket) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_TEST_END,<<>>,fun decode_le_test_end_rp/1). + +le_transmitter_test(Socket,Frequency,Length,Payload) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_TRANSMITTER_TEST,<<?le_transmitter_test_cp_bin(Frequency,Length,Payload)>>,undefined). + +le_receiver_test(Socket,Frequency) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_RECEIVER_TEST,<<?le_receiver_test_cp_bin(Frequency)>>,undefined). + +le_read_supported_states(Socket) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_READ_SUPPORTED_STATES,<<>>,fun decode_le_read_supported_states_rp/1). + +le_ltk_neg_reply(Socket,Handle) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_LTK_NEG_REPLY,<<?le_ltk_neg_reply_cp_bin(Handle)>>,fun decode_le_ltk_neg_reply_rp/1). + +le_ltk_reply(Socket,Handle,Key) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_LTK_REPLY,<<?le_ltk_reply_cp_bin(Handle,Key)>>,fun decode_le_ltk_reply_rp/1). + +le_start_encryption(Socket,Handle,Random,Diversifier,Key) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_START_ENCRYPTION,<<?le_start_encryption_cp_bin(Handle,Random,Diversifier,Key)>>,undefined). + +le_rand(Socket) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_RAND,<<>>,fun decode_le_rand_rp/1). + +le_encrypt(Socket,Key,Plaintext) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_ENCRYPT,<<?le_encrypt_cp_bin(Key,Plaintext)>>,fun decode_le_encrypt_rp/1). + +le_read_remote_used_features(Socket,Handle) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_READ_REMOTE_USED_FEATURES,<<?le_read_remote_used_features_cp_bin(Handle)>>,undefined). + +le_read_channel_map(Socket,Handle) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_READ_CHANNEL_MAP,<<?le_read_channel_map_cp_bin(Handle)>>,fun decode_le_read_channel_map_rp/1). + +le_set_host_channel_classification(Socket,Map) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_SET_HOST_CHANNEL_CLASSIFICATION,<<?le_set_host_channel_classification_cp_bin(Map)>>,undefined). + +le_conn_update(Socket) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_CONN_UPDATE,<<>>,undefined). + +le_remove_device_from_white_list(Socket,Bdaddr_type,Bdaddr) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_REMOVE_DEVICE_FROM_WHITE_LIST,<<?le_remove_device_from_white_list_cp_bin(Bdaddr_type,Bdaddr)>>,undefined). + +le_add_device_to_white_list(Socket,Bdaddr_type,Bdaddr) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_ADD_DEVICE_TO_WHITE_LIST,<<?le_add_device_to_white_list_cp_bin(Bdaddr_type,Bdaddr)>>,undefined). + +le_clear_white_list(Socket) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_CLEAR_WHITE_LIST,<<>>,undefined). + +le_read_white_list_size(Socket) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_READ_WHITE_LIST_SIZE,<<>>,fun decode_le_read_white_list_size_rp/1). + +le_create_conn_cancel(Socket) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_CREATE_CONN_CANCEL,<<>>,undefined). + +le_create_conn(Socket) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_CREATE_CONN,<<>>,undefined). + +le_set_scan_enable(Socket,Enable,Filter_dup) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_SET_SCAN_ENABLE,<<?le_set_scan_enable_cp_bin(Enable,Filter_dup)>>,undefined). + +le_set_scan_parameters(Socket,Type,Interval,Window,Own_bdaddr_type,Filter) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_SET_SCAN_PARAMETERS,<<?le_set_scan_parameters_cp_bin(Type,Interval,Window,Own_bdaddr_type,Filter)>>,undefined). + +le_set_advertise_enable(Socket,Enable) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_SET_ADVERTISE_ENABLE,<<?le_set_advertise_enable_cp_bin(Enable)>>,undefined). + +le_set_scan_response_data(Socket,Length,Data) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_SET_SCAN_RESPONSE_DATA,<<?le_set_scan_response_data_cp_bin(Length,Data)>>,undefined). + +le_set_advertising_data(Socket,Length,Data) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_SET_ADVERTISING_DATA,<<?le_set_advertising_data_cp_bin(Length,Data)>>,undefined). + +le_read_advertising_channel_tx_power(Socket) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_READ_ADVERTISING_CHANNEL_TX_POWER,<<>>,fun decode_le_read_advertising_channel_tx_power_rp/1). + +le_set_advertising_parameters(Socket,Min_interval,Max_interval,Advtype,Own_bdaddr_type,Direct_bdaddr_type,Direct_bdaddr,Chan_map,Filter) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_SET_ADVERTISING_PARAMETERS,<<?le_set_advertising_parameters_cp_bin(Min_interval,Max_interval,Advtype,Own_bdaddr_type,Direct_bdaddr_type,Direct_bdaddr,Chan_map,Filter)>>,undefined). + +le_set_random_address(Socket,Bdaddr) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_SET_RANDOM_ADDRESS,<<?le_set_random_address_cp_bin(Bdaddr)>>,undefined). + +le_read_local_supported_features(Socket) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_READ_LOCAL_SUPPORTED_FEATURES,<<>>,fun decode_le_read_local_supported_features_rp/1). + +le_read_buffer_size(Socket) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_READ_BUFFER_SIZE,<<>>,fun decode_le_read_buffer_size_rp/1). + +le_set_event_mask(Socket,Mask) -> + hci_socket:call(Socket,?OGF_LE_CTL,?OCF_LE_SET_EVENT_MASK,<<?le_set_event_mask_cp_bin(Mask)>>,undefined). + +write_simple_pairing_debug_mode(Socket,Mode) -> + hci_socket:call(Socket,?OGF_TESTING_CMD,?OCF_WRITE_SIMPLE_PAIRING_DEBUG_MODE,<<?write_simple_pairing_debug_mode_cp_bin(Mode)>>,fun decode_write_simple_pairing_debug_mode_rp/1). + +enable_device_under_test_mode(Socket) -> + hci_socket:call(Socket,?OGF_TESTING_CMD,?OCF_ENABLE_DEVICE_UNDER_TEST_MODE,<<>>,undefined). + +write_loopback_mode(Socket) -> + hci_socket:call(Socket,?OGF_TESTING_CMD,?OCF_WRITE_LOOPBACK_MODE,<<>>,undefined). + +read_loopback_mode(Socket) -> + hci_socket:call(Socket,?OGF_TESTING_CMD,?OCF_READ_LOOPBACK_MODE,<<>>,undefined). + +write_remote_amp_assoc(Socket,Handle,Length_so_far,Assoc_length,Fragment) -> + hci_socket:call(Socket,?OGF_STATUS_PARAM,?OCF_WRITE_REMOTE_AMP_ASSOC,<<?write_remote_amp_assoc_cp_bin(Handle,Length_so_far,Assoc_length,Fragment)>>,fun decode_write_remote_amp_assoc_rp/1). + +read_local_amp_assoc(Socket,Handle,Len_so_far,Max_len) -> + hci_socket:call(Socket,?OGF_STATUS_PARAM,?OCF_READ_LOCAL_AMP_ASSOC,<<?read_local_amp_assoc_cp_bin(Handle,Len_so_far,Max_len)>>,fun decode_read_local_amp_assoc_rp/1). + +read_local_amp_info(Socket) -> + hci_socket:call(Socket,?OGF_STATUS_PARAM,?OCF_READ_LOCAL_AMP_INFO,<<>>,fun decode_read_local_amp_info_rp/1). + +read_clock(Socket,Handle,Which_clock) -> + hci_socket:call(Socket,?OGF_STATUS_PARAM,?OCF_READ_CLOCK,<<?read_clock_cp_bin(Handle,Which_clock)>>,fun decode_read_clock_rp/1). + +read_afh_map(Socket) -> + hci_socket:call(Socket,?OGF_STATUS_PARAM,?OCF_READ_AFH_MAP,<<>>,fun decode_read_afh_map_rp/1). + +read_rssi(Socket) -> + hci_socket:call(Socket,?OGF_STATUS_PARAM,?OCF_READ_RSSI,<<>>,fun decode_read_rssi_rp/1). + +read_link_quality(Socket) -> + hci_socket:call(Socket,?OGF_STATUS_PARAM,?OCF_READ_LINK_QUALITY,<<>>,fun decode_read_link_quality_rp/1). + +reset_failed_contact_counter(Socket) -> + hci_socket:call(Socket,?OGF_STATUS_PARAM,?OCF_RESET_FAILED_CONTACT_COUNTER,<<>>,fun decode_reset_failed_contact_counter_rp/1). + +read_failed_contact_counter(Socket) -> + hci_socket:call(Socket,?OGF_STATUS_PARAM,?OCF_READ_FAILED_CONTACT_COUNTER,<<>>,fun decode_read_failed_contact_counter_rp/1). + +read_bd_addr(Socket) -> + hci_socket:call(Socket,?OGF_INFO_PARAM,?OCF_READ_BD_ADDR,<<>>,fun decode_read_bd_addr_rp/1). + +read_country_code(Socket) -> + hci_socket:call(Socket,?OGF_INFO_PARAM,?OCF_READ_COUNTRY_CODE,<<>>,undefined). + +read_buffer_size(Socket) -> + hci_socket:call(Socket,?OGF_INFO_PARAM,?OCF_READ_BUFFER_SIZE,<<>>,fun decode_read_buffer_size_rp/1). + +read_local_ext_features(Socket,Page_num) -> + hci_socket:call(Socket,?OGF_INFO_PARAM,?OCF_READ_LOCAL_EXT_FEATURES,<<?read_local_ext_features_cp_bin(Page_num)>>,fun decode_read_local_ext_features_rp/1). + +read_local_features(Socket) -> + hci_socket:call(Socket,?OGF_INFO_PARAM,?OCF_READ_LOCAL_FEATURES,<<>>,fun decode_read_local_features_rp/1). + +read_local_commands(Socket) -> + hci_socket:call(Socket,?OGF_INFO_PARAM,?OCF_READ_LOCAL_COMMANDS,<<>>,fun decode_read_local_commands_rp/1). + +read_local_version(Socket) -> + hci_socket:call(Socket,?OGF_INFO_PARAM,?OCF_READ_LOCAL_VERSION,<<>>,fun decode_read_local_version_rp/1). + +write_le_host_supported(Socket,Le,Simul) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_LE_HOST_SUPPORTED,<<?write_le_host_supported_cp_bin(Le,Simul)>>,undefined). + +read_le_host_supported(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_LE_HOST_SUPPORTED,<<>>,fun decode_read_le_host_supported_rp/1). + +write_best_effort_flush_timeout(Socket,Handle,Timeout) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_BEST_EFFORT_FLUSH_TIMEOUT,<<?write_best_effort_flush_timeout_cp_bin(Handle,Timeout)>>,fun decode_write_best_effort_flush_timeout_rp/1). + +read_best_effort_flush_timeout(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_BEST_EFFORT_FLUSH_TIMEOUT,<<>>,fun decode_read_best_effort_flush_timeout_rp/1). + +read_enhanced_transmit_power_level(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_ENHANCED_TRANSMIT_POWER_LEVEL,<<>>,fun decode_read_enhanced_transmit_power_level_rp/1). + +write_flow_control_mode(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_FLOW_CONTROL_MODE,<<>>,undefined). + +read_flow_control_mode(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_FLOW_CONTROL_MODE,<<>>,undefined). + +write_location_data(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_LOCATION_DATA,<<>>,undefined). + +read_location_data(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_LOCATION_DATA,<<>>,undefined). + +set_event_mask_page_2(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_SET_EVENT_MASK_PAGE_2,<<>>,undefined). + +write_logical_link_accept_timeout(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_LOGICAL_LINK_ACCEPT_TIMEOUT,<<>>,undefined). + +read_logical_link_accept_timeout(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_LOGICAL_LINK_ACCEPT_TIMEOUT,<<>>,undefined). + +send_keypress_notify(Socket,Bdaddr,Type) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_SEND_KEYPRESS_NOTIFY,<<?send_keypress_notify_cp_bin(Bdaddr,Type)>>,fun decode_send_keypress_notify_rp/1). + +enhanced_flush(Socket,Handle,Type) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_ENHANCED_FLUSH,<<?enhanced_flush_cp_bin(Handle,Type)>>,undefined). + +write_default_error_data_reporting(Socket,Reporting) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_DEFAULT_ERROR_DATA_REPORTING,<<?write_default_error_data_reporting_cp_bin(Reporting)>>,fun decode_write_default_error_data_reporting_rp/1). + +read_default_error_data_reporting(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_DEFAULT_ERROR_DATA_REPORTING,<<>>,fun decode_read_default_error_data_reporting_rp/1). + +write_inquiry_transmit_power_level(Socket,Level) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_INQUIRY_TRANSMIT_POWER_LEVEL,<<?write_inquiry_transmit_power_level_cp_bin(Level)>>,fun decode_write_inquiry_transmit_power_level_rp/1). + +read_inquiry_transmit_power_level(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_INQUIRY_TRANSMIT_POWER_LEVEL,<<>>,fun decode_read_inquiry_transmit_power_level_rp/1). + +read_inq_response_tx_power_level(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_INQ_RESPONSE_TX_POWER_LEVEL,<<>>,fun decode_read_inq_response_tx_power_level_rp/1). + +read_local_oob_data(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_LOCAL_OOB_DATA,<<>>,fun decode_read_local_oob_data_rp/1). + +write_simple_pairing_mode(Socket,Mode) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_SIMPLE_PAIRING_MODE,<<?write_simple_pairing_mode_cp_bin(Mode)>>,fun decode_write_simple_pairing_mode_rp/1). + +read_simple_pairing_mode(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_SIMPLE_PAIRING_MODE,<<>>,fun decode_read_simple_pairing_mode_rp/1). + +refresh_encryption_key(Socket,Handle) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_REFRESH_ENCRYPTION_KEY,<<?refresh_encryption_key_cp_bin(Handle)>>,fun decode_refresh_encryption_key_rp/1). + +write_ext_inquiry_response(Socket,Fec,Data) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_EXT_INQUIRY_RESPONSE,<<?write_ext_inquiry_response_cp_bin(Fec,Data)>>,fun decode_write_ext_inquiry_response_rp/1). + +read_ext_inquiry_response(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_EXT_INQUIRY_RESPONSE,<<>>,fun decode_read_ext_inquiry_response_rp/1). + +write_afh_mode(Socket,Mode) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_AFH_MODE,<<?write_afh_mode_cp_bin(Mode)>>,fun decode_write_afh_mode_rp/1). + +read_afh_mode(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_AFH_MODE,<<>>,fun decode_read_afh_mode_rp/1). + +write_page_scan_type(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_PAGE_SCAN_TYPE,<<>>,undefined). + +read_page_scan_type(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_PAGE_SCAN_TYPE,<<>>,undefined). + +write_inquiry_mode(Socket,Mode) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_INQUIRY_MODE,<<?write_inquiry_mode_cp_bin(Mode)>>,fun decode_write_inquiry_mode_rp/1). + +read_inquiry_mode(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_INQUIRY_MODE,<<>>,fun decode_read_inquiry_mode_rp/1). + +write_inquiry_scan_type(Socket,Type) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_INQUIRY_SCAN_TYPE,<<?write_inquiry_scan_type_cp_bin(Type)>>,fun decode_write_inquiry_scan_type_rp/1). + +read_inquiry_scan_type(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_INQUIRY_SCAN_TYPE,<<>>,fun decode_read_inquiry_scan_type_rp/1). + +set_afh_classification(Socket,Map) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_SET_AFH_CLASSIFICATION,<<?set_afh_classification_cp_bin(Map)>>,fun decode_set_afh_classification_rp/1). + +write_page_scan_mode(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_PAGE_SCAN_MODE,<<>>,undefined). + +read_page_scan_mode(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_PAGE_SCAN_MODE,<<>>,undefined). + +write_page_scan_period_mode(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_PAGE_SCAN_PERIOD_MODE,<<>>,undefined). + +read_page_scan_period_mode(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_PAGE_SCAN_PERIOD_MODE,<<>>,undefined). + +write_current_iac_lap(Socket,Num_current_iac,Lap) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_CURRENT_IAC_LAP,<<?write_current_iac_lap_cp_bin(Num_current_iac,Lap)>>,undefined). + +read_current_iac_lap(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_CURRENT_IAC_LAP,<<>>,fun decode_read_current_iac_lap_rp/1). + +read_num_supported_iac(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_NUM_SUPPORTED_IAC,<<>>,undefined). + +write_link_supervision_timeout(Socket,Handle,Timeout) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_LINK_SUPERVISION_TIMEOUT,<<?write_link_supervision_timeout_cp_bin(Handle,Timeout)>>,fun decode_write_link_supervision_timeout_rp/1). + +read_link_supervision_timeout(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_LINK_SUPERVISION_TIMEOUT,<<>>,fun decode_read_link_supervision_timeout_rp/1). + +host_num_comp_pkts(Socket,Num_hndl) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_HOST_NUM_COMP_PKTS,<<?host_num_comp_pkts_cp_bin(Num_hndl)>>,undefined). + +host_buffer_size(Socket,Acl_mtu,Sco_mtu,Acl_max_pkt,Sco_max_pkt) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_HOST_BUFFER_SIZE,<<?host_buffer_size_cp_bin(Acl_mtu,Sco_mtu,Acl_max_pkt,Sco_max_pkt)>>,undefined). + +set_controller_to_host_fc(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_SET_CONTROLLER_TO_HOST_FC,<<>>,undefined). + +write_sync_flow_enable(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_SYNC_FLOW_ENABLE,<<>>,undefined). + +read_sync_flow_enable(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_SYNC_FLOW_ENABLE,<<>>,undefined). + +read_transmit_power_level(Socket,Handle,Type) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_TRANSMIT_POWER_LEVEL,<<?read_transmit_power_level_cp_bin(Handle,Type)>>,fun decode_read_transmit_power_level_rp/1). + +write_hold_mode_activity(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_HOLD_MODE_ACTIVITY,<<>>,undefined). + +read_hold_mode_activity(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_HOLD_MODE_ACTIVITY,<<>>,undefined). + +write_num_broadcast_retrans(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_NUM_BROADCAST_RETRANS,<<>>,undefined). + +read_num_broadcast_retrans(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_NUM_BROADCAST_RETRANS,<<>>,undefined). + +write_automatic_flush_timeout(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_AUTOMATIC_FLUSH_TIMEOUT,<<>>,undefined). + +read_automatic_flush_timeout(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_AUTOMATIC_FLUSH_TIMEOUT,<<>>,undefined). + +write_voice_setting(Socket,Voice_setting) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_VOICE_SETTING,<<?write_voice_setting_cp_bin(Voice_setting)>>,undefined). + +read_voice_setting(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_VOICE_SETTING,<<>>,fun decode_read_voice_setting_rp/1). + +write_class_of_dev(Socket,Dev_class) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_CLASS_OF_DEV,<<?write_class_of_dev_cp_bin(Dev_class)>>,undefined). + +read_class_of_dev(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_CLASS_OF_DEV,<<>>,fun decode_read_class_of_dev_rp/1). + +write_encrypt_mode(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_ENCRYPT_MODE,<<>>,undefined). + +read_encrypt_mode(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_ENCRYPT_MODE,<<>>,undefined). + +write_auth_enable(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_AUTH_ENABLE,<<>>,undefined). + +read_auth_enable(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_AUTH_ENABLE,<<>>,undefined). + +write_inq_activity(Socket,Interval,Window) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_INQ_ACTIVITY,<<?write_inq_activity_cp_bin(Interval,Window)>>,undefined). + +read_inq_activity(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_INQ_ACTIVITY,<<>>,fun decode_read_inq_activity_rp/1). + +write_page_activity(Socket,Interval,Window) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_PAGE_ACTIVITY,<<?write_page_activity_cp_bin(Interval,Window)>>,undefined). + +read_page_activity(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_PAGE_ACTIVITY,<<>>,fun decode_read_page_activity_rp/1). + +write_scan_enable(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_SCAN_ENABLE,<<>>,undefined). + +read_scan_enable(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_SCAN_ENABLE,<<>>,fun decode_read_scan_enable_rp/1). + +write_page_timeout(Socket,Timeout) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_PAGE_TIMEOUT,<<?write_page_timeout_cp_bin(Timeout)>>,undefined). + +read_page_timeout(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_PAGE_TIMEOUT,<<>>,fun decode_read_page_timeout_rp/1). + +write_conn_accept_timeout(Socket,Timeout) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_CONN_ACCEPT_TIMEOUT,<<?write_conn_accept_timeout_cp_bin(Timeout)>>,undefined). + +read_conn_accept_timeout(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_CONN_ACCEPT_TIMEOUT,<<>>,fun decode_read_conn_accept_timeout_rp/1). + +read_local_name(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_LOCAL_NAME,<<>>,fun decode_read_local_name_rp/1). + +change_local_name(Socket,Name) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_CHANGE_LOCAL_NAME,<<?change_local_name_cp_bin(Name)>>,undefined). + +delete_stored_link_key(Socket,Bdaddr,Delete_all) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_DELETE_STORED_LINK_KEY,<<?delete_stored_link_key_cp_bin(Bdaddr,Delete_all)>>,fun decode_delete_stored_link_key_rp/1). + +write_stored_link_key(Socket,Num_keys) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_STORED_LINK_KEY,<<?write_stored_link_key_cp_bin(Num_keys)>>,fun decode_write_stored_link_key_rp/1). + +read_stored_link_key(Socket,Bdaddr,Read_all) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_STORED_LINK_KEY,<<?read_stored_link_key_cp_bin(Bdaddr,Read_all)>>,fun decode_read_stored_link_key_rp/1). + +create_new_unit_key(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_CREATE_NEW_UNIT_KEY,<<>>,undefined). + +write_pin_type(Socket,Pin_type) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_WRITE_PIN_TYPE,<<?write_pin_type_cp_bin(Pin_type)>>,undefined). + +read_pin_type(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_READ_PIN_TYPE,<<>>,fun decode_read_pin_type_rp/1). + +flush(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_FLUSH,<<>>,undefined). + +set_event_flt(Socket,Flt_type,Cond_type,Condition) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_SET_EVENT_FLT,<<?set_event_flt_cp_bin(Flt_type,Cond_type,Condition)>>,undefined). + +reset(Socket) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_RESET,<<>>,undefined). + +set_event_mask(Socket,Mask) -> + hci_socket:call(Socket,?OGF_HOST_CTL,?OCF_SET_EVENT_MASK,<<?set_event_mask_cp_bin(Mask)>>,undefined). + +sniff_subrating(Socket,Handle,Max_latency,Min_remote_timeout,Min_local_timeout) -> + hci_socket:call(Socket,?OGF_LINK_POLICY,?OCF_SNIFF_SUBRATING,<<?sniff_subrating_cp_bin(Handle,Max_latency,Min_remote_timeout,Min_local_timeout)>>,undefined). + +flow_specification(Socket) -> + hci_socket:call(Socket,?OGF_LINK_POLICY,?OCF_FLOW_SPECIFICATION,<<>>,undefined). + +write_default_link_policy(Socket) -> + hci_socket:call(Socket,?OGF_LINK_POLICY,?OCF_WRITE_DEFAULT_LINK_POLICY,<<>>,undefined). + +read_default_link_policy(Socket) -> + hci_socket:call(Socket,?OGF_LINK_POLICY,?OCF_READ_DEFAULT_LINK_POLICY,<<>>,undefined). + +write_link_policy(Socket,Handle,Policy) -> + hci_socket:call(Socket,?OGF_LINK_POLICY,?OCF_WRITE_LINK_POLICY,<<?write_link_policy_cp_bin(Handle,Policy)>>,fun decode_write_link_policy_rp/1). + +read_link_policy(Socket,Handle) -> + hci_socket:call(Socket,?OGF_LINK_POLICY,?OCF_READ_LINK_POLICY,<<?read_link_policy_cp_bin(Handle)>>,fun decode_read_link_policy_rp/1). + +switch_role(Socket,Bdaddr,Role) -> + hci_socket:call(Socket,?OGF_LINK_POLICY,?OCF_SWITCH_ROLE,<<?switch_role_cp_bin(Bdaddr,Role)>>,undefined). + +role_discovery(Socket,Handle) -> + hci_socket:call(Socket,?OGF_LINK_POLICY,?OCF_ROLE_DISCOVERY,<<?role_discovery_cp_bin(Handle)>>,fun decode_role_discovery_rp/1). + +qos_setup(Socket,Handle,Flags,Qos) -> + hci_socket:call(Socket,?OGF_LINK_POLICY,?OCF_QOS_SETUP,<<?qos_setup_cp_bin(Handle,Flags,Qos)>>,undefined). + +exit_park_mode(Socket,Handle) -> + hci_socket:call(Socket,?OGF_LINK_POLICY,?OCF_EXIT_PARK_MODE,<<?exit_park_mode_cp_bin(Handle)>>,undefined). + +park_mode(Socket,Handle,Max_interval,Min_interval) -> + hci_socket:call(Socket,?OGF_LINK_POLICY,?OCF_PARK_MODE,<<?park_mode_cp_bin(Handle,Max_interval,Min_interval)>>,undefined). + +exit_sniff_mode(Socket,Handle) -> + hci_socket:call(Socket,?OGF_LINK_POLICY,?OCF_EXIT_SNIFF_MODE,<<?exit_sniff_mode_cp_bin(Handle)>>,undefined). + +sniff_mode(Socket,Handle,Max_interval,Min_interval,Attempt,Timeout) -> + hci_socket:call(Socket,?OGF_LINK_POLICY,?OCF_SNIFF_MODE,<<?sniff_mode_cp_bin(Handle,Max_interval,Min_interval,Attempt,Timeout)>>,undefined). + +hold_mode(Socket,Handle,Max_interval,Min_interval) -> + hci_socket:call(Socket,?OGF_LINK_POLICY,?OCF_HOLD_MODE,<<?hold_mode_cp_bin(Handle,Max_interval,Min_interval)>>,undefined). + +flow_spec_modify(Socket) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_FLOW_SPEC_MODIFY,<<>>,undefined). + +logical_link_cancel(Socket) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_LOGICAL_LINK_CANCEL,<<>>,undefined). + +disconnect_logical_link(Socket,Handle) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_DISCONNECT_LOGICAL_LINK,<<?disconnect_logical_link_cp_bin(Handle)>>,undefined). + +accept_logical_link(Socket) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_ACCEPT_LOGICAL_LINK,<<>>,undefined). + +create_logical_link(Socket,Handle,Tx_flow,Rx_flow) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_CREATE_LOGICAL_LINK,<<?create_logical_link_cp_bin(Handle,Tx_flow,Rx_flow)>>,undefined). + +disconnect_physical_link(Socket,Handle,Reason) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_DISCONNECT_PHYSICAL_LINK,<<?disconnect_physical_link_cp_bin(Handle,Reason)>>,undefined). + +accept_physical_link(Socket) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_ACCEPT_PHYSICAL_LINK,<<>>,undefined). + +create_physical_link(Socket,Handle,Key_length,Key_type,Key) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_CREATE_PHYSICAL_LINK,<<?create_physical_link_cp_bin(Handle,Key_length,Key_type,Key)>>,undefined). + +io_capability_neg_reply(Socket,Bdaddr,Reason) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_IO_CAPABILITY_NEG_REPLY,<<?io_capability_neg_reply_cp_bin(Bdaddr,Reason)>>,undefined). + +remote_oob_data_neg_reply(Socket) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_REMOTE_OOB_DATA_NEG_REPLY,<<>>,undefined). + +remote_oob_data_reply(Socket,Bdaddr,Hash,Randomizer) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_REMOTE_OOB_DATA_REPLY,<<?remote_oob_data_reply_cp_bin(Bdaddr,Hash,Randomizer)>>,undefined). + +user_passkey_neg_reply(Socket) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_USER_PASSKEY_NEG_REPLY,<<>>,undefined). + +user_passkey_reply(Socket,Bdaddr,Passkey) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_USER_PASSKEY_REPLY,<<?user_passkey_reply_cp_bin(Bdaddr,Passkey)>>,undefined). + +user_confirm_neg_reply(Socket) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_USER_CONFIRM_NEG_REPLY,<<>>,undefined). + +user_confirm_reply(Socket,Bdaddr) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_USER_CONFIRM_REPLY,<<?user_confirm_reply_cp_bin(Bdaddr)>>,undefined). + +io_capability_reply(Socket,Bdaddr,Capability,Oob_data,Authentication) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_IO_CAPABILITY_REPLY,<<?io_capability_reply_cp_bin(Bdaddr,Capability,Oob_data,Authentication)>>,undefined). + +reject_sync_conn_req(Socket,Bdaddr,Reason) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_REJECT_SYNC_CONN_REQ,<<?reject_sync_conn_req_cp_bin(Bdaddr,Reason)>>,undefined). + +accept_sync_conn_req(Socket,Bdaddr,Tx_bandwith,Rx_bandwith,Max_latency,Voice_setting,Retrans_effort,Pkt_type) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_ACCEPT_SYNC_CONN_REQ,<<?accept_sync_conn_req_cp_bin(Bdaddr,Tx_bandwith,Rx_bandwith,Max_latency,Voice_setting,Retrans_effort,Pkt_type)>>,undefined). + +setup_sync_conn(Socket,Handle,Tx_bandwith,Rx_bandwith,Max_latency,Voice_setting,Retrans_effort,Pkt_type) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_SETUP_SYNC_CONN,<<?setup_sync_conn_cp_bin(Handle,Tx_bandwith,Rx_bandwith,Max_latency,Voice_setting,Retrans_effort,Pkt_type)>>,undefined). + +read_lmp_handle(Socket) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_READ_LMP_HANDLE,<<>>,undefined). + +read_clock_offset(Socket,Handle) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_READ_CLOCK_OFFSET,<<?read_clock_offset_cp_bin(Handle)>>,undefined). + +read_remote_version(Socket,Handle) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_READ_REMOTE_VERSION,<<?read_remote_version_cp_bin(Handle)>>,undefined). + +read_remote_ext_features(Socket,Handle,Page_num) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_READ_REMOTE_EXT_FEATURES,<<?read_remote_ext_features_cp_bin(Handle,Page_num)>>,undefined). + +read_remote_features(Socket,Handle) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_READ_REMOTE_FEATURES,<<?read_remote_features_cp_bin(Handle)>>,undefined). + +remote_name_req_cancel(Socket,Bdaddr) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_REMOTE_NAME_REQ_CANCEL,<<?remote_name_req_cancel_cp_bin(Bdaddr)>>,undefined). + +remote_name_req(Socket,Bdaddr,Pscan_rep_mode,Pscan_mode,Clock_offset) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_REMOTE_NAME_REQ,<<?remote_name_req_cp_bin(Bdaddr,Pscan_rep_mode,Pscan_mode,Clock_offset)>>,undefined). + +master_link_key(Socket,Key_flag) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_MASTER_LINK_KEY,<<?master_link_key_cp_bin(Key_flag)>>,undefined). + +change_conn_link_key(Socket,Handle) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_CHANGE_CONN_LINK_KEY,<<?change_conn_link_key_cp_bin(Handle)>>,undefined). + +set_conn_encrypt(Socket,Handle,Encrypt) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_SET_CONN_ENCRYPT,<<?set_conn_encrypt_cp_bin(Handle,Encrypt)>>,undefined). + +auth_requested(Socket,Handle) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_AUTH_REQUESTED,<<?auth_requested_cp_bin(Handle)>>,undefined). + +set_conn_ptype(Socket,Handle,Pkt_type) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_SET_CONN_PTYPE,<<?set_conn_ptype_cp_bin(Handle,Pkt_type)>>,undefined). + +pin_code_neg_reply(Socket) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_PIN_CODE_NEG_REPLY,<<>>,undefined). + +pin_code_reply(Socket,Bdaddr,Pin_len,Pin_code) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_PIN_CODE_REPLY,<<?pin_code_reply_cp_bin(Bdaddr,Pin_len,Pin_code)>>,undefined). + +link_key_neg_reply(Socket,Bdaddr) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_LINK_KEY_NEG_REPLY,<<?link_key_neg_reply_cp_bin(Bdaddr)>>,undefined). + +link_key_reply(Socket,Bdaddr,Link_key) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_LINK_KEY_REPLY,<<?link_key_reply_cp_bin(Bdaddr,Link_key)>>,undefined). + +reject_conn_req(Socket,Bdaddr,Reason) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_REJECT_CONN_REQ,<<?reject_conn_req_cp_bin(Bdaddr,Reason)>>,undefined). + +accept_conn_req(Socket,Bdaddr,Role) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_ACCEPT_CONN_REQ,<<?accept_conn_req_cp_bin(Bdaddr,Role)>>,undefined). + +create_conn_cancel(Socket,Bdaddr) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_CREATE_CONN_CANCEL,<<?create_conn_cancel_cp_bin(Bdaddr)>>,undefined). + +add_sco(Socket,Handle,Pkt_type) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_ADD_SCO,<<?add_sco_cp_bin(Handle,Pkt_type)>>,undefined). + +disconnect(Socket,Handle,Reason) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_DISCONNECT,<<?disconnect_cp_bin(Handle,Reason)>>,undefined). + +create_conn(Socket,Bdaddr,Pkt_type,Pscan_rep_mode,Pscan_mode,Clock_offset,Role_switch) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_CREATE_CONN,<<?create_conn_cp_bin(Bdaddr,Pkt_type,Pscan_rep_mode,Pscan_mode,Clock_offset,Role_switch)>>,undefined). + +exit_periodic_inquiry(Socket) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_EXIT_PERIODIC_INQUIRY,<<>>,undefined). + +periodic_inquiry(Socket,Max_period,Min_period,Lap,Length,Num_rsp) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_PERIODIC_INQUIRY,<<?periodic_inquiry_cp_bin(Max_period,Min_period,Lap,Length,Num_rsp)>>,undefined). + +inquiry_cancel(Socket) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_INQUIRY_CANCEL,<<>>,undefined). + +inquiry(Socket,Lap,Length,Num_rsp) -> + hci_socket:call(Socket,?OGF_LINK_CTL,?OCF_INQUIRY,<<?inquiry_cp_bin(Lap,Length,Num_rsp)>>,undefined). + diff --git a/deps/bt/src/hci_api.hrl b/deps/bt/src/hci_api.hrl new file mode 100644 index 0000000..4cf89d6 --- /dev/null +++ b/deps/bt/src/hci_api.hrl @@ -0,0 +1,1935 @@ +%% -*- erlang -*- +-ifndef(__HCI_HRL__). +-define(__HCI_HRL__,true). + + +-define(HCI_MAX_DEV, 16). +-define(HCI_MAX_ACL_SIZE, 1024). +-define(HCI_MAX_SCO_SIZE, 255). +-define(HCI_MAX_EVENT_SIZE, 260). +-define(HCI_MAX_FRAME_SIZE, (?HCI_MAX_ACL_SIZE + 4)). +-define(HCI_DEV_REG, 1). +-define(HCI_DEV_UNREG, 2). +-define(HCI_DEV_UP, 3). +-define(HCI_DEV_DOWN, 4). +-define(HCI_DEV_SUSPEND, 5). +-define(HCI_DEV_RESUME, 6). +-define(HCI_UNKNOWN_COMMAND, 1). +-define(HCI_NO_CONNECTION, 2). +-define(HCI_HARDWARE_FAILURE, 3). +-define(HCI_PAGE_TIMEOUT, 4). +-define(HCI_AUTHENTICATION_FAILURE, 5). +-define(HCI_PIN_OR_KEY_MISSING, 6). +-define(HCI_MEMORY_FULL, 7). +-define(HCI_CONNECTION_TIMEOUT, 8). +-define(HCI_MAX_NUMBER_OF_CONNECTIONS, 9). +-define(HCI_MAX_NUMBER_OF_SCO_CONNECTIONS, 10). +-define(HCI_ACL_CONNECTION_EXISTS, 11). +-define(HCI_COMMAND_DISALLOWED, 12). +-define(HCI_REJECTED_LIMITED_RESOURCES, 13). +-define(HCI_REJECTED_SECURITY, 14). +-define(HCI_REJECTED_PERSONAL, 15). +-define(HCI_HOST_TIMEOUT, 16). +-define(HCI_UNSUPPORTED_FEATURE, 17). +-define(HCI_INVALID_PARAMETERS, 18). +-define(HCI_OE_USER_ENDED_CONNECTION, 19). +-define(HCI_OE_LOW_RESOURCES, 20). +-define(HCI_OE_POWER_OFF, 21). +-define(HCI_CONNECTION_TERMINATED, 22). +-define(HCI_REPEATED_ATTEMPTS, 23). +-define(HCI_PAIRING_NOT_ALLOWED, 24). +-define(HCI_UNKNOWN_LMP_PDU, 25). +-define(HCI_UNSUPPORTED_REMOTE_FEATURE, 26). +-define(HCI_SCO_OFFSET_REJECTED, 27). +-define(HCI_SCO_INTERVAL_REJECTED, 28). +-define(HCI_AIR_MODE_REJECTED, 29). +-define(HCI_INVALID_LMP_PARAMETERS, 30). +-define(HCI_UNSPECIFIED_ERROR, 31). +-define(HCI_UNSUPPORTED_LMP_PARAMETER_VALUE, 32). +-define(HCI_ROLE_CHANGE_NOT_ALLOWED, 33). +-define(HCI_LMP_RESPONSE_TIMEOUT, 34). +-define(HCI_LMP_ERROR_TRANSACTION_COLLISION, 35). +-define(HCI_LMP_PDU_NOT_ALLOWED, 36). +-define(HCI_ENCRYPTION_MODE_NOT_ACCEPTED, 37). +-define(HCI_UNIT_LINK_KEY_USED, 38). +-define(HCI_QOS_NOT_SUPPORTED, 39). +-define(HCI_INSTANT_PASSED, 40). +-define(HCI_PAIRING_NOT_SUPPORTED, 41). +-define(HCI_TRANSACTION_COLLISION, 42). +-define(HCI_QOS_UNACCEPTABLE_PARAMETER, 44). +-define(HCI_QOS_REJECTED, 45). +-define(HCI_CLASSIFICATION_NOT_SUPPORTED, 46). +-define(HCI_INSUFFICIENT_SECURITY, 47). +-define(HCI_PARAMETER_OUT_OF_RANGE, 48). +-define(HCI_ROLE_SWITCH_PENDING, 50). +-define(HCI_SLOT_VIOLATION, 52). +-define(HCI_ROLE_SWITCH_FAILED, 53). +-define(HCI_EIR_TOO_LARGE, 54). +-define(HCI_SIMPLE_PAIRING_NOT_SUPPORTED, 55). +-define(HCI_HOST_BUSY_PAIRING, 56). +-define(OGF_LINK_CTL, 1). +-define(OCF_INQUIRY, 1). +-record(inquiry_cp, { + lap, + length, + num_rsp +}). +-define(inquiry_cp_bin(Lap,Length,Num_rsp),Lap:3/unit:8-binary,Length:1/unsigned-unit:8,Num_rsp:1/unsigned-unit:8). +-define(INQUIRY_CP_SIZE, 5). +-record(status_bdaddr_rp, { + status, + bdaddr +}). +-define(status_bdaddr_rp_bin(Status,Bdaddr),Status:1/unsigned-unit:8,Bdaddr:6/unit:8-binary). +-define(STATUS_BDADDR_RP_SIZE, 7). +-define(OCF_INQUIRY_CANCEL, 2). +-define(OCF_PERIODIC_INQUIRY, 3). +-record(periodic_inquiry_cp, { + max_period, + min_period, + lap, + length, + num_rsp +}). +-define(periodic_inquiry_cp_bin(Max_period,Min_period,Lap,Length,Num_rsp),Max_period:1/little-unsigned-unit:16,Min_period:1/little-unsigned-unit:16,Lap:3/unit:8-binary,Length:1/unsigned-unit:8,Num_rsp:1/unsigned-unit:8). +-define(PERIODIC_INQUIRY_CP_SIZE, 9). +-define(OCF_EXIT_PERIODIC_INQUIRY, 4). +-define(OCF_CREATE_CONN, 5). +-record(create_conn_cp, { + bdaddr, + pkt_type, + pscan_rep_mode, + pscan_mode, + clock_offset, + role_switch +}). +-define(create_conn_cp_bin(Bdaddr,Pkt_type,Pscan_rep_mode,Pscan_mode,Clock_offset,Role_switch),Bdaddr:6/unit:8-binary,Pkt_type:1/little-unsigned-unit:16,Pscan_rep_mode:1/unsigned-unit:8,Pscan_mode:1/unsigned-unit:8,Clock_offset:1/little-unsigned-unit:16,Role_switch:1/unsigned-unit:8). +-define(CREATE_CONN_CP_SIZE, 13). +-define(OCF_DISCONNECT, 6). +-record(disconnect_cp, { + handle, + reason +}). +-define(disconnect_cp_bin(Handle,Reason),Handle:1/little-unsigned-unit:16,Reason:1/unsigned-unit:8). +-define(DISCONNECT_CP_SIZE, 3). +-define(OCF_ADD_SCO, 7). +-record(add_sco_cp, { + handle, + pkt_type +}). +-define(add_sco_cp_bin(Handle,Pkt_type),Handle:1/little-unsigned-unit:16,Pkt_type:1/little-unsigned-unit:16). +-define(ADD_SCO_CP_SIZE, 4). +-define(OCF_CREATE_CONN_CANCEL, 8). +-record(create_conn_cancel_cp, { + bdaddr +}). +-define(create_conn_cancel_cp_bin(Bdaddr),Bdaddr:6/unit:8-binary). +-define(CREATE_CONN_CANCEL_CP_SIZE, 6). +-define(OCF_ACCEPT_CONN_REQ, 9). +-record(accept_conn_req_cp, { + bdaddr, + role +}). +-define(accept_conn_req_cp_bin(Bdaddr,Role),Bdaddr:6/unit:8-binary,Role:1/unsigned-unit:8). +-define(ACCEPT_CONN_REQ_CP_SIZE, 7). +-define(OCF_REJECT_CONN_REQ, 10). +-record(reject_conn_req_cp, { + bdaddr, + reason +}). +-define(reject_conn_req_cp_bin(Bdaddr,Reason),Bdaddr:6/unit:8-binary,Reason:1/unsigned-unit:8). +-define(REJECT_CONN_REQ_CP_SIZE, 7). +-define(OCF_LINK_KEY_REPLY, 11). +-record(link_key_reply_cp, { + bdaddr, + link_key +}). +-define(link_key_reply_cp_bin(Bdaddr,Link_key),Bdaddr:6/unit:8-binary,Link_key:16/unit:8-binary). +-define(LINK_KEY_REPLY_CP_SIZE, 22). +-define(OCF_LINK_KEY_NEG_REPLY, 12). +-record(link_key_neg_reply_cp, { + bdaddr +}). +-define(link_key_neg_reply_cp_bin(Bdaddr),Bdaddr:6/unit:8-binary). +-define(LINK_KEY_NEG_REPLY_CP_SIZE, 6). +-define(OCF_PIN_CODE_REPLY, 13). +-record(pin_code_reply_cp, { + bdaddr, + pin_len, + pin_code +}). +-define(pin_code_reply_cp_bin(Bdaddr,Pin_len,Pin_code),Bdaddr:6/unit:8-binary,Pin_len:1/unsigned-unit:8,Pin_code:16/unit:8-binary). +-define(PIN_CODE_REPLY_CP_SIZE, 23). +-define(OCF_PIN_CODE_NEG_REPLY, 14). +-define(OCF_SET_CONN_PTYPE, 15). +-record(set_conn_ptype_cp, { + handle, + pkt_type +}). +-define(set_conn_ptype_cp_bin(Handle,Pkt_type),Handle:1/little-unsigned-unit:16,Pkt_type:1/little-unsigned-unit:16). +-define(SET_CONN_PTYPE_CP_SIZE, 4). +-define(OCF_AUTH_REQUESTED, 17). +-record(auth_requested_cp, { + handle +}). +-define(auth_requested_cp_bin(Handle),Handle:1/little-unsigned-unit:16). +-define(AUTH_REQUESTED_CP_SIZE, 2). +-define(OCF_SET_CONN_ENCRYPT, 19). +-record(set_conn_encrypt_cp, { + handle, + encrypt +}). +-define(set_conn_encrypt_cp_bin(Handle,Encrypt),Handle:1/little-unsigned-unit:16,Encrypt:1/unsigned-unit:8). +-define(SET_CONN_ENCRYPT_CP_SIZE, 3). +-define(OCF_CHANGE_CONN_LINK_KEY, 21). +-record(change_conn_link_key_cp, { + handle +}). +-define(change_conn_link_key_cp_bin(Handle),Handle:1/little-unsigned-unit:16). +-define(CHANGE_CONN_LINK_KEY_CP_SIZE, 2). +-define(OCF_MASTER_LINK_KEY, 23). +-record(master_link_key_cp, { + key_flag +}). +-define(master_link_key_cp_bin(Key_flag),Key_flag:1/unsigned-unit:8). +-define(MASTER_LINK_KEY_CP_SIZE, 1). +-define(OCF_REMOTE_NAME_REQ, 25). +-record(remote_name_req_cp, { + bdaddr, + pscan_rep_mode, + pscan_mode, + clock_offset +}). +-define(remote_name_req_cp_bin(Bdaddr,Pscan_rep_mode,Pscan_mode,Clock_offset),Bdaddr:6/unit:8-binary,Pscan_rep_mode:1/unsigned-unit:8,Pscan_mode:1/unsigned-unit:8,Clock_offset:1/little-unsigned-unit:16). +-define(REMOTE_NAME_REQ_CP_SIZE, 10). +-define(OCF_REMOTE_NAME_REQ_CANCEL, 26). +-record(remote_name_req_cancel_cp, { + bdaddr +}). +-define(remote_name_req_cancel_cp_bin(Bdaddr),Bdaddr:6/unit:8-binary). +-define(REMOTE_NAME_REQ_CANCEL_CP_SIZE, 6). +-define(OCF_READ_REMOTE_FEATURES, 27). +-record(read_remote_features_cp, { + handle +}). +-define(read_remote_features_cp_bin(Handle),Handle:1/little-unsigned-unit:16). +-define(READ_REMOTE_FEATURES_CP_SIZE, 2). +-define(OCF_READ_REMOTE_EXT_FEATURES, 28). +-record(read_remote_ext_features_cp, { + handle, + page_num +}). +-define(read_remote_ext_features_cp_bin(Handle,Page_num),Handle:1/little-unsigned-unit:16,Page_num:1/unsigned-unit:8). +-define(READ_REMOTE_EXT_FEATURES_CP_SIZE, 3). +-define(OCF_READ_REMOTE_VERSION, 29). +-record(read_remote_version_cp, { + handle +}). +-define(read_remote_version_cp_bin(Handle),Handle:1/little-unsigned-unit:16). +-define(READ_REMOTE_VERSION_CP_SIZE, 2). +-define(OCF_READ_CLOCK_OFFSET, 31). +-record(read_clock_offset_cp, { + handle +}). +-define(read_clock_offset_cp_bin(Handle),Handle:1/little-unsigned-unit:16). +-define(READ_CLOCK_OFFSET_CP_SIZE, 2). +-define(OCF_READ_LMP_HANDLE, 32). +-define(OCF_SETUP_SYNC_CONN, 40). +-record(setup_sync_conn_cp, { + handle, + tx_bandwith, + rx_bandwith, + max_latency, + voice_setting, + retrans_effort, + pkt_type +}). +-define(setup_sync_conn_cp_bin(Handle,Tx_bandwith,Rx_bandwith,Max_latency,Voice_setting,Retrans_effort,Pkt_type),Handle:1/little-unsigned-unit:16,Tx_bandwith:1/little-unsigned-unit:32,Rx_bandwith:1/little-unsigned-unit:32,Max_latency:1/little-unsigned-unit:16,Voice_setting:1/little-unsigned-unit:16,Retrans_effort:1/unsigned-unit:8,Pkt_type:1/little-unsigned-unit:16). +-define(SETUP_SYNC_CONN_CP_SIZE, 17). +-define(OCF_ACCEPT_SYNC_CONN_REQ, 41). +-record(accept_sync_conn_req_cp, { + bdaddr, + tx_bandwith, + rx_bandwith, + max_latency, + voice_setting, + retrans_effort, + pkt_type +}). +-define(accept_sync_conn_req_cp_bin(Bdaddr,Tx_bandwith,Rx_bandwith,Max_latency,Voice_setting,Retrans_effort,Pkt_type),Bdaddr:6/unit:8-binary,Tx_bandwith:1/little-unsigned-unit:32,Rx_bandwith:1/little-unsigned-unit:32,Max_latency:1/little-unsigned-unit:16,Voice_setting:1/little-unsigned-unit:16,Retrans_effort:1/unsigned-unit:8,Pkt_type:1/little-unsigned-unit:16). +-define(ACCEPT_SYNC_CONN_REQ_CP_SIZE, 21). +-define(OCF_REJECT_SYNC_CONN_REQ, 42). +-record(reject_sync_conn_req_cp, { + bdaddr, + reason +}). +-define(reject_sync_conn_req_cp_bin(Bdaddr,Reason),Bdaddr:6/unit:8-binary,Reason:1/unsigned-unit:8). +-define(REJECT_SYNC_CONN_REQ_CP_SIZE, 7). +-define(OCF_IO_CAPABILITY_REPLY, 43). +-record(io_capability_reply_cp, { + bdaddr, + capability, + oob_data, + authentication +}). +-define(io_capability_reply_cp_bin(Bdaddr,Capability,Oob_data,Authentication),Bdaddr:6/unit:8-binary,Capability:1/unsigned-unit:8,Oob_data:1/unsigned-unit:8,Authentication:1/unsigned-unit:8). +-define(IO_CAPABILITY_REPLY_CP_SIZE, 9). +-define(OCF_USER_CONFIRM_REPLY, 44). +-record(user_confirm_reply_cp, { + bdaddr +}). +-define(user_confirm_reply_cp_bin(Bdaddr),Bdaddr:6/unit:8-binary). +-define(USER_CONFIRM_REPLY_CP_SIZE, 6). +-define(OCF_USER_CONFIRM_NEG_REPLY, 45). +-define(OCF_USER_PASSKEY_REPLY, 46). +-record(user_passkey_reply_cp, { + bdaddr, + passkey +}). +-define(user_passkey_reply_cp_bin(Bdaddr,Passkey),Bdaddr:6/unit:8-binary,Passkey:1/little-unsigned-unit:32). +-define(USER_PASSKEY_REPLY_CP_SIZE, 10). +-define(OCF_USER_PASSKEY_NEG_REPLY, 47). +-define(OCF_REMOTE_OOB_DATA_REPLY, 48). +-record(remote_oob_data_reply_cp, { + bdaddr, + hash, + randomizer +}). +-define(remote_oob_data_reply_cp_bin(Bdaddr,Hash,Randomizer),Bdaddr:6/unit:8-binary,Hash:16/unit:8-binary,Randomizer:16/unit:8-binary). +-define(REMOTE_OOB_DATA_REPLY_CP_SIZE, 38). +-define(OCF_REMOTE_OOB_DATA_NEG_REPLY, 51). +-define(OCF_IO_CAPABILITY_NEG_REPLY, 52). +-record(io_capability_neg_reply_cp, { + bdaddr, + reason +}). +-define(io_capability_neg_reply_cp_bin(Bdaddr,Reason),Bdaddr:6/unit:8-binary,Reason:1/unsigned-unit:8). +-define(IO_CAPABILITY_NEG_REPLY_CP_SIZE, 7). +-define(OCF_CREATE_PHYSICAL_LINK, 53). +-record(create_physical_link_cp, { + handle, + key_length, + key_type, + key +}). +-define(create_physical_link_cp_bin(Handle,Key_length,Key_type,Key),Handle:1/unsigned-unit:8,Key_length:1/unsigned-unit:8,Key_type:1/unsigned-unit:8,Key:32/unit:8-binary). +-define(CREATE_PHYSICAL_LINK_CP_SIZE, 35). +-define(OCF_ACCEPT_PHYSICAL_LINK, 54). +-define(OCF_DISCONNECT_PHYSICAL_LINK, 55). +-record(disconnect_physical_link_cp, { + handle, + reason +}). +-define(disconnect_physical_link_cp_bin(Handle,Reason),Handle:1/unsigned-unit:8,Reason:1/unsigned-unit:8). +-define(DISCONNECT_PHYSICAL_LINK_CP_SIZE, 2). +-define(OCF_CREATE_LOGICAL_LINK, 56). +-record(create_logical_link_cp, { + handle, + tx_flow, + rx_flow +}). +-define(create_logical_link_cp_bin(Handle,Tx_flow,Rx_flow),Handle:1/unsigned-unit:8,Tx_flow:16/unit:8-binary,Rx_flow:16/unit:8-binary). +-define(CREATE_LOGICAL_LINK_CP_SIZE, 33). +-define(OCF_ACCEPT_LOGICAL_LINK, 57). +-define(OCF_DISCONNECT_LOGICAL_LINK, 58). +-record(disconnect_logical_link_cp, { + handle +}). +-define(disconnect_logical_link_cp_bin(Handle),Handle:1/little-unsigned-unit:16). +-define(DISCONNECT_LOGICAL_LINK_CP_SIZE, 2). +-define(OCF_LOGICAL_LINK_CANCEL, 59). +-record(cancel_logical_link_cp, { + handle, + tx_flow_id +}). +-define(cancel_logical_link_cp_bin(Handle,Tx_flow_id),Handle:1/unsigned-unit:8,Tx_flow_id:1/unsigned-unit:8). +-define(LOGICAL_LINK_CANCEL_CP_SIZE, 2). +-record(cancel_logical_link_rp, { + status, + handle, + tx_flow_id +}). +-define(cancel_logical_link_rp_bin(Status,Handle,Tx_flow_id),Status:1/unsigned-unit:8,Handle:1/unsigned-unit:8,Tx_flow_id:1/unsigned-unit:8). +-define(LOGICAL_LINK_CANCEL_RP_SIZE, 3). +-define(OCF_FLOW_SPEC_MODIFY, 60). +-define(OGF_LINK_POLICY, 2). +-define(OCF_HOLD_MODE, 1). +-record(hold_mode_cp, { + handle, + max_interval, + min_interval +}). +-define(hold_mode_cp_bin(Handle,Max_interval,Min_interval),Handle:1/little-unsigned-unit:16,Max_interval:1/little-unsigned-unit:16,Min_interval:1/little-unsigned-unit:16). +-define(HOLD_MODE_CP_SIZE, 6). +-define(OCF_SNIFF_MODE, 3). +-record(sniff_mode_cp, { + handle, + max_interval, + min_interval, + attempt, + timeout +}). +-define(sniff_mode_cp_bin(Handle,Max_interval,Min_interval,Attempt,Timeout),Handle:1/little-unsigned-unit:16,Max_interval:1/little-unsigned-unit:16,Min_interval:1/little-unsigned-unit:16,Attempt:1/little-unsigned-unit:16,Timeout:1/little-unsigned-unit:16). +-define(SNIFF_MODE_CP_SIZE, 10). +-define(OCF_EXIT_SNIFF_MODE, 4). +-record(exit_sniff_mode_cp, { + handle +}). +-define(exit_sniff_mode_cp_bin(Handle),Handle:1/little-unsigned-unit:16). +-define(EXIT_SNIFF_MODE_CP_SIZE, 2). +-define(OCF_PARK_MODE, 5). +-record(park_mode_cp, { + handle, + max_interval, + min_interval +}). +-define(park_mode_cp_bin(Handle,Max_interval,Min_interval),Handle:1/little-unsigned-unit:16,Max_interval:1/little-unsigned-unit:16,Min_interval:1/little-unsigned-unit:16). +-define(PARK_MODE_CP_SIZE, 6). +-define(OCF_EXIT_PARK_MODE, 6). +-record(exit_park_mode_cp, { + handle +}). +-define(exit_park_mode_cp_bin(Handle),Handle:1/little-unsigned-unit:16). +-define(EXIT_PARK_MODE_CP_SIZE, 2). +-define(OCF_QOS_SETUP, 7). +-record(hci_qos, { + service_type, + token_rate, + peak_bandwidth, + latency, + delay_variation +}). +-define(hci_qos_bin(Service_type,Token_rate,Peak_bandwidth,Latency,Delay_variation),Service_type:1/unsigned-unit:8,Token_rate:1/little-unsigned-unit:32,Peak_bandwidth:1/little-unsigned-unit:32,Latency:1/little-unsigned-unit:32,Delay_variation:1/little-unsigned-unit:32). +-define(HCI_QOS_CP_SIZE, 17). +-record(qos_setup_cp, { + handle, + flags, + qos +}). +-define(qos_setup_cp_bin(Handle,Flags,Qos),Handle:1/little-unsigned-unit:16,Flags:1/unsigned-unit:8,Qos:17/unit:8-binary). +-define(QOS_SETUP_CP_SIZE, (3 + ?HCI_QOS_CP_SIZE)). +-define(OCF_ROLE_DISCOVERY, 9). +-record(role_discovery_cp, { + handle +}). +-define(role_discovery_cp_bin(Handle),Handle:1/little-unsigned-unit:16). +-define(ROLE_DISCOVERY_CP_SIZE, 2). +-record(role_discovery_rp, { + status, + handle, + role +}). +-define(role_discovery_rp_bin(Status,Handle,Role),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Role:1/unsigned-unit:8). +-define(ROLE_DISCOVERY_RP_SIZE, 4). +-define(OCF_SWITCH_ROLE, 11). +-record(switch_role_cp, { + bdaddr, + role +}). +-define(switch_role_cp_bin(Bdaddr,Role),Bdaddr:6/unit:8-binary,Role:1/unsigned-unit:8). +-define(SWITCH_ROLE_CP_SIZE, 7). +-define(OCF_READ_LINK_POLICY, 12). +-record(read_link_policy_cp, { + handle +}). +-define(read_link_policy_cp_bin(Handle),Handle:1/little-unsigned-unit:16). +-define(READ_LINK_POLICY_CP_SIZE, 2). +-record(read_link_policy_rp, { + status, + handle, + policy +}). +-define(read_link_policy_rp_bin(Status,Handle,Policy),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Policy:1/little-unsigned-unit:16). +-define(READ_LINK_POLICY_RP_SIZE, 5). +-define(OCF_WRITE_LINK_POLICY, 13). +-record(write_link_policy_cp, { + handle, + policy +}). +-define(write_link_policy_cp_bin(Handle,Policy),Handle:1/little-unsigned-unit:16,Policy:1/little-unsigned-unit:16). +-define(WRITE_LINK_POLICY_CP_SIZE, 4). +-record(write_link_policy_rp, { + status, + handle +}). +-define(write_link_policy_rp_bin(Status,Handle),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16). +-define(WRITE_LINK_POLICY_RP_SIZE, 3). +-define(OCF_READ_DEFAULT_LINK_POLICY, 14). +-define(OCF_WRITE_DEFAULT_LINK_POLICY, 15). +-define(OCF_FLOW_SPECIFICATION, 16). +-define(OCF_SNIFF_SUBRATING, 17). +-record(sniff_subrating_cp, { + handle, + max_latency, + min_remote_timeout, + min_local_timeout +}). +-define(sniff_subrating_cp_bin(Handle,Max_latency,Min_remote_timeout,Min_local_timeout),Handle:1/little-unsigned-unit:16,Max_latency:1/little-unsigned-unit:16,Min_remote_timeout:1/little-unsigned-unit:16,Min_local_timeout:1/little-unsigned-unit:16). +-define(SNIFF_SUBRATING_CP_SIZE, 8). +-define(OGF_HOST_CTL, 3). +-define(OCF_SET_EVENT_MASK, 1). +-record(set_event_mask_cp, { + mask +}). +-define(set_event_mask_cp_bin(Mask),Mask:8/unit:8-binary). +-define(SET_EVENT_MASK_CP_SIZE, 8). +-define(OCF_RESET, 3). +-define(OCF_SET_EVENT_FLT, 5). +-record(set_event_flt_cp, { + flt_type, + cond_type, + condition +}). +-define(set_event_flt_cp_bin(Flt_type,Cond_type,Condition),Flt_type:1/unsigned-unit:8,Cond_type:1/unsigned-unit:8,Condition:0/unit:8-binary). +-define(SET_EVENT_FLT_CP_SIZE, 2). +-define(FLT_CLEAR_ALL, 0). +-define(FLT_INQ_RESULT, 1). +-define(FLT_CONN_SETUP, 2). +-define(INQ_RESULT_RETURN_ALL, 0). +-define(INQ_RESULT_RETURN_CLASS, 1). +-define(INQ_RESULT_RETURN_BDADDR, 2). +-define(CONN_SETUP_ALLOW_ALL, 0). +-define(CONN_SETUP_ALLOW_CLASS, 1). +-define(CONN_SETUP_ALLOW_BDADDR, 2). +-define(CONN_SETUP_AUTO_OFF, 1). +-define(CONN_SETUP_AUTO_ON, 2). +-define(OCF_FLUSH, 8). +-define(OCF_READ_PIN_TYPE, 9). +-record(read_pin_type_rp, { + status, + pin_type +}). +-define(read_pin_type_rp_bin(Status,Pin_type),Status:1/unsigned-unit:8,Pin_type:1/unsigned-unit:8). +-define(READ_PIN_TYPE_RP_SIZE, 2). +-define(OCF_WRITE_PIN_TYPE, 10). +-record(write_pin_type_cp, { + pin_type +}). +-define(write_pin_type_cp_bin(Pin_type),Pin_type:1/unsigned-unit:8). +-define(WRITE_PIN_TYPE_CP_SIZE, 1). +-define(OCF_CREATE_NEW_UNIT_KEY, 11). +-define(OCF_READ_STORED_LINK_KEY, 13). +-record(read_stored_link_key_cp, { + bdaddr, + read_all +}). +-define(read_stored_link_key_cp_bin(Bdaddr,Read_all),Bdaddr:6/unit:8-binary,Read_all:1/unsigned-unit:8). +-define(READ_STORED_LINK_KEY_CP_SIZE, 7). +-record(read_stored_link_key_rp, { + status, + max_keys, + num_keys +}). +-define(read_stored_link_key_rp_bin(Status,Max_keys,Num_keys),Status:1/unsigned-unit:8,Max_keys:1/little-unsigned-unit:16,Num_keys:1/little-unsigned-unit:16). +-define(READ_STORED_LINK_KEY_RP_SIZE, 5). +-define(OCF_WRITE_STORED_LINK_KEY, 17). +-record(write_stored_link_key_cp, { + num_keys +}). +-define(write_stored_link_key_cp_bin(Num_keys),Num_keys:1/unsigned-unit:8). +-define(WRITE_STORED_LINK_KEY_CP_SIZE, 1). +-record(write_stored_link_key_rp, { + status, + num_keys +}). +-define(write_stored_link_key_rp_bin(Status,Num_keys),Status:1/unsigned-unit:8,Num_keys:1/unsigned-unit:8). +-define(READ_WRITE_LINK_KEY_RP_SIZE, 2). +-define(OCF_DELETE_STORED_LINK_KEY, 18). +-record(delete_stored_link_key_cp, { + bdaddr, + delete_all +}). +-define(delete_stored_link_key_cp_bin(Bdaddr,Delete_all),Bdaddr:6/unit:8-binary,Delete_all:1/unsigned-unit:8). +-define(DELETE_STORED_LINK_KEY_CP_SIZE, 7). +-record(delete_stored_link_key_rp, { + status, + num_keys +}). +-define(delete_stored_link_key_rp_bin(Status,Num_keys),Status:1/unsigned-unit:8,Num_keys:1/little-unsigned-unit:16). +-define(DELETE_STORED_LINK_KEY_RP_SIZE, 3). +-define(HCI_MAX_NAME_LENGTH, 248). +-define(OCF_CHANGE_LOCAL_NAME, 19). +-record(change_local_name_cp, { + name +}). +-define(change_local_name_cp_bin(Name),Name:(?HCI_MAX_NAME_LENGTH)/unit:8-binary). +-define(CHANGE_LOCAL_NAME_CP_SIZE, 248). +-define(OCF_READ_LOCAL_NAME, 20). +-record(read_local_name_rp, { + status, + name +}). +-define(read_local_name_rp_bin(Status,Name),Status:1/unsigned-unit:8,Name:(?HCI_MAX_NAME_LENGTH)/unit:8-binary). +-define(READ_LOCAL_NAME_RP_SIZE, 249). +-define(OCF_READ_CONN_ACCEPT_TIMEOUT, 21). +-record(read_conn_accept_timeout_rp, { + status, + timeout +}). +-define(read_conn_accept_timeout_rp_bin(Status,Timeout),Status:1/unsigned-unit:8,Timeout:1/little-unsigned-unit:16). +-define(READ_CONN_ACCEPT_TIMEOUT_RP_SIZE, 3). +-define(OCF_WRITE_CONN_ACCEPT_TIMEOUT, 22). +-record(write_conn_accept_timeout_cp, { + timeout +}). +-define(write_conn_accept_timeout_cp_bin(Timeout),Timeout:1/little-unsigned-unit:16). +-define(WRITE_CONN_ACCEPT_TIMEOUT_CP_SIZE, 2). +-define(OCF_READ_PAGE_TIMEOUT, 23). +-record(read_page_timeout_rp, { + status, + timeout +}). +-define(read_page_timeout_rp_bin(Status,Timeout),Status:1/unsigned-unit:8,Timeout:1/little-unsigned-unit:16). +-define(READ_PAGE_TIMEOUT_RP_SIZE, 3). +-define(OCF_WRITE_PAGE_TIMEOUT, 24). +-record(write_page_timeout_cp, { + timeout +}). +-define(write_page_timeout_cp_bin(Timeout),Timeout:1/little-unsigned-unit:16). +-define(WRITE_PAGE_TIMEOUT_CP_SIZE, 2). +-define(OCF_READ_SCAN_ENABLE, 25). +-record(read_scan_enable_rp, { + status, + enable +}). +-define(read_scan_enable_rp_bin(Status,Enable),Status:1/unsigned-unit:8,Enable:1/unsigned-unit:8). +-define(READ_SCAN_ENABLE_RP_SIZE, 2). +-define(OCF_WRITE_SCAN_ENABLE, 26). +-define(OCF_READ_PAGE_ACTIVITY, 27). +-record(read_page_activity_rp, { + status, + interval, + window +}). +-define(read_page_activity_rp_bin(Status,Interval,Window),Status:1/unsigned-unit:8,Interval:1/little-unsigned-unit:16,Window:1/little-unsigned-unit:16). +-define(READ_PAGE_ACTIVITY_RP_SIZE, 5). +-define(OCF_WRITE_PAGE_ACTIVITY, 28). +-record(write_page_activity_cp, { + interval, + window +}). +-define(write_page_activity_cp_bin(Interval,Window),Interval:1/little-unsigned-unit:16,Window:1/little-unsigned-unit:16). +-define(WRITE_PAGE_ACTIVITY_CP_SIZE, 4). +-define(OCF_READ_INQ_ACTIVITY, 29). +-record(read_inq_activity_rp, { + status, + interval, + window +}). +-define(read_inq_activity_rp_bin(Status,Interval,Window),Status:1/unsigned-unit:8,Interval:1/little-unsigned-unit:16,Window:1/little-unsigned-unit:16). +-define(READ_INQ_ACTIVITY_RP_SIZE, 5). +-define(OCF_WRITE_INQ_ACTIVITY, 30). +-record(write_inq_activity_cp, { + interval, + window +}). +-define(write_inq_activity_cp_bin(Interval,Window),Interval:1/little-unsigned-unit:16,Window:1/little-unsigned-unit:16). +-define(WRITE_INQ_ACTIVITY_CP_SIZE, 4). +-define(OCF_READ_AUTH_ENABLE, 31). +-define(OCF_WRITE_AUTH_ENABLE, 32). +-define(AUTH_DISABLED, 0). +-define(AUTH_ENABLED, 1). +-define(OCF_READ_ENCRYPT_MODE, 33). +-define(OCF_WRITE_ENCRYPT_MODE, 34). +-define(ENCRYPT_DISABLED, 0). +-define(ENCRYPT_P2P, 1). +-define(ENCRYPT_BOTH, 2). +-define(OCF_READ_CLASS_OF_DEV, 35). +-record(read_class_of_dev_rp, { + status, + dev_class +}). +-define(read_class_of_dev_rp_bin(Status,Dev_class),Status:1/unsigned-unit:8,Dev_class:3/unit:8-binary). +-define(READ_CLASS_OF_DEV_RP_SIZE, 4). +-define(OCF_WRITE_CLASS_OF_DEV, 36). +-record(write_class_of_dev_cp, { + dev_class +}). +-define(write_class_of_dev_cp_bin(Dev_class),Dev_class:3/unit:8-binary). +-define(WRITE_CLASS_OF_DEV_CP_SIZE, 3). +-define(OCF_READ_VOICE_SETTING, 37). +-record(read_voice_setting_rp, { + status, + voice_setting +}). +-define(read_voice_setting_rp_bin(Status,Voice_setting),Status:1/unsigned-unit:8,Voice_setting:1/little-unsigned-unit:16). +-define(READ_VOICE_SETTING_RP_SIZE, 3). +-define(OCF_WRITE_VOICE_SETTING, 38). +-record(write_voice_setting_cp, { + voice_setting +}). +-define(write_voice_setting_cp_bin(Voice_setting),Voice_setting:1/little-unsigned-unit:16). +-define(WRITE_VOICE_SETTING_CP_SIZE, 2). +-define(OCF_READ_AUTOMATIC_FLUSH_TIMEOUT, 39). +-define(OCF_WRITE_AUTOMATIC_FLUSH_TIMEOUT, 40). +-define(OCF_READ_NUM_BROADCAST_RETRANS, 41). +-define(OCF_WRITE_NUM_BROADCAST_RETRANS, 42). +-define(OCF_READ_HOLD_MODE_ACTIVITY, 43). +-define(OCF_WRITE_HOLD_MODE_ACTIVITY, 44). +-define(OCF_READ_TRANSMIT_POWER_LEVEL, 45). +-record(read_transmit_power_level_cp, { + handle, + type +}). +-define(read_transmit_power_level_cp_bin(Handle,Type),Handle:1/little-unsigned-unit:16,Type:1/unsigned-unit:8). +-define(READ_TRANSMIT_POWER_LEVEL_CP_SIZE, 3). +-record(read_transmit_power_level_rp, { + status, + handle, + level +}). +-define(read_transmit_power_level_rp_bin(Status,Handle,Level),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Level:1/signed-unit:8). +-define(READ_TRANSMIT_POWER_LEVEL_RP_SIZE, 4). +-define(OCF_READ_SYNC_FLOW_ENABLE, 46). +-define(OCF_WRITE_SYNC_FLOW_ENABLE, 47). +-define(OCF_SET_CONTROLLER_TO_HOST_FC, 49). +-define(OCF_HOST_BUFFER_SIZE, 51). +-record(host_buffer_size_cp, { + acl_mtu, + sco_mtu, + acl_max_pkt, + sco_max_pkt +}). +-define(host_buffer_size_cp_bin(Acl_mtu,Sco_mtu,Acl_max_pkt,Sco_max_pkt),Acl_mtu:1/little-unsigned-unit:16,Sco_mtu:1/unsigned-unit:8,Acl_max_pkt:1/little-unsigned-unit:16,Sco_max_pkt:1/little-unsigned-unit:16). +-define(HOST_BUFFER_SIZE_CP_SIZE, 7). +-define(OCF_HOST_NUM_COMP_PKTS, 53). +-record(host_num_comp_pkts_cp, { + num_hndl +}). +-define(host_num_comp_pkts_cp_bin(Num_hndl),Num_hndl:1/unsigned-unit:8). +-define(HOST_NUM_COMP_PKTS_CP_SIZE, 1). +-define(OCF_READ_LINK_SUPERVISION_TIMEOUT, 54). +-record(read_link_supervision_timeout_rp, { + status, + handle, + timeout +}). +-define(read_link_supervision_timeout_rp_bin(Status,Handle,Timeout),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Timeout:1/little-unsigned-unit:16). +-define(READ_LINK_SUPERVISION_TIMEOUT_RP_SIZE, 5). +-define(OCF_WRITE_LINK_SUPERVISION_TIMEOUT, 55). +-record(write_link_supervision_timeout_cp, { + handle, + timeout +}). +-define(write_link_supervision_timeout_cp_bin(Handle,Timeout),Handle:1/little-unsigned-unit:16,Timeout:1/little-unsigned-unit:16). +-define(WRITE_LINK_SUPERVISION_TIMEOUT_CP_SIZE, 4). +-record(write_link_supervision_timeout_rp, { + status, + handle +}). +-define(write_link_supervision_timeout_rp_bin(Status,Handle),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16). +-define(WRITE_LINK_SUPERVISION_TIMEOUT_RP_SIZE, 3). +-define(OCF_READ_NUM_SUPPORTED_IAC, 56). +-define(MAX_IAC_LAP, 64). +-define(OCF_READ_CURRENT_IAC_LAP, 57). +-record(read_current_iac_lap_rp, { + status, + num_current_iac, + lap +}). +-define(read_current_iac_lap_rp_bin(Status,Num_current_iac,Lap),Status:1/unsigned-unit:8,Num_current_iac:1/unsigned-unit:8,Lap:(3*?MAX_IAC_LAP)/unit:8-binary). +-define(READ_CURRENT_IAC_LAP_RP_SIZE, 2+3*?MAX_IAC_LAP). +-define(OCF_WRITE_CURRENT_IAC_LAP, 58). +-record(write_current_iac_lap_cp, { + num_current_iac, + lap +}). +-define(write_current_iac_lap_cp_bin(Num_current_iac,Lap),Num_current_iac:1/unsigned-unit:8,Lap:(3*?MAX_IAC_LAP)/unit:8-binary). +-define(WRITE_CURRENT_IAC_LAP_CP_SIZE, 1+3*?MAX_IAC_LAP). +-define(OCF_READ_PAGE_SCAN_PERIOD_MODE, 59). +-define(OCF_WRITE_PAGE_SCAN_PERIOD_MODE, 60). +-define(OCF_READ_PAGE_SCAN_MODE, 61). +-define(OCF_WRITE_PAGE_SCAN_MODE, 62). +-define(OCF_SET_AFH_CLASSIFICATION, 63). +-record(set_afh_classification_cp, { + map +}). +-define(set_afh_classification_cp_bin(Map),Map:10/unit:8-binary). +-define(SET_AFH_CLASSIFICATION_CP_SIZE, 10). +-record(set_afh_classification_rp, { + status +}). +-define(set_afh_classification_rp_bin(Status),Status:1/unsigned-unit:8). +-define(SET_AFH_CLASSIFICATION_RP_SIZE, 1). +-define(OCF_READ_INQUIRY_SCAN_TYPE, 66). +-record(read_inquiry_scan_type_rp, { + status, + type +}). +-define(read_inquiry_scan_type_rp_bin(Status,Type),Status:1/unsigned-unit:8,Type:1/unsigned-unit:8). +-define(READ_INQUIRY_SCAN_TYPE_RP_SIZE, 2). +-define(OCF_WRITE_INQUIRY_SCAN_TYPE, 67). +-record(write_inquiry_scan_type_cp, { + type +}). +-define(write_inquiry_scan_type_cp_bin(Type),Type:1/unsigned-unit:8). +-define(WRITE_INQUIRY_SCAN_TYPE_CP_SIZE, 1). +-record(write_inquiry_scan_type_rp, { + status +}). +-define(write_inquiry_scan_type_rp_bin(Status),Status:1/unsigned-unit:8). +-define(WRITE_INQUIRY_SCAN_TYPE_RP_SIZE, 1). +-define(OCF_READ_INQUIRY_MODE, 68). +-record(read_inquiry_mode_rp, { + status, + mode +}). +-define(read_inquiry_mode_rp_bin(Status,Mode),Status:1/unsigned-unit:8,Mode:1/unsigned-unit:8). +-define(READ_INQUIRY_MODE_RP_SIZE, 2). +-define(OCF_WRITE_INQUIRY_MODE, 69). +-record(write_inquiry_mode_cp, { + mode +}). +-define(write_inquiry_mode_cp_bin(Mode),Mode:1/unsigned-unit:8). +-define(WRITE_INQUIRY_MODE_CP_SIZE, 1). +-record(write_inquiry_mode_rp, { + status +}). +-define(write_inquiry_mode_rp_bin(Status),Status:1/unsigned-unit:8). +-define(WRITE_INQUIRY_MODE_RP_SIZE, 1). +-define(OCF_READ_PAGE_SCAN_TYPE, 70). +-define(OCF_WRITE_PAGE_SCAN_TYPE, 71). +-define(PAGE_SCAN_TYPE_STANDARD, 0). +-define(PAGE_SCAN_TYPE_INTERLACED, 1). +-define(OCF_READ_AFH_MODE, 72). +-record(read_afh_mode_rp, { + status, + mode +}). +-define(read_afh_mode_rp_bin(Status,Mode),Status:1/unsigned-unit:8,Mode:1/unsigned-unit:8). +-define(READ_AFH_MODE_RP_SIZE, 2). +-define(OCF_WRITE_AFH_MODE, 73). +-record(write_afh_mode_cp, { + mode +}). +-define(write_afh_mode_cp_bin(Mode),Mode:1/unsigned-unit:8). +-define(WRITE_AFH_MODE_CP_SIZE, 1). +-record(write_afh_mode_rp, { + status +}). +-define(write_afh_mode_rp_bin(Status),Status:1/unsigned-unit:8). +-define(WRITE_AFH_MODE_RP_SIZE, 1). +-define(HCI_MAX_EIR_LENGTH, 240). +-define(OCF_READ_EXT_INQUIRY_RESPONSE, 81). +-record(read_ext_inquiry_response_rp, { + status, + fec, + data +}). +-define(read_ext_inquiry_response_rp_bin(Status,Fec,Data),Status:1/unsigned-unit:8,Fec:1/unsigned-unit:8,Data:(?HCI_MAX_EIR_LENGTH)/unit:8-binary). +-define(READ_EXT_INQUIRY_RESPONSE_RP_SIZE, 242). +-define(OCF_WRITE_EXT_INQUIRY_RESPONSE, 82). +-record(write_ext_inquiry_response_cp, { + fec, + data +}). +-define(write_ext_inquiry_response_cp_bin(Fec,Data),Fec:1/unsigned-unit:8,Data:(?HCI_MAX_EIR_LENGTH)/unit:8-binary). +-define(WRITE_EXT_INQUIRY_RESPONSE_CP_SIZE, 241). +-record(write_ext_inquiry_response_rp, { + status +}). +-define(write_ext_inquiry_response_rp_bin(Status),Status:1/unsigned-unit:8). +-define(WRITE_EXT_INQUIRY_RESPONSE_RP_SIZE, 1). +-define(OCF_REFRESH_ENCRYPTION_KEY, 83). +-record(refresh_encryption_key_cp, { + handle +}). +-define(refresh_encryption_key_cp_bin(Handle),Handle:1/little-unsigned-unit:16). +-define(REFRESH_ENCRYPTION_KEY_CP_SIZE, 2). +-record(refresh_encryption_key_rp, { + status +}). +-define(refresh_encryption_key_rp_bin(Status),Status:1/unsigned-unit:8). +-define(REFRESH_ENCRYPTION_KEY_RP_SIZE, 1). +-define(OCF_READ_SIMPLE_PAIRING_MODE, 85). +-record(read_simple_pairing_mode_rp, { + status, + mode +}). +-define(read_simple_pairing_mode_rp_bin(Status,Mode),Status:1/unsigned-unit:8,Mode:1/unsigned-unit:8). +-define(READ_SIMPLE_PAIRING_MODE_RP_SIZE, 2). +-define(OCF_WRITE_SIMPLE_PAIRING_MODE, 86). +-record(write_simple_pairing_mode_cp, { + mode +}). +-define(write_simple_pairing_mode_cp_bin(Mode),Mode:1/unsigned-unit:8). +-define(WRITE_SIMPLE_PAIRING_MODE_CP_SIZE, 1). +-record(write_simple_pairing_mode_rp, { + status +}). +-define(write_simple_pairing_mode_rp_bin(Status),Status:1/unsigned-unit:8). +-define(WRITE_SIMPLE_PAIRING_MODE_RP_SIZE, 1). +-define(OCF_READ_LOCAL_OOB_DATA, 87). +-record(read_local_oob_data_rp, { + status, + hash, + randomizer +}). +-define(read_local_oob_data_rp_bin(Status,Hash,Randomizer),Status:1/unsigned-unit:8,Hash:16/unit:8-binary,Randomizer:16/unit:8-binary). +-define(READ_LOCAL_OOB_DATA_RP_SIZE, 33). +-define(OCF_READ_INQ_RESPONSE_TX_POWER_LEVEL, 88). +-record(read_inq_response_tx_power_level_rp, { + status, + level +}). +-define(read_inq_response_tx_power_level_rp_bin(Status,Level),Status:1/unsigned-unit:8,Level:1/signed-unit:8). +-define(READ_INQ_RESPONSE_TX_POWER_LEVEL_RP_SIZE, 2). +-define(OCF_READ_INQUIRY_TRANSMIT_POWER_LEVEL, 88). +-record(read_inquiry_transmit_power_level_rp, { + status, + level +}). +-define(read_inquiry_transmit_power_level_rp_bin(Status,Level),Status:1/unsigned-unit:8,Level:1/signed-unit:8). +-define(READ_INQUIRY_TRANSMIT_POWER_LEVEL_RP_SIZE, 2). +-define(OCF_WRITE_INQUIRY_TRANSMIT_POWER_LEVEL, 89). +-record(write_inquiry_transmit_power_level_cp, { + level +}). +-define(write_inquiry_transmit_power_level_cp_bin(Level),Level:1/signed-unit:8). +-define(WRITE_INQUIRY_TRANSMIT_POWER_LEVEL_CP_SIZE, 1). +-record(write_inquiry_transmit_power_level_rp, { + status +}). +-define(write_inquiry_transmit_power_level_rp_bin(Status),Status:1/unsigned-unit:8). +-define(WRITE_INQUIRY_TRANSMIT_POWER_LEVEL_RP_SIZE, 1). +-define(OCF_READ_DEFAULT_ERROR_DATA_REPORTING, 90). +-record(read_default_error_data_reporting_rp, { + status, + reporting +}). +-define(read_default_error_data_reporting_rp_bin(Status,Reporting),Status:1/unsigned-unit:8,Reporting:1/unsigned-unit:8). +-define(READ_DEFAULT_ERROR_DATA_REPORTING_RP_SIZE, 2). +-define(OCF_WRITE_DEFAULT_ERROR_DATA_REPORTING, 91). +-record(write_default_error_data_reporting_cp, { + reporting +}). +-define(write_default_error_data_reporting_cp_bin(Reporting),Reporting:1/unsigned-unit:8). +-define(WRITE_DEFAULT_ERROR_DATA_REPORTING_CP_SIZE, 1). +-record(write_default_error_data_reporting_rp, { + status +}). +-define(write_default_error_data_reporting_rp_bin(Status),Status:1/unsigned-unit:8). +-define(WRITE_DEFAULT_ERROR_DATA_REPORTING_RP_SIZE, 1). +-define(OCF_ENHANCED_FLUSH, 95). +-record(enhanced_flush_cp, { + handle, + type +}). +-define(enhanced_flush_cp_bin(Handle,Type),Handle:1/little-unsigned-unit:16,Type:1/unsigned-unit:8). +-define(ENHANCED_FLUSH_CP_SIZE, 3). +-define(OCF_SEND_KEYPRESS_NOTIFY, 96). +-record(send_keypress_notify_cp, { + bdaddr, + type +}). +-define(send_keypress_notify_cp_bin(Bdaddr,Type),Bdaddr:6/unit:8-binary,Type:1/unsigned-unit:8). +-define(SEND_KEYPRESS_NOTIFY_CP_SIZE, 7). +-record(send_keypress_notify_rp, { + status +}). +-define(send_keypress_notify_rp_bin(Status),Status:1/unsigned-unit:8). +-define(SEND_KEYPRESS_NOTIFY_RP_SIZE, 1). +-define(OCF_READ_LOGICAL_LINK_ACCEPT_TIMEOUT, 97). +-record(read_log_link_accept_timeout_rp, { + status, + timeout +}). +-define(read_log_link_accept_timeout_rp_bin(Status,Timeout),Status:1/unsigned-unit:8,Timeout:1/little-unsigned-unit:16). +-define(READ_LOGICAL_LINK_ACCEPT_TIMEOUT_RP_SIZE, 3). +-define(OCF_WRITE_LOGICAL_LINK_ACCEPT_TIMEOUT, 98). +-record(write_log_link_accept_timeout_cp, { + timeout +}). +-define(write_log_link_accept_timeout_cp_bin(Timeout),Timeout:1/little-unsigned-unit:16). +-define(WRITE_LOGICAL_LINK_ACCEPT_TIMEOUT_CP_SIZE, 2). +-define(OCF_SET_EVENT_MASK_PAGE_2, 99). +-define(OCF_READ_LOCATION_DATA, 100). +-define(OCF_WRITE_LOCATION_DATA, 101). +-define(OCF_READ_FLOW_CONTROL_MODE, 102). +-define(OCF_WRITE_FLOW_CONTROL_MODE, 103). +-define(OCF_READ_ENHANCED_TRANSMIT_POWER_LEVEL, 104). +-record(read_enhanced_transmit_power_level_rp, { + status, + handle, + level_gfsk, + level_dqpsk, + level_8dpsk +}). +-define(read_enhanced_transmit_power_level_rp_bin(Status,Handle,Level_gfsk,Level_dqpsk,Level_8dpsk),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Level_gfsk:1/signed-unit:8,Level_dqpsk:1/signed-unit:8,Level_8dpsk:1/signed-unit:8). +-define(READ_ENHANCED_TRANSMIT_POWER_LEVEL_RP_SIZE, 6). +-define(OCF_READ_BEST_EFFORT_FLUSH_TIMEOUT, 105). +-record(read_best_effort_flush_timeout_rp, { + status, + timeout +}). +-define(read_best_effort_flush_timeout_rp_bin(Status,Timeout),Status:1/unsigned-unit:8,Timeout:1/little-unsigned-unit:32). +-define(READ_BEST_EFFORT_FLUSH_TIMEOUT_RP_SIZE, 5). +-define(OCF_WRITE_BEST_EFFORT_FLUSH_TIMEOUT, 106). +-record(write_best_effort_flush_timeout_cp, { + handle, + timeout +}). +-define(write_best_effort_flush_timeout_cp_bin(Handle,Timeout),Handle:1/little-unsigned-unit:16,Timeout:1/little-unsigned-unit:32). +-define(WRITE_BEST_EFFORT_FLUSH_TIMEOUT_CP_SIZE, 6). +-record(write_best_effort_flush_timeout_rp, { + status +}). +-define(write_best_effort_flush_timeout_rp_bin(Status),Status:1/unsigned-unit:8). +-define(WRITE_BEST_EFFORT_FLUSH_TIMEOUT_RP_SIZE, 1). +-define(OCF_READ_LE_HOST_SUPPORTED, 108). +-record(read_le_host_supported_rp, { + status, + le, + simul +}). +-define(read_le_host_supported_rp_bin(Status,Le,Simul),Status:1/unsigned-unit:8,Le:1/unsigned-unit:8,Simul:1/unsigned-unit:8). +-define(READ_LE_HOST_SUPPORTED_RP_SIZE, 3). +-define(OCF_WRITE_LE_HOST_SUPPORTED, 109). +-record(write_le_host_supported_cp, { + le, + simul +}). +-define(write_le_host_supported_cp_bin(Le,Simul),Le:1/unsigned-unit:8,Simul:1/unsigned-unit:8). +-define(WRITE_LE_HOST_SUPPORTED_CP_SIZE, 2). +-define(OGF_INFO_PARAM, 4). +-define(OCF_READ_LOCAL_VERSION, 1). +-record(read_local_version_rp, { + status, + hci_ver, + hci_rev, + lmp_ver, + manufacturer, + lmp_subver +}). +-define(read_local_version_rp_bin(Status,Hci_ver,Hci_rev,Lmp_ver,Manufacturer,Lmp_subver),Status:1/unsigned-unit:8,Hci_ver:1/unsigned-unit:8,Hci_rev:1/little-unsigned-unit:16,Lmp_ver:1/unsigned-unit:8,Manufacturer:1/little-unsigned-unit:16,Lmp_subver:1/little-unsigned-unit:16). +-define(READ_LOCAL_VERSION_RP_SIZE, 9). +-define(OCF_READ_LOCAL_COMMANDS, 2). +-record(read_local_commands_rp, { + status, + commands +}). +-define(read_local_commands_rp_bin(Status,Commands),Status:1/unsigned-unit:8,Commands:64/unit:8-binary). +-define(READ_LOCAL_COMMANDS_RP_SIZE, 65). +-define(OCF_READ_LOCAL_FEATURES, 3). +-record(read_local_features_rp, { + status, + features +}). +-define(read_local_features_rp_bin(Status,Features),Status:1/unsigned-unit:8,Features:8/unit:8-binary). +-define(READ_LOCAL_FEATURES_RP_SIZE, 9). +-define(OCF_READ_LOCAL_EXT_FEATURES, 4). +-record(read_local_ext_features_cp, { + page_num +}). +-define(read_local_ext_features_cp_bin(Page_num),Page_num:1/unsigned-unit:8). +-define(READ_LOCAL_EXT_FEATURES_CP_SIZE, 1). +-record(read_local_ext_features_rp, { + status, + page_num, + max_page_num, + features +}). +-define(read_local_ext_features_rp_bin(Status,Page_num,Max_page_num,Features),Status:1/unsigned-unit:8,Page_num:1/unsigned-unit:8,Max_page_num:1/unsigned-unit:8,Features:8/unit:8-binary). +-define(READ_LOCAL_EXT_FEATURES_RP_SIZE, 11). +-define(OCF_READ_BUFFER_SIZE, 5). +-record(read_buffer_size_rp, { + status, + acl_mtu, + sco_mtu, + acl_max_pkt, + sco_max_pkt +}). +-define(read_buffer_size_rp_bin(Status,Acl_mtu,Sco_mtu,Acl_max_pkt,Sco_max_pkt),Status:1/unsigned-unit:8,Acl_mtu:1/little-unsigned-unit:16,Sco_mtu:1/unsigned-unit:8,Acl_max_pkt:1/little-unsigned-unit:16,Sco_max_pkt:1/little-unsigned-unit:16). +-define(READ_BUFFER_SIZE_RP_SIZE, 8). +-define(OCF_READ_COUNTRY_CODE, 7). +-define(OCF_READ_BD_ADDR, 9). +-record(read_bd_addr_rp, { + status, + bdaddr +}). +-define(read_bd_addr_rp_bin(Status,Bdaddr),Status:1/unsigned-unit:8,Bdaddr:6/unit:8-binary). +-define(READ_BD_ADDR_RP_SIZE, 7). +-define(OGF_STATUS_PARAM, 5). +-define(OCF_READ_FAILED_CONTACT_COUNTER, 1). +-record(read_failed_contact_counter_rp, { + status, + handle, + counter +}). +-define(read_failed_contact_counter_rp_bin(Status,Handle,Counter),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Counter:1/unsigned-unit:8). +-define(READ_FAILED_CONTACT_COUNTER_RP_SIZE, 4). +-define(OCF_RESET_FAILED_CONTACT_COUNTER, 2). +-record(reset_failed_contact_counter_rp, { + status, + handle +}). +-define(reset_failed_contact_counter_rp_bin(Status,Handle),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16). +-define(RESET_FAILED_CONTACT_COUNTER_RP_SIZE, 4). +-define(OCF_READ_LINK_QUALITY, 3). +-record(read_link_quality_rp, { + status, + handle, + link_quality +}). +-define(read_link_quality_rp_bin(Status,Handle,Link_quality),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Link_quality:1/unsigned-unit:8). +-define(READ_LINK_QUALITY_RP_SIZE, 4). +-define(OCF_READ_RSSI, 5). +-record(read_rssi_rp, { + status, + handle, + rssi +}). +-define(read_rssi_rp_bin(Status,Handle,Rssi),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Rssi:1/signed-unit:8). +-define(READ_RSSI_RP_SIZE, 4). +-define(OCF_READ_AFH_MAP, 6). +-record(read_afh_map_rp, { + status, + handle, + mode, + map +}). +-define(read_afh_map_rp_bin(Status,Handle,Mode,Map),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Mode:1/unsigned-unit:8,Map:10/unit:8-binary). +-define(READ_AFH_MAP_RP_SIZE, 14). +-define(OCF_READ_CLOCK, 7). +-record(read_clock_cp, { + handle, + which_clock +}). +-define(read_clock_cp_bin(Handle,Which_clock),Handle:1/little-unsigned-unit:16,Which_clock:1/unsigned-unit:8). +-define(READ_CLOCK_CP_SIZE, 3). +-record(read_clock_rp, { + status, + handle, + clock, + accuracy +}). +-define(read_clock_rp_bin(Status,Handle,Clock,Accuracy),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Clock:1/little-unsigned-unit:32,Accuracy:1/little-unsigned-unit:16). +-define(READ_CLOCK_RP_SIZE, 9). +-define(OCF_READ_LOCAL_AMP_INFO, 9). +-record(read_local_amp_info_rp, { + status, + amp_status, + total_bandwidth, + max_guaranteed_bandwidth, + min_latency, + max_pdu_size, + controller_type, + pal_caps, + max_amp_assoc_length, + max_flush_timeout, + best_effort_flush_timeout +}). +-define(read_local_amp_info_rp_bin(Status,Amp_status,Total_bandwidth,Max_guaranteed_bandwidth,Min_latency,Max_pdu_size,Controller_type,Pal_caps,Max_amp_assoc_length,Max_flush_timeout,Best_effort_flush_timeout),Status:1/unsigned-unit:8,Amp_status:1/unsigned-unit:8,Total_bandwidth:1/little-unsigned-unit:32,Max_guaranteed_bandwidth:1/little-unsigned-unit:32,Min_latency:1/little-unsigned-unit:32,Max_pdu_size:1/little-unsigned-unit:32,Controller_type:1/unsigned-unit:8,Pal_caps:1/little-unsigned-unit:16,Max_amp_assoc_length:1/little-unsigned-unit:16,Max_flush_timeout:1/little-unsigned-unit:32,Best_effort_flush_timeout:1/little-unsigned-unit:32). +-define(READ_LOCAL_AMP_INFO_RP_SIZE, 31). +-define(OCF_READ_LOCAL_AMP_ASSOC, 10). +-record(read_local_amp_assoc_cp, { + handle, + len_so_far, + max_len +}). +-define(read_local_amp_assoc_cp_bin(Handle,Len_so_far,Max_len),Handle:1/unsigned-unit:8,Len_so_far:1/little-unsigned-unit:16,Max_len:1/little-unsigned-unit:16). +-record(read_local_amp_assoc_rp, { + status, + handle, + rem_len, + frag +}). +-define(read_local_amp_assoc_rp_bin(Status,Handle,Rem_len,Frag),Status:1/unsigned-unit:8,Handle:1/unsigned-unit:8,Rem_len:1/little-unsigned-unit:16,Frag:0/unit:8-binary). +-define(OCF_WRITE_REMOTE_AMP_ASSOC, 11). +-record(write_remote_amp_assoc_cp, { + handle, + length_so_far, + assoc_length, + fragment +}). +-define(write_remote_amp_assoc_cp_bin(Handle,Length_so_far,Assoc_length,Fragment),Handle:1/unsigned-unit:8,Length_so_far:1/little-unsigned-unit:16,Assoc_length:1/little-unsigned-unit:16,Fragment:(?HCI_MAX_NAME_LENGTH)/unit:8-binary). +-define(WRITE_REMOTE_AMP_ASSOC_CP_SIZE, 253). +-record(write_remote_amp_assoc_rp, { + status, + handle +}). +-define(write_remote_amp_assoc_rp_bin(Status,Handle),Status:1/unsigned-unit:8,Handle:1/unsigned-unit:8). +-define(WRITE_REMOTE_AMP_ASSOC_RP_SIZE, 2). +-define(OGF_TESTING_CMD, 62). +-define(OCF_READ_LOOPBACK_MODE, 1). +-define(OCF_WRITE_LOOPBACK_MODE, 2). +-define(OCF_ENABLE_DEVICE_UNDER_TEST_MODE, 3). +-define(OCF_WRITE_SIMPLE_PAIRING_DEBUG_MODE, 4). +-record(write_simple_pairing_debug_mode_cp, { + mode +}). +-define(write_simple_pairing_debug_mode_cp_bin(Mode),Mode:1/unsigned-unit:8). +-define(WRITE_SIMPLE_PAIRING_DEBUG_MODE_CP_SIZE, 1). +-record(write_simple_pairing_debug_mode_rp, { + status +}). +-define(write_simple_pairing_debug_mode_rp_bin(Status),Status:1/unsigned-unit:8). +-define(WRITE_SIMPLE_PAIRING_DEBUG_MODE_RP_SIZE, 1). +-define(OGF_LE_CTL, 8). +-define(OCF_LE_SET_EVENT_MASK, 1). +-record(le_set_event_mask_cp, { + mask +}). +-define(le_set_event_mask_cp_bin(Mask),Mask:8/unit:8-binary). +-define(LE_SET_EVENT_MASK_CP_SIZE, 8). +-define(OCF_LE_READ_BUFFER_SIZE, 2). +-record(le_read_buffer_size_rp, { + status, + pkt_len, + max_pkt +}). +-define(le_read_buffer_size_rp_bin(Status,Pkt_len,Max_pkt),Status:1/unsigned-unit:8,Pkt_len:1/little-unsigned-unit:16,Max_pkt:1/unsigned-unit:8). +-define(LE_READ_BUFFER_SIZE_RP_SIZE, 4). +-define(OCF_LE_READ_LOCAL_SUPPORTED_FEATURES, 3). +-record(le_read_local_supported_features_rp, { + status, + features +}). +-define(le_read_local_supported_features_rp_bin(Status,Features),Status:1/unsigned-unit:8,Features:8/unit:8-binary). +-define(LE_READ_LOCAL_SUPPORTED_FEATURES_RP_SIZE, 9). +-define(OCF_LE_SET_RANDOM_ADDRESS, 5). +-record(le_set_random_address_cp, { + bdaddr +}). +-define(le_set_random_address_cp_bin(Bdaddr),Bdaddr:6/unit:8-binary). +-define(LE_SET_RANDOM_ADDRESS_CP_SIZE, 6). +-define(OCF_LE_SET_ADVERTISING_PARAMETERS, 6). +-record(le_set_advertising_parameters_cp, { + min_interval, + max_interval, + advtype, + own_bdaddr_type, + direct_bdaddr_type, + direct_bdaddr, + chan_map, + filter +}). +-define(le_set_advertising_parameters_cp_bin(Min_interval,Max_interval,Advtype,Own_bdaddr_type,Direct_bdaddr_type,Direct_bdaddr,Chan_map,Filter),Min_interval:1/little-unsigned-unit:16,Max_interval:1/little-unsigned-unit:16,Advtype:1/unsigned-unit:8,Own_bdaddr_type:1/unsigned-unit:8,Direct_bdaddr_type:1/unsigned-unit:8,Direct_bdaddr:6/unit:8-binary,Chan_map:1/unsigned-unit:8,Filter:1/unsigned-unit:8). +-define(LE_SET_ADVERTISING_PARAMETERS_CP_SIZE, 15). +-define(OCF_LE_READ_ADVERTISING_CHANNEL_TX_POWER, 7). +-record(le_read_advertising_channel_tx_power_rp, { + status, + level +}). +-define(le_read_advertising_channel_tx_power_rp_bin(Status,Level),Status:1/unsigned-unit:8,Level:1/unsigned-unit:8). +-define(LE_READ_ADVERTISING_CHANNEL_TX_POWER_RP_SIZE, 2). +-define(OCF_LE_SET_ADVERTISING_DATA, 8). +-record(le_set_advertising_data_cp, { + length, + data +}). +-define(le_set_advertising_data_cp_bin(Length,Data),Length:1/unsigned-unit:8,Data:31/unit:8-binary). +-define(LE_SET_ADVERTISING_DATA_CP_SIZE, 32). +-define(OCF_LE_SET_SCAN_RESPONSE_DATA, 9). +-record(le_set_scan_response_data_cp, { + length, + data +}). +-define(le_set_scan_response_data_cp_bin(Length,Data),Length:1/unsigned-unit:8,Data:31/unit:8-binary). +-define(LE_SET_SCAN_RESPONSE_DATA_CP_SIZE, 32). +-define(OCF_LE_SET_ADVERTISE_ENABLE, 10). +-record(le_set_advertise_enable_cp, { + enable +}). +-define(le_set_advertise_enable_cp_bin(Enable),Enable:1/unsigned-unit:8). +-define(LE_SET_ADVERTISE_ENABLE_CP_SIZE, 1). +-define(OCF_LE_SET_SCAN_PARAMETERS, 11). +-record(le_set_scan_parameters_cp, { + type, + interval, + window, + own_bdaddr_type, + filter +}). +-define(le_set_scan_parameters_cp_bin(Type,Interval,Window,Own_bdaddr_type,Filter),Type:1/unsigned-unit:8,Interval:1/little-unsigned-unit:16,Window:1/little-unsigned-unit:16,Own_bdaddr_type:1/unsigned-unit:8,Filter:1/unsigned-unit:8). +-define(LE_SET_SCAN_PARAMETERS_CP_SIZE, 7). +-define(OCF_LE_SET_SCAN_ENABLE, 12). +-record(le_set_scan_enable_cp, { + enable, + filter_dup +}). +-define(le_set_scan_enable_cp_bin(Enable,Filter_dup),Enable:1/unsigned-unit:8,Filter_dup:1/unsigned-unit:8). +-define(LE_SET_SCAN_ENABLE_CP_SIZE, 2). +-define(OCF_LE_CREATE_CONN, 13). +-record(le_create_connection_cp, { + interval, + window, + initiator_filter, + peer_bdaddr_type, + peer_bdaddr, + own_bdaddr_type, + min_interval, + max_interval, + latency, + supervision_timeout, + min_ce_length, + max_ce_length +}). +-define(le_create_connection_cp_bin(Interval,Window,Initiator_filter,Peer_bdaddr_type,Peer_bdaddr,Own_bdaddr_type,Min_interval,Max_interval,Latency,Supervision_timeout,Min_ce_length,Max_ce_length),Interval:1/little-unsigned-unit:16,Window:1/little-unsigned-unit:16,Initiator_filter:1/unsigned-unit:8,Peer_bdaddr_type:1/unsigned-unit:8,Peer_bdaddr:6/unit:8-binary,Own_bdaddr_type:1/unsigned-unit:8,Min_interval:1/little-unsigned-unit:16,Max_interval:1/little-unsigned-unit:16,Latency:1/little-unsigned-unit:16,Supervision_timeout:1/little-unsigned-unit:16,Min_ce_length:1/little-unsigned-unit:16,Max_ce_length:1/little-unsigned-unit:16). +-define(LE_CREATE_CONN_CP_SIZE, 25). +-define(OCF_LE_CREATE_CONN_CANCEL, 14). +-define(OCF_LE_READ_WHITE_LIST_SIZE, 15). +-record(le_read_white_list_size_rp, { + status, + size +}). +-define(le_read_white_list_size_rp_bin(Status,Size),Status:1/unsigned-unit:8,Size:1/unsigned-unit:8). +-define(LE_READ_WHITE_LIST_SIZE_RP_SIZE, 2). +-define(OCF_LE_CLEAR_WHITE_LIST, 16). +-define(OCF_LE_ADD_DEVICE_TO_WHITE_LIST, 17). +-record(le_add_device_to_white_list_cp, { + bdaddr_type, + bdaddr +}). +-define(le_add_device_to_white_list_cp_bin(Bdaddr_type,Bdaddr),Bdaddr_type:1/unsigned-unit:8,Bdaddr:6/unit:8-binary). +-define(LE_ADD_DEVICE_TO_WHITE_LIST_CP_SIZE, 7). +-define(OCF_LE_REMOVE_DEVICE_FROM_WHITE_LIST, 18). +-record(le_remove_device_from_white_list_cp, { + bdaddr_type, + bdaddr +}). +-define(le_remove_device_from_white_list_cp_bin(Bdaddr_type,Bdaddr),Bdaddr_type:1/unsigned-unit:8,Bdaddr:6/unit:8-binary). +-define(LE_REMOVE_DEVICE_FROM_WHITE_LIST_CP_SIZE, 7). +-define(OCF_LE_CONN_UPDATE, 19). +-record(le_connection_update_cp, { + handle, + min_interval, + max_interval, + latency, + supervision_timeout, + min_ce_length, + max_ce_length +}). +-define(le_connection_update_cp_bin(Handle,Min_interval,Max_interval,Latency,Supervision_timeout,Min_ce_length,Max_ce_length),Handle:1/little-unsigned-unit:16,Min_interval:1/little-unsigned-unit:16,Max_interval:1/little-unsigned-unit:16,Latency:1/little-unsigned-unit:16,Supervision_timeout:1/little-unsigned-unit:16,Min_ce_length:1/little-unsigned-unit:16,Max_ce_length:1/little-unsigned-unit:16). +-define(LE_CONN_UPDATE_CP_SIZE, 14). +-define(OCF_LE_SET_HOST_CHANNEL_CLASSIFICATION, 20). +-record(le_set_host_channel_classification_cp, { + map +}). +-define(le_set_host_channel_classification_cp_bin(Map),Map:5/unit:8-binary). +-define(LE_SET_HOST_CHANNEL_CLASSIFICATION_CP_SIZE, 5). +-define(OCF_LE_READ_CHANNEL_MAP, 21). +-record(le_read_channel_map_cp, { + handle +}). +-define(le_read_channel_map_cp_bin(Handle),Handle:1/little-unsigned-unit:16). +-define(LE_READ_CHANNEL_MAP_CP_SIZE, 2). +-record(le_read_channel_map_rp, { + status, + handle, + map +}). +-define(le_read_channel_map_rp_bin(Status,Handle,Map),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Map:5/unit:8-binary). +-define(LE_READ_CHANNEL_MAP_RP_SIZE, 8). +-define(OCF_LE_READ_REMOTE_USED_FEATURES, 22). +-record(le_read_remote_used_features_cp, { + handle +}). +-define(le_read_remote_used_features_cp_bin(Handle),Handle:1/little-unsigned-unit:16). +-define(LE_READ_REMOTE_USED_FEATURES_CP_SIZE, 2). +-define(OCF_LE_ENCRYPT, 23). +-record(le_encrypt_cp, { + key, + plaintext +}). +-define(le_encrypt_cp_bin(Key,Plaintext),Key:16/unit:8-binary,Plaintext:16/unit:8-binary). +-define(LE_ENCRYPT_CP_SIZE, 32). +-record(le_encrypt_rp, { + status, + data +}). +-define(le_encrypt_rp_bin(Status,Data),Status:1/unsigned-unit:8,Data:16/unit:8-binary). +-define(LE_ENCRYPT_RP_SIZE, 17). +-define(OCF_LE_RAND, 24). +-record(le_rand_rp, { + status, + random +}). +-define(le_rand_rp_bin(Status,Random),Status:1/unsigned-unit:8,Random:1/little-unsigned-unit:64). +-define(LE_RAND_RP_SIZE, 9). +-define(OCF_LE_START_ENCRYPTION, 25). +-record(le_start_encryption_cp, { + handle, + random, + diversifier, + key +}). +-define(le_start_encryption_cp_bin(Handle,Random,Diversifier,Key),Handle:1/little-unsigned-unit:16,Random:1/little-unsigned-unit:64,Diversifier:1/little-unsigned-unit:16,Key:16/unit:8-binary). +-define(LE_START_ENCRYPTION_CP_SIZE, 28). +-define(OCF_LE_LTK_REPLY, 26). +-record(le_ltk_reply_cp, { + handle, + key +}). +-define(le_ltk_reply_cp_bin(Handle,Key),Handle:1/little-unsigned-unit:16,Key:16/unit:8-binary). +-define(LE_LTK_REPLY_CP_SIZE, 18). +-record(le_ltk_reply_rp, { + status, + handle +}). +-define(le_ltk_reply_rp_bin(Status,Handle),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16). +-define(LE_LTK_REPLY_RP_SIZE, 3). +-define(OCF_LE_LTK_NEG_REPLY, 27). +-record(le_ltk_neg_reply_cp, { + handle +}). +-define(le_ltk_neg_reply_cp_bin(Handle),Handle:1/little-unsigned-unit:16). +-define(LE_LTK_NEG_REPLY_CP_SIZE, 2). +-record(le_ltk_neg_reply_rp, { + status, + handle +}). +-define(le_ltk_neg_reply_rp_bin(Status,Handle),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16). +-define(LE_LTK_NEG_REPLY_RP_SIZE, 3). +-define(OCF_LE_READ_SUPPORTED_STATES, 28). +-record(le_read_supported_states_rp, { + status, + states +}). +-define(le_read_supported_states_rp_bin(Status,States),Status:1/unsigned-unit:8,States:1/little-unsigned-unit:64). +-define(LE_READ_SUPPORTED_STATES_RP_SIZE, 9). +-define(OCF_LE_RECEIVER_TEST, 29). +-record(le_receiver_test_cp, { + frequency +}). +-define(le_receiver_test_cp_bin(Frequency),Frequency:1/unsigned-unit:8). +-define(LE_RECEIVER_TEST_CP_SIZE, 1). +-define(OCF_LE_TRANSMITTER_TEST, 30). +-record(le_transmitter_test_cp, { + frequency, + length, + payload +}). +-define(le_transmitter_test_cp_bin(Frequency,Length,Payload),Frequency:1/unsigned-unit:8,Length:1/unsigned-unit:8,Payload:1/unsigned-unit:8). +-define(LE_TRANSMITTER_TEST_CP_SIZE, 3). +-define(OCF_LE_TEST_END, 31). +-record(le_test_end_rp, { + status, + num_pkts +}). +-define(le_test_end_rp_bin(Status,Num_pkts),Status:1/unsigned-unit:8,Num_pkts:1/little-unsigned-unit:16). +-define(LE_TEST_END_RP_SIZE, 3). +-define(OGF_VENDOR_CMD, 63). +-define(EVT_INQUIRY_COMPLETE, 1). +-define(EVT_INQUIRY_RESULT, 2). +-record(inquiry_info, { + bdaddr, + pscan_rep_mode, + pscan_period_mode, + pscan_mode, + dev_class, + clock_offset +}). +-define(inquiry_info_bin(Bdaddr,Pscan_rep_mode,Pscan_period_mode,Pscan_mode,Dev_class,Clock_offset),Bdaddr:6/unit:8-binary,Pscan_rep_mode:1/unsigned-unit:8,Pscan_period_mode:1/unsigned-unit:8,Pscan_mode:1/unsigned-unit:8,Dev_class:3/unit:8-binary,Clock_offset:1/little-unsigned-unit:16). +-define(INQUIRY_INFO_SIZE, 14). +-define(EVT_CONN_COMPLETE, 3). +-record(evt_conn_complete, { + status, + handle, + bdaddr, + link_type, + encr_mode +}). +-define(evt_conn_complete_bin(Status,Handle,Bdaddr,Link_type,Encr_mode),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Bdaddr:6/unit:8-binary,Link_type:1/unsigned-unit:8,Encr_mode:1/unsigned-unit:8). +-define(EVT_CONN_COMPLETE_SIZE, 13). +-define(EVT_CONN_REQUEST, 4). +-record(evt_conn_request, { + bdaddr, + dev_class, + link_type +}). +-define(evt_conn_request_bin(Bdaddr,Dev_class,Link_type),Bdaddr:6/unit:8-binary,Dev_class:3/unit:8-binary,Link_type:1/unsigned-unit:8). +-define(EVT_CONN_REQUEST_SIZE, 10). +-define(EVT_DISCONN_COMPLETE, 5). +-record(evt_disconn_complete, { + status, + handle, + reason +}). +-define(evt_disconn_complete_bin(Status,Handle,Reason),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Reason:1/unsigned-unit:8). +-define(EVT_DISCONN_COMPLETE_SIZE, 4). +-define(EVT_AUTH_COMPLETE, 6). +-record(evt_auth_complete, { + status, + handle +}). +-define(evt_auth_complete_bin(Status,Handle),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16). +-define(EVT_AUTH_COMPLETE_SIZE, 3). +-define(EVT_REMOTE_NAME_REQ_COMPLETE, 7). +-record(evt_remote_name_req_complete, { + status, + bdaddr, + name +}). +-define(evt_remote_name_req_complete_bin(Status,Bdaddr,Name),Status:1/unsigned-unit:8,Bdaddr:6/unit:8-binary,Name:(?HCI_MAX_NAME_LENGTH)/unit:8-binary). +-define(EVT_REMOTE_NAME_REQ_COMPLETE_SIZE, 255). +-define(EVT_ENCRYPT_CHANGE, 8). +-record(evt_encrypt_change, { + status, + handle, + encrypt +}). +-define(evt_encrypt_change_bin(Status,Handle,Encrypt),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Encrypt:1/unsigned-unit:8). +-define(EVT_ENCRYPT_CHANGE_SIZE, 5). +-define(EVT_CHANGE_CONN_LINK_KEY_COMPLETE, 9). +-record(evt_change_conn_link_key_complete, { + status, + handle +}). +-define(evt_change_conn_link_key_complete_bin(Status,Handle),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16). +-define(EVT_CHANGE_CONN_LINK_KEY_COMPLETE_SIZE, 3). +-define(EVT_MASTER_LINK_KEY_COMPLETE, 10). +-record(evt_master_link_key_complete, { + status, + handle, + key_flag +}). +-define(evt_master_link_key_complete_bin(Status,Handle,Key_flag),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Key_flag:1/unsigned-unit:8). +-define(EVT_MASTER_LINK_KEY_COMPLETE_SIZE, 4). +-define(EVT_READ_REMOTE_FEATURES_COMPLETE, 11). +-record(evt_read_remote_features_complete, { + status, + handle, + features +}). +-define(evt_read_remote_features_complete_bin(Status,Handle,Features),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Features:8/unit:8-binary). +-define(EVT_READ_REMOTE_FEATURES_COMPLETE_SIZE, 11). +-define(EVT_READ_REMOTE_VERSION_COMPLETE, 12). +-record(evt_read_remote_version_complete, { + status, + handle, + lmp_ver, + manufacturer, + lmp_subver +}). +-define(evt_read_remote_version_complete_bin(Status,Handle,Lmp_ver,Manufacturer,Lmp_subver),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Lmp_ver:1/unsigned-unit:8,Manufacturer:1/little-unsigned-unit:16,Lmp_subver:1/little-unsigned-unit:16). +-define(EVT_READ_REMOTE_VERSION_COMPLETE_SIZE, 8). +-define(EVT_QOS_SETUP_COMPLETE, 13). +-record(evt_qos_setup_complete, { + status, + handle, + flags, + qos +}). +-define(evt_qos_setup_complete_bin(Status,Handle,Flags,Qos),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Flags:1/unsigned-unit:8,Qos:17/unit:8-binary). +-define(EVT_QOS_SETUP_COMPLETE_SIZE, (4 + ?HCI_QOS_CP_SIZE)). +-define(EVT_CMD_COMPLETE, 14). +-record(evt_cmd_complete, { + ncmd, + opcode +}). +-define(evt_cmd_complete_bin(Ncmd,Opcode),Ncmd:1/unsigned-unit:8,Opcode:1/little-unsigned-unit:16). +-define(EVT_CMD_COMPLETE_SIZE, 3). +-define(EVT_CMD_STATUS, 15). +-record(evt_cmd_status, { + status, + ncmd, + opcode +}). +-define(evt_cmd_status_bin(Status,Ncmd,Opcode),Status:1/unsigned-unit:8,Ncmd:1/unsigned-unit:8,Opcode:1/little-unsigned-unit:16). +-define(EVT_CMD_STATUS_SIZE, 4). +-define(EVT_HARDWARE_ERROR, 16). +-record(evt_hardware_error, { + code +}). +-define(evt_hardware_error_bin(Code),Code:1/unsigned-unit:8). +-define(EVT_HARDWARE_ERROR_SIZE, 1). +-define(EVT_FLUSH_OCCURRED, 17). +-record(evt_flush_occured, { + handle +}). +-define(evt_flush_occured_bin(Handle),Handle:1/little-unsigned-unit:16). +-define(EVT_FLUSH_OCCURRED_SIZE, 2). +-define(EVT_ROLE_CHANGE, 18). +-record(evt_role_change, { + status, + bdaddr, + role +}). +-define(evt_role_change_bin(Status,Bdaddr,Role),Status:1/unsigned-unit:8,Bdaddr:6/unit:8-binary,Role:1/unsigned-unit:8). +-define(EVT_ROLE_CHANGE_SIZE, 8). +-define(EVT_NUM_COMP_PKTS, 19). +-record(evt_num_comp_pkts, { + num_hndl +}). +-define(evt_num_comp_pkts_bin(Num_hndl),Num_hndl:1/unsigned-unit:8). +-define(EVT_NUM_COMP_PKTS_SIZE, 1). +-define(EVT_MODE_CHANGE, 20). +-record(evt_mode_change, { + status, + handle, + mode, + interval +}). +-define(evt_mode_change_bin(Status,Handle,Mode,Interval),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Mode:1/unsigned-unit:8,Interval:1/little-unsigned-unit:16). +-define(EVT_MODE_CHANGE_SIZE, 6). +-define(EVT_RETURN_LINK_KEYS, 21). +-record(evt_return_link_keys, { + num_keys +}). +-define(evt_return_link_keys_bin(Num_keys),Num_keys:1/unsigned-unit:8). +-define(EVT_RETURN_LINK_KEYS_SIZE, 1). +-define(EVT_PIN_CODE_REQ, 22). +-record(evt_pin_code_req, { + bdaddr +}). +-define(evt_pin_code_req_bin(Bdaddr),Bdaddr:6/unit:8-binary). +-define(EVT_PIN_CODE_REQ_SIZE, 6). +-define(EVT_LINK_KEY_REQ, 23). +-record(evt_link_key_req, { + bdaddr +}). +-define(evt_link_key_req_bin(Bdaddr),Bdaddr:6/unit:8-binary). +-define(EVT_LINK_KEY_REQ_SIZE, 6). +-define(EVT_LINK_KEY_NOTIFY, 24). +-record(evt_link_key_notify, { + bdaddr, + link_key, + key_type +}). +-define(evt_link_key_notify_bin(Bdaddr,Link_key,Key_type),Bdaddr:6/unit:8-binary,Link_key:16/unit:8-binary,Key_type:1/unsigned-unit:8). +-define(EVT_LINK_KEY_NOTIFY_SIZE, 23). +-define(EVT_LOOPBACK_COMMAND, 25). +-define(EVT_DATA_BUFFER_OVERFLOW, 26). +-record(evt_data_buffer_overflow, { + link_type +}). +-define(evt_data_buffer_overflow_bin(Link_type),Link_type:1/unsigned-unit:8). +-define(EVT_DATA_BUFFER_OVERFLOW_SIZE, 1). +-define(EVT_MAX_SLOTS_CHANGE, 27). +-record(evt_max_slots_change, { + handle, + max_slots +}). +-define(evt_max_slots_change_bin(Handle,Max_slots),Handle:1/little-unsigned-unit:16,Max_slots:1/unsigned-unit:8). +-define(EVT_MAX_SLOTS_CHANGE_SIZE, 3). +-define(EVT_READ_CLOCK_OFFSET_COMPLETE, 28). +-record(evt_read_clock_offset_complete, { + status, + handle, + clock_offset +}). +-define(evt_read_clock_offset_complete_bin(Status,Handle,Clock_offset),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Clock_offset:1/little-unsigned-unit:16). +-define(EVT_READ_CLOCK_OFFSET_COMPLETE_SIZE, 5). +-define(EVT_CONN_PTYPE_CHANGED, 29). +-record(evt_conn_ptype_changed, { + status, + handle, + ptype +}). +-define(evt_conn_ptype_changed_bin(Status,Handle,Ptype),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Ptype:1/little-unsigned-unit:16). +-define(EVT_CONN_PTYPE_CHANGED_SIZE, 5). +-define(EVT_QOS_VIOLATION, 30). +-record(evt_qos_violation, { + handle +}). +-define(evt_qos_violation_bin(Handle),Handle:1/little-unsigned-unit:16). +-define(EVT_QOS_VIOLATION_SIZE, 2). +-define(EVT_PSCAN_REP_MODE_CHANGE, 32). +-record(evt_pscan_rep_mode_change, { + bdaddr, + pscan_rep_mode +}). +-define(evt_pscan_rep_mode_change_bin(Bdaddr,Pscan_rep_mode),Bdaddr:6/unit:8-binary,Pscan_rep_mode:1/unsigned-unit:8). +-define(EVT_PSCAN_REP_MODE_CHANGE_SIZE, 7). +-define(EVT_FLOW_SPEC_COMPLETE, 33). +-record(evt_flow_spec_complete, { + status, + handle, + flags, + direction, + qos +}). +-define(evt_flow_spec_complete_bin(Status,Handle,Flags,Direction,Qos),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Flags:1/unsigned-unit:8,Direction:1/unsigned-unit:8,Qos:17/unit:8-binary). +-define(EVT_FLOW_SPEC_COMPLETE_SIZE, (5 + ?HCI_QOS_CP_SIZE)). +-define(EVT_INQUIRY_RESULT_WITH_RSSI, 34). +-record(inquiry_info_with_rssi, { + bdaddr, + pscan_rep_mode, + pscan_period_mode, + dev_class, + clock_offset, + rssi +}). +-define(inquiry_info_with_rssi_bin(Bdaddr,Pscan_rep_mode,Pscan_period_mode,Dev_class,Clock_offset,Rssi),Bdaddr:6/unit:8-binary,Pscan_rep_mode:1/unsigned-unit:8,Pscan_period_mode:1/unsigned-unit:8,Dev_class:3/unit:8-binary,Clock_offset:1/little-unsigned-unit:16,Rssi:1/signed-unit:8). +-define(INQUIRY_INFO_WITH_RSSI_SIZE, 14). +-record(inquiry_info_with_rssi_and_pscan_mode, { + bdaddr, + pscan_rep_mode, + pscan_period_mode, + pscan_mode, + dev_class, + clock_offset, + rssi +}). +-define(inquiry_info_with_rssi_and_pscan_mode_bin(Bdaddr,Pscan_rep_mode,Pscan_period_mode,Pscan_mode,Dev_class,Clock_offset,Rssi),Bdaddr:6/unit:8-binary,Pscan_rep_mode:1/unsigned-unit:8,Pscan_period_mode:1/unsigned-unit:8,Pscan_mode:1/unsigned-unit:8,Dev_class:3/unit:8-binary,Clock_offset:1/little-unsigned-unit:16,Rssi:1/signed-unit:8). +-define(INQUIRY_INFO_WITH_RSSI_AND_PSCAN_MODE_SIZE, 15). +-define(EVT_READ_REMOTE_EXT_FEATURES_COMPLETE, 35). +-record(evt_read_remote_ext_features_complete, { + status, + handle, + page_num, + max_page_num, + features +}). +-define(evt_read_remote_ext_features_complete_bin(Status,Handle,Page_num,Max_page_num,Features),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Page_num:1/unsigned-unit:8,Max_page_num:1/unsigned-unit:8,Features:8/unit:8-binary). +-define(EVT_READ_REMOTE_EXT_FEATURES_COMPLETE_SIZE, 13). +-define(EVT_SYNC_CONN_COMPLETE, 44). +-record(evt_sync_conn_complete, { + status, + handle, + bdaddr, + link_type, + trans_interval, + retrans_window, + rx_pkt_len, + tx_pkt_len, + air_mode +}). +-define(evt_sync_conn_complete_bin(Status,Handle,Bdaddr,Link_type,Trans_interval,Retrans_window,Rx_pkt_len,Tx_pkt_len,Air_mode),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Bdaddr:6/unit:8-binary,Link_type:1/unsigned-unit:8,Trans_interval:1/unsigned-unit:8,Retrans_window:1/unsigned-unit:8,Rx_pkt_len:1/little-unsigned-unit:16,Tx_pkt_len:1/little-unsigned-unit:16,Air_mode:1/unsigned-unit:8). +-define(EVT_SYNC_CONN_COMPLETE_SIZE, 17). +-define(EVT_SYNC_CONN_CHANGED, 45). +-record(evt_sync_conn_changed, { + status, + handle, + trans_interval, + retrans_window, + rx_pkt_len, + tx_pkt_len +}). +-define(evt_sync_conn_changed_bin(Status,Handle,Trans_interval,Retrans_window,Rx_pkt_len,Tx_pkt_len),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Trans_interval:1/unsigned-unit:8,Retrans_window:1/unsigned-unit:8,Rx_pkt_len:1/little-unsigned-unit:16,Tx_pkt_len:1/little-unsigned-unit:16). +-define(EVT_SYNC_CONN_CHANGED_SIZE, 9). +-define(EVT_SNIFF_SUBRATING, 46). +-record(evt_sniff_subrating, { + status, + handle, + max_tx_latency, + max_rx_latency, + min_remote_timeout, + min_local_timeout +}). +-define(evt_sniff_subrating_bin(Status,Handle,Max_tx_latency,Max_rx_latency,Min_remote_timeout,Min_local_timeout),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Max_tx_latency:1/little-unsigned-unit:16,Max_rx_latency:1/little-unsigned-unit:16,Min_remote_timeout:1/little-unsigned-unit:16,Min_local_timeout:1/little-unsigned-unit:16). +-define(EVT_SNIFF_SUBRATING_SIZE, 11). +-define(EVT_EXTENDED_INQUIRY_RESULT, 47). +-record(extended_inquiry_info, { + bdaddr, + pscan_rep_mode, + pscan_period_mode, + dev_class, + clock_offset, + rssi, + data +}). +-define(extended_inquiry_info_bin(Bdaddr,Pscan_rep_mode,Pscan_period_mode,Dev_class,Clock_offset,Rssi,Data),Bdaddr:6/unit:8-binary,Pscan_rep_mode:1/unsigned-unit:8,Pscan_period_mode:1/unsigned-unit:8,Dev_class:3/unit:8-binary,Clock_offset:1/little-unsigned-unit:16,Rssi:1/signed-unit:8,Data:(?HCI_MAX_EIR_LENGTH)/unit:8-binary). +-define(EXTENDED_INQUIRY_INFO_SIZE, 254). +-define(EVT_ENCRYPTION_KEY_REFRESH_COMPLETE, 48). +-record(evt_encryption_key_refresh_complete, { + status, + handle +}). +-define(evt_encryption_key_refresh_complete_bin(Status,Handle),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16). +-define(EVT_ENCRYPTION_KEY_REFRESH_COMPLETE_SIZE, 3). +-define(EVT_IO_CAPABILITY_REQUEST, 49). +-record(evt_io_capability_request, { + bdaddr +}). +-define(evt_io_capability_request_bin(Bdaddr),Bdaddr:6/unit:8-binary). +-define(EVT_IO_CAPABILITY_REQUEST_SIZE, 6). +-define(EVT_IO_CAPABILITY_RESPONSE, 50). +-record(evt_io_capability_response, { + bdaddr, + capability, + oob_data, + authentication +}). +-define(evt_io_capability_response_bin(Bdaddr,Capability,Oob_data,Authentication),Bdaddr:6/unit:8-binary,Capability:1/unsigned-unit:8,Oob_data:1/unsigned-unit:8,Authentication:1/unsigned-unit:8). +-define(EVT_IO_CAPABILITY_RESPONSE_SIZE, 9). +-define(EVT_USER_CONFIRM_REQUEST, 51). +-record(evt_user_confirm_request, { + bdaddr, + passkey +}). +-define(evt_user_confirm_request_bin(Bdaddr,Passkey),Bdaddr:6/unit:8-binary,Passkey:1/little-unsigned-unit:32). +-define(EVT_USER_CONFIRM_REQUEST_SIZE, 10). +-define(EVT_USER_PASSKEY_REQUEST, 52). +-record(evt_user_passkey_request, { + bdaddr +}). +-define(evt_user_passkey_request_bin(Bdaddr),Bdaddr:6/unit:8-binary). +-define(EVT_USER_PASSKEY_REQUEST_SIZE, 6). +-define(EVT_REMOTE_OOB_DATA_REQUEST, 53). +-record(evt_remote_oob_data_request, { + bdaddr +}). +-define(evt_remote_oob_data_request_bin(Bdaddr),Bdaddr:6/unit:8-binary). +-define(EVT_REMOTE_OOB_DATA_REQUEST_SIZE, 6). +-define(EVT_SIMPLE_PAIRING_COMPLETE, 54). +-record(evt_simple_pairing_complete, { + status, + bdaddr +}). +-define(evt_simple_pairing_complete_bin(Status,Bdaddr),Status:1/unsigned-unit:8,Bdaddr:6/unit:8-binary). +-define(EVT_SIMPLE_PAIRING_COMPLETE_SIZE, 7). +-define(EVT_LINK_SUPERVISION_TIMEOUT_CHANGED, 56). +-record(evt_link_supervision_timeout_changed, { + handle, + timeout +}). +-define(evt_link_supervision_timeout_changed_bin(Handle,Timeout),Handle:1/little-unsigned-unit:16,Timeout:1/little-unsigned-unit:16). +-define(EVT_LINK_SUPERVISION_TIMEOUT_CHANGED_SIZE, 4). +-define(EVT_ENHANCED_FLUSH_COMPLETE, 57). +-record(evt_enhanced_flush_complete, { + handle +}). +-define(evt_enhanced_flush_complete_bin(Handle),Handle:1/little-unsigned-unit:16). +-define(EVT_ENHANCED_FLUSH_COMPLETE_SIZE, 2). +-define(EVT_USER_PASSKEY_NOTIFY, 59). +-record(evt_user_passkey_notify, { + bdaddr, + passkey, + entered +}). +-define(evt_user_passkey_notify_bin(Bdaddr,Passkey,Entered),Bdaddr:6/unit:8-binary,Passkey:1/little-unsigned-unit:32,Entered:1/unsigned-unit:8). +-define(EVT_USER_PASSKEY_NOTIFY_SIZE, 11). +-define(EVT_KEYPRESS_NOTIFY, 60). +-record(evt_keypress_notify, { + bdaddr, + type +}). +-define(evt_keypress_notify_bin(Bdaddr,Type),Bdaddr:6/unit:8-binary,Type:1/unsigned-unit:8). +-define(EVT_KEYPRESS_NOTIFY_SIZE, 7). +-define(EVT_REMOTE_HOST_FEATURES_NOTIFY, 61). +-record(evt_remote_host_features_notify, { + bdaddr, + features +}). +-define(evt_remote_host_features_notify_bin(Bdaddr,Features),Bdaddr:6/unit:8-binary,Features:8/unit:8-binary). +-define(EVT_REMOTE_HOST_FEATURES_NOTIFY_SIZE, 14). +-define(EVT_LE_META_EVENT, 62). +-record(evt_le_meta_event, { + subevent, + data +}). +-define(evt_le_meta_event_bin(Subevent,Data),Subevent:1/unsigned-unit:8,Data:0/unit:8-binary). +-define(EVT_LE_META_EVENT_SIZE, 1). +-define(EVT_LE_CONN_COMPLETE, 1). +-record(evt_le_connection_complete, { + status, + handle, + role, + peer_bdaddr_type, + peer_bdaddr, + interval, + latency, + supervision_timeout, + master_clock_accuracy +}). +-define(evt_le_connection_complete_bin(Status,Handle,Role,Peer_bdaddr_type,Peer_bdaddr,Interval,Latency,Supervision_timeout,Master_clock_accuracy),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Role:1/unsigned-unit:8,Peer_bdaddr_type:1/unsigned-unit:8,Peer_bdaddr:6/unit:8-binary,Interval:1/little-unsigned-unit:16,Latency:1/little-unsigned-unit:16,Supervision_timeout:1/little-unsigned-unit:16,Master_clock_accuracy:1/unsigned-unit:8). +-define(EVT_LE_CONN_COMPLETE_SIZE, 18). +-define(EVT_LE_ADVERTISING_REPORT, 2). +-record(le_advertising_info, { + evt_type, + bdaddr_type, + bdaddr, + length, + data +}). +-define(le_advertising_info_bin(Evt_type,Bdaddr_type,Bdaddr,Length,Data),Evt_type:1/unsigned-unit:8,Bdaddr_type:1/unsigned-unit:8,Bdaddr:6/unit:8-binary,Length:1/unsigned-unit:8,Data:0/unit:8-binary). +-define(LE_ADVERTISING_INFO_SIZE, 9). +-define(EVT_LE_CONN_UPDATE_COMPLETE, 3). +-record(evt_le_connection_update_complete, { + status, + handle, + interval, + latency, + supervision_timeout +}). +-define(evt_le_connection_update_complete_bin(Status,Handle,Interval,Latency,Supervision_timeout),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Interval:1/little-unsigned-unit:16,Latency:1/little-unsigned-unit:16,Supervision_timeout:1/little-unsigned-unit:16). +-define(EVT_LE_CONN_UPDATE_COMPLETE_SIZE, 9). +-define(EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE, 4). +-record(evt_le_read_remote_used_features_complete, { + status, + handle, + features +}). +-define(evt_le_read_remote_used_features_complete_bin(Status,Handle,Features),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16,Features:8/unit:8-binary). +-define(EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE_SIZE, 11). +-define(EVT_LE_LTK_REQUEST, 5). +-record(evt_le_long_term_key_request, { + handle, + random, + diversifier +}). +-define(evt_le_long_term_key_request_bin(Handle,Random,Diversifier),Handle:1/little-unsigned-unit:16,Random:1/little-unsigned-unit:64,Diversifier:1/little-unsigned-unit:16). +-define(EVT_LE_LTK_REQUEST_SIZE, 12). +-define(EVT_PHYSICAL_LINK_COMPLETE, 64). +-record(evt_physical_link_complete, { + status, + handle +}). +-define(evt_physical_link_complete_bin(Status,Handle),Status:1/unsigned-unit:8,Handle:1/unsigned-unit:8). +-define(EVT_PHYSICAL_LINK_COMPLETE_SIZE, 2). +-define(EVT_CHANNEL_SELECTED, 65). +-define(EVT_DISCONNECT_PHYSICAL_LINK_COMPLETE, 66). +-record(evt_disconn_physical_link_complete, { + status, + handle, + reason +}). +-define(evt_disconn_physical_link_complete_bin(Status,Handle,Reason),Status:1/unsigned-unit:8,Handle:1/unsigned-unit:8,Reason:1/unsigned-unit:8). +-define(EVT_DISCONNECT_PHYSICAL_LINK_COMPLETE_SIZE, 3). +-define(EVT_PHYSICAL_LINK_LOSS_EARLY_WARNING, 67). +-record(evt_physical_link_loss_warning, { + handle, + reason +}). +-define(evt_physical_link_loss_warning_bin(Handle,Reason),Handle:1/unsigned-unit:8,Reason:1/unsigned-unit:8). +-define(EVT_PHYSICAL_LINK_LOSS_WARNING_SIZE, 2). +-define(EVT_PHYSICAL_LINK_RECOVERY, 68). +-record(evt_physical_link_recovery, { + handle +}). +-define(evt_physical_link_recovery_bin(Handle),Handle:1/unsigned-unit:8). +-define(EVT_PHYSICAL_LINK_RECOVERY_SIZE, 1). +-define(EVT_LOGICAL_LINK_COMPLETE, 69). +-record(evt_logical_link_complete, { + status, + log_handle, + handle, + tx_flow_id +}). +-define(evt_logical_link_complete_bin(Status,Log_handle,Handle,Tx_flow_id),Status:1/unsigned-unit:8,Log_handle:1/little-unsigned-unit:16,Handle:1/unsigned-unit:8,Tx_flow_id:1/unsigned-unit:8). +-define(EVT_LOGICAL_LINK_COMPLETE_SIZE, 5). +-define(EVT_DISCONNECT_LOGICAL_LINK_COMPLETE, 70). +-define(EVT_FLOW_SPEC_MODIFY_COMPLETE, 71). +-record(evt_flow_spec_modify_complete, { + status, + handle +}). +-define(evt_flow_spec_modify_complete_bin(Status,Handle),Status:1/unsigned-unit:8,Handle:1/little-unsigned-unit:16). +-define(EVT_FLOW_SPEC_MODIFY_COMPLETE_SIZE, 3). +-define(EVT_NUMBER_COMPLETED_BLOCKS, 72). +-define(EVT_AMP_STATUS_CHANGE, 77). +-record(evt_amp_status_change, { + status, + amp_status +}). +-define(evt_amp_status_change_bin(Status,Amp_status),Status:1/unsigned-unit:8,Amp_status:1/unsigned-unit:8). +-define(EVT_AMP_STATUS_CHANGE_SIZE, 2). +-define(EVT_TESTING, 254). +-define(EVT_VENDOR, 255). +-define(EVT_STACK_INTERNAL, 253). +-record(evt_stack_internal, { + type, + data +}). +-define(evt_stack_internal_bin(Type,Data),Type:1/little-unsigned-unit:16,Data:0/unit:8-binary). +-define(EVT_STACK_INTERNAL_SIZE, 2). +-define(EVT_SI_DEVICE, 1). +-record(evt_si_device, { + event, + dev_id +}). +-define(evt_si_device_bin(Event,Dev_id),Event:1/little-unsigned-unit:16,Dev_id:1/little-unsigned-unit:16). +-define(EVT_SI_DEVICE_SIZE, 4). +-endif. diff --git a/deps/bt/src/hci_drv.erl b/deps/bt/src/hci_drv.erl new file mode 100644 index 0000000..8f3d062 --- /dev/null +++ b/deps/bt/src/hci_drv.erl @@ -0,0 +1,514 @@ +%%% @author Tony Rogvall <tony@up13> +%%% @copyright (C) 2015, Tony Rogvall +%%% @doc +%%% hci_drv +%%% @end +%%% Created : 5 Apr 2015 by Tony Rogvall <tony@up13> + +-module(hci_drv). + +-export([open/0]). +-export([bind/2]). +-export([close/1]). +-export([send/2]). +-export([activate/1]). +-export([deactivate/1]). +-export([activate/2]). +-export([debug/2]). +-export([dev_up/2]). +-export([dev_down/2]). +-export([dev_reset/2]). %% down,up? +-export([dev_restat/2]). %% reset statistics +-export([get_dev_list/1]). +-export([get_dev_info/2]). +-export([get_conn_list/2]). +-export([get_conn_info/3]). +-export([get_auth_info/2]). +-export([set_raw/2]). +-export([set_auth/3]). +-export([set_encrypt/3]). +-export([set_ptype/3]). +-export([set_link_policy/3]). +-export([set_link_mode/3]). +-export([set_scan/3]). +-export([set_acl_mtu/4]). +-export([set_sco_mtu/4]). +-export([block/2]). +-export([unblock/2]). +-export([inquiry/5]). + +-export([set_filter/2]). +-export([get_filter/1]). +-export([set_filter_ptype/2]). +-export([clr_filter_ptype/2]). +-export([set_filter_event/2]). +-export([clr_filter_event/2]). +-export([set_filter_opcode/2]). +-export([make_filter/3]). + +-include("../include/hci_drv.hrl"). +-include("hci_api.hrl"). + +%% deugging +-compile(export_all). + +-define(CMD_ACTIVE, 1). +-define(CMD_DEBUG, 2). +-define(CMD_BIND, 3). +-define(CMD_GETFILTER, 4). +-define(CMD_SETFILTER, 5). + +-define(CMD_HCIDEVUP, 201). +-define(CMD_HCIDEVDOWN, 202). +-define(CMD_HCIDEVRESET, 203). +-define(CMD_HCIDEVRESTAT, 204). +-define(CMD_HCIGETDEVLIST, 210). +-define(CMD_HCIGETDEVINFO, 211). +-define(CMD_HCIGETCONNLIST, 212). +-define(CMD_HCIGETCONNINFO, 213). +-define(CMD_HCIGETAUTHINFO, 215). +-define(CMD_HCISETRAW, 220). +-define(CMD_HCISETSCAN, 221). +-define(CMD_HCISETAUTH, 222). +-define(CMD_HCISETENCRYPT, 223). +-define(CMD_HCISETPTYPE, 224). +-define(CMD_HCISETLINKPOL, 225). +-define(CMD_HCISETLINKMODE, 226). +-define(CMD_HCISETACLMTU, 227). +-define(CMD_HCISETSCOMTU, 228). +-define(CMD_HCIBLOCKADDR, 230). +-define(CMD_HCIUNBLOCKADDR, 231). +-define(CMD_HCIINQUIRY, 240). + + +-define(DLOG_DEBUG, 7). +-define(DLOG_INFO, 6). +-define(DLOG_NOTICE, 5). +-define(DLOG_WARNING, 4). +-define(DLOG_ERROR, 3). +-define(DLOG_CRITICAL, 2). +-define(DLOG_ALERT, 1). +-define(DLOG_EMERGENCY, 0). +-define(DLOG_NONE, -1). + + +-spec open() -> hci_socket_t(). +open() -> + Driver = "hci_drv", + Path = code:priv_dir(bt), + %% io:format("load_driver '~s' from: '~s'\n", [Driver, Path]), + case erl_ddll:load_driver(Path, Driver) of + ok -> + erlang:open_port({spawn_driver, Driver}, [binary]); + {error,Error} -> + io:format("Error: ~s\n", [erl_ddll:format_error_int(Error)]), + erlang:error(Error) + end. + +%% Close the HCI socket +-spec close(Hci::hci_socket_t()) -> boolean(). +close(Hci) when is_port(Hci) -> + erlang:port_close(Hci). + +%% Bind HCI socket to device +-spec bind(Hci::hci_socket_t(), DevID::hci_devid_t()) -> ok | {error,posix()}. + +bind(Hci, DevID) when is_port(Hci), is_integer(DevID) -> + port_call(Hci, ?CMD_BIND, <<DevID:32/signed>>). + +%% Bring the device UP +-spec dev_up(Hci::hci_socket_t(), DevID::hci_devid_t()) -> ok | {error,posix()}. + +dev_up(Hci,DevID) when is_port(Hci), is_integer(DevID) -> + port_call(Hci, ?CMD_HCIDEVUP, <<DevID:32/signed>>). + +%% Bring the device DOWN +-spec dev_down(Hci::hci_socket_t(), DevID::hci_devid_t()) -> ok | {error,posix()}. + +dev_down(Hci,DevID) when is_port(Hci), is_integer(DevID) -> + port_call(Hci, ?CMD_HCIDEVDOWN, <<DevID:32/signed>>). + +%% Reset the device, maybe do down/up? as seen in library code elsewhere +-spec dev_reset(Hci::hci_socket_t(), DevID::hci_devid_t()) -> ok | {error,posix()}. + +dev_reset(Hci,DevID) when is_port(Hci), is_integer(DevID) -> + port_call(Hci, ?CMD_HCIDEVRESET, <<DevID:32/signed>>). + +%% Reset device statistics +-spec dev_restat(Hci::hci_socket_t(), DevID::hci_devid_t()) -> ok | {error,posix()}. + +dev_restat(Hci,DevID) when is_port(Hci), is_integer(DevID) -> + port_call(Hci, ?CMD_HCIDEVRESTAT, <<DevID:32/signed>>). + +%% Get list of [{device(),options()}] +-spec get_dev_list(Hci::hci_socket_t()) -> + {ok,[{DevID::hci_devid_t(), Opt::integer()}]} | + {error,posix()}. +get_dev_list(Hci) when is_port(Hci) -> + case port_call(Hci, ?CMD_HCIGETDEVLIST, <<>>) of + {ok, DevList} -> + {ok, + [{DevID,DevOpt} || + <<DevID:16, DevOpt:32>> + <= DevList ]}; + Error -> + Error + end. + +%% get device info +-spec get_dev_info(Hci::hci_socket_t(), DevID::hci_devid_t()) -> + {ok,[#hci_dev_info{}]} | {error,posix()}. + +get_dev_info(Hci, DevID) -> + case port_call(Hci, ?CMD_HCIGETDEVINFO, <<DevID:32/signed>>) of + {ok, Info} -> + {ok, decode_hci_dev_info(Info)}; + Error -> + Error + end. + +%% get device info +-spec get_conn_list(Hci::hci_socket_t(), DevID::hci_devid_t()) -> + {ok,[#hci_conn_info{}]} | {error,posix()}. +get_conn_list(Hci, DevID) -> + case port_call(Hci, ?CMD_HCIGETCONNLIST, <<DevID:32/signed>>) of + {ok, Data} -> + {ok, decode_hci_conn_list(Data)}; + Error -> + Error + end. + +-spec get_conn_info(Hci::hci_socket_t(), Addr::bdaddr_t(), Type::uint8_t()) -> + {ok,#hci_conn_info{}} | {error,posix()}. + +get_conn_info(Hci, Addr, Type) when is_port(Hci) -> + case bt:getaddr(Addr) of + {ok,{A,B,C,D,E,F}} -> + case port_call(Hci, ?CMD_HCIGETCONNINFO, <<F,E,D,C,B,A,Type>>) of + {ok, Info} -> + {ok, decode_hci_conn_info(Info)}; + Error -> + Error + end; + Error -> + Error + end. + +-spec get_auth_info(Hci::hci_socket_t(), Addr::bdaddr_t()) -> + {ok, Type::uint8_t()} | {error,posix()}. + +get_auth_info(Hci, Addr) when is_port(Hci) -> + case bt:getaddr(Addr) of + {ok,{A,B,C,D,E,F}} -> + port_call(Hci, ?CMD_HCIGETAUTHINFO, <<F,E,D,C,B,A>>); + Error -> + Error + end. + +%% Set raw processing? +-spec set_raw(Hci::hci_socket_t(), DevID::hci_devid_t()) -> ok | {error,posix()}. + +set_raw(Hci,DevID) when is_port(Hci), is_integer(DevID) -> + port_call(Hci, ?CMD_HCISETRAW, <<DevID:32/signed>>). + +%% Enable/Disable authentication +-spec set_auth(Hci::hci_socket_t(), DevID::hci_devid_t(), DoAuth::boolean()) -> + ok | {error,posix()}. +set_auth(Hci,DevID,DoAuth) when is_port(Hci), is_integer(DevID), + is_boolean(DoAuth) -> + port_call(Hci, ?CMD_HCISETAUTH, <<DevID:32/signed,DoAuth:8>>). + +%% Enable/Disable authentication +-spec set_encrypt(Hci::hci_socket_t(), DevID::hci_devid_t(), DoAuth::boolean()) -> + ok | {error,posix()}. +set_encrypt(Hci,DevID,DoEncrypt) when is_port(Hci), is_integer(DevID), + is_boolean(DoEncrypt) -> + port_call(Hci, ?CMD_HCISETENCRYPT, <<DevID:32/signed,DoEncrypt:8>>). + +%% Set device packet type +-spec set_ptype(Hci::hci_socket_t(),DevID::hci_devid_t(),PType::[string()]) -> + ok | {error,posix()}. +set_ptype(Hci,DevID,PType0) when is_port(Hci), is_integer(DevID), + is_list(PType0) -> + PType = set_bits(PType0, hci_util:kv_pkt_ptype()), + port_call(Hci, ?CMD_HCISETPTYPE, <<DevID:32/signed,PType:32>>). + +-spec set_link_policy(Hci::hci_socket_t(),DevID::hci_devid_t(),Pol::string()) -> + ok | {error,posix()}. +set_link_policy(Hci,DevID,Pol0) when is_port(Hci), is_integer(DevID) -> + LinkPolicy = hci_util:find_enum_value(Pol0, hci_util:kv_link_policy()), + port_call(Hci, ?CMD_HCISETLINKPOL, <<DevID:32/signed,LinkPolicy:32>>). + +-spec set_link_mode(Hci::hci_socket_t(),DevID::hci_devid_t(),Mode::string()) -> + ok | {error,posix()}. +set_link_mode(Hci,DevID,Mode0) when is_port(Hci), is_integer(DevID) -> + LinkMode = hci_util:find_enum_value(Mode0, hci_util:kv_link_mode()), + port_call(Hci, ?CMD_HCISETLINKMODE, <<DevID:32/signed,LinkMode:32>>). + +-spec set_scan(Hci::hci_socket_t(),DevID::hci_devid_t(),Scan::string()) -> + ok | {error,posix()}. +set_scan(Hci,DevID,Scan0) when is_port(Hci) -> + Scan = hci_util:find_enum_value(Scan0, hci_util:kv_scan()), + port_call(Hci, ?CMD_HCISETSCAN, <<DevID:32/signed,Scan:32>>). + +-spec set_acl_mtu(Hci::hci_socket_t(),DevID::hci_devid_t(), + Mtu::integer(),Mpkt::integer()) -> + ok | {error,posix()}. +set_acl_mtu(Hci,DevID,Mtu,Mpkt) when is_port(Hci) -> + port_call(Hci, ?CMD_HCISETACLMTU, <<DevID:32/signed,Mtu:16,Mpkt:16>>). + +-spec set_sco_mtu(Hci::hci_socket_t(),DevID::hci_devid_t(), + Mtu::integer(),Mpkt::integer()) -> + ok | {error,posix()}. +set_sco_mtu(Hci,DevID,Mtu,Mpkt) when is_port(Hci) -> + port_call(Hci, ?CMD_HCISETSCOMTU, <<DevID:32/signed,Mtu:16,Mpkt:16>>). + +%% The Hci socket must be bound before this operation +-spec block(Hci::hci_socket_t(), Addr::bdaddr_t()) -> + ok | {error,posix()}. +block(Hci, Addr) when is_port(Hci) -> + case bt:getaddr(Addr) of + {ok,{A,B,C,D,E,F}} -> + port_call(Hci, ?CMD_HCIBLOCKADDR, <<F,E,D,C,B,A>>); + Error -> + Error + end. + +%% The Hci socket must be bound before this operation +-spec unblock(Hci::hci_socket_t(), Addr:: all | bdaddr_t()) -> + ok | {error,posix()}. +unblock(Hci, all) when is_port(Hci) -> + unblock(Hci, {0,0,0,0,0,0}); +unblock(Hci,Addr) when is_port(Hci) -> + case bt:getaddr(Addr) of + {ok, {A,B,C,D,E,F}} -> + port_call(Hci, ?CMD_HCIUNBLOCKADDR, <<F,E,D,C,B,A>>); + Error -> + Error + end. + +%% Timeout is in millisecs but is converted into 1.28s units +%% 1 len = 1.28s = 1280 ms +inquiry(Hci, Timeout, NumRsp, Lap0, Flags) -> + Len = (Timeout div 1280) + (if Timeout rem 1280 =:= 0 -> 0; true -> 1 end), + Lap = if Lap0 =:= 0; Lap0 =:= <<>> -> <<16#33,16#8b,16#9e>>; + byte_size(Lap0) =:= 3 -> Lap0 + end, + Args = <<Len, NumRsp, Lap/binary, Flags:32>>, + case port_call(Hci, ?CMD_HCIINQUIRY, Args) of + {ok, Bin} -> + {ok, [hci_api:decode_inquiry_info(Info) || + <<Info:?INQUIRY_INFO_SIZE/binary>> <= Bin]}; + Error -> + Error + end. + + +-spec deactivate(Hci::hci_socket_t()) -> ok | {error,posix()}. + +deactivate(Hci) when is_port(Hci) -> + activate(Hci, 0). + +-spec activate(Hci::hci_socket_t()) -> ok | {error,posix()}. +activate(Hci) when is_port(Hci) -> + activate(Hci, -1). + +-spec activate(Hci::hci_socket_t(),N::integer()) -> ok | {error,posix()}. +activate(Hci, N) when is_port(Hci), + is_integer(N), N >= -1, N < 16#7fffffff -> + port_call(Hci, ?CMD_ACTIVE, <<N:32>>). + +-spec debug(Hci::hci_socket_t(), Level::level_t()) -> ok. +debug(Hci,Level) when is_port(Hci), is_atom(Level) -> + L = level(Level), + port_call(Hci, ?CMD_DEBUG, <<L:32>>). + +set_filter_ptype(T, F = #hci_filter { type_mask = M}) -> + Bit = if T =:= ?HCI_VENDOR_PKT -> 1; + true -> 1 bsl (T band ?HCI_FLT_TYPE_BITS) + end, + F#hci_filter { type_mask = M bor Bit }. + +clr_filter_ptype(T, F = #hci_filter { type_mask = M}) -> + Bit = if T =:= ?HCI_VENDOR_PKT -> 1; + true -> 1 bsl (T band ?HCI_FLT_TYPE_BITS) + end, + F#hci_filter { type_mask = M band (bnot Bit) }. + +set_filter_event(E, F = #hci_filter { event_mask = M}) -> + Bit = 1 bsl (E band ?HCI_FLT_EVENT_BITS), + F#hci_filter { event_mask = M bor Bit }. + +clr_filter_event(E, F = #hci_filter { event_mask = M}) -> + Bit = 1 bsl (E band ?HCI_FLT_EVENT_BITS), + F#hci_filter { event_mask = M band (bnot Bit) }. + +set_filter_opcode(Opcode, F = #hci_filter { }) -> + F#hci_filter { opcode = Opcode }. + +make_filter(Opcode, Ts, Es) when is_integer(Opcode), + is_list(Ts), + is_list(Es) -> + + Type_mask = make_bits(Ts, 0) band 16#ffffffff, + Event_mask = make_bits(Es, 0) band 16#ffffffffffffffff, + #hci_filter { type_mask = Type_mask, + event_mask = Event_mask, + opcode = Opcode }. + +make_bits([255|Ns], Bits) -> %% ?HCI_VENDOR_PKT! -> 1 + make_bits(Ns, Bits bor 1); +make_bits([-1|_], _Bits) -> 16#ffffffffffffffff; +make_bits([Nr|Ns], Bits) when is_integer(Nr), Nr >= 0 -> + make_bits(Ns, Bits bor (1 bsl Nr)); +make_bits([], Bits) -> + Bits. + + +-spec set_filter(Hci::hci_socket_t(), Filter::#hci_filter{}) -> + ok | {error,posix()}. +set_filter(Hci,Filter) when is_port(Hci), is_record(Filter, hci_filter) -> + port_call(Hci, ?CMD_SETFILTER, encode_hci_filter(Filter)). + +-spec get_filter(Hci::hci_socket_t()) -> + {ok, Filter::#hci_filter{}} | {error,posix()}. + +get_filter(Hci) when is_port(Hci) -> + case port_call(Hci, ?CMD_GETFILTER, <<>>) of + {ok, Data} -> {ok, decode_hci_filter(Data)}; + Error -> Error + end. + +-spec send(Hci::hci_socket_t(), Command::iolist()) -> + boolean(). +send(Hci, Command) -> + erlang:port_command(Hci, Command). + +port_call(Hci, Cmd, Data) -> + case erlang:port_control(Hci, Cmd, Data) of + <<0>> -> + ok; + <<255,E/binary>> -> + {error, erlang:binary_to_atom(E, latin1)}; + <<254,E/binary>> -> + {error, binary_to_list(E)}; + <<1,Y:8>> -> {ok,Y}; + <<1,Y:16/native-unsigned>> -> {ok, Y}; + <<1,Y:32/native-unsigned>> -> {ok, Y}; + <<1,Y:64/native-unsigned>> -> {ok, Y}; + <<2,X:32/native-unsigned,Y:32/native-unsigned>> -> {ok,{X,Y}}; + <<3,X/binary>> -> {ok,X}; + <<4,X/binary>> -> {ok,binary_to_list(X)} + end. + +%% convert symbolic to numeric level +level(true) -> ?DLOG_DEBUG; +level(false) -> ?DLOG_NONE; +level(debug) -> ?DLOG_DEBUG; +level(info) -> ?DLOG_INFO; +level(notice) -> ?DLOG_NOTICE; +level(warning) -> ?DLOG_WARNING; +level(error) -> ?DLOG_ERROR; +level(critical) -> ?DLOG_CRITICAL; +level(alert) -> ?DLOG_ALERT; +level(emergency) -> ?DLOG_EMERGENCY; +level(none) -> ?DLOG_NONE. + +%% note: bluetooth addresses are reversed in hci +decode_hci_dev_info( + <<Dev_id:16, Name0:8/binary, F,E,D,C,B,A, + Flags:32, Type:8, Features:8/binary, + Pkt_type:32, Link_policy:32, Link_mode:32, + Acl_mtu:16, Acl_pkts:16, + Sco_mtu:16, Sco_pkts:16, + Stat/binary>>) -> + Name = hci_util:c_string(Name0), + BdAddr = {A,B,C,D,E,F}, + S = decode_hci_dev_stats(Stat), + #hci_dev_info { + dev_id = Dev_id, + name = Name, + bdaddr = BdAddr, + flags = Flags, + type = Type, + features = Features, + pkt_type = Pkt_type, + link_policy = Link_policy, + link_mode = Link_mode, + acl_mtu = Acl_mtu, + acl_pkts = Acl_pkts, + sco_mtu = Sco_mtu, + sco_pkts = Sco_pkts, + stat = S + }. + +%% 10*4 = 40 bytes +decode_hci_dev_stats( + << Err_rx:32, Err_tx:32, + Cmd_tx:32, Evt_rx:32, + Acl_tx:32, Acl_rx:32, + Sco_tx:32, Sco_rx:32, + Byte_rx:32, Byte_tx:32>>) -> + #hci_dev_stats { + err_rx = Err_rx, + err_tx = Err_tx, + cmd_tx = Cmd_tx, + evt_rx = Evt_rx, + acl_tx = Acl_tx, + acl_rx = Acl_rx, + sco_tx = Sco_tx, + sco_rx = Sco_rx, + byte_rx = Byte_rx, + byte_tx = Byte_tx }. + +%% bdaddr is reverse in hci +decode_hci_conn_info(<<Handle:16, + F,E,D,C,B,A, + Type:8, Out:8, + State:16, Link_mode:32>>) -> + #hci_conn_info { handle=Handle, bdaddr={A,B,C,D,E,F}, + type=Type, out=Out, + state=State, link_mode = Link_mode }. + +decode_hci_conn_list(<<Item:16/binary, List/binary>>) -> + [decode_hci_conn_info(Item) | decode_hci_conn_list(List)]; +decode_hci_conn_list(<<>>) -> + []. + +encode_hci_filter(#hci_filter { type_mask = Type_mask, + event_mask = Event_mask, + opcode = Opcode }) -> + Event0 = Event_mask band 16#ffffffff, + Event1 = (Event_mask bsr 32) band 16#ffffffff, + <<Type_mask:32, Event0:32, Event1:32, Opcode:16>>. + +decode_hci_filter(<<Type_mask:32, Event0:32, Event1:32, Opcode:16>>) -> + Event_mask = Event0 + (Event1 bsl 32), + #hci_filter { type_mask = Type_mask, + event_mask = Event_mask, + opcode = Opcode }. + +%% given a list of atoms/strings/integers +%% build a bitmask from the values + +set_bits(Names, Flags) -> + set_bits(Names, Flags, 0). + +set_bits([Name|Names], Flags, Acc) when is_atom(Name) -> + case lists:keyfind(atom_to_list(Name), 1, Flags) of + false -> + exit(badarg); + {_, Bit} -> + set_bits(Names, Flags, Bit bor Acc) + end; +set_bits([Name|Names], Flags, Acc) when is_list(Name) -> + case lists:keyfind(Name, 1, Flags) of + false -> + exit(badarg); + {_, Bit} -> + set_bits(Names, Flags, Bit bor Acc) + end; +set_bits([Bits|Names], Flags, Acc) when is_integer(Bits) -> + set_bits(Names, Flags, Bits bor Acc); +set_bits([], _Flags, Acc) -> + Acc. diff --git a/deps/bt/src/hci_socket.erl b/deps/bt/src/hci_socket.erl new file mode 100644 index 0000000..4549c44 --- /dev/null +++ b/deps/bt/src/hci_socket.erl @@ -0,0 +1,314 @@ +%%%------------------------------------------------------------------- +%%% @author Tony Rogvall <tony@up13> +%%% @copyright (C) 2015, Tony Rogvall +%%% @doc +%%% HCI socket server +%%% @end +%%% Created : 6 Apr 2015 by Tony Rogvall <tony@up13> +%%%------------------------------------------------------------------- +-module(hci_socket). + +-behaviour(gen_server). + +%% API +-export([open/1]). +-export([close/1]). +-export([send/4]). +-export([call/5]). +-export([get_info/1]). +-export([get_conn_list/1]). +-export([get_conn_info/3]). +-export([get_auth_info/2]). +-export([debug/2]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-define(SERVER, ?MODULE). + +-include("../include/hci_drv.hrl"). +-include("hci_api.hrl"). + +-record(state, + { + hci :: hci_socket_t(), + devid :: hci_devid_t(), + from, + saved_filter :: #hci_filter {}, + current_opcode :: uint16_t(), + current_event :: uint16_t(), + current_decode :: function() + }). + +%%%=================================================================== +%%% API +%%%=================================================================== + +%%-------------------------------------------------------------------- +%% @doc +%% Starts the server +%% +%% @spec start_link() -> {ok, Pid} | ignore | {error, Error} +%% @end +%%-------------------------------------------------------------------- +open(DevID) -> + gen_server:start_link(?MODULE, [DevID], []). + +close(Pid) -> + gen_server:call(Pid, close). + +send(Pid, OGF, OCG, Data) -> + gen_server:call(Pid, {send, OGF, OCG, Data}). + +call(Pid, OGF, OCG, Data, Event) -> + gen_server:call(Pid, {call, OGF, OCG, Data, Event}). + +get_info(Pid) -> + gen_server:call(Pid, get_info). + +get_conn_list(Pid) -> + gen_server:call(Pid, get_conn_list). + +get_conn_info(Pid,Addr,Type) -> + gen_server:call(Pid, {get_conn_info,Addr,Type}). + +get_auth_info(Pid,Addr) -> + gen_server:call(Pid, {get_auth_info,Addr}). + +debug(Pid, Level) -> + gen_server:call(Pid, {debug, Level}). + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Initializes the server +%% +%% @spec init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% @end +%%-------------------------------------------------------------------- +init([DevID]) -> + Hci = hci_drv:open(), + case hci_drv:bind(Hci, DevID) of + ok -> + hci_drv:activate(Hci), %% receive async data + {ok, #state{ hci = Hci, devid = DevID }}; + Error -> {stop,Error} + end. +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handling call messages +%% +%% @spec handle_call(Request, From, State) -> +%% {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_call({send,OGF,OCF,Data}, _From, State) -> + Pkt = [?HCI_COMMAND_PKT, + <<(?cmd_opcode_pack(OGF,OCF)):16/little>>, + <<(byte_size(Data)):8>>, + Data], + R = hci_drv:send(State#state.hci,Pkt), + %% io:format("send pkt ~p = ~p\n", [Pkt,R]), + {reply, R, State}; + +handle_call({call,OGF,OCF,Data,Decode}, From, State) -> + Opcode = ?cmd_opcode_pack(OGF,OCF), + {ok,OldFilter} = hci_drv:get_filter(State#state.hci), + %% io:format("call: saved_filter = ~p\n", [OldFilter]), + Event = 0, %% fixme, use ?EVT_xxxx + NewFilter = hci_drv:make_filter(Opcode, + [?HCI_EVENT_PKT], + [?EVT_CMD_STATUS, + ?EVT_CMD_COMPLETE, + ?EVT_LE_META_EVENT, + ?EVT_REMOTE_NAME_REQ_COMPLETE, + Event]), + %% io:format("call: new_filter = ~p\n", [NewFilter]), + case hci_drv:set_filter(State#state.hci, NewFilter) of + ok -> + Pkt = [?HCI_COMMAND_PKT, + <<Opcode:16/little>>, + <<(byte_size(Data)):8>>, + Data], + _R = hci_drv:send(State#state.hci,Pkt), + %% io:format("send pkt ~p = ~p\n", [Pkt,_R]), + {noreply, State#state { from = From, + saved_filter = OldFilter, + current_opcode = Opcode, + current_event = Event, + current_decode = Decode + }}; + Error -> + {reply, Error, State} + end; + +handle_call(get_info, _From, State) -> + {reply, hci_drv:get_dev_info(State#state.hci, State#state.devid), State}; +handle_call(get_conn_list, _From, State) -> + {reply, hci_drv:get_conn_list(State#state.hci, State#state.devid), State}; +handle_call({get_conn_info,Addr,Type}, _From, State) -> + {reply, hci_drv:get_conn_info(State#state.hci,Addr,Type), State}; +handle_call({get_auth_info,Addr},_From, State) -> + {reply, hci_drv:get_auth_info(State#state.hci, Addr), State}; +handle_call(close, _From, State) -> + hci_drv:close(State#state.hci), + {stop, normal, ok, State#state { hci=undefined }}; +handle_call({debug,Level}, _From, State) -> + {reply, hci_drv:debug(State#state.hci, Level), State}; +handle_call(_Request, _From, State) -> + {reply, {error,bad_call}, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handling cast messages +%% +%% @spec handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_cast(_Msg, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handling all non call/cast messages +%% +%% @spec handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_info({Hci,{data,_D0= <<_:8,Evt:8,Plen:8,Packet:Plen/binary,_R/binary>>}}, + State) when Hci =:= State#state.hci -> + case Evt of + ?EVT_CMD_COMPLETE -> %% Fixme: match opcode + %%io:format("EVT_CMD_COMPLETE, data ~p, rest=~p\n", [_D0,_R]), + <<?evt_cmd_complete_bin(_Ncmd,_Opcode),Reply/binary>> = Packet, + %% io:format("Ncmd=~w,Opcode=~p,Reply=~p\n",[_Ncmd,_Opcode,Reply]), + State1 = do_reply(Reply, State), + {noreply, State1 }; + + ?EVT_CMD_STATUS -> %% Fixme: match opcode + %% io:format("EVT_CMD_STATUS, data ~p, rest=~p\n", [_D0,_R]), + <<?evt_cmd_status_bin(_Status,_Ncmd,_Opcode),Reply/binary>> = Packet, + %% io:format("Status=~w,Ncmd=~w,Opcode=~p,Reply=~p\n",[_Status,_Ncmd,_Opcode,Reply]), + State1 = do_reply(Reply, State), + {noreply, State1 }; + + ?EVT_REMOTE_NAME_REQ_COMPLETE -> + %% io:format("EVT_REMOTE_NAME_REQ_COMPLETE,data ~p,rest=~p\n", [_D0,_R]), + <<?evt_remote_name_req_complete_bin(Status,Bdaddr,Name)>> = Packet, + NameComplete = #evt_remote_name_req_complete { status=Status, + bdaddr=Bdaddr, + name=cname(Name)}, + gen_server:reply(State#state.from, {ok, NameComplete}), + hci_drv:set_filter(State#state.hci, + State#state.saved_filter), + {noreply, State#state { from = undefined, + saved_filter = undefined }}; + ?EVT_LE_META_EVENT -> + %% io:format("EVT_LE_META_EVENT, data ~p, rest=~p\n", [_D0,_R]), + <<?evt_le_meta_event_bin(Subevent,_D1),LePacket/binary>> = Packet, + Le = hci_api:decode_le(Subevent, LePacket), + gen_server:reply(State#state.from, {ok, Le}), + hci_drv:set_filter(State#state.hci, + State#state.saved_filter), + {noreply, State#state { from = undefined, + saved_filter = undefined }}; + _ -> + try hci_api:decode(Evt,Packet) of + ReplyEvent -> + %% io:format("ReplyEvent ~p\n", [ReplyEvent]), + %% fixme: check that event match + gen_server:reply(State#state.from, {ok, ReplyEvent}), + hci_drv:set_filter(State#state.hci, + State#state.saved_filter), + {noreply, State#state { from = undefined, + saved_filter = undefined }} + catch + error:Reason -> + io:format("decode error: ~p ~p\n", + [Reason, erlang:get_stacktrace()]), + if State#state.from =:= undefined -> + {noreply, State}; + true -> + hci_drv:set_filter(State#state.hci, + State#state.saved_filter), + gen_server:reply(State#state.from, {error, Reason}), + {noreply, State#state { from = undefined, + saved_filter = undefined }} + end + end + end; +handle_info(_Info, State) -> + io:format("Got info: ~p\n", [_Info]), + {noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any +%% necessary cleaning up. When it returns, the gen_server terminates +%% with Reason. The return value is ignored. +%% +%% @spec terminate(Reason, State) -> void() +%% @end +%%-------------------------------------------------------------------- +terminate(_Reason, _State) -> + ok. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Convert process state when code is changed +%% +%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} +%% @end +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + +cname(<<0,_/binary>>) -> []; +cname(<<C,Cs/binary>>) -> [C|cname(Cs)]; +cname(<<>>) -> []. + + +do_reply(Binary, State) -> + case State#state.current_decode of + undefined -> + hci_drv:set_filter(State#state.hci, State#state.saved_filter), + gen_server:reply(State#state.from, {ok, Binary}), + State#state { from=undefined, saved_filter=undefined }; + F when is_function(F,1) -> + Decoded = F(Binary), + gen_server:reply(State#state.from, {ok, Decoded}), + hci_drv:set_filter(State#state.hci, State#state.saved_filter), + State#state { from = undefined, + current_event = undefined, + current_opcode = undefined, + current_decode = undefined, + saved_filter = undefined + } + end. diff --git a/deps/bt/src/hci_test.erl b/deps/bt/src/hci_test.erl new file mode 100644 index 0000000..015e98f --- /dev/null +++ b/deps/bt/src/hci_test.erl @@ -0,0 +1,48 @@ +%%% @author Tony Rogvall <tony@up13> +%%% @copyright (C) 2015, Tony Rogvall +%%% @doc +%%% Testing HCI socket +%%% @end +%%% Created : 14 Apr 2015 by Tony Rogvall <tony@up13> + +-module(hci_test). + +-compile(export_all). +-include("hci_api.hrl"). + +inquiry() -> + with_socket(0, fun(S) -> inquiry(S) end). + +pinquiry() -> + with_socket(0, fun(S) -> pinquiry(S) end). + +%% scan for 10*1.28 seconds, wait for max 5 replies +inquiry(Hci) -> + Lap = <<16#33,16#8b,16#9e>>, + hci_api:inquiry(Hci, Lap, 10, 5). + +%% scan for 10*1.28 seconds, wait for max 5 replies +pinquiry(Hci) -> + Lap = <<16#33,16#8b,16#9e>>, + Max = 100, Min = 50, + hci_api:periodic_inquiry(Hci, Max, Min, Lap, 10, 2). + + +local_name() -> local_name(0). +local_name(DevID) -> + with_socket(DevID, fun(S) -> hci_api:read_local_name(S) end). + + + +with_socket(DevID, Fun) -> + {ok,S} = hci_socket:open(DevID), + %% hci_socket:debug(S, debug), + try Fun(S) of + Result -> + hci_socket:close(S), + Result + catch + error:Error -> + hci_socket:close(S), + {error,Error} + end. diff --git a/deps/bt/src/hci_util.erl b/deps/bt/src/hci_util.erl new file mode 100644 index 0000000..626ab19 --- /dev/null +++ b/deps/bt/src/hci_util.erl @@ -0,0 +1,221 @@ +%% +%% Utils used by HCI +%% +-module(hci_util). +-compile(export_all). + +-include("include/hci_drv.hrl"). + + +c_string(Data) when is_binary(Data) -> + c_string_(binary_to_list(Data)); +c_string(Data) when is_list(Data) -> + c_string_(Data). + +c_string_([0|_]) -> []; +c_string_([H|T]) -> [H|c_string_(T)]. + + +find_enum_name(Value, Flags, Default) when is_integer(Value) -> + case lists:keyfind(Value, 2, Flags) of + false -> Default; + {Name, _Value} -> Name + end. + +find_enum_name(Value, Flags) -> + case find_enum_name(Value, Flags, undefined) of + undefined -> erlang:error(badarg); + Name -> Name + end. + +find_enum_value(Name, Flags, Default) when is_atom(Name) -> + case lists:keyfind(atom_to_list(Name), 1, Flags) of + false -> Default; + {_, Value} -> Value + end; +find_enum_value(Name, Flags, Default) when is_list(Name) -> + case lists:keyfind(Name, 1, Flags) of + false -> Default; + {_, Value} -> Value + end. + +find_enum_value(Name, Flags) -> + case find_enum_value(Name, Flags, undefined) of + undefined -> erlang:error(badarg); + Value -> Value + end. + + +format_enum(Value, Kv) -> + case lists:keyfind(Value, 2, Kv) of + false -> ""; + {Name, Value} -> Name + end. + +format_bits(Value, KvList) -> + format_bits_(Value, KvList, []). + +format_bits_(0, _Kv, Acc) -> + string:join(lists:reverse(Acc), ","); +format_bits_(Value, [{Key,Bits}|Kv], Acc) -> + if Value band Bits =:= Bits -> + format_bits_(Value band (bnot Bits),Kv,[Key|Acc]); + true -> + format_bits_(Value, Kv, Acc) + end; +format_bits_(Value, [], Acc) -> + Acc1 = [integer_to_list(Value) | Acc], + string:join(lists:reverse(Acc1), ","). + +format_hci_dev_info(#hci_dev_info { + dev_id = _Dev_id, + name = Name, + bdaddr = BdAddr, + flags = Flags, + type = Type, + features = _Features, + pkt_type = _Pkt_type, + link_policy = _Link_policy, + link_mode = _Link_mode, + acl_mtu = Acl_mtu, + acl_pkts = Acl_pkts, + sco_mtu = Sco_mtu, + sco_pkts = Sco_pkts, + stat = S}) -> + Bus = find_enum_name(Type band 16#0f, + kv_dev_bus(), "UNKNOWN"), + Type1 = find_enum_name((Type band 16#30) bsr 4, + kv_dev_type(), "UNKNOWN"), + io_lib:format( + "~s: Type: ~s Bus: ~s\n" + "\tBD Address: ~s ACL MTU: ~w:~w SCO MTU: ~w:~w\n" + "\t~s\n" + "~s\n", + [Name, Type1, Bus, + bt:format_address(BdAddr), + Acl_mtu, Acl_pkts, Sco_mtu, Sco_pkts, + format_bits(Flags, kv_hci_dev_info_flags()), + format_hci_dev_stats(S)]). + +format_hci_dev_stats(#hci_dev_stats { + err_rx = Err_rx, + err_tx = Err_tx, + cmd_tx = Cmd_tx, + evt_rx = Evt_rx, + acl_tx = Acl_tx, + acl_rx = Acl_rx, + sco_tx = Sco_tx, + sco_rx = Sco_rx, + byte_rx = Byte_rx, + byte_tx = Byte_tx }) -> + io_lib:format( + "\tRX bytes:~w acl:~w sco:~w events:~w errors:~w\n" + "\tTX bytes:~w acl:~w sco:~w commands:~w errors:~w", + [Byte_rx, Acl_rx, Sco_rx, Evt_rx, Err_rx, + Byte_tx, Acl_tx, Sco_tx, Cmd_tx, Err_tx]). + +bdaddr({A,B,C,D,E,F}) -> + {ok,<<F,E,D,C,B,A>>}; +bdaddr(Bin = <<_,_,_,_,_,_>>) -> {ok,Bin}; %% assume binary format is internal! +bdaddr(Addr) when is_list(Addr) -> + case bt:getaddr(Addr) of + {ok,{A,B,C,D,E,F}} -> + {ok,<<F,E,D,C,B,A>>}; + Error -> + Error + end. + +kv_dev_type() -> +[ + {"BR/EDR", ?HCI_BREDR}, + {"AMP", ?HCI_AMP} +]. + +kv_dev_bus() -> +[ + {"VIRTUAL",?HCI_VIRTUAL}, + {"USB", ?HCI_USB }, + {"PCCARD", ?HCI_PCCARD }, + {"UART", ?HCI_UART }, + {"RS232",?HCI_RS232}, + {"PCI", ?HCI_PCI}, + {"SDIO", ?HCI_SDIO} +]. + + +%% scan modes +kv_scan() -> +[ + { "OFF", ?SCAN_DISABLED }, + { "ISCAN", ?SCAN_INQUIRY }, + { "PSCAN", ?SCAN_PAGE }, + { "PISCAN", ?SCAN_PAGE bor ?SCAN_INQUIRY } +]. + +%% HCI device flags +kv_hci_dev_info_flags() -> +[ + { "UP", (1 bsl ?HCI_UP) }, + { "INIT", (1 bsl ?HCI_INIT) }, + { "RUNNING", (1 bsl ?HCI_RUNNING) }, + { "PSCAN", (1 bsl ?HCI_PSCAN) }, + { "ISCAN", (1 bsl ?HCI_ISCAN) }, + { "AUTH", (1 bsl ?HCI_AUTH) }, + { "ENCRYPT", (1 bsl ?HCI_ENCRYPT) }, + { "INQUIRY", (1 bsl ?HCI_INQUIRY) }, + { "RAW", (1 bsl ?HCI_RAW) } +]. + +kv_pkt_ptype() -> +[ + { "DM1", ?HCI_DM1 }, + { "DM3", ?HCI_DM3 }, + { "DM5", ?HCI_DM5 }, + { "DH1", ?HCI_DH1 }, + { "DH3", ?HCI_DH3 }, + { "DH5", ?HCI_DH5 }, + { "HV1", ?HCI_HV1 }, + { "HV2", ?HCI_HV2 }, + { "HV3", ?HCI_HV3 }, + { "2-DH1", ?HCI_2DH1 }, + { "2-DH3", ?HCI_2DH3 }, + { "2-DH5", ?HCI_2DH5 }, + { "3-DH1", ?HCI_3DH1 }, + { "3-DH3", ?HCI_3DH3 }, + { "3-DH5", ?HCI_3DH5 } +]. + +kv_sco_ptype() -> +[ + { "HV1", 16#0001 }, + { "HV2", 16#0002 }, + { "HV3", 16#0004 }, + { "EV3", ?HCI_EV3 }, + { "EV4", ?HCI_EV4 }, + { "EV5", ?HCI_EV5 }, + { "2-EV3", ?HCI_2EV3 }, + { "2-EV5", ?HCI_2EV5 }, + { "3-EV3", ?HCI_3EV3 }, + { "3-EV5", ?HCI_3EV5 } +]. + +kv_link_policy() -> +[ + { "NONE", 0 }, + { "RSWITCH", ?HCI_LP_RSWITCH }, + { "HOLD", ?HCI_LP_HOLD }, + { "SNIFF", ?HCI_LP_SNIFF }, + { "PARK", ?HCI_LP_PARK } +]. + +kv_link_mode() -> +[ + { "NONE", 0 }, + { "ACCEPT", ?HCI_LM_ACCEPT }, + { "MASTER", ?HCI_LM_MASTER }, + { "AUTH", ?HCI_LM_AUTH }, + { "ENCRYPT", ?HCI_LM_ENCRYPT }, + { "TRUSTED", ?HCI_LM_TRUSTED }, + { "RELIABLE", ?HCI_LM_RELIABLE }, + { "SECURE", ?HCI_LM_SECURE } +]. diff --git a/deps/bt/src/l2cap.erl b/deps/bt/src/l2cap.erl new file mode 100644 index 0000000..3bdf96a --- /dev/null +++ b/deps/bt/src/l2cap.erl @@ -0,0 +1,44 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +%%% File : l2cap.erl +%%% Author : Tony Rogvall <tony@pbook.synap.se> +%%% Description : L2CAP wrapper +%%% Created : 19 Jul 2006 by Tony Rogvall <tony@pbook.synap.se> + +-module(l2cap). + +-export([open/2, close/1, send/2, listen/1, accept/1, accept/2]). + +open(Address, Psm) -> + bt_drv:l2cap_open(Address, Psm). + +close(L2CAP) -> + bt_drv:l2cap_close(L2CAP). + +send(L2CAP, Data) -> + bt_drv:l2cap_send(L2CAP, Data). + +listen(Psm) -> + bt_drv:l2cap_listen(Psm). + +accept(ListenRef) -> + bt_drv:l2cap_accept(ListenRef). + +accept(ListenRef,Timeout) when Timeout==infinity; + is_integer(Timeout),Timeout>0 -> + bt_drv:l2cap_accept(ListenRef,Timeout). + diff --git a/deps/bt/src/obex.erl b/deps/bt/src/obex.erl new file mode 100644 index 0000000..c523413 --- /dev/null +++ b/deps/bt/src/obex.erl @@ -0,0 +1,1696 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +%%%------------------------------------------------------------------- +%%% File : obex.erl +%%% Author : Tony Rogvall <tony@PBook.local> +%%% Description : OBEX implemenation +%%% +%%% Created : 19 May 2006 by Tony Rogvall <tony@PBook.local> +%%%------------------------------------------------------------------- +-module(obex). + +-behaviour(gen_server). + +%% API +-export([open/2, open/3, close/1]). +-export([connect/1, connect/2]). +-export([disconnect/1, disconnect/2]). +-export([put_chunk_start/2, put_chunk/2, put_chunk_end/2]). +-export([put/3]). +-export([get_chunk_start/2, get_chunk/1]). +-export([get/2]). +-export([abort/2]). +-export([setpath/2, setpath/3]). +-export([command/2]). +%% SERVER API +-export([server/2, server_link/2]). +-export([server_init/6]). + + +%% Xtra - API +-export([connect_dm_client/1]). +-export([connect_syncml_client/1]). +-export([connect_folder_browsing/1]). +-export([set_cwd/2, make_dir/2]). +-export([list_dir/1]). +-export([capability/1, object_profile/2]). +-export([client/4, client_link/4]). +-export([client/3, client_link/3]). +-export([time_iso/1, utc_time_iso/1]). + +%% Debug +-export([encode_headers/1, decode_headers/1, decode_packet/2]). +-export([size_header/2, size_headers/1]). +-export([encode_header/2]). +-export([make_packets/4, make_get_packets/2]). + +-import(lists, [reverse/1, member/2, foldl/3]). + +-include("../include/obex.hrl"). +-include("../include/uuid.hrl"). +-include("../include/sdp.hrl"). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-ifdef(debug). +-define(dbg(Fmt,As), io:format("~s:~w:" Fmt "\n", [?FILE,?LINE | As])). +-else. +-define(dbg(Fmt,As), ok). +-endif. + + +-record(obex_packet, + { + final, %% 0 or 1 + opcode, %% 7 bit opcode + args, %% extra args (list) + headers, %% headers + callback %% packet reply callback + }). + +-record(s, + { + role, %% client | server + transport, %% transport module + ref, %% transport reference + sref, %% pid - server handle + smod, %% server module + sstate, %% server state + channel, %% channel/port number + id, %% connection ID when connected + %% Host side info + host_mtu = ?OBEX_DEFAULT_MTU, %% our recieve buffer size + host_flags = 0, %% out Flags + host_version = ?OBEX_VERSION, %% our OBEX version + %% Peer side info + peer_mtu = ?OBEX_DEFAULT_MTU, %% peer recive buffer size + peer_flags = 0, %% peer flags + peer_version = 0, %% peer OBEX version + %% Receive buffer + recv_remain = -3, %% data remain to be received + recv_buffer = <<>>, %% input buffer + recv_queue = [], %% input packet queue + %% Send buffer + send_queue = [], %% output packet queue + %% + opts=[] %% obex options + }). + + +-define(size_byte_sequence(Value), + if is_binary((Value)) -> size((Value)); + is_list((Value)) -> length((Value)) + end). + +-define(size_unicode(Value), + if (Value) == "" -> 0; + true -> 2*(length((Value))+1) + end). + +%% Note: The length field includes it self and the tag! +-define(enc_byte_sequence(ID, Value), + if is_binary((Value)) -> + [<<(ID):8, (size((Value))+3):16>>, Value]; + is_list((Value)) -> + [<<(ID):8, (length((Value))+3):16>>, Value] + end). + +%% For null terminate byte-ascii sequences +-define(enc_null_sequence(ID, Value), + if is_binary((Value)) -> + [<<(ID):8, (size((Value))+4):16>>, Value, 0]; + is_list((Value)) -> + [<<(ID):8, (length((Value))+4):16>>, Value, 0] + end). + +%%==================================================================== +%% API +%%==================================================================== +open(Address, Channel) -> + open(Address, Channel, [{transport,rfcomm}]). + +open(Address, Channel, Opts) -> + client_link(Address, Channel, Opts). + +close(Obex) -> + gen_server:call(Obex, close). + +%% +%% OBEX CONNECT +%% +connect(Obex) -> + connect(Obex, []). + +connect(Obex, Headers) -> + gen_server:call(Obex, {connect,Headers}, 10000). + +%% +%% OBEX DISCONNECT +%% +disconnect(Obex) -> + disconnect(Obex, []). + +disconnect(Obex, Headers) -> + gen_server:call(Obex, {disconnect,Headers}, 10000). + +%% +%% OBEX PUT +%% +put_chunk_start(Obex, Headers) -> + gen_server:call(Obex, {put_chunk_start, Headers}, 10000). +put_chunk(Obex, Data) -> + gen_server:call(Obex, {put_chunk, Data}, 10000). +put_chunk_end(Obex, Data) -> + gen_server:call(Obex, {put_chunk_end, Data}, 10000). + +put(Obex, Headers, Body) -> + case put_chunk_start(Obex, Headers) of + continue -> + put_chunk_end(Obex,Body); + Error -> Error + end. + +%% +%% OBEX GET +%% +get_chunk_start(Obex, Headers) -> + gen_server:call(Obex, {get_chunk_start, Headers}, 10000). +get_chunk(Obex) -> + gen_server:call(Obex, get_chunk, 10000). + +get(Obex, Headers) -> + case get_chunk_start(Obex, Headers) of + {continue,Hs} -> + get_continue(Obex,[Hs]); + {ok,Hs} -> get_reply([Hs]); + Error -> Error + end. + +get_continue(Obex, Acc) -> + case get_chunk(Obex) of + {continue,Hs} -> get_continue(Obex,[Hs|Acc]); + {ok,Hs} -> get_reply([Hs|Acc]); + Error -> Error + end. + +%% Generate a sorted and useful combined reply +get_reply([]) -> + {ok,[],<<>>}; +get_reply([Hs|HsList]) -> + get_reply(reverse(Hs),HsList,[],[]). + +get_reply([],[],HAcc,BAcc) -> + {ok,reverse(HAcc),list_to_binary(BAcc)}; +get_reply([{bodyEnd,B}|Hs],HsList,HAcc,BAcc) -> + get_reply(Hs, HsList, HAcc, [B | BAcc]); +get_reply([{body,B}|Hs],HsList,HAcc,BAcc) -> + get_reply(Hs, HsList, HAcc, [B | BAcc]); +get_reply([{H,V}|Hs],HsList,HAcc,BAcc) -> + get_reply(Hs, HsList, [{H,V}|HAcc], BAcc); +get_reply([], [Hs|HsList], HAcc, BAcc) -> + get_reply(reverse(Hs), HsList, HAcc, BAcc). + +%% +%% OBEX ABORT +%% +abort(Obex, Headers) -> + gen_server:call(Obex, {abort,Headers}, 10000). + +%% +%% OBEX SETPATH +%% +setpath(Obex,Hs) -> + setpath(Obex,[],Hs). + +setpath(Obex,Opts,Hs) -> + gen_server:call(Obex, {setpath,Opts,Hs}, 10000). + +%% +%% COMMAND +%% +command(Obex, Hs) -> + gen_server:call(Obex, {command, Hs}, 10000). + +%% +%% UTIL COMMANDS +%% + +%% Special - simpified connectes +connect_dm_client(Obex) -> + connect(Obex, [{target,?TARGET_SYNCML_DM}]). + +connect_syncml_client(Obex) -> + connect(Obex, [{target,?TARGET_SYNCML_SYNC}]). + +connect_folder_browsing(Obex) -> + connect(Obex, [{target,?TARGET_FOLDER_BROWSING}]). + +%% requires - connect_folder_browsing +set_cwd(Obex, "..") -> + setpath(Obex,[backup,dont_create],[]); +set_cwd(Obex, Dir) -> + setpath(Obex,[dont_create],[{name,Dir}]). + +%% both connect_folder_browsing and file transfer +make_dir(Obex, Dir) -> + setpath(Obex,[],[{name,Dir}]). + +%% requires - connect_folder_browsing +list_dir(Obex) -> + case get(Obex, [{type,"x-obex/folder-listing"}]) of + {ok,_Hs,Bin} -> + makeup:string(Bin); + Error -> + Error + end. + +%% requires - connect [] +capability(Obex) -> + get(Obex, [{type,"x-obex/capability"}]). + +%% requires - connect [] +object_profile(Obex, MimeType) -> + get(Obex, [{type,"x-obex/object-profile"}, + {name,MimeType}]). + +%% +%% START Functions +%% +client(Address, Channel, Opts) -> + gen_server:start(?MODULE, [Address,Channel,Opts], []). + +client(Name, Address, Channel, Opts) -> + gen_server:start({local,Name},?MODULE,[Address,Channel,Opts],[]). + +client_link(Address, Channel, Opts) -> + gen_server:start_link(?MODULE, [Address,Channel,Opts], []). + +client_link(Name, Address, Channel, Opts) -> + gen_server:start_link({local,Name},?MODULE,[Address,Channel,Opts],[]). + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% Description: Initiates the server +%%-------------------------------------------------------------------- +init([Address,Channel,Opts]) -> + case transport(Opts, Address) of + undefined -> {stop, {error, unknown_transport}}; + Transport -> + Mod = transport_module(Transport), + S0 = #s { role = client, + transport = Mod, + host_version = ?OBEX_VERSION, + host_mtu = getopt(mtu,Opts,?OBEX_DEFAULT_MTU), + host_flags = getopt(mtu,Opts,0), + opts = Opts + }, + TransOpts = getopt(transport_options, Opts, [binary]), + case catch Mod:open(Address,Channel,TransOpts) of + {ok,Ref} -> + {ok, S0#s{ ref=Ref }}; + {'EXIT',Reason} -> + {stop, {error, Reason}}; + Error -> + {stop, Error} + end + end. + +%%-------------------------------------------------------------------- +%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, State} +%% Description: Handling call messages +%%-------------------------------------------------------------------- + +handle_call({connect,Hs},From,S) -> + Args = [S#s.host_version, S#s.host_flags, S#s.host_mtu], + P = #obex_packet { final = 1, + opcode = connect, + args = Args, + headers = Hs, + callback = fun(S0,#obex_packet { opcode={ok,What}, + headers=Hs1, + args=[Version,Flags1,Mtu] + }) -> + %% Keep track on connectionID + %% this simplifies alot... + ID=getopt(connectionID,Hs1), + gen_server:reply(From, {ok,What}), + {noreply,S0#s { id = ID, + peer_mtu = Mtu, + peer_flags=Flags1, + peer_version = Version }}; + (S0,#obex_packet { opcode=Error }) -> + gen_server:reply(From, Error), + {noreply, S0} + end + }, + send_packet(P, S); + +handle_call({disconnect,Hs}, From, S) -> + P = #obex_packet { final = 1, + opcode = disconnect, + headers = prepare_headers(Hs,S), + callback = fun(S0,#obex_packet { opcode=Reply }) -> + gen_server:reply(From, Reply), + {noreply,S0#s { id=undefined }} + end + }, + send_packet(P, S); + +handle_call({abort,Hs}, From, S) -> + P = #obex_packet { final = 1, + opcode = abort, + headers = prepare_headers(Hs,S), %% needed? + callback = fun(S0,#obex_packet { opcode=Reply }) -> + gen_server:reply(From, Reply), + {noreply,S0} + end + }, + send_packet(P, S); + +handle_call({get_chunk_start,Hs}, From, S) -> + Hs1 = prepare_headers(Hs, S), + handle_get_chunk_start(Hs1, From, S); + +handle_call(get_chunk, From, S) -> + handle_get_chunk(From, S); + +handle_call({put_chunk_start,Hs}, From, S) -> + Hs1 = prepare_headers(Hs, S), + handle_put_chunk_start(Hs1, From, S); + +handle_call({put_chunk,Data}, From, S) -> + handle_put_chunk(Data, From, S); + +handle_call({put_chunk_end,Data}, From, S) -> + handle_put_chunk_end(Data, From, S); + +handle_call({setpath,Opts,Hs}, From, S) -> + %% MUST FIT in one packet + Constants = 0, %% reserved MUST be 0 (version=1.2) + Flags = case member(backup, Opts) of + true -> 16#01; + false -> 16#00 + end bor + case member(dont_create, Opts) of + true -> 16#02; + false -> 16#00 + end, + P = #obex_packet { final = 1, + opcode = setpath, + args = [Flags, Constants], + headers = prepare_headers(Hs, S), + callback = fun(S0,#obex_packet { opcode=Reply }) -> + gen_server:reply(From, Reply), + {noreply,S0} + end + }, + send_packet(P, S); + +handle_call({command,Hs}, From, S) -> + P = #obex_packet { final = 1, + opcode = command, + headers = prepare_headers(Hs, S), + callback = fun (S0,_) -> + gen_server:reply(From, ok), + {noreply,S0} + end + }, + send_packet(P, S); + +handle_call(close, From, S) -> + if S#s.ref == undefined -> + {stop, normal, ok, S}; + true -> + Callback = fun(S0,_) -> + gen_server:reply(From, ok), + ?dbg("Transport close: ~w\n", [S#s.ref]), + (S#s.transport):close(S#s.ref), + {stop, normal, S0#s { ref=undefined }} + end, + P = #obex_packet { final = 1, + callback = Callback, + opcode = undefined, %% sync packet + headers = [] }, + send_packet(P, S) + end; + +handle_call(_Request, _From, S) -> + {reply, {error, bad_call}, S}. + +%%-------------------------------------------------------------------- +%% Function: handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling cast messages +%%-------------------------------------------------------------------- +handle_cast(_Msg, S) -> + {noreply, S}. + +%%-------------------------------------------------------------------- +%% Function: handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling all non call/cast messages +%%-------------------------------------------------------------------- + +handle_info({rfcomm,Ref,{data,Data}}, S) when S#s.ref == Ref -> + ?dbg("RFCOMM: Packet received: ~p\n", [Data]), + recv(Data, S); +handle_info({tcp,Ref,Data}, S) when S#s.ref == Ref -> + ?dbg("TCP: Packet received: ~p\n", [Data]), + recv(Data, S); +handle_info({rfcomm,Ref,closed}, S) when S#s.ref == Ref -> + ?dbg("RFCOMM: closed\n", []), + recv_closed(S); +handle_info({tcp_closed,Ref}, S) when S#s.ref == Ref -> + ?dbg("TCP: closed\n", []), + recv_closed(S); +handle_info(_Info, S) -> + ?dbg("Unhandled INFO ~p\n", [_Info]), + {noreply, S}. + +%%-------------------------------------------------------------------- +%% Function: terminate(Reason, State) -> void() +%% Description: This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any necessary +%% cleaning up. When it returns, the gen_server terminates with Reason. +%% The return value is ignored. +%%-------------------------------------------------------------------- +terminate(_Reason, S) -> + if S#s.ref == undefined -> + ok; + true -> + ?dbg("Transport close: ~w\n", [S#s.ref]), + (S#s.transport):close(S#s.ref) + end. + + +%%-------------------------------------------------------------------- +%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} +%% Description: Convert process state when code is changed +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +%% Get transport from options +transport(Opts) -> + transport(Opts, undefined). + +transport(Opts, Address) -> + case getopt(transport, Opts) of + undefined -> + if Address == undefined -> rfcomm; + true -> + case inet_parse:address(Address) of + {ok, _} -> tcp; + _ -> + case bt_util:getaddr(Address) of + {ok,_} -> rfcomm; + _ -> + case inet_parse:domain(Address) of + true -> tcp; + _ -> undefined + end + end + end + end; + Transport when is_atom(Transport) -> Transport; + _ -> undefined + end. + +transport_module(rfcomm) -> obex_rfcomm; +transport_module(tcp) -> obex_tcp; +transport_module(T) -> list_to_atom("obex_" ++ atom_to_list(T)). + +server_link(Channel, Opts) -> + case transport(Opts) of + undefined -> + {error, unknown_transport}; + Transport -> + Mod = transport_module(Transport), + Mod:server_link(Channel, Opts) + end. + +server(Channel, Opts) -> + case transport(Opts) of + undefined -> + {error, unknown_transport}; + Transport -> + Mod = transport_module(Transport), + Mod:server(Channel, Opts) + end. + + +%% A new connection from Transport backend +server_init(SrvHandle, Ref, _Address, Channel, Transport, Opts) -> + ?dbg("obex [~w] Accept from ~w on channel=~w\n", + [Transport,_Address,Channel]), + Wait = #obex_packet { final = 1, + opcode = wait, + callback = fun handle_connect/2 }, + S = #s { role = server, + sref = SrvHandle, + transport = Transport, + ref = Ref, + channel = Channel, + host_mtu = getopt(mtu,Opts,?OBEX_DEFAULT_MTU), + host_flags = getopt(flags,Opts,0), + host_version = ?OBEX_VERSION, + opts = Opts, + send_queue = [Wait] + }, + gen_server:enter_loop(?MODULE, [], S). + +%% handle connect request +handle_connect(S0,#obex_packet { opcode=connect, + headers=Hs, + args=[Version,Flags,Mtu] }) -> + ?dbg("handle_connect: version=~w, flags=~w, mtu=~w, hs=~w", + [Version,Flags,Mtu,Hs]), + S = S0#s { peer_version=Version, peer_mtu=Mtu,peer_flags=Flags }, + case lists:keysearch(target, 1, Hs) of + false -> + handle_connect_target(S, undefined, obex_generic); + {value,{_,Target}} -> + case (S#s.transport):lookup(S#s.sref, Target) of + {error, not_found} -> + Packet = #obex_packet { final = 1, + opcode = {error,not_found}, + headers = [], + callback = fun handle_connect/2 }, + send_packet(Packet, S); + {ok,Module} -> + handle_connect_target(S, Target, Module) + end + end; +handle_connect(S,P=#obex_packet { opcode=_Opcode }) -> + ?dbg("handle_connect: opcode ~w", [_Opcode]), + TS = obex_generic:init(S#s.sref, S#s.opts, S#s.peer_mtu), + handle_request(S#s { smod=obex_generic, sstate=TS},P). + +%% +%% Connected to target handled by Mod +%% +handle_connect_target(S, Target, Mod) -> + TS = Mod:init(S#s.sref, S#s.opts, S#s.peer_mtu), + case target_connect(Target, S#s { smod=Mod, sstate=TS}) of + {reply,{ok,Hs}, S1} -> + Packet = #obex_packet { final = 1, + opcode = connect_response, + args=[S#s.host_version, + S#s.host_flags, + S#s.host_mtu], + headers = Hs, + callback = fun handle_request/2 }, + send_packet(Packet, S1); + {reply,Error,S1} -> + Packet = #obex_packet { final = 1, + opcode = Error, + headers = [], + callback = fun handle_request/2 }, + send_packet(Packet, S1) + end. + +%% +%% Collect ALL get headers before doing the target callback +%% +next_get(HsAcc) -> + fun(S, #obex_packet { final=0, opcode=get, headers=Hs }) -> + ?dbg("GET continue request ~p\n", [Hs]), + Packet = #obex_packet { final=0, opcode=continue, headers=[], + callback=next_get(HsAcc++Hs) }, + send_packet(Packet, S); + (S, P=#obex_packet { final=1, opcode=get, headers=Hs }) -> + handle_request(S,P#obex_packet { headers=HsAcc++Hs }); + (S, _P=#obex_packet { final=1, opcode=abort }) -> + Packet = #obex_packet { final=0, opcode={ok,success}, headers=[], + callback=fun handle_request/2 }, + send_packet(Packet, S); + (S, _P) -> + ?dbg("Got ~p while collecting GET headers",[_P#obex_packet.opcode]), + Packet = #obex_packet { final=0, opcode={error,conflict}, headers=[], + callback=fun handle_request/2 }, + send_packet(Packet, S) + end. +%% +%% Collect ALL put headers before doing the target callback +%% +next_put(HsAcc) -> + fun(S, #obex_packet { final=0, opcode=put, headers=Hs }) -> + ?dbg("PUT continue request ~p\n", [Hs]), + continue_put(S,Hs,HsAcc); + (S, #obex_packet { final=1, opcode=put, headers=Hs }) -> + final_put(S,Hs,HsAcc); + (S, _P=#obex_packet { final=1, opcode=abort }) -> + Packet = #obex_packet { final=0, opcode={ok,success}, headers=[], + callback=fun handle_request/2 }, + send_packet(Packet, S); + (S, _P) -> + ?dbg("Got ~p while collecting PUT headers",[_P#obex_packet.opcode]), + Packet = #obex_packet { final=0, opcode={error,conflict}, + headers=[], + callback=fun handle_request/2 }, + send_packet(Packet, S) + end. + + +continue_put(S,Hs,HsAcc) -> + case split_headers(Hs) of + {true,Hs1,Data} -> %% bodyEnd in non final? trailer? + ?dbg("got bodyEnd in continue put packet!", []), + {reply,Reply,S1} = target_put(HsAcc++Hs1, Data, S), + Packet = #obex_packet { final=1, opcode=Reply, + headers=[], + callback=fun handle_request/2 + }, + send_packet(Packet, S1); + {false,Hs1,<<>>} -> %% no data no end + Packet = #obex_packet { final=1, opcode=continue, + headers=[], + callback=next_put(HsAcc++Hs1) + }, + send_packet(Packet,S); + {false,Hs1,Data} -> %% data found not end + {reply,Reply,S1} = target_put(HsAcc++Hs1, Data, S), + Packet = #obex_packet { final=1, opcode=Reply, + headers=[], + callback=fun handle_request/2 }, + send_packet(Packet,S1) + end. + +final_put(S,Hs,HsAcc) -> + case split_headers(Hs) of + {true,Hs1,Data} -> + case target_put(HsAcc++Hs1, Data, S) of + {reply,continue,S1} -> + {reply,Reply,S2} = target_put([],<<>>,S1), + Packet = #obex_packet { final=1, opcode=Reply, + headers=[], + callback=fun handle_request/2 + }, + send_packet(Packet,S2); + {reply,Reply,S1} -> + Packet = #obex_packet { final=1, opcode=Reply, + headers=[], + callback=fun handle_request/2 + }, + send_packet(Packet,S1) + end; + {false,_Hs1,_Data} -> + ?dbg("missing bodyEnd in final put packet!", []), + Packet = #obex_packet { final=1, opcode={error,bad_request}, + headers=[], + callback=fun handle_request/2}, + send_packet(Packet, S) + end. + +%% +%% Send GET reply packets +%% +send_reply_packets([], _End, S) -> + ?dbg("send_reply_packets: NO packets to send???",[]), + {noreply, S}; +send_reply_packets([P], End, S) -> + P1 = if End == true -> + P#obex_packet { final=1,opcode={ok,success}, + callback=fun handle_request/2}; + true -> + P#obex_packet { final=1, opcode=continue, + callback=fun handle_request/2} + end, + send_packet(P1, S); +send_reply_packets([P|Ps], End, S) -> + Callback = fun(S0,_Pi=#obex_packet { opcode=get }) -> %% ignoring final!!! + send_reply_packets(Ps, End, S0); + (S0,Pi) -> + handle_request(S0, Pi) + end, + P1 = P#obex_packet { final=1, opcode=continue, callback=Callback }, + send_packet(P1, S). + +%% +%% Handle GET +%% +handle_request(S,#obex_packet { final=1, opcode=get, headers=Hs }) -> + ?dbg("GET final=1 request ~p\n", [Hs]), + case target_get(Hs, S) of + {reply,{continue,ReplyHs,Data},S1} -> + Ps = make_packets(ReplyHs, Data, false, S1#s.peer_mtu), + send_reply_packets(Ps, false, S1); + {reply,{ok,ReplyHs,Data},S1} -> + Ps = make_packets(ReplyHs, Data, true, S1#s.peer_mtu), + send_reply_packets(Ps, true, S1); + {reply,Error,S1} -> + Packet = #obex_packet { final=1, + opcode=Error, + headers=[], + callback=fun handle_request/2}, + send_packet(Packet, S1) + end; +handle_request(S,#obex_packet { final=0, opcode=get, headers=Hs }) -> + ?dbg("GET continue request ~p\n", [Hs]), + Packet = #obex_packet { final=0, opcode=continue, headers=[], + callback=next_get(Hs) }, + send_packet(Packet, S); +%% +%% Handle PUT (collect headers until we find body or bodyEnd) +%% +handle_request(S,#obex_packet { final=0, opcode=put, headers=Hs}) -> + ?dbg("PUT continue request ~p\n", [Hs]), + continue_put(S,Hs,[]); +handle_request(S,#obex_packet { final=1, opcode=put, headers=Hs}) -> + ?dbg("PUT final request ~p\n", [Hs]), + final_put(S,Hs,[]); + +%% +%% Handle SETPATH +%% +handle_request(S,#obex_packet { final=1, opcode=setpath, args=Args, headers=Hs }) -> + ?dbg("SETPATH hs=~p\n", [Hs]), + {reply,Reply,S1} = target_setpath(Hs,Args,S), + Packet = #obex_packet { final=1, opcode=Reply, headers=[], + callback=fun handle_request/2 }, + send_packet(Packet, S1); +%% +%% ABORT +%% +handle_request(S,#obex_packet { final=1, opcode=abort, headers=Hs }) -> + ?dbg("ABORT hs=~p\n", [Hs]), + {reply,Reply,S1} = target_abort(Hs, S), + Packet = #obex_packet { final=1, opcode=Reply, headers=[], + callback=fun handle_request/2 }, + send_packet(Packet, S1); +%% +%% DISCONNECT +%% +handle_request(S,#obex_packet { final=1, opcode=disconnect, headers=Hs }) -> + ?dbg("DISCONNECT hs=~p\n", [Hs]), + {reply,Reply,S1} = target_disconnect(Hs, S), + Packet = #obex_packet { final=1, opcode=Reply, headers=[], + callback=fun handle_request/2 }, + send_packet(Packet, S1); +%% +%% COMMAND +%% +handle_request(S,#obex_packet { final=1, opcode=command, headers=Hs }) -> + ?dbg("COMMAND hs=~p\n", [Hs]), + {reply,Reply,S1} = target_command(Hs, S), + Packet = #obex_packet { final=1, opcode=Reply, headers=[], + callback=fun handle_request/2 }, + send_packet(Packet, S1); +%% +%% OTHER packets (final=0 strange opcodes etc) +%% +handle_request(S,_P) -> + ?dbg("OTHER packet=~p", [_P]), + Packet = #obex_packet { final=1, opcode={error,bad_request}, headers=[], + callback=fun handle_request/2 }, + send_packet(Packet, S). + + +%% Wrap calls to target +target_connect(Target, S) -> + {reply,Reply,TS} = (S#s.smod):handle_connect(Target,S#s.sstate), + {reply,Reply,S#s { sstate = TS}}. + +target_disconnect(Hs, S) -> + {reply,Reply,TS} = (S#s.smod):handle_disconnect(Hs,S#s.sstate), + {reply,Reply,S#s { sstate = TS}}. + +target_get(Hs, S) -> + {reply,Reply,TS} = (S#s.smod):handle_get(Hs,S#s.sstate), + {reply,Reply,S#s { sstate = TS}}. + +target_put(Hs, Data, S) -> + {reply,Reply,TS} = (S#s.smod):handle_put(Hs,Data,S#s.sstate), + {reply,Reply,S#s { sstate = TS}}. + +target_abort(Hs, S) -> + {reply,Reply,TS} = (S#s.smod):handle_abort(Hs,S#s.sstate), + {reply,Reply,S#s { sstate = TS}}. + +target_setpath(Hs,Args,S) -> + {reply,Reply,TS} = (S#s.smod):handle_setpath(Hs,Args,S#s.sstate), + {reply,Reply,S#s { sstate = TS}}. + +target_command(Hs, S) -> + {reply,Reply,TS} = (S#s.smod):handle_command(Hs,S#s.sstate), + {reply,Reply,S#s { sstate = TS}}. + + +%% update header with connection id +prepare_headers(Hs, S) -> + if S#s.id == undefined -> + Hs; + true -> + [{connectionID,S#s.id}|Hs] + end. + +handle_get_chunk_start(Hs, From, S) -> + Packets = make_get_packets(Hs, S#s.peer_mtu), + send_get_packets(Packets, From, S). + +handle_get_chunk(From, S) -> + send_get_packet_1(#obex_packet { opcode=get, headers=[] }, From, S). + +handle_put_chunk_start(Hs, From, S) -> + Packets = make_packets(Hs, <<>>, false, S#s.peer_mtu), + send_put_packets(Packets, 0, From, S). + +handle_put_chunk(Data, From, S) -> + Packets = make_packets([], Data, false, S#s.peer_mtu), + send_put_packets(Packets, 0, From, S). + +handle_put_chunk_end(Data, From, S) -> + Packets = make_packets([], Data, true, S#s.peer_mtu), + send_put_packets(Packets, 1, From, S). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% send_get_packets: +%% Send the GET header packets util all packets have been sent +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +send_get_packets([], From, S) -> + %% send a empty get packet + send_get_packet_1(#obex_packet { opcode=get, headers=[] }, From, S); +send_get_packets([P], From, S) -> + send_get_packet_1(P, From, S); +send_get_packets([P|Ps], From, S) -> + Callback = fun(S0,_Pi=#obex_packet { final=0, opcode=_Reply}) -> + ?dbg("send_get_packets: input=~p\n", [_Pi]), + send_get_packets(Ps, From, S0); + (S0, _Pi=#obex_packet { final=1, opcode=Reply}) -> + ?dbg("send_get_packets: input=~p\n", [_Pi]), + gen_server:reply(From, Reply), + {noreply,S0} + end, + Pout = P#obex_packet { final=0, callback=Callback}, + send_packet(Pout, S). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% Send the GET final=1 packet +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +send_get_packet_1(P, From, S) -> + Callback = + fun (S0,_P=#obex_packet { final=1,opcode=continue,headers=Hs}) -> + ?dbg("send_get_packet_1: input=~p\n", [_P]), + gen_server:reply(From, {continue,Hs}), + {noreply,S0}; + (S0, _P=#obex_packet { final=1,opcode=Reply,headers=Hs}) -> + ?dbg("send_get_packet_1: input=~p\n", [_P]), + case Reply of + {ok,success} -> + gen_server:reply(From, {ok,Hs}); + Error -> + gen_server:reply(From, Error) + end, + {noreply,S0}; + (S0,_P=#obex_packet { final=0,opcode=_Reply,headers=Hs}) -> + ?dbg("send_get_packet_1: input=~p\n", [_P]), + %% request more request headers !!! + gen_server:reply(From, {more, Hs}), + {noreply,S0} + end, + Packet = P#obex_packet { final= 1, callback = Callback }, + send_packet(Packet, S). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% send_put_packets: +%% Send the PUT packets util all packets have been sent +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +send_put_packets([], _Finally, _From, S) -> + {noreply, S}; +send_put_packets([P], Finally, From, S) -> + Callback = fun(S0,_Pi=#obex_packet { final=0, opcode=Reply}) -> + ?dbg("handle_put: input=~p\n", [_Pi]), + gen_server:reply(From, Reply), + {noreply,S0}; + (S0, _Pi=#obex_packet { final=1, opcode=Reply}) -> + ?dbg("handle_put: input=~p\n", [_Pi]), + gen_server:reply(From, Reply), + {noreply,S0} + end, + Pout = P#obex_packet { opcode=put, final=Finally, callback=Callback}, + send_packet(Pout, S); +send_put_packets([P|Ps], Finally, From, S) -> + Callback = fun(S0,_Pi=#obex_packet { final=0, opcode=_Reply}) -> + ?dbg("handle_put: input=~p\n", [_Pi]), + send_put_packets(Ps, Finally, From, S0); + (S0, _Pi=#obex_packet { final=1, opcode=Reply}) -> + ?dbg("handle_put: input=~p\n", [_Pi]), + gen_server:reply(From, Reply), + {noreply,S0} + end, + Pout = P#obex_packet { opcode=put, final=0, callback=Callback}, + send_packet(Pout, S). + +recv_closed(S) when S#s.role == server -> + ?dbg("server connection closed (normal)\n", []), + {stop, normal, S}; +recv_closed(S) when S#s.role == client -> + ?dbg("client connection closed\n", []), + {noreply, S#s { ref=undefined, id=undefined }}. + +recv(Data, S) -> + Buf = recv_concat(Data, S#s.recv_buffer), + BufLen = size(Buf), + Remain = S#s.recv_remain, + if Remain < 0 -> + if BufLen + Remain < 0 -> + {noreply, S#s { recv_remain=BufLen+Remain, + recv_buffer = Buf }}; + true -> + <<_OP:8,PacketLen:16,_/binary>> = Buf, + if BufLen < PacketLen -> + {noreply, S#s { recv_remain=PacketLen-BufLen, + recv_buffer=Buf }}; + BufLen >= PacketLen -> + <<Packet:PacketLen/binary,Buf1/binary>> = Buf, + recv_packet(Packet, Buf1, S#s { recv_remain=0, + recv_buffer=(<<>>) + }) + end + end; + Remain > 0 -> + if BufLen < Remain -> + {noreply, S#s { recv_remain=Remain - BufLen, + recv_buffer=Buf }}; + true -> + <<_OP:8,PacketLen:16,_/binary>> = Buf, + <<Packet:PacketLen/binary, Buf1/binary>> = Buf, + recv_packet(Packet, Buf1, S#s { recv_remain=0, + recv_buffer=(<<>>) }) + end + end. + +recv_packet(Data, Buf, S) -> + Cmd = case S#s.send_queue of + [P|_] -> P#obex_packet.opcode; + [] -> undefined + end, + Reply = decode_packet(Data, Cmd), + ?dbg("recv_packet: ~p\n", [Reply]), + S1 = push_packets(Buf, S), %% push rest of packet data + case S1#s.send_queue of + [#obex_packet { callback=Callback }|_] -> + %% Note that the callback may send packets, but they + %% are queued after current packet. + case Callback(S1, Reply) of + {noreply, S2} -> + Q = tl(S2#s.send_queue), + send_packet(S2#s { send_queue = Q }); + {stop,Reason,Reply,S2} -> + {stop,Reason,Reply,S2}; + {stop,Reason,S2} -> + {stop,Reason,S2} + end; + [] -> + ?dbg("Reply not expected\n",[]), + {noreply, S1} + end. + +decode_opcode(Code) -> + case Code band 16#7f of + ?OBEX_CMD_CONNECT -> connect; + ?OBEX_CMD_DISCONNECT -> disconnect; + ?OBEX_CMD_PUT -> put; + ?OBEX_CMD_GET -> get; + ?OBEX_CMD_COMMAND -> command; + ?OBEX_CMD_SETPATH -> setpath; + ?OBEX_CMD_ABORT -> abort; + + %% 1x + ?OBEX_RSP_CONTINUE -> continue; + ?OBEX_RSP_SWITCH_PRO -> switch_protocols; + + %% 2x + ?OBEX_RSP_SUCCESS -> {ok,success}; + ?OBEX_RSP_CREATED -> {ok,created}; + ?OBEX_RSP_ACCEPTED -> {ok,accepted}; + ?OBEX_RSP_NO_CONTENT -> {ok,no_content}; + ?OBEX_RSP_RESET_CONTENT -> {ok, reset_content}; + ?OBEX_RSP_PARTIAL_CONTENT -> {ok, partial_content}; + + %% 3x + ?OBEX_RSP_MULTIPLE_CHOICES -> {error,multiple_choices}; + ?OBEX_RSP_MOVED_PERMANENTLY -> {error,moved_permanently}; + ?OBEX_RSP_MOVED_TEMPORARILY -> {error,moved_temporarily}; + ?OBEX_RSP_SEE_OTHER -> {error,see_other}; + ?OBEX_RSP_NOT_MODIFIED -> {error,not_modified}; + ?OBEX_RSP_USE_PROXY -> {error,use_proxy}; + + %% 4x + ?OBEX_RSP_BAD_REQUEST -> {error,bad_request}; + ?OBEX_RSP_UNAUTHORIZED -> {error,unauthorized}; + ?OBEX_RSP_PAYMENT_REQUIRED -> {error,payment_required}; + ?OBEX_RSP_FORBIDDEN -> {error, forbidden}; + ?OBEX_RSP_NOT_FOUND -> {error, not_found}; + ?OBEX_RSP_METHOD_NOT_ALLOWED -> {error,not_allowed}; + ?OBEX_RSP_CONFLICT -> {error, conflict}; + ?OBEX_RSP_GONE -> {error, gone}; + ?OBEX_RSP_LENGTH_REQUIRED -> {error, length_required}; + ?OBEX_RSP_PRECONDITION_FAILED -> {error, precondition_failed}; + ?OBEX_RSP_ENTITY_TOO_LARGE -> {error, entity_too_large}; + ?OBEX_RSP_URL_TOO_LARGE -> {error, url_too_large}; + ?OBEX_RSP_UNSUPPORTED_MEDIA -> {error, unsupported_media}; + + %% + ?OBEX_RSP_INTERNAL_SERVER_ERROR -> {error, internal_server_error}; + ?OBEX_RSP_NOT_IMPLEMENTED -> {error, not_implemented}; + ?OBEX_RSP_BAD_GATEWAY -> {error, bad_gateway}; + ?OBEX_RSP_SERVICE_UNAVAILABLE -> {error, service_unavailable}; + ?OBEX_RSP_GATEWAY_TIMEOUT -> {error, gateway_timeout}; + ?OBEX_RSP_HTTP_VERSION_NOT_SUPPORTED -> {error, http_version_not_supported}; + + %% + ?OBEX_RSP_DATABASE_FULL -> {error, database_full}; + ?OBEX_RSP_DATABASE_LOCKED -> {error, database_locked}; + %% + Code1 -> + case (Code1 bsr 4) of + 2 -> {ok,Code1}; + _ -> {error,Code1} + end + end. + + +encode_opcode(Code) -> + case Code of + %% 0x + connect -> ?OBEX_CMD_CONNECT; + disconnect -> ?OBEX_CMD_DISCONNECT; + put -> ?OBEX_CMD_PUT; + get -> ?OBEX_CMD_GET; + command -> ?OBEX_CMD_COMMAND; + setpath -> ?OBEX_CMD_SETPATH; + abort -> ?OBEX_CMD_ABORT; + %% 1x + continue -> ?OBEX_RSP_CONTINUE; + switch_protocols -> ?OBEX_RSP_SWITCH_PRO; + %% 2x + {ok,success} -> ?OBEX_RSP_SUCCESS; + {ok,created} -> ?OBEX_RSP_CREATED; + {ok,accetped} -> ?OBEX_RSP_ACCEPTED; + {ok,no_content} -> ?OBEX_RSP_NO_CONTENT; + %% 3x + {error,multiple_choices} -> ?OBEX_RSP_MULTIPLE_CHOICES; + {error,moved_permanently} -> ?OBEX_RSP_MOVED_PERMANENTLY; + {error,moved_temporarily} -> ?OBEX_RSP_MOVED_TEMPORARILY; + {error,see_other} -> ?OBEX_RSP_SEE_OTHER; + {error,not_modified} -> ?OBEX_RSP_NOT_MODIFIED; + {error,use_proxy} -> ?OBEX_RSP_USE_PROXY; + + %% 4x + {error,bad_request} -> ?OBEX_RSP_BAD_REQUEST; + {error,unauthorized} -> ?OBEX_RSP_UNAUTHORIZED; + {error,payment_required} -> ?OBEX_RSP_PAYMENT_REQUIRED; + {error,forbidden} -> ?OBEX_RSP_FORBIDDEN; + {error,not_found} -> ?OBEX_RSP_NOT_FOUND; + {error,not_allowed} -> ?OBEX_RSP_METHOD_NOT_ALLOWED; + {error,conflict} -> ?OBEX_RSP_CONFLICT; + {error, gone} -> ?OBEX_RSP_GONE; + {error, length_required} -> ?OBEX_RSP_LENGTH_REQUIRED; + {error, precondition_failed} -> ?OBEX_RSP_PRECONDITION_FAILED; + {error, entity_too_large} -> ?OBEX_RSP_ENTITY_TOO_LARGE; + {error, url_too_large} -> ?OBEX_RSP_URL_TOO_LARGE; + {error, unsupported_media} -> ?OBEX_RSP_UNSUPPORTED_MEDIA; + %% 5x + {error,internal_server_error} -> ?OBEX_RSP_INTERNAL_SERVER_ERROR; + {error,not_implemented} -> ?OBEX_RSP_NOT_IMPLEMENTED; + %% 6x + {error,database_full} -> ?OBEX_RSP_DATABASE_FULL; + {error,database_locked} -> ?OBEX_RSP_DATABASE_LOCKED; + %% + {ok,Code} when Code >= 0, Code =< 15 -> Code; + {error,Code} -> Code + end. + +%% push back extra packet data (ABORT?) +%% (should never happend but why should we not try to support pipelining ...) +push_packets(Buf, S) when size(Buf) < 3 -> + S#s { recv_remain = -3 + size(Buf), recv_buffer = Buf }; +push_packets(Buf = <<_Op:8, PacketLen:16,_>>, S) -> + DataLen = size(Buf), + if DataLen >= PacketLen -> + <<Packet:PacketLen/binary, Buf1/binary>> = Buf, + Qs = S#s.recv_queue ++ [Packet], + push_packets(Buf1, S#s { recv_queue = Qs }); + true -> + S#s { recv_remain = PacketLen-DataLen, + recv_buffer = Buf } + end. + +%% +%% Generate a sequence of packets with +%% Headers body and bodyEnd +%% End == false - Do not generated bodyEnd headers +%% Hs == [] - Only generate body or bodyEnd headers +%% Data == <<>> - No data possible a empty dataEnd is generated +%% +make_packets(Hs, Data, End, Mtu) -> + make_headers(Hs,Mtu-3,Mtu-3,End,Data,[],[]). + +make_headers([{Key,Value}|Hs],Remain,Mtu,End,Data,HsAcc,PsAcc) -> + Sz = size_header(Key,Value), + if Sz =< Remain -> + make_headers(Hs,Remain-Sz,Mtu,End,Data, + [{Key,Value}|HsAcc],PsAcc); + Sz > Mtu -> + io:format("WARNING: SIZE(~p,~p)=~w,MTU=~w\n",[Key,Value,Sz,Mtu]), + if HsAcc == [] -> + P = #obex_packet { headers=[{Key,Value}]}, + make_headers(Hs,Mtu,Mtu,End,Data,[],[P|PsAcc]); + true -> + P1 = #obex_packet { headers=reverse(HsAcc) }, + P2 = #obex_packet { headers=[{Key,Value}]}, + make_headers(Hs,Mtu,Mtu,End,Data,[],[P2,P1|PsAcc]) + end; + true -> + P = #obex_packet { headers=reverse(HsAcc) }, + make_headers(Hs,Mtu-Sz,Mtu,End,Data,[{Key,Value}],[P|PsAcc]) + end; +make_headers([],Remain,Mtu,End,Data,HsAcc,PsAcc) -> + make_data(Data,Remain,Mtu,End,HsAcc,PsAcc). + +make_data(Data,Remain,Mtu,End,HsAcc,PsAcc) -> + Sz = size(Data)+3, %% DataSize + Tag & length + if + Sz =< Remain -> + if End == true -> + HsAcc1 = [{bodyEnd,Data}|HsAcc], + P = #obex_packet { headers=reverse(HsAcc1) }, + reverse([P|PsAcc]); + Sz == 3, HsAcc == [] -> %% No data and No End + reverse(PsAcc); + Sz == 3 -> + P = #obex_packet { headers=reverse(HsAcc) }, + reverse([P|PsAcc]); + true -> %% Data but not Last Chunk + HsAcc1 = [{body,Data}|HsAcc], + P = #obex_packet { headers=reverse(HsAcc1) }, + reverse([P|PsAcc]) + end; + Remain < 4 -> + P = #obex_packet { headers=reverse(HsAcc) }, + make_data(Data,Mtu,Mtu,End,[],[P|PsAcc]); + Sz > Remain -> + Len = Remain - 3, + <<Data1:Len/binary, Data2/binary>> = Data, + HsAcc1 = [{body,Data1} | HsAcc], + P = #obex_packet { headers=reverse(HsAcc1) }, + make_data(Data2,Mtu,Mtu,End,[],[P|PsAcc]) + end. + +%% +%% Create a sequence of GET packets covering +%% the sending of the initial headers +%% +make_get_packets(Hs, Mtu) -> + make_get_headers(Hs,Mtu-3,Mtu-3,[],[]). + +make_get_headers([{Key,Value}|Hs],Remain,Mtu,HsAcc,PsAcc) -> + Sz = size_header(Key,Value), + if Sz =< Remain -> + make_get_headers(Hs,Remain-Sz,Mtu, + [{Key,Value}|HsAcc],PsAcc); + Sz > Mtu -> + io:format("WARNING: SIZE(~p,~p)=~w,MTU=~w\n", [Key,Value,Sz,Mtu]), + if HsAcc == [] -> + P = #obex_packet { opcode=get, headers=[{Key,Value}]}, + make_get_headers(Hs,Mtu,Mtu,[],[P|PsAcc]); + true -> + P1 = #obex_packet { opcode=get, headers=reverse(HsAcc) }, + P2 = #obex_packet { opcode=get, headers=[{Key,Value}]}, + make_get_headers(Hs,Mtu,Mtu,[],[P2,P1|PsAcc]) + end; + true -> + P = #obex_packet { opcode=get, headers=reverse(HsAcc) }, + make_get_headers(Hs,Mtu-Sz,Mtu,[{Key,Value}],[P|PsAcc]) + end; +make_get_headers([],_Remain,_Mtu,[],PsAcc) -> + reverse(PsAcc); +make_get_headers([],_Remain,_Mtu,HsAcc,PsAcc) -> + P = #obex_packet { opcode=get,headers=reverse(HsAcc)}, + reverse([P|PsAcc]). + +%% +%% split headers info {Regular, Body} +%% +split_headers(Hs) -> + split_headers(Hs, [], [], false). + +split_headers([{body,Data}|Hs], Regular, Body, End) -> + split_headers(Hs, Regular, [Data|Body], End); +split_headers([{bodyEnd,Data}|Hs], Regular, Body, _End) -> + split_headers(Hs, Regular, [Data|Body], true); +split_headers([H | Hs], Regular, Body, End) -> + split_headers(Hs, [H|Regular], Body, End); +split_headers([], Regular, Body, End) -> + {End, reverse(Regular), list_to_binary(reverse(Body))}. + +%% +%% Decode packet +%% + +%% 0x - commands +decode_packet(Data = <<Final:1,?OBEX_CMD_CONNECT:7,_:16, + Version:8,Flags:8,MaxPacketLength:16,_/binary>>, _Cmd) -> + #obex_packet { final = Final, + opcode = connect, + args = [Version,Flags,MaxPacketLength], + headers = decode_headers(Data, 7) }; +decode_packet(Data = <<Final:1,?OBEX_CMD_SETPATH:7,_:16, + Flags:8,Constants:8,_/binary>>, _Cmd) -> + #obex_packet { final = Final, + opcode = setpath, + args = [Flags,Constants], + headers = decode_headers(Data, 5) }; +%% 2x - ok (connect reply) +decode_packet(Data = <<Final:1,Code:7,_:16, + Version,Flags,MaxPacketLength:16,_/binary>>, connect) -> + #obex_packet { final=Final, + opcode = decode_opcode(Code), + args = [Version,Flags,MaxPacketLength], + headers = decode_headers(Data, 7)}; +decode_packet(Data = <<Final:1,Code:7,_:16,_/binary>>,_Cmd) -> + #obex_packet { final = Final, + opcode = decode_opcode(Code), + headers = decode_headers(Data, 3) }. + + +recv_concat(Data, []) -> Data; +recv_concat(Data, Buffer) -> <<Buffer/binary, Data/binary>>. + +%% send or buffer a packet +send_packet(_P, S) when S#s.ref == undefined -> + ?dbg("send_packet: transport closed\n", []), + {reply, {error,closed}, S}; +send_packet(P, S) when is_record(P, obex_packet) -> + if S#s.send_queue == [] -> + send_packet(S#s { send_queue = [P]}); + true -> + ?dbg("send_packet: queued ~p\n", [P]), + Q = S#s.send_queue ++ [P], + {noreply, S#s { send_queue = Q }} + end. + +%% send the first packet on the queue +send_packet(S) -> + case S#s.send_queue of + [] -> + {noreply, S}; + [P=#obex_packet { opcode = undefined, callback=Callback } | Q] -> + %% sync / close / packet + Callback(S#s { send_queue=Q} , P); + + [P=#obex_packet { final=Final, opcode=command, callback=Callback, + headers=Hs }| Q] -> + ?dbg("send_packet: ~p\n", [P]), + Data = encode_packet(Final, ?OBEX_CMD_COMMAND,<<>>, Hs, + S#s.peer_mtu), + (S#s.transport):send(S#s.ref, Data), + Callback(S#s { send_queue=Q }, P); + + [_P=#obex_packet { final=Final, opcode=connect, + args=[Version,Flags,MaxPacketLength], + headers=Hs }|_Q] -> + ?dbg("send_packet: ~p\n", [_P]), + Data = encode_packet(Final, ?OBEX_CMD_CONNECT, + <<Version:8,Flags:8,MaxPacketLength:16>>, + Hs, 0), + (S#s.transport):send(S#s.ref, Data), + {noreply,S}; + + %% Synthetic opcode!!! + [_P=#obex_packet { final=Final, opcode=connect_response, + args=[Version,Flags,MaxPacketLength], + headers=Hs }|_Q] -> + ?dbg("send_packet: ~p\n", [_P]), + Data = encode_packet(Final, ?OBEX_RSP_SUCCESS, + <<Version:8,Flags:8,MaxPacketLength:16>>, + Hs, 0), + (S#s.transport):send(S#s.ref, Data), + {noreply,S}; + + [_P=#obex_packet { final=Final, opcode=setpath, + args=[Flags,Constants], + headers=Hs } | _Q] -> + ?dbg("send_packet: ~p\n", [_P]), + Data = encode_packet(Final, ?OBEX_CMD_SETPATH, + <<Flags:8, Constants:8>>, + Hs, S#s.peer_mtu), + (S#s.transport):send(S#s.ref, Data), + {noreply,S}; + + + + [_P=#obex_packet { final=Final, opcode=Op, + args=undefined, + headers=Hs } | _Q] -> + ?dbg("send_packet: ~p\n", [_P]), + OpCode = encode_opcode(Op), + Data = encode_packet(Final, OpCode, <<>>, + Hs, S#s.peer_mtu), + (S#s.transport):send(S#s.ref, Data), + {noreply,S} + end. + + + +getopt(Key, Opts) -> + getopt(Key, Opts, undefined). + +getopt(Key, Opts, Default) -> + case lists:keysearch(Key, 1, Opts) of + {value, {_,Value}} -> + Value; + false -> + Default + end. + +decode_headers(Bin) -> + decode_headers(Bin, 0). + +decode_headers(Bin, Offset) -> + ?dbg("decode_headers: offset=~p\n", [Offset]), + decode_headers(Bin, Offset, []). + +decode_headers(Bin, Offset, Hs) when Offset >= size(Bin) -> + reverse(Hs); +decode_headers(Bin, Offset, Hs) -> + ?dbg("decode_headers: offset=~p\n", [Offset]), + case Bin of + <<_:Offset/binary,?OBEX_V0:2, ID:6,HeaderLen:16,_/binary>> -> + Offset1 = Offset+3, + ValueLen = HeaderLen - 3, %% not counting tag and length + <<_:Offset1/binary, Value:ValueLen/binary, _/binary>> = Bin, + Offset2 = Offset1+ValueLen, + Val = decode_unicode(Value, 0, []), + case ((?OBEX_V0 bsl 6) bor ID) of + ?OBEX_HDR_NAME -> + ?dbg("header: name=~p\n", [Val]), + decode_headers(Bin,Offset2, + [{name,Val}|Hs]); + ?OBEX_HDR_DESCRIPTION -> + ?dbg("header: desciption=~p\n", [Val]), + decode_headers(Bin,Offset2, + [{description,Val}|Hs]); + ID1 -> + ?dbg("header: ~w=~p\n", [ID1,Val]), + decode_headers(Bin,Offset2, + [{ID1,Val}|Hs]) + end; + + <<_:Offset/binary,?OBEX_Vn:2,ID:6,HeaderLen:16,_/binary>> -> + Offset1 = Offset+3, + ValueLen = HeaderLen - 3, + <<_:Offset1/binary,Value:ValueLen/binary,_/binary>> = Bin, + Offset2 = Offset1+ValueLen, + case ((?OBEX_Vn bsl 6) bor ID) of + ?OBEX_HDR_TYPE -> + Val = decode_ascii(Value, 0, []), + ?dbg("header: type=~p\n", [Val]), + decode_headers(Bin,Offset2,[{type,Val}|Hs]); + ?OBEX_HDR_TIME -> + Val = binary_to_list(Value), + ?dbg("header: time=~p\n", [Val]), + decode_headers(Bin,Offset2,[{time,Val}|Hs]); + ?OBEX_HDR_TARGET -> + Val = binary_to_list(Value), + ?dbg("header: target=~p\n", [Val]), + decode_headers(Bin,Offset2,[{target,Val}|Hs]); + ?OBEX_HDR_HTTP -> + Val = binary_to_list(Value), + ?dbg("header: http=~p\n", [Val]), + decode_headers(Bin,Offset2,[{http,Val}|Hs]); + ?OBEX_HDR_BODY -> + ?dbg("header: body=~p\n", [Value]), + decode_headers(Bin,Offset2,[{body,Value}|Hs]); + ?OBEX_HDR_BODY_END -> + ?dbg("header: bodyEnd=~p\n", [Value]), + decode_headers(Bin,Offset2,[{bodyEnd,Value}|Hs]); + ?OBEX_HDR_WHO -> + Val = binary_to_list(Value), + ?dbg("header: who=~p\n", [Val]), + decode_headers(Bin,Offset2,[{who,Val}|Hs]); + + ?OBEX_HDR_APPARAM -> + ?dbg("header: appparam=~p\n", [Value]), + decode_headers(Bin,Offset2, + [{appParameters,Value}|Hs]); + + ?OBEX_HDR_AUTHCHAL -> + ?dbg("header: authChal=~p\n", [Value]), + decode_headers(Bin,Offset2, + [{authorizationChallange,Value}|Hs]); + + ?OBEX_HDR_AUTHRESP -> + ?dbg("header: authResp=~p\n", [Value]), + decode_headers(Bin,Offset2, + [{authorizationResponse,Value}|Hs]); + + ?OBEX_HDR_OBJCLASS -> + Val = binary_to_list(Value), + ?dbg("header: objClass=~p\n", [Val]), + decode_headers(Bin,Offset2,[{objectClass,Val}|Hs]); + + ID1 -> + ?dbg("header: ~p=~p\n", [ID1,Value]), + decode_headers(Bin,Offset2, [{ID1,Value} | Hs]) + end; + + <<_:Offset/binary,?OBEX_I4:2,ID:6,Value:32,_/binary>> -> + Offset1 = Offset+1+4, + case ((?OBEX_I4 bsl 6) bor ID) of + ?OBEX_HDR_COUNT -> + ?dbg("header: count=~p\n", [Value]), + decode_headers(Bin,Offset1, + [{count,Value}|Hs]); + ?OBEX_HDR_CONNECTION_ID -> + ?dbg("header: connectionID=~p\n", [Value]), + decode_headers(Bin,Offset1, + [{connectionID,Value}|Hs]); + ?OBEX_HDR_LENGTH -> + ?dbg("header: length=~p\n", [Value]), + decode_headers(Bin,Offset1, + [{length,Value}|Hs]); + ?OBEX_HDR_TIME2 -> + ?dbg("header: time2=~p\n", [Value]), + decode_headers(Bin,Offset1, + [{time2,Value}|Hs]); + ID1 -> + ?dbg("header: ~p=~p\n", [ID1,Value]), + decode_headers(Bin,Offset1, + [{ID1,Value} | Hs]) + end; + + <<_:Offset/binary,?OBEX_I1:2,ID:6,Value:8,_/binary>> -> + Offset1 = Offset+1+1, + case ((?OBEX_I1 bsl 6) bor ID) of + ID1 -> + ?dbg("header: ~p=~p\n", [ID1,Value]), + decode_headers(Bin,Offset1, + [{ID1,Value} | Hs]) + end + end. + + +decode_unicode(Bin, Offset, Acc) when Offset >= size(Bin) -> + ?dbg("Warning: unicode string not null terminated\n", []), + reverse(Acc); +decode_unicode(Bin, Offset, Acc) -> + case Bin of + <<_:Offset/binary, 0, 0>> -> reverse(Acc); + <<_:Offset/binary, Char:16, _/binary>> -> + decode_unicode(Bin, Offset+2, [Char|Acc]) + end. + +decode_ascii(Bin, Offset, Acc) when Offset >= size(Bin) -> + reverse(Acc); +decode_ascii(Bin, Offset, Acc) -> + case Bin of + <<_:Offset/binary, 0>> -> + reverse(Acc); + <<_:Offset/binary, Char:8, _/binary>> -> + decode_ascii(Bin, Offset+1, [Char|Acc]) + end. + +encode_packet(Final, Opcode, BinArgs, Headers, Mtu) -> + BinHs = encode_headers(Headers), + PacketLen = 3+size(BinArgs)+size(BinHs), + if Mtu == 0 -> + ok; + PacketLen > Mtu -> + ?dbg("Waring: packet bigger than peer mtu (~w)\n", [Mtu]); + true -> ok + end, + [<<Final:1, Opcode:7, PacketLen:16>>, BinArgs, BinHs]. + + +encode_headers(Hs) -> + list_to_binary(enc_headers(Hs)). + +enc_headers([{Key,Value}|Hs]) -> + [encode_header(Key,Value) | enc_headers(Hs)]; +enc_headers([]) -> + []. + +encode_header(Key,Value) -> + case Key of + %% null terminated unicode unicode + name -> + encode_unicode(?OBEX_HDR_NAME, Value); + description -> + encode_unicode(?OBEX_HDR_DESCRIPTION,Value); + ID when is_integer(ID), (ID band ?OBEX_HDR_MASK) == ?OBEX_V0 -> + encode_unicode(ID,Value); + %% byte sequence + type -> + ?enc_null_sequence(?OBEX_HDR_TYPE,Value); + time -> + ?enc_byte_sequence(?OBEX_HDR_TIME,Value); + target -> + ?enc_byte_sequence(?OBEX_HDR_TARGET,Value); + http -> + ?enc_byte_sequence(?OBEX_HDR_HTTP,Value); + body -> + ?enc_byte_sequence(?OBEX_HDR_BODY,Value); + bodyEnd -> + ?enc_byte_sequence(?OBEX_HDR_BODY_END,Value); + who -> + ?enc_byte_sequence(?OBEX_HDR_WHO,Value); + appParameters -> + ?enc_byte_sequence(?OBEX_HDR_APPARAM,Value); + authorizationChallange -> + ?enc_byte_sequence(?OBEX_HDR_AUTHCHAL,Value); + authorizationResponse -> + ?enc_byte_sequence(?OBEX_HDR_AUTHRESP,Value); + objectClass -> + ?enc_byte_sequence(?OBEX_HDR_OBJCLASS,Value); + ID when is_integer(ID), (ID band ?OBEX_HDR_MASK) == ?OBEX_Vn -> + ?enc_byte_sequence(ID, Value); + %% 4 byte values + count -> <<?OBEX_HDR_COUNT,Value:32>>; + length -> <<?OBEX_HDR_LENGTH,Value:32>>; + time2 -> <<?OBEX_HDR_TIME2, Value:32>>; + connectionID -> (<<?OBEX_HDR_CONNECTION_ID,Value:32>>); + ID when is_integer(ID), (ID band ?OBEX_HDR_MASK) == ?OBEX_I4 -> + (<<ID, Value:32>>); + ID when is_integer(ID), (ID band ?OBEX_HDR_MASK) == ?OBEX_I1 -> + (<<ID, Value:8>>) + end. + +%% time header should be in UTC time +utc_time_iso(Time) when is_list(Time) -> + Time; +utc_time_iso(Time={Ms,S,Us}) when is_integer(Ms),is_integer(S),is_integer(Us) -> + utc_time_iso(calendar:now_to_universal_time(Time)); +utc_time_iso({{YYYY,Mon,Day},{H,M,S}}) -> + lists:flatten(io_lib:format("~4..0w~2..0w~2..0wT~2..0w~2..0w~2..0wZ", + [YYYY,Mon,Day,H,M,S])). + +%% but here is a local version +time_iso(Time) when is_list(Time) -> + Time; +time_iso(Time={Ms,S,Us}) + when is_integer(Ms),is_integer(S),is_integer(Us) -> + time_iso(calendar:now_to_local_time(Time)); +time_iso({{YYYY,Mon,Day},{H,M,S}}) -> + lists:flatten(io_lib:format("~4..0w~2..0w~2..0wT~2..0w~2..0w~2..0w", + [YYYY,Mon,Day,H,M,S])). +%% +%% Calculate size for a header list +%% +size_headers(Hs) when is_list(Hs) -> + size_headers(Hs, 0). + +size_headers([{Key,Value}|Hs], Sz) -> + size_headers(Hs, size_header(Key,Value)+Sz); +size_headers([], Sz) -> + Sz. + +%% +%% Calculate the size of header +%% Including <tag> <len:16> <data> +%% +size_header(Key,Value) -> + case Key of + %% null terminates + name -> 3+?size_unicode(Value); + description -> 3+?size_unicode(Value); + ID when is_integer(ID), (ID band ?OBEX_HDR_MASK) == ?OBEX_V0 -> + 3+?size_unicode(Value); + %% byte sequence + type -> 4+?size_byte_sequence(Value); %% (+1 for null termination) + time -> 3+?size_byte_sequence(Value); + target -> 3+?size_byte_sequence(Value); + http -> 3+?size_byte_sequence(Value); + body -> 3+?size_byte_sequence(Value); + bodyEnd -> 3+?size_byte_sequence(Value); + who -> 3+?size_byte_sequence(Value); + appParameters -> 3+?size_byte_sequence(Value); + authorizationChallange -> 3+?size_byte_sequence(Value); + authorizationResponse -> 3+?size_byte_sequence(Value); + objectClass -> 3+?size_byte_sequence(Value); + ID when is_integer(ID), (ID band ?OBEX_HDR_MASK) == ?OBEX_Vn -> + 3+?size_byte_sequence(Value); + %% 4 byte values + count -> 5; + length -> 5; + time2 -> 5; + connectionID -> 5; + ID when is_integer(ID), (ID band ?OBEX_HDR_MASK) == ?OBEX_I4 -> 5; + ID when is_integer(ID), (ID band ?OBEX_HDR_MASK) == ?OBEX_I1 -> 2 + end. + +%% Note: length field include it self and tag + unicode null termination 0,0 +encode_unicode(ID, "") -> %% speical (test) + [ID, <<3:16>>]; +encode_unicode(ID, Value) -> + Len = (length(Value)+1)*2 + 3, + [ID, <<Len:16>> | encode_chars(Value)]. + +encode_chars([H|T]) -> + [<<H:16>> | encode_chars(T)]; +encode_chars([]) -> + [<<0:16>>]. diff --git a/deps/bt/src/obex_bip.erl b/deps/bt/src/obex_bip.erl new file mode 100644 index 0000000..c215c49 --- /dev/null +++ b/deps/bt/src/obex_bip.erl @@ -0,0 +1,243 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +%%% File : obex_bip.erl +%%% Author : Tony Rogvall <tony@PBook.local> +%%% Description : Basic Imaging profile +%%% Created : 2 Jun 2006 by Tony Rogvall <tony@PBook.local> + +-module(obex_bip). + +-include("../include/uuid.hrl"). +-include("../include/obex.hrl"). + +-compile(export_all). + +-export([start/0, start/1]). +-export([init/3, + terminate/1, + handle_connect/2, + handle_disconnect/2, + handle_get/2, + handle_put/3, + handle_abort/2, + handle_setpath/3, + handle_command/2]). +-export([sdp_info/1]). + +-include("../include/sdp.hrl"). + +-record(s, + { + sref, + opts + }). + +-ifdef(debug). +-define(dbg(Fmt,As), io:format("~s:~w:" Fmt "\n", [?FILE,?LINE | As])). +-else. +-define(dbg(Fmt,As), ok). +-endif. + + +%% Basic Imaging primary session UUID +-define(Basic_Imaging_Image_Push, + ?UUID(16#E33D9545,16#8374,16#4AD7,16#9EC5,16#C16BE31EDE8E)). +-define(Basic_Imaging_Image_Pull, + ?UUID(16#8EE9B3D0,16#4608,16#11D5,16#841A,16#0002A5325B4E)). +%% Basic Imaging Advanced Image +-define(Basic_Imaging_Advanced_Image_Printing, + ?UUID(16#92353350,16#4608,16#11D5,16#841A,16#0002A5325B4E)). +-define(Basic_Imaging_Automatic_Archive, + ?UUID(16#940126C0,16#4608,16#11D5,16#841A,16#0002A5325B4E)). +-define(Basic_Imaging_Remote_Camera, + ?UUID(16#947E7420,16#4608,16#11D5,16#841A,16#0002A5325B4E)). +-define(Basic_Imaging_Remote_Display, + ?UUID(16#94C7CD20,16#4608,16#11D5,16#841A,16#0002A5325B4E)). + +%% Basic Imaging secondary session UUID +-define(Basic_Imaging_Referenced_Objects, + ?UUID(16#8E61F95D,16#1A79,16#11D4,16#8EA4,16#00805F9B9834)). +-define(Basic_Imaging_Archived_Objects, + ?UUID(16#8E61F95E,16#1A79,16#11D4,16#8EA4,16#00805F9B9834)). + +-define(BIP_HDR_IMG_HANDLE, 16#30). %% unicode null terminated +-define(BIP_HDR_IMG_DESCRIPTOR, 16#71). %% bytes sequence + +%% Capabilities +-define(CAPA_GENERIC_IMAGING, 2#0001). +-define(CAPA_CAPTURING, 2#0010). +-define(CAPA_PRINING, 2#0100). +-define(CAPA_DISPLAYING, 2#1000). + +%% Features +-define(FEATURE_IMAGE_PUSH, 2#000000001). +-define(FEATURE_IMAGE_PUSH_STORE, 2#000000010). +-define(FEATURE_IMAGE_PUSH_PRINT, 2#000000100). +-define(FEATURE_IMAGE_PUSH_DISPLAY, 2#000001000). +-define(FEATURE_IMAGE_PULL, 2#000010000). +-define(FEATURE_ADVANCED_IMAGE_PRINING, 2#000100000). +-define(FEATURE_AUTOMATIC_ARCHIVE, 2#001000000). +-define(FEATURE_REMOTE_CAMERA, 2#001000000). +-define(FEATURE_REMOTE_DISPLAY, 2#010000000). + +%% Functions +-define(FUNC_GetCapabilities, 2#00000000000000001). +-define(FUNC_PutImage, 2#00000000000000010). +-define(FUNC_PutLinkedAttachment, 2#00000000000000100). +-define(FUNC_PutLinkedThumbnail, 2#00000000000001000). +-define(FUNC_RemoteDisplay, 2#00000000000010000). +-define(FUNC_GetImagesList, 2#00000000000100000). +-define(FUNC_GetImageProperties, 2#00000000001000000). +-define(FUNC_GetImage, 2#00000000010000000). +-define(FUNC_GetLinkedThumbnail, 2#00000000100000000). +-define(FUNC_GetLinkedAttachment, 2#00000001000000000). +-define(FUNC_DeleteImage, 2#00000010000000000). +-define(FUNC_StartPrint, 2#00000100000000000). +-define(FUNC_Reserved_12, 2#00001000000000000). +-define(FUNC_StartArchive, 2#00010000000000000). +-define(FUNC_GetMonitoringImage, 2#00100000000000000). +-define(FUNC_Reserved_15, 2#01000000000000000). +-define(FUNC_GetStatus, 2#10000000000000000). + + +sdp_info(Channel) -> + Base1 = 16#0100, + [{?ATTR_ServiceRecordHandle, {uint32, 65000}}, %% Should not be needed ? + {?ATTR_ServiceClassIDList, + {sequence,[{uuid,?UUID_ImagingResponder}]}}, + {?ATTR_ProtocolDescriptorList, + {sequence,[{sequence,[{uuid,?UUID_L2CAP}]}, + {sequence,[{uuid,?UUID_RFCOMM},{uint8,Channel}]}, + {sequence,[{uuid,?UUID_OBEX}]}]}}, + {?ATTR_BrowseGroupList, + {sequence,[{uuid,?UUID_PublicBrowseGroup}]}}, + {?ATTR_LanguageBaseAttributeIDList, + {sequence, [{uint16, ?LANGUAGE($e,$n)}, + {uint16, ?ENCODING_UTF8}, + {uint16, Base1}]}}, + {?ATTR_ServiceName+Base1, {text,"Imaging"}}, + {?ATTR_BluetoothProfileDescriptorList, + {sequence,[{uuid,?UUID_Imaging}, + {uint16, 16#0100}]}}, + {?ATTR_BIP_SupportedCapabilities, + {uint8, ?CAPA_GENERIC_IMAGING bor ?CAPA_DISPLAYING}}, + {?ATTR_BIP_SupportedFeatures, + {uint16, ?FEATURE_IMAGE_PUSH bor ?FEATURE_IMAGE_PUSH_DISPLAY}}, + {?ATTR_BIP_SupportedFunctions, + {uint32, ?FUNC_GetCapabilities bor ?FUNC_RemoteDisplay}}, + {?ATTR_BIP_TotalImagingDataCapacity,{uint64, (1 bsl 32)}} + ]. + +start() -> + start(15). + +start(Channel) -> + %% NOTE: The Channel number is just a hint, the + %% number may be any in the range 1-30 + obex:server(Channel, [{transport,rfcomm}, + {sdp, sdp_info(Channel)}, + {target,{?Basic_Imaging_Remote_Display,?MODULE}}]). + + + + +%% Basic Imaging Image Push +connect_push(Obex) -> + obex:connect(Obex, [{target, ?Basic_Imaging_Image_Push}]). + +connect_remote_display(Obex) -> + obex:connect(Obex, [{target, ?Basic_Imaging_Remote_Display}]). + +getCapabilites(Obex) -> + case obex:get(Obex, [{type,"x-bt/img-capabilities"}]) of + {ok,_Hs,Bin} -> + makeup:string(Bin); + Error -> + Error + end. + +getListing(Obex) -> + obex:get(Obex, [{type,"x-bt/img-listing"}]). + +getProperties(Obex,Name) -> + obex:get(Obex, [{type,"x-bt/img-properties"},{name,Name}]). + +getImage(Obex,Name) -> + obex:get(Obex, [{type,"x-bt/img-imq"},{name,Name}]). + +getThumbnail(Obex,Name) -> + obex:get(Obex, [{type,"x-bt/img-thm"},{name,Name}]). + +remoteDisplay(Obex) -> + obex:get(Obex, [{type,"x-bt/img-display"}]). + + +putImage(Obex,Name,Descriptor,Image) -> + obex:put(Obex, [{type,"x-bt/img-img"}, + {name,Name}, + {?BIP_HDR_IMG_DESCRIPTOR, Descriptor}], Image). + +%% +%% Imageing server functions +%% + + +init(SrvRef, SrvOpts, _Mtu) -> + ?dbg("init: ~p, opts=~p, peer_mtu=~p", [SrvRef, SrvOpts, _Mtu]), + #s { sref = SrvRef, opts = SrvOpts}. + +terminate(_S) -> + ?dbg("termiate", []), + ok. + +handle_connect(Target,S) -> + ?dbg("handle_connect: ~p", [Target]), + ID = 1235, %% FIXME + {reply, {ok,[{connectionID,ID},{who,Target}]}, S}. + +handle_disconnect(_Hs, S) -> + ?dbg("handle_disconnect: ~p", [_Hs]), + {reply, {ok,success}, S}. + +handle_get(_Hs, S) -> + ?dbg("handle_get: ~p", [_Hs]), + {reply, {ok,[],<<"Hello">>}, S}. + +handle_put(_Hs,<<>>,S) -> + ?dbg("handle_put: ~p data=~p", [_Hs,<<>>]), + {reply, {ok,success}, S}; +handle_put(_Hs,_Data,S) -> + ?dbg("handle_put: ~p data=~p", [_Hs,_Data]), + {reply, continue, S}. + +handle_abort(_Hs, S) -> + ?dbg("handle_abort: ~p", [_Hs]), + {reply, {ok,success}, S}. + +handle_setpath(_Hs, _Args, S) -> + ?dbg("handle_setpath: ~p args=~p", [_Hs,_Args]), + {reply, {error,not_allowed}, S}. + +handle_command(_Hs, S) -> + ?dbg("handle_command: ~p", [_Hs]), + {reply, {error,not_allowed}, S}. + + + + + + diff --git a/deps/bt/src/obex_dm_server.erl b/deps/bt/src/obex_dm_server.erl new file mode 100644 index 0000000..99e5d68 --- /dev/null +++ b/deps/bt/src/obex_dm_server.erl @@ -0,0 +1,140 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +%%% File : obex_dm_server.erl +%%% Author : Tony Rogvall <tony@PBook.local> +%%% Description : OBEX DM Server +%%% Created : 29 May 2006 by Tony Rogvall <tony@PBook.local> + +-module(obex_dm_server). + +-export([start/0, start/1]). +-export([init/3, + terminate/1, + handle_connect/2, + handle_disconnect/2, + handle_get/2, + handle_put/3, + handle_abort/2, + handle_setpath/3, + handle_command/2]). +-export([sdp_info/1]). + +-include("../include/sdp.hrl"). + +-record(s, + { + sref, + opts + }). + +-ifdef(debug). +-define(dbg(Fmt,As), io:format("~s:~w:" Fmt "\n", [?FILE,?LINE | As])). +-else. +-define(dbg(Fmt,As), ok). +-endif. + + +sdp_info(Channel) -> + Base1 = 16#0100, + [{?ATTR_ServiceRecordHandle, {uint32, 65000}}, %% Should not be needed ? + {?ATTR_ServiceClassIDList, + {sequence,[{uuid,?UUID_SyncMLDMServer}]}}, + {?ATTR_ProtocolDescriptorList, + {sequence,[{sequence,[{uuid,?UUID_L2CAP}]}, + {sequence,[{uuid,?UUID_RFCOMM},{uint8,Channel}]}, + {sequence,[{uuid,?UUID_OBEX}]}]}}, + {?ATTR_BrowseGroupList, + {sequence,[{uuid,?UUID_PublicBrowseGroup}]}}, + {?ATTR_LanguageBaseAttributeIDList, + {sequence, [{uint16, ?LANGUAGE($e,$n)}, + {uint16, ?ENCODING_UTF8}, + {uint16, Base1}]}}, + {?ATTR_ServiceName+Base1, {text,"SyncML DM Server"}}]. + + +start() -> + start(14). + +start(Channel) -> + %% NOTE: The Channel number is just a hint, the + %% number may be any in the range 1-30 + obex:server(Channel, [{transport,rfcomm}, + {sdp, sdp_info(Channel)}, + {target,{"SYNCML-DM",?MODULE}}]). + +%% +%% Below is NOT server functions +%% but the connection side of the server +%% + +%% +%% Handle GET +%% +init(SrvRef, SrvOpts, _Mtu) -> + ?dbg("init: ~p, opts=~p, peer_mtu=~p", [SrvRef, SrvOpts, _Mtu]), + #s { sref = SrvRef, opts = SrvOpts}. + +terminate(_S) -> + ?dbg("termiate", []), + ok. + +handle_connect(Target,S) -> + ?dbg("handle_connect: ~p", [Target]), + ID = 1235, + {reply, {ok,[{connectionID,ID},{who,Target}]}, S}. + +handle_disconnect(_Hs, S) -> + ?dbg("handle_disconnect: ~p", [_Hs]), + {reply, {ok,success}, S}. + +handle_get(_Hs, S) -> + ?dbg("handle_get: ~p", [_Hs]), + {reply, {ok,[],<<"Hello">>}, S}. + +handle_put(_Hs,<<>>,S) -> + ?dbg("handle_put: ~p data=~p", [_Hs,<<>>]), + {reply, {ok,success}, S}; +handle_put(_Hs,_Data,S) -> + ?dbg("handle_put: ~p data=~p", [_Hs,_Data]), + {reply, continue, S}. + + +handle_abort(_Hs, S) -> + ?dbg("handle_abort: ~p", [_Hs]), + {reply, {ok,success}, S}. + +handle_setpath(_Hs, _Args, S) -> + ?dbg("handle_setpath: ~p args=~p", [_Hs,_Args]), + {reply, {error,not_allowed}, S}. + +handle_command(_Hs, S) -> + ?dbg("handle_command: ~p", [_Hs]), + {reply, {error,not_allowed}, S}. + + + + + + + + + + + + + + diff --git a/deps/bt/src/obex_generic.erl b/deps/bt/src/obex_generic.erl new file mode 100644 index 0000000..dca928a --- /dev/null +++ b/deps/bt/src/obex_generic.erl @@ -0,0 +1,293 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +%%% File : obex_generic.erl +%%% Author : Tony Rogvall <tony@PBook.local> +%%% Description : Generic OBEX server +%%% Created : 31 May 2006 by Tony Rogvall <tony@PBook.local> + +-module(obex_generic). + +-export([init/3, + terminate/1, + handle_connect/2, + handle_disconnect/2, + handle_get/2, + handle_put/3, + handle_abort/2, + handle_setpath/3, + handle_command/2]). + +-include_lib("kernel/include/file.hrl"). + +-record(s, + { + path, %% current path + id, %% connection id + sref, %% server reference + fd, %% current file + opts, %% server options + mtu %% peer mtu + }). + +-ifdef(debug). +-define(dbg(Fmt,As), io:format("~s:~w:" Fmt "\n", [?FILE,?LINE | As])). +-else. +-define(dbg(Fmt,As), ok). +-endif. + +%% +%% init(SrvRef, SrvOpts) -> S +%% +init(SrvRef, SrvOpts, PeerMtu) -> + ?dbg("init: ~p, opts=~p, peer_mtu=~p", [SrvRef, SrvOpts, PeerMtu]), + {ok,Path} = file:get_cwd(), + #s { path=Path, sref = SrvRef, opts = SrvOpts, mtu=PeerMtu }. + +%% +%% Terminate +%% +terminate(S) -> + ?dbg("termiate", []), + if S#s.fd == undefined -> + ok; + true -> + file:close(S#s.fd) + end. +%% +%% handle_connect(Target,S) -> +%% {reply, {ok,ConnectHeaders}, S'} +%% | {reply, {error,Reason}, S'} +%% +handle_connect(Target,S) -> + ?dbg("handle_connect: ~p", [Target]), + if Target == undefined -> + {reply, {ok,[]}, S}; + true -> + ID = 1234, + {reply, {ok,[{connectionID,ID}, {who,Target}]}, + S#s { id = ID }} + end. + +%% +%% handle_disconnect(Headers,S) -> +%% {reply, {ok,success}, S'} +%% | {reply, {error,Reason}, S'} +%% +handle_disconnect(_Hs, S) -> + ?dbg("handle_disconnect: ~p", [_Hs]), + {reply, {ok,success}, S}. + +%% +%% handle_get(Headers, S) -> +%% +%% {reply, {ok,Headers,Data}, S'} +%% | {reply, {continue,Headers,Data}, S'} +%% | {reply, {error,Reason}, S'} +%% +%% handle_get is called the first time after all request headers +%% has been read. +%% After that handle_get will be until bodyEnd is returned +%% +%% +handle_get(Hs, S) -> + ?dbg("handle_get: ~p", [Hs]), + if S#s.fd == undefined -> + get_init(Hs, S); + true -> + get_continue(Hs, S) + end. + +%% +%% handle_put(Headers, Data, S) -> +%% {reply, continue, S'} +%% | {reply, {error,Reason}, S'} +%% | {reply, {ok,success}, S'} +%% +%% handle_put will be called after all request headers +%% (not including body nor bodyEnd) +%% Data found among inital request headers are put in the Data field +%% +%% The last call will have Headers=[] and Data=<<>> +%% +handle_put(Hs, Data, S) -> + ?dbg("handle_put: ~p data=~p", [Hs,Data]), + if S#s.fd == undefined -> + put_init(Hs, Data, S); + true -> + put_continue(Hs,Data,S) + end. + +%% +%% handle_abort(Headers, S) -> +%% {reply, {ok,success}, S} +%% | {reply, {error,Reason}, S} +%% +handle_abort(_Hs, S) -> + ?dbg("handle_abort: ~p", [_Hs]), + if S#s.fd == undefined -> + {reply, {ok,success}, S}; + true -> + file:close(S#s.fd), + {reply, {ok,success}, S#s { fd = undefined} } + end. + +%% +%% handle_setpath(Header, Args, S) -> +%% {reply, {ok,success}, S} +%% | {reply, {error,Reason}, S} +%% +handle_setpath(Hs, _Args, S) -> + ?dbg("handle_setpath: ~p args=~p", [Hs,_Args]), + %% FIXME: use Args to create path and do .. + case lists:keysearch(name, 1, Hs) of + false -> + {reply, {error, conflict}, S}; + {value,{_,Path}} -> + Path1 = filename:join(S#s.path, Path), + {reply, {ok, success}, S#s { path=Path1}} + end. + +%% +%% handle_command(Headers, S) -> +%% {reply, {ok,sucess}, S'} +%% | {reply, {error,Reason}, S'} +%% +%% +%% +handle_command(_Hs, S) -> + ?dbg("handle_command: ~p", [_Hs]), + {reply, {ok,success}, S}. + +%% +%% Return first headers +%% Normally return things like: +%% [{name, FileName}, +%% {type,MimeType}, +%% {length,Size}, +%% {time,IsoTimeStamp}, +%% {body,Data}, {bodyEnd,Data}] +%% +get_init(Hs, S) -> + case lists:keysearch(name, 1, Hs) of + false -> + {reply, {error, conflict}, S}; + {value,{_,File}} -> + Path = filename:join(S#s.path, File), + case get_info(Path) of + {ok,Info} -> + case file:open(Path, [read,binary]) of + {ok,Fd} -> + Hs = [{name,File}|Info], + RdSz = data_mtu(Hs, S#s.mtu), + ?dbg("obex_generic: data_mtu: ~w\n", [RdSz]), + case file:read(Fd, RdSz) of + eof -> + file:close(Fd), + {reply,{ok,Hs,<<>>},S}; + {ok,Data} -> + {reply,{continue,Hs,Data},S#s{fd=Fd}} + end; + _Error -> + {reply,{error,bad_request}, S} + end; + Error -> + {reply, Error, S} + end + end. + +%% +%% Continue +%% +get_continue(_Hs, S) -> + RdSz = data_mtu([], S#s.mtu), + ?dbg("obex_generic: data_mtu: ~w\n", [RdSz]), + case file:read(S#s.fd, RdSz) of + eof -> + file:close(S#s.fd), + {reply,{ok,[],<<>>}, S#s { fd = undefined }}; + {ok,Data} -> + {reply,{continue,[],Data}, S} + end. + +%% +%% Get info headers +%% +get_info(File) -> + case file:read_file_info(File) of + {error,enoent} -> + {error,not_found}; + {error,eaccess} -> + {error, unauthorized}; + {ok,I} -> + if I#file_info.type =/= regular -> + {error, not_allowed}; + I#file_info.access == none; I#file_info.access == write -> + {error, unauthorized}; + true -> + {ok,[{length,I#file_info.size}, + {time,obex:time_iso(I#file_info.mtime)}]} + end + end. + +%% +%% Calculate usable data MTU +%% +data_mtu(Hs, Mtu) -> + Sz = obex:size_headers(Hs), + %% remove 3 byte for data header and 3 byte for response header + (Mtu - (Sz rem Mtu))-6. + +%% +%% Put init: process initial put request +%% +put_init(Hs, Data, S) -> + case lists:keysearch(name, 1, Hs) of + false -> + {reply, {error, conflict}, S}; + {value, {_,File}} -> + Path = filename:join(S#s.path, File), + case file:open(Path, [write,binary]) of + {ok,Fd} -> + put_write(Fd, Data, S); + _Error -> + %% FIXME: check reason + {reply, {error,unauthorized}, S} + end + end. + +put_continue(_Hs, <<>>, S) -> + file:close(S#s.fd), + {reply, {ok,success}, S#s {fd = undefined}}; +put_continue(_Hs, Data, S) -> + put_write(S#s.fd, Data, S). + +put_write(Fd, <<>>, S) -> + {reply, continue, S#s { fd=Fd }}; +put_write(Fd, Data, S) -> + file:write(Fd, Data), %% FIXME: handle error + {reply, continue, S#s { fd=Fd }}. + + + + + + + + + + + diff --git a/deps/bt/src/obex_rfcomm.erl b/deps/bt/src/obex_rfcomm.erl new file mode 100644 index 0000000..cec5bb2 --- /dev/null +++ b/deps/bt/src/obex_rfcomm.erl @@ -0,0 +1,254 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +%%%------------------------------------------------------------------- +%%% File : obex_rfcomm.erl +%%% Author : Tony Rogvall <tony@PBook.local> +%%% Description : OBEX RFCOMM API +%%% +%%% Created : 31 May 2006 by Tony Rogvall <tony@PBook.local> +%%%------------------------------------------------------------------- +-module(obex_rfcomm). + +-behaviour(gen_server). + +%% SERVER +-export([server_link/2, server/2]). +-export([stop/1]). +-export([server_accept/3]). +-export([register/3, unregister/2, lookup/2]). + +-export([open/3, close/1, listen/2, accept/1, send/2]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-ifdef(debug). +-define(dbg(Fmt,As), io:format("~s:~w:" Fmt "\n", [?FILE,?LINE | As])). +-else. +-define(dbg(Fmt,As), ok). +-endif. + +-record(state, + { + channel, %% listen channel + ref, %% ListenRef + apid, %% Current accept process + targets=[], %% Targets associated + opts %% Options + }). + +%%==================================================================== +%% API +%%==================================================================== + +open(Address, Channel, _Opts) -> + rfcomm:open(Address, Channel). + +listen(Channel, _Opts) -> + rfcomm:listen(Channel). + +accept(ListenRef) -> %% async accept!! + rfcomm:accept(ListenRef). + +close(RFComm) -> + rfcomm:close(RFComm). + +send(RFComm, Data) -> + rfcomm:send(RFComm, Data). + +register(Server, Target, Module) -> + gen_server:call(Server, {register, Target, Module}). + +unregister(Server, Target) -> + gen_server:call(Server, {register, Target}). + +lookup(Server, Target) -> + gen_server:call(Server, {lookup, Target}). + +%%-------------------------------------------------------------------- +%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} +%% Description: Starts the server +%%-------------------------------------------------------------------- +server_link(Port, Opts) -> + gen_server:start_link(?MODULE, [Port,Opts], []). + +server(Port, Opts) -> + gen_server:start(?MODULE, [Port,Opts], []). + +stop(Server) -> + gen_server:call(Server, stop). + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% Description: Initiates the server +%%-------------------------------------------------------------------- +init([Channel,Opts]) -> + Channel1 = + case lists:keysearch(sdp, 1, Opts) of + false -> + Channel; + {value,{_,ServiceRecord}} -> + case bt_drv:service_add(ServiceRecord) of + {ok,Handle} -> + case bt_drv:service_rfcomm(Handle) of + {ok,RealChannel} -> + io:format("Registered server: ~p rfcomm=~p\n", + [Handle,RealChannel]), + RealChannel; + _Error -> + io:format("No RFCOMM channel found,handle=~p\n", + [Handle]), + Channel + end; + SdpError -> + io:format("Unable to registered DM server: ~p\n", + [SdpError]), + Channel + end + end, + case listen(Channel1, [binary]) of + {ok,ListenRef} -> + process_flag(trap_exit, true), + APid = proc_lib:spawn_link(?MODULE, server_accept, + [self(), ListenRef,Opts]), + {ok, #state { channel=Channel, + ref = ListenRef, + apid = APid, + targets=opt_targets(Opts), + opts = Opts }}; + Error -> + {stop, Error} + end. + +%%-------------------------------------------------------------------- +%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, State} +%% Description: Handling call messages +%%-------------------------------------------------------------------- +handle_call(stop, _From, State) -> + {stop, normal, ok, State}; +handle_call({register, Target, Module}, _From, State) -> + Targets = State#state.targets, + case lists:keysearch(Target, 1, Targets) of + false -> + State1 = State#state { targets = [{Target,Module}|Targets]}, + {reply, ok, State1}; + {value,_} -> + {reply, {error, already_registered}, State} + end; +handle_call({unregister, Target}, _From, State) -> + Targets = lists:keydelete(Target, 1, State#state.targets), + {reply, ok, State#state { targets = Targets }}; +handle_call({lookup, Target}, _From, State) -> + case lists:keysearch(Target, 1, State#state.targets) of + false -> {reply, {error, not_found}, State}; + {value,{_, Module}} -> {reply, {ok, Module}, State} + end; +handle_call(_Request, _From, State) -> + {reply, {error, bad_call}, State}. + +%%-------------------------------------------------------------------- +%% Function: handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling cast messages +%%-------------------------------------------------------------------- +handle_cast({accept,Pid,{error,closed}}, State) when Pid == State#state.apid -> + {stop, closed, State}; %% Listen socket has closed +handle_cast({accept,Pid,Reason}, State) when Pid == State#state.apid -> + io:format("~s: accept ~p\n", [?MODULE, Reason]), + unlink(State#state.apid), + APid = proc_lib:spawn_link(?MODULE, server_accept, [self(), State#state.ref, State#state.opts]), + {noreply, State#state { apid = APid }}; +handle_cast({'EXIT',Pid,Reason}, State) when Pid == State#state.apid -> + io:format("~s: EXIT ~p\n", [?MODULE, Reason]), + APid = proc_lib:spawn_link(?MODULE, server_accept, [self(), State#state.ref, State#state.opts]), + {noreply, State#state { apid = APid }}; +handle_cast(_Msg, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling all non call/cast messages +%%-------------------------------------------------------------------- +handle_info(_Info, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: terminate(Reason, State) -> void() +%% Description: This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any necessary +%% cleaning up. When it returns, the gen_server terminates with Reason. +%% The return value is ignored. +%%-------------------------------------------------------------------- +terminate(_Reason, _State) -> + ok. + +%%-------------------------------------------------------------------- +%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} +%% Description: Convert process state when code is changed +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +opt_targets(Opts) -> + opt_targets(Opts,[]). + +opt_targets([{target,{Target,Module}}|Opts], Ts) + when is_list(Target), is_atom(Module) -> + opt_targets(Opts, [{Target,Module}|Ts]); +opt_targets([_|Opts], Ts) -> + opt_targets(Opts, Ts); +opt_targets([], Ts) -> + Ts. + + +server_accept(Server, ListenRef, Opts) -> + case accept(ListenRef) of + {ok,ARef} -> + receive + {rfcomm,ARef,{accept,Address,Channel}} -> + gen_server:cast(Server, {accept, self(), ok}), + obex:server_init(Server, ARef, Address, Channel, ?MODULE, Opts); + {rfcomm,ARef,{data,_Data}} -> + ?dbg("Error: got data ~p\n", [_Data]), + close(ARef), + gen_server:cast(Server, {accept, self(), ok}); + {rfcomm,ARef,closed} -> + gen_server:cast(Server, {accept, self(), {error,closed}}) + end; + Error -> + gen_server:cast(Server, {accept, self(), Error}) + end. diff --git a/deps/bt/src/obex_sync_server.erl b/deps/bt/src/obex_sync_server.erl new file mode 100644 index 0000000..f966a88 --- /dev/null +++ b/deps/bt/src/obex_sync_server.erl @@ -0,0 +1,137 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +%%% File : obex_dm_server.erl +%%% Author : Tony Rogvall <tony@PBook.local> +%%% Description : OBEX SYNCML Server +%%% Created : 29 May 2006 by Tony Rogvall <tony@PBook.local> + +-module(obex_sync_server). + +-export([start/0, start/1]). +-export([init/3, + terminate/1, + handle_connect/2, + handle_disconnect/2, + handle_get/2, + handle_put/3, + handle_abort/2, + handle_setpath/3, + handle_command/2]). +-export([sdp_info/1]). + +-include("../include/sdp.hrl"). + +-record(s, + { + sref, + opts + }). + +-ifdef(debug). +-define(dbg(Fmt,As), io:format("~s:~w:" Fmt "\n", [?FILE,?LINE | As])). +-else. +-define(dbg(Fmt,As), ok). +-endif. + + +sdp_info(Channel) -> + Base1 = 16#0100, + [{?ATTR_ServiceRecordHandle, {uint32, 65000}}, %% Should not be needed ? + {?ATTR_ServiceClassIDList, + {sequence,[{uuid,?UUID_SyncMLServer}]}}, + {?ATTR_ProtocolDescriptorList, + {sequence,[{sequence,[{uuid,?UUID_L2CAP}]}, + {sequence,[{uuid,?UUID_RFCOMM},{uint8,Channel}]}, + {sequence,[{uuid,?UUID_OBEX}]}]}}, + {?ATTR_BrowseGroupList, + {sequence,[{uuid,?UUID_PublicBrowseGroup}]}}, + {?ATTR_LanguageBaseAttributeIDList, + {sequence, [{uint16, ?LANGUAGE($e,$n)}, + {uint16, ?ENCODING_UTF8}, + {uint16, Base1}]}}, + {?ATTR_ServiceName+Base1, {text,"SyncMLServer"}}]. + + +start() -> + start(14). + +start(Channel) -> + %% NOTE: The Channel number is just a hint, the + %% number may be any in the range 1-30 + obex:server(Channel, [{transport,rfcomm}, + {sdp, sdp_info(Channel)}, + {target,{"SYNCML-SYNC",?MODULE}}]). + +%% +%% Below is NOT server functions +%% but the connection side of the server +%% + + +init(SrvRef, SrvOpts, _Mtu) -> + ?dbg("init: ~p, opts=~p, peer_mtu=~p", [SrvRef, SrvOpts, _Mtu]), + #s { sref = SrvRef, opts = SrvOpts}. + +terminate(_S) -> + ?dbg("termiate", []), + ok. + +handle_connect(Target,S) -> + ?dbg("handle_connect: ~p", [Target]), + ID = 1235, %% FIXME + {reply, {ok,[{connectionID,ID},{who,Target}]}, S}. + +handle_disconnect(_Hs, S) -> + ?dbg("handle_disconnect: ~p", [_Hs]), + {reply, {ok,success}, S}. + +handle_get(_Hs, S) -> + ?dbg("handle_get: ~p", [_Hs]), + {reply, {ok,[],<<"Hello">>}, S}. + +handle_put(_Hs,<<>>,S) -> + ?dbg("handle_put: ~p data=~p", [_Hs,<<>>]), + {reply, {ok,success}, S}; +handle_put(_Hs,_Data,S) -> + ?dbg("handle_put: ~p data=~p", [_Hs,_Data]), + {reply, continue, S}. + +handle_abort(_Hs, S) -> + ?dbg("handle_abort: ~p", [_Hs]), + {reply, {ok,success}, S}. + +handle_setpath(_Hs, _Args, S) -> + ?dbg("handle_setpath: ~p args=~p", [_Hs,_Args]), + {reply, {error,not_allowed}, S}. + +handle_command(_Hs, S) -> + ?dbg("handle_command: ~p", [_Hs]), + {reply, {error,not_allowed}, S}. + + + + + + + + + + + + + + diff --git a/deps/bt/src/obex_tcp.erl b/deps/bt/src/obex_tcp.erl new file mode 100644 index 0000000..9067a3d --- /dev/null +++ b/deps/bt/src/obex_tcp.erl @@ -0,0 +1,224 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +%%%------------------------------------------------------------------- +%%% File : obex_tcp.erl +%%% Author : Tony Rogvall <tony@PBook.local> +%%% Description : OBEX TCP API +%%% +%%% Created : 31 May 2006 by Tony Rogvall <tony@PBook.local> +%%%------------------------------------------------------------------- +-module(obex_tcp). + +-behaviour(gen_server). + +%% SERVER +-export([server_link/2, server/2]). +-export([stop/1]). +-export([server_accept/3]). +-export([register/3, unregister/2, lookup/2]). + +-export([open/3, close/1, listen/2, accept/1, send/2]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-ifdef(debug). +-define(dbg(Fmt,As), io:format("~s:~w:" Fmt "\n", [?FILE,?LINE | As])). +-else. +-define(dbg(Fmt,As), ok). +-endif. + + +-record(state, + { + channel, %% listen channel + ref, %% ListenRef + apid, %% Current accept process + targets=[], %% Targets associated + opts %% Options + }). + +%%==================================================================== +%% API +%%==================================================================== + +open(Address, Port, Opts) -> + gen_tcp:connect(Address, Port, Opts). + +listen(Port, Opts) -> + gen_tcp:listen(Port, Opts). + +accept(Listen) -> + gen_tcp:accept(Listen). + +close(Tcp) -> + gen_tcp:close(Tcp). + +send(Tcp, Data) -> + gen_tcp:send(Tcp, Data). + +register(Server, Target, Module) -> + gen_server:call(Server, {register, Target, Module}). + +unregister(Server, Target) -> + gen_server:call(Server, {register, Target}). + +lookup(Server, Target) -> + gen_server:call(Server, {lookup, Target}). + +%%-------------------------------------------------------------------- +%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} +%% Description: Starts the server +%%-------------------------------------------------------------------- + +server_link(Port, Opts) -> + gen_server:start_link(?MODULE, [Port,Opts], []). + +server(Port, Opts) -> + gen_server:start(?MODULE, [Port,Opts], []). + +stop(Server) -> + gen_server:call(Server, stop). +%%==================================================================== +%% gen_server callbacks +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% Description: Initiates the server +%%-------------------------------------------------------------------- +init([Port,Opts]) -> + case listen(Port, [binary]) of + {ok,ListenRef} -> + process_flag(trap_exit, true), + APid = proc_lib:spawn_link(?MODULE, server_accept, [self(), ListenRef,Opts]), + {ok, #state { channel=Port, + ref = ListenRef, + apid = APid, + targets=opt_targets(Opts), + opts = Opts }}; + Error -> + {stop, Error} + end. + +%%-------------------------------------------------------------------- +%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, State} +%% Description: Handling call messages +%%-------------------------------------------------------------------- +handle_call(stop, _From, State) -> + {stop, normal, ok, State}; +handle_call({register, Target, Module}, _From, State) -> + Targets = State#state.targets, + case lists:keysearch(Target, 1, Targets) of + false -> + State1 = State#state { targets = [{Target,Module}|Targets]}, + {reply, ok, State1}; + {value,_} -> + {reply, {error, already_registered}, State} + end; +handle_call({unregister, Target}, _From, State) -> + Targets = lists:keydelete(Target, 1, State#state.targets), + {reply, ok, State#state { targets = Targets }}; +handle_call({lookup, Target}, _From, State) -> + case lists:keysearch(Target, 1, State#state.targets) of + false -> {reply, {error, not_found}, State}; + {value,{_, Module}} -> {reply, {ok, Module}, State} + end; +handle_call(_Request, _From, State) -> + {reply, {error, bad_call}, State}. + +%%-------------------------------------------------------------------- +%% Function: handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling cast messages +%%-------------------------------------------------------------------- +handle_cast({accept,Pid,{error,closed}}, State) when Pid == State#state.apid -> + {stop, closed, State}; %% Listen socket has closed +handle_cast({accept,Pid,Reason}, State) when Pid == State#state.apid -> + io:format("~s: accept ~p\n", [?MODULE, Reason]), + unlink(State#state.apid), + APid = proc_lib:spawn_link(?MODULE, server_accept, [self(), State#state.ref, State#state.opts]), + {noreply, State#state { apid = APid }}; +handle_cast({'EXIT',Pid,Reason}, State) when Pid == State#state.apid -> + io:format("~s: EXIT ~p\n", [?MODULE, Reason]), + APid = proc_lib:spawn_link(?MODULE, server_accept, [self(), State#state.ref, State#state.opts]), + {noreply, State#state { apid = APid }}; +handle_cast(_Msg, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling all non call/cast messages +%%-------------------------------------------------------------------- +handle_info(_Info, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: terminate(Reason, State) -> void() +%% Description: This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any necessary +%% cleaning up. When it returns, the gen_server terminates with Reason. +%% The return value is ignored. +%%-------------------------------------------------------------------- +terminate(_Reason, _State) -> + ok. + +%%-------------------------------------------------------------------- +%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} +%% Description: Convert process state when code is changed +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +opt_targets(Opts) -> + opt_targets(Opts,[]). + +opt_targets([{target,{Target,Module}}|Opts], Ts) + when is_list(Target), is_atom(Module) -> + opt_targets(Opts, [{Target,Module}|Ts]); +opt_targets([_|Opts], Ts) -> + opt_targets(Opts, Ts); +opt_targets([], Ts) -> + Ts. + + +server_accept(Server, ListenRef, Opts) -> + case accept(ListenRef) of + {ok,ARef} -> + gen_server:cast(Server, {accept, self(), ok}), + {ok,{Address,_Port}} = inet:peername(ARef), + {ok,{_Address,Port}} = inet:sockname(ListenRef), + obex:server_init(Server, ARef, Address, Port, ?MODULE, Opts); + Error -> + gen_server:cast(Server, {accept, self(), Error}) + end. diff --git a/deps/bt/src/rfcomm.erl b/deps/bt/src/rfcomm.erl new file mode 100644 index 0000000..9b15d72 --- /dev/null +++ b/deps/bt/src/rfcomm.erl @@ -0,0 +1,53 @@ +%%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%%% +%%% Copyright (C) 2006 - 2014, Rogvall Invest AB, <tony@rogvall.se> +%%% +%%% This software is licensed as described in the file COPYRIGHT, which +%%% you should have received as part of this distribution. The terms +%%% are also available at http://www.rogvall.se/docs/copyright.txt. +%%% +%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell +%%% copies of the Software, and permit persons to whom the Software is +%%% furnished to do so, under the terms of the COPYRIGHT file. +%%% +%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +%%% KIND, either express or implied. +%%% +%%%---- END COPYRIGHT --------------------------------------------------------- +%%% File : rfcomm.erl +%%% Author : Tony Rogvall <tony@pbook.synap.se> +%%% Description : RFCOMM wrapper +%%% Created : 2 Feb 2006 by Tony Rogvall <tony@pbook.synap.se> + +-module(rfcomm). + +-export([open/2, + close/1, + send/2, + listen/1, + accept/1, + accept/2, + accept/3]). + +open(Address, Channel) -> + bt_drv:rfcomm_open(Address, Channel). + +close(RFComm) -> + bt_drv:rfcomm_close(RFComm). + +send(RFComm, Data) -> + bt_drv:rfcomm_send(RFComm, Data). + +listen(Channel) -> + bt_drv:rfcomm_listen(Channel). + +accept(ListenRef) -> + accept(ListenRef, infinity, self() ). + +accept(ListenRef, Timeout) -> + accept(ListenRef, Timeout, self()). + +accept(ListenRef,Timeout, CtlPid) when Timeout==infinity; + is_integer(Timeout),Timeout>0 -> + bt_drv:rfcomm_accept(ListenRef,Timeout, CtlPid). + |