@stoprocent/bleno 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.jshintrc +5 -0
- package/LICENSE +20 -0
- package/README.md +409 -0
- package/binding.gyp +17 -0
- package/examples/battery-service/README.md +14 -0
- package/examples/battery-service/battery-level-characteristic.js +45 -0
- package/examples/battery-service/battery-service.js +16 -0
- package/examples/battery-service/main.js +28 -0
- package/examples/battery-service/package-lock.json +1314 -0
- package/examples/battery-service/package.json +20 -0
- package/examples/blink1/README.md +44 -0
- package/examples/blink1/blink1-fade-rgb-characteristic.js +42 -0
- package/examples/blink1/blink1-rgb-characteristic.js +38 -0
- package/examples/blink1/blink1-service.js +19 -0
- package/examples/blink1/device-information-service.js +19 -0
- package/examples/blink1/hardware-revision-characteristic.js +32 -0
- package/examples/blink1/main.js +32 -0
- package/examples/blink1/serial-number-characteristic.js +21 -0
- package/examples/echo/characteristic.js +45 -0
- package/examples/echo/main.js +33 -0
- package/examples/pizza/README.md +16 -0
- package/examples/pizza/peripheral.js +57 -0
- package/examples/pizza/pizza-bake-characteristic.js +40 -0
- package/examples/pizza/pizza-crust-characteristic.js +52 -0
- package/examples/pizza/pizza-service.js +20 -0
- package/examples/pizza/pizza-toppings-characteristic.js +41 -0
- package/examples/pizza/pizza.js +58 -0
- package/examples/uart/main.js +23 -0
- package/index.d.ts +153 -0
- package/index.js +1 -0
- package/lib/bleno.js +231 -0
- package/lib/characteristic.js +91 -0
- package/lib/descriptor.js +17 -0
- package/lib/hci-socket/acl-stream.js +37 -0
- package/lib/hci-socket/bindings.js +219 -0
- package/lib/hci-socket/crypto.js +74 -0
- package/lib/hci-socket/gap.js +212 -0
- package/lib/hci-socket/gatt.js +1028 -0
- package/lib/hci-socket/hci-status.json +67 -0
- package/lib/hci-socket/hci.js +796 -0
- package/lib/hci-socket/mgmt.js +89 -0
- package/lib/hci-socket/smp.js +160 -0
- package/lib/hci-socket/vs.js +156 -0
- package/lib/mac/binding.gyp +39 -0
- package/lib/mac/bindings.js +12 -0
- package/lib/mac/src/ble_peripheral_manager.h +32 -0
- package/lib/mac/src/ble_peripheral_manager.mm +241 -0
- package/lib/mac/src/bleno_mac.h +26 -0
- package/lib/mac/src/bleno_mac.mm +167 -0
- package/lib/mac/src/callbacks.h +76 -0
- package/lib/mac/src/callbacks.mm +124 -0
- package/lib/mac/src/napi_objc.h +30 -0
- package/lib/mac/src/napi_objc.mm +286 -0
- package/lib/mac/src/noble_mac.h +34 -0
- package/lib/mac/src/noble_mac.mm +260 -0
- package/lib/mac/src/objc_cpp.h +27 -0
- package/lib/mac/src/objc_cpp.mm +144 -0
- package/lib/mac/src/peripheral.h +23 -0
- package/lib/primary-service.js +19 -0
- package/lib/resolve-bindings.js +19 -0
- package/lib/uuid-util.js +7 -0
- package/package.json +77 -0
- package/prebuilds/android-arm/node.napi.armv7.node +0 -0
- package/prebuilds/android-arm64/node.napi.armv8.node +0 -0
- package/prebuilds/darwin-x64+arm64/node.napi.node +0 -0
- package/prebuilds/linux-arm/node.napi.armv6.node +0 -0
- package/prebuilds/linux-arm/node.napi.armv7.node +0 -0
- package/prebuilds/linux-arm64/node.napi.armv8.node +0 -0
- package/prebuilds/linux-x64/node.napi.glibc.node +0 -0
- package/prebuilds/linux-x64/node.napi.musl.node +0 -0
- package/prebuilds/win32-ia32/node.napi.node +0 -0
- package/prebuilds/win32-x64/node.napi.node +0 -0
- package/test/characteristic.test.js +174 -0
- package/test/descriptor.test.js +46 -0
- package/test/mocha.setup.js +0 -0
- package/with-bindings.js +5 -0
- package/with-custom-binding.js +6 -0
|
@@ -0,0 +1,1028 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
/* jshint loopfunc: true */
|
|
3
|
+
|
|
4
|
+
const debug = require('debug')('gatt');
|
|
5
|
+
|
|
6
|
+
const { EventEmitter } = require('events');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
|
|
9
|
+
const ATT_OP_ERROR = 0x01;
|
|
10
|
+
const ATT_OP_MTU_REQ = 0x02;
|
|
11
|
+
const ATT_OP_MTU_RESP = 0x03;
|
|
12
|
+
const ATT_OP_FIND_INFO_REQ = 0x04;
|
|
13
|
+
const ATT_OP_FIND_INFO_RESP = 0x05;
|
|
14
|
+
const ATT_OP_FIND_BY_TYPE_REQ = 0x06;
|
|
15
|
+
const ATT_OP_FIND_BY_TYPE_RESP = 0x07;
|
|
16
|
+
const ATT_OP_READ_BY_TYPE_REQ = 0x08;
|
|
17
|
+
const ATT_OP_READ_BY_TYPE_RESP = 0x09;
|
|
18
|
+
const ATT_OP_READ_REQ = 0x0a;
|
|
19
|
+
const ATT_OP_READ_RESP = 0x0b;
|
|
20
|
+
const ATT_OP_READ_BLOB_REQ = 0x0c;
|
|
21
|
+
const ATT_OP_READ_BLOB_RESP = 0x0d;
|
|
22
|
+
const ATT_OP_READ_MULTI_REQ = 0x0e;
|
|
23
|
+
const ATT_OP_READ_MULTI_RESP = 0x0f;
|
|
24
|
+
const ATT_OP_READ_BY_GROUP_REQ = 0x10;
|
|
25
|
+
const ATT_OP_READ_BY_GROUP_RESP = 0x11;
|
|
26
|
+
const ATT_OP_WRITE_REQ = 0x12;
|
|
27
|
+
const ATT_OP_WRITE_RESP = 0x13;
|
|
28
|
+
const ATT_OP_WRITE_CMD = 0x52;
|
|
29
|
+
const ATT_OP_PREP_WRITE_REQ = 0x16;
|
|
30
|
+
const ATT_OP_PREP_WRITE_RESP = 0x17;
|
|
31
|
+
const ATT_OP_EXEC_WRITE_REQ = 0x18;
|
|
32
|
+
const ATT_OP_EXEC_WRITE_RESP = 0x19;
|
|
33
|
+
const ATT_OP_HANDLE_NOTIFY = 0x1b;
|
|
34
|
+
const ATT_OP_HANDLE_IND = 0x1d;
|
|
35
|
+
const ATT_OP_HANDLE_CNF = 0x1e;
|
|
36
|
+
const ATT_OP_SIGNED_WRITE_CMD = 0xd2;
|
|
37
|
+
|
|
38
|
+
const GATT_PRIM_SVC_UUID = 0x2800;
|
|
39
|
+
const GATT_INCLUDE_UUID = 0x2802;
|
|
40
|
+
const GATT_CHARAC_UUID = 0x2803;
|
|
41
|
+
|
|
42
|
+
const GATT_CLIENT_CHARAC_CFG_UUID = 0x2902;
|
|
43
|
+
const GATT_SERVER_CHARAC_CFG_UUID = 0x2903;
|
|
44
|
+
|
|
45
|
+
const ATT_ECODE_SUCCESS = 0x00;
|
|
46
|
+
const ATT_ECODE_INVALID_HANDLE = 0x01;
|
|
47
|
+
const ATT_ECODE_READ_NOT_PERM = 0x02;
|
|
48
|
+
const ATT_ECODE_WRITE_NOT_PERM = 0x03;
|
|
49
|
+
const ATT_ECODE_INVALID_PDU = 0x04;
|
|
50
|
+
const ATT_ECODE_AUTHENTICATION = 0x05;
|
|
51
|
+
const ATT_ECODE_REQ_NOT_SUPP = 0x06;
|
|
52
|
+
const ATT_ECODE_INVALID_OFFSET = 0x07;
|
|
53
|
+
const ATT_ECODE_AUTHORIZATION = 0x08;
|
|
54
|
+
const ATT_ECODE_PREP_QUEUE_FULL = 0x09;
|
|
55
|
+
const ATT_ECODE_ATTR_NOT_FOUND = 0x0a;
|
|
56
|
+
const ATT_ECODE_ATTR_NOT_LONG = 0x0b;
|
|
57
|
+
const ATT_ECODE_INSUFF_ENCR_KEY_SIZE = 0x0c;
|
|
58
|
+
const ATT_ECODE_INVAL_ATTR_VALUE_LEN = 0x0d;
|
|
59
|
+
const ATT_ECODE_UNLIKELY = 0x0e;
|
|
60
|
+
const ATT_ECODE_INSUFF_ENC = 0x0f;
|
|
61
|
+
const ATT_ECODE_UNSUPP_GRP_TYPE = 0x10;
|
|
62
|
+
const ATT_ECODE_INSUFF_RESOURCES = 0x11;
|
|
63
|
+
|
|
64
|
+
const ATT_CID = 0x0004;
|
|
65
|
+
|
|
66
|
+
class Gatt extends EventEmitter {
|
|
67
|
+
constructor () {
|
|
68
|
+
super();
|
|
69
|
+
|
|
70
|
+
this.maxMtu = 256;
|
|
71
|
+
this._mtu = 23;
|
|
72
|
+
this._preparedWriteRequest = null;
|
|
73
|
+
|
|
74
|
+
this.setServices([]);
|
|
75
|
+
|
|
76
|
+
this.onAclStreamDataBinded = this.onAclStreamData.bind(this);
|
|
77
|
+
this.onAclStreamEndBinded = this.onAclStreamEnd.bind(this);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setServices (services) {
|
|
81
|
+
const deviceName = process.env.BLENO_DEVICE_NAME || os.hostname();
|
|
82
|
+
|
|
83
|
+
// base services and characteristics
|
|
84
|
+
const allServices = [
|
|
85
|
+
{
|
|
86
|
+
uuid: '1800',
|
|
87
|
+
characteristics: [
|
|
88
|
+
{
|
|
89
|
+
uuid: '2a00',
|
|
90
|
+
properties: ['read'],
|
|
91
|
+
secure: [],
|
|
92
|
+
value: Buffer.from(deviceName),
|
|
93
|
+
descriptors: []
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
uuid: '2a01',
|
|
97
|
+
properties: ['read'],
|
|
98
|
+
secure: [],
|
|
99
|
+
value: Buffer.from([0x80, 0x00]),
|
|
100
|
+
descriptors: []
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
uuid: '1801',
|
|
106
|
+
characteristics: [
|
|
107
|
+
{
|
|
108
|
+
uuid: '2a05',
|
|
109
|
+
properties: ['indicate'],
|
|
110
|
+
secure: [],
|
|
111
|
+
value: Buffer.from([0x00, 0x00, 0x00, 0x00]),
|
|
112
|
+
descriptors: []
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
].concat(services);
|
|
117
|
+
|
|
118
|
+
this._handles = [];
|
|
119
|
+
|
|
120
|
+
let handle = 0;
|
|
121
|
+
|
|
122
|
+
for (let i = 0; i < allServices.length; i++) {
|
|
123
|
+
const service = allServices[i];
|
|
124
|
+
|
|
125
|
+
handle++;
|
|
126
|
+
const serviceHandle = handle;
|
|
127
|
+
|
|
128
|
+
this._handles[serviceHandle] = {
|
|
129
|
+
type: 'service',
|
|
130
|
+
uuid: service.uuid,
|
|
131
|
+
attribute: service,
|
|
132
|
+
startHandle: serviceHandle
|
|
133
|
+
// endHandle filled in below
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
for (let j = 0; j < service.characteristics.length; j++) {
|
|
137
|
+
const characteristic = service.characteristics[j];
|
|
138
|
+
|
|
139
|
+
let properties = 0;
|
|
140
|
+
let secure = 0;
|
|
141
|
+
|
|
142
|
+
if (characteristic.properties.indexOf('read') !== -1) {
|
|
143
|
+
properties |= 0x02;
|
|
144
|
+
|
|
145
|
+
if (characteristic.secure.indexOf('read') !== -1) {
|
|
146
|
+
secure |= 0x02;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (characteristic.properties.indexOf('writeWithoutResponse') !== -1) {
|
|
151
|
+
properties |= 0x04;
|
|
152
|
+
|
|
153
|
+
if (characteristic.secure.indexOf('writeWithoutResponse') !== -1) {
|
|
154
|
+
secure |= 0x04;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (characteristic.properties.indexOf('write') !== -1) {
|
|
159
|
+
properties |= 0x08;
|
|
160
|
+
|
|
161
|
+
if (characteristic.secure.indexOf('write') !== -1) {
|
|
162
|
+
secure |= 0x08;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (characteristic.properties.indexOf('notify') !== -1) {
|
|
167
|
+
properties |= 0x10;
|
|
168
|
+
|
|
169
|
+
if (characteristic.secure.indexOf('notify') !== -1) {
|
|
170
|
+
secure |= 0x10;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (characteristic.properties.indexOf('indicate') !== -1) {
|
|
175
|
+
properties |= 0x20;
|
|
176
|
+
|
|
177
|
+
if (characteristic.secure.indexOf('indicate') !== -1) {
|
|
178
|
+
secure |= 0x20;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
handle++;
|
|
183
|
+
const characteristicHandle = handle;
|
|
184
|
+
|
|
185
|
+
handle++;
|
|
186
|
+
const characteristicValueHandle = handle;
|
|
187
|
+
|
|
188
|
+
this._handles[characteristicHandle] = {
|
|
189
|
+
type: 'characteristic',
|
|
190
|
+
uuid: characteristic.uuid,
|
|
191
|
+
properties,
|
|
192
|
+
secure,
|
|
193
|
+
attribute: characteristic,
|
|
194
|
+
startHandle: characteristicHandle,
|
|
195
|
+
valueHandle: characteristicValueHandle
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
this._handles[characteristicValueHandle] = {
|
|
199
|
+
type: 'characteristicValue',
|
|
200
|
+
handle: characteristicValueHandle,
|
|
201
|
+
value: characteristic.value
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
if (properties & 0x30) { // notify or indicate
|
|
205
|
+
// add client characteristic configuration descriptor
|
|
206
|
+
|
|
207
|
+
handle++;
|
|
208
|
+
const clientCharacteristicConfigurationDescriptorHandle = handle;
|
|
209
|
+
this._handles[clientCharacteristicConfigurationDescriptorHandle] = {
|
|
210
|
+
type: 'descriptor',
|
|
211
|
+
handle: clientCharacteristicConfigurationDescriptorHandle,
|
|
212
|
+
uuid: '2902',
|
|
213
|
+
attribute: characteristic,
|
|
214
|
+
properties: (0x02 | 0x04 | 0x08), // read/write
|
|
215
|
+
secure: (secure & 0x10) ? (0x02 | 0x04 | 0x08) : 0,
|
|
216
|
+
value: Buffer.from([0x00, 0x00])
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
for (let k = 0; k < characteristic.descriptors.length; k++) {
|
|
221
|
+
const descriptor = characteristic.descriptors[k];
|
|
222
|
+
|
|
223
|
+
handle++;
|
|
224
|
+
const descriptorHandle = handle;
|
|
225
|
+
|
|
226
|
+
this._handles[descriptorHandle] = {
|
|
227
|
+
type: 'descriptor',
|
|
228
|
+
handle: descriptorHandle,
|
|
229
|
+
uuid: descriptor.uuid,
|
|
230
|
+
attribute: descriptor,
|
|
231
|
+
properties: 0x02, // read only
|
|
232
|
+
secure: 0x00,
|
|
233
|
+
value: descriptor.value
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
this._handles[serviceHandle].endHandle = handle;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const debugHandles = [];
|
|
242
|
+
for (let i = 0; i < this._handles.length; i++) {
|
|
243
|
+
handle = this._handles[i];
|
|
244
|
+
|
|
245
|
+
debugHandles[i] = {};
|
|
246
|
+
for (const j in handle) {
|
|
247
|
+
if (Buffer.isBuffer(handle[j])) {
|
|
248
|
+
debugHandles[i][j] = handle[j] ? 'Buffer(\'' + handle[j].toString('hex') + '\', \'hex\')' : null;
|
|
249
|
+
} else if (j !== 'attribute') {
|
|
250
|
+
debugHandles[i][j] = handle[j];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
debug('handles = ' + JSON.stringify(debugHandles, null, 2));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
setAclStream (aclStream) {
|
|
259
|
+
this._mtu = 23;
|
|
260
|
+
this._preparedWriteRequest = null;
|
|
261
|
+
|
|
262
|
+
this._aclStream = aclStream;
|
|
263
|
+
|
|
264
|
+
this._aclStream.on('data', this.onAclStreamDataBinded);
|
|
265
|
+
this._aclStream.on('end', this.onAclStreamEndBinded);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
onAclStreamData (cid, data) {
|
|
269
|
+
if (cid !== ATT_CID) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
this.handleRequest(data);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
onAclStreamEnd () {
|
|
277
|
+
this._aclStream.removeListener('data', this.onAclStreamDataBinded);
|
|
278
|
+
this._aclStream.removeListener('end', this.onAclStreamEndBinded);
|
|
279
|
+
|
|
280
|
+
for (let i = 0; i < this._handles.length; i++) {
|
|
281
|
+
if (this._handles[i] && this._handles[i].type === 'descriptor' &&
|
|
282
|
+
this._handles[i].uuid === '2902' && this._handles[i].value.readUInt16LE(0) !== 0) {
|
|
283
|
+
this._handles[i].value = Buffer.from([0x00, 0x00]);
|
|
284
|
+
|
|
285
|
+
if (this._handles[i].attribute && this._handles[i].attribute.emit) {
|
|
286
|
+
this._handles[i].attribute.emit('unsubscribe');
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
send (data) {
|
|
293
|
+
debug('send: ' + data.toString('hex'));
|
|
294
|
+
this._aclStream.write(ATT_CID, data);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
errorResponse (opcode, handle, status) {
|
|
298
|
+
const buf = Buffer.alloc(5);
|
|
299
|
+
|
|
300
|
+
buf.writeUInt8(ATT_OP_ERROR, 0);
|
|
301
|
+
buf.writeUInt8(opcode, 1);
|
|
302
|
+
buf.writeUInt16LE(handle, 2);
|
|
303
|
+
buf.writeUInt8(status, 4);
|
|
304
|
+
|
|
305
|
+
return buf;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
handleRequest (request) {
|
|
309
|
+
debug('handing request: ' + request.toString('hex'));
|
|
310
|
+
|
|
311
|
+
const requestType = request[0];
|
|
312
|
+
let response = null;
|
|
313
|
+
|
|
314
|
+
switch (requestType) {
|
|
315
|
+
case ATT_OP_MTU_REQ:
|
|
316
|
+
response = this.handleMtuRequest(request);
|
|
317
|
+
break;
|
|
318
|
+
|
|
319
|
+
case ATT_OP_FIND_INFO_REQ:
|
|
320
|
+
response = this.handleFindInfoRequest(request);
|
|
321
|
+
break;
|
|
322
|
+
|
|
323
|
+
case ATT_OP_FIND_BY_TYPE_REQ:
|
|
324
|
+
response = this.handleFindByTypeRequest(request);
|
|
325
|
+
break;
|
|
326
|
+
|
|
327
|
+
case ATT_OP_READ_BY_TYPE_REQ:
|
|
328
|
+
response = this.handleReadByTypeRequest(request);
|
|
329
|
+
break;
|
|
330
|
+
|
|
331
|
+
case ATT_OP_READ_REQ:
|
|
332
|
+
case ATT_OP_READ_BLOB_REQ:
|
|
333
|
+
response = this.handleReadOrReadBlobRequest(request);
|
|
334
|
+
break;
|
|
335
|
+
|
|
336
|
+
case ATT_OP_READ_BY_GROUP_REQ:
|
|
337
|
+
response = this.handleReadByGroupRequest(request);
|
|
338
|
+
break;
|
|
339
|
+
|
|
340
|
+
case ATT_OP_WRITE_REQ:
|
|
341
|
+
case ATT_OP_WRITE_CMD:
|
|
342
|
+
response = this.handleWriteRequestOrCommand(request);
|
|
343
|
+
break;
|
|
344
|
+
|
|
345
|
+
case ATT_OP_PREP_WRITE_REQ:
|
|
346
|
+
response = this.handlePrepareWriteRequest(request);
|
|
347
|
+
break;
|
|
348
|
+
|
|
349
|
+
case ATT_OP_EXEC_WRITE_REQ:
|
|
350
|
+
response = this.handleExecuteWriteRequest(request);
|
|
351
|
+
break;
|
|
352
|
+
|
|
353
|
+
case ATT_OP_HANDLE_CNF:
|
|
354
|
+
this.handleConfirmation(request);
|
|
355
|
+
break;
|
|
356
|
+
|
|
357
|
+
default:
|
|
358
|
+
response = this.errorResponse(requestType, 0x0000, ATT_ECODE_REQ_NOT_SUPP);
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (response) {
|
|
363
|
+
debug('response: ' + response.toString('hex'));
|
|
364
|
+
|
|
365
|
+
this.send(response);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
handleMtuRequest (request) {
|
|
370
|
+
let mtu = request.readUInt16LE(1);
|
|
371
|
+
|
|
372
|
+
if (mtu < 23) {
|
|
373
|
+
mtu = 23;
|
|
374
|
+
} else if (mtu > this.maxMtu) {
|
|
375
|
+
mtu = this.maxMtu;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
this._mtu = mtu;
|
|
379
|
+
|
|
380
|
+
this.emit('mtuChange', this._mtu);
|
|
381
|
+
|
|
382
|
+
const response = Buffer.alloc(3);
|
|
383
|
+
|
|
384
|
+
response.writeUInt8(ATT_OP_MTU_RESP, 0);
|
|
385
|
+
response.writeUInt16LE(mtu, 1);
|
|
386
|
+
|
|
387
|
+
return response;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
handleFindInfoRequest (request) {
|
|
391
|
+
const startHandle = request.readUInt16LE(1);
|
|
392
|
+
const endHandle = request.readUInt16LE(3);
|
|
393
|
+
|
|
394
|
+
const infos = [];
|
|
395
|
+
let uuid = null;
|
|
396
|
+
|
|
397
|
+
for (let i = startHandle; i <= endHandle; i++) {
|
|
398
|
+
const handle = this._handles[i];
|
|
399
|
+
|
|
400
|
+
if (!handle) {
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
uuid = null;
|
|
405
|
+
|
|
406
|
+
if (handle.type === 'service') {
|
|
407
|
+
uuid = '2800';
|
|
408
|
+
} else if (handle.type === 'includedService') {
|
|
409
|
+
uuid = '2802';
|
|
410
|
+
} else if (handle.type === 'characteristic') {
|
|
411
|
+
uuid = '2803';
|
|
412
|
+
} else if (handle.type === 'characteristicValue') {
|
|
413
|
+
uuid = this._handles[i - 1].uuid;
|
|
414
|
+
} else if (handle.type === 'descriptor') {
|
|
415
|
+
uuid = handle.uuid;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (uuid) {
|
|
419
|
+
infos.push({
|
|
420
|
+
handle: i,
|
|
421
|
+
uuid
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (infos.length) {
|
|
427
|
+
const uuidSize = infos[0].uuid.length / 2;
|
|
428
|
+
let numInfo = 1;
|
|
429
|
+
|
|
430
|
+
for (let i = 1; i < infos.length; i++) {
|
|
431
|
+
if (infos[0].uuid.length !== infos[i].uuid.length) {
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
numInfo++;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const lengthPerInfo = (uuidSize === 2) ? 4 : 18;
|
|
438
|
+
const maxInfo = Math.floor((this._mtu - 2) / lengthPerInfo);
|
|
439
|
+
numInfo = Math.min(numInfo, maxInfo);
|
|
440
|
+
|
|
441
|
+
const response = Buffer.alloc(2 + numInfo * lengthPerInfo);
|
|
442
|
+
|
|
443
|
+
response[0] = ATT_OP_FIND_INFO_RESP;
|
|
444
|
+
response[1] = (uuidSize === 2) ? 0x01 : 0x2;
|
|
445
|
+
|
|
446
|
+
for (let i = 0; i < numInfo; i++) {
|
|
447
|
+
const info = infos[i];
|
|
448
|
+
|
|
449
|
+
response.writeUInt16LE(info.handle, 2 + i * lengthPerInfo);
|
|
450
|
+
|
|
451
|
+
uuid = Buffer.from(info.uuid.match(/.{1,2}/g).reverse().join(''), 'hex');
|
|
452
|
+
for (let j = 0; j < uuid.length; j++) {
|
|
453
|
+
response[2 + i * lengthPerInfo + 2 + j] = uuid[j];
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return response;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return this.errorResponse(ATT_OP_FIND_INFO_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
handleFindByTypeRequest (request) {
|
|
463
|
+
const startHandle = request.readUInt16LE(1);
|
|
464
|
+
const endHandle = request.readUInt16LE(3);
|
|
465
|
+
const uuid = request.slice(5, 7).toString('hex').match(/.{1,2}/g).reverse().join('');
|
|
466
|
+
const value = request.slice(7).toString('hex').match(/.{1,2}/g).reverse().join('');
|
|
467
|
+
|
|
468
|
+
const handles = [];
|
|
469
|
+
let handle;
|
|
470
|
+
|
|
471
|
+
for (let i = startHandle; i <= endHandle; i++) {
|
|
472
|
+
handle = this._handles[i];
|
|
473
|
+
|
|
474
|
+
if (!handle) {
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (uuid === '2800' && handle.type === 'service' && handle.uuid === value) {
|
|
479
|
+
handles.push({
|
|
480
|
+
start: handle.startHandle,
|
|
481
|
+
end: handle.endHandle
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (handles.length) {
|
|
487
|
+
const lengthPerHandle = 4;
|
|
488
|
+
let numHandles = handles.length;
|
|
489
|
+
const maxHandles = Math.floor((this._mtu - 1) / lengthPerHandle);
|
|
490
|
+
|
|
491
|
+
numHandles = Math.min(numHandles, maxHandles);
|
|
492
|
+
|
|
493
|
+
const response = Buffer.alloc(1 + numHandles * lengthPerHandle);
|
|
494
|
+
|
|
495
|
+
response[0] = ATT_OP_FIND_BY_TYPE_RESP;
|
|
496
|
+
|
|
497
|
+
for (let i = 0; i < numHandles; i++) {
|
|
498
|
+
handle = handles[i];
|
|
499
|
+
|
|
500
|
+
response.writeUInt16LE(handle.start, 1 + i * lengthPerHandle);
|
|
501
|
+
response.writeUInt16LE(handle.end, 1 + i * lengthPerHandle + 2);
|
|
502
|
+
}
|
|
503
|
+
return response;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return this.errorResponse(ATT_OP_FIND_BY_TYPE_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
handleReadByGroupRequest (request) {
|
|
510
|
+
const startHandle = request.readUInt16LE(1);
|
|
511
|
+
const endHandle = request.readUInt16LE(3);
|
|
512
|
+
const uuid = request.slice(5).toString('hex').match(/.{1,2}/g).reverse().join('');
|
|
513
|
+
|
|
514
|
+
debug('read by group: startHandle = 0x' + startHandle.toString(16) + ', endHandle = 0x' + endHandle.toString(16) + ', uuid = 0x' + uuid.toString(16));
|
|
515
|
+
|
|
516
|
+
if (uuid === '2800' || uuid === '2802') {
|
|
517
|
+
const services = [];
|
|
518
|
+
const type = (uuid === '2800') ? 'service' : 'includedService';
|
|
519
|
+
|
|
520
|
+
for (let i = startHandle; i <= endHandle; i++) {
|
|
521
|
+
const handle = this._handles[i];
|
|
522
|
+
|
|
523
|
+
if (!handle) {
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (handle.type === type) {
|
|
528
|
+
services.push(handle);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (services.length) {
|
|
533
|
+
const uuidSize = services[0].uuid.length / 2;
|
|
534
|
+
let numServices = 1;
|
|
535
|
+
|
|
536
|
+
for (let i = 1; i < services.length; i++) {
|
|
537
|
+
if (services[0].uuid.length !== services[i].uuid.length) {
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
numServices++;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const lengthPerService = (uuidSize === 2) ? 6 : 20;
|
|
544
|
+
const maxServices = Math.floor((this._mtu - 2) / lengthPerService);
|
|
545
|
+
numServices = Math.min(numServices, maxServices);
|
|
546
|
+
|
|
547
|
+
const response = Buffer.alloc(2 + numServices * lengthPerService);
|
|
548
|
+
|
|
549
|
+
response[0] = ATT_OP_READ_BY_GROUP_RESP;
|
|
550
|
+
response[1] = lengthPerService;
|
|
551
|
+
|
|
552
|
+
for (let i = 0; i < numServices; i++) {
|
|
553
|
+
const service = services[i];
|
|
554
|
+
|
|
555
|
+
response.writeUInt16LE(service.startHandle, 2 + i * lengthPerService);
|
|
556
|
+
response.writeUInt16LE(service.endHandle, 2 + i * lengthPerService + 2);
|
|
557
|
+
|
|
558
|
+
const serviceUuid = Buffer.from(service.uuid.match(/.{1,2}/g).reverse().join(''), 'hex');
|
|
559
|
+
for (let j = 0; j < serviceUuid.length; j++) {
|
|
560
|
+
response[2 + i * lengthPerService + 4 + j] = serviceUuid[j];
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return response;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return this.errorResponse(ATT_OP_READ_BY_GROUP_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
return this.errorResponse(ATT_OP_READ_BY_GROUP_REQ, startHandle, ATT_ECODE_UNSUPP_GRP_TYPE);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
handleReadByTypeRequest (request) {
|
|
573
|
+
let response = null;
|
|
574
|
+
const requestType = request[0];
|
|
575
|
+
|
|
576
|
+
const startHandle = request.readUInt16LE(1);
|
|
577
|
+
const endHandle = request.readUInt16LE(3);
|
|
578
|
+
const uuid = request.slice(5).toString('hex').match(/.{1,2}/g).reverse().join('');
|
|
579
|
+
|
|
580
|
+
debug('read by type: startHandle = 0x' + startHandle.toString(16) + ', endHandle = 0x' + endHandle.toString(16) + ', uuid = 0x' + uuid.toString(16));
|
|
581
|
+
|
|
582
|
+
if (uuid === '2803') {
|
|
583
|
+
const characteristics = [];
|
|
584
|
+
|
|
585
|
+
for (let i = startHandle; i <= endHandle; i++) {
|
|
586
|
+
const handle = this._handles[i];
|
|
587
|
+
|
|
588
|
+
if (!handle) {
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (handle.type === 'characteristic') {
|
|
593
|
+
characteristics.push(handle);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (characteristics.length) {
|
|
598
|
+
const uuidSize = characteristics[0].uuid.length / 2;
|
|
599
|
+
let numCharacteristics = 1;
|
|
600
|
+
|
|
601
|
+
for (let i = 1; i < characteristics.length; i++) {
|
|
602
|
+
if (characteristics[0].uuid.length !== characteristics[i].uuid.length) {
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
numCharacteristics++;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const lengthPerCharacteristic = (uuidSize === 2) ? 7 : 21;
|
|
609
|
+
const maxCharacteristics = Math.floor((this._mtu - 2) / lengthPerCharacteristic);
|
|
610
|
+
numCharacteristics = Math.min(numCharacteristics, maxCharacteristics);
|
|
611
|
+
|
|
612
|
+
response = Buffer.alloc(2 + numCharacteristics * lengthPerCharacteristic);
|
|
613
|
+
|
|
614
|
+
response[0] = ATT_OP_READ_BY_TYPE_RESP;
|
|
615
|
+
response[1] = lengthPerCharacteristic;
|
|
616
|
+
|
|
617
|
+
for (let i = 0; i < numCharacteristics; i++) {
|
|
618
|
+
const characteristic = characteristics[i];
|
|
619
|
+
|
|
620
|
+
response.writeUInt16LE(characteristic.startHandle, 2 + i * lengthPerCharacteristic);
|
|
621
|
+
response.writeUInt8(characteristic.properties, 2 + i * lengthPerCharacteristic + 2);
|
|
622
|
+
response.writeUInt16LE(characteristic.valueHandle, 2 + i * lengthPerCharacteristic + 3);
|
|
623
|
+
|
|
624
|
+
const characteristicUuid = Buffer.from(characteristic.uuid.match(/.{1,2}/g).reverse().join(''), 'hex');
|
|
625
|
+
for (let j = 0; j < characteristicUuid.length; j++) {
|
|
626
|
+
response[2 + i * lengthPerCharacteristic + 5 + j] = characteristicUuid[j];
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
} else {
|
|
630
|
+
response = this.errorResponse(ATT_OP_READ_BY_TYPE_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND);
|
|
631
|
+
}
|
|
632
|
+
} else {
|
|
633
|
+
let handleAttribute = null;
|
|
634
|
+
let valueHandle = null;
|
|
635
|
+
let secure = false;
|
|
636
|
+
|
|
637
|
+
for (let i = startHandle; i <= endHandle; i++) {
|
|
638
|
+
const handle = this._handles[i];
|
|
639
|
+
|
|
640
|
+
if (!handle) {
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (handle.type === 'characteristic' && handle.uuid === uuid) {
|
|
645
|
+
handleAttribute = handle.attribute;
|
|
646
|
+
valueHandle = handle.valueHandle;
|
|
647
|
+
secure = handle.secure & 0x02;
|
|
648
|
+
break;
|
|
649
|
+
} else if (handle.type === 'descriptor' && handle.uuid === uuid) {
|
|
650
|
+
valueHandle = i;
|
|
651
|
+
secure = handle.secure & 0x02;
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (secure && !this._aclStream.encrypted) {
|
|
657
|
+
response = this.errorResponse(ATT_OP_READ_BY_TYPE_REQ, startHandle, ATT_ECODE_AUTHENTICATION);
|
|
658
|
+
} else if (valueHandle) {
|
|
659
|
+
const callback = (function (valueHandle) {
|
|
660
|
+
return function (result, data) {
|
|
661
|
+
let callbackResponse;
|
|
662
|
+
|
|
663
|
+
if (ATT_ECODE_SUCCESS === result) {
|
|
664
|
+
const dataLength = Math.min(data.length, this._mtu - 4);
|
|
665
|
+
callbackResponse = Buffer.alloc(4 + dataLength);
|
|
666
|
+
|
|
667
|
+
callbackResponse[0] = ATT_OP_READ_BY_TYPE_RESP;
|
|
668
|
+
callbackResponse[1] = dataLength + 2;
|
|
669
|
+
callbackResponse.writeUInt16LE(valueHandle, 2);
|
|
670
|
+
for (let i = 0; i < dataLength; i++) {
|
|
671
|
+
callbackResponse[4 + i] = data[i];
|
|
672
|
+
}
|
|
673
|
+
} else {
|
|
674
|
+
callbackResponse = this.errorResponse(requestType, valueHandle, result);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
debug('read by type response: ' + callbackResponse.toString('hex'));
|
|
678
|
+
|
|
679
|
+
this.send(callbackResponse);
|
|
680
|
+
}.bind(this);
|
|
681
|
+
}.bind(this))(valueHandle);
|
|
682
|
+
|
|
683
|
+
const data = this._handles[valueHandle].value;
|
|
684
|
+
|
|
685
|
+
if (data) {
|
|
686
|
+
callback(ATT_ECODE_SUCCESS, data);
|
|
687
|
+
} else if (handleAttribute) {
|
|
688
|
+
handleAttribute.emit('readRequest', 0, callback);
|
|
689
|
+
} else {
|
|
690
|
+
callback(ATT_ECODE_UNLIKELY);
|
|
691
|
+
}
|
|
692
|
+
} else {
|
|
693
|
+
response = this.errorResponse(ATT_OP_READ_BY_TYPE_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return response;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
handleReadOrReadBlobRequest (request) {
|
|
701
|
+
let response = null;
|
|
702
|
+
|
|
703
|
+
const requestType = request[0];
|
|
704
|
+
const valueHandle = request.readUInt16LE(1);
|
|
705
|
+
const offset = (requestType === ATT_OP_READ_BLOB_REQ) ? request.readUInt16LE(3) : 0;
|
|
706
|
+
|
|
707
|
+
const handle = this._handles[valueHandle];
|
|
708
|
+
|
|
709
|
+
if (handle) {
|
|
710
|
+
let result = null;
|
|
711
|
+
let data = null;
|
|
712
|
+
const handleType = handle.type;
|
|
713
|
+
|
|
714
|
+
const callback = (function (requestType, valueHandle) {
|
|
715
|
+
return function (result, data) {
|
|
716
|
+
let callbackResponse;
|
|
717
|
+
|
|
718
|
+
if (ATT_ECODE_SUCCESS === result) {
|
|
719
|
+
const dataLength = Math.min(data.length, this._mtu - 1);
|
|
720
|
+
callbackResponse = Buffer.alloc(1 + dataLength);
|
|
721
|
+
|
|
722
|
+
callbackResponse[0] = (requestType === ATT_OP_READ_BLOB_REQ) ? ATT_OP_READ_BLOB_RESP : ATT_OP_READ_RESP;
|
|
723
|
+
for (let i = 0; i < dataLength; i++) {
|
|
724
|
+
callbackResponse[1 + i] = data[i];
|
|
725
|
+
}
|
|
726
|
+
} else {
|
|
727
|
+
callbackResponse = this.errorResponse(requestType, valueHandle, result);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
debug('read response: ' + callbackResponse.toString('hex'));
|
|
731
|
+
|
|
732
|
+
this.send(callbackResponse);
|
|
733
|
+
}.bind(this);
|
|
734
|
+
}.bind(this))(requestType, valueHandle);
|
|
735
|
+
|
|
736
|
+
if (handleType === 'service' || handleType === 'includedService') {
|
|
737
|
+
result = ATT_ECODE_SUCCESS;
|
|
738
|
+
data = Buffer.from(handle.uuid.match(/.{1,2}/g).reverse().join(''), 'hex');
|
|
739
|
+
} else if (handleType === 'characteristic') {
|
|
740
|
+
const uuid = Buffer.from(handle.uuid.match(/.{1,2}/g).reverse().join(''), 'hex');
|
|
741
|
+
|
|
742
|
+
result = ATT_ECODE_SUCCESS;
|
|
743
|
+
data = Buffer.alloc(3 + uuid.length);
|
|
744
|
+
data.writeUInt8(handle.properties, 0);
|
|
745
|
+
data.writeUInt16LE(handle.valueHandle, 1);
|
|
746
|
+
|
|
747
|
+
for (let i = 0; i < uuid.length; i++) {
|
|
748
|
+
data[i + 3] = uuid[i];
|
|
749
|
+
}
|
|
750
|
+
} else if (handleType === 'characteristicValue' || handleType === 'descriptor') {
|
|
751
|
+
let handleProperties = handle.properties;
|
|
752
|
+
let handleSecure = handle.secure;
|
|
753
|
+
let handleAttribute = handle.attribute;
|
|
754
|
+
if (handleType === 'characteristicValue') {
|
|
755
|
+
handleProperties = this._handles[valueHandle - 1].properties;
|
|
756
|
+
handleSecure = this._handles[valueHandle - 1].secure;
|
|
757
|
+
handleAttribute = this._handles[valueHandle - 1].attribute;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
if (handleProperties & 0x02) {
|
|
761
|
+
if (handleSecure & 0x02 && !this._aclStream.encrypted) {
|
|
762
|
+
result = ATT_ECODE_AUTHENTICATION;
|
|
763
|
+
} else {
|
|
764
|
+
data = handle.value;
|
|
765
|
+
|
|
766
|
+
if (data) {
|
|
767
|
+
result = ATT_ECODE_SUCCESS;
|
|
768
|
+
} else {
|
|
769
|
+
handleAttribute.emit('readRequest', offset, callback);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
} else {
|
|
773
|
+
result = ATT_ECODE_READ_NOT_PERM; // non-readable
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (data && typeof data === 'string') {
|
|
778
|
+
data = Buffer.from(data);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (result === ATT_ECODE_SUCCESS && data && offset) {
|
|
782
|
+
if (data.length < offset) {
|
|
783
|
+
result = ATT_ECODE_INVALID_OFFSET;
|
|
784
|
+
data = null;
|
|
785
|
+
} else {
|
|
786
|
+
data = data.slice(offset);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (result !== null) {
|
|
791
|
+
callback(result, data);
|
|
792
|
+
}
|
|
793
|
+
} else {
|
|
794
|
+
response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_HANDLE);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
return response;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
handleWriteRequestOrCommand (request) {
|
|
801
|
+
let response = null;
|
|
802
|
+
|
|
803
|
+
const requestType = request[0];
|
|
804
|
+
const withoutResponse = (requestType === ATT_OP_WRITE_CMD);
|
|
805
|
+
const valueHandle = request.readUInt16LE(1);
|
|
806
|
+
const data = request.slice(3);
|
|
807
|
+
const offset = 0;
|
|
808
|
+
|
|
809
|
+
let handle = this._handles[valueHandle];
|
|
810
|
+
|
|
811
|
+
if (handle) {
|
|
812
|
+
if (handle.type === 'characteristicValue') {
|
|
813
|
+
handle = this._handles[valueHandle - 1];
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const handleProperties = handle.properties;
|
|
817
|
+
const handleSecure = handle.secure;
|
|
818
|
+
|
|
819
|
+
if (handleProperties && (withoutResponse ? (handleProperties & 0x04) : (handleProperties & 0x08))) {
|
|
820
|
+
const callback = (function (requestType, valueHandle, withoutResponse) {
|
|
821
|
+
return function (result) {
|
|
822
|
+
if (!withoutResponse) {
|
|
823
|
+
let callbackResponse;
|
|
824
|
+
|
|
825
|
+
if (ATT_ECODE_SUCCESS === result) {
|
|
826
|
+
callbackResponse = Buffer.from([ATT_OP_WRITE_RESP]);
|
|
827
|
+
} else {
|
|
828
|
+
callbackResponse = this.errorResponse(requestType, valueHandle, result);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
debug('write response: ' + callbackResponse.toString('hex'));
|
|
832
|
+
|
|
833
|
+
this.send(callbackResponse);
|
|
834
|
+
}
|
|
835
|
+
}.bind(this);
|
|
836
|
+
}.bind(this))(requestType, valueHandle, withoutResponse);
|
|
837
|
+
|
|
838
|
+
if (handleSecure & (withoutResponse ? 0x04 : 0x08) && !this._aclStream.encrypted) {
|
|
839
|
+
response = this.errorResponse(requestType, valueHandle, ATT_ECODE_AUTHENTICATION);
|
|
840
|
+
} else if (handle.type === 'descriptor' || handle.uuid === '2902') {
|
|
841
|
+
let result;
|
|
842
|
+
|
|
843
|
+
if (data.length !== 2) {
|
|
844
|
+
result = ATT_ECODE_INVAL_ATTR_VALUE_LEN;
|
|
845
|
+
} else {
|
|
846
|
+
const value = data.readUInt16LE(0);
|
|
847
|
+
const handleAttribute = handle.attribute;
|
|
848
|
+
|
|
849
|
+
handle.value = data;
|
|
850
|
+
|
|
851
|
+
if (value & 0x0003) {
|
|
852
|
+
const updateValueCallback = (function (valueHandle, attribute) {
|
|
853
|
+
return function (data) {
|
|
854
|
+
const dataLength = Math.min(data.length, this._mtu - 3);
|
|
855
|
+
const useNotify = attribute.properties.indexOf('notify') !== -1;
|
|
856
|
+
const useIndicate = attribute.properties.indexOf('indicate') !== -1;
|
|
857
|
+
|
|
858
|
+
if (useNotify) {
|
|
859
|
+
const notifyMessage = Buffer.alloc(3 + dataLength);
|
|
860
|
+
|
|
861
|
+
notifyMessage.writeUInt8(ATT_OP_HANDLE_NOTIFY, 0);
|
|
862
|
+
notifyMessage.writeUInt16LE(valueHandle, 1);
|
|
863
|
+
|
|
864
|
+
for (let i = 0; i < dataLength; i++) {
|
|
865
|
+
notifyMessage[3 + i] = data[i];
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
debug('notify message: ' + notifyMessage.toString('hex'));
|
|
869
|
+
this.send(notifyMessage);
|
|
870
|
+
|
|
871
|
+
attribute.emit('notify');
|
|
872
|
+
} else if (useIndicate) {
|
|
873
|
+
const indicateMessage = Buffer.alloc(3 + dataLength);
|
|
874
|
+
|
|
875
|
+
indicateMessage.writeUInt8(ATT_OP_HANDLE_IND, 0);
|
|
876
|
+
indicateMessage.writeUInt16LE(valueHandle, 1);
|
|
877
|
+
|
|
878
|
+
for (let i = 0; i < dataLength; i++) {
|
|
879
|
+
indicateMessage[3 + i] = data[i];
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
this._lastIndicatedAttribute = attribute;
|
|
883
|
+
|
|
884
|
+
debug('indicate message: ' + indicateMessage.toString('hex'));
|
|
885
|
+
this.send(indicateMessage);
|
|
886
|
+
}
|
|
887
|
+
}.bind(this);
|
|
888
|
+
}.bind(this))(valueHandle - 1, handleAttribute);
|
|
889
|
+
|
|
890
|
+
if (handleAttribute.emit) {
|
|
891
|
+
handleAttribute.emit('subscribe', this._mtu - 3, updateValueCallback);
|
|
892
|
+
}
|
|
893
|
+
} else {
|
|
894
|
+
if (handleAttribute.emit) {
|
|
895
|
+
handleAttribute.emit('unsubscribe');
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
result = ATT_ECODE_SUCCESS;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
callback(result);
|
|
903
|
+
} else {
|
|
904
|
+
handle.attribute.emit('writeRequest', data, offset, withoutResponse, callback);
|
|
905
|
+
}
|
|
906
|
+
} else {
|
|
907
|
+
response = this.errorResponse(requestType, valueHandle, ATT_ECODE_WRITE_NOT_PERM);
|
|
908
|
+
}
|
|
909
|
+
} else {
|
|
910
|
+
response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_HANDLE);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
return response;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
handlePrepareWriteRequest (request) {
|
|
917
|
+
let response;
|
|
918
|
+
|
|
919
|
+
const requestType = request[0];
|
|
920
|
+
const valueHandle = request.readUInt16LE(1);
|
|
921
|
+
const offset = request.readUInt16LE(3);
|
|
922
|
+
const data = request.slice(5);
|
|
923
|
+
|
|
924
|
+
let handle = this._handles[valueHandle];
|
|
925
|
+
|
|
926
|
+
if (handle) {
|
|
927
|
+
if (handle.type === 'characteristicValue') {
|
|
928
|
+
handle = this._handles[valueHandle - 1];
|
|
929
|
+
|
|
930
|
+
const handleProperties = handle.properties;
|
|
931
|
+
const handleSecure = handle.secure;
|
|
932
|
+
|
|
933
|
+
if (handleProperties && (handleProperties & 0x08)) {
|
|
934
|
+
if ((handleSecure & 0x08) && !this._aclStream.encrypted) {
|
|
935
|
+
response = this.errorResponse(requestType, valueHandle, ATT_ECODE_AUTHENTICATION);
|
|
936
|
+
} else if (this._preparedWriteRequest) {
|
|
937
|
+
if (this._preparedWriteRequest.handle !== handle) {
|
|
938
|
+
response = this.errorResponse(requestType, valueHandle, ATT_ECODE_UNLIKELY);
|
|
939
|
+
} else if (offset === (this._preparedWriteRequest.offset + this._preparedWriteRequest.data.length)) {
|
|
940
|
+
this._preparedWriteRequest.data = Buffer.concat([
|
|
941
|
+
this._preparedWriteRequest.data,
|
|
942
|
+
data
|
|
943
|
+
]);
|
|
944
|
+
|
|
945
|
+
response = Buffer.alloc(request.length);
|
|
946
|
+
request.copy(response);
|
|
947
|
+
response[0] = ATT_OP_PREP_WRITE_RESP;
|
|
948
|
+
} else {
|
|
949
|
+
response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_OFFSET);
|
|
950
|
+
}
|
|
951
|
+
} else {
|
|
952
|
+
this._preparedWriteRequest = {
|
|
953
|
+
handle,
|
|
954
|
+
valueHandle,
|
|
955
|
+
offset,
|
|
956
|
+
data
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
response = Buffer.alloc(request.length);
|
|
960
|
+
request.copy(response);
|
|
961
|
+
response[0] = ATT_OP_PREP_WRITE_RESP;
|
|
962
|
+
}
|
|
963
|
+
} else {
|
|
964
|
+
response = this.errorResponse(requestType, valueHandle, ATT_ECODE_WRITE_NOT_PERM);
|
|
965
|
+
}
|
|
966
|
+
} else {
|
|
967
|
+
response = this.errorResponse(requestType, valueHandle, ATT_ECODE_ATTR_NOT_LONG);
|
|
968
|
+
}
|
|
969
|
+
} else {
|
|
970
|
+
response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_HANDLE);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
return response;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
handleExecuteWriteRequest (request) {
|
|
977
|
+
let response = null;
|
|
978
|
+
|
|
979
|
+
const requestType = request[0];
|
|
980
|
+
const flag = request[1];
|
|
981
|
+
|
|
982
|
+
if (this._preparedWriteRequest) {
|
|
983
|
+
const valueHandle = this._preparedWriteRequest.valueHandle;
|
|
984
|
+
|
|
985
|
+
if (flag === 0x00) {
|
|
986
|
+
response = Buffer.from([ATT_OP_EXEC_WRITE_RESP]);
|
|
987
|
+
} else if (flag === 0x01) {
|
|
988
|
+
const callback = (function (requestType, valueHandle) {
|
|
989
|
+
return function (result) {
|
|
990
|
+
let callbackResponse;
|
|
991
|
+
|
|
992
|
+
if (ATT_ECODE_SUCCESS === result) {
|
|
993
|
+
callbackResponse = Buffer.from([ATT_OP_EXEC_WRITE_RESP]);
|
|
994
|
+
} else {
|
|
995
|
+
callbackResponse = this.errorResponse(requestType, valueHandle, result);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
debug('execute write response: ' + callbackResponse.toString('hex'));
|
|
999
|
+
|
|
1000
|
+
this.send(callbackResponse);
|
|
1001
|
+
}.bind(this);
|
|
1002
|
+
}.bind(this))(requestType, this._preparedWriteRequest.valueHandle);
|
|
1003
|
+
|
|
1004
|
+
this._preparedWriteRequest.handle.attribute.emit('writeRequest', this._preparedWriteRequest.data, this._preparedWriteRequest.offset, false, callback);
|
|
1005
|
+
} else {
|
|
1006
|
+
response = this.errorResponse(requestType, 0x0000, ATT_ECODE_UNLIKELY);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
this._preparedWriteRequest = null;
|
|
1010
|
+
} else {
|
|
1011
|
+
response = this.errorResponse(requestType, 0x0000, ATT_ECODE_UNLIKELY);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
return response;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
handleConfirmation (request) {
|
|
1018
|
+
if (this._lastIndicatedAttribute) {
|
|
1019
|
+
if (this._lastIndicatedAttribute.emit) {
|
|
1020
|
+
this._lastIndicatedAttribute.emit('indicate');
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
this._lastIndicatedAttribute = null;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
module.exports = Gatt;
|