UNPKG

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 78.2 kB
import{CreateBucketCommand as S,DeleteObjectCommand as g,GetObjectCommand as p,HeadBucketCommand as C,HeadObjectCommand as E,ListObjectsV2Command as x,PutObjectCommand as m,S3Client as I}from"@aws-sdk/client-s3";async function y(w){if(w instanceof Blob)return await w.text();if(w.getReader){let e=w.getReader(),t=[];for(;;){let{done:i,value:s}=await e.read();if(i)break;t.push(s)}let r=new Uint8Array(t.reduce((i,s)=>i+s.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=[],n=w,i=()=>{n.removeAllListeners(),typeof n.destroy=="function"&&n.destroy()};n.on("data",s=>r.push(Buffer.from(s))),n.on("error",s=>{i(),t(s)}),n.on("end",()=>{i(),e(Buffer.concat(r).toString("utf8"))})})}var b=class{s3Client;bucketName;keyPrefix;maxRetries;retryDelayMs;maxRetryDelayMs;useJitter;lockTimeoutMs;clockSkewToleranceMs;ownerId;createBucketIfNotExists;bucketInitialized=!1;heldLocks=new Map;lockRequests=new Map;lockDependencies=new Map;constructor(e){this.s3Client=e.s3Client??new I({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.createBucketIfNotExists=e.createBucketIfNotExists??!1,this.ownerId=`${process.pid}-${Date.now()}-${Math.random().toString(36).substring(2,15)}`}async ensureBucketExists(){if(!this.bucketInitialized)try{await this.s3Client.send(new C({Bucket:this.bucketName})),this.bucketInitialized=!0;return}catch(e){let t=e;if((t.$metadata?.httpStatusCode===404||t.name==="NoSuchBucket")&&this.createBucketIfNotExists)try{await this.s3Client.send(new S({Bucket:this.bucketName})),this.bucketInitialized=!0;return}catch(r){let n=r;if(n.$metadata?.httpStatusCode===409||n.name==="BucketAlreadyExists"){this.bucketInitialized=!0;return}throw new Error(`Failed to create bucket ${this.bucketName}: ${r.message}`)}else if(t.$metadata?.httpStatusCode===404||t.name==="NoSuchBucket")throw new Error(`Bucket ${this.bucketName} does not exist. Set createBucketIfNotExists to true to create it automatically.`);throw new Error(`Failed to access bucket ${this.bucketName}: ${e.message}`)}}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}`,s=new Error(i);throw s.cause=e,s}async initializeLock(e){let t=this.getLockKey(e);try{let r={locked:!1},n=await this.s3Client.send(new m({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 E({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 p({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),s=this.cleanETag(r.ETag)||"";return{lockInfo:i,etag:s}}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 m({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 s=`Error updating lock ${e}: ${i.message}`,o=new Error(s);throw o.cause=i,o}}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 s=this.lockDependencies.get(i);if(s)for(let o of s){if(this.heldLocks.has(o))return!0;try{let{lockInfo:a}=await this.getLockInfo(o);a.locked&&a.owner&&a.owner!==i&&r.push(a.owner)}catch(a){console.warn(`Error getting lock info for deadlock detection: ${a}`)}}}return!1}async acquireLock(e,t,r=0){try{await this.ensureBucketExists(),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 s=0;s<this.maxRetries;s++){if(Date.now()-n>i)return!1;try{let{lockInfo:o,etag:a}=await this.getLockInfo(e);if(o.locked&&o.owner===this.ownerId)return this.heldLocks.set(e,a),this.lockRequests.delete(e),!0;o.locked&&o.owner&&this.registerLockDependency(this.ownerId,e,o.owner);let c=Date.now(),l=!o.locked||o.expiresAt!==void 0&&o.expiresAt+this.clockSkewToleranceMs<c,d=o.locked&&o.priority!==void 0&&r>o.priority&&await this.isPotentialDeadlock(e,o.owner);if(l||d){let h={locked:!0,owner:this.ownerId,acquiredAt:c,expiresAt:c+this.lockTimeoutMs,priority:r},u=await this.updateLockInfo(e,h,a);if(u){this.heldLocks.set(e,u),this.lockRequests.delete(e);let f=this.lockDependencies.get(this.ownerId);return f&&(f.delete(e),f.size===0&&this.lockDependencies.delete(this.ownerId)),!0}}await this.exponentialBackoff(s)}catch(o){let a=o;a.$metadata?.httpStatusCode===503?console.warn(`S3 service unavailable while acquiring lock ${e}. Retrying...`):a.name==="ThrottlingException"?console.warn(`AWS request throttling encountered while acquiring lock ${e}. Retrying...`):a.$metadata?.httpStatusCode===404?await this.initializeLock(e):console.warn(`Error acquiring lock ${e}: ${a.message}. Retrying...`),await this.exponentialBackoff(s)}}return!1}catch(n){return console.error(`Critical error acquiring lock ${e}:`,n),!1}finally{if(!this.heldLocks.has(e)){this.lockRequests.delete(e);let n=this.lockDependencies.get(this.ownerId);n&&(n.delete(e),n.size===0&&this.lockDependencies.delete(this.ownerId))}}}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},s=await this.updateLockInfo(e,i,n);this.heldLocks.delete(e),this.lockRequests.delete(e);for(let[o,a]of this.lockDependencies.entries())a.delete(e),a.size===0&&this.lockDependencies.delete(o);return!!s}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 s=0;s<i;s++){if(await this.acquireLock(e,n)){let a=null,c=()=>{a&&(clearInterval(a),a=null)};try{let l=Math.max(this.lockTimeoutMs/3,1e3);a=setInterval(async()=>{if(a)try{await this.refreshLock(e)||(console.warn(`Failed to refresh lock ${e}, stopping heartbeat`),c())}catch(h){console.warn(`Error refreshing lock ${e}: ${h}`),c()}},l);let d=await t();return c(),d}catch(l){throw console.error(`Error in function executed with lock ${e}:`,l),c(),await(async(h=5e3)=>{let u=this.releaseLock(e),f=new Promise((k,$)=>setTimeout(()=>$(new Error("Release timeout")),h));try{await Promise.race([u,f])}catch(k){console.warn(`Failed to release lock ${e}: ${k}`)}})(),l}finally{c();try{await this.releaseLock(e)}catch(l){console.warn(`Failed to release lock ${e} in finally block: ${l}`)}}}await this.exponentialBackoff(s)}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 g({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={}){await this.ensureBucketExists();let t=e.prefix||this.keyPrefix,r=e.olderThan||Date.now()-this.lockTimeoutMs,n=e.dryRun,i=0,s=0,o=0,a;try{do{let c=await this.s3Client.send(new x({Bucket:this.bucketName,Prefix:t,ContinuationToken:a}));if(a=c.NextContinuationToken,!!c.Contents){for(let l of c.Contents)if(l.Key){s++;try{let d=await this.s3Client.send(new p({Bucket:this.bucketName,Key:l.Key}));if(!d.Body)continue;let h=await y(d.Body),u=JSON.parse(h),f=Date.now();u.locked&&(u.acquiredAt!==void 0&&u.acquiredAt<r||u.expiresAt!==void 0&&u.expiresAt<f)&&(o++,n||(await this.s3Client.send(new g({Bucket:this.bucketName,Key:l.Key})),i++))}catch(d){console.warn(`Error processing lock ${l.Key}: ${d}`)}}}}while(a);return{cleaned:i,total:s,stale:o}}catch(c){return console.error(`Error cleaning up stale locks: ${c}`),{cleaned:i,total:s,stale:o}}}};export{b as S3Mutex,y as streamToString}; //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vc3JjL2luZGV4LnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvLyBzcmMvaW5kZXgudHNcbmltcG9ydCB7XG5cdENyZWF0ZUJ1Y2tldENvbW1hbmQsXG5cdERlbGV0ZU9iamVjdENvbW1hbmQsXG5cdEdldE9iamVjdENvbW1hbmQsXG5cdEhlYWRCdWNrZXRDb21tYW5kLFxuXHRIZWFkT2JqZWN0Q29tbWFuZCxcblx0TGlzdE9iamVjdHNWMkNvbW1hbmQsXG5cdFB1dE9iamVjdENvbW1hbmQsXG5cdFMzQ2xpZW50LFxuXHR0eXBlIFMzQ2xpZW50Q29uZmlnLFxufSBmcm9tIFwiQGF3cy1zZGsvY2xpZW50LXMzXCI7XG5cbi8vIHNyYy91dGlscy50c1xuaW1wb3J0IHR5cGUgeyBSZWFkYWJsZSB9IGZyb20gXCJub2RlOnN0cmVhbVwiO1xuXG4vKipcbiAqIENvbnZlcnRzIGEgcmVhZGFibGUgc3RyZWFtIHRvIGEgc3RyaW5nXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBzdHJlYW1Ub1N0cmluZyhcblx0c3RyZWFtOiBSZWFkYWJsZSB8IFJlYWRhYmxlU3RyZWFtPFVpbnQ4QXJyYXk+IHwgQmxvYixcbik6IFByb21pc2U8c3RyaW5nPiB7XG5cdC8vIEhhbmRsZSBCbG9iXG5cdGlmIChzdHJlYW0gaW5zdGFuY2VvZiBCbG9iKSB7XG5cdFx0cmV0dXJuIGF3YWl0IHN0cmVhbS50ZXh0KCk7XG5cdH1cblxuXHQvLyBIYW5kbGUgUmVhZGFibGVTdHJlYW0gZnJvbSBicm93c2Vyc1xuXHRpZiAoKHN0cmVhbSBhcyBSZWFkYWJsZVN0cmVhbTxVaW50OEFycmF5PikuZ2V0UmVhZGVyKSB7XG5cdFx0Y29uc3QgcmVhZGVyID0gKHN0cmVhbSBhcyBSZWFkYWJsZVN0cmVhbTxVaW50OEFycmF5PikuZ2V0UmVhZGVyKCk7XG5cdFx0Y29uc3QgY2h1bmtzOiBVaW50OEFycmF5W10gPSBbXTtcblxuXHRcdHdoaWxlICh0cnVlKSB7XG5cdFx0XHRjb25zdCB7IGRvbmUsIHZhbHVlIH0gPSBhd2FpdCByZWFkZXIucmVhZCgpO1xuXHRcdFx0aWYgKGRvbmUpIHtcblx0XHRcdFx0YnJlYWs7XG5cdFx0XHR9XG5cdFx0XHRjaHVua3MucHVzaCh2YWx1ZSk7XG5cdFx0fVxuXG5cdFx0Y29uc3QgY29uY2F0ZW5hdGVkID0gbmV3IFVpbnQ4QXJyYXkoXG5cdFx0XHRjaHVua3MucmVkdWNlKChhY2MsIGNodW5rKSA9PiBhY2MgKyBjaHVuay5sZW5ndGgsIDApLFxuXHRcdCk7XG5cdFx0bGV0IHBvc2l0aW9uID0gMDtcblxuXHRcdGZvciAoY29uc3QgY2h1bmsgb2YgY2h1bmtzKSB7XG5cdFx0XHRjb25jYXRlbmF0ZWQuc2V0KGNodW5rLCBwb3NpdGlvbik7XG5cdFx0XHRwb3NpdGlvbiArPSBjaHVuay5sZW5ndGg7XG5cdFx0fVxuXG5cdFx0cmV0dXJuIG5ldyBUZXh0RGVjb2RlcigpLmRlY29kZShjb25jYXRlbmF0ZWQpO1xuXHR9XG5cblx0Ly8gSGFuZGxlIE5vZGUuanMgUmVhZGFibGUgc3RyZWFtXG5cdHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG5cdFx0Y29uc3QgY2h1bmtzOiBCdWZmZXJbXSA9IFtdO1xuXHRcdGNvbnN0IHJlYWRhYmxlU3RyZWFtID0gc3RyZWFtIGFzIFJlYWRhYmxlO1xuXG5cdFx0Y29uc3QgY2xlYW51cCA9ICgpID0+IHtcblx0XHRcdHJlYWRhYmxlU3RyZWFtLnJlbW92ZUFsbExpc3RlbmVycygpO1xuXHRcdFx0aWYgKHR5cGVvZiByZWFkYWJsZVN0cmVhbS5kZXN0cm95ID09PSBcImZ1bmN0aW9uXCIpIHtcblx0XHRcdFx0cmVhZGFibGVTdHJlYW0uZGVzdHJveSgpO1xuXHRcdFx0fVxuXHRcdH07XG5cblx0XHRyZWFkYWJsZVN0cmVhbS5vbihcImRhdGFcIiwgKGNodW5rKSA9PiBjaHVua3MucHVzaChCdWZmZXIuZnJvbShjaHVuaykpKTtcblx0XHRyZWFkYWJsZVN0cmVhbS5vbihcImVycm9yXCIsIChlcnIpID0+IHtcblx0XHRcdGNsZWFudXAoKTtcblx0XHRcdHJlamVjdChlcnIpO1xuXHRcdH0pO1xuXHRcdHJlYWRhYmxlU3RyZWFtLm9uKFwiZW5kXCIsICgpID0+IHtcblx0XHRcdGNsZWFudXAoKTtcblx0XHRcdHJlc29sdmUoQnVmZmVyLmNvbmNhdChjaHVua3MpLnRvU3RyaW5nKFwidXRmOFwiKSk7XG5cdFx0fSk7XG5cdH0pO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIFMzTXV0ZXhPcHRpb25zIHtcblx0LyoqXG5cdCAqIEFXUyBTMyBjbGllbnQgaW5zdGFuY2Vcblx0ICovXG5cdHMzQ2xpZW50PzogUzNDbGllbnQ7XG5cblx0czNDbGllbnRDb25maWc/OiBTM0NsaWVudENvbmZpZztcblxuXHQvKipcblx0ICogUzMgYnVja2V0IG5hbWUgd2hlcmUgbG9ja3Mgd2lsbCBiZSBzdG9yZWRcblx0ICovXG5cdGJ1Y2tldE5hbWU6IHN0cmluZztcblxuXHQvKipcblx0ICogV2hldGhlciB0byBjcmVhdGUgdGhlIGJ1Y2tldCBpZiBpdCBkb2Vzbid0IGV4aXN0XG5cdCAqIEBkZWZhdWx0IGZhbHNlXG5cdCAqL1xuXHRjcmVhdGVCdWNrZXRJZk5vdEV4aXN0cz86IGJvb2xlYW47XG5cblx0LyoqXG5cdCAqIEtleSBwcmVmaXggZm9yIGxvY2sgZmlsZXMgKG9wdGlvbmFsKVxuXHQgKi9cblx0a2V5UHJlZml4Pzogc3RyaW5nO1xuXG5cdC8qKlxuXHQgKiBNYXggbnVtYmVyIG9mIHJldHJpZXMgd2hlbiBhY3F1aXJpbmcgYSBsb2NrXG5cdCAqIEBkZWZhdWx0IDVcblx0ICovXG5cdG1heFJldHJpZXM/OiBudW1iZXI7XG5cblx0LyoqXG5cdCAqIEJhc2UgZGVsYXkgYmV0d2VlbiByZXRyaWVzIGluIG1pbGxpc2Vjb25kcyAodXNlZCBmb3IgZXhwb25lbnRpYWwgYmFja29mZilcblx0ICogQGRlZmF1bHQgMjAwXG5cdCAqL1xuXHRyZXRyeURlbGF5TXM/OiBudW1iZXI7XG5cblx0LyoqXG5cdCAqIE1heGltdW0gZGVsYXkgYmV0d2VlbiByZXRyaWVzIGluIG1pbGxpc2Vjb25kcyAoY2FwcyB0aGUgZXhwb25lbnRpYWwgYmFja29mZilcblx0ICogQGRlZmF1bHQgNTAwMCAoNSBzZWNvbmRzKVxuXHQgKi9cblx0bWF4UmV0cnlEZWxheU1zPzogbnVtYmVyO1xuXG5cdC8qKlxuXHQgKiBXaGV0aGVyIHRvIGFkZCBqaXR0ZXIgdG8gcmV0cnkgZGVsYXlzIHRvIHByZXZlbnQgc3luY2hyb25pemVkIHJldHJpZXNcblx0ICogQGRlZmF1bHQgdHJ1ZVxuXHQgKi9cblx0dXNlSml0dGVyPzogYm9vbGVhbjtcblxuXHQvKipcblx0ICogTG9jayB0aW1lb3V0IGluIG1pbGxpc2Vjb25kcy4gQWZ0ZXIgdGhpcyB0aW1lLCB0aGUgbG9jayBpcyBjb25zaWRlcmVkIHN0YWxlXG5cdCAqIGFuZCBjYW4gYmUgZm9yY2VmdWxseSBhY3F1aXJlZCBieSBhbm90aGVyIHByb2Nlc3MuXG5cdCAqIEBkZWZhdWx0IDYwMDAwICgxIG1pbnV0ZSlcblx0ICovXG5cdGxvY2tUaW1lb3V0TXM/OiBudW1iZXI7XG5cblx0LyoqXG5cdCAqIENsb2NrIHNrZXcgdG9sZXJhbmNlIGluIG1pbGxpc2Vjb25kcy4gVGhpcyB2YWx1ZSBpcyBhZGRlZCB0byBleHBpcmF0aW9uIGNhbGN1bGF0aW9uc1xuXHQgKiB0byBhY2NvdW50IGZvciBkaWZmZXJlbmNlcyBpbiBzeXN0ZW0gY2xvY2tzIGJldHdlZW4gZGlzdHJpYnV0ZWQgcHJvY2Vzc2VzLlxuXHQgKiBAZGVmYXVsdCAxMDAwICgxIHNlY29uZClcblx0ICovXG5cdGNsb2NrU2tld1RvbGVyYW5jZU1zPzogbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIExvY2tJbmZvIHtcblx0bG9ja2VkOiBib29sZWFuO1xuXHRvd25lcj86IHN0cmluZztcblx0YWNxdWlyZWRBdD86IG51bWJlcjtcblx0ZXhwaXJlc0F0PzogbnVtYmVyO1xuXHRwcmlvcml0eT86IG51bWJlcjtcbn1cblxuLy8gVXNlZCBmb3IgZGVhZGxvY2sgcHJldmVudGlvblxuaW50ZXJmYWNlIExvY2tSZXF1ZXN0IHtcblx0bG9ja05hbWU6IHN0cmluZztcblx0cHJpb3JpdHk6IG51bWJlcjtcblx0YWNxdWlyZWRBdDogbnVtYmVyO1xuXHRvd25lcjogc3RyaW5nO1xufVxuXG5leHBvcnQgY2xhc3MgUzNNdXRleCB7XG5cdHByaXZhdGUgczNDbGllbnQ6IFMzQ2xpZW50O1xuXHRwcml2YXRlIGJ1Y2tldE5hbWU6IHN0cmluZztcblx0cHJpdmF0ZSBrZXlQcmVmaXg6IHN0cmluZztcblx0cHJpdmF0ZSBtYXhSZXRyaWVzOiBudW1iZXI7XG5cdHByaXZhdGUgcmV0cnlEZWxheU1zOiBudW1iZXI7XG5cdHByaXZhdGUgbWF4UmV0cnlEZWxheU1zOiBudW1iZXI7XG5cdHByaXZhdGUgdXNlSml0dGVyOiBib29sZWFuO1xuXHRwcml2YXRlIGxvY2tUaW1lb3V0TXM6IG51bWJlcjtcblx0cHJpdmF0ZSBjbG9ja1NrZXdUb2xlcmFuY2VNczogbnVtYmVyO1xuXHRwcml2YXRlIG93bmVySWQ6IHN0cmluZztcblx0cHJpdmF0ZSBjcmVhdGVCdWNrZXRJZk5vdEV4aXN0czogYm9vbGVhbjtcblx0cHJpdmF0ZSBidWNrZXRJbml0aWFsaXplZCA9IGZhbHNlO1xuXHRwcml2YXRlIGhlbGRMb2NrczogTWFwPHN0cmluZywgc3RyaW5nPiA9IG5ldyBNYXAoKTsgLy8gbG9ja05hbWUgLT4gZXRhZ1xuXHRwcml2YXRlIGxvY2tSZXF1ZXN0czogTWFwPHN0cmluZywgTG9ja1JlcXVlc3Q+ID0gbmV3IE1hcCgpOyAvLyBsb2NrTmFtZSAtPiByZXF1ZXN0IGluZm9cblx0Ly8gVHJhY2sgbG9jayBkZXBlbmRlbmNpZXMgZm9yIGRlYWRsb2NrIGRldGVjdGlvbiAtIGtleTogb3duZXIsIHZhbHVlOiBzZXQgb2YgbG9jayBuYW1lcyBvd25lciBpcyB3YWl0aW5nIGZvclxuXHRwcml2YXRlIGxvY2tEZXBlbmRlbmNpZXM6IE1hcDxzdHJpbmcsIFNldDxzdHJpbmc+PiA9IG5ldyBNYXAoKTtcblxuXHRjb25zdHJ1Y3RvcihvcHRpb25zOiBTM011dGV4T3B0aW9ucykge1xuXHRcdHRoaXMuczNDbGllbnQgPVxuXHRcdFx0b3B0aW9ucy5zM0NsaWVudCA/P1xuXHRcdFx0bmV3IFMzQ2xpZW50KHtcblx0XHRcdFx0Zm9yY2VQYXRoU3R5bGU6IHRydWUsXG5cdFx0XHRcdC4uLm9wdGlvbnMuczNDbGllbnRDb25maWcsXG5cdFx0XHR9KTtcblx0XHR0aGlzLmJ1Y2tldE5hbWUgPSBvcHRpb25zLmJ1Y2tldE5hbWU7XG5cdFx0dGhpcy5rZXlQcmVmaXggPSBvcHRpb25zLmtleVByZWZpeCB8fCBcImxvY2tzL1wiO1xuXHRcdHRoaXMubWF4UmV0cmllcyA9IG9wdGlvbnMubWF4UmV0cmllcyB8fCA1O1xuXHRcdHRoaXMucmV0cnlEZWxheU1zID0gb3B0aW9ucy5yZXRyeURlbGF5TXMgfHwgMjAwO1xuXHRcdHRoaXMubWF4UmV0cnlEZWxheU1zID0gb3B0aW9ucy5tYXhSZXRyeURlbGF5TXMgfHwgNTAwMDsgLy8gNSBzZWNvbmRzIGRlZmF1bHQgbWF4XG5cdFx0dGhpcy51c2VKaXR0ZXIgPSBvcHRpb25zLnVzZUppdHRlciAhPT0gdW5kZWZpbmVkID8gb3B0aW9ucy51c2VKaXR0ZXIgOiB0cnVlO1xuXHRcdHRoaXMubG9ja1RpbWVvdXRNcyA9IG9wdGlvbnMubG9ja1RpbWVvdXRNcyB8fCA2MDAwMDsgLy8gMSBtaW51dGUgZGVmYXVsdFxuXHRcdHRoaXMuY2xvY2tTa2V3VG9sZXJhbmNlTXMgPSBvcHRpb25zLmNsb2NrU2tld1RvbGVyYW5jZU1zIHx8IDEwMDA7IC8vIDEgc2Vjb25kIGRlZmF1bHRcblx0XHR0aGlzLmNyZWF0ZUJ1Y2tldElmTm90RXhpc3RzID0gb3B0aW9ucy5jcmVhdGVCdWNrZXRJZk5vdEV4aXN0cyA/PyBmYWxzZTtcblx0XHR0aGlzLm93bmVySWQgPSBgJHtwcm9jZXNzLnBpZH0tJHtEYXRlLm5vdygpfS0ke01hdGgucmFuZG9tKCkudG9TdHJpbmcoMzYpLnN1YnN0cmluZygyLCAxNSl9YDtcblx0fVxuXG5cdC8qKlxuXHQgKiBFbnN1cmVzIHRoZSBTMyBidWNrZXQgZXhpc3RzLCBjcmVhdGluZyBpdCBpZiBuZWNlc3NhcnkgYW5kIGNvbmZpZ3VyZWQgdG8gZG8gc29cblx0ICovXG5cdHByaXZhdGUgYXN5bmMgZW5zdXJlQnVja2V0RXhpc3RzKCk6IFByb21pc2U8dm9pZD4ge1xuXHRcdGlmICh0aGlzLmJ1Y2tldEluaXRpYWxpemVkKSB7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXG5cdFx0dHJ5IHtcblx0XHRcdC8vIENoZWNrIGlmIHRoZSBidWNrZXQgZXhpc3RzXG5cdFx0XHRhd2FpdCB0aGlzLnMzQ2xpZW50LnNlbmQoXG5cdFx0XHRcdG5ldyBIZWFkQnVja2V0Q29tbWFuZCh7XG5cdFx0XHRcdFx0QnVja2V0OiB0aGlzLmJ1Y2tldE5hbWUsXG5cdFx0XHRcdH0pLFxuXHRcdFx0KTtcblxuXHRcdFx0dGhpcy5idWNrZXRJbml0aWFsaXplZCA9IHRydWU7XG5cdFx0XHRyZXR1cm47XG5cdFx0fSBjYXRjaCAoZXJyb3IpIHtcblx0XHRcdGNvbnN0IGVyciA9IGVycm9yIGFzIHtcblx0XHRcdFx0JG1ldGFkYXRhPzogeyBodHRwU3RhdHVzQ29kZT86IG51bWJlciB9O1xuXHRcdFx0XHRuYW1lPzogc3RyaW5nO1xuXHRcdFx0fTtcblxuXHRcdFx0Ly8gSWYgYnVja2V0IGRvZXNuJ3QgZXhpc3QgYW5kIHdlJ3JlIGNvbmZpZ3VyZWQgdG8gY3JlYXRlIGl0XG5cdFx0XHRpZiAoXG5cdFx0XHRcdChlcnIuJG1ldGFkYXRhPy5odHRwU3RhdHVzQ29kZSA9PT0gNDA0IHx8XG5cdFx0XHRcdFx0ZXJyLm5hbWUgPT09IFwiTm9TdWNoQnVja2V0XCIpICYmXG5cdFx0XHRcdHRoaXMuY3JlYXRlQnVja2V0SWZOb3RFeGlzdHNcblx0XHRcdCkge1xuXHRcdFx0XHR0cnkge1xuXHRcdFx0XHRcdGF3YWl0IHRoaXMuczNDbGllbnQuc2VuZChcblx0XHRcdFx0XHRcdG5ldyBDcmVhdGVCdWNrZXRDb21tYW5kKHtcblx0XHRcdFx0XHRcdFx0QnVja2V0OiB0aGlzLmJ1Y2tldE5hbWUsXG5cdFx0XHRcdFx0XHR9KSxcblx0XHRcdFx0XHQpO1xuXG5cdFx0XHRcdFx0dGhpcy5idWNrZXRJbml0aWFsaXplZCA9IHRydWU7XG5cdFx0XHRcdFx0cmV0dXJuO1xuXHRcdFx0XHR9IGNhdGNoIChjcmVhdGVFcnJvcikge1xuXHRcdFx0XHRcdGNvbnN0IGNyZWF0ZUVyciA9IGNyZWF0ZUVycm9yIGFzIHtcblx0XHRcdFx0XHRcdCRtZXRhZGF0YT86IHsgaHR0cFN0YXR1c0NvZGU/OiBudW1iZXIgfTtcblx0XHRcdFx0XHRcdG5hbWU/OiBzdHJpbmc7XG5cdFx0XHRcdFx0fTtcblxuXHRcdFx0XHRcdC8vIElmIHRoZSBidWNrZXQgd2FzIGFscmVhZHkgY3JlYXRlZCBieSBzb21lb25lIGVsc2UsIHRoYXQncyBmaW5lXG5cdFx0XHRcdFx0aWYgKFxuXHRcdFx0XHRcdFx0Y3JlYXRlRXJyLiRtZXRhZGF0YT8uaHR0cFN0YXR1c0NvZGUgPT09IDQwOSB8fFxuXHRcdFx0XHRcdFx0Y3JlYXRlRXJyLm5hbWUgPT09IFwiQnVja2V0QWxyZWFkeUV4aXN0c1wiXG5cdFx0XHRcdFx0KSB7XG5cdFx0XHRcdFx0XHR0aGlzLmJ1Y2tldEluaXRpYWxpemVkID0gdHJ1ZTtcblx0XHRcdFx0XHRcdHJldHVybjtcblx0XHRcdFx0XHR9XG5cblx0XHRcdFx0XHR0aHJvdyBuZXcgRXJyb3IoXG5cdFx0XHRcdFx0XHRgRmFpbGVkIHRvIGNyZWF0ZSBidWNrZXQgJHt0aGlzLmJ1Y2tldE5hbWV9OiAkeyhjcmVhdGVFcnJvciBhcyBFcnJvcikubWVzc2FnZX1gLFxuXHRcdFx0XHRcdCk7XG5cdFx0XHRcdH1cblx0XHRcdH0gZWxzZSBpZiAoXG5cdFx0XHRcdGVyci4kbWV0YWRhdGE/Lmh0dHBTdGF0dXNDb2RlID09PSA0MDQgfHxcblx0XHRcdFx0ZXJyLm5hbWUgPT09IFwiTm9TdWNoQnVja2V0XCJcblx0XHRcdCkge1xuXHRcdFx0XHQvLyBCdWNrZXQgZG9lc24ndCBleGlzdCBidXQgd2UncmUgbm90IGNvbmZpZ3VyZWQgdG8gY3JlYXRlIGl0XG5cdFx0XHRcdHRocm93IG5ldyBFcnJvcihcblx0XHRcdFx0XHRgQnVja2V0ICR7dGhpcy5idWNrZXROYW1lfSBkb2VzIG5vdCBleGlzdC4gU2V0IGNyZWF0ZUJ1Y2tldElmTm90RXhpc3RzIHRvIHRydWUgdG8gY3JlYXRlIGl0IGF1dG9tYXRpY2FsbHkuYCxcblx0XHRcdFx0KTtcblx0XHRcdH1cblxuXHRcdFx0Ly8gUmUtdGhyb3cgb3RoZXIgZXJyb3JzIChhY2Nlc3MgZGVuaWVkLCBldGMuKVxuXHRcdFx0dGhyb3cgbmV3IEVycm9yKFxuXHRcdFx0XHRgRmFpbGVkIHRvIGFjY2VzcyBidWNrZXQgJHt0aGlzLmJ1Y2tldE5hbWV9OiAkeyhlcnJvciBhcyBFcnJvcikubWVzc2FnZX1gLFxuXHRcdFx0KTtcblx0XHR9XG5cdH1cblxuXHQvKipcblx0ICogSW1wbGVtZW50cyBleHBvbmVudGlhbCBiYWNrb2ZmIHdpdGggb3B0aW9uYWwgaml0dGVyXG5cdCAqIEBwYXJhbSBhdHRlbXB0IFRoZSBjdXJyZW50IGF0dGVtcHQgbnVtYmVyICgwLWJhc2VkKVxuXHQgKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyBhZnRlciB0aGUgY2FsY3VsYXRlZCBkZWxheVxuXHQgKi9cblx0cHJpdmF0ZSBhc3luYyBleHBvbmVudGlhbEJhY2tvZmYoYXR0ZW1wdDogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG5cdFx0Ly8gQ2FsY3VsYXRlIGJhc2UgZGVsYXkgd2l0aCBleHBvbmVudGlhbCBiYWNrb2ZmXG5cdFx0Y29uc3QgYmFzZURlbGF5ID0gTWF0aC5taW4oXG5cdFx0XHR0aGlzLnJldHJ5RGVsYXlNcyAqIDIgKiogYXR0ZW1wdCxcblx0XHRcdHRoaXMubWF4UmV0cnlEZWxheU1zLFxuXHRcdCk7XG5cblx0XHQvLyBBZGQgaml0dGVyIHRvIHByZXZlbnQgc3luY2hyb25pemVkIHJldHJpZXMgKHVwIHRvIDMwJSB2YXJpYXRpb24pXG5cdFx0Y29uc3Qgaml0dGVyID0gdGhpcy51c2VKaXR0ZXIgPyBNYXRoLnJhbmRvbSgpICogMC4zICogYmFzZURlbGF5IDogMDtcblx0XHRjb25zdCBkZWxheSA9IE1hdGguZmxvb3IoYmFzZURlbGF5ICsgaml0dGVyKTtcblxuXHRcdC8vIFdhaXQgZm9yIHRoZSBjYWxjdWxhdGVkIGRlbGF5XG5cdFx0YXdhaXQgbmV3IFByb21pc2UoKHJlc29sdmUpID0+IHNldFRpbWVvdXQocmVzb2x2ZSwgZGVsYXkpKTtcblx0fVxuXG5cdC8qKlxuXHQgKiBGb3JtYXRzIHRoZSBmdWxsIFMzIGtleSBmb3IgYSBsb2NrXG5cdCAqL1xuXHRwcml2YXRlIGdldExvY2tLZXkobG9ja05hbWU6IHN0cmluZyk6IHN0cmluZyB7XG5cdFx0Ly8gTWFrZSBzdXJlIHRoZSBrZXkgZG9lc24ndCBjb250YWluIGludmFsaWQgY2hhcmFjdGVyc1xuXHRcdGNvbnN0IHNhbml0aXplZExvY2tOYW1lID0gbG9ja05hbWUucmVwbGFjZSgvW15hLXpBLVowLTktX10vZywgXCJfXCIpO1xuXHRcdHJldHVybiBgJHt0aGlzLmtleVByZWZpeH0ke3Nhbml0aXplZExvY2tOYW1lfS5qc29uYDtcblx0fVxuXG5cdC8qKlxuXHQgKiBVdGlsaXR5IGZ1bmN0aW9uIHRvIGNsZWFuIGFuIEVUYWcgYnkgcmVtb3ZpbmcgcXVvdGVzXG5cdCAqL1xuXHRwcml2YXRlIGNsZWFuRVRhZyhldGFnOiBzdHJpbmcgfCB1bmRlZmluZWQpOiBzdHJpbmcgfCB1bmRlZmluZWQge1xuXHRcdHJldHVybiBldGFnPy5yZXBsYWNlKC9cIi9nLCBcIlwiKTtcblx0fVxuXG5cdC8qKlxuXHQgKiBTdGFuZGFyZCBlcnJvciBoYW5kbGluZyBmb3IgUzMgb3BlcmF0aW9uc1xuXHQgKi9cblx0cHJpdmF0ZSBoYW5kbGVTM0Vycm9yKFxuXHRcdGVycm9yOiB1bmtub3duLFxuXHRcdG9wZXJhdGlvbjogc3RyaW5nLFxuXHRcdGxvY2tOYW1lOiBzdHJpbmcsXG5cdCk6IG5ldmVyIHtcblx0XHRjb25zdCBlcnIgPSBlcnJvciBhcyB7XG5cdFx0XHQkbWV0YWRhdGE/OiB7IGh0dHBTdGF0dXNDb2RlPzogbnVtYmVyIH07XG5cdFx0XHRuYW1lPzogc3RyaW5nO1xuXHRcdFx0bWVzc2FnZTogc3RyaW5nO1xuXHRcdH07XG5cblx0XHRpZiAoZXJyLiRtZXRhZGF0YT8uaHR0cFN0YXR1c0NvZGUgPT09IDUwMykge1xuXHRcdFx0dGhyb3cgbmV3IEVycm9yKFxuXHRcdFx0XHRgUzMgc2VydmljZSB1bmF2YWlsYWJsZSB3aGlsZSAke29wZXJhdGlvbn0gbG9jayAke2xvY2tOYW1lfTogJHtlcnIubWVzc2FnZX1gLFxuXHRcdFx0KTtcblx0XHR9XG5cdFx0aWYgKGVyci5uYW1lID09PSBcIlRocm90dGxpbmdFeGNlcHRpb25cIikge1xuXHRcdFx0dGhyb3cgbmV3IEVycm9yKFxuXHRcdFx0XHRgQVdTIHJlcXVlc3QgdGhyb3R0bGluZyBlbmNvdW50ZXJlZCB3aGlsZSAke29wZXJhdGlvbn0gbG9jayAke2xvY2tOYW1lfTogJHtlcnIubWVzc2FnZX1gLFxuXHRcdFx0KTtcblx0XHR9XG5cblx0XHRjb25zdCBlcnJvck1lc3NhZ2UgPSBgRXJyb3IgJHtvcGVyYXRpb259IGxvY2sgJHtsb2NrTmFtZX06ICR7ZXJyLm1lc3NhZ2V9YDtcblx0XHRjb25zdCBuZXdFcnJvciA9IG5ldyBFcnJvcihlcnJvck1lc3NhZ2UpO1xuXHRcdC8vIEB0cy1pZ25vcmUgLSB1c2luZyBjYXVzZSBwcm9wZXJ0eSB3aGljaCBtaWdodCBub3QgYmUgaW4gRXJyb3IgdHlwZSBkZWZpbml0aW9uXG5cdFx0bmV3RXJyb3IuY2F1c2UgPSBlcnJvcjtcblx0XHR0aHJvdyBuZXdFcnJvcjtcblx0fVxuXG5cdC8qKlxuXHQgKiBBdG9taWNhbGx5IGluaXRpYWxpemVzIGEgbG9jayBpZiBpdCBkb2Vzbid0IGV4aXN0XG5cdCAqIFVzZXMgYSBtb3JlIGF0b21pYyBhcHByb2FjaCB0byBpbml0aWFsaXplIHRoZSBsb2NrIGZpbGVcblx0ICovXG5cdHByaXZhdGUgYXN5bmMgaW5pdGlhbGl6ZUxvY2soXG5cdFx0bG9ja05hbWU6IHN0cmluZyxcblx0KTogUHJvbWlzZTx7IGluaXRpYWxpemVkOiBib29sZWFuOyBldGFnPzogc3RyaW5nIH0+IHtcblx0XHRjb25zdCBsb2NrS2V5ID0gdGhpcy5nZXRMb2NrS2V5KGxvY2tOYW1lKTtcblxuXHRcdHRyeSB7XG5cdFx0XHQvLyBUcnkgdG8gZGlyZWN0bHkgY3JlYXRlIHRoZSBsb2NrIHdpdGggYSBjb25kaXRpb24gdGhhdCBpdCBkb2Vzbid0IGV4aXN0XG5cdFx0XHQvLyBVc2luZyBQdXRPYmplY3Qgd2l0aCBjb25kaXRpb25hbCBjaGVja3MgZm9yIGF0b21pY2l0eVxuXHRcdFx0Y29uc3QgaW5pdGlhbExvY2tJbmZvOiBMb2NrSW5mbyA9IHtcblx0XHRcdFx0bG9ja2VkOiBmYWxzZSxcblx0XHRcdH07XG5cblx0XHRcdGNvbnN0IHB1dFJlc3BvbnNlID0gYXdhaXQgdGhpcy5zM0NsaWVudC5zZW5kKFxuXHRcdFx0XHRuZXcgUHV0T2JqZWN0Q29tbWFuZCh7XG5cdFx0XHRcdFx0QnVja2V0OiB0aGlzLmJ1Y2tldE5hbWUsXG5cdFx0XHRcdFx0S2V5OiBsb2NrS2V5LFxuXHRcdFx0XHRcdEJvZHk6IEpTT04uc3RyaW5naWZ5KGluaXRpYWxMb2NrSW5mbyksXG5cdFx0XHRcdFx0Q29udGVudFR5cGU6IFwiYXBwbGljYXRpb24vanNvblwiLFxuXHRcdFx0XHRcdC8vIE9ubHkgY3JlYXRlIGlmIHRoZSBvYmplY3QgZG9lc24ndCBleGlzdCAodGhpcyBpcyBtb3JlIGF0b21pYylcblx0XHRcdFx0XHQvLyBodHRwczovL2RvY3MuYXdzLmFtYXpvbi5jb20vQW1hem9uUzMvbGF0ZXN0L0FQSS9BUElfUHV0T2JqZWN0Lmh0bWxcblx0XHRcdFx0XHRJZk5vbmVNYXRjaDogXCIqXCIsXG5cdFx0XHRcdH0pLFxuXHRcdFx0KTtcblxuXHRcdFx0Ly8gSWYgd2UgZ2V0IGhlcmUsIHRoZSBsb2NrIHdhcyBjcmVhdGVkXG5cdFx0XHRyZXR1cm4geyBpbml0aWFsaXplZDogdHJ1ZSwgZXRhZzogdGhpcy5jbGVhbkVUYWcocHV0UmVzcG9uc2UuRVRhZykgfTtcblx0XHR9IGNhdGNoIChlcnJvcikge1xuXHRcdFx0Ly8gSWYgdGhlIGVycm9yIGlzIGEgNDEyIFByZWNvbmRpdGlvbiBGYWlsZWQsIHRoZSBsb2NrIGFscmVhZHkgZXhpc3RzXG5cdFx0XHRjb25zdCBlcnIgPSBlcnJvciBhcyB7ICRtZXRhZGF0YT86IHsgaHR0cFN0YXR1c0NvZGU/OiBudW1iZXIgfSB9O1xuXHRcdFx0aWYgKGVyci4kbWV0YWRhdGE/Lmh0dHBTdGF0dXNDb2RlID09PSA0MTIpIHtcblx0XHRcdFx0Ly8gR2V0IHRoZSBleGlzdGluZyBsb2NrIGluZm8gdG8gcmV0dXJuIGl0cyBFVGFnXG5cdFx0XHRcdGNvbnN0IGhlYWRSZXNwb25zZSA9IGF3YWl0IHRoaXMuczNDbGllbnQuc2VuZChcblx0XHRcdFx0XHRuZXcgSGVhZE9iamVjdENvbW1hbmQoe1xuXHRcdFx0XHRcdFx0QnVja2V0OiB0aGlzLmJ1Y2tldE5hbWUsXG5cdFx0XHRcdFx0XHRLZXk6IGxvY2tLZXksXG5cdFx0XHRcdFx0fSksXG5cdFx0XHRcdCk7XG5cblx0XHRcdFx0cmV0dXJuIHtcblx0XHRcdFx0XHRpbml0aWFsaXplZDogdHJ1ZSxcblx0XHRcdFx0XHRldGFnOiB0aGlzLmNsZWFuRVRhZyhoZWFkUmVzcG9uc2UuRVRhZyksXG5cdFx0XHRcdH07XG5cdFx0XHR9XG5cblx0XHRcdHRoaXMuaGFuZGxlUzNFcnJvcihlcnJvciwgXCJpbml0aWFsaXppbmdcIiwgbG9ja05hbWUpO1xuXHRcdH1cblx0fVxuXG5cdC8qKlxuXHQgKiBHZXRzIHRoZSBjdXJyZW50IHN0YXRlIG9mIGEgbG9ja1xuXHQgKi9cblx0cHJpdmF0ZSBhc3luYyBnZXRMb2NrSW5mbyhcblx0XHRsb2NrTmFtZTogc3RyaW5nLFxuXHQpOiBQcm9taXNlPHsgbG9ja0luZm86IExvY2tJbmZvOyBldGFnOiBzdHJpbmcgfT4ge1xuXHRcdGNvbnN0IGxvY2tLZXkgPSB0aGlzLmdldExvY2tLZXkobG9ja05hbWUpO1xuXG5cdFx0dHJ5IHtcblx0XHRcdGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgdGhpcy5zM0NsaWVudC5zZW5kKFxuXHRcdFx0XHRuZXcgR2V0T2JqZWN0Q29tbWFuZCh7XG5cdFx0XHRcdFx0QnVja2V0OiB0aGlzLmJ1Y2tldE5hbWUsXG5cdFx0XHRcdFx0S2V5OiBsb2NrS2V5LFxuXHRcdFx0XHR9KSxcblx0XHRcdCk7XG5cblx0XHRcdGlmICghcmVzcG9uc2UuQm9keSkge1xuXHRcdFx0XHR0aHJvdyBuZXcgRXJyb3IoYEZhaWxlZCB0byBnZXQgbG9jayBpbmZvIGZvciAke2xvY2tOYW1lfWApO1xuXHRcdFx0fVxuXG5cdFx0XHRpZiAoIXJlc3BvbnNlLkVUYWcpIHtcblx0XHRcdFx0dGhyb3cgbmV3IEVycm9yKGBObyBFVGFnIGZvdW5kIGZvciBsb2NrICR7bG9ja05hbWV9YCk7XG5cdFx0XHR9XG5cblx0XHRcdGNvbnN0IGJvZHkgPSBhd2FpdCBzdHJlYW1Ub1N0cmluZyhyZXNwb25zZS5Cb2R5KTtcblx0XHRcdGNvbnN0IGxvY2tJbmZvID0gSlNPTi5wYXJzZShib2R5KSBhcyBMb2NrSW5mbztcblx0XHRcdGNvbnN0IGV0YWcgPSB0aGlzLmNsZWFuRVRhZyhyZXNwb25zZS5FVGFnKSB8fCBcIlwiO1xuXG5cdFx0XHRyZXR1cm4geyBsb2NrSW5mbywgZXRhZyB9O1xuXHRcdH0gY2F0Y2ggKGVycm9yKSB7XG5cdFx0XHRjb25zdCBlcnIgPSBlcnJvciBhcyB7ICRtZXRhZGF0YT86IHsgaHR0cFN0YXR1c0NvZGU/OiBudW1iZXIgfSB9O1xuXHRcdFx0Ly8gRW5oYW5jZWQgZXJyb3IgaGFuZGxpbmcgZm9yIGdldExvY2tJbmZvXG5cdFx0XHRpZiAoZXJyLiRtZXRhZGF0YT8uaHR0cFN0YXR1c0NvZGUgPT09IDQwNCkge1xuXHRcdFx0XHQvLyBDcmVhdGUgYSBuZXcgZXJyb3IgYnV0IHByZXNlcnZlIHRoZSBvcmlnaW5hbCBtZXRhZGF0YSBmb3IgY2hlY2tpbmcgaW4gaXNMb2NrZWQvaXNPd25lZEJ5VXNcblx0XHRcdFx0Y29uc3Qgbm90Rm91bmRFcnJvciA9IG5ldyBFcnJvcihcblx0XHRcdFx0XHRgTG9jayBmaWxlICR7bG9ja05hbWV9IG5vdCBmb3VuZGAsXG5cdFx0XHRcdCkgYXMgRXJyb3IgJiB7ICRtZXRhZGF0YT86IHsgaHR0cFN0YXR1c0NvZGU/OiBudW1iZXIgfSB9O1xuXHRcdFx0XHRub3RGb3VuZEVycm9yLiRtZXRhZGF0YSA9IGVyci4kbWV0YWRhdGE7XG5cdFx0XHRcdHRocm93IG5vdEZvdW5kRXJyb3I7XG5cdFx0XHR9XG5cblx0XHRcdHRoaXMuaGFuZGxlUzNFcnJvcihlcnJvciwgXCJnZXR0aW5nIGluZm8gZm9yXCIsIGxvY2tOYW1lKTtcblx0XHR9XG5cdH1cblxuXHQvKipcblx0ICogVXBkYXRlcyB0aGUgbG9jayBpbmZvIGluIFMzIHdpdGggaW1wcm92ZWQgZXJyb3IgaGFuZGxpbmdcblx0ICovXG5cdHByaXZhdGUgYXN5bmMgdXBkYXRlTG9ja0luZm8oXG5cdFx0bG9ja05hbWU6IHN0cmluZyxcblx0XHRsb2NrSW5mbzogTG9ja0luZm8sXG5cdFx0ZXRhZzogc3RyaW5nLFxuXHQpOiBQcm9taXNlPHN0cmluZyB8IHVuZGVmaW5lZD4ge1xuXHRcdGNvbnN0IGxvY2tLZXkgPSB0aGlzLmdldExvY2tLZXkobG9ja05hbWUpO1xuXG5cdFx0dHJ5IHtcblx0XHRcdGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgdGhpcy5zM0NsaWVudC5zZW5kKFxuXHRcdFx0XHRuZXcgUHV0T2JqZWN0Q29tbWFuZCh7XG5cdFx0XHRcdFx0QnVja2V0OiB0aGlzLmJ1Y2tldE5hbWUsXG5cdFx0XHRcdFx0S2V5OiBsb2NrS2V5LFxuXHRcdFx0XHRcdEJvZHk6IEpTT04uc3RyaW5naWZ5KGxvY2tJbmZvKSxcblx0XHRcdFx0XHRDb250ZW50VHlwZTogXCJhcHBsaWNhdGlvbi9qc29uXCIsXG5cdFx0XHRcdFx0SWZNYXRjaDogZXRhZyxcblx0XHRcdFx0fSksXG5cdFx0XHQpO1xuXG5cdFx0XHRyZXR1cm4gcmVzcG9uc2UuRVRhZz8ucmVwbGFjZSgvXCIvZywgXCJcIik7XG5cdFx0XHQvLyBiaW9tZS1pZ25vcmUgbGludC9zdXNwaWNpb3VzL25vRXhwbGljaXRBbnk6IDxleHBsYW5hdGlvbj5cblx0XHR9IGNhdGNoIChlcnJvcjogYW55KSB7XG5cdFx0XHQvLyBFbmhhbmNlZCBlcnJvciBoYW5kbGluZyB3aXRoIHNwZWNpZmljIGVycm9yIHR5cGVzXG5cblx0XHRcdC8vIElmIHRoZSBlcnJvciBpcyBhIHByZWNvbmRpdGlvbiBmYWlsZWQsIHRoZSBFVGFnIGhhcyBjaGFuZ2VkXG5cdFx0XHRpZiAoZXJyb3IuJG1ldGFkYXRhPy5odHRwU3RhdHVzQ29kZSA9PT0gNDEyKSB7XG5cdFx0XHRcdHJldHVybiB1bmRlZmluZWQ7IC8vIEVUYWcgbWlzbWF0Y2gsIGxvY2sgd2FzIG1vZGlmaWVkXG5cdFx0XHR9XG5cblx0XHRcdC8vIEhhbmRsZSBzcGVjaWZpYyBBV1MgZXJyb3IgdHlwZXNcblx0XHRcdGlmIChlcnJvci4kbWV0YWRhdGE/Lmh0dHBTdGF0dXNDb2RlID09PSA1MDMpIHtcblx0XHRcdFx0dGhyb3cgbmV3IEVycm9yKFxuXHRcdFx0XHRcdGBTMyBzZXJ2aWNlIHVuYXZhaWxhYmxlIHdoaWxlIHVwZGF0aW5nIGxvY2sgJHtsb2NrTmFtZX06ICR7ZXJyb3IubWVzc2FnZX1gLFxuXHRcdFx0XHQpO1xuXHRcdFx0fVxuXHRcdFx0aWYgKGVycm9yLm5hbWUgPT09IFwiVGhyb3R0bGluZ0V4Y2VwdGlvblwiKSB7XG5cdFx0XHRcdHRocm93IG5ldyBFcnJvcihcblx0XHRcdFx0XHRgQVdTIHJlcXVlc3QgdGhyb3R0bGluZyBlbmNvdW50ZXJlZCB3aGlsZSB1cGRhdGluZyBsb2NrICR7bG9ja05hbWV9OiAke2Vycm9yLm1lc3NhZ2V9YCxcblx0XHRcdFx0KTtcblx0XHRcdH1cblx0XHRcdGlmIChlcnJvci4kbWV0YWRhdGE/Lmh0dHBTdGF0dXNDb2RlID09PSA0MDQpIHtcblx0XHRcdFx0dGhyb3cgbmV3IEVycm9yKFxuXHRcdFx0XHRcdGBMb2NrIGZpbGUgJHtsb2NrTmFtZX0gZGlzYXBwZWFyZWQgZHVyaW5nIHVwZGF0ZSBvcGVyYXRpb25gLFxuXHRcdFx0XHQpO1xuXHRcdFx0fVxuXHRcdFx0aWYgKFxuXHRcdFx0XHRlcnJvci5uYW1lID09PSBcIk5ldHdvcmtFcnJvclwiIHx8XG5cdFx0XHRcdGVycm9yLiRtZXRhZGF0YT8uaHR0cFN0YXR1c0NvZGUgPj0gNTAwXG5cdFx0XHQpIHtcblx0XHRcdFx0dGhyb3cgbmV3IEVycm9yKFxuXHRcdFx0XHRcdGBOZXR3b3JrIG9yIHNlcnZlciBlcnJvciB3aGlsZSB1cGRhdGluZyBsb2NrICR7bG9ja05hbWV9OiAke2Vycm9yLm1lc3NhZ2V9YCxcblx0XHRcdFx0KTtcblx0XHRcdH1cblxuXHRcdFx0Ly8gRm9yIHVua25vd24gZXJyb3JzLCBhZGQgY29udGV4dCBhbmQgcmV0aHJvd1xuXHRcdFx0Y29uc3QgZXJyb3JNZXNzYWdlID0gYEVycm9yIHVwZGF0aW5nIGxvY2sgJHtsb2NrTmFtZX06ICR7ZXJyb3IubWVzc2FnZX1gO1xuXHRcdFx0Y29uc3QgbmV3RXJyb3IgPSBuZXcgRXJyb3IoZXJyb3JNZXNzYWdlKTtcblx0XHRcdG5ld0Vycm9yLmNhdXNlID0gZXJyb3I7XG5cdFx0XHR0aHJvdyBuZXdFcnJvcjtcblx0XHR9XG5cdH1cblxuXHQvKipcblx0ICogUmVnaXN0ZXJzIGEgbG9jayBkZXBlbmRlbmN5IHRvIGhlbHAgd2l0aCBkZWFkbG9jayBkZXRlY3Rpb25cblx0ICogQHBhcmFtIHdhaXRpbmdPd25lciBUaGUgb3duZXIgd2FpdGluZyBmb3IgdGhlIGxvY2tcblx0ICogQHBhcmFtIHRhcmdldExvY2tOYW1lIFRoZSBsb2NrIGJlaW5nIHdhaXRlZCBmb3Jcblx0ICogQHBhcmFtIHRhcmdldE93bmVyIFRoZSBvd25lciBjdXJyZW50bHkgaG9sZGluZyB0aGUgbG9ja1xuXHQgKi9cblx0cHJpdmF0ZSByZWdpc3RlckxvY2tEZXBlbmRlbmN5KFxuXHRcdHdhaXRpbmdPd25lcjogc3RyaW5nLFxuXHRcdHRhcmdldExvY2tOYW1lOiBzdHJpbmcsXG5cdFx0dGFyZ2V0T3duZXI6IHN0cmluZyB8IHVuZGVmaW5lZCxcblx0KTogdm9pZCB7XG5cdFx0aWYgKCF0YXJnZXRPd25lcikge1xuXHRcdFx0cmV0dXJuO1xuXHRcdH1cblxuXHRcdC8vIEdldCBvciBjcmVhdGUgdGhlIHNldCBvZiBsb2NrcyB0aGlzIG93bmVyIGlzIHdhaXRpbmcgZm9yXG5cdFx0bGV0IHdhaXRpbmdGb3IgPSB0aGlzLmxvY2tEZXBlbmRlbmNpZXMuZ2V0KHdhaXRpbmdPd25lcik7XG5cdFx0aWYgKCF3YWl0aW5nRm9yKSB7XG5cdFx0XHR3YWl0aW5nRm9yID0gbmV3IFNldDxzdHJpbmc+KCk7XG5cdFx0XHR0aGlzLmxvY2tEZXBlbmRlbmNpZXMuc2V0KHdhaXRpbmdPd25lciwgd2FpdGluZ0Zvcik7XG5cdFx0fVxuXG5cdFx0Ly8gQWRkIHRoZSB0YXJnZXQgbG9jayB0byB0aGUgc2V0XG5cdFx0d2FpdGluZ0Zvci5hZGQodGFyZ2V0TG9ja05hbWUpO1xuXHR9XG5cblx0LyoqXG5cdCAqIENoZWNrcyBpZiB0aGVyZSdzIGEgcG90ZW50aWFsIGRlYWRsb2NrIHNjZW5hcmlvIGJ5IGRldGVjdGluZyBjaXJjdWxhciB3YWl0IGNvbmRpdGlvbnNcblx0ICogQHBhcmFtIGxvY2tOYW1lIFRoZSBsb2NrIHdlJ3JlIHRyeWluZyB0byBhY3F1aXJlXG5cdCAqIEBwYXJhbSBjdXJyZW50T3duZXIgVGhlIGN1cnJlbnQgb3duZXIgb2YgdGhlIGxvY2tcblx0ICogQHJldHVybnMgVHJ1ZSBpZiB0aGVyZSdzIGEgcG90ZW50aWFsIGRlYWRsb2NrXG5cdCAqL1xuXHRwcml2YXRlIGFzeW5jIGlzUG90ZW50aWFsRGVhZGxvY2soXG5cdFx0X2xvY2tOYW1lOiBzdHJpbmcsXG5cdFx0Y3VycmVudE93bmVyPzogc3RyaW5nLFxuXHQpOiBQcm9taXNlPGJvb2xlYW4+IHtcblx0XHRpZiAoIWN1cnJlbnRPd25lcikge1xuXHRcdFx0cmV0dXJuIGZhbHNlO1xuXHRcdH1cblxuXHRcdC8vIElmIHdlJ3JlIG5vdCB3YWl0aW5nIGZvciBhbnkgbG9ja3MsIHRoZXJlIGNhbid0IGJlIGEgY3ljbGVcblx0XHRpZiAodGhpcy5sb2NrUmVxdWVzdHMuc2l6ZSA9PT0gMCkge1xuXHRcdFx0cmV0dXJuIGZhbHNlO1xuXHRcdH1cblxuXHRcdC8vIENoZWNrIGlmIHRoZSBjdXJyZW50IG93bmVyIGlzIHdhaXRpbmcgZm9yIGFueSBsb2NrcyB3ZSBob2xkXG5cdFx0Ly8gVG8gZG8gdGhpcywgd2UgbmVlZCB0byBjaGVjayBmb3IgY3ljbGVzIGluIHRoZSBkZXBlbmRlbmN5IGdyYXBoXG5cblx0XHQvLyBJbml0aWFsaXplIHRoZSBxdWV1ZSB3aXRoIHRoZSBjdXJyZW50IG93bmVyXG5cdFx0Y29uc3QgcXVldWU6IHN0cmluZ1tdID0gW2N1cnJlbnRPd25lcl07XG5cdFx0Y29uc3QgdmlzaXRlZCA9IG5ldyBTZXQ8c3RyaW5nPigpO1xuXG5cdFx0d2hpbGUgKHF1ZXVlLmxlbmd0aCA+IDApIHtcblx0XHRcdGNvbnN0IG93bmVyID0gcXVldWUuc2hpZnQoKTtcblx0XHRcdC8vIEVhcmx5IHJldHVybiBpZiBzb21laG93IHdlIGdldCBhbiB1bmRlZmluZWQgb3duZXIgKHNob3VsZG4ndCBoYXBwZW4sIGJ1dCBiZWluZyBzYWZlKVxuXHRcdFx0aWYgKCFvd25lcikge1xuXHRcdFx0XHRjb250aW51ZTtcblx0XHRcdH1cblxuXHRcdFx0Ly8gU2tpcCBhbHJlYWR5IHZpc2l0ZWQgb3duZXJzXG5cdFx0XHRpZiAodmlzaXRlZC5oYXMob3duZXIpKSB7XG5cdFx0XHRcdGNvbnRpbnVlO1xuXHRcdFx0fVxuXHRcdFx0dmlzaXRlZC5hZGQob3duZXIpO1xuXG5cdFx0XHQvLyBHZXQgdGhlIGxvY2tzIHRoaXMgb3duZXIgaXMgd2FpdGluZyBmb3Jcblx0XHRcdGNvbnN0IHdhaXRpbmdGb3IgPSB0aGlzLmxvY2tEZXBlbmRlbmNpZXMuZ2V0KG93bmVyKTtcblx0XHRcdGlmICghd2FpdGluZ0Zvcikge1xuXHRcdFx0XHRjb250aW51ZTtcblx0XHRcdH1cblxuXHRcdFx0Ly8gQ2hlY2sgZWFjaCBsb2NrIHRoZSBvd25lciBpcyB3YWl0aW5nIGZvclxuXHRcdFx0Zm9yIChjb25zdCB3YWl0TG9ja05hbWUgb2Ygd2FpdGluZ0Zvcikge1xuXHRcdFx0XHQvLyBJZiB0aGUgb3duZXIgaXMgd2FpdGluZyBmb3IgYSBsb2NrIHdlIGhvbGQsIHdlIGhhdmUgYSBjeWNsZVxuXHRcdFx0XHQvLyBTaW1wbHkgY2hlY2sgb3VyIGxvY2FsIGhlbGRMb2NrcyBtYXAgaW5zdGVhZCBvZiBtYWtpbmcgUzMgY2FsbHNcblx0XHRcdFx0aWYgKHRoaXMuaGVsZExvY2tzLmhhcyh3YWl0TG9ja05hbWUpKSB7XG5cdFx0XHRcdFx0Ly8gV2UndmUgZGV0ZWN0ZWQgYSBkZWFkbG9jayBjeWNsZVxuXHRcdFx0XHRcdHJldHVybiB0cnVlO1xuXHRcdFx0XHR9XG5cblx0XHRcdFx0Ly8gRmluZCBvdXQgd2hvIG93bnMgdGhpcyBsb2NrIGFuZCBhZGQgdGhlbSB0byB0aGUgcXVldWVcblx0XHRcdFx0dHJ5IHtcblx0XHRcdFx0XHRjb25zdCB7IGxvY2tJbmZvIH0gPSBhd2FpdCB0aGlzLmdldExvY2tJbmZvKHdhaXRMb2NrTmFtZSk7XG5cdFx0XHRcdFx0aWYgKGxvY2tJbmZvLmxvY2tlZCAmJiBsb2NrSW5mby5vd25lciAmJiBsb2NrSW5mby5vd25lciAhPT0gb3duZXIpIHtcblx0XHRcdFx0XHRcdHF1ZXVlLnB1c2gobG9ja0luZm8ub3duZXIpO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0fSBjYXRjaCAoZXJyb3IpIHtcblx0XHRcdFx0XHQvLyBJZiB3ZSBjYW4ndCBnZXQgbG9jayBpbmZvLCBjb250aW51ZSB3aXRoIHRoZSBuZXh0IGxvY2tcblx0XHRcdFx0XHRjb25zb2xlLndhcm4oXG5cdFx0XHRcdFx0XHRgRXJyb3IgZ2V0dGluZyBsb2NrIGluZm8gZm9yIGRlYWRsb2NrIGRldGVjdGlvbjogJHtlcnJvcn1gLFxuXHRcdFx0XHRcdCk7XG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHR9XG5cblx0XHRyZXR1cm4gZmFsc2U7XG5cdH1cblxuXHQvKipcblx0ICogQXR0ZW1wdHMgdG8gYWNxdWlyZSBhIGxvY2tcblx0ICogQHBhcmFtIGxvY2tOYW1lIE5hbWUgb2YgdGhlIGxvY2sgdG8gYWNxdWlyZVxuXHQgKiBAcGFyYW0gdGltZW91dE1zIE1heGltdW0gdGltZSB0byB3YWl0IGZvciBsb2NrIGFjcXVpc2l0aW9uXG5cdCAqIEBwYXJhbSBwcmlvcml0eSBPcHRpb25hbCBwcmlvcml0eSBmb3IgdGhpcyBsb2NrIHJlcXVlc3QgKGhpZ2hlciB2YWx1ZXMgaGF2ZSBoaWdoZXIgcHJpb3JpdHkpXG5cdCAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHRvIHRydWUgaWYgdGhlIGxvY2sgd2FzIGFjcXVpcmVkLCBmYWxzZSBvdGhlcndpc2Vcblx0ICovXG5cdHB1YmxpYyBhc3luYyBhY3F1aXJlTG9jayhcblx0XHRsb2NrTmFtZTogc3RyaW5nLFxuXHRcdHRpbWVvdXRNcz86IG51bWJlcixcblx0XHRwcmlvcml0eSA9IDAsXG5cdCk6IFByb21pc2U8Ym9vbGVhbj4ge1xuXHRcdHRyeSB7XG5cdFx0XHQvLyBFbnN1cmUgdGhlIGJ1Y2tldCBleGlzdHMgYmVmb3JlIHByb2NlZWRpbmdcblx0XHRcdGF3YWl0IHRoaXMuZW5zdXJlQnVja2V0RXhpc3RzKCk7XG5cblx0XHRcdC8vIFJlZ2lzdGVyIHRoaXMgbG9jayByZXF1ZXN0IGZvciBkZWFkbG9jayBkZXRlY3Rpb25cblx0XHRcdHRoaXMubG9ja1JlcXVlc3RzLnNldChsb2NrTmFtZSwge1xuXHRcdFx0XHRsb2NrTmFtZSxcblx0XHRcdFx0cHJpb3JpdHksXG5cdFx0XHRcdGFjcXVpcmVkQXQ6IERhdGUubm93KCksXG5cdFx0XHRcdG93bmVyOiB0aGlzLm93bmVySWQsXG5cdFx0XHR9KTtcblxuXHRcdFx0Ly8gTWFrZSBzdXJlIHRoZSBsb2NrIGZpbGUgZXhpc3RzICh1c2luZyBhdG9taWMgaW5pdGlhbGl6YXRpb24pXG5cdFx0XHRhd2FpdCB0aGlzLmluaXRpYWxpemVMb2NrKGxvY2tOYW1lKTtcblxuXHRcdFx0Y29uc3Qgc3RhcnRUaW1lID0gRGF0ZS5ub3coKTtcblx0XHRcdGNvbnN0IG1heFdhaXRUaW1lID0gdGltZW91dE1zIHx8IHRoaXMubG9ja1RpbWVvdXRNcztcblxuXHRcdFx0Zm9yIChsZXQgYXR0ZW1wdCA9IDA7IGF0dGVtcHQgPCB0aGlzLm1heFJldHJpZXM7IGF0dGVtcHQrKykge1xuXHRcdFx0XHQvLyBDaGVjayBpZiB3ZSd2ZSBleGNlZWRlZCB0aGUgb3ZlcmFsbCB0aW1lb3V0XG5cdFx0XHRcdGlmIChEYXRlLm5vdygpIC0gc3RhcnRUaW1lID4gbWF4V2FpdFRpbWUpIHtcblx0XHRcdFx0XHRyZXR1cm4gZmFsc2U7XG5cdFx0XHRcdH1cblxuXHRcdFx0XHR0cnkge1xuXHRcdFx0XHRcdC8vIEdldCB0aGUgY3VycmVudCBsb2NrIGluZm9cblx0XHRcdFx0XHRjb25zdCB7IGxvY2tJbmZvLCBldGFnIH0gPSBhd2FpdCB0aGlzLmdldExvY2tJbmZvKGxvY2tOYW1lKTtcblxuXHRcdFx0XHRcdC8vIENoZWNrIGlmIHRoZSBsb2NrIGlzIGFscmVhZHkgaGVsZCBieSB1c1xuXHRcdFx0XHRcdGlmIChsb2NrSW5mby5sb2NrZWQgJiYgbG9ja0luZm8ub3duZXIgPT09IHRoaXMub3duZXJJZCkge1xuXHRcdFx0XHRcdFx0Ly8gV2UgYWxyZWFkeSBvd24gdGhpcyBsb2NrXG5cdFx0XHRcdFx0XHR0aGlzLmhlbGRMb2Nrcy5zZXQobG9ja05hbWUsIGV0YWcpO1xuXHRcdFx0XHRcdFx0dGhpcy5sb2NrUmVxdWVzdHMuZGVsZXRlKGxvY2tOYW1lKTtcblx0XHRcdFx0XHRcdHJldHVybiB0cnVlO1xuXHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdC8vIFJlZ2lzdGVyIHRoaXMgbG9jayBkZXBlbmRlbmN5IGZvciBkZWFkbG9jayBkZXRlY3Rpb25cblx0XHRcdFx0XHRpZiAobG9ja0luZm8ubG9ja2VkICYmIGxvY2tJbmZvLm93bmVyKSB7XG5cdFx0XHRcdFx0XHR0aGlzLnJlZ2lzdGVyTG9ja0RlcGVuZGVuY3kodGhpcy5vd25lcklkLCBsb2NrTmFtZSwgbG9ja0luZm8ub3duZXIpO1xuXHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdC8vIENoZWNrIGlmIHRoZSBsb2NrIGlzIHVubG9ja2VkIG9yIGV4cGlyZWRcblx0XHRcdFx0XHQvLyBJbmNsdWRlIGNsb2NrIHNrZXcgdG9sZXJhbmNlIGluIHRoZSBleHBpcmF0aW9uIGNoZWNrXG5cdFx0XHRcdFx0Y29uc3Qgbm93ID0gRGF0ZS5ub3coKTtcblx0XHRcdFx0XHRjb25zdCBpc0xvY2tGcmVlID1cblx0XHRcdFx0XHRcdCFsb2NrSW5mby5sb2NrZWQgfHxcblx0XHRcdFx0XHRcdChsb2NrSW5mby5leHBpcmVzQXQgIT09IHVuZGVmaW5lZCAmJlxuXHRcdFx0XHRcdFx0XHRsb2NrSW5mby5leHBpcmVzQXQgKyB0aGlzLmNsb2NrU2tld1RvbGVyYW5jZU1zIDwgbm93KTtcblxuXHRcdFx0XHRcdC8vIElmIHRoZSBsb2NrIGlzIGhlbGQgYnV0IHRoZSBjdXJyZW50IHJlcXVlc3QgaGFzIGhpZ2hlciBwcmlvcml0eSxcblx0XHRcdFx0XHQvLyB3ZSdsbCB0cnkgdG8gZm9yY2UtYWNxdWlyZSBpdCBpZiB3ZSBkZXRlY3QgYSBwb3RlbnRpYWwgZGVhZGxvY2tcblx0XHRcdFx0XHRjb25zdCBjYW5Gb3JjZUFjcXVpcmUgPVxuXHRcdFx0XHRcdFx0bG9ja0luZm8ubG9ja2VkICYmXG5cdFx0XHRcdFx0XHRsb2NrSW5mby5wcmlvcml0eSAhPT0gdW5kZWZpbmVkICYmXG5cdFx0XHRcdFx0XHRwcmlvcml0eSA+IGxvY2tJbmZvLnByaW9yaXR5ICYmXG5cdFx0XHRcdFx0XHQoYXdhaXQgdGhpcy5pc1BvdGVudGlhbERlYWRsb2NrKGxvY2tOYW1lLCBsb2NrSW5mby5vd25lcikpO1xuXG5cdFx0XHRcdFx0aWYgKGlzTG9ja0ZyZWUgfHwgY2FuRm9yY2VBY3F1aXJlKSB7XG5cdFx0XHRcdFx0XHQvLyBUcnkgdG8gYWNxdWlyZSB0aGUgbG9ja1xuXHRcdFx0XHRcdFx0Y29uc3QgbmV3TG9ja0luZm86IExvY2tJbmZvID0ge1xuXHRcdFx0XHRcdFx0XHRsb2NrZWQ6IHRydWUsXG5cdFx0XHRcdFx0XHRcdG93bmVyOiB0aGlzLm93bmVySWQsXG5cdFx0XHRcdFx0XHRcdGFjcXVpcmVkQXQ6IG5vdyxcblx0XHRcdFx0XHRcdFx0ZXhwaXJlc0F0OiBub3cgKyB0aGlzLmxvY2tUaW1lb3V0TXMsXG5cdFx0XHRcdFx0XHRcdHByaW9yaXR5OiBwcmlvcml0eSxcblx0XHRcdFx0XHRcdH07XG5cblx0XHRcdFx0XHRcdGNvbnN0IG5ld0V0YWcgPSBhd2FpdCB0aGlzLnVwZGF0ZUxvY2tJbmZvKFxuXHRcdFx0XHRcdFx0XHRsb2NrTmFtZSxcblx0XHRcdFx0XHRcdFx0bmV3TG9ja0luZm8sXG5cdFx0XHRcdFx0XHRcdGV0YWcsXG5cdFx0XHRcdFx0XHQpO1xuXG5cdFx0XHRcdFx0XHRpZiAobmV3RXRhZykge1xuXHRcdFx0XHRcdFx0XHQvLyBXZSBzdWNjZXNzZnVsbHkgYWNxdWlyZWQgdGhlIGxvY2tcblx0XHRcdFx0XHRcdFx0dGhpcy5oZWxkTG9ja3Muc2V0KGxvY2tOYW1lLCBuZXdFdGFnKTtcblx0XHRcdFx0XHRcdFx0dGhpcy5sb2NrUmVxdWVzdHMuZGVsZXRlKGxvY2tOYW1lKTtcblx0XHRcdFx0XHRcdFx0Ly8gQ2xlYW4gdXAgdGhlIGRlcGVuZGVuY3kgYXMgd2Ugbm93IG93biB0aGUgbG9ja1xuXHRcdFx0XHRcdFx0XHRjb25zdCBvdXJEZXBlbmRlbmNpZXMgPSB0aGlzLmxvY2tEZXBlbmRlbmNpZXMuZ2V0KHRoaXMub3duZXJJZCk7XG5cdFx0XHRcdFx0XHRcdGlmIChvdXJEZXBlbmRlbmNpZXMpIHtcblx0XHRcdFx0XHRcdFx0XHRvdXJEZXBlbmRlbmNpZXMuZGVsZXRlKGxvY2tOYW1lKTtcblx0XHRcdFx0XHRcdFx0XHRpZiAob3VyRGVwZW5kZW5jaWVzLnNpemUgPT09IDApIHtcblx0XHRcdFx0XHRcdFx0XHRcdHRoaXMubG9ja0RlcGVuZGVuY2llcy5kZWxldGUodGhpcy5vd25lcklkKTtcblx0XHRcdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHRcdFx0cmV0dXJuIHRydWU7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fVxuXG5cdFx0XHRcdFx0Ly8gV2FpdCB3aXRoIGV4cG9uZW50aWFsIGJhY2tvZmYgYmVmb3JlIHJldHJ5aW5nXG5cdFx0XHRcdFx0YXdhaXQgdGhpcy5leHBvbmVudGlhbEJhY2tvZmYoYXR0ZW1wdCk7XG5cdFx0XHRcdH0gY2F0Y2ggKGVycm9yKSB7XG5cdFx0XHRcdFx0Ly8gRW5oYW5jZWQgZXJyb3IgaGFuZGxpbmdcblx0XHRcdFx0XHRjb25zdCBlcnIgPSBlcnJvciBhcyB7XG5cdFx0XHRcdFx0XHQkbWV0YWRhdGE/OiB7IGh0dHBTdGF0dXNDb2RlPzogbnVtYmVyIH07XG5cdFx0XHRcdFx0XHRuYW1lPzogc3RyaW5nO1xuXHRcdFx0XHRcdFx0bWVzc2FnZTogc3RyaW5nO1xuXHRcdFx0XHRcdH07XG5cdFx0XHRcdFx0aWYgKGVyci4kbWV0YWRhdGE/Lmh0dHBTdGF0dXNDb2RlID09PSA1MDMpIHtcblx0XHRcdFx0XHRcdGNvbnNvbGUud2Fybihcblx0XHRcdFx0XHRcdFx0YFMzIHNlcnZpY2UgdW5hdmFpbGFibGUgd2hpbGUgYWNxdWlyaW5nIGxvY2sgJHtsb2NrTmFtZX0uIFJldHJ5aW5nLi4uYCxcblx0XHRcdFx0XHRcdCk7XG5cdFx0XHRcdFx0fSBlbHNlIGlmIChlcnIubmFtZSA9PT0gXCJUaHJvdHRsaW5nRXhjZXB0aW9uXCIpIHtcblx0XHRcdFx0XHRcdGNvbnNvbGUud2Fybihcblx0XHRcdFx0XHRcdFx0YEFXUyByZXF1ZXN0IHRocm90dGxpbmcgZW5jb3VudGVyZWQgd2hpbGUgYWNxdWlyaW5nIGxvY2sgJHtsb2NrTmFtZX0uIFJldHJ5aW5nLi4uYCxcblx0XHRcdFx0XHRcdCk7XG5cdFx0XHRcdFx0fSBlbHNlIGlmIChlcnIuJG1ldGFkYXRhPy5odHRwU3RhdHVzQ29kZSA9PT0gNDA0KSB7XG5cdFx0XHRcdFx0XHQvLyBMb2NrIGZpbGUgZGlzYXBwZWFyZWQsIHRyeSB0byBpbml0aWFsaXplIGl0IGFnYWluXG5cdFx0XHRcdFx0XHRhd2FpdCB0aGlzLmluaXRpYWxpemVMb2NrKGxvY2tOYW1lKTtcblx0XHRcdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRcdFx0Y29uc29sZS53YXJuKFxuXHRcdFx0XHRcdFx0XHRgRXJyb3IgYWNxdWlyaW5nIGxvY2sgJHtsb2NrTmFtZX06ICR7ZXJyLm1lc3NhZ2V9LiBSZXRyeWluZy4uLmAsXG5cdFx0XHRcdFx0XHQpO1xuXHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdC8vIFVzZSBleHBvbmVudGlhbCBiYWNrb2ZmXG5cdFx0XHRcdFx0YXdhaXQgdGhpcy5leHBvbmVudGlhbEJhY2tvZmYoYXR0ZW1wdCk7XG5cdFx0XHRcdH1cblx0XHRcdH1cblxuXHRcdFx0cmV0dXJuIGZhbHNlO1xuXHRcdH0gY2F0Y2ggKGVycm9yKSB7XG5cdFx0XHQvLyBIYW5kbGUgY3JpdGljYWwgZXJyb3JzIHRoYXQgcHJldmVudCBsb2NrIGFjcXVpc2l0aW9uXG5cdFx0XHRjb25zb2xlLmVycm9yKGBDcml0aWNhbCBlcnJvciBhY3F1aXJpbmcgbG9jayAke2xvY2tOYW1lfTpgLCBlcnJvcik7XG5cdFx0XHRyZXR1cm4gZmFsc2U7XG5cdFx0fSBmaW5hbGx5IHtcblx0XHRcdC8vIEFsd2F5cyBjbGVhbiB1cCBsb2NrIHJlcXVlc3RzIGFuZCBkZXBlbmRlbmNpZXMgaWYgbG9jayBhY3F1aXNpdGlvbiBmYWlsZWRcblx0XHRcdC8vIFRoaXMgcHJldmVudHMgbWVtb3J5IGxlYWtzIGZyb20gYWNjdW11bGF0aW5nIGZhaWxlZCBsb2NrIGF0dGVtcHRzXG5cdFx0XHRpZiAoIXRoaXMuaGVsZExvY2tzLmhhcyhsb2NrTmFtZSkpIHtcblx0XHRcdFx0dGhpcy5sb2NrUmVxdWVzdHMuZGVsZXRlKGxvY2tOYW1lKTtcblxuXHRcdFx0XHQvLyBDbGVhbiB1cCBkZXBlbmRlbmNpZXMgbW9yZSBjYXJlZnVsbHkgLSBvbmx5IHJlbW92ZSB0aGlzIHNwZWNpZmljIGxvY2sgZGVwZW5kZW5jeVxuXHRcdFx0XHRjb25zdCBvdXJEZXBlbmRlbmNpZXMgPSB0aGlzLmxvY2tEZXBlbmRlbmNpZXMuZ2V0KHRoaXMub3duZXJJZCk7XG5cdFx0XHRcdGlmIChvdXJEZXBlbmRlbmNpZXMpIHtcblx0XHRcdFx0XHRvdXJEZXBlbmRlbmNpZXMuZGVsZXRlKGxvY2tOYW1lKTtcblx0XHRcdFx0XHRpZiAob3VyRGVwZW5kZW5jaWVzLnNpemUgPT09IDApIHtcblx0XHRcdFx0XHRcdHRoaXMubG9ja0RlcGVuZGVuY2llcy5kZWxldGUodGhpcy5vd25lcklkKTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHR9XG5cdH1cblxuXHQvKipcblx0ICogUmVmcmVzaGVzIGEgbG9jaydzIGV4cGlyYXRpb24gdGltZVxuXHQgKiBAcGFyYW0gbG9ja05hbWUgTmFtZSBvZiB0aGUgbG9jayB0byByZWZyZXNoXG5cdCAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHRvIHRydWUgaWYgdGhlIGxvY2sgd2FzIHJlZnJlc2hlZCwgZmFsc2Ugb3RoZXJ3aXNlXG5cdCAqL1xuXHRwdWJsaWMgYXN5bmMgcmVmcmVzaExvY2sobG9ja05hbWU6IHN0cmluZyk6IFByb21pc2U8Ym9vbGVhbj4ge1xuXHRcdHRyeSB7XG5cdFx0XHQvLyBHZXQgdGhlIGN1cnJlbnQgbG9jayBpbmZvXG5cdFx0XHRjb25zdCB7IGxvY2tJbmZvLCBldGFnIH0gPSBhd2FpdCB0aGlzLmdldExvY2tJbmZvKGxvY2tOYW1lKTtcblxuXHRcdFx0Ly8gQ2hlY2sgaWYgdGhlIGxvY2sgaXMgaGVsZCBieSB1c1xuXHRcdFx0Y29uc3Qgbm93ID0gRGF0ZS5ub3coKTtcblxuXHRcdFx0Ly8gQ2hlY2sgaWYgdGhlIGxvY2sgaXMgaGVsZCBieSB1cyBBTkQgbm90IGV4cGlyZWRcblx0XHRcdGlmIChcblx0XHRcdFx0IWxvY2tJbmZvLmxvY2tlZCB8fFxuXHRcdFx0XHRsb2NrSW5mby5vd25lciAhPT0gdGhpcy5vd25lcklkIHx8XG5cdFx0XHRcdChsb2NrSW5mby5leHBpcmVzQXQgIT09IHVuZGVmaW5lZCAmJiBsb2NrSW5mby5leHBpcmVzQXQgPCBub3cpXG5cdFx0XHQpIHtcblx0XHRcdFx0cmV0dXJuIGZhbHNlO1xuXHRcdFx0fVxuXG5cdFx0XHQvLyBVcGRhdGUgdGhlIGV4cGlyYXRpb24gdGltZVxuXHRcdFx0bG9ja0luZm8uZXhwaXJlc0F0ID0gbm93ICsgdGhpcy5sb2NrVGltZW91dE1zO1xuXG5cdFx0XHRjb25zdCBuZXdFdGFnID0gYXdhaXQgdGhpcy51cGRhdGVMb2NrSW5mbyhsb2NrTmFtZSwgbG9ja0luZm8sIGV0YWcpO1xuXG5cdFx0XHRyZXR1cm4gISFuZXdFdGFnO1xuXHRcdFx0Ly8gYmlvbWUtaWdub3JlIGxpbnQvc3VzcGljaW91cy9ub0V4cGxpY2l0QW55OiA8ZXhwbGFuYXRpb24+XG5cdFx0fSBjYXRjaCAoZXJyb3I6IGFueSkge1xuXHRcdFx0Ly8gRW5oYW5jZWQgZXJyb3IgaGFuZGxpbmdcblx0XHRcdGlmIChlcnJvci4kbWV0YWRhdGE/Lmh0dHBTdGF0dXNDb2RlID09PSA0MDQpIHtcblx0XHRcdFx0Y29uc29sZS53YXJuKGBMb2NrIGZpbGUgJHtsb2NrTmFtZX0gbm90IGZvdW5kIGR1cmluZyByZWZyZXNoYCk7XG5cdFx0XHR9IGVsc2UgaWYgKGVycm9yLiRtZXRhZGF0YT8uaHR0cFN0YXR1c0NvZGUgPT09IDUwMykge1xuXHRcdFx0XHRjb25zb2xlLndhcm4oXG5cdFx0XHRcdFx0YFMzIHNlcnZpY2UgdW5hdmFpbGFibGUgd2hpbGUgcmVmcmVzaGluZyBsb2NrICR7bG9ja05hbWV9YCxcblx0XHRcdFx0KTtcblx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdGNvbnNvbGUud2FybihgRXJyb3IgcmVmcmVzaGluZyBsb2NrICR7bG9ja05hbWV9OiAke2Vycm9yLm1lc3NhZ2V9YCk7XG5cdFx0XHR9XG5cblx0XHRcdHJldHVybiBmYWxzZTtcblx0XHR9XG5cdH1cblxuXHQvKipcblx0ICogUmVsZWFzZXMgYSBsb2NrXG5cdCAqIEBwYXJhbSBsb2NrTmFtZSBOYW1lIG9mIHRoZSBsb2NrIHRvIHJlbGVhc2Vcblx0ICogQHBhcmFtIGZvcmNlIElmIHRydWUsIHJlbGVhc2UgdGhlIGxvY2sgZXZlbiBpZiBpdCdzIG5vdCBvd25lZCBieSB1c1xuXHQgKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB0byB0cnVlIGlmIHRoZSBsb2NrIHdhcyByZWxlYXNlZCwgZmFsc2Ugb3RoZXJ3aXNlXG5cdCAqL1xuXHRwdWJsaWMgYXN5bmMgcmVsZWFzZUxvY2sobG9ja05hbWU6IHN0cmluZywgZm9yY2UgPSBmYWxzZSk6IFByb21pc2U8Ym9vbGVhbj4ge1xuXHRcdHRyeSB7XG5cdFx0XHQvLyBHZXQgdGhlIGN1cnJlbnQgbG9jayBpbmZvXG5cdFx0XHRjb25zdCB7IGxvY2tJbmZvLCBldGFnIH0gPSBhd2FpdCB0aGlzLmdldExvY2tJbmZvKGxvY2tOYW1lKTtcblxuXHRcdFx0Ly8gQ2hlY2sgaWYgdGhlIGxvY2sgaXMgaGVsZCBieSB1cyBvciBpZiB3ZSdyZSBmb3JjaW5nXG5cdFx0XHRpZiAobG9ja0luZm8ubG9ja2VkICYmICFmb3JjZSAmJiBsb2NrSW5mby5vd25lciAhPT0gdGhpcy5vd25lcklkKSB7XG5cdFx0XHRcdHJldHVybiBmYWxzZTtcblx0XHRcdH1cblxuXHRcdFx0Ly8gUmVsZWFzZSB0aGUgbG9ja1xuXHRcdFx0Y29uc3QgbmV3TG9ja0luZm86IExvY2tJbmZvID0ge1xuXHRcdFx0XHRsb2NrZWQ6IGZhbHNlLFxuXHRcdFx0fTtcblxuXHRcdFx0Y29uc3QgbmV3RXRhZyA9IGF3YWl0IHRoaXMudXBkYXRlTG9ja0luZm8obG9ja05hbWUsIG5ld0xvY2tJbmZvLCBldGFnKTtcblxuXHRcdFx0Ly8gUmVtb3ZlIGZyb20gb3VyIGhlbGQgbG9ja3Ncblx0XHRcdHRoaXMuaGVsZExvY2tzLmRlbGV0ZShsb2NrTmFtZSk7XG5cdFx0XHR0aGlzLmxvY2tSZXF1ZXN0cy5kZWxldGUobG9ja05hbWUpO1xuXG5cdFx0XHQvLyBDbGVhbiB1cCBhbnkgZGVwZW5kZW5jaWVzIHJlbGF0ZWQgdG8gdGhpcyBsb2NrXG5cdFx0XHQvLyBGb3IgYWxsIG93bmVycyB3YWl0aW5nIGZvciB0aGlzIGxvY2ssIHJlbW92ZSBpdCBmcm9tIHRoZWlyIHdhaXRpbmcgc2V0XG5cdFx0XHRmb3IgKGNvbnN0IFtvd25lciwgd2FpdGluZ0Zvcl0gb2YgdGhpcy5sb2NrRGVwZW5kZW5jaWVzLmVudHJpZXMoKSkge1xuXHRcdFx0XHR3YWl0aW5nRm9yLmRlbGV0ZShsb2NrTmFtZSk7XG5cdFx0XHRcdGlmICh3YWl0aW5nRm9yLnNpemUgPT09IDApIHtcblx0XHRcdFx0XHR0aGlzLmxvY2tEZXBlbmRlbmNpZXMuZGVsZXRlKG93bmVyKTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXG5cdFx0XHRyZXR1cm4gISFuZXdFdGFnO1xuXHRcdFx0Ly8gYmlvbWUtaWdub3JlIGxpbnQvc3VzcGljaW91cy9ub0V4cGxpY2l0QW55OiA8ZXhwbGFuYXRpb24+XG5cdFx0fSBjYXRjaCAoZXJyb3I6IGFueSkge1xuXHRcdFx0Ly8gRW5oYW5jZWQgZXJyb3IgaGFuZGxpbmdcblx0XHRcdGlmIChlcnJvci4kbWV0YWRhdGE/Lmh0dHBTdGF0dXNDb2RlID09PSA0MDQpIHtcblx0XHRcdFx0Y29uc29sZS53YXJuKGBMb2NrIGZpbGUgJHtsb2NrTmFtZX0gbm90IGZvdW5kIGR1cmluZyByZWxlYXNlYCk7XG5cdFx0XHR9IGVsc2UgaWYgKGVycm9yLiRtZXRhZGF0YT8uaHR0cFN0YXR1c0NvZGUgPT09IDUwMykge1xuXHRcdFx0XHRjb25zb2xlLndhcm4oYFMzIHNlcnZpY2UgdW5hdmFpbGFibGUgd2hpbGUgcmVsZWFzaW5nIGxvY2sgJHtsb2NrTmFtZX1gKTtcblx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdGNvbnNvbGUud2FybihgRXJyb3IgcmVsZWFzaW5nIGxvY2sgJHtsb2NrTmFtZX06ICR7ZXJyb3IubWVzc2FnZX1gKTtcblx0XHRcdH1cblxuXHRcdFx0cmV0dXJuIGZhbHNlO1xuXHRcdH1cblx0fVxuXG5cdC8qKlxuXHQgKiBFeGVjdXRlcyBhIGZ1bmN0aW9uIHdoaWxlIGhvbGRpbmcgYSBsb2NrXG5cdCAqIEBwYXJhbSBsb2NrTmFtZSBOYW1lIG9mIHRoZSBsb2NrIHRvIGFjcXVpcmVcblx0ICogQHBhcmFtIGZuIEZ1bmN0aW9uIHRvIGV4ZWN1dGUgd2hpbGUgaG9sZGluZyB0aGUgbG9ja1xuXHQgKiBAcGFyYW0gb3B0aW9ucyBPcHRpb25zIGZvciBsb2NrIGFjcXVpc2l0aW9uXG5cdCAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHRvIHRoZSByZXN1bHQgb2YgdGhlIGZ1bmN0aW9uIG9yIG51bGwgaWYgdGhlIGxvY2sgY291bGRuJ3QgYmUgYWNxdWlyZWRcblx0ICovXG5cdHB1YmxpYyBhc3luYyB3aXRoTG9jazxUPihcblx0XHRsb2NrTmFtZTogc3RyaW5nLFxuXHRcdGZuOiAoKSA9PiBQcm9taXNlPFQ+LFxuXHRcdG9wdGlvbnM6IHsgdGltZW91dE1zPzogbnVtYmVyOyByZXRyaWVzPzogbnVtYmVyIH0gPSB7fSxcblx0KTogUHJvbWlzZTxUIHwgbnVsbD4ge1xuXHRcdGNvbnN0IGFjcXVpcmVUaW1lb3V0ID0gb3B0aW9ucy50aW1lb3V0TXMgfHwgdGhpcy5sb2NrVGltZW91dE1zO1xuXHRcdGNvbnN0IG1heFJldHJpZXMgPSBvcHRpb25zLnJldHJpZXMgfHwgdGhpcy5tYXhSZXRyaWVzO1xuXG5cdFx0Ly8gVHJ5IHRvIGFjcXVpcmUgdGhlIGxvY2tcblx0XHRmb3IgKGxldCBpID0gMDsgaSA8IG1heFJldHJpZXM7IGkrKykge1xuXHRcdFx0Y29uc3QgYWNxdWlyZWQgPSBhd2FpdCB0aGlzLmFjcXVpcmVMb2NrKGxvY2tOYW1lLCBhY3F1aXJlVGltZW91dCk7XG5cblx0XHRcdGlmIChhY3F1aXJlZCkge1xuXHRcdFx0XHQvLyBTZXR1cCBmb3IgaGVhcnRiZWF0IHdpdGggYXRvbWljIGNsZWFudXAgZnVuY3Rpb25cblx0XHRcdFx0bGV0IGhlYXJ0YmVhdEludGVydmFsOiBOb2RlSlMuVGltZW91dCB8IG51bGwgPSBudWxsO1xuXG5cdFx0XHRcdGNvbnN0IHN0b3BIZWFydGJlYXQgPSAoKSA9PiB7XG5cdFx0XHRcdFx0aWYgKGhlYXJ0YmVhdEludGVydmFsKSB7XG5cdFx0XHRcdFx0XHRjbGVhckludGVydmFsKGhlYXJ0YmVhdEludGVydmFsKTtcblx0XHRcdFx0XHRcdGhlYXJ0YmVhdEludGVydmFsID0gbnVsbDtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdH07XG5cblx0XHRcdFx0dHJ5IHtcblx0XHRcdFx0XHQvLyBTZXQgdXAgYSB0aW1lciB0byByZWZyZXNoIHRoZSBsb2NrIHBlcmlvZGljYWxseVxuXHRcdFx0XHRcdGNvbnN0IHJlZnJlc2hJbnRlcnZhbCA9IE1hdGgubWF4KHRoaXMubG9ja1RpbWVvdXRNcyAvIDMsIDEwMDApO1xuXG5cdFx0XHRcdFx0aGVhcnRiZWF0SW50ZXJ2YWwgPSBzZXRJbnRlcnZhbChhc3luYyAoKSA9PiB7XG5cdFx0XHRcdFx0XHQvLyBDaGVjayBpZiBpbnRlcnZhbCBpcyBzdGlsbCB2YWxpZCAocHJldmVudHMgcmFjZSBjb25kaXRpb25zKVxuXHRcdFx0XHRcdFx0aWYgKCFoZWFydGJlYXRJbnRlcnZhbCkge1xuXHRcdFx0XHRcdFx0XHRyZXR1cm47XG5cdFx0XHRcdFx0XHR9XG5cblx0XHRcdFx0XHRcdHRyeSB7XG5cdFx0XHRcdFx0XHRcdGNvbnN0IHJlZnJlc2hlZCA9IGF3YWl0IHRoaXMucmVmcmVzaExvY2sobG9ja05hbWUpO1xuXHRcdFx0XHRcdFx0XHQvLyBJZiByZWZyZXNoIGZhaWxzLCBzdG9wIHRoZSBoZWFydGJlYXQgdG8gcHJldmVudCBmdXJ0aGVyIGF0dGVtcHRzXG5cdFx0XHRcdFx0XHRcdGlmICghcmVmcmVzaGVkKSB7XG5cdFx0XHRcdFx0XHRcdFx0Y29uc29sZS53YXJuKFxuXHRcdFx0XHRcdFx0XHRcdFx0YEZhaWxlZCB0byByZWZyZXNoIGxvY2sgJHtsb2NrTmFtZX0sIHN0b3BwaW5nIGhlYXJ0YmVhdGAsXG5cdFx0XHRcdFx0XHRcdFx0KTtcblx0XHRcdFx0XHRcdFx0XHRzdG9wSGVhcnRiZWF0KCk7XG5cdFx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHRcdH0gY2F0Y2ggKGVycm9yKSB7XG5cdFx0XHRcdFx0XHRcdGNvbnNvbGUud2FybihgRXJyb3IgcmVmcmVzaGluZyBsb2NrICR7bG9ja05hbWV9OiAke2Vycm9yfWApO1xuXHRcdFx0XHRcdFx0XHQvLyBBbHNvIHN0b3Agb24gZXJyb3JzXG5cdFx0XHRcdFx0XHRcdHN0b3BIZWFydGJlYXQoKT