bleadvertise
Version:
Parse and generate Bluetooth Low Energy peripheral advertising packets
183 lines (155 loc) • 6.66 kB
JavaScript
// Copyright 2014 Technical Machine, Inc. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
function Packet(dataType, data, byteOrder) {
this.typeFlag = dataType;
this.raw = data;
this.byteOrder = byteOrder;
// If data isn't a buffer, get mad
if (data && !Buffer.isBuffer(data)) {
throw new Error("Data must be a buffer");
}
// Grab type from data structure
var type = Packet.ADTypes[dataType];
// If it exists
if (type) {
// Set name
this.type = type.name;
// Set data based on the best way to resolve the data (ie toString, to hex array)
this.data = type.resolve ? type.resolve(data, dataType, byteOrder) : data;
}
else {
// Set defaults if type not known
this.type = "Unknown";
this.data = data;
}
}
// Data structure
Packet.ADTypes = {
0x01 : { name : "Flags", resolve: toStringArray },
0x02 : { name : "Incomplete List of 16-bit Service Class UUIDs", resolve: toOctetStringArray.bind(null, 2)},
0x03 : { name : "Complete List of 16-bit Service Class UUIDs", resolve: toOctetStringArray.bind(null, 2) },
0x04 : { name : "Incomplete List of 32-bit Service Class UUIDs", resolve: toOctetStringArray.bind(null, 4) },
0x05 : { name : "Complete List of 32-bit Service Class UUIDs", resolve: toOctetStringArray.bind(null, 4) },
0x06 : { name : "Incomplete List of 128-bit Service Class UUIDs", resolve: toOctetStringArray.bind(null, 16) },
0x07 : { name : "Complete List of 128-bit Service Class UUIDs", resolve: toOctetStringArray.bind(null, 16) },
0x08 : { name : "Shortened Local Name", resolve: toString },
0x09 : { name : "Complete Local Name", resolve: toString },
0x0A : { name : "Tx Power Level", resolve: toSignedInt },
0x0D : { name : "Class of Device", resolve: toOctetString.bind(null, 3) },
0x0E : { name : "Simple Pairing Hash C", resolve: toOctetString.bind(null, 16) },
0x0F : { name : "Simple Pairing Randomizer R", resolve: toOctetString.bind(null, 16) },
0x10 : { name : "Device ID", resolve: toOctetString.bind(null, 16) },
// 0x10 : { name : "Security Manager TK Value", resolve: null }
0x11 : { name : "Security Manager Out of Band Flags", resolve : toOctetString.bind(null, 16) },
0x12 : { name : "Slave Connection Interval Range", resolve : toOctetStringArray.bind(null, 2) },
0x14 : { name : "List of 16-bit Service Solicitation UUIDs", resolve : toOctetStringArray.bind(null, 2) },
0x1F : { name : "List of 32-bit Service Solicitation UUIDs", resolve : toOctetStringArray.bind(null, 4) },
0x15 : { name : "List of 128-bit Service Solicitation UUIDs", resolve : toOctetStringArray.bind(null, 8) },
0x16 : { name : "Service Data", resolve : toOctetStringArray.bind(null, 1) },
0x17 : { name : "Public Target Address", resolve : toOctetStringArray.bind(null, 6) },
0x18 : { name : "Random Target Address", resolve : toOctetStringArray.bind(null, 6) },
0x19 : { name : "Appearance" , resolve : null },
0x1A : { name : "Advertising Interval" , resolve : toOctetStringArray.bind(null, 2) },
0x1B : { name : "LE Bluetooth Device Address", resolve : toOctetStringArray.bind(null, 6) },
0x1C : { name : "LE Role", resolve : null },
0x1D : { name : "Simple Pairing Hash C-256", resolve : toOctetStringArray.bind(null, 16) },
0x1E : { name : "Simple Pairing Randomizer R-256", resolve : toOctetStringArray.bind(null, 16) },
0x20 : { name : "Service Data - 32-bit UUID", resolve : toOctetStringArray.bind(null, 4) },
0x21 : { name : "Service Data - 128-bit UUID", resolve : toOctetStringArray.bind(null, 16) },
0x3D : { name : "3D Information Data", resolve : null },
0xFF : { name : "Manufacturer Specific Data", resolve : null },
}
// Converts data flags to an array of readable strings
function toStringArray(data) {
var arr = [];
if (!data) {
arr.push('None');
}
else {
if (data[0] & (1 << 0)) {
arr.push('LE Limited Discoverable Mode');
}
if (data[0] & (1 << 1)) {
arr.push('LE General Discoverable Mode');
}
if (data[0] & (1 << 2)) {
arr.push('BR/EDR Not Supported');
}
if (data[0] & (1 << 3)) {
arr.push('Simultaneous LE and BR/EDR to Same Device Capable (Controller)');
}
if (data[0] & (1 << 4)) {
arr.push('Simultaneous LE and BR/EDR to Same Device Capable (Host)');
}
if (!arr.length) {
arr.push('None');
}
}
return arr;
}
// Converts buffer to array of strings
// (some uuids are 128 bits which we can't represent as numbers b/c it's too big
// and I don't want the user to have to worry about endian-ness)
function toOctetStringArray(numBytes, data, dataType, byteOrder) {
var uuids = [];
var bytes;
// If data is undefined, returne empty array
if (!data) return [];
// Make sure there are enough bytes
if (numBytes > data.length) {
throw new Error("Not enough bytes for single UUID");
}
// Make sure it will divide nicely
if (data.length % numBytes) {
throw new Error("Not enough bytes to complete each UUID. Needs to be multiple of " + numBytes.toString());
}
// Go through the array
while (data) {
// grab an octet string
var uuid = toOctetString(numBytes, data, dataType, byteOrder);
// Put into the array
uuids.push(uuid);
// Move the buffer forward
data = data.slice(numBytes, data.length);
// Weird hack
if (!data || !data.length) data = null;
}
return uuids;
}
// Simply prints buffer, utf8 encoded
function toString(data) {
return data.toString('utf8');
}
// Only uses for signal strength
function toSignedInt(data) {
if (!data) return 0;
return data.readInt8(0);
}
// converts buffer to array of uuid strings
function toOctetString(numOctets, data, dataType, byteOrder) {
var bytes = [];
// For each octet
for (var i = 0; i < numOctets; i++) {
// Ready as hex string
var str = data.readUInt8(i).toString(16);
// If it's only 1 byte, add 0 to front
if (str.length == 1) str = "0" + str;
// Put in array
bytes.push(str);
}
// If we're big endian, reverse byte order
if (byteOrder == "BE") {
bytes.reverse();
}
// Add '0x' to front
var uuid = bytes.join('');
return uuid;
}
module.exports.Packet = Packet;
module.exports.toOctetStringArray = toOctetStringArray;