@jsprismarine/prismarine
Version:
Dedicated Minecraft Bedrock Edition server written in TypeScript
543 lines (541 loc) • 67.1 kB
JavaScript
import GameruleManager, { GameRules } from './GameruleManager.es.js';
import fs from 'node:fs';
import { parseJSON5 } from 'confbox';
import { Vector3 } from '@jsprismarine/math';
import { getGametypeName } from '@jsprismarine/minecraft';
import '@jsprismarine/errors';
import '@jsprismarine/jsbinaryutils';
import 'node:assert';
import { withCwd } from '../utils/cwd.es.js';
import '../block/BlockToolType.es.js';
import '@jsprismarine/bedrock-data';
import Timer from '../utils/Timer.es.js';
import { Item } from '../item/Item.es.js';
import Chunk from './chunk/Chunk.es.js';
import 'node:path';
import { BlockMappings } from '../block/BlockMappings.es.js';
import '@jsprismarine/brigadier';
import * as entity_Entities from '../entity/Entities.es.js';
import '../config/Config.es.js';
import '../network/packet/AddActorPacket.es.js';
import '../network/packet/MoveActorAbsolutePacket.es.js';
import '../network/packet/RemoveActorPacket.es.js';
import UUID from '../utils/UUID.es.js';
import '../network/handler/AnimateHandler.es.js';
import '../network/handler/CommandRequestHandler.es.js';
import '../network/handler/ContainerCloseHandler.es.js';
import '../network/handler/EmoteListHandler.es.js';
import '../network/handler/InteractHandler.es.js';
import '../network/handler/InventoryTransactionHandler.es.js';
import '../network/handler/LevelSoundEventHandler.es.js';
import '../network/handler/LoginHandler.es.js';
import '../network/handler/MobEquipmentHandler.es.js';
import '../network/handler/MovePlayerHandler.es.js';
import '../network/handler/PacketViolationWarningHandler.es.js';
import '../network/handler/PlayerActionHandler.es.js';
import '../network/handler/RequestChunkRadiusHandler.es.js';
import '../network/handler/RequestNetworkSettingsHandler.es.js';
import '../network/handler/ResourcePackResponseHandler.es.js';
import '../network/handler/ServerSettingsRequestHandler.es.js';
import '../network/handler/SetDefaultGametypeHandler.es.js';
import '../network/handler/SetLocalPlayerAsInitializedHandler.es.js';
import '../network/handler/SetPlayerGametypeHandler.es.js';
import '../network/handler/TextHandler.es.js';
import '../network/handler/TickSyncHandler.es.js';
import '../network/packet/ActorFallPacket.es.js';
import '../network/packet/AddItemActorPacket.es.js';
import '../network/packet/AddPlayerPacket.es.js';
import '../network/packet/AnimatePacket.es.js';
import '../network/packet/AvailableActorIdentifiersPacket.es.js';
import '../network/packet/AvailableCommandsPacket.es.js';
import 'zlib';
import '../network/CompressionProvider.es.js';
import '../network/packet/NetworkSettingsPacket.es.js';
import '../network/packet/BiomeDefinitionListPacket.es.js';
import '../network/packet/ChangeDimensionPacket.es.js';
import '../network/packet/ChunkRadiusUpdatedPacket.es.js';
import '../network/packet/CommandRequestPacket.es.js';
import '../network/packet/ContainerClosePacket.es.js';
import '../network/packet/ContainerOpenPacket.es.js';
import '../network/packet/CreativeContentPacket.es.js';
import '../network/packet/DisconnectPacket.es.js';
import '../network/packet/EmoteListPacket.es.js';
import '../network/packet/InteractPacket.es.js';
import '../network/packet/InventoryContentPacket.es.js';
import '../network/packet/InventoryTransactionPacket.es.js';
import '../network/packet/ItemComponentPacket.es.js';
import '../network/packet/ItemStackRequestPacket.es.js';
import '../network/packet/ItemStackResponsePacket.es.js';
import '../network/packet/LevelChunkPacket.es.js';
import LevelSoundEventPacket from '../network/packet/LevelSoundEventPacket.es.js';
import '../network/packet/LoginPacket.es.js';
import '../network/packet/MobEquipmentPacket.es.js';
import '../network/packet/MovePlayerPacket.es.js';
import '../network/packet/NetworkChunkPublisherUpdatePacket.es.js';
import '../network/packet/OnScreenTextureAnimationPacket.es.js';
import '../network/packet/PacketViolationWarningPacket.es.js';
import '../network/packet/PlaySoundPacket.es.js';
import '../network/packet/PlayStatusPacket.es.js';
import '../network/packet/PlayerActionPacket.es.js';
import '../network/packet/PlayerListPacket.es.js';
import '../network/packet/PlayerSkinPacket.es.js';
import '../network/packet/RequestChunkRadiusPacket.es.js';
import '../network/packet/RequestNetworkSettingsPacket.es.js';
import '../network/packet/ResourcePackResponsePacket.es.js';
import '../network/packet/ResourcePackStackPacket.es.js';
import '../network/packet/ResourcePacksInfoPacket.es.js';
import '../network/packet/ServerSettingsRequestPacket.es.js';
import '../network/packet/SetActorDataPacket.es.js';
import '../network/packet/SetDefaultGametypePacket.es.js';
import '../network/packet/SetHealthPacket.es.js';
import '../network/packet/SetLocalPlayerAsInitializedPacket.es.js';
import '../network/packet/SetPlayerGametypePacket.es.js';
import '../network/packet/SetTimePacket.es.js';
import '../network/packet/ShowProfilePacket.es.js';
import '../network/packet/StartGamePacket.es.js';
import '../network/packet/TextPacket.es.js';
import '../network/packet/TickSyncPacket.es.js';
import '../network/packet/TransferPacket.es.js';
import '../network/packet/UpdateAdventureSettingsPacket.es.js';
import '../network/packet/UpdateAttributesPacket.es.js';
import UpdateBlockPacket from '../network/packet/UpdateBlockPacket.es.js';
import WorldEventPacket from '../network/packet/WorldEventPacket.es.js';
import 'node:process';
import 'node:readline';
import 'heap';
import '../network/packet/UpdateAbilitiesPacket.es.js';
import '@jsprismarine/raknet';
import 'evt';
import 'assert';
const LEVEL_DATA_FILE_NAME = "level.json";
const WORLDS_FOLDER_NAME = "worlds";
class World {
uuid = UUID.randomString();
name;
entities = /* @__PURE__ */ new Map();
chunks = /* @__PURE__ */ new Map();
gameruleManager;
currentTick = 0;
provider;
server;
seed;
generator;
config;
spawn = null;
constructor({ name, server, provider, seed, generator, config }) {
this.name = name;
this.server = server;
this.provider = provider;
this.gameruleManager = new GameruleManager(server);
this.seed = seed;
this.generator = generator;
this.config = config ?? {};
this.gameruleManager.setGamerule(GameRules.ShowCoordinates, true, true);
try {
const path = withCwd(WORLDS_FOLDER_NAME, this.name, "playerdata");
if (!fs.existsSync(path)) fs.mkdirSync(path, { recursive: true });
} catch (error) {
this.server.getLogger().error(`Failed to create world folders for ${this.name}`);
this.server.getLogger().error(error);
}
}
/**
* On enable hook.
* @group Lifecycle
*/
async enable() {
this.server.on("tick", async (evt) => this.update(evt.getTick()));
const level = await this.getLevelData();
if (level.spawn) this.setSpawnPosition(Vector3.fromObject(level.spawn));
if (level.gameRules) {
level.gameRules.forEach(
([name, [value, editable]]) => this.gameruleManager.setGamerule(name, value, editable)
);
}
if (level.entities) {
for (const entityData of level.entities) {
const Entity = Array.from(Object.values(entity_Entities)).find((e) => e.MOB_ID === entityData.type);
if (!Entity) {
this.server.getLogger().warn(`Entity type ${entityData.type} not found`);
continue;
}
await this.addEntity(
new Entity({
world: this,
uuid: entityData.uuid,
...entityData.position,
server: this.server
})
);
}
}
this.provider.setWorld(this);
await this.provider.enable();
this.server.getLogger().info(`Preparing start region for dimension ${this.getFormattedName()}`);
const chunksToLoad = [];
const timer = new Timer();
const size = this.server.getConfig().getViewDistance() * 5;
for (let x = 0; x < size; x++) {
for (let z = 0; z < size; z++) {
chunksToLoad.push(this.loadChunk(x, z, true));
}
}
await Promise.all(chunksToLoad);
this.server.getLogger().verbose(`(took §e${timer.stop()} ms§r)`);
}
/**
* On disable hook.
* @group Lifecycle
*/
async disable() {
await this.save();
await this.provider.disable();
}
getGenerator() {
return this.generator;
}
/**
* Called every tick.
*
* @param tick
*/
async update(tick) {
this.currentTick++;
if (this.currentTick / 20 === 120) {
await this.save();
}
await Promise.all(this.getEntities().map((entity) => entity.update(tick)));
await this.sendTime();
}
/**
* Returns a block instance in the given world position.
* @param {number} x - block x
* @param {number} y - block y
* @param {number} z - block z
* @param {number} [layer=0] - block storage layer (0 for blocks, 1 for liquids)
*/
async getBlock(x, y, z, layer = 0) {
const blockId = (await this.getChunkAt(x, z)).getBlock(x, y, z, layer);
const block = this.server.getBlockManager().getBlockByIdAndMeta(blockId.id, blockId.meta);
if (!block) return this.server.getBlockManager().getBlock("minecraft:air");
return block;
}
/**
* Returns the chunk in the specifies x and z, if the chunk doesn't exists
* it is generated.
*/
async getChunk(cx, cz) {
const index = Chunk.packXZ(cx, cz);
if (!this.chunks.has(index)) return this.loadChunk(cx, cz);
return this.chunks.get(index);
}
/**
* Loads a chunk in a given x and z and returns its.
* @param {number} x - x coordinate.
* @param {number} z - z coordinate.
*/
async loadChunk(x, z, _ignoreWarn) {
const index = Chunk.packXZ(x, z);
const chunk = await this.provider.readChunk(x, z, this.seed, this.generator, this.config);
this.chunks.set(index, chunk);
return chunk;
}
/**
* Sends a world event packet to all the viewers in the position chunk.
* @param {Vector3} position - world position.
* @param {number} event - event identifier.
* @param {number} data - event data.
*/
async sendWorldEvent(position, event, data) {
const worldEventPacket = new WorldEventPacket();
worldEventPacket.eventId = event;
worldEventPacket.data = data;
await Promise.all(this.getPlayers().map((player) => player.getNetworkSession().send(worldEventPacket)));
}
async getChunkAt(x, z = 0) {
if (x instanceof Vector3) {
return this.getChunkAt(x.getX(), x.getZ());
}
return this.getChunk(x >> 4, z >> 4);
}
/**
* Returns the world default spawn position.
*/
async getSpawnPosition() {
if (this.spawn) return this.spawn;
const x = 0;
const z = 0;
const chunk = await this.getChunkAt(x, z);
const y = chunk.getHighestBlockAt(x, z) + 1;
return new Vector3(z, y + 2, z);
}
/**
* Set the world's spawn position.
* @param {Vector3} pos - The position.
*/
setSpawnPosition(pos) {
this.spawn = pos;
}
// TODO: move this?
async useItemOn(itemInHand, blockPosition, face, clickPosition, player) {
if (itemInHand instanceof Item) return;
const block = itemInHand;
const blockId = (await this.getChunkAt(blockPosition)).getBlock(blockPosition);
const clickedBlock = this.server.getBlockManager().getBlockByIdAndMeta(blockId.id, blockId.meta);
if (!block || !clickedBlock) return;
if (clickedBlock.getName() === "minecraft:air" || !block.canBePlaced()) return;
const placedPosition = new Vector3(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
if (!clickedBlock.canBeReplaced())
switch (face) {
case 0:
placedPosition.setY(placedPosition.getY() - 1);
break;
case 1:
placedPosition.setY(placedPosition.getY() + 1);
break;
case 2:
placedPosition.setZ(placedPosition.getZ() - 1);
break;
case 3:
placedPosition.setZ(placedPosition.getZ() + 1);
break;
case 4:
placedPosition.setX(placedPosition.getX() - 1);
break;
case 5:
placedPosition.setX(placedPosition.getX() + 1);
break;
default:
throw new Error("Invalid Face");
}
if (blockPosition.getY() < 0 || blockPosition.getY() > 255) return;
const success = await new Promise(async (resolve) => {
try {
const chunk = await this.getChunkAt(placedPosition.getX(), placedPosition.getZ());
chunk.setBlock(placedPosition.getX(), placedPosition.getY(), placedPosition.getZ(), block);
resolve(true);
} catch (error) {
player.getServer().getLogger().warn(`${player.getName()} failed to place block due to ${error}`);
await player.sendMessage(error?.message);
resolve(false);
}
});
if (!success) {
if (placedPosition.getY() < 0) return;
const blockUpdate2 = new UpdateBlockPacket();
blockUpdate2.x = placedPosition.getX();
blockUpdate2.y = placedPosition.getY();
blockUpdate2.z = placedPosition.getZ();
blockUpdate2.blockRuntimeId = BlockMappings.getRuntimeId(clickedBlock.getName());
return;
}
const runtimeId = BlockMappings.getRuntimeId(block.getName());
const blockUpdate = new UpdateBlockPacket();
blockUpdate.x = placedPosition.getX();
blockUpdate.y = placedPosition.getY();
blockUpdate.z = placedPosition.getZ();
blockUpdate.blockRuntimeId = runtimeId;
await Promise.all(
this.server.getSessionManager().getAllPlayers().map(
async (onlinePlayer) => onlinePlayer.getNetworkSession().getConnection().sendDataPacket(blockUpdate)
)
);
const pk = new LevelSoundEventPacket();
pk.sound = 6;
pk.positionX = placedPosition.getX();
pk.positionY = placedPosition.getY();
pk.positionZ = placedPosition.getZ();
pk.extraData = runtimeId;
pk.disableRelativeVolume = false;
await Promise.all(
player.getWorld().getPlayers().map((target) => target.getNetworkSession().send(pk))
);
}
/**
* Sends the current time to all players in the world.
*/
async sendTime() {
await Promise.all(this.getPlayers().map((player) => player.getNetworkSession().sendTime(this.getTicks())));
}
/**
* Adds an entity to the level.
* @param {Entity} entity - The entity to add.
*/
async addEntity(entity) {
this.entities.set(entity.getRuntimeId(), entity);
if (!entity.isPlayer()) await entity.sendSpawn();
else await Promise.all(this.getEntities().map((e) => e.sendSpawn(entity)));
}
/**
* Removes an entity from the level.
* @param {Entity} entity - The entity to remove.
*/
async removeEntity(entity) {
if (!entity.isPlayer()) await entity.sendDespawn();
else await Promise.all(this.getEntities().map((e) => e.sendDespawn(entity)));
this.entities.delete(entity.getRuntimeId());
}
/**
* Get all entities in this world.
* @returns {Entity[]} the entities.
*/
getEntities() {
return Array.from(this.entities.values());
}
/**
* Get all players in this world.
* @returns {Player[]} the players.
*/
getPlayers() {
return this.getEntities().filter((e) => e.isPlayer()).filter((p) => p.isOnline());
}
/**
* Saves changed chunks into disk.
*/
async saveChunks() {
const timer = new Timer();
this.server.getLogger().info(`Saving chunks for level ${this.getFormattedName()}`);
await Promise.all(
Array.from(this.chunks.values()).filter((c) => c.getHasChanged()).map(async (chunk) => this.provider.writeChunk(chunk))
);
this.server.getLogger().verbose(`(took §e${timer.stop()} ms§r)!`);
}
async save() {
this.getPlayers().forEach(async (player) => {
await this.savePlayerData(player);
});
await this.saveChunks();
await this.saveLevelData();
}
getGameruleManager() {
return this.gameruleManager;
}
getTicks() {
return this.currentTick;
}
setTicks(tick) {
this.currentTick = tick;
}
getProvider() {
return this.provider;
}
// This is used for example in start game packet
getUUID() {
return this.uuid;
}
getName() {
return this.name;
}
getFormattedName() {
return `§b'${this.name}'/${this.generator.constructor.name}§r`;
}
getSeed() {
return this.seed;
}
async getLevelData() {
const path = withCwd(WORLDS_FOLDER_NAME, this.name, LEVEL_DATA_FILE_NAME);
if (!fs.existsSync(path)) return {};
try {
const raw = await fs.promises.readFile(path, "utf-8");
return parseJSON5(raw.toString());
} catch (error) {
this.server.getLogger().error(error);
}
return {};
}
async saveLevelData() {
const data = {
spawn: await this.getSpawnPosition(),
gamerules: Array.from(this.getGameruleManager().getGamerules()),
entities: this.getEntities().filter((entity) => !entity.isPlayer() && !entity.isConsole()).map((entity) => ({
uuid: entity.getUUID(),
type: entity.getType(),
position: {
x: entity.getX(),
y: entity.getY(),
z: entity.getZ(),
pitch: entity.pitch,
yaw: entity.yaw,
headYaw: entity.headYaw
}
}))
};
try {
await fs.promises.writeFile(
// FIXME: This overwrites comments in the file.
withCwd(WORLDS_FOLDER_NAME, this.name, LEVEL_DATA_FILE_NAME),
JSON.stringify(data, null, 4)
);
} catch (error) {
this.server.getLogger().error(`Failed to save level data`);
this.server.getLogger().error(error);
}
}
/**
* Get the player data for a player.
* @param {Player} player - The player to get the data for.
* @returns {Promise<WorldPlayerData>} The player data.
*/
async getPlayerData(player) {
try {
const fileName = player.getXUID();
if (!fileName) {
throw new Error("Player has no XUID");
}
const raw = await fs.promises.readFile(
withCwd(WORLDS_FOLDER_NAME, this.name, "playerdata", `${player.getXUID() || player.getName()}.json`),
{ flag: "r", encoding: "utf-8" }
);
return parseJSON5(raw.toString());
} catch (error) {
this.server.getLogger().debug(`PlayerData is missing for player ${player.getXUID()}`);
this.server.getLogger().error(error);
const spawn = await this.getSpawnPosition();
return {
gamemode: this.server.getConfig().getGamemode(),
position: {
x: spawn.getX(),
y: spawn.getY(),
z: spawn.getZ(),
pitch: 0,
yaw: 0,
headYaw: 0
}
};
}
}
async savePlayerData(player) {
const data = {
uuid: player.getUUID(),
username: player.getName(),
gamemode: getGametypeName(player.gamemode),
position: {
x: player.getX(),
y: player.getY(),
z: player.getZ(),
pitch: player.pitch,
yaw: player.yaw,
headYaw: player.headYaw
}
};
try {
await fs.promises.writeFile(
// FIXME: This overwrites comments in the file.
withCwd(WORLDS_FOLDER_NAME, this.name, "playerdata", `${player.getXUID() || player.getName()}.json`),
JSON.stringify(data, null, 4),
{ flag: "w+", encoding: "utf-8", flush: true }
);
} catch (error) {
this.server.getLogger().error(`Failed to save player data`);
this.server.getLogger().error(error);
}
}
/**
* @returns {Server} The server instance.
*/
getServer() {
return this.server;
}
}
export { World };
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiV29ybGQuZXMuanMiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy93b3JsZC9Xb3JsZC50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgR2FtZXJ1bGVNYW5hZ2VyLCB7IEdhbWVSdWxlcyB9IGZyb20gJy4vR2FtZXJ1bGVNYW5hZ2VyJztcblxuaW1wb3J0IGZzIGZyb20gJ25vZGU6ZnMnO1xuXG5pbXBvcnQgeyBwYXJzZUpTT041IH0gZnJvbSAnY29uZmJveCc7XG5cbmltcG9ydCB7IFZlY3RvcjMgfSBmcm9tICdAanNwcmlzbWFyaW5lL21hdGgnO1xuaW1wb3J0IHsgZ2V0R2FtZXR5cGVOYW1lIH0gZnJvbSAnQGpzcHJpc21hcmluZS9taW5lY3JhZnQnO1xuaW1wb3J0IHR5cGUgeyBCbG9jaywgUGxheWVyLCBTZXJ2ZXIsIFNlcnZpY2UgfSBmcm9tICcuLi8nO1xuaW1wb3J0IHsgVGltZXIsIFVVSUQgfSBmcm9tICcuLi8nO1xuaW1wb3J0IHsgQmxvY2tNYXBwaW5ncyB9IGZyb20gJy4uL2Jsb2NrL0Jsb2NrTWFwcGluZ3MnO1xuaW1wb3J0ICogYXMgRW50aXRpZXMgZnJvbSAnLi4vZW50aXR5L0VudGl0aWVzJztcbmltcG9ydCB0eXBlIHsgRW50aXR5IH0gZnJvbSAnLi4vZW50aXR5L0VudGl0eSc7XG5pbXBvcnQgeyBJdGVtIH0gZnJvbSAnLi4vaXRlbS9JdGVtJztcbmltcG9ydCBMZXZlbFNvdW5kRXZlbnRQYWNrZXQgZnJvbSAnLi4vbmV0d29yay9wYWNrZXQvTGV2ZWxTb3VuZEV2ZW50UGFja2V0JztcbmltcG9ydCBVcGRhdGVCbG9ja1BhY2tldCBmcm9tICcuLi9uZXR3b3JrL3BhY2tldC9VcGRhdGVCbG9ja1BhY2tldCc7XG5pbXBvcnQgdHlwZSB7IFdvcmxkRXZlbnQgfSBmcm9tICcuLi9uZXR3b3JrL3BhY2tldC9Xb3JsZEV2ZW50UGFja2V0JztcbmltcG9ydCBXb3JsZEV2ZW50UGFja2V0IGZyb20gJy4uL25ldHdvcmsvcGFja2V0L1dvcmxkRXZlbnRQYWNrZXQnO1xuaW1wb3J0IHsgd2l0aEN3ZCB9IGZyb20gJy4uL3V0aWxzL2N3ZCc7XG5pbXBvcnQgdHlwZSB7IEdlbmVyYXRvciB9IGZyb20gJy4vR2VuZXJhdG9yJztcbmltcG9ydCBDaHVuayBmcm9tICcuL2NodW5rL0NodW5rJztcbmltcG9ydCB0eXBlIEJhc2VQcm92aWRlciBmcm9tICcuL3Byb3ZpZGVycy9CYXNlUHJvdmlkZXInO1xuXG5jb25zdCBMRVZFTF9EQVRBX0ZJTEVfTkFNRSA9ICdsZXZlbC5qc29uJztcbmNvbnN0IFdPUkxEU19GT0xERVJfTkFNRSA9ICd3b3JsZHMnO1xuXG5leHBvcnQgaW50ZXJmYWNlIFdvcmxkRGF0YSB7XG4gICAgbmFtZTogc3RyaW5nO1xuICAgIHBhdGg6IHN0cmluZztcbiAgICBzZXJ2ZXI6IFNlcnZlcjtcbiAgICBwcm92aWRlcjogQmFzZVByb3ZpZGVyO1xuICAgIHNlZWQ6IG51bWJlcjtcbiAgICBnZW5lcmF0b3I6IEdlbmVyYXRvcjtcbiAgICBjb25maWc/OiBhbnk7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgTGV2ZWxEYXRhIHtcbiAgICBzcGF3bjogeyB4OiBudW1iZXI7IHk6IG51bWJlcjsgejogbnVtYmVyIH0gfCB1bmRlZmluZWQ7XG4gICAgZ2FtZVJ1bGVzOiBBcnJheTxbc3RyaW5nLCBhbnldPjtcbiAgICBlbnRpdGllczogQXJyYXk8e1xuICAgICAgICB1dWlkOiBzdHJpbmc7XG4gICAgICAgIHR5cGU6IHN0cmluZztcbiAgICAgICAgcG9zaXRpb246IHtcbiAgICAgICAgICAgIHg6IG51bWJlcjtcbiAgICAgICAgICAgIHk6IG51bWJlcjtcbiAgICAgICAgICAgIHo6IG51bWJlcjtcbiAgICAgICAgfTtcbiAgICB9Pjtcbn1cbmV4cG9ydCBpbnRlcmZhY2UgV29ybGRQbGF5ZXJEYXRhIHtcbiAgICBnYW1lbW9kZTogc3RyaW5nO1xuICAgIHBvc2l0aW9uOiB7XG4gICAgICAgIHg6IG51bWJlcjtcbiAgICAgICAgeTogbnVtYmVyO1xuICAgICAgICB6OiBudW1iZXI7XG4gICAgICAgIHBpdGNoOiBudW1iZXI7XG4gICAgICAgIHlhdzogbnVtYmVyO1xuICAgICAgICBoZWFkWWF3OiBudW1iZXI7XG4gICAgfTtcbn1cblxuZXhwb3J0IGNsYXNzIFdvcmxkIGltcGxlbWVudHMgU2VydmljZSB7XG4gICAgcHJpdmF0ZSByZWFkb25seSB1dWlkOiBzdHJpbmcgPSBVVUlELnJhbmRvbVN0cmluZygpO1xuICAgIHByaXZhdGUgbmFtZTogc3RyaW5nO1xuXG4gICAgcHJpdmF0ZSByZWFkb25seSBlbnRpdGllczogTWFwPGJpZ2ludCwgRW50aXR5PiA9IG5ldyBNYXAoKTtcbiAgICBwcml2YXRlIHJlYWRvbmx5IGNodW5rczogTWFwPGJpZ2ludCwgQ2h1bms+ID0gbmV3IE1hcCgpO1xuICAgIHByaXZhdGUgcmVhZG9ubHkgZ2FtZXJ1bGVNYW5hZ2VyOiBHYW1lcnVsZU1hbmFnZXI7XG4gICAgcHJpdmF0ZSBjdXJyZW50VGljayA9IDA7XG4gICAgcHJpdmF0ZSByZWFkb25seSBwcm92aWRlcjogQmFzZVByb3ZpZGVyO1xuICAgIHByaXZhdGUgcmVhZG9ubHkgc2VydmVyOiBTZXJ2ZXI7XG4gICAgcHJpdmF0ZSByZWFkb25seSBzZWVkOiBudW1iZXI7XG4gICAgcHJpdmF0ZSByZWFkb25seSBnZW5lcmF0b3I6IEdlbmVyYXRvcjtcbiAgICBwcml2YXRlIHJlYWRvbmx5IGNvbmZpZzogT2JqZWN0O1xuICAgIHByaXZhdGUgc3Bhd246IFZlY3RvcjMgfCBudWxsID0gbnVsbDtcblxuICAgIHB1YmxpYyBjb25zdHJ1Y3Rvcih7IG5hbWUsIHNlcnZlciwgcHJvdmlkZXIsIHNlZWQsIGdlbmVyYXRvciwgY29uZmlnIH06IFdvcmxkRGF0YSkge1xuICAgICAgICB0aGlzLm5hbWUgPSBuYW1lO1xuICAgICAgICB0aGlzLnNlcnZlciA9IHNlcnZlcjtcbiAgICAgICAgdGhpcy5wcm92aWRlciA9IHByb3ZpZGVyO1xuICAgICAgICB0aGlzLmdhbWVydWxlTWFuYWdlciA9IG5ldyBHYW1lcnVsZU1hbmFnZXIoc2VydmVyKTtcbiAgICAgICAgdGhpcy5zZWVkID0gc2VlZDtcbiAgICAgICAgdGhpcy5nZW5lcmF0b3IgPSBnZW5lcmF0b3I7XG4gICAgICAgIHRoaXMuY29uZmlnID0gY29uZmlnID8/IHt9O1xuXG4gICAgICAgIHRoaXMuZ2FtZXJ1bGVNYW5hZ2VyLnNldEdhbWVydWxlKEdhbWVSdWxlcy5TaG93Q29vcmRpbmF0ZXMsIHRydWUsIHRydWUpO1xuXG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICAvLyBDcmVhdGUgZm9sZGVycyBpZiB0aGV5IGRvbid0IGV4aXN0LlxuICAgICAgICAgICAgY29uc3QgcGF0aCA9IHdpdGhDd2QoV09STERTX0ZPTERFUl9OQU1FLCB0aGlzLm5hbWUsICdwbGF5ZXJkYXRhJyk7XG4gICAgICAgICAgICBpZiAoIWZzLmV4aXN0c1N5bmMocGF0aCkpIGZzLm1rZGlyU3luYyhwYXRoLCB7IHJlY3Vyc2l2ZTogdHJ1ZSB9KTtcbiAgICAgICAgfSBjYXRjaCAoZXJyb3I6IHVua25vd24pIHtcbiAgICAgICAgICAgIHRoaXMuc2VydmVyLmdldExvZ2dlcigpLmVycm9yKGBGYWlsZWQgdG8gY3JlYXRlIHdvcmxkIGZvbGRlcnMgZm9yICR7dGhpcy5uYW1lfWApO1xuICAgICAgICAgICAgdGhpcy5zZXJ2ZXIuZ2V0TG9nZ2VyKCkuZXJyb3IoZXJyb3IpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogT24gZW5hYmxlIGhvb2suXG4gICAgICogQGdyb3VwIExpZmVjeWNsZVxuICAgICAqL1xuICAgIHB1YmxpYyBhc3luYyBlbmFibGUoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHRoaXMuc2VydmVyLm9uKCd0aWNrJywgYXN5bmMgKGV2dCkgPT4gdGhpcy51cGRhdGUoZXZ0LmdldFRpY2soKSkpO1xuXG4gICAgICAgIGNvbnN0IGxldmVsID0gYXdhaXQgdGhpcy5nZXRMZXZlbERhdGEoKTtcbiAgICAgICAgaWYgKGxldmVsLnNwYXduKSB0aGlzLnNldFNwYXduUG9zaXRpb24oVmVjdG9yMy5mcm9tT2JqZWN0KGxldmVsLnNwYXduKSk7XG4gICAgICAgIGlmIChsZXZlbC5nYW1lUnVsZXMpIHtcbiAgICAgICAgICAgIGxldmVsLmdhbWVSdWxlcy5mb3JFYWNoKChbbmFtZSwgW3ZhbHVlLCBlZGl0YWJsZV1dKSA9PlxuICAgICAgICAgICAgICAgIHRoaXMuZ2FtZXJ1bGVNYW5hZ2VyLnNldEdhbWVydWxlKG5hbWUsIHZhbHVlLCBlZGl0YWJsZSlcbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGxldmVsLmVudGl0aWVzKSB7XG4gICAgICAgICAgICBmb3IgKGNvbnN0IGVudGl0eURhdGEgb2YgbGV2ZWwuZW50aXRpZXMpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBFbnRpdHkgPSBBcnJheS5mcm9tKE9iamVjdC52YWx1ZXMoRW50aXRpZXMpKS5maW5kKChlKSA9PiBlLk1PQl9JRCA9PT0gZW50aXR5RGF0YS50eXBlKTtcbiAgICAgICAgICAgICAgICBpZiAoIUVudGl0eSkge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLnNlcnZlci5nZXRMb2dnZXIoKS53YXJuKGBFbnRpdHkgdHlwZSAke2VudGl0eURhdGEudHlwZX0gbm90IGZvdW5kYCk7XG4gICAgICAgICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGF3YWl0IHRoaXMuYWRkRW50aXR5KFxuICAgICAgICAgICAgICAgICAgICBuZXcgRW50aXR5KHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHdvcmxkOiB0aGlzLFxuICAgICAgICAgICAgICAgICAgICAgICAgdXVpZDogZW50aXR5RGF0YS51dWlkLFxuICAgICAgICAgICAgICAgICAgICAgICAgLi4uZW50aXR5RGF0YS5wb3NpdGlvbixcbiAgICAgICAgICAgICAgICAgICAgICAgIHNlcnZlcjogdGhpcy5zZXJ2ZXJcbiAgICAgICAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5wcm92aWRlci5zZXRXb3JsZCh0aGlzKTtcbiAgICAgICAgYXdhaXQgdGhpcy5wcm92aWRlci5lbmFibGUoKTtcblxuICAgICAgICB0aGlzLnNlcnZlci5nZXRMb2dnZXIoKS5pbmZvKGBQcmVwYXJpbmcgc3RhcnQgcmVnaW9uIGZvciBkaW1lbnNpb24gJHt0aGlzLmdldEZvcm1hdHRlZE5hbWUoKX1gKTtcbiAgICAgICAgY29uc3QgY2h1bmtzVG9Mb2FkOiBBcnJheTxQcm9taXNlPENodW5rPj4gPSBbXTtcbiAgICAgICAgY29uc3QgdGltZXIgPSBuZXcgVGltZXIoKTtcblxuICAgICAgICBjb25zdCBzaXplID0gdGhpcy5zZXJ2ZXIuZ2V0Q29uZmlnKCkuZ2V0Vmlld0Rpc3RhbmNlKCkgKiA1O1xuICAgICAgICBmb3IgKGxldCB4ID0gMDsgeCA8IHNpemU7IHgrKykge1xuICAgICAgICAgICAgZm9yIChsZXQgeiA9IDA7IHogPCBzaXplOyB6KyspIHtcbiAgICAgICAgICAgICAgICBjaHVua3NUb0xvYWQucHVzaCh0aGlzLmxvYWRDaHVuayh4LCB6LCB0cnVlKSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBhd2FpdCBQcm9taXNlLmFsbChjaHVua3NUb0xvYWQpO1xuICAgICAgICB0aGlzLnNlcnZlci5nZXRMb2dnZXIoKS52ZXJib3NlKGAodG9vayDCp2Uke3RpbWVyLnN0b3AoKX0gbXPCp3IpYCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogT24gZGlzYWJsZSBob29rLlxuICAgICAqIEBncm91cCBMaWZlY3ljbGVcbiAgICAgKi9cbiAgICBwdWJsaWMgYXN5bmMgZGlzYWJsZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgYXdhaXQgdGhpcy5zYXZlKCk7XG4gICAgICAgIGF3YWl0IHRoaXMucHJvdmlkZXIuZGlzYWJsZSgpO1xuICAgIH1cblxuICAgIHB1YmxpYyBnZXRHZW5lcmF0b3IoKTogR2VuZXJhdG9yIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZ2VuZXJhdG9yO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENhbGxlZCBldmVyeSB0aWNrLlxuICAgICAqXG4gICAgICogQHBhcmFtIHRpY2tcbiAgICAgKi9cbiAgICBwdWJsaWMgYXN5bmMgdXBkYXRlKHRpY2s6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICAvLyBUT0RPOiB0aWNrIGNodW5rc1xuXG4gICAgICAgIC8vIENvbnRpbnVlIHdvcmxkIHRpbWUgdGlja3NcbiAgICAgICAgdGhpcy5jdXJyZW50VGljaysrO1xuXG4gICAgICAgIC8vIEF1dG8gc2F2ZSBldmVyeSAyIG1pbnV0ZXNcbiAgICAgICAgaWYgKHRoaXMuY3VycmVudFRpY2sgLyAyMCA9PT0gMTIwKSB7XG4gICAgICAgICAgICBhd2FpdCB0aGlzLnNhdmUoKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGF3YWl0IFByb21pc2UuYWxsKHRoaXMuZ2V0RW50aXRpZXMoKS5tYXAoKGVudGl0eSkgPT4gZW50aXR5LnVwZGF0ZSh0aWNrKSkpO1xuICAgICAgICBhd2FpdCB0aGlzLnNlbmRUaW1lKCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyBhIGJsb2NrIGluc3RhbmNlIGluIHRoZSBnaXZlbiB3b3JsZCBwb3NpdGlvbi5cbiAgICAgKiBAcGFyYW0ge251bWJlcn0geCAtIGJsb2NrIHhcbiAgICAgKiBAcGFyYW0ge251bWJlcn0geSAtIGJsb2NrIHlcbiAgICAgKiBAcGFyYW0ge251bWJlcn0geiAtIGJsb2NrIHpcbiAgICAgKiBAcGFyYW0ge251bWJlcn0gW2xheWVyPTBdIC0gYmxvY2sgc3RvcmFnZSBsYXllciAoMCBmb3IgYmxvY2tzLCAxIGZvciBsaXF1aWRzKVxuICAgICAqL1xuICAgIHB1YmxpYyBhc3luYyBnZXRCbG9jayh4OiBudW1iZXIsIHk6IG51bWJlciwgejogbnVtYmVyLCBsYXllciA9IDApOiBQcm9taXNlPEJsb2NrPiB7XG4gICAgICAgIGNvbnN0IGJsb2NrSWQgPSAoYXdhaXQgdGhpcy5nZXRDaHVua0F0KHgsIHopKS5nZXRCbG9jayh4LCB5LCB6LCBsYXllcik7XG4gICAgICAgIGNvbnN0IGJsb2NrID0gdGhpcy5zZXJ2ZXIuZ2V0QmxvY2tNYW5hZ2VyKCkuZ2V0QmxvY2tCeUlkQW5kTWV0YShibG9ja0lkLmlkLCBibG9ja0lkLm1ldGEpO1xuXG4gICAgICAgIGlmICghYmxvY2spIHJldHVybiB0aGlzLnNlcnZlci5nZXRCbG9ja01hbmFnZXIoKS5nZXRCbG9jaygnbWluZWNyYWZ0OmFpcicpO1xuICAgICAgICByZXR1cm4gYmxvY2s7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgY2h1bmsgaW4gdGhlIHNwZWNpZmllcyB4IGFuZCB6LCBpZiB0aGUgY2h1bmsgZG9lc24ndCBleGlzdHNcbiAgICAgKiBpdCBpcyBnZW5lcmF0ZWQuXG4gICAgICovXG4gICAgcHVibGljIGFzeW5jIGdldENodW5rKGN4OiBudW1iZXIsIGN6OiBudW1iZXIpOiBQcm9taXNlPENodW5rPiB7XG4gICAgICAgIGNvbnN0IGluZGV4ID0gQ2h1bmsucGFja1haKGN4LCBjeik7XG4gICAgICAgIGlmICghdGhpcy5jaHVua3MuaGFzKGluZGV4KSkgcmV0dXJuIHRoaXMubG9hZENodW5rKGN4LCBjeik7XG5cbiAgICAgICAgcmV0dXJuIHRoaXMuY2h1bmtzLmdldChpbmRleCkhO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIExvYWRzIGEgY2h1bmsgaW4gYSBnaXZlbiB4IGFuZCB6IGFuZCByZXR1cm5zIGl0cy5cbiAgICAgKiBAcGFyYW0ge251bWJlcn0geCAtIHggY29vcmRpbmF0ZS5cbiAgICAgKiBAcGFyYW0ge251bWJlcn0geiAtIHogY29vcmRpbmF0ZS5cbiAgICAgKi9cbiAgICBwdWJsaWMgYXN5bmMgbG9hZENodW5rKHg6IG51bWJlciwgejogbnVtYmVyLCBfaWdub3JlV2Fybj86IGJvb2xlYW4pOiBQcm9taXNlPENodW5rPiB7XG4gICAgICAgIGNvbnN0IGluZGV4ID0gQ2h1bmsucGFja1haKHgsIHopO1xuICAgICAgICAvLyBUcnkgLSBjYXRjaCBmb3IgcHJvdmlkZXIgZXJyb3JzXG4gICAgICAgIGNvbnN0IGNodW5rID0gYXdhaXQgdGhpcy5wcm92aWRlci5yZWFkQ2h1bmsoeCwgeiwgdGhpcy5zZWVkLCB0aGlzLmdlbmVyYXRvciwgdGhpcy5jb25maWcpO1xuICAgICAgICB0aGlzLmNodW5rcy5zZXQoaW5kZXgsIGNodW5rKTtcblxuICAgICAgICAvLyBUT0RPOiBldmVudCBoZXJlLCBlZyBvbkNodW5rTG9hZFxuICAgICAgICByZXR1cm4gY2h1bms7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2VuZHMgYSB3b3JsZCBldmVudCBwYWNrZXQgdG8gYWxsIHRoZSB2aWV3ZXJzIGluIHRoZSBwb3NpdGlvbiBjaHVuay5cbiAgICAgKiBAcGFyYW0ge1ZlY3RvcjN9IHBvc2l0aW9uIC0gd29ybGQgcG9zaXRpb24uXG4gICAgICogQHBhcmFtIHtudW1iZXJ9IGV2ZW50IC0gZXZlbnQgaWRlbnRpZmllci5cbiAgICAgKiBAcGFyYW0ge251bWJlcn0gZGF0YSAtIGV2ZW50IGRhdGEuXG4gICAgICovXG4gICAgcHVibGljIGFzeW5jIHNlbmRXb3JsZEV2ZW50KHBvc2l0aW9uOiBWZWN0b3IzIHwgbnVsbCwgZXZlbnQ6IFdvcmxkRXZlbnQsIGRhdGE6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBjb25zdCB3b3JsZEV2ZW50UGFja2V0ID0gbmV3IFdvcmxkRXZlbnRQYWNrZXQoKTtcbiAgICAgICAgd29ybGRFdmVudFBhY2tldC5ldmVudElkID0gZXZlbnQ7XG4gICAgICAgIC8vd29ybGRFdmVudFBhY2tldC5wb3NpdGlvbiA9IHBvc2l0aW9uO1xuICAgICAgICB3b3JsZEV2ZW50UGFja2V0LmRhdGEgPSBkYXRhO1xuXG4gICAgICAgIC8vIFRPRE86IExpbWl0IGRpc3RhbmNlLlxuICAgICAgICBhd2FpdCBQcm9taXNlLmFsbCh0aGlzLmdldFBsYXllcnMoKS5tYXAoKHBsYXllcikgPT4gcGxheWVyLmdldE5ldHdvcmtTZXNzaW9uKCkuc2VuZCh3b3JsZEV2ZW50UGFja2V0KSkpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYSBjaHVuayBmcm9tIGEgYmxvY2sgcG9zaXRpb24ncyB4IGFuZCB6IGNvb3JkaW5hdGVzLlxuICAgICAqL1xuICAgIHB1YmxpYyBhc3luYyBnZXRDaHVua0F0KHg6IFZlY3RvcjMpOiBQcm9taXNlPENodW5rPjtcbiAgICBwdWJsaWMgYXN5bmMgZ2V0Q2h1bmtBdCh4OiBudW1iZXIsIHo6IG51bWJlcik6IFByb21pc2U8Q2h1bms+O1xuICAgIHB1YmxpYyBhc3luYyBnZXRDaHVua0F0KHg6IFZlY3RvcjMgfCBudW1iZXIsIHo6IG51bWJlciA9IDApOiBQcm9taXNlPENodW5rPiB7XG4gICAgICAgIGlmICh4IGluc3RhbmNlb2YgVmVjdG9yMykge1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMuZ2V0Q2h1bmtBdCh4LmdldFgoKSwgeC5nZXRaKCkpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHRoaXMuZ2V0Q2h1bmsoeCA+PiA0LCB6ID4+IDQpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIHdvcmxkIGRlZmF1bHQgc3Bhd24gcG9zaXRpb24uXG4gICAgICovXG4gICAgcHVibGljIGFzeW5jIGdldFNwYXduUG9zaXRpb24oKTogUHJvbWlzZTxWZWN0b3IzPiB7XG4gICAgICAgIGlmICh0aGlzLnNwYXduKSByZXR1cm4gdGhpcy5zcGF3bjtcblxuICAgICAgICBjb25zdCB4ID0gMDtcbiAgICAgICAgY29uc3QgeiA9IDA7IC8vIFRPRE86IHJlcGxhY2Ugd2l0aCBhY3R1YWwgZGF0YVxuICAgICAgICBjb25zdCBjaHVuayA9IGF3YWl0IHRoaXMuZ2V0Q2h1bmtBdCh4LCB6KTtcbiAgICAgICAgY29uc3QgeSA9IGNodW5rLmdldEhpZ2hlc3RCbG9ja0F0KHgsIHopICsgMTtcbiAgICAgICAgcmV0dXJuIG5ldyBWZWN0b3IzKHosIHkgKyAyLCB6KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXQgdGhlIHdvcmxkJ3Mgc3Bhd24gcG9zaXRpb24uXG4gICAgICogQHBhcmFtIHtWZWN0b3IzfSBwb3MgLSBUaGUgcG9zaXRpb24uXG4gICAgICovXG4gICAgcHVibGljIHNldFNwYXduUG9zaXRpb24ocG9zOiBWZWN0b3IzKSB7XG4gICAgICAgIHRoaXMuc3Bhd24gPSBwb3M7XG4gICAgfVxuXG4gICAgLy8gVE9ETzogbW92ZSB0aGlzP1xuICAgIHB1YmxpYyBhc3luYyB1c2VJdGVtT24oXG4gICAgICAgIGl0ZW1JbkhhbmQ6IEl0ZW0gfCBCbG9jayB8IG51bGwsXG4gICAgICAgIGJsb2NrUG9zaXRpb246IFZlY3RvcjMsXG4gICAgICAgIGZhY2U6IG51bWJlcixcbiAgICAgICAgY2xpY2tQb3NpdGlvbjogVmVjdG9yMyxcbiAgICAgICAgcGxheWVyOiBQbGF5ZXJcbiAgICApOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgaWYgKGl0ZW1JbkhhbmQgaW5zdGFuY2VvZiBJdGVtKSByZXR1cm47IC8vIFRPRE9cblxuICAgICAgICAvLyBUT0RPOiBjaGVja3NcbiAgICAgICAgLy8gVE9ETzogY2FuSW50ZXJhY3RcblxuICAgICAgICBjb25zdCBibG9jayA9IGl0ZW1JbkhhbmQ7IC8vIFRPRE86IGdldCBibG9jayBmcm9tIGl0ZW1JbkhhbmRcbiAgICAgICAgY29uc3QgYmxvY2tJZCA9IChhd2FpdCB0aGlzLmdldENodW5rQXQoYmxvY2tQb3NpdGlvbikpLmdldEJsb2NrKGJsb2NrUG9zaXRpb24pO1xuXG4gICAgICAgIGNvbnN0IGNsaWNrZWRCbG9jayA9IHRoaXMuc2VydmVyLmdldEJsb2NrTWFuYWdlcigpLmdldEJsb2NrQnlJZEFuZE1ldGEoYmxvY2tJZC5pZCwgYmxvY2tJZC5tZXRhKTtcblxuICAgICAgICBpZiAoIWJsb2NrIHx8ICFjbGlja2VkQmxvY2spIHJldHVybjtcbiAgICAgICAgaWYgKGNsaWNrZWRCbG9jay5nZXROYW1lKCkgPT09ICdtaW5lY3JhZnQ6YWlyJyB8fCAhYmxvY2suY2FuQmVQbGFjZWQoKSkgcmV0dXJuO1xuXG4gICAgICAgIGNvbnN0IHBsYWNlZFBvc2l0aW9uID0gbmV3IFZlY3RvcjMoYmxvY2tQb3NpdGlvbi5nZXRYKCksIGJsb2NrUG9zaXRpb24uZ2V0WSgpLCBibG9ja1Bvc2l0aW9uLmdldFooKSk7XG5cbiAgICAgICAgLy8gT25seSBzZXQgY29ycmVjdCBmYWNlIGlmIHRoZSBibG9jayBjYW4ndCBiZSByZXBsYWNlZFxuICAgICAgICBpZiAoIWNsaWNrZWRCbG9jay5jYW5CZVJlcGxhY2VkKCkpXG4gICAgICAgICAgICBzd2l0Y2ggKGZhY2UpIHtcbiAgICAgICAgICAgICAgICBjYXNlIDA6IC8vIEJvdHRvbVxuICAgICAgICAgICAgICAgICAgICBwbGFjZWRQb3NpdGlvbi5zZXRZKHBsYWNlZFBvc2l0aW9uLmdldFkoKSAtIDEpO1xuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICBjYXNlIDE6IC8vIFRvcFxuICAgICAgICAgICAgICAgICAgICBwbGFjZWRQb3NpdGlvbi5zZXRZKHBsYWNlZFBvc2l0aW9uLmdldFkoKSArIDEpO1xuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICBjYXNlIDI6IC8vIEZyb250XG4gICAgICAgICAgICAgICAgICAgIHBsYWNlZFBvc2l0aW9uLnNldFoocGxhY2VkUG9zaXRpb24uZ2V0WigpIC0gMSk7XG4gICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgIGNhc2UgMzogLy8gQmFja1xuICAgICAgICAgICAgICAgICAgICBwbGFjZWRQb3NpdGlvbi5zZXRaKHBsYWNlZFBvc2l0aW9uLmdldFooKSArIDEpO1xuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICBjYXNlIDQ6IC8vIFJpZ2h0XG4gICAgICAgICAgICAgICAgICAgIHBsYWNlZFBvc2l0aW9uLnNldFgocGxhY2VkUG9zaXRpb24uZ2V0WCgpIC0gMSk7XG4gICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgIGNhc2UgNTogLy8gTGVmdFxuICAgICAgICAgICAgICAgICAgICBwbGFjZWRQb3NpdGlvbi5zZXRYKHBsYWNlZFBvc2l0aW9uLmdldFgoKSArIDEpO1xuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICBkZWZhdWx0OlxuICAgICAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0ludmFsaWQgRmFjZScpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgIGlmIChibG9ja1Bvc2l0aW9uLmdldFkoKSA8IDAgfHwgYmxvY2tQb3NpdGlvbi5nZXRZKCkgPiAyNTUpIHJldHVybjtcblxuICAgICAgICBjb25zdCBzdWNjZXNzOiBib29sZWFuID0gYXdhaXQgbmV3IFByb21pc2UoYXN5bmMgKHJlc29sdmUpID0+IHtcbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgY29uc3QgY2h1bmsgPSBhd2FpdCB0aGlzLmdldENodW5rQXQocGxhY2VkUG9zaXRpb24uZ2V0WCgpLCBwbGFjZWRQb3NpdGlvbi5nZXRaKCkpO1xuXG4gICAgICAgICAgICAgICAgY2h1bmsuc2V0QmxvY2socGxhY2VkUG9zaXRpb24uZ2V0WCgpLCBwbGFjZWRQb3NpdGlvbi5nZXRZKCksIHBsYWNlZFBvc2l0aW9uLmdldFooKSwgYmxvY2spO1xuICAgICAgICAgICAgICAgIHJlc29sdmUodHJ1ZSk7XG4gICAgICAgICAgICB9IGNhdGNoIChlcnJvcjogdW5rbm93bikge1xuICAgICAgICAgICAgICAgIHBsYXllci5nZXRTZXJ2ZXIoKS5nZXRMb2dnZXIoKS53YXJuKGAke3BsYXllci5nZXROYW1lKCl9IGZhaWxlZCB0byBwbGFjZSBibG9jayBkdWUgdG8gJHtlcnJvcn1gKTtcbiAgICAgICAgICAgICAgICBhd2FpdCBwbGF5ZXIuc2VuZE1lc3NhZ2UoKGVycm9yIGFzIGFueSk/Lm1lc3NhZ2UpO1xuXG4gICAgICAgICAgICAgICAgcmVzb2x2ZShmYWxzZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuXG4gICAgICAgIGlmICghc3VjY2Vzcykge1xuICAgICAgICAgICAgaWYgKHBsYWNlZFBvc2l0aW9uLmdldFkoKSA8IDApIHJldHVybjtcblxuICAgICAgICAgICAgY29uc3QgYmxvY2tVcGRhdGUgPSBuZXcgVXBkYXRlQmxvY2tQYWNrZXQoKTtcbiAgICAgICAgICAgIGJsb2NrVXBkYXRlLnggPSBwbGFjZWRQb3NpdGlvbi5nZXRYKCk7XG4gICAgICAgICAgICBibG9ja1VwZGF0ZS55ID0gcGxhY2VkUG9zaXRpb24uZ2V0WSgpO1xuICAgICAgICAgICAgYmxvY2tVcGRhdGUueiA9IHBsYWNlZFBvc2l0aW9uLmdldFooKTtcbiAgICAgICAgICAgIGJsb2NrVXBkYXRlLmJsb2NrUnVudGltZUlkID0gQmxvY2tNYXBwaW5ncy5nZXRSdW50aW1lSWQoY2xpY2tlZEJsb2NrLmdldE5hbWUoKSk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBydW50aW1lSWQgPSBCbG9ja01hcHBpbmdzLmdldFJ1bnRpbWVJZChibG9jay5nZXROYW1lKCkpO1xuXG4gICAgICAgIGNvbnN0IGJsb2NrVXBkYXRlID0gbmV3IFVwZGF0ZUJsb2NrUGFja2V0KCk7XG4gICAgICAgIGJsb2NrVXBkYXRlLnggPSBwbGFjZWRQb3NpdGlvbi5nZXRYKCk7XG4gICAgICAgIGJsb2NrVXBkYXRlLnkgPSBwbGFjZWRQb3NpdGlvbi5nZXRZKCk7XG4gICAgICAgIGJsb2NrVXBkYXRlLnogPSBwbGFjZWRQb3NpdGlvbi5nZXRaKCk7XG4gICAgICAgIGJsb2NrVXBkYXRlLmJsb2NrUnVudGltZUlkID0gcnVudGltZUlkO1xuXG4gICAgICAgIGF3YWl0IFByb21pc2UuYWxsKFxuICAgICAgICAgICAgdGhpcy5zZXJ2ZXJcbiAgICAgICAgICAgICAgICAuZ2V0U2Vzc2lvbk1hbmFnZXIoKVxuICAgICAgICAgICAgICAgIC5nZXRBbGxQbGF5ZXJzKClcbiAgICAgICAgICAgICAgICAubWFwKGFzeW5jIChvbmxpbmVQbGF5ZXIpID0+XG4gICAgICAgICAgICAgICAgICAgIG9ubGluZVBsYXllci5nZXROZXR3b3JrU2Vzc2lvbigpLmdldENvbm5lY3Rpb24oKS5zZW5kRGF0YVBhY2tldChibG9ja1VwZGF0ZSlcbiAgICAgICAgICAgICAgICApXG4gICAgICAgICk7XG5cbiAgICAgICAgY29uc3QgcGsgPSBuZXcgTGV2ZWxTb3VuZEV2ZW50UGFja2V0KCk7XG4gICAgICAgIHBrLnNvdW5kID0gNjsgLy8gVE9ETzogZW51bVxuXG4gICAgICAgIHBrLnBvc2l0aW9uWCA9IHBsYWNlZFBvc2l0aW9uLmdldFgoKTtcbiAgICAgICAgcGsucG9zaXRpb25ZID0gcGxhY2VkUG9zaXRpb24uZ2V0WSgpO1xuICAgICAgICBway5wb3NpdGlvblogPSBwbGFjZWRQb3NpdGlvbi5nZXRaKCk7XG5cbiAgICAgICAgcGsuZXh0cmFEYXRhID0gcnVudGltZUlkOyAvLyBJbiB0aGlzIGNhc2UgcmVmZXJzIHRvIGJsb2NrIHJ1bnRpbWUgSWRcbiAgICAgICAgcGsuZGlzYWJsZVJlbGF0aXZlVm9sdW1lID0gZmFsc2U7XG5cbiAgICAgICAgYXdhaXQgUHJvbWlzZS5hbGwoXG4gICAgICAgICAgICBwbGF5ZXJcbiAgICAgICAgICAgICAgICAuZ2V0V29ybGQoKVxuICAgICAgICAgICAgICAgIC5nZXRQbGF5ZXJzKClcbiAgICAgICAgICAgICAgICAubWFwKCh0YXJnZXQpID0+IHRhcmdldC5nZXROZXR3b3JrU2Vzc2lvbigpLnNlbmQocGspKVxuICAgICAgICApO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNlbmRzIHRoZSBjdXJyZW50IHRpbWUgdG8gYWxsIHBsYXllcnMgaW4gdGhlIHdvcmxkLlxuICAgICAqL1xuICAgIHB1YmxpYyBhc3luYyBzZW5kVGltZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgLy8gVHJ5IHRvIHNlbmQgaXQgYXQgdGhlIHNhbWUgdGltZSB0byBhbGxcbiAgICAgICAgYXdhaXQgUHJvbWlzZS5hbGwodGhpcy5nZXRQbGF5ZXJzKCkubWFwKChwbGF5ZXIpID0+IHBsYXllci5nZXROZXR3b3JrU2Vzc2lvbigpLnNlbmRUaW1lKHRoaXMuZ2V0VGlja3MoKSkpKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBBZGRzIGFuIGVudGl0eSB0byB0aGUgbGV2ZWwuXG4gICAgICogQHBhcmFtIHtFbnRpdHl9IGVudGl0eSAtIFRoZSBlbnRpdHkgdG8gYWRkLlxuICAgICAqL1xuICAgIHB1YmxpYyBhc3luYyBhZGRFbnRpdHkoZW50aXR5OiBFbnRpdHkpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgdGhpcy5lbnRpdGllcy5zZXQoZW50aXR5LmdldFJ1bnRpbWVJZCgpLCBlbnRpdHkpO1xuXG4gICAgICAgIGlmICghZW50aXR5LmlzUGxheWVyKCkpIGF3YWl0IGVudGl0eS5zZW5kU3Bhd24oKTtcbiAgICAgICAgZWxzZSBhd2FpdCBQcm9taXNlLmFsbCh0aGlzLmdldEVudGl0aWVzKCkubWFwKChlKSA9PiBlLnNlbmRTcGF3bihlbnRpdHkgYXMgUGxheWVyKSkpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJlbW92ZXMgYW4gZW50aXR5IGZyb20gdGhlIGxldmVsLlxuICAgICAqIEBwYXJhbSB7RW50aXR5fSBlbnRpdHkgLSBUaGUgZW50aXR5IHRvIHJlbW92ZS5cbiAgICAgKi9cbiAgICBwdWJsaWMgYXN5bmMgcmVtb3ZlRW50aXR5KGVudGl0eTogRW50aXR5KTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGlmICghZW50aXR5LmlzUGxheWVyKCkpIGF3YWl0IGVudGl0eS5zZW5kRGVzcGF3bigpO1xuICAgICAgICBlbHNlIGF3YWl0IFByb21pc2UuYWxsKHRoaXMuZ2V0RW50aXRpZXMoKS5tYXAoKGUpID0+IGUuc2VuZERlc3Bhd24oZW50aXR5IGFzIFBsYXllcikpKTtcblxuICAgICAgICB0aGlzLmVudGl0aWVzLmRlbGV0ZShlbnRpdHkuZ2V0UnVudGltZUlkKCkpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEdldCBhbGwgZW50aXRpZXMgaW4gdGhpcyB3b3JsZC5cbiAgICAgKiBAcmV0dXJucyB7RW50aXR5W119IHRoZSBlbnRpdGllcy5cbiAgICAgKi9cbiAgICBwdWJsaWMgZ2V0RW50aXRpZXMoKTogRW50aXR5W10ge1xuICAgICAgICByZXR1cm4gQXJyYXkuZnJvbSh0aGlzLmVudGl0aWVzLnZhbHVlcygpKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogR2V0IGFsbCBwbGF5ZXJzIGluIHRoaXMgd29ybGQuXG4gICAgICogQHJldHVybnMge1BsYXllcltdfSB0aGUgcGxheWVycy5cbiAgICAgKi9cbiAgICBwdWJsaWMgZ2V0UGxheWVycygpOiBQbGF5ZXJbXSB7XG4gICAgICAgIHJldHVybiAodGhpcy5nZXRFbnRpdGllcygpLmZpbHRlcigoZSkgPT4gZS5pc1BsYXllcigpKSBhcyBQbGF5ZXJbXSkuZmlsdGVyKChwKSA9PiBwLmlzT25saW5lKCkpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNhdmVzIGNoYW5nZWQgY2h1bmtzIGludG8gZGlzay5cbiAgICAgKi9cbiAgICBwdWJsaWMgYXN5bmMgc2F2ZUNodW5rcygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgY29uc3QgdGltZXIgPSBuZXcgVGltZXIoKTtcbiAgICAgICAgdGhpcy5zZXJ2ZXIuZ2V0TG9nZ2VyKCkuaW5mbyhgU2F2aW5nIGNodW5rcyBmb3IgbGV2ZWwgJHt0aGlzLmdldEZvcm1hdHRlZE5hbWUoKX1gKTtcblxuICAgICAgICBhd2FpdCBQcm9taXNlLmFsbChcbiAgICAgICAgICAgIEFycmF5LmZyb20odGhpcy5jaHVua3MudmFsdWVzKCkpXG4gICAgICAgICAgICAgICAgLmZpbHRlcigoYykgPT4gYy5nZXRIYXNDaGFuZ2VkKCkpXG4gICAgICAgICAgICAgICAgLm1hcChhc3luYyAoY2h1bmspID0+IHRoaXMucHJvdmlkZXIud3JpdGVDaHVuayhjaHVuaykpXG4gICAgICAgICk7XG4gICAgICAgIHRoaXMuc2VydmVyLmdldExvZ2dlcigpLnZlcmJvc2UoYCh0b29rIMKnZSR7dGltZXIuc3RvcCgpfSBtc8KncikhYCk7XG4gICAgfVxuXG4gICAgcHVibGljIGFzeW5jIHNhdmUoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIC8vIFNhdmUgY2h1bmtzXG4gICAgICAgIHRoaXMuZ2V0UGxheWVycygpLmZvckVhY2goYXN5bmMgKHBsYXllcikgPT4ge1xuICAgICAgICAgICAgYXdhaXQgdGhpcy5zYXZlUGxheWVyRGF0YShwbGF5ZXIpO1xuICAgICAgICB9KTtcbiAgICAgICAgYXdhaXQgdGhpcy5zYXZlQ2h1bmtzKCk7XG4gICAgICAgIGF3YWl0IHRoaXMuc2F2ZUxldmVsRGF0YSgpO1xuICAgIH1cblxuICAgIHB1YmxpYyBnZXRHYW1lcnVsZU1hbmFnZXIoKTogR2FtZXJ1bGVNYW5hZ2VyIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZ2FtZXJ1bGVNYW5hZ2VyO1xuICAgIH1cblxuICAgIHB1YmxpYyBnZXRUaWNrcygpOiBudW1iZXIge1xuICAgICAgICByZXR1cm4gdGhpcy5jdXJyZW50VGljaztcbiAgICB9XG5cbiAgICBwdWJsaWMgc2V0VGlja3ModGljazogbnVtYmVyKTogdm9pZCB7XG4gICAgICAgIHRoaXMuY3VycmVudFRpY2sgPSB0aWNrO1xuICAgIH1cblxuICAgIHB1YmxpYyBnZXRQcm92aWRlcigpOiBhbnkge1xuICAgICAgICByZXR1cm4gdGhpcy5wcm92aWRlcjtcbiAgICB9XG5cbiAgICAvLyBUaGlzIGlzIHVzZWQgZm9yIGV4YW1wbGUgaW4gc3RhcnQgZ2FtZSBwYWNrZXRcbiAgICBwdWJsaWMgZ2V0VVVJRCgpOiBzdHJpbmcge1xuICAgICAgICByZXR1cm4gdGhpcy51dWlkO1xuICAgIH1cblxuICAgIHB1YmxpYyBnZXROYW1lKCk6IHN0cmluZyB7XG4gICAgICAgIHJldHVybiB0aGlzLm5hbWU7XG4gICAgfVxuICAgIHB1YmxpYyBnZXRGb3JtYXR0ZWROYW1lKCk6IHN0cmluZyB7XG4gICAgICAgIHJldHVybiBgwqdiJyR7dGhpcy5uYW1lfScvJHt0aGlzLmdlbmVyYXRvci5jb25zdHJ1Y3Rvci5uYW1lfcKncmA7XG4gICAgfVxuXG4gICAgcHVibGljIGdldFNlZWQoKTogbnVtYmVyIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuc2VlZDtcbiAgICB9XG5cbiAgICBwcml2YXRlIGFzeW5jIGdldExldmVsRGF0YSgpIHtcbiAgICAgICAgY29uc3QgcGF0aCA9IHdpdGhDd2QoV09STERTX0ZPTERFUl9OQU1FLCB0aGlzLm5hbWUsIExFVkVMX0RBVEFfRklMRV9OQU1FKTtcbiAgICAgICAgaWYgKCFmcy5leGlzdHNTeW5jKHBhdGgpKSByZXR1cm4ge307XG5cbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGNvbnN0IHJhdyA9IGF3YWl0IGZzLnByb21pc2VzLnJlYWRGaWxlKHBhdGgsICd1dGYtOCcpO1xuICAgICAgICAgICAgcmV0dXJuIHBhcnNlSlNPTjUocmF3LnRvU3RyaW5nKCkpIGFzIFBhcnRpYWw8TGV2ZWxEYXRhPjtcbiAgICAgICAgfSBjYXRjaCAoZXJyb3I6IGFueSkge1xuICAgICAgICAgICAgLy8gU29tZXRoaW5nIHdlbnQgd3Jvbmcgd2hpbGUgcmVhZGluZyBvciBwYXJzaW5nIHRoZSBsZXZlbCBkYXRhLlxuICAgICAgICAgICAgdGhpcy5zZXJ2ZXIuZ2V0TG9nZ2VyKCkuZXJyb3IoZXJyb3IpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHt9O1xuICAgIH1cbiAgICBwdWJsaWMgYXN5bmMgc2F2ZUxldmVsRGF0YSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgY29uc3QgZGF0YSA9IHtcbiAgICAgICAgICAgIHNwYXduOiBhd2FpdCB0aGlzLmdldFNwYXduUG9zaXRpb24oKSxcbiAgICAgICAgICAgIGdhbWVydWxlczogQXJyYXkuZnJvbSh0aGlzLmdldEdhbWVydWxlTWFuYWdlcigpLmdldEdhbWVydWxlcygpKSxcbiAgICAgICAgICAgIGVudGl0aWVzOiB0aGlzLmdldEVudGl0aWVzKClcbiAgICAgICAgICAgICAgICAuZmlsdGVyKChlbnRpdHkpID0+ICFlbnRpdHkuaXNQbGF5ZXIoKSAmJiAhZW50aXR5LmlzQ29uc29sZSgpKVxuICAgICAgICAgICAgICAgIC5tYXAoKGVudGl0eSkgPT4gKHtcbiAgICAgICAgICAgICAgICAgICAgdXVpZDogZW50aXR5LmdldFVVSUQoKSxcbiAgICAgICAgICAgICAgICAgICAgdHlwZTogZW50aXR5LmdldFR5cGUoKSxcbiAgICAgICAgICAgICAgICAgICAgcG9zaXRpb246IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHg6IGVudGl0eS5nZXRYKCksXG4gICAgICAgICAgICAgICAgICAgICAgICB5OiBlbnRpdHkuZ2V0WSgpLFxuICAgICAgICAgICAgICAgICAgICAgICAgejogZW50aXR5LmdldFooKSxcbiAgICAgICAgICAgICAgICAgICAgICAgIHBpdGNoOiBlbnRpdHkucGl0Y2gsXG4gICAgICAgICAgICAgICAgICAgICAgICB5YXc6IGVudGl0eS55YXcsXG4gICAgICAgICAgICAgICAgICAgICAgICBoZWFkWWF3OiBlbnRpdHkuaGVhZFlhd1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfSkpXG4gICAgICAgIH07XG5cbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGF3YWl0IGZzLnByb21pc2VzLndyaXRlRmlsZShcbiAgICAgICAgICAgICAgICAvLyBGSVhNRTogVGhpcyBvdmVyd3JpdGVzIGNvbW1lbnRzIGluIHRoZSBmaWxlLlxuICAgICAgICAgICAgICAgIHdpdGhDd2QoV09STERTX0ZPTERFUl9OQU1FLCB0aGlzLm5hbWUsIExFVkVMX0RBVEFfRklMRV9OQU1FKSxcbiAgICAgICAgICAgICAgICBKU09OLnN0cmluZ2lmeShkYXRhLCBudWxsLCA0KVxuICAgICAgICAgICAgKTtcbiAgICAgICAgfSBjYXRjaCAoZXJyb3I6IHVua25vd24pIHtcbiAgICAgICAgICAgIHRoaXMuc2VydmVyLmdldExvZ2dlcigpLmVycm9yKGBGYWlsZWQgdG8gc2F2ZSBsZXZlbCBkYXRhYCk7XG4gICAgICAgICAgICB0aGlzLnNlcnZlci5nZXRMb2dnZXIoKS5lcnJvcihlcnJvcik7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBHZXQgdGhlIHBsYXllciBkYXRhIGZvciBhIHBsYXllci5cbiAgICAgKiBAcGFyYW0ge1BsYXllcn0gcGxheWVyIC0gVGhlIHBsYXllciB0byBnZXQgdGhlIGRhdGEgZm9yLlxuICAgICAqIEByZXR1cm5zIHtQcm9taXNlPFdvcmxkUGxheWVyRGF0YT59IFRoZSBwbGF5ZXIgZGF0YS5cbiAgICAgKi9cbiAgICBwdWJsaWMgYXN5bmMgZ2V0UGxheWVyRGF0YShwbGF5ZXI6IFBsYXllcik6IFByb21pc2U8UGFydGlhbDxXb3JsZFBsYXllckRhdGE+PiB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBjb25zdCBmaWxlTmFtZSA9IHBsYXllci5nZXRYVUlEKCk7XG4gICAgICAgICAgICBpZiAoIWZpbGVOYW1lKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdQbGF5ZXIgaGFzIG5vIFhVSUQnKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgY29uc3QgcmF3ID0gYXdhaXQgZnMucHJvbWlzZXMucmVhZEZpbGUoXG4gICAgICAgICAgICAgICAgd2l0aEN3ZChXT1JMRFNfRk9MREVSX05BTUUsIHRoaXMubmFtZSwgJ3BsYXllcmRhdGEnLCBgJHtwbGF5ZXIuZ2V0WFVJRCgpIHx8IHBsYXllci5nZXROYW1lKCl9Lmpzb25gKSxcbiAgICAgICAgICAgICAgICB7IGZsYWc6ICdyJywgZW5jb2Rpbmc6ICd1dGYtOCcgfVxuICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIHJldHVybiBwYXJzZUpTT041KHJhdy50b1N0cmluZygpKSBhcyBQYXJ0aWFsPFdvcmxkUGxheWVyRGF0YT47XG4gICAgICAgIH0gY2F0Y2ggKGVycm9yOiB1bmtub3duKSB7XG4gICAgICAgICAgICB0aGlzLnNlcnZlci5nZXRMb2dnZXIoKS5kZWJ1ZyhgUGxheWVyRGF0YSBpcyBtaXNzaW5nIGZvciBwbGF5ZXIgJHtwbGF5ZXIuZ2V0WFVJRCgpfWApO1xuICAgICAgICAgICAgdGhpcy5zZXJ2ZXIuZ2V0TG9nZ2VyKCkuZXJyb3IoZXJyb3IpO1xuXG4gICAgICAgICAgICBjb25zdCBzcGF3biA9IGF3YWl0IHRoaXMuZ2V0U3Bhd25Qb3NpdGlvbigpO1xuICAgICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgICAgICBnYW1lbW9kZTogdGhpcy5zZXJ2ZXIuZ2V0Q29uZmlnKCkuZ2V0R2FtZW1vZGUoKSxcbiAgICAgICAgICAgICAgICBwb3NpdGlvbjoge1xuICAgICAgICAgICAgICAgICAgICB4OiBzcGF3bi5nZXRYKCksXG4gICAgICAgICAgICAgICAgICAgIHk6IHNwYXduLmdldFkoKSxcbiAgICAgICAgICAgICAgICAgICAgejogc3Bhd24uZ2V0WigpLFxuICAgICAgICAgICAgICAgICAgICBwaXRjaDogMCxcbiAgICAgICAgICAgICAgICAgICAgeWF3OiAwLFxuICAgICAgICAgICAgICAgICAgICBoZWFkWWF3OiAwXG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBwdWJsaWMgYXN5bmMgc2F2ZVBsYXllckRhdGEocGxheWVyOiBQbGF5ZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgY29uc3QgZGF0YSA9IHtcbiAgICAgICAgICAgIHV1aWQ6IHBsYXllci5nZXRVVUlEKCksXG4gICAgICAgICAgICB1c2VybmFtZTogcGxheWVyLmdldE5hbWUoKSxcbiAgICAgICAgICAgIGdhbWVtb2RlOiBnZXRHYW1ldHlwZU5hbWUocGxheWVyLmdhbWVtb2RlKSxcbiAgICAgICAgICAgIHBvc2l0aW9uOiB7XG4gICAgICAgICAgICAgICAgeDogcGxheWVyLmdldFgoKSxcbiAgICAgICAgICAgICAgICB5OiBwbGF5ZXIuZ2V0WSgpLFxuICAgICAgICAgICAgICAgIHo6IHBsYXllci5nZXRaKCksXG4gICAgICAgICAgICAgICAgcGl0Y2g6IHBsYXllci5waXRjaCxcbiAgICAgICAgICAgICAgICB5YXc6IHBsYXllci55YXcsXG4gICAgICAgICAgICAgICAgaGVhZFlhdzogcGxheWVyLmhlYWRZYXdcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSBhcyBXb3JsZFBsYXllckRhdGE7XG5cbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGF3YWl0IGZzLnByb21pc2VzLndyaXRlRmlsZShcbiAgICAgICAgICAgICAgICAvLyBGSVhNRTogVGhpcyBvdmVyd3JpdGVzIGNvbW1lbnRzIGluIHRoZSBmaWxlLlxuICAgICAgICAgICAgICAgIHdpdGhDd2QoV09STERTX0ZPTERFUl9OQU1FLCB0aGlzLm5hbWUsICdwbGF5ZXJkYXRhJywgYCR7cGxheWVyLmdldFhVSUQoKSB8fCBwbGF5ZXIuZ2V0TmFtZSgpfS5qc29uYCksXG4gICAgICAgICAgICAgICAgSlNPTi5zdHJpbmdpZnkoZGF0YSwgbnVsbCwgNCksXG4gICAgICAgICAgICAgICAgeyBmbGFnOiAndysnLCBlbmNvZGluZzogJ3V0Zi04JywgZmx1c2g6IHRydWUgfVxuICAgICAgICAgICAgKTtcbiAgICAgICAgfSBjYXRjaCAoZXJyb3I6IHVua25vd24pIHtcbiAgICAgICAgICAgIHRoaXMuc2VydmVyLmdldExvZ2dlcigpLmVycm9yKGBGYWlsZWQgdG8gc2F2ZSBwbGF5ZXIgZGF0YWApO1xuICAgICAgICAgICAgdGhpcy5zZXJ2ZXIuZ2V0TG9nZ2VyKCkuZXJyb3IoZXJyb3IpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQHJldHVybnMge1NlcnZlcn0gVGhlIHNlcnZlciBpbnN0YW5jZS5cbiAgICAgKi9cbiAgICBwdWJsaWMgZ2V0U2VydmVyKCk6IFNlcnZlciB7XG4gICAgICAgIHJldHVybiB0aGlzLnNlcnZlcjtcbiAgICB9XG59XG4iXSwibmFtZXMiOlsiRW50aXRpZXMiLCJibG9ja1VwZGF0ZSJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUF1QkEsTUFBTSxvQkFBdUIsR0FBQSxZQUFBO0FBQzdCLE1BQU0sa0JBQXFCLEdBQUEsUUFBQTtBQXFDcEIsTUFBTSxLQUF5QixDQUFBO0FBQUEsRUFDakIsSUFBQSxHQUFlLEtBQUssWUFBYSxFQUFBO0FBQUEsRUFDMUMsSUFBQTtBQUFBLEVBRVMsUUFBQSx1QkFBb0MsR0FBSSxFQUFBO0FBQUEsRUFDeEMsTUFBQSx1QkFBaUMsR0FBSSxFQUFBO0FBQUEsRUFDckMsZUFBQTtBQUFBLEVBQ1QsV0FBYyxHQUFBLENBQUE7QUFBQSxFQUNMLFFBQUE7QUFBQSxFQUNBLE1BQUE7QUFBQSxFQUNBLElBQUE7QUFBQSxFQUNBLFNBQUE7QUFBQSxFQUNBLE1BQUE7QUFBQSxFQUNULEtBQXdCLEdBQUEsSUFBQTtBQUFBLEVBRXpCLFdBQUEsQ0FBWSxFQUFFLElBQU0sRUFBQSxNQUFBLEVBQVEsVUFBVSxJQUFNLEVBQUEsU0FBQSxFQUFXLFFBQXFCLEVBQUE7QUFDL0UsSUFBQSxJQUFBLENBQUssSUFBTyxHQUFBLElBQUE7QUFDWixJQUFBLElBQUEsQ0FBSyxNQUFTLEdBQUEsTUFBQTtBQUNkLElBQUEsSUFBQSxDQUFLLFFBQVcsR0FBQSxRQUFBO0FBQ2hCLElBQUssSUFBQSxDQUFBLGVBQUEsR0FBa0IsSUFBSSxlQUFBLENBQWdCLE1BQU0sQ0FBQTtBQUNqRCxJQUFBLElBQUEsQ0FBSyxJQUFPLEdBQUEsSUFBQTtBQUNaLElBQUEsSUFBQSxDQUFLLFNBQVksR0FBQSxTQUFBO0FBQ2pCLElBQUssSUFBQSxDQUFBLE1BQUEsR0FBUyxVQUFVLEVBQUM7QUFFekIsSUFBQSxJQUFBLENBQUssZUFBZ0IsQ0FBQSxXQUFBLENBQVksU0FBVSxDQUFBLGVBQUEsRUFBaUIsTUFBTSxJQUFJLENBQUE7QUFFdEUsSUFBSSxJQUFBO0FBRUEsTUFBQSxNQUFNLElBQU8sR0FBQSxPQUFBLENBQVEsa0JBQW9CLEVBQUEsSUFBQSxDQUFLLE1BQU0sWUFBWSxDQUFBO0FBQ2hFLE1BQUksSUFBQSxDQUFDLEVBQUcsQ0FBQSxVQUFBLENBQVcsSUFBSSxDQUFBLEVBQU0sRUFBQSxDQUFBLFNBQUEsQ0FBVSxJQUFNLEVBQUEsRUFBRSxTQUFXLEVBQUEsSUFBQSxFQUFNLENBQUE7QUFBQSxhQUMzRCxLQUFnQixFQUFBO0FBQ3JCLE1BQUEsSUFBQSxDQUFLLE9BQU8sU0FBVSxFQUFBLENBQUUsTUFBTSxDQUFzQyxtQ0FBQSxFQUFBLElBQUEsQ0FBSyxJQUFJLENBQUUsQ0FBQSxDQUFBO0FBQy9FLE1BQUEsSUFBQSxDQUFLLE1BQU8sQ0FBQSxTQUFBLEVBQVksQ0FBQSxLQUFBLENBQU0sS0FBSyxDQUFBO0FBQUE7QUFDdkM7QUFDSjtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBTUEsTUFBYSxNQUF3QixHQUFBO0FBQ2pDLElBQUssSUFBQSxDQUFBLE1BQUEsQ0FBTyxFQUFHLENBQUEsTUFBQSxFQUFRLE9BQU8sR0FBQSxLQUFRLEtBQUssTUFBTyxDQUFBLEdBQUEsQ0FBSSxPQUFRLEVBQUMsQ0FBQyxDQUFBO0FBRWhFLElBQU0sTUFBQSxLQUFBLEdBQVEsTUFBTSxJQUFBLENBQUssWUFBYSxFQUFBO0FBQ3RDLElBQUksSUFBQSxLQUFBLENBQU0sT0FBWSxJQUFBLENBQUEsZ0JBQUEsQ0FBaUIsUUFBUSxVQUFXLENBQUEsS0FBQSxDQUFNLEtBQUssQ0FBQyxDQUFBO0FBQ3RFLElBQUEsSUFBSSxNQUFNLFNBQVcsRUFBQTtBQUNqQixNQUFBLEtBQUEsQ0FBTSxTQUFVLENBQUEsT0FBQTtBQUFBLFFBQVEsQ0FBQyxDQUFDLElBQU0sRUFBQSxDQUFDLEtBQU8sRUFBQSxRQUFRLENBQUMsQ0FBQSxLQUM3QyxJQUFLLENBQUEsZUFBQSxDQUFnQixXQUFZLENBQUEsSUFBQSxFQUFNLE9BQU8sUUFBUTtBQUFBLE9BQzFEO0FBQUE7QUFFSixJQUFBLElBQUksTUFBTSxRQUFVLEVBQUE7QUFDaEIsTUFBVyxLQUFBLE1BQU