| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2000-2002 Maxim Krasnyansky <maxk@qualcomm.com> |
| * Copyright (C) 2003-2010 Marcel Holtmann <marcel@holtmann.org> |
| * |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <signal.h> |
| #include <getopt.h> |
| #include <syslog.h> |
| #include <sys/time.h> |
| #include <sys/epoll.h> |
| #include <sys/socket.h> |
| #include <sys/resource.h> |
| #include <sys/stat.h> |
| |
| #include <bluetooth/bluetooth.h> |
| #include <bluetooth/hci.h> |
| #include <bluetooth/hci_lib.h> |
| #include <bluetooth/l2cap.h> |
| |
| #define VHCI_DEV "/dev/vhci" |
| |
| #define VHCI_MAX_CONN 12 |
| |
| #define VHCI_ACL_MTU 192 |
| #define VHCI_ACL_MAX_PKT 8 |
| |
| struct vhci_device { |
| uint8_t features[8]; |
| uint8_t name[248]; |
| uint8_t dev_class[3]; |
| uint8_t scan_enable; |
| uint8_t ssp_mode; |
| uint8_t inq_mode; |
| uint8_t eir_fec; |
| uint8_t eir_data[HCI_MAX_EIR_LENGTH]; |
| uint8_t le_mode; |
| uint8_t le_simul; |
| uint16_t acl_cnt; |
| bdaddr_t bdaddr; |
| int dev_fd; |
| int scan_fd; |
| int dd; |
| }; |
| |
| struct vhci_conn { |
| bdaddr_t dest; |
| uint16_t handle; |
| int fd; |
| }; |
| |
| struct vhci_link_info { |
| bdaddr_t bdaddr; |
| uint8_t dev_class[3]; |
| uint8_t link_type; |
| uint8_t role; |
| } __attribute__ ((packed)); |
| |
| static struct vhci_device vdev; |
| static struct vhci_conn *vconn[VHCI_MAX_CONN]; |
| |
| struct btsnoop_hdr { |
| uint8_t id[8]; /* Identification Pattern */ |
| uint32_t version; /* Version Number = 1 */ |
| uint32_t type; /* Datalink Type */ |
| } __attribute__ ((packed)); |
| #define BTSNOOP_HDR_SIZE (sizeof(struct btsnoop_hdr)) |
| |
| struct btsnoop_pkt { |
| uint32_t size; /* Original Length */ |
| uint32_t len; /* Included Length */ |
| uint32_t flags; /* Packet Flags */ |
| uint32_t drops; /* Cumulative Drops */ |
| uint64_t ts; /* Timestamp microseconds */ |
| uint8_t data[0]; /* Packet Data */ |
| } __attribute__ ((packed)); |
| #define BTSNOOP_PKT_SIZE (sizeof(struct btsnoop_pkt)) |
| |
| static uint8_t btsnoop_id[] = { 0x62, 0x74, 0x73, 0x6e, 0x6f, 0x6f, 0x70, 0x00 }; |
| |
| #define MAX_EPOLL_EVENTS 10 |
| |
| static int epoll_fd; |
| |
| static volatile sig_atomic_t __io_canceled = 0; |
| |
| static void sig_term(int sig) |
| { |
| __io_canceled = 1; |
| } |
| |
| static inline int read_n(int fd, void *buf, int len) |
| { |
| register int w, t = 0; |
| |
| while (!__io_canceled && len > 0) { |
| if ((w = read(fd, buf, len)) < 0 ){ |
| if( errno == EINTR || errno == EAGAIN ) |
| continue; |
| return -1; |
| } |
| if (!w) |
| return 0; |
| len -= w; buf += w; t += w; |
| } |
| return t; |
| } |
| |
| /* Write exactly len bytes (Signal safe)*/ |
| static inline int write_n(int fd, void *buf, int len) |
| { |
| register int w, t = 0; |
| |
| while (!__io_canceled && len > 0) { |
| if ((w = write(fd, buf, len)) < 0 ){ |
| if( errno == EINTR || errno == EAGAIN ) |
| continue; |
| return -1; |
| } |
| if (!w) |
| return 0; |
| len -= w; buf += w; t += w; |
| } |
| return t; |
| } |
| |
| static int create_snoop(char *file) |
| { |
| struct btsnoop_hdr hdr; |
| int fd, len; |
| |
| fd = open(file, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| if (fd < 0) |
| return fd; |
| |
| memcpy(hdr.id, btsnoop_id, sizeof(btsnoop_id)); |
| hdr.version = htonl(1); |
| hdr.type = htonl(1002); |
| |
| len = write(fd, &hdr, BTSNOOP_HDR_SIZE); |
| if (len < 0) { |
| close(fd); |
| return -EIO; |
| } |
| |
| if (len != BTSNOOP_HDR_SIZE) { |
| close(fd); |
| return -1; |
| } |
| |
| return fd; |
| } |
| |
| static int write_snoop(int fd, int type, int incoming, |
| unsigned char *buf, int len) |
| { |
| struct btsnoop_pkt pkt; |
| struct timeval tv; |
| uint32_t size = len; |
| uint64_t ts; |
| |
| if (fd < 0) |
| return -1; |
| |
| memset(&tv, 0, sizeof(tv)); |
| gettimeofday(&tv, NULL); |
| ts = (tv.tv_sec - 946684800ll) * 1000000ll + tv.tv_usec; |
| |
| pkt.size = htonl(size); |
| pkt.len = pkt.size; |
| pkt.flags = ntohl(incoming & 0x01); |
| pkt.drops = htonl(0); |
| pkt.ts = hton64(ts + 0x00E03AB44A676000ll); |
| |
| if (type == HCI_COMMAND_PKT || type == HCI_EVENT_PKT) |
| pkt.flags |= ntohl(0x02); |
| |
| if (write(fd, &pkt, BTSNOOP_PKT_SIZE) < 0) |
| return -errno; |
| |
| if (write(fd, buf, size) < 0) |
| return -errno; |
| |
| return 0; |
| } |
| |
| static struct vhci_conn *conn_get_by_bdaddr(bdaddr_t *ba) |
| { |
| register int i; |
| |
| for (i = 0; i < VHCI_MAX_CONN; i++) |
| if (!bacmp(&vconn[i]->dest, ba)) |
| return vconn[i]; |
| |
| return NULL; |
| } |
| |
| static void reset_vdev(void) |
| { |
| /* Device settings */ |
| vdev.features[0] = 0xff; |
| vdev.features[1] = 0xff; |
| vdev.features[2] = 0x8f; |
| vdev.features[3] = 0xfe; |
| vdev.features[4] = 0x9b; |
| vdev.features[5] = 0xf9; |
| vdev.features[6] = 0x00; |
| vdev.features[7] = 0x80; |
| |
| vdev.features[4] |= 0x40; /* LE Supported */ |
| vdev.features[6] |= 0x01; /* Extended Inquiry Response */ |
| vdev.features[6] |= 0x02; /* BR/EDR and LE */ |
| vdev.features[6] |= 0x08; /* Secure Simple Pairing */ |
| |
| memset(vdev.name, 0, sizeof(vdev.name)); |
| strncpy((char *) vdev.name, "BlueZ (Virtual HCI)", |
| sizeof(vdev.name) - 1); |
| |
| vdev.dev_class[0] = 0x00; |
| vdev.dev_class[1] = 0x00; |
| vdev.dev_class[2] = 0x00; |
| |
| vdev.scan_enable = 0x00; |
| vdev.ssp_mode = 0x00; |
| vdev.inq_mode = 0x00; |
| vdev.eir_fec = 0x00; |
| memset(vdev.eir_data, 0, sizeof(vdev.eir_data)); |
| vdev.le_mode = 0x00; |
| vdev.le_simul = 0x00; |
| } |
| |
| static void command_status(uint16_t ogf, uint16_t ocf, uint8_t status) |
| { |
| uint8_t buf[HCI_MAX_FRAME_SIZE], *ptr = buf; |
| evt_cmd_status *cs; |
| hci_event_hdr *he; |
| |
| /* Packet type */ |
| *ptr++ = HCI_EVENT_PKT; |
| |
| /* Event header */ |
| he = (void *) ptr; ptr += HCI_EVENT_HDR_SIZE; |
| |
| he->evt = EVT_CMD_STATUS; |
| he->plen = EVT_CMD_STATUS_SIZE; |
| |
| cs = (void *) ptr; ptr += EVT_CMD_STATUS_SIZE; |
| |
| cs->status = status; |
| cs->ncmd = 1; |
| cs->opcode = htobs(cmd_opcode_pack(ogf, ocf)); |
| |
| write_snoop(vdev.dd, HCI_EVENT_PKT, 1, buf, ptr - buf); |
| |
| if (write(vdev.dev_fd, buf, ptr - buf) < 0) |
| syslog(LOG_ERR, "Can't send event: %s(%d)", |
| strerror(errno), errno); |
| } |
| |
| static void command_complete(uint16_t ogf, uint16_t ocf, int plen, void *data) |
| { |
| uint8_t buf[HCI_MAX_FRAME_SIZE], *ptr = buf; |
| evt_cmd_complete *cc; |
| hci_event_hdr *he; |
| |
| /* Packet type */ |
| *ptr++ = HCI_EVENT_PKT; |
| |
| /* Event header */ |
| he = (void *) ptr; ptr += HCI_EVENT_HDR_SIZE; |
| |
| he->evt = EVT_CMD_COMPLETE; |
| he->plen = EVT_CMD_COMPLETE_SIZE + plen; |
| |
| cc = (void *) ptr; ptr += EVT_CMD_COMPLETE_SIZE; |
| |
| cc->ncmd = 1; |
| cc->opcode = htobs(cmd_opcode_pack(ogf, ocf)); |
| |
| if (plen) { |
| memcpy(ptr, data, plen); |
| ptr += plen; |
| } |
| |
| write_snoop(vdev.dd, HCI_EVENT_PKT, 1, buf, ptr - buf); |
| |
| if (write(vdev.dev_fd, buf, ptr - buf) < 0) |
| syslog(LOG_ERR, "Can't send event: %s(%d)", |
| strerror(errno), errno); |
| } |
| |
| static void connect_request(struct vhci_conn *conn) |
| { |
| uint8_t buf[HCI_MAX_FRAME_SIZE], *ptr = buf; |
| evt_conn_request *cr; |
| hci_event_hdr *he; |
| |
| /* Packet type */ |
| *ptr++ = HCI_EVENT_PKT; |
| |
| /* Event header */ |
| he = (void *) ptr; ptr += HCI_EVENT_HDR_SIZE; |
| |
| he->evt = EVT_CONN_REQUEST; |
| he->plen = EVT_CONN_REQUEST_SIZE; |
| |
| cr = (void *) ptr; ptr += EVT_CONN_REQUEST_SIZE; |
| |
| bacpy(&cr->bdaddr, &conn->dest); |
| memset(&cr->dev_class, 0, sizeof(cr->dev_class)); |
| cr->link_type = ACL_LINK; |
| |
| write_snoop(vdev.dd, HCI_EVENT_PKT, 1, buf, ptr - buf); |
| |
| if (write(vdev.dev_fd, buf, ptr - buf) < 0) |
| syslog(LOG_ERR, "Can't send event: %s (%d)", |
| strerror(errno), errno); |
| } |
| |
| static void connect_complete(struct vhci_conn *conn) |
| { |
| uint8_t buf[HCI_MAX_FRAME_SIZE], *ptr = buf; |
| evt_conn_complete *cc; |
| hci_event_hdr *he; |
| |
| /* Packet type */ |
| *ptr++ = HCI_EVENT_PKT; |
| |
| /* Event header */ |
| he = (void *) ptr; ptr += HCI_EVENT_HDR_SIZE; |
| |
| he->evt = EVT_CONN_COMPLETE; |
| he->plen = EVT_CONN_COMPLETE_SIZE; |
| |
| cc = (void *) ptr; ptr += EVT_CONN_COMPLETE_SIZE; |
| |
| bacpy(&cc->bdaddr, &conn->dest); |
| cc->status = 0x00; |
| cc->handle = htobs(conn->handle); |
| cc->link_type = ACL_LINK; |
| cc->encr_mode = 0x00; |
| |
| write_snoop(vdev.dd, HCI_EVENT_PKT, 1, buf, ptr - buf); |
| |
| if (write(vdev.dev_fd, buf, ptr - buf) < 0) |
| syslog(LOG_ERR, "Can't send event: %s (%d)", |
| strerror(errno), errno); |
| |
| /* TODO: Add io_acl_data() handling */ |
| } |
| |
| static void disconn_complete(struct vhci_conn *conn) |
| { |
| uint8_t buf[HCI_MAX_FRAME_SIZE], *ptr = buf; |
| evt_disconn_complete *dc; |
| hci_event_hdr *he; |
| |
| /* Packet type */ |
| *ptr++ = HCI_EVENT_PKT; |
| |
| /* Event header */ |
| he = (void *) ptr; ptr += HCI_EVENT_HDR_SIZE; |
| |
| he->evt = EVT_DISCONN_COMPLETE; |
| he->plen = EVT_DISCONN_COMPLETE_SIZE; |
| |
| dc = (void *) ptr; ptr += EVT_DISCONN_COMPLETE_SIZE; |
| |
| dc->status = 0x00; |
| dc->handle = htobs(conn->handle); |
| dc->reason = 0x00; |
| |
| write_snoop(vdev.dd, HCI_EVENT_PKT, 1, buf, ptr - buf); |
| |
| if (write(vdev.dev_fd, buf, ptr - buf) < 0) |
| syslog(LOG_ERR, "Can't send event: %s (%d)", |
| strerror(errno), errno); |
| |
| vdev.acl_cnt = 0; |
| } |
| |
| static void num_completed_pkts(struct vhci_conn *conn) |
| { |
| uint8_t buf[HCI_MAX_FRAME_SIZE], *ptr = buf; |
| evt_num_comp_pkts *np; |
| hci_event_hdr *he; |
| |
| /* Packet type */ |
| *ptr++ = HCI_EVENT_PKT; |
| |
| /* Event header */ |
| he = (void *) ptr; ptr += HCI_EVENT_HDR_SIZE; |
| |
| he->evt = EVT_NUM_COMP_PKTS; |
| he->plen = EVT_NUM_COMP_PKTS_SIZE; |
| |
| np = (void *) ptr; ptr += EVT_NUM_COMP_PKTS_SIZE; |
| np->num_hndl = 1; |
| |
| bt_put_le16(conn->handle, ptr); |
| ptr += 2; |
| bt_put_le16(vdev.acl_cnt, ptr); |
| ptr += 2; |
| |
| write_snoop(vdev.dd, HCI_EVENT_PKT, 1, buf, ptr - buf); |
| |
| if (write(vdev.dev_fd, buf, ptr - buf) < 0) |
| syslog(LOG_ERR, "Can't send event: %s (%d)", |
| strerror(errno), errno); |
| } |
| |
| static uint8_t scan_enable(uint8_t *data) |
| { |
| #if 0 |
| struct epoll_event scan_event; |
| struct sockaddr_in sa; |
| bdaddr_t ba; |
| int sk, opt; |
| |
| if (!(*data & SCAN_PAGE)) { |
| if (vdev.scan_fd >= 0) { |
| close(vdev.scan_fd); |
| vdev.scan_fd = -1; |
| } |
| return 0; |
| } |
| |
| if (vdev.scan_fd >= 0) |
| return 0; |
| |
| if ((sk = socket(AF_INET, SOCK_STREAM, 0)) < 0) { |
| syslog(LOG_ERR, "Can't create socket: %s (%d)", |
| strerror(errno), errno); |
| return 1; |
| } |
| |
| opt = 1; |
| setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); |
| |
| baswap(&ba, &vdev.bdaddr); |
| sa.sin_family = AF_INET; |
| memcpy(&sa.sin_addr.s_addr, &ba, sizeof(sa.sin_addr.s_addr)); |
| memcpy(&sa.sin_port, &ba.b[4], sizeof(sa.sin_port)); |
| if (bind(sk, (struct sockaddr *) &sa, sizeof(sa))) { |
| syslog(LOG_ERR, "Can't bind socket: %s (%d)", |
| strerror(errno), errno); |
| goto failed; |
| } |
| |
| if (listen(sk, 10)) { |
| syslog(LOG_ERR, "Can't listen on socket: %s (%d)", |
| strerror(errno), errno); |
| goto failed; |
| } |
| |
| memset(&scan_event, 0, sizeof(scan_event)); |
| scan_event.events = EPOLLIN; |
| scan_event.data.fd = sk; |
| |
| if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sk, &scan_event) < 0) { |
| syslog(LOG_ERR, "Failed to setup scan event watch"); |
| goto failed; |
| } |
| |
| vdev.scan_fd = sk; |
| return 0; |
| |
| failed: |
| close(sk); |
| return 1; |
| #endif |
| |
| return data[0]; |
| } |
| |
| static void accept_connection(uint8_t *data) |
| { |
| accept_conn_req_cp *cp = (void *) data; |
| struct vhci_conn *conn; |
| |
| if (!(conn = conn_get_by_bdaddr(&cp->bdaddr))) |
| return; |
| |
| connect_complete(conn); |
| } |
| |
| static void close_connection(struct vhci_conn *conn) |
| { |
| char addr[18]; |
| |
| ba2str(&conn->dest, addr); |
| syslog(LOG_INFO, "Closing connection %s handle %d", |
| addr, conn->handle); |
| |
| close(conn->fd); |
| |
| vconn[conn->handle - 1] = NULL; |
| disconn_complete(conn); |
| free(conn); |
| } |
| |
| static void disconnect(uint8_t *data) |
| { |
| disconnect_cp *cp = (void *) data; |
| struct vhci_conn *conn; |
| uint16_t handle; |
| |
| handle = btohs(cp->handle); |
| |
| if (handle > VHCI_MAX_CONN) |
| return; |
| |
| if (!(conn = vconn[handle-1])) |
| return; |
| |
| close_connection(conn); |
| } |
| |
| static void create_connection(uint8_t *data) |
| { |
| create_conn_cp *cp = (void *) data; |
| struct vhci_link_info info; |
| struct vhci_conn *conn; |
| struct sockaddr_in sa; |
| int h, sk, opt; |
| bdaddr_t ba; |
| |
| for (h = 0; h < VHCI_MAX_CONN; h++) |
| if (!vconn[h]) |
| goto do_connect; |
| |
| syslog(LOG_ERR, "Too many connections"); |
| return; |
| |
| do_connect: |
| if ((sk = socket(AF_INET, SOCK_STREAM, 0)) < 0) { |
| syslog(LOG_ERR, "Can't create socket: %s (%d)", |
| strerror(errno), errno); |
| return; |
| } |
| |
| opt = 1; |
| setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); |
| |
| baswap(&ba, &vdev.bdaddr); |
| sa.sin_family = AF_INET; |
| sa.sin_addr.s_addr = INADDR_ANY; // *(uint32_t *) &ba; |
| sa.sin_port = 0; // *(uint16_t *) &ba.b[4]; |
| if (bind(sk, (struct sockaddr *) &sa, sizeof(sa))) { |
| syslog(LOG_ERR, "Can't bind socket: %s (%d)", |
| strerror(errno), errno); |
| close(sk); |
| return; |
| } |
| |
| baswap(&ba, &cp->bdaddr); |
| sa.sin_family = AF_INET; |
| memcpy(&sa.sin_addr.s_addr, &ba, sizeof(sa.sin_addr.s_addr)); |
| memcpy(&sa.sin_port, &ba.b[4], sizeof(sa.sin_port)); |
| if (connect(sk, (struct sockaddr *) &sa, sizeof(sa)) < 0) { |
| syslog(LOG_ERR, "Can't connect: %s (%d)", |
| strerror(errno), errno); |
| close(sk); |
| return; |
| } |
| |
| /* Send info */ |
| memset(&info, 0, sizeof(info)); |
| bacpy(&info.bdaddr, &vdev.bdaddr); |
| info.link_type = ACL_LINK; |
| info.role = 1; |
| write_n(sk, (void *) &info, sizeof(info)); |
| |
| if (!(conn = malloc(sizeof(*conn)))) { |
| syslog(LOG_ERR, "Can't alloc new connection: %s (%d)", |
| strerror(errno), errno); |
| close(sk); |
| return; |
| } |
| |
| memcpy((uint8_t *) &ba, (uint8_t *) &sa.sin_addr, 4); |
| memcpy((uint8_t *) &ba.b[4], (uint8_t *) &sa.sin_port, 2); |
| baswap(&conn->dest, &ba); |
| |
| vconn[h] = conn; |
| conn->handle = h + 1; |
| conn->fd = sk; |
| |
| connect_complete(conn); |
| } |
| |
| static void hci_link_control(uint16_t ocf, int plen, uint8_t *data) |
| { |
| const uint16_t ogf = OGF_LINK_CTL; |
| |
| switch (ocf) { |
| case OCF_CREATE_CONN: |
| command_status(ogf, ocf, 0x00); |
| create_connection(data); |
| break; |
| |
| case OCF_ACCEPT_CONN_REQ: |
| command_status(ogf, ocf, 0x00); |
| accept_connection(data); |
| break; |
| |
| case OCF_DISCONNECT: |
| command_status(ogf, ocf, 0x00); |
| disconnect(data); |
| break; |
| |
| default: |
| command_status(ogf, ocf, 0x01); |
| break; |
| } |
| } |
| |
| static void hci_link_policy(uint16_t ocf, int plen, uint8_t *data) |
| { |
| const uint16_t ogf = OGF_INFO_PARAM; |
| |
| switch (ocf) { |
| default: |
| command_status(ogf, ocf, 0x01); |
| break; |
| } |
| } |
| |
| static void hci_host_control(uint16_t ocf, int plen, uint8_t *data) |
| { |
| read_scan_enable_rp se; |
| read_local_name_rp ln; |
| read_class_of_dev_rp cd; |
| read_inquiry_mode_rp im; |
| read_ext_inquiry_response_rp ir; |
| read_simple_pairing_mode_rp pm; |
| read_le_host_supported_rp hs; |
| uint8_t status; |
| |
| const uint16_t ogf = OGF_HOST_CTL; |
| |
| switch (ocf) { |
| case OCF_RESET: |
| status = 0x00; |
| reset_vdev(); |
| command_complete(ogf, ocf, 1, &status); |
| break; |
| |
| case OCF_SET_EVENT_FLT: |
| status = 0x00; |
| command_complete(ogf, ocf, 1, &status); |
| break; |
| |
| case OCF_CHANGE_LOCAL_NAME: |
| status = 0x00; |
| memcpy(vdev.name, data, sizeof(vdev.name)); |
| command_complete(ogf, ocf, 1, &status); |
| break; |
| |
| case OCF_READ_LOCAL_NAME: |
| ln.status = 0x00; |
| memcpy(ln.name, vdev.name, sizeof(ln.name)); |
| command_complete(ogf, ocf, sizeof(ln), &ln); |
| break; |
| |
| case OCF_WRITE_CONN_ACCEPT_TIMEOUT: |
| case OCF_WRITE_PAGE_TIMEOUT: |
| status = 0x00; |
| command_complete(ogf, ocf, 1, &status); |
| break; |
| |
| case OCF_READ_SCAN_ENABLE: |
| se.status = 0x00; |
| se.enable = vdev.scan_enable; |
| command_complete(ogf, ocf, sizeof(se), &se); |
| break; |
| |
| case OCF_WRITE_SCAN_ENABLE: |
| status = 0x00; |
| vdev.scan_enable = scan_enable(data); |
| command_complete(ogf, ocf, 1, &status); |
| break; |
| |
| case OCF_WRITE_AUTH_ENABLE: |
| status = 0x00; |
| command_complete(ogf, ocf, 1, &status); |
| break; |
| |
| case OCF_WRITE_ENCRYPT_MODE: |
| status = 0x00; |
| command_complete(ogf, ocf, 1, &status); |
| break; |
| |
| case OCF_READ_CLASS_OF_DEV: |
| cd.status = 0x00; |
| memcpy(cd.dev_class, vdev.dev_class, 3); |
| command_complete(ogf, ocf, sizeof(cd), &cd); |
| break; |
| |
| case OCF_WRITE_CLASS_OF_DEV: |
| status = 0x00; |
| memcpy(vdev.dev_class, data, 3); |
| command_complete(ogf, ocf, 1, &status); |
| break; |
| |
| case OCF_READ_INQUIRY_MODE: |
| im.status = 0x00; |
| im.mode = vdev.inq_mode; |
| command_complete(ogf, ocf, sizeof(im), &im); |
| break; |
| |
| case OCF_WRITE_INQUIRY_MODE: |
| status = 0x00; |
| vdev.inq_mode = data[0]; |
| command_complete(ogf, ocf, 1, &status); |
| break; |
| |
| case OCF_READ_EXT_INQUIRY_RESPONSE: |
| ir.status = 0x00; |
| ir.fec = vdev.eir_fec; |
| memcpy(ir.data, vdev.eir_data, HCI_MAX_EIR_LENGTH); |
| command_complete(ogf, ocf, sizeof(ir), &ir); |
| break; |
| |
| case OCF_WRITE_EXT_INQUIRY_RESPONSE: |
| status = 0x00; |
| vdev.eir_fec = data[0]; |
| memcpy(vdev.eir_data, data + 1, HCI_MAX_EIR_LENGTH); |
| command_complete(ogf, ocf, 1, &status); |
| break; |
| |
| case OCF_READ_SIMPLE_PAIRING_MODE: |
| pm.status = 0x00; |
| pm.mode = vdev.ssp_mode; |
| command_complete(ogf, ocf, sizeof(pm), &pm); |
| break; |
| |
| case OCF_WRITE_SIMPLE_PAIRING_MODE: |
| status = 0x00; |
| vdev.ssp_mode = data[0]; |
| command_complete(ogf, ocf, 1, &status); |
| break; |
| |
| case OCF_READ_LE_HOST_SUPPORTED: |
| hs.status = 0x00; |
| hs.le = vdev.le_mode; |
| hs.simul = vdev.le_simul; |
| command_complete(ogf, ocf, sizeof(hs), &hs); |
| break; |
| |
| case OCF_WRITE_LE_HOST_SUPPORTED: |
| status = 0x00; |
| vdev.le_mode = data[0]; |
| vdev.le_simul = data[1]; |
| command_complete(ogf, ocf, 1, &status); |
| break; |
| |
| default: |
| command_status(ogf, ocf, 0x01); |
| break; |
| } |
| } |
| |
| static void hci_info_param(uint16_t ocf, int plen, uint8_t *data) |
| { |
| read_local_version_rp lv; |
| read_local_features_rp lf; |
| read_local_ext_features_rp ef; |
| read_buffer_size_rp bs; |
| read_bd_addr_rp ba; |
| |
| const uint16_t ogf = OGF_INFO_PARAM; |
| |
| switch (ocf) { |
| case OCF_READ_LOCAL_VERSION: |
| lv.status = 0x00; |
| lv.hci_ver = 0x06; |
| lv.hci_rev = htobs(0x0000); |
| lv.lmp_ver = 0x06; |
| lv.manufacturer = htobs(63); |
| lv.lmp_subver = htobs(0x0000); |
| command_complete(ogf, ocf, sizeof(lv), &lv); |
| break; |
| |
| case OCF_READ_LOCAL_FEATURES: |
| lf.status = 0x00; |
| memcpy(lf.features, vdev.features, 8); |
| command_complete(ogf, ocf, sizeof(lf), &lf); |
| break; |
| |
| case OCF_READ_LOCAL_EXT_FEATURES: |
| ef.status = 0x00; |
| if (*data == 0) { |
| ef.page_num = 0; |
| ef.max_page_num = 1; |
| memcpy(ef.features, vdev.features, 8); |
| } else if (*data == 1) { |
| ef.page_num = 1; |
| ef.max_page_num = 1; |
| memset(ef.features, 0, 8); |
| ef.features[0] |= (!!vdev.ssp_mode << 0); |
| ef.features[0] |= (!!vdev.le_mode << 1); |
| ef.features[0] |= (!!vdev.le_simul << 2); |
| } else { |
| ef.page_num = *data; |
| ef.max_page_num = 0; |
| memset(ef.features, 0, 8); |
| } |
| command_complete(ogf, ocf, sizeof(ef), &ef); |
| break; |
| |
| case OCF_READ_BUFFER_SIZE: |
| bs.status = 0x00; |
| bs.acl_mtu = htobs(VHCI_ACL_MTU); |
| bs.sco_mtu = 0; |
| bs.acl_max_pkt = htobs(VHCI_ACL_MAX_PKT); |
| bs.sco_max_pkt = htobs(0); |
| command_complete(ogf, ocf, sizeof(bs), &bs); |
| break; |
| |
| case OCF_READ_BD_ADDR: |
| ba.status = 0x00; |
| bacpy(&ba.bdaddr, &vdev.bdaddr); |
| command_complete(ogf, ocf, sizeof(ba), &ba); |
| break; |
| |
| default: |
| command_status(ogf, ocf, 0x01); |
| break; |
| } |
| } |
| |
| static void hci_status_param(uint16_t ocf, int plen, uint8_t *data) |
| { |
| read_local_amp_info_rp ai; |
| |
| const uint16_t ogf = OGF_STATUS_PARAM; |
| |
| switch (ocf) { |
| case OCF_READ_LOCAL_AMP_INFO: |
| memset(&ai, 0, sizeof(ai)); |
| |
| /* BT only */ |
| ai.amp_status = 0x01; |
| ai.max_pdu_size = htobl(L2CAP_DEFAULT_MTU); |
| ai.controller_type = HCI_AMP; |
| ai.max_amp_assoc_length = htobl(HCI_MAX_ACL_SIZE); |
| /* No flushing at all */ |
| ai.max_flush_timeout = 0xFFFFFFFF; |
| ai.best_effort_flush_timeout = 0xFFFFFFFF; |
| |
| command_complete(ogf, ocf, sizeof(ai), &ai); |
| break; |
| |
| default: |
| command_status(ogf, ocf, 0x01); |
| break; |
| } |
| } |
| |
| static void hci_le_control(uint16_t ocf, int plen, uint8_t *data) |
| { |
| le_read_buffer_size_rp bs; |
| |
| const uint16_t ogf = OGF_LE_CTL; |
| |
| switch (ocf) { |
| case OCF_LE_READ_BUFFER_SIZE: |
| bs.status = 0; |
| bs.pkt_len = htobs(VHCI_ACL_MTU); |
| bs.max_pkt = htobs(VHCI_ACL_MAX_PKT); |
| command_complete(ogf, ocf, sizeof(bs), &bs); |
| break; |
| |
| default: |
| command_status(ogf, ocf, 0x01); |
| break; |
| } |
| } |
| |
| static void hci_command(uint8_t *data) |
| { |
| hci_command_hdr *ch; |
| uint8_t *ptr = data; |
| uint16_t ogf, ocf; |
| |
| ch = (hci_command_hdr *) ptr; |
| ptr += HCI_COMMAND_HDR_SIZE; |
| |
| ch->opcode = btohs(ch->opcode); |
| ogf = cmd_opcode_ogf(ch->opcode); |
| ocf = cmd_opcode_ocf(ch->opcode); |
| |
| switch (ogf) { |
| case OGF_LINK_CTL: |
| hci_link_control(ocf, ch->plen, ptr); |
| break; |
| |
| case OGF_LINK_POLICY: |
| hci_link_policy(ocf, ch->plen, ptr); |
| break; |
| |
| case OGF_HOST_CTL: |
| hci_host_control(ocf, ch->plen, ptr); |
| break; |
| |
| case OGF_INFO_PARAM: |
| hci_info_param(ocf, ch->plen, ptr); |
| break; |
| |
| case OGF_STATUS_PARAM: |
| hci_status_param(ocf, ch->plen, ptr); |
| break; |
| |
| case OGF_LE_CTL: |
| hci_le_control(ocf, ch->plen, ptr); |
| break; |
| |
| default: |
| command_status(ogf, ocf, 0x01); |
| break; |
| } |
| } |
| |
| static void hci_acl_data(uint8_t *data) |
| { |
| hci_acl_hdr *ah = (void *) data; |
| struct vhci_conn *conn; |
| uint16_t handle; |
| |
| handle = acl_handle(btohs(ah->handle)); |
| |
| if (handle > VHCI_MAX_CONN || !(conn = vconn[handle - 1])) { |
| syslog(LOG_ERR, "Bad connection handle %d", handle); |
| return; |
| } |
| |
| if (write_n(conn->fd, data, btohs(ah->dlen) + HCI_ACL_HDR_SIZE) < 0) { |
| close_connection(conn); |
| return; |
| } |
| |
| if (++vdev.acl_cnt > VHCI_ACL_MAX_PKT - 1) { |
| /* Send num of complete packets event */ |
| num_completed_pkts(conn); |
| vdev.acl_cnt = 0; |
| } |
| } |
| |
| #if 0 |
| static void io_acl_data(void *data) |
| { |
| struct vhci_conn *conn = data; |
| unsigned char buf[HCI_MAX_FRAME_SIZE], *ptr; |
| hci_acl_hdr *ah; |
| uint16_t flags; |
| int len; |
| |
| ptr = buf + 1; |
| if (read_n(conn->fd, ptr, HCI_ACL_HDR_SIZE) <= 0) { |
| close_connection(conn); |
| return; |
| } |
| |
| ah = (void *) ptr; |
| ptr += HCI_ACL_HDR_SIZE; |
| |
| len = btohs(ah->dlen); |
| if (read_n(conn->fd, ptr, len) <= 0) { |
| close_connection(conn); |
| return; |
| } |
| |
| buf[0] = HCI_ACLDATA_PKT; |
| |
| flags = acl_flags(btohs(ah->handle)); |
| ah->handle = htobs(acl_handle_pack(conn->handle, flags)); |
| len += HCI_ACL_HDR_SIZE + 1; |
| |
| write_snoop(vdev.dd, HCI_ACLDATA_PKT, 1, buf, len); |
| |
| if (write(vdev.dev_fd, buf, len) < 0) |
| syslog(LOG_ERR, "ACL data write error"); |
| } |
| #endif |
| |
| static void io_conn_ind(void) |
| { |
| struct vhci_link_info info; |
| struct vhci_conn *conn; |
| struct sockaddr_in sa; |
| socklen_t len; |
| int nsk, h; |
| |
| len = sizeof(sa); |
| if ((nsk = accept(vdev.scan_fd, (struct sockaddr *) &sa, &len)) < 0) |
| return; |
| |
| if (read_n(nsk, &info, sizeof(info)) < 0) { |
| syslog(LOG_ERR, "Can't read link info"); |
| return; |
| } |
| |
| if (!(conn = malloc(sizeof(*conn)))) { |
| syslog(LOG_ERR, "Can't alloc new connection"); |
| close(nsk); |
| return; |
| } |
| |
| bacpy(&conn->dest, &info.bdaddr); |
| |
| for (h = 0; h < VHCI_MAX_CONN; h++) |
| if (!vconn[h]) |
| goto accepted; |
| |
| syslog(LOG_ERR, "Too many connections"); |
| free(conn); |
| close(nsk); |
| return; |
| |
| accepted: |
| vconn[h] = conn; |
| conn->handle = h + 1; |
| conn->fd = nsk; |
| connect_request(conn); |
| } |
| |
| static void io_hci_data(void) |
| { |
| unsigned char buf[HCI_MAX_FRAME_SIZE], *ptr; |
| int type; |
| ssize_t len; |
| |
| ptr = buf; |
| |
| len = read(vdev.dev_fd, buf, sizeof(buf)); |
| if (len < 0) { |
| if (errno == EAGAIN) |
| return; |
| |
| syslog(LOG_ERR, "Read failed: %s (%d)", strerror(errno), errno); |
| __io_canceled = 1; |
| return; |
| } |
| |
| type = *ptr++; |
| |
| write_snoop(vdev.dd, type, 0, buf, len); |
| |
| switch (type) { |
| case HCI_COMMAND_PKT: |
| hci_command(ptr); |
| break; |
| |
| case HCI_ACLDATA_PKT: |
| hci_acl_data(ptr); |
| break; |
| |
| default: |
| syslog(LOG_ERR, "Unknown packet type 0x%2.2x", type); |
| break; |
| } |
| } |
| |
| static int getbdaddrbyname(char *str, bdaddr_t *ba) |
| { |
| int i, n, len; |
| |
| len = strlen(str); |
| |
| /* Check address format */ |
| for (i = 0, n = 0; i < len; i++) |
| if (str[i] == ':') |
| n++; |
| |
| if (n == 5) { |
| /* BD address */ |
| str2ba(str, ba); |
| return 0; |
| } |
| |
| if (n == 0) { |
| /* loopback port */ |
| in_addr_t addr = INADDR_LOOPBACK; |
| uint16_t be16 = htons(atoi(str)); |
| bdaddr_t b; |
| |
| memcpy(&b, &addr, 4); |
| memcpy(&b.b[4], &be16, sizeof(be16)); |
| baswap(ba, &b); |
| |
| return 0; |
| } |
| |
| fprintf(stderr, "Invalid address format\n"); |
| |
| return -1; |
| } |
| |
| static void usage(void) |
| { |
| printf("hciemu - HCI emulator ver %s\n", VERSION); |
| printf("Usage: \n"); |
| printf("\thciemu [options] port_number\n" |
| "Options:\n" |
| "\t[-d device] use specified device node\n" |
| "\t[-s file] create snoop file\n" |
| "\t[-n] do not detach\n" |
| "\t[-h] help, you are looking at it\n"); |
| } |
| |
| static const struct option options[] = { |
| { "device", 1, 0, 'd' }, |
| { "bdaddr", 1, 0, 'b' }, |
| { "snoop", 1, 0, 's' }, |
| { "nodetach", 0, 0, 'n' }, |
| { "help", 0, 0, 'h' }, |
| { } |
| }; |
| |
| int main(int argc, char *argv[]) |
| { |
| int exitcode = EXIT_FAILURE; |
| struct sigaction sa; |
| char *device = NULL, *snoop = NULL; |
| int device_fd; |
| struct epoll_event device_event; |
| int dd, opt, detach = 1; |
| |
| while ((opt=getopt_long(argc, argv, "d:s:nh", options, NULL)) != EOF) { |
| switch(opt) { |
| case 'd': |
| device = strdup(optarg); |
| break; |
| case 's': |
| snoop = strdup(optarg); |
| break; |
| case 'n': |
| detach = 0; |
| break; |
| case 'h': |
| usage(); |
| exit(0); |
| default: |
| usage(); |
| exit(1); |
| } |
| } |
| |
| argc -= optind; |
| argv += optind; |
| optind = 0; |
| |
| if (argc < 1) { |
| usage(); |
| exit(1); |
| } |
| |
| if (getbdaddrbyname(argv[0], &vdev.bdaddr) < 0) |
| exit(1); |
| |
| if (detach) { |
| if (daemon(0, 0)) { |
| perror("Can't start daemon"); |
| exit(1); |
| } |
| } |
| |
| /* Start logging to syslog and stderr */ |
| openlog("hciemu", LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_DAEMON); |
| syslog(LOG_INFO, "HCI emulation daemon ver %s started", VERSION); |
| |
| memset(&sa, 0, sizeof(sa)); |
| sa.sa_flags = SA_NOCLDSTOP; |
| sa.sa_handler = SIG_IGN; |
| sigaction(SIGCHLD, &sa, NULL); |
| sigaction(SIGPIPE, &sa, NULL); |
| |
| sa.sa_handler = sig_term; |
| sigaction(SIGTERM, &sa, NULL); |
| sigaction(SIGINT, &sa, NULL); |
| |
| if (!device) |
| device = strdup(VHCI_DEV); |
| |
| /* Open and create virtual HCI device */ |
| device_fd = open(device, O_RDWR); |
| if (device_fd < 0) { |
| syslog(LOG_ERR, "Can't open device %s: %s (%d)", |
| device, strerror(errno), errno); |
| free(device); |
| return exitcode; |
| } |
| |
| free(device); |
| |
| /* Create snoop file */ |
| if (snoop) { |
| dd = create_snoop(snoop); |
| if (dd < 0) |
| syslog(LOG_ERR, "Can't create snoop file %s: %s (%d)", |
| snoop, strerror(errno), errno); |
| free(snoop); |
| } else |
| dd = -1; |
| |
| /* Create event loop */ |
| epoll_fd = epoll_create1(EPOLL_CLOEXEC); |
| if (epoll_fd < 0) { |
| perror("Failed to create epoll descriptor"); |
| goto close_device; |
| } |
| |
| reset_vdev(); |
| |
| vdev.dev_fd = device_fd; |
| vdev.dd = dd; |
| |
| memset(&device_event, 0, sizeof(device_event)); |
| device_event.events = EPOLLIN; |
| device_event.data.fd = device_fd; |
| |
| if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, device_fd, &device_event) < 0) { |
| perror("Failed to setup device event watch"); |
| goto close_device; |
| } |
| |
| setpriority(PRIO_PROCESS, 0, -19); |
| |
| /* Start event processor */ |
| for (;;) { |
| struct epoll_event events[MAX_EPOLL_EVENTS]; |
| int n, nfds; |
| |
| if (__io_canceled) |
| break; |
| |
| nfds = epoll_wait(epoll_fd, events, MAX_EPOLL_EVENTS, -1); |
| if (nfds < 0) |
| continue; |
| |
| for (n = 0; n < nfds; n++) { |
| if (events[n].data.fd == vdev.dev_fd) |
| io_hci_data(); |
| else if (events[n].data.fd == vdev.scan_fd) |
| io_conn_ind(); |
| } |
| } |
| |
| exitcode = EXIT_SUCCESS; |
| |
| epoll_ctl(epoll_fd, EPOLL_CTL_DEL, device_fd, NULL); |
| |
| close_device: |
| close(device_fd); |
| |
| if (dd >= 0) |
| close(dd); |
| |
| close(epoll_fd); |
| |
| syslog(LOG_INFO, "Exit"); |
| |
| return exitcode; |
| } |