@abandonware/noble
Version:
A Node.js BLE (Bluetooth Low Energy) central library.
1,654 lines (1,414 loc) • 74 kB
JavaScript
const should = require('should');
const sinon = require('sinon');
const { assert } = sinon;
const Gatt = require('../../../lib/hci-socket/gatt');
describe('hci-socket gatt', () => {
let gatt;
const address = 'address';
const aclStream = {
on: sinon.spy()
};
beforeEach(() => {
gatt = new Gatt(address, aclStream);
});
afterEach(() => {
sinon.reset();
});
it('constructor', () => {
should(gatt._address).equal(address);
should(gatt._aclStream).deepEqual(aclStream);
should(gatt._services).deepEqual({});
should(gatt._characteristics).deepEqual({});
should(gatt._descriptors).deepEqual({});
should(gatt._currentCommand).equal(null);
should(gatt._commandQueue).deepEqual([]);
should(gatt._mtu).equal(23);
should(gatt._security).equal('low');
assert.callCount(aclStream.on, 4);
assert.calledWithMatch(aclStream.on, 'data', sinon.match.func);
assert.calledWithMatch(aclStream.on, 'encrypt', sinon.match.func);
assert.calledWithMatch(aclStream.on, 'encryptFail', sinon.match.func);
assert.calledWithMatch(aclStream.on, 'end', sinon.match.func);
});
describe('onAclStreamData', () => {
const handleNotifyCallback = sinon.spy();
const handleConfirmationCallback = sinon.spy();
const notificationCallback = sinon.spy();
it('cid !== ATT_CID', () => {
const cid = 'NOT_ATT_CID';
const data = Buffer.from([0x00]);
// Register events
gatt.on('handleNotify', handleNotifyCallback);
gatt.on('handleConfirmation', handleConfirmationCallback);
gatt.on('notification', notificationCallback);
gatt.onAclStreamData(cid, data);
// No changes
should(gatt._address).equal(address);
should(gatt._aclStream).deepEqual(aclStream);
should(gatt._services).deepEqual({});
should(gatt._characteristics).deepEqual({});
should(gatt._descriptors).deepEqual({});
should(gatt._currentCommand).equal(null);
should(gatt._commandQueue).deepEqual([]);
should(gatt._mtu).equal(23);
should(gatt._security).equal('low');
// No events
assert.notCalled(handleNotifyCallback);
assert.notCalled(handleConfirmationCallback);
assert.notCalled(notificationCallback);
});
it('send same current command', () => {
// ATT_CID
const cid = 0x0004;
const data = Buffer.from([0x00]);
const currentCommand = {
buffer: data
};
// Setup current command
gatt._currentCommand = currentCommand;
// Register events
gatt.on('handleNotify', handleNotifyCallback);
gatt.on('handleConfirmation', handleConfirmationCallback);
gatt.on('notification', notificationCallback);
gatt.onAclStreamData(cid, data);
// No changes
should(gatt._address).equal(address);
should(gatt._aclStream).deepEqual(aclStream);
should(gatt._services).deepEqual({});
should(gatt._characteristics).deepEqual({});
should(gatt._descriptors).deepEqual({});
should(gatt._currentCommand).equal(currentCommand);
should(gatt._commandQueue).deepEqual([]);
should(gatt._mtu).equal(23);
should(gatt._security).equal('low');
// No events
assert.notCalled(handleNotifyCallback);
assert.notCalled(handleConfirmationCallback);
assert.notCalled(notificationCallback);
});
it('REQ_NOT_SUPP - not same as current', () => {
aclStream.write = sinon.spy();
// ATT_CID
const cid = 0x0004;
const data = Buffer.from([0x00]);
const currentCommand = {
buffer: Buffer.from([0xff])
};
// Setup current command
gatt._currentCommand = currentCommand;
// Register events
gatt.on('handleNotify', handleNotifyCallback);
gatt.on('handleConfirmation', handleConfirmationCallback);
gatt.on('notification', notificationCallback);
gatt.onAclStreamData(cid, data);
// No changes
should(gatt._address).equal(address);
should(gatt._aclStream).deepEqual(aclStream);
should(gatt._services).deepEqual({});
should(gatt._characteristics).deepEqual({});
should(gatt._descriptors).deepEqual({});
should(gatt._currentCommand).equal(currentCommand);
should(gatt._commandQueue).deepEqual([]);
should(gatt._mtu).equal(23);
should(gatt._security).equal('low');
// No events
assert.notCalled(handleNotifyCallback);
assert.notCalled(handleConfirmationCallback);
assert.notCalled(notificationCallback);
assert.calledOnceWithExactly(
aclStream.write,
4,
Buffer.from([0x01, 0x00, 0x00, 0x00, 0x06])
);
});
it('ATT_OP_HANDLE_NOTIFY', () => {
// ATT_CID
const cid = 0x0004;
const data = Buffer.from([0x1b, 0x01, 0x02, 0x03, 0x04]);
const services = { service1: {}, service2: {} };
const characteristics = {
service1: {
char1: {
valueHandle: 0
},
char2: {
valueHandle: 513
}
},
service2: {
char3: {
valueHandle: 513
}
}
};
// Setup
gatt._services = services;
gatt._characteristics = characteristics;
// Register events
gatt.on('handleNotify', handleNotifyCallback);
gatt.on('handleConfirmation', handleConfirmationCallback);
gatt.on('notification', notificationCallback);
gatt.onAclStreamData(cid, data);
// No changes
should(gatt._address).equal(address);
should(gatt._aclStream).deepEqual(aclStream);
should(gatt._services).deepEqual(services);
should(gatt._characteristics).deepEqual(characteristics);
should(gatt._descriptors).deepEqual({});
should(gatt._currentCommand).equal(null);
should(gatt._commandQueue).deepEqual([]);
should(gatt._mtu).equal(23);
should(gatt._security).equal('low');
// Events
assert.calledOnceWithExactly(
handleNotifyCallback,
address,
513,
Buffer.from([0x03, 0x04])
);
assert.notCalled(handleConfirmationCallback);
assert.callCount(notificationCallback, 2);
assert.calledWithExactly(
notificationCallback,
address,
'service1',
'char2',
Buffer.from([0x03, 0x04])
);
assert.calledWithExactly(
notificationCallback,
address,
'service1',
'char2',
Buffer.from([0x03, 0x04])
);
});
it('ATT_OP_HANDLE_IND', () => {
// ATT_CID
const cid = 0x0004;
const data = Buffer.from([0x1d, 0x01, 0x02, 0x03, 0x04]);
const services = { service1: {}, service2: {} };
const characteristics = {
service1: {
char1: {
valueHandle: 0
},
char2: {
valueHandle: 513
}
},
service2: {
char3: {
valueHandle: 513
}
}
};
// Setup
gatt._currentCommand = { buffer: Buffer.from([0x01]) };
gatt._services = services;
gatt._characteristics = characteristics;
// Register events
gatt.on('handleNotify', handleNotifyCallback);
gatt.on('handleConfirmation', handleConfirmationCallback);
gatt.on('notification', notificationCallback);
gatt.onAclStreamData(cid, data);
// No changes
should(gatt._address).equal(address);
should(gatt._aclStream).deepEqual(aclStream);
should(gatt._services).deepEqual(services);
should(gatt._characteristics).deepEqual(characteristics);
should(gatt._descriptors).deepEqual({});
should(gatt._currentCommand).deepEqual({ buffer: Buffer.from([0x01]) });
should(gatt._commandQueue).has.size(1);
should(gatt._mtu).equal(23);
should(gatt._security).equal('low');
// Events
assert.calledOnceWithExactly(
handleNotifyCallback,
address,
513,
Buffer.from([0x03, 0x04])
);
assert.notCalled(handleConfirmationCallback);
assert.callCount(notificationCallback, 2);
assert.calledWithExactly(
notificationCallback,
address,
'service1',
'char2',
Buffer.from([0x03, 0x04])
);
assert.calledWithExactly(
notificationCallback,
address,
'service1',
'char2',
Buffer.from([0x03, 0x04])
);
});
it('no current command', () => {
// ATT_CID
const cid = 0x0004;
const data = Buffer.from([0xFF, 0x01, 0x02, 0x03, 0x04]);
// Register events
gatt.on('handleNotify', handleNotifyCallback);
gatt.on('handleConfirmation', handleConfirmationCallback);
gatt.on('notification', notificationCallback);
gatt.onAclStreamData(cid, data);
// No changes
should(gatt._address).equal(address);
should(gatt._aclStream).deepEqual(aclStream);
should(gatt._services).deepEqual({});
should(gatt._characteristics).deepEqual({});
should(gatt._descriptors).deepEqual({});
should(gatt._currentCommand).equal(null);
should(gatt._commandQueue).deepEqual([]);
should(gatt._mtu).equal(23);
should(gatt._security).equal('low');
// Events
assert.notCalled(handleNotifyCallback);
assert.notCalled(handleConfirmationCallback);
assert.notCalled(notificationCallback);
});
[0x05, 0x08, 0x0f].forEach((errorCode) => {
it(`ATT_OP_ERROR ${errorCode} and security low`, () => {
aclStream.encrypt = sinon.spy();
// ATT_CID
const cid = 0x0004;
const data = Buffer.from([0x01, 0x01, 0x02, 0x03, errorCode]);
// Setup
gatt._currentCommand = { buffer: Buffer.from([0x00]) };
// Register events
gatt.on('handleNotify', handleNotifyCallback);
gatt.on('handleConfirmation', handleConfirmationCallback);
gatt.on('notification', notificationCallback);
gatt.onAclStreamData(cid, data);
// No changes
should(gatt._address).equal(address);
should(gatt._aclStream).deepEqual(aclStream);
should(gatt._services).deepEqual({});
should(gatt._characteristics).deepEqual({});
should(gatt._descriptors).deepEqual({});
should(gatt._currentCommand).deepEqual({ buffer: Buffer.from([0x00]) });
should(gatt._commandQueue).deepEqual([]);
should(gatt._mtu).equal(23);
should(gatt._security).equal('low');
// Events
assert.notCalled(handleNotifyCallback);
assert.notCalled(handleConfirmationCallback);
assert.notCalled(notificationCallback);
assert.calledOnceWithExactly(aclStream.encrypt);
});
it(`ATT_OP_ERROR ${errorCode} and security medium`, () => {
// ATT_CID
const cid = 0x0004;
const data = Buffer.from([0x01, 0x01, 0x02, 0x03, errorCode]);
const callback = sinon.spy();
const currentCommand = { buffer: Buffer.from([0x00]), callback };
// Setup
gatt._currentCommand = currentCommand;
gatt._security = 'medium';
// Register events
gatt.on('handleNotify', handleNotifyCallback);
gatt.on('handleConfirmation', handleConfirmationCallback);
gatt.on('notification', notificationCallback);
gatt.onAclStreamData(cid, data);
// No changes
should(gatt._address).equal(address);
should(gatt._aclStream).deepEqual(aclStream);
should(gatt._services).deepEqual({});
should(gatt._characteristics).deepEqual({});
should(gatt._descriptors).deepEqual({});
should(gatt._currentCommand).equal(null);
should(gatt._commandQueue).deepEqual([]);
should(gatt._mtu).equal(23);
should(gatt._security).equal('medium');
// Events
assert.notCalled(handleNotifyCallback);
assert.notCalled(handleConfirmationCallback);
assert.notCalled(notificationCallback);
assert.calledOnceWithExactly(callback, data);
});
});
it('command callback with queue', () => {
// ATT_CID
const cid = 0x0004;
const data = Buffer.from([0x01, 0x01, 0x02, 0x03, 0x00]);
const callback = sinon.spy();
const currentCommand = { buffer: Buffer.from([0x00]), callback };
const queueCallback = sinon.spy();
const queueWriteCallback = sinon.spy();
const commandQueue = [{ buffer: Buffer.from([0x98]), callback: queueCallback }, { buffer: Buffer.from([0x99]), writeCallback: queueWriteCallback }];
// Setup
gatt._currentCommand = currentCommand;
gatt._commandQueue = [...commandQueue];
// Register events
gatt.on('handleNotify', handleNotifyCallback);
gatt.on('handleConfirmation', handleConfirmationCallback);
gatt.on('notification', notificationCallback);
gatt.onAclStreamData(cid, data);
// No changes
should(gatt._address).equal(address);
should(gatt._aclStream).deepEqual(aclStream);
should(gatt._services).deepEqual({});
should(gatt._characteristics).deepEqual({});
should(gatt._descriptors).deepEqual({});
should(gatt._currentCommand).deepEqual(commandQueue[0]);
should(gatt._commandQueue).deepEqual([commandQueue[1]]);
should(gatt._mtu).equal(23);
should(gatt._security).equal('low');
// Events
assert.notCalled(handleNotifyCallback);
assert.notCalled(handleConfirmationCallback);
assert.notCalled(notificationCallback);
assert.calledOnceWithExactly(callback, data);
assert.notCalled(queueCallback);
assert.notCalled(queueWriteCallback);
assert.calledOnceWithExactly(aclStream.write, 4, Buffer.from([0x98]));
});
it('write command callback with queue', () => {
// ATT_CID
const cid = 0x0004;
const data = Buffer.from([0x01, 0x01, 0x02, 0x03, 0x00]);
const callback = sinon.spy();
const currentCommand = { buffer: Buffer.from([0x00]), callback };
const queueCallback = sinon.spy();
const queueWriteCallback = sinon.spy();
const commandQueue = [{ buffer: Buffer.from([0x99]), writeCallback: queueWriteCallback }, { buffer: Buffer.from([0x98]), callback: queueCallback }];
// Setup
gatt._currentCommand = currentCommand;
gatt._commandQueue = [...commandQueue];
// Register events
gatt.on('handleNotify', handleNotifyCallback);
gatt.on('handleConfirmation', handleConfirmationCallback);
gatt.on('notification', notificationCallback);
gatt.onAclStreamData(cid, data);
// No changes
should(gatt._address).equal(address);
should(gatt._aclStream).deepEqual(aclStream);
should(gatt._services).deepEqual({});
should(gatt._characteristics).deepEqual({});
should(gatt._descriptors).deepEqual({});
should(gatt._currentCommand).deepEqual(commandQueue[1]);
should(gatt._commandQueue).deepEqual([]);
should(gatt._mtu).equal(23);
should(gatt._security).equal('low');
// Events
assert.notCalled(handleNotifyCallback);
assert.notCalled(handleConfirmationCallback);
assert.notCalled(notificationCallback);
assert.calledOnceWithExactly(callback, data);
assert.notCalled(queueCallback);
assert.calledOnceWithExactly(queueWriteCallback);
assert.callCount(aclStream.write, 2);
assert.calledWithExactly(aclStream.write, 4, Buffer.from([0x98]));
assert.calledWithExactly(aclStream.write, 4, Buffer.from([0x99]));
});
});
describe('onAclStreamEncrypt', () => {
it('should not write attribute', () => {
aclStream.write = sinon.spy();
// Setup
gatt._security = 'low';
gatt.onAclStreamEncrypt(false);
should(gatt._security).equal('low');
assert.notCalled(aclStream.write);
});
it('should write attribute', () => {
aclStream.write = sinon.spy();
const buffer = Buffer.from([0x01, 0x99]);
// Setup
gatt._security = 'low';
gatt._currentCommand = { buffer };
gatt.onAclStreamEncrypt(true);
should(gatt._security).equal('medium');
assert.calledOnceWithExactly(aclStream.write, 4, buffer);
});
});
it('onAclStreamEnd should remove listeners', () => {
aclStream.removeListener = sinon.spy();
gatt.onAclStreamEnd();
assert.callCount(aclStream.removeListener, 4);
assert.calledWithMatch(aclStream.removeListener, 'data', sinon.match.func);
assert.calledWithMatch(aclStream.removeListener, 'encrypt', sinon.match.func);
assert.calledWithMatch(aclStream.removeListener, 'encryptFail', sinon.match.func);
assert.calledWithMatch(aclStream.removeListener, 'end', sinon.match.func);
});
it('writeAtt should call acl write', () => {
aclStream.write = sinon.spy();
gatt.writeAtt('data');
assert.calledOnceWithMatch(aclStream.write, 4, 'data');
});
it('errorResponse', () => {
aclStream.write = sinon.spy();
const opcode = 8;
const handle = 3000;
const status = 7;
const result = gatt.errorResponse(opcode, handle, status);
should(result).deepEqual(Buffer.from([0x01, 0x08, 0xb8, 0x0b, 0x07]));
});
describe('queueCommand', () => {
it('should only queue', () => {
const buffer = Buffer.from([0x01, 0x01, 0x02, 0x03, 0x00]);
const callback = sinon.spy();
const writeCallback = sinon.spy();
// Setup
gatt._currentCommand = 'command';
gatt._queueCommand(buffer, callback, writeCallback);
// No changes
should(gatt._address).equal(address);
should(gatt._aclStream).deepEqual(aclStream);
should(gatt._services).deepEqual({});
should(gatt._characteristics).deepEqual({});
should(gatt._descriptors).deepEqual({});
should(gatt._currentCommand).equal('command');
should(gatt._commandQueue).deepEqual([{ buffer, callback, writeCallback }]);
should(gatt._mtu).equal(23);
assert.notCalled(callback);
assert.notCalled(writeCallback);
});
it('should queue and unqueue', () => {
aclStream.write = sinon.spy();
const buffer = Buffer.from([0x01, 0x01, 0x02, 0x03, 0x00]);
const callback = sinon.spy();
const writeCallback = sinon.spy();
const queueCallback = sinon.spy();
const queueWriteCallback = sinon.spy();
const commandQueue = [{ buffer: Buffer.from([0x98]), callback: queueCallback }, { buffer: Buffer.from([0x99]), writeCallback: queueWriteCallback }];
// Setup
gatt._commandQueue = [...commandQueue];
gatt._queueCommand(buffer, callback, writeCallback);
// No changes
should(gatt._address).equal(address);
should(gatt._aclStream).deepEqual(aclStream);
should(gatt._services).deepEqual({});
should(gatt._characteristics).deepEqual({});
should(gatt._descriptors).deepEqual({});
should(gatt._currentCommand).deepEqual(commandQueue[0]);
should(gatt._commandQueue).deepEqual([commandQueue[1], { buffer, callback, writeCallback }]);
should(gatt._mtu).equal(23);
should(gatt._security).equal('low');
assert.notCalled(queueCallback);
assert.notCalled(queueWriteCallback);
assert.calledOnceWithExactly(aclStream.write, 4, Buffer.from([0x98]));
});
it('write command callback with queue', () => {
aclStream.write = sinon.spy();
const buffer = Buffer.from([0x01, 0x01, 0x02, 0x03, 0x00]);
const callback = sinon.spy();
const writeCallback = sinon.spy();
const queueCallback = sinon.spy();
const queueWriteCallback = sinon.spy();
const commandQueue = [{ buffer: Buffer.from([0x99]), writeCallback: queueWriteCallback }, { buffer: Buffer.from([0x98]), callback: queueCallback }];
// Setup
gatt._commandQueue = [...commandQueue];
gatt._queueCommand(buffer, callback, writeCallback);
// No changes
should(gatt._address).equal(address);
should(gatt._aclStream).deepEqual(aclStream);
should(gatt._services).deepEqual({});
should(gatt._characteristics).deepEqual({});
should(gatt._descriptors).deepEqual({});
should(gatt._currentCommand).deepEqual(commandQueue[1]);
should(gatt._commandQueue).deepEqual([{ buffer, callback, writeCallback }]);
should(gatt._mtu).equal(23);
should(gatt._security).equal('low');
assert.notCalled(queueCallback);
assert.calledOnceWithExactly(queueWriteCallback);
assert.callCount(aclStream.write, 2);
assert.calledWithExactly(aclStream.write, 4, Buffer.from([0x98]));
assert.calledWithExactly(aclStream.write, 4, Buffer.from([0x99]));
});
});
it('mtuRequest', () => {
const mtu = 67;
const result = gatt.mtuRequest(mtu);
should(result).deepEqual(Buffer.from([0x02, 0x43, 0x00]));
});
it('readByGroupRequest', () => {
const startHandle = 8;
const endHandle = 3000;
const groupUuid = 7;
const result = gatt.readByGroupRequest(startHandle, endHandle, groupUuid);
should(result).deepEqual(Buffer.from([0x10, 0x08, 0x00, 0xb8, 0x0b, 0x07, 0x00]));
});
it('readByTypeRequest', () => {
const startHandle = 8;
const endHandle = 3000;
const groupUuid = 7;
const result = gatt.readByTypeRequest(startHandle, endHandle, groupUuid);
should(result).deepEqual(Buffer.from([0x08, 0x08, 0x00, 0xb8, 0x0b, 0x07, 0x00]));
});
it('readRequest', () => {
const handle = 67;
const result = gatt.readRequest(handle);
should(result).deepEqual(Buffer.from([0x0a, 0x43, 0x00]));
});
it('readBlobRequest', () => {
const handle = 67;
const offset = 68;
const result = gatt.readBlobRequest(handle, offset);
should(result).deepEqual(Buffer.from([0x0c, 0x43, 0x00, 0x44, 0x00]));
});
it('findInfoRequest', () => {
const startHandle = 67;
const endHandle = 68;
const result = gatt.findInfoRequest(startHandle, endHandle);
should(result).deepEqual(Buffer.from([0x04, 0x43, 0x00, 0x44, 0x00]));
});
it('writeRequest withoutResponse', () => {
const handle = 67;
const data = Buffer.from([0x05, 0x06, 0x07]);
const withoutResponse = true;
const result = gatt.writeRequest(handle, data, withoutResponse);
should(result).deepEqual(Buffer.from([0x52, 0x43, 0x00, 0x05, 0x06, 0x07]));
});
it('writeRequest withResponse', () => {
const handle = 67;
const data = Buffer.from([0x05, 0x06, 0x07]);
const withResponse = false;
const result = gatt.writeRequest(handle, data, withResponse);
should(result).deepEqual(Buffer.from([0x12, 0x43, 0x00, 0x05, 0x06, 0x07]));
});
it('prepareWriteRequest', () => {
const handle = 67;
const offset = 68;
const data = Buffer.from([0x05, 0x06, 0x07]);
const result = gatt.prepareWriteRequest(handle, offset, data);
should(result).deepEqual(Buffer.from([0x16, 0x43, 0x00, 0x44, 0x00, 0x05, 0x06, 0x07]));
});
it('executeWriteRequest cancelPreparedWrites', () => {
const handle = 67;
const cancelPreparedWrites = true;
const result = gatt.executeWriteRequest(handle, cancelPreparedWrites);
should(result).deepEqual(Buffer.from([0x18, 0x00]));
});
it('executeWriteRequest preparedWrites', () => {
const handle = 67;
const cancelPreparedWrites = false;
const result = gatt.executeWriteRequest(handle, cancelPreparedWrites);
should(result).deepEqual(Buffer.from([0x18, 0x01]));
});
it('handleConfirmation', () => {
const result = gatt.handleConfirmation();
should(result).deepEqual(Buffer.from([0x1e]));
});
it('exchangeMtu', () => {
const mtu = 63;
const queueCommand = sinon.spy();
const callback = sinon.stub();
gatt._queueCommand = queueCommand;
gatt._mtu = 22;
gatt.on('mtu', callback);
gatt.exchangeMtu(mtu);
assert.calledOnce(queueCommand);
queueCommand.callArgWith(1, ['d', 'a', 't', 'a']);
assert.calledOnceWithExactly(callback, address, 22);
should(gatt._mtu).equal(22);
});
it('exchangeMtu ATT_OP_MTU_RESP', () => {
const mtu = 63;
const queueCommand = sinon.spy();
const callback = sinon.stub();
gatt._queueCommand = queueCommand;
gatt._mtu = 22;
gatt.on('mtu', callback);
gatt.exchangeMtu(mtu);
assert.calledOnce(queueCommand);
queueCommand.callArgWith(1, Buffer.from([0x03, 0x12, 0x33]));
assert.calledOnceWithExactly(callback, address, 13074);
should(gatt._mtu).equal(13074);
});
it('addService', () => {
const service = { uuid: 'service' };
gatt.addService(service);
should(gatt._services).deepEqual({ service });
});
describe('discoverServices', () => {
beforeEach(() => {
gatt._queueCommand = sinon.spy();
gatt.readByGroupRequest = sinon.spy();
});
it('not ATT_OP_READ_BY_GROUP_RESP > non discovered', () => {
const callbackDiscovered = sinon.stub();
const callbackDiscover = sinon.stub();
gatt.on('servicesDiscovered', callbackDiscovered);
gatt.on('servicesDiscover', callbackDiscover);
gatt.discoverServices(['service1']);
gatt._queueCommand.callArgWith(1, Buffer.from([0x00]));
assert.calledOnceWithExactly(callbackDiscovered, address, []);
assert.calledOnceWithExactly(callbackDiscover, address, []);
assert.callCount(gatt._queueCommand, 1);
assert.calledOnceWithExactly(gatt.readByGroupRequest, 0x0001, 0xffff, 10240);
});
it('ATT_OP_READ_BY_GROUP_RESP > queue', () => {
const callbackDiscovered = sinon.stub();
const callbackDiscover = sinon.stub();
gatt.on('servicesDiscovered', callbackDiscovered);
gatt.on('servicesDiscover', callbackDiscover);
gatt.discoverServices(['service1']);
// Build data
const data = [17, 6];
for (let i = 0; i < data[1]; i++) {
for (let j = 0; j < 8; j++) {
data.push(i * 10 + j);
}
}
gatt._queueCommand.callArgWith(1, Buffer.from(data));
assert.notCalled(callbackDiscovered);
assert.notCalled(callbackDiscover);
assert.callCount(gatt._queueCommand, 2);
assert.callCount(gatt.readByGroupRequest, 2);
assert.calledWithExactly(gatt.readByGroupRequest, 0x0001, 0xffff, 10240);
assert.calledWithExactly(gatt.readByGroupRequest, 14135, 0xffff, 10240);
});
it('ATT_OP_READ_BY_GROUP_RESP > event', () => {
const callbackDiscovered = sinon.stub();
const callbackDiscover = sinon.stub();
gatt.on('servicesDiscovered', callbackDiscovered);
gatt.on('servicesDiscover', callbackDiscover);
gatt.discoverServices([]);
// Build data
const data = [17, 7];
for (let i = 0; i < data[1]; i++) {
for (let j = 0; j < 8; j++) {
data.push(255);
}
}
gatt._queueCommand.callArgWith(1, Buffer.from(data));
const services = [{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffff'
},
{ startHandle: 65535, endHandle: 65535, uuid: 'ffffff' }];
assert.calledOnceWithExactly(callbackDiscovered, address, services);
const serviceUuids = [
'ffffffffffffffffffffffffffffffff',
'ffffffffffffffffffff',
'ffffff'
];
assert.calledOnceWithExactly(callbackDiscover, address, serviceUuids);
assert.callCount(gatt._queueCommand, 1);
assert.calledOnceWithExactly(gatt.readByGroupRequest, 0x0001, 0xffff, 10240);
should(gatt._services).deepEqual({
ffffffffffffffffffffffffffffffff: {
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
ffffffffffffffffffff: {
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffff'
},
ffffff: {
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffff'
}
});
});
it('ATT_OP_READ_BY_GROUP_RESP > event matching uuid', () => {
const callbackDiscovered = sinon.stub();
const callbackDiscover = sinon.stub();
gatt.on('servicesDiscovered', callbackDiscovered);
gatt.on('servicesDiscover', callbackDiscover);
gatt.discoverServices(['ffffffffffffffffffff']);
// Build data
const data = [17, 7];
for (let i = 0; i < data[1]; i++) {
for (let j = 0; j < 8; j++) {
data.push(255);
}
}
gatt._queueCommand.callArgWith(1, Buffer.from(data));
const services = [{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffff'
},
{ startHandle: 65535, endHandle: 65535, uuid: 'ffffff' }];
assert.calledOnceWithExactly(callbackDiscovered, address, services);
const serviceUuids = [
'ffffffffffffffffffff'
];
assert.calledOnceWithExactly(callbackDiscover, address, serviceUuids);
assert.callCount(gatt._queueCommand, 1);
assert.calledOnceWithExactly(gatt.readByGroupRequest, 0x0001, 0xffff, 10240);
should(gatt._services).deepEqual({
ffffffffffffffffffffffffffffffff: {
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
ffffffffffffffffffff: {
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffff'
},
ffffff: {
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffff'
}
});
});
it('ATT_OP_READ_BY_GROUP_RESP > event not matching uuid', () => {
const callbackDiscovered = sinon.stub();
const callbackDiscover = sinon.stub();
gatt.on('servicesDiscovered', callbackDiscovered);
gatt.on('servicesDiscover', callbackDiscover);
gatt.discoverServices(['bbbbbbbbbbbbbbbbbbbbbbbbbbb']);
// Build data
const data = [17, 7];
for (let i = 0; i < data[1]; i++) {
for (let j = 0; j < 8; j++) {
data.push(255);
}
}
gatt._queueCommand.callArgWith(1, Buffer.from(data));
const services = [{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffff'
},
{ startHandle: 65535, endHandle: 65535, uuid: 'ffffff' }];
assert.calledOnceWithExactly(callbackDiscovered, address, services);
const serviceUuids = [];
assert.calledOnceWithExactly(callbackDiscover, address, serviceUuids);
assert.callCount(gatt._queueCommand, 1);
assert.calledOnceWithExactly(gatt.readByGroupRequest, 0x0001, 0xffff, 10240);
should(gatt._services).deepEqual({
ffffffffffffffffffffffffffffffff: {
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff'
},
ffffffffffffffffffff: {
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffffffffffffffffff'
},
ffffff: {
startHandle: 65535,
endHandle: 65535,
uuid: 'ffffff'
}
});
});
});
describe('discoverIncludedServices', () => {
beforeEach(() => {
gatt._queueCommand = sinon.spy();
gatt.readByTypeRequest = sinon.spy();
});
it('not ATT_OP_READ_BY_TYPE_RESP > non discovered', () => {
const callback = sinon.stub();
const service = {
startHandle: 0xaaaa,
endHandle: 0xdddd,
uuid: 'uuid'
};
gatt._services[service.uuid] = service;
gatt.on('includedServicesDiscover', callback);
gatt.discoverIncludedServices(service.uuid);
gatt._queueCommand.callArgWith(1, Buffer.from([0x00]));
assert.calledOnceWithExactly(callback, address, service.uuid, []);
assert.callCount(gatt._queueCommand, 1);
assert.calledOnceWithExactly(gatt.readByTypeRequest, service.startHandle, service.endHandle, 10242);
});
it('ATT_OP_READ_BY_TYPE_RESP > queue', () => {
const callback = sinon.stub();
const service = {
startHandle: 0xaaaa,
endHandle: 0xdddd,
uuid: 'uuid'
};
gatt._services[service.uuid] = service;
gatt.on('includedServicesDiscover', callback);
gatt.discoverIncludedServices(service.uuid);
// Build data
const data = [9, 8];
for (let i = 0; i < data[1]; i++) {
for (let j = 0; j < 10; j++) {
data.push(i * 10 + j);
}
}
gatt._queueCommand.callArgWith(1, Buffer.from(data));
assert.notCalled(callback);
assert.callCount(gatt._queueCommand, 2);
assert.callCount(gatt.readByTypeRequest, 2);
assert.calledWithExactly(gatt.readByTypeRequest, service.startHandle, service.endHandle, 10242);
assert.calledWithExactly(gatt.readByTypeRequest, 18761, 56797, 10242);
});
it('ATT_OP_READ_BY_TYPE_RESP > event', () => {
const callback = sinon.stub();
const service = {
startHandle: 0xaaaa,
endHandle: 0xffff,
uuid: 'uuid'
};
gatt._services[service.uuid] = service;
gatt.on('includedServicesDiscover', callback);
gatt.discoverIncludedServices(service.uuid, []);
// Build data
const data = [9, 7];
for (let i = 0; i < data[1]; i++) {
for (let j = 0; j < 10; j++) {
data.push(255);
}
}
gatt._queueCommand.callArgWith(1, Buffer.from(data));
const includedServiceUuids = [
'ffffffffffffffffffffffffffffffff',
'ffffffffffffffffffffffffffffffff',
'ffffffffffffffffffffffffffffffff',
'ffffffffffffffffffffffffffffffff',
'ffffffffffffffffffffffffffffffff',
'ffffffffffffffffffffffffffffffff',
'ffffffffffffffffffffffffffffffff',
'ffffffffffffffffffffffffffffff',
'ffffffffffffffff',
'ff'
];
assert.calledOnceWithExactly(callback, address, service.uuid, includedServiceUuids);
assert.callCount(gatt._queueCommand, 1);
assert.calledOnceWithExactly(gatt.readByTypeRequest, service.startHandle, service.endHandle, 10242);
});
it('ATT_OP_READ_BY_TYPE_RESP > event matching uuid', () => {
const callback = sinon.stub();
const service = {
startHandle: 0xaaaa,
endHandle: 0xffff,
uuid: 'uuid'
};
gatt._services[service.uuid] = service;
gatt.on('includedServicesDiscover', callback);
gatt.discoverIncludedServices(service.uuid, ['ff']);
// Build data
const data = [9, 7];
for (let i = 0; i < data[1]; i++) {
for (let j = 0; j < 10; j++) {
data.push(255);
}
}
gatt._queueCommand.callArgWith(1, Buffer.from(data));
assert.calledOnceWithExactly(callback, address, service.uuid, ['ff']);
assert.callCount(gatt._queueCommand, 1);
assert.calledOnceWithExactly(gatt.readByTypeRequest, service.startHandle, service.endHandle, 10242);
});
it('ATT_OP_READ_BY_TYPE_RESP > event not matching uuid', () => {
const callback = sinon.stub();
const service = {
startHandle: 0xaaaa,
endHandle: 0xcccc,
uuid: 'uuid'
};
gatt._services[service.uuid] = service;
gatt.on('includedServicesDiscover', callback);
gatt.discoverIncludedServices(service.uuid, ['ff']);
// Build data
const data = [9, 7];
for (let i = 0; i < data[1]; i++) {
for (let j = 0; j < 10; j++) {
data.push(255);
}
}
gatt._queueCommand.callArgWith(1, Buffer.from(data));
assert.notCalled(callback);
assert.callCount(gatt._queueCommand, 2);
assert.callCount(gatt.readByTypeRequest, 2);
assert.calledWithExactly(gatt.readByTypeRequest, service.startHandle, service.endHandle, 10242);
assert.calledWithExactly(gatt.readByTypeRequest, service.startHandle, service.endHandle, 10242);
});
});
describe('addCharacteristics', () => {
it('register none', () => {
const callback = sinon.stub();
const serviceUuid = 'uuid';
const characteristics = [];
gatt.on('includedServicesDiscover', callback);
gatt.addCharacteristics(serviceUuid, characteristics);
should(gatt._characteristics).deepEqual({ [serviceUuid]: {} });
should(gatt._descriptors).deepEqual({ [serviceUuid]: {} });
});
it('register new', () => {
const callback = sinon.stub();
const serviceUuid = 'uuid';
const characteristics = [
{
uuid: 'c_uuid',
number: 1
},
{
uuid: 'c_uuid',
number: 2
},
{
uuid: 'c_uuid_2'
}
];
gatt.on('includedServicesDiscover', callback);
gatt.addCharacteristics(serviceUuid, characteristics);
should(gatt._characteristics).deepEqual({
[serviceUuid]: {
c_uuid: {
uuid: 'c_uuid',
number: 2
},
c_uuid_2: {
uuid: 'c_uuid_2'
}
}
});
should(gatt._descriptors).deepEqual({ [serviceUuid]: {} });
});
it('replace existing', () => {
const callback = sinon.stub();
const serviceUuid = 'uuid';
const characteristics = [
{
uuid: 'c_uuid',
number: 2
}
];
gatt._characteristics[serviceUuid] = {
c_uuid: {
uuid: 'c_uuid',
number: 1
}
};
gatt.on('includedServicesDiscover', callback);
gatt.addCharacteristics(serviceUuid, characteristics);
should(gatt._characteristics).deepEqual({
[serviceUuid]: {
c_uuid: {
uuid: 'c_uuid',
number: 2
}
}
});
should(gatt._descriptors).deepEqual({ [serviceUuid]: {} });
});
});
describe('discoverCharacteristics', () => {
beforeEach(() => {
gatt._queueCommand = sinon.spy();
gatt.readByTypeRequest = sinon.spy();
});
it('not ATT_OP_READ_BY_TYPE_RESP > non discovered', () => {
const callbackDiscovered = sinon.stub();
const callbackDiscover = sinon.stub();
const service = {
startHandle: 0xaaaa,
endHandle: 0xdddd,
uuid: 'uuid'
};
gatt._services[service.uuid] = service;
gatt.on('characteristicsDiscovered', callbackDiscovered);
gatt.on('characteristicsDiscover', callbackDiscover);
gatt.discoverCharacteristics(service.uuid);
gatt._queueCommand.callArgWith(1, Buffer.from([0x00]));
assert.calledOnceWithExactly(callbackDiscovered, address, service.uuid, []);
assert.calledOnceWithExactly(callbackDiscover, address, service.uuid, []);
assert.callCount(gatt._queueCommand, 1);
assert.calledOnceWithExactly(gatt.readByTypeRequest, service.startHandle, service.endHandle, 10243);
});
it('ATT_OP_READ_BY_TYPE_RESP > queue', () => {
const callbackDiscovered = sinon.stub();
const callbackDiscover = sinon.stub();
const service = {
startHandle: 0xaaaa,
endHandle: 0xdddd,
uuid: 'uuid'
};
gatt._services[service.uuid] = service;
gatt.on('characteristicsDiscovered', callbackDiscovered);
gatt.on('characteristicsDiscover', callbackDiscover);
gatt.discoverCharacteristics(service.uuid);
// Build data
const data = [9, 8];
for (let i = 0; i < data[1]; i++) {
for (let j = 0; j < 10; j++) {
data.push(i * 10 + j);
}
}
gatt._queueCommand.callArgWith(1, Buffer.from(data));
assert.notCalled(callbackDiscovered);
assert.notCalled(callbackDiscover);
assert.callCount(gatt._queueCommand, 2);
assert.callCount(gatt.readByTypeRequest, 2);
assert.calledWithExactly(gatt.readByTypeRequest, service.startHandle, service.endHandle, 10243);
assert.calledWithExactly(gatt.readByTypeRequest, 19532, 56797, 10243);
});
it('ATT_OP_READ_BY_TYPE_RESP > event', () => {
const callbackDiscovered = sinon.stub();
const callbackDiscover = sinon.stub();
const service = {
startHandle: 0xaaaa,
endHandle: 0xffff,
uuid: 'uuid'
};
gatt._services[service.uuid] = service;
gatt.on('characteristicsDiscovered', callbackDiscovered);
gatt.on('characteristicsDiscover', callbackDiscover);
gatt.discoverCharacteristics(service.uuid, []);
// Build data
const data = [9, 8];
for (let i = 0; i < data[1]; i++) {
for (let j = 0; j < 7; j++) {
data.push(255);
}
}
gatt._queueCommand.callArgWith(1, Buffer.from(data));
const characteristics = [
{
properties: [
'broadcast',
'read',
'writeWithoutResponse',
'write',
'notify',
'indicate',
'authenticatedSignedWrites',
'extendedProperties'
],
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
properties: [
'broadcast',
'read',
'writeWithoutResponse',
'write',
'notify',
'indicate',
'authenticatedSignedWrites',
'extendedProperties'
],
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
properties: [
'broadcast',
'read',
'writeWithoutResponse',
'write',
'notify',
'indicate',
'authenticatedSignedWrites',
'extendedProperties'
],
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
properties: [
'broadcast',
'read',
'writeWithoutResponse',
'write',
'notify',
'indicate',
'authenticatedSignedWrites',
'extendedProperties'
],
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
properties: [
'broadcast',
'read',
'writeWithoutResponse',
'write',
'notify',
'indicate',
'authenticatedSignedWrites',
'extendedProperties'
],
uuid: 'ffffffffffffffffffffffffffffffff'
},
{
properties: [
'broadcast',
'read',
'writeWithoutResponse',
'write',
'notify',
'indicate',
'authenticatedSignedWrites',
'extendedProperties'
],
uuid: 'ffffffffffffffffffffff'
},
{
properties: [
'broadcast',
'read',
'writeWithoutResponse',
'write',
'notify',
'indicate',
'authenticatedSignedWrites',
'extendedProperties'
],
uuid: 'ffffff'
}
];
assert.calledOnceWithExactly(callbackDiscover, address, service.uuid, characteristics);
const discoveredChars = [
{
startHandle: 65535,
properties: 255,
valueHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff',
propsDecoded: [
'broadcast',
'read',
'writeWithoutResponse',
'write',
'notify',
'indicate',
'authenticatedSignedWrites',
'extendedProperties'
],
rawProps: 255,
endHandle: 65534
},
{
startHandle: 65535,
properties: 255,
valueHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff',
propsDecoded: [
'broadcast',
'read',
'writeWithoutResponse',
'write',
'notify',
'indicate',
'authenticatedSignedWrites',
'extendedProperties'
],
rawProps: 255,
endHandle: 65534
},
{
startHandle: 65535,
properties: 255,
valueHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff',
propsDecoded: [
'broadcast',
'read',
'writeWithoutResponse',
'write',
'notify',
'indicate',
'authenticatedSignedWrites',
'extendedProperties'
],
rawProps: 255,
endHandle: 65534
},
{
startHandle: 65535,
properties: 255,
valueHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff',
propsDecoded: [
'broadcast',
'read',
'writeWithoutResponse',
'write',
'notify',
'indicate',
'authenticatedSignedWrites',
'extendedProperties'
],
rawProps: 255,
endHandle: 65534
},
{
startHandle: 65535,
properties: 255,
valueHandle: 65535,
uuid: 'ffffffffffffffffffffffffffffffff',
propsDecoded: [
'broadcast',
'read',
'writeWithoutResponse',
'write',
'notify',
'indicate',
'authenticatedSignedWrites',
'extendedProperties'
],
rawProps: 255,
endHandle: 65534
},
{
startHandle: 65535,
properties: 255,
valueHandle: 65535,
uuid: 'ffffffffffffffffffffff',
propsDecoded: [
'broadcast',
'read',
'writeWithoutResponse',
'write',
'notify',
'indicate',
'authenticatedSignedWrites',
'extendedProperties'
],
rawProps: 255,
endHandle: 65534
},
{
startHandle: 65535,
properties: 255,
valueHandle: 65535,
uuid: 'ffffff',
propsDecoded: [
'broadcast',
'read',
'writeWithoutResponse',
'write',
'notify',
'indicate',
'authenticatedSignedWrites',
'extendedProperties'
],
rawProps: 255,
endHandle: 65535
}
];
assert.calledOnceWithExactly(callbackDiscovered, address, service.uuid, discoveredChars);
assert.callCount(gatt._queueCommand, 1);
assert.calledOnceWithExactly(gatt.readByTypeRequest, service.startHandle, service.endHandle, 10243);
});
it('ATT_OP_READ_BY_TYPE_RESP > event not matching uuid', () => {
const callbackDiscover = sinon.stub();
const service = {
startHandle: 0xaaaa,
endHandle: 0xffff,
uuid: 'uuid'
};
gatt._services[service.uuid] = service;
gatt.on('characteristicsDiscover', callbackDiscover);
gatt.discoverCharacteristics(service.uuid, ['ffffff']);
// Build data
const data = [9, 8];
for (let i = 0; i < data[1]; i++) {
for (let j = 0; j < 7; j++) {
data.push(255);
}
}
gatt._queueCommand.callArgWith(1, Buffer.from(data));
const discoveredChars = [
{
properties: [
'broadcast',
'read',
'writeWithoutResponse',
'write',
'notify',
'indicate',
'authenticatedSignedWrites',
'extendedProperties'
],
uuid: 'ffffff'
}
];
assert.calledOnceWithExactly(callbackDiscover, address, service.uuid, discoveredChars);