<commit_before>/**
 * File: easyble.js
 * Description: Library for making BLE programming easier.
 * Author: Miki
 *
 * Note: The object type called "device" below, is the "DeviceInfo"
 * object obtained by calling evothings.ble.startScan, enhanced with
 * additional properties and functions to allow easy access to
 * object methods. Properties are also added to the Characteristic
 * and Descriptor object. Added properties are prefixed with two
 * underscores.
 */

evothings.loadScript('libs/evothings/util/util.js');

var base64 = cordova.require('cordova/base64');

if (!window.evothings) { window.evothings = {}; }

// Object that holds BLE data and functions.
evothings.easyble = (function()
{
	/** Main object in the EasyBLE API. */
	var easyble = {};

	/**
	 * Set to true to report found devices only once,
	 * set to false to report continuously.
	 */
	var reportDeviceOnce = false;

	var serviceFilter = false;

	/** Internal properties and functions. */
	var internal = {};

	/** Internal variable used to track reading of service data. */
	var readCounter = 0;

	/** Table of discovered devices. */
	internal.knownDevices = {};

	/** Table of connected devices. */
	internal.connectedDevices = {};

	/**
	 * Set to true to report found devices only once.
	 * Set to false to report continuously.
	 * The default is to report continously.
	 */
	easyble.reportDeviceOnce = function(reportOnce)
	{
		reportDeviceOnce = reportOnce;
	};

	/**
	* Set to an Array of UUID strings to enable filtering of devices found by startScan().
	* Set to false to disable filtering.
	* The default is to not filter.
	* An empty array will cause no devices to be reported.
	*/
	easyble.filterDevicesByService = function(services) {
		serviceFilter = services;
	};

	/** Start scanning for devices. */
	easyble.startScan = function(win, fail)
	{
		easyble.stopScan();
		internal.knownDevices = {};
		evothings.ble.startScan(function(device)
		{
			// Ensure we have advertisementData.
			internal.ensureAdvertisementData(device);

			// Check if the device matches the filter, if we have a filter.
			if(!internal.deviceMatchesServiceFilter(device)) {
				return;
			}

			// Check if we already have got the device.
			var existingDevice = internal.knownDevices[device.address]
			if (existingDevice)
			{
				// Do not report device again if flag is set.
				if (reportDeviceOnce) { return; }

				// Flag not set, report device again.
				existingDevice.rssi = device.rssi;
				existingDevice.name = device.name;
				existingDevice.scanRecord = device.scanRecord;
				existingDevice.advertisementData = device.advertisementData;
				win(existingDevice);
				return;
			}

			// New device, add to known devices.
			internal.knownDevices[device.address] = device;

			// Add methods to the device info object.
			internal.addMethodsToDeviceObject(device);

			// Call callback function with device info.
			win(device);
		},
		function(errorCode)
		{
			fail(errorCode);
		});
	};

	/** Stop scanning for devices. */
	easyble.stopScan = function()
	{
		evothings.ble.stopScan();
	};

	/** Close all connected devices. */
	easyble.closeConnectedDevices = function()
	{
		for (var key in internal.connectedDevices)
		{
			var device = internal.connectedDevices[key];
			device && device.close();
			internal.connectedDevices[key] = null;
		}
	};

	/**
	* If device has advertisementData, does nothing.
	* If device instead has scanRecord, creates advertisementData.
	* See ble.js for AdvertisementData reference.
	*/
	internal.ensureAdvertisementData = function(device) {
		if(device.advertisementData) {
			//console.log("iOS ad: "+JSON.stringify(device.advertisementData));
			return;
		}
		if(!device.scanRecord) { return; }

		// Here we parse BLE/GAP Scan Response Data.
		// See the Bluetooth Specification, v4.0, Volume 3, Part C, Section 11,
		// for details.

		var byteArray = evothings.util.base64DecToArr(device.scanRecord);
		var pos = 0;
		var advertisementData = {};
		var serviceUUIDs;
		var serviceData;
		// The scan record is a list of structures.
		// Each structure has a length byte, a type byte, and (length-1) data bytes.
		// The format of the data bytes depends on the type.
		// Malformed scanRecords will likely cause an exception in this function.
		while(pos < byteArray.length) {
			var length = byteArray[pos++];
			if(length == 0) {
				break;
			}
			length -= 1;
			var type = byteArray[pos++];

			// Parse types we know and care about.
			// Skip other types.

			var BLUETOOTH_BASE_UUID = '-0000-1000-8000-00805f9b34fb'

			// Convert 16-byte Uint8Array to RFC-4122-formatted UUID.
			function arrayToUUID(array) {
				var k=0;
				var string = '';
				var UUID_format = [4, 2, 2, 2, 6];
				for(var l=0; l<UUID_format.length; l++) {
					if(l != 0) {
						string += '-';
					}
					for(var j=0; j<UUID_format[l]; j++, k++) {
						string += evothings.util.toHexString(array[k], 1);
					}
				}
				return string;
			}

			if(type == 0x02 || type == 0x03) {	// 16-bit Service Class UUIDs.
				var data = new Uint16Array(byteArray.buffer, pos, length);
				serviceUUIDs = serviceUUIDs ? serviceUUIDs : [];
				for(var i=0; i<length; i+=2) {
					serviceUUIDs.push('0000'+evothings.util.toHexString(
						evothings.util.littleEndianToUint16(byteArray, pos + i), 2)+BLUETOOTH_BASE_UUID);
				}
			}
			if(type == 0x04 || type == 0x05) {	// 32-bit Service Class UUIDs.
				serviceUUIDs = serviceUUIDs ? serviceUUIDs : [];
				for(var i=0; i<length; i+=4) {
					serviceUUIDs.push(evothings.util.toHexString(
						evothings.util.littleEndianToUint32(byteArray, pos + i), 4)+BLUETOOTH_BASE_UUID);
				}
			}
			if(type == 0x06 || type == 0x07) {	// 128-bit Service Class UUIDs.
				serviceUUIDs = serviceUUIDs ? serviceUUIDs : [];
				for(var i=0; i<length; i+=16) {
					serviceUUIDs.push(arrayToUUID(new Uint8Array(byteArray.buffer, pos + i, 16)));
				}
			}
			if(type == 0x08 || type == 0x09) {	// Local Name.
				advertisementData.kCBAdvDataLocalName = evothings.ble.fromUtf8(new Uint8Array(byteArray.buffer, pos, length));
			}
			if(type == 0x0a) {	// TX Power Level.
				advertisementData.kCBAdvDataTxPowerLevel = (new Int8Array(byteArray.buffer, pos, 1))[0];
			}
			if(type == 0x16) {	// Service Data, 16-bit UUID.
				serviceData = serviceData ? serviceData : {};
				var uuid = '0000'+evothings.util.toHexString(evothings.util.littleEndianToUint16(byteArray, pos), 2)+BLUETOOTH_BASE_UUID;
				var data = new Uint8Array(byteArray.buffer, pos+2, length-2);
				serviceData[uuid] = base64.fromArrayBuffer(data);
			}
			if(type == 0x20) {	// Service Data, 32-bit UUID.
				serviceData = serviceData ? serviceData : {};
				var uuid = evothings.util.toHexString(evothings.util.littleEndianToUint32(byteArray, pos), 4)+BLUETOOTH_BASE_UUID;
				var data = new Uint8Array(byteArray.buffer, pos+4, length-4);
				serviceData[uuid] = base64.fromArrayBuffer(data);
			}
			if(type == 0x21) {	// Service Data, 128-bit UUID.
				serviceData = serviceData ? serviceData : {};
				var uuid = arrayToUUID(new Uint8Array(byteArray.buffer, pos, 16));
				var data = new Uint8Array(byteArray.buffer, pos+16, length-16);
				serviceData[uuid] = base64.fromArrayBuffer(data);
			}
			if(type == 0xff) {	// Manufacturer-specific Data.
				// Annoying to have to transform base64 back and forth, but it has to be done in order to maintain the API.
				advertisementData.kCBAdvDataManufacturerData = base64.fromArrayBuffer(new Uint8Array(byteArray.buffer, pos, length));
			}

			pos += length;
		}
		advertisementData.kCBAdvDataServiceUUIDs = serviceUUIDs;
		advertisementData.kCBAdvDataServiceData = serviceData;
		device.advertisementData = advertisementData;

		/*
		// Log raw data for debugging purposes.
		var srs = ''
		for(var i=0; i<byteArray.length; i++) {
			srs += evothings.util.toHexString(byteArray[i], 1);
		}
		console.log("scanRecord: "+srs);

		console.log(JSON.stringify(advertisementData));<commit_msg>easyble.ensureAdvertisementData() now uses fewer TypedArrays. Fixes an exception that could be thrown when Service Class UUIDs were unaligned.<commit_after>/**
 * File: easyble.js
 * Description: Library for making BLE programming easier.
 * Author: Miki
 *
 * Note: The object type called "device" below, is the "DeviceInfo"
 * object obtained by calling evothings.ble.startScan, enhanced with
 * additional properties and functions to allow easy access to
 * object methods. Properties are also added to the Characteristic
 * and Descriptor object. Added properties are prefixed with two
 * underscores.
 */

evothings.loadScript('libs/evothings/util/util.js');

var base64 = cordova.require('cordova/base64');

if (!window.evothings) { window.evothings = {}; }

// Object that holds BLE data and functions.
evothings.easyble = (function()
{
	/** Main object in the EasyBLE API. */
	var easyble = {};

	/**
	 * Set to true to report found devices only once,
	 * set to false to report continuously.
	 */
	var reportDeviceOnce = false;

	var serviceFilter = false;

	/** Internal properties and functions. */
	var internal = {};

	/** Internal variable used to track reading of service data. */
	var readCounter = 0;

	/** Table of discovered devices. */
	internal.knownDevices = {};

	/** Table of connected devices. */
	internal.connectedDevices = {};

	/**
	 * Set to true to report found devices only once.
	 * Set to false to report continuously.
	 * The default is to report continously.
	 */
	easyble.reportDeviceOnce = function(reportOnce)
	{
		reportDeviceOnce = reportOnce;
	};

	/**
	* Set to an Array of UUID strings to enable filtering of devices found by startScan().
	* Set to false to disable filtering.
	* The default is to not filter.
	* An empty array will cause no devices to be reported.
	*/
	easyble.filterDevicesByService = function(services) {
		serviceFilter = services;
	};

	/** Start scanning for devices. */
	easyble.startScan = function(win, fail)
	{
		easyble.stopScan();
		internal.knownDevices = {};
		evothings.ble.startScan(function(device)
		{
			// Ensure we have advertisementData.
			internal.ensureAdvertisementData(device);

			// Check if the device matches the filter, if we have a filter.
			if(!internal.deviceMatchesServiceFilter(device)) {
				return;
			}

			// Check if we already have got the device.
			var existingDevice = internal.knownDevices[device.address]
			if (existingDevice)
			{
				// Do not report device again if flag is set.
				if (reportDeviceOnce) { return; }

				// Flag not set, report device again.
				existingDevice.rssi = device.rssi;
				existingDevice.name = device.name;
				existingDevice.scanRecord = device.scanRecord;
				existingDevice.advertisementData = device.advertisementData;
				win(existingDevice);
				return;
			}

			// New device, add to known devices.
			internal.knownDevices[device.address] = device;

			// Add methods to the device info object.
			internal.addMethodsToDeviceObject(device);

			// Call callback function with device info.
			win(device);
		},
		function(errorCode)
		{
			fail(errorCode);
		});
	};

	/** Stop scanning for devices. */
	easyble.stopScan = function()
	{
		evothings.ble.stopScan();
	};

	/** Close all connected devices. */
	easyble.closeConnectedDevices = function()
	{
		for (var key in internal.connectedDevices)
		{
			var device = internal.connectedDevices[key];
			device && device.close();
			internal.connectedDevices[key] = null;
		}
	};

	/**
	* If device has advertisementData, does nothing.
	* If device instead has scanRecord, creates advertisementData.
	* See ble.js for AdvertisementData reference.
	*/
	internal.ensureAdvertisementData = function(device) {
		if(device.advertisementData) {
			//console.log("iOS ad: "+JSON.stringify(device.advertisementData));
			return;
		}
		if(!device.scanRecord) { return; }

		// Here we parse BLE/GAP Scan Response Data.
		// See the Bluetooth Specification, v4.0, Volume 3, Part C, Section 11,
		// for details.

		var byteArray = evothings.util.base64DecToArr(device.scanRecord);
		var pos = 0;
		var advertisementData = {};
		var serviceUUIDs;
		var serviceData;
		// The scan record is a list of structures.
		// Each structure has a length byte, a type byte, and (length-1) data bytes.
		// The format of the data bytes depends on the type.
		// Malformed scanRecords will likely cause an exception in this function.
		while(pos < byteArray.length) {
			var length = byteArray[pos++];
			if(length == 0) {
				break;
			}
			length -= 1;
			var type = byteArray[pos++];

			// Parse types we know and care about.
			// Skip other types.

			var BLUETOOTH_BASE_UUID = '-0000-1000-8000-00805f9b34fb'

			// Convert 16-byte Uint8Array to RFC-4122-formatted UUID.
			function arrayToUUID(array, offset) {
				var k=0;
				var string = '';
				var UUID_format = [4, 2, 2, 2, 6];
				for(var l=0; l<UUID_format.length; l++) {
					if(l != 0) {
						string += '-';
					}
					for(var j=0; j<UUID_format[l]; j++, k++) {
						string += evothings.util.toHexString(array[offset+k], 1);
					}
				}
				return string;
			}

			if(type == 0x02 || type == 0x03) {	// 16-bit Service Class UUIDs.
				serviceUUIDs = serviceUUIDs ? serviceUUIDs : [];
				for(var i=0; i<length; i+=2) {
					serviceUUIDs.push('0000'+evothings.util.toHexString(
						evothings.util.littleEndianToUint16(byteArray, pos + i), 2)+BLUETOOTH_BASE_UUID);
				}
			}
			if(type == 0x04 || type == 0x05) {	// 32-bit Service Class UUIDs.
				serviceUUIDs = serviceUUIDs ? serviceUUIDs : [];
				for(var i=0; i<length; i+=4) {
					serviceUUIDs.push(evothings.util.toHexString(
						evothings.util.littleEndianToUint32(byteArray, pos + i), 4)+BLUETOOTH_BASE_UUID);
				}
			}
			if(type == 0x06 || type == 0x07) {	// 128-bit Service Class UUIDs.
				serviceUUIDs = serviceUUIDs ? serviceUUIDs : [];
				for(var i=0; i<length; i+=16) {
					serviceUUIDs.push(arrayToUUID(byteArray, pos + i));
				}
			}
			if(type == 0x08 || type == 0x09) {	// Local Name.
				advertisementData.kCBAdvDataLocalName = evothings.ble.fromUtf8(new Uint8Array(byteArray.buffer, pos, length));
			}
			if(type == 0x0a) {	// TX Power Level.
				advertisementData.kCBAdvDataTxPowerLevel = evothings.util.littleEndianToInt8(byteArray, pos);
			}
			if(type == 0x16) {	// Service Data, 16-bit UUID.
				serviceData = serviceData ? serviceData : {};
				var uuid = '0000'+evothings.util.toHexString(evothings.util.littleEndianToUint16(byteArray, pos), 2)+BLUETOOTH_BASE_UUID;
				var data = new Uint8Array(byteArray.buffer, pos+2, length-2);
				serviceData[uuid] = base64.fromArrayBuffer(data);
			}
			if(type == 0x20) {	// Service Data, 32-bit UUID.
				serviceData = serviceData ? serviceData : {};
				var uuid = evothings.util.toHexString(evothings.util.littleEndianToUint32(byteArray, pos), 4)+BLUETOOTH_BASE_UUID;
				var data = new Uint8Array(byteArray.buffer, pos+4, length-4);
				serviceData[uuid] = base64.fromArrayBuffer(data);
			}
			if(type == 0x21) {	// Service Data, 128-bit UUID.
				serviceData = serviceData ? serviceData : {};
				var uuid = arrayToUUID(byteArray, pos);
				var data = new Uint8Array(byteArray.buffer, pos+16, length-16);
				serviceData[uuid] = base64.fromArrayBuffer(data);
			}
			if(type == 0xff) {	// Manufacturer-specific Data.
				// Annoying to have to transform base64 back and forth, but it has to be done in order to maintain the API.
				advertisementData.kCBAdvDataManufacturerData = base64.fromArrayBuffer(new Uint8Array(byteArray.buffer, pos, length));
			}

			pos += length;
		}
		advertisementData.kCBAdvDataServiceUUIDs = serviceUUIDs;
		advertisementData.kCBAdvDataServiceData = serviceData;
		device.advertisementData = advertisementData;

		/*
		// Log raw data for debugging purposes.
		var srs = ''
		for(var i=0; i<byteArray.length; i++) {
			srs += evothings.util.toHexString(byteArray[i], 1);
		}
		console.log("scanRecord: "+srs);

		console.log(JSON.stringify(advertisementData));
{'fraction_non_alphanumeric': 0.12157749309218789, 'fraction_numerical': 0.020346646571213264, 'mean_word_length': 3.452054794520548, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3096101', 'n_tokens_mistral': 6115, 'n_tokens_neox': 5131, 'n_words': 1381}