var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(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);
};
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 __generator = (this && this.__generator) || function (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 };
    }
};
import { bufferToHexString } from '@iotize/common/byte-converter';
import { AesEcb128Converter } from '@iotize/common/crypto';
import { deepCopy } from '@iotize/common/utility';
import { ServiceCallRunner, TapError, TapResponse, TapResponseStatusError } from '@iotize/tap';
import { ResultCode, } from '@iotize/tap/client/api';
import { CryptedFrameConverter, TapClientError, TapRequestHelper, TapStreamWriter } from '@iotize/tap/client/impl';
import { from, throwError } from 'rxjs';
import { map, switchMap, tap as rxTap } from 'rxjs/operators';
import { debug } from './debug';
var INITIALIZATION_VECTOR_LENGTH = 16;
var DEFAULT_INITIALIZATION_VECTOR = '00000000000000000000000000000000';
var TAG = 'ScramInterceptor';
function _hex(data) {
    return data ? 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 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 TapClientError.illegalStateError("Encryption algo has not been set yet");
            }
            return this._encryptionAlgo;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(ScramInterceptor.prototype, "ivSupported", {
        set: function (v) {
            this._ivSupported = v;
        },
        enumerable: true,
        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: true,
        configurable: true
    });
    Object.defineProperty(ScramInterceptor.prototype, "options", {
        /**
         * Get a copy of encryption options
         */
        get: function () {
            return deepCopy(this._options);
        },
        enumerable: true,
        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, "exec " + 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(map(function (response) {
                var data = response.rawBody();
                return {
                    status: response.status,
                    data: data
                };
            }));
        }
        else {
            obs = next.handle(context);
        }
        return obs
            .pipe(rxTap(function (response) {
            debug(TAG, "Response to " + TapRequestHelper.toString(request) + " => " + response.status + " " + bufferToHexString(response.data));
        }));
    };
    ScramInterceptor.prototype._sendWithEncryption = function (context, next) {
        var _this = this;
        if (!this._encryptedFrameConverter) {
            return throwError(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, "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(__assign(__assign({}, context), { skipEncryption: true }), next, call).pipe(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, "Refreshing decoding initialization vector: " + _hex(body.iv));
                _this._setDecodeIV(body.iv);
                return TapResponse.create(response.status, body.response);
            }));
        }
        else {
            if (this._options.initializationVectorResetPeriod > 1 &&
                this._ivFrameCounter >=
                    this._options.initializationVectorResetPeriod) {
                debug(TAG, "sending with iv period " + this._ivFrameCounter + "/" + this._options.initializationVectorResetPeriod);
                this._ivFrameCounter = -1;
                debug(TAG, "_ivInterceptor refreshEncryptionInitializationVector");
                callObs = 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);
                // }),
                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(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 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 = 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 = ServiceCallRunner.toTapRequest(call);
        var bodyDecoderFct = ServiceCallRunner.resolveResponseBodyDecoder(call);
        var bodyDecoder = bodyDecoderFct ? {
            decode: bodyDecoderFct
        } : undefined;
        return next
            .handle(__assign(__assign({}, context), { request: tapRequest, bodyDecoder: bodyDecoder }))
            .pipe(map(function (tapResponse) {
            return new TapResponse(tapResponse, context.request, bodyDecoder);
        }));
    };
    /**
     * Refresh initialization vectors
     */
    ScramInterceptor.prototype.refreshInitializationVectors = function () {
        return __awaiter(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, "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, "Cannot refresh initialization vector: " + err_1.message);
                        if (err_1 instanceof TapResponseStatusError) {
                            tapResponseStatus = err_1.response.status;
                            if (tapResponseStatus === ResultCode.NOT_IMPLEMENTED || tapResponseStatus === ResultCode.NOT_FOUND) {
                                debug(TAG, "Cannot initialize encryption vectors (firmware version does not support it). Error: " + err_1.message);
                                this._ivSupported = false;
                                throw TapError.initializationVectorNotSupported(err_1);
                            }
                        }
                        return [3 /*break*/, 4];
                    case 4: return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Resume encryption session
     */
    ScramInterceptor.prototype.resumeEncryption = function () {
        debug(TAG, "resume encryption");
        this._options.encryption = true;
    };
    /**
     * Pause encryption without destorying session (session keys will not be removed)
     */
    ScramInterceptor.prototype.pauseEncryption = function () {
        debug(TAG, "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(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, "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, "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 !== TapError.Code.InitializationVectorNotSupported) {
                            throw err_2;
                        }
                        return [3 /*break*/, 5];
                    case 5: return [2 /*return*/, newSessionKey];
                    case 6:
                        err_3 = _a.sent();
                        throw 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 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;
}());
export { ScramInterceptor };
//# sourceMappingURL=data:application/json;base64, 