import { TapRequestFrame, TapApduRequest, ApduResponse, ResultCode } from '@iotize/tap/client/api';
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 { Subject, throwError } from 'rxjs';
import { map } from 'rxjs/operators';
import { Buffer } from 'buffer';
import { TLV } from '@iotize/common/tlv';

var prefix = '@iotize/tap/client/impl';
var debug = createDebugger(prefix);

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var ConverterError = /** @class */ (function (_super) {
    __extends(ConverterError, _super);
    function ConverterError(code, msg, cause) {
        var _this = _super.call(this, msg, code) || this;
        _this.cause = cause;
        return _this;
    }
    ConverterError.unexpectedBufferSize = function (expected, buffer) {
        return new ConverterError(ConverterError.Code.UnexpectedBufferSizeError, 'Expected buffer of ' +
            expected +
            ' byte(s) but found only ' +
            buffer.length +
            ' byte(s)');
    };
    ConverterError.nullBufferError = function (msg) {
        return new ConverterError(ConverterError.Code.NullBufferError, msg);
    };
    return ConverterError;
}(CodeError));
(function (ConverterError) {
    var Code;
    (function (Code) {
        Code["UnexpectedBufferSizeError"] = "UnexpectedBufferSizeError";
        Code["NullBufferError"] = "NullBufferError";
    })(Code = ConverterError.Code || (ConverterError.Code = {}));
})(ConverterError || (ConverterError = {}));

var NumberConverter = /** @class */ (function () {
    function NumberConverter(options) {
        this.options = options;
    }
    /**
     * Number as unsigned 8 bits converter
     * @param lsb true for least significant bit first
     */
    NumberConverter.uint8 = function (lsb) {
        if (lsb === void 0) { 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
     */
    NumberConverter.uint16 = function (lsb) {
        if (lsb === void 0) { 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
     */
    NumberConverter.uint32 = function (lsb) {
        if (lsb === void 0) { 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
     */
    NumberConverter.int8 = function (lsb) {
        if (lsb === void 0) { 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
     */
    NumberConverter.int16 = function (lsb) {
        if (lsb === void 0) { 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
     */
    NumberConverter.int32 = function (lsb) {
        if (lsb === void 0) { lsb = false; }
        return new NumberConverter({
            signed: true,
            sizeOf: 32,
            leastSignificantBitFirst: lsb
        });
    };
    NumberConverter.prototype.decode = function (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);
    };
    NumberConverter.prototype.encode = function (value) {
        var result = new Uint8Array(this.options.sizeOf >> 3);
        for (var i = 0; i < result.length; i++) {
            var decalage = (result.length - i - 1) * 8;
            result[i] = (value >> decalage) & 0xff;
        }
        if (this.options.leastSignificantBitFirst) {
            result.reverse();
        }
        return result;
    };
    NumberConverter.toOpaqueMsb = function (input, sizeOf) {
        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");
        }
    };
    NumberConverter.fromOpaqueMsb = function (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
     */
    NumberConverter.fromBytes = function (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;
    };
    return NumberConverter;
}());

var _mpeg2Instance;
var CRC = /** @class */ (function () {
    function CRC(polynomial) {
        this.polynomial = polynomial;
    }
    CRC.mpeg2 = function () {
        if (!_mpeg2Instance) {
            _mpeg2Instance = new CRC(CRC.POLYNOMIAL_CRC32_MPEG2);
        }
        return _mpeg2Instance;
    };
    CRC.prototype.compute = function (buffer, firstValue) {
        if (firstValue === void 0) { firstValue = CRC.FIRST_VALUE; }
        if (buffer instanceof Uint32Array) {
            return CRC.fromWord(buffer, firstValue, this.polynomial);
        }
        else {
            return CRC.fromBytes(buffer, firstValue, this.polynomial);
        }
    };
    CRC.fromBytes = function (data, crc, polynomial) {
        if (crc === void 0) { crc = CRC.FIRST_VALUE; }
        if (polynomial === void 0) { 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);
        }
        var uint32Data = new Uint32Array(data.length / 4);
        for (var 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.
    CRC.fromWord = function (buffer, crc, polynomial) {
        if (crc === void 0) { crc = CRC.FIRST_VALUE; }
        if (polynomial === void 0) { 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');
        }
        var myword;
        for (var i = 0; i < buffer.length; i++) {
            myword = buffer[i]; // Get next word.
            crc = CRC.appendCrcNumber(crc, myword, polynomial);
        }
        return crc;
    };
    CRC.appendCrcNumber = function (crc, data, polynomial) {
        crc = crc ^ data;
        for (var 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;
    return CRC;
}());

var __extends$1 = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
function enumKeyOrValueToNumber(key, mapping) {
    if (typeof key === 'string') {
        // if (!(key in mapping)) {
        //     throw new Error(``)
        // }
        return mapping[key];
    }
    else {
        return key;
    }
}
var TapStreamReader = /** @class */ (function (_super) {
    __extends$1(TapStreamReader, _super);
    function TapStreamReader() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    TapStreamReader.fromArray = function (array) {
        var buffer = typedArrayToBuffer(array);
        return new TapStreamReader(buffer);
    };
    TapStreamReader.create = function (packet) {
        return packet instanceof Uint8Array
            ? TapStreamReader.fromArray(packet)
            : packet;
    };
    TapStreamReader.prototype.readStr = function (n) {
        return String.fromCharCode.apply(null, this.readBytes(n));
    };
    /**
     * Read given byte size as string
     * @param n number of byte to read
     */
    TapStreamReader.prototype.readString = function (n) {
        if (n === void 0) { n = this.byteLeft; }
        return this.readStr(n);
    };
    /**
     * Read string until terminator character is reached
     */
    TapStreamReader.prototype.readStringTerminator = function (terminator) {
        var result = '';
        while (true) {
            var lastByte = this.readU1();
            var lastChar = String.fromCharCode(lastByte);
            if (lastChar !== terminator) {
                result += lastChar;
            }
            else {
                break;
            }
        }
        return result;
    };
    TapStreamReader.prototype.readU2 = function () {
        return this._isBigEndian ? this.readU2be() : this.readU2le();
    };
    TapStreamReader.prototype.readU4 = function () {
        return this._isBigEndian ? this.readU4be() : this.readU4le();
    };
    TapStreamReader.prototype.readF4 = function () {
        return this._isBigEndian ? this.readF4be() : this.readF4le();
    };
    TapStreamReader.prototype.subStream = function (length) {
        return TapStreamReader.fromArray(this.readBytes(length));
    };
    return TapStreamReader;
}(KaitaiStreamReader));
var TapStreamWriter = /** @class */ (function (_super) {
    __extends$1(TapStreamWriter, _super);
    function TapStreamWriter(lengthOrBuffer, byteOffset, isBigEndian) {
        if (lengthOrBuffer === void 0) { lengthOrBuffer = 512; }
        if (isBigEndian === void 0) { isBigEndian = true; }
        var _this = this;
        if (typeof lengthOrBuffer === 'number') {
            lengthOrBuffer = new ArrayBuffer(lengthOrBuffer);
        }
        _this = _super.call(this, lengthOrBuffer, byteOffset, isBigEndian) || this;
        return _this;
    }
    TapStreamWriter.create = function (size) {
        return new TapStreamWriter(new ArrayBuffer(size));
    };
    TapStreamWriter.prototype.writeFunction = function (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);
        }
    };
    TapStreamWriter.prototype.addPadding = function (padSize) {
        this.writeBytes(new Uint8Array(padSize));
        return this;
    };
    TapStreamWriter.prototype.addCRC = function (options) {
        var buffer = this.toBytes;
        if (options && options.offset) {
            buffer = buffer.slice(options.offset);
        }
        var 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;
    };
    TapStreamWriter.prototype.writeUnsigned = function (value, length) {
        this.writeU(value, length);
        return this;
    };
    TapStreamWriter.prototype.writeU4 = function (value) {
        this.writeU(value, 4);
        return this;
    };
    TapStreamWriter.prototype.writeU2 = function (value) {
        this.writeU(value, 2);
        return this;
    };
    TapStreamWriter.prototype.writeU1 = function (value) {
        this.writeU(value, 1);
        return this;
    };
    TapStreamWriter.prototype.writeF4 = function (value) {
        this.writeF(value, 4);
        return this;
    };
    TapStreamWriter.prototype.writeFloat = function (value, length) {
        this.writeF(value, length);
        return this;
    };
    TapStreamWriter.prototype.writeSigned = function (value, length) {
        this.writeSigned(value, length);
        return this;
    };
    TapStreamWriter.prototype.writeString = function (value, length) {
        if (length === void 0) { length = (value === null || value === void 0 ? void 0 : value.length) || 0; }
        return this.writeStr(value || '', length);
    };
    TapStreamWriter.prototype.writeStr = function (value, length) {
        if (length === void 0) { length = value.length; }
        var i = 1;
        var stringWriteLength = Math.min(value.length, length);
        for (; i <= stringWriteLength; i++) {
            this.writeByte(value.charCodeAt(i - 1));
        }
        var remaining = length - stringWriteLength;
        if (remaining > 0) {
            this.writeBytes(new Uint8Array(remaining));
        }
        return this;
    };
    TapStreamWriter.prototype.writeBitsInt = function (value, n) {
        this.writeB(value, n);
        return this;
    };
    TapStreamWriter.prototype.writeBits = function (value, n) {
        this.writeB(value, n);
        return this;
    };
    TapStreamWriter.prototype.writeBoolean = function (value, n) {
        this.writeBits(value ? 1 : 0, n);
        return this;
    };
    return TapStreamWriter;
}(KaitaiStreamWriter));

/**
 *   Generated file. Do not edit
 */
TapStreamReader.prototype.readApduRequest = function () {
    var model = {};
    model.header = this.readApduRequestHeader();
    model.data = this.readBytes(model.header.lc);
    return model;
};
TapStreamReader.prototype.readApduRequestHeader = function () {
    var 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 () {
    var model = {};
    model.data = this.readBytes(this.getStreamSize() - 2);
    model.status = this.readUnsigned(2);
    return model;
};
TapStreamReader.prototype.readTapRequestEncrypted = function () {
    var model = {};
    model.header = this.readApduRequestHeader();
    model.request = this.readTapRequestFrame();
    return model;
};
TapStreamReader.prototype.readTapApduRequest = function () {
    var model = {};
    return model;
};
TapStreamReader.prototype.readTapEncryptedFrame = function () {
    var 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 () {
    var model = {};
    model.header = this.readTapRequestFrameHeader();
    model.payload = this.readBytes();
    return model;
};
TapStreamReader.prototype.readTapRequestFrameHeader = function () {
    var model = {};
    model.methodType = this.readUnsigned(1);
    model.path = this.readTapRequestFramePath();
    return model;
};
TapStreamReader.prototype.readTapRequestFramePath = function () {
    var model = {};
    model.objectId = this.readUnsigned(2);
    model.objectInstanceId = this.readUnsigned(2);
    model.resourceId = this.readUnsigned(2);
    return model;
};
TapStreamReader.prototype.readTapResponseFrame = function () {
    var 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
 */
var ApduRequestConverter = /** @class */ (function () {
    function ApduRequestConverter() {
    }
    ApduRequestConverter.prototype.encode = function (model, stream) {
        if (stream === void 0) { stream = new TapStreamWriter(); }
        stream.writeApduRequest(model);
        return stream.toBytes;
    };
    ApduRequestConverter.prototype.decode = function (data) {
        var stream = data instanceof TapStreamReader ? data : TapStreamReader.create(data);
        return stream.readApduRequest();
    };
    return ApduRequestConverter;
}());
var ApduResponseConverter = /** @class */ (function () {
    function ApduResponseConverter() {
    }
    ApduResponseConverter.prototype.encode = function (model, stream) {
        if (stream === void 0) { stream = new TapStreamWriter(); }
        stream.writeApduResponse(model);
        return stream.toBytes;
    };
    ApduResponseConverter.prototype.decode = function (data) {
        var stream = data instanceof TapStreamReader ? data : TapStreamReader.create(data);
        return stream.readApduResponse();
    };
    return ApduResponseConverter;
}());
var TapRequestEncryptedConverter = /** @class */ (function () {
    function TapRequestEncryptedConverter() {
    }
    TapRequestEncryptedConverter.prototype.encode = function (model, stream) {
        if (stream === void 0) { stream = new TapStreamWriter(); }
        stream.writeTapRequestEncrypted(model);
        return stream.toBytes;
    };
    TapRequestEncryptedConverter.prototype.decode = function (data) {
        var stream = data instanceof TapStreamReader ? data : TapStreamReader.create(data);
        return stream.readTapRequestEncrypted();
    };
    return TapRequestEncryptedConverter;
}());
var TapApduRequestConverter = /** @class */ (function () {
    function TapApduRequestConverter() {
    }
    TapApduRequestConverter.prototype.encode = function (model, stream) {
        if (stream === void 0) { stream = new TapStreamWriter(); }
        stream.writeTapApduRequest(model);
        return stream.toBytes;
    };
    TapApduRequestConverter.prototype.decode = function (data) {
        var stream = data instanceof TapStreamReader ? data : TapStreamReader.create(data);
        return stream.readTapApduRequest();
    };
    return TapApduRequestConverter;
}());
var TapEncryptedFrameConverter = /** @class */ (function () {
    function TapEncryptedFrameConverter() {
    }
    TapEncryptedFrameConverter.prototype.encode = function (model, stream) {
        if (stream === void 0) { stream = new TapStreamWriter(); }
        stream.writeTapEncryptedFrame(model);
        return stream.toBytes;
    };
    TapEncryptedFrameConverter.prototype.decode = function (data) {
        var stream = data instanceof TapStreamReader ? data : TapStreamReader.create(data);
        return stream.readTapEncryptedFrame();
    };
    return TapEncryptedFrameConverter;
}());
var TapRequestFrameConverter = /** @class */ (function () {
    function TapRequestFrameConverter() {
    }
    TapRequestFrameConverter.prototype.encode = function (model, stream) {
        if (stream === void 0) { stream = new TapStreamWriter(); }
        stream.writeTapRequestFrame(model);
        return stream.toBytes;
    };
    TapRequestFrameConverter.prototype.decode = function (data) {
        var stream = data instanceof TapStreamReader ? data : TapStreamReader.create(data);
        return stream.readTapRequestFrame();
    };
    return TapRequestFrameConverter;
}());
var TapResponseFrameConverter = /** @class */ (function () {
    function TapResponseFrameConverter() {
    }
    TapResponseFrameConverter.prototype.encode = function (model, stream) {
        if (stream === void 0) { stream = new TapStreamWriter(); }
        stream.writeTapResponseFrame(model);
        return stream.toBytes;
    };
    TapResponseFrameConverter.prototype.decode = function (data) {
        var stream = data instanceof TapStreamReader ? data : TapStreamReader.create(data);
        return stream.readTapResponseFrame();
    };
    return TapResponseFrameConverter;
}());

function createApduFromTapRequest(request) {
    var data = new TapRequestFrameConverter().encode(request); // TODO 
    // TODO check if size > 255 ?
    var 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: ins,
            p1: 0,
            p2: 0,
            lc: data.length
        },
        data: data
    };
}

var TapRequestFrameBuilder = /** @class */ (function () {
    function 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;
    // }
    TapRequestFrameBuilder.parsePath = function (path) {
        if (!path || path.length === 0) {
            throw new Error('Missing LWM2M path, it must not be empty'); // TODO better error
        }
        path = path.trim();
        var 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
        }
        var 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;
    };
    TapRequestFrameBuilder.GET = function (url, body) {
        return TapRequestFrameBuilder.create(TapRequestFrame.MethodType.GET, url, body);
    };
    TapRequestFrameBuilder.PUT = function (url, body) {
        return TapRequestFrameBuilder.create(TapRequestFrame.MethodType.PUT, url, body);
    };
    TapRequestFrameBuilder.POST = function (url, body) {
        return TapRequestFrameBuilder.create(TapRequestFrame.MethodType.POST, url, body);
    };
    TapRequestFrameBuilder.create = function (method, url, data) {
        return {
            header: {
                path: TapRequestFrameBuilder.parsePath(url),
                methodType: method
            },
            payload: data || new Uint8Array()
        };
    };
    TapRequestFrameBuilder.validateId = function (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;
    return TapRequestFrameBuilder;
}());
(function (TapRequestFrameBuilder) {
    TapRequestFrameBuilder.PATH_EXPRESSION = /^\/?([0-9]+)\/([0-9]+)?\/([0-9]+)\/?([0-9]+)?$/;
})(TapRequestFrameBuilder || (TapRequestFrameBuilder = {}));

var TapRequestHelper = /** @class */ (function () {
    function TapRequestHelper() {
    }
    // static toString(request: TapRequestFrame) {
    //     return `${TapRequestFrame.MethodType[request.header.methodType]} ${TapRequestHelper.pathToString(request.header.path)}`;
    // }
    TapRequestHelper.toString = function (request, options) {
        var 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;
    };
    TapRequestHelper.pathToString = function (path, options) {
        var 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 : ''));
    };
    return TapRequestHelper;
}());

/**
 *   Generated file. Do not edit
 */
var 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.",
    /**
     * 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"
};

var __extends$2 = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var TapClientError = /** @class */ (function (_super) {
    __extends$2(TapClientError, _super);
    function TapClientError(code, message, cause) {
        var _this = _super.call(this, message, code) || this;
        _this.cause = cause;
        return _this;
    }
    TapClientError.illegalArgument = function (msg) {
        return new TapClientError(TapClientError.Code.IllegalArgumentError, msg);
    };
    TapClientError.illegalStateError = function (msg) {
        return new TapClientError(TapClientError.Code.IllegalStateError, msg);
    };
    TapClientError.encodeRequestError = function (command, cause) {
        return new TapClientError(TapClientError.Code.EncodeRequestError, 'Cannot encode this request: ' + command + '. Cause: ' + cause, cause);
    };
    TapClientError.decodeResponseError = function (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);
    };
    TapClientError.notConnectedError = function () {
        return new TapClientError(TapClientError.Code.NotConnectedError, 'Trying to execute command but device is not connected');
    };
    TapClientError.cannotDecodeResponseError = function (err, bufferData) {
        return new TapClientError(TapClientError.Code.UnexpectedTapResponse, "Received from device an unexpected response that cannot be decoded (" + err.message + "). Frame: 0x" + bufferToHexString(bufferData));
    };
    TapClientError.unexpectedApduStatusCode = function (apduResponse) {
        return new TapClientError(TapClientError.Code.UnexpectedApduResponseStatus, "Received from device an unexpected response with an invalid APDU status code 0x" + apduResponse.status.toString(16) + "\n            but expecting 0x" + ApduResponse.Status.OK.toString(16));
    };
    TapClientError.cannotEncodeRequest = function (err, tapRequest) {
        return new TapClientError(TapClientError.Code.CannotEncodeRequest, "Cannot encode request " + tapRequest + ": " + err.message);
    };
    return TapClientError;
}(CodeError));
(function (TapClientError) {
    var 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';
var TapClientResponseStatusError = /** @class */ (function (_super) {
    __extends$2(TapClientResponseStatusError, _super);
    // public code: string;
    function TapClientResponseStatusError(response, request) {
        var _this = _super.call(this, TapClientError.Code.TapClientResponseStatusError, TapClientResponseStatusError.createErrorMessage(response, request)) || this;
        _this.response = response;
        _this.request = request;
        return _this;
    }
    TapClientResponseStatusError.createErrorMessage = function (response, request) {
        var codeRet = response.status;
        var msg = 'Tap request failed. ';
        if (request) {
            msg = "Tap request " + TapRequestHelper.toString(request) + " failed. ";
        }
        var errorExplained = codeRet in ResultCodeTranslation
            ? ResultCodeTranslation[codeRet]
            : 'an unknown error';
        msg += errorExplained + " (code=0x" + codeRet.toString(16) + ")";
        return msg;
    };
    return TapClientResponseStatusError;
}(TapClientError));

var TapApduRequestConverter$1 = /** @class */ (function () {
    function TapApduRequestConverter() {
    }
    TapApduRequestConverter.prototype.encode = function (tapRequest) {
        try {
            var 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;
            }
        }
    };
    TapApduRequestConverter.prototype.decode = function (bufferData) {
        var stream = bufferData instanceof TapStreamReader ? bufferData : TapStreamReader.create(bufferData);
        var apduHeader = stream.readApduRequestHeader();
        var tapRequestFrame = stream.readTapRequestFrame();
        return tapRequestFrame;
    };
    return TapApduRequestConverter;
}());
var TapApduResponseConverter = /** @class */ (function () {
    function TapApduResponseConverter() {
    }
    TapApduResponseConverter.prototype.encode = function (tapResponseFrame) {
        var stream = new TapStreamWriter();
        var tapRequestStream = new TapStreamWriter();
        tapRequestStream.writeTapResponseFrame(tapResponseFrame);
        stream.writeApduResponse({
            data: tapRequestStream.toBytes,
            status: ApduResponse.Status.OK,
        });
        return stream.toBytes;
    };
    TapApduResponseConverter.prototype.decode = function (bufferData) {
        try {
            var stream = bufferData instanceof TapStreamReader ? bufferData : TapStreamReader.create(bufferData);
            var 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;
            }
        }
    };
    return TapApduResponseConverter;
}());
var TAP_REQUEST_FRAME_CONVERTER = new TapApduRequestConverter$1();
var TAP_RESPONSE_FRAME_CONVERTER = new TapApduResponseConverter();

var InterceptorChain = /** @class */ (function () {
    function InterceptorChain(target) {
        this.target = target;
        this.interceptors = [];
    }
    InterceptorChain.prototype.addInterceptor = function (interceptor) {
        this.interceptors.push(typeof interceptor === 'function'
            ? {
                intercept: interceptor
            }
            : interceptor);
        return this;
    };
    InterceptorChain.prototype.execute = function (context) {
        var _this = this;
        var runner = {
            index: 0,
            run: function (context) {
                if (runner.index < _this.interceptors.length) {
                    runner.index++;
                    return _this.interceptors[runner.index - 1].intercept(context, {
                        handle: function (context) {
                            return runner.run(context);
                        }
                    });
                }
                else {
                    return _this.target(context);
                }
            }
        };
        return runner.run(context);
    };
    return InterceptorChain;
}());

var TAG = 'Client';
var TapClient = /** @class */ (function () {
    function TapClient(requestEncoder, responseDecoder) {
        var _this = this;
        this._onProtocolChange = new Subject();
        this._commandEncoder = requestEncoder;
        this._responseDecoder = responseDecoder;
        this.protocols = {};
        this._interceptorChain = new InterceptorChain(function (context) {
            try {
                var command = context.request;
                // const bodyDecoder = context.bodyDecoder;
                return _this._command(command);
            }
            catch (err) {
                return throwError(err);
            }
        });
    }
    TapClient.create = function (protocol) {
        var client = new TapClient(TAP_REQUEST_FRAME_CONVERTER, TAP_RESPONSE_FRAME_CONVERTER);
        if (protocol) {
            client.addComProtocol(protocol);
        }
        return client;
    };
    Object.defineProperty(TapClient.prototype, "interceptorChain", {
        // public isEncryptionEnabled(): boolean {
        //     return this._options.encryption;
        // }
        // public get cryptedFrameConverter() {
        //     return this._cryptedFrameConverter;
        // }
        get: function () {
            return this._interceptorChain;
        },
        enumerable: true,
        configurable: true
    });
    TapClient.prototype.addInterceptor = function (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();
    //     }
    // }
    TapClient.prototype.isConnected = function () {
        return (this.hasProtocol(this.currentProtocolId) &&
            this.getCurrentProtocol().isConnected());
    };
    Object.defineProperty(TapClient.prototype, "protocol", {
        /**
         * see {@link getCurrentProtocol}
         */
        get: function () {
            return this.getCurrentProtocol();
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(TapClient.prototype, "commandEncoder", {
        get: function () {
            return this._commandEncoder;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(TapClient.prototype, "responseDecoder", {
        get: function () {
            return this._responseDecoder;
        },
        enumerable: true,
        configurable: true
    });
    /**
     * Get current protocol or throw an error if no protocol
     */
    TapClient.prototype.getCurrentProtocol = function () {
        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
     */
    TapClient.prototype.addComProtocol = function (newProtocol, id) {
        if (id === void 0) { 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
     */
    TapClient.prototype.switchProtocol = function (name) {
        if (!this.hasProtocol(name)) {
            throw TapClientError.illegalArgument('Unkonwn protocol: ' + name);
        }
        var oldProtocol = this.getCurrentProtocol();
        this.currentProtocolId = name;
        debug(TAG, "Changing protocol to: " + name);
        this._onProtocolChange.next({
            newProtocol: this.getCurrentProtocol(),
            oldProtocol: 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
     */
    TapClient.prototype.useComProtocol = function (protocol) {
        var 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
     */
    TapClient.prototype.hasProtocol = function (id) {
        return id in this.protocols;
    };
    TapClient.prototype.connect = function () {
        try {
            return this.getCurrentProtocol().connect();
        }
        catch (err) {
            return throwError(err);
        }
    };
    TapClient.prototype.disconnect = function () {
        try {
            return this.getCurrentProtocol().disconnect();
        }
        catch (err) {
            return throwError(err);
        }
    };
    /**
     * Send request
     * @param request
     * @param bodyDecoder
     */
    TapClient.prototype.request = function (request) {
        return this._interceptorChain.execute({
            // bodyDecoder,
            request: request,
            client: this
        });
    };
    /**
     * Send raw request
     * No interceptors/No encryption will be triggered
     * @param dataIn request bytes
     * @param bodyDecoder optional body decoder to use
     */
    TapClient.prototype.send = function (dataIn) {
        var _this = this;
        try {
            return this.getCurrentProtocol()
                .send(dataIn)
                .pipe(map(function (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..
     */
    TapClient.prototype.onConnectionStateChange = function () {
        try {
            return this.getCurrentProtocol().onConnectionStateChange();
        }
        catch (err) {
            return throwError(err);
        }
    };
    TapClient.prototype.onProtocolChange = function () {
        try {
            return this._onProtocolChange.asObservable();
        }
        catch (err) {
            return throwError(err);
        }
    };
    TapClient.prototype._command = function (command
    // bodyDecoder?: BodyDecoder<T>
    ) {
        var _this = this;
        try {
            if (!this.isConnected()) {
                return throwError(TapClientError.notConnectedError());
            }
            var dataIn = void 0;
            try {
                dataIn = this._commandEncoder.encode(command);
            }
            catch (e) {
                return throwError(TapClientError.encodeRequestError(command, e));
            }
            debug(TAG, "Sending " + dataIn.length + " bytes 0x" + bufferToHexString(dataIn));
            return this.getCurrentProtocol()
                .send(dataIn)
                .pipe(map(function (result) {
                // debug(
                //     TAG,
                //     'Received result: ',
                //     bufferToHexString(result),
                //     'DECODER: ',
                //     bodyDecoder ? bodyDecoder.constructor.name : 'NO'
                // );
                try {
                    var response = _this._responseDecoder.decode(result);
                    // if (bodyDecoder) {
                    //     response.setBodyDecoder(bodyDecoder);
                    // }
                    return response;
                }
                catch (err) {
                    throw TapClientError.decodeResponseError(err, command);
                }
            }));
        }
        catch (err) {
            return throwError(err);
        }
    };
    return TapClient;
}());

var TapResponseFrameBuilder = /** @class */ (function () {
    function TapResponseFrameBuilder() {
    }
    TapResponseFrameBuilder.SUCCESS = function (data) {
        return TapResponseFrameBuilder.create(ResultCode.OK, data);
    };
    TapResponseFrameBuilder.ERROR = function (status, data) {
        if (status === void 0) { status = ResultCode.BAD_REQUEST; }
        return TapResponseFrameBuilder.create(status, data);
    };
    TapResponseFrameBuilder.create = function (status, data) {
        return {
            status: status,
            data: data || new Uint8Array()
        };
    };
    return TapResponseFrameBuilder;
}());

function tapResponseStatusToString(status) {
    return ResultCodeTranslation[status] ? ResultCodeTranslation[status] : 'Unknown tap response status';
}

var __values = (this && this.__values) || function(o) {
    var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
    if (m) return m.call(o);
    if (o && typeof o.length === "number") return {
        next: function () {
            if (o && i >= o.length) o = void 0;
            return { value: o && o[i++], done: !o };
        }
    };
    throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var ArrayConverter = /** @class */ (function () {
    function ArrayConverter(_itemConverter, options) {
        this._itemConverter = _itemConverter;
        this.options = options;
    }
    ArrayConverter.prototype.decode = function (bytes) {
        var items = [];
        var sizeOfItem = this.options.sizeOfItem;
        for (var offset = 0; offset < bytes.length; offset += sizeOfItem) {
            var slice = bytes.subarray(offset, offset + sizeOfItem);
            var item = this._itemConverter.decode(slice);
            items.push(item);
        }
        return items;
    };
    ArrayConverter.prototype.encode = function (items) {
        var e_1, _a;
        var result = new Uint8Array(this.options.sizeOfItem * items.length);
        var offset = 0;
        try {
            for (var items_1 = __values(items), items_1_1 = items_1.next(); !items_1_1.done; items_1_1 = items_1.next()) {
                var item = items_1_1.value;
                var 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;
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (items_1_1 && !items_1_1.done && (_a = items_1.return)) _a.call(items_1);
            }
            finally { if (e_1) throw e_1.error; }
        }
        return result;
    };
    return ArrayConverter;
}());

var BooleanConverter = /** @class */ (function () {
    function BooleanConverter(_mask) {
        if (_mask === void 0) { _mask = 0x1; }
        this._mask = _mask;
    }
    BooleanConverter.prototype.decode = function (data) {
        var value = NumberConverter.fromBytes(data, data.length, false, BooleanConverter.IS_LEAST_SIGNIFICANT_BIT_FIRST);
        return BooleanConverter.decodeFromNumber(this._mask, value);
    };
    BooleanConverter.prototype.encode = function (value) {
        return Uint8Array.from([value ? this._mask : ~this._mask]);
    };
    BooleanConverter.decodeFromNumber = function (mask, value) {
        return (mask == 0 && value == 0) || (value & mask) != 0;
    };
    BooleanConverter.instanceBit0 = function () {
        if (!BooleanConverter._instance) {
            BooleanConverter._instance = new BooleanConverter(1);
        }
        return BooleanConverter._instance;
    };
    BooleanConverter.IS_LEAST_SIGNIFICANT_BIT_FIRST = true;
    BooleanConverter._instance = undefined;
    return BooleanConverter;
}());

var BYTE_SWAP_ORDER_REGEX = /^(B[0-9]+)(_B[0-9]+)*$/;
var ByteSwapConverter = /** @class */ (function () {
    function ByteSwapConverter(order) {
        this.order = order;
    }
    ByteSwapConverter.prototype.encode = function (input) {
        return swapBytes(input, this.order);
    };
    ByteSwapConverter.prototype.decode = function (input) {
        return this.encode(input);
    };
    return ByteSwapConverter;
}());
/**
 * 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);
    }
    var parts = order.split('_');
    // // Create a mapping between byte number and byte position
    // // For example B0_B3_B1
    // // Will give mapping 
    // // { B0: 0, B3: 1, B1: 2 }
    // const indexMap: { [key: string]: number } = parts.reduce(
    //     (accumulator: { [key: string]: number }, current: string, index: number) => {
    //         accumulator[current] = index;
    //         return accumulator;
    //     },
    //     {}
    // );
    // if (parts.length !== input.length) {
    //     throw new Error(
    //         `swatpBytes error: ${parts.length} bytes but found ${input.length} bytes`
    //     );
    // }
    var result = new Uint8Array(parts.length);
    // let maxLength = Math.min(parts.length, input.length)
    for (var index = 0; index < result.length; index++) {
        var prevByteIndex = parseInt(parts[index].substr(1));
        if (prevByteIndex >= input.length) {
            throw new Error("swapBytes error, byte index " + prevByteIndex + " is out of range (input length is " + input.length + " byte(s))");
        }
        result[index] = input[input.length - prevByteIndex - 1];
        // const byteIndex = (result.length - index - 1);
        // const newIndex = indexMap['B' + byteIndex];
        // if (newIndex !== undefined) {
        //     result[newIndex] = input[index];
        // }
    }
    return result;
}

function createTapEncryptedFrame(id, payload, paddingGenerator) {
    var len = payload.length;
    // let padSize = computePadding(8 + len, 16)
    // let padding = paddingGenerator ? paddingGenerator(padSize) : undefined
    return {
        id: id,
        len: len,
        payload: payload,
    };
}

/**
 *
 */
var CryptedFrameConverter = /** @class */ (function () {
    function CryptedFrameConverter(frameIdGenerator) {
        this.frameIdGenerator = frameIdGenerator;
    }
    CryptedFrameConverter.prototype.encode = function (data) {
        var encryptedModel = createTapEncryptedFrame(this.frameIdGenerator.next(), data
        // this.paddingGenerator
        );
        return new TapStreamWriter().writeTapEncryptedFrame(encryptedModel)
            .toBytes;
    };
    CryptedFrameConverter.prototype.decode = function (frame) {
        if (!frame) {
            throw ConverterError.nullBufferError('Illegal data: frame is null');
        }
        var encryptedFrame = TapStreamReader.fromArray(frame).readTapEncryptedFrame();
        var subData = encryptedFrame.payload;
        // TODO add back crc
        // let crc = encryptedFrame.crc;
        // let computedCrc = this._computeCRC(byteBuffer.data);
        // if (crc != computedCrc){
        //     if (this.options.skipCRCCheck){
        //     }
        //     else{
        //         throw new Error(`Invalid crc read ${crc}. Should be ${computedCrc}`);
        //     }
        // }
        return subData;
    };
    return CryptedFrameConverter;
}());

var __extends$3 = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __values$1 = (this && this.__values) || function(o) {
    var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
    if (m) return m.call(o);
    if (o && typeof o.length === "number") return {
        next: function () {
            if (o && i >= o.length) o = void 0;
            return { value: o && o[i++], done: !o };
        }
    };
    throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var EnumConverter = /** @class */ (function () {
    function EnumConverter(mapping) {
        this.mapping = mapping;
    }
    EnumConverter.prototype.decode = function (value) {
        var literalValue = this.decodeToLiteral(value);
        var enumStringValue = this.mapping[literalValue];
        if (!enumStringValue) {
            throw new Error("Unknown litteral value " + literalValue + ".");
        }
        return literalValue;
    };
    EnumConverter.prototype.encode = function (value) {
        if (typeof value !== 'number') {
            throw Error("Illegal argument when encoding enum value. Expecting  number but found " + typeof value);
        }
        return this.encodeToLiteral(value);
    };
    return EnumConverter;
}());
var FixedSizeEnumConverter = /** @class */ (function (_super) {
    __extends$3(FixedSizeEnumConverter, _super);
    function FixedSizeEnumConverter(mapping, size) {
        if (size === void 0) { size = 1; }
        var _this = _super.call(this, mapping) || this;
        _this.mapping = mapping;
        _this.size = size;
        return _this;
    }
    FixedSizeEnumConverter.prototype.decodeToLiteral = function (value) {
        return TapStreamReader.fromArray(value).readU(this.size);
    };
    FixedSizeEnumConverter.prototype.encodeToLiteral = function (value) {
        return new TapStreamWriter(this.size).writeU(value, this.size).toBytes;
    };
    return FixedSizeEnumConverter;
}(EnumConverter));
var EnumListConverter = /** @class */ (function () {
    function EnumListConverter(size, mapping) {
        this.size = size;
        this.mapping = mapping;
    }
    EnumListConverter.prototype.decode = function (value) {
        var e_1, _a;
        var _this = this;
        var bits = this.decodeToLiteral(TapStreamReader.create(value));
        var results = [];
        var enumValues = Object.keys(this.mapping).filter(function (key) { return !isNaN(Number(_this.mapping[key])); });
        try {
            // console.log("numval", enumValues)
            for (var enumValues_1 = __values$1(enumValues), enumValues_1_1 = enumValues_1.next(); !enumValues_1_1.done; enumValues_1_1 = enumValues_1.next()) {
                var enumName = enumValues_1_1.value;
                var enumValue = this.mapping[enumName];
                var mask = parseInt(enumValue);
                if (mask == 0) {
                    if (bits == 0) {
                        results.push(enumValue);
                    }
                }
                else if ((bits & mask) == mask) {
                    results.push(enumValue);
                }
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (enumValues_1_1 && !enumValues_1_1.done && (_a = enumValues_1.return)) _a.call(enumValues_1);
            }
            finally { if (e_1) throw e_1.error; }
        }
        return results;
    };
    EnumListConverter.prototype.encode = function (values) {
        var e_2, _a;
        var mask = 0;
        try {
            for (var values_1 = __values$1(values), values_1_1 = values_1.next(); !values_1_1.done; values_1_1 = values_1.next()) {
                var value = values_1_1.value;
                if (typeof value !== 'number') {
                    throw Error("Illegal argument when encoding enum value. Expecting number but found " + typeof value);
                }
                mask |= value;
            }
        }
        catch (e_2_1) { e_2 = { error: e_2_1 }; }
        finally {
            try {
                if (values_1_1 && !values_1_1.done && (_a = values_1.return)) _a.call(values_1);
            }
            finally { if (e_2) throw e_2.error; }
        }
        var result = new TapStreamWriter(this.size).writeU(mask, this.size)
            .toBytes;
        return result;
    };
    EnumListConverter.prototype.decodeToLiteral = function (stream) {
        return stream.readU(this.size);
    };
    return EnumListConverter;
}());

var FloatConverter = /** @class */ (function () {
    function FloatConverter(options) {
        this.options = options;
    }
    FloatConverter.prototype.encode = function (value) {
        return NumberConverter.toOpaqueMsb(FloatConverter.toArray(value), 32);
    };
    FloatConverter.prototype.decode = function (body) {
        return FloatConverter.toFloat(body);
    };
    /**
     *
     * @see https://stackoverflow.com/questions/3096646/how-to-convert-a-floating-point-number-to-its-binary-representation-ieee-754-i
     * @param number
     */
    FloatConverter.toArray = function (number) {
        var n = +number, status = n !== n || n == -Infinity || n == +Infinity ? n : 0, exp = 0, len = 281, // 2 * 127 + 1 + 23 + 3,
        bin = new Array(len), signal = (n = status !== 0 ? 0 : n) < 0, n = Math.abs(n), intPart = Math.floor(n), floatPart = n - intPart, i, lastBit, rounded, j, exponent;
        if (status !== 0) {
            if (n !== n) {
                return 0x7fc00000;
            }
            if (n === Infinity) {
                return 0x7f800000;
            }
            if (n === -Infinity) {
                return 0xff800000;
            }
        }
        i = len;
        while (i) {
            bin[--i] = 0;
        }
        i = 129;
        while (intPart && i) {
            bin[--i] = intPart % 2;
            intPart = Math.floor(intPart / 2);
        }
        i = 128;
        while (floatPart > 0 && i) {
            (bin[++i] = (floatPart *= 2) >= 1 ? 1 : 0) && --floatPart;
        }
        i = -1;
        while (++i < len && !bin[i])
            ;
        if (bin[(lastBit =
            22 +
                (i =
                    (exp = 128 - i) >= -126 && exp <= 127
                        ? i + 1
                        : 128 - (exp = -127))) + 1]) {
            if (!(rounded = bin[lastBit])) {
                j = lastBit + 2;
                while (!rounded && j < len) {
                    rounded = bin[j++];
                }
            }
            j = lastBit + 1;
            while (rounded && --j >= 0) {
                (bin[j] = !bin[j] - 0) && (rounded = 0);
            }
        }
        i = i - 2 < 0 ? -1 : i - 3;
        while (++i < len && !bin[i])
            ;
        (exp = 128 - i) >= -126 && exp <= 127
            ? ++i
            : exp < -126 && ((i = 255), (exp = -127));
        (intPart || status !== 0) &&
            ((exp = 128),
                (i = 129),
                status == -Infinity
                    ? (signal = true)
                    : status !== status && (bin[i] = 1));
        n = Math.abs(exp + 127);
        exponent = 0;
        j = 0;
        while (j < 8) {
            exponent += n % 2 << j;
            n >>= 1;
            j++;
        }
        var mantissa = 0;
        n = i + 23;
        for (; i < n; i++) {
            mantissa = (mantissa << 1) + bin[i];
        }
        return ((signal ? 0x80000000 : 0) + (exponent << 23) + mantissa) | 0;
    };
    FloatConverter.toFloat = function (val) {
        return FloatConverter.numberToFloat(NumberConverter.fromOpaqueMsb(val, 32));
    };
    FloatConverter.numberToFloat = function (val) {
        var bits = 1 * val;
        var sign = (bits & 0x80000000) == 0 ? 1 : -1;
        var exponent = (bits & 0x7f800000) >> 23;
        var mantissa = bits & 0x007fffff;
        mantissa |= 0x00800000;
        var f = 1.0 * (sign * mantissa * Math.pow(2, exponent - 150));
        return parseFloat(f.toFixed(5));
    };
    FloatConverter.instance32 = function () {
        if (!FloatConverter._instance32) {
            FloatConverter._instance32 = new FloatConverter();
        }
        return FloatConverter._instance32;
    };
    FloatConverter._instance32 = undefined;
    return FloatConverter;
}());

var HexStringConverter = /** @class */ (function () {
    function HexStringConverter() {
    }
    HexStringConverter.prototype.decode = function (value) {
        return bufferToHexString(value);
    };
    HexStringConverter.prototype.encode = function (value) {
        if (!value || value.length == 0) {
            return new Uint8Array(0);
        }
        if (value.length % 2 > 0) {
            // Prefix with 0
            value = '0' + value;
        }
        var result = [];
        for (var i = 0, len = value.length; i < len; i += 2) {
            result.push(parseInt(value.substr(i, 2), 16));
        }
        return new Uint8Array(result);
    };
    HexStringConverter.instance = function () {
        if (!HexStringConverter._instance) {
            HexStringConverter._instance = new HexStringConverter();
        }
        return HexStringConverter._instance;
    };
    HexStringConverter._instance = undefined;
    return HexStringConverter;
}());

var __values$2 = (this && this.__values) || function(o) {
    var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
    if (m) return m.call(o);
    if (o && typeof o.length === "number") return {
        next: function () {
            if (o && i >= o.length) o = void 0;
            return { value: o && o[i++], done: !o };
        }
    };
    throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var Ipv4StringConverter = /** @class */ (function () {
    function Ipv4StringConverter() {
    }
    Ipv4StringConverter.prototype.decode = function (body) {
        var e_1, _a;
        if (body == null || body.length != 4) {
            throw ConverterError.unexpectedBufferSize(4, body);
        }
        var result = '';
        var values = Array.from(body);
        try {
            for (var values_1 = __values$2(values), values_1_1 = values_1.next(); !values_1_1.done; values_1_1 = values_1.next()) {
                var part = values_1_1.value;
                result += part.toString() + '.';
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (values_1_1 && !values_1_1.done && (_a = values_1.return)) _a.call(values_1);
            }
            finally { if (e_1) throw e_1.error; }
        }
        return result.substr(0, result.length - 1);
    };
    Ipv4StringConverter.prototype.encode = function (ip) {
        var parts = ip.split('.');
        if (parts.length != 4) {
            throw new Error('Invalid ip format. Should be x.x.x.x');
        }
        var result = new Uint8Array(4);
        parts.map(function (value, index) {
            result[index] = parseInt(value) & 0xff;
        });
        return result;
    };
    Ipv4StringConverter.instance = function () {
        if (!Ipv4StringConverter._instance) {
            Ipv4StringConverter._instance = new Ipv4StringConverter();
        }
        return Ipv4StringConverter._instance;
    };
    Ipv4StringConverter._instance = undefined;
    return Ipv4StringConverter;
}());

var EXPECTED_ARRAY_LENGTH = 6;
var MacAddressStringConverter = /** @class */ (function () {
    function MacAddressStringConverter() {
    }
    MacAddressStringConverter.prototype.decode = function (body) {
        if (!body || body.length !== EXPECTED_ARRAY_LENGTH) {
            throw ConverterError.unexpectedBufferSize(EXPECTED_ARRAY_LENGTH, body);
        }
        var result = '';
        for (var i = 0; i < body.length; i++) {
            var part = body.slice(i, i + 1);
            result += bufferToHexString(part).toUpperCase() + ':';
        }
        return result.substr(0, result.length - 1);
    };
    MacAddressStringConverter.prototype.encode = function (value) {
        var values = value.split(':').map(function (v) { return parseInt(v, 16) & 0xff; });
        if (values.length !== EXPECTED_ARRAY_LENGTH) {
            throw new Error('Invalid mac address format: ' + value);
        }
        return Uint8Array.from(values);
    };
    MacAddressStringConverter.instance = function () {
        if (!MacAddressStringConverter._instance) {
            MacAddressStringConverter._instance = new MacAddressStringConverter();
        }
        return MacAddressStringConverter._instance;
    };
    MacAddressStringConverter._instance = undefined;
    return MacAddressStringConverter;
}());

var __values$3 = (this && this.__values) || function(o) {
    var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
    if (m) return m.call(o);
    if (o && typeof o.length === "number") return {
        next: function () {
            if (o && i >= o.length) o = void 0;
            return { value: o && o[i++], done: !o };
        }
    };
    throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
function swapMap(val) {
    var ret = {};
    for (var key in val) {
        ret[val[key]] = key;
    }
    return ret;
}
var MultipleMaskConverter = /** @class */ (function () {
    // protected static _instance: UniqMaskDecoder;
    function MultipleMaskConverter(mapping) {
        this._mapping = mapping;
        // this._mapping[0x00] = (null as any as OutputType);
        // this._mapping[0x01] = (null as any as OutputType);
    }
    MultipleMaskConverter.prototype.decode = function (body) {
        return MultipleMaskConverter.decodeAll(body, this._mapping);
    };
    /**
     * TODO check that not use more than the first 7 bits
     * @param types
     */
    MultipleMaskConverter.prototype.encode = function (types) {
        return MultipleMaskConverter.encodeAll(types, this._mapping);
    };
    MultipleMaskConverter.encodeAll = function (values, mapping) {
        var e_1, _a;
        var _reverseMapping = swapMap(mapping);
        var result = 0x0;
        try {
            for (var values_1 = __values$3(values), values_1_1 = values_1.next(); !values_1_1.done; values_1_1 = values_1.next()) {
                var type = values_1_1.value;
                if (type in _reverseMapping) {
                    var mask = parseInt(_reverseMapping[type]);
                    result = result | mask;
                }
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (values_1_1 && !values_1_1.done && (_a = values_1.return)) _a.call(values_1);
            }
            finally { if (e_1) throw e_1.error; }
        }
        return Uint8Array.from([result & 0xff]);
    };
    MultipleMaskConverter.decodeAll = function (value, mapping) {
        var results = [];
        if (typeof value !== 'number') {
            value = NumberConverter.fromOpaqueMsb(value, Math.min(value.length * 8, 32));
        }
        for (var key in mapping) {
            var mask = parseInt(key);
            if (mask == 0 && value == 0) {
                results.push(mapping[mask]);
            }
            else if ((mask & value) != 0) {
                results.push(mapping[mask]);
            }
        }
        return results;
    };
    return MultipleMaskConverter;
}());

var __values$4 = (this && this.__values) || function(o) {
    var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
    if (m) return m.call(o);
    if (o && typeof o.length === "number") return {
        next: function () {
            if (o && i >= o.length) o = void 0;
            return { value: o && o[i++], done: !o };
        }
    };
    throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var PipeConverterBuilder = /** @class */ (function () {
    function PipeConverterBuilder(aliases) {
        if (aliases === void 0) { aliases = {}; }
        this.aliases = aliases;
    }
    PipeConverterBuilder.prototype.pipe = function (converter) {
        return new PipeConverter([], this.aliases).pipe(converter);
    };
    return PipeConverterBuilder;
}());
/**
 * Pipe multiple converter one after another
 */
var PipeConverter = /** @class */ (function () {
    function PipeConverter(converters, aliases) {
        if (aliases === void 0) { aliases = {}; }
        this.converters = converters;
        this.aliases = aliases;
    }
    PipeConverter.prototype.decode = function (body) {
        var e_1, _a;
        var output = body;
        try {
            for (var _b = __values$4(this.converters), _c = _b.next(); !_c.done; _c = _b.next()) {
                var converter = _c.value;
                if (typeof converter == 'function') {
                    output = converter(output);
                }
                else {
                    output = converter.decode(output);
                }
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
            }
            finally { if (e_1) throw e_1.error; }
        }
        return output;
    };
    PipeConverter.prototype.encode = function (value) {
        var e_2, _a;
        var output = value;
        try {
            for (var _b = __values$4(this.converters.reverse()), _c = _b.next(); !_c.done; _c = _b.next()) {
                var converter = _c.value;
                if (typeof converter == 'function') {
                    output = converter(output);
                }
                else {
                    output = converter.encode(output);
                }
            }
        }
        catch (e_2_1) { e_2 = { error: e_2_1 }; }
        finally {
            try {
                if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
            }
            finally { if (e_2) throw e_2.error; }
        }
        return output;
    };
    PipeConverter.prototype.pipe = function (converter) {
        if (typeof converter === 'string') {
            if (!(converter in this.aliases)) {
                throw new Error("Illegal argument, missing converter id: " + converter);
            }
            converter = this.aliases[converter];
        }
        this.converters.push(converter);
        return this;
    };
    PipeConverter.pipe = function (converter) {
        return new PipeConverter([converter], {});
    };
    return PipeConverter;
}());

var StringConverter = /** @class */ (function () {
    function StringConverter(encoding, options) {
        if (encoding === void 0) { encoding = 'ascii'; }
        if (options === void 0) { options = {
            stripEndOfStringCharacter: true
        }; }
        this.encoding = encoding;
        this.options = options;
    }
    StringConverter.prototype.encode = function (value) {
        var buffer = Buffer.from(value, this.encoding);
        return Uint8Array.from(buffer);
    };
    StringConverter.prototype.decode = function (value) {
        var str = Buffer.from(value).toString(this.encoding);
        if (this.options.stripEndOfStringCharacter) {
            var indexOfEndOfString = str.indexOf('\0');
            if (indexOfEndOfString >= 0) {
                str = str.substr(0, indexOfEndOfString);
            }
        }
        return str;
    };
    /**
     * Get ascii instance
     * @deprecated use constructor instead
     */
    StringConverter.ascii = function () {
        if (!StringConverter._ascii) {
            StringConverter._ascii = new StringConverter('ascii');
        }
        return StringConverter._ascii;
    };
    StringConverter._ascii = undefined;
    return StringConverter;
}());

var TLVConverter = /** @class */ (function () {
    function TLVConverter() {
        this.serializer = new TLV.Serializer();
        this.parser = new TLV.Parser();
    }
    TLVConverter.prototype.encode = function (data) {
        return this.serializer.serialize(data);
    };
    TLVConverter.prototype.decode = function (data) {
        var tlvTree = this.parser.parse(data);
        return tlvTree;
    };
    return TLVConverter;
}());

var __values$5 = (this && this.__values) || function(o) {
    var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
    if (m) return m.call(o);
    if (o && typeof o.length === "number") return {
        next: function () {
            if (o && i >= o.length) o = void 0;
            return { value: o && o[i++], done: !o };
        }
    };
    throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var TlvBundleConverter = /** @class */ (function () {
    function TlvBundleConverter(variables, tlvConverter) {
        if (variables === void 0) { variables = []; }
        if (tlvConverter === void 0) { tlvConverter = new TLVConverter(); }
        this.variables = variables;
        this.tlvConverter = tlvConverter;
        this.bundleId = 0;
    }
    TlvBundleConverter.prototype.decode = function (data, context) {
        if (data instanceof Uint8Array) {
            // Why this? 
            // const buffer = Buffer.from(data.buffer);
            data = this.tlvConverter.decode(data);
        }
        var result = this._convertFromTlvTree(data);
        if (!result || typeof result !== 'object' || result instanceof Uint8Array) {
            throw new Error("Invalid TLV frame"); // TODO proper error code
        }
        return result;
    };
    // {
    //     "header": {
    //         "id": 1,
    //         "type": 2,
    //         "len": 12
    //     },
    //     "children": [
    //         {
    //             "header": {
    //                 "id": 1,
    //                 "type": 3,
    //                 "len": 4
    //             },
    //             "payload": {
    //                 "type": "Buffer",
    //                 "data": [
    //                     64,
    //                     83,
    //                     127,
    //                     58
    //                 ]
    //             }
    //         },
    //         {
    //             "header": {
    //                 "id": 2,
    //                 "type": 3,
    //                 "len": 4
    //             },
    //             "payload": {
    //                 "type": "Buffer",
    //                 "data": [
    //                     65,
    //                     243,
    //                     141,
    //                     114
    //                 ]
    //             }
    //         }
    //     ]
    // }
    TlvBundleConverter.prototype.encode = function (data) {
        var _a;
        var tree = {
            header: {
                id: this.bundleId,
                len: 0,
                type: TLV.IdentifierType.MULTIPLE_RESOURCE
            },
            children: []
        };
        var totalLength = 0;
        var _loop_1 = function (key) {
            var value = data[key];
            var encodedValue = void 0;
            var meta = this_1.variables.find(function (m) { return m.name === key; });
            if (!meta) {
                meta = this_1.variables.find(function (m) { return m.id === parseInt(key, 10); });
            }
            // if (!meta) {
            //     // TODO use proper error with ErrorCode
            //     throw new Error(`Cannot find variable by identifier "${key}"`);
            // }
            if (meta === null || meta === void 0 ? void 0 : meta.converter) {
                encodedValue = (_a = meta.converter) === null || _a === void 0 ? void 0 : _a.encode(value);
            }
            else {
                if (!(value instanceof Uint8Array)) {
                    throw new Error("Add converter for variable with identifier \"" + key + "\"");
                }
                encodedValue = value;
            }
            if (!tree.children) {
                tree.children = [];
            }
            var id = (meta === null || meta === void 0 ? void 0 : meta.id) || parseInt(key, 10);
            if (isNaN(id)) {
                throw new Error("Cannot find variable by identifier \"" + key + "\"");
            }
            tree.children.push({
                header: {
                    id: id,
                    len: encodedValue.length,
                    type: TLV.IdentifierType.RESOURCE_VALUE
                },
                payload: encodedValue
            });
            totalLength += encodedValue.length + 2; // 2 for header length
        };
        var this_1 = this;
        for (var key in data) {
            _loop_1(key);
        }
        tree.header.len = totalLength;
        return this.tlvConverter.encode(tree);
    };
    /**
     * Converter tlv tree to variable name => value
     */
    TlvBundleConverter.prototype._convertFromTlvTree = function (tree, variableMeta) {
        var e_1, _a;
        // console.log('TLV =>', JSON.stringify(tree, null, 4));
        var result = {};
        // Recursive call to decode children
        if (tree.children) {
            result = {};
            var _loop_2 = function (child) {
                var e_2, _a;
                var variableMetas = this_2.variables.filter(function (v) { return v.id == child.header.id; });
                if (variableMetas.length == 0) {
                    variableMetas.push({
                        id: child.header.id
                    });
                }
                try {
                    for (var variableMetas_1 = (e_2 = void 0, __values$5(variableMetas)), variableMetas_1_1 = variableMetas_1.next(); !variableMetas_1_1.done; variableMetas_1_1 = variableMetas_1.next()) {
                        var variableMeta_1 = variableMetas_1_1.value;
                        var key = variableMeta_1 && variableMeta_1.name
                            ? variableMeta_1.name
                            : child.header.id.toString();
                        result[key] = this_2._convertFromTlvTree(child, variableMeta_1);
                    }
                }
                catch (e_2_1) { e_2 = { error: e_2_1 }; }
                finally {
                    try {
                        if (variableMetas_1_1 && !variableMetas_1_1.done && (_a = variableMetas_1.return)) _a.call(variableMetas_1);
                    }
                    finally { if (e_2) throw e_2.error; }
                }
            };
            var this_2 = this;
            try {
                for (var _b = __values$5(tree.children), _c = _b.next(); !_c.done; _c = _b.next()) {
                    var child = _c.value;
                    _loop_2(child);
                }
            }
            catch (e_1_1) { e_1 = { error: e_1_1 }; }
            finally {
                try {
                    if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
                }
                finally { if (e_1) throw e_1.error; }
            }
        }
        // Decode current node
        else if (tree.payload) {
            var converter = variableMeta ? variableMeta.converter : undefined;
            if (converter) {
                result = converter.decode(tree.payload);
            }
            else {
                result = Uint8Array.from(tree.payload);
            }
        }
        return result;
    };
    return TlvBundleConverter;
}());

var TAG$1 = 'Converter';
/**
 * TODO improv it . We don't know the size of input buffer...
 */
var UniqMaskConverter = /** @class */ (function () {
    // protected _reverseMapping: { [key: number]: OutputType };
    function UniqMaskConverter(mapping) {
        this._mapping = mapping;
    }
    UniqMaskConverter.prototype.decode = function (body) {
        return UniqMaskConverter.decodeOne(body, this._mapping);
    };
    UniqMaskConverter.prototype.encode = function (value) {
        return UniqMaskConverter.encodeOneAsBuffer(value, this._mapping);
    };
    UniqMaskConverter.decodeOneExact = function (data, mapping) {
        if (typeof data !== 'number') {
            data = NumberConverter.fromOpaqueMsb(data);
        }
        for (var key in mapping) {
            var mask = parseInt(key);
            if (data == mask) {
                return mapping[mask];
            }
        }
        throw new Error("Cannot decode " + (typeof data === 'number'
            ? 'number ' + data
            : '0x' + bufferToHexString(data)) + ". Available keys are " + Object.keys(mapping));
    };
    UniqMaskConverter.decodeOne = function (data, mapping) {
        var results = MultipleMaskConverter.decodeAll(data, mapping);
        if (results.length == 0) {
            throw new Error("Cannot decode value " + (typeof data === 'number'
                ? data
                : '0x' + bufferToHexString(data)) + ". Available keys are " + Object.keys(mapping));
        }
        else if (results.length > 1) {
            debug(TAG$1, 'Multiple result for data : ', results, data, mapping);
        }
        return results[0];
    };
    UniqMaskConverter.encodeOneAsNumber = function (data, mapping) {
        var swappedMap = UniqMaskConverter.swapKeyValue(mapping);
        var dataAsString = data.toString();
        if (!(dataAsString in swappedMap)) {
            throw new Error("Key " + dataAsString + " does not exist in mapping. Available keys are " + Object.keys(swappedMap));
        }
        return parseInt(swappedMap[dataAsString]);
    };
    UniqMaskConverter.encodeOneAsBuffer = function (data, mapping) {
        var result = new Uint8Array(1); // TODO change according to conf
        var encodedValue = UniqMaskConverter.encodeOneAsNumber(data, mapping);
        result[0] = encodedValue & 0xff;
        return result;
    };
    UniqMaskConverter.swapKeyValue = function (input) {
        var ret = {};
        for (var key in input) {
            ret[input[key]] = key;
        }
        return ret;
    };
    return UniqMaskConverter;
}());

function safeParseInt(v) {
    var res = parseInt(v);
    if (isNaN(res)) {
        throw invalidStringVersionFormatError(v);
        ;
    }
    return res;
}
function invalidStringVersionFormatError(version) {
    return new Error("Invalid string version format for \"" + version + "\". Should be x.x.x. For example: 1.0.3.");
}
var StringVersionConverter = /** @class */ (function () {
    function StringVersionConverter() {
    }
    StringVersionConverter.prototype.encode = function (version, stream) {
        if (stream === void 0) { stream = new TapStreamWriter(); }
        var parts = version.split('.');
        if (parts.length !== 3) {
            throw invalidStringVersionFormatError(version);
        }
        stream.writeUnsigned(safeParseInt(parts[0]), 1);
        stream.writeUnsigned(safeParseInt(parts[1]), 1);
        stream.writeUnsigned(safeParseInt(parts[2]), 2);
        return stream.toBytes;
    };
    StringVersionConverter.prototype.decode = function (data) {
        // if (!data || data.length !== 4) {
        //     throw ConverterError.unexpectedBufferSize(4, data);
        // }
        var stream = data instanceof TapStreamReader ? data : TapStreamReader.create(data);
        return stream.readUnsigned(1) + "." + stream.readUnsigned(1) + "." + stream.readUnsigned(2);
    };
    return StringVersionConverter;
}());

var Checksum = /** @class */ (function () {
    function Checksum() {
    }
    Checksum.compute = function (data) {
        var computedChecksum = 0;
        for (var i = 0; i < data.length; i++) {
            computedChecksum = computedChecksum + data[i];
        }
        return computedChecksum;
    };
    return Checksum;
}());

/**
 * Generated bundle index. Do not edit.
 */

export { ApduRequestConverter, ApduResponseConverter, ArrayConverter, BooleanConverter, ByteSwapConverter, CRC, Checksum, ConverterError, CryptedFrameConverter, EnumConverter, EnumListConverter, FixedSizeEnumConverter, FloatConverter, HexStringConverter, Ipv4StringConverter, MacAddressStringConverter, MultipleMaskConverter, NumberConverter, PipeConverter, PipeConverterBuilder, ResultCodeTranslation, StringConverter, StringVersionConverter, TAP_REQUEST_FRAME_CONVERTER, TAP_RESPONSE_FRAME_CONVERTER, TLVConverter, TapApduRequestConverter$1 as TapApduRequestConverter, TapApduResponseConverter, TapClient, TapClientError, TapEncryptedFrameConverter, TapRequestEncryptedConverter, TapRequestFrameBuilder, TapRequestFrameConverter, TapRequestHelper, TapResponseFrameBuilder, TapResponseFrameConverter, TapStreamReader, TapStreamWriter, TlvBundleConverter, UniqMaskConverter, createApduFromTapRequest, createTapEncryptedFrame, enumKeyOrValueToNumber, tapResponseStatusToString };
//# sourceMappingURL=iotize-tap-client-impl.js.map
