s3-mutex
Version:
A robust distributed locking mechanism for Node.js applications using AWS S3 as the backend storage, with support for deadlock detection, timeout handling, automatic lock refresh, retry with backoff, and cleanup utilities.
2 lines • 74.5 kB
JavaScript
"use strict";var p=Object.defineProperty;var b=Object.getOwnPropertyDescriptor;var $=Object.getOwnPropertyNames;var S=Object.prototype.hasOwnProperty;var L=(l,e)=>{for(var t in e)p(l,t,{get:e[t],enumerable:!0})},C=(l,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of $(e))!S.call(l,n)&&n!==t&&p(l,n,{get:()=>e[n],enumerable:!(r=b(e,n))||r.enumerable});return l};var I=l=>C(p({},"__esModule",{value:!0}),l);var x={};L(x,{S3Mutex:()=>m,streamToString:()=>y});module.exports=I(x);var d=require("@aws-sdk/client-s3");async function y(l){if(l instanceof Blob)return await l.text();if(l.getReader){let e=l.getReader(),t=[];for(;;){let{done:i,value:a}=await e.read();if(i)break;t.push(a)}let r=new Uint8Array(t.reduce((i,a)=>i+a.length,0)),n=0;for(let i of t)r.set(i,n),n+=i.length;return new TextDecoder().decode(r)}return new Promise((e,t)=>{let r=[];l.on("data",n=>r.push(Buffer.from(n))),l.on("error",n=>t(n)),l.on("end",()=>e(Buffer.concat(r).toString("utf8")))})}var m=class{s3Client;bucketName;keyPrefix;maxRetries;retryDelayMs;maxRetryDelayMs;useJitter;lockTimeoutMs;clockSkewToleranceMs;ownerId;heldLocks=new Map;lockRequests=new Map;lockDependencies=new Map;constructor(e){this.s3Client=e.s3Client??new d.S3Client({forcePathStyle:!0,...e.s3ClientConfig}),this.bucketName=e.bucketName,this.keyPrefix=e.keyPrefix||"locks/",this.maxRetries=e.maxRetries||5,this.retryDelayMs=e.retryDelayMs||200,this.maxRetryDelayMs=e.maxRetryDelayMs||5e3,this.useJitter=e.useJitter!==void 0?e.useJitter:!0,this.lockTimeoutMs=e.lockTimeoutMs||6e4,this.clockSkewToleranceMs=e.clockSkewToleranceMs||1e3,this.ownerId=`${process.pid}-${Date.now()}-${Math.random().toString(36).substring(2,15)}`}async exponentialBackoff(e){let t=Math.min(this.retryDelayMs*2**e,this.maxRetryDelayMs),r=this.useJitter?Math.random()*.3*t:0,n=Math.floor(t+r);await new Promise(i=>setTimeout(i,n))}getLockKey(e){let t=e.replace(/[^a-zA-Z0-9-_]/g,"_");return`${this.keyPrefix}${t}.json`}cleanETag(e){return e?.replace(/"/g,"")}handleS3Error(e,t,r){let n=e;if(n.$metadata?.httpStatusCode===503)throw new Error(`S3 service unavailable while ${t} lock ${r}: ${n.message}`);if(n.name==="ThrottlingException")throw new Error(`AWS request throttling encountered while ${t} lock ${r}: ${n.message}`);let i=`Error ${t} lock ${r}: ${n.message}`,a=new Error(i);throw a.cause=e,a}async initializeLock(e){let t=this.getLockKey(e);try{let r={locked:!1},n=await this.s3Client.send(new d.PutObjectCommand({Bucket:this.bucketName,Key:t,Body:JSON.stringify(r),ContentType:"application/json",IfNoneMatch:"*"}));return{initialized:!0,etag:this.cleanETag(n.ETag)}}catch(r){if(r.$metadata?.httpStatusCode===412){let i=await this.s3Client.send(new d.HeadObjectCommand({Bucket:this.bucketName,Key:t}));return{initialized:!0,etag:this.cleanETag(i.ETag)}}this.handleS3Error(r,"initializing",e)}}async getLockInfo(e){let t=this.getLockKey(e);try{let r=await this.s3Client.send(new d.GetObjectCommand({Bucket:this.bucketName,Key:t}));if(!r.Body)throw new Error(`Failed to get lock info for ${e}`);if(!r.ETag)throw new Error(`No ETag found for lock ${e}`);let n=await y(r.Body),i=JSON.parse(n),a=this.cleanETag(r.ETag)||"";return{lockInfo:i,etag:a}}catch(r){let n=r;if(n.$metadata?.httpStatusCode===404){let i=new Error(`Lock file ${e} not found`);throw i.$metadata=n.$metadata,i}this.handleS3Error(r,"getting info for",e)}}async updateLockInfo(e,t,r){let n=this.getLockKey(e);try{return(await this.s3Client.send(new d.PutObjectCommand({Bucket:this.bucketName,Key:n,Body:JSON.stringify(t),ContentType:"application/json",IfMatch:r}))).ETag?.replace(/"/g,"")}catch(i){if(i.$metadata?.httpStatusCode===412)return;if(i.$metadata?.httpStatusCode===503)throw new Error(`S3 service unavailable while updating lock ${e}: ${i.message}`);if(i.name==="ThrottlingException")throw new Error(`AWS request throttling encountered while updating lock ${e}: ${i.message}`);if(i.$metadata?.httpStatusCode===404)throw new Error(`Lock file ${e} disappeared during update operation`);if(i.name==="NetworkError"||i.$metadata?.httpStatusCode>=500)throw new Error(`Network or server error while updating lock ${e}: ${i.message}`);let a=`Error updating lock ${e}: ${i.message}`,c=new Error(a);throw c.cause=i,c}}registerLockDependency(e,t,r){if(!r)return;let n=this.lockDependencies.get(e);n||(n=new Set,this.lockDependencies.set(e,n)),n.add(t)}async isPotentialDeadlock(e,t){if(!t||this.lockRequests.size===0)return!1;let r=[t],n=new Set;for(;r.length>0;){let i=r.shift();if(!i||n.has(i))continue;n.add(i);let a=this.lockDependencies.get(i);if(a)for(let c of a){if(this.heldLocks.has(c))try{if(await this.isOwnedByUs(c))return!0}catch(s){console.warn(`Error checking ownership for deadlock detection: ${s}`)}try{let{lockInfo:s}=await this.getLockInfo(c);s.locked&&s.owner&&s.owner!==i&&r.push(s.owner)}catch(s){console.warn(`Error getting lock info for deadlock detection: ${s}`)}}}return!1}async acquireLock(e,t,r=0){try{this.lockRequests.set(e,{lockName:e,priority:r,acquiredAt:Date.now(),owner:this.ownerId}),await this.initializeLock(e);let n=Date.now(),i=t||this.lockTimeoutMs;for(let c=0;c<this.maxRetries;c++){if(Date.now()-n>i)return this.lockRequests.delete(e),this.lockDependencies.delete(this.ownerId),!1;try{let{lockInfo:s,etag:o}=await this.getLockInfo(e);if(s.locked&&s.owner===this.ownerId)return this.heldLocks.set(e,o),this.lockRequests.delete(e),!0;s.locked&&s.owner&&this.registerLockDependency(this.ownerId,e,s.owner);let u=Date.now(),f=!s.locked||s.expiresAt!==void 0&&s.expiresAt<u-this.clockSkewToleranceMs,h=s.locked&&s.priority!==void 0&&r>s.priority&&await this.isPotentialDeadlock(e,s.owner);if(f||h){let w={locked:!0,owner:this.ownerId,acquiredAt:u,expiresAt:u+this.lockTimeoutMs,priority:r},g=await this.updateLockInfo(e,w,o);if(g){this.heldLocks.set(e,g),this.lockRequests.delete(e);let k=this.lockDependencies.get(this.ownerId);return k&&(k.delete(e),k.size===0&&this.lockDependencies.delete(this.ownerId)),!0}}await this.exponentialBackoff(c)}catch(s){let o=s;o.$metadata?.httpStatusCode===503?console.warn(`S3 service unavailable while acquiring lock ${e}. Retrying...`):o.name==="ThrottlingException"?console.warn(`AWS request throttling encountered while acquiring lock ${e}. Retrying...`):o.$metadata?.httpStatusCode===404?await this.initializeLock(e):console.warn(`Error acquiring lock ${e}: ${o.message}. Retrying...`),await this.exponentialBackoff(c)}}this.lockRequests.delete(e);let a=this.lockDependencies.get(this.ownerId);return a&&(a.delete(e),a.size===0&&this.lockDependencies.delete(this.ownerId)),!1}catch(n){return this.lockRequests.delete(e),this.lockDependencies.delete(this.ownerId),console.error(`Critical error acquiring lock ${e}:`,n),!1}}async refreshLock(e){try{let{lockInfo:t,etag:r}=await this.getLockInfo(e),n=Date.now();return!t.locked||t.owner!==this.ownerId||t.expiresAt!==void 0&&t.expiresAt<n?!1:(t.expiresAt=n+this.lockTimeoutMs,!!await this.updateLockInfo(e,t,r))}catch(t){return t.$metadata?.httpStatusCode===404?console.warn(`Lock file ${e} not found during refresh`):t.$metadata?.httpStatusCode===503?console.warn(`S3 service unavailable while refreshing lock ${e}`):console.warn(`Error refreshing lock ${e}: ${t.message}`),!1}}async releaseLock(e,t=!1){try{let{lockInfo:r,etag:n}=await this.getLockInfo(e);if(r.locked&&!t&&r.owner!==this.ownerId)return!1;let i={locked:!1},a=await this.updateLockInfo(e,i,n);this.heldLocks.delete(e),this.lockRequests.delete(e);for(let[c,s]of this.lockDependencies.entries())s.delete(e),s.size===0&&this.lockDependencies.delete(c);return!!a}catch(r){return r.$metadata?.httpStatusCode===404?console.warn(`Lock file ${e} not found during release`):r.$metadata?.httpStatusCode===503?console.warn(`S3 service unavailable while releasing lock ${e}`):console.warn(`Error releasing lock ${e}: ${r.message}`),!1}}async withLock(e,t,r={}){let n=r.timeoutMs||this.lockTimeoutMs,i=r.retries||this.maxRetries;for(let a=0;a<i;a++){if(await this.acquireLock(e,n)){let s=!0,o=null;try{let u=Math.max(this.lockTimeoutMs/3,1e3);s=!1,o=setInterval(async()=>{if(s){o&&(clearInterval(o),o=null);return}try{await this.refreshLock(e)||(console.warn(`Failed to refresh lock ${e}, stopping heartbeat`),s=!0,o&&(clearInterval(o),o=null))}catch(h){console.warn(`Error refreshing lock ${e}: ${h}`),s=!0,o&&(clearInterval(o),o=null)}},u);let f=await t();return s=!0,o&&(clearInterval(o),o=null),f}catch(u){console.error(`Error in function executed with lock ${e}:`,u),o&&(s=!0,clearInterval(o));let f=!1;for(let h=0;h<3&&!f;h++)try{f=await this.releaseLock(e),f||await new Promise(w=>setTimeout(w,100*(h+1)))}catch(w){console.warn(`Failed to release lock ${e} (attempt ${h+1}): ${w}`),h<2&&await new Promise(g=>setTimeout(g,100*(h+1)))}throw u}finally{o&&(s=!0,clearInterval(o));try{await this.releaseLock(e)}catch(u){console.warn(`Failed to release lock ${e} in finally block: ${u}`)}}}await this.exponentialBackoff(a)}return null}async isLocked(e){try{let{lockInfo:t}=await this.getLockInfo(e),r=Date.now();return t.locked&&(!t.expiresAt||t.expiresAt>r)}catch(t){if(t.$metadata?.httpStatusCode===404)return!1;throw t}}async isOwnedByUs(e){try{let{lockInfo:t}=await this.getLockInfo(e),r=Date.now();return t.locked&&t.owner===this.ownerId&&(!t.expiresAt||t.expiresAt>r)}catch(t){if(t.$metadata?.httpStatusCode===404)return!1;throw t}}async deleteLock(e,t=!1){try{if(!t)try{if(!await this.isOwnedByUs(e))return!1}catch{}let r=this.getLockKey(e);return await this.s3Client.send(new d.DeleteObjectCommand({Bucket:this.bucketName,Key:r})),!0}catch(r){return r.$metadata?.httpStatusCode===404?!0:(r.$metadata?.httpStatusCode===503?console.warn(`S3 service unavailable while deleting lock ${e}`):console.warn(`Error deleting lock ${e}: ${r.message}`),!1)}}async cleanupStaleLocks(e={}){let t=e.prefix||this.keyPrefix,r=e.olderThan||Date.now()-this.lockTimeoutMs,n=e.dryRun,i=0,a=0,c=0,s;try{do{let o=await this.s3Client.send(new d.ListObjectsV2Command({Bucket:this.bucketName,Prefix:t,ContinuationToken:s}));if(s=o.NextContinuationToken,!!o.Contents){for(let u of o.Contents)if(u.Key){a++;try{let f=await this.s3Client.send(new d.GetObjectCommand({Bucket:this.bucketName,Key:u.Key}));if(!f.Body)continue;let h=await y(f.Body),w=JSON.parse(h);w.locked&&w.acquiredAt!==void 0&&w.acquiredAt<r&&(c++,n||(await this.s3Client.send(new d.DeleteObjectCommand({Bucket:this.bucketName,Key:u.Key})),i++))}catch(f){console.warn(`Error processing lock ${u.Key}: ${f}`)}}}}while(s);return{cleaned:i,total:a,stale:c}}catch(o){return console.error(`Error cleaning up stale locks: ${o}`),{cleaned:i,total:a,stale:c}}}};0&&(module.exports={S3Mutex,streamToString});
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vc3JjL2luZGV4LnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvLyBzcmMvaW5kZXgudHNcbmltcG9ydCB7XG5cdERlbGV0ZU9iamVjdENvbW1hbmQsXG5cdEdldE9iamVjdENvbW1hbmQsXG5cdEhlYWRPYmplY3RDb21tYW5kLFxuXHRMaXN0T2JqZWN0c1YyQ29tbWFuZCxcblx0UHV0T2JqZWN0Q29tbWFuZCxcblx0UzNDbGllbnQsXG5cdHR5cGUgUzNDbGllbnRDb25maWcsXG59IGZyb20gXCJAYXdzLXNkay9jbGllbnQtczNcIjtcblxuLy8gc3JjL3V0aWxzLnRzXG5pbXBvcnQgdHlwZSB7IFJlYWRhYmxlIH0gZnJvbSBcIm5vZGU6c3RyZWFtXCI7XG5cbi8qKlxuICogQ29udmVydHMgYSByZWFkYWJsZSBzdHJlYW0gdG8gYSBzdHJpbmdcbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHN0cmVhbVRvU3RyaW5nKFxuXHRzdHJlYW06IFJlYWRhYmxlIHwgUmVhZGFibGVTdHJlYW08VWludDhBcnJheT4gfCBCbG9iLFxuKTogUHJvbWlzZTxzdHJpbmc+IHtcblx0Ly8gSGFuZGxlIEJsb2Jcblx0aWYgKHN0cmVhbSBpbnN0YW5jZW9mIEJsb2IpIHtcblx0XHRyZXR1cm4gYXdhaXQgc3RyZWFtLnRleHQoKTtcblx0fVxuXG5cdC8vIEhhbmRsZSBSZWFkYWJsZVN0cmVhbSBmcm9tIGJyb3dzZXJzXG5cdGlmICgoc3RyZWFtIGFzIFJlYWRhYmxlU3RyZWFtPFVpbnQ4QXJyYXk+KS5nZXRSZWFkZXIpIHtcblx0XHRjb25zdCByZWFkZXIgPSAoc3RyZWFtIGFzIFJlYWRhYmxlU3RyZWFtPFVpbnQ4QXJyYXk+KS5nZXRSZWFkZXIoKTtcblx0XHRjb25zdCBjaHVua3M6IFVpbnQ4QXJyYXlbXSA9IFtdO1xuXG5cdFx0d2hpbGUgKHRydWUpIHtcblx0XHRcdGNvbnN0IHsgZG9uZSwgdmFsdWUgfSA9IGF3YWl0IHJlYWRlci5yZWFkKCk7XG5cdFx0XHRpZiAoZG9uZSkge1xuXHRcdFx0XHRicmVhaztcblx0XHRcdH1cblx0XHRcdGNodW5rcy5wdXNoKHZhbHVlKTtcblx0XHR9XG5cblx0XHRjb25zdCBjb25jYXRlbmF0ZWQgPSBuZXcgVWludDhBcnJheShcblx0XHRcdGNodW5rcy5yZWR1Y2UoKGFjYywgY2h1bmspID0+IGFjYyArIGNodW5rLmxlbmd0aCwgMCksXG5cdFx0KTtcblx0XHRsZXQgcG9zaXRpb24gPSAwO1xuXG5cdFx0Zm9yIChjb25zdCBjaHVuayBvZiBjaHVua3MpIHtcblx0XHRcdGNvbmNhdGVuYXRlZC5zZXQoY2h1bmssIHBvc2l0aW9uKTtcblx0XHRcdHBvc2l0aW9uICs9IGNodW5rLmxlbmd0aDtcblx0XHR9XG5cblx0XHRyZXR1cm4gbmV3IFRleHREZWNvZGVyKCkuZGVjb2RlKGNvbmNhdGVuYXRlZCk7XG5cdH1cblxuXHQvLyBIYW5kbGUgTm9kZS5qcyBSZWFkYWJsZSBzdHJlYW1cblx0cmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcblx0XHRjb25zdCBjaHVua3M6IEJ1ZmZlcltdID0gW107XG5cblx0XHQoc3RyZWFtIGFzIFJlYWRhYmxlKS5vbihcImRhdGFcIiwgKGNodW5rKSA9PiBjaHVua3MucHVzaChCdWZmZXIuZnJvbShjaHVuaykpKTtcblx0XHQoc3RyZWFtIGFzIFJlYWRhYmxlKS5vbihcImVycm9yXCIsIChlcnIpID0+IHJlamVjdChlcnIpKTtcblx0XHQoc3RyZWFtIGFzIFJlYWRhYmxlKS5vbihcImVuZFwiLCAoKSA9PlxuXHRcdFx0cmVzb2x2ZShCdWZmZXIuY29uY2F0KGNodW5rcykudG9TdHJpbmcoXCJ1dGY4XCIpKSxcblx0XHQpO1xuXHR9KTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBTM011dGV4T3B0aW9ucyB7XG5cdC8qKlxuXHQgKiBBV1MgUzMgY2xpZW50IGluc3RhbmNlXG5cdCAqL1xuXHRzM0NsaWVudD86IFMzQ2xpZW50O1xuXG5cdHMzQ2xpZW50Q29uZmlnPzogUzNDbGllbnRDb25maWc7XG5cblx0LyoqXG5cdCAqIFMzIGJ1Y2tldCBuYW1lIHdoZXJlIGxvY2tzIHdpbGwgYmUgc3RvcmVkXG5cdCAqL1xuXHRidWNrZXROYW1lOiBzdHJpbmc7XG5cblx0LyoqXG5cdCAqIEtleSBwcmVmaXggZm9yIGxvY2sgZmlsZXMgKG9wdGlvbmFsKVxuXHQgKi9cblx0a2V5UHJlZml4Pzogc3RyaW5nO1xuXG5cdC8qKlxuXHQgKiBNYXggbnVtYmVyIG9mIHJldHJpZXMgd2hlbiBhY3F1aXJpbmcgYSBsb2NrXG5cdCAqIEBkZWZhdWx0IDVcblx0ICovXG5cdG1heFJldHJpZXM/OiBudW1iZXI7XG5cblx0LyoqXG5cdCAqIEJhc2UgZGVsYXkgYmV0d2VlbiByZXRyaWVzIGluIG1pbGxpc2Vjb25kcyAodXNlZCBmb3IgZXhwb25lbnRpYWwgYmFja29mZilcblx0ICogQGRlZmF1bHQgMjAwXG5cdCAqL1xuXHRyZXRyeURlbGF5TXM/OiBudW1iZXI7XG5cblx0LyoqXG5cdCAqIE1heGltdW0gZGVsYXkgYmV0d2VlbiByZXRyaWVzIGluIG1pbGxpc2Vjb25kcyAoY2FwcyB0aGUgZXhwb25lbnRpYWwgYmFja29mZilcblx0ICogQGRlZmF1bHQgNTAwMCAoNSBzZWNvbmRzKVxuXHQgKi9cblx0bWF4UmV0cnlEZWxheU1zPzogbnVtYmVyO1xuXG5cdC8qKlxuXHQgKiBXaGV0aGVyIHRvIGFkZCBqaXR0ZXIgdG8gcmV0cnkgZGVsYXlzIHRvIHByZXZlbnQgc3luY2hyb25pemVkIHJldHJpZXNcblx0ICogQGRlZmF1bHQgdHJ1ZVxuXHQgKi9cblx0dXNlSml0dGVyPzogYm9vbGVhbjtcblxuXHQvKipcblx0ICogTG9jayB0aW1lb3V0IGluIG1pbGxpc2Vjb25kcy4gQWZ0ZXIgdGhpcyB0aW1lLCB0aGUgbG9jayBpcyBjb25zaWRlcmVkIHN0YWxlXG5cdCAqIGFuZCBjYW4gYmUgZm9yY2VmdWxseSBhY3F1aXJlZCBieSBhbm90aGVyIHByb2Nlc3MuXG5cdCAqIEBkZWZhdWx0IDYwMDAwICgxIG1pbnV0ZSlcblx0ICovXG5cdGxvY2tUaW1lb3V0TXM/OiBudW1iZXI7XG5cblx0LyoqXG5cdCAqIENsb2NrIHNrZXcgdG9sZXJhbmNlIGluIG1pbGxpc2Vjb25kcy4gVGhpcyB2YWx1ZSBpcyBhZGRlZCB0byBleHBpcmF0aW9uIGNhbGN1bGF0aW9uc1xuXHQgKiB0byBhY2NvdW50IGZvciBkaWZmZXJlbmNlcyBpbiBzeXN0ZW0gY2xvY2tzIGJldHdlZW4gZGlzdHJpYnV0ZWQgcHJvY2Vzc2VzLlxuXHQgKiBAZGVmYXVsdCAxMDAwICgxIHNlY29uZClcblx0ICovXG5cdGNsb2NrU2tld1RvbGVyYW5jZU1zPzogbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIExvY2tJbmZvIHtcblx0bG9ja2VkOiBib29sZWFuO1xuXHRvd25lcj86IHN0cmluZztcblx0YWNxdWlyZWRBdD86IG51bWJlcjtcblx0ZXhwaXJlc0F0PzogbnVtYmVyO1xuXHRwcmlvcml0eT86IG51bWJlcjtcbn1cblxuLy8gVXNlZCBmb3IgZGVhZGxvY2sgcHJldmVudGlvblxuaW50ZXJmYWNlIExvY2tSZXF1ZXN0IHtcblx0bG9ja05hbWU6IHN0cmluZztcblx0cHJpb3JpdHk6IG51bWJlcjtcblx0YWNxdWlyZWRBdDogbnVtYmVyO1xuXHRvd25lcjogc3RyaW5nO1xufVxuXG5leHBvcnQgY2xhc3MgUzNNdXRleCB7XG5cdHByaXZhdGUgczNDbGllbnQ6IFMzQ2xpZW50O1xuXHRwcml2YXRlIGJ1Y2tldE5hbWU6IHN0cmluZztcblx0cHJpdmF0ZSBrZXlQcmVmaXg6IHN0cmluZztcblx0cHJpdmF0ZSBtYXhSZXRyaWVzOiBudW1iZXI7XG5cdHByaXZhdGUgcmV0cnlEZWxheU1zOiBudW1iZXI7XG5cdHByaXZhdGUgbWF4UmV0cnlEZWxheU1zOiBudW1iZXI7XG5cdHByaXZhdGUgdXNlSml0dGVyOiBib29sZWFuO1xuXHRwcml2YXRlIGxvY2tUaW1lb3V0TXM6IG51bWJlcjtcblx0cHJpdmF0ZSBjbG9ja1NrZXdUb2xlcmFuY2VNczogbnVtYmVyO1xuXHRwcml2YXRlIG93bmVySWQ6IHN0cmluZztcblx0cHJpdmF0ZSBoZWxkTG9ja3M6IE1hcDxzdHJpbmcsIHN0cmluZz4gPSBuZXcgTWFwKCk7IC8vIGxvY2tOYW1lIC0+IGV0YWdcblx0cHJpdmF0ZSBsb2NrUmVxdWVzdHM6IE1hcDxzdHJpbmcsIExvY2tSZXF1ZXN0PiA9IG5ldyBNYXAoKTsgLy8gbG9ja05hbWUgLT4gcmVxdWVzdCBpbmZvXG5cdC8vIFRyYWNrIGxvY2sgZGVwZW5kZW5jaWVzIGZvciBkZWFkbG9jayBkZXRlY3Rpb24gLSBrZXk6IG93bmVyLCB2YWx1ZTogc2V0IG9mIGxvY2sgbmFtZXMgb3duZXIgaXMgd2FpdGluZyBmb3Jcblx0cHJpdmF0ZSBsb2NrRGVwZW5kZW5jaWVzOiBNYXA8c3RyaW5nLCBTZXQ8c3RyaW5nPj4gPSBuZXcgTWFwKCk7XG5cblx0Y29uc3RydWN0b3Iob3B0aW9uczogUzNNdXRleE9wdGlvbnMpIHtcblx0XHR0aGlzLnMzQ2xpZW50ID1cblx0XHRcdG9wdGlvbnMuczNDbGllbnQgPz9cblx0XHRcdG5ldyBTM0NsaWVudCh7XG5cdFx0XHRcdGZvcmNlUGF0aFN0eWxlOiB0cnVlLFxuXHRcdFx0XHQuLi5vcHRpb25zLnMzQ2xpZW50Q29uZmlnLFxuXHRcdFx0fSk7XG5cdFx0dGhpcy5idWNrZXROYW1lID0gb3B0aW9ucy5idWNrZXROYW1lO1xuXHRcdHRoaXMua2V5UHJlZml4ID0gb3B0aW9ucy5rZXlQcmVmaXggfHwgXCJsb2Nrcy9cIjtcblx0XHR0aGlzLm1heFJldHJpZXMgPSBvcHRpb25zLm1heFJldHJpZXMgfHwgNTtcblx0XHR0aGlzLnJldHJ5RGVsYXlNcyA9IG9wdGlvbnMucmV0cnlEZWxheU1zIHx8IDIwMDtcblx0XHR0aGlzLm1heFJldHJ5RGVsYXlNcyA9IG9wdGlvbnMubWF4UmV0cnlEZWxheU1zIHx8IDUwMDA7IC8vIDUgc2Vjb25kcyBkZWZhdWx0IG1heFxuXHRcdHRoaXMudXNlSml0dGVyID0gb3B0aW9ucy51c2VKaXR0ZXIgIT09IHVuZGVmaW5lZCA/IG9wdGlvbnMudXNlSml0dGVyIDogdHJ1ZTtcblx0XHR0aGlzLmxvY2tUaW1lb3V0TXMgPSBvcHRpb25zLmxvY2tUaW1lb3V0TXMgfHwgNjAwMDA7IC8vIDEgbWludXRlIGRlZmF1bHRcblx0XHR0aGlzLmNsb2NrU2tld1RvbGVyYW5jZU1zID0gb3B0aW9ucy5jbG9ja1NrZXdUb2xlcmFuY2VNcyB8fCAxMDAwOyAvLyAxIHNlY29uZCBkZWZhdWx0XG5cdFx0dGhpcy5vd25lcklkID0gYCR7cHJvY2Vzcy5waWR9LSR7RGF0ZS5ub3coKX0tJHtNYXRoLnJhbmRvbSgpLnRvU3RyaW5nKDM2KS5zdWJzdHJpbmcoMiwgMTUpfWA7XG5cdH1cblxuXHQvKipcblx0ICogSW1wbGVtZW50cyBleHBvbmVudGlhbCBiYWNrb2ZmIHdpdGggb3B0aW9uYWwgaml0dGVyXG5cdCAqIEBwYXJhbSBhdHRlbXB0IFRoZSBjdXJyZW50IGF0dGVtcHQgbnVtYmVyICgwLWJhc2VkKVxuXHQgKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyBhZnRlciB0aGUgY2FsY3VsYXRlZCBkZWxheVxuXHQgKi9cblx0cHJpdmF0ZSBhc3luYyBleHBvbmVudGlhbEJhY2tvZmYoYXR0ZW1wdDogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG5cdFx0Ly8gQ2FsY3VsYXRlIGJhc2UgZGVsYXkgd2l0aCBleHBvbmVudGlhbCBiYWNrb2ZmXG5cdFx0Y29uc3QgYmFzZURlbGF5ID0gTWF0aC5taW4oXG5cdFx0XHR0aGlzLnJldHJ5RGVsYXlNcyAqIDIgKiogYXR0ZW1wdCxcblx0XHRcdHRoaXMubWF4UmV0cnlEZWxheU1zLFxuXHRcdCk7XG5cblx0XHQvLyBBZGQgaml0dGVyIHRvIHByZXZlbnQgc3luY2hyb25pemVkIHJldHJpZXMgKHVwIHRvIDMwJSB2YXJpYXRpb24pXG5cdFx0Y29uc3Qgaml0dGVyID0gdGhpcy51c2VKaXR0ZXIgPyBNYXRoLnJhbmRvbSgpICogMC4zICogYmFzZURlbGF5IDogMDtcblx0XHRjb25zdCBkZWxheSA9IE1hdGguZmxvb3IoYmFzZURlbGF5ICsgaml0dGVyKTtcblxuXHRcdC8vIFdhaXQgZm9yIHRoZSBjYWxjdWxhdGVkIGRlbGF5XG5cdFx0YXdhaXQgbmV3IFByb21pc2UoKHJlc29sdmUpID0+IHNldFRpbWVvdXQocmVzb2x2ZSwgZGVsYXkpKTtcblx0fVxuXG5cdC8qKlxuXHQgKiBGb3JtYXRzIHRoZSBmdWxsIFMzIGtleSBmb3IgYSBsb2NrXG5cdCAqL1xuXHRwcml2YXRlIGdldExvY2tLZXkobG9ja05hbWU6IHN0cmluZyk6IHN0cmluZyB7XG5cdFx0Ly8gTWFrZSBzdXJlIHRoZSBrZXkgZG9lc24ndCBjb250YWluIGludmFsaWQgY2hhcmFjdGVyc1xuXHRcdGNvbnN0IHNhbml0aXplZExvY2tOYW1lID0gbG9ja05hbWUucmVwbGFjZSgvW15hLXpBLVowLTktX10vZywgXCJfXCIpO1xuXHRcdHJldHVybiBgJHt0aGlzLmtleVByZWZpeH0ke3Nhbml0aXplZExvY2tOYW1lfS5qc29uYDtcblx0fVxuXG5cdC8qKlxuXHQgKiBVdGlsaXR5IGZ1bmN0aW9uIHRvIGNsZWFuIGFuIEVUYWcgYnkgcmVtb3ZpbmcgcXVvdGVzXG5cdCAqL1xuXHRwcml2YXRlIGNsZWFuRVRhZyhldGFnOiBzdHJpbmcgfCB1bmRlZmluZWQpOiBzdHJpbmcgfCB1bmRlZmluZWQge1xuXHRcdHJldHVybiBldGFnPy5yZXBsYWNlKC9cIi9nLCBcIlwiKTtcblx0fVxuXG5cdC8qKlxuXHQgKiBTdGFuZGFyZCBlcnJvciBoYW5kbGluZyBmb3IgUzMgb3BlcmF0aW9uc1xuXHQgKi9cblx0cHJpdmF0ZSBoYW5kbGVTM0Vycm9yKFxuXHRcdGVycm9yOiB1bmtub3duLFxuXHRcdG9wZXJhdGlvbjogc3RyaW5nLFxuXHRcdGxvY2tOYW1lOiBzdHJpbmcsXG5cdCk6IG5ldmVyIHtcblx0XHRjb25zdCBlcnIgPSBlcnJvciBhcyB7XG5cdFx0XHQkbWV0YWRhdGE/OiB7IGh0dHBTdGF0dXNDb2RlPzogbnVtYmVyIH07XG5cdFx0XHRuYW1lPzogc3RyaW5nO1xuXHRcdFx0bWVzc2FnZTogc3RyaW5nO1xuXHRcdH07XG5cblx0XHRpZiAoZXJyLiRtZXRhZGF0YT8uaHR0cFN0YXR1c0NvZGUgPT09IDUwMykge1xuXHRcdFx0dGhyb3cgbmV3IEVycm9yKFxuXHRcdFx0XHRgUzMgc2VydmljZSB1bmF2YWlsYWJsZSB3aGlsZSAke29wZXJhdGlvbn0gbG9jayAke2xvY2tOYW1lfTogJHtlcnIubWVzc2FnZX1gLFxuXHRcdFx0KTtcblx0XHR9XG5cdFx0aWYgKGVyci5uYW1lID09PSBcIlRocm90dGxpbmdFeGNlcHRpb25cIikge1xuXHRcdFx0dGhyb3cgbmV3IEVycm9yKFxuXHRcdFx0XHRgQVdTIHJlcXVlc3QgdGhyb3R0bGluZyBlbmNvdW50ZXJlZCB3aGlsZSAke29wZXJhdGlvbn0gbG9jayAke2xvY2tOYW1lfTogJHtlcnIubWVzc2FnZX1gLFxuXHRcdFx0KTtcblx0XHR9XG5cblx0XHRjb25zdCBlcnJvck1lc3NhZ2UgPSBgRXJyb3IgJHtvcGVyYXRpb259IGxvY2sgJHtsb2NrTmFtZX06ICR7ZXJyLm1lc3NhZ2V9YDtcblx0XHRjb25zdCBuZXdFcnJvciA9IG5ldyBFcnJvcihlcnJvck1lc3NhZ2UpO1xuXHRcdC8vIEB0cy1pZ25vcmUgLSB1c2luZyBjYXVzZSBwcm9wZXJ0eSB3aGljaCBtaWdodCBub3QgYmUgaW4gRXJyb3IgdHlwZSBkZWZpbml0aW9uXG5cdFx0bmV3RXJyb3IuY2F1c2UgPSBlcnJvcjtcblx0XHR0aHJvdyBuZXdFcnJvcjtcblx0fVxuXG5cdC8qKlxuXHQgKiBBdG9taWNhbGx5IGluaXRpYWxpemVzIGEgbG9jayBpZiBpdCBkb2Vzbid0IGV4aXN0XG5cdCAqIFVzZXMgYSBtb3JlIGF0b21pYyBhcHByb2FjaCB0byBpbml0aWFsaXplIHRoZSBsb2NrIGZpbGVcblx0ICovXG5cdHByaXZhdGUgYXN5bmMgaW5pdGlhbGl6ZUxvY2soXG5cdFx0bG9ja05hbWU6IHN0cmluZyxcblx0KTogUHJvbWlzZTx7IGluaXRpYWxpemVkOiBib29sZWFuOyBldGFnPzogc3RyaW5nIH0+IHtcblx0XHRjb25zdCBsb2NrS2V5ID0gdGhpcy5nZXRMb2NrS2V5KGxvY2tOYW1lKTtcblxuXHRcdHRyeSB7XG5cdFx0XHQvLyBUcnkgdG8gZGlyZWN0bHkgY3JlYXRlIHRoZSBsb2NrIHdpdGggYSBjb25kaXRpb24gdGhhdCBpdCBkb2Vzbid0IGV4aXN0XG5cdFx0XHQvLyBVc2luZyBQdXRPYmplY3Qgd2l0aCBjb25kaXRpb25hbCBjaGVja3MgZm9yIGF0b21pY2l0eVxuXHRcdFx0Y29uc3QgaW5pdGlhbExvY2tJbmZvOiBMb2NrSW5mbyA9IHtcblx0XHRcdFx0bG9ja2VkOiBmYWxzZSxcblx0XHRcdH07XG5cblx0XHRcdGNvbnN0IHB1dFJlc3BvbnNlID0gYXdhaXQgdGhpcy5zM0NsaWVudC5zZW5kKFxuXHRcdFx0XHRuZXcgUHV0T2JqZWN0Q29tbWFuZCh7XG5cdFx0XHRcdFx0QnVja2V0OiB0aGlzLmJ1Y2tldE5hbWUsXG5cdFx0XHRcdFx0S2V5OiBsb2NrS2V5LFxuXHRcdFx0XHRcdEJvZHk6IEpTT04uc3RyaW5naWZ5KGluaXRpYWxMb2NrSW5mbyksXG5cdFx0XHRcdFx0Q29udGVudFR5cGU6IFwiYXBwbGljYXRpb24vanNvblwiLFxuXHRcdFx0XHRcdC8vIE9ubHkgY3JlYXRlIGlmIHRoZSBvYmplY3QgZG9lc24ndCBleGlzdCAodGhpcyBpcyBtb3JlIGF0b21pYylcblx0XHRcdFx0XHQvLyBodHRwczovL2RvY3MuYXdzLmFtYXpvbi5jb20vQW1hem9uUzMvbGF0ZXN0L0FQSS9BUElfUHV0T2JqZWN0Lmh0bWxcblx0XHRcdFx0XHRJZk5vbmVNYXRjaDogXCIqXCIsXG5cdFx0XHRcdH0pLFxuXHRcdFx0KTtcblxuXHRcdFx0Ly8gSWYgd2UgZ2V0IGhlcmUsIHRoZSBsb2NrIHdhcyBjcmVhdGVkXG5cdFx0XHRyZXR1cm4geyBpbml0aWFsaXplZDogdHJ1ZSwgZXRhZzogdGhpcy5jbGVhbkVUYWcocHV0UmVzcG9uc2UuRVRhZykgfTtcblx0XHR9IGNhdGNoIChlcnJvcikge1xuXHRcdFx0Ly8gSWYgdGhlIGVycm9yIGlzIGEgNDEyIFByZWNvbmRpdGlvbiBGYWlsZWQsIHRoZSBsb2NrIGFscmVhZHkgZXhpc3RzXG5cdFx0XHRjb25zdCBlcnIgPSBlcnJvciBhcyB7ICRtZXRhZGF0YT86IHsgaHR0cFN0YXR1c0NvZGU/OiBudW1iZXIgfSB9O1xuXHRcdFx0aWYgKGVyci4kbWV0YWRhdGE/Lmh0dHBTdGF0dXNDb2RlID09PSA0MTIpIHtcblx0XHRcdFx0Ly8gR2V0IHRoZSBleGlzdGluZyBsb2NrIGluZm8gdG8gcmV0dXJuIGl0cyBFVGFnXG5cdFx0XHRcdGNvbnN0IGhlYWRSZXNwb25zZSA9IGF3YWl0IHRoaXMuczNDbGllbnQuc2VuZChcblx0XHRcdFx0XHRuZXcgSGVhZE9iamVjdENvbW1hbmQoe1xuXHRcdFx0XHRcdFx0QnVja2V0OiB0aGlzLmJ1Y2tldE5hbWUsXG5cdFx0XHRcdFx0XHRLZXk6IGxvY2tLZXksXG5cdFx0XHRcdFx0fSksXG5cdFx0XHRcdCk7XG5cblx0XHRcdFx0cmV0dXJuIHtcblx0XHRcdFx0XHRpbml0aWFsaXplZDogdHJ1ZSxcblx0XHRcdFx0XHRldGFnOiB0aGlzLmNsZWFuRVRhZyhoZWFkUmVzcG9uc2UuRVRhZyksXG5cdFx0XHRcdH07XG5cdFx0XHR9XG5cblx0XHRcdHRoaXMuaGFuZGxlUzNFcnJvcihlcnJvciwgXCJpbml0aWFsaXppbmdcIiwgbG9ja05hbWUpO1xuXHRcdH1cblx0fVxuXG5cdC8qKlxuXHQgKiBHZXRzIHRoZSBjdXJyZW50IHN0YXRlIG9mIGEgbG9ja1xuXHQgKi9cblx0cHJpdmF0ZSBhc3luYyBnZXRMb2NrSW5mbyhcblx0XHRsb2NrTmFtZTogc3RyaW5nLFxuXHQpOiBQcm9taXNlPHsgbG9ja0luZm86IExvY2tJbmZvOyBldGFnOiBzdHJpbmcgfT4ge1xuXHRcdGNvbnN0IGxvY2tLZXkgPSB0aGlzLmdldExvY2tLZXkobG9ja05hbWUpO1xuXG5cdFx0dHJ5IHtcblx0XHRcdGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgdGhpcy5zM0NsaWVudC5zZW5kKFxuXHRcdFx0XHRuZXcgR2V0T2JqZWN0Q29tbWFuZCh7XG5cdFx0XHRcdFx0QnVja2V0OiB0aGlzLmJ1Y2tldE5hbWUsXG5cdFx0XHRcdFx0S2V5OiBsb2NrS2V5LFxuXHRcdFx0XHR9KSxcblx0XHRcdCk7XG5cblx0XHRcdGlmICghcmVzcG9uc2UuQm9keSkge1xuXHRcdFx0XHR0aHJvdyBuZXcgRXJyb3IoYEZhaWxlZCB0byBnZXQgbG9jayBpbmZvIGZvciAke2xvY2tOYW1lfWApO1xuXHRcdFx0fVxuXG5cdFx0XHRpZiAoIXJlc3BvbnNlLkVUYWcpIHtcblx0XHRcdFx0dGhyb3cgbmV3IEVycm9yKGBObyBFVGFnIGZvdW5kIGZvciBsb2NrICR7bG9ja05hbWV9YCk7XG5cdFx0XHR9XG5cblx0XHRcdGNvbnN0IGJvZHkgPSBhd2FpdCBzdHJlYW1Ub1N0cmluZyhyZXNwb25zZS5Cb2R5KTtcblx0XHRcdGNvbnN0IGxvY2tJbmZvID0gSlNPTi5wYXJzZShib2R5KSBhcyBMb2NrSW5mbztcblx0XHRcdGNvbnN0IGV0YWcgPSB0aGlzLmNsZWFuRVRhZyhyZXNwb25zZS5FVGFnKSB8fCBcIlwiO1xuXG5cdFx0XHRyZXR1cm4geyBsb2NrSW5mbywgZXRhZyB9O1xuXHRcdH0gY2F0Y2ggKGVycm9yKSB7XG5cdFx0XHRjb25zdCBlcnIgPSBlcnJvciBhcyB7ICRtZXRhZGF0YT86IHsgaHR0cFN0YXR1c0NvZGU/OiBudW1iZXIgfSB9O1xuXHRcdFx0Ly8gRW5oYW5jZWQgZXJyb3IgaGFuZGxpbmcgZm9yIGdldExvY2tJbmZvXG5cdFx0XHRpZiAoZXJyLiRtZXRhZGF0YT8uaHR0cFN0YXR1c0NvZGUgPT09IDQwNCkge1xuXHRcdFx0XHQvLyBDcmVhdGUgYSBuZXcgZXJyb3IgYnV0IHByZXNlcnZlIHRoZSBvcmlnaW5hbCBtZXRhZGF0YSBmb3IgY2hlY2tpbmcgaW4gaXNMb2NrZWQvaXNPd25lZEJ5VXNcblx0XHRcdFx0Y29uc3Qgbm90Rm91bmRFcnJvciA9IG5ldyBFcnJvcihcblx0XHRcdFx0XHRgTG9jayBmaWxlICR7bG9ja05hbWV9IG5vdCBmb3VuZGAsXG5cdFx0XHRcdCkgYXMgRXJyb3IgJiB7ICRtZXRhZGF0YT86IHsgaHR0cFN0YXR1c0NvZGU/OiBudW1iZXIgfSB9O1xuXHRcdFx0XHRub3RGb3VuZEVycm9yLiRtZXRhZGF0YSA9IGVyci4kbWV0YWRhdGE7XG5cdFx0XHRcdHRocm93IG5vdEZvdW5kRXJyb3I7XG5cdFx0XHR9XG5cblx0XHRcdHRoaXMuaGFuZGxlUzNFcnJvcihlcnJvciwgXCJnZXR0aW5nIGluZm8gZm9yXCIsIGxvY2tOYW1lKTtcblx0XHR9XG5cdH1cblxuXHQvKipcblx0ICogVXBkYXRlcyB0aGUgbG9jayBpbmZvIGluIFMzIHdpdGggaW1wcm92ZWQgZXJyb3IgaGFuZGxpbmdcblx0ICovXG5cdHByaXZhdGUgYXN5bmMgdXBkYXRlTG9ja0luZm8oXG5cdFx0bG9ja05hbWU6IHN0cmluZyxcblx0XHRsb2NrSW5mbzogTG9ja0luZm8sXG5cdFx0ZXRhZzogc3RyaW5nLFxuXHQpOiBQcm9taXNlPHN0cmluZyB8IHVuZGVmaW5lZD4ge1xuXHRcdGNvbnN0IGxvY2tLZXkgPSB0aGlzLmdldExvY2tLZXkobG9ja05hbWUpO1xuXG5cdFx0dHJ5IHtcblx0XHRcdGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgdGhpcy5zM0NsaWVudC5zZW5kKFxuXHRcdFx0XHRuZXcgUHV0T2JqZWN0Q29tbWFuZCh7XG5cdFx0XHRcdFx0QnVja2V0OiB0aGlzLmJ1Y2tldE5hbWUsXG5cdFx0XHRcdFx0S2V5OiBsb2NrS2V5LFxuXHRcdFx0XHRcdEJvZHk6IEpTT04uc3RyaW5naWZ5KGxvY2tJbmZvKSxcblx0XHRcdFx0XHRDb250ZW50VHlwZTogXCJhcHBsaWNhdGlvbi9qc29uXCIsXG5cdFx0XHRcdFx0SWZNYXRjaDogZXRhZyxcblx0XHRcdFx0fSksXG5cdFx0XHQpO1xuXG5cdFx0XHRyZXR1cm4gcmVzcG9uc2UuRVRhZz8ucmVwbGFjZSgvXCIvZywgXCJcIik7XG5cdFx0XHQvLyBiaW9tZS1pZ25vcmUgbGludC9zdXNwaWNpb3VzL25vRXhwbGljaXRBbnk6IDxleHBsYW5hdGlvbj5cblx0XHR9IGNhdGNoIChlcnJvcjogYW55KSB7XG5cdFx0XHQvLyBFbmhhbmNlZCBlcnJvciBoYW5kbGluZyB3aXRoIHNwZWNpZmljIGVycm9yIHR5cGVzXG5cblx0XHRcdC8vIElmIHRoZSBlcnJvciBpcyBhIHByZWNvbmRpdGlvbiBmYWlsZWQsIHRoZSBFVGFnIGhhcyBjaGFuZ2VkXG5cdFx0XHRpZiAoZXJyb3IuJG1ldGFkYXRhPy5odHRwU3RhdHVzQ29kZSA9PT0gNDEyKSB7XG5cdFx0XHRcdHJldHVybiB1bmRlZmluZWQ7IC8vIEVUYWcgbWlzbWF0Y2gsIGxvY2sgd2FzIG1vZGlmaWVkXG5cdFx0XHR9XG5cblx0XHRcdC8vIEhhbmRsZSBzcGVjaWZpYyBBV1MgZXJyb3IgdHlwZXNcblx0XHRcdGlmIChlcnJvci4kbWV0YWRhdGE/Lmh0dHBTdGF0dXNDb2RlID09PSA1MDMpIHtcblx0XHRcdFx0dGhyb3cgbmV3IEVycm9yKFxuXHRcdFx0XHRcdGBTMyBzZXJ2aWNlIHVuYXZhaWxhYmxlIHdoaWxlIHVwZGF0aW5nIGxvY2sgJHtsb2NrTmFtZX06ICR7ZXJyb3IubWVzc2FnZX1gLFxuXHRcdFx0XHQpO1xuXHRcdFx0fVxuXHRcdFx0aWYgKGVycm9yLm5hbWUgPT09IFwiVGhyb3R0bGluZ0V4Y2VwdGlvblwiKSB7XG5cdFx0XHRcdHRocm93IG5ldyBFcnJvcihcblx0XHRcdFx0XHRgQVdTIHJlcXVlc3QgdGhyb3R0bGluZyBlbmNvdW50ZXJlZCB3aGlsZSB1cGRhdGluZyBsb2NrICR7bG9ja05hbWV9OiAke2Vycm9yLm1lc3NhZ2V9YCxcblx0XHRcdFx0KTtcblx0XHRcdH1cblx0XHRcdGlmIChlcnJvci4kbWV0YWRhdGE/Lmh0dHBTdGF0dXNDb2RlID09PSA0MDQpIHtcblx0XHRcdFx0dGhyb3cgbmV3IEVycm9yKFxuXHRcdFx0XHRcdGBMb2NrIGZpbGUgJHtsb2NrTmFtZX0gZGlzYXBwZWFyZWQgZHVyaW5nIHVwZGF0ZSBvcGVyYXRpb25gLFxuXHRcdFx0XHQpO1xuXHRcdFx0fVxuXHRcdFx0aWYgKFxuXHRcdFx0XHRlcnJvci5uYW1lID09PSBcIk5ldHdvcmtFcnJvclwiIHx8XG5cdFx0XHRcdGVycm9yLiRtZXRhZGF0YT8uaHR0cFN0YXR1c0NvZGUgPj0gNTAwXG5cdFx0XHQpIHtcblx0XHRcdFx0dGhyb3cgbmV3IEVycm9yKFxuXHRcdFx0XHRcdGBOZXR3b3JrIG9yIHNlcnZlciBlcnJvciB3aGlsZSB1cGRhdGluZyBsb2NrICR7bG9ja05hbWV9OiAke2Vycm9yLm1lc3NhZ2V9YCxcblx0XHRcdFx0KTtcblx0XHRcdH1cblxuXHRcdFx0Ly8gRm9yIHVua25vd24gZXJyb3JzLCBhZGQgY29udGV4dCBhbmQgcmV0aHJvd1xuXHRcdFx0Y29uc3QgZXJyb3JNZXNzYWdlID0gYEVycm9yIHVwZGF0aW5nIGxvY2sgJHtsb2NrTmFtZX06ICR7ZXJyb3IubWVzc2FnZX1gO1xuXHRcdFx0Y29uc3QgbmV3RXJyb3IgPSBuZXcgRXJyb3IoZXJyb3JNZXNzYWdlKTtcblx0XHRcdG5ld0Vycm9yLmNhdXNlID0gZXJyb3I7XG5cdFx0XHR0aHJvdyBuZXdFcnJvcjtcblx0XHR9XG5cdH1cblxuXHQvKipcblx0ICogUmVnaXN0ZXJzIGEgbG9jayBkZXBlbmRlbmN5IHRvIGhlbHAgd2l0aCBkZWFkbG9jayBkZXRlY3Rpb25cblx0ICogQHBhcmFtIHdhaXRpbmdPd25lciBUaGUgb3duZXIgd2FpdGluZyBmb3IgdGhlIGxvY2tcblx0ICogQHBhcmFtIHRhcmdldExvY2tOYW1lIFRoZSBsb2NrIGJlaW5nIHdhaXRlZCBmb3Jcblx0ICogQHBhcmFtIHRhcmdldE93bmVyIFRoZSBvd25lciBjdXJyZW50bHkgaG9sZGluZyB0aGUgbG9ja1xuXHQgKi9cblx0cHJpdmF0ZSByZWdpc3RlckxvY2tEZXBlbmRlbmN5KFxuXHRcdHdhaXRpbmdPd25lcjogc3RyaW5nLFxuXHRcdHRhcmdldExvY2tOYW1lOiBzdHJpbmcsXG5cdFx0dGFyZ2V0T3duZXI6IHN0cmluZyB8IHVuZGVmaW5lZCxcblx0KTogdm9pZCB7XG5cdFx0aWYgKCF0YXJnZXRPd25lcikge1xuXHRcdFx0cmV0dXJuO1xuXHRcdH1cblxuXHRcdC8vIEdldCBvciBjcmVhdGUgdGhlIHNldCBvZiBsb2NrcyB0aGlzIG93bmVyIGlzIHdhaXRpbmcgZm9yXG5cdFx0bGV0IHdhaXRpbmdGb3IgPSB0aGlzLmxvY2tEZXBlbmRlbmNpZXMuZ2V0KHdhaXRpbmdPd25lcik7XG5cdFx0aWYgKCF3YWl0aW5nRm9yKSB7XG5cdFx0XHR3YWl0aW5nRm9yID0gbmV3IFNldDxzdHJpbmc+KCk7XG5cdFx0XHR0aGlzLmxvY2tEZXBlbmRlbmNpZXMuc2V0KHdhaXRpbmdPd25lciwgd2FpdGluZ0Zvcik7XG5cdFx0fVxuXG5cdFx0Ly8gQWRkIHRoZSB0YXJnZXQgbG9jayB0byB0aGUgc2V0XG5cdFx0d2FpdGluZ0Zvci5hZGQodGFyZ2V0TG9ja05hbWUpO1xuXHR9XG5cblx0LyoqXG5cdCAqIENoZWNrcyBpZiB0aGVyZSdzIGEgcG90ZW50aWFsIGRlYWRsb2NrIHNjZW5hcmlvIGJ5IGRldGVjdGluZyBjaXJjdWxhciB3YWl0IGNvbmRpdGlvbnNcblx0ICogQHBhcmFtIGxvY2tOYW1lIFRoZSBsb2NrIHdlJ3JlIHRyeWluZyB0byBhY3F1aXJlXG5cdCAqIEBwYXJhbSBjdXJyZW50T3duZXIgVGhlIGN1cnJlbnQgb3duZXIgb2YgdGhlIGxvY2tcblx0ICogQHJldHVybnMgVHJ1ZSBpZiB0aGVyZSdzIGEgcG90ZW50aWFsIGRlYWRsb2NrXG5cdCAqL1xuXHRwcml2YXRlIGFzeW5jIGlzUG90ZW50aWFsRGVhZGxvY2soXG5cdFx0X2xvY2tOYW1lOiBzdHJpbmcsXG5cdFx0Y3VycmVudE93bmVyPzogc3RyaW5nLFxuXHQpOiBQcm9taXNlPGJvb2xlYW4+IHtcblx0XHRpZiAoIWN1cnJlbnRPd25lcikge1xuXHRcdFx0cmV0dXJuIGZhbHNlO1xuXHRcdH1cblxuXHRcdC8vIElmIHdlJ3JlIG5vdCB3YWl0aW5nIGZvciBhbnkgbG9ja3MsIHRoZXJlIGNhbid0IGJlIGEgY3ljbGVcblx0XHRpZiAodGhpcy5sb2NrUmVxdWVzdHMuc2l6ZSA9PT0gMCkge1xuXHRcdFx0cmV0dXJuIGZhbHNlO1xuXHRcdH1cblxuXHRcdC8vIENoZWNrIGlmIHRoZSBjdXJyZW50IG93bmVyIGlzIHdhaXRpbmcgZm9yIGFueSBsb2NrcyB3ZSBob2xkXG5cdFx0Ly8gVG8gZG8gdGhpcywgd2UgbmVlZCB0byBjaGVjayBmb3IgY3ljbGVzIGluIHRoZSBkZXBlbmRlbmN5IGdyYXBoXG5cblx0XHQvLyBJbml0aWFsaXplIHRoZSBxdWV1ZSB3aXRoIHRoZSBjdXJyZW50IG93bmVyXG5cdFx0Y29uc3QgcXVldWU6IHN0cmluZ1tdID0gW2N1cnJlbnRPd25lcl07XG5cdFx0Y29uc3QgdmlzaXRlZCA9IG5ldyBTZXQ8c3RyaW5nPigpO1xuXG5cdFx0d2hpbGUgKHF1ZXVlLmxlbmd0aCA+IDApIHtcblx0XHRcdGNvbnN0IG93bmVyID0gcXVldWUuc2hpZnQoKTtcblx0XHRcdC8vIEVhcmx5IHJldHVybiBpZiBzb21laG93IHdlIGdldCBhbiB1bmRlZmluZWQgb3duZXIgKHNob3VsZG4ndCBoYXBwZW4sIGJ1dCBiZWluZyBzYWZlKVxuXHRcdFx0aWYgKCFvd25lcikge1xuXHRcdFx0XHRjb250aW51ZTtcblx0XHRcdH1cblxuXHRcdFx0Ly8gU2tpcCBhbHJlYWR5IHZpc2l0ZWQgb3duZXJzXG5cdFx0XHRpZiAodmlzaXRlZC5oYXMob3duZXIpKSB7XG5cdFx0XHRcdGNvbnRpbnVlO1xuXHRcdFx0fVxuXHRcdFx0dmlzaXRlZC5hZGQob3duZXIpO1xuXG5cdFx0XHQvLyBHZXQgdGhlIGxvY2tzIHRoaXMgb3duZXIgaXMgd2FpdGluZyBmb3Jcblx0XHRcdGNvbnN0IHdhaXRpbmdGb3IgPSB0aGlzLmxvY2tEZXBlbmRlbmNpZXMuZ2V0KG93bmVyKTtcblx0XHRcdGlmICghd2FpdGluZ0Zvcikge1xuXHRcdFx0XHRjb250aW51ZTtcblx0XHRcdH1cblxuXHRcdFx0Ly8gQ2hlY2sgZWFjaCBsb2NrIHRoZSBvd25lciBpcyB3YWl0aW5nIGZvclxuXHRcdFx0Zm9yIChjb25zdCB3YWl0TG9ja05hbWUgb2Ygd2FpdGluZ0Zvcikge1xuXHRcdFx0XHQvLyBJZiB0aGUgb3duZXIgaXMgd2FpdGluZyBmb3IgYSBsb2NrIHdlIGhvbGQsIHdlIGhhdmUgYSBjeWNsZVxuXHRcdFx0XHRpZiAodGhpcy5oZWxkTG9ja3MuaGFzKHdhaXRMb2NrTmFtZSkpIHtcblx0XHRcdFx0XHQvLyBHZXQgdGhlIGxvY2sgaW5mbyB0byB2ZXJpZnkgd2Ugb3duIGl0XG5cdFx0XHRcdFx0dHJ5IHtcblx0XHRcdFx0XHRcdGNvbnN0IGlzT3duZWRCeVVzID0gYXdhaXQgdGhpcy5pc093bmVkQnlVcyh3YWl0TG9ja05hbWUpO1xuXHRcdFx0XHRcdFx0aWYgKGlzT3duZWRCeVVzKSB7XG5cdFx0XHRcdFx0XHRcdC8vIFdlJ3ZlIGRldGVjdGVkIGEgZGVhZGxvY2sgY3ljbGVcblx0XHRcdFx0XHRcdFx0cmV0dXJuIHRydWU7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fSBjYXRjaCAoZXJyb3IpIHtcblx0XHRcdFx0XHRcdC8vIElmIHRoZXJlJ3MgYW4gZXJyb3IgY2hlY2tpbmcgb3duZXJzaGlwLCBiZSBjb25zZXJ2YXRpdmUgYW5kIGFzc3VtZSBubyBkZWFkbG9ja1xuXHRcdFx0XHRcdFx0Y29uc29sZS53YXJuKFxuXHRcdFx0XHRcdFx0XHRgRXJyb3IgY2hlY2tpbmcgb3duZXJzaGlwIGZvciBkZWFkbG9jayBkZXRlY3Rpb246ICR7ZXJyb3J9YCxcblx0XHRcdFx0XHRcdCk7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHR9XG5cblx0XHRcdFx0Ly8gRmluZCBvdXQgd2hvIG93bnMgdGhpcyBsb2NrIGFuZCBhZGQgdGhlbSB0byB0aGUgcXVldWVcblx0XHRcdFx0dHJ5IHtcblx0XHRcdFx0XHRjb25zdCB7IGxvY2tJbmZvIH0gPSBhd2FpdCB0aGlzLmdldExvY2tJbmZvKHdhaXRMb2NrTmFtZSk7XG5cdFx0XHRcdFx0aWYgKGxvY2tJbmZvLmxvY2tlZCAmJiBsb2NrSW5mby5vd25lciAmJiBsb2NrSW5mby5vd25lciAhPT0gb3duZXIpIHtcblx0XHRcdFx0XHRcdHF1ZXVlLnB1c2gobG9ja0luZm8ub3duZXIpO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0fSBjYXRjaCAoZXJyb3IpIHtcblx0XHRcdFx0XHQvLyBJZiB3ZSBjYW4ndCBnZXQgbG9jayBpbmZvLCBjb250aW51ZSB3aXRoIHRoZSBuZXh0IGxvY2tcblx0XHRcdFx0XHRjb25zb2xlLndhcm4oXG5cdFx0XHRcdFx0XHRgRXJyb3IgZ2V0dGluZyBsb2NrIGluZm8gZm9yIGRlYWRsb2NrIGRldGVjdGlvbjogJHtlcnJvcn1gLFxuXHRcdFx0XHRcdCk7XG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHR9XG5cblx0XHRyZXR1cm4gZmFsc2U7XG5cdH1cblxuXHQvKipcblx0ICogQXR0ZW1wdHMgdG8gYWNxdWlyZSBhIGxvY2tcblx0ICogQHBhcmFtIGxvY2tOYW1lIE5hbWUgb2YgdGhlIGxvY2sgdG8gYWNxdWlyZVxuXHQgKiBAcGFyYW0gdGltZW91dE1zIE1heGltdW0gdGltZSB0byB3YWl0IGZvciBsb2NrIGFjcXVpc2l0aW9uXG5cdCAqIEBwYXJhbSBwcmlvcml0eSBPcHRpb25hbCBwcmlvcml0eSBmb3IgdGhpcyBsb2NrIHJlcXVlc3QgKGhpZ2hlciB2YWx1ZXMgaGF2ZSBoaWdoZXIgcHJpb3JpdHkpXG5cdCAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHRvIHRydWUgaWYgdGhlIGxvY2sgd2FzIGFjcXVpcmVkLCBmYWxzZSBvdGhlcndpc2Vcblx0ICovXG5cdHB1YmxpYyBhc3luYyBhY3F1aXJlTG9jayhcblx0XHRsb2NrTmFtZTogc3RyaW5nLFxuXHRcdHRpbWVvdXRNcz86IG51bWJlcixcblx0XHRwcmlvcml0eSA9IDAsXG5cdCk6IFByb21pc2U8Ym9vbGVhbj4ge1xuXHRcdHRyeSB7XG5cdFx0XHQvLyBSZWdpc3RlciB0aGlzIGxvY2sgcmVxdWVzdCBmb3IgZGVhZGxvY2sgZGV0ZWN0aW9uXG5cdFx0XHR0aGlzLmxvY2tSZXF1ZXN0cy5zZXQobG9ja05hbWUsIHtcblx0XHRcdFx0bG9ja05hbWUsXG5cdFx0XHRcdHByaW9yaXR5LFxuXHRcdFx0XHRhY3F1aXJlZEF0OiBEYXRlLm5vdygpLFxuXHRcdFx0XHRvd25lcjogdGhpcy5vd25lcklkLFxuXHRcdFx0fSk7XG5cblx0XHRcdC8vIE1ha2Ugc3VyZSB0aGUgbG9jayBmaWxlIGV4aXN0cyAodXNpbmcgYXRvbWljIGluaXRpYWxpemF0aW9uKVxuXHRcdFx0YXdhaXQgdGhpcy5pbml0aWFsaXplTG9jayhsb2NrTmFtZSk7XG5cblx0XHRcdGNvbnN0IHN0YXJ0VGltZSA9IERhdGUubm93KCk7XG5cdFx0XHRjb25zdCBtYXhXYWl0VGltZSA9IHRpbWVvdXRNcyB8fCB0aGlzLmxvY2tUaW1lb3V0TXM7XG5cblx0XHRcdGZvciAobGV0IGF0dGVtcHQgPSAwOyBhdHRlbXB0IDwgdGhpcy5tYXhSZXRyaWVzOyBhdHRlbXB0KyspIHtcblx0XHRcdFx0Ly8gQ2hlY2sgaWYgd2UndmUgZXhjZWVkZWQgdGhlIG92ZXJhbGwgdGltZW91dFxuXHRcdFx0XHRpZiAoRGF0ZS5ub3coKSAtIHN0YXJ0VGltZSA+IG1heFdhaXRUaW1lKSB7XG5cdFx0XHRcdFx0dGhpcy5sb2NrUmVxdWVzdHMuZGVsZXRlKGxvY2tOYW1lKTtcblx0XHRcdFx0XHR0aGlzLmxvY2tEZXBlbmRlbmNpZXMuZGVsZXRlKHRoaXMub3duZXJJZCk7XG5cdFx0XHRcdFx0cmV0dXJuIGZhbHNlO1xuXHRcdFx0XHR9XG5cblx0XHRcdFx0dHJ5IHtcblx0XHRcdFx0XHQvLyBHZXQgdGhlIGN1cnJlbnQgbG9jayBpbmZvXG5cdFx0XHRcdFx0Y29uc3QgeyBsb2NrSW5mbywgZXRhZyB9ID0gYXdhaXQgdGhpcy5nZXRMb2NrSW5mbyhsb2NrTmFtZSk7XG5cblx0XHRcdFx0XHQvLyBDaGVjayBpZiB0aGUgbG9jayBpcyBhbHJlYWR5IGhlbGQgYnkgdXNcblx0XHRcdFx0XHRpZiAobG9ja0luZm8ubG9ja2VkICYmIGxvY2tJbmZvLm93bmVyID09PSB0aGlzLm93bmVySWQpIHtcblx0XHRcdFx0XHRcdC8vIFdlIGFscmVhZHkgb3duIHRoaXMgbG9ja1xuXHRcdFx0XHRcdFx0dGhpcy5oZWxkTG9ja3Muc2V0KGxvY2tOYW1lLCBldGFnKTtcblx0XHRcdFx0XHRcdHRoaXMubG9ja1JlcXVlc3RzLmRlbGV0ZShsb2NrTmFtZSk7XG5cdFx0XHRcdFx0XHRyZXR1cm4gdHJ1ZTtcblx0XHRcdFx0XHR9XG5cblx0XHRcdFx0XHQvLyBSZWdpc3RlciB0aGlzIGxvY2sgZGVwZW5kZW5jeSBmb3IgZGVhZGxvY2sgZGV0ZWN0aW9uXG5cdFx0XHRcdFx0aWYgKGxvY2tJbmZvLmxvY2tlZCAmJiBsb2NrSW5mby5vd25lcikge1xuXHRcdFx0XHRcdFx0dGhpcy5yZWdpc3RlckxvY2tEZXBlbmRlbmN5KHRoaXMub3duZXJJZCwgbG9ja05hbWUsIGxvY2tJbmZvLm93bmVyKTtcblx0XHRcdFx0XHR9XG5cblx0XHRcdFx0XHQvLyBDaGVjayBpZiB0aGUgbG9jayBpcyB1bmxvY2tlZCBvciBleHBpcmVkXG5cdFx0XHRcdFx0Ly8gSW5jbHVkZSBjbG9jayBza2V3IHRvbGVyYW5jZSBpbiB0aGUgZXhwaXJhdGlvbiBjaGVja1xuXHRcdFx0XHRcdGNvbnN0IG5vdyA9IERhdGUubm93KCk7XG5cdFx0XHRcdFx0Y29uc3QgaXNMb2NrRnJlZSA9XG5cdFx0XHRcdFx0XHQhbG9ja0luZm8ubG9ja2VkIHx8XG5cdFx0XHRcdFx0XHQobG9ja0luZm8uZXhwaXJlc0F0ICE9PSB1bmRlZmluZWQgJiZcblx0XHRcdFx0XHRcdFx0bG9ja0luZm8uZXhwaXJlc0F0IDwgbm93IC0gdGhpcy5jbG9ja1NrZXdUb2xlcmFuY2VNcyk7XG5cblx0XHRcdFx0XHQvLyBJZiB0aGUgbG9jayBpcyBoZWxkIGJ1dCB0aGUgY3VycmVudCByZXF1ZXN0IGhhcyBoaWdoZXIgcHJpb3JpdHksXG5cdFx0XHRcdFx0Ly8gd2UnbGwgdHJ5IHRvIGZvcmNlLWFjcXVpcmUgaXQgaWYgd2UgZGV0ZWN0IGEgcG90ZW50aWFsIGRlYWRsb2NrXG5cdFx0XHRcdFx0Y29uc3QgY2FuRm9yY2VBY3F1aXJlID1cblx0XHRcdFx0XHRcdGxvY2tJbmZvLmxvY2tlZCAmJlxuXHRcdFx0XHRcdFx0bG9ja0luZm8ucHJpb3JpdHkgIT09IHVuZGVmaW5lZCAmJlxuXHRcdFx0XHRcdFx0cHJpb3JpdHkgPiBsb2NrSW5mby5wcmlvcml0eSAmJlxuXHRcdFx0XHRcdFx0KGF3YWl0IHRoaXMuaXNQb3RlbnRpYWxEZWFkbG9jayhsb2NrTmFtZSwgbG9ja0luZm8ub3duZXIpKTtcblxuXHRcdFx0XHRcdGlmIChpc0xvY2tGcmVlIHx8IGNhbkZvcmNlQWNxdWlyZSkge1xuXHRcdFx0XHRcdFx0Ly8gVHJ5IHRvIGFjcXVpcmUgdGhlIGxvY2tcblx0XHRcdFx0XHRcdGNvbnN0IG5ld0xvY2tJbmZvOiBMb2NrSW5mbyA9IHtcblx0XHRcdFx0XHRcdFx0bG9ja2VkOiB0cnVlLFxuXHRcdFx0XHRcdFx0XHRvd25lcjogdGhpcy5vd25lcklkLFxuXHRcdFx0XHRcdFx0XHRhY3F1aXJlZEF0OiBub3csXG5cdFx0XHRcdFx0XHRcdGV4cGlyZXNBdDogbm93ICsgdGhpcy5sb2NrVGltZW91dE1zLFxuXHRcdFx0XHRcdFx0XHRwcmlvcml0eTogcHJpb3JpdHksXG5cdFx0XHRcdFx0XHR9O1xuXG5cdFx0XHRcdFx0XHRjb25zdCBuZXdFdGFnID0gYXdhaXQgdGhpcy51cGRhdGVMb2NrSW5mbyhcblx0XHRcdFx0XHRcdFx0bG9ja05hbWUsXG5cdFx0XHRcdFx0XHRcdG5ld0xvY2tJbmZvLFxuXHRcdFx0XHRcdFx0XHRldGFnLFxuXHRcdFx0XHRcdFx0KTtcblxuXHRcdFx0XHRcdFx0aWYgKG5ld0V0YWcpIHtcblx0XHRcdFx0XHRcdFx0Ly8gV2Ugc3VjY2Vzc2Z1bGx5IGFjcXVpcmVkIHRoZSBsb2NrXG5cdFx0XHRcdFx0XHRcdHRoaXMuaGVsZExvY2tzLnNldChsb2NrTmFtZSwgbmV3RXRhZyk7XG5cdFx0XHRcdFx0XHRcdHRoaXMubG9ja1JlcXVlc3RzLmRlbGV0ZShsb2NrTmFtZSk7XG5cdFx0XHRcdFx0XHRcdC8vIENsZWFuIHVwIHRoZSBkZXBlbmRlbmN5IGFzIHdlIG5vdyBvd24gdGhlIGxvY2tcblx0XHRcdFx0XHRcdFx0Y29uc3Qgb3VyRGVwZW5kZW5jaWVzID0gdGhpcy5sb2NrRGVwZW5kZW5jaWVzLmdldCh0aGlzLm93bmVySWQpO1xuXHRcdFx0XHRcdFx0XHRpZiAob3VyRGVwZW5kZW5jaWVzKSB7XG5cdFx0XHRcdFx0XHRcdFx0b3VyRGVwZW5kZW5jaWVzLmRlbGV0ZShsb2NrTmFtZSk7XG5cdFx0XHRcdFx0XHRcdFx0aWYgKG91ckRlcGVuZGVuY2llcy5zaXplID09PSAwKSB7XG5cdFx0XHRcdFx0XHRcdFx0XHR0aGlzLmxvY2tEZXBlbmRlbmNpZXMuZGVsZXRlKHRoaXMub3duZXJJZCk7XG5cdFx0XHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHRcdHJldHVybiB0cnVlO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdC8vIFdhaXQgd2l0aCBleHBvbmVudGlhbCBiYWNrb2ZmIGJlZm9yZSByZXRyeWluZ1xuXHRcdFx0XHRcdGF3YWl0IHRoaXMuZXhwb25lbnRpYWxCYWNrb2ZmKGF0dGVtcHQpO1xuXHRcdFx0XHR9IGNhdGNoIChlcnJvcikge1xuXHRcdFx0XHRcdC8vIEVuaGFuY2VkIGVycm9yIGhhbmRsaW5nXG5cdFx0XHRcdFx0Y29uc3QgZXJyID0gZXJyb3IgYXMge1xuXHRcdFx0XHRcdFx0JG1ldGFkYXRhPzogeyBodHRwU3RhdHVzQ29kZT86IG51bWJlciB9O1xuXHRcdFx0XHRcdFx0bmFtZT86IHN0cmluZztcblx0XHRcdFx0XHRcdG1lc3NhZ2U6IHN0cmluZztcblx0XHRcdFx0XHR9O1xuXHRcdFx0XHRcdGlmIChlcnIuJG1ldGFkYXRhPy5odHRwU3RhdHVzQ29kZSA9PT0gNTAzKSB7XG5cdFx0XHRcdFx0XHRjb25zb2xlLndhcm4oXG5cdFx0XHRcdFx0XHRcdGBTMyBzZXJ2aWNlIHVuYXZhaWxhYmxlIHdoaWxlIGFjcXVpcmluZyBsb2NrICR7bG9ja05hbWV9LiBSZXRyeWluZy4uLmAsXG5cdFx0XHRcdFx0XHQpO1xuXHRcdFx0XHRcdH0gZWxzZSBpZiAoZXJyLm5hbWUgPT09IFwiVGhyb3R0bGluZ0V4Y2VwdGlvblwiKSB7XG5cdFx0XHRcdFx0XHRjb25zb2xlLndhcm4oXG5cdFx0XHRcdFx0XHRcdGBBV1MgcmVxdWVzdCB0aHJvdHRsaW5nIGVuY291bnRlcmVkIHdoaWxlIGFjcXVpcmluZyBsb2NrICR7bG9ja05hbWV9LiBSZXRyeWluZy4uLmAsXG5cdFx0XHRcdFx0XHQpO1xuXHRcdFx0XHRcdH0gZWxzZSBpZiAoZXJyLiRtZXRhZGF0YT8uaHR0cFN0YXR1c0NvZGUgPT09IDQwNCkge1xuXHRcdFx0XHRcdFx0Ly8gTG9jayBmaWxlIGRpc2FwcGVhcmVkLCB0cnkgdG8gaW5pdGlhbGl6ZSBpdCBhZ2FpblxuXHRcdFx0XHRcdFx0YXdhaXQgdGhpcy5pbml0aWFsaXplTG9jayhsb2NrTmFtZSk7XG5cdFx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRcdGNvbnNvbGUud2Fybihcblx0XHRcdFx0XHRcdFx0YEVycm9yIGFjcXVpcmluZyBsb2NrICR7bG9ja05hbWV9OiAke2Vyci5tZXNzYWdlfS4gUmV0cnlpbmcuLi5gLFxuXHRcdFx0XHRcdFx0KTtcblx0XHRcdFx0XHR9XG5cblx0XHRcdFx0XHQvLyBVc2UgZXhwb25lbnRpYWwgYmFja29mZlxuXHRcdFx0XHRcdGF3YWl0IHRoaXMuZXhwb25lbnRpYWxCYWNrb2ZmKGF0dGVtcHQpO1xuXHRcdFx0XHR9XG5cdFx0XHR9XG5cblx0XHRcdHRoaXMubG9ja1JlcXVlc3RzLmRlbGV0ZShsb2NrTmFtZSk7XG5cdFx0XHQvLyBDbGVhbiB1cCBkZXBlbmRlbmNpZXMgYXMgd2UgZmFpbGVkIHRvIGFjcXVpcmUgdGhlIGxvY2tcblx0XHRcdGNvbnN0IG91ckRlcGVuZGVuY2llcyA9IHRoaXMubG9ja0RlcGVuZGVuY2llcy5nZXQodGhpcy5vd25lcklkKTtcblx0XHRcdGlmIChvdXJEZXBlbmRlbmNpZXMpIHtcblx0XHRcdFx0b3VyRGVwZW5kZW5jaWVzLmRlbGV0ZShsb2NrTmFtZSk7XG5cdFx0XHRcdGlmIChvdXJEZXBlbmRlbmNpZXMuc2l6ZSA9PT0gMCkge1xuXHRcdFx0XHRcdHRoaXMubG9ja0RlcGVuZGVuY2llcy5kZWxldGUodGhpcy5vd25lcklkKTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdFx0cmV0dXJuIGZhbHNlO1xuXHRcdH0gY2F0Y2ggKGVycm9yKSB7XG5cdFx0XHQvLyBIYW5kbGUgY3JpdGljYWwgZXJyb3JzIHRoYXQgcHJldmVudCBsb2NrIGFjcXVpc2l0aW9uXG5cdFx0XHR0aGlzLmxvY2tSZXF1ZXN0cy5kZWxldGUobG9ja05hbWUpO1xuXHRcdFx0Ly8gQ2xlYW4gdXAgZGVwZW5kZW5jaWVzXG5cdFx0XHR0aGlzLmxvY2tEZXBlbmRlbmNpZXMuZGVsZXRlKHRoaXMub3duZXJJZCk7XG5cdFx0XHRjb25zb2xlLmVycm9yKGBDcml0aWNhbCBlcnJvciBhY3F1aXJpbmcgbG9jayAke2xvY2tOYW1lfTpgLCBlcnJvcik7XG5cdFx0XHRyZXR1cm4gZmFsc2U7XG5cdFx0fVxuXHR9XG5cblx0LyoqXG5cdCAqIFJlZnJlc2hlcyBhIGxvY2sncyBleHBpcmF0aW9uIHRpbWVcblx0ICogQHBhcmFtIGxvY2tOYW1lIE5hbWUgb2YgdGhlIGxvY2sgdG8gcmVmcmVzaFxuXHQgKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB0byB0cnVlIGlmIHRoZSBsb2NrIHdhcyByZWZyZXNoZWQsIGZhbHNlIG90aGVyd2lzZVxuXHQgKi9cblx0cHVibGljIGFzeW5jIHJlZnJlc2hMb2NrKGxvY2tOYW1lOiBzdHJpbmcpOiBQcm9taXNlPGJvb2xlYW4+IHtcblx0XHR0cnkge1xuXHRcdFx0Ly8gR2V0IHRoZSBjdXJyZW50IGxvY2sgaW5mb1xuXHRcdFx0Y29uc3QgeyBsb2NrSW5mbywgZXRhZyB9ID0gYXdhaXQgdGhpcy5nZXRMb2NrSW5mbyhsb2NrTmFtZSk7XG5cblx0XHRcdC8vIENoZWNrIGlmIHRoZSBsb2NrIGlzIGhlbGQgYnkgdXNcblx0XHRcdGNvbnN0IG5vdyA9IERhdGUubm93KCk7XG5cblx0XHRcdC8vIENoZWNrIGlmIHRoZSBsb2NrIGlzIGhlbGQgYnkgdXMgQU5EIG5vdCBleHBpcmVkXG5cdFx0XHRpZiAoXG5cdFx0XHRcdCFsb2NrSW5mby5sb2NrZWQgfHxcblx0XHRcdFx0bG9ja0luZm8ub3duZXIgIT09IHRoaXMub3duZXJJZCB8fFxuXHRcdFx0XHQobG9ja0luZm8uZXhwaXJlc0F0ICE9PSB1bmRlZmluZWQgJiYgbG9ja0luZm8uZXhwaXJlc0F0IDwgbm93KVxuXHRcdFx0KSB7XG5cdFx0XHRcdHJldHVybiBmYWxzZTtcblx0XHRcdH1cblxuXHRcdFx0Ly8gVXBkYXRlIHRoZSBleHBpcmF0aW9uIHRpbWVcblx0XHRcdGxvY2tJbmZvLmV4cGlyZXNBdCA9IG5vdyArIHRoaXMubG9ja1RpbWVvdXRNcztcblxuXHRcdFx0Y29uc3QgbmV3RXRhZyA9IGF3YWl0IHRoaXMudXBkYXRlTG9ja0luZm8obG9ja05hbWUsIGxvY2tJbmZvLCBldGFnKTtcblxuXHRcdFx0cmV0dXJuICEhbmV3RXRhZztcblx0XHRcdC8vIGJpb21lLWlnbm9yZSBsaW50L3N1c3BpY2lvdXMvbm9FeHBsaWNpdEFueTogPGV4cGxhbmF0aW9uPlxuXHRcdH0gY2F0Y2ggKGVycm9yOiBhbnkpIHtcblx0XHRcdC8vIEVuaGFuY2VkIGVycm9yIGhhbmRsaW5nXG5cdFx0XHRpZiAoZXJyb3IuJG1ldGFkYXRhPy5odHRwU3RhdHVzQ29kZSA9PT0gNDA0KSB7XG5cdFx0XHRcdGNvbnNvbGUud2FybihgTG9jayBmaWxlICR7bG9ja05hbWV9IG5vdCBmb3VuZCBkdXJpbmcgcmVmcmVzaGApO1xuXHRcdFx0fSBlbHNlIGlmIChlcnJvci4kbWV0YWRhdGE/Lmh0dHBTdGF0dXNDb2RlID09PSA1MDMpIHtcblx0XHRcdFx0Y29uc29sZS53YXJuKFxuXHRcdFx0XHRcdGBTMyBzZXJ2aWNlIHVuYXZhaWxhYmxlIHdoaWxlIHJlZnJlc2hpbmcgbG9jayAke2xvY2tOYW1lfWAsXG5cdFx0XHRcdCk7XG5cdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRjb25zb2xlLndhcm4oYEVycm9yIHJlZnJlc2hpbmcgbG9jayAke2xvY2tOYW1lfTogJHtlcnJvci5tZXNzYWdlfWApO1xuXHRcdFx0fVxuXG5cdFx0XHRyZXR1cm4gZmFsc2U7XG5cdFx0fVxuXHR9XG5cblx0LyoqXG5cdCAqIFJlbGVhc2VzIGEgbG9ja1xuXHQgKiBAcGFyYW0gbG9ja05hbWUgTmFtZSBvZiB0aGUgbG9jayB0byByZWxlYXNlXG5cdCAqIEBwYXJhbSBmb3JjZSBJZiB0cnVlLCByZWxlYXNlIHRoZSBsb2NrIGV2ZW4gaWYgaXQncyBub3Qgb3duZWQgYnkgdXNcblx0ICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgdG8gdHJ1ZSBpZiB0aGUgbG9jayB3YXMgcmVsZWFzZWQsIGZhbHNlIG90aGVyd2lzZVxuXHQgKi9cblx0cHVibGljIGFzeW5jIHJlbGVhc2VMb2NrKGxvY2tOYW1lOiBzdHJpbmcsIGZvcmNlID0gZmFsc2UpOiBQcm9taXNlPGJvb2xlYW4+IHtcblx0XHR0cnkge1xuXHRcdFx0Ly8gR2V0IHRoZSBjdXJyZW50IGxvY2sgaW5mb1xuXHRcdFx0Y29uc3QgeyBsb2NrSW5mbywgZXRhZyB9ID0gYXdhaXQgdGhpcy5nZXRMb2NrSW5mbyhsb2NrTmFtZSk7XG5cblx0XHRcdC8vIENoZWNrIGlmIHRoZSBsb2NrIGlzIGhlbGQgYnkgdXMgb3IgaWYgd2UncmUgZm9yY2luZ1xuXHRcdFx0aWYgKGxvY2tJbmZvLmxvY2tlZCAmJiAhZm9yY2UgJiYgbG9ja0luZm8ub3duZXIgIT09IHRoaXMub3duZXJJZCkge1xuXHRcdFx0XHRyZXR1cm4gZmFsc2U7XG5cdFx0XHR9XG5cblx0XHRcdC8vIFJlbGVhc2UgdGhlIGxvY2tcblx0XHRcdGNvbnN0IG5ld0xvY2tJbmZvOiBMb2NrSW5mbyA9IHtcblx0XHRcdFx0bG9ja2VkOiBmYWxzZSxcblx0XHRcdH07XG5cblx0XHRcdGNvbnN0IG5ld0V0YWcgPSBhd2FpdCB0aGlzLnVwZGF0ZUxvY2tJbmZvKGxvY2tOYW1lLCBuZXdMb2NrSW5mbywgZXRhZyk7XG5cblx0XHRcdC8vIFJlbW92ZSBmcm9tIG91ciBoZWxkIGxvY2tzXG5cdFx0XHR0aGlzLmhlbGRMb2Nrcy5kZWxldGUobG9ja05hbWUpO1xuXHRcdFx0dGhpcy5sb2NrUmVxdWVzdHMuZGVsZXRlKGxvY2tOYW1lKTtcblxuXHRcdFx0Ly8gQ2xlYW4gdXAgYW55IGRlcGVuZGVuY2llcyByZWxhdGVkIHRvIHRoaXMgbG9ja1xuXHRcdFx0Ly8gRm9yIGFsbCBvd25lcnMgd2FpdGluZyBmb3IgdGhpcyBsb2NrLCByZW1vdmUgaXQgZnJvbSB0aGVpciB3YWl0aW5nIHNldFxuXHRcdFx0Zm9yIChjb25zdCBbb3duZXIsIHdhaXRpbmdGb3JdIG9mIHRoaXMubG9ja0RlcGVuZGVuY2llcy5lbnRyaWVzKCkpIHtcblx0XHRcdFx0d2FpdGluZ0Zvci5kZWxldGUobG9ja05hbWUpO1xuXHRcdFx0XHRpZiAod2FpdGluZ0Zvci5zaXplID09PSAwKSB7XG5cdFx0XHRcdFx0dGhpcy5sb2NrRGVwZW5kZW5jaWVzLmRlbGV0ZShvd25lcik7XG5cdFx0XHRcdH1cblx0XHRcdH1cblxuXHRcdFx0cmV0dXJuICEhbmV3RXRhZztcblx0XHRcdC8vIGJpb21lLWlnbm9yZSBsaW50L3N1c3BpY2lvdXMvbm9FeHBsaWNpdEFueTogPGV4cGxhbmF0aW9uPlxuXHRcdH0gY2F0Y2ggKGVycm9yOiBhbnkpIHtcblx0XHRcdC8vIEVuaGFuY2VkIGVycm9yIGhhbmRsaW5nXG5cdFx0XHRpZiAoZXJyb3IuJG1ldGFkYXRhPy5odHRwU3RhdHVzQ29kZSA9PT0gNDA0KSB7XG5cdFx0XHRcdGNvbnNvbGUud2FybihgTG9jayBmaWxlICR7bG9ja05hbWV9IG5vdCBmb3VuZCBkdXJpbmcgcmVsZWFzZWApO1xuXHRcdFx0fSBlbHNlIGlmIChlcnJvci4kbWV0YWRhdGE/Lmh0dHBTdGF0dXNDb2RlID09PSA1MDMpIHtcblx0XHRcdFx0Y29uc29sZS53YXJuKGBTMyBzZXJ2aWNlIHVuYXZhaWxhYmxlIHdoaWxlIHJlbGVhc2luZyBsb2NrICR7bG9ja05hbWV9YCk7XG5cdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRjb25zb2xlLndhcm4oYEVycm9yIHJlbGVhc2luZyBsb2NrICR7bG9ja05hbWV9OiAke2Vycm9yLm1lc3NhZ2V9YCk7XG5cdFx0XHR9XG5cblx0XHRcdHJldHVybiBmYWxzZTtcblx0XHR9XG5cdH1cblxuXHQvKipcblx0ICogRXhlY3V0ZXMgYSBmdW5jdGlvbiB3aGlsZSBob2xkaW5nIGEgbG9ja1xuXHQgKiBAcGFyYW0gbG9ja05hbWUgTmFtZSBvZiB0aGUgbG9jayB0byBhY3F1aXJlXG5cdCAqIEBwYXJhbSBmbiBGdW5jdGlvbiB0byBleGVjdXRlIHdoaWxlIGhvbGRpbmcgdGhlIGxvY2tcblx0ICogQHBhcmFtIG9wdGlvbnMgT3B0aW9ucyBmb3IgbG9jayBhY3F1aXNpdGlvblxuXHQgKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB0byB0aGUgcmVzdWx0IG9mIHRoZSBmdW5jdGlvbiBvciBudWxsIGlmIHRoZSBsb2NrIGNvdWxkbid0IGJlIGFjcXVpcmVkXG5cdCAqL1xuXHRwdWJsaWMgYXN5bmMgd2l0aExvY2s8VD4oXG5cdFx0bG9ja05hbWU6IHN0cmluZyxcblx0XHRmbjogKCkgPT4gUHJvbWlzZTxUPixcblx0XHRvcHRpb25zOiB7IHRpbWVvdXRNcz86IG51bWJlcjsgcmV0cmllcz86IG51bWJlciB9ID0ge30sXG5cdCk6IFByb21pc2U8VCB8IG51bGw+IHtcblx0XHRjb25zdCBhY3F1aXJlVGltZW91dCA9IG9wdGlvbnMudGltZW91dE1zIHx8IHRoaXMubG9ja1RpbWVvdXRNcztcblx0XHRjb25zdCBtYXhSZXRyaWVzID0gb3B0aW9ucy5yZXRyaWVzIHx8IHRoaXMubWF4UmV0cmllcztcblxuXHRcdC8vIFRyeSB0byBhY3F1aXJlIHRoZSBsb2NrXG5cdFx0Zm9yIChsZXQgaSA9IDA7IGkgPCBtYXhSZXRyaWVzOyBpKyspIHtcblx0XHRcdGNvbnN0IGFjcXVpcmVkID0gYXdhaXQgdGhpcy5hY3F1aXJlTG9jayhsb2NrTmFtZSwgYWNxdWlyZVRpbWVvdXQpO1xuXG5cdFx0XHRpZiAoYWNxdWlyZWQpIHtcblx0XHRcdFx0Ly8gU2V0dXAgZm9yIGhlYXJ0YmVhdFxuXHRcdFx0XHRsZXQgaGVhcnRiZWF0U3RvcHBlZCA9IHRydWU7IC8vIFN0YXJ0IGFzIHRydWUgdG8gcHJldmVudCBhbnkgZWFybHkgaGVhcnRiZWF0c1xuXHRcdFx0XHRsZXQgaGVhcnRiZWF0SW50ZXJ2YWw6IE5vZGVKUy5UaW1lb3V0IHwgbnVsbCA9IG51bGw7XG5cblx0XHRcdFx0dHJ5IHtcblx0XHRcdFx0XHQvLyBTZXQgdXAgYSB0aW1lciB0byByZWZyZXNoIHRoZSBsb2NrIHBlcmlvZGljYWxseSwgYnV0IG9ubHkgYWZ0ZXIgd2UncmVcblx0XHRcdFx0XHQvLyByZWFkeSB0byBleGVjdXRlIHRoZSBmdW5jdGlvblxuXHRcdFx0XHRcdGNvbnN0IHJlZnJlc2hJbnRlcnZhbCA9IE1hdGgubWF4KHRoaXMubG9ja1RpbWVvdXRNcyAvIDMsIDEwMDApO1xuXG5cdFx0XHRcdFx0Ly8gTm93IHdlIGNhbiBlbmFibGUgaGVhcnRiZWF0c1xuXHRcdFx0XHRcdGhlYXJ0YmVhdFN0b3BwZWQgPSBmYWxzZTtcblxuXHRcdFx0XHRcdGhlYXJ0YmVhdEludGVydmFsID0gc2V0SW50ZXJ2YWwoYXN5bmMgKCkgPT4ge1xuXHRcdFx0XHRcdFx0aWYgKGhlYXJ0YmVhdFN0b3BwZWQpIHtcblx0XHRcdFx0XHRcdFx0Ly8gRG91YmxlIGNoZWNrIHRvIHByZXZlbnQgYW55IHJhY2UgY29uZGl0aW9uc1xuXHRcdFx0XHRcdFx0XHRpZiAoaGVhcnRiZWF0SW50ZXJ2YWwpIHtcblx0XHRcdFx0XHRcdFx0XHRjbGVhckludGVydmFsKGhlYXJ0YmVhdEludGVydmFsKTtcblx0XHRcdFx0XHRcdFx0XHRoZWFydGJlYXRJbnRlcnZhbCA9IG51bGw7XG5cdFx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHRcdFx0cmV0dXJuO1xuXHRcdFx0XHRcdFx0fVxuXG5cdFx0XHRcdFx0XHR0cnkge1xuXHRcdFx0XHRcdFx0XHRjb25zdCByZWZyZXNoZWQgPSBhd2FpdCB0aGlzLnJlZnJlc2hMb2NrKGxvY2tOYW1lKTtcblx0XHRcdFx0XHRcdFx0Ly8gSWYgcmVmcmVzaCBmYWlscywgc3RvcCB0aGUgaGVhcnRiZWF0IHRvIHByZXZlbnQgZnVydGhlciBhdHRlbXB0c1xuXHRcdFx0XHRcdFx0XHRpZiAoIXJlZnJlc2hlZCkge1xuXHRcdFx0XHRcdFx0XHRcdGNvbnNvbGUud2Fybihcblx0XHRcdFx0XHRcdFx0XHRcdGBGYWlsZWQgdG8gcmVmcmVzaCBsb2NrICR7bG9ja05hbWV9LCBzdG9wcGluZyBoZWFydGJlYXRgLFxuXHRcdFx0XHRcdFx0XHRcdCk7XG5cdFx0XHRcdFx0XHRcdFx0aGVhcnRiZWF0U3RvcHBlZCA9IHRydWU7XG5cdFx0XHRcdFx0XHRcdFx0aWYgKGhlYXJ0YmVhdEludGVydmFsKSB7XG5cdFx0XHRcdFx0XHRcdFx0XHRjbGVhckludGVydmFsKGhlYXJ0YmVhdEludGVydmFsKTtcblx0XHRcdFx0XHRcdFx0XHRcdGhlYXJ0YmVhdEludGVydmFsID0gbnVsbDtcblx0XHRcdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHRcdH0gY2F0Y2ggKGVycm9yKSB7XG5cdFx0XHRcdFx0XHRcdGNvbnNvbGUud2FybihgRXJyb3IgcmVmcmVzaGluZyBsb2NrICR7bG9ja05hbWV9OiAke2Vycm9yfWApO1xuXHRcdFx0XHRcdFx0XHQvLyBBbHNvIHN0b3Agb24gZXJyb3JzXG5cdFx0XHRcdFx0XHRcdGhlYXJ0YmVhdFN0b3BwZWQgPSB0cnVlO1xuXHRcdFx0XHRcdFx0XHRpZiAoaGVhcnRiZWF0SW50ZXJ2YWwpIHtcblx0XHRcdFx0XHRcdFx0XHRjbGVhckludGVydmFsKGhlYXJ0YmVhdEludGVydmFsKTtcblx0XHRcdFx0XHRcdFx0XHRoZWFydGJlYXRJbnRlcnZhbCA9IG51bGw7XG5cdFx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9LCByZWZyZXNoSW50ZXJ2YWwpO1xuXG5cdFx0XHRcdFx0Ly8gRXhlY3V0ZSB0aGUgZnVuY3Rpb25cblx0XHRcdFx0XHRjb25zdCByZXN1bHQgPSBhd2FpdCBmbigpO1xuXG5cdFx0XHRcdFx0Ly8gRW5zdXJlIGhlYXJ0YmVhdCBpcyBzdG9wcGVkIGJlZm9yZSByZXR1cm5pbmdcblx0XHRcdFx0XHRoZWFydGJlYXRTdG9wcGVkID0gdHJ1ZTtcblx0XHRcdFx0XHRpZiAoaGVhcnRiZWF0SW50ZXJ2YWwpIHtcblx0XHRcdFx0XHRcdGNsZWFySW50ZXJ2YWwoaGVhcnRiZWF0SW50ZXJ2YWwpO1xuXHRcdFx0XHRcdFx0aGVhcnRiZWF0SW50ZXJ2YWwgPSBudWxsO1xuXHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdC8vIFJldHVybiB0aGUgcmVzdWx0XG5cdFx0XHRcdFx0cmV0dXJuIHJlc3VsdDtcblx0XHRcdFx0fSBjYXRjaCAoZXJyb3IpIHtcblx0XHRcdFx0XHQvLyBMb2cgdGhlIGVycm9yIGFuZCBlbnN1cmUgaXQncyBwcm9wYWdhdGVkIGFmdGVyIGNsZWFudXBcblx0XHRcdFx0XHRjb25zb2xlLmVycm9yKFxuXHRcdFx0XHRcdFx0YEVycm9yIGluIGZ1bmN0aW9uIGV4ZWN1dGVkIHdpdGggbG9jayAke2xvY2tOYW1lfTpgLFxuXHRcdFx0XHRcdFx0ZXJyb3IsXG5cdFx0XHRcdFx0KTtcblxuXHRcdFx0XHRcdC8vIEVuc3VyZSB0aGUgbG9jayBpcyByZWxlYXNlZCBhbmQgdGhlbiByZXRocm93XG5cdFx0XHRcdFx0aWYgKGhlYXJ0YmVhdEludGVydmFsKSB7XG5cdFx0XHRcdFx0XHRoZWFydGJlYXRTdG9wcGVkID0gdHJ1ZTtcblx0XHRcdFx0XHRcdGNsZWFySW50ZXJ2YWwoaGVhcnRiZWF0SW50ZXJ2YWwpO1xuXHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdC8vIE1ha2UgYSBkZXRlcm1pbmVkIGVmZm9ydCB0byByZWxlYXNlIHRoZSBsb2NrIC0gcmV0cnkgb24gZmFpbHVyZVxuXHRcdFx0XHRcdGxldCByZWxlYXNlZCA9IGZhbHNlO1xuXHRcdFx0XHRcdGZvciAobGV0IGF0dGVtcHQgPSAwOyBhdHRlbXB0IDwgMyAmJiAhcmVsZWFzZWQ7IGF0dGVtcHQrKykge1xuXHRcdFx0XHRcdFx0dHJ5IHtcblx0XHRcdFx0XHRcdFx0cmVsZWFzZWQgPSBhd2FpdCB0aGlzLnJlbGVhc2VMb2NrKGxvY2tOYW1lKTtcblx0XHRcdFx0XHRcdFx0aWYgKCFyZWxlYXNlZCkge1xuXHRcdFx0XHRcdFx0XHRcdC8vIFdhaXQgYSBiaXQgYmVmb3JlIHJldHJ5aW5nXG5cdFx0XHRcdFx0XHRcdFx0YXdhaXQgbmV3IFByb21pc2UoKHJlc29sdmUpID0+XG5cdFx0XHRcdFx0XHRcdFx0XHRzZXRUaW1lb3V0KHJlc29sdmUsIDEwMCAqIChhdHRlbXB0ICsgMSkpLFxuXHRcdFx0XHRcdFx0XHRcdCk7XG5cdFx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHRcdH0gY2F0Y2ggKHJlbGVhc2VFcnJvcikge1xuXHRcdFx0XHRcdFx0XHRjb25zb2xlLndhcm4oXG5cdFx0XHRcdFx0XHRcdFx0YEZhaWxlZCB0byByZWxlYXNlIGxvY2sgJHtsb2NrTmFtZX0gKGF0dGVtcHQgJHthdHRlbXB0ICsgMX0pOiAke3JlbGVhc2VFcnJvcn1gLFxuXHRcdFx0XHRcdFx0XHQpO1xuXHRcdFx0XHRcdFx0XHRpZiAoYXR0ZW1wdCA8IDIpIHtcblx0XHRcdFx0XHRcdFx0XHRhd2FpdCBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT5cblx0XHRcdFx0XHRcdFx0XHRcdHNldFRpbWVvdXQocmVzb2x2ZSwgMTAwICogKGF0dGVtcHQgKyAxKSksXG5cdFx0XHRcdFx0XHRcdFx0KTtcblx0XHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdC8vIFJldGhyb3cgdGhlIG9yaWdpbmFsIGVycm9yXG5cdFx0XHRcdFx0dGhyb3cgZXJyb3I7XG5cdFx0XHRcdH0gZmluYWxseSB7XG5cdFx0XHRcdFx0Ly8gQ2xlYW4gdXAgaGVhcnRiZWF0XG5cdFx0XHRcdFx0aWYgKGhlYXJ0YmVhdEludGVydmFsKSB7XG5cdFx0XHRcdFx0XHRoZWFydGJlYXRTdG9wcGVkID0gdHJ1ZTtcblx0XHRcdFx0XHRcdGNsZWFySW50ZXJ2YWwoaGVhcnRiZWF0SW50ZXJ2YWwpO1xuXHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdC8vIFJlbGVhc2UgdGhlIGxvY2sgKHRoaXMgaXMgYWxzbyBkb25lIGluIHRoZSBjYXRjaCBibG9jayBidXQgd2UgbmVlZCBpdCBoZXJlIGZvciBub3JtYWwgZXhpdClcblx0XHRcdFx0XHR0cnkge1xuXHRcdFx0XHRcdFx0YXdhaXQgdGhpcy5yZWxlYXNlTG9jayhsb2NrTmFtZSk7XG5cdFx0XHRcdFx0fSBjYXRjaCAoZXJyb3IpIHtcblx0XHRcdFx0XHRcdGNvbnNvbGUud2Fybihcblx0XHRcdFx0XHRcdFx0YEZhaWxlZCB0byByZWxlYXNlIGxvY2sgJHtsb2NrTmFtZX0gaW4gZmluYWxseSBibG9jazogJHtlcnJvcn1gLFxuXHRcdFx0XHRcdFx0KTtcblx0X