UNPKG

matrix-react-sdk

Version:
477 lines (388 loc) 54.2 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.VoiceRecording = exports.RecordingState = exports.RECORDING_PLAYBACK_SAMPLES = exports.SAMPLE_RATE = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var Recorder = _interopRequireWildcard(require("opus-recorder")); var _encoderWorkerMin = _interopRequireDefault(require("opus-recorder/dist/encoderWorker.min.js")); var _CallMediaHandler = _interopRequireDefault(require("../CallMediaHandler")); var _matrixWidgetApi = require("matrix-widget-api"); var _numbers = require("../utils/numbers"); var _events = _interopRequireDefault(require("events")); var _Singleflight = require("../utils/Singleflight"); var _consts = require("./consts"); var _AsyncStore = require("../stores/AsyncStore"); var _Playback = require("./Playback"); var _compat = require("./compat"); /* Copyright 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ const CHANNELS = 1; // stereo isn't important const SAMPLE_RATE = 48000; // 48khz is what WebRTC uses. 12khz is where we lose quality. exports.SAMPLE_RATE = SAMPLE_RATE; const BITRATE = 24000; // 24kbps is pretty high quality for our use case in opus. const TARGET_MAX_LENGTH = 120; // 2 minutes in seconds. Somewhat arbitrary, though longer == larger files. const TARGET_WARN_TIME_LEFT = 10; // 10 seconds, also somewhat arbitrary. const RECORDING_PLAYBACK_SAMPLES = 44; /*:: export interface IRecordingUpdate { waveform: number[]; // floating points between 0 (low) and 1 (high). timeSeconds: number; // float }*/ exports.RECORDING_PLAYBACK_SAMPLES = RECORDING_PLAYBACK_SAMPLES; let RecordingState; exports.RecordingState = RecordingState; (function (RecordingState) { RecordingState["Started"] = "started"; RecordingState["EndingSoon"] = "ending_soon"; RecordingState["Ended"] = "ended"; RecordingState["Uploading"] = "uploading"; RecordingState["Uploaded"] = "uploaded"; })(RecordingState || (exports.RecordingState = RecordingState = {})); class VoiceRecording extends _events.default /*:: implements IDestroyable*/ { // use this.audioBuffer to access // at each second mark, generated constructor(client /*: MatrixClient*/ ) { super(); this.client /*:: */ = client /*:: */ ; (0, _defineProperty2.default)(this, "recorder", void 0); (0, _defineProperty2.default)(this, "recorderContext", void 0); (0, _defineProperty2.default)(this, "recorderSource", void 0); (0, _defineProperty2.default)(this, "recorderStream", void 0); (0, _defineProperty2.default)(this, "recorderFFT", void 0); (0, _defineProperty2.default)(this, "recorderWorklet", void 0); (0, _defineProperty2.default)(this, "recorderProcessor", void 0); (0, _defineProperty2.default)(this, "buffer", new Uint8Array(0)); (0, _defineProperty2.default)(this, "mxc", void 0); (0, _defineProperty2.default)(this, "recording", false); (0, _defineProperty2.default)(this, "observable", void 0); (0, _defineProperty2.default)(this, "amplitudes", []); (0, _defineProperty2.default)(this, "playback", void 0); (0, _defineProperty2.default)(this, "onAudioProcess", (ev /*: AudioProcessingEvent*/ ) => { this.processAudioUpdate(ev.playbackTime); // We skip the functionality of the worklet regarding waveform calculations: we // should get that information pretty quick during the playback info. }); (0, _defineProperty2.default)(this, "processAudioUpdate", (timeSeconds /*: number*/ ) => { if (!this.recording) return; // The time domain is the input to the FFT, which means we use an array of the same // size. The time domain is also known as the audio waveform. We're ignoring the // output of the FFT here (frequency data) because we're not interested in it. const data = new Float32Array(this.recorderFFT.fftSize); if (!this.recorderFFT.getFloatTimeDomainData) { // Safari compat const data2 = new Uint8Array(this.recorderFFT.fftSize); this.recorderFFT.getByteTimeDomainData(data2); for (let i = 0; i < data2.length; i++) { data[i] = (0, _numbers.percentageWithin)((0, _numbers.percentageOf)(data2[i], 0, 256), -1, 1); } } else { this.recorderFFT.getFloatTimeDomainData(data); } // We can't just `Array.from()` the array because we're dealing with 32bit floats // and the built-in function won't consider that when converting between numbers. // However, the runtime will convert the float32 to a float64 during the math operations // which is why the loop works below. Note that a `.map()` call also doesn't work // and will instead return a Float32Array still. const translatedData /*: number[]*/ = []; for (let i = 0; i < data.length; i++) { // We're clamping the values so we can do that math operation mentioned above, // and to ensure that we produce consistent data (it's possible for the array // to exceed the specified range with some audio input devices). translatedData.push((0, _numbers.clamp)(data[i], 0, 1)); } this.observable.update({ waveform: translatedData, timeSeconds: timeSeconds }); // Now that we've updated the data/waveform, let's do a time check. We don't want to // go horribly over the limit. We also emit a warning state if needed. // // We use the recorder's perspective of time to make sure we don't cut off the last // frame of audio, otherwise we end up with a 1:59 clip (119.68 seconds). This extra // safety can allow us to overshoot the target a bit, but at least when we say 2min // maximum we actually mean it. // // In testing, recorder time and worker time lag by about 400ms, which is roughly the // time needed to encode a sample/frame. // // Ref for recorderSeconds: https://github.com/chris-rudmin/opus-recorder#instance-fields const recorderSeconds = this.recorder.encodedSamplePosition / 48000; const secondsLeft = TARGET_MAX_LENGTH - recorderSeconds; if (secondsLeft < 0) { // go over to make sure we definitely capture that last frame // noinspection JSIgnoredPromiseFromCall - we aren't concerned with it overlapping this.stop(); } else if (secondsLeft <= TARGET_WARN_TIME_LEFT) { _Singleflight.Singleflight.for(this, "ending_soon").do(() => { this.emit(RecordingState.EndingSoon, { secondsLeft }); return _Singleflight.Singleflight.Void; }); } }); } get contentType() /*: string*/ { return "audio/ogg"; } get contentLength() /*: number*/ { return this.buffer.length; } get durationSeconds() /*: number*/ { if (!this.recorder) throw new Error("Duration not available without a recording"); return this.recorderContext.currentTime; } get isRecording() /*: boolean*/ { return this.recording; } emit(event /*: string*/ , ...args) /*: boolean*/ { super.emit(event, ...args); super.emit(_AsyncStore.UPDATE_EVENT, event, ...args); return true; // we don't ever care if the event had listeners, so just return "yes" } async makeRecorder() { try { this.recorderStream = await navigator.mediaDevices.getUserMedia({ audio: { channelCount: CHANNELS, noiseSuppression: true, // browsers ignore constraints they can't honour deviceId: _CallMediaHandler.default.getAudioInput() } }); this.recorderContext = (0, _compat.createAudioContext)({// latencyHint: "interactive", // we don't want a latency hint (this causes data smoothing) }); this.recorderSource = this.recorderContext.createMediaStreamSource(this.recorderStream); this.recorderFFT = this.recorderContext.createAnalyser(); // Bring the FFT time domain down a bit. The default is 2048, and this must be a power // of two. We use 64 points because we happen to know down the line we need less than // that, but 32 would be too few. Large numbers are not helpful here and do not add // precision: they introduce higher precision outputs of the FFT (frequency data), but // it makes the time domain less than helpful. this.recorderFFT.fftSize = 64; // Set up our worklet. We use this for timing information and waveform analysis: the // web audio API prefers this be done async to avoid holding the main thread with math. const mxRecorderWorkletPath = document.body.dataset.vectorRecorderWorkletScript; if (!mxRecorderWorkletPath) { // noinspection ExceptionCaughtLocallyJS throw new Error("Unable to create recorder: no worklet script registered"); } // Connect our inputs and outputs this.recorderSource.connect(this.recorderFFT); if (this.recorderContext.audioWorklet) { await this.recorderContext.audioWorklet.addModule(mxRecorderWorkletPath); this.recorderWorklet = new AudioWorkletNode(this.recorderContext, _consts.WORKLET_NAME); this.recorderSource.connect(this.recorderWorklet); this.recorderWorklet.connect(this.recorderContext.destination); // Dev note: we can't use `addEventListener` for some reason. It just doesn't work. this.recorderWorklet.port.onmessage = ev => { switch (ev.data['ev']) { case _consts.PayloadEvent.Timekeep: this.processAudioUpdate(ev.data['timeSeconds']); break; case _consts.PayloadEvent.AmplitudeMark: // Sanity check to make sure we're adding about one sample per second if (ev.data['forSecond'] === this.amplitudes.length) { this.amplitudes.push(ev.data['amplitude']); } break; } }; } else { // Safari fallback: use a processor node instead, buffered to 1024 bytes of data // like the worklet is. this.recorderProcessor = this.recorderContext.createScriptProcessor(1024, CHANNELS, CHANNELS); this.recorderSource.connect(this.recorderProcessor); this.recorderProcessor.connect(this.recorderContext.destination); this.recorderProcessor.addEventListener("audioprocess", this.onAudioProcess); } this.recorder = new Recorder({ encoderPath: _encoderWorkerMin.default, // magic from webpack encoderSampleRate: SAMPLE_RATE, encoderApplication: 2048, // voice (default is "audio") streamPages: true, // this speeds up the encoding process by using CPU over time encoderFrameSize: 20, // ms, arbitrary frame size we send to the encoder numberOfChannels: CHANNELS, sourceNode: this.recorderSource, encoderBitRate: BITRATE, // We use low values for the following to ease CPU usage - the resulting waveform // is indistinguishable for a voice message. Note that the underlying library will // pick defaults which prefer the highest possible quality, CPU be damned. encoderComplexity: 3, // 0-10, 10 is slow and high quality. resampleQuality: 3 // 0-10, 10 is slow and high quality }); this.recorder.ondataavailable = (a /*: ArrayBuffer*/ ) => { const buf = new Uint8Array(a); const newBuf = new Uint8Array(this.buffer.length + buf.length); newBuf.set(this.buffer, 0); newBuf.set(buf, this.buffer.length); this.buffer = newBuf; }; } catch (e) { console.error("Error starting recording: ", e); if (e instanceof DOMException) { // Unhelpful DOMExceptions are common - parse them sanely console.error(`${e.name} (${e.code}): ${e.message}`); } // Clean up as best as possible if (this.recorderStream) this.recorderStream.getTracks().forEach(t => t.stop()); if (this.recorderSource) this.recorderSource.disconnect(); if (this.recorder) this.recorder.close(); if (this.recorderContext) { // noinspection ES6MissingAwait - not important that we wait this.recorderContext.close(); } throw e; // rethrow so upstream can handle it } } get audioBuffer() /*: Uint8Array*/ { // We need a clone of the buffer to avoid accidentally changing the position // on the real thing. return this.buffer.slice(0); } get liveData() /*: SimpleObservable<IRecordingUpdate>*/ { if (!this.recording) throw new Error("No observable when not recording"); return this.observable; } get isSupported() /*: boolean*/ { return !!Recorder.isRecordingSupported(); } get hasRecording() /*: boolean*/ { return this.buffer.length > 0; } get mxcUri() /*: string*/ { if (!this.mxc) { throw new Error("Recording has not been uploaded yet"); } return this.mxc; } async start() /*: Promise<void>*/ { if (this.mxc || this.hasRecording) { throw new Error("Recording already prepared"); } if (this.recording) { throw new Error("Recording already in progress"); } if (this.observable) { this.observable.close(); } this.observable = new _matrixWidgetApi.SimpleObservable(); await this.makeRecorder(); await this.recorder.start(); this.recording = true; this.emit(RecordingState.Started); } async stop() /*: Promise<Uint8Array>*/ { return _Singleflight.Singleflight.for(this, "stop").do(async () => { if (!this.recording) { throw new Error("No recording to stop"); } // Disconnect the source early to start shutting down resources await this.recorder.stop(); // stop first to flush the last frame this.recorderSource.disconnect(); if (this.recorderWorklet) this.recorderWorklet.disconnect(); if (this.recorderProcessor) { this.recorderProcessor.disconnect(); this.recorderProcessor.removeEventListener("audioprocess", this.onAudioProcess); } // close the context after the recorder so the recorder doesn't try to // connect anything to the context (this would generate a warning) await this.recorderContext.close(); // Now stop all the media tracks so we can release them back to the user/OS this.recorderStream.getTracks().forEach(t => t.stop()); // Finally do our post-processing and clean up this.recording = false; await this.recorder.close(); this.emit(RecordingState.Ended); return this.audioBuffer; }); } /** * Gets a playback instance for this voice recording. Note that the playback will not * have been prepared fully, meaning the `prepare()` function needs to be called on it. * * The same playback instance is returned each time. * * @returns {Playback} The playback instance. */ getPlayback() /*: Playback*/ { this.playback = _Singleflight.Singleflight.for(this, "playback").do(() => { return new _Playback.Playback(this.audioBuffer.buffer, this.amplitudes); // cast to ArrayBuffer proper; }); return this.playback; } destroy() { // noinspection JSIgnoredPromiseFromCall - not concerned about stop() being called async here this.stop(); this.removeAllListeners(); _Singleflight.Singleflight.forgetAllFor(this); // noinspection JSIgnoredPromiseFromCall - not concerned about being called async here this.playback?.destroy(); this.observable.close(); } async upload() /*: Promise<string>*/ { if (!this.hasRecording) { throw new Error("No recording available to upload"); } if (this.mxc) return this.mxc; this.emit(RecordingState.Uploading); this.mxc = await this.client.uploadContent(new Blob([this.audioBuffer], { type: this.contentType }), { onlyContentUri: false // to stop the warnings in the console }).then(r => r['content_uri']); this.emit(RecordingState.Uploaded); return this.mxc; } } exports.VoiceRecording = VoiceRecording; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy92b2ljZS9Wb2ljZVJlY29yZGluZy50cyJdLCJuYW1lcyI6WyJDSEFOTkVMUyIsIlNBTVBMRV9SQVRFIiwiQklUUkFURSIsIlRBUkdFVF9NQVhfTEVOR1RIIiwiVEFSR0VUX1dBUk5fVElNRV9MRUZUIiwiUkVDT1JESU5HX1BMQVlCQUNLX1NBTVBMRVMiLCJSZWNvcmRpbmdTdGF0ZSIsIlZvaWNlUmVjb3JkaW5nIiwiRXZlbnRFbWl0dGVyIiwiY29uc3RydWN0b3IiLCJjbGllbnQiLCJVaW50OEFycmF5IiwiZXYiLCJwcm9jZXNzQXVkaW9VcGRhdGUiLCJwbGF5YmFja1RpbWUiLCJ0aW1lU2Vjb25kcyIsInJlY29yZGluZyIsImRhdGEiLCJGbG9hdDMyQXJyYXkiLCJyZWNvcmRlckZGVCIsImZmdFNpemUiLCJnZXRGbG9hdFRpbWVEb21haW5EYXRhIiwiZGF0YTIiLCJnZXRCeXRlVGltZURvbWFpbkRhdGEiLCJpIiwibGVuZ3RoIiwidHJhbnNsYXRlZERhdGEiLCJwdXNoIiwib2JzZXJ2YWJsZSIsInVwZGF0ZSIsIndhdmVmb3JtIiwicmVjb3JkZXJTZWNvbmRzIiwicmVjb3JkZXIiLCJlbmNvZGVkU2FtcGxlUG9zaXRpb24iLCJzZWNvbmRzTGVmdCIsInN0b3AiLCJTaW5nbGVmbGlnaHQiLCJmb3IiLCJkbyIsImVtaXQiLCJFbmRpbmdTb29uIiwiVm9pZCIsImNvbnRlbnRUeXBlIiwiY29udGVudExlbmd0aCIsImJ1ZmZlciIsImR1cmF0aW9uU2Vjb25kcyIsIkVycm9yIiwicmVjb3JkZXJDb250ZXh0IiwiY3VycmVudFRpbWUiLCJpc1JlY29yZGluZyIsImV2ZW50IiwiYXJncyIsIlVQREFURV9FVkVOVCIsIm1ha2VSZWNvcmRlciIsInJlY29yZGVyU3RyZWFtIiwibmF2aWdhdG9yIiwibWVkaWFEZXZpY2VzIiwiZ2V0VXNlck1lZGlhIiwiYXVkaW8iLCJjaGFubmVsQ291bnQiLCJub2lzZVN1cHByZXNzaW9uIiwiZGV2aWNlSWQiLCJDYWxsTWVkaWFIYW5kbGVyIiwiZ2V0QXVkaW9JbnB1dCIsInJlY29yZGVyU291cmNlIiwiY3JlYXRlTWVkaWFTdHJlYW1Tb3VyY2UiLCJjcmVhdGVBbmFseXNlciIsIm14UmVjb3JkZXJXb3JrbGV0UGF0aCIsImRvY3VtZW50IiwiYm9keSIsImRhdGFzZXQiLCJ2ZWN0b3JSZWNvcmRlcldvcmtsZXRTY3JpcHQiLCJjb25uZWN0IiwiYXVkaW9Xb3JrbGV0IiwiYWRkTW9kdWxlIiwicmVjb3JkZXJXb3JrbGV0IiwiQXVkaW9Xb3JrbGV0Tm9kZSIsIldPUktMRVRfTkFNRSIsImRlc3RpbmF0aW9uIiwicG9ydCIsIm9ubWVzc2FnZSIsIlBheWxvYWRFdmVudCIsIlRpbWVrZWVwIiwiQW1wbGl0dWRlTWFyayIsImFtcGxpdHVkZXMiLCJyZWNvcmRlclByb2Nlc3NvciIsImNyZWF0ZVNjcmlwdFByb2Nlc3NvciIsImFkZEV2ZW50TGlzdGVuZXIiLCJvbkF1ZGlvUHJvY2VzcyIsIlJlY29yZGVyIiwiZW5jb2RlclBhdGgiLCJlbmNvZGVyU2FtcGxlUmF0ZSIsImVuY29kZXJBcHBsaWNhdGlvbiIsInN0cmVhbVBhZ2VzIiwiZW5jb2RlckZyYW1lU2l6ZSIsIm51bWJlck9mQ2hhbm5lbHMiLCJzb3VyY2VOb2RlIiwiZW5jb2RlckJpdFJhdGUiLCJlbmNvZGVyQ29tcGxleGl0eSIsInJlc2FtcGxlUXVhbGl0eSIsIm9uZGF0YWF2YWlsYWJsZSIsImEiLCJidWYiLCJuZXdCdWYiLCJzZXQiLCJlIiwiY29uc29sZSIsImVycm9yIiwiRE9NRXhjZXB0aW9uIiwibmFtZSIsImNvZGUiLCJtZXNzYWdlIiwiZ2V0VHJhY2tzIiwiZm9yRWFjaCIsInQiLCJkaXNjb25uZWN0IiwiY2xvc2UiLCJhdWRpb0J1ZmZlciIsInNsaWNlIiwibGl2ZURhdGEiLCJpc1N1cHBvcnRlZCIsImlzUmVjb3JkaW5nU3VwcG9ydGVkIiwiaGFzUmVjb3JkaW5nIiwibXhjVXJpIiwibXhjIiwic3RhcnQiLCJTaW1wbGVPYnNlcnZhYmxlIiwiU3RhcnRlZCIsInJlbW92ZUV2ZW50TGlzdGVuZXIiLCJFbmRlZCIsImdldFBsYXliYWNrIiwicGxheWJhY2siLCJQbGF5YmFjayIsImRlc3Ryb3kiLCJyZW1vdmVBbGxMaXN0ZW5lcnMiLCJmb3JnZXRBbGxGb3IiLCJ1cGxvYWQiLCJVcGxvYWRpbmciLCJ1cGxvYWRDb250ZW50IiwiQmxvYiIsInR5cGUiLCJvbmx5Q29udGVudFVyaSIsInRoZW4iLCJyIiwiVXBsb2FkZWQiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7QUFnQkE7O0FBQ0E7O0FBRUE7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBRUE7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBNUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQWdCQSxNQUFNQSxRQUFRLEdBQUcsQ0FBakIsQyxDQUFvQjs7QUFDYixNQUFNQyxXQUFXLEdBQUcsS0FBcEIsQyxDQUEyQjs7O0FBQ2xDLE1BQU1DLE9BQU8sR0FBRyxLQUFoQixDLENBQXVCOztBQUN2QixNQUFNQyxpQkFBaUIsR0FBRyxHQUExQixDLENBQStCOztBQUMvQixNQUFNQyxxQkFBcUIsR0FBRyxFQUE5QixDLENBQWtDOztBQUUzQixNQUFNQywwQkFBMEIsR0FBRyxFQUFuQzs7QUFwQ1A7QUFDQTtBQUNBOzs7SUF5Q1lDLGM7OztXQUFBQSxjO0FBQUFBLEVBQUFBLGM7QUFBQUEsRUFBQUEsYztBQUFBQSxFQUFBQSxjO0FBQUFBLEVBQUFBLGM7QUFBQUEsRUFBQUEsYztHQUFBQSxjLDhCQUFBQSxjOztBQVFMLE1BQU1DLGNBQU4sU0FBNkJDO0FBQTdCO0FBQWtFO0FBUWpDO0FBSUQ7QUFHNUJDLEVBQUFBLFdBQVAsQ0FBMkJDO0FBQTNCO0FBQUEsSUFBaUQ7QUFDN0M7QUFENkMsU0FBdEJBO0FBQXNCO0FBQUEsTUFBdEJBO0FBQXNCO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLGtE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QyxLQUFLd0MsVUFBM0MsQ0FBUCxDQUR3RCxDQUNPO0FBQ2xFLEtBRmUsQ0FBaEI7QUFHQSxXQUFPLEtBQUsrQyxRQUFaO0FBQ0g7O0FBRU1FLEVBQUFBLE9BQVAsR0FBaUI7QUFDYjtBQUNBLFNBQUtsRyxJQUFMO0FBQ0EsU0FBS21HLGtCQUFMOztBQUNBbEcsK0JBQWFtRyxZQUFiLENBQTBCLElBQTFCLEVBSmEsQ0FLYjs7O0FBQ0EsU0FBS0osUUFBTCxFQUFlRSxPQUFmO0FBQ0EsU0FBS3pHLFVBQUwsQ0FBZ0J3RixLQUFoQjtBQUNIOztBQUVELFFBQWFvQixNQUFiO0FBQUE7QUFBdUM7QUFDbkMsUUFBSSxDQUFDLEtBQUtkLFlBQVYsRUFBd0I7QUFDcEIsWUFBTSxJQUFJNUUsS0FBSixDQUFVLGtDQUFWLENBQU47QUFDSDs7QUFFRCxRQUFJLEtBQUs4RSxHQUFULEVBQWMsT0FBTyxLQUFLQSxHQUFaO0FBRWQsU0FBS3JGLElBQUwsQ0FBVWpDLGNBQWMsQ0FBQ21JLFNBQXpCO0FBQ0EsU0FBS2IsR0FBTCxHQUFXLE1BQU0sS0FBS2xILE1BQUwsQ0FBWWdJLGFBQVosQ0FBMEIsSUFBSUMsSUFBSixDQUFTLENBQUMsS0FBS3RCLFdBQU4sQ0FBVCxFQUE2QjtBQUNwRXVCLE1BQUFBLElBQUksRUFBRSxLQUFLbEc7QUFEeUQsS0FBN0IsQ0FBMUIsRUFFYjtBQUNBbUcsTUFBQUEsY0FBYyxFQUFFLEtBRGhCLENBQ3VCOztBQUR2QixLQUZhLEVBSWRDLElBSmMsQ0FJVEMsQ0FBQyxJQUFJQSxDQUFDLENBQUMsYUFBRCxDQUpHLENBQWpCO0FBS0EsU0FBS3hHLElBQUwsQ0FBVWpDLGNBQWMsQ0FBQzBJLFFBQXpCO0FBQ0EsV0FBTyxLQUFLcEIsR0FBWjtBQUNIOztBQXhVb0UiLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuQ29weXJpZ2h0IDIwMjEgVGhlIE1hdHJpeC5vcmcgRm91bmRhdGlvbiBDLkkuQy5cblxuTGljZW5zZWQgdW5kZXIgdGhlIEFwYWNoZSBMaWNlbnNlLCBWZXJzaW9uIDIuMCAodGhlIFwiTGljZW5zZVwiKTtcbnlvdSBtYXkgbm90IHVzZSB0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS5cbllvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdFxuXG4gICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wXG5cblVubGVzcyByZXF1aXJlZCBieSBhcHBsaWNhYmxlIGxhdyBvciBhZ3JlZWQgdG8gaW4gd3JpdGluZywgc29mdHdhcmVcbmRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuIFwiQVMgSVNcIiBCQVNJUyxcbldJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLlxuU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZFxubGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuXG4qL1xuXG5pbXBvcnQgKiBhcyBSZWNvcmRlciBmcm9tICdvcHVzLXJlY29yZGVyJztcbmltcG9ydCBlbmNvZGVyUGF0aCBmcm9tICdvcHVzLXJlY29yZGVyL2Rpc3QvZW5jb2Rlcldvcmtlci5taW4uanMnO1xuaW1wb3J0IHtNYXRyaXhDbGllbnR9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy9jbGllbnRcIjtcbmltcG9ydCBDYWxsTWVkaWFIYW5kbGVyIGZyb20gXCIuLi9DYWxsTWVkaWFIYW5kbGVyXCI7XG5pbXBvcnQge1NpbXBsZU9ic2VydmFibGV9IGZyb20gXCJtYXRyaXgtd2lkZ2V0LWFwaVwiO1xuaW1wb3J0IHtjbGFtcCwgcGVyY2VudGFnZU9mLCBwZXJjZW50YWdlV2l0aGlufSBmcm9tIFwiLi4vdXRpbHMvbnVtYmVyc1wiO1xuaW1wb3J0IEV2ZW50RW1pdHRlciBmcm9tIFwiZXZlbnRzXCI7XG5pbXBvcnQge0lEZXN0cm95YWJsZX0gZnJvbSBcIi4uL3V0aWxzL0lEZXN0cm95YWJsZVwiO1xuaW1wb3J0IHtTaW5nbGVmbGlnaHR9IGZyb20gXCIuLi91dGlscy9TaW5nbGVmbGlnaHRcIjtcbmltcG9ydCB7UGF5bG9hZEV2ZW50LCBXT1JLTEVUX05BTUV9IGZyb20gXCIuL2NvbnN0c1wiO1xuaW1wb3J0IHtVUERBVEVfRVZFTlR9IGZyb20gXCIuLi9zdG9yZXMvQXN5bmNTdG9yZVwiO1xuaW1wb3J0IHtQbGF5YmFja30gZnJvbSBcIi4vUGxheWJhY2tcIjtcbmltcG9ydCB7Y3JlYXRlQXVkaW9Db250ZXh0fSBmcm9tIFwiLi9jb21wYXRcIjtcblxuY29uc3QgQ0hBTk5FTFMgPSAxOyAvLyBzdGVyZW8gaXNuJ3QgaW1wb3J0YW50XG5leHBvcnQgY29uc3QgU0FNUExFX1JBVEUgPSA0ODAwMDsgLy8gNDhraHogaXMgd2hhdCBXZWJSVEMgdXNlcy4gMTJraHogaXMgd2hlcmUgd2UgbG9zZSBxdWFsaXR5LlxuY29uc3QgQklUUkFURSA9IDI0MDAwOyAvLyAyNGticHMgaXMgcHJldHR5IGhpZ2ggcXVhbGl0eSBmb3Igb3VyIHVzZSBjYXNlIGluIG9wdXMuXG5jb25zdCBUQVJHRVRfTUFYX0xFTkdUSCA9IDEyMDsgLy8gMiBtaW51dGVzIGluIHNlY29uZHMuIFNvbWV3aGF0IGFyYml0cmFyeSwgdGhvdWdoIGxvbmdlciA9PSBsYXJnZXIgZmlsZXMuXG5jb25zdCBUQVJHRVRfV0FSTl9USU1FX0xFRlQgPSAxMDsgLy8gMTAgc2Vjb25kcywgYWxzbyBzb21ld2hhdCBhcmJpdHJhcnkuXG5cbmV4cG9ydCBjb25zdCBSRUNPUkRJTkdfUExBWUJBQ0tfU0FNUExFUyA9IDQ0O1xuXG5leHBvcnQgaW50ZXJmYWNlIElSZWNvcmRpbmdVcGRhdGUge1xuICAgIHdhdmVmb3JtOiBudW1iZXJbXTsgLy8gZmxvYXRpbmcgcG9pbnRzIGJldHdlZW4gMCAobG93KSBhbmQgMSAoaGlnaCkuXG4gICAgdGltZVNlY29uZHM6IG51bWJlcjsgLy8gZmxvYXRcbn1cblxuZXhwb3J0IGVudW0gUmVjb3JkaW5nU3RhdGUge1xuICAgIFN0YXJ0ZWQgPSBcInN0YXJ0ZWRcIixcbiAgICBFbmRpbmdTb29uID0gXCJlbmRpbmdfc29vblwiLCAvLyBlbWl0cyBhbiBvYmplY3Qgd2l0aCBhIHNpbmdsZSBudW1lcmljYWwgdmFsdWU6IHNlY29uZHNMZWZ0XG4gICAgRW5kZWQgPSBcImVuZGVkXCIsXG4gICAgVXBsb2FkaW5nID0gXCJ1cGxvYWRpbmdcIixcbiAgICBVcGxvYWRlZCA9IFwidXBsb2FkZWRcIixcbn1cblxuZXhwb3J0IGNsYXNzIFZvaWNlUmVjb3JkaW5nIGV4dGVuZHMgRXZlbnRFbWl0dGVyIGltcGxlbWVudHMgSURlc3Ryb3lhYmxlIHtcbiAgICBwcml2YXRlIHJlY29yZGVyOiBSZWNvcmRlcjtcbiAgICBwcml2YXRlIHJlY29yZGVyQ29udGV4dDogQXVkaW9Db250ZXh0O1xuICAgIHByaXZhdGUgcmVjb3JkZXJTb3VyY2U6IE1lZGlhU3RyZWFtQXVkaW9Tb3VyY2VOb2RlO1xuICAgIHByaXZhdGUgcmVjb3JkZXJTdHJlYW06IE1lZGlhU3RyZWFtO1xuICAgIHByaXZhdGUgcmVjb3JkZXJGRlQ6IEFuYWx5c2VyTm9kZTtcbiAgICBwcml2YXRlIHJlY29yZGVyV29ya2xldDogQXVkaW9Xb3JrbGV0Tm9kZTtcbiAgICBwcml2YXRlIHJlY29yZGVyUHJvY2Vzc29yOiBTY3JpcHRQcm9jZXNzb3JOb2RlO1xuICAgIHByaXZhdGUgYnVmZmVyID0gbmV3IFVpbnQ4QXJyYXkoMCk7IC8vIHVzZSB0aGlzLmF1ZGlvQnVmZmVyIHRvIGFjY2Vzc1xuICAgIHByaXZhdGUgbXhjOiBzdHJpbmc7XG4gICAgcHJpdmF0ZSByZWNvcmRpbmcgPSBmYWxzZTtcbiAgICBwcml2YXRlIG9ic2VydmFibGU6IFNpbXBsZU9ic2VydmFibGU8SVJlY29yZGluZ1VwZGF0ZT47XG4gICAgcHJpdmF0ZSBhbXBsaXR1ZGVzOiBudW1iZXJbXSA9IFtdOyAvLyBhdCBlYWNoIHNlY29uZCBtYXJrLCBnZW5lcmF0ZWRcbiAgICBwcml2YXRlIHBsYXliYWNrOiBQbGF5YmFjaztcblxuICAgIHB1YmxpYyBjb25zdHJ1Y3Rvcihwcml2YXRlIGNsaWVudDogTWF0cml4Q2xpZW50KSB7XG4gICAgICAgIHN1cGVyKCk7XG4gICAgfVxuXG4gICAgcHVibGljIGdldCBjb250ZW50VHlwZSgpOiBzdHJpbmcge1xuICAgICAgICByZXR1cm4gXCJhdWRpby9vZ2dcIjtcbiAgICB9XG5cbiAgICBwdWJsaWMgZ2V0IGNvbnRlbnRMZW5ndGgoKTogbnVtYmVyIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuYnVmZmVyLmxlbmd0aDtcbiAgICB9XG5cbiAgICBwdWJsaWMgZ2V0IGR1cmF0aW9uU2Vjb25kcygpOiBudW1iZXIge1xuICAgICAgICBpZiAoIXRoaXMucmVjb3JkZXIpIHRocm93IG5ldyBFcnJvcihcIkR1cmF0aW9uIG5vdCBhdmFpbGFibGUgd2l0aG91dCBhIHJlY29yZGluZ1wiKTtcbiAgICAgICAgcmV0dXJuIHRoaXMucmVjb3JkZXJDb250ZXh0LmN1cnJlbnRUaW1lO1xuICAgIH1cblxuICAgIHB1YmxpYyBnZXQgaXNSZWNvcmRpbmcoKTogYm9vbGVhbiB7XG4gICAgICAgIHJldHVybiB0aGlzLnJlY29yZGluZztcbiAgICB9XG5cbiAgICBwdWJsaWMgZW1pdChldmVudDogc3RyaW5nLCAuLi5hcmdzOiBhbnlbXSk6IGJvb2xlYW4ge1xuICAgICAgICBzdXBlci5lbWl0KGV2ZW50LCAuLi5hcmdzKTtcbiAgICAgICAgc3VwZXIuZW1pdChVUERBVEVfRVZFTlQsIGV2ZW50LCAuLi5hcmdzKTtcbiAgICAgICAgcmV0dXJuIHRydWU7IC8vIHdlIGRvbid0IGV2ZXIgY2FyZSBpZiB0aGUgZXZlbnQgaGFkIGxpc3RlbmVycywgc28ganVzdCByZXR1cm4gXCJ5ZXNcIlxuICAgIH1cblxuICAgIHByaXZhdGUgYXN5bmMgbWFrZVJlY29yZGVyKCkge1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgdGhpcy5yZWNvcmRlclN0cmVhbSA9IGF3YWl0IG5hdmlnYXRvci5tZWRpYURldmljZXMuZ2V0VXNlck1lZGlhKHtcbiAgICAgICAgICAgICAgICBhdWRpbzoge1xuICAgICAgICAgICAgICAgICAgICBjaGFubmVsQ291bnQ6IENIQU5ORUxTLFxuICAgICAgICAgICAgICAgICAgICBub2lzZVN1cHByZXNzaW9uOiB0cnVlLCAvLyBicm93c2VycyBpZ25vcmUgY29uc3RyYWludHMgdGhleSBjYW4ndCBob25vdXJcbiAgICAgICAgICAgICAgICAgICAgZGV2aWNlSWQ6IENhbGxNZWRpYUhhbmRsZXIuZ2V0QXVkaW9JbnB1dCgpLFxuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIHRoaXMucmVjb3JkZXJDb250ZXh0ID0gY3JlYXRlQXVkaW9Db250ZXh0KHtcbiAgICAgICAgICAgICAgICAvLyBsYXRlbmN5SGludDogXCJpbnRlcmFjdGl2ZVwiLCAvLyB3ZSBkb24ndCB3YW50IGEgbGF0ZW5jeSBoaW50ICh0aGlzIGNhdXNlcyBkYXRhIHNtb290aGluZylcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgdGhpcy5yZWNvcmRlclNvdXJjZSA9IHRoaXMucmVjb3JkZXJDb250ZXh0LmNyZWF0ZU1lZGlhU3RyZWFtU291cmNlKHRoaXMucmVjb3JkZXJTdHJlYW0pO1xuICAgICAgICAgICAgdGhpcy5yZWNvcmRlckZGVCA9IHRoaXMucmVjb3JkZXJDb250ZXh0LmNyZWF0ZUFuYWx5c2VyKCk7XG5cbiAgICAgICAgICAgIC8vIEJyaW5nIHRoZSBGRlQgdGltZSBkb21haW4gZG93biBhIGJpdC4gVGhlIGRlZmF1bHQgaXMgMjA0OCwgYW5kIHRoaXMgbXVzdCBiZSBhIHBvd2VyXG4gICAgICAgICAgICAvLyBvZiB0d28uIFdlIHVzZSA2NCBwb2ludHMgYmVjYXVzZSB3ZSBoYXBwZW4gdG8ga25vdyBkb3duIHRoZSBsaW5lIHdlIG5lZWQgbGVzcyB0aGFuXG4gICAgICAgICAgICAvLyB0aGF0LCBidXQgMzIgd291bGQgYmUgdG9vIGZldy4gTGFyZ2UgbnVtYmVycyBhcmUgbm90IGhlbHBmdWwgaGVyZSBhbmQgZG8gbm90IGFkZFxuICAgICAgICAgICAgLy8gcHJlY2lzaW9uOiB0aGV5IGludHJvZHVjZSBoaWdoZXIgcHJlY2lzaW9uIG91dHB1dHMgb2YgdGhlIEZGVCAoZnJlcXVlbmN5IGRhdGEpLCBidXRcbiAgICAgICAgICAgIC8vIGl0IG1ha2VzIHRoZSB0aW1lIGRvbWFpbiBsZXNzIHRoYW4gaGVscGZ1bC5cbiAgICAgICAgICAgIHRoaXMucmVjb3JkZXJGRlQuZmZ0U2l6ZSA9IDY0O1xuXG4gICAgICAgICAgICAvLyBTZXQgdXAgb3VyIHdvcmtsZXQuIFdlIHVzZSB0aGlzIGZvciB0aW1pbmcgaW5mb3JtYXRpb24gYW5kIHdhdmVmb3JtIGFuYWx5c2lzOiB0aGVcbiAgICAgICAgICAgIC8vIHdlYiBhdWRpbyBBUEkgcHJlZmVycyB0aGlzIGJlIGRvbmUgYXN5bmMgdG8gYXZvaWQgaG9sZGluZyB0aGUgbWFpbiB0aHJlYWQgd2l0aCBtYXRoLlxuICAgICAgICAgICAgY29uc3QgbXhSZWNvcmRlcldvcmtsZXRQYXRoID0gZG9jdW1lbnQuYm9keS5kYXRhc2V0LnZlY3RvclJlY29yZGVyV29ya2xldFNjcmlwdDtcbiAgICAgICAgICAgIGlmICghbXhSZWNvcmRlcldvcmtsZXRQYXRoKSB7XG4gICAgICAgICAgICAgICAgLy8gbm9pbnNwZWN0aW9uIEV4Y2VwdGlvbkNhdWdodExvY2FsbHlKU1xuICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcIlVuYWJsZSB0byBjcmVhdGUgcmVjb3JkZXI6IG5vIHdvcmtsZXQgc2NyaXB0IHJlZ2lzdGVyZWRcIik7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIENvbm5lY3Qgb3VyIGlucHV0cyBhbmQgb3V0cHV0c1xuICAgICAgICAgICAgdGhpcy5yZWNvcmRlclNvdXJjZS5jb25uZWN0KHRoaXMucmVjb3JkZXJGRlQpO1xuXG4gICAgICAgICAgICBpZiAodGhpcy5yZWNvcmRlckNvbnRleHQuYXVkaW9Xb3JrbGV0KSB7XG4gICAgICAgICAgICAgICAgYXdhaXQgdGhpcy5yZWNvcmRlckNvbnRleHQuYXVkaW9Xb3JrbGV0LmFkZE1vZHVsZShteFJlY29yZGVyV29ya2xldFBhdGgpO1xuICAgICAgICAgICAgICAgIHRoaXMucmVjb3JkZXJXb3JrbGV0ID0gbmV3IEF1ZGlvV29ya2xldE5vZGUodGhpcy5yZWNvcmRlckNvbnRleHQsIFdPUktMRVRfTkFNRSk7XG4gICAgICAgICAgICAgICAgdGhpcy5yZWNvcmRlclNvdXJjZS5jb25uZWN0KHRoaXMucmVjb3JkZXJXb3JrbGV0KTtcbiAgICAgICAgICAgICAgICB0aGlzLnJlY29yZGVyV29ya2xldC5jb25uZWN0KHRoaXMucmVjb3JkZXJDb250ZXh0LmRlc3RpbmF0aW9uKTtcblxuICAgICAgICAgICAgICAgIC8vIERldiBub3RlOiB3ZSBjYW4ndCB1c2UgYGFkZEV2ZW50TGlzdGVuZXJgIGZvciBzb21lIHJlYXNvbi4gSXQganVzdCBkb2Vzbid0IHdvcmsuXG4gICAgICAgICAgICAgICAgdGhpcy5yZWNvcmRlcldvcmtsZXQucG9ydC5vbm1lc3NhZ2UgPSAoZXYpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgc3dpdGNoIChldi5kYXRhWydldiddKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBjYXNlIFBheWxvYWRFdmVudC5UaW1la2VlcDpcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnByb2Nlc3NBdWRpb1VwZGF0ZShldi5kYXRhWyd0aW1lU2Vjb25kcyddKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICAgICAgICAgIGNhc2UgUGF5bG9hZEV2ZW50LkFtcGxpdHVkZU1hcms6XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gU2FuaXR5IGNoZWNrIHRvIG1ha2Ugc3VyZSB3ZSdyZSBhZGRpbmcgYWJvdXQgb25lIHNhbXBsZSBwZXIgc2Vjb25kXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGV2LmRhdGFbJ2ZvclNlY29uZCddID09PSB0aGlzLmFtcGxpdHVkZXMubGVuZ3RoKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuYW1wbGl0dWRlcy5wdXNoKGV2LmRhdGFbJ2FtcGxpdHVkZSddKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAvLyBTYWZhcmkgZmFsbGJhY2s6IHVzZSBhIHByb2Nlc3NvciBub2RlIGluc3RlYWQsIGJ1ZmZlcmVkIHRvIDEwMjQgYnl0ZXMgb2YgZGF0YVxuICAgICAgICAgICAgICAgIC8vIGxpa2UgdGhlIHdvcmtsZXQgaXMuXG4gICAgICAgICAgICAgICAgdGhpcy5yZWNvcmRlclByb2Nlc3NvciA9IHRoaXMucmVjb3JkZXJDb250ZXh0LmNyZWF0ZVNjcmlwdFByb2Nlc3NvcigxMDI0LCBDSEFOTkVMUywgQ0hBTk5FTFMpO1xuICAgICAgICAgICAgICAgIHRoaXMucmVjb3JkZXJTb3VyY2UuY29ubmVjdCh0aGlzLnJlY29yZGVyUHJvY2Vzc29yKTtcbiAgICAgICAgICAgICAgICB0aGlzLnJlY29yZGVyUHJvY2Vzc29yLmNvbm5lY3QodGhpcy5yZWNvcmRlckNvbnRleHQuZGVzdGluYXRpb24pO1xuICAgICAgICAgICAgICAgIHRoaXMucmVjb3JkZXJQcm9jZXNzb3IuYWRkRXZlbnRMaXN0ZW5lcihcImF1ZGlvcHJvY2Vzc1wiLCB0aGlzLm9uQXVkaW9Qcm9jZXNzKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdGhpcy5yZWNvcmRlciA9IG5ldyBSZWNvcmRlcih7XG4gICAgICAgICAgICAgICAgZW5jb2RlclBhdGgsIC8vIG1hZ2ljIGZyb20gd2VicGFja1xuICAgICAgICAgICAgICAgIGVuY29kZXJTYW1wbGVSYXRlOiBTQU1QTEVfUkFURSxcbiAgICAgICAgICAgICAgICBlbmNvZGVyQXBwbGljYXRpb246IDIwNDgsIC8vIHZvaWNlIChkZWZhdWx0IGlzIFwiYXVkaW9cIilcbiAgICAgICAgICAgICAgICBzdHJlYW1QYWdlczogdHJ1ZSwgLy8gdGhpcyBzcGVlZHMgdXAgdGhlIGVuY29kaW5nIHByb2Nlc3MgYnkgdXNpbmcgQ1BVIG92ZXIgdGltZVxuICAgICAgICAgICAgICAgIGVuY29kZXJGcmFtZVNpemU6IDIwLCAvLyBtcywgYXJiaXRyYXJ5IGZyYW1lIHNpemUgd2Ugc2VuZCB0byB0aGUgZW5jb2RlclxuICAgICAgICAgICAgICAgIG51bWJlck9mQ2hhbm5lbHM6IENIQU5ORUxTLFxuICAgICAgICAgICAgICAgIHNvdXJjZU5vZGU6IHRoaXMucmVjb3JkZXJTb3VyY2UsXG4gICAgICAgICAgICAgICAgZW5jb2RlckJpdFJhdGU6IEJJVFJBVEUsXG5cbiAgICAgICAgICAgICAgICAvLyBXZSB1c2UgbG93IHZhbHVlcyBmb3IgdGhlIGZvbGxvd2luZyB0byBlYXNlIENQVSB1c2FnZSAtIHRoZSByZXN1bHRpbmcgd2F2ZWZvcm1cbiAgICAgICAgICAgICAgICAvLyBpcyBpbmRpc3Rpbmd1aXNoYWJsZSBmb3IgYSB2b2ljZSBtZXNzYWdlLiBOb3RlIHRoYXQgdGhlIHVuZGVybHlpbmcgbGlicmFyeSB3aWxsXG4gICAgICAgICAgICAgICAgLy8gcGljayBkZWZhdWx0cyB3aGljaCBwcmVmZXIgdGhlIGhpZ2hlc3QgcG9zc2libGUgcXVhbGl0eSwgQ1BVIGJlIGRhbW5lZC5cbiAgICAgICAgICAgICAgICBlbmNvZGVyQ29tcGxleGl0eTogMywgLy8gMC0xMCwgMTAgaXMgc2xvdyBhbmQgaGlnaCBxdWFsaXR5LlxuICAgICAgICAgICAgICAgIHJlc2FtcGxlUXVhbGl0eTogMywgLy8gMC0xMCwgMTAgaXMgc2xvdyBhbmQgaGlnaCBxdWFsaXR5XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIHRoaXMucmVjb3JkZXIub25kYXRhYXZhaWxhYmxlID0gKGE6IEFycmF5QnVmZmVyKSA9PiB7XG4gICAgICAgICAgICAgICAgY29uc3QgYnVmID0gbmV3IFVpbnQ4QXJyYXkoYSk7XG4gICAgICAgICAgICAgICAgY29uc3QgbmV3QnVmID0gbmV3IFVpbnQ4QXJyYXkodGhpcy5idWZmZXIubGVuZ3RoICsgYnVmLmxlbmd0aCk7XG4gICAgICAgICAgICAgICAgbmV3QnVmLnNldCh0aGlzLmJ1ZmZlciwgMCk7XG4gICAgICAgICAgICAgICAgbmV3QnVmLnNldChidWYsIHRoaXMuYnVmZmVyLmxlbmd0aCk7XG4gICAgICAgICAgICAgICAgdGhpcy5idWZmZXIgPSBuZXdCdWY7XG4gICAgICAgICAgICB9O1xuICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgICBjb25zb2xlLmVycm9yKFwiRXJyb3Igc3RhcnRpbmcgcmVjb3JkaW5nOiBcIiwgZSk7XG4gICAgICAgICAgICBpZiAoZSBpbnN0YW5jZW9mIERPTUV4Y2VwdGlvbikgeyAvLyBVbmhlbHBmdWwgRE9NRXhjZXB0aW9ucyBhcmUgY29tbW9uIC0gcGFyc2UgdGhlbSBzYW5lbHlcbiAgICAgICAgICAgICAgICBjb25zb2xlLmVycm9yKGAke2UubmFtZX0gKCR7ZS5jb2RlfSk6ICR7ZS5tZXNzYWdlfWApO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBDbGVhbiB1cCBhcyBiZXN0IGFzIHBvc3NpYmxlXG4gICAgICAgICAgICBpZiAodGhpcy5yZWNvcmRlclN0cmVhbSkgdGhpcy5yZWNvcmRlclN0cmVhbS5nZXRUcmFja3MoKS5mb3JFYWNoKHQgPT4gdC5zdG9wKCkpO1xuICAgICAgICAgICAgaWYgKHRoaXMucmVjb3JkZXJTb3VyY2UpIHRoaXMucmVjb3JkZXJTb3VyY2UuZGlzY29ubmVjdCgpO1xuICAgICAgICAgICAgaWYgKHRoaXMucmVjb3JkZXIpIHRoaXMucmVjb3JkZXIuY2xvc2UoKTtcbiAgICAgICAgICAgIGlmICh0aGlzLnJlY29yZGVyQ29udGV4dCkge1xuICAgICAgICAgICAgICAgIC8vIG5vaW5zcGVjdGlvbiBFUzZNaXNzaW5nQXdhaXQgLSBub3QgaW1wb3J0YW50IHRoYXQgd2Ugd2FpdFxuICAgICAgICAgICAgICAgIHRoaXMucmVjb3JkZXJDb250ZXh0LmNsb3NlKCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRocm93IGU7IC8vIHJldGhyb3cgc28gdXBzdHJlYW0gY2FuIGhhbmRsZSBpdFxuICAgICAgICB9XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBnZXQgYXVkaW9CdWZmZXIoKTogVWludDhBcnJheSB7XG4gICAgICAgIC8vIFdlIG5lZWQgYSBjbG9uZSBvZiB0aGUgYnVmZmVyIHRvIGF2b2lkIGFjY2lkZW50YWxseSBjaGFuZ2luZyB0aGUgcG9zaXRpb25cbiAgICAgICAgLy8gb24gdGhlIHJlYWwgdGhpbmcuXG4gICAgICAgIHJldHVybiB0aGlzLmJ1ZmZlci5zbGljZSgwKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgZ2V0IGxpdmVEYXRhKCk6IFNpbXBsZU9ic2VydmFibGU8SVJlY29yZGluZ1VwZGF0ZT4ge1xuICAgICAgICBpZiAoIXRoaXMucmVjb3JkaW5nKSB0aHJvdyBuZXcgRXJyb3IoXCJObyBvYnNlcnZhYmxlIHdoZW4gbm90IHJlY29yZGluZ1wiKTtcbiAgICAgICAgcmV0dXJuIHRoaXMub2JzZXJ2YWJsZTtcbiAgICB9XG5cbiAgICBwdWJsaWMgZ2V0IGlzU3VwcG9ydGVkKCk6IGJvb2xlYW4ge1xuICAgICAgICByZXR1cm4gISFSZWNvcmRlci5pc1JlY29yZGluZ1N1cHBvcnRlZCgpO1xuICAgIH1cblxuICAgIHB1YmxpYyBnZXQgaGFzUmVjb3JkaW5nKCk6IGJvb2xlYW4ge1xuICAgICAgICByZXR1cm4gdGhpcy5idWZmZXIubGVuZ3RoID4gMDtcbiAgICB9XG5cbiAgICBwdWJsaWMgZ2V0IG14Y1VyaSgpOiBzdHJpbmcge1xuICAgICAgICBpZiAoIXRoaXMubXhjKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJSZWNvcmRpbmcgaGFzIG5vdCBiZWVuIHVwbG9hZGVkIHlldFwiKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5teGM7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBvbkF1ZGlvUHJvY2VzcyA9IChldjogQXVkaW9Qcm9jZXNzaW5nRXZlbnQpID0+IHtcbiAgICAgICAgdGhpcy5wcm9jZXNzQXVkaW9VcGRhdGUoZXYucGxheWJhY2tUaW1lKTtcblxuICAgICAgICAvLyBXZSBza2lwIHRoZSBmdW5jdGlvbmFsaXR5IG9mIHRoZSB3b3JrbGV0IHJlZ2FyZGluZyB3YXZlZm9ybSBjYWxjdWxhdGlvbnM6IHdlXG4gICAgICAgIC8vIHNob3VsZCBnZXQgdGhhdCBpbmZvcm1hdGlvbiBwcmV0dHkgcXVpY2sgZHVyaW5nIHRoZSBwbGF5YmFjayBpbmZvLlxuICAgIH07XG5cbiAgICBwcml2YXRlIHByb2Nlc3NBdWRpb1VwZGF0ZSA9ICh0aW1lU2Vjb25kczogbnVtYmVyKSA9PiB7XG4gICAgICAgIGlmICghdGhpcy5yZWNvcmRpbmcpIHJldHVybjtcblxuICAgICAgICAvLyBUaGUgdGltZSBkb21haW4gaXMgdGhlIGlucHV0IHRvIHRoZSBGRlQsIHdoaWNoIG1lYW5zIHdlIHVzZSBhbiBhcnJheSBvZiB0aGUgc2FtZVxuICAgICAgICAvLyBzaXplLiBUaGUgdGltZSBkb21haW4gaXMgYWxzbyBrbm93biBhcyB0aGUgYXVkaW8gd2F2ZWZvcm0uIFdlJ3JlIGlnbm9yaW5nIHRoZVxuICAgICAgICAvLyBvdXRwdXQgb2YgdGhlIEZGVCBoZXJlIChmcmVxdWVuY3kgZGF0YSkgYmVjYXVzZSB3ZSdyZSBub3QgaW50ZXJlc3RlZCBpbiBpdC5cbiAgICAgICAgY29uc3QgZGF0YSA9IG5ldyBGbG9hdDMyQXJyYXkodGhpcy5yZWNvcmRlckZGVC5mZnRTaXplKTtcbiAgICAgICAgaWYgKCF0aGlzLnJlY29yZGVyRkZULmdldEZsb2F0VGltZURvbWFpbkRhdGEpIHtcbiAgICAgICAgICAgIC8vIFNhZmFyaSBjb21wYXRcbiAgICAgICAgICAgIGNvbnN0IGRhdGEyID0gbmV3IFVpbnQ4QXJyYXkodGhpcy5yZWNvcmRlckZGVC5mZnRTaXplKTtcbiAgICAgICAgICAgIHRoaXMucmVjb3JkZXJGRlQuZ2V0Qnl0ZVRpbWVEb21haW5EYXRhKGRhdGEyKTtcbiAgICAgICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgZGF0YTIubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICBkYXRhW2ldID0gcGVyY2VudGFnZVdpdGhpbihwZXJjZW50YWdlT2YoZGF0YTJbaV0sIDAsIDI1NiksIC0xLCAxKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMucmVjb3JkZXJGRlQuZ2V0RmxvYXRUaW1lRG9tYWluRGF0YShkYXRhKTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFdlIGNhbid0IGp1c3QgYEFycmF5LmZyb20oKWAgdGhlIGFycmF5IGJlY2F1c2Ugd2UncmUgZGVhbGluZyB3aXRoIDMyYml0IGZsb2F0c1xuICAgICAgICAvLyBhbmQgdGhlIGJ1aWx0LWluIGZ1bmN0aW9uIHdvbid0IGNvbnNpZGVyIHRoYXQgd2hlbiBjb252ZXJ0aW5nIGJldHdlZW4gbnVtYmVycy5cbiAgICAgICAgLy8gSG93ZXZlciwgdGhlIHJ1bnRpbWUgd2lsbCBjb252ZXJ0IHRoZSBmbG9hdDMyIHRvIGEgZmxvYXQ2NCBkdXJpbmcgdGhlIG1hdGggb3BlcmF0aW9uc1xuICAgICAgICAvLyB3aGljaCBpcyB3aHkgdGhlIGxvb3Agd29ya3MgYmVsb3cuIE5vdGUgdGhhdCBhIGAubWFwKClgIGNhbGwgYWxzbyBkb2Vzbid0IHdvcmtcbiAgICAgICAgLy8gYW5kIHdpbGwgaW5zdGVhZCByZXR1cm4gYSBGbG9hdDMyQXJyYXkgc3RpbGwuXG4gICAgICAgIGNvbnN0IHRyYW5zbGF0ZWREYXRhOiBudW1iZXJbXSA9IFtdO1xuICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IGRhdGEubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIC8vIFdlJ3JlIGNsYW1waW5nIHRoZSB2YWx1ZXMgc28gd2UgY2FuIGRvIHRoYXQgbWF0aCBvcGVyYXRpb24gbWVudGlvbmVkIGFib3ZlLFxuICAgICAgICAgICAgLy8gYW5kIHRvIGVuc3VyZSB0aGF0IHdlIHByb2R1Y2UgY29uc2lzdGVudCBkYXRhIChpdCdzIHBvc3NpYmxlIGZvciB0aGUgYXJyYXlcbiAgICAgICAgICAgIC8vIHRvIGV4Y2VlZCB0aGUgc3BlY2lmaWVkIHJhbmdlIHdpdGggc29tZSBhdWRpbyBpbnB1dCBkZXZpY2VzKS5cbiAgICAgICAgICAgIHRyYW5zbGF0ZWREYXRhLnB1c2goY2xhbXAoZGF0YVtpXSwgMCwgMSkpO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5vYnNlcnZhYmxlLnVwZGF0ZSh7XG4gICAgICAgICAgICB3YXZlZm9ybTogdHJhbnNsYXRlZERhdGEsXG4gICAgICAgICAgICB0aW1lU2Vjb25kczogdGltZVNlY29uZHMsXG4gICAgICAgIH0pO1xuXG4gICAgICAgIC8vIE5vdyB0aGF0IHdlJ3ZlIHVwZGF0ZWQgdGhlIGRhdGEvd2F2ZWZvcm0sIGxldCdzIGRvIGEgdGltZSBjaGVjay4gV2UgZG9uJ3Qgd2FudCB0b1xuICAgICAgICAvLyBnbyBob3JyaWJseSBvdmVyIHRoZSBsaW1pdC4gV2UgYWxzbyBlbWl0IGEgd2FybmluZyBzdGF0ZSBpZiBuZWVkZWQuXG4gICAgICAgIC8vXG4gICAgICAgIC8vIFdlIHVzZSB0aGUgcmVjb3JkZXIncyBwZXJzcGVjdGl2ZSBvZiB0aW1lIHRvIG1ha2Ugc3VyZSB3ZSBkb24ndCBjdXQgb2ZmIHRoZSBsYXN0XG4gICAgICAgIC8vIGZyYW1lIG9mIGF1ZGlvLCBvdGhlcndpc2Ugd2UgZW5kIHVwIHdpdGggYSAxOjU5IGNsaXAgKDExOS42OCBzZWNvbmRzKS4gVGhpcyBleHRyYVxuICAgICAgICAvLyBzYWZldHkgY2FuIGFsbG93IHVzIHRvIG92ZXJzaG9vdCB0aGUgdGFyZ2V0IGEgYml0LCBidXQgYXQgbGVhc3Qgd2hlbiB3ZSBzYXkgMm1pblxuICAgICAgICAvLyBtYXhpbXVtIHdlIGFjdHVhbGx5IG1lYW4gaXQuXG4gICAgICAgIC8vXG4gICAgICAgIC8vIEluIHRlc3RpbmcsIHJlY29yZGVyIHRpbWUgYW5kIHdvcmtlciB0aW1lIGxhZyBieSBhYm91dCA0MDBtcywgd2hpY2ggaXMgcm91Z2hseSB0aGVcbiAgICAgICAgLy8gdGltZSBuZWVkZWQgdG8gZW5jb2RlIGEgc2FtcGxlL2ZyYW1lLlxuICAgICAgICAvL1xuICAgICAgICAvLyBSZWYgZm9yIHJlY29yZGVyU2Vjb25kczogaHR0cHM6Ly9naXRodWIuY29tL2NocmlzLXJ1ZG1pbi9vcHVzLXJlY29yZGVyI2luc3RhbmNlLWZpZWxkc1xuICAgICAgICBjb25zdCByZWNvcmRlclNlY29uZHMgPSB0aGlzLnJlY29yZGVyLmVuY29kZWRTYW1wbGVQb3NpdGlvbiAvIDQ4MDAwO1xuICAgICAgICBjb25zdCBzZWNvbmRzTGVmdCA9IFRBUkdFVF9NQVhfTEVOR1RIIC0gcmVjb3JkZXJTZWNvbmRzO1xuICAgICAgICBpZiAoc2Vjb25kc0xlZnQgPCAwKSB7IC8vIGdvIG92ZXIgdG8gbWFrZSBzdXJlIHdlIGRlZmluaXRlbHkgY2FwdHVyZSB0aGF0IGxhc3QgZnJhbWVcbiAgICAgICAgICAgIC8vIG5vaW5zcGVjdGlvbiBKU0lnbm9yZWRQcm9taXNlRnJvbUNhbGwgLSB3ZSBhcmVuJ3QgY29uY2VybmVkIHdpdGggaXQgb3ZlcmxhcHBpbmdcbiAgICAgICAgICAgIHRoaXMuc3RvcCgpO1xuICAgICAgICB9IGVsc2UgaWYgKHNlY29uZHNMZWZ0IDw9IFRBUkdFVF9XQVJOX1RJTUVfTEVGVCkge1xuICAgICAgICAgICAgU2luZ2xlZmxpZ2h0LmZvcih0aGlzLCBcImVuZGluZ19zb29uXCIpLmRvKCgpID0+IHtcbiAgICAgICAgICAgICAgICB0aGlzLmVtaXQoUmVjb3JkaW5nU3RhdGUuRW5kaW5nU29vbiwge3NlY29uZHNMZWZ0fSk7XG4gICAgICAgICAgICAgICAgcmV0dXJuIFNpbmdsZWZsaWdodC5Wb2lkO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgcHVibGljIGFzeW5jIHN0YXJ0KCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBpZiAodGhpcy5teGMgfHwgdGhpcy5oYXNSZWNvcmRpbmcpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcIlJlY29yZGluZyBhbHJlYWR5IHByZXBhcmVkXCIpO1xuICAgICAgICB9XG4gICAgICAgIGlmICh0aGlzLnJlY29yZGluZykge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiUmVjb3JkaW5nIGFscmVhZHkgaW4gcHJvZ3Jlc3NcIik7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHRoaXMub2JzZXJ2YWJsZSkge1xuICAgICAgICAgICAgdGhpcy5