@iotize/tap
Version:
IoTize Device client for Javascript
1,489 lines (1,468 loc) • 76.8 kB
JavaScript
import { typedArrayToBuffer, bufferToHexString } from '@iotize/common/byte-converter';
import { KaitaiStreamReader, KaitaiStreamWriter } from '@iotize/common/byte-stream';
import { createDebugger } from '@iotize/common/debug';
import { CodeError } from '@iotize/common/error';
import { TapRequestFrame, TapApduRequest, ApduResponse, ResultCode } from '@iotize/tap/client/api';
import { Subject, throwError } from 'rxjs';
import { map } from 'rxjs/operators';
import { Buffer } from 'buffer';
import { TLV } from '@iotize/common/tlv';
const prefix = '@iotize/tap/client/impl';
const debug = createDebugger(prefix);
class ConverterError extends CodeError {
constructor(code, msg, cause) {
super(msg, code);
this.cause = cause;
}
static unexpectedBufferSize(expected, buffer) {
return new ConverterError(ConverterError.Code.UnexpectedBufferSizeError, 'Expected buffer of ' +
expected +
' byte(s) but found only ' +
buffer.length +
' byte(s)');
}
static nullBufferError(msg) {
return new ConverterError(ConverterError.Code.NullBufferError, msg);
}
static valueTooBigError(value, maxValue) {
return new ConverterError(ConverterError.Code.ValueTooBigError, `Value ${value} is not acceptable. Maximum authorized value is ${maxValue}.`);
}
}
(function (ConverterError) {
let Code;
(function (Code) {
Code["UnexpectedBufferSizeError"] = "UnexpectedBufferSizeError";
Code["NullBufferError"] = "NullBufferError";
Code["ValueTooBigError"] = "ConverterErrorValueTooBigError";
})(Code = ConverterError.Code || (ConverterError.Code = {}));
})(ConverterError || (ConverterError = {}));
class NumberConverter {
constructor(options) {
this.options = options;
}
/**
* Number as unsigned 8 bits converter
* @param lsb true for least significant bit first
*/
static uint8(lsb = false) {
return new NumberConverter({
signed: false,
sizeOf: 8,
leastSignificantBitFirst: lsb,
});
}
/**
* Number as unsigned 16 bits converter
* @param lsb true for least significant bit first
*/
static uint16(lsb = false) {
return new NumberConverter({
signed: false,
sizeOf: 16,
leastSignificantBitFirst: lsb,
});
}
/**
* Number as unsigned 32 bits converter
* @param lsb true for least significant bit first
*/
static uint32(lsb = false) {
return new NumberConverter({
signed: false,
sizeOf: 32,
leastSignificantBitFirst: lsb,
});
}
/**
* Number as signed 8 bits converter
* @param lsb true for least significant bit first
*/
static int8(lsb = false) {
return new NumberConverter({
signed: true,
sizeOf: 8,
leastSignificantBitFirst: lsb,
});
}
/**
* Number as signed 16 bits converter
* @param lsb true for least significant bit first
*/
static int16(lsb = false) {
return new NumberConverter({
signed: true,
sizeOf: 16,
leastSignificantBitFirst: lsb,
});
}
/**
* Number as signed 32 bits converter
* @param lsb true for least significant bit first
*/
static int32(lsb = false) {
return new NumberConverter({
signed: true,
sizeOf: 32,
leastSignificantBitFirst: lsb,
});
}
decode(body) {
if (body.length * 8 < this.options.sizeOf) {
throw ConverterError.unexpectedBufferSize(this.options.sizeOf / 8, body);
}
return NumberConverter.fromBytes(body, this.options.sizeOf / 8, this.options.signed, this.options.leastSignificantBitFirst);
}
encode(value) {
const maxValue = Math.pow(2, this.options.sizeOf) - 1;
if (value > maxValue) {
throw ConverterError.valueTooBigError(value, maxValue);
}
const result = new Uint8Array(this.options.sizeOf >> 3);
for (var i = 0; i < result.length; i++) {
const decalage = (result.length - i - 1) * 8;
result[i] = (value >> decalage) & 0xff;
}
if (this.options.leastSignificantBitFirst) {
result.reverse();
}
return result;
}
static toOpaqueMsb(input, sizeOf) {
const maxValue = Math.pow(2, sizeOf) - 1;
if (input > maxValue) {
throw ConverterError.valueTooBigError(input, maxValue);
}
if (sizeOf === 8) {
return new Uint8Array([input & 0xff]);
}
else if (sizeOf === 16) {
return new Uint8Array([(input >> 8) & 0xff, input & 0xff]);
}
else if (sizeOf === 32) {
return new Uint8Array([
(input >> 24) & 0xff,
(input >> 16) & 0xff,
(input >> 8) & 0xff,
input & 0xff,
]);
}
else {
throw new Error(`Invalid size ${sizeOf}. Must be 8, 16 or 32`);
}
}
static fromOpaqueMsb(data, sizeOf) {
if (!sizeOf) {
sizeOf = data.length * 8;
}
if (sizeOf === 8) {
return data[0] & 0xff;
}
if (sizeOf === 16) {
return (data[1] & 0xff) + ((data[0] & 0xff) << 8);
}
if (sizeOf === 32) {
return ((data[3] & 0xff) +
((data[2] & 0xff) << 8) +
((data[1] & 0xff) << 16) +
((data[0] & 0xff) << 24));
}
throw new Error(`Invalid size ${sizeOf}. Must be 8, 16 or 32`);
}
/**
* @param size size in bytes
*/
static fromBytes(data, size, signed, lsb) {
if (lsb) {
data = data.reverse();
}
var result = 0;
var last = 0;
var decalage;
for (var i = 0; i < size; i++) {
decalage = (size - i - 1) * 8;
result += ((data[i] & 0xff) << decalage) >>> 0;
if (last > result && !signed) {
throw new Error('Overflow. Last value: ' + last + ' but new is ' + result);
}
last = result;
}
if (signed && (data[0] & 0x80) === 0x80) {
result = -(Math.pow(2, 8 * size) - result);
}
return result;
}
}
let _mpeg2Instance;
class CRC {
constructor(polynomial) {
this.polynomial = polynomial;
}
static mpeg2() {
if (!_mpeg2Instance) {
_mpeg2Instance = new CRC(CRC.POLYNOMIAL_CRC32_MPEG2);
}
return _mpeg2Instance;
}
compute(buffer, firstValue = CRC.FIRST_VALUE) {
if (buffer instanceof Uint32Array) {
return CRC.fromWord(buffer, firstValue, this.polynomial);
}
else {
return CRC.fromBytes(buffer, firstValue, this.polynomial);
}
}
static fromBytes(data, crc = CRC.FIRST_VALUE, polynomial = CRC.POLYNOMIAL_CRC32_MPEG2) {
if (data.length % CRC.MODULO !== 0) {
throw new Error(`Cannot compute CRC, input size must be a multiple of 4. Length is ${data.length} % ${CRC.MODULO} = ${data.length % CRC.MODULO}`);
}
const uint32Data = new Uint32Array(data.length / 4);
for (let offset = 0; offset < uint32Data.byteLength; offset++) {
uint32Data[offset] = NumberConverter.fromOpaqueMsb(data.slice(offset * 4, (offset + 1) * 4), 32);
}
return CRC.fromWord(uint32Data, crc, polynomial);
}
// crc 32 calculation by word.
static fromWord(buffer, crc = CRC.FIRST_VALUE, polynomial = CRC.POLYNOMIAL_CRC32_MPEG2) {
// don't follow null pointers
if (!buffer) {
throw new Error('Buffer is null');
}
// don't accept buffers larger than 16MB
if (buffer.byteLength > 16 * 1024 * 1024) {
throw new Error('Buffers larger than 16MB');
}
let myword;
for (let i = 0; i < buffer.length; i++) {
myword = buffer[i]; // Get next word.
crc = CRC.appendCrcNumber(crc, myword, polynomial);
}
return crc;
}
static appendCrcNumber(crc, data, polynomial) {
crc = crc ^ data;
for (let i = 0; i < 32; i++) {
if (crc & 0x80000000) {
crc = ((crc << 1) ^ polynomial) >>> 0;
}
else {
crc = (crc << 1) >>> 0;
}
}
return crc;
}
}
CRC.POLYNOMIAL_CRC32_MPEG2 = 0x4c11db7;
CRC.MODULO = 4;
CRC.FIRST_VALUE = 0xffffffff;
function enumKeyOrValueToNumber(key, mapping) {
if (typeof key === 'string') {
// if (!(key in mapping)) {
// throw new Error(``)
// }
return mapping[key];
}
else {
return key;
}
}
class TapStreamReader extends KaitaiStreamReader {
static fromArray(array) {
const buffer = typedArrayToBuffer(array);
return new TapStreamReader(buffer);
}
static create(packet) {
return packet instanceof Uint8Array
? TapStreamReader.fromArray(packet)
: packet;
}
readStr(n) {
return String.fromCharCode.apply(null, this.readBytes(n));
}
/**
* Read given byte size as string
* @param n number of byte to read
*/
readString(n = this.byteLeft) {
return this.readStr(n);
}
/**
* Read string until terminator character is reached
*/
readStringTerminator(terminator) {
let result = '';
while (true) {
const lastByte = this.readU1();
const lastChar = String.fromCharCode(lastByte);
if (lastChar !== terminator) {
result += lastChar;
}
else {
break;
}
}
return result;
}
readU2() {
return this._isBigEndian ? this.readU2be() : this.readU2le();
}
readU4() {
return this._isBigEndian ? this.readU4be() : this.readU4le();
}
readF4() {
return this._isBigEndian ? this.readF4be() : this.readF4le();
}
subStream(length) {
return TapStreamReader.fromArray(this.readBytes(length));
}
}
class TapStreamWriter extends KaitaiStreamWriter {
constructor(lengthOrBuffer = 512, byteOffset, isBigEndian = true) {
if (typeof lengthOrBuffer === 'number') {
lengthOrBuffer = new ArrayBuffer(lengthOrBuffer);
}
super(lengthOrBuffer, byteOffset, isBigEndian);
}
static create(size) {
return new TapStreamWriter(new ArrayBuffer(size));
}
writeFunction(type, options, fieldValue, fieldSize) {
switch (type) {
case 'crc32':
return this.addCRC(options);
case 'padding':
if (fieldValue) {
debug('Padding is already set');
return this.writeBytes(fieldValue);
}
else {
if (typeof fieldSize == undefined) {
throw new Error(`Padding function expect padding size as a second argument. Args: ${JSON.stringify(arguments)}`);
}
return this.addPadding(fieldSize);
}
default:
throw new Error(`Invalid writer function type: ${type}`);
}
}
addPadding(padSize) {
this.writeBytes(new Uint8Array(padSize));
return this;
}
addCRC(options) {
let buffer = this.toBytes;
if (options && options.offset) {
buffer = buffer.slice(options.offset);
}
const crc = CRC.fromBytes(buffer);
this.writeU4(crc);
// console.info(`TODO REMOVE Compute CRC from ${options ? options.offset : 0}:${buffer.length} 0x${bufferToHexString(buffer)} => 0x${crc.toString(16)}`)
return this;
}
writeUnsigned(value, length) {
this.writeU(value, length);
return this;
}
writeU4(value) {
this.writeU(value, 4);
return this;
}
writeU2(value) {
this.writeU(value, 2);
return this;
}
writeU1(value) {
this.writeU(value, 1);
return this;
}
writeF4(value) {
this.writeF(value, 4);
return this;
}
writeFloat(value, length) {
this.writeF(value, length);
return this;
}
writeSigned(value, length) {
this.writeSigned(value, length);
return this;
}
writeString(value, length = (value === null || value === void 0 ? void 0 : value.length) || 0) {
return this.writeStr(value || '', length);
}
writeStr(value, length = value.length) {
let i = 1;
const stringWriteLength = Math.min(value.length, length);
for (; i <= stringWriteLength; i++) {
this.writeByte(value.charCodeAt(i - 1));
}
const remaining = length - stringWriteLength;
if (remaining > 0) {
this.writeBytes(new Uint8Array(remaining));
}
return this;
}
writeBitsInt(value, n) {
this.writeB(value, n);
return this;
}
writeBits(value, n) {
this.writeB(value, n);
return this;
}
writeBoolean(value, n) {
this.writeBits(value ? 1 : 0, n);
return this;
}
}
/**
* Generated file. Do not edit
*/
TapStreamReader.prototype.readApduRequest = function () {
const model = {};
model.header = this.readApduRequestHeader();
model.data = this.readBytes(model.header.lc);
return model;
};
TapStreamReader.prototype.readApduRequestHeader =
function () {
const model = {};
model.cla = this.readUnsigned(1);
model.ins = this.readUnsigned(1);
model.p1 = this.readUnsigned(1);
model.p2 = this.readUnsigned(1);
model.lc = this.readUnsigned(1);
return model;
};
TapStreamReader.prototype.readApduResponse = function () {
const model = {};
model.data = this.readBytes(this.getStreamSize() - 2);
model.status = this.readUnsigned(2);
return model;
};
TapStreamReader.prototype.readTapRequestEncrypted =
function () {
const model = {};
model.header = this.readApduRequestHeader();
model.request = this.readTapRequestFrame();
return model;
};
TapStreamReader.prototype.readTapApduRequest = function () {
const model = {};
return model;
};
TapStreamReader.prototype.readTapEncryptedFrame =
function () {
const model = {};
model.id = this.readUnsigned(2);
model.len = this.readUnsigned(2);
model.payload = this.readBytes(model.len);
model.padding = this.readBytes((16 - ((2 + 2 + (model.len === undefined ? 0 : model.len) + 4) % 16)) %
16);
model.crc = this.readUnsigned(4);
return model;
};
TapStreamReader.prototype.readTapRequestFrame = function () {
const model = {};
model.header = this.readTapRequestFrameHeader();
model.payload = this.readBytes();
return model;
};
TapStreamReader.prototype.readTapRequestFrameHeader =
function () {
const model = {};
model.methodType = this.readUnsigned(1);
model.path = this.readTapRequestFramePath();
return model;
};
TapStreamReader.prototype.readTapRequestFramePath =
function () {
const model = {};
model.objectId = this.readUnsigned(2);
model.objectInstanceId = this.readUnsigned(2);
model.resourceId = this.readUnsigned(2);
return model;
};
TapStreamReader.prototype.readTapResponseFrame = function () {
const model = {};
model.status = this.readUnsigned(1);
model.data = this.readBytes();
return model;
};
// TapStreamWriter.prototype.write(model: ApduRequest) : TapStreamWriter{
// return this.writeApduRequest(model)
// }
TapStreamWriter.prototype.writeApduRequest = function (model) {
this.writeApduRequestHeader(model.header);
this.writeBytes(model.data, model.header.lc);
return this;
};
TapStreamWriter.prototype.writeApduRequestHeader = function (model) {
this.writeUnsigned(model.cla, 1);
this.writeUnsigned(model.ins, 1);
this.writeUnsigned(model.p1, 1);
this.writeUnsigned(model.p2, 1);
this.writeUnsigned(model.lc, 1);
return this;
};
// TapStreamWriter.prototype.write(model: ApduResponse) : TapStreamWriter{
// return this.writeApduResponse(model)
// }
TapStreamWriter.prototype.writeApduResponse = function (model) {
this.writeBytes(model.data, undefined);
this.writeUnsigned(model.status, 2);
return this;
};
// TapStreamWriter.prototype.write(model: TapRequestEncrypted) : TapStreamWriter{
// return this.writeTapRequestEncrypted(model)
// }
TapStreamWriter.prototype.writeTapRequestEncrypted = function (model) {
this.writeApduRequestHeader(model.header);
this.writeTapRequestFrame(model.request);
return this;
};
// TapStreamWriter.prototype.write(model: TapApduRequest) : TapStreamWriter{
// return this.writeTapApduRequest(model)
// }
TapStreamWriter.prototype.writeTapApduRequest = function (model) {
return this;
};
// TapStreamWriter.prototype.write(model: TapEncryptedFrame) : TapStreamWriter{
// return this.writeTapEncryptedFrame(model)
// }
TapStreamWriter.prototype.writeTapEncryptedFrame = function (model) {
this.writeUnsigned(model.id, 2);
this.writeUnsigned(model.len, 2);
this.writeBytes(model.payload, model.len);
this.writeFunction('padding', undefined, model.padding, (16 - ((2 + 2 + (model.len === undefined ? 0 : model.len) + 4) % 16)) % 16);
this.writeFunction('crc32', undefined, model.crc, 4);
return this;
};
// TapStreamWriter.prototype.write(model: TapRequestFrame) : TapStreamWriter{
// return this.writeTapRequestFrame(model)
// }
TapStreamWriter.prototype.writeTapRequestFrame = function (model) {
this.writeTapRequestFrameHeader(model.header);
this.writeBytes(model.payload);
return this;
};
TapStreamWriter.prototype.writeTapRequestFrameHeader = function (model) {
this.writeBitsInt(enumKeyOrValueToNumber(model.methodType, TapRequestFrame.MethodType), 8);
this.writeTapRequestFramePath(model.path);
return this;
};
TapStreamWriter.prototype.writeTapRequestFramePath = function (model) {
this.writeUnsigned(model.objectId, 2);
this.writeUnsigned(model.objectInstanceId, 2);
this.writeUnsigned(model.resourceId, 2);
return this;
};
// TapStreamWriter.prototype.write(model: TapResponseFrame) : TapStreamWriter{
// return this.writeTapResponseFrame(model)
// }
TapStreamWriter.prototype.writeTapResponseFrame = function (model) {
this.writeUnsigned(model.status, 1);
this.writeBytes(model.data);
return this;
};
/**
* Generated file. Do not edit
*/
class ApduRequestConverter {
encode(model, stream = new TapStreamWriter()) {
stream.writeApduRequest(model);
return stream.toBytes;
}
decode(data) {
const stream = data instanceof TapStreamReader ? data : TapStreamReader.create(data);
return stream.readApduRequest();
}
}
class ApduResponseConverter {
encode(model, stream = new TapStreamWriter()) {
stream.writeApduResponse(model);
return stream.toBytes;
}
decode(data) {
const stream = data instanceof TapStreamReader ? data : TapStreamReader.create(data);
return stream.readApduResponse();
}
}
class TapRequestEncryptedConverter {
encode(model, stream = new TapStreamWriter()) {
stream.writeTapRequestEncrypted(model);
return stream.toBytes;
}
decode(data) {
const stream = data instanceof TapStreamReader ? data : TapStreamReader.create(data);
return stream.readTapRequestEncrypted();
}
}
class TapApduRequestConverter$1 {
encode(model, stream = new TapStreamWriter()) {
stream.writeTapApduRequest(model);
return stream.toBytes;
}
decode(data) {
const stream = data instanceof TapStreamReader ? data : TapStreamReader.create(data);
return stream.readTapApduRequest();
}
}
class TapEncryptedFrameConverter {
encode(model, stream = new TapStreamWriter()) {
stream.writeTapEncryptedFrame(model);
return stream.toBytes;
}
decode(data) {
const stream = data instanceof TapStreamReader ? data : TapStreamReader.create(data);
return stream.readTapEncryptedFrame();
}
}
class TapRequestFrameConverter {
encode(model, stream = new TapStreamWriter()) {
stream.writeTapRequestFrame(model);
return stream.toBytes;
}
decode(data) {
const stream = data instanceof TapStreamReader ? data : TapStreamReader.create(data);
return stream.readTapRequestFrame();
}
}
class TapResponseFrameConverter {
encode(model, stream = new TapStreamWriter()) {
stream.writeTapResponseFrame(model);
return stream.toBytes;
}
decode(data) {
const stream = data instanceof TapStreamReader ? data : TapStreamReader.create(data);
return stream.readTapResponseFrame();
}
}
const generatedWriteApduRequestHeader = TapStreamWriter.prototype.writeApduRequestHeader;
TapStreamWriter.prototype.writeApduRequestHeader = function (model) {
if (model.lc > 0xff) {
this.writeUnsigned(model.cla, 1);
this.writeUnsigned(model.ins, 1);
this.writeUnsigned(model.p1, 1);
this.writeUnsigned(model.p2, 1);
this.writeUnsigned(0, 1);
this.writeUnsigned(model.lc, 2);
return this;
}
else {
return generatedWriteApduRequestHeader.apply(this, [model]);
}
};
function createApduFromTapRequest(request) {
const data = new TapRequestFrameConverter().encode(request); // TODO
// TODO check if size > 255 ?
let ins;
switch (request.header.methodType) {
case TapRequestFrame.MethodType.GET:
ins = TapApduRequest.MethodType.GET;
break;
case TapRequestFrame.MethodType.PUT:
case TapRequestFrame.MethodType.POST:
ins = TapApduRequest.MethodType.PUT_OR_POST;
break;
default:
throw new Error(`Internal error: request type "${request.header.methodType}" is invalid`);
}
return {
header: {
cla: TapApduRequest.Default.CLA,
ins,
p1: 0,
p2: 0,
lc: data.length,
},
data,
};
}
class TapRequestFrameBuilder {
// public static fromBytes(bytes: Uint8Array): Header {
// const path = TapStreamReader.fromArray(bytes).readTapRequestPath();
// const header = new Header(path);
// if (bytes.length == 8) {
// // What is that ???
// header.resourceInstance =
// ((bytes[6] << 8) & Header.MAX_ID) + (bytes[7] & 0xff);
// header.resourceInstance &= Header.MAX_ID;
// }
// // return header;
// return header;
// }
static parsePath(path) {
if (!path || path.length === 0) {
throw new Error('Missing LWM2M path, it must not be empty'); // TODO better error
}
path = path.trim();
const result = path.match(TapRequestFrameBuilder.PATH_EXPRESSION);
if (!result || result.length === 0) {
throw new Error(`Invalid LWM2M path "${path}", it does not match expected format`); // TODO better error
}
const header = {
objectId: this.validateId(parseInt(result[1])),
objectInstanceId: result[2] ? parseInt(result[2]) : 0xffff,
resourceId: parseInt(result[3]),
};
if (result[4]) {
header['resourceInstanceId'] = parseInt(result[4], 10);
}
return header;
}
static GET(url, body) {
return TapRequestFrameBuilder.create(TapRequestFrame.MethodType.GET, url, body);
}
static PUT(url, body) {
return TapRequestFrameBuilder.create(TapRequestFrame.MethodType.PUT, url, body);
}
static POST(url, body) {
return TapRequestFrameBuilder.create(TapRequestFrame.MethodType.POST, url, body);
}
static create(method, url, data) {
return {
header: {
path: TapRequestFrameBuilder.parsePath(url),
methodType: method,
},
payload: data || new Uint8Array(),
};
}
static validateId(id) {
if (id < 0 || id > TapRequestFrameBuilder.MAX_ID) {
throw new Error(`Invalid identifier "${id}". It must be a number between ${id} and ${TapRequestFrameBuilder.MAX_ID}`);
}
return id;
}
}
TapRequestFrameBuilder.MAX_ID = 0xffff;
(function (TapRequestFrameBuilder) {
TapRequestFrameBuilder.PATH_EXPRESSION = /^\/?([0-9]+)\/([0-9]+)?\/([0-9]+)\/?([0-9]+)?$/;
})(TapRequestFrameBuilder || (TapRequestFrameBuilder = {}));
class TapRequestHelper {
// static toString(request: TapRequestFrame) {
// return `${TapRequestFrame.MethodType[request.header.methodType]} ${TapRequestHelper.pathToString(request.header.path)}`;
// }
static toString(request, options) {
let result = `${TapRequestFrame.MethodType[request.header.methodType]} ${TapRequestHelper.pathToString(request.header.path, options)}`;
if (request.payload && request.payload.length > 0) {
result += ` 0x${bufferToHexString(request.payload)}`;
}
return result;
}
static pathToString(path, options) {
const resourceInstanceId = path['resourceInstanceId'];
return ('/' +
path.objectId +
'/' +
(path.objectInstanceId === TapRequestFrameBuilder.MAX_ID
? (options === null || options === void 0 ? void 0 : options.printMaxId)
? path.objectInstanceId
: ''
: path.objectInstanceId) +
'/' +
path.resourceId +
(resourceInstanceId ? '/' + resourceInstanceId : ''));
}
}
/**
* Generated file. Do not edit
*/
const ResultCodeTranslation = {
/**
* OK
* decimal: 0
*/
0x0: 'The request was successful',
/**
* CREATED
* decimal: 65
*/
0x41: 'Create request was successful',
/**
* DELETED
* decimal: 66
*/
0x42: 'Delete request was successful',
/**
* CHANGED
* decimal: 68
*/
0x44: 'Update request was successful',
/**
* CONTENT
* decimal: 69
*/
0x45: 'The server has fulfilled the request and sent a response',
/**
* BUSY
* decimal: 70
*/
0x46: 'The server is busy',
/**
* BAD_REQUEST
* decimal: 128
*/
0x80: 'Bad request. Meaning device cannot understand your request.',
/**
* UNAUTHORIZED
* decimal: 129
*/
0x81: 'Current user is not authorized to access this ressource',
/**
* NOT_FOUND
* decimal: 132
*/
0x84: 'The server has not found anything matching the Request',
/**
* METHOD_NOT_ALLOWED
* decimal: 133
*/
0x85: 'Current user is not authorized to access this ressource',
/**
* NOT_ACCEPTABLE
* decimal: 134
*/
0x86: 'Given parameters are not acceptable',
/**
* RESOURCE_LOCKED
* decimal: 135
*/
0x87: 'Given resource is not available with the current TAP configuration.',
/**
* INTERNAL_SERVER_ERROR
* decimal: 160
*/
0xa0: 'Internal server error',
/**
* NOT_IMPLEMENTED
* decimal: 161
*/
0xa1: 'This resource has not been implemented yet',
/**
* SERVICE_UNAVAILABLE
* decimal: 163
*/
0xa3: 'Service is currently unavailable. Try again later.',
/**
* NVM_ERROR
* decimal: 164
*/
0xa4: 'Writing non volatible memory failed',
/**
* UNABLE_TO_CONNECT_TO_TARGET
* decimal: 165
*/
0xa5: 'Tap was not able to connect to the target',
/**
* TARGET_POWER_FAILURE
* decimal: 166
*/
0xa6: 'Target power failure',
/**
* NVM_FULL
* decimal: 167
*/
0xa7: 'Non volatile memory is full',
/**
* DEVICE_UNAVAILABLE
* decimal: 168
*/
0xa8: "It means we can't access this resource because someone is already connected to this device and he has the priority over us.",
/**
* RESPONSE_TOO_LARGE
* decimal: 169
*/
0xa9: 'Response is bigger than you current protocol limit',
/**
* TARGET_PROTOCOL_ERROR
* decimal: 176
*/
0xb0: 'Target protocol error',
/**
* TARGET_PROTOCOL_BUSY
* decimal: 177
*/
0xb1: 'Target protocol is busy',
/**
* TARGET_PROTOCOL_REAL
* decimal: 178
*/
0xb2: 'Target protocol error',
/**
* TARGET_PROTOCOL_WRONG_PARAM
* decimal: 179
*/
0xb3: 'Target protocol parameters are not valid',
/**
* TARGET_PROTOCOL_FORBIDDEN
* decimal: 180
*/
0xb4: 'Target protocol is forbidden',
/**
* TARGET_PROTOCOL_DATA
* decimal: 181
*/
0xb5: 'Target protocol data are not the one expected. For example a CRC error.',
/**
* TARGET_PROTOCOL_COM
* decimal: 182
*/
0xb6: 'Target protocol communication error',
/**
* TARGET_PROTOCOL_LOST_COM
* decimal: 183
*/
0xb7: 'Target protocol lost communication',
/**
* TARGET_PROTOCOL_PARAM
* decimal: 184
*/
0xb8: 'Target protocol invalid parameter',
/**
* TARGET_PROTOCOL_INT
* decimal: 185
*/
0xb9: 'Internal error with target communication',
/**
* TARGET_PROTOCOL_ABORT
* decimal: 186
*/
0xba: 'Operation aborted because one of the command did not return the expected result code.',
/**
* TARGET_PROTOCOL_NOT_IMPLEMENTED
* decimal: 187
*/
0xbb: 'Target protocol is not implemented',
};
class TapClientError extends CodeError {
constructor(code, message, cause) {
super(message, code);
this.cause = cause;
}
static illegalArgument(msg) {
return new TapClientError(TapClientError.Code.IllegalArgumentError, msg);
}
static illegalStateError(msg) {
return new TapClientError(TapClientError.Code.IllegalStateError, msg);
}
static encodeRequestError(command, cause) {
return new TapClientError(TapClientError.Code.EncodeRequestError, 'Cannot encode this request: ' + command + '. Cause: ' + cause, cause);
}
static decodeResponseError(cause, frameOrCommand) {
return new TapClientError(TapClientError.Code.DecodeResponseError, `Cannot decode response to command ${frameOrCommand instanceof Uint8Array
? `0x${bufferToHexString(frameOrCommand)}`
: TapRequestHelper.toString(frameOrCommand)}. Cause: ` + cause, cause);
}
static notConnectedError() {
return new TapClientError(TapClientError.Code.NotConnectedError, 'Trying to execute command but device is not connected');
}
static cannotDecodeResponseError(err, bufferData) {
return new TapClientError(TapClientError.Code.UnexpectedTapResponse, `Received from device an unexpected response that cannot be decoded (${err.message}). Frame: 0x${bufferToHexString(bufferData)}`);
}
static unexpectedApduStatusCode(apduResponse) {
return new TapClientError(TapClientError.Code.UnexpectedApduResponseStatus, `Received from device an unexpected response with an invalid APDU status code 0x${apduResponse.status.toString(16)}
but expecting 0x${ApduResponse.Status.OK.toString(16)}`);
}
static cannotEncodeRequest(err, tapRequest) {
return new TapClientError(TapClientError.Code.CannotEncodeRequest, `Cannot encode request ${tapRequest}: ${err.message}`);
}
}
(function (TapClientError) {
let Code;
(function (Code) {
Code["NotConnectedError"] = "TapClientErrorNotConnected";
Code["EncodeRequestError"] = "TapClientErrorEncodeRequest";
Code["IllegalArgumentError"] = "TapClientErrorIllegalArgument";
Code["IllegalStateError"] = "TapClientErrorIllegalState";
Code["DecodeResponseError"] = "TapClientErrorDecodeResponse";
Code["UnexpectedTapResponse"] = "TapClientErrorUnexpectedTapResponse";
Code["UnexpectedApduResponseStatus"] = "TapClientErrorUnexpectedApduResponseStatus";
Code["CannotEncodeRequest"] = "TapClientErrorCannotEncodeRequest";
Code["TapClientResponseStatusError"] = "TapClientErrorResponseStatus";
})(Code = TapClientError.Code || (TapClientError.Code = {}));
})(TapClientError || (TapClientError = {}));
// export const TapResponseErrorCode = 'TapResponseError';
class TapClientResponseStatusError extends TapClientError {
// public code: string;
constructor(response, request) {
super(TapClientError.Code.TapClientResponseStatusError, TapClientResponseStatusError.createErrorMessage(response, request));
this.response = response;
this.request = request;
}
static createErrorMessage(response, request) {
const codeRet = response.status;
let msg = 'Tap request failed. ';
if (request) {
msg = `Tap request ${TapRequestHelper.toString(request)} failed. `;
}
const errorExplained = codeRet in ResultCodeTranslation
? ResultCodeTranslation[codeRet]
: 'an unknown error';
msg += `${errorExplained} (code=0x${codeRet.toString(16)})`;
return msg;
}
}
class TapApduRequestConverter {
encode(tapRequest) {
try {
const stream = new TapStreamWriter(5 + 1 + tapRequest.payload.length + 1);
stream.writeApduRequest(createApduFromTapRequest(tapRequest));
return stream.toBytes;
}
catch (err) {
if (!(err instanceof TapClientError)) {
throw TapClientError.cannotEncodeRequest(err, tapRequest);
}
else {
throw err;
}
}
}
decode(bufferData) {
const stream = bufferData instanceof TapStreamReader
? bufferData
: TapStreamReader.create(bufferData);
const apduHeader = stream.readApduRequestHeader();
const tapRequestFrame = stream.readTapRequestFrame();
return tapRequestFrame;
}
}
class TapApduResponseConverter {
encode(tapResponseFrame) {
const stream = new TapStreamWriter();
const tapRequestStream = new TapStreamWriter();
tapRequestStream.writeTapResponseFrame(tapResponseFrame);
stream.writeApduResponse({
data: tapRequestStream.toBytes,
status: ApduResponse.Status.OK,
});
return stream.toBytes;
}
decode(bufferData) {
try {
const stream = bufferData instanceof TapStreamReader
? bufferData
: TapStreamReader.create(bufferData);
const apduResponse = stream.readApduResponse();
if (apduResponse.status !== ApduResponse.Status.OK) {
throw TapClientError.unexpectedApduStatusCode(apduResponse);
}
return TapStreamReader.fromArray(apduResponse.data).readTapResponseFrame();
}
catch (err) {
if (!(err instanceof TapClientError)) {
throw TapClientError.cannotDecodeResponseError(err, bufferData instanceof TapStreamReader
? bufferData.toBytes
: bufferData);
}
else {
throw err;
}
}
}
}
const TAP_REQUEST_FRAME_CONVERTER = new TapApduRequestConverter();
const TAP_RESPONSE_FRAME_CONVERTER = new TapApduResponseConverter();
class InterceptorChain {
constructor(target) {
this.target = target;
this.interceptors = [];
}
addInterceptor(interceptor) {
this.interceptors.push(typeof interceptor === 'function'
? {
intercept: interceptor,
}
: interceptor);
return this;
}
execute(context) {
const runner = {
index: 0,
run: (context) => {
if (runner.index < this.interceptors.length) {
runner.index++;
return this.interceptors[runner.index - 1].intercept(context, {
handle: (context) => {
return runner.run(context);
},
});
}
else {
return this.target(context);
}
},
};
return runner.run(context);
}
}
const TAG$1 = 'Client';
class TapClient {
constructor(requestEncoder, responseDecoder) {
this._onProtocolChange = new Subject();
this._commandEncoder = requestEncoder;
this._responseDecoder = responseDecoder;
this.protocols = {};
this._interceptorChain = new InterceptorChain((context) => {
try {
const command = context.request;
// const bodyDecoder = context.bodyDecoder;
return this._command(command);
}
catch (err) {
return throwError(err);
}
});
}
static create(protocol) {
const client = new TapClient(TAP_REQUEST_FRAME_CONVERTER, TAP_RESPONSE_FRAME_CONVERTER);
if (protocol) {
client.addComProtocol(protocol);
}
return client;
}
// public isEncryptionEnabled(): boolean {
// return this._options.encryption;
// }
// public get cryptedFrameConverter() {
// return this._cryptedFrameConverter;
// }
get interceptorChain() {
return this._interceptorChain;
}
addInterceptor(interceptor) {
this.interceptorChain.addInterceptor(interceptor);
return this;
}
// /**
// * Enable/Disable encrytion when communicating with a device
// * @param value true if requests must be encrypted
// * @param algo optional if you have already provided the algo with @{link setEncryptionAlgo(algo)} method
// */
// public enableEncryption(value: boolean, algo?: EncryptionAlgo): Promise<any> {
// if (value) {
// algo = algo || this._encryptionAlgo;
// if (!algo) {
// return Promise.reject(
// TapClientError.illegalArgument(
// 'Illegal argument, encryption algo must be set'
// )
// );
// }
// return this._startEncryption(algo);
// }
// else {
// return this._stopEncryption();
// }
// }
isConnected() {
return (this.hasProtocol(this.currentProtocolId) &&
this.getCurrentProtocol().isConnected());
}
/**
* see {@link getCurrentProtocol}
*/
get protocol() {
return this.getCurrentProtocol();
}
get commandEncoder() {
return this._commandEncoder;
}
get responseDecoder() {
return this._responseDecoder;
}
/**
* Get current protocol or throw an error if no protocol
*/
getCurrentProtocol() {
if (!this.currentProtocolId) {
throw TapClientError.illegalStateError('Tap client protocol is required');
}
if (!this.hasProtocol(this.currentProtocolId)) {
throw TapClientError.illegalArgument('Protocol ' + this.currentProtocolId + ' does not exist');
}
return this.protocols[this.currentProtocolId];
}
/**
* Register/replace a communication protocol with given id
* If no communication protocol is selected yet, it will select it.
* @param newProtocol
* @param id
*/
addComProtocol(newProtocol, id = 'default') {
this.protocols[id] = newProtocol;
if (!this.currentProtocolId) {
this.currentProtocolId = id;
}
return this;
}
/**
* Switch to given communication protocol identifier
* @throws if protocol identifier does not exist
* @param name
*/
switchProtocol(name) {
if (!this.hasProtocol(name)) {
throw TapClientError.illegalArgument('Unkonwn protocol: ' + name);
}
const oldProtocol = this.getCurrentProtocol();
this.currentProtocolId = name;
debug(TAG$1, `Changing protocol to: ${name}`);
this._onProtocolChange.next({
newProtocol: this.getCurrentProtocol(),
oldProtocol,
});
return this;
}
/**
* Change communication protocol
* @throws if protocol identifier does not exist
* @param protocol can either be the communication protocol instance or a string identifier
*/
useComProtocol(protocol) {
let protocolId;
if (typeof protocol == 'string') {
protocolId = protocol;
}
else {
protocolId = protocol.constructor.name;
this.addComProtocol(protocol, protocolId);
}
this.switchProtocol(protocolId);
return this;
}
/**
* Return true if protocol identifier is registered
* @param id
*/
hasProtocol(id) {
return id in this.protocols;
}
connect() {
try {
return this.getCurrentProtocol().connect();
}
catch (err) {
return throwError(err);
}
}
disconnect() {
try {
return this.getCurrentProtocol().disconnect();
}
catch (err) {
return throwError(err);
}
}
/**
* Send request
* @param request
* @param bodyDecoder
*/
request(request) {
return this._interceptorChain.execute({
// bodyDecoder,
request,
client: this,
});
}
/**
* Send raw request
* No interceptors/No encryption will be triggered
* @param dataIn request bytes
* @param bodyDecoder optional body decoder to use
*/
send(dataIn) {
try {
return this.getCurrentProtocol()
.send(dataIn)
.pipe(map((result) => {
// debug(
// TAG,
// 'Received result: ',
// bufferToHexString(result),
// 'DECODER: ',
// bodyDecoder ? bodyDecoder.constructor.name : 'NO'
// );
try {
return this._responseDecoder.decode(result);
}
catch (err) {
throw TapClientError.decodeResponseError(err, dataIn);
}
}));
}
catch (err) {
return throwError(err);
}
}
/**
* Listen to connection state change event
* We have to resubscribe when protocol change..
*/
onConnectionStateChange() {
try {
return this.getCurrentProtocol().onConnectionStateChange();
}
catch (err) {
return throwError(err);
}
}
onProtocolChange() {
try {
return this._onProtocolChange.asObservable();
}
catch (err) {
return throwError(err);
}
}
_command(command) {
try {
if (!this.isConnected()) {
return throwError(TapClientError.notConnectedError());
}
let dataIn;
try {
dataIn = this._commandEncoder.encode(command);
}
catch (e) {
return throwError(TapClientError.encodeRequestError(command, e));
}
debug(TAG$1, `Sending ${TapRequestHelper.toString(command)} (${dataIn.length} bytes 0x${bufferToHexString(dataIn)})`);
return this.getCurrentProtocol()
.send(dataIn)
.pipe(map((result) => {
// debug(
// TAG,
// 'Received result: ',
// bufferToHexString(result),
// 'DECODER: ',
// bodyDecoder ? bodyDecoder.constructor.name : 'NO'
// );
try {
const response = this._responseDecoder.decode(result);
// if (bodyDecoder) {
// response.setBodyDecoder(bodyDecoder);
// }
return response;
}
catch (err) {
throw TapClientError.decodeResponseError(err, command);
}
}));
}
catch (err) {
return throwError(err);
}
}
}
class TapResponseFrameBuilder {
static SUCCESS(data) {
return TapResponseFrameBuilder.create(ResultCode.OK, data);
}
static ERROR(status = ResultCode.BAD_REQUEST, data) {
return TapResponseFrameBuilder.create(status, data);
}
static create(status, data) {
return {
status,
data: data || new Uint8Array(),
};
}
}
function tapResponseStatusToString(status) {
return ResultCodeTranslation[status]
? ResultCodeTranslation[status]
: 'Unknown tap response status';
}
class ArrayConverter {
constructor(_itemConverter, options) {
this._itemConverter = _itemConverter;
this.options = options;
}
get itemConverter() {
return this._itemConverter;
}
decode(bytes) {
let items = [];
let sizeOfItem = this.options.sizeOfItem;
for (let offset = 0; offset < bytes.length; offset += sizeOfItem) {
let slice = bytes.subarray(offset, offset + sizeOfItem);
let item = this._itemConverter.decode(slice);
items.push(item);
}
return items;
}
encode(items) {
let result = new Uint8Array(this.options.sizeOfItem * items.length);
let offset = 0;
for (let item of items) {
let encodedItem = this._itemConverter.encode(item); // TODO
if (encodedItem.length > this.options.sizeOfItem) {
throw new Error(`Encoded item size overflow: ${encodedItem.length} (max is ${this.options.sizeOfItem})`);
}
result.set(encodedItem, offset);
offset += this.options.sizeOfItem;
}
return result;
}
}
class BooleanConverter {
constructor(_mask = 0x1) {
this._mask = _mask;
}
decode(data) {
var value = NumberConverter.fromBytes(data, data.length, false, BooleanConverter.IS_LEAST_SIGNIFICANT_BIT_FIRST);
return BooleanConverter.decodeFromNumber(this._mask, value);
}
encode(value) {
return Uint8Array.from([value ? this._mask : 0]);
}
static decodeFromNumber(mask, value) {
return (mask == 0 && value == 0) || (value & mask) != 0;
}
static instanceBit0() {
if (!BooleanConverter._instance) {
BooleanConverter._instance = new BooleanConverter(0b1);
}
return BooleanConverter._instance;
}
}
BooleanConverter.IS_LEAST_SIGNIFICANT_BIT_FIRST = true;
BooleanConverter._instance = undefined;
const BYTE_SWAP_ORDER_REGEX = /^(B[0-9]+)(_B[0-9]+)*$/;
class ByteSwapConverter {
constructor(order) {
this.order = order;
}
encode(input) {
return swapBytes(input, this.order);
}
decode(input) {
return this.encode(input);
}
}
/**
* Swap byte position
* B0 represents the first byte from the right
* BN represents the N byte from the right
*
* This function is symetric
*
* Example:
* ```typescript
* const input = Uint8Array.from([1,2,3,4]);
* expect(swapBytes(input, 'B2_B3_B0_B1')).to.be.deep.eq(Uint8Array.from([2,1,4,3]));
* ```
*
* @param input array
* @param order string that defined how to swap bytes
* @return a new array with byte swap
*/
function swapBytes(input, order) {
if (!order.match(BYTE_SWAP_ORDER_REGEX)) {
throw new Error(`swatpBytes error, invalid byte order "${order}". Should match regex ${BYTE_SWAP_ORDER_REGEX}`);
}
const parts = order.split('_');
// // C