(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@iotize/tap'), require('@iotize/tap/service/impl/group'), require('@iotize/common/error'), require('@iotize/tap/client/api'), require('rxjs'), require('@iotize/tap/service/impl/interface'), require('@iotize/common/debug'), require('@iotize/common/byte-converter'), require('@iotize/tap/crypto'), require('@iotize/common/crypto'), require('@iotize/common/utility'), require('@iotize/tap/client/impl'), require('rxjs/operators'), require('@iotize/common/byte-stream')) :
    typeof define === 'function' && define.amd ? define('@iotize/tap/auth', ['exports', '@iotize/tap', '@iotize/tap/service/impl/group', '@iotize/common/error', '@iotize/tap/client/api', 'rxjs', '@iotize/tap/service/impl/interface', '@iotize/common/debug', '@iotize/common/byte-converter', '@iotize/tap/crypto', '@iotize/common/crypto', '@iotize/common/utility', '@iotize/tap/client/impl', 'rxjs/operators', '@iotize/common/byte-stream'], factory) :
    (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.iotize = global.iotize || {}, global.iotize.tap = global.iotize.tap || {}, global.iotize.tap.auth = {}), global.iotize.tap, global.iotize.tap.service.impl.group, global.error, global.iotize.tap.client.api, global.rxjs, global.iotize.tap.service.impl["interface"], global.debug$1, global.byteConverter, global.iotize.tap.crypto, global.crypto$1, global.utility, global.iotize.tap.client.impl, global.rxjs.operators, global.byteStream));
})(this, (function (exports, tap, group, error, api, rxjs, _interface, debug$1, byteConverter, crypto, crypto$1, utility, impl, operators, byteStream) { 'use strict';

    /*! *****************************************************************************
    Copyright (c) Microsoft Corporation.

    Permission to use, copy, modify, and/or distribute this software for any
    purpose with or without fee is hereby granted.

    THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
    REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
    AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
    INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
    LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
    OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
    PERFORMANCE OF THIS SOFTWARE.
    ***************************************************************************** */
    /* global Reflect, Promise */
    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 (Object.prototype.hasOwnProperty.call(b, p))
                    d[p] = b[p]; };
        return extendStatics(d, b);
    };
    function __extends(d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    }
    var __assign = function () {
        __assign = Object.assign || function __assign(t) {
            for (var s, i = 1, n = arguments.length; i < n; i++) {
                s = arguments[i];
                for (var p in s)
                    if (Object.prototype.hasOwnProperty.call(s, p))
                        t[p] = s[p];
            }
            return t;
        };
        return __assign.apply(this, arguments);
    };
    function __rest(s, e) {
        var t = {};
        for (var p in s)
            if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
                t[p] = s[p];
        if (s != null && typeof Object.getOwnPropertySymbols === "function")
            for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
                if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                    t[p[i]] = s[p[i]];
            }
        return t;
    }
    function __decorate(decorators, target, key, desc) {
        var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
        if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
            r = Reflect.decorate(decorators, target, key, desc);
        else
            for (var i = decorators.length - 1; i >= 0; i--)
                if (d = decorators[i])
                    r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
        return c > 3 && r && Object.defineProperty(target, key, r), r;
    }
    function __param(paramIndex, decorator) {
        return function (target, key) { decorator(target, key, paramIndex); };
    }
    function __metadata(metadataKey, metadataValue) {
        if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
            return Reflect.metadata(metadataKey, metadataValue);
    }
    function __awaiter$5(thisArg, _arguments, P, generator) {
        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
        return new (P || (P = Promise))(function (resolve, reject) {
            function fulfilled(value) { try {
                step(generator.next(value));
            }
            catch (e) {
                reject(e);
            } }
            function rejected(value) { try {
                step(generator["throw"](value));
            }
            catch (e) {
                reject(e);
            } }
            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
            step((generator = generator.apply(thisArg, _arguments || [])).next());
        });
    }
    function __generator(thisArg, body) {
        var _ = { label: 0, sent: function () { if (t[0] & 1)
                throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
        return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function () { return this; }), g;
        function verb(n) { return function (v) { return step([n, v]); }; }
        function step(op) {
            if (f)
                throw new TypeError("Generator is already executing.");
            while (_)
                try {
                    if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done)
                        return t;
                    if (y = 0, t)
                        op = [op[0] & 2, t.value];
                    switch (op[0]) {
                        case 0:
                        case 1:
                            t = op;
                            break;
                        case 4:
                            _.label++;
                            return { value: op[1], done: false };
                        case 5:
                            _.label++;
                            y = op[1];
                            op = [0];
                            continue;
                        case 7:
                            op = _.ops.pop();
                            _.trys.pop();
                            continue;
                        default:
                            if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
                                _ = 0;
                                continue;
                            }
                            if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
                                _.label = op[1];
                                break;
                            }
                            if (op[0] === 6 && _.label < t[1]) {
                                _.label = t[1];
                                t = op;
                                break;
                            }
                            if (t && _.label < t[2]) {
                                _.label = t[2];
                                _.ops.push(op);
                                break;
                            }
                            if (t[2])
                                _.ops.pop();
                            _.trys.pop();
                            continue;
                    }
                    op = body.call(thisArg, _);
                }
                catch (e) {
                    op = [6, e];
                    y = 0;
                }
                finally {
                    f = t = 0;
                }
            if (op[0] & 5)
                throw op[1];
            return { value: op[0] ? op[1] : void 0, done: true };
        }
    }
    var __createBinding = Object.create ? (function (o, m, k, k2) {
        if (k2 === undefined)
            k2 = k;
        Object.defineProperty(o, k2, { enumerable: true, get: function () { return m[k]; } });
    }) : (function (o, m, k, k2) {
        if (k2 === undefined)
            k2 = k;
        o[k2] = m[k];
    });
    function __exportStar(m, o) {
        for (var p in m)
            if (p !== "default" && !Object.prototype.hasOwnProperty.call(o, p))
                __createBinding(o, m, p);
    }
    function __values(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 __read(o, n) {
        var m = typeof Symbol === "function" && o[Symbol.iterator];
        if (!m)
            return o;
        var i = m.call(o), r, ar = [], e;
        try {
            while ((n === void 0 || n-- > 0) && !(r = i.next()).done)
                ar.push(r.value);
        }
        catch (error) {
            e = { error: error };
        }
        finally {
            try {
                if (r && !r.done && (m = i["return"]))
                    m.call(i);
            }
            finally {
                if (e)
                    throw e.error;
            }
        }
        return ar;
    }
    /** @deprecated */
    function __spread() {
        for (var ar = [], i = 0; i < arguments.length; i++)
            ar = ar.concat(__read(arguments[i]));
        return ar;
    }
    /** @deprecated */
    function __spreadArrays() {
        for (var s = 0, i = 0, il = arguments.length; i < il; i++)
            s += arguments[i].length;
        for (var r = Array(s), k = 0, i = 0; i < il; i++)
            for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
                r[k] = a[j];
        return r;
    }
    function __spreadArray(to, from, pack) {
        if (pack || arguments.length === 2)
            for (var i = 0, l = from.length, ar; i < l; i++) {
                if (ar || !(i in from)) {
                    if (!ar)
                        ar = Array.prototype.slice.call(from, 0, i);
                    ar[i] = from[i];
                }
            }
        return to.concat(ar || Array.prototype.slice.call(from));
    }
    function __await(v) {
        return this instanceof __await ? (this.v = v, this) : new __await(v);
    }
    function __asyncGenerator(thisArg, _arguments, generator) {
        if (!Symbol.asyncIterator)
            throw new TypeError("Symbol.asyncIterator is not defined.");
        var g = generator.apply(thisArg, _arguments || []), i, q = [];
        return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
        function verb(n) { if (g[n])
            i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
        function resume(n, v) { try {
            step(g[n](v));
        }
        catch (e) {
            settle(q[0][3], e);
        } }
        function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
        function fulfill(value) { resume("next", value); }
        function reject(value) { resume("throw", value); }
        function settle(f, v) { if (f(v), q.shift(), q.length)
            resume(q[0][0], q[0][1]); }
    }
    function __asyncDelegator(o) {
        var i, p;
        return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i;
        function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; }
    }
    function __asyncValues(o) {
        if (!Symbol.asyncIterator)
            throw new TypeError("Symbol.asyncIterator is not defined.");
        var m = o[Symbol.asyncIterator], i;
        return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
        function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
        function settle(resolve, reject, d, v) { Promise.resolve(v).then(function (v) { resolve({ value: v, done: d }); }, reject); }
    }
    function __makeTemplateObject(cooked, raw) {
        if (Object.defineProperty) {
            Object.defineProperty(cooked, "raw", { value: raw });
        }
        else {
            cooked.raw = raw;
        }
        return cooked;
    }
    ;
    var __setModuleDefault = Object.create ? (function (o, v) {
        Object.defineProperty(o, "default", { enumerable: true, value: v });
    }) : function (o, v) {
        o["default"] = v;
    };
    function __importStar(mod) {
        if (mod && mod.__esModule)
            return mod;
        var result = {};
        if (mod != null)
            for (var k in mod)
                if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k))
                    __createBinding(result, mod, k);
        __setModuleDefault(result, mod);
        return result;
    }
    function __importDefault(mod) {
        return (mod && mod.__esModule) ? mod : { default: mod };
    }
    function __classPrivateFieldGet(receiver, state, kind, f) {
        if (kind === "a" && !f)
            throw new TypeError("Private accessor was defined without a getter");
        if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver))
            throw new TypeError("Cannot read private member from an object whose class did not declare it");
        return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
    }
    function __classPrivateFieldSet(receiver, state, value, kind, f) {
        if (kind === "m")
            throw new TypeError("Private method is not writable");
        if (kind === "a" && !f)
            throw new TypeError("Private accessor was defined without a setter");
        if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver))
            throw new TypeError("Cannot write private member to an object whose class did not declare it");
        return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
    }

    var prefix = '@iotize/tap/auth';
    var debug = debug$1.createDebugger(prefix);

    var INITIAL_SESSION_STATE = {
        groupId: 0,
        lifeTime: -1,
        name: 'anonymous',
        startTime: undefined,
    };
    var PASSWORD_LENGTH = 16;

    function hashLoginPassword(password) {
        return byteConverter.hexStringToBuffer(crypto.passwordHasher.hash(password).substring(0, PASSWORD_LENGTH * 2));
    }
    function XOR(value1, value2) {
        if (value1.length !== value2.length) {
            throw new Error('Length does not match between the two array. Cannot compute XOR');
        }
        var result = new Uint8Array(value1.length);
        for (var i = 0; i < result.length; i++) {
            result[i] = value1[i] ^ value2[i];
        }
        return result;
    }

    var __awaiter$4 = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
        return new (P || (P = Promise))(function (resolve, reject) {
            function fulfilled(value) { try {
                step(generator.next(value));
            }
            catch (e) {
                reject(e);
            } }
            function rejected(value) { try {
                step(generator["throw"](value));
            }
            catch (e) {
                reject(e);
            } }
            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
            step((generator = generator.apply(thisArg, _arguments || [])).next());
        });
    };
    var TAG$3 = 'BasicAuth';
    var BasicAuth = /** @class */ (function () {
        function BasicAuth(tap, options) {
            if (options === void 0) { options = {
                hashPassword: false,
            }; }
            this.tap = tap;
            this.options = options;
        }
        Object.defineProperty(BasicAuth.prototype, "hashPassword", {
            get: function () {
                var _a;
                return ((_a = this.options) === null || _a === void 0 ? void 0 : _a.hashPassword) || false;
            },
            enumerable: false,
            configurable: true
        });
        /**
         * Tap login
         *
         */
        BasicAuth.prototype.login = function (params) {
            return __awaiter$4(this, void 0, void 0, function () {
                var username, password, response;
                return __generator(this, function (_b) {
                    switch (_b.label) {
                        case 0:
                            username = params.username, password = params.password;
                            if (!this.hashPassword) return [3 /*break*/, 2];
                            debug(TAG$3, "Using hash password login");
                            return [4 /*yield*/, this.tap.service.interface.loginWithHash({
                                    username: username,
                                    password: hashLoginPassword(password),
                                })];
                        case 1:
                            response = _b.sent();
                            return [3 /*break*/, 4];
                        case 2:
                            debug(TAG$3, "Using regular login");
                            return [4 /*yield*/, this.tap.service.interface.login({
                                    username: username,
                                    password: password,
                                })];
                        case 3:
                            response = _b.sent();
                            _b.label = 4;
                        case 4:
                            response.successful();
                            return [2 /*return*/, {}];
                    }
                });
            });
        };
        BasicAuth.prototype.logout = function () {
            return __awaiter$4(this, void 0, void 0, function () {
                return __generator(this, function (_b) {
                    switch (_b.label) {
                        case 0: return [4 /*yield*/, this.tap.service.interface.logout()];
                        case 1:
                            (_b.sent()).successful();
                            return [2 /*return*/];
                    }
                });
            });
        };
        /**
         * Change password for the groupId (If groupId is not specified, it will be changed for the current group id)
         * @param newPassword the new password
         * @param userIdOrName default to the current group id
         * @param userPassword the current user password. if provided it will perform a login before changing password
         */
        BasicAuth.prototype.changePassword = function (newPassword, userId) {
            return __awaiter$4(this, void 0, void 0, function () {
                var passwordBytes;
                return __generator(this, function (_b) {
                    switch (_b.label) {
                        case 0:
                            if (!this.hashPassword) return [3 /*break*/, 2];
                            passwordBytes = hashLoginPassword(newPassword);
                            return [4 /*yield*/, this.tap.service.group.changePasswordKey(userId, passwordBytes)];
                        case 1:
                            (_b.sent()).successful();
                            return [3 /*break*/, 4];
                        case 2: return [4 /*yield*/, this.tap.service.group.changePassword(userId, newPassword)];
                        case 3:
                            (_b.sent()).successful();
                            _b.label = 4;
                        case 4: return [2 /*return*/];
                    }
                });
            });
        };
        return BasicAuth;
    }());

    var __awaiter$3 = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
        return new (P || (P = Promise))(function (resolve, reject) {
            function fulfilled(value) { try {
                step(generator.next(value));
            }
            catch (e) {
                reject(e);
            } }
            function rejected(value) { try {
                step(generator["throw"](value));
            }
            catch (e) {
                reject(e);
            } }
            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
            step((generator = generator.apply(thisArg, _arguments || [])).next());
        });
    };
    var INITIALIZATION_VECTOR_LENGTH = 16;
    var DEFAULT_INITIALIZATION_VECTOR = '00000000000000000000000000000000';
    var TAG$2 = 'ScramInterceptor';
    function _hex(data) {
        return data ? byteConverter.bufferToHexString(data) : '';
    }
    var ScramInterceptor = /** @class */ (function () {
        function ScramInterceptor(scramService) {
            var _this = this;
            this.scramService = scramService;
            this._ivFrameCounter = 0;
            this._ivSupported = true;
            this._options = {
                encryption: false,
                initializationVectorResetPeriod: 100,
                keys: {},
                frameCounter: 0,
            };
            this._encryptedFrameConverter = new impl.CryptedFrameConverter({
                next: function () {
                    return _this._options.frameCounter++;
                },
            });
            this.initializationVectorGenerator = function (length) {
                var data = new Array(length).fill(0).map(function (_) {
                    return Math.floor(Math.random() * 0xff);
                });
                return Uint8Array.from(data);
            };
        }
        Object.defineProperty(ScramInterceptor.prototype, "encryptionAlgo", {
            get: function () {
                if (!this._encryptionAlgo) {
                    throw impl.TapClientError.illegalStateError("Encryption algo has not been set yet");
                }
                return this._encryptionAlgo;
            },
            enumerable: false,
            configurable: true
        });
        Object.defineProperty(ScramInterceptor.prototype, "ivSupported", {
            set: function (v) {
                this._ivSupported = v;
            },
            enumerable: false,
            configurable: true
        });
        Object.defineProperty(ScramInterceptor.prototype, "sessionKey", {
            get: function () {
                return this._options.keys.sessionKey;
            },
            /**
             * Setter for the session key
             * @param key if null, it will stop encryption and remove session key. If true it will update session key used for encryption
             */
            set: function (key) {
                this._options.keys.sessionKey = key;
                this._options.frameCounter = 0;
                if (this._options.keys.sessionKey) {
                    this.refreshEncryptionAlgo();
                }
                else {
                    this.pauseEncryption();
                }
            },
            enumerable: false,
            configurable: true
        });
        Object.defineProperty(ScramInterceptor.prototype, "options", {
            /**
             * Get a copy of encryption options
             */
            get: function () {
                return utility.deepCopy(this._options);
            },
            enumerable: false,
            configurable: true
        });
        ScramInterceptor.prototype.setEncryptionKeys = function (options) {
            this._options.keys = options;
            this.refreshEncryptionAlgo();
        };
        ScramInterceptor.prototype.getEncryptionKeys = function () {
            return this._options.keys;
        };
        ScramInterceptor.prototype.setFrameCounter = function (value) {
            this._options.frameCounter = value;
        };
        ScramInterceptor.prototype.setInitializationVectorRefreshPeriod = function (period) {
            this._options.initializationVectorResetPeriod = period;
        };
        ScramInterceptor.prototype.intercept = function (context, next) {
            var request = context.request;
            debug(TAG$2, "exec " + impl.TapRequestHelper.toString(request) + " (encryption:" + this._options.encryption + ", frameCounter: " + this._options.frameCounter + " - skip " + context.skipEncryption + ", key: " + _hex(this.sessionKey) + " iv decode: " + _hex(this._options.keys.ivDecode) + ", iv encode: " + _hex(this._options.keys.ivEncode) + ")");
            var obs;
            if (this._options.encryption && !context.skipEncryption) {
                obs = this._sendWithEncryption(context, next).pipe(operators.map(function (response) {
                    var data = response.rawBody();
                    return {
                        status: response.status,
                        data: data,
                    };
                }));
            }
            else {
                obs = next.handle(context);
            }
            return obs.pipe(operators.tap(function (response) {
                debug(TAG$2, "Response to " + impl.TapRequestHelper.toString(request) + " => " + response.status + " " + byteConverter.bufferToHexString(response.data));
            }));
        };
        ScramInterceptor.prototype._sendWithEncryption = function (context, next) {
            var _this = this;
            if (!this._encryptedFrameConverter) {
                return rxjs.throwError(impl.TapClientError.illegalStateError("Encrypted frame converter has not been specified yet"));
            }
            var callObs;
            var newClientIV;
            if (this._options.initializationVectorResetPeriod === 1) {
                newClientIV = this.initializationVectorGenerator(INITIALIZATION_VECTOR_LENGTH);
                this._setEncodeIV(newClientIV);
                debug(TAG$2, "Changing initialization vector for every requests. New client IV: " + _hex(newClientIV));
                var cryptedFrame = this._buildEncryptedFrame(context);
                var call = this.scramService.sendWithIVCall({
                    request: cryptedFrame,
                    iv: newClientIV,
                });
                callObs = this._toCallObservable(Object.assign(Object.assign({}, context), { skipEncryption: true }), next, call).pipe(operators.map(function (response) {
                    // if (response.isSuccessful() && response.rawBody().length < INITIALIZATION_VECTOR_LENGTH){
                    //     // tap firmware version is too old apparentl.
                    //     // CCOM IV resource was already used to generate rand number before firmware version 1.83, thats why there is a success code result
                    //     throw TapError.initializationVectorNotSupported(
                    //         new Error(`Tap response is too short`)
                    //     );
                    // }
                    var body = response.body();
                    debug(TAG$2, "Refreshing decoding initialization vector: " + _hex(body.iv));
                    _this._setDecodeIV(body.iv);
                    return tap.TapResponse.create(response.status, body.response);
                }));
            }
            else {
                if (this._options.initializationVectorResetPeriod > 1 &&
                    this._ivFrameCounter >= this._options.initializationVectorResetPeriod) {
                    debug(TAG$2, "sending with iv period " + this._ivFrameCounter + "/" + this._options.initializationVectorResetPeriod);
                    this._ivFrameCounter = -1;
                    debug(TAG$2, "_ivInterceptor refreshEncryptionInitializationVector");
                    callObs = rxjs.from(this.refreshInitializationVectors()).pipe(
                    // catchError((err) => {
                    //     debug(TAG, `refreshEncryptionInitializationVector failed: ${err.message}`);
                    //     if (err.code === TapResponseErrorCode) {
                    //         let tapResultCode = (err as ResponseError).response.status;
                    //         if (tapResultCode == ResultCode.NOT_IMPLEMENTED
                    //             || ResultCode.NOT_FOUND) {
                    //             this._ivSupported = false;
                    //             return of(1)
                    //         }
                    //     }
                    //     return of(err);
                    // }),
                    operators.switchMap(function (_) {
                        var encryptedFrame = _this._buildEncryptedFrame(context);
                        return _this._toCallObservable(context, next, _this.scramService.sendCall(encryptedFrame));
                    }));
                }
                else {
                    var encryptedFrame = this._buildEncryptedFrame(context);
                    var call = this.scramService.sendCall(encryptedFrame);
                    callObs = this._toCallObservable(context, next, call);
                }
            }
            return callObs.pipe(operators.map(function (encryptedWarpperResponse) {
                _this._ivFrameCounter++;
                // debug(TAG, `Iotize client crypted frame: ${encryptedWarpperResponse}`);
                encryptedWarpperResponse.successful();
                var cryptedFrameContent = _this.encryptionAlgo.decode(encryptedWarpperResponse.rawBody());
                // debug(TAG, `Decrypted frame value: ${_hex(cryptedFrameContent)}`);
                var apduFrame = _this._encryptedFrameConverter.decode(cryptedFrameContent);
                // debug(TAG, `apduFrame frame value: ${_hex(apduFrame)}`);
                var tapResponse = context.client.responseDecoder.decode(apduFrame);
                var apiResponse = new tap.TapResponse(tapResponse, context.request);
                // let response: TapResponse<any> = this.lwm2mResponseConverter.decode(lwm2mResponseFrame.data);
                if (context.bodyDecoder) {
                    apiResponse.setBodyDecoder(context.bodyDecoder);
                }
                // debug(TAG, `Iotize client decoded response: ${response}`)
                return apiResponse;
            }));
        };
        ScramInterceptor.prototype._buildEncryptedFrame = function (context) {
            var iotizeFrame = impl.TapStreamWriter.create(10 + context.request.payload.length).writeTapRequestFrame(context.request).toBytes;
            var encryptedFrameModel = this._encryptedFrameConverter.encode(iotizeFrame);
            var encryptedFrame = this.encryptionAlgo.encode(encryptedFrameModel);
            return encryptedFrame;
        };
        ScramInterceptor.prototype._setEncodeIV = function (iv) {
            this._options.keys.ivEncode = iv;
            this.refreshEncryptionAlgo();
        };
        ScramInterceptor.prototype._setDecodeIV = function (iv) {
            this._options.keys.ivDecode = iv;
            this.refreshEncryptionAlgo();
        };
        ScramInterceptor.prototype._toCallObservable = function (context, next, call) {
            var tapRequest = tap.ServiceCallRunner.toTapRequest(call);
            var bodyDecoderFct = tap.ServiceCallRunner.resolveResponseBodyDecoder(call);
            var bodyDecoder = bodyDecoderFct
                ? {
                    decode: bodyDecoderFct,
                }
                : undefined;
            return next
                .handle(Object.assign(Object.assign({}, context), { request: tapRequest, bodyDecoder: bodyDecoder }))
                .pipe(operators.map(function (tapResponse) {
                return new tap.TapResponse(tapResponse, context.request, bodyDecoder);
            }));
        };
        /**
         * Refresh initialization vectors
         */
        ScramInterceptor.prototype.refreshInitializationVectors = function () {
            return __awaiter$3(this, void 0, void 0, function () {
                var clientIV, serverIV, err_1, tapResponseStatus;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            clientIV = this.initializationVectorGenerator(INITIALIZATION_VECTOR_LENGTH);
                            _a.label = 1;
                        case 1:
                            _a.trys.push([1, 3, , 4]);
                            return [4 /*yield*/, this.scramService.setInitializationVector(clientIV)];
                        case 2:
                            serverIV = (_a.sent()).body();
                            debug(TAG$2, "Initialization vector refreshed: server " + _hex(serverIV) + " client=" + _hex(clientIV) + ")");
                            this._options.keys.ivEncode = clientIV;
                            this._options.keys.ivDecode = serverIV;
                            this._ivFrameCounter = 0;
                            this.refreshEncryptionAlgo();
                            return [3 /*break*/, 4];
                        case 3:
                            err_1 = _a.sent();
                            debug(TAG$2, "Cannot refresh initialization vector: " + err_1.message);
                            if (err_1 instanceof tap.TapResponseStatusError) {
                                tapResponseStatus = err_1.response.status;
                                if (tapResponseStatus === api.ResultCode.NOT_IMPLEMENTED ||
                                    tapResponseStatus === api.ResultCode.NOT_FOUND) {
                                    debug(TAG$2, "Cannot initialize encryption vectors (firmware version does not support it). Error: " + err_1.message);
                                    this._ivSupported = false;
                                    throw tap.TapError.initializationVectorNotSupported(err_1);
                                }
                            }
                            return [3 /*break*/, 4];
                        case 4: return [2 /*return*/];
                    }
                });
            });
        };
        /**
         * Resume encryption session
         */
        ScramInterceptor.prototype.resumeEncryption = function () {
            debug(TAG$2, "resume encryption");
            this._options.encryption = true;
        };
        /**
         * Pause encryption without destorying session (session keys will not be removed)
         */
        ScramInterceptor.prototype.pauseEncryption = function () {
            debug(TAG$2, "pause encryption");
            this._options.encryption = false;
        };
        /**
         * Initialize a new encrypted sesssion
         * Session key will be changed. Initialization vector too (if firmware supports it)
         */
        ScramInterceptor.prototype.newSession = function () {
            return __awaiter$3(this, void 0, void 0, function () {
                var response, newSessionKey, err_2, err_3;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            _a.trys.push([0, 6, , 7]);
                            debug(TAG$2, "Creating new encryption session");
                            this.clearSession();
                            return [4 /*yield*/, this.scramService.initialize()];
                        case 1:
                            response = _a.sent();
                            response.successful();
                            newSessionKey = response.body();
                            this.sessionKey = newSessionKey;
                            debug(TAG$2, "Session key will be " + _hex(newSessionKey) + " (length=" + newSessionKey.length + ")");
                            if (!(this._options.initializationVectorResetPeriod > 0)) return [3 /*break*/, 5];
                            _a.label = 2;
                        case 2:
                            _a.trys.push([2, 4, , 5]);
                            return [4 /*yield*/, this.refreshInitializationVectors()];
                        case 3:
                            _a.sent();
                            return [3 /*break*/, 5];
                        case 4:
                            err_2 = _a.sent();
                            // Ignore initialization vector not supported error
                            if (err_2.code !== tap.TapError.Code.InitializationVectorNotSupported) {
                                throw err_2;
                            }
                            return [3 /*break*/, 5];
                        case 5: return [2 /*return*/, newSessionKey];
                        case 6:
                            err_3 = _a.sent();
                            throw tap.TapError.cannotStartScram(err_3);
                        case 7: return [2 /*return*/];
                    }
                });
            });
        };
        /**
         * Clear encrypted session.
         * Destroys frame counter, session keys and initialization vectors
         * This will also stop encrypted communication if it was running
         */
        ScramInterceptor.prototype.clearSession = function () {
            this.pauseEncryption();
            this._options.keys.ivDecode = undefined;
            this._options.keys.ivEncode = undefined;
            this._options.keys.sessionKey = undefined;
            this._ivFrameCounter = 0;
        };
        ScramInterceptor.prototype.refreshEncryptionAlgo = function () {
            if (this._options.keys.sessionKey) {
                var algo = new crypto$1.AesEcb128Converter({
                    key: _hex(this._options.keys.sessionKey),
                    ivDecode: this._options.keys.ivDecode
                        ? _hex(this._options.keys.ivDecode)
                        : DEFAULT_INITIALIZATION_VECTOR,
                    ivEncode: this._options.keys.ivEncode
                        ? _hex(this._options.keys.ivEncode)
                        : DEFAULT_INITIALIZATION_VECTOR,
                });
                this._encryptionAlgo = algo;
            }
        };
        return ScramInterceptor;
    }());

    var __awaiter$2 = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
        return new (P || (P = Promise))(function (resolve, reject) {
            function fulfilled(value) { try {
                step(generator.next(value));
            }
            catch (e) {
                reject(e);
            } }
            function rejected(value) { try {
                step(generator["throw"](value));
            }
            catch (e) {
                reject(e);
            } }
            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
            step((generator = generator.apply(thisArg, _arguments || [])).next());
        });
    };
    var TapEncryption = /** @class */ (function () {
        // private _sessionState: BehaviorSubject<EncryptionKeys>;
        function TapEncryption(tap) {
            var _this = this;
            this.tap = tap;
            this.scramInterceptor = new ScramInterceptor(this.tap.service.scram);
            this.tap.client.addInterceptor(this.scramInterceptor);
            // this._sessionState = new BehaviorSubject<EncryptionKeys>(this._scramInterceptor.options.keys);
            this.tap.events.subscribe(function (event) {
                switch (event.type) {
                    // Stop encryption on tap logout
                    case 'tap-logout':
                        _this.stop();
                        break;
                }
            });
        }
        Object.defineProperty(TapEncryption.prototype, "isStarted", {
            get: function () {
                return this.scramInterceptor.options.encryption;
            },
            enumerable: false,
            configurable: true
        });
        TapEncryption.prototype.setEncryptionKeys = function (keys) {
            this.scramInterceptor.setEncryptionKeys(keys);
        };
        TapEncryption.prototype.setEncryptedFrameCounter = function (frameCounter) {
            this.scramInterceptor.setFrameCounter(frameCounter);
        };
        TapEncryption.prototype.getEncryptionOptions = function () {
            return this.scramInterceptor.options;
        };
        TapEncryption.prototype.refreshEncryptionInitializationVector = function () {
            return this.scramInterceptor.refreshInitializationVectors();
        };
        TapEncryption.prototype.setInitializationVectorRefreshPeriod = function (period) {
            return this.scramInterceptor.setInitializationVectorRefreshPeriod(period);
        };
        // /**
        //  * Enable/Disable encrytion when communicating with a device
        //  * @param enable true if requests must be encrypted
        //  * @param resetSessionKey true if you want to reset the the session key.
        //  * @param refreshInitializationVector true if you want to change initialization vectors
        //  *
        //  * @deprecated use {@link encryption()} instead
        //  */
        // public async enableEncryption(
        //     enable: boolean,
        //     resetSessionKey = false,
        //     refreshInitializationVector = false
        // ): Promise<void> {
        //     return this.encryption(
        //         enable,
        //         resetSessionKey,
        //         refreshInitializationVector
        //     );
        // }
        // /**
        //  * Enable/Disable encrytion when communicating with a device
        //  * @param enable true if requests must be encrypted
        //  * @param resetSessionKey true if you want to reset the the session key.
        //  * @param refreshInitializationVector true if you want to change initialization vectors
        //  *
        //  */
        // public async encryption(
        //     enable: boolean,
        //     resetSessionKey = false,
        //     refreshInitializationVector = false
        // ): Promise<void> {
        //     await this.scramInterceptor.encryption(
        //         enable,
        //         resetSessionKey,
        //         refreshInitializationVector
        //     );
        // }
        /**
         * Enable encryption
         */
        TapEncryption.prototype.start = function () {
            return __awaiter$2(this, void 0, void 0, function () {
                var keys;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0: return [4 /*yield*/, this.scramInterceptor.newSession()];
                        case 1:
                            keys = _a.sent();
                            this.scramInterceptor.resumeEncryption();
                            return [2 /*return*/, keys];
                    }
                });
            });
        };
        /**
         * Disable encryption
         */
        TapEncryption.prototype.stop = function () {
            this.scramInterceptor.clearSession();
        };
        /**
         * Pause encryption
         */
        TapEncryption.prototype.pause = function () {
            this.scramInterceptor.pauseEncryption();
        };
        /**
         * Pause encryption
         */
        TapEncryption.prototype.resume = function () {
            this.scramInterceptor.resumeEncryption();
        };
        Object.defineProperty(TapEncryption.prototype, "sessionKey", {
            /**
             * Get current session key for encrypted communicatioin
             */
            get: function () {
                return this.scramInterceptor.sessionKey;
            },
            /**
             * Setter for the session key
             * @param key if null, it will stop encryption and remove session key. If true it will update session key used for encryption
             */
            set: function (key) {
                this.scramInterceptor.sessionKey = key;
            },
            enumerable: false,
            configurable: true
        });
        return TapEncryption;
    }());

    var ɵ0$1 = function (context) { return new TapEncryption(context.tap); };
    var _TAP_EXTENSION_ENCRYPTION_ = tap.defineTapPropertyExtension('encryption', ɵ0$1);

    exports.TapAuthError = /** @class */ (function (_super) {
        __extends(TapAuthError, _super);
        function TapAuthError(code, message, cause) {
            var _this = _super.call(this, message + (" (Error code '" + code + "')"), code) || this;
            _this.cause = cause;
            return _this;
        }
        TapAuthError.tapRequestError = function (params, err) {
            return new TapAuthError(TapAuthError.Code.TapRequestError, "Login failed. " + err.message, err);
        };
        TapAuthError.tooManyLoginFailedError = function (params, err) {
            return new TapAuthError(TapAuthError.Code.TooManyLoginAttempt, "Login failed due to too many login attempt. You must wait before trying to login again.", err);
        };
        TapAuthError.invalidCredentialsError = function (credentials, err) {
            return new TapAuthError(TapAuthError.Code.InvalidCredentials, "Login failed. Given username or password is not valid.", err);
        };
        return TapAuthError;
    }(error.CodeError));
    (function (TapAuthError) {
        var Code;
        (function (Code) {
            Code["InvalidServerProof"] = "TapAuthErrorInvalidServerProof";
            Code["ScramDisabled"] = "TapAuthErrorScramDisabled";
            Code["InvalidCredentials"] = "TapAuthErrorInvalidCredentials";
            Code["TooManyLoginAttempt"] = "TapAuthErrorTooManyLoginAttempt";
            Code["TapRequestError"] = "TapAuthErrorTapRequestError";
        })(Code = TapAuthError.Code || (TapAuthError.Code = {}));
        var InvalidServerKey = /** @class */ (function (_super) {
            __extends(InvalidServerKey, _super);
            function InvalidServerKey(deviceServerProof, expectedServerProof) {
                var _this = _super.call(this, Code.InvalidServerProof, "Login fail invalid server proof ('" + byteConverter.bufferToHexString(deviceServerProof) + "' != '" + byteConverter.bufferToHexString(expectedServerProof) + "') ") || this;
                _this.deviceServerProof = deviceServerProof;
                _this.expectedServerProof = expectedServerProof;
                return _this;
            }
            return InvalidServerKey;
        }(TapAuthError));
        TapAuthError.InvalidServerKey = InvalidServerKey;
    })(exports.TapAuthError || (exports.TapAuthError = {}));

    var __awaiter$1 = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
        return new (P || (P = Promise))(function (resolve, reject) {
            function fulfilled(value) { try {
                step(generator.next(value));
            }
            catch (e) {
                reject(e);
            } }
            function rejected(value) { try {
                step(generator["throw"](value));
            }
            catch (e) {
                reject(e);
            } }
            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
            step((generator = generator.apply(thisArg, _arguments || [])).next());
        });
    };
    var TAG$1 = 'Scram';
    function DEFAULT_SCRAM_SALT_GENERATOR() {
        var data = new Array(ScramAuth.USER_SALT_SIZE).fill(0).map(function (_) {
            return Math.floor(Math.random() * 0xff);
        });
        // To be 100% sure that salt will never be [0,0,0,0] which will disable login for the user
        if (data[0] === 0) {
            data[0] = 1;
        }
        return Uint8Array.from(data);
    }
    var ScramAuth = /** @class */ (function () {
        // protected _sessionState!: BehaviorSubject<SessionState>;
        // /**
        //  * Listen to session state changed
        //  */
        // public get sessionState(): Observable<SessionState> {
        //     return this._sessionState.asObservable();
        // }
        // /**
        //  * Get current session state
        //  */
        // public get sessionStateSnapshot(): SessionState {
        //     return this._sessionState.value;
        // }
        function ScramAuth(tap) {
            this.tap = tap;
            this.nonceGenerator = function () {
                return Math.floor(Math.random() * 0xffffffff);
            };
            this.saltGenerator = DEFAULT_SCRAM_SALT_GENERATOR;
            // this.sessionData = {};
        }
        // async changeCurrentUserPassword(newPassword: string): Promise<void> {
        //     let groupId = (await this.device.service.interface.getCurrentGroupId()).body()!;
        //     if (!this.sessionData.options || !groupId){
        //         throw new AuthError(AuthError.Code.NOT_LOGGED_IN, 'Cannot change password, your are not logged in');
        //     }
        //     let newPasswordKey: Uint8Array = ScramAuth.createScramPasswordKey(newPassword, this.sessionData.options);
        //     (await this.device.service.group.changePasswordKey(groupId, newPasswordKey)).successful();
        // }
        // /**
        //  * Enable/Disable scram for this tap
        //  * @param enabled
        //  */
        // async enable(enabled = true): Promise<void> {
        //     const lockInfo = (await this.device.service.interface.getSecurityOptions()).body();
        //     if (lockInfo.scramActivated === enabled) {
        //         return;
        //     }
        //     lockInfo.scramActivated = enabled;
        //     if (enabled) {
        //         lockInfo.hashPassword = true;
        //     }
        //     (await this.device.service.interface.putSecurityOptions(lockInfo)).successful();
        // }
        ScramAuth.prototype.changePassword = function (newPassword, groupId, salt) {
            if (salt === void 0) { salt = this.saltGenerator(); }
            return __awaiter$1(this, void 0, void 0, function () {
                var options, _a, newPasswordKey;
                return __generator(this, function (_b) {
                    switch (_b.label) {
                        case 0:
                            _a = {};
                            return [4 /*yield*/, this.tap.service.scram.getHashIteration()];
                        case 1:
                            options = (_a.iterationNumber = (_b.sent()).body(),
                                _a.salt = salt,
                                _a);
                            newPasswordKey = ScramAuth.createScramPasswordKey(newPassword, options);
                            debug(TAG$1, "Changing password key: " + byteConverter.bufferToHexString(newPasswordKey) + " salt=" + byteConverter.bufferToHexString(salt) + "; iteration=" + options.iterationNumber + " for user id \"" + groupId + "\"");
                            return [4 /*yield*/, this.tap.service.group.changePasswordKey(groupId, newPasswordKey)];
                        case 2:
                            (_b.sent()).successful();
                            return [2 /*return*/];
                    }
                });
            });
        };
        /**
         * Perform login
         *
         * @param params
         *
         * @throws Error if scram is not activated
         */
        ScramAuth.prototype.login = function (params) {
            return __awaiter$1(this, void 0, void 0, function () {
                var clientNonce, loginParams, loginBody, keys, sessionData, serverNonce, deviceServerProof, expectedServerProof;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            clientNonce = this.generateNonce();
                            debug(TAG$1, 'clientNonce', clientNonce);
                            loginParams = {
                                username: params.username,
                                clientNonce: clientNonce,
                            };
                            return [4 /*yield*/, this.tap.service.scram.login(loginParams)];
                        case 1:
                            loginBody = (_a.sent()).body();
                            keys = ScramAuth.computeKeys(params, loginBody, clientNonce);
                            sessionData = {};
                            sessionData.storedKey = keys.storedKey;
                            sessionData.serverKey = keys.serverKey;
                            serverNonce = loginBody.serverNonce;
                            return [4 /*yield*/, this.tap.service.scram.loginProof(keys.clientProof)];
                        case 2:
                            deviceServerProof = (_a.sent()).body();
                            expectedServerProof = keys.serverProof;
                            // TODO maybe we can find something better to test array equals ...
                            if (byteConverter.bufferToHexString(expectedServerProof) !==
                                byteConverter.bufferToHexString(deviceServerProof)) {
                                throw new exports.TapAuthError.InvalidServerKey(expectedServerProof, deviceServerProof);
                            }
                            sessionData.clientNonce = clientNonce;
                            sessionData.key = ScramAuth.computeSessionKey(clientNonce, serverNonce, loginBody.salt, keys.serverKey, keys.storedKey);
                            return [2 /*return*/, sessionData];
                    }
                });
            });
        };
        ScramAuth.createScramPasswordKey = function (newPassword, options) {
            var keys = ScramAuth.computeBaseKeys(newPassword, options);
            var saltCopy = new Uint8Array(options.salt);
            var buffer = byteStream.KaitaiStreamWriter.create(ScramAuth.SCRAM_PASSWORD_LENGTH);
            buffer
                .writeBytes(keys.storedKey, ScramAuth.KEY_SIZE)
                .writeBytes(keys.serverKey, ScramAuth.KEY_SIZE)
                .writeBytes(saltCopy, ScramAuth.USER_SALT_SIZE);
            return buffer.toBytes;
        };
        ScramAuth.computeBaseKeys = function (password, options) {
            var hashedPassword = hashLoginPassword(password);
            // debug(TAG, 'ScramAuth', 'hashedPassword', bufferToHexString(hashedPassword));
            var saltedPassword = ScramAuth.saltedPassword(hashedPassword, options.salt, options.iterationNumber);
            // debug(TAG, 'ScramAuth', 'saltedPassword', bufferToHexString(saltedPassword));
            // let clientKey: Uint8Array = ScramAuth.clientKey(saltedPassword);
            // debug(TAG, 'ScramAuth', 'clientKey', bufferToHexString(clientKey));
            var storedKey = ScramAuth.storedKey(saltedPassword);
            debug(TAG$1, 'storeKey', byteConverter.bufferToHexString(storedKey));
            var serverKey = ScramAuth.serverKey(saltedPassword);
            debug(TAG$1, 'serverKey', byteConverter.bufferToHexString(serverKey));
            return {
                hashedPassword: hashedPassword,
                saltedPassword: saltedPassword,
                storedKey: storedKey,
                serverKey: serverKey,
            };
        };
        ScramAuth.computeKeys = function (credentials, loginBody, clientNonce) {
            var keys = ScramAuth.computeBaseKeys(credentials.password, loginBody);
            var clientProof = ScramAuth.clientProof(keys.storedKey, clientNonce, loginBody.serverNonce);
            // debug(TAG, 'ScramAuth', 'clientProof', bufferToHexString(clientProof));
            var serverProof = ScramAuth.serverProof(keys.serverKey, clientNonce, loginBody.serverNonce);
            // debug(TAG, 'ScramAuth', 'serverProof', bufferToHexString(serverProof));
            return Object.assign(Object.assign({}, keys), { clientProof: clientProof, serverProof: serverProof });
        };
        ScramAuth.prototype.logout = function () {
            return __awaiter$1(this, void 0, void 0, function () {
                var response;
                return __generator(this, function (_a) {
                    response = this.tap.service.interface.logout();
                    this.tap.encryption.stop();
                    return [2 /*return*/, response];
                });
            });
        };
        ScramAuth.clientProof = function (storedKey, clientNonce, serverNonce) {
            return ScramAuth.computeProof(storedKey, clientNonce, serverNonce).subarray(0, ScramAuth.KEY_SIZE);
        };
        ScramAuth.serverProof = function (serverKey, clientNonce, serverNonce) {
            return ScramAuth.computeProof(serverKey, serverNonce, clientNonce).subarray(0, ScramAuth.KEY_SIZE);
        };
        // getSessionKey(): Uint8Array {
        //     if (!this.sessionData.key) {
        //         throw new Error('Session not started');
        //     }
        //     return this.sessionData.key;
        // }
        ScramAuth.prototype.generateNonce = function () {
            return this.nonceGenerator();
        };
        /**
         * SaltedPwd = PBKDF2 ( HashedPassword, UserSalt, ItCnt )
         * @param password
         * @param userSalt
         * @param iteration
         */
        ScramAuth.saltedPassword = function (hashedPassword, userSalt, iterations) {
            return crypto.pbkdf2(hashedPassword, userSalt, iterations);
        };
        /**
         * ClientKey = HMAC ( SaltedPwd | « ClientKey »)
         * @param saltedPassword
         */
        // public static clientKey(saltedPassword: InputDataType): Uint8Array{
        //     let encodedLabel = ScramAuth.encodeLabel(ScramAuth.CLIENT_KEY_LABEL);
        //     let encodedLabel2 : Uint8Array = KaitaiStreamWriter.mergeArrays(
        //         encodedLabel,
        //         Uint8Array.from([0])
        //     ).data;
        //     // debug(TAG, 'ScramAuth', 'clientKey()', 'data => ', bufferToHexString(data), 'len', data.length, `(${saltedPassword.length} + ${encodedLabel.length})`);
        //     return ScramAuth.HMAC(
        //         saltedPassword,
        //         // userSalt
        //         encodedLabel2
        //     ).subarray(0, ScramAuth.KEY_SIZE);
        // }
        /**
         * StoredKey = H ( ClientKey )
         * @param saltedPassword
         */
        ScramAuth.storedKey = function (saltedPassword) {
            return ScramAuth.HASH(saltedPassword, ScramAuth.CLIENT_KEY_LABEL, ScramAuth.CLIENT_KEY_ITERATION_NUMBER);
        };
        ScramAuth.serverKey = function (saltedPassword) {
            return ScramAuth.HASH(saltedPassword, ScramAuth.SERVER_KEY_LABEL, ScramAuth.SERVER_KEY_ITERATION_NUMBER);
        };
        ScramAuth.HASH = function (key, label, iteration) {
            return crypto.pbkdf2(key, label, iteration, ScramAuth.KEY_SIZE / 4);
        };
        /**
         * ClientSignature = HMAC ( StoredKey | ClientNonce | ServerNonce )
         * @param key
         * @param nonce1
         * @param nonce2
         */
        ScramAuth.computeProof = function (key, nonce1, nonce2) {
            var buffer = byteStream.KaitaiStreamWriter.create(key.length + ScramAuth.CLIENT_NONCE_SIZE + ScramAuth.SERVER_NONCE_SIZE);
            buffer
                .writeU(nonce1, ScramAuth.CLIENT_NONCE_SIZE)
                .writeBytes(key)
                .writeU(nonce2, ScramAuth.SERVER_NONCE_SIZE);
            return crypto.hmacSHA256(buffer.toBytes, key);
        };
        /**
         * ClientProofCheck = StoredKey ^ ClientProof
         * @param storedKey
         * @param clientProof
         */
        ScramAuth.clientProofCheck = function (storedKey, clientProof) {
            return XOR(storedKey, clientProof);
        };
        /**
         * CommunicationKey = H ( ClientNonce | ServerNonce | StoredKey | « CommunicationKey » )
         * @param clientNonce
         * @param serverNonce
         * @param storedKey
         */
        ScramAuth.computeSessionKey = function (clientNonce, serverNonce, userSalt, serverKey, storedKey) {
            var encodedLabel = ScramAuth.encodeLabel(ScramAuth.COMMUNICATION_KEY_LABEL);
            var buffer = byteStream.KaitaiStreamWriter.create(ScramAuth.CLIENT_NONCE_SIZE +
                ScramAuth.SERVER_NONCE_SIZE +
                userSalt.length +
                serverKey.length +
                storedKey.length
            // + encodedLabel.length
            );
            buffer
                .writeU(clientNonce, ScramAuth.CLIENT_NONCE_SIZE)
                .writeBytes(serverKey)
                .writeBytes(userSalt)
                .writeBytes(storedKey)
                .writeU(serverNonce, ScramAuth.SERVER_NONCE_SIZE);
            // .add(encodedLabel)
            return crypto.hmacSHA256(buffer.toBytes, serverKey).subarray(0, ScramAuth.KEY_SIZE);
        };
        /**
         *
         * @param input
         */
        ScramAuth.encodeLabel = function (input) {
            return this.stringConverter.encode(input);
        };
        return ScramAuth;
    }());
    ScramAuth.CRC_LENGTH = 4;
    ScramAuth.CLIENT_NONCE_SIZE = 4;
    ScramAuth.SERVER_NONCE_SIZE = 4;
    ScramAuth.ITERATION_NUMBER_SIZE = 4;
    // public sessionData: ScramAuth.SessionData;
    ScramAuth.COMMUNICATION_KEY_LABEL = 'CommunicationKey';
    // static CLIENT_PROOF_LABEL: string = "ClientProof";
    ScramAuth.CLIENT_KEY_LABEL = 'ClientKey';
    ScramAuth.SERVER_KEY_LABEL = 'ServerKey';
    ScramAuth.KEY_SIZE = 16;
    ScramAuth.CLIENT_KEY_ITERATION_NUMBER = 2;
    ScramAuth.SERVER_KEY_ITERATION_NUMBER = 2;
    ScramAuth.USER_SALT_SIZE = 4;
    ScramAuth.SCRAM_PASSWORD_LENGTH = ScramAuth.KEY_SIZE * 2 + ScramAuth.USER_SALT_SIZE;
    ScramAuth.stringConverter = impl.StringConverter.ascii();

    var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
        return new (P || (P = Promise))(function (resolve, reject) {
            function fulfilled(value) { try {
                step(generator.next(value));
            }
            catch (e) {
                reject(e);
            } }
            function rejected(value) { try {
                step(generator["throw"](value));
            }
            catch (e) {
                reject(e);
            } }
            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
            step((generator = generator.apply(thisArg, _arguments || [])).next());
        });
    };
    var TAG = 'TapAuth';
    var TapAuth = /** @class */ (function () {
        function TapAuth(tap) {
            this.tap = tap;
            this._sessionState = new rxjs.BehaviorSubject(INITIAL_SESSION_STATE);
            this._sessionData = new rxjs.BehaviorSubject({});
        }
        Object.defineProperty(TapAuth.prototype, "sessionStateSnapshot", {
            /**
             * Get current session state snapshot
             */
            get: function () {
                return this._sessionState.value;
            },
            enumerable: false,
            configurable: true
        });
        Object.defineProperty(TapAuth.prototype, "sessionState", {
            /**
             * Listen to session state changed
             */
            get: function () {
                return this._sessionState.asObservable();
            },
            enumerable: false,
            configurable: true
        });
        Object.defineProperty(TapAuth.prototype, "sessionDataSnapshot", {
            /**
             * Get current session data snapshot
             */
            get: function () {
                return this._sessionData.value;
            },
            enumerable: false,
            configurable: true
        });
        Object.defineProperty(TapAuth.prototype, "sessionData", {
            /**
             * Listen to session data changed
             */
            get: function () {
                return this._sessionData.asObservable();
            },
            enumerable: false,
            configurable: true
        });
        Object.defineProperty(TapAuth.prototype, "service", {
            get: function () {
                return this.tap.service;
            },
            enumerable: false,
            configurable: true
        });
        TapAuth.prototype.setAuthMethod = function (method) {
            this._auth = method;
        };
        /**
         * Clear auth cache.
         * Usefull for example if Tap configuration has changed
         */
        TapAuth.prototype.clearCache = function () {
            this._auth = undefined;
        };
        // async test() {
        //     const lockInfo = await this.getLockInfo();
        //     if (userPassword) {
        //         if (currentUserId === undefined) {
        //             currentUserId = (await this.service.interface.getCurrentGroupId()).body();
        //         }
        //         const currentUsername = (await this.tap.service.group.getName(
        //             currentUserId!
        //         )).body()!;
        //         await this.login(currentUsername, userPassword, false);
        //     }
        // }
        TapAuth.prototype.login = function (params, options) {
            return __awaiter(this, void 0, void 0, function () {
                var auth, sessionData, event, err_1;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            debug(TAG, "Tap login as " + params.username + "...");
                            return [4 /*yield*/, this.setupTapAuthEngineIfRequired()];
                        case 1:
                            auth = _a.sent();
                            _a.label = 2;
                        case 2:
                            _a.trys.push([2, 6, , 7]);
                            return [4 /*yield*/, auth.login(params)];
                        case 3:
                            sessionData = _a.sent();
                            if (sessionData && sessionData.key) {
                                this.tap.encryption.sessionKey = sessionData.key;
                                this.tap.encryption.resume();
                            }
                            this._sessionData.next(sessionData);
                            if (!!(options === null || options === void 0 ? void 0 : options.noRefreshSessionState)) return [3 /*break*/, 5];
                            return [4 /*yield*/, this.refreshSessionState({
                                    username: params.username,
                                })];
                        case 4:
                            _a.sent();
                            _a.label = 5;
                        case 5:
                            event = {
                                type: 'tap-login',
                            };
                            this.tap.notifyEvent(event);
                            return [2 /*return*/, sessionData];
                        case 6:
                            err_1 = _a.sent();
                            if (error.isCodeError(tap.TapError.Code.ResponseStatusError, err_1)) {
                                if (err_1.response.status === api.ResultCode.NOT_ACCEPTABLE ||
                                    err_1.response.status === api.ResultCode.UNAUTHORIZED) {
                                    throw exports.TapAuthError.invalidCredentialsError(params, err_1);
                                }
                                else if (err_1.response.status === api.ResultCode.SERVICE_UNAVAILABLE) {
                                    throw exports.TapAuthError.tooManyLoginFailedError(params, err_1);
                                }
                            }
                            throw exports.TapAuthError.tapRequestError(params, err_1);
                        case 7: return [2 /*return*/];
                    }
                });
            });
        };
        /**
         * Tap logout
         * Reject if logout failed
         */
        TapAuth.prototype.logout = function () {
            return __awaiter(this, void 0, void 0, function () {
                var auth, event;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            debug(TAG, "Tap logout...");
                            return [4 /*yield*/, this.setupTapAuthEngineIfRequired()];
                        case 1:
                            auth = _a.sent();
                            return [4 /*yield*/, auth.logout()];
                        case 2:
                            _a.sent();
                            // we keep session keys even after logout
                            this._sessionState.next(INITIAL_SESSION_STATE);
                            this._sessionData.next({});
                            event = {
                                type: 'tap-logout',
                            };
                            this.tap.notifyEvent(event);
                            return [2 /*return*/];
                    }
                });
            });
        };
        /**
         * Read session state from the Tap
         * This will perform a few tap requests
         *
         * TODO we should invalidate encryption key if session state changed
         * TODO we should use multi commands to improve performance
         */
        TapAuth.prototype.refreshSessionState = function (defaults) {
            return __awaiter(this, void 0, void 0, function () {
                var groupId, currentSessionState, _a, _b, _c;
                return __generator(this, function (_d) {
                    switch (_d.label) {
                        case 0:
                            debug(TAG, '[TAP]', "Refreshing session state (current=" + this.sessionStateSnapshot.name + ")...");
                            return [4 /*yield*/, this.service.interface.getCurrentGroupId()];
                        case 1:
                            groupId = (_d.sent()).body();
                            if (!(groupId !== this.sessionStateSnapshot.groupId)) return [3 /*break*/, 5];
                            currentSessionState = Object.assign({}, this.sessionStateSnapshot);
                            currentSessionState.groupId = groupId;
                            _a = currentSessionState;
                            _b = (defaults === null || defaults === void 0 ? void 0 : defaults.username);
                            if (_b) return [3 /*break*/, 3];
                            return [4 /*yield*/, this.service.group.getName(groupId)];
                        case 2:
                            _b = (_d.sent()).body();
                            _d.label = 3;
                        case 3:
                            _a.name = _b;
                            _c = currentSessionState;
                            return [4 /*yield*/, this.service.group.getSessionLifetime(groupId)];
                        case 4:
                            _c.lifeTime = (_d.sent()).body();
                            debug(TAG, '[TAP]', "Notifying new session state: " + currentSessionState.name);
                            this._sessionState.next(currentSessionState);
                            return [3 /*break*/, 6];
                        case 5:
                            debug(TAG, '[TAP]', "Session state did not changed: " + this.sessionStateSnapshot.name + " (id=" + groupId + ")");
                            _d.label = 6;
                        case 6: return [2 /*return*/, this.sessionStateSnapshot];
                    }
                });
            });
        };
        // private _isLoggedIn(): boolean {
        //     return (
        //         this.sessionStateSnapshot != undefined &&
        //         this.sessionStateSnapshot.name !==
        //         INITIAL_SESSION_STATE.name
        //     );
        // }
        /**
         * Change password for given user. If userIdOrName is not set, it will change password for the currenty
         * connected user.
         * @param newPassword
         * @param userIdOrUndefined user identifier for which password will be change
         */
        TapAuth.prototype.changePassword = function (newPassword, userIdOrUndefined) {
            return __awaiter(this, void 0, void 0, function () {
                var userId, username, auth, event;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            if (!(userIdOrUndefined === undefined)) return [3 /*break*/, 3];
                            return [4 /*yield*/, this.service.interface.getCurrentGroupId()];
                        case 1:
                            userId = (_a.sent()).body();
                            return [4 /*yield*/, this.service.group.getName(userId)];
                        case 2:
                            username = (_a.sent()).body();
                            return [3 /*break*/, 5];
                        case 3:
                            // TODO for now we cannot get the user id from the username
                            // if (typeof userIdOrName === "string") {
                            //     username = userIdOrName;
                            //     userId = (await this.service.group.getUserIdFromName(username)).body();
                            // }
                            // else {
                            userId = userIdOrUndefined;
                            return [4 /*yield*/, this.service.group.getName(userId)];
                        case 4:
                            username = (_a.sent()).body();
                            _a.label = 5;
                        case 5: return [4 /*yield*/, this.setupTapAuthEngineIfRequired()];
                        case 6:
                            auth = _a.sent();
                            return [4 /*yield*/, auth.changePassword(newPassword, userId)];
                        case 7:
                            _a.sent();
                            event = {
                                type: 'tap-user-password-change',
                                user: {
                                    id: userId,
                                    username: username,
                                },
                            };
                            this.tap.notifyEvent(event);
                            return [2 /*return*/];
                    }
                });
            });
        };
        TapAuth.prototype.setupTapAuthEngineIfRequired = function () {
            return __awaiter(this, void 0, void 0, function () {
                var _a;
                return __generator(this, function (_b) {
                    switch (_b.label) {
                        case 0:
                            if (!!this._auth) return [3 /*break*/, 2];
                            _a = this;
                            return [4 /*yield*/, this.setupTapAuthEngine()];
                        case 1:
                            _a._auth = _b.sent();
                            _b.label = 2;
                        case 2: return [2 /*return*/, this._auth];
                    }
                });
            });
        };
        TapAuth.prototype.setupTapAuthEngine = function () {
            return __awaiter(this, void 0, void 0, function () {
                var securityOptions;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0: return [4 /*yield*/, this.service.interface.getSecurityOptions()];
                        case 1:
                            securityOptions = (_a.sent()).body();
                            debug(TAG, "securityOptions " + JSON.stringify(securityOptions));
                            if (securityOptions.scramActivated) {
                                debug(TAG, 'Setup with ScramAuth');
                                return [2 /*return*/, new ScramAuth(this.tap)];
                            }
                            else {
                                debug(TAG, 'Setup with BasicAuth');
                                return [2 /*return*/, new BasicAuth(this.tap, {
                                        hashPassword: securityOptions.hashPassword,
                                    })];
                            }
                            return [2 /*return*/];
                    }
                });
            });
        };
        return TapAuth;
    }());

    var ɵ0 = function (context) { return new TapAuth(context.tap); };
    var _TAP_EXTENSION_AUTH_ = tap.defineTapPropertyExtension('auth', ɵ0);

    // export interface TapEncryptionInterface {
    //     start();
    //     stop();
    // }

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

    exports.BasicAuth = BasicAuth;
    exports.DEFAULT_SCRAM_SALT_GENERATOR = DEFAULT_SCRAM_SALT_GENERATOR;
    exports.INITIAL_SESSION_STATE = INITIAL_SESSION_STATE;
    exports.ScramAuth = ScramAuth;
    exports.TapAuth = TapAuth;
    exports._TAP_EXTENSION_AUTH_ = _TAP_EXTENSION_AUTH_;
    exports._TAP_EXTENSION_ENCRYPTION_ = _TAP_EXTENSION_ENCRYPTION_;
    exports.hashLoginPassword = hashLoginPassword;
    exports["ɵa"] = PASSWORD_LENGTH;
    exports["ɵb"] = XOR;

    Object.defineProperty(exports, '__esModule', { value: true });

}));
//# sourceMappingURL=iotize-tap-auth.umd.js.map
