@nebulae/angular-ble
Version:
A Web Bluetooth (Bluetooth Low Energy) module for angular (v2+)
534 lines (533 loc) • 76.8 kB
JavaScript
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,uselessCode} checked by tsc
*/
import { Injectable, EventEmitter } from '@angular/core';
import { map, mergeMap, concat, mapTo, filter, takeUntil, scan, tap, take, timeout, retryWhen, delay } from 'rxjs/operators';
import { Observable, forkJoin, Subject, defer, fromEvent, of, from } from 'rxjs';
import { GattServices } from './gatt-services';
import { BrowserWebBluetooth } from '../platform/browser';
import { CypherAesService } from '../cypher/cypher-aes.service';
import { ConsoleLoggerService } from '../logger.service';
import * as i0 from "@angular/core";
import * as i1 from "../platform/browser";
import * as i2 from "../cypher/cypher-aes.service";
import * as i3 from "../logger.service";
export class BluetoothService extends Subject {
/**
* @param {?} _webBle
* @param {?} cypherAesService
* @param {?} _console
*/
constructor(_webBle, cypherAesService, _console) {
super();
this._webBle = _webBle;
this.cypherAesService = cypherAesService;
this._console = _console;
this.serviceCharacteristicVsSubscriptionList = {};
this.notifierSubject = new Subject();
this.notifierStartedSubject = new Subject();
this.bluetoothAvailable = false;
this._device$ = new EventEmitter();
if (_webBle._ble) {
this.bluetoothAvailable = true;
}
}
/**
* @return {?}
*/
isBluetoothAvailable() {
return this.bluetoothAvailable;
}
/**
* get the current device, if the device return null is because the connection has lost
* @return {?} the current connceted device
*/
getDevice$() {
return this._device$;
}
/**
* @return {?}
*/
getNotifierStartedSubject$() {
return this.notifierStartedSubject;
}
/**
* start a stream by notifiers characteristics
* @param {?} service The service to which the characteristic belongs
* @param {?} characteristic The characteristic whose value you want to listen
* @param {?} options object that contains the
* startByte:number (required), stopByte:number (required),
* lengthPosition: { start: number, end: number, lengthPadding: number } (required)
* @return {?} A DataView than contains the characteristic value
*/
startNotifierListener$(service, characteristic, options) {
return defer(() => {
of(service)
.pipe(tap(() => this._console.log('Inicia el notifier con la instancia: ', this.serviceCharacteristicVsSubscriptionList)), filter(_ => Object.keys(this.serviceCharacteristicVsSubscriptionList).indexOf(`${service}-${characteristic}`) === -1))
.subscribe(() => {
this.serviceCharacteristicVsSubscriptionList[`${service}-${characteristic}`] =
this.buildNotifierListener$(service, characteristic, options).subscribe(message => {
this.notifierSubject.next(message);
});
}, err => {
this._console.log('[BLE::Info] Error in notifier: ', err);
}, () => { });
return of(`notifier as subscribed: service= ${service}, characteristic= ${characteristic}`);
});
}
/**
* @param {?} service
* @param {?} characteristic
* @return {?}
*/
stopNotifierListener$(service, characteristic) {
return defer(() => {
// this.serviceCharacteristicVsSubscriptionList[`${service}-${characteristic}`].unsubscribe();
delete this.serviceCharacteristicVsSubscriptionList[`${service}-${characteristic}`];
return of(`the notifier of the characteristic ${characteristic} as been stopped`);
});
}
/**
* @param {?} service
* @param {?} characteristic
* @param {?} options
* @return {?}
*/
buildNotifierListener$(service, characteristic, options) {
return this.getPrimaryService$(service).pipe(tap(serviceInstance => this._console.log(`toma exitosamente el servicio ================> ${serviceInstance}`)), mergeMap(primaryService => this.getCharacteristic$(primaryService, characteristic)
.pipe(tap(char => this._console.log(`toma exitosamente la caracteristica ================> ${char}`)))), mergeMap((char) => {
return defer(() => {
// enable the characteristic notifier
return char.startNotifications();
}).pipe(retryWhen(error => error.pipe(tap(() => this._console.log('ERROR EN EL startNotifications ================> ')), delay(1000), take(5))), tap(() => {
this.notifierStartedSubject.next(true);
this._console.log(`incia las notifiaciones de la caracteristica 2 ================> ${characteristic}`);
}), mergeMap(_ => {
// start the lister from the even characteristicvaluechanged to get all changes on the specific
// characteristic
return fromEvent(char, 'characteristicvaluechanged').pipe(takeUntil(fromEvent(char, 'gattserverdisconnected')), map((event) => {
// get a event from the characteristic and map that
return {
startByteMatches: false,
stopByteMatches: false,
lengthMatches: false,
messageLength: 0,
timestamp: Date.now(),
data: Array.from(new Uint8Array((/** @type {?} */ ((/** @type {?} */ (event.target))
.value)).buffer))
};
}), scan((acc, value) => {
acc.timestamp = acc.timestamp === 0 ? value.timestamp : acc.timestamp;
// if the current accumulator value is a valid message, then is restarted to get the next
// message
if ((acc.lengthMatches &&
acc.startByteMatches &&
acc.stopByteMatches)
|| acc.timestamp + 1000 < Date.now()) {
acc = {
startByteMatches: false,
stopByteMatches: false,
lengthMatches: false,
messageLength: 0,
timestamp: 0,
data: []
};
}
// validate the start byte
if (!acc.startByteMatches &&
value.data[0] === options.startByte) {
// get the message length using the start and end position
acc.messageLength =
new DataView(new Uint8Array(value.data.slice(options.lengthPosition.start, options.lengthPosition.end)).buffer).getInt16(0, false) +
(options.lengthPosition.lengthPadding
? options.lengthPosition.lengthPadding
: 0);
// valid that the initial byte was found
acc.startByteMatches = true;
}
if (!acc.stopByteMatches &&
value.data[value.data.length - 1] === options.stopByte) {
// valid that the end byte was found
acc.stopByteMatches = true;
}
if (acc.startByteMatches) {
// merge the new data bytes to the old bytes
acc.data = acc.data.concat(value.data);
}
acc.lengthMatches =
acc.startByteMatches &&
acc.stopByteMatches &&
acc.messageLength === acc.data.length;
return acc;
}, {
startByteMatches: false,
stopByteMatches: false,
lengthMatches: false,
messageLength: 0,
timestamp: 0,
data: []
}),
// only publish the complete and valid message
filter(data => data.lengthMatches &&
data.startByteMatches &&
data.stopByteMatches),
// remove all custom data and publish the message data
map(result => result.data));
}));
}));
}
/**
* Send a request to the device and wait a unique response
* @param {?} message Message to send
* @param {?} service The service to which the characteristic belongs
* @param {?} characteristic The characteristic whose value you want to send the message
* @param {?} responseType filter to use to identify the response, Sample: [{position: 3, byteToMatch: 0x83},
* {position: 13, byteToMatch: 0x45}]
* @param {?=} cypherMasterKey master key to decrypt the message, only use this para if the message to receive is encrypted
* @return {?}
*/
sendAndWaitResponse$(message, service, characteristic, responseType, cypherMasterKey) {
this._console.log('[BLE::Info] Send message to device: ', this.cypherAesService.bytesTohex(message));
return forkJoin(this.subscribeToNotifierListener(responseType, cypherMasterKey).pipe(take(1)), this.sendToNotifier$(message, service, characteristic)).pipe(map(([messageResp, _]) => messageResp), timeout(3000));
}
/**
* Subscribe to the notifiers filtering by byte checking
* @param {?} filterOptions must specific the position and the byte to match Sample:
* [{position: 3, byteToMatch: 0x83}, {position: 13, byteToMatch: 0x45}]
* @param {?=} cypherMasterKey master key to decrypt the message, only use this para if the message to receive is encrypted
* @return {?}
*/
subscribeToNotifierListener(filterOptions, cypherMasterKey) {
return this.notifierSubject.pipe(map(messageUnformated => {
/** @type {?} */
let messageFormmated = /** @type {?} */ (messageUnformated);
// validate if the message is cyphered
if (cypherMasterKey) {
/** @type {?} */
const datablockLength = new DataView(new Uint8Array(messageFormmated.slice(1, 3)).buffer).getInt16(0, false);
this.cypherAesService.config(cypherMasterKey);
/** @type {?} */
const datablock = Array.from(this.cypherAesService.decrypt((/** @type {?} */ (messageUnformated)).slice(3, datablockLength + 3)));
// merge the datablock and the message
messageFormmated = (/** @type {?} */ (messageUnformated))
.slice(0, 3)
.concat(datablock)
.concat((/** @type {?} */ (messageUnformated)).slice(-2));
}
this._console.log('[BLE::Info] Notification reived from device: ', this.cypherAesService.bytesTohex(messageFormmated));
return messageFormmated;
}),
// filter the message using the filter options
filter(message => {
/** @type {?} */
let availableMessage = false;
for (const option of filterOptions) {
if (message[option.position] === option.byteToMatch) {
availableMessage = true;
}
else {
availableMessage = false;
break;
}
}
return availableMessage;
}));
}
/**
* Start a request to the browser to list all available bluetooth devices
* @param {?=} options Options to request the devices the structure is:
* acceptAllDevices: true|false
* filters: BluetoothDataFilterInit (see https://webbluetoothcg.github.io/web-bluetooth/#dictdef-bluetoothlescanfilterinit for more info)
* optionalServices: [] (services that are going to be used in
* communication with the device, must use the UIID or GATT identfier to list ther services)
* @return {?}
*/
discoverDevice$(options = /** @type {?} */ ({})) {
return defer(() => this._webBle.requestDevice(options)).pipe(mergeMap(device => {
this.device = device;
this._device$.emit(device);
return this.configureDeviceDisconnection$(device).pipe(mapTo(device));
}));
}
/**
* @param {?} device
* @return {?}
*/
configureDeviceDisconnection$(device) {
return Observable.create(observer => {
of(device)
.pipe(mergeMap(dev => fromEvent(device, 'gattserverdisconnected')), take(1))
.subscribe(() => {
this._console.log('Se desconecta disp en OnDevice disconnected!!!!!!!');
this.device = null;
this._device$.emit(null);
}, err => {
this._console.log('[BLE::Info] Error in notifier: ', err);
observer.error(err);
}, () => {
});
observer.next(`DisconnectionEvent as been register`);
});
}
/**
* Discover all available devices and connect to a selected device
* @param {?=} options Options to request the devices the structure is:
* acceptAllDevices: true|false
* filters: BluetoothDataFilterInit (see https://webbluetoothcg.github.io/web-bluetooth/#dictdef-bluetoothlescanfilterinit for more info)
* optionalServices: [] (services that are going to be used in
* communication with the device, must use the UIID or GATT identfier to list ther services)
* @return {?} the connected device
*/
connectDevice$(options) {
if (!options) {
options = {
acceptAllDevices: true,
optionalServices: []
};
}
else if (!options.optionalServices) {
options.optionalServices = [];
}
options.optionalServices.push(GattServices.GENERIC_ACCESS.SERVICE);
options.optionalServices.push(GattServices.BATTERY.SERVICE);
options.optionalServices.push(GattServices.DEVICE_INFORMATION.SERVICE);
return this.discoverDevice$(options).pipe(mergeMap(device => {
return defer(() => (/** @type {?} */ (device)).gatt.connect());
}));
}
/**
* Disconnect the current device
* @return {?}
*/
disconnectDevice() {
if (this.device) {
this._console.log('se deconecta dispositivo');
this.device.gatt.disconnect();
}
}
/**
* get a data from the device using the characteristic
* @param {?} service UUID or GATT identifier service
* @param {?} characteristic UUID or GATT identifier characteristic
* @return {?} The characteristic data in a DataView object
*/
readDeviceValue$(service, characteristic) {
if (!this.device) {
throw new Error('Must start a connection to a device before read the device value');
}
return this.getPrimaryService$(service).pipe(mergeMap(primaryService => this.getCharacteristic$(primaryService, characteristic)), mergeMap((characteristicValue) => this.readValue$(characteristicValue)));
}
/**
* write a value in the selected characteristic
* @param {?} service
* @param {?} characteristic the characterisitc where you want write the value
* @param {?} value value the value to write
* @return {?}
*/
writeDeviceValue$(service, characteristic, value) {
if (!this.device) {
throw new Error('Must start a connection to a device before read the device value');
}
return this.getPrimaryService$(service).pipe(mergeMap(primaryService => this.getCharacteristic$(primaryService, characteristic)), mergeMap((characteristicValue) => this.writeValue$(characteristicValue, value)));
}
/**
* get a primary service instance using the service UIID or GATT identifier
* @param {?} service service identifier
* @return {?} service instance
*/
getPrimaryService$(service) {
return of(this.device).pipe(mergeMap(device => {
return device.gatt.getPrimaryService(service);
}));
}
/**
* Get a characterisitic instance using the service instance and a characteristic UUID
* @param {?} primaryService service instance
* @param {?} characteristic characterisitic identifier
* @return {?} characteristic instance
*/
getCharacteristic$(primaryService, characteristic) {
return defer(() => primaryService.getCharacteristic(characteristic));
}
/**
* read the characteristic value
* @param {?} characteristic characteristic instance
* @return {?} The characteristic data in a DataView object
*/
readValue$(characteristic) {
return from(characteristic
.readValue()
.then((data) => Promise.resolve(data), (error) => Promise.reject(`${error.message}`)));
}
/**
* write a value in the selected characteristic
* @param {?} characteristic the characterisitc where you want write the value
* @param {?} value the value to write
* @return {?}
*/
writeValue$(characteristic, value) {
return defer(() => characteristic
.writeValue(value));
}
/**
* change the state of the characteristic to enable it
* @param {?} service parent service of the characteristic
* @param {?} characteristic characteristic to change the state
* @param {?=} state new state
* @return {?}
*/
enableCharacteristic$(service, characteristic, state) {
state = state || new Uint8Array([1]);
return this.setCharacteristicState$(service, characteristic, state);
}
/**
* change the state of the characteristic to disable it
* @param {?} service parent service of the characteristic
* @param {?} characteristic characteristic to change the state
* @param {?=} state new state
* @return {?}
*/
disbaleCharacteristic$(service, characteristic, state) {
state = state || new Uint8Array([0]);
return this.setCharacteristicState$(service, characteristic, state);
}
/**
* set a state to an specific characteristic
* @param {?} service parent service of the characteristic
* @param {?} characteristic characteristic to change the state
* @param {?} state new state
* @return {?}
*/
setCharacteristicState$(service, characteristic, state) {
/** @type {?} */
const primaryService = this.getPrimaryService$(service);
return primaryService.pipe(mergeMap(_primaryService => this.getCharacteristic$(_primaryService, characteristic)), map((_characteristic) => this.writeValue$(_characteristic, state)));
}
/**
* Send a message using a notifier characteristic
* @param {?} message message to send
* @param {?} service service to which the characteristic belongs
* @param {?} characteristic feature in which you want to send the notification
* @return {?}
*/
sendToNotifier$(message, service, characteristic) {
return this.getPrimaryService$(service).pipe(mergeMap((primaryService) => this.getCharacteristic$(primaryService, characteristic)), mergeMap((char) => {
if (message.length > 16) {
/** @type {?} */
let obsTest = of(undefined);
while (message.length > 16) {
obsTest = obsTest.pipe(concat(this.writeValue$(char, message.slice(0, 16))));
message = message.slice(16, message.length);
}
if (message.length > 0) {
obsTest = obsTest.pipe(concat(this.writeValue$(char, message)));
}
return obsTest;
}
else {
return this.writeValue$(char, message);
}
}));
}
/**
* The Battery Level characteristic is read using the GATT Read Characteristic
* Value sub-procedure and returns the current battery level as a percentage
* from 0% to 100%; 0% represents a battery that is fully discharged, 100%
* represents a battery that is fully charged
* @return {?}
*/
getBatteryLevel$() {
return this.readDeviceValue$(GattServices.BATTERY.SERVICE, GattServices.BATTERY.BATTERY_LEVEL).pipe(map(value => value.getUint8(0)));
}
/**
* This characteristic represents the name of the manufacturer of the device.
* @return {?}
*/
getManufacturerName$() {
return this.readDeviceValue$(GattServices.DEVICE_INFORMATION.SERVICE, GattServices.DEVICE_INFORMATION.MANUFACTURER_NAME).pipe(map(dataView => this.cypherAesService.bytesToText(new Uint8Array(dataView.buffer))));
}
/**
* This characteristic represents the model number that is assigned by the device vendor.
* @return {?}
*/
getModelNumber$() {
return this.readDeviceValue$(GattServices.DEVICE_INFORMATION.SERVICE, GattServices.DEVICE_INFORMATION.MODEL_NUMBER).pipe(map(dataView => this.cypherAesService.bytesToText(new Uint8Array(dataView.buffer))));
}
/**
* This characteristic represents the serial number for a particular instance of the device.
* @return {?}
*/
getSerialNumber$() {
return this.readDeviceValue$(GattServices.DEVICE_INFORMATION.SERVICE, GattServices.DEVICE_INFORMATION.SERIAL_NUMBER).pipe(map(dataView => this.cypherAesService.bytesToText(new Uint8Array(dataView.buffer))));
}
/**
* This characteristic represents the hardware revision for the hardware within the device.
* @return {?}
*/
getHardwareRevision$() {
return this.readDeviceValue$(GattServices.DEVICE_INFORMATION.SERVICE, GattServices.DEVICE_INFORMATION.HARDWARE_REVISION).pipe(map(dataView => this.cypherAesService.bytesToText(new Uint8Array(dataView.buffer))));
}
/**
* This characteristic represents the firmware revision for the firmware within the device.
* @return {?}
*/
getFirmwareRevision$() {
return this.readDeviceValue$(GattServices.DEVICE_INFORMATION.SERVICE, GattServices.DEVICE_INFORMATION.FIRMWARE_REVISION).pipe(map(dataView => this.cypherAesService.bytesToText(new Uint8Array(dataView.buffer))));
}
/**
* This characteristic represents the software revision for the software within the device.
* @return {?}
*/
getSoftwareRevision$() {
return this.readDeviceValue$(GattServices.DEVICE_INFORMATION.SERVICE, GattServices.DEVICE_INFORMATION.SOFTWARE_REVISION).pipe(map(dataView => this.cypherAesService.bytesToText(new Uint8Array(dataView.buffer))));
}
/**
* This characteristic represents a structure containing an Organizationally Unique Identifier
* (OUI) followed by a manufacturer-defined identifier and is unique for each individual instance of the product.
* @return {?}
*/
getSystemId$() {
return this.readDeviceValue$(GattServices.DEVICE_INFORMATION.SERVICE, GattServices.DEVICE_INFORMATION.SYSTEM_ID);
}
/**
* The PnP_ID characteristic is a set of values used to create a device ID value that is unique for this device.
* @return {?}
*/
getPnpId$() {
return this.readDeviceValue$(GattServices.DEVICE_INFORMATION.SERVICE, GattServices.DEVICE_INFORMATION.PNP_ID);
}
}
BluetoothService.decorators = [
{ type: Injectable, args: [{
providedIn: 'root'
},] },
];
/** @nocollapse */
BluetoothService.ctorParameters = () => [
{ type: BrowserWebBluetooth },
{ type: CypherAesService },
{ type: ConsoleLoggerService }
];
/** @nocollapse */ BluetoothService.ngInjectableDef = i0.defineInjectable({ factory: function BluetoothService_Factory() { return new BluetoothService(i0.inject(i1.BrowserWebBluetooth), i0.inject(i2.CypherAesService), i0.inject(i3.ConsoleLoggerService)); }, token: BluetoothService, providedIn: "root" });
if (false) {
/** @type {?} */
BluetoothService.prototype._device$;
/** @type {?} */
BluetoothService.prototype.device;
/** @type {?} */
BluetoothService.prototype.serviceCharacteristicVsSubscriptionList;
/** @type {?} */
BluetoothService.prototype.notifierSubject;
/** @type {?} */
BluetoothService.prototype.notifierStartedSubject;
/** @type {?} */
BluetoothService.prototype.bluetoothAvailable;
/** @type {?} */
BluetoothService.prototype._webBle;
/** @type {?} */
BluetoothService.prototype.cypherAesService;
/** @type {?} */
BluetoothService.prototype._console;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmx1ZXRvb3RoLnNlcnZpY2UuanMiLCJzb3VyY2VSb290Ijoibmc6Ly9AbmVidWxhZS9hbmd1bGFyLWJsZS8iLCJzb3VyY2VzIjpbImxpYi9ibHVldG9vdGgvYmx1ZXRvb3RoLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7OztBQUFBLE9BQU8sRUFBRSxVQUFVLEVBQUUsWUFBWSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ3pELE9BQU8sRUFDTCxHQUFHLEVBQ0gsUUFBUSxFQUNSLE1BQU0sRUFDTixLQUFLLEVBQ0wsTUFBTSxFQUNOLFNBQVMsRUFFVCxJQUFJLEVBQ0osR0FBRyxFQUNILElBQUksRUFDSixPQUFPLEVBQ1AsU0FBUyxFQUNULEtBQUssRUFDTixNQUFNLGdCQUFnQixDQUFDO0FBQ3hCLE9BQU8sRUFDTCxVQUFVLEVBQ1YsUUFBUSxFQUNSLE9BQU8sRUFDUCxLQUFLLEVBQ0wsU0FBUyxFQUNULEVBQUUsRUFDRixJQUFJLEVBRUwsTUFBTSxNQUFNLENBQUM7QUFDZCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDL0MsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDMUQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDaEUsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sbUJBQW1CLENBQUM7Ozs7O0FBS3pELE1BQU0sdUJBQXdCLFNBQVEsT0FBeUI7Ozs7OztJQU83RCxZQUNTLFNBQ0Msa0JBQ0Q7UUFFUCxLQUFLLEVBQUUsQ0FBQztRQUpELFlBQU8sR0FBUCxPQUFPO1FBQ04scUJBQWdCLEdBQWhCLGdCQUFnQjtRQUNqQixhQUFRLEdBQVIsUUFBUTt1REFQaUMsRUFBRTsrQkFDMUIsSUFBSSxPQUFPLEVBQUU7c0NBQ04sSUFBSSxPQUFPLEVBQUU7a0NBQ2pCLEtBQUs7UUFPaEMsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLFlBQVksRUFBbUIsQ0FBQztRQUNwRCxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztZQUNqQixJQUFJLENBQUMsa0JBQWtCLEdBQUcsSUFBSSxDQUFDO1NBQ2hDO0tBQ0Y7Ozs7SUFFRCxvQkFBb0I7UUFDbEIsTUFBTSxDQUFDLElBQUksQ0FBQyxrQkFBa0IsQ0FBQztLQUNoQzs7Ozs7SUFLRCxVQUFVO1FBQ1IsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUM7S0FDdEI7Ozs7SUFFRCwwQkFBMEI7UUFDeEIsTUFBTSxDQUFDLElBQUksQ0FBQyxzQkFBc0IsQ0FBQztLQUNwQzs7Ozs7Ozs7OztJQVVELHNCQUFzQixDQUFDLE9BQU8sRUFBRSxjQUFjLEVBQUUsT0FBTztRQUNyRCxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRTtZQUNoQixFQUFFLENBQUMsT0FBTyxDQUFDO2lCQUNSLElBQUksQ0FDSCxHQUFHLENBQUMsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsdUNBQXVDLEVBQUUsSUFBSSxDQUFDLHVDQUF1QyxDQUFDLENBQUMsRUFDbkgsTUFBTSxDQUNKLENBQUMsQ0FBQyxFQUFFLENBQ0YsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsdUNBQXVDLENBQUMsQ0FBQyxPQUFPLENBQy9ELEdBQUcsT0FBTyxJQUFJLGNBQWMsRUFBRSxDQUMvQixLQUFLLENBQUMsQ0FBQyxDQUNYLENBQ0Y7aUJBQ0EsU0FBUyxDQUNSLEdBQUcsRUFBRTtnQkFDSCxJQUFJLENBQUMsdUNBQXVDLENBQUMsR0FBRyxPQUFPLElBQUksY0FBYyxFQUFFLENBQUM7b0JBQzFFLElBQUksQ0FBQyxzQkFBc0IsQ0FDekIsT0FBTyxFQUNQLGNBQWMsRUFDZCxPQUFPLENBQ1IsQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLEVBQUU7d0JBQ3BCLElBQUksQ0FBQyxlQUFlLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDO3FCQUNwQyxDQUFDLENBQUM7YUFFTixFQUNELEdBQUcsQ0FBQyxFQUFFO2dCQUNKLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLGlDQUFpQyxFQUFFLEdBQUcsQ0FBQyxDQUFDO2FBQzNELEVBQ0QsR0FBRyxFQUFFLElBQUksQ0FDWixDQUFDO1lBQ0YsTUFBTSxDQUFDLEVBQUUsQ0FBQyxvQ0FBb0MsT0FBTyxxQkFBcUIsY0FBYyxFQUFFLENBQUMsQ0FBQztTQUM3RixDQUFDLENBQUM7S0FFSjs7Ozs7O0lBRUQscUJBQXFCLENBQUMsT0FBTyxFQUFFLGNBQWM7UUFDM0MsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUU7O1lBRWhCLE9BQU8sSUFBSSxDQUFDLHVDQUF1QyxDQUFDLEdBQUcsT0FBTyxJQUFJLGNBQWMsRUFBRSxDQUFDLENBQUM7WUFDcEYsTUFBTSxDQUFDLEVBQUUsQ0FBQyxzQ0FBc0MsY0FBYyxrQkFBa0IsQ0FBQyxDQUFDO1NBQ25GLENBQUMsQ0FBQztLQUNKOzs7Ozs7O0lBRU8sc0JBQXNCLENBQUMsT0FBTyxFQUFFLGNBQWMsRUFBRSxPQUFPO1FBQzdELE1BQU0sQ0FBQyxJQUFJLENBQUMsa0JBQWtCLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxDQUMxQyxHQUFHLENBQUMsZUFBZSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxtREFBbUQsZUFBZSxFQUFFLENBQUMsQ0FBQyxFQUMvRyxRQUFRLENBQUMsY0FBYyxDQUFDLEVBQUUsQ0FDeEIsSUFBSSxDQUFDLGtCQUFrQixDQUFDLGNBQWMsRUFBRSxjQUFjLENBQUM7YUFDdEQsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLHlEQUF5RCxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FDdkcsRUFDRCxRQUFRLENBQUMsQ0FBQyxJQUF1QyxFQUFFLEVBQUU7WUFDbkQsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUU7O2dCQUVoQixNQUFNLENBQUMsSUFBSSxDQUFDLGtCQUFrQixFQUFFLENBQUM7YUFDbEMsQ0FBQyxDQUFDLElBQUksQ0FDTCxTQUFTLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FDaEIsS0FBSyxDQUFDLElBQUksQ0FDUixHQUFHLENBQUMsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsbURBQW1ELENBQUMsQ0FBQyxFQUNqRixLQUFLLENBQUMsSUFBSSxDQUFDLEVBQ1gsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUNSLENBQ0YsRUFDRCxHQUFHLENBQUMsR0FBRyxFQUFFO2dCQUNQLElBQUksQ0FBQyxzQkFBc0IsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7Z0JBQ3ZDLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLG9FQUFvRSxjQUFjLEVBQUUsQ0FBQyxDQUFDO2FBQ3pHLENBQUMsRUFDRixRQUFRLENBQUMsQ0FBQyxDQUFDLEVBQUU7OztnQkFHWCxNQUFNLENBQUMsU0FBUyxDQUFDLElBQUksRUFBRSw0QkFBNEIsQ0FBQyxDQUFDLElBQUksQ0FDdkQsU0FBUyxDQUFDLFNBQVMsQ0FBQyxJQUFJLEVBQUUsd0JBQXdCLENBQUMsQ0FBQyxFQUNwRCxHQUFHLENBQUMsQ0FBQyxLQUFZLEVBQUUsRUFBRTs7b0JBRW5CLE1BQU0sQ0FBQzt3QkFDTCxnQkFBZ0IsRUFBRSxLQUFLO3dCQUN2QixlQUFlLEVBQUUsS0FBSzt3QkFDdEIsYUFBYSxFQUFFLEtBQUs7d0JBQ3BCLGFBQWEsRUFBRSxDQUFDO3dCQUNoQixTQUFTLEVBQUUsSUFBSSxDQUFDLEdBQUcsRUFBRTt3QkFDckIsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJLENBQ2QsSUFBSSxVQUFVLENBQ1osbUJBQUMsbUJBQUMsS0FBSyxDQUFDLE1BQTJDLEVBQUM7NkJBQ2pELEtBQWlCLEVBQUMsQ0FBQyxNQUFNLENBQzdCLENBQ0Y7cUJBQ0YsQ0FBQztpQkFDSCxDQUFDLEVBQ0YsSUFBSSxDQUNGLENBQUMsR0FBRyxFQUFFLEtBQUssRUFBRSxFQUFFO29CQUNiLEdBQUcsQ0FBQyxTQUFTLEdBQUcsR0FBRyxDQUFDLFNBQVMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUM7OztvQkFHdEUsRUFBRSxDQUFDLENBQ0QsQ0FBQyxHQUFHLENBQUMsYUFBYTt3QkFDaEIsR0FBRyxDQUFDLGdCQUFnQjt3QkFDcEIsR0FBRyxDQUFDLGVBQWUsQ0FBQzsyQkFDbkIsR0FBRyxDQUFDLFNBQVMsR0FBRyxJQUFJLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFDcEMsQ0FBQyxDQUFDLENBQUM7d0JBQ0QsR0FBRyxHQUFHOzRCQUNKLGdCQUFnQixFQUFFLEtBQUs7NEJBQ3ZCLGVBQWUsRUFBRSxLQUFLOzRCQUN0QixhQUFhLEVBQUUsS0FBSzs0QkFDcEIsYUFBYSxFQUFFLENBQUM7NEJBQ2hCLFNBQVMsRUFBRSxDQUFDOzRCQUNaLElBQUksRUFBRSxFQUFFO3lCQUNULENBQUM7cUJBQ0g7O29CQUVELEVBQUUsQ0FBQyxDQUNELENBQUMsR0FBRyxDQUFDLGdCQUFnQjt3QkFDckIsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsS0FBSyxPQUFPLENBQUMsU0FDNUIsQ0FBQyxDQUFDLENBQUM7O3dCQUVELEdBQUcsQ0FBQyxhQUFhOzRCQUNmLElBQUksUUFBUSxDQUNWLElBQUksVUFBVSxDQUNaLEtBQUssQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUNkLE9BQU8sQ0FBQyxjQUFjLENBQUMsS0FBSyxFQUM1QixPQUFPLENBQUMsY0FBYyxDQUFDLEdBQUcsQ0FDM0IsQ0FDRixDQUFDLE1BQU0sQ0FDVCxDQUFDLFFBQVEsQ0FBQyxDQUFDLEVBQUUsS0FBSyxDQUFDO2dDQUNwQixDQUFDLE9BQU8sQ0FBQyxjQUFjLENBQUMsYUFBYTtvQ0FDbkMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxjQUFjLENBQUMsYUFBYTtvQ0FDdEMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDOzt3QkFFVCxHQUFHLENBQUMsZ0JBQWdCLEdBQUcsSUFBSSxDQUFDO3FCQUM3QjtvQkFDRCxFQUFFLENBQUMsQ0FDRCxDQUFDLEdBQUcsQ0FBQyxlQUFlO3dCQUNwQixLQUFLLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxLQUFLLE9BQU8sQ0FBQyxRQUNoRCxDQUFDLENBQUMsQ0FBQzs7d0JBRUQsR0FBRyxDQUFDLGVBQWUsR0FBRyxJQUFJLENBQUM7cUJBQzVCO29CQUNELEVBQUUsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLENBQUM7O3dCQUV6QixHQUFHLENBQUMsSUFBSSxHQUFHLEdBQUcsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztxQkFDeEM7b0JBQ0QsR0FBRyxDQUFDLGFBQWE7d0JBQ2YsR0FBRyxDQUFDLGdCQUFnQjs0QkFDcEIsR0FBRyxDQUFDLGVBQWU7NEJBQ25CLEdBQUcsQ0FBQyxhQUFhLEtBQUssR0FBRyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUM7b0JBQ3hDLE1BQU0sQ0FBQyxHQUFHLENBQUM7aUJBQ1osRUFDRDtvQkFDRSxnQkFBZ0IsRUFBRSxLQUFLO29CQUN2QixlQUFlLEVBQUUsS0FBSztvQkFDdEIsYUFBYSxFQUFFLEtBQUs7b0JBQ3BCLGFBQWEsRUFBRSxDQUFDO29CQUNoQixTQUFTLEVBQUUsQ0FBQztvQkFDWixJQUFJLEVBQUUsRUFBRTtpQkFDVCxDQUNGOztnQkFFRCxNQUFNLENBQ0osSUFBSSxDQUFDLEVBQUUsQ0FDTCxJQUFJLENBQUMsYUFBYTtvQkFDbEIsSUFBSSxDQUFDLGdCQUFnQjtvQkFDckIsSUFBSSxDQUFDLGVBQWUsQ0FDdkI7O2dCQUVELEdBQUcsQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FDM0IsQ0FBQzthQUNILENBQUMsQ0FDSCxDQUFDO1NBQ0gsQ0FBQyxDQUNILENBQUM7Ozs7Ozs7Ozs7OztJQVdKLG9CQUFvQixDQUNsQixPQUFPLEVBQ1AsT0FBTyxFQUNQLGNBQWMsRUFDZCxZQUFZLEVBQ1osZUFBZ0I7UUFFaEIsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQ2Ysc0NBQXNDLEVBQ3RDLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLENBQzFDLENBQUM7UUFDRixNQUFNLENBQUMsUUFBUSxDQUNiLElBQUksQ0FBQywyQkFBMkIsQ0FBQyxZQUFZLEVBQUUsZUFBZSxDQUFDLENBQUMsSUFBSSxDQUNsRSxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQ1IsRUFDRCxJQUFJLENBQUMsZUFBZSxDQUFDLE9BQU8sRUFBRSxPQUFPLEVBQUUsY0FBYyxDQUFDLENBQ3ZELENBQUMsSUFBSSxDQUNKLEdBQUcsQ0FBQyxDQUFDLENBQUMsV0FBVyxFQUFFLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxXQUFXLENBQUMsRUFDdEMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUNkLENBQUM7S0FDSDs7Ozs7Ozs7SUFPRCwyQkFBMkIsQ0FBQyxhQUFhLEVBQUUsZUFBZ0I7UUFDekQsTUFBTSxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUMsSUFBSSxDQUM5QixHQUFHLENBQUMsaUJBQWlCLENBQUMsRUFBRTs7WUFFdEIsSUFBSSxnQkFBZ0IscUJBQUcsaUJBQXdCLEVBQUM7O1lBRWhELEVBQUUsQ0FBQyxDQUFDLGVBQWUsQ0FBQyxDQUFDLENBQUM7O2dCQUVwQixNQUFNLGVBQWUsR0FBRyxJQUFJLFFBQVEsQ0FDbEMsSUFBSSxVQUFVLENBQUMsZ0JBQWdCLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FDcEQsQ0FBQyxRQUFRLENBQUMsQ0FBQyxFQUFFLEtBQUssQ0FBQyxDQUFDO2dCQUNyQixJQUFJLENBQUMsZ0JBQWdCLENBQUMsTUFBTSxDQUFDLGVBQWUsQ0FBQyxDQUFDOztnQkFFOUMsTUFBTSxTQUFTLEdBQUcsS0FBSyxDQUFDLElBQUksQ0FDMUIsSUFBSSxDQUFDLGdCQUFnQixDQUFDLE9BQU8sQ0FDM0IsbUJBQUMsaUJBQXdCLEVBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLGVBQWUsR0FBRyxDQUFDLENBQUMsQ0FDekQsQ0FDRixDQUFDOztnQkFFRixnQkFBZ0IsR0FBRyxtQkFBQyxpQkFBd0IsRUFBQztxQkFDMUMsS0FBSyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUM7cUJBQ1gsTUFBTSxDQUFDLFNBQVMsQ0FBQztxQkFDakIsTUFBTSxDQUFDLG1CQUFDLGlCQUF3QixFQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQzthQUNqRDtZQUNELElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUNmLCtDQUErQyxFQUMvQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsVUFBVSxDQUFDLGdCQUFnQixDQUFDLENBQ25ELENBQUM7WUFDRixNQUFNLENBQUMsZ0JBQWdCLENBQUM7U0FDekIsQ0FBQzs7UUFFRixNQUFNLENBQUMsT0FBTyxDQUFDLEVBQUU7O1lBQ2YsSUFBSSxnQkFBZ0IsR0FBRyxLQUFLLENBQUM7WUFDN0IsR0FBRyxDQUFDLENBQUMsTUFBTSxNQUFNLElBQUksYUFBYSxDQUFDLENBQUMsQ0FBQztnQkFDbkMsRUFBRSxDQUFDLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsS0FBSyxNQUFNLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQztvQkFDcEQsZ0JBQWdCLEdBQUcsSUFBSSxDQUFDO2lCQUN6QjtnQkFBQyxJQUFJLENBQUMsQ0FBQztvQkFDTixnQkFBZ0IsR0FBRyxLQUFLLENBQUM7b0JBQ3pCLEtBQUssQ0FBQztpQkFDUDthQUNGO1lBQ0QsTUFBTSxDQUFDLGdCQUFnQixDQUFDO1NBQ3pCLENBQUMsQ0FDSCxDQUFDO0tBQ0g7Ozs7Ozs7Ozs7SUFVTyxlQUFlLENBQ3JCLDRCQUFnQyxFQUEwQixDQUFBO1FBRTFELE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxhQUFhLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQzFELFFBQVEsQ0FBQyxNQUFNLENBQUMsRUFBRTtZQUNoQixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztZQUNyQixJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUMzQixNQUFNLENBQUMsSUFBSSxDQUFDLDZCQUE2QixDQUFDLE1BQU0sQ0FBQyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztTQUN2RSxDQUFDLENBQ0gsQ0FBQzs7Ozs7O0lBR0ksNkJBQTZCLENBQUMsTUFBTTtRQUMxQyxNQUFNLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsRUFBRTtZQUNsQyxFQUFFLENBQUMsTUFBTSxDQUFDO2lCQUNQLElBQUksQ0FDSCxRQUFRLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFLHdCQUF3QixDQUFDLENBQUMsRUFDNUQsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUNSO2lCQUNBLFNBQVMsQ0FDUixHQUFHLEVBQUU7Z0JBQ0gsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQ2Ysb0RBQW9ELENBQ3JELENBQUM7Z0JBQ0YsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUM7Z0JBQ25CLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO2FBQzFCLEVBQ0QsR0FBRyxDQUFDLEVBQUU7Z0JBQ0osSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsaUNBQWlDLEVBQUUsR0FBRyxDQUFDLENBQUM7Z0JBQzFELFFBQVEsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7YUFDckIsRUFDSCxHQUFHLEVBQUU7YUFDRixDQUNGLENBQUM7WUFDSixRQUFRLENBQUMsSUFBSSxDQUFDLHFDQUFxQyxDQUFDLENBQUM7U0FDdEQsQ0FBQyxDQUFDOzs7Ozs7Ozs7OztJQVlMLGNBQWMsQ0FBQyxPQUE4QjtRQUMzQyxFQUFFLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7WUFDYixPQUFPLEdBQUc7Z0JBQ1IsZ0JBQWdCLEVBQUUsSUFBSTtnQkFDdEIsZ0JBQWdCLEVBQUUsRUFBRTthQUNyQixDQUFDO1NBQ0g7UUFBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxDQUFDO1lBQ3JDLE9BQU8sQ0FBQyxnQkFBZ0IsR0FBRyxFQUFFLENBQUM7U0FDL0I7UUFDRCxPQUFPLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxjQUFjLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDbkUsT0FBTyxDQUFDLGdCQUFnQixDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQzVELE9BQU8sQ0FBQyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLGtCQUFrQixDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ3ZFLE1BQU0sQ0FBQyxJQUFJLENBQUMsZUFBZSxDQUFDLE9BQU8sQ0FBQyxDQUFDLElBQUksQ0FDdkMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxFQUFFO1lBQ2hCLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxFQUFFLENBQUMsbUJBQUMsTUFBYSxFQUFDLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUM7U0FDcEQsQ0FBQyxDQUNILENBQUM7S0FDSDs7Ozs7SUFJRCxnQkFBZ0I7UUFDZCxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztZQUNoQixJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQywwQkFBMEIsQ0FBQyxDQUFDO1lBQzlDLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO1NBQy9CO0tBQ0Y7Ozs7Ozs7SUFRRCxnQkFBZ0IsQ0FBQyxPQUFPLEVBQUUsY0FBYztRQUN0QyxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO1lBQ2pCLE1BQU0sSUFBSSxLQUFLLENBQ2Isa0VBQWtFLENBQ25FLENBQUM7U0FDSDtRQUNELE1BQU0sQ0FBQyxJQUFJLENBQUMsa0JBQWtCLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxDQUMxQyxRQUFRLENBQUMsY0FBYyxDQUFDLEVBQUUsQ0FDeEIsSUFBSSxDQUFDLGtCQUFrQixDQUFDLGNBQWMsRUFBRSxjQUFjLENBQUMsQ0FDeEQsRUFDRCxRQUFRLENBQUMsQ0FBQyxtQkFBc0QsRUFBRSxFQUFFLENBQ2xFLElBQUksQ0FBQyxVQUFVLENBQUMsbUJBQW1CLENBQUMsQ0FDckMsQ0FDRixDQUFDO0tBQ0g7Ozs7Ozs7O0lBT0QsaUJBQWlCLENBQUMsT0FBTyxFQUFFLGNBQWMsRUFBRSxLQUFLO1FBQzlDLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7WUFDakIsTUFBTSxJQUFJLEtBQUssQ0FDYixrRUFBa0UsQ0FDbkUsQ0FBQztTQUNIO1FBQ0QsTUFBTSxDQUFDLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLENBQUMsQ0FBQyxJQUFJLENBQzFDLFFBQVEsQ0FBQyxjQUFjLENBQUMsRUFBRSxDQUN4QixJQUFJLENBQUMsa0JBQWtCLENBQUMsY0FBYyxFQUFFLGNBQWMsQ0FBQyxDQUN4RCxFQUNELFFBQVEsQ0FBQyxDQUFDLG1CQUFzRCxFQUFFLEVBQUUsQ0FDbEUsSUFBSSxDQUFDLFdBQVcsQ0FBQyxtQkFBbUIsRUFBRSxLQUFLLENBQUMsQ0FDN0MsQ0FDRixDQUFDO0tBQ0g7Ozs7OztJQU9ELGtCQUFrQixDQUFDLE9BQTZCO1FBQzlDLE1BQU0sQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLElBQUksQ0FDekIsUUFBUSxDQUFDLE1BQU0sQ0FBQyxFQUFFO1lBQ2hCLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLE9BQU8sQ0FBQyxDQUFDO1NBQy9DLENBQUMsQ0FDSCxDQUFDO0tBQ0g7Ozs7Ozs7SUFRRCxrQkFBa0IsQ0FDaEIsY0FBMEMsRUFDMUMsY0FBMkM7UUFFM0MsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUUsQ0FBQyxjQUFjLENBQUMsaUJBQWlCLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQztLQUN0RTs7Ozs7O0lBT08sVUFBVSxDQUNoQixjQUFpRDtRQUVqRCxNQUFNLENBQUMsSUFBSSxDQUNULGNBQWM7YUFDWCxTQUFTLEVBQUU7YUFDWCxJQUFJLENBQ0gsQ0FBQyxJQUFjLEVBQUUsRUFBRSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLEVBQ3pDLENBQUMsS0FBbUIsRUFBRSxFQUFFLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxHQUFHLEtBQUssQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUM1RCxDQUNKLENBQUM7Ozs7Ozs7O0lBUUksV0FBVyxDQUNqQixjQUFpRCxFQUNqRCxLQUErQjtRQUUvQixNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRSxDQUNoQixjQUFjO2FBQ1gsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUNyQixDQUFDOzs7Ozs7Ozs7SUFTSixxQkFBcUIsQ0FDbkIsT0FBNkIsRUFDN0IsY0FBMkMsRUFDM0MsS0FBVztRQUVYLEtBQUssR0FBRyxLQUFLLElBQUksSUFBSSxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3JDLE1BQU0sQ0FBQyxJQUFJLENBQUMsdUJBQXVCLENBQUMsT0FBTyxFQUFFLGNBQWMsRUFBRSxLQUFLLENBQUMsQ0FBQztLQUNyRTs7Ozs7Ozs7SUFRRCxzQkFBc0IsQ0FDcEIsT0FBNkIsRUFDN0IsY0FBMkMsRUFDM0MsS0FBVztRQUVYLEtBQUssR0FBRyxLQUFLLElBQUksSUFBSSxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3JDLE1BQU0sQ0FBQyxJQUFJLENBQUMsdUJBQXVCLENBQUMsT0FBTyxFQUFFLGNBQWMsRUFBRSxLQUFLLENBQUMsQ0FBQztLQUNyRTs7Ozs7Ozs7SUFTRCx1QkFBdUIsQ0FDckIsT0FBNkIsRUFDN0IsY0FBMkMsRUFDM0MsS0FBa0I7O1FBRWxCLE1BQU0sY0FBYyxHQUFHLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUN4RCxNQUFNLENBQUMsY0FBYyxDQUFDLElBQUksQ0FDeEIsUUFBUSxDQUFDLGVBQWUsQ0FBQyxFQUFFLENBQ3pCLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxlQUFlLEVBQUUsY0FBYyxDQUFDLENBQ3pELEVBQ0QsR0FBRyxDQUFDLENBQUMsZUFBa0QsRUFBRSxFQUFFLENBQ3pELElBQUksQ0FBQyxXQUFXLENBQUMsZUFBZSxFQUFFLEtBQUssQ0FBQyxDQUN6QyxDQUNGLENBQUM7S0FDSDs7Ozs7Ozs7SUFRRCxlQUFlLENBQUMsT0FBTyxFQUFFLE9BQU8sRUFBRSxjQUFjO1FBQzlDLE1BQU0sQ0FBQyxJQUFJLENBQUMsa0JBQWtCLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxDQUMxQyxRQUFRLENBQUMsQ0FBQyxjQUEwQyxFQUFFLEVBQUUsQ0FDdEQsSUFBSSxDQUFDLGtCQUFrQixDQUFDLGNBQWMsRUFBRSxjQUFjLENBQUMsQ0FDeEQsRUFDRCxRQUFRLENBQUMsQ0FBQyxJQUF1QyxFQUFFLEVBQUU7WUFDbkQsRUFBRSxDQUFDLENBQUMsT0FBTyxDQUFDLE1BQU0sR0FBRyxFQUFFLENBQUMsQ0FBQyxDQUFDOztnQkFDeEIsSUFBSSxPQUFPLEdBQUcsRUFBRSxDQUFDLFNBQVMsQ0FBQyxDQUFDO2dCQUM1QixPQUFPLE9BQU8sQ0FBQyxNQUFNLEdBQUcsRUFBRSxFQUFFLENBQUM7b0JBQzNCLE9BQU8sR0FBRyxPQUFPLENBQUMsSUFBSSxDQUNwQixNQUFNLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUNyRCxDQUFDO29CQUNGLE9BQU8sR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUUsRUFBRSxPQUFPLENBQUMsTUFBTSxDQUFDLENBQUM7aUJBQzdDO2dCQUNELEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQztvQkFDdkIsT0FBTyxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQztpQkFDakU7Z0JBQ0QsTUFBTSxDQUFDLE9BQU8sQ0FBQzthQUNoQjtZQUFDLElBQUksQ0FBQyxDQUFDO2dCQUNOLE1BQU0sQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksRUFBRSxPQUFPLENBQUMsQ0FBQzthQUN4QztTQUNGLENBQUMsQ0FDSCxDQUFDO0tBQ0g7Ozs7Ozs7O0lBT0QsZ0JBQWdCO1FBQ2QsTUFBTSxDQUFDLElBQUksQ0FBQyxnQkFBZ0IsQ0FDMUIsWUFBWSxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQzVCLFlBQVksQ0FBQyxPQUFPLENBQUMsYUFBYSxDQUNuQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztLQUN6Qzs7Ozs7SUFJRCxvQkFBb0I7UUFDbEIsTUFBTSxDQUFDLElBQUksQ0FBQyxnQkFBZ0IsQ0FDMUIsWUFBWSxDQUFDLGtCQUFrQixDQUFDLE9BQU8sRUFDdkMsWUFBWSxDQUFDLGtCQUFrQixDQUFDLGlCQUFpQixDQUNsRCxDQUFDLElBQUksQ0FDSixHQUFHLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FDYixJQUFJLENBQUMsZ0JBQWdCLENBQUMsV0FBVyxDQUFDLElBQUksVUFBVSxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUNuRSxDQUNGLENBQUM7S0FDSDs7Ozs7SUFJRCxlQUFlO1FBQ2IsTUFBTSxDQUFDLElBQUksQ0FBQyxnQkFBZ0IsQ0FDMUIsWUFBWSxDQUFDLGtCQUFrQixDQUFDLE9BQU8sRUFDdkMsWUFBWSxDQUFDLGtCQUFrQixDQUFDLFlBQVksQ0FDN0MsQ0FBQyxJQUFJLENBQ0osR0FBRyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQ2IsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFdBQVcsQ0FBQyxJQUFJLFVBQVUsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FDbkUsQ0FDRixDQUFDO0tBQ0g7Ozs7O0lBSUQsZ0JBQWdCO1FBQ2QsTUFBTSxDQUFDLElBQUksQ0FBQyxnQkFBZ0IsQ0FDMUIsWUFBWSxDQUFDLGtCQUFrQixDQUFDLE9BQU8sRUFDdkMsWUFBWSxDQUFDLGtCQUFrQixDQUFDLGFBQWEsQ0FDOUMsQ0FBQyxJQUFJLENBQ0osR0FBRyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQ2IsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFdBQVcsQ0FBQyxJQUFJLFVBQVUsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FDbkUsQ0FDRixDQUFDO0tBQ0g7Ozs7O0lBSUQsb0JBQW9CO1FBQ2xCLE1BQU0sQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQzFCLFlBQVksQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLEVBQ3ZDLFlBQVksQ0FBQyxrQkFBa0IsQ0FBQyxpQkFBaUIsQ0FDbEQsQ0FBQyxJQUFJLENBQ0osR0FBRyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQ2IsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFdBQVcsQ0FBQyxJQUFJLFVBQVUsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FDbkUsQ0FDRixDQUFDO0tBQ0g7Ozs7O0lBSUQsb0JBQW9CO1FBQ2xCLE1BQU0sQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQzFCLFlBQVksQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLEVBQ3ZDLFlBQVksQ0FBQyxrQkFBa0IsQ0FBQyxpQkFBaUIsQ0FDbEQsQ0FBQyxJQUFJLENBQ0osR0FBRyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQ2IsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFdBQVcsQ0FBQyxJQUFJLFVBQVUsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FDbkUsQ0FDRixDQUFDO0tBQ0g7Ozs7O0lBSUQsb0JBQW9CO1FBQ2xCLE1BQU0sQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQzFCLFlBQVksQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLEVBQ3ZDLFlBQVksQ0FBQyxrQkFBa0IsQ0FBQyxpQkFBaUIsQ0FDbEQsQ0FBQyxJQUFJLENBQ0osR0FBRyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQ2IsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFdBQVcsQ0FBQyxJQUFJLFVBQVUsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FDbkUsQ0FDRixDQUFDO0tBQ0g7Ozs7OztJQUtELFlBQVk7UUFDVixNQUFNLENBQUMsSUFBSSxDQUFDLGdCQUFnQixDQUMxQixZQUFZLENBQUMsa0JBQWtCLENBQUMsT0FBTyxFQUN2QyxZQUFZLENBQUMsa0JBQWtCLENBQUMsU0FBUyxDQUMxQyxDQUFDO0tBQ0g7Ozs7O0lBSUQsU0FBUztRQUNQLE1BQU0sQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQzFCLFlBQVksQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLEVBQ3ZDLFlBQVksQ0FBQyxrQkFBa0IsQ0FBQyxNQUFNLENBQ3ZDLENBQUM7S0FDSDs7O1lBL3BCRixVQUFVLFNBQUM7Z0JBQ1YsVUFBVSxFQUFFLE1BQU07YUFDbkI7Ozs7WUFOUSxtQkFBbUI7WUFDbkIsZ0JBQWdCO1lBQ2hCLG9CQUFvQiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGFibGUsIEV2ZW50RW1pdHRlciB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHtcbiAgbWFwLFxuICBtZXJnZU1hcCxcbiAgY29uY2F0LFxuICBtYXBUbyxcbiAgZmlsdGVyLFxuICB0YWtlVW50aWwsXG4gIGJ1ZmZlclRvZ2dsZSxcbiAgc2NhbixcbiAgdGFwLFxuICB0YWtlLFxuICB0aW1lb3V0LFxuICByZXRyeVdoZW4sXG4gIGRlbGF5XG59IGZyb20gJ3J4anMvb3BlcmF0b3JzJztcbmltcG9ydCB7XG4gIE9ic2VydmFibGUsXG4gIGZvcmtKb2luLFxuICBTdWJqZWN0LFxuICBkZWZlcixcbiAgZnJvbUV2ZW50LFxuICBvZixcbiAgZnJvbSxcbiAgaW50ZXJ2YWxcbn0gZnJvbSAncnhqcyc7XG5pbXBvcnQgeyBHYXR0U2VydmljZXMgfSBmcm9tICcuL2dhdHQtc2VydmljZXMnO1xuaW1wb3J0IHsgQnJvd3NlcldlYkJsdWV0b290aCB9IGZyb20gJy4uL3BsYXRmb3JtL2Jyb3dzZXInO1xuaW1wb3J0IHsgQ3lwaGVyQWVzU2VydmljZSB9IGZyb20gJy4uL2N5cGhlci9jeXBoZXItYWVzLnNlcnZpY2UnO1xuaW1wb3J0IHsgQ29uc29sZUxvZ2dlclNlcnZpY2UgfSBmcm9tICcuLi9sb2dnZXIuc2VydmljZSc7XG5cbkBJbmplY3RhYmxlKHtcbiAgcHJvdmlkZWRJbjogJ3Jvb3QnXG59KVxuZXhwb3J0IGNsYXNzIEJsdWV0b290aFNlcnZpY2UgZXh0ZW5kcyBTdWJqZWN0PEJsdWV0b290aFNlcnZpY2U+IHtcbiAgcHVibGljIF9kZXZpY2UkOiBFdmVudEVtaXR0ZXI8Qmx1ZXRvb3RoRGV2aWNlPjtcbiAgcHJpdmF0ZSBkZXZpY2U6IEJsdWV0b290aERldmljZTtcbiAgcHJpdmF0ZSBzZXJ2aWNlQ2hhcmFjdGVyaXN0aWNWc1N1YnNjcmlwdGlvbkxpc3QgPSB7fTtcbiAgcHJpdmF0ZSBub3RpZmllclN1YmplY3QgPSBuZXcgU3ViamVjdCgpO1xuICBwcml2YXRlIG5vdGlmaWVyU3RhcnRlZFN1YmplY3QgPSBuZXcgU3ViamVjdCgpO1xuICBwcml2YXRlIGJsdWV0b290aEF2YWlsYWJsZSA9IGZhbHNlO1xuICBjb25zdHJ1Y3RvcihcbiAgICBwdWJsaWMgX3dlYkJsZTogQnJvd3NlcldlYkJsdWV0b290aCxcbiAgICBwcml2YXRlIGN5cGhlckFlc1NlcnZpY2U6IEN5cGhlckFlc1NlcnZpY2UsXG4gICAgcHVibGljIF9jb25zb2xlOiBDb25zb2xlTG9nZ2VyU2VydmljZVxuICApIHtcbiAgICBzdXBlcigpO1xuICAgIHRoaXMuX2RldmljZSQgPSBuZXcgRXZlbnRFbWl0dGVyPEJsdWV0b290aERldmljZT4oKTtcbiAgICBpZiAoX3dlYkJsZS5fYmxlKSB7XG4gICAgICB0aGlzLmJsdWV0b290aEF2YWlsYWJsZSA9IHRydWU7XG4gICAgfVxuICB9XG5cbiAgaXNCbHVldG9vdGhBdmFpbGFibGUoKSB7XG4gICAgcmV0dXJuIHRoaXMuYmx1ZXRvb3RoQXZhaWxhYmxlO1xuICB9XG4gIC8qKlxuICAgKiBnZXQgdGhlIGN1cnJlbnQgZGV2aWNlLCBpZiB0aGUgZGV2aWNlIHJldHVybiBudWxsIGlzIGJlY2F1c2UgdGhlIGNvbm5lY3Rpb24gaGFzIGxvc3RcbiAgICogQHJldHVybnMgdGhlIGN1cnJlbnQgY29ubmNldGVkIGRldmljZVxuICAgKi9cbiAgZ2V0RGV2aWNlJCgpOiBPYnNlcnZhYmxlPEJsdWV0b290aERldmljZT4ge1xuICAgIHJldHVybiB0aGlzLl9kZXZpY2UkO1xuICB9XG5cbiAgZ2V0Tm90aWZpZXJTdGFydGVkU3ViamVjdCQoKSB7XG4gICAgcmV0dXJuIHRoaXMubm90aWZpZXJTdGFydGVkU3ViamVjdDtcbiAgfVxuICAvKipcbiAgICogc3RhcnQgYSBzdHJlYW0gYnkgbm90aWZpZXJzIGNoYXJhY3RlcmlzdGljc1xuICAgKiBAcGFyYW0gc2VydmljZSBUaGUgc2VydmljZSB0byB3aGljaCB0aGUgY2hhcmFjdGVyaXN0aWMgYmVsb25nc1xuICAgKiBAcGFyYW0gY2hhcmFjdGVyaXN0aWMgVGhlIGNoYXJhY3RlcmlzdGljIHdob3NlIHZhbHVlIHlvdSB3YW50IHRvIGxpc3RlblxuICAgKiBAcGFyYW0gb3B0aW9ucyBvYmplY3QgdGhhdCBjb250YWlucyB0aGVcbiAgICogc3RhcnRCeXRlOm51bWJlciAocmVxdWlyZWQpLCBzdG9wQnl0ZTpudW1iZXIgKHJlcXVpcmVkKSxcbiAgICogbGVuZ3RoUG9zaXRpb246IHsgc3RhcnQ6IG51bWJlciwgZW5kOiBudW1iZXIsIGxlbmd0aFBhZGRpbmc6IG51bWJlciB9IChyZXF1aXJlZClcbiAgICogQHJldHVybnMgQSBEYXRhVmlldyB0aGFuIGNvbnRhaW5zIHRoZSBjaGFyYWN0ZXJpc3RpYyB2YWx1ZVxuICAgKi9cbiAgc3RhcnROb3RpZmllckxpc3RlbmVyJChzZXJ2aWNlLCBjaGFyYWN0ZXJpc3RpYywgb3B0aW9ucykge1xuICAgIHJldHVybiBkZWZlcigoKSA9PiB7XG4gICAgICBvZihzZXJ2aWNlKVxuICAgICAgICAucGlwZShcbiAgICAgICAgICB0YXAoKCkgPT4gdGhpcy5fY29uc29sZS5sb2coJ0luaWNpYSBlbCBub3RpZmllciBjb24gbGEgaW5zdGFuY2lhOiAnLCB0aGlzLnNlcnZpY2VDaGFyYWN0ZXJpc3RpY1ZzU3Vic2NyaXB0aW9uTGlzdCkpLFxuICAgICAgICAgIGZpbHRlcihcbiAgICAgICAgICAgIF8gPT5cbiAgICAgICAgICAgICAgT2JqZWN0LmtleXModGhpcy5zZXJ2aWNlQ2hhcmFjdGVyaXN0aWNWc1N1YnNjcmlwdGlvbkxpc3QpLmluZGV4T2YoXG4gICAgICAgICAgICAgICAgYCR7c2VydmljZX0tJHtjaGFyYWN0ZXJpc3RpY31gXG4gICAgICAgICAgICAgICkgPT09IC0xXG4gICAgICAgICAgKVxuICAgICAgICApXG4gICAgICAgIC5zdWJzY3JpYmUoXG4gICAgICAgICAgKCkgPT4ge1xuICAgICAgICAgICAgdGhpcy5zZXJ2aWNlQ2hhcmFjdGVyaXN0aWNWc1N1YnNjcmlwdGlvbkxpc3RbYCR7c2VydmljZX0tJHtjaGFyYWN0ZXJpc3RpY31gXSA9XG4gICAgICAgICAgICAgIHRoaXMuYnVpbGROb3RpZmllckxpc3RlbmVyJChcbiAgICAgICAgICAgICAgICBzZXJ2aWNlLFxuICAgICAgICAgICAgICAgIGNoYXJhY3RlcmlzdGljLFxuICAgICAgICAgICAgICAgIG9wdGlvbnNcbiAgICAgICAgICAgICAgKS5zdWJzY3JpYmUobWVzc2FnZSA9PiB7XG4gICAgICAgICAgICAgICAgdGhpcy5ub3RpZmllclN1YmplY3QubmV4dChtZXNzYWdlKTtcbiAgICAgICAgICAgICAgfSk7XG5cbiAgICAgICAgICB9LFxuICAgICAgICAgIGVyciA9PiB7XG4gICAgICAgICAgICB0aGlzLl9jb25zb2xlLmxvZygnW0JMRTo6SW5mb10gRXJyb3IgaW4gbm90aWZpZXI6ICcsIGVycik7XG4gICAgICAgICAgfSxcbiAgICAgICAgICAoKSA9PiB7IH1cbiAgICAgICk7XG4gICAgICByZXR1cm4gb2YoYG5vdGlmaWVyIGFzIHN1YnNjcmliZWQ6IHNlcnZpY2U9ICR7c2VydmljZX0sIGNoYXJhY3RlcmlzdGljPSAke2NoYXJhY3RlcmlzdGljfWApO1xuICAgIH0pO1xuXG4gIH1cblxuICBzdG9wTm90aWZpZXJMaXN0ZW5lciQoc2VydmljZSwgY2hhcmFjdGVyaXN0aWMpIHtcbiAgICByZXR1cm4gZGVmZXIoKCkgPT4ge1xuICAgICAgLy8gdGhpcy5zZXJ2aWNlQ2hhcmFjdGVyaXN0aWNWc1N1YnNjcmlwdGlvbkxpc3RbYCR7c2VydmljZX0tJHtjaGFyYWN0ZXJpc3RpY31gXS51bnN1YnNjcmliZSgpO1xuICAgICAgZGVsZXRlIHRoaXMuc2VydmljZUNoYXJhY3RlcmlzdGljVnNTdWJzY3JpcHRpb25MaXN0W2Ake3NlcnZpY2V9LSR7Y2hhcmFjdGVyaXN0aWN9YF07XG4gICAgICByZXR1cm4gb2YoYHRoZSBub3RpZmllciBvZiB0aGUgY2hhcmFjdGVyaXN0aWMgJHtjaGFyYWN0ZXJpc3RpY30gYXMgYmVlbiBzdG9wcGVkYCk7XG4gICAgfSk7XG4gIH1cblxuICBwcml2YXRlIGJ1aWxkTm90aWZpZXJMaXN0ZW5lciQoc2VydmljZSwgY2hhcmFjdGVyaXN0aWMsIG9wdGlvbnMpIHtcbiAgICByZXR1cm4gdGhpcy5nZXRQcmltYXJ5U2VydmljZSQoc2VydmljZSkucGlwZShcbiAgICAgIHRhcChzZXJ2aWNlSW5zdGFuY2UgPT4gdGhpcy5fY29uc29sZS5sb2coYHRvbWEgZXhpdG9zYW1lbnRlIGVsIHNlcnZpY2lvID09PT09PT09PT09PT09PT0+ICR7c2VydmljZUluc3RhbmNlfWApKSxcbiAgICAgIG1lcmdlTWFwKHByaW1hcnlTZXJ2aWNlID0+XG4gICAgICAgIHRoaXMuZ2V0Q2hhcmFjdGVyaXN0aWMkKHByaW1hcnlTZXJ2aWNlLCBjaGFyYWN0ZXJpc3RpYylcbiAgICAgICAgLnBpcGUodGFwKGNoYXIgPT4gdGhpcy5fY29uc29sZS5sb2coYHRvbWEgZXhpdG9zYW1lbnRlIGxhIGNhcmFjdGVyaXN0aWNhID09PT09PT09PT09PT09PT0+ICR7Y2hhcn1gKSkpXG4gICAgICApLFxuICAgICAgbWVyZ2VNYXAoKGNoYXI6IEJsdWV0b290aFJlbW90ZUdBVFRDaGFyYWN0ZXJpc3RpYykgPT4ge1xuICAgICAgICByZXR1cm4gZGVmZXIoKCkgPT4ge1xuICAgICAgICAgIC8vIGVuYWJsZSB0aGUgY2hhcmFjdGVyaXN0aWMgbm90aWZpZXJcbiAgICAgICAgICByZXR1cm4gY2hhci5zdGFydE5vdGlmaWNhdGlvbnMoKTtcbiAgICAgICAgfSkucGlwZShcbiAgICAgICAgICByZXRyeVdoZW4oZXJyb3IgPT5cbiAgICAgICAgICAgIGVycm9yLnBpcGUoXG4gICAgICAgICAgICAgIHRhcCgoKSA9PiB0aGlzLl9jb25zb2xlLmxvZygnRVJST1IgRU4gRUwgc3RhcnROb3RpZmljYXRpb25zID09PT09PT09PT09PT09PT0+ICcpKSxcbiAgICAgICAgICAgICAgZGVsYXkoMTAwMCksXG4gICAgICAgICAgICAgIHRha2UoNSlcbiAgICAgICA