#include "BluetoothImpl.h" #include "ble/misc.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Got to love magic constants. Taken from bluez.git/tools/btgatt-client.c #define ATT_CID 4 namespace trygvis { namespace bluetooth { namespace linux { //using namespace uuids; using namespace std::chrono_literals; using namespace std; using std::to_string; class LinuxBluetoothGatt; class LinuxBluetoothDevice; class LinuxBluetoothAdapter; class LinuxBluetoothManager; class LinuxBluetoothGattService : public DefaultBluetoothGattService { public: explicit LinuxBluetoothGattService(LinuxBluetoothDevice &device, const Uuid &uuid, const uint16_t handle, const uint16_t endGroupHandle) : DefaultBluetoothGattService(device, uuid, handle, endGroupHandle) { }; LinuxBluetoothGattService(const LinuxBluetoothGattService &o) = delete; }; class LinuxBluetoothAdapter final : public DefaultBluetoothAdapter { public: explicit LinuxBluetoothAdapter(int hciDeviceId, Mac &mac); ~LinuxBluetoothAdapter() override; LinuxBluetoothAdapter(const LinuxBluetoothAdapter &) = delete; LinuxBluetoothAdapter &operator=(const LinuxBluetoothAdapter &) = delete; void runScan(std::function &device)>) override; shared_ptr getDevice(Mac &mac) override; private: void startScan() override; void stopScan() override; int hciDeviceId; int hciSocket; struct hci_filter hciFilter; bool scanning; map> devices; }; class LinuxBluetoothDevice final : public DefaultBluetoothDevice, public std::enable_shared_from_this { public: explicit LinuxBluetoothDevice(LinuxBluetoothAdapter &adapter, Mac &mac); ~LinuxBluetoothDevice() override; LinuxBluetoothDevice(const LinuxBluetoothDevice &) = delete; LinuxBluetoothDevice &operator=(const LinuxBluetoothDevice &) = delete; shared_ptr connectGatt(BluetoothCallback* callback) override; private: LinuxBluetoothGatt *gatt; }; class LinuxBluetoothGatt final : public DefaultBluetoothGatt { public: explicit LinuxBluetoothGatt(LinuxBluetoothDevice &device, BluetoothCallback *callback); ~LinuxBluetoothGatt() override; LinuxBluetoothGatt(const LinuxBluetoothGatt &) = delete; LinuxBluetoothGatt &operator=(const LinuxBluetoothGatt &) = delete; bool isConnected() const override; void discoverServices() override; void process(std::chrono::system_clock::duration max_duration) override; void writeValue(const BluetoothGattCharacteristicPtr &characteristic, const ByteBuffer &bytes) override; void write(const BluetoothGattDescriptorPtr &descriptor) override; ByteBuffer readValue(const BluetoothGattCharacteristicPtr &c, ByteBuffer &response) override; private: void connect(); void disconnect(); vector discoverServices(uint16_t startHandle); vector discoverCharacteristics(uint16_t startHandle, uint16_t endHandle); void discoverDescriptors(shared_ptr &c); void writeHandle(uint16_t handle, const ByteBuffer &bytes); void writeL2cap(ByteBuffer &buffer); void writeAndRead(const ByteBuffer &buffer, ByteBuffer &response); AttVariant processAvailableMessages(ByteBuffer &buffer); template AttVariant exchangeAndWaitFor(ByteBuffer &buffer, std::chrono::time_point time); template AttVariant waitFor(ByteBuffer &buffer, std::chrono::time_point time); template T exchangeAndWaitFor(ByteBuffer &buffer, std::chrono::time_point time, bool fail_on_error); void logError(const ErrorRes &); uint16_t mtu = 256; int l2cap; }; // Utilities string errnoAsString() { return string(strerror(errno)); }; string errnoAsString(int err) { return string(strerror(err)); }; // ----------------------------------------------------------------------- // Mac // ----------------------------------------------------------------------- Mac parseMac(bdaddr_t &a) { return Mac{a.b[5], a.b[4], a.b[3], a.b[2], a.b[1], a.b[0]}; } // ----------------------------------------------------------------------- // Device // ----------------------------------------------------------------------- LinuxBluetoothDevice::LinuxBluetoothDevice(LinuxBluetoothAdapter &adapter, Mac &mac) : DefaultBluetoothDevice(adapter, mac), gatt(nullptr) { } LinuxBluetoothDevice::~LinuxBluetoothDevice() { LOG_DEBUG("Closing device " << mac.str()); delete gatt; }; shared_ptr LinuxBluetoothDevice::connectGatt(BluetoothCallback* callback) { // Make sure that we close the old connection and create a new one when the user asks for it. delete gatt; gatt = new LinuxBluetoothGatt(*this, callback); return shared_ptr(shared_from_this(), gatt); } // ----------------------------------------------------------------------- // Gatt // ----------------------------------------------------------------------- LinuxBluetoothGatt::LinuxBluetoothGatt(LinuxBluetoothDevice &device, BluetoothCallback *callback) : DefaultBluetoothGatt(device, callback), l2cap() { connect(); } LinuxBluetoothGatt::~LinuxBluetoothGatt() { disconnect(); } bool LinuxBluetoothGatt::isConnected() const { return l2cap >= 0; } void LinuxBluetoothGatt::connect() { struct sockaddr_l2 addr{}; LOG_DEBUG("connect: mac=" << device.getMac().str()); l2cap = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); if (l2cap < 0) { throw BluetoothException(&device, "connect(): socket(): " + errnoAsString()); } memset(&addr, 0, sizeof(addr)); addr.l2_family = AF_BLUETOOTH; addr.l2_bdaddr = {{0, 0, 0, 0, 0, 0}}; addr.l2_cid = htobs(ATT_CID); addr.l2_bdaddr_type = BDADDR_LE_PUBLIC; if (::bind(l2cap, (struct sockaddr *) &addr, sizeof(addr)) < 0) { close(l2cap); throw BluetoothException(&device, "bind(): " + errnoAsString()); } struct bt_security btsec{}; memset(&btsec, 0, sizeof(btsec)); btsec.level = BT_SECURITY_LOW; if (setsockopt(l2cap, SOL_BLUETOOTH, BT_SECURITY, &btsec, sizeof(btsec)) != 0) { close(l2cap); throw BluetoothException(&device, "setsockopt(): " + errnoAsString()); } memset(&addr, 0, sizeof(addr)); addr.l2_family = AF_BLUETOOTH; addr.l2_cid = htobs(ATT_CID); addr.l2_bdaddr_type = BDADDR_LE_RANDOM; device.getMac().copy( addr.l2_bdaddr.b[5], addr.l2_bdaddr.b[4], addr.l2_bdaddr.b[3], addr.l2_bdaddr.b[2], addr.l2_bdaddr.b[1], addr.l2_bdaddr.b[0]); if (::connect(l2cap, (struct sockaddr *) &addr, sizeof(addr)) < 0) { close(l2cap); throw BluetoothException(&device, "connect(): " + errnoAsString()); } } void LinuxBluetoothGatt::disconnect() { if (l2cap < 0) { return; } LOG_DEBUG("Disconnecting from mac = " << device.getMac().str()); close(l2cap); } Uuid readUuid(BluetoothDevice *device, ByteBuffer &bytes) { size_t bytesLeft = bytes.getBytesLeft(); if (bytesLeft == 2) { uint8_t bs[16] = BLUETOOTH_UUID_INITIALIZER; bs[3] = bytes.read8(); bs[2] = bytes.read8(); return Uuid(bs); } else if (bytesLeft == 16) { uint8_t bs[16] = { bytes.get8(15), bytes.get8(14), bytes.get8(13), bytes.get8(12), bytes.get8(11), bytes.get8(10), bytes.get8(9), bytes.get8(8), bytes.get8(7), bytes.get8(6), bytes.get8(5), bytes.get8(4), bytes.get8(3), bytes.get8(2), bytes.get8(1), bytes.get8(0), }; bytes.skip(16); return Uuid{bs}; } else { throw BluetoothException(device, "Unexpected bytes left: " + to_string(bytesLeft)); } } void LinuxBluetoothGatt::writeValue(const BluetoothGattCharacteristicPtr &c, const ByteBuffer &bytes) { LOG_DEBUG("Writing to characteristic " << c->getUuid() << ": " << bytes.toString()); writeHandle(c->getValueHandle(), bytes); } void LinuxBluetoothGatt::write(const BluetoothGattDescriptorPtr &d) { auto bytes = d->getValue(); LOG_DEBUG("Writing to descriptor " << d->getUuid() << ": " << bytes.toString()); writeHandle(d->getHandle(), bytes); } void LinuxBluetoothGatt::writeHandle(uint16_t handle, const ByteBuffer &bytes) { uint8_t b[mtu]; ByteBuffer buffer{b, mtu}; AttPdu::makeWrite(buffer, handle, bytes); writeAndRead(buffer, buffer); auto cursor = buffer.getPosition(); buffer.setCursor(0); AttPdu::parseWrite(buffer); auto extra_bytes = cursor - buffer.getPosition(); LOG_DEBUG("WRITE response has " + to_string(extra_bytes) + " extra bytes"); if (extra_bytes) { throw BluetoothException(&device, "Got extra bytes from ::read(): " + to_string(extra_bytes)); } } ByteBuffer LinuxBluetoothGatt::readValue(const BluetoothGattCharacteristicPtr &c, ByteBuffer& response) { uint8_t b[mtu]; ByteBuffer buffer{b, mtu}; AttPdu::makeRead(buffer, c->getValueHandle()); writeAndRead(buffer, response); auto cursor = response.getPosition(); response.setCursor(0); AttPdu::parseRead(response); auto view = response.viewForward(cursor - response.getPosition()); LOG_DEBUG("READ response has " + to_string(view.getSize()) + " bytes"); LOG_DEBUG("Value of characteristic " << c->getUuid() << "=" << view.toString()); return view; } void LinuxBluetoothGatt::discoverServices() { uint16_t startHandle = 0x0001; removeServices(); LOG_DEBUG("Discovering services"); do { vector values = discoverServices(startHandle); // Shouldn't happen, but you never know. if (values.empty()) { break; } uint16_t endGroupHandle = startHandle; for (auto &data: values) { endGroupHandle = data.value.read16le(); LOG_DEBUG("service handle: 0x" << hex << setw(4) << setfill('0') << data.handle << ", endGroupHandle: 0x" << hex << setw(4) << setfill('0') << endGroupHandle << ", value: " << data.value.toString()); auto u = readUuid(&device, data.value); addService(make_shared(device, u, data.handle, endGroupHandle)); } startHandle = endGroupHandle; } while (startHandle != 0xffff); // for (auto &s : services) { // D << "service: " << to_string(s->getUuid()) << ", handle: " << s->getHandle() << ", end group handle: " << s->getEndGroupHandle(); // D << "s= " << (uint64_t) s; // } auto it = services.begin(), end = services.end(); if (it == end) { return; } startHandle = 0x0001; auto s = *it; do { vector values = discoverCharacteristics(startHandle, 0xffff); if (values.empty()) { break; } uint16_t lastHandle = 0xffff; for (auto &c : values) { while (c.handle > s->getEndGroupHandle() && it != end) { s = *it++; // D << "service: " << s->getHandle(); } lastHandle = c.handle; uint8_t properties = c.value.read8(); uint16_t valueHandle = c.value.read16le(); auto uuid = readUuid(&device, c.value); LOG_DEBUG("characteristic: handle: " << setw(2) << setfill('0') << hex << (int) c.handle << ", properties: " << setw(2) << setfill('0') << hex << (int) properties << ", valueHandle: 0x" << setw(4) << setfill('0') << hex << (int) valueHandle << ", uuid: " << uuid); auto characteristic = make_shared( s, c.handle, uuid, properties, valueHandle); discoverDescriptors(characteristic); s->addCharacteristic(std::move(characteristic)); } startHandle = lastHandle + (uint8_t) 2; } while (startHandle != 0xffff); } void LinuxBluetoothGatt::writeAndRead(const ByteBuffer &buffer, ByteBuffer& response) { // LOG_DEBUG("pdu size=" << out.getCursor()); auto to_be_written = buffer.getPosition(); ssize_t written = ::write(l2cap, buffer.cbegin(), to_be_written); if (to_be_written != written) { throw BluetoothException(&device, "Expected to write " + to_string(to_be_written) + " but wrote only " + to_string(written)); } // LOG_DEBUG("written=" << written); ssize_t r = ::read(l2cap, response.underlying(), response.getSize()); if (r == -1) { throw BluetoothException(&device, "read(): " + errnoAsString()); } response.setCursor(static_cast(r)); LOG_DEBUG("read: " << r << " bytes: " << response.viewBeginningToCursor().toString()); response.setCursor(static_cast(r)); } void LinuxBluetoothGatt::writeL2cap(ByteBuffer &buffer) { auto to_be_written = buffer.getPosition(); ssize_t written = ::write(l2cap, buffer.cbegin(), to_be_written); if (to_be_written != written) { throw BluetoothException(&device, "Expected to write " + to_string(to_be_written) + " but wrote only " + to_string(written)); } buffer.setCursor(0); } AttVariant LinuxBluetoothGatt::processAvailableMessages(ByteBuffer &buffer) { ssize_t bytes_available = 1; int ret = ioctl(l2cap, FIONREAD, &bytes_available); // LOG_DEBUG("ret=" << to_string(ret) << "/" << errnoAsString(ret) << // ", bytes available: " << to_string(bytes_available)); while (!ret && bytes_available) { ssize_t r = ::read(l2cap, buffer.underlying(), buffer.getBytesLeft()); if (r == -1) { throw BluetoothException(&device, "read(): " + errnoAsString()); } auto packet_buffer = buffer.viewForward(size_t(r)); auto type = static_cast(packet_buffer.get8(0)); LOG_DEBUG("Got ATT message " << to_string(type) << ", size: " << to_string(r)); auto v = AttPdu::parse(packet_buffer); if (holds_alternative(v)) { auto req = get(v); LOG_DEBUG("Got exchange MTU request, clientRxMtu=" + to_string(req.clientRxMtu)); this->mtu = std::min(123, req.clientRxMtu); AttPdu::makeExchangeMtuRes(buffer, mtu); writeL2cap(buffer); } else if (holds_alternative(v)) { auto value = get(v); LOG_DEBUG("got notification: " + to_string(value.data.getSize()) + ", pos=" + to_string(value.data.getSize())); } else { return v; } ret = ioctl(l2cap, FIONREAD, &bytes_available); // LOG_DEBUG("ret=" << to_string(ret) << "/" << errnoAsString(ret) << // ", bytes available: " << to_string(bytes_available)); } return monostate{}; } static BluetoothException makeUnexpectedTypeException(const AttVariant &variant) { auto otype = attPduType(variant); auto msg = otype ? to_string(otype.value()) : "Unknown"; return BluetoothException("Unexpected message type: " + msg); } template static void assertIsVariant(const AttVariant &variant) { if (holds_alternative(variant)) { return; } throw makeUnexpectedTypeException(variant); }; void LinuxBluetoothGatt::logError(const ErrorRes &error) { LOG_WARN(std::hex << std::showbase << std::setw(2) << "Got error: request opcode=" << int(error.requestOpcode) << std::setw(4) << ", attribute handle=" << error.attributeHandle << std::setw(2) << ", error code=" << int(error.errorCode)); } template T LinuxBluetoothGatt::exchangeAndWaitFor(ByteBuffer &buffer, std::chrono::time_point time, bool fail_on_error) { auto v = exchangeAndWaitFor(buffer, time); if (holds_alternative(v)) { return get(v); } if (fail_on_error && holds_alternative(v)) { return get(v); } throw makeUnexpectedTypeException(v); } template AttVariant LinuxBluetoothGatt::exchangeAndWaitFor(ByteBuffer &buffer, std::chrono::time_point time) { uint8_t bytes[mtu]; ByteBuffer buffer2{bytes, mtu}; auto v = processAvailableMessages(buffer2); assertIsVariant(v); LOG_DEBUG("Writing message " << to_string(static_cast(buffer.get8(0)))); this->writeL2cap(buffer); return waitFor(buffer, time); } template AttVariant LinuxBluetoothGatt::waitFor(ByteBuffer &buffer, std::chrono::time_point time) { fd_set rfds; struct timeval tv{}; FD_ZERO(&rfds); FD_SET(l2cap, &rfds); auto start = clock::now(); auto seconds = std::chrono::duration_cast(time - start); auto ms = std::chrono::duration_cast(time - start) - seconds; tv.tv_sec = seconds.count(); tv.tv_usec = ms.count(); int ret = select(l2cap + 1, &rfds, nullptr, nullptr, &tv); if (ret == -1) { throw BluetoothException("select() failed."); } else if (ret == 0) { disconnect(); throw BluetoothException("Timeout while waiting for correct response."); } return processAvailableMessages(buffer); } vector LinuxBluetoothGatt::discoverServices(uint16_t startHandle) { uint8_t bytes[mtu]; ByteBuffer buffer{bytes, mtu}; AttPdu::makeReadByGroupType(buffer, startHandle, 0xffff, uuids::PRIMARY_SERVICE); auto res = exchangeAndWaitFor(buffer, std::chrono::system_clock::now() + 10s, true); auto values = res.attributes; LOG_DEBUG("READ_BY_GROUP_TYPE response has " + to_string(values.size()) + " values"); return values; } vector LinuxBluetoothGatt::discoverCharacteristics(uint16_t startHandle, uint16_t endHandle) { uint8_t bytes[mtu]; ByteBuffer buffer{bytes, mtu}; AttPdu::makeReadByType(buffer, startHandle, endHandle, uuids::CHARACTERISTIC); auto res = exchangeAndWaitFor(buffer, std::chrono::system_clock::now() + 10s); if (holds_alternative(res)) { auto values = get(res).attributes; LOG_DEBUG("READ_BY_TYPE response has " + to_string(values.size()) + " values"); return values; } if (holds_alternative(res)) { ErrorRes err = get(res); if (err.errorCode == ErrorCode::ATTRIBUTE_NOT_FOUND) { return {}; } logError(err); } throw makeUnexpectedTypeException(res); } void LinuxBluetoothGatt::discoverDescriptors(shared_ptr &c) { auto p = c->getProperties(); if (!p.indicate() && !p.notify()) { return; } uint8_t bytes[mtu]; ByteBuffer buffer{bytes, mtu}; uint16_t handle = c->getValueHandle() + uint16_t(1); AttPdu::makeFindInformationReq(buffer, handle, handle); auto res = exchangeAndWaitFor(buffer, std::chrono::system_clock::now() + 10s); if (holds_alternative(res)) { auto values = get(res).information; LOG_DEBUG("FIND_INFORMATION response has " + to_string(values.size()) + " values"); for (auto &value : values) { c->addDescriptor(std::move(make_shared(c, value.handle, value.uuid))); LOG_DEBUG("UUID=" + value.uuid.str()); } } } void LinuxBluetoothGatt::process(std::chrono::system_clock::duration max_duration) { uint8_t b[mtu]; ByteBuffer buffer{b, mtu}; processAvailableMessages(buffer); auto variant = waitFor(buffer, std::chrono::system_clock::now() + max_duration); } // ----------------------------------------------------------------------- // Adapter // ----------------------------------------------------------------------- LinuxBluetoothAdapter::LinuxBluetoothAdapter(int hciDeviceId, Mac &mac) : DefaultBluetoothAdapter(mac), scanning(false), hciFilter() { LOG_DEBUG("hciDeviceId=" << hciDeviceId); this->hciDeviceId = hciDeviceId; hciSocket = ::hci_open_dev(hciDeviceId); LOG_DEBUG("HCI socket: " << hciSocket); if (hciSocket == -1) { throw BluetoothException(this, "Could not open HCI device " + to_string(hciDeviceId)); } hci_filter_clear(&hciFilter); hci_filter_set_ptype(HCI_EVENT_PKT, &hciFilter); hci_filter_set_event(EVT_LE_META_EVENT, &hciFilter); hci_filter_set_event(EVT_LE_ADVERTISING_REPORT, &hciFilter); setsockopt(hciSocket, SOL_HCI, HCI_FILTER, &hciFilter, sizeof(hciFilter)); } LinuxBluetoothAdapter::~LinuxBluetoothAdapter() { LOG_DEBUG("Closing adapter #" << hciDeviceId); stopScan(); close(hciSocket); // Hopefully all devices are disposed at this point. devices.clear(); } void LinuxBluetoothAdapter::startScan() { struct hci_dev_info di{}; if (hci_devinfo(hciDeviceId, &di) < 0) { throw BluetoothException(this, "Could not query device info: " + errnoAsString()); } LOG_INFO("Starting scan on device #" << hciDeviceId); LOG_DEBUG("hciDeviceId.dev_id=" << di.dev_id); LOG_DEBUG("hciDeviceId.bdaddr=" << parseMac(di.bdaddr).str()); LOG_DEBUG("hciDeviceId.flags=" << setw(8) << setfill('0') << hex << di.flags); LOG_DEBUG("hciDeviceId.flags RUNNING = " << hci_test_bit(HCI_RUNNING, &di.flags)); LOG_DEBUG("hciDeviceId.flags UP = " << hci_test_bit(HCI_UP, &di.flags)); LOG_DEBUG("hciDeviceId.flags PSCAN = " << hci_test_bit(HCI_PSCAN, &di.flags)); LOG_DEBUG("hciDeviceId.flags ISCAN = " << hci_test_bit(HCI_ISCAN, &di.flags)); LOG_DEBUG("hciDeviceId.name=" << di.name); int up = hci_test_bit(HCI_UP, &di.flags); if (!up) { throw BluetoothException(this, "HCI adapter is not up: " + to_string(hciDeviceId)); } if (hci_le_set_scan_parameters(hciSocket, 0x01, htobs(0x0010), htobs(0x0010), 0x00, 0, 1000) < 0) { if (errno == EPERM) { throw BluetoothException(this, "hci_le_set_scan_parameters: " + errnoAsString() + ". root privileges are probably required"); } throw BluetoothException(this, "hci_le_set_scan_parameters: " + errnoAsString()); } if (hci_le_set_scan_enable(hciSocket, 1, 0, 1000)) { throw BluetoothException(this, "Could not start scanning: other" + errnoAsString()); } scanning = true; } void LinuxBluetoothAdapter::stopScan() { if (!scanning) { return; } LOG_INFO("Stopping scan"); scanning = false; if (hci_le_set_scan_enable(hciSocket, 0, 0, 1000) < 0) { LOG_WARN("stopScan: hci_le_set_scan_enable: " << errnoAsString()); } } shared_ptr LinuxBluetoothAdapter::getDevice(Mac &mac) { auto it = devices.find(mac); if (it == devices.end()) { auto device = make_shared(*this, mac); devices[mac] = device; return device; } return it->second; } void LinuxBluetoothAdapter::runScan(std::function &device)> callback) { fd_set rfds; FD_ZERO(&rfds); startScan(); while (scanning) { FD_SET(hciSocket, &rfds); // Linux can change tv, so it has to be reinitialized struct timeval tv{}; tv.tv_sec = 1; tv.tv_usec = 0; int selected = select(hciSocket + 1, &rfds, nullptr, nullptr, &tv); if (selected == -1) { // Someone stopped the scan, so this is no problem if (errno == EINTR && !scanning) { break; } throw BluetoothException(this, "select() failed: " + errnoAsString()); } if (selected == 0) { LOG_TRACE("select() timed out"); // Timeout, just continue continue; } unsigned char hciEventBuf[HCI_MAX_EVENT_SIZE]; ssize_t len = read(hciSocket, hciEventBuf, sizeof(hciEventBuf)); auto *metaEvent = (evt_le_meta_event *) (hciEventBuf + (1 + HCI_EVENT_HDR_SIZE)); len -= (1 + HCI_EVENT_HDR_SIZE); LOG_DEBUG("metaEvent->subevent = " << std::hex << (int) metaEvent->subevent); if (metaEvent->subevent == EVT_LE_ADVERTISING_REPORT) { auto *advertisingInfo = (le_advertising_info *) (metaEvent->data + 1); Mac mac = parseMac(advertisingInfo->bdaddr); auto device = getDevice(mac); callback(device); } } stopScan(); } } // namespace linux } // namespace bluetooth } // namespace trygvis // ----------------------------------------------------------------------- // Implementation of platform-specific method. // ----------------------------------------------------------------------- namespace trygvis { namespace bluetooth { using namespace trygvis::bluetooth::linux; shared_ptr getAdapterImpl(string name) { int hciDevice; try { hciDevice = std::stoi(name); } catch (const std::invalid_argument &) { throw BluetoothException("Bad device name: " + name); } catch (const std::out_of_range &) { throw BluetoothException("Bad device name: " + name); } struct hci_dev_info di{}; if (hci_devinfo(hciDevice, &di) < 0) { throw BluetoothException("Could not query device info: " + errnoAsString()); } auto mac = parseMac(di.bdaddr); auto adapter = make_shared(hciDevice, mac); return std::static_pointer_cast(adapter); } void shutdownImpl() { } } }