diff --git a/.github/workflows/nodepackage.yml b/.github/workflows/nodepackage.yml new file mode 100644 index 0000000..1176a5e --- /dev/null +++ b/.github/workflows/nodepackage.yml @@ -0,0 +1,37 @@ +name: Node.js package + +on: + pull_request: + branches: + - master + push: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node_version: [ + 6, + 8, + 10, + 12, + ] + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node_version }} + - name: Install dependencies + run: | + sudo apt -qq update + sudo apt install -y libudev-dev + - name: Build + run: | + export PATH=./node_modules/.bin:${PATH} + npm install --build-from-source --node-gyp=$(which pangyp) + ./node_modules/.bin/node-pre-gyp package + - name: Test + run: npm test \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4707af0..0000000 --- a/.travis.yml +++ /dev/null @@ -1,34 +0,0 @@ -branches: - only: - - node-pre-gyp - - /^[0-9]/ - -language: cpp - -dist: trusty - -env: - matrix: - - NODE_VERSION="4" - - NODE_VERSION="5" - - NODE_VERSION="6" - -before_install: - - sudo apt-get install build-essential libudev-dev -y - - export NVM_DIR=~/.nvm - - . $HOME/.nvm/nvm.sh - - nvm install $NODE_VERSION - - node --version - - npm --version - -install: -- export PATH=./node_modules/.bin/:$PATH -- npm install -g node-pre-gyp-github - -script: -- npm install --build-from-source --node-gyp=$(which pangyp) -- "./node_modules/.bin/node-pre-gyp package" -- echo $TRAVIS_TAG -- > - ([[ "$TRAVIS_TAG" =~ ^[0-9] ]] && - node-pre-gyp-github publish) || true diff --git a/README.md b/README.md index 43f792a..ba9d95a 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ This library needs raw USB access to a Bluetooth 4.0 USB adapter, as it needs to A [WinUSB](https://msdn.microsoft.com/en-ca/library/windows/hardware/ff540196(v=vs.85).aspx) driver is required, use [Zadig tool](http://zadig.akeo.ie) to replace the driver for your adapter. __WARNING:__ This will make the adapter unavailable in Windows Bluetooth settings! To roll back to the original driver go to: ```Device Manager -> Open Device -> Update Driver``` +Note: +- that one should select "Delete the driver software for this device" as per Zadig instructions if the generation of the system restoral point by Zadig fails if one wishes to use restore system restoral point as an option. #### Compatible Bluetooth 4.0 USB Adapter's @@ -31,6 +33,7 @@ __WARNING:__ This will make the adapter unavailable in Windows Bluetooth setting | BCM920702 Bluetooth 4.0 | 0x0a5c | 0x21e8 | | BCM20702A0 Bluetooth 4.0 | 0x19ff | 0x0239 | | BCM20702A0 Bluetooth 4.0 | 0x0489 | 0xe07a | +| BCM20702A0 Bluetooth 4.0 | 0x413c | 0x8143 | | CSR8510 A10 | 0x0a12 | 0x0001 | | Asus BT-400 | 0x0b05 | 0x17cb | | Intel Wireless Bluetooth 6235 | 0x8087 | 0x07da | diff --git a/appveyor.yml b/appveyor.yml index 955348e..4529889 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,17 +7,15 @@ environment: GYP_MSVS_VERSION: 2013 matrix: - - node_version: '4' - node_version: '5' - node_version: '6' + - node_version: '8' + - node_version: '10' + - node_version: '12' install: - ps: Install-Product node $env:node_version $env:platform - ps: npm install -g node-gyp node-pre-gyp-github - # This fixes an issue with Node 4.x on x86. If it builds without this line - # needing to be set, we can remove it. - - ps: npm config -g set node-gyp "$(npm config -g get prefix)\node_modules\node-gyp\bin\node-gyp.js" - - ps: if ($env:appveyor_repo_tag -match "true" -and $env:appveyor_repo_tag_name -match '^v?[0-9]') { $publish_binary=1; } build_script: diff --git a/index.js b/index.js index 58db67e..8b8ccf0 100644 --- a/index.js +++ b/index.js @@ -7,5 +7,5 @@ if (process.env.BLUETOOTH_HCI_SOCKET_FORCE_USB || platform === 'win32' || platfo } else if (platform === 'linux' || platform === 'android') { module.exports = require('./lib/native'); } else { - throw new Error('Unsupported platform'); + module.exports = require('./lib/unsupported'); } diff --git a/lib/native.js b/lib/native.js index 68c4e79..051c847 100644 --- a/lib/native.js +++ b/lib/native.js @@ -2,7 +2,7 @@ var events = require('events'); var binary = require('node-pre-gyp'); var path = require('path'); -var binding_path = binary.find(path.resolve(path.join(__dirname,'./package.json'))); +var binding_path = binary.find(path.resolve(path.join(__dirname,'../package.json'))); var binding = require(binding_path); var BluetoothHciSocket = binding.BluetoothHciSocket; diff --git a/lib/unsupported.js b/lib/unsupported.js new file mode 100644 index 0000000..11dc682 --- /dev/null +++ b/lib/unsupported.js @@ -0,0 +1,5 @@ +function UnsupportedPlatformBluetoothHciSocket() { + throw new Error('Unsupported platform'); +} + +module.exports = UnsupportedPlatformBluetoothHciSocket; \ No newline at end of file diff --git a/lib/usb.js b/lib/usb.js index 1027b36..c7d2193 100644 --- a/lib/usb.js +++ b/lib/usb.js @@ -15,6 +15,7 @@ var VENDOR_DEVICE_LIST = [ {vid: 0x0CF3, pid: 0xE300 }, // Qualcomm Atheros QCA61x4 {vid: 0x0a5c, pid: 0x21e8 }, // Broadcom BCM20702A0 {vid: 0x19ff, pid: 0x0239 }, // Broadcom BCM20702A0 + {vid: 0x413c, pid: 0x8143 }, // Broadcom BCM20702A0 {vid: 0x0a12, pid: 0x0001 }, // CSR {vid: 0x0b05, pid: 0x17cb }, // ASUS BT400 {vid: 0x8087, pid: 0x07da }, // Intel 6235 @@ -114,7 +115,7 @@ BluetoothHciSocket.prototype.getDeviceList = function() { "busNumber": dev.busNumber, "deviceAddress": dev.deviceAddress, })); -} +}; BluetoothHciSocket.prototype.bindControl = function() { this._mode = 'control'; diff --git a/package.json b/package.json index 43ee21f..dbad5eb 100644 --- a/package.json +++ b/package.json @@ -26,26 +26,28 @@ "win32" ], "dependencies": { - "debug": "^2.2.0", - "nan": "^2.0.5", - "node-pre-gyp": "0.6.x" + "debug": "^4.1.0", + "nan": "^2.14.0", + "node-pre-gyp": "^0.13.0" }, "optionalDependencies": { - "usb": "^1.1.0" + "usb": "^1.6.0" }, "devDependencies": { "jshint": "^2.8.0" }, "scripts": { - "preinstall": "npm install node-pre-gyp", - "install": "node-pre-gyp install --fallback-to-build", - "test": "jshint lib/*.js" + "install": "node-pre-gyp install --fallback-to-build", + "test": "jshint lib/*.js && node test.js" }, "binary": { - "module_name": "binding", - "module_path": "./lib/binding/", - "host": "https://github.com/sandeepmistry/node-bluetooth-hci-socket/releases/download/", - "package_name": "{module_name}-{version}-{node_abi}-{platform}-{arch}.tar.gz", - "remote_path": "{version}" + "module_name": "binding", + "module_path": "./lib/binding/", + "host": "https://github.com/sandeepmistry/node-bluetooth-hci-socket/releases/download/", + "package_name": "{module_name}-{version}-{node_abi}-{platform}-{arch}.tar.gz", + "remote_path": "{version}" + }, + "jshintConfig": { + "esversion": 6 } } diff --git a/src/BluetoothHciSocket.cpp b/src/BluetoothHciSocket.cpp index 469cda2..0cd3878 100644 --- a/src/BluetoothHciSocket.cpp +++ b/src/BluetoothHciSocket.cpp @@ -126,15 +126,30 @@ NAN_MODULE_INIT(BluetoothHciSocket::Init) { Nan::SetPrototypeMethod(tmpl, "stop", Stop); Nan::SetPrototypeMethod(tmpl, "write", Write); - target->Set(Nan::New("BluetoothHciSocket").ToLocalChecked(), tmpl->GetFunction()); + Nan::Set(target, Nan::New("BluetoothHciSocket").ToLocalChecked(), Nan::GetFunction(tmpl).ToLocalChecked()); } BluetoothHciSocket::BluetoothHciSocket() : - node::ObjectWrap() { - - this->_socket = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); + node::ObjectWrap(), + _mode(0), + _socket(-1), + _devId(0), + _pollHandle(), + _address(), + _addressType(0) + { + + int fd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); + if (fd == -1) { + Nan::ThrowError(Nan::ErrnoException(errno, "socket")); + return; + } + this->_socket = fd; - uv_poll_init(uv_default_loop(), &this->_pollHandle, this->_socket); + if (uv_poll_init(uv_default_loop(), &this->_pollHandle, this->_socket) < 0) { + Nan::ThrowError("uv_poll_init failed"); + return; + } this->_pollHandle.data = this; } @@ -146,12 +161,14 @@ BluetoothHciSocket::~BluetoothHciSocket() { } void BluetoothHciSocket::start() { - uv_poll_start(&this->_pollHandle, UV_READABLE, BluetoothHciSocket::PollCallback); + if (uv_poll_start(&this->_pollHandle, UV_READABLE, BluetoothHciSocket::PollCallback) < 0) { + Nan::ThrowError("uv_poll_start failed"); + } } int BluetoothHciSocket::bindRaw(int* devId) { - struct sockaddr_hci a; - struct hci_dev_info di; + struct sockaddr_hci a = {}; + struct hci_dev_info di = {}; memset(&a, 0, sizeof(a)); a.hci_family = AF_BLUETOOTH; @@ -161,7 +178,10 @@ int BluetoothHciSocket::bindRaw(int* devId) { this->_devId = a.hci_dev; this->_mode = HCI_CHANNEL_RAW; - bind(this->_socket, (struct sockaddr *) &a, sizeof(a)); + if (bind(this->_socket, (struct sockaddr *) &a, sizeof(a)) < 0) { + Nan::ThrowError(Nan::ErrnoException(errno, "bind")); + return -1; + } // get the local address and address type memset(&di, 0x00, sizeof(di)); @@ -183,7 +203,7 @@ int BluetoothHciSocket::bindRaw(int* devId) { } int BluetoothHciSocket::bindUser(int* devId) { - struct sockaddr_hci a; + struct sockaddr_hci a = {}; memset(&a, 0, sizeof(a)); a.hci_family = AF_BLUETOOTH; @@ -193,13 +213,16 @@ int BluetoothHciSocket::bindUser(int* devId) { this->_devId = a.hci_dev; this->_mode = HCI_CHANNEL_USER; - bind(this->_socket, (struct sockaddr *) &a, sizeof(a)); + if (bind(this->_socket, (struct sockaddr *) &a, sizeof(a)) < 0) { + Nan::ThrowError(Nan::ErrnoException(errno, "bind")); + return -1; + } return this->_devId; } void BluetoothHciSocket::bindControl() { - struct sockaddr_hci a; + struct sockaddr_hci a = {}; memset(&a, 0, sizeof(a)); a.hci_family = AF_BLUETOOTH; @@ -208,11 +231,14 @@ void BluetoothHciSocket::bindControl() { this->_mode = HCI_CHANNEL_CONTROL; - bind(this->_socket, (struct sockaddr *) &a, sizeof(a)); + if (bind(this->_socket, (struct sockaddr *) &a, sizeof(a)) < 0) { + Nan::ThrowError(Nan::ErrnoException(errno, "bind")); + return; + } } bool BluetoothHciSocket::isDevUp() { - struct hci_dev_info di; + struct hci_dev_info di = {}; bool isUp = false; memset(&di, 0x00, sizeof(di)); @@ -227,7 +253,7 @@ bool BluetoothHciSocket::isDevUp() { void BluetoothHciSocket::setFilter(char* data, int length) { if (setsockopt(this->_socket, SOL_HCI, HCI_FILTER, data, length) < 0) { - this->emitErrnoError(); + this->emitErrnoError("setsockopt"); } } @@ -241,6 +267,7 @@ void BluetoothHciSocket::poll() { if (length > 0) { if (this->_mode == HCI_CHANNEL_RAW) { + // TODO: This does not check for the retval of this function – should it? this->kernelDisconnectWorkArounds(length, data); } @@ -249,7 +276,13 @@ void BluetoothHciSocket::poll() { Nan::CopyBuffer(data, length).ToLocalChecked() }; - Nan::MakeCallback(Nan::New(this->This), Nan::New("emit").ToLocalChecked(), 2, argv); + Nan::AsyncResource res("BluetoothHciSocket::poll"); + res.runInAsyncScope( + Nan::New(this->This), + Nan::New("emit").ToLocalChecked(), + 2, + argv + ).FromMaybe(v8::Local()); } } @@ -259,34 +292,30 @@ void BluetoothHciSocket::stop() { void BluetoothHciSocket::write_(char* data, int length) { if (write(this->_socket, data, length) < 0) { - this->emitErrnoError(); + this->emitErrnoError("write"); } } -void BluetoothHciSocket::emitErrnoError() { - Nan::HandleScope scope; - - Local globalObj = Nan::GetCurrentContext()->Global(); - Local errorConstructor = Local::Cast(globalObj->Get(Nan::New("Error").ToLocalChecked())); - - Local constructorArgs[1] = { - Nan::New(strerror(errno)).ToLocalChecked() - }; - - Local error = errorConstructor->NewInstance(1, constructorArgs); +void BluetoothHciSocket::emitErrnoError(const char *syscall) { + v8::Local error = Nan::ErrnoException(errno, syscall, strerror(errno)); Local argv[2] = { Nan::New("error").ToLocalChecked(), error }; - - Nan::MakeCallback(Nan::New(this->This), Nan::New("emit").ToLocalChecked(), 2, argv); + Nan::AsyncResource res("BluetoothHciSocket::emitErrnoError"); + res.runInAsyncScope( + Nan::New(this->This), + Nan::New("emit").ToLocalChecked(), + 2, + argv + ).FromMaybe(v8::Local()); } -int BluetoothHciSocket::devIdFor(int* pDevId, bool isUp) { +int BluetoothHciSocket::devIdFor(const int* pDevId, bool isUp) { int devId = 0; // default - if (pDevId == NULL) { + if (pDevId == nullptr) { struct hci_dev_list_req *dl; struct hci_dev_req *dr; @@ -298,7 +327,7 @@ int BluetoothHciSocket::devIdFor(int* pDevId, bool isUp) { if (ioctl(this->_socket, HCIGETDEVLIST, dl) > -1) { for (int i = 0; i < dl->dev_num; i++, dr++) { bool devUp = dr->dev_opt & (1 << HCI_UP); - bool match = isUp ? devUp : !devUp; + bool match = (isUp == devUp); if (match) { // choose the first device that is match @@ -317,13 +346,13 @@ int BluetoothHciSocket::devIdFor(int* pDevId, bool isUp) { return devId; } -void BluetoothHciSocket::kernelDisconnectWorkArounds(int length, char* data) { +int BluetoothHciSocket::kernelDisconnectWorkArounds(int length, char* data) { // HCI Event - LE Meta Event - LE Connection Complete => manually create L2CAP socket to force kernel to book keep // HCI Event - Disconn Complete =======================> close socket from above if (length == 22 && data[0] == 0x04 && data[1] == 0x3e && data[2] == 0x13 && data[3] == 0x01 && data[4] == 0x00) { int l2socket; - struct sockaddr_l2 l2a; + struct sockaddr_l2 l2a = {}; unsigned short l2cid; unsigned short handle = *((unsigned short*)(&data[5])); @@ -336,13 +365,19 @@ void BluetoothHciSocket::kernelDisconnectWorkArounds(int length, char* data) { #endif l2socket = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if(l2socket == -1) { + return -1; + } memset(&l2a, 0, sizeof(l2a)); l2a.l2_family = AF_BLUETOOTH; l2a.l2_cid = l2cid; memcpy(&l2a.l2_bdaddr, _address, sizeof(l2a.l2_bdaddr)); l2a.l2_bdaddr_type = _addressType; - bind(l2socket, (struct sockaddr*)&l2a, sizeof(l2a)); + if (bind(l2socket, (struct sockaddr*)&l2a, sizeof(l2a)) < 0) { + close(l2socket); + return -2; + } memset(&l2a, 0, sizeof(l2a)); l2a.l2_family = AF_BLUETOOTH; @@ -350,7 +385,10 @@ void BluetoothHciSocket::kernelDisconnectWorkArounds(int length, char* data) { l2a.l2_cid = l2cid; l2a.l2_bdaddr_type = data[8] + 1; // BDADDR_LE_PUBLIC (0x01), BDADDR_LE_RANDOM (0x02) - connect(l2socket, (struct sockaddr *)&l2a, sizeof(l2a)); + if (connect(l2socket, (struct sockaddr *)&l2a, sizeof(l2a)) < -1) { + close(l2socket); + return -3; + } this->_l2sockets[handle] = l2socket; } else if (length == 7 && data[0] == 0x04 && data[1] == 0x05 && data[2] == 0x04 && data[3] == 0x00) { @@ -361,6 +399,7 @@ void BluetoothHciSocket::kernelDisconnectWorkArounds(int length, char* data) { this->_l2sockets.erase(handle); } } + return 0; } NAN_METHOD(BluetoothHciSocket::New) { @@ -388,12 +427,12 @@ NAN_METHOD(BluetoothHciSocket::BindRaw) { BluetoothHciSocket* p = node::ObjectWrap::Unwrap(info.This()); int devId = 0; - int* pDevId = NULL; + int* pDevId = nullptr; if (info.Length() > 0) { Local arg0 = info[0]; if (arg0->IsInt32() || arg0->IsUint32()) { - devId = arg0->IntegerValue(); + devId = Nan::To(arg0).FromJust(); pDevId = &devId; } @@ -410,12 +449,12 @@ NAN_METHOD(BluetoothHciSocket::BindUser) { BluetoothHciSocket* p = node::ObjectWrap::Unwrap(info.This()); int devId = 0; - int* pDevId = NULL; + int* pDevId = nullptr; if (info.Length() > 0) { Local arg0 = info[0]; if (arg0->IsInt32() || arg0->IsUint32()) { - devId = arg0->IntegerValue(); + devId = Nan::To(arg0).FromJust(); pDevId = &devId; } @@ -466,14 +505,15 @@ NAN_METHOD(BluetoothHciSocket::GetDeviceList) { for (int i = 0; i < dl->dev_num; i++, dr++) { uint16_t devId = dr->dev_id; bool devUp = dr->dev_opt & (1 << HCI_UP); - if (dr != NULL) { + // TODO: smells like there's a bug here (but dr isn't read so...) + if (dr != nullptr) { v8::Local obj = Nan::New(); - obj->Set(Nan::New("devId").ToLocalChecked(), Nan::New(devId)); - obj->Set(Nan::New("devUp").ToLocalChecked(), Nan::New(devUp)); - obj->Set(Nan::New("idVendor").ToLocalChecked(), Nan::Null()); - obj->Set(Nan::New("idProduct").ToLocalChecked(), Nan::Null()); - obj->Set(Nan::New("busNumber").ToLocalChecked(), Nan::Null()); - obj->Set(Nan::New("deviceAddress").ToLocalChecked(), Nan::Null()); + Nan::Set(obj, Nan::New("devId").ToLocalChecked(), Nan::New(devId)); + Nan::Set(obj, Nan::New("devUp").ToLocalChecked(), Nan::New(devUp)); + Nan::Set(obj, Nan::New("idVendor").ToLocalChecked(), Nan::Null()); + Nan::Set(obj, Nan::New("idProduct").ToLocalChecked(), Nan::Null()); + Nan::Set(obj, Nan::New("busNumber").ToLocalChecked(), Nan::Null()); + Nan::Set(obj, Nan::New("deviceAddress").ToLocalChecked(), Nan::Null()); Nan::Set(deviceList, di++, obj); } } diff --git a/src/BluetoothHciSocket.h b/src/BluetoothHciSocket.h index 97e4244..34e6b81 100644 --- a/src/BluetoothHciSocket.h +++ b/src/BluetoothHciSocket.h @@ -39,9 +39,9 @@ class BluetoothHciSocket : public node::ObjectWrap { void poll(); - void emitErrnoError(); - int devIdFor(int* devId, bool isUp); - void kernelDisconnectWorkArounds(int length, char* data); + void emitErrnoError(const char *syscall); + int devIdFor(const int* devId, bool isUp); + int kernelDisconnectWorkArounds(int length, char* data); static void PollCloseCallback(uv_poll_t* handle); static void PollCallback(uv_poll_t* handle, int status, int events); diff --git a/test.js b/test.js new file mode 100644 index 0000000..e599d55 --- /dev/null +++ b/test.js @@ -0,0 +1,8 @@ +const BluetoothHCISocket = require('.'); + +// This test basically just makes sure we don't segfault at initialization time. +try { + console.log(new BluetoothHCISocket()); +} catch(err) { + console.error(err); +} \ No newline at end of file