UNPKG

@jsprismarine/prismarine

Version:

Dedicated Minecraft Bedrock Edition server written in TypeScript

543 lines (541 loc) 67.1 kB
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