1.0.6 • Published 5 years ago

@nebulae/angular-ble v1.0.6

Weekly downloads
1
License
MIT
Repository
github
Last release
5 years ago

@nebulae/angular-ble

The general purpose of this service is establish a communication channel between two devices using Bluetooth specifically implementing the GATT protocol , additionally, the library exposes multiple tools to facilitate the encrypt and decrypt, currently just is implemented the AES protocol using CBC, CTR, ECB, CFB and OFB modes of operation.

Table of Contents

Installation

to install @nebulae/angular-ble library in your angular project just execute the command

npm install @nebulae/angular-ble

Use it

  • Be sure you already have install @types/web-bluetooth and aes-js, if already not installed, execute the next commands
npm install @types/web-bluetooth
npm install aes-js

Import the AngularBleModule in your module

import { NgModule } from '@angular/core';
import { AngularBleModule } from 'angular-ble';

import { AppComponent } from './app.component';

@NgModule({
  imports: [
    //...,
    AngularBleModule.forRoot()
  ]
  //...,
})
export class AppModule {}
  • Use AES cypher in your service/component

    here is an annotated example using the @nebulae/angular-ble cypher service

    import { Component, OnInit } from '@angular/core';
    import { CypherAesService } from 'angular-ble';
    @Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
    })
    export class AppComponent implements OnInit {
    // input value used to encrypt
    valueToEncrypt = '';
    // encrypted value converted to hex
    encryptedHex = '';
    // encrypted value converted to bytes
    encryptedBytes = '';
    // input value used to decrypt
    valueToDecrypt = '';
    // decrypted value converted to hex
    decryptedHex = '';
    // decrypted value converted to bytes
    decryptedBytes = '';
    // decrypted value converted to text
    decryptedText = '';
    constructor(private cypherAesService: CypherAesService) {}
    
    title = 'angular-ble-app';
    
    ngOnInit(): void {
    }
    
    encryptValue() {
      // Call this method to configurate the aes service
      this.cypherAesService.config([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ]);
      /* Next to it call the service and specifically the method called encrypt, this returns an Uint8Array than contains the encrypted input value.
      The encrypt method only receives Uint8Array or Uint16Array or Uint32Array, because of this the text is parsed to Uint8Array
      */
      const encryptedValue = this.cypherAesService.encrypt(this.cypherAesService.textToBytes(this.valueToEncrypt));
      this.encryptedHex = this.cypherAesService.bytesTohex(encryptedValue);
      this.encryptedBytes = '[' + Array.from(encryptedValue).toString() + ']';
    }
    
    decryptValue() {
      // Call this method to configurate the aes service
      this.cypherAesService.config([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ]);
      /* Next to it call the service and specifically the method called decrypt, this returns an Uint8Array than contains the decrypted input value.
      To this example the input receives an encrypted hexa. but the decrypt method only receives Uint8Array or Uint16Array or Uint32Array, because of this the hexa is parsed to Uint8Array
      */
      const decryptedValue = this.cypherAesService.decrypt(this.cypherAesService.hexToBytes(this.valueToDecrypt));
      this.decryptedHex = this.cypherAesService.bytesTohex(decryptedValue);
      this.decryptedBytes = '[' + Array.from(decryptedValue).toString() + ']';
      this.decryptedText = this.cypherAesService.bytesToText(decryptedValue);
    }
    }
  • Use Bluetooth BLE in your service/component

    here is an annotated example using the @nebulae/angular-ble bluetooth service

 import { Component, OnInit } from '@angular/core';
  import { CypherAesService } from 'angular-ble';
  @Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
  })
  export class AppComponent implements OnInit {
  // Current device status
  deviceConnected = false;
  // Current device battery level
  batteryLevel = '';
  // Current device manufacturer name
  manufacturerName = '';
  // Current device model number
  modelNumber = '';
  // Current device serial number
  serialNumber = '';
  // Current device hardware Revision
  hardwareRevision = '';
  // Current device firmware revision
  firmwareRevision = '';
  // Current device software revision
  softwareRevision = '';
  // Current device system id
  systemId;
  // Current device pnp id
  pnpId;
  constructor(private bluetoothService: BluetoothService) {}

  title = 'angular-ble-app';

  ngOnInit(): void {
    // Start a listener that delivers the currently connected device (if the returned
    // device is null means than the device connection has been lost)
    this.bluetoothService.getDevice$().subscribe(device => {
      this.deviceConnected = device? true : false;
    });
  }

  //stablish a connection between the browser and a bluetooth device
  connectToDevice() {
    this.bluetoothService.connectDevice$().subscribe(res => {});
  } 
  // end the stablished connection between the browser and a bluetooth 
  //device
  disconnectToDevice() {
    this.bluetoothService.disconnectDevice();
  }
  //get the current device battery level
  getBatteryLevel() {
    this.bluetoothService.getBatteryLevel$().subscribe(res => {
      this.batteryLevel = res+"";
    })
  }
  }

AES cypher

This service use as base the library aes-js

Keys

All keys must be 128 bits (16 bytes), 192 bits (24 bytes) or 256 bits (32 bytes) long.

The library work with Array, Uint8Array and Buffer objects as well as any array-like object (i.e. must have a length property, and have a valid byte value for each entry).
  // 128-bit, 192-bit and 256-bit keys
  const key_128 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
  const key_192 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
             16, 17, 18, 19, 20, 21, 22, 23];
  const key_256 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
             16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
             29, 30, 31];
  // or, you may use Uint8Array:
  const key_128_array = new Uint8Array(key_128);
  const key_192_array = new Uint8Array(key_192);
  const key_256_array = new Uint8Array(key_256);

Exposed Methods

Common Modes of Operation

There are several modes of operations, each with various pros and cons. In general though, the CBC and CTR modes are recommended. The ECB is NOT recommended., and is included primarily for completeness.

CTR - Counter (recommended)

const textToEncrypt = 'Text may be any length you wish, no padding is required.';
// An example 128-bit key (16 bytes * 8 bits/byte = 128 bits), CTR doesnt require inital vector so this param is passed as undefined and as additional param is added the value counter (required)
this.cypherAesService.config([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], undefined, 'CTR', {counter: 5});
const encryptedValue = this.cypherAesService.encrypt(this.cypherAesService.textToBytes(textToEncrypt));

console.log(encryptedValue);
//The result must be: [143, 148, 252, 101, 14, 212, 112, 111, 121,
//    19, 78, 143, 174, 156, 210, 4, 246, 31, 104, 126, 222, 170, 190, 21, 112, 94, 124, 48
//    , 58, 166, 224, 223, 52, 66, 62, 233, 223, 77, 149, 189, 71, 231, 80, 228, 146, 178,
//    173, 8, 215, 99, 175, 0, 35, 20, 27, 187]


// When ready to decrypt the data, use the decrypt method
const decryptedValue = this.cypherAesService.decrypt(new Uint8Array([143, 148, 252, 101, 14, 212, 112, 111, 121,
      19, 78, 143, 174, 156, 210, 4, 246, 31, 104, 126, 222, 170, 190, 21, 112, 94, 124, 48
      , 58, 166, 224, 223, 52, 66, 62, 233, 223, 77, 149, 189, 71, 231, 80, 228, 146, 178,
      173, 8, 215, 99, 175, 0, 35, 20, 27, 187]));
console.log(this.cypherAesService.bytesToText(decryptedValue));
//The result must be: Text may be any length you wish, no padding is required.

CBC - Cipher-Block Chaining (recommended)

// If the text is not a 16 byte size, the library use a padding method to fill the missing with 0xFF
const textToEncrypt = 'TextMustBe16Byte';
// An example 128-bit key
// The initialization vector must be 16 bytes (this value is optional, the predefined value is a 16 bytes iv empty 0xFF)
// The CBC method is the default so doesn't is necessary set the method type
this.cypherAesService.config([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ],
    [ 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36 ]);
const encryptedValue = this.cypherAesService.encrypt(this.cypherAesService.textToBytes(textToEncrypt));

console.log(encryptedValue);
//The result must be: [16, 79, 176, 115, 249, 161, 49, 242, 202, 180, 145, 132, 187, 134, 76, 162]


// When ready to decrypt the data, use the decrypt method
const decryptedValue = this.cypherAesService.decrypt(new Uint8Array([16, 79, 176, 115, 249, 161, 49, 242, 202, 180, 145, 132, 187, 134, 76, 162]));
console.log(this.cypherAesService.bytesToText(decryptedValue));
//The result must be: TextMustBe16Byte.

CFB - Cipher Feedback

// the text must be multiple of the segment size assigned
const textToEncrypt = 'TextMustBeAMultipleOfSegmentSize';
// An example 128-bit key
// The initialization vector must be 16 bytes (this value is optional, the predefined value is a 16 bytes iv empty 0xFF)
// As additional param is added the value segmentSize (required)
this.cypherAesService.config([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
    [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36], 'CFB', { segmentSize: 8 });
const encryptedValue = this.cypherAesService.encrypt(this.cypherAesService.textToBytes(textToEncrypt));

console.log(encryptedValue);
//The result must be: [85, 227, 175, 38, 56, 197, 96, 180, 253, 185, 210, 106, 99, 7, 51, 234, 96, 25, 126, 194,
//      61, 235, 133, 177, 246, 15, 113, 241, 4, 9, 206, 39]


// When ready to decrypt the data, use the decrypt method
const decryptedValue = this.cypherAesService.decrypt(new Uint8Array([85, 227, 175, 38, 56, 197, 96, 180, 253, 185, 210, 106, 99, 7, 51, 234, 96, 25, 126, 194, 61, 235, 133, 177, 246, 15, 113, 241, 4, 9, 206, 39]));
console.log(this.cypherAesService.bytesToText(decryptedValue));
//The result must be: TextMustBeAMultipleOfSegmentSize.

OFB - Output Feedback

const textToEncrypt = 'Text may be any length you wish, no padding is required.';
// An example 128-bit key
// The initialization vector must be 16 bytes (this value is optional, the predefined value is a 16 bytes iv empty 0xFF)
this.cypherAesService.config([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
    [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36], 'OFB');
const encryptedValue = this.cypherAesService.encrypt(this.cypherAesService.textToBytes(textToEncrypt));

console.log(encryptedValue);
//The result must be: [85, 227, 175, 38, 85, 221, 114, 185, 243, 36, 86, 4, 47, 57, 186, 233,
//    172, 207, 246, 37, 145, 89, 230, 8, 190, 85, 161, 170, 49, 60, 89, 141, 180,
//    177, 132, 6, 216, 156, 131, 132, 28, 157, 26, 241, 59, 86, 222, 142, 218, 143, 207, 233, 236, 142, 117, 232]


// When ready to decrypt the data, use the decrypt method
const decryptedValue = this.cypherAesService.decrypt(new Uint8Array([85, 227, 175, 38, 85, 221, 114, 185, 243, 36, 86, 4, 47, 57, 186, 233,
    172, 207, 246, 37, 145, 89, 230, 8, 190, 85, 161, 170, 49, 60, 89, 141, 180,
    177, 132, 6, 216, 156, 131, 132, 28, 157, 26, 241, 59, 86, 222, 142, 218, 143, 207, 233, 236, 142, 117, 232]));
console.log(this.cypherAesService.bytesToText(decryptedValue));
//The result must be: Text may be any length you wish, no padding is required..

ECB - Electronic Codebook (NOT recommended)

// If the text is not a 16 byte size, the library use a padding method to fill the missing with 0xFF
const textToEncrypt = 'TextMustBe16Byte';
// An example 128-bit key
// ECB doesnt require inital vector so this param is passed as undefined  
this.cypherAesService.config([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
    undefined, 'ECB');
const encryptedValue = this.cypherAesService.encrypt(this.cypherAesService.textToBytes(textToEncrypt));

console.log(encryptedValue);
//The result must be: [167, 217, 59, 53, 54,
//      133, 25, 250, 195, 71, 73, 141, 236, 24, 180, 88]


// When ready to decrypt the data, use the decrypt method
const decryptedValue = this.cypherAesService.decrypt(new Uint8Array([167, 217, 59, 53, 54,
      133, 25, 250, 195, 71, 73, 141, 236, 24, 180, 88]));
console.log(this.cypherAesService.bytesToText(decryptedValue));
//The result must be: Text may be any length you wish, no padding is required..

Utils

config

This method is used to configurate the params required by the cypher service

// key used to encrypt and decrypt (Required) 
const masterKey = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
// vector used to encrypt abd decrypt except when ECB encrypt method is used (optional)
const initialVector = [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36];
// type of encrypt method is used, the possible options are: CBC, CTR, CFB, OFB, ECB (optional)
const encryptMethod = 'CFB';
//configuration params used by the selected encrypt method.
// Note: if the method CTR or CFB is used this param is required otherwise is an optinal param.
// By CTR require the param counter and by CFB require the param segmentSize
const additionalEncryptMethodParams = { segmentSize: 8 };
// defines if the initial vector is changed or not when the data are encrypted or not
const isStaticInitialVector = true;

this.cypherAesService.config(masterKey,
    initialVector, encryptMethod, additionalEncryptMethodParams, isStaticInitialVector);

encrypt

Encrypt the data using the encrypt method previously configured. The data must be a Uint8Array or Uint16Array or Uint32Array

this.cypherAesService.encrypt(dataToEncrypt)

decrypt

Decrypt the data using the encrypt method previously configured. The data must be a Uint8Array or Uint16Array or Uint32Array

this.cypherAesService.decrypt(dataToDecrypt)

changeInitialVector

Change the current initalVector

this.cypherAesService.changeInitialVector(newInitalVector)

changeEncryptMethod

Change the current encyptMethod

this.cypherAesService.changeEncryptMethod(newEncryptMethod)

changeStaticInitialVector

Change the current isStaticInitialVector

this.cypherAesService.changeStaticInitialVector(true|false)

changeMasterKey

Change the current masterKey

this.cypherAesService.changeMasterKey(newMasterKey)

textToBytes

Convert the text to bytes

this.cypherAesService.textToBytes(text)

bytesToText

Convert the bytes to text

this.cypherAesService.bytesToText(bytes)

bytesTohex

Convert the bytes to hex

this.cypherAesService.bytesTohex(bytes)

hexToBytes

Convert the hex to bytes

this.cypherAesService.hexToBytes(hex)

Bluetooth BLE

this service is based on Web Bluetooth please see for more info

Exposed Methods

Bluetooth BLE commons

getDevice$

get the current device, if the device return null is because the connection has lost

this.bluetoothService.getDevice$().subscribe(device => {
    //here you receive the device        
    });

startNotifierListener$

start a stream by notifiers characteristics

this.bluetoothService.startNotifierListener$('battery_service','battery_level').subscribe(result => {
      console.log('stream value: ', result);
    });

connectDevice$

Discover all available devices and connect to a selected device

this.bluetoothService.connectDevice$().subscribe(res => {
  //here you receive the device if the connection is succeful
});

disconnectDevice

Disconnect the current device this.bluetoothService.disconnectDevice();

readDeviceValue

get a data from the device using a service and the characteristic

this.bluetoothService.readDeviceValue$('battery_service','battery_level').subscribe(result => {
      console.log('stream value: ', result);
    });

sendToNotifier$

Send a message using a notifier characteristic (message must be in bytes)

this.bluetoothService.sentToNotifier$('hereMessage','here service', 'here characterisitic').subscribe(result =>{})

getPrimaryService$

Get a primary service instance using the service UIID or GATT identifier

this.bluetoothService.getPrimariService$('here_the_service_identifier').subscribe(result =>{
  console.log('Service instance: ',result);
})

getCharacteristic$

Get a characterisitic instance using the service instance and a characteristic UUID

this.bluetoothService.getCharacteristic$(serviceInstance,'here_the_characteristic_identifier').subscribe(result =>{
  console.log('Service instance: ',result);
})

GATT Utils

getBatteryLevel$

The Battery Level characteristic is read using the GATT Read Characteristic Value sub-procedure and returns the current battery level as a percentage from 0% to 100%; 0% represents a battery that is fully discharged, 100% represents a battery that is fully charged

this.bluetoothService.getBatteryLevel$().subscribe(res => {
      this.batteryLevel = res+"";
    })

getManufacturerName$

This characteristic represents the name of the manufacturer of the device.

this.bluetoothService.getManufacturerName$().subscribe(res => {
      this.manufacturerName = res;      
    });

getModelNumber$

This characteristic represents the model number that is assigned by the device vendor.

this.bluetoothService.getModelNumber$().subscribe(res => {
      this.modelNumber = res;
    });

getSerialNumber$

This characteristic represents the serial number for a particular instance of the device.

getSerialNumber() {
    this.bluetoothService.getSerialNumber$().subscribe(res => {
      this.serialNumber = res;
    });
  }

getHardwareRevision$

This characteristic represents the hardware revision for the hardware within the device.

getHardwareRevision() {
    this.bluetoothService.getHardwareRevision$().subscribe(res => {
      this.hardwareRevision = res;
    });
  }

getFirmwareRevision$

This characteristic represents the firmware revision for the firmware within the device.

getFirmwareRevision() {
    this.bluetoothService.getFirmwareRevision$().subscribe(res => {
      this.firmwareRevision = res;
    });
  }

getSoftwareRevision$

This characteristic represents the software revision for the software within the device.

getSoftwareRevision() {
    this.bluetoothService.getSoftwareRevision$().subscribe(res => {
      this.softwareRevision = res;
    });
  }

getSystemId$

This characteristic represents a structure containing an Organizationally Unique Identifier (OUI) followed by a manufacturer-defined identifier and is unique for each individual instance of the product.

getSystemId() {
    this.bluetoothService.getSystemId$().subscribe(res => {
      this.systemId = res+"";
    });
  }

getPnpId$

The PnP_ID characteristic is a set of values used to create a device ID value that is unique for this device.

getPnpId() {
    this.bluetoothService.getPnpId$().subscribe(res => {
      this.pnpId = res+"";
    });
  }
1.0.6

5 years ago

1.0.5

6 years ago

1.0.4

6 years ago

1.0.3

6 years ago

1.0.2

6 years ago

1.0.1

6 years ago

1.0.0

6 years ago

0.0.6

6 years ago

0.0.3

6 years ago

0.0.2

6 years ago

0.0.1

6 years ago