UNPKG

metaapi.cloud-sdk

Version:

SDK for MetaApi, a professional cloud forex API which includes MetaTrader REST API and MetaTrader websocket API. Supports both MetaTrader 5 (MT5) and MetaTrader 4 (MT4). CopyFactory copy trading API included. (https://metaapi.cloud)

182 lines (181 loc) 28.1 kB
'use strict'; import LatencyListener from '../clients/metaApi/latencyListener'; import StatisticalReservoir from './reservoir/statisticalReservoir'; import Reservoir from './reservoir/reservoir'; let LatencyMonitor = class LatencyMonitor extends LatencyListener { /** * Invoked with latency information when application receives a response to RPC request * @param {string} accountId account id * @param {string} type request type * @param {ResponseTimestamps} timestamps request timestamps object containing latency information */ onResponse(accountId, type, timestamps) { if (!this._requestReservoirs[type]) { this._requestReservoirs[type] = { branch: true, clientLatency: this._initializeReservoirs(), serverLatency: this._initializeReservoirs() }; } if (timestamps.serverProcessingStarted && timestamps.serverProcessingFinished) { let serverLatency = timestamps.serverProcessingFinished.getTime() - timestamps.serverProcessingStarted.getTime(); this._saveMeasurement(this._requestReservoirs[type].serverLatency, serverLatency); } if (timestamps.clientProcessingStarted && timestamps.clientProcessingFinished && timestamps.serverProcessingStarted && timestamps.serverProcessingFinished) { let serverLatency = timestamps.serverProcessingFinished.getTime() - timestamps.serverProcessingStarted.getTime(); let clientLatency = timestamps.clientProcessingFinished.getTime() - timestamps.clientProcessingStarted.getTime() - serverLatency; this._saveMeasurement(this._requestReservoirs[type].clientLatency, clientLatency); } } /** * Returns request processing latencies * @returns {Object} request processing latencies */ get requestLatencies() { return this._constructLatenciesRecursively(this._requestReservoirs); } /** * Invoked with latency information when application receives symbol price update event * @param {string} accountId account id * @param {string} symbol price symbol * @param {SymbolPriceTimestamps} timestamps timestamps object containing latency information about price streaming */ onSymbolPrice(accountId, symbol, timestamps) { if (timestamps.eventGenerated && timestamps.serverProcessingStarted) { let brokerLatency = timestamps.serverProcessingStarted.getTime() - timestamps.eventGenerated.getTime(); this._saveMeasurement(this._priceReservoirs.brokerLatency, brokerLatency); } if (timestamps.serverProcessingStarted && timestamps.serverProcessingFinished) { let serverLatency = timestamps.serverProcessingFinished.getTime() - timestamps.serverProcessingStarted.getTime(); this._saveMeasurement(this._priceReservoirs.serverLatency, serverLatency); } if (timestamps.serverProcessingFinished && timestamps.clientProcessingFinished) { let clientLatency = timestamps.clientProcessingFinished.getTime() - timestamps.serverProcessingFinished.getTime(); this._saveMeasurement(this._priceReservoirs.clientLatency, clientLatency); } } /** * Returns price streaming latencies * @returns {Object} price streaming latencies */ get priceLatencies() { return this._constructLatenciesRecursively(this._priceReservoirs); } /** * Invoked with latency information when application receives update event * @param {string} accountId account id * @param {UpdateTimestamps} timestamps timestamps object containing latency information about update streaming */ onUpdate(accountId, timestamps) { if (timestamps.eventGenerated && timestamps.serverProcessingStarted) { let brokerLatency = timestamps.serverProcessingStarted.getTime() - timestamps.eventGenerated.getTime(); this._saveMeasurement(this._updateReservoirs.brokerLatency, brokerLatency); } if (timestamps.serverProcessingStarted && timestamps.serverProcessingFinished) { let serverLatency = timestamps.serverProcessingFinished.getTime() - timestamps.serverProcessingStarted.getTime(); this._saveMeasurement(this._updateReservoirs.serverLatency, serverLatency); } if (timestamps.serverProcessingFinished && timestamps.clientProcessingFinished) { let clientLatency = timestamps.clientProcessingFinished.getTime() - timestamps.serverProcessingFinished.getTime(); this._saveMeasurement(this._updateReservoirs.clientLatency, clientLatency); } } /** * Returns update streaming latencies * @returns {Object} update streaming latencies */ get updateLatencies() { return this._constructLatenciesRecursively(this._updateReservoirs); } /** * Invoked with latency information when application receives trade response * @param {string} accountId account id * @param {TradeTimestamps} timestamps timestamps object containing latency information about a trade */ onTrade(accountId, timestamps) { if (timestamps.clientProcessingStarted && timestamps.serverProcessingStarted) { let clientLatency = timestamps.serverProcessingStarted.getTime() - timestamps.clientProcessingStarted.getTime(); this._saveMeasurement(this._tradeReservoirs.clientLatency, clientLatency); } if (timestamps.serverProcessingStarted && timestamps.tradeStarted) { let serverLatency = timestamps.tradeStarted.getTime() - timestamps.serverProcessingStarted.getTime(); this._saveMeasurement(this._tradeReservoirs.serverLatency, serverLatency); } if (timestamps.tradeStarted && timestamps.tradeExecuted) { let brokerLatency = timestamps.tradeExecuted.getTime() - timestamps.tradeStarted.getTime(); this._saveMeasurement(this._tradeReservoirs.brokerLatency, brokerLatency); } } /** * Returns trade latencies * @returns {Object} trade latencies */ get tradeLatencies() { return this._constructLatenciesRecursively(this._tradeReservoirs); } _saveMeasurement(reservoirs, clientLatency) { for (let e of Object.entries(reservoirs)){ if (e[0] === 'branch') { continue; } e[1].percentiles.pushMeasurement(clientLatency); e[1].reservoir.pushMeasurement(clientLatency); } } _constructLatenciesRecursively(reservoirs) { let result = {}; for (let e of Object.entries(reservoirs)){ if (e[0] === 'branch') { continue; } result[e[0]] = e[1].branch ? this._constructLatenciesRecursively(e[1]) : { p50: e[1].percentiles.getPercentile(50), p75: e[1].percentiles.getPercentile(75), p90: e[1].percentiles.getPercentile(90), p95: e[1].percentiles.getPercentile(95), p98: e[1].percentiles.getPercentile(98), avg: e[1].reservoir.getStatistics().average, count: e[1].reservoir.getStatistics().count, min: e[1].reservoir.getStatistics().min, max: e[1].reservoir.getStatistics().max }; } return result; } _initializeReservoirs() { return { branch: true, '1h': { percentiles: new StatisticalReservoir(1000, 60 * 60 * 1000), reservoir: new Reservoir(60, 60 * 60 * 1000) }, '1d': { percentiles: new StatisticalReservoir(1000, 24 * 60 * 60 * 1000), reservoir: new Reservoir(60, 24 * 60 * 60 * 1000) }, '1w': { percentiles: new StatisticalReservoir(1000, 7 * 24 * 60 * 60 * 1000), reservoir: new Reservoir(60, 7 * 24 * 60 * 60 * 1000) } }; } /** * Constructs latency monitor instance */ constructor(){ super(); this._tradeReservoirs = { clientLatency: this._initializeReservoirs(), serverLatency: this._initializeReservoirs(), brokerLatency: this._initializeReservoirs() }; this._updateReservoirs = { clientLatency: this._initializeReservoirs(), serverLatency: this._initializeReservoirs(), brokerLatency: this._initializeReservoirs() }; this._priceReservoirs = { clientLatency: this._initializeReservoirs(), serverLatency: this._initializeReservoirs(), brokerLatency: this._initializeReservoirs() }; this._requestReservoirs = { branch: true }; } }; /** * Responsible for monitoring MetaApi application latencies */ export { LatencyMonitor as default }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCBMYXRlbmN5TGlzdGVuZXIgZnJvbSAnLi4vY2xpZW50cy9tZXRhQXBpL2xhdGVuY3lMaXN0ZW5lcic7XG5pbXBvcnQgU3RhdGlzdGljYWxSZXNlcnZvaXIgZnJvbSAnLi9yZXNlcnZvaXIvc3RhdGlzdGljYWxSZXNlcnZvaXInO1xuaW1wb3J0IFJlc2Vydm9pciBmcm9tICcuL3Jlc2Vydm9pci9yZXNlcnZvaXInO1xuXG4vKipcbiAqIFJlc3BvbnNpYmxlIGZvciBtb25pdG9yaW5nIE1ldGFBcGkgYXBwbGljYXRpb24gbGF0ZW5jaWVzXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIExhdGVuY3lNb25pdG9yIGV4dGVuZHMgTGF0ZW5jeUxpc3RlbmVyIHtcblxuICAvKipcbiAgICogQ29uc3RydWN0cyBsYXRlbmN5IG1vbml0b3IgaW5zdGFuY2VcbiAgICovXG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIHN1cGVyKCk7XG4gICAgdGhpcy5fdHJhZGVSZXNlcnZvaXJzID0ge1xuICAgICAgY2xpZW50TGF0ZW5jeTogdGhpcy5faW5pdGlhbGl6ZVJlc2Vydm9pcnMoKSxcbiAgICAgIHNlcnZlckxhdGVuY3k6IHRoaXMuX2luaXRpYWxpemVSZXNlcnZvaXJzKCksXG4gICAgICBicm9rZXJMYXRlbmN5OiB0aGlzLl9pbml0aWFsaXplUmVzZXJ2b2lycygpXG4gICAgfTtcbiAgICB0aGlzLl91cGRhdGVSZXNlcnZvaXJzID0ge1xuICAgICAgY2xpZW50TGF0ZW5jeTogdGhpcy5faW5pdGlhbGl6ZVJlc2Vydm9pcnMoKSxcbiAgICAgIHNlcnZlckxhdGVuY3k6IHRoaXMuX2luaXRpYWxpemVSZXNlcnZvaXJzKCksXG4gICAgICBicm9rZXJMYXRlbmN5OiB0aGlzLl9pbml0aWFsaXplUmVzZXJ2b2lycygpXG4gICAgfTtcbiAgICB0aGlzLl9wcmljZVJlc2Vydm9pcnMgPSB7XG4gICAgICBjbGllbnRMYXRlbmN5OiB0aGlzLl9pbml0aWFsaXplUmVzZXJ2b2lycygpLFxuICAgICAgc2VydmVyTGF0ZW5jeTogdGhpcy5faW5pdGlhbGl6ZVJlc2Vydm9pcnMoKSxcbiAgICAgIGJyb2tlckxhdGVuY3k6IHRoaXMuX2luaXRpYWxpemVSZXNlcnZvaXJzKClcbiAgICB9O1xuICAgIHRoaXMuX3JlcXVlc3RSZXNlcnZvaXJzID0ge1xuICAgICAgYnJhbmNoOiB0cnVlXG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBJbnZva2VkIHdpdGggbGF0ZW5jeSBpbmZvcm1hdGlvbiB3aGVuIGFwcGxpY2F0aW9uIHJlY2VpdmVzIGEgcmVzcG9uc2UgdG8gUlBDIHJlcXVlc3RcbiAgICogQHBhcmFtIHtzdHJpbmd9IGFjY291bnRJZCBhY2NvdW50IGlkXG4gICAqIEBwYXJhbSB7c3RyaW5nfSB0eXBlIHJlcXVlc3QgdHlwZVxuICAgKiBAcGFyYW0ge1Jlc3BvbnNlVGltZXN0YW1wc30gdGltZXN0YW1wcyByZXF1ZXN0IHRpbWVzdGFtcHMgb2JqZWN0IGNvbnRhaW5pbmcgbGF0ZW5jeSBpbmZvcm1hdGlvblxuICAgKi9cbiAgb25SZXNwb25zZShhY2NvdW50SWQsIHR5cGUsIHRpbWVzdGFtcHMpIHtcbiAgICBpZiAoIXRoaXMuX3JlcXVlc3RSZXNlcnZvaXJzW3R5cGVdKSB7XG4gICAgICB0aGlzLl9yZXF1ZXN0UmVzZXJ2b2lyc1t0eXBlXSA9IHtcbiAgICAgICAgYnJhbmNoOiB0cnVlLFxuICAgICAgICBjbGllbnRMYXRlbmN5OiB0aGlzLl9pbml0aWFsaXplUmVzZXJ2b2lycygpLFxuICAgICAgICBzZXJ2ZXJMYXRlbmN5OiB0aGlzLl9pbml0aWFsaXplUmVzZXJ2b2lycygpXG4gICAgICB9O1xuICAgIH1cbiAgICBpZiAodGltZXN0YW1wcy5zZXJ2ZXJQcm9jZXNzaW5nU3RhcnRlZCAmJiB0aW1lc3RhbXBzLnNlcnZlclByb2Nlc3NpbmdGaW5pc2hlZCkge1xuICAgICAgbGV0IHNlcnZlckxhdGVuY3kgPSB0aW1lc3RhbXBzLnNlcnZlclByb2Nlc3NpbmdGaW5pc2hlZC5nZXRUaW1lKCkgLSB0aW1lc3RhbXBzLnNlcnZlclByb2Nlc3NpbmdTdGFydGVkLmdldFRpbWUoKTtcbiAgICAgIHRoaXMuX3NhdmVNZWFzdXJlbWVudCh0aGlzLl9yZXF1ZXN0UmVzZXJ2b2lyc1t0eXBlXS5zZXJ2ZXJMYXRlbmN5LCBzZXJ2ZXJMYXRlbmN5KTtcbiAgICB9XG4gICAgaWYgKHRpbWVzdGFtcHMuY2xpZW50UHJvY2Vzc2luZ1N0YXJ0ZWQgJiYgdGltZXN0YW1wcy5jbGllbnRQcm9jZXNzaW5nRmluaXNoZWQgJiZcbiAgICAgIHRpbWVzdGFtcHMuc2VydmVyUHJvY2Vzc2luZ1N0YXJ0ZWQgJiYgdGltZXN0YW1wcy5zZXJ2ZXJQcm9jZXNzaW5nRmluaXNoZWQpIHtcbiAgICAgIGxldCBzZXJ2ZXJMYXRlbmN5ID0gdGltZXN0YW1wcy5zZXJ2ZXJQcm9jZXNzaW5nRmluaXNoZWQuZ2V0VGltZSgpIC0gdGltZXN0YW1wcy5zZXJ2ZXJQcm9jZXNzaW5nU3RhcnRlZC5nZXRUaW1lKCk7XG4gICAgICBsZXQgY2xpZW50TGF0ZW5jeSA9IHRpbWVzdGFtcHMuY2xpZW50UHJvY2Vzc2luZ0ZpbmlzaGVkLmdldFRpbWUoKSAtIHRpbWVzdGFtcHMuY2xpZW50UHJvY2Vzc2luZ1N0YXJ0ZWQuZ2V0VGltZSgpIC1cbiAgICAgICAgc2VydmVyTGF0ZW5jeTtcbiAgICAgIHRoaXMuX3NhdmVNZWFzdXJlbWVudCh0aGlzLl9yZXF1ZXN0UmVzZXJ2b2lyc1t0eXBlXS5jbGllbnRMYXRlbmN5LCBjbGllbnRMYXRlbmN5KTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyByZXF1ZXN0IHByb2Nlc3NpbmcgbGF0ZW5jaWVzXG4gICAqIEByZXR1cm5zIHtPYmplY3R9IHJlcXVlc3QgcHJvY2Vzc2luZyBsYXRlbmNpZXNcbiAgICovXG4gIGdldCByZXF1ZXN0TGF0ZW5jaWVzKCkge1xuICAgIHJldHVybiB0aGlzLl9jb25zdHJ1Y3RMYXRlbmNpZXNSZWN1cnNpdmVseSh0aGlzLl9yZXF1ZXN0UmVzZXJ2b2lycyk7XG4gIH1cblxuICAvKipcbiAgICogSW52b2tlZCB3aXRoIGxhdGVuY3kgaW5mb3JtYXRpb24gd2hlbiBhcHBsaWNhdGlvbiByZWNlaXZlcyBzeW1ib2wgcHJpY2UgdXBkYXRlIGV2ZW50XG4gICAqIEBwYXJhbSB7c3RyaW5nfSBhY2NvdW50SWQgYWNjb3VudCBpZFxuICAgKiBAcGFyYW0ge3N0cmluZ30gc3ltYm9sIHByaWNlIHN5bWJvbFxuICAgKiBAcGFyYW0ge1N5bWJvbFByaWNlVGltZXN0YW1wc30gdGltZXN0YW1wcyB0aW1lc3RhbXBzIG9iamVjdCBjb250YWluaW5nIGxhdGVuY3kgaW5mb3JtYXRpb24gYWJvdXQgcHJpY2Ugc3RyZWFtaW5nXG4gICAqL1xuICBvblN5bWJvbFByaWNlKGFjY291bnRJZCwgc3ltYm9sLCB0aW1lc3RhbXBzKSB7XG4gICAgaWYgKHRpbWVzdGFtcHMuZXZlbnRHZW5lcmF0ZWQgJiYgdGltZXN0YW1wcy5zZXJ2ZXJQcm9jZXNzaW5nU3RhcnRlZCkge1xuICAgICAgbGV0IGJyb2tlckxhdGVuY3kgPSB0aW1lc3RhbXBzLnNlcnZlclByb2Nlc3NpbmdTdGFydGVkLmdldFRpbWUoKSAtIHRpbWVzdGFtcHMuZXZlbnRHZW5lcmF0ZWQuZ2V0VGltZSgpO1xuICAgICAgdGhpcy5fc2F2ZU1lYXN1cmVtZW50KHRoaXMuX3ByaWNlUmVzZXJ2b2lycy5icm9rZXJMYXRlbmN5LCBicm9rZXJMYXRlbmN5KTtcbiAgICB9XG4gICAgaWYgKHRpbWVzdGFtcHMuc2VydmVyUHJvY2Vzc2luZ1N0YXJ0ZWQgJiYgdGltZXN0YW1wcy5zZXJ2ZXJQcm9jZXNzaW5nRmluaXNoZWQpIHtcbiAgICAgIGxldCBzZXJ2ZXJMYXRlbmN5ID0gdGltZXN0YW1wcy5zZXJ2ZXJQcm9jZXNzaW5nRmluaXNoZWQuZ2V0VGltZSgpIC0gdGltZXN0YW1wcy5zZXJ2ZXJQcm9jZXNzaW5nU3RhcnRlZC5nZXRUaW1lKCk7XG4gICAgICB0aGlzLl9zYXZlTWVhc3VyZW1lbnQodGhpcy5fcHJpY2VSZXNlcnZvaXJzLnNlcnZlckxhdGVuY3ksIHNlcnZlckxhdGVuY3kpO1xuICAgIH1cbiAgICBpZiAodGltZXN0YW1wcy5zZXJ2ZXJQcm9jZXNzaW5nRmluaXNoZWQgJiYgdGltZXN0YW1wcy5jbGllbnRQcm9jZXNzaW5nRmluaXNoZWQpIHtcbiAgICAgIGxldCBjbGllbnRMYXRlbmN5ID0gdGltZXN0YW1wcy5jbGllbnRQcm9jZXNzaW5nRmluaXNoZWQuZ2V0VGltZSgpIC0gdGltZXN0YW1wcy5zZXJ2ZXJQcm9jZXNzaW5nRmluaXNoZWQuZ2V0VGltZSgpO1xuICAgICAgdGhpcy5fc2F2ZU1lYXN1cmVtZW50KHRoaXMuX3ByaWNlUmVzZXJ2b2lycy5jbGllbnRMYXRlbmN5LCBjbGllbnRMYXRlbmN5KTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBwcmljZSBzdHJlYW1pbmcgbGF0ZW5jaWVzXG4gICAqIEByZXR1cm5zIHtPYmplY3R9IHByaWNlIHN0cmVhbWluZyBsYXRlbmNpZXNcbiAgICovXG4gIGdldCBwcmljZUxhdGVuY2llcygpIHtcbiAgICByZXR1cm4gdGhpcy5fY29uc3RydWN0TGF0ZW5jaWVzUmVjdXJzaXZlbHkodGhpcy5fcHJpY2VSZXNlcnZvaXJzKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBJbnZva2VkIHdpdGggbGF0ZW5jeSBpbmZvcm1hdGlvbiB3aGVuIGFwcGxpY2F0aW9uIHJlY2VpdmVzIHVwZGF0ZSBldmVudFxuICAgKiBAcGFyYW0ge3N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWRcbiAgICogQHBhcmFtIHtVcGRhdGVUaW1lc3RhbXBzfSB0aW1lc3RhbXBzIHRpbWVzdGFtcHMgb2JqZWN0IGNvbnRhaW5pbmcgbGF0ZW5jeSBpbmZvcm1hdGlvbiBhYm91dCB1cGRhdGUgc3RyZWFtaW5nXG4gICAqL1xuICBvblVwZGF0ZShhY2NvdW50SWQsIHRpbWVzdGFtcHMpIHtcbiAgICBpZiAodGltZXN0YW1wcy5ldmVudEdlbmVyYXRlZCAmJiB0aW1lc3RhbXBzLnNlcnZlclByb2Nlc3NpbmdTdGFydGVkKSB7XG4gICAgICBsZXQgYnJva2VyTGF0ZW5jeSA9IHRpbWVzdGFtcHMuc2VydmVyUHJvY2Vzc2luZ1N0YXJ0ZWQuZ2V0VGltZSgpIC0gdGltZXN0YW1wcy5ldmVudEdlbmVyYXRlZC5nZXRUaW1lKCk7XG4gICAgICB0aGlzLl9zYXZlTWVhc3VyZW1lbnQodGhpcy5fdXBkYXRlUmVzZXJ2b2lycy5icm9rZXJMYXRlbmN5LCBicm9rZXJMYXRlbmN5KTtcbiAgICB9XG4gICAgaWYgKHRpbWVzdGFtcHMuc2VydmVyUHJvY2Vzc2luZ1N0YXJ0ZWQgJiYgdGltZXN0YW1wcy5zZXJ2ZXJQcm9jZXNzaW5nRmluaXNoZWQpIHtcbiAgICAgIGxldCBzZXJ2ZXJMYXRlbmN5ID0gdGltZXN0YW1wcy5zZXJ2ZXJQcm9jZXNzaW5nRmluaXNoZWQuZ2V0VGltZSgpIC0gdGltZXN0YW1wcy5zZXJ2ZXJQcm9jZXNzaW5nU3RhcnRlZC5nZXRUaW1lKCk7XG4gICAgICB0aGlzLl9zYXZlTWVhc3VyZW1lbnQodGhpcy5fdXBkYXRlUmVzZXJ2b2lycy5zZXJ2ZXJMYXRlbmN5LCBzZXJ2ZXJMYXRlbmN5KTtcbiAgICB9XG4gICAgaWYgKHRpbWVzdGFtcHMuc2VydmVyUHJvY2Vzc2luZ0ZpbmlzaGVkICYmIHRpbWVzdGFtcHMuY2xpZW50UHJvY2Vzc2luZ0ZpbmlzaGVkKSB7XG4gICAgICBsZXQgY2xpZW50TGF0ZW5jeSA9IHRpbWVzdGFtcHMuY2xpZW50UHJvY2Vzc2luZ0ZpbmlzaGVkLmdldFRpbWUoKSAtIHRpbWVzdGFtcHMuc2VydmVyUHJvY2Vzc2luZ0ZpbmlzaGVkLmdldFRpbWUoKTtcbiAgICAgIHRoaXMuX3NhdmVNZWFzdXJlbWVudCh0aGlzLl91cGRhdGVSZXNlcnZvaXJzLmNsaWVudExhdGVuY3ksIGNsaWVudExhdGVuY3kpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHVwZGF0ZSBzdHJlYW1pbmcgbGF0ZW5jaWVzXG4gICAqIEByZXR1cm5zIHtPYmplY3R9IHVwZGF0ZSBzdHJlYW1pbmcgbGF0ZW5jaWVzXG4gICAqL1xuICBnZXQgdXBkYXRlTGF0ZW5jaWVzKCkge1xuICAgIHJldHVybiB0aGlzLl9jb25zdHJ1Y3RMYXRlbmNpZXNSZWN1cnNpdmVseSh0aGlzLl91cGRhdGVSZXNlcnZvaXJzKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBJbnZva2VkIHdpdGggbGF0ZW5jeSBpbmZvcm1hdGlvbiB3aGVuIGFwcGxpY2F0aW9uIHJlY2VpdmVzIHRyYWRlIHJlc3BvbnNlXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBhY2NvdW50SWQgYWNjb3VudCBpZFxuICAgKiBAcGFyYW0ge1RyYWRlVGltZXN0YW1wc30gdGltZXN0YW1wcyB0aW1lc3RhbXBzIG9iamVjdCBjb250YWluaW5nIGxhdGVuY3kgaW5mb3JtYXRpb24gYWJvdXQgYSB0cmFkZVxuICAgKi9cbiAgb25UcmFkZShhY2NvdW50SWQsIHRpbWVzdGFtcHMpIHtcbiAgICBpZiAodGltZXN0YW1wcy5jbGllbnRQcm9jZXNzaW5nU3RhcnRlZCAmJiB0aW1lc3RhbXBzLnNlcnZlclByb2Nlc3NpbmdTdGFydGVkKSB7XG4gICAgICBsZXQgY2xpZW50TGF0ZW5jeSA9IHRpbWVzdGFtcHMuc2VydmVyUHJvY2Vzc2luZ1N0YXJ0ZWQuZ2V0VGltZSgpIC0gdGltZXN0YW1wcy5jbGllbnRQcm9jZXNzaW5nU3RhcnRlZC5nZXRUaW1lKCk7XG4gICAgICB0aGlzLl9zYXZlTWVhc3VyZW1lbnQodGhpcy5fdHJhZGVSZXNlcnZvaXJzLmNsaWVudExhdGVuY3ksIGNsaWVudExhdGVuY3kpO1xuICAgIH1cbiAgICBpZiAodGltZXN0YW1wcy5zZXJ2ZXJQcm9jZXNzaW5nU3RhcnRlZCAmJiB0aW1lc3RhbXBzLnRyYWRlU3RhcnRlZCkge1xuICAgICAgbGV0IHNlcnZlckxhdGVuY3kgPSB0aW1lc3RhbXBzLnRyYWRlU3RhcnRlZC5nZXRUaW1lKCkgLSB0aW1lc3RhbXBzLnNlcnZlclByb2Nlc3NpbmdTdGFydGVkLmdldFRpbWUoKTtcbiAgICAgIHRoaXMuX3NhdmVNZWFzdXJlbWVudCh0aGlzLl90cmFkZVJlc2Vydm9pcnMuc2VydmVyTGF0ZW5jeSwgc2VydmVyTGF0ZW5jeSk7XG4gICAgfVxuICAgIGlmICh0aW1lc3RhbXBzLnRyYWRlU3RhcnRlZCAmJiB0aW1lc3RhbXBzLnRyYWRlRXhlY3V0ZWQpIHtcbiAgICAgIGxldCBicm9rZXJMYXRlbmN5ID0gdGltZXN0YW1wcy50cmFkZUV4ZWN1dGVkLmdldFRpbWUoKSAtIHRpbWVzdGFtcHMudHJhZGVTdGFydGVkLmdldFRpbWUoKTtcbiAgICAgIHRoaXMuX3NhdmVNZWFzdXJlbWVudCh0aGlzLl90cmFkZVJlc2Vydm9pcnMuYnJva2VyTGF0ZW5jeSwgYnJva2VyTGF0ZW5jeSk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdHJhZGUgbGF0ZW5jaWVzXG4gICAqIEByZXR1cm5zIHtPYmplY3R9IHRyYWRlIGxhdGVuY2llc1xuICAgKi9cbiAgZ2V0IHRyYWRlTGF0ZW5jaWVzKCkge1xuICAgIHJldHVybiB0aGlzLl9jb25zdHJ1Y3RMYXRlbmNpZXNSZWN1cnNpdmVseSh0aGlzLl90cmFkZVJlc2Vydm9pcnMpO1xuICB9XG5cbiAgX3NhdmVNZWFzdXJlbWVudChyZXNlcnZvaXJzLCBjbGllbnRMYXRlbmN5KSB7XG4gICAgZm9yIChsZXQgZSBvZiBPYmplY3QuZW50cmllcyhyZXNlcnZvaXJzKSkge1xuICAgICAgaWYgKGVbMF0gPT09ICdicmFuY2gnKSB7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuICAgICAgZVsxXS5wZXJjZW50aWxlcy5wdXNoTWVhc3VyZW1lbnQoY2xpZW50TGF0ZW5jeSk7XG4gICAgICBlWzFdLnJlc2Vydm9pci5wdXNoTWVhc3VyZW1lbnQoY2xpZW50TGF0ZW5jeSk7XG4gICAgfVxuICB9XG5cbiAgX2NvbnN0cnVjdExhdGVuY2llc1JlY3Vyc2l2ZWx5KHJlc2Vydm9pcnMpIHtcbiAgICBsZXQgcmVzdWx0ID0ge307XG4gICAgZm9yIChsZXQgZSBvZiBPYmplY3QuZW50cmllcyhyZXNlcnZvaXJzKSkge1xuICAgICAgaWYgKGVbMF0gPT09ICdicmFuY2gnKSB7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuICAgICAgcmVzdWx0W2VbMF1dID0gZVsxXS5icmFuY2ggPyB0aGlzLl9jb25zdHJ1Y3RMYXRlbmNpZXNSZWN1cnNpdmVseShlWzFdKSA6IHtcbiAgICAgICAgcDUwOiBlWzFdLnBlcmNlbnRpbGVzLmdldFBlcmNlbnRpbGUoNTApLFxuICAgICAgICBwNzU6IGVbMV0ucGVyY2VudGlsZXMuZ2V0UGVyY2VudGlsZSg3NSksXG4gICAgICAgIHA5MDogZVsxXS5wZXJjZW50aWxlcy5nZXRQZXJjZW50aWxlKDkwKSxcbiAgICAgICAgcDk1OiBlWzFdLnBlcmNlbnRpbGVzLmdldFBlcmNlbnRpbGUoOTUpLFxuICAgICAgICBwOTg6IGVbMV0ucGVyY2VudGlsZXMuZ2V0UGVyY2VudGlsZSg5OCksXG4gICAgICAgIGF2ZzogZVsxXS5yZXNlcnZvaXIuZ2V0U3RhdGlzdGljcygpLmF2ZXJhZ2UsXG4gICAgICAgIGNvdW50OiBlWzFdLnJlc2Vydm9pci5nZXRTdGF0aXN0aWNzKCkuY291bnQsXG4gICAgICAgIG1pbjogZVsxXS5yZXNlcnZvaXIuZ2V0U3RhdGlzdGljcygpLm1pbixcbiAgICAgICAgbWF4OiBlWzFdLnJlc2Vydm9pci5nZXRTdGF0aXN0aWNzKCkubWF4XG4gICAgICB9O1xuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG5cbiAgX2luaXRpYWxpemVSZXNlcnZvaXJzKCkge1xuICAgIHJldHVybiB7XG4gICAgICBicmFuY2g6IHRydWUsXG4gICAgICAnMWgnOiB7XG4gICAgICAgIHBlcmNlbnRpbGVzOiBuZXcgU3RhdGlzdGljYWxSZXNlcnZvaXIoMTAwMCwgNjAgKiA2MCAqIDEwMDApLFxuICAgICAgICByZXNlcnZvaXI6IG5ldyBSZXNlcnZvaXIoNjAsIDYwICogNjAgKiAxMDAwKVxuICAgICAgfSxcbiAgICAgICcxZCc6IHtcbiAgICAgICAgcGVyY2VudGlsZXM6IG5ldyBTdGF0aXN0aWNhbFJlc2Vydm9pcigxMDAwLCAyNCAqIDYwICogNjAgKiAxMDAwKSxcbiAgICAgICAgcmVzZXJ2b2lyOiBuZXcgUmVzZXJ2b2lyKDYwLCAyNCAqIDYwICogNjAgKiAxMDAwKVxuICAgICAgfSxcbiAgICAgICcxdyc6IHtcbiAgICAgICAgcGVyY2VudGlsZXM6IG5ldyBTdGF0aXN0aWNhbFJlc2Vydm9pcigxMDAwLCA3ICogMjQgKiA2MCAqIDYwICogMTAwMCksXG4gICAgICAgIHJlc2Vydm9pcjogbmV3IFJlc2Vydm9pcig2MCwgNyAqIDI0ICogNjAgKiA2MCAqIDEwMDApXG4gICAgICB9XG4gICAgfTtcbiAgfVxuXG59XG4iXSwibmFtZXMiOlsiTGF0ZW5jeUxpc3RlbmVyIiwiU3RhdGlzdGljYWxSZXNlcnZvaXIiLCJSZXNlcnZvaXIiLCJMYXRlbmN5TW9uaXRvciIsIm9uUmVzcG9uc2UiLCJhY2NvdW50SWQiLCJ0eXBlIiwidGltZXN0YW1wcyIsIl9yZXF1ZXN0UmVzZXJ2b2lycyIsImJyYW5jaCIsImNsaWVudExhdGVuY3kiLCJfaW5pdGlhbGl6ZVJlc2Vydm9pcnMiLCJzZXJ2ZXJMYXRlbmN5Iiwic2VydmVyUHJvY2Vzc2luZ1N0YXJ0ZWQiLCJzZXJ2ZXJQcm9jZXNzaW5nRmluaXNoZWQiLCJnZXRUaW1lIiwiX3NhdmVNZWFzdXJlbWVudCIsImNsaWVudFByb2Nlc3NpbmdTdGFydGVkIiwiY2xpZW50UHJvY2Vzc2luZ0ZpbmlzaGVkIiwicmVxdWVzdExhdGVuY2llcyIsIl9jb25zdHJ1Y3RMYXRlbmNpZXNSZWN1cnNpdmVseSIsIm9uU3ltYm9sUHJpY2UiLCJzeW1ib2wiLCJldmVudEdlbmVyYXRlZCIsImJyb2tlckxhdGVuY3kiLCJfcHJpY2VSZXNlcnZvaXJzIiwicHJpY2VMYXRlbmNpZXMiLCJvblVwZGF0ZSIsIl91cGRhdGVSZXNlcnZvaXJzIiwidXBkYXRlTGF0ZW5jaWVzIiwib25UcmFkZSIsIl90cmFkZVJlc2Vydm9pcnMiLCJ0cmFkZVN0YXJ0ZWQiLCJ0cmFkZUV4ZWN1dGVkIiwidHJhZGVMYXRlbmNpZXMiLCJyZXNlcnZvaXJzIiwiZSIsIk9iamVjdCIsImVudHJpZXMiLCJwZXJjZW50aWxlcyIsInB1c2hNZWFzdXJlbWVudCIsInJlc2Vydm9pciIsInJlc3VsdCIsInA1MCIsImdldFBlcmNlbnRpbGUiLCJwNzUiLCJwOTAiLCJwOTUiLCJwOTgiLCJhdmciLCJnZXRTdGF0aXN0aWNzIiwiYXZlcmFnZSIsImNvdW50IiwibWluIiwibWF4IiwiY29uc3RydWN0b3IiXSwibWFwcGluZ3MiOiJBQUFBO0FBRUEsT0FBT0EscUJBQXFCLHFDQUFxQztBQUNqRSxPQUFPQywwQkFBMEIsbUNBQW1DO0FBQ3BFLE9BQU9DLGVBQWUsd0JBQXdCO0FBSy9CLElBQUEsQUFBTUMsaUJBQU4sTUFBTUEsdUJBQXVCSDtJQTJCMUM7Ozs7O0dBS0MsR0FDREksV0FBV0MsU0FBUyxFQUFFQyxJQUFJLEVBQUVDLFVBQVUsRUFBRTtRQUN0QyxJQUFJLENBQUMsSUFBSSxDQUFDQyxrQkFBa0IsQ0FBQ0YsS0FBSyxFQUFFO1lBQ2xDLElBQUksQ0FBQ0Usa0JBQWtCLENBQUNGLEtBQUssR0FBRztnQkFDOUJHLFFBQVE7Z0JBQ1JDLGVBQWUsSUFBSSxDQUFDQyxxQkFBcUI7Z0JBQ3pDQyxlQUFlLElBQUksQ0FBQ0QscUJBQXFCO1lBQzNDO1FBQ0Y7UUFDQSxJQUFJSixXQUFXTSx1QkFBdUIsSUFBSU4sV0FBV08sd0JBQXdCLEVBQUU7WUFDN0UsSUFBSUYsZ0JBQWdCTCxXQUFXTyx3QkFBd0IsQ0FBQ0MsT0FBTyxLQUFLUixXQUFXTSx1QkFBdUIsQ0FBQ0UsT0FBTztZQUM5RyxJQUFJLENBQUNDLGdCQUFnQixDQUFDLElBQUksQ0FBQ1Isa0JBQWtCLENBQUNGLEtBQUssQ0FBQ00sYUFBYSxFQUFFQTtRQUNyRTtRQUNBLElBQUlMLFdBQVdVLHVCQUF1QixJQUFJVixXQUFXVyx3QkFBd0IsSUFDM0VYLFdBQVdNLHVCQUF1QixJQUFJTixXQUFXTyx3QkFBd0IsRUFBRTtZQUMzRSxJQUFJRixnQkFBZ0JMLFdBQVdPLHdCQUF3QixDQUFDQyxPQUFPLEtBQUtSLFdBQVdNLHVCQUF1QixDQUFDRSxPQUFPO1lBQzlHLElBQUlMLGdCQUFnQkgsV0FBV1csd0JBQXdCLENBQUNILE9BQU8sS0FBS1IsV0FBV1UsdUJBQXVCLENBQUNGLE9BQU8sS0FDNUdIO1lBQ0YsSUFBSSxDQUFDSSxnQkFBZ0IsQ0FBQyxJQUFJLENBQUNSLGtCQUFrQixDQUFDRixLQUFLLENBQUNJLGFBQWEsRUFBRUE7UUFDckU7SUFDRjtJQUVBOzs7R0FHQyxHQUNELElBQUlTLG1CQUFtQjtRQUNyQixPQUFPLElBQUksQ0FBQ0MsOEJBQThCLENBQUMsSUFBSSxDQUFDWixrQkFBa0I7SUFDcEU7SUFFQTs7Ozs7R0FLQyxHQUNEYSxjQUFjaEIsU0FBUyxFQUFFaUIsTUFBTSxFQUFFZixVQUFVLEVBQUU7UUFDM0MsSUFBSUEsV0FBV2dCLGNBQWMsSUFBSWhCLFdBQVdNLHVCQUF1QixFQUFFO1lBQ25FLElBQUlXLGdCQUFnQmpCLFdBQVdNLHVCQUF1QixDQUFDRSxPQUFPLEtBQUtSLFdBQVdnQixjQUFjLENBQUNSLE9BQU87WUFDcEcsSUFBSSxDQUFDQyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUNTLGdCQUFnQixDQUFDRCxhQUFhLEVBQUVBO1FBQzdEO1FBQ0EsSUFBSWpCLFdBQVdNLHVCQUF1QixJQUFJTixXQUFXTyx3QkFBd0IsRUFBRTtZQUM3RSxJQUFJRixnQkFBZ0JMLFdBQVdPLHdCQUF3QixDQUFDQyxPQUFPLEtBQUtSLFdBQVdNLHVCQUF1QixDQUFDRSxPQUFPO1lBQzlHLElBQUksQ0FBQ0MsZ0JBQWdCLENBQUMsSUFBSSxDQUFDUyxnQkFBZ0IsQ0FBQ2IsYUFBYSxFQUFFQTtRQUM3RDtRQUNBLElBQUlMLFdBQVdPLHdCQUF3QixJQUFJUCxXQUFXVyx3QkFBd0IsRUFBRTtZQUM5RSxJQUFJUixnQkFBZ0JILFdBQVdXLHdCQUF3QixDQUFDSCxPQUFPLEtBQUtSLFdBQVdPLHdCQUF3QixDQUFDQyxPQUFPO1lBQy9HLElBQUksQ0FBQ0MsZ0JBQWdCLENBQUMsSUFBSSxDQUFDUyxnQkFBZ0IsQ0FBQ2YsYUFBYSxFQUFFQTtRQUM3RDtJQUNGO0lBRUE7OztHQUdDLEdBQ0QsSUFBSWdCLGlCQUFpQjtRQUNuQixPQUFPLElBQUksQ0FBQ04sOEJBQThCLENBQUMsSUFBSSxDQUFDSyxnQkFBZ0I7SUFDbEU7SUFFQTs7OztHQUlDLEdBQ0RFLFNBQVN0QixTQUFTLEVBQUVFLFVBQVUsRUFBRTtRQUM5QixJQUFJQSxXQUFXZ0IsY0FBYyxJQUFJaEIsV0FBV00sdUJBQXVCLEVBQUU7WUFDbkUsSUFBSVcsZ0JBQWdCakIsV0FBV00sdUJBQXVCLENBQUNFLE9BQU8sS0FBS1IsV0FBV2dCLGNBQWMsQ0FBQ1IsT0FBTztZQUNwRyxJQUFJLENBQUNDLGdCQUFnQixDQUFDLElBQUksQ0FBQ1ksaUJBQWlCLENBQUNKLGFBQWEsRUFBRUE7UUFDOUQ7UUFDQSxJQUFJakIsV0FBV00sdUJBQXVCLElBQUlOLFdBQVdPLHdCQUF3QixFQUFFO1lBQzdFLElBQUlGLGdCQUFnQkwsV0FBV08sd0JBQXdCLENBQUNDLE9BQU8sS0FBS1IsV0FBV00sdUJBQXVCLENBQUNFLE9BQU87WUFDOUcsSUFBSSxDQUFDQyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUNZLGlCQUFpQixDQUFDaEIsYUFBYSxFQUFFQTtRQUM5RDtRQUNBLElBQUlMLFdBQVdPLHdCQUF3QixJQUFJUCxXQUFXVyx3QkFBd0IsRUFBRTtZQUM5RSxJQUFJUixnQkFBZ0JILFdBQVdXLHdCQUF3QixDQUFDSCxPQUFPLEtBQUtSLFdBQVdPLHdCQUF3QixDQUFDQyxPQUFPO1lBQy9HLElBQUksQ0FBQ0MsZ0JBQWdCLENBQUMsSUFBSSxDQUFDWSxpQkFBaUIsQ0FBQ2xCLGFBQWEsRUFBRUE7UUFDOUQ7SUFDRjtJQUVBOzs7R0FHQyxHQUNELElBQUltQixrQkFBa0I7UUFDcEIsT0FBTyxJQUFJLENBQUNULDhCQUE4QixDQUFDLElBQUksQ0FBQ1EsaUJBQWlCO0lBQ25FO0lBRUE7Ozs7R0FJQyxHQUNERSxRQUFRekIsU0FBUyxFQUFFRSxVQUFVLEVBQUU7UUFDN0IsSUFBSUEsV0FBV1UsdUJBQXVCLElBQUlWLFdBQVdNLHVCQUF1QixFQUFFO1lBQzVFLElBQUlILGdCQUFnQkgsV0FBV00sdUJBQXVCLENBQUNFLE9BQU8sS0FBS1IsV0FBV1UsdUJBQXVCLENBQUNGLE9BQU87WUFDN0csSUFBSSxDQUFDQyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUNlLGdCQUFnQixDQUFDckIsYUFBYSxFQUFFQTtRQUM3RDtRQUNBLElBQUlILFdBQVdNLHVCQUF1QixJQUFJTixXQUFXeUIsWUFBWSxFQUFFO1lBQ2pFLElBQUlwQixnQkFBZ0JMLFdBQVd5QixZQUFZLENBQUNqQixPQUFPLEtBQUtSLFdBQVdNLHVCQUF1QixDQUFDRSxPQUFPO1lBQ2xHLElBQUksQ0FBQ0MsZ0JBQWdCLENBQUMsSUFBSSxDQUFDZSxnQkFBZ0IsQ0FBQ25CLGFBQWEsRUFBRUE7UUFDN0Q7UUFDQSxJQUFJTCxXQUFXeUIsWUFBWSxJQUFJekIsV0FBVzBCLGFBQWEsRUFBRTtZQUN2RCxJQUFJVCxnQkFBZ0JqQixXQUFXMEIsYUFBYSxDQUFDbEIsT0FBTyxLQUFLUixXQUFXeUIsWUFBWSxDQUFDakIsT0FBTztZQUN4RixJQUFJLENBQUNDLGdCQUFnQixDQUFDLElBQUksQ0FBQ2UsZ0JBQWdCLENBQUNQLGFBQWEsRUFBRUE7UUFDN0Q7SUFDRjtJQUVBOzs7R0FHQyxHQUNELElBQUlVLGlCQUFpQjtRQUNuQixPQUFPLElBQUksQ0FBQ2QsOEJBQThCLENBQUMsSUFBSSxDQUFDVyxnQkFBZ0I7SUFDbEU7SUFFQWYsaUJBQWlCbUIsVUFBVSxFQUFFekIsYUFBYSxFQUFFO1FBQzFDLEtBQUssSUFBSTBCLEtBQUtDLE9BQU9DLE9BQU8sQ0FBQ0gsWUFBYTtZQUN4QyxJQUFJQyxDQUFDLENBQUMsRUFBRSxLQUFLLFVBQVU7Z0JBQ3JCO1lBQ0Y7WUFDQUEsQ0FBQyxDQUFDLEVBQUUsQ0FBQ0csV0FBVyxDQUFDQyxlQUFlLENBQUM5QjtZQUNqQzBCLENBQUMsQ0FBQyxFQUFFLENBQUNLLFNBQVMsQ0FBQ0QsZUFBZSxDQUFDOUI7UUFDakM7SUFDRjtJQUVBVSwrQkFBK0JlLFVBQVUsRUFBRTtRQUN6QyxJQUFJTyxTQUFTLENBQUM7UUFDZCxLQUFLLElBQUlOLEtBQUtDLE9BQU9DLE9BQU8sQ0FBQ0gsWUFBYTtZQUN4QyxJQUFJQyxDQUFDLENBQUMsRUFBRSxLQUFLLFVBQVU7Z0JBQ3JCO1lBQ0Y7WUFDQU0sTUFBTSxDQUFDTixDQUFDLENBQUMsRUFBRSxDQUFDLEdBQUdBLENBQUMsQ0FBQyxFQUFFLENBQUMzQixNQUFNLEdBQUcsSUFBSSxDQUFDVyw4QkFBOEIsQ0FBQ2dCLENBQUMsQ0FBQyxFQUFFLElBQUk7Z0JBQ3ZFTyxLQUFLUCxDQUFDLENBQUMsRUFBRSxDQUFDRyxXQUFXLENBQUNLLGFBQWEsQ0FBQztnQkFDcENDLEtBQUtULENBQUMsQ0FBQyxFQUFFLENBQUNHLFdBQVcsQ0FBQ0ssYUFBYSxDQUFDO2dCQUNwQ0UsS0FBS1YsQ0FBQyxDQUFDLEVBQUUsQ0FBQ0csV0FBVyxDQUFDSyxhQUFhLENBQUM7Z0JBQ3BDRyxLQUFLWCxDQUFDLENBQUMsRUFBRSxDQUFDRyxXQUFXLENBQUNLLGFBQWEsQ0FBQztnQkFDcENJLEtBQUtaLENBQUMsQ0FBQyxFQUFFLENBQUNHLFdBQVcsQ0FBQ0ssYUFBYSxDQUFDO2dCQUNwQ0ssS0FBS2IsQ0FBQyxDQUFDLEVBQUUsQ0FBQ0ssU0FBUyxDQUFDUyxhQUFhLEdBQUdDLE9BQU87Z0JBQzNDQyxPQUFPaEIsQ0FBQyxDQUFDLEVBQUUsQ0FBQ0ssU0FBUyxDQUFDUyxhQUFhLEdBQUdFLEtBQUs7Z0JBQzNDQyxLQUFLakIsQ0FBQyxDQUFDLEVBQUUsQ0FBQ0ssU0FBUyxDQUFDUyxhQUFhLEdBQUdHLEdBQUc7Z0JBQ3ZDQyxLQUFLbEIsQ0FBQyxDQUFDLEVBQUUsQ0FBQ0ssU0FBUyxDQUFDUyxhQUFhLEdBQUdJLEdBQUc7WUFDekM7UUFDRjtRQUNBLE9BQU9aO0lBQ1Q7SUFFQS9CLHdCQUF3QjtRQUN0QixPQUFPO1lBQ0xGLFFBQVE7WUFDUixNQUFNO2dCQUNKOEIsYUFBYSxJQUFJdEMscUJBQXFCLE1BQU0sS0FBSyxLQUFLO2dCQUN0RHdDLFdBQVcsSUFBSXZDLFVBQVUsSUFBSSxLQUFLLEtBQUs7WUFDekM7WUFDQSxNQUFNO2dCQUNKcUMsYUFBYSxJQUFJdEMscUJBQXFCLE1BQU0sS0FBSyxLQUFLLEtBQUs7Z0JBQzNEd0MsV0FBVyxJQUFJdkMsVUFBVSxJQUFJLEtBQUssS0FBSyxLQUFLO1lBQzlDO1lBQ0EsTUFBTTtnQkFDSnFDLGFBQWEsSUFBSXRDLHFCQUFxQixNQUFNLElBQUksS0FBSyxLQUFLLEtBQUs7Z0JBQy9Ed0MsV0FBVyxJQUFJdkMsVUFBVSxJQUFJLElBQUksS0FBSyxLQUFLLEtBQUs7WUFDbEQ7UUFDRjtJQUNGO0lBaE1BOztHQUVDLEdBQ0RxRCxhQUFjO1FBQ1osS0FBSztRQUNMLElBQUksQ0FBQ3hCLGdCQUFnQixHQUFHO1lBQ3RCckIsZUFBZSxJQUFJLENBQUNDLHFCQUFxQjtZQUN6Q0MsZUFBZSxJQUFJLENBQUNELHFCQUFxQjtZQUN6Q2EsZUFBZSxJQUFJLENBQUNiLHFCQUFxQjtRQUMzQztRQUNBLElBQUksQ0FBQ2lCLGlCQUFpQixHQUFHO1lBQ3ZCbEIsZUFBZSxJQUFJLENBQUNDLHFCQUFxQjtZQUN6Q0MsZUFBZSxJQUFJLENBQUNELHFCQUFxQjtZQUN6Q2EsZUFBZSxJQUFJLENBQUNiLHFCQUFxQjtRQUMzQztRQUNBLElBQUksQ0FBQ2MsZ0JBQWdCLEdBQUc7WUFDdEJmLGVBQWUsSUFBSSxDQUFDQyxxQkFBcUI7WUFDekNDLGVBQWUsSUFBSSxDQUFDRCxxQkFBcUI7WUFDekNhLGVBQWUsSUFBSSxDQUFDYixxQkFBcUI7UUFDM0M7UUFDQSxJQUFJLENBQUNILGtCQUFrQixHQUFHO1lBQ3hCQyxRQUFRO1FBQ1Y7SUFDRjtBQTJLRjtBQXZNQTs7Q0FFQyxHQUNELFNBQXFCTiw0QkFvTXBCIn0=