@pipeline-builder/pipeline-events 4.0.10 → 4.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1,968 +1,716 @@
1
- // Copyright 2026 Pipeline Builder Contributors
2
- // SPDX-License-Identifier: Apache-2.0
3
- /**
4
- * SINGLE-FILE BY DEPLOYMENT CONSTRAINT — do not split this module.
5
- *
6
- * `pipeline-manager`'s `setup-events` command deploys this Lambda by copying
7
- * the compiled `lib/index.js` **as one file** into the zip
8
- * (`commands/setup-events.ts`: `copyFileSync(handlerSrc, index.mjs)` → `zip -j`).
9
- * Nothing bundles it. So the moment this file gains a relative import of a
10
- * sibling module, the emitted `lib/index.js` references a file that is not in
11
- * the zip and the Lambda dies at init with "Cannot find module './…'".
12
- *
13
- * That makes the usual hygiene instinct — "973 lines and nine module-level
14
- * caches, split it into scm-resolvers / parse-record / dlq-health" — a
15
- * production outage rather than a cleanup. It is also why `_resetForTests()`
16
- * exists: with every cache in one module, tests have no other seam.
17
- *
18
- * If this genuinely needs splitting, add a bundling step (esbuild → one
19
- * `lib/index.js`) to the build FIRST, and only then break the file up.
20
- */
21
- import { GetSecretValueCommand, SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
22
- let codepipelineMod;
23
- async function loadCodePipeline() {
24
- if (!codepipelineMod)
25
- codepipelineMod = (await import('@aws-sdk/client-codepipeline'));
26
- return codepipelineMod;
27
- }
28
- // Variable-specifier loader for SDK clients that are NOT static devDependencies of
29
- // this package (present only in the Lambda runtime / jest mocks). Passing the
30
- // specifier as a `string` keeps `tsc` from resolving the module at build time.
31
- async function loadSdk(pkg) {
32
- return (await import(pkg));
33
- }
34
- /**
35
- * Pipeline event ingestion Lambda handler.
36
- *
37
- * Receives CodePipeline/CodeBuild events from SQS (sourced by EventBridge),
38
- * parses them into a normalized format, and POSTs them to the reporting service
39
- * via PLATFORM_BASE_URL.
40
- *
41
- * Authentication (#N2 — service-account keys):
42
- * The Lambda holds an opaque SERVICE-ACCOUNT KEY (`pb_sa_…`), never a JWT, and
43
- * trades it at platform's `/auth/token/exchange` for a 5-minute token per batch.
44
- * It verifies NOTHING locally: an opaque key carries no claims to check, and the
45
- * exchange is the authority (a revoked key stops working within one token
46
- * lifetime, which is the entire point of the shape). The key is minted by
47
- * `pipeline-manager infra store-token` and rotated in place by the token-renew
48
- * Lambda, so the secret's value changes underneath a warm container — which is
49
- * why an auth failure re-reads the SECRET, not just the token.
50
- *
51
- * Environment variables:
52
- * - PLATFORM_BASE_URL — Base URL of the platform
53
- * - PLATFORM_ACCESS_KEY — the `pb_sa_…` key set directly (no Secrets Manager call), or
54
- * - PLATFORM_SECRET_NAME — Secrets Manager secret containing { password: <pb_sa_ key> }
55
- * - EVENT_DLQ_ARN — (optional) override for the dead-letter queue ARN used by the
56
- * self-healing redrive; defaults to `<main-queue-arn>-dlq` derived from the SQS
57
- * trigger's eventSourceARN.
58
- */
59
- const log = {
60
- info: (msg, data) => console.log(JSON.stringify({ level: 'INFO', message: msg, data, ts: new Date().toISOString() })),
61
- warn: (msg, data) => console.log(JSON.stringify({ level: 'WARN', message: msg, data, ts: new Date().toISOString() })),
62
- error: (msg, data) => console.error(JSON.stringify({ level: 'ERROR', message: msg, data, ts: new Date().toISOString() })),
1
+ // ../pipeline-core/lib/handlers/platform-credential.js
2
+ import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
3
+ var ACCESS_KEY_PREFIXES = ["pb_sa_", "pb_pat_"];
4
+ function assertAccessKey(value, source) {
5
+ if (ACCESS_KEY_PREFIXES.some((p) => value.startsWith(p)))
6
+ return;
7
+ const looksLikeJwt = value.split(".").length === 3;
8
+ throw new Error(`${source} does not hold an opaque access key (expected a "pb_sa_\u2026" value)` + (looksLikeJwt ? ' \u2014 it holds a JWT, not a service-account key. Re-run "pipeline-manager infra store-token" to issue one.' : "."));
9
+ }
10
+ async function readCredentialSecret(secretName, client = new SecretsManagerClient({})) {
11
+ const response = await client.send(new GetSecretValueCommand({ SecretId: secretName }));
12
+ if (!response.SecretString)
13
+ throw new Error(`Secret "${secretName}" is empty`);
14
+ return JSON.parse(response.SecretString);
15
+ }
16
+ async function readAccessKeyFromSecret(secretName) {
17
+ const secret = await readCredentialSecret(secretName);
18
+ const key = typeof secret.password === "string" ? secret.password : "";
19
+ if (!key) {
20
+ throw new Error(`Secret "${secretName}" missing password \u2014 run "pipeline-manager infra store-token" to provision a service-account key`);
21
+ }
22
+ assertAccessKey(key, `secret "${secretName}"`);
23
+ return key;
24
+ }
25
+ function createPlatformCredential(opts) {
26
+ let cached = null;
27
+ let fromEnv = false;
28
+ return {
29
+ async getKey() {
30
+ if (cached)
31
+ return cached;
32
+ const direct = opts.envKeyVar ? process.env[opts.envKeyVar] : void 0;
33
+ if (direct) {
34
+ assertAccessKey(direct, opts.envKeyVar);
35
+ cached = direct;
36
+ fromEnv = true;
37
+ return cached;
38
+ }
39
+ const secretName = typeof opts.secretName === "function" ? opts.secretName() : opts.secretName;
40
+ if (!secretName) {
41
+ throw new Error(`${opts.envKeyVar ? `${opts.envKeyVar} or ` : ""}PLATFORM_SECRET_NAME environment variable is required`);
42
+ }
43
+ cached = await readAccessKeyFromSecret(secretName);
44
+ fromEnv = false;
45
+ return cached;
46
+ },
47
+ invalidate() {
48
+ if (!fromEnv)
49
+ cached = null;
50
+ },
51
+ reset() {
52
+ cached = null;
53
+ fromEnv = false;
54
+ }
55
+ };
56
+ }
57
+
58
+ // src/util.ts
59
+ var log = {
60
+ info: (msg, data) => console.log(JSON.stringify({ level: "INFO", message: msg, data, ts: (/* @__PURE__ */ new Date()).toISOString() })),
61
+ warn: (msg, data) => console.log(JSON.stringify({ level: "WARN", message: msg, data, ts: (/* @__PURE__ */ new Date()).toISOString() })),
62
+ error: (msg, data) => console.error(JSON.stringify({ level: "ERROR", message: msg, data, ts: (/* @__PURE__ */ new Date()).toISOString() }))
63
63
  };
64
- // ── Tag resolution: pb.pipeline-id + pb.deploys ──────────
65
- // Pipelines are tagged at CDK synth with:
66
- // pb.pipeline-id = <platform pipelineId>
67
- // pb.deploys = <stage>:<env> pairs joined by '+', e.g.
68
- // "Deploy-stg:staging+Deploy-prod:production"
69
- // We resolve those tags from the live CodePipeline (region-aware). The ARN/account
70
- // never leave AWS, so there's no masking. Cached per ARN (warm-container) to avoid
71
- // hammering the ListTags API.
72
- //
73
- // Requires the Lambda execution role to allow `codepipeline:ListTagsForResource`.
74
- // AccessDenied is a config error (alert loudly); a missing pb.pipeline-id tag is
75
- // treated as "not yet registered" (skip).
76
- const PIPELINE_ID_TAG = 'pb.pipeline-id';
77
- const DEPLOYS_TAG = 'pb.deploys';
78
- // Canonical per-pipeline org tag (applied at synth by pipeline-core). It is the
79
- // RESOLVED org for every event of this pipeline — used to key the per-org GitHub
80
- // token and to attribute ingest-health. orgId is a platform id, never an AWS
81
- // account id, so it is safe to read/forward.
82
- const ORG_ID_TAG = 'OrgId';
83
- const clientsByRegion = new Map();
84
- // pipelineId=null is a negative cache (resolved-but-untagged); `ts` lets negatives
85
- // expire so a pipeline tagged AFTER its first event becomes resolvable without
86
- // recycling the warm container. Positive results stay cached for the lifetime.
87
- const resolvedByArn = new Map();
88
- const NEG_CACHE_TTL_MS = 5 * 60 * 1000;
89
- /** Cap on commit/ref/environment fields — the ingest contract allows max 255. */
90
- const MAX_ATTR = 255;
91
- /**
92
- * Parse the `pb.deploys` tag value into a stage→environment map. The value is
93
- * `<stage>:<env>` pairs joined by `+` (CodePipeline-tag-safe; no JSON). A stage
94
- * present here is a deploy; its mapped environment is what the forwarder stamps on
95
- * that stage's STAGE/ACTION events (DORA deploy attribution). Malformed pairs are
96
- * skipped defensively.
97
- */
98
- function parseDeploys(value) {
99
- const map = new Map();
100
- if (!value)
101
- return map;
102
- for (const pair of value.split('+')) {
103
- const idx = pair.indexOf(':');
104
- if (idx <= 0)
105
- continue;
106
- const stage = pair.slice(0, idx).trim();
107
- const env = pair.slice(idx + 1).trim();
108
- if (stage && env)
109
- map.set(stage, env.slice(0, MAX_ATTR));
110
- }
111
- return map;
64
+ var BoundedMap = class extends Map {
65
+ constructor(max) {
66
+ super();
67
+ this.max = max;
68
+ }
69
+ max;
70
+ set(key, value) {
71
+ if (super.has(key)) {
72
+ super.delete(key);
73
+ } else if (this.size >= this.max) {
74
+ const oldest = this.keys().next().value;
75
+ if (oldest !== void 0) super.delete(oldest);
76
+ }
77
+ return super.set(key, value);
78
+ }
79
+ };
80
+ var CACHE_MAX_ENTRIES = 5e3;
81
+ async function loadSdk(pkg) {
82
+ return await import(pkg);
83
+ }
84
+
85
+ // src/auth.ts
86
+ var TOKEN_REFRESH_FRACTION = 0.8;
87
+ var MIN_TOKEN_CACHE_MS = 3e4;
88
+ var EXCHANGE_TIMEOUT_MS = 5e3;
89
+ var credential = createPlatformCredential({
90
+ secretName: () => process.env.PLATFORM_SECRET_NAME,
91
+ envKeyVar: "PLATFORM_ACCESS_KEY"
92
+ });
93
+ var cachedToken = null;
94
+ var cachedTokenExpiresAt = 0;
95
+ async function getAuthToken() {
96
+ if (cachedToken && Date.now() < cachedTokenExpiresAt) return cachedToken;
97
+ const baseUrl = (process.env.PLATFORM_BASE_URL || "").replace(/\/+$/, "");
98
+ if (!baseUrl) throw new Error("PLATFORM_BASE_URL environment variable is required");
99
+ const key = await credential.getKey();
100
+ const controller = new AbortController();
101
+ const timer = setTimeout(() => controller.abort(), EXCHANGE_TIMEOUT_MS);
102
+ let res;
103
+ try {
104
+ res = await fetch(`${baseUrl}/api/auth/token/exchange`, {
105
+ method: "POST",
106
+ headers: { "Content-Type": "application/json", "Accept": "application/json" },
107
+ body: JSON.stringify({ key }),
108
+ signal: controller.signal
109
+ });
110
+ } finally {
111
+ clearTimeout(timer);
112
+ }
113
+ if (!res.ok) {
114
+ if (res.status === 401 || res.status === 403) {
115
+ throw new Error(
116
+ `Access-key exchange refused (${res.status}) \u2014 the stored key is unknown, revoked, expired, its service account is disabled, or this address is outside the key\u2019s IP allowlist`
117
+ );
118
+ }
119
+ throw new Error(`Access-key exchange failed: ${res.status}`);
120
+ }
121
+ const body = await res.json().catch(() => ({}));
122
+ const accessToken = body.data?.accessToken;
123
+ if (!accessToken) throw new Error("Access-key exchange returned no access token");
124
+ const ttlMs = (typeof body.data?.expiresIn === "number" && body.data.expiresIn > 0 ? body.data.expiresIn : 300) * 1e3;
125
+ cachedToken = accessToken;
126
+ cachedTokenExpiresAt = Date.now() + Math.max(MIN_TOKEN_CACHE_MS, ttlMs * TOKEN_REFRESH_FRACTION);
127
+ log.info("Exchanged the stored service-account key for a platform token");
128
+ return accessToken;
112
129
  }
113
- async function pipelineClient(region) {
114
- let client = clientsByRegion.get(region);
115
- if (!client) {
116
- const { CodePipelineClient } = await loadCodePipeline();
117
- client = new CodePipelineClient({ region });
118
- clientsByRegion.set(region, client);
119
- }
120
- return client;
121
- }
122
- /**
123
- * Resolve a CodePipeline's tags → the platform pipelineId (pb.pipeline-id) plus the
124
- * deploy-stage→environment map (pb.deploys). `pipelineId` is null when the pipeline
125
- * has no pb.pipeline-id tag (unregistered). Throws on AccessDenied so a missing IAM
126
- * grant surfaces loudly instead of silently dropping every event.
127
- */
128
- async function resolvePipeline(arn, region) {
129
- const cached = resolvedByArn.get(arn);
130
- // Serve a positive hit for the container lifetime; serve a negative hit only
131
- // until it expires (then re-resolve, in case the pipeline was since tagged).
132
- if (cached && (cached.pipelineId !== null || Date.now() - cached.ts < NEG_CACHE_TTL_MS)) {
133
- return { pipelineId: cached.pipelineId, orgId: cached.orgId, deploys: cached.deploys };
134
- }
135
- try {
136
- const client = await pipelineClient(region);
137
- const { ListTagsForResourceCommand } = await loadCodePipeline();
138
- const out = await client.send(new ListTagsForResourceCommand({ resourceArn: arn }));
139
- const pipelineId = out.tags?.find(t => t.key === PIPELINE_ID_TAG)?.value ?? null;
140
- const orgId = out.tags?.find(t => t.key === ORG_ID_TAG)?.value ?? null;
141
- const deploys = parseDeploys(out.tags?.find(t => t.key === DEPLOYS_TAG)?.value);
142
- if (!pipelineId)
143
- log.warn('Pipeline missing pb.pipeline-id tag — skipping (register it?)', { arn });
144
- resolvedByArn.set(arn, { pipelineId, orgId, deploys, ts: Date.now() });
145
- return { pipelineId, orgId, deploys };
146
- }
147
- catch (err) {
148
- const name = err?.name;
149
- if (name === 'AccessDeniedException') {
150
- // Don't cache — this is a fixable misconfig, not a property of the pipeline.
151
- log.error('AccessDenied calling codepipeline:ListTagsForResource — grant it to the Lambda role', { arn, error: name });
152
- throw err;
153
- }
154
- // Transient/infra errors (throttling, timeouts, 5xx) MUST propagate so the SQS
155
- // batch is retried rather than silently dropping the event — an event dropped
156
- // BEFORE it reaches the ingest is lost permanently (the ingest's partial-unique
157
- // index only dedupes events that were actually POSTed). Only swallow when the
158
- // failure is a definitive answer that this ARN has no usable tag.
159
- const e = err;
160
- const status = e.$metadata?.httpStatusCode;
161
- const retryable = e.$retryable != null
162
- || (typeof status === 'number' && status >= 500)
163
- || /throttl|timeout|toomanyrequests|serviceunavailable|econnreset|networkingerror/i.test(name ?? '');
164
- if (retryable) {
165
- log.error('Transient error resolving pb.pipeline-id tag — will retry the batch', { arn, error: name ?? String(err) });
166
- throw err;
167
- }
168
- log.error('Failed to resolve pb.pipeline-id tag', { arn, error: name ?? String(err) });
169
- return { pipelineId: null, orgId: null, deploys: new Map() };
170
- }
130
+ function invalidateCredential() {
131
+ cachedToken = null;
132
+ cachedTokenExpiresAt = 0;
133
+ credential.invalidate();
134
+ }
135
+ function _resetAuthForTests() {
136
+ cachedToken = null;
137
+ cachedTokenExpiresAt = 0;
138
+ credential.reset();
139
+ }
140
+
141
+ // src/health.ts
142
+ var sqsMod;
143
+ var sqsClientsByRegion;
144
+ async function sqsClient(region) {
145
+ if (!sqsMod) sqsMod = await loadSdk("@aws-sdk/client-sqs");
146
+ if (!sqsClientsByRegion) sqsClientsByRegion = /* @__PURE__ */ new Map();
147
+ let client = sqsClientsByRegion.get(region);
148
+ if (!client) {
149
+ client = new sqsMod.SQSClient({ region });
150
+ sqsClientsByRegion.set(region, client);
151
+ }
152
+ return client;
153
+ }
154
+ var forwardedByOrg = new BoundedMap(CACHE_MAX_ENTRIES);
155
+ var lastEventAtByOrg = new BoundedMap(CACHE_MAX_ENTRIES);
156
+ var lastHealthAt = 0;
157
+ var lastRedriveAt = 0;
158
+ var HEALTH_THROTTLE_MS = 60 * 1e3;
159
+ var REDRIVE_THROTTLE_MS = 5 * 60 * 1e3;
160
+ var MAX_DLQ_REDRIVE_AGE_SEC = 6 * 60 * 60;
161
+ function deriveQueues(mainQueueArn) {
162
+ if (!mainQueueArn) return void 0;
163
+ const parts = mainQueueArn.split(":");
164
+ if (parts.length < 6 || parts[2] !== "sqs") return void 0;
165
+ const [, , , region, account, name] = parts;
166
+ const dlqArn = process.env.EVENT_DLQ_ARN || `arn:aws:sqs:${region}:${account}:${name}-dlq`;
167
+ const dlqName = dlqArn.split(":").pop() ?? `${name}-dlq`;
168
+ const dlqUrl = `https://sqs.${region}.amazonaws.com/${account}/${dlqName}`;
169
+ return { region, mainArn: mainQueueArn, dlqArn, dlqUrl };
171
170
  }
172
- // ─── Auth ───────────────────────────────────────────────
173
- //
174
- // The stored credential is an OPAQUE service-account key (`pb_sa_…`), traded at
175
- // platform for a short-lived JWT. Nothing is verified here: an opaque key has no
176
- // claims and no signature, so there is nothing a local check could tell you that
177
- // the exchange does not tell you better — and the exchange re-reads the account,
178
- // its org and the key's own state every time, which is what makes a revocation
179
- // take effect within one token lifetime.
180
- //
181
- // This code is deliberately self-contained (see the file header: the Lambda ships
182
- // as ONE file), so it does not use api-core's exchange client. It keeps the same
183
- // contract: cache the token in the warm container, refresh EARLY (before expiry)
184
- // so a batch never presents a token that dies mid-flight, and fail the batch on
185
- // an exchange failure rather than shipping events unauthenticated.
186
- /** Refresh at this fraction of the token's life — never at the last moment. */
187
- const TOKEN_REFRESH_FRACTION = 0.8;
188
- /** Floor on the cached lifetime, so a surprisingly short TTL can't cause a
189
- * refresh storm (one exchange per batch at worst). */
190
- const MIN_TOKEN_CACHE_MS = 30_000;
191
- /** Per-request timeout for the exchange call. */
192
- const EXCHANGE_TIMEOUT_MS = 5000;
193
- /** Access-key prefixes platform issues. A stored JWT is NOT one of these. */
194
- const ACCESS_KEY_PREFIXES = ['pb_sa_', 'pb_pat_'];
195
- let cachedKey = null;
196
- let cachedToken = null;
197
- let cachedTokenExpiresAt = 0;
198
- /**
199
- * Read (and cache) the service-account key: `PLATFORM_ACCESS_KEY` if set,
200
- * otherwise the `password` field of the Secrets Manager secret named by
201
- * `PLATFORM_SECRET_NAME`.
202
- *
203
- * Fails CLOSED and LOUDLY on a stored JWT. After the #N2 cutover the secret holds
204
- * a key, and a deployment still carrying the old machine-session JWT would
205
- * otherwise fail later as an opaque 401 from the reporting API — three layers
206
- * away from the thing an operator has to fix.
207
- */
208
- async function getAccessKey() {
209
- if (cachedKey)
210
- return cachedKey;
211
- const direct = process.env.PLATFORM_ACCESS_KEY;
212
- if (direct) {
213
- assertAccessKey(direct, 'PLATFORM_ACCESS_KEY');
214
- cachedKey = direct;
215
- log.info('Using PLATFORM_ACCESS_KEY from environment');
216
- return cachedKey;
217
- }
218
- const secretName = process.env.PLATFORM_SECRET_NAME;
219
- if (!secretName) {
220
- throw new Error('PLATFORM_ACCESS_KEY or PLATFORM_SECRET_NAME environment variable is required');
221
- }
222
- log.info(`Fetching access key from Secrets Manager: ${secretName}`);
223
- const client = new SecretsManagerClient({});
224
- const response = await client.send(new GetSecretValueCommand({ SecretId: secretName }));
225
- if (!response.SecretString)
226
- throw new Error(`Secret "${secretName}" is empty`);
227
- const secret = JSON.parse(response.SecretString);
228
- // `password` stays the canonical field (CodeBuild's secretsManagerCredentials
229
- // and the plugin-lookup Lambda read the same one) — only its VALUE changed,
230
- // from a JWT to an opaque key.
231
- const key = secret.password;
232
- if (!key) {
233
- throw new Error(`Secret "${secretName}" missing password — run "pipeline-manager infra store-token" to provision a service-account key`);
171
+ async function dlqStats(region, dlqUrl) {
172
+ if (!sqsMod) sqsMod = await loadSdk("@aws-sdk/client-sqs");
173
+ const client = await sqsClient(region);
174
+ const out = await client.send(new sqsMod.GetQueueAttributesCommand({
175
+ QueueUrl: dlqUrl,
176
+ AttributeNames: ["ApproximateNumberOfMessages", "ApproximateAgeOfOldestMessage"]
177
+ }));
178
+ const n = Number(out.Attributes?.ApproximateNumberOfMessages ?? "0");
179
+ const age = Number(out.Attributes?.ApproximateAgeOfOldestMessage ?? "0");
180
+ return {
181
+ depth: Number.isFinite(n) ? n : 0,
182
+ oldestAgeSec: Number.isFinite(age) ? age : 0
183
+ };
184
+ }
185
+ async function hasActiveMoveTask(region, dlqArn) {
186
+ if (!sqsMod) sqsMod = await loadSdk("@aws-sdk/client-sqs");
187
+ const client = await sqsClient(region);
188
+ const out = await client.send(new sqsMod.ListMessageMoveTasksCommand({ SourceArn: dlqArn }));
189
+ return (out.Results ?? []).some((t) => t.Status === "RUNNING");
190
+ }
191
+ async function startMoveTask(region, dlqArn, mainArn) {
192
+ if (!sqsMod) sqsMod = await loadSdk("@aws-sdk/client-sqs");
193
+ const client = await sqsClient(region);
194
+ await client.send(new sqsMod.StartMessageMoveTaskCommand({ SourceArn: dlqArn, DestinationArn: mainArn }));
195
+ }
196
+ async function postIngestHealth(baseUrl, body) {
197
+ try {
198
+ const token = await getAuthToken();
199
+ const res = await fetch(`${baseUrl}/api/reports/ingest-health`, {
200
+ method: "POST",
201
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
202
+ body: JSON.stringify(body)
203
+ });
204
+ if (!res.ok) {
205
+ log.warn("ingest-health POST returned non-2xx (ignored)", { status: res.status, orgId: body.orgId });
206
+ return false;
234
207
  }
235
- assertAccessKey(key, `secret "${secretName}"`);
236
- cachedKey = key;
237
- log.info('Using stored service-account key from Secrets Manager');
238
- return cachedKey;
208
+ return true;
209
+ } catch (err) {
210
+ log.warn("ingest-health POST failed (ignored)", { error: String(err), orgId: body.orgId });
211
+ return false;
212
+ }
239
213
  }
240
- /** Refuse anything that is not an opaque access key, naming where it came from. */
241
- function assertAccessKey(value, source) {
242
- if (ACCESS_KEY_PREFIXES.some((p) => value.startsWith(p)))
243
- return;
244
- const looksLikeJwt = value.split('.').length === 3;
245
- throw new Error(`${source} does not hold an opaque access key (expected a "pb_sa_…" value)`
246
- + (looksLikeJwt
247
- ? ' — it still holds a JWT from before the service-account cutover. Re-run "pipeline-manager infra store-token" to reissue it as a key.'
248
- : '.'));
249
- }
250
- /**
251
- * Trade the stored key for a short-lived platform token, cached per warm
252
- * container until it is 80% through its life. Throws on any failure, which fails
253
- * the batch so SQS retries it — an unauthenticated ship is never an option.
254
- */
255
- async function getAuthToken() {
256
- if (cachedToken && Date.now() < cachedTokenExpiresAt)
257
- return cachedToken;
258
- const baseUrl = (process.env.PLATFORM_BASE_URL || '').replace(/\/+$/, '');
259
- if (!baseUrl)
260
- throw new Error('PLATFORM_BASE_URL environment variable is required');
261
- const key = await getAccessKey();
262
- const controller = new AbortController();
263
- const timer = setTimeout(() => controller.abort(), EXCHANGE_TIMEOUT_MS);
264
- let res;
265
- try {
266
- res = await fetch(`${baseUrl}/api/auth/token/exchange`, {
267
- method: 'POST',
268
- headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
269
- body: JSON.stringify({ key }),
270
- signal: controller.signal,
214
+ async function reportHealthAndRedrive(baseUrl, mainQueueArn) {
215
+ const now = Date.now();
216
+ if (now - lastHealthAt < HEALTH_THROTTLE_MS) return;
217
+ lastHealthAt = now;
218
+ try {
219
+ const q = deriveQueues(mainQueueArn);
220
+ let depth = 0;
221
+ let oldestAgeSec = 0;
222
+ if (q) {
223
+ try {
224
+ const stats = await dlqStats(q.region, q.dlqUrl);
225
+ depth = stats.depth;
226
+ oldestAgeSec = stats.oldestAgeSec;
227
+ } catch (err) {
228
+ log.warn("DLQ stats check failed (ignored)", { error: err?.name ?? String(err) });
229
+ }
230
+ }
231
+ const orgKeys = /* @__PURE__ */ new Set([...forwardedByOrg.keys(), ...lastEventAtByOrg.keys()]);
232
+ if (orgKeys.size === 0) orgKeys.add("");
233
+ for (const key of orgKeys) {
234
+ const forwarded = forwardedByOrg.get(key) ?? 0;
235
+ const lastEventAt = lastEventAtByOrg.get(key);
236
+ const ok = await postIngestHealth(baseUrl, {
237
+ ...key ? { orgId: key } : {},
238
+ forwarded,
239
+ dropped: depth,
240
+ lastEventAt
241
+ });
242
+ if (ok) forwardedByOrg.set(key, 0);
243
+ }
244
+ if (q && depth > 0 && now - lastRedriveAt >= REDRIVE_THROTTLE_MS) {
245
+ if (oldestAgeSec > MAX_DLQ_REDRIVE_AGE_SEC) {
246
+ log.warn("DLQ oldest message exceeds poison-age ceiling \u2014 skipping redrive (needs manual attention)", {
247
+ dlqArn: q.dlqArn,
248
+ dropped: depth,
249
+ oldestAgeSec,
250
+ ceilingSec: MAX_DLQ_REDRIVE_AGE_SEC
271
251
  });
272
- }
273
- finally {
274
- clearTimeout(timer);
275
- }
276
- if (!res.ok) {
277
- // A rejected key is the one failure an operator has to act on, and platform
278
- // deliberately answers every refusal the same way — so say what to check
279
- // rather than echoing an undifferentiated 401.
280
- if (res.status === 401 || res.status === 403) {
281
- throw new Error(`Access-key exchange refused (${res.status}) — the stored key is unknown, revoked, expired, `
282
- + 'its service account is disabled, or this address is outside the key’s IP allowlist');
252
+ } else {
253
+ try {
254
+ if (!await hasActiveMoveTask(q.region, q.dlqArn)) {
255
+ lastRedriveAt = now;
256
+ await startMoveTask(q.region, q.dlqArn, q.mainArn);
257
+ log.info("Started self-healing DLQ\u2192main redrive", { dlqArn: q.dlqArn, dropped: depth, oldestAgeSec });
258
+ }
259
+ } catch (err) {
260
+ log.warn("DLQ redrive failed (ignored)", { error: err?.name ?? String(err) });
283
261
  }
284
- throw new Error(`Access-key exchange failed: ${res.status}`);
285
- }
286
- const body = await res.json().catch(() => ({}));
287
- const accessToken = body.data?.accessToken;
288
- if (!accessToken)
289
- throw new Error('Access-key exchange returned no access token');
290
- const ttlMs = (typeof body.data?.expiresIn === 'number' && body.data.expiresIn > 0 ? body.data.expiresIn : 300) * 1000;
291
- cachedToken = accessToken;
292
- cachedTokenExpiresAt = Date.now() + Math.max(MIN_TOKEN_CACHE_MS, ttlMs * TOKEN_REFRESH_FRACTION);
293
- log.info('Exchanged the stored service-account key for a platform token');
294
- return accessToken;
295
- }
296
- /**
297
- * Drop everything the container is holding about its credential after a 401/403
298
- * from the API. The token is dropped for the obvious reason; the KEY is dropped
299
- * because the token-renew Lambda rotates the secret underneath us — a warm
300
- * container that only re-exchanged would keep presenting the retired key
301
- * forever. The next call re-reads the secret and picks up the replacement.
302
- */
303
- function invalidateCredential() {
304
- cachedToken = null;
305
- cachedTokenExpiresAt = 0;
306
- // Only the SECRET path can be refreshed; an env-provided key cannot change
307
- // without a new container, so keep it rather than re-reading an env var.
308
- if (!process.env.PLATFORM_ACCESS_KEY)
309
- cachedKey = null;
310
- }
311
- // Per-(pipeline,sha) resolved result — computed once, reused across the many events
312
- // (PIPELINE/STAGE/ACTION) that share a source revision and across redeliveries.
313
- const commitResultCache = new Map();
314
- // Last sha we successfully resolved per pipeline — the exclusive lower bound for the
315
- // next range ("commits since the last deploy"). In-memory only: a cold start resets
316
- // it, so the first post-cold event resolves as a single commit (expected, defensive).
317
- const lastShaByPipeline = new Map();
318
- // Per-sha CodeCommit metadata cache (date + first-parent) for range walks.
319
- const commitMetaCache = new Map();
320
- // SCM rate-limit cooldown: after a 403/429 we stop calling the SCM for a while.
321
- let scmCooldownUntil = 0;
322
- const SCM_COOLDOWN_MS = 2 * 60 * 1000;
323
- // Cap the CodeCommit first-parent walk so a long range can't blow the Lambda timeout.
324
- const MAX_COMMIT_WALK = 50;
325
- // Per-request timeout for external SCM calls (defensive against slow/hung hosts).
326
- const SCM_FETCH_TIMEOUT_MS = 3000;
327
- // The forwarder is a fleet Lambda: one warm container resolves events for MANY
328
- // orgs, so the GitHub token MUST be keyed by the event's resolved org (not a single
329
- // container-global token). Each org's token lives at `<prefix>/<orgId>/github-token`,
330
- // derived from PLATFORM_SECRET_NAME (`<prefix>/<containerOrg>/platform`). null caches
331
- // a definitive miss (no secret / read failed) → unauthenticated best-effort.
332
- const githubTokenByOrg = new Map();
333
- /**
334
- * Derive a specific org's github-token secret name from PLATFORM_SECRET_NAME
335
- * (`<prefix>/<containerOrg>/platform`). With an `orgId` we swap the org segment
336
- * (`<prefix>/<orgId>/github-token`); without one (pipeline carried no OrgId tag) we
337
- * fall back to the container's own sibling secret. Returns undefined when
338
- * PLATFORM_SECRET_NAME isn't the expected `.../platform` shape.
339
- */
262
+ }
263
+ }
264
+ } catch (err) {
265
+ log.warn("health/redrive step failed (ignored)", { error: String(err) });
266
+ }
267
+ }
268
+ function recordForwarded(events) {
269
+ for (const e of events) {
270
+ const key = e.orgId ?? "";
271
+ forwardedByOrg.set(key, (forwardedByOrg.get(key) ?? 0) + 1);
272
+ const at = e.completedAt || e.startedAt;
273
+ if (at) {
274
+ const prev = lastEventAtByOrg.get(key);
275
+ if (!prev || at > prev) lastEventAtByOrg.set(key, at);
276
+ }
277
+ }
278
+ }
279
+ function _resetHealthForTests() {
280
+ forwardedByOrg.clear();
281
+ lastEventAtByOrg.clear();
282
+ lastHealthAt = 0;
283
+ lastRedriveAt = 0;
284
+ }
285
+
286
+ // src/scm.ts
287
+ import { GetSecretValueCommand as GetSecretValueCommand2, SecretsManagerClient as SecretsManagerClient2 } from "@aws-sdk/client-secrets-manager";
288
+ var commitResultCache = new BoundedMap(CACHE_MAX_ENTRIES);
289
+ var lastShaByPipeline = new BoundedMap(CACHE_MAX_ENTRIES);
290
+ var commitMetaCache = new BoundedMap(CACHE_MAX_ENTRIES);
291
+ var scmCooldownUntil = 0;
292
+ var SCM_COOLDOWN_MS = 2 * 60 * 1e3;
293
+ var MAX_COMMIT_WALK = 50;
294
+ var SCM_FETCH_TIMEOUT_MS = 3e3;
295
+ var githubTokenByOrg = new BoundedMap(CACHE_MAX_ENTRIES);
340
296
  function githubTokenSecretName(orgId) {
341
- const name = process.env.PLATFORM_SECRET_NAME;
342
- if (!name || !name.endsWith('/platform'))
343
- return undefined;
344
- const base = name.slice(0, -'/platform'.length); // <prefix>/<containerOrg>
345
- if (orgId) {
346
- const slash = base.lastIndexOf('/');
347
- const prefix = slash > 0 ? base.slice(0, slash) : base;
348
- return `${prefix}/${orgId}/github-token`;
349
- }
350
- return `${base}/github-token`;
297
+ const name = process.env.PLATFORM_SECRET_NAME;
298
+ if (!name || !name.endsWith("/platform")) return void 0;
299
+ const base = name.slice(0, -"/platform".length);
300
+ if (orgId) {
301
+ const slash = base.lastIndexOf("/");
302
+ const prefix = slash > 0 ? base.slice(0, slash) : base;
303
+ return `${prefix}/${orgId}/github-token`;
304
+ }
305
+ return `${base}/github-token`;
351
306
  }
352
- /** Read (and cache, per org) the org github token; null → unauthenticated best-effort. */
353
307
  async function getGitHubToken(orgId) {
354
- const key = orgId ?? '';
355
- const cached = githubTokenByOrg.get(key);
356
- if (cached !== undefined)
357
- return cached;
358
- const secretName = githubTokenSecretName(orgId);
359
- if (!secretName) {
360
- githubTokenByOrg.set(key, null);
361
- return null;
362
- }
363
- let token;
364
- try {
365
- const client = new SecretsManagerClient({});
366
- const res = await client.send(new GetSecretValueCommand({ SecretId: secretName }));
367
- // The github-token secret is a RAW token string (not JSON — CDK resolves it via
368
- // the default SecretString with no key).
369
- token = res.SecretString && res.SecretString.length > 0 ? res.SecretString.trim() : null;
370
- }
371
- catch (err) {
372
- log.warn('No github-token secret — GitHub commit resolution will be unauthenticated', { orgId, error: err?.name });
373
- token = null;
374
- }
375
- githubTokenByOrg.set(key, token);
376
- return token;
308
+ const key = orgId ?? "";
309
+ const cached = githubTokenByOrg.get(key);
310
+ if (cached !== void 0) return cached;
311
+ const secretName = githubTokenSecretName(orgId);
312
+ if (!secretName) {
313
+ githubTokenByOrg.set(key, null);
314
+ return null;
315
+ }
316
+ let token;
317
+ try {
318
+ const client = new SecretsManagerClient2({});
319
+ const res = await client.send(new GetSecretValueCommand2({ SecretId: secretName }));
320
+ token = res.SecretString && res.SecretString.length > 0 ? res.SecretString.trim() : null;
321
+ } catch (err) {
322
+ log.warn("No github-token secret \u2014 GitHub commit resolution will be unauthenticated", { orgId, error: err?.name });
323
+ token = null;
324
+ }
325
+ githubTokenByOrg.set(key, token);
326
+ return token;
377
327
  }
378
328
  async function fetchWithTimeout(url, opts) {
379
- const controller = new AbortController();
380
- const timer = setTimeout(() => controller.abort(), SCM_FETCH_TIMEOUT_MS);
381
- try {
382
- return await fetch(url, { ...opts, signal: controller.signal });
383
- }
384
- finally {
385
- clearTimeout(timer);
386
- }
329
+ const controller = new AbortController();
330
+ const timer = setTimeout(() => controller.abort(), SCM_FETCH_TIMEOUT_MS);
331
+ try {
332
+ return await fetch(url, { ...opts, signal: controller.signal });
333
+ } finally {
334
+ clearTimeout(timer);
335
+ }
387
336
  }
388
- /** Normalize a commit date to ISO 8601. Accepts ISO strings and CodeCommit's
389
- * "<epoch-seconds> +ZZZZ" form. Returns undefined when unparseable. */
390
337
  function toIso(value) {
391
- if (typeof value !== 'string' || value.length === 0)
392
- return undefined;
393
- const epoch = /^(\d{9,})\b/.exec(value.trim());
394
- if (epoch) {
395
- const d = new Date(Number(epoch[1]) * 1000);
396
- return Number.isNaN(d.getTime()) ? undefined : d.toISOString();
397
- }
398
- const d = new Date(value);
399
- return Number.isNaN(d.getTime()) ? undefined : d.toISOString();
338
+ if (typeof value !== "string" || value.length === 0) return void 0;
339
+ const epoch = /^(\d{9,})\b/.exec(value.trim());
340
+ if (epoch) {
341
+ const d2 = new Date(Number(epoch[1]) * 1e3);
342
+ return Number.isNaN(d2.getTime()) ? void 0 : d2.toISOString();
343
+ }
344
+ const d = new Date(value);
345
+ return Number.isNaN(d.getTime()) ? void 0 : d.toISOString();
400
346
  }
401
- /** Classify a source-revision URL into a provider + repo coordinates. */
402
347
  function classifySource(url) {
403
- if (!url)
404
- return { provider: 'unknown' };
405
- try {
406
- const u = new URL(url);
407
- const host = u.hostname.toLowerCase();
408
- if (host === 'github.com' || host.endsWith('.github.com')) {
409
- const parts = u.pathname.split('/').filter(Boolean);
410
- if (parts.length >= 2)
411
- return { provider: 'github', owner: parts[0], repo: parts[1].replace(/\.git$/, '') };
412
- }
413
- if (host === 'bitbucket.org') {
414
- const parts = u.pathname.split('/').filter(Boolean);
415
- if (parts.length >= 2)
416
- return { provider: 'bitbucket', owner: parts[0], repo: parts[1].replace(/\.git$/, '') };
417
- }
418
- if (u.pathname.toLowerCase().includes('codecommit') || u.hash.toLowerCase().includes('codecommit') || u.pathname.includes('/repositories/')) {
419
- const m = /repositor(?:y|ies)\/([^/#?]+)/.exec(u.pathname + u.hash);
420
- if (m)
421
- return { provider: 'codecommit', repositoryName: decodeURIComponent(m[1]) };
422
- }
423
- }
424
- catch {
425
- // not a URL → unknown
426
- }
427
- return { provider: 'unknown' };
428
- }
429
- let codecommitClientsByRegion;
430
- let codecommitMod;
348
+ if (!url) return { provider: "unknown" };
349
+ try {
350
+ const u = new URL(url);
351
+ const host = u.hostname.toLowerCase();
352
+ if (host === "github.com" || host.endsWith(".github.com")) {
353
+ const parts = u.pathname.split("/").filter(Boolean);
354
+ if (parts.length >= 2) return { provider: "github", owner: parts[0], repo: parts[1].replace(/\.git$/, "") };
355
+ }
356
+ if (host === "bitbucket.org") {
357
+ const parts = u.pathname.split("/").filter(Boolean);
358
+ if (parts.length >= 2) return { provider: "bitbucket", owner: parts[0], repo: parts[1].replace(/\.git$/, "") };
359
+ }
360
+ if (u.pathname.toLowerCase().includes("codecommit") || u.hash.toLowerCase().includes("codecommit") || u.pathname.includes("/repositories/")) {
361
+ const m = /repositor(?:y|ies)\/([^/#?]+)/.exec(u.pathname + u.hash);
362
+ if (m) return { provider: "codecommit", repositoryName: decodeURIComponent(m[1]) };
363
+ }
364
+ } catch {
365
+ }
366
+ return { provider: "unknown" };
367
+ }
368
+ var codecommitClientsByRegion;
369
+ var codecommitMod;
431
370
  async function codeCommitClient(region) {
432
- if (!codecommitMod)
433
- codecommitMod = await loadSdk('@aws-sdk/client-codecommit');
434
- if (!codecommitClientsByRegion)
435
- codecommitClientsByRegion = new Map();
436
- let client = codecommitClientsByRegion.get(region);
437
- if (!client) {
438
- client = new codecommitMod.CodeCommitClient({ region });
439
- codecommitClientsByRegion.set(region, client);
440
- }
441
- return client;
371
+ if (!codecommitMod) codecommitMod = await loadSdk("@aws-sdk/client-codecommit");
372
+ if (!codecommitClientsByRegion) codecommitClientsByRegion = /* @__PURE__ */ new Map();
373
+ let client = codecommitClientsByRegion.get(region);
374
+ if (!client) {
375
+ client = new codecommitMod.CodeCommitClient({ region });
376
+ codecommitClientsByRegion.set(region, client);
377
+ }
378
+ return client;
442
379
  }
443
- /** Fetch (cached) a single CodeCommit commit's date + first parents via GetCommit. */
444
380
  async function codeCommitMeta(region, repositoryName, commitId) {
445
- const cached = commitMetaCache.get(commitId);
446
- if (cached)
447
- return cached;
448
- if (!codecommitMod)
449
- codecommitMod = await loadSdk('@aws-sdk/client-codecommit');
450
- const client = await codeCommitClient(region);
451
- const out = await client.send(new codecommitMod.GetCommitCommand({ repositoryName, commitId }));
452
- const commit = out.commit;
453
- const meta = { date: toIso(commit?.committer?.date ?? commit?.author?.date), parents: commit?.parents };
454
- commitMetaCache.set(commitId, meta);
455
- return meta;
381
+ const cached = commitMetaCache.get(commitId);
382
+ if (cached) return cached;
383
+ if (!codecommitMod) codecommitMod = await loadSdk("@aws-sdk/client-codecommit");
384
+ const client = await codeCommitClient(region);
385
+ const out = await client.send(new codecommitMod.GetCommitCommand({ repositoryName, commitId }));
386
+ const commit = out.commit;
387
+ const meta = { date: toIso(commit?.committer?.date ?? commit?.author?.date), parents: commit?.parents };
388
+ commitMetaCache.set(commitId, meta);
389
+ return meta;
456
390
  }
457
391
  async function resolveCodeCommit(region, repositoryName, sha, sinceSha) {
458
- let cur = sha;
459
- let count = 0;
460
- let oldest;
461
- let truncated = false;
462
- for (let i = 0;; i++) {
463
- if (!cur)
464
- break; // reached history root — the whole range is covered
465
- if (sinceSha && cur === sinceSha)
466
- break; // reached the exclusive lower bound
467
- if (i >= MAX_COMMIT_WALK) {
468
- truncated = true;
469
- break;
470
- } // hit the cap before the boundary
471
- const meta = await codeCommitMeta(region, repositoryName, cur);
472
- if (!meta?.date)
473
- break;
474
- count++;
475
- oldest = meta.date;
476
- if (!sinceSha)
477
- break; // no range → single commit
478
- cur = meta.parents?.[0];
479
- }
480
- // A truncated walk yields a commit NEWER than the true oldest-unshipped commit,
481
- // which would UNDERSTATE lead time — omit the fields entirely rather than report a
482
- // wrong (too-small) value. Reporting falls back to `unknown`.
483
- if (truncated)
484
- return {};
485
- return oldest ? { commitTimestamp: oldest, commitCount: count } : {};
392
+ let cur = sha;
393
+ let count = 0;
394
+ let oldest;
395
+ let truncated = false;
396
+ for (let i = 0; ; i++) {
397
+ if (!cur) break;
398
+ if (sinceSha && cur === sinceSha) break;
399
+ if (i >= MAX_COMMIT_WALK) {
400
+ truncated = true;
401
+ break;
402
+ }
403
+ const meta = await codeCommitMeta(region, repositoryName, cur);
404
+ if (!meta?.date) break;
405
+ count++;
406
+ oldest = meta.date;
407
+ if (!sinceSha) break;
408
+ cur = meta.parents?.[0];
409
+ }
410
+ if (truncated) return {};
411
+ return oldest ? { commitTimestamp: oldest, commitCount: count } : {};
486
412
  }
487
413
  async function resolveGitHub(owner, repo, sha, sinceSha, orgId) {
488
- const token = await getGitHubToken(orgId);
489
- const headers = {
490
- 'Accept': 'application/vnd.github+json',
491
- 'User-Agent': 'pipeline-builder-forwarder',
492
- 'X-GitHub-Api-Version': '2022-11-28',
493
- };
494
- if (token)
495
- headers.Authorization = `Bearer ${token}`;
496
- const rateLimited = (r) => r.status === 403 || r.status === 429;
497
- if (sinceSha) {
498
- const r = await fetchWithTimeout(`https://api.github.com/repos/${owner}/${repo}/compare/${sinceSha}...${sha}`, { headers });
499
- if (rateLimited(r)) {
500
- scmCooldownUntil = Date.now() + SCM_COOLDOWN_MS;
501
- return {};
502
- }
503
- if (r.ok) {
504
- const j = await r.json();
505
- const commits = Array.isArray(j.commits) ? j.commits : [];
506
- if (commits.length > 0) {
507
- const oldest = toIso(commits[0]?.commit?.committer?.date ?? commits[0]?.commit?.author?.date);
508
- if (oldest)
509
- return { commitTimestamp: oldest, commitCount: commits.length };
510
- }
511
- }
512
- // fall through to single-commit resolution
513
- }
514
- const r2 = await fetchWithTimeout(`https://api.github.com/repos/${owner}/${repo}/commits/${sha}`, { headers });
515
- if (rateLimited(r2)) {
516
- scmCooldownUntil = Date.now() + SCM_COOLDOWN_MS;
517
- return {};
518
- }
519
- if (r2.ok) {
520
- const j = await r2.json();
521
- const d = toIso(j.commit?.committer?.date ?? j.commit?.author?.date);
522
- if (d)
523
- return { commitTimestamp: d, commitCount: 1 };
414
+ const token = await getGitHubToken(orgId);
415
+ const headers = {
416
+ "Accept": "application/vnd.github+json",
417
+ "User-Agent": "pipeline-builder-forwarder",
418
+ "X-GitHub-Api-Version": "2022-11-28"
419
+ };
420
+ if (token) headers.Authorization = `Bearer ${token}`;
421
+ const rateLimited = (r) => r.status === 403 || r.status === 429;
422
+ if (sinceSha) {
423
+ const r = await fetchWithTimeout(`https://api.github.com/repos/${owner}/${repo}/compare/${sinceSha}...${sha}`, { headers });
424
+ if (rateLimited(r)) {
425
+ scmCooldownUntil = Date.now() + SCM_COOLDOWN_MS;
426
+ return {};
524
427
  }
428
+ if (r.ok) {
429
+ const j = await r.json();
430
+ const commits = Array.isArray(j.commits) ? j.commits : [];
431
+ if (commits.length > 0) {
432
+ const oldest = toIso(commits[0]?.commit?.committer?.date ?? commits[0]?.commit?.author?.date);
433
+ if (oldest) return { commitTimestamp: oldest, commitCount: commits.length };
434
+ }
435
+ }
436
+ }
437
+ const r2 = await fetchWithTimeout(`https://api.github.com/repos/${owner}/${repo}/commits/${sha}`, { headers });
438
+ if (rateLimited(r2)) {
439
+ scmCooldownUntil = Date.now() + SCM_COOLDOWN_MS;
525
440
  return {};
441
+ }
442
+ if (r2.ok) {
443
+ const j = await r2.json();
444
+ const d = toIso(j.commit?.committer?.date ?? j.commit?.author?.date);
445
+ if (d) return { commitTimestamp: d, commitCount: 1 };
446
+ }
447
+ return {};
526
448
  }
527
449
  async function resolveBitbucket(owner, repo, sha) {
528
- const r = await fetchWithTimeout(`https://api.bitbucket.org/2.0/repositories/${owner}/${repo}/commit/${sha}`, {
529
- headers: { 'User-Agent': 'pipeline-builder-forwarder', 'Accept': 'application/json' },
530
- });
531
- if (r.status === 429) {
532
- scmCooldownUntil = Date.now() + SCM_COOLDOWN_MS;
533
- return {};
534
- }
535
- if (r.ok) {
536
- const j = await r.json();
537
- const d = toIso(j.date);
538
- if (d)
539
- return { commitTimestamp: d, commitCount: 1 };
540
- }
450
+ const r = await fetchWithTimeout(`https://api.bitbucket.org/2.0/repositories/${owner}/${repo}/commit/${sha}`, {
451
+ headers: { "User-Agent": "pipeline-builder-forwarder", "Accept": "application/json" }
452
+ });
453
+ if (r.status === 429) {
454
+ scmCooldownUntil = Date.now() + SCM_COOLDOWN_MS;
541
455
  return {};
456
+ }
457
+ if (r.ok) {
458
+ const j = await r.json();
459
+ const d = toIso(j.date);
460
+ if (d) return { commitTimestamp: d, commitCount: 1 };
461
+ }
462
+ return {};
542
463
  }
543
- /**
544
- * Resolve `commitTimestamp` + `commitCount` for a source/deploy event, in-account,
545
- * best-effort. Computed once per (pipelineId, sha); advances the pipeline's range
546
- * boundary only on success. Never throws — returns {} on any failure.
547
- */
548
464
  async function resolveCommitInfo(pipelineId, orgId, region, src) {
549
- if (src.provider === 'unknown')
550
- return {};
551
- const rkey = `${pipelineId}|${src.sha}`;
552
- const cached = commitResultCache.get(rkey);
553
- if (cached)
554
- return cached;
555
- const prev = lastShaByPipeline.get(pipelineId);
556
- const sinceSha = prev && prev !== src.sha ? prev : undefined;
557
- let info = {};
558
- try {
559
- if (src.provider === 'codecommit' && src.repositoryName) {
560
- info = await resolveCodeCommit(region, src.repositoryName, src.sha, sinceSha);
561
- }
562
- else if (src.provider === 'github' && src.owner && src.repo) {
563
- if (Date.now() >= scmCooldownUntil)
564
- info = await resolveGitHub(src.owner, src.repo, src.sha, sinceSha, orgId);
565
- }
566
- else if (src.provider === 'bitbucket' && src.owner && src.repo) {
567
- if (Date.now() >= scmCooldownUntil)
568
- info = await resolveBitbucket(src.owner, src.repo, src.sha);
569
- }
570
- }
571
- catch (err) {
572
- log.warn('Commit resolution failed (omitting commitTimestamp/commitCount)', { provider: src.provider, error: err?.name ?? String(err) });
573
- info = {};
574
- }
465
+ if (src.provider === "unknown") return {};
466
+ const rkey = `${pipelineId}|${src.sha}`;
467
+ const cached = commitResultCache.get(rkey);
468
+ if (cached) return cached;
469
+ const prev = lastShaByPipeline.get(pipelineId);
470
+ const sinceSha = prev && prev !== src.sha ? prev : void 0;
471
+ let info = {};
472
+ try {
473
+ if (src.provider === "codecommit" && src.repositoryName) {
474
+ info = await resolveCodeCommit(region, src.repositoryName, src.sha, sinceSha);
475
+ } else if (src.provider === "github" && src.owner && src.repo) {
476
+ if (Date.now() >= scmCooldownUntil) info = await resolveGitHub(src.owner, src.repo, src.sha, sinceSha, orgId);
477
+ } else if (src.provider === "bitbucket" && src.owner && src.repo) {
478
+ if (Date.now() >= scmCooldownUntil) info = await resolveBitbucket(src.owner, src.repo, src.sha);
479
+ }
480
+ } catch (err) {
481
+ log.warn("Commit resolution failed (omitting commitTimestamp/commitCount)", { provider: src.provider, error: err?.name ?? String(err) });
482
+ info = {};
483
+ }
484
+ if (info.commitTimestamp) {
575
485
  commitResultCache.set(rkey, info);
576
- if (info.commitTimestamp)
577
- lastShaByPipeline.set(pipelineId, src.sha);
578
- return info;
486
+ lastShaByPipeline.set(pipelineId, src.sha);
487
+ }
488
+ return info;
489
+ }
490
+ function _resetScmForTests() {
491
+ commitResultCache.clear();
492
+ lastShaByPipeline.clear();
493
+ commitMetaCache.clear();
494
+ scmCooldownUntil = 0;
495
+ githubTokenByOrg.clear();
496
+ }
497
+
498
+ // src/tags.ts
499
+ var codepipelineMod;
500
+ async function loadCodePipeline() {
501
+ if (!codepipelineMod) codepipelineMod = await import("@aws-sdk/client-codepipeline");
502
+ return codepipelineMod;
503
+ }
504
+ var PIPELINE_ID_TAG = "pb.pipeline-id";
505
+ var DEPLOYS_TAG = "pb.deploys";
506
+ var ORG_ID_TAG = "OrgId";
507
+ var clientsByRegion = /* @__PURE__ */ new Map();
508
+ var resolvedByArn = new BoundedMap(CACHE_MAX_ENTRIES);
509
+ var NEG_CACHE_TTL_MS = 5 * 60 * 1e3;
510
+ var POS_CACHE_TTL_MS = 15 * 60 * 1e3;
511
+ var MAX_ATTR = 255;
512
+ function parseDeploys(value) {
513
+ const map = /* @__PURE__ */ new Map();
514
+ if (!value) return map;
515
+ for (const pair of value.split("+")) {
516
+ const idx = pair.indexOf(":");
517
+ if (idx <= 0) continue;
518
+ const stage = pair.slice(0, idx).trim();
519
+ const env = pair.slice(idx + 1).trim();
520
+ if (stage && env) map.set(stage, env.slice(0, MAX_ATTR));
521
+ }
522
+ return map;
523
+ }
524
+ async function pipelineClient(region) {
525
+ let client = clientsByRegion.get(region);
526
+ if (!client) {
527
+ const { CodePipelineClient } = await loadCodePipeline();
528
+ client = new CodePipelineClient({ region });
529
+ clientsByRegion.set(region, client);
530
+ }
531
+ return client;
579
532
  }
580
- /** Cap on the stored failure summary — CodeBuild/Deploy summaries can be long. */
581
- const MAX_ERROR_MESSAGE = 4000;
582
- /** First non-empty string among the candidates, capped to the ingest max. */
533
+ async function resolvePipeline(arn, region) {
534
+ const cached = resolvedByArn.get(arn);
535
+ if (cached && Date.now() - cached.ts < (cached.pipelineId !== null ? POS_CACHE_TTL_MS : NEG_CACHE_TTL_MS)) {
536
+ return { pipelineId: cached.pipelineId, orgId: cached.orgId, deploys: cached.deploys };
537
+ }
538
+ try {
539
+ const client = await pipelineClient(region);
540
+ const { ListTagsForResourceCommand } = await loadCodePipeline();
541
+ const out = await client.send(new ListTagsForResourceCommand({ resourceArn: arn }));
542
+ const pipelineId = out.tags?.find((t) => t.key === PIPELINE_ID_TAG)?.value ?? null;
543
+ const orgId = out.tags?.find((t) => t.key === ORG_ID_TAG)?.value ?? null;
544
+ const deploys = parseDeploys(out.tags?.find((t) => t.key === DEPLOYS_TAG)?.value);
545
+ if (!pipelineId) log.warn("Pipeline missing pb.pipeline-id tag \u2014 skipping (register it?)", { arn });
546
+ resolvedByArn.set(arn, { pipelineId, orgId, deploys, ts: Date.now() });
547
+ return { pipelineId, orgId, deploys };
548
+ } catch (err) {
549
+ const name = err?.name;
550
+ if (name === "AccessDeniedException") {
551
+ log.error("AccessDenied calling codepipeline:ListTagsForResource \u2014 grant it to the Lambda role", { arn, error: name });
552
+ throw err;
553
+ }
554
+ const e = err;
555
+ const status = e.$metadata?.httpStatusCode;
556
+ const retryable = e.$retryable != null || typeof status === "number" && status >= 500 || /throttl|timeout|toomanyrequests|serviceunavailable|econnreset|networkingerror/i.test(name ?? "");
557
+ if (retryable) {
558
+ log.error("Transient error resolving pb.pipeline-id tag \u2014 will retry the batch", { arn, error: name ?? String(err) });
559
+ throw err;
560
+ }
561
+ log.error("Failed to resolve pb.pipeline-id tag", { arn, error: name ?? String(err) });
562
+ return { pipelineId: null, orgId: null, deploys: /* @__PURE__ */ new Map() };
563
+ }
564
+ }
565
+ function _resetTagsForTests() {
566
+ resolvedByArn.clear();
567
+ }
568
+
569
+ // src/parse.ts
570
+ var MAX_ERROR_MESSAGE = 4e3;
583
571
  function firstAttr(...vals) {
584
- for (const v of vals) {
585
- if (typeof v === 'string' && v.length > 0)
586
- return v.slice(0, MAX_ATTR);
587
- }
588
- return undefined;
589
- }
590
- /**
591
- * Best-effort extraction of the source commit id + ref (and the revision URL, used
592
- * only in-account to classify the source type for commit-range resolution) from a
593
- * CodePipeline event detail. CodePipeline surfaces the source revision under a few
594
- * shapes depending on the event type / source-action; we check the known ones and
595
- * leave the fields undefined when none are present (NULL downstream). Never pulls an
596
- * ARN/account id: only revision/branch/url string fields are read; the revisionUrl
597
- * is consumed in-account (source classification) and never forwarded on its own.
598
- */
572
+ for (const v of vals) {
573
+ if (typeof v === "string" && v.length > 0) return v.slice(0, MAX_ATTR);
574
+ }
575
+ return void 0;
576
+ }
599
577
  function extractCommit(detail) {
600
- // `source-revisions` (kebab, EventBridge) / `sourceRevisions` (camel) — first entry.
601
- const revs = (detail['source-revisions'] ?? detail.sourceRevisions);
602
- const firstRev = Array.isArray(revs) ? revs[0] : undefined;
603
- // On source actions the revision id can also ride on `execution-result`.
604
- const execResult = detail['execution-result'];
605
- const commitSha = firstAttr(detail.commitId, detail.revisionId, firstRev?.revisionId, execResult?.revisionId);
606
- const commitRef = firstAttr(detail.commitRef, detail.branch, detail.sourceBranch, firstRev?.branchName, firstRev?.branch);
607
- const revisionUrl = typeof firstRev?.revisionUrl === 'string' ? firstRev.revisionUrl : undefined;
608
- return { ...(commitSha && { commitSha }), ...(commitRef && { commitRef }), ...(revisionUrl && { revisionUrl }) };
578
+ const revs = detail["source-revisions"] ?? detail.sourceRevisions;
579
+ const firstRev = Array.isArray(revs) ? revs[0] : void 0;
580
+ const execResult = detail["execution-result"];
581
+ const commitSha = firstAttr(
582
+ detail.commitId,
583
+ detail.revisionId,
584
+ firstRev?.revisionId,
585
+ execResult?.revisionId
586
+ );
587
+ const commitRef = firstAttr(
588
+ detail.commitRef,
589
+ detail.branch,
590
+ detail.sourceBranch,
591
+ firstRev?.branchName,
592
+ firstRev?.branch
593
+ );
594
+ const revisionUrl = typeof firstRev?.revisionUrl === "string" ? firstRev.revisionUrl : void 0;
595
+ return { ...commitSha && { commitSha }, ...commitRef && { commitRef }, ...revisionUrl && { revisionUrl } };
609
596
  }
610
597
  function classifyEvent(detailType) {
611
- if (detailType.includes('Pipeline Execution'))
612
- return { eventType: 'PIPELINE', eventSource: 'codepipeline' };
613
- if (detailType.includes('Stage Execution'))
614
- return { eventType: 'STAGE', eventSource: 'codepipeline' };
615
- if (detailType.includes('Action Execution'))
616
- return { eventType: 'ACTION', eventSource: 'codepipeline' };
617
- // CodeBuild "Build State" events identify a build *project*, not a pipeline, so
618
- // parseRecord drops them on the `eventSource !== 'codepipeline'` check below —
619
- // the 'BUILD' eventType is never persisted here (plugin BUILD events are
620
- // recorded directly by the plugin service). The 'codebuild' source is what
621
- // routes the drop; keep this branch only for that.
622
- if (detailType.includes('Build State'))
623
- return { eventType: 'BUILD', eventSource: 'codebuild' };
624
- return { eventType: 'PIPELINE', eventSource: 'codepipeline' };
625
- }
626
- /** Parse + resolve one record to a reportable event, or null to skip it. */
627
- async function parseRecord(record) {
628
- let event;
629
- // A malformed body is a dead message (retrying won't help) — log + skip it so
630
- // it can't fail the whole batch. Resolution errors below (e.g. AccessDenied)
631
- // are NOT caught here: those are transient/infra and must propagate to retry.
632
- try {
633
- event = JSON.parse(record.body);
634
- }
635
- catch (err) {
636
- log.warn('Skipping SQS record with unparseable body', { messageId: record.messageId, error: String(err) });
637
- return null;
638
- }
639
- const { eventType, eventSource } = classifyEvent(event['detail-type']);
640
- const detail = { ...event.detail };
641
- // The raw account never needs to leave AWS now — drop it from the payload.
642
- delete detail.account;
643
- // CodeBuild "Build State" events identify a build *project*, not a pipeline,
644
- // and a project can be shared across pipelines — there's no clean 1:1 mapping
645
- // to a pipeline id, so skip them.
646
- if (eventSource !== 'codepipeline') {
647
- log.warn('Skipping non-CodePipeline event (no pipeline tag to resolve)', { detailType: event['detail-type'] });
648
- return null;
649
- }
650
- const pipelineName = detail.pipeline;
651
- if (!pipelineName) {
652
- log.warn('CodePipeline event missing pipeline name — skipping');
653
- return null;
654
- }
655
- // Resolve the pipeline's pb.pipeline-id (= platform pipelineId) + pb.deploys map.
656
- // The ARN is only a transient handle for the tag lookup; it is never stored.
657
- const arn = `arn:aws:codepipeline:${event.region}:${event.account}:${pipelineName}`;
658
- const { pipelineId, orgId, deploys } = await resolvePipeline(arn, event.region);
659
- if (!pipelineId)
660
- return null; // untagged / unregistered → skip
661
- const startedAt = detail['start-time'] || event.time;
662
- const state = detail.state;
663
- let durationMs;
664
- let completedAt;
665
- if (['SUCCEEDED', 'FAILED', 'CANCELED', 'STOPPED'].includes(state)) {
666
- completedAt = event.time;
667
- if (startedAt && completedAt) {
668
- const ms = new Date(completedAt).getTime() - new Date(startedAt).getTime();
669
- if (ms >= 0)
670
- durationMs = ms;
671
- }
672
- }
673
- // Promote the failure reason to a typed field. On Action events AWS puts the
674
- // human-readable summary (and a log URL + error-code, which stay in `detail`)
675
- // under `execution-result`.
676
- const result = detail['execution-result'];
677
- const summary = result?.['external-execution-summary'];
678
- const errorMessage = typeof summary === 'string' && summary.length > 0
679
- ? summary.slice(0, MAX_ERROR_MESSAGE)
680
- : undefined;
681
- // DORA deploy attribution — all optional: omitted (undefined) when the
682
- // event/pipeline doesn't carry them.
683
- const { commitSha, commitRef, revisionUrl } = extractCommit(detail);
684
- const executionId = detail['execution-id'];
685
- const stageName = detail.stage;
686
- const actionName = detail.action;
687
- // Environment (⇒ isDeploy) is set ONLY when this event's stage is listed in the
688
- // pipeline's pb.deploys tag. PIPELINE events have no stage → never a deploy.
689
- const environment = stageName ? deploys.get(stageName) : undefined;
690
- // Phase 4 — resolve the commit range (oldest-unshipped timestamp + count) for any
691
- // event carrying a commit. Best-effort/in-account; omitted on any failure.
692
- // GATED on `DORA_ENABLED` (set by `setup-events --with-dora`): DORA lead time is an
693
- // advanced_reporting add-on, so orgs without it skip the SCM/secret cost entirely —
694
- // standard reporting (commitSha + all other fields) still forwards regardless.
695
- let commitTimestamp;
696
- let commitCount;
697
- if (commitSha && process.env.DORA_ENABLED === 'true') {
698
- const src = classifySource(revisionUrl);
699
- const info = await resolveCommitInfo(pipelineId, orgId, event.region, { sha: commitSha, ...src });
700
- commitTimestamp = info.commitTimestamp;
701
- commitCount = info.commitCount;
702
- }
703
- return {
704
- orgId,
705
- pipelineId,
706
- eventSource,
707
- eventType,
708
- status: state,
709
- executionId,
710
- stageName,
711
- actionName,
712
- errorMessage,
713
- startedAt,
714
- completedAt,
715
- durationMs,
716
- ...(commitSha && { commitSha }),
717
- ...(commitRef && { commitRef }),
718
- ...(commitTimestamp && { commitTimestamp }),
719
- ...(commitCount && { commitCount }),
720
- ...(environment && { environment }),
721
- detail,
722
- };
723
- }
724
- let sqsMod;
725
- let sqsClientsByRegion;
726
- async function sqsClient(region) {
727
- if (!sqsMod)
728
- sqsMod = await loadSdk('@aws-sdk/client-sqs');
729
- if (!sqsClientsByRegion)
730
- sqsClientsByRegion = new Map();
731
- let client = sqsClientsByRegion.get(region);
732
- if (!client) {
733
- client = new sqsMod.SQSClient({ region });
734
- sqsClientsByRegion.set(region, client);
735
- }
736
- return client;
737
- }
738
- // Delivery-health counters are attributed PER RESOLVED ORG (the forwarder is a fleet
739
- // Lambda serving many orgs; a single global counter would mis-attribute one org's
740
- // throughput to another). Keyed by orgId ('' bucket = pipeline with no OrgId tag).
741
- // The DLQ depth (`dropped`) is genuinely fleet-global — one shared DLQ — so it is a
742
- // single snapshot posted alongside each org's health, documented as the shared depth.
743
- const forwardedByOrg = new Map();
744
- const lastEventAtByOrg = new Map();
745
- let lastHealthAt = 0;
746
- let lastRedriveAt = 0;
747
- const HEALTH_THROTTLE_MS = 60 * 1000;
748
- const REDRIVE_THROTTLE_MS = 5 * 60 * 1000;
749
- // Poison-message ceiling: if the oldest DLQ message is older than this, we STOP
750
- // redriving it (DLQ→main→fail→DLQ would loop forever). Such messages are left for an
751
- // alarm / manual inspection instead of churning the fleet indefinitely.
752
- const MAX_DLQ_REDRIVE_AGE_SEC = 6 * 60 * 60; // 6 hours
753
- /** Parse the SQS trigger ARN and derive the main + DLQ ARNs and the DLQ URL. */
754
- function deriveQueues(mainQueueArn) {
755
- if (!mainQueueArn)
756
- return undefined;
757
- // arn:aws:sqs:{region}:{account}:{name}
758
- const parts = mainQueueArn.split(':');
759
- if (parts.length < 6 || parts[2] !== 'sqs')
760
- return undefined;
761
- const [, , , region, account, name] = parts;
762
- const dlqArn = process.env.EVENT_DLQ_ARN || `arn:aws:sqs:${region}:${account}:${name}-dlq`;
763
- const dlqName = dlqArn.split(':').pop() ?? `${name}-dlq`;
764
- const dlqUrl = `https://sqs.${region}.amazonaws.com/${account}/${dlqName}`;
765
- return { region, mainArn: mainQueueArn, dlqArn, dlqUrl };
766
- }
767
- /** Snapshot the DLQ depth + the age (seconds) of its oldest message (for the
768
- * poison-message redrive ceiling). Missing/NaN attributes fall back to 0. */
769
- async function dlqStats(region, dlqUrl) {
770
- if (!sqsMod)
771
- sqsMod = await loadSdk('@aws-sdk/client-sqs');
772
- const client = await sqsClient(region);
773
- const out = await client.send(new sqsMod.GetQueueAttributesCommand({
774
- QueueUrl: dlqUrl,
775
- AttributeNames: ['ApproximateNumberOfMessages', 'ApproximateAgeOfOldestMessage'],
776
- }));
777
- const n = Number(out.Attributes?.ApproximateNumberOfMessages ?? '0');
778
- const age = Number(out.Attributes?.ApproximateAgeOfOldestMessage ?? '0');
779
- return {
780
- depth: Number.isFinite(n) ? n : 0,
781
- oldestAgeSec: Number.isFinite(age) ? age : 0,
782
- };
783
- }
784
- async function hasActiveMoveTask(region, dlqArn) {
785
- if (!sqsMod)
786
- sqsMod = await loadSdk('@aws-sdk/client-sqs');
787
- const client = await sqsClient(region);
788
- const out = await client.send(new sqsMod.ListMessageMoveTasksCommand({ SourceArn: dlqArn }));
789
- return (out.Results ?? []).some(t => t.Status === 'RUNNING');
790
- }
791
- async function startMoveTask(region, dlqArn, mainArn) {
792
- if (!sqsMod)
793
- sqsMod = await loadSdk('@aws-sdk/client-sqs');
794
- const client = await sqsClient(region);
795
- await client.send(new sqsMod.StartMessageMoveTaskCommand({ SourceArn: dlqArn, DestinationArn: mainArn }));
796
- }
797
- /** POST one org's ingest-health signal. Returns true only on a confirmed 2xx so the
798
- * caller resets that org's `forwarded` counter ONLY when the signal was accepted
799
- * (a dropped/failed POST must not silently zero the accumulated count). */
800
- async function postIngestHealth(baseUrl, body) {
801
- try {
802
- const token = await getAuthToken();
803
- const res = await fetch(`${baseUrl}/api/reports/ingest-health`, {
804
- method: 'POST',
805
- headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
806
- body: JSON.stringify(body),
807
- });
808
- if (!res.ok) {
809
- log.warn('ingest-health POST returned non-2xx (ignored)', { status: res.status, orgId: body.orgId });
810
- return false;
811
- }
812
- return true;
813
- }
814
- catch (err) {
815
- log.warn('ingest-health POST failed (ignored)', { error: String(err), orgId: body.orgId });
816
- return false;
817
- }
818
- }
819
- /**
820
- * Throttled, best-effort post-success work: emit per-org ingest-health and self-heal
821
- * the DLQ. Never throws. `forwarded`/`lastEventAt` accumulate PER ORG across batches
822
- * between health posts; `dropped` is the shared fleet DLQ depth snapshot. A per-org
823
- * counter is zeroed only after that org's health POST is confirmed 2xx.
824
- */
825
- async function reportHealthAndRedrive(baseUrl, mainQueueArn) {
826
- const now = Date.now();
827
- if (now - lastHealthAt < HEALTH_THROTTLE_MS)
828
- return;
829
- lastHealthAt = now;
830
- try {
831
- const q = deriveQueues(mainQueueArn);
832
- let depth = 0;
833
- let oldestAgeSec = 0;
834
- if (q) {
835
- try {
836
- const stats = await dlqStats(q.region, q.dlqUrl);
837
- depth = stats.depth;
838
- oldestAgeSec = stats.oldestAgeSec;
839
- }
840
- catch (err) {
841
- log.warn('DLQ stats check failed (ignored)', { error: err?.name ?? String(err) });
842
- }
843
- }
844
- // Post one health signal per resolved org, attributing its own forwarded count +
845
- // last event time. `dropped` (the shared fleet DLQ depth) rides along on each.
846
- // Snapshot the keys first so we don't mutate the map while iterating.
847
- const orgKeys = new Set([...forwardedByOrg.keys(), ...lastEventAtByOrg.keys()]);
848
- if (orgKeys.size === 0)
849
- orgKeys.add(''); // nothing forwarded yet → still report depth
850
- for (const key of orgKeys) {
851
- const forwarded = forwardedByOrg.get(key) ?? 0;
852
- const lastEventAt = lastEventAtByOrg.get(key);
853
- const ok = await postIngestHealth(baseUrl, {
854
- ...(key ? { orgId: key } : {}),
855
- forwarded,
856
- dropped: depth,
857
- lastEventAt,
858
- });
859
- // Reset the forwarded counter ONLY on a confirmed 2xx — a failed/dropped POST
860
- // keeps the count so the next tick re-reports it instead of losing it.
861
- if (ok)
862
- forwardedByOrg.set(key, 0);
863
- }
864
- if (q && depth > 0 && now - lastRedriveAt >= REDRIVE_THROTTLE_MS) {
865
- // Poison-message ceiling: never redrive a DLQ whose oldest message has aged
866
- // past the ceiling — that message keeps failing and would loop DLQ→main→DLQ.
867
- if (oldestAgeSec > MAX_DLQ_REDRIVE_AGE_SEC) {
868
- log.warn('DLQ oldest message exceeds poison-age ceiling — skipping redrive (needs manual attention)', {
869
- dlqArn: q.dlqArn, dropped: depth, oldestAgeSec, ceilingSec: MAX_DLQ_REDRIVE_AGE_SEC,
870
- });
871
- }
872
- else {
873
- try {
874
- if (!(await hasActiveMoveTask(q.region, q.dlqArn))) {
875
- lastRedriveAt = now;
876
- await startMoveTask(q.region, q.dlqArn, q.mainArn);
877
- log.info('Started self-healing DLQ→main redrive', { dlqArn: q.dlqArn, dropped: depth, oldestAgeSec });
878
- }
879
- }
880
- catch (err) {
881
- log.warn('DLQ redrive failed (ignored)', { error: err?.name ?? String(err) });
882
- }
883
- }
884
- }
885
- }
886
- catch (err) {
887
- log.warn('health/redrive step failed (ignored)', { error: String(err) });
888
- }
598
+ if (detailType.includes("Pipeline Execution")) return { eventType: "PIPELINE", eventSource: "codepipeline" };
599
+ if (detailType.includes("Stage Execution")) return { eventType: "STAGE", eventSource: "codepipeline" };
600
+ if (detailType.includes("Action Execution")) return { eventType: "ACTION", eventSource: "codepipeline" };
601
+ if (detailType.includes("Build State")) return { eventType: "BUILD", eventSource: "codebuild" };
602
+ return { eventType: "PIPELINE", eventSource: "codepipeline" };
889
603
  }
890
- // ─── Handler ────────────────────────────────────────────
891
- export const handler = async (event) => {
892
- const baseUrl = process.env.PLATFORM_BASE_URL;
893
- if (!baseUrl)
894
- throw new Error('PLATFORM_BASE_URL environment variable is required');
895
- // Parse + resolve all records (tag + commit lookups run concurrently); drop
896
- // intentional skips (null). A malformed record BODY is handled inside parseRecord
897
- // (logged + skipped) so one bad message can't fail the whole batch — while genuine
898
- // infra errors (e.g. AccessDenied on the tag lookup) still propagate so a missing
899
- // IAM grant surfaces and SQS retries the batch.
900
- const events = (await Promise.all(event.Records.map(parseRecord)))
901
- .filter((e) => e !== null);
902
- if (events.length === 0) {
903
- log.info('No resolvable CodePipeline events in batch');
904
- return;
905
- }
906
- // POST batch to reporting service. On a 401/403 the credential this container
907
- // holds is stale — most often because the token-renew Lambda rotated the key in
908
- // Secrets Manager — so drop BOTH the cached token and the cached key, re-read,
909
- // and retry ONCE. `orgId` is INTERNAL (token/health keying) — strip it here so
910
- // it never rides the wire; the ingest resolves org from the pipeline registry,
911
- // not the body.
912
- const payloadEvents = events.map(({ orgId: _orgId, ...rest }) => rest);
913
- const post = (token) => fetch(`${baseUrl}/api/reports/events`, {
914
- method: 'POST',
915
- headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
916
- body: JSON.stringify({ events: payloadEvents }),
917
- });
918
- let res = await post(await getAuthToken());
919
- if (res.status === 401 || res.status === 403) {
920
- log.warn('Reporting API auth failed; re-reading the stored key and retrying once', { status: res.status });
921
- invalidateCredential();
922
- res = await post(await getAuthToken());
923
- }
924
- if (!res.ok) {
925
- const body = await res.text().catch(() => '');
926
- log.error(`Reporting API returned ${res.status}`, { body });
927
- throw new Error(`Reporting API failed: ${res.status}`);
928
- }
929
- // The insert already succeeded (2xx). The parsed body is only used for a log
930
- // line, so a non-JSON/empty body must not throw and fail the whole batch —
931
- // that would trigger an SQS redelivery and re-POST (duplicate) already-inserted
932
- // events. The ingest's partial-unique index dedupes, but avoid the churn anyway.
933
- const result = await res.json().catch(() => ({}));
934
- log.info(`Ingested ${events.length} events`, { inserted: result.data });
935
- // Accumulate delivery-health counters PER RESOLVED ORG, then run the throttled
936
- // post-success health + self-healing DLQ redrive (best-effort, never fails batch).
937
- for (const e of events) {
938
- const key = e.orgId ?? '';
939
- forwardedByOrg.set(key, (forwardedByOrg.get(key) ?? 0) + 1);
940
- const at = e.completedAt || e.startedAt;
941
- if (at) {
942
- const prev = lastEventAtByOrg.get(key);
943
- if (!prev || at > prev)
944
- lastEventAtByOrg.set(key, at);
945
- }
946
- }
947
- await reportHealthAndRedrive(baseUrl, event.Records[0]?.eventSourceARN);
604
+ async function parseRecord(record) {
605
+ let event;
606
+ try {
607
+ event = JSON.parse(record.body);
608
+ } catch (err) {
609
+ log.warn("Skipping SQS record with unparseable body", { messageId: record.messageId, error: String(err) });
610
+ return null;
611
+ }
612
+ const { eventType, eventSource } = classifyEvent(event["detail-type"]);
613
+ const detail = { ...event.detail };
614
+ delete detail.account;
615
+ if (eventSource !== "codepipeline") {
616
+ log.warn("Skipping non-CodePipeline event (no pipeline tag to resolve)", { detailType: event["detail-type"] });
617
+ return null;
618
+ }
619
+ const pipelineName = detail.pipeline;
620
+ if (!pipelineName) {
621
+ log.warn("CodePipeline event missing pipeline name \u2014 skipping");
622
+ return null;
623
+ }
624
+ const arn = `arn:aws:codepipeline:${event.region}:${event.account}:${pipelineName}`;
625
+ const { pipelineId, orgId, deploys } = await resolvePipeline(arn, event.region);
626
+ if (!pipelineId) return null;
627
+ const startedAt = detail["start-time"] || event.time;
628
+ const state = detail.state;
629
+ let durationMs;
630
+ let completedAt;
631
+ if (["SUCCEEDED", "FAILED", "CANCELED", "STOPPED"].includes(state)) {
632
+ completedAt = event.time;
633
+ if (startedAt && completedAt) {
634
+ const ms = new Date(completedAt).getTime() - new Date(startedAt).getTime();
635
+ if (ms >= 0) durationMs = ms;
636
+ }
637
+ }
638
+ const result = detail["execution-result"];
639
+ const summary = result?.["external-execution-summary"];
640
+ const errorMessage = typeof summary === "string" && summary.length > 0 ? summary.slice(0, MAX_ERROR_MESSAGE) : void 0;
641
+ const { commitSha, commitRef, revisionUrl } = extractCommit(detail);
642
+ const executionId = detail["execution-id"];
643
+ const stageName = detail.stage;
644
+ const actionName = detail.action;
645
+ const environment = stageName ? deploys.get(stageName) : void 0;
646
+ let commitTimestamp;
647
+ let commitCount;
648
+ if (commitSha && process.env.DORA_ENABLED === "true") {
649
+ const src = classifySource(revisionUrl);
650
+ const info = await resolveCommitInfo(pipelineId, orgId, event.region, { sha: commitSha, ...src });
651
+ commitTimestamp = info.commitTimestamp;
652
+ commitCount = info.commitCount;
653
+ }
654
+ return {
655
+ orgId,
656
+ pipelineId,
657
+ eventSource,
658
+ eventType,
659
+ status: state,
660
+ executionId,
661
+ stageName,
662
+ actionName,
663
+ errorMessage,
664
+ startedAt,
665
+ completedAt,
666
+ durationMs,
667
+ ...commitSha && { commitSha },
668
+ ...commitRef && { commitRef },
669
+ ...commitTimestamp && { commitTimestamp },
670
+ ...commitCount && { commitCount },
671
+ ...environment && { environment },
672
+ detail
673
+ };
674
+ }
675
+
676
+ // src/index.ts
677
+ var handler = async (event) => {
678
+ const baseUrl = process.env.PLATFORM_BASE_URL;
679
+ if (!baseUrl) throw new Error("PLATFORM_BASE_URL environment variable is required");
680
+ const events = (await Promise.all(event.Records.map(parseRecord))).filter((e) => e !== null);
681
+ if (events.length === 0) {
682
+ log.info("No resolvable CodePipeline events in batch");
683
+ return;
684
+ }
685
+ const payloadEvents = events.map(({ orgId: _orgId, ...rest }) => rest);
686
+ const post = (token) => fetch(`${baseUrl}/api/reports/events`, {
687
+ method: "POST",
688
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
689
+ body: JSON.stringify({ events: payloadEvents })
690
+ });
691
+ let res = await post(await getAuthToken());
692
+ if (res.status === 401 || res.status === 403) {
693
+ log.warn("Reporting API auth failed; re-reading the stored key and retrying once", { status: res.status });
694
+ invalidateCredential();
695
+ res = await post(await getAuthToken());
696
+ }
697
+ if (!res.ok) {
698
+ const body = await res.text().catch(() => "");
699
+ log.error(`Reporting API returned ${res.status}`, { body });
700
+ throw new Error(`Reporting API failed: ${res.status}`);
701
+ }
702
+ const result = await res.json().catch(() => ({}));
703
+ log.info(`Ingested ${events.length} events`, { inserted: result.data });
704
+ recordForwarded(events);
705
+ await reportHealthAndRedrive(baseUrl, event.Records[0]?.eventSourceARN);
706
+ };
707
+ function _resetForTests() {
708
+ _resetTagsForTests();
709
+ _resetScmForTests();
710
+ _resetHealthForTests();
711
+ _resetAuthForTests();
712
+ }
713
+ export {
714
+ _resetForTests,
715
+ handler
948
716
  };
949
- /**
950
- * @internal Test-only: reset all module-level caches/throttle state so tests are
951
- * isolated from one another. Stripped from the published .d.ts (stripInternal).
952
- */
953
- export function _resetForTests() {
954
- resolvedByArn.clear();
955
- commitResultCache.clear();
956
- lastShaByPipeline.clear();
957
- commitMetaCache.clear();
958
- scmCooldownUntil = 0;
959
- githubTokenByOrg.clear();
960
- forwardedByOrg.clear();
961
- lastEventAtByOrg.clear();
962
- lastHealthAt = 0;
963
- lastRedriveAt = 0;
964
- cachedKey = null;
965
- cachedToken = null;
966
- cachedTokenExpiresAt = 0;
967
- }
968
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0Qzs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FpQkc7QUFFSCxPQUFPLEVBQUUscUJBQXFCLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQWtCOUYsSUFBSSxlQUErQyxDQUFDO0FBQ3BELEtBQUssVUFBVSxnQkFBZ0I7SUFDN0IsSUFBSSxDQUFDLGVBQWU7UUFBRSxlQUFlLEdBQUcsQ0FBQyxNQUFNLE1BQU0sQ0FBQyw4QkFBOEIsQ0FBQyxDQUFrQyxDQUFDO0lBQ3hILE9BQU8sZUFBZSxDQUFDO0FBQ3pCLENBQUM7QUFFRCxtRkFBbUY7QUFDbkYsOEVBQThFO0FBQzlFLCtFQUErRTtBQUMvRSxLQUFLLFVBQVUsT0FBTyxDQUFJLEdBQVc7SUFDbkMsT0FBTyxDQUFDLE1BQU0sTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFNLENBQUM7QUFDbEMsQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0F3Qkc7QUFFSCxNQUFNLEdBQUcsR0FBRztJQUNWLElBQUksRUFBRSxDQUFDLEdBQVcsRUFBRSxJQUFjLEVBQUUsRUFBRSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLEdBQUcsRUFBRSxJQUFJLEVBQUUsRUFBRSxFQUFFLElBQUksSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBQ3ZJLElBQUksRUFBRSxDQUFDLEdBQVcsRUFBRSxJQUFjLEVBQUUsRUFBRSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLEdBQUcsRUFBRSxJQUFJLEVBQUUsRUFB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QUFTLEdBQUcsSUFBSSxDQUFDO0lBQ2pCLFdBQVcsR0FBRyxJQUFJLENBQUM7SUFDbkIsb0JBQW9CLEdBQUcsQ0FBQyxDQUFDO0FBQzNCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBTSU5HTEUtRklMRSBCWSBERVBMT1lNRU5UIENPTlNUUkFJTlQg4oCUIGRvIG5vdCBzcGxpdCB0aGlzIG1vZHVsZS5cbiAqXG4gKiBgcGlwZWxpbmUtbWFuYWdlcmAncyBgc2V0dXAtZXZlbnRzYCBjb21tYW5kIGRlcGxveXMgdGhpcyBMYW1iZGEgYnkgY29weWluZ1xuICogdGhlIGNvbXBpbGVkIGBsaWIvaW5kZXguanNgICoqYXMgb25lIGZpbGUqKiBpbnRvIHRoZSB6aXBcbiAqIChgY29tbWFuZHMvc2V0dXAtZXZlbnRzLnRzYDogYGNvcHlGaWxlU3luYyhoYW5kbGVyU3JjLCBpbmRleC5tanMpYCDihpIgYHppcCAtamApLlxuICogTm90aGluZyBidW5kbGVzIGl0LiBTbyB0aGUgbW9tZW50IHRoaXMgZmlsZSBnYWlucyBhIHJlbGF0aXZlIGltcG9ydCBvZiBhXG4gKiBzaWJsaW5nIG1vZHVsZSwgdGhlIGVtaXR0ZWQgYGxpYi9pbmRleC5qc2AgcmVmZXJlbmNlcyBhIGZpbGUgdGhhdCBpcyBub3QgaW5cbiAqIHRoZSB6aXAgYW5kIHRoZSBMYW1iZGEgZGllcyBhdCBpbml0IHdpdGggXCJDYW5ub3QgZmluZCBtb2R1bGUgJy4v4oCmJ1wiLlxuICpcbiAqIFRoYXQgbWFrZXMgdGhlIHVzdWFsIGh5Z2llbmUgaW5zdGluY3Qg4oCUIFwiOTczIGxpbmVzIGFuZCBuaW5lIG1vZHVsZS1sZXZlbFxuICogY2FjaGVzLCBzcGxpdCBpdCBpbnRvIHNjbS1yZXNvbHZlcnMgLyBwYXJzZS1yZWNvcmQgLyBkbHEtaGVhbHRoXCIg4oCUIGFcbiAqIHByb2R1Y3Rpb24gb3V0YWdlIHJhdGhlciB0aGFuIGEgY2xlYW51cC4gSXQgaXMgYWxzbyB3aHkgYF9yZXNldEZvclRlc3RzKClgXG4gKiBleGlzdHM6IHdpdGggZXZlcnkgY2FjaGUgaW4gb25lIG1vZHVsZSwgdGVzdHMgaGF2ZSBubyBvdGhlciBzZWFtLlxuICpcbiAqIElmIHRoaXMgZ2VudWluZWx5IG5lZWRzIHNwbGl0dGluZywgYWRkIGEgYnVuZGxpbmcgc3RlcCAoZXNidWlsZCDihpIgb25lXG4gKiBgbGliL2luZGV4LmpzYCkgdG8gdGhlIGJ1aWxkIEZJUlNULCBhbmQgb25seSB0aGVuIGJyZWFrIHRoZSBmaWxlIHVwLlxuICovXG5cbmltcG9ydCB7IEdldFNlY3JldFZhbHVlQ29tbWFuZCwgU2VjcmV0c01hbmFnZXJDbGllbnQgfSBmcm9tICdAYXdzLXNkay9jbGllbnQtc2VjcmV0cy1tYW5hZ2VyJztcbmltcG9ydCB0eXBlIHsgU1FTRXZlbnQsIFNRU1JlY29yZCB9IGZyb20gJ2F3cy1sYW1iZGEnO1xuXG4vLyBBV1MgTGFtYmRhJ3MgTm9kZSBydW50aW1lIGJ1bmRsZXMgQGF3cy1zZGsgdjMsIHNvIHRoZSBDb2RlUGlwZWxpbmUvQ29kZUNvbW1pdC9TUVNcbi8vIGNsaWVudHMgYXJlIGxvYWRlZCBsYXppbHkgdmlhIGR5bmFtaWMgaW1wb3J0KCkg4oCUIGEgc3RhdGljIHdvcmtzcGFjZSBkZXBlbmRlbmN5IG9uXG4vLyB0aGVtIHdvdWxkIHBlcnR1cmIgdGhlIHNoYXJlZCBAYXdzLXNkayB2ZXJzaW9uIHRyZWUgYW5kIGJyZWFrIG90aGVyIHBhY2thZ2VzJ1xuLy8gdHlwaW5ncy4gVGhlIGR5bmFtaWMgaW1wb3J0IGtlZXBzIHRoZW0gb3V0IG9mIHRoZSBzdGF0aWMgZ3JhcGggd2hpbGUgc3RheWluZ1xuLy8gRVNNLWNvcnJlY3QgKG5vIGByZXF1aXJlYCBpbiBhIHR5cGU6bW9kdWxlIHBhY2thZ2UpIGFuZCBtb2NrYWJsZS4gTWluaW1hbCBsb2NhbFxuLy8gdHlwZXMga2VlcCB0aGUgY2FsbCBzaXRlcyB0eXBlLWNoZWNrZWQuIGBAYXdzLXNkay9jbGllbnQtY29kZWNvbW1pdGAgYW5kXG4vLyBgQGF3cy1zZGsvY2xpZW50LXNxc2AgYXJlIE5PVCBkZXZEZXBlbmRlbmNpZXMgb2YgdGhpcyBwYWNrYWdlICh0aGV5IHJlc29sdmUgb25seVxuLy8gaW5zaWRlIHRoZSBMYW1iZGEgcnVudGltZSAvIGplc3QgbW9ja3MpLCBzbyB0aGV5IGFyZSBpbXBvcnRlZCB0aHJvdWdoIGFcbi8vIHZhcmlhYmxlLXNwZWNpZmllciBoZWxwZXIgYmVsb3cgc28gYHRzY2AgZG9lcyBub3QgdHJ5IHRvIHJlc29sdmUgdGhlbS5cbmludGVyZmFjZSBMaXN0VGFnc091dHB1dCB7IHRhZ3M/OiBBcnJheTx7IGtleT86IHN0cmluZzsgdmFsdWU/OiBzdHJpbmcgfT4gfVxuaW50ZXJmYWNlIENvZGVQaXBlbGluZUNsaWVudExpa2UgeyBzZW5kKGNvbW1hbmQ6IHVua25vd24pOiBQcm9taXNlPExpc3RUYWdzT3V0cHV0PiB9XG5pbnRlcmZhY2UgQ29kZVBpcGVsaW5lTW9kdWxlIHtcbiAgQ29kZVBpcGVsaW5lQ2xpZW50OiBuZXcgKGNvbmZpZzogeyByZWdpb246IHN0cmluZyB9KSA9PiBDb2RlUGlwZWxpbmVDbGllbnRMaWtlO1xuICBMaXN0VGFnc0ZvclJlc291cmNlQ29tbWFuZDogbmV3IChpbnB1dDogeyByZXNvdXJjZUFybjogc3RyaW5nIH0pID0+IHVua25vd247XG59XG5sZXQgY29kZXBpcGVsaW5lTW9kOiBDb2RlUGlwZWxpbmVNb2R1bGUgfCB1bmRlZmluZWQ7XG5hc3luYyBmdW5jdGlvbiBsb2FkQ29kZVBpcGVsaW5lKCk6IFByb21pc2U8Q29kZVBpcGVsaW5lTW9kdWxlPiB7XG4gIGlmICghY29kZXBpcGVsaW5lTW9kKSBjb2RlcGlwZWxpbmVNb2QgPSAoYXdhaXQgaW1wb3J0KCdAYXdzLXNkay9jbGllbnQtY29kZXBpcGVsaW5lJykpIGFzIHVua25vd24gYXMgQ29kZVBpcGVsaW5lTW9kdWxlO1xuICByZXR1cm4gY29kZXBpcGVsaW5lTW9kO1xufVxuXG4vLyBWYXJpYWJsZS1zcGVjaWZpZXIgbG9hZGVyIGZvciBTREsgY2xpZW50cyB0aGF0IGFyZSBOT1Qgc3RhdGljIGRldkRlcGVuZGVuY2llcyBvZlxuLy8gdGhpcyBwYWNrYWdlIChwcmVzZW50IG9ubHkgaW4gdGhlIExhbWJkYSBydW50aW1lIC8gamVzdCBtb2NrcykuIFBhc3NpbmcgdGhlXG4vLyBzcGVjaWZpZXIgYXMgYSBgc3RyaW5nYCBrZWVwcyBgdHNjYCBmcm9tIHJlc29sdmluZyB0aGUgbW9kdWxlIGF0IGJ1aWxkIHRpbWUuXG5hc3luYyBmdW5jdGlvbiBsb2FkU2RrPFQ+KHBrZzogc3RyaW5nKTogUHJvbWlzZTxUPiB7XG4gIHJldHVybiAoYXdhaXQgaW1wb3J0KHBrZykpIGFzIFQ7XG59XG5cbi8qKlxuICogUGlwZWxpbmUgZXZlbnQgaW5nZXN0aW9uIExhbWJkYSBoYW5kbGVyLlxuICpcbiAqIFJlY2VpdmVzIENvZGVQaXBlbGluZS9Db2RlQnVpbGQgZXZlbnRzIGZyb20gU1FTIChzb3VyY2VkIGJ5IEV2ZW50QnJpZGdlKSxcbiAqIHBhcnNlcyB0aGVtIGludG8gYSBub3JtYWxpemVkIGZvcm1hdCwgYW5kIFBPU1RzIHRoZW0gdG8gdGhlIHJlcG9ydGluZyBzZXJ2aWNlXG4gKiB2aWEgUExBVEZPUk1fQkFTRV9VUkwuXG4gKlxuICogQXV0aGVudGljYXRpb24gKCNOMiDigJQgc2VydmljZS1hY2NvdW50IGtleXMpOlxuICogVGhlIExhbWJkYSBob2xkcyBhbiBvcGFxdWUgU0VSVklDRS1BQ0NPVU5UIEtFWSAoYHBiX3NhX+KApmApLCBuZXZlciBhIEpXVCwgYW5kXG4gKiB0cmFkZXMgaXQgYXQgcGxhdGZvcm0ncyBgL2F1dGgvdG9rZW4vZXhjaGFuZ2VgIGZvciBhIDUtbWludXRlIHRva2VuIHBlciBiYXRjaC5cbiAqIEl0IHZlcmlmaWVzIE5PVEhJTkcgbG9jYWxseTogYW4gb3BhcXVlIGtleSBjYXJyaWVzIG5vIGNsYWltcyB0byBjaGVjaywgYW5kIHRoZVxuICogZXhjaGFuZ2UgaXMgdGhlIGF1dGhvcml0eSAoYSByZXZva2VkIGtleSBzdG9wcyB3b3JraW5nIHdpdGhpbiBvbmUgdG9rZW5cbiAqIGxpZmV0aW1lLCB3aGljaCBpcyB0aGUgZW50aXJlIHBvaW50IG9mIHRoZSBzaGFwZSkuIFRoZSBrZXkgaXMgbWludGVkIGJ5XG4gKiBgcGlwZWxpbmUtbWFuYWdlciBpbmZyYSBzdG9yZS10b2tlbmAgYW5kIHJvdGF0ZWQgaW4gcGxhY2UgYnkgdGhlIHRva2VuLXJlbmV3XG4gKiBMYW1iZGEsIHNvIHRoZSBzZWNyZXQncyB2YWx1ZSBjaGFuZ2VzIHVuZGVybmVhdGggYSB3YXJtIGNvbnRhaW5lciDigJQgd2hpY2ggaXNcbiAqIHdoeSBhbiBhdXRoIGZhaWx1cmUgcmUtcmVhZHMgdGhlIFNFQ1JFVCwgbm90IGp1c3QgdGhlIHRva2VuLlxuICpcbiAqIEVudmlyb25tZW50IHZhcmlhYmxlczpcbiAqIC0gUExBVEZPUk1fQkFTRV9VUkwg4oCUIEJhc2UgVVJMIG9mIHRoZSBwbGF0Zm9ybVxuICogLSBQTEFURk9STV9BQ0NFU1NfS0VZIOKAlCB0aGUgYHBiX3NhX+KApmAga2V5IHNldCBkaXJlY3RseSAobm8gU2VjcmV0cyBNYW5hZ2VyIGNhbGwpLCBvclxuICogLSBQTEFURk9STV9TRUNSRVRfTkFNRSDigJQgU2VjcmV0cyBNYW5hZ2VyIHNlY3JldCBjb250YWluaW5nIHsgcGFzc3dvcmQ6IDxwYl9zYV8ga2V5PiB9XG4gKiAtIEVWRU5UX0RMUV9BUk4g4oCUIChvcHRpb25hbCkgb3ZlcnJpZGUgZm9yIHRoZSBkZWFkLWxldHRlciBxdWV1ZSBBUk4gdXNlZCBieSB0aGVcbiAqICAgc2VsZi1oZWFsaW5nIHJlZHJpdmU7IGRlZmF1bHRzIHRvIGA8bWFpbi1xdWV1ZS1hcm4+LWRscWAgZGVyaXZlZCBmcm9tIHRoZSBTUVNcbiAqICAgdHJpZ2dlcidzIGV2ZW50U291cmNlQVJOLlxuICovXG5cbmNvbnN0IGxvZyA9IHtcbiAgaW5mbzogKG1zZzogc3RyaW5nLCBkYXRhPzogdW5rbm93bikgPT4gY29uc29sZS5sb2coSlNPTi5zdHJpbmdpZnkoeyBsZXZlbDogJ0lORk8nLCBtZXNzYWdlOiBtc2csIGRhdGEsIHRzOiBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCkgfSkpLFxuICB3YXJuOiAobXNnOiBzdHJpbmcsIGRhdGE/OiB1bmtub3duKSA9PiBjb25zb2xlLmxvZyhKU09OLnN0cmluZ2lmeSh7IGxldmVsOiAnV0FSTicsIG1lc3NhZ2U6IG1zZywgZGF0YSwgdHM6IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKSB9KSksXG4gIGVycm9yOiAobXNnOiBzdHJpbmcsIGRhdGE/OiB1bmtub3duKSA9PiBjb25zb2xlLmVycm9yKEpTT04uc3RyaW5naWZ5KHsgbGV2ZWw6ICdFUlJPUicsIG1lc3NhZ2U6IG1zZywgZGF0YSwgdHM6IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKSB9KSksXG59O1xuXG4vLyDilIDilIAgVGFnIHJlc29sdXRpb246IHBiLnBpcGVsaW5lLWlkICsgcGIuZGVwbG95cyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcbi8vIFBpcGVsaW5lcyBhcmUgdGFnZ2VkIGF0IENESyBzeW50aCB3aXRoOlxuLy8gICBwYi5waXBlbGluZS1pZCA9IDxwbGF0Zm9ybSBwaXBlbGluZUlkPlxuLy8gICBwYi5kZXBsb3lzICAgICA9IDxzdGFnZT46PGVudj4gcGFpcnMgam9pbmVkIGJ5ICcrJywgZS5nLlxuLy8gICAgICAgICAgICAgICAgICAgIFwiRGVwbG95LXN0ZzpzdGFnaW5nK0RlcGxveS1wcm9kOnByb2R1Y3Rpb25cIlxuLy8gV2UgcmVzb2x2ZSB0aG9zZSB0YWdzIGZyb20gdGhlIGxpdmUgQ29kZVBpcGVsaW5lIChyZWdpb24tYXdhcmUpLiBUaGUgQVJOL2FjY291bnRcbi8vIG5ldmVyIGxlYXZlIEFXUywgc28gdGhlcmUncyBubyBtYXNraW5nLiBDYWNoZWQgcGVyIEFSTiAod2FybS1jb250YWluZXIpIHRvIGF2b2lkXG4vLyBoYW1tZXJpbmcgdGhlIExpc3RUYWdzIEFQSS5cbi8vXG4vLyBSZXF1aXJlcyB0aGUgTGFtYmRhIGV4ZWN1dGlvbiByb2xlIHRvIGFsbG93IGBjb2RlcGlwZWxpbmU6TGlzdFRhZ3NGb3JSZXNvdXJjZWAuXG4vLyBBY2Nlc3NEZW5pZWQgaXMgYSBjb25maWcgZXJyb3IgKGFsZXJ0IGxvdWRseSk7IGEgbWlzc2luZyBwYi5waXBlbGluZS1pZCB0YWcgaXNcbi8vIHRyZWF0ZWQgYXMgXCJub3QgeWV0IHJlZ2lzdGVyZWRcIiAoc2tpcCkuXG5cbmNvbnN0IFBJUEVMSU5FX0lEX1RBRyA9ICdwYi5waXBlbGluZS1pZCc7XG5jb25zdCBERVBMT1lTX1RBRyA9ICdwYi5kZXBsb3lzJztcbi8vIENhbm9uaWNhbCBwZXItcGlwZWxpbmUgb3JnIHRhZyAoYXBwbGllZCBhdCBzeW50aCBieSBwaXBlbGluZS1jb3JlKS4gSXQgaXMgdGhlXG4vLyBSRVNPTFZFRCBvcmcgZm9yIGV2ZXJ5IGV2ZW50IG9mIHRoaXMgcGlwZWxpbmUg4oCUIHVzZWQgdG8ga2V5IHRoZSBwZXItb3JnIEdpdEh1YlxuLy8gdG9rZW4gYW5kIHRvIGF0dHJpYnV0ZSBpbmdlc3QtaGVhbHRoLiBvcmdJZCBpcyBhIHBsYXRmb3JtIGlkLCBuZXZlciBhbiBBV1Ncbi8vIGFjY291bnQgaWQsIHNvIGl0IGlzIHNhZmUgdG8gcmVhZC9mb3J3YXJkLlxuY29uc3QgT1JHX0lEX1RBRyA9ICdPcmdJZCc7XG5jb25zdCBjbGllbnRzQnlSZWdpb24gPSBuZXcgTWFwPHN0cmluZywgQ29kZVBpcGVsaW5lQ2xpZW50TGlrZT4oKTtcbi8vIHBpcGVsaW5lSWQ9bnVsbCBpcyBhIG5lZ2F0aXZlIGNhY2hlIChyZXNvbHZlZC1idXQtdW50YWdnZWQpOyBgdHNgIGxldHMgbmVnYXRpdmVzXG4vLyBleHBpcmUgc28gYSBwaXBlbGluZSB0YWdnZWQgQUZURVIgaXRzIGZpcnN0IGV2ZW50IGJlY29tZXMgcmVzb2x2YWJsZSB3aXRob3V0XG4vLyByZWN5Y2xpbmcgdGhlIHdhcm0gY29udGFpbmVyLiBQb3NpdGl2ZSByZXN1bHRzIHN0YXkgY2FjaGVkIGZvciB0aGUgbGlmZXRpbWUuXG5jb25zdCByZXNvbHZlZEJ5QXJuID0gbmV3IE1hcDxzdHJpbmcsIHsgcGlwZWxpbmVJZDogc3RyaW5nIHwgbnVsbDsgb3JnSWQ6IHN0cmluZyB8IG51bGw7IGRlcGxveXM6IE1hcDxzdHJpbmcsIHN0cmluZz47IHRzOiBudW1iZXIgfT4oKTtcbmNvbnN0IE5FR19DQUNIRV9UVExfTVMgPSA1ICogNjAgKiAxMDAwO1xuXG4vKiogUmVzb2x2ZWQgcGlwZWxpbmUgbWV0YWRhdGEgcmVhZCBmcm9tIHRoZSBDb2RlUGlwZWxpbmUgcmVzb3VyY2UgdGFncy4gKi9cbmludGVyZmFjZSBSZXNvbHZlZFBpcGVsaW5lIHtcbiAgLyoqIFBsYXRmb3JtIHBpcGVsaW5lSWQgKHBiLnBpcGVsaW5lLWlkIHRhZyk7IG51bGwgd2hlbiB1bnRhZ2dlZC91bnJlZ2lzdGVyZWQuICovXG4gIHBpcGVsaW5lSWQ6IHN0cmluZyB8IG51bGw7XG4gIC8qKiBQbGF0Zm9ybSBvcmdJZCAoT3JnSWQgdGFnKTsgbnVsbCB3aGVuIHRoZSBwaXBlbGluZSBjYXJyaWVzIG5vIG9yZyB0YWcuICovXG4gIG9yZ0lkOiBzdHJpbmcgfCBudWxsO1xuICAvKiogTWFwIG9mIGRlcGxveS1zdGFnZSBuYW1lIOKGkiBlbnZpcm9ubWVudCAocGFyc2VkIGZyb20gcGIuZGVwbG95cykuIEVtcHR5IHdoZW4gbm9uZS4gKi9cbiAgZGVwbG95czogTWFwPHN0cmluZywgc3RyaW5nPjtcbn1cblxuLyoqIENhcCBvbiBjb21taXQvcmVmL2Vudmlyb25tZW50IGZpZWxkcyDigJQgdGhlIGluZ2VzdCBjb250cmFjdCBhbGxvd3MgbWF4IDI1NS4gKi9cbmNvbnN0IE1BWF9BVFRSID0gMjU1O1xuXG4vKipcbiAqIFBhcnNlIHRoZSBgcGIuZGVwbG95c2AgdGFnIHZhbHVlIGludG8gYSBzdGFnZeKGkmVudmlyb25tZW50IG1hcC4gVGhlIHZhbHVlIGlzXG4gKiBgPHN0YWdlPjo8ZW52PmAgcGFpcnMgam9pbmVkIGJ5IGArYCAoQ29kZVBpcGVsaW5lLXRhZy1zYWZlOyBubyBKU09OKS4gQSBzdGFnZVxuICogcHJlc2VudCBoZXJlIGlzIGEgZGVwbG95OyBpdHMgbWFwcGVkIGVudmlyb25tZW50IGlzIHdoYXQgdGhlIGZvcndhcmRlciBzdGFtcHMgb25cbiAqIHRoYXQgc3RhZ2UncyBTVEFHRS9BQ1RJT04gZXZlbnRzIChET1JBIGRlcGxveSBhdHRyaWJ1dGlvbikuIE1hbGZvcm1lZCBwYWlycyBhcmVcbiAqIHNraXBwZWQgZGVmZW5zaXZlbHkuXG4gKi9cbmZ1bmN0aW9uIHBhcnNlRGVwbG95cyh2YWx1ZTogc3RyaW5nIHwgdW5kZWZpbmVkKTogTWFwPHN0cmluZywgc3RyaW5nPiB7XG4gIGNvbnN0IG1hcCA9IG5ldyBNYXA8c3RyaW5nLCBzdHJpbmc+KCk7XG4gIGlmICghdmFsdWUpIHJldHVybiBtYXA7XG4gIGZvciAoY29uc3QgcGFpciBvZiB2YWx1ZS5zcGxpdCgnKycpKSB7XG4gICAgY29uc3QgaWR4ID0gcGFpci5pbmRleE9mKCc6Jyk7XG4gICAgaWYgKGlkeCA8PSAwKSBjb250aW51ZTtcbiAgICBjb25zdCBzdGFnZSA9IHBhaXIuc2xpY2UoMCwgaWR4KS50cmltKCk7XG4gICAgY29uc3QgZW52ID0gcGFpci5zbGljZShpZHggKyAxKS50cmltKCk7XG4gICAgaWYgKHN0YWdlICYmIGVudikgbWFwLnNldChzdGFnZSwgZW52LnNsaWNlKDAsIE1BWF9BVFRSKSk7XG4gIH1cbiAgcmV0dXJuIG1hcDtcbn1cblxuYXN5bmMgZnVuY3Rpb24gcGlwZWxpbmVDbGllbnQocmVnaW9uOiBzdHJpbmcpOiBQcm9taXNlPENvZGVQaXBlbGluZUNsaWVudExpa2U+IHtcbiAgbGV0IGNsaWVudCA9IGNsaWVudHNCeVJlZ2lvbi5nZXQocmVnaW9uKTtcbiAgaWYgKCFjbGllbnQpIHtcbiAgICBjb25zdCB7IENvZGVQaXBlbGluZUNsaWVudCB9ID0gYXdhaXQgbG9hZENvZGVQaXBlbGluZSgpO1xuICAgIGNsaWVudCA9IG5ldyBDb2RlUGlwZWxpbmVDbGllbnQoeyByZWdpb24gfSk7XG4gICAgY2xpZW50c0J5UmVnaW9uLnNldChyZWdpb24sIGNsaWVudCk7XG4gIH1cbiAgcmV0dXJuIGNsaWVudDtcbn1cblxuLyoqXG4gKiBSZXNvbHZlIGEgQ29kZVBpcGVsaW5lJ3MgdGFncyDihpIgdGhlIHBsYXRmb3JtIHBpcGVsaW5lSWQgKHBiLnBpcGVsaW5lLWlkKSBwbHVzIHRoZVxuICogZGVwbG95LXN0YWdl4oaSZW52aXJvbm1lbnQgbWFwIChwYi5kZXBsb3lzKS4gYHBpcGVsaW5lSWRgIGlzIG51bGwgd2hlbiB0aGUgcGlwZWxpbmVcbiAqIGhhcyBubyBwYi5waXBlbGluZS1pZCB0YWcgKHVucmVnaXN0ZXJlZCkuIFRocm93cyBvbiBBY2Nlc3NEZW5pZWQgc28gYSBtaXNzaW5nIElBTVxuICogZ3JhbnQgc3VyZmFjZXMgbG91ZGx5IGluc3RlYWQgb2Ygc2lsZW50bHkgZHJvcHBpbmcgZXZlcnkgZXZlbnQuXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIHJlc29sdmVQaXBlbGluZShhcm46IHN0cmluZywgcmVnaW9uOiBzdHJpbmcpOiBQcm9taXNlPFJlc29sdmVkUGlwZWxpbmU+IHtcbiAgY29uc3QgY2FjaGVkID0gcmVzb2x2ZWRCeUFybi5nZXQoYXJuKTtcbiAgLy8gU2VydmUgYSBwb3NpdGl2ZSBoaXQgZm9yIHRoZSBjb250YWluZXIgbGlmZXRpbWU7IHNlcnZlIGEgbmVnYXRpdmUgaGl0IG9ubHlcbiAgLy8gdW50aWwgaXQgZXhwaXJlcyAodGhlbiByZS1yZXNvbHZlLCBpbiBjYXNlIHRoZSBwaXBlbGluZSB3YXMgc2luY2UgdGFnZ2VkKS5cbiAgaWYgKGNhY2hlZCAmJiAoY2FjaGVkLnBpcGVsaW5lSWQgIT09IG51bGwgfHwgRGF0ZS5ub3coKSAtIGNhY2hlZC50cyA8IE5FR19DQUNIRV9UVExfTVMpKSB7XG4gICAgcmV0dXJuIHsgcGlwZWxpbmVJZDogY2FjaGVkLnBpcGVsaW5lSWQsIG9yZ0lkOiBjYWNoZWQub3JnSWQsIGRlcGxveXM6IGNhY2hlZC5kZXBsb3lzIH07XG4gIH1cbiAgdHJ5IHtcbiAgICBjb25zdCBjbGllbnQgPSBhd2FpdCBwaXBlbGluZUNsaWVudChyZWdpb24pO1xuICAgIGNvbnN0IHsgTGlzdFRhZ3NGb3JSZXNvdXJjZUNvbW1hbmQgfSA9IGF3YWl0IGxvYWRDb2RlUGlwZWxpbmUoKTtcbiAgICBjb25zdCBvdXQgPSBhd2FpdCBjbGllbnQuc2VuZChuZXcgTGlzdFRhZ3NGb3JSZXNvdXJjZUNvbW1hbmQoeyByZXNvdXJjZUFybjogYXJuIH0pKTtcbiAgICBjb25zdCBwaXBlbGluZUlkID0gb3V0LnRhZ3M/LmZpbmQodCA9PiB0LmtleSA9PT0gUElQRUxJTkVfSURfVEFHKT8udmFsdWUgPz8gbnVsbDtcbiAgICBjb25zdCBvcmdJZCA9IG91dC50YWdzPy5maW5kKHQgPT4gdC5rZXkgPT09IE9SR19JRF9UQUcpPy52YWx1ZSA/PyBudWxsO1xuICAgIGNvbnN0IGRlcGxveXMgPSBwYXJzZURlcGxveXMob3V0LnRhZ3M/LmZpbmQodCA9PiB0LmtleSA9PT0gREVQTE9ZU19UQUcpPy52YWx1ZSk7XG4gICAgaWYgKCFwaXBlbGluZUlkKSBsb2cud2FybignUGlwZWxpbmUgbWlzc2luZyBwYi5waXBlbGluZS1pZCB0YWcg4oCUIHNraXBwaW5nIChyZWdpc3RlciBpdD8pJywgeyBhcm4gfSk7XG4gICAgcmVzb2x2ZWRCeUFybi5zZXQoYXJuLCB7IHBpcGVsaW5lSWQsIG9yZ0lkLCBkZXBsb3lzLCB0czogRGF0ZS5ub3coKSB9KTtcbiAgICByZXR1cm4geyBwaXBlbGluZUlkLCBvcmdJZCwgZGVwbG95cyB9O1xuICB9IGNhdGNoIChlcnIpIHtcbiAgICBjb25zdCBuYW1lID0gKGVyciBhcyB7IG5hbWU/OiBzdHJpbmcgfSk/Lm5hbWU7XG4gICAgaWYgKG5hbWUgPT09ICdBY2Nlc3NEZW5pZWRFeGNlcHRpb24nKSB7XG4gICAgICAvLyBEb24ndCBjYWNoZSDigJQgdGhpcyBpcyBhIGZpeGFibGUgbWlzY29uZmlnLCBub3QgYSBwcm9wZXJ0eSBvZiB0aGUgcGlwZWxpbmUuXG4gICAgICBsb2cuZXJyb3IoJ0FjY2Vzc0RlbmllZCBjYWxsaW5nIGNvZGVwaXBlbGluZTpMaXN0VGFnc0ZvclJlc291cmNlIOKAlCBncmFudCBpdCB0byB0aGUgTGFtYmRhIHJvbGUnLCB7IGFybiwgZXJyb3I6IG5hbWUgfSk7XG4gICAgICB0aHJvdyBlcnI7XG4gICAgfVxuICAgIC8vIFRyYW5zaWVudC9pbmZyYSBlcnJvcnMgKHRocm90dGxpbmcsIHRpbWVvdXRzLCA1eHgpIE1VU1QgcHJvcGFnYXRlIHNvIHRoZSBTUVNcbiAgICAvLyBiYXRjaCBpcyByZXRyaWVkIHJhdGhlciB0aGFuIHNpbGVudGx5IGRyb3BwaW5nIHRoZSBldmVudCDigJQgYW4gZXZlbnQgZHJvcHBlZFxuICAgIC8vIEJFRk9SRSBpdCByZWFjaGVzIHRoZSBpbmdlc3QgaXMgbG9zdCBwZXJtYW5lbnRseSAodGhlIGluZ2VzdCdzIHBhcnRpYWwtdW5pcXVlXG4gICAgLy8gaW5kZXggb25seSBkZWR1cGVzIGV2ZW50cyB0aGF0IHdlcmUgYWN0dWFsbHkgUE9TVGVkKS4gT25seSBzd2FsbG93IHdoZW4gdGhlXG4gICAgLy8gZmFpbHVyZSBpcyBhIGRlZmluaXRpdmUgYW5zd2VyIHRoYXQgdGhpcyBBUk4gaGFzIG5vIHVzYWJsZSB0YWcuXG4gICAgY29uc3QgZSA9IGVyciBhcyB7IG5hbWU/OiBzdHJpbmc7ICRyZXRyeWFibGU/OiB1bmtub3duOyAkbWV0YWRhdGE/OiB7IGh0dHBTdGF0dXNDb2RlPzogbnVtYmVyIH0gfTtcbiAgICBjb25zdCBzdGF0dXMgPSBlLiRtZXRhZGF0YT8uaHR0cFN0YXR1c0NvZGU7XG4gICAgY29uc3QgcmV0cnlhYmxlID0gZS4kcmV0cnlhYmxlICE9IG51bGxcbiAgICAgIHx8ICh0eXBlb2Ygc3RhdHVzID09PSAnbnVtYmVyJyAmJiBzdGF0dXMgPj0gNTAwKVxuICAgICAgfHwgL3Rocm90dGx8dGltZW91dHx0b29tYW55cmVxdWVzdHN8c2VydmljZXVuYXZhaWxhYmxlfGVjb25ucmVzZXR8bmV0d29ya2luZ2Vycm9yL2kudGVzdChuYW1lID8/ICcnKTtcbiAgICBpZiAocmV0cnlhYmxlKSB7XG4gICAgICBsb2cuZXJyb3IoJ1RyYW5zaWVudCBlcnJvciByZXNvbHZpbmcgcGIucGlwZWxpbmUtaWQgdGFnIOKAlCB3aWxsIHJldHJ5IHRoZSBiYXRjaCcsIHsgYXJuLCBlcnJvcjogbmFtZSA/PyBTdHJpbmcoZXJyKSB9KTtcbiAgICAgIHRocm93IGVycjtcbiAgICB9XG4gICAgbG9nLmVycm9yKCdGYWlsZWQgdG8gcmVzb2x2ZSBwYi5waXBlbGluZS1pZCB0YWcnLCB7IGFybiwgZXJyb3I6IG5hbWUgPz8gU3RyaW5nKGVycikgfSk7XG4gICAgcmV0dXJuIHsgcGlwZWxpbmVJZDogbnVsbCwgb3JnSWQ6IG51bGwsIGRlcGxveXM6IG5ldyBNYXAoKSB9O1xuICB9XG59XG5cbi8vIOKUgOKUgOKUgCBBdXRoIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuLy9cbi8vIFRoZSBzdG9yZWQgY3JlZGVudGlhbCBpcyBhbiBPUEFRVUUgc2VydmljZS1hY2NvdW50IGtleSAoYHBiX3NhX+KApmApLCB0cmFkZWQgYXRcbi8vIHBsYXRmb3JtIGZvciBhIHNob3J0LWxpdmVkIEpXVC4gTm90aGluZyBpcyB2ZXJpZmllZCBoZXJlOiBhbiBvcGFxdWUga2V5IGhhcyBub1xuLy8gY2xhaW1zIGFuZCBubyBzaWduYXR1cmUsIHNvIHRoZXJlIGlzIG5vdGhpbmcgYSBsb2NhbCBjaGVjayBjb3VsZCB0ZWxsIHlvdSB0aGF0XG4vLyB0aGUgZXhjaGFuZ2UgZG9lcyBub3QgdGVsbCB5b3UgYmV0dGVyIOKAlCBhbmQgdGhlIGV4Y2hhbmdlIHJlLXJlYWRzIHRoZSBhY2NvdW50LFxuLy8gaXRzIG9yZyBhbmQgdGhlIGtleSdzIG93biBzdGF0ZSBldmVyeSB0aW1lLCB3aGljaCBpcyB3aGF0IG1ha2VzIGEgcmV2b2NhdGlvblxuLy8gdGFrZSBlZmZlY3Qgd2l0aGluIG9uZSB0b2tlbiBsaWZldGltZS5cbi8vXG4vLyBUaGlzIGNvZGUgaXMgZGVsaWJlcmF0ZWx5IHNlbGYtY29udGFpbmVkIChzZWUgdGhlIGZpbGUgaGVhZGVyOiB0aGUgTGFtYmRhIHNoaXBzXG4vLyBhcyBPTkUgZmlsZSksIHNvIGl0IGRvZXMgbm90IHVzZSBhcGktY29yZSdzIGV4Y2hhbmdlIGNsaWVudC4gSXQga2VlcHMgdGhlIHNhbWVcbi8vIGNvbnRyYWN0OiBjYWNoZSB0aGUgdG9rZW4gaW4gdGhlIHdhcm0gY29udGFpbmVyLCByZWZyZXNoIEVBUkxZIChiZWZvcmUgZXhwaXJ5KVxuLy8gc28gYSBiYXRjaCBuZXZlciBwcmVzZW50cyBhIHRva2VuIHRoYXQgZGllcyBtaWQtZmxpZ2h0LCBhbmQgZmFpbCB0aGUgYmF0Y2ggb25cbi8vIGFuIGV4Y2hhbmdlIGZhaWx1cmUgcmF0aGVyIHRoYW4gc2hpcHBpbmcgZXZlbnRzIHVuYXV0aGVudGljYXRlZC5cblxuLyoqIFJlZnJlc2ggYXQgdGhpcyBmcmFjdGlvbiBvZiB0aGUgdG9rZW4ncyBsaWZlIOKAlCBuZXZlciBhdCB0aGUgbGFzdCBtb21lbnQuICovXG5jb25zdCBUT0tFTl9SRUZSRVNIX0ZSQUNUSU9OID0gMC44O1xuLyoqIEZsb29yIG9uIHRoZSBjYWNoZWQgbGlmZXRpbWUsIHNvIGEgc3VycHJpc2luZ2x5IHNob3J0IFRUTCBjYW4ndCBjYXVzZSBhXG4gKiAgcmVmcmVzaCBzdG9ybSAob25lIGV4Y2hhbmdlIHBlciBiYXRjaCBhdCB3b3JzdCkuICovXG5jb25zdCBNSU5fVE9LRU5fQ0FDSEVfTVMgPSAzMF8wMDA7XG4vKiogUGVyLXJlcXVlc3QgdGltZW91dCBmb3IgdGhlIGV4Y2hhbmdlIGNhbGwuICovXG5jb25zdCBFWENIQU5HRV9USU1FT1VUX01TID0gNTAwMDtcblxuLyoqIEFjY2Vzcy1rZXkgcHJlZml4ZXMgcGxhdGZvcm0gaXNzdWVzLiBBIHN0b3JlZCBKV1QgaXMgTk9UIG9uZSBvZiB0aGVzZS4gKi9cbmNvbnN0IEFDQ0VTU19LRVlfUFJFRklYRVMgPSBbJ3BiX3NhXycsICdwYl9wYXRfJ107XG5cbmxldCBjYWNoZWRLZXk6IHN0cmluZyB8IG51bGwgPSBudWxsO1xubGV0IGNhY2hlZFRva2VuOiBzdHJpbmcgfCBudWxsID0gbnVsbDtcbmxldCBjYWNoZWRUb2tlbkV4cGlyZXNBdCA9IDA7XG5cbi8qKlxuICogUmVhZCAoYW5kIGNhY2hlKSB0aGUgc2VydmljZS1hY2NvdW50IGtleTogYFBMQVRGT1JNX0FDQ0VTU19LRVlgIGlmIHNldCxcbiAqIG90aGVyd2lzZSB0aGUgYHBhc3N3b3JkYCBmaWVsZCBvZiB0aGUgU2VjcmV0cyBNYW5hZ2VyIHNlY3JldCBuYW1lZCBieVxuICogYFBMQVRGT1JNX1NFQ1JFVF9OQU1FYC5cbiAqXG4gKiBGYWlscyBDTE9TRUQgYW5kIExPVURMWSBvbiBhIHN0b3JlZCBKV1QuIEFmdGVyIHRoZSAjTjIgY3V0b3ZlciB0aGUgc2VjcmV0IGhvbGRzXG4gKiBhIGtleSwgYW5kIGEgZGVwbG95bWVudCBzdGlsbCBjYXJyeWluZyB0aGUgb2xkIG1hY2hpbmUtc2Vzc2lvbiBKV1Qgd291bGRcbiAqIG90aGVyd2lzZSBmYWlsIGxhdGVyIGFzIGFuIG9wYXF1ZSA0MDEgZnJvbSB0aGUgcmVwb3J0aW5nIEFQSSDigJQgdGhyZWUgbGF5ZXJzXG4gKiBhd2F5IGZyb20gdGhlIHRoaW5nIGFuIG9wZXJhdG9yIGhhcyB0byBmaXguXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGdldEFjY2Vzc0tleSgpOiBQcm9taXNlPHN0cmluZz4ge1xuICBpZiAoY2FjaGVkS2V5KSByZXR1cm4gY2FjaGVkS2V5O1xuXG4gIGNvbnN0IGRpcmVjdCA9IHByb2Nlc3MuZW52LlBMQVRGT1JNX0FDQ0VTU19LRVk7XG4gIGlmIChkaXJlY3QpIHtcbiAgICBhc3NlcnRBY2Nlc3NLZXkoZGlyZWN0LCAnUExBVEZPUk1fQUNDRVNTX0tFWScpO1xuICAgIGNhY2hlZEtleSA9IGRpcmVjdDtcbiAgICBsb2cuaW5mbygnVXNpbmcgUExBVEZPUk1fQUNDRVNTX0tFWSBmcm9tIGVudmlyb25tZW50Jyk7XG4gICAgcmV0dXJuIGNhY2hlZEtleTtcbiAgfVxuXG4gIGNvbnN0IHNlY3JldE5hbWUgPSBwcm9jZXNzLmVudi5QTEFURk9STV9TRUNSRVRfTkFNRTtcbiAgaWYgKCFzZWNyZXROYW1lKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdQTEFURk9STV9BQ0NFU1NfS0VZIG9yIFBMQVRGT1JNX1NFQ1JFVF9OQU1FIGVudmlyb25tZW50IHZhcmlhYmxlIGlzIHJlcXVpcmVkJyk7XG4gIH1cblxuICBsb2cuaW5mbyhgRmV0Y2hpbmcgYWNjZXNzIGtleSBmcm9tIFNlY3JldHMgTWFuYWdlcjogJHtzZWNyZXROYW1lfWApO1xuICBjb25zdCBjbGllbnQgPSBuZXcgU2VjcmV0c01hbmFnZXJDbGllbnQoe30pO1xuICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGNsaWVudC5zZW5kKG5ldyBHZXRTZWNyZXRWYWx1ZUNvbW1hbmQoeyBTZWNyZXRJZDogc2VjcmV0TmFtZSB9KSk7XG4gIGlmICghcmVzcG9uc2UuU2VjcmV0U3RyaW5nKSB0aHJvdyBuZXcgRXJyb3IoYFNlY3JldCBcIiR7c2VjcmV0TmFtZX1cIiBpcyBlbXB0eWApO1xuXG4gIGNvbnN0IHNlY3JldCA9IEpTT04ucGFyc2UocmVzcG9uc2UuU2VjcmV0U3RyaW5nKSBhcyBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+O1xuICAvLyBgcGFzc3dvcmRgIHN0YXlzIHRoZSBjYW5vbmljYWwgZmllbGQgKENvZGVCdWlsZCdzIHNlY3JldHNNYW5hZ2VyQ3JlZGVudGlhbHNcbiAgLy8gYW5kIHRoZSBwbHVnaW4tbG9va3VwIExhbWJkYSByZWFkIHRoZSBzYW1lIG9uZSkg4oCUIG9ubHkgaXRzIFZBTFVFIGNoYW5nZWQsXG4gIC8vIGZyb20gYSBKV1QgdG8gYW4gb3BhcXVlIGtleS5cbiAgY29uc3Qga2V5ID0gc2VjcmV0LnBhc3N3b3JkO1xuICBpZiAoIWtleSkge1xuICAgIHRocm93IG5ldyBFcnJvcihgU2VjcmV0IFwiJHtzZWNyZXROYW1lfVwiIG1pc3NpbmcgcGFzc3dvcmQg4oCUIHJ1biBcInBpcGVsaW5lLW1hbmFnZXIgaW5mcmEgc3RvcmUtdG9rZW5cIiB0byBwcm92aXNpb24gYSBzZXJ2aWNlLWFjY291bnQga2V5YCk7XG4gIH1cbiAgYXNzZXJ0QWNjZXNzS2V5KGtleSwgYHNlY3JldCBcIiR7c2VjcmV0TmFtZX1cImApO1xuXG4gIGNhY2hlZEtleSA9IGtleTtcbiAgbG9nLmluZm8oJ1VzaW5nIHN0b3JlZCBzZXJ2aWNlLWFjY291bnQga2V5IGZyb20gU2VjcmV0cyBNYW5hZ2VyJyk7XG4gIHJldHVybiBjYWNoZWRLZXk7XG59XG5cbi8qKiBSZWZ1c2UgYW55dGhpbmcgdGhhdCBpcyBub3QgYW4gb3BhcXVlIGFjY2VzcyBrZXksIG5hbWluZyB3aGVyZSBpdCBjYW1lIGZyb20uICovXG5mdW5jdGlvbiBhc3NlcnRBY2Nlc3NLZXkodmFsdWU6IHN0cmluZywgc291cmNlOiBzdHJpbmcpOiB2b2lkIHtcbiAgaWYgKEFDQ0VTU19LRVlfUFJFRklYRVMuc29tZSgocCkgPT4gdmFsdWUuc3RhcnRzV2l0aChwKSkpIHJldHVybjtcbiAgY29uc3QgbG9va3NMaWtlSnd0ID0gdmFsdWUuc3BsaXQoJy4nKS5sZW5ndGggPT09IDM7XG4gIHRocm93IG5ldyBFcnJvcihcbiAgICBgJHtzb3VyY2V9IGRvZXMgbm90IGhvbGQgYW4gb3BhcXVlIGFjY2VzcyBrZXkgKGV4cGVjdGVkIGEgXCJwYl9zYV/igKZcIiB2YWx1ZSlgXG4gICAgKyAobG9va3NMaWtlSnd0XG4gICAgICA/ICcg4oCUIGl0IHN0aWxsIGhvbGRzIGEgSldUIGZyb20gYmVmb3JlIHRoZSBzZXJ2aWNlLWFjY291bnQgY3V0b3Zlci4gUmUtcnVuIFwicGlwZWxpbmUtbWFuYWdlciBpbmZyYSBzdG9yZS10b2tlblwiIHRvIHJlaXNzdWUgaXQgYXMgYSBrZXkuJ1xuICAgICAgOiAnLicpLFxuICApO1xufVxuXG4vKipcbiAqIFRyYWRlIHRoZSBzdG9yZWQga2V5IGZvciBhIHNob3J0LWxpdmVkIHBsYXRmb3JtIHRva2VuLCBjYWNoZWQgcGVyIHdhcm1cbiAqIGNvbnRhaW5lciB1bnRpbCBpdCBpcyA4MCUgdGhyb3VnaCBpdHMgbGlmZS4gVGhyb3dzIG9uIGFueSBmYWlsdXJlLCB3aGljaCBmYWlsc1xuICogdGhlIGJhdGNoIHNvIFNRUyByZXRyaWVzIGl0IOKAlCBhbiB1bmF1dGhlbnRpY2F0ZWQgc2hpcCBpcyBuZXZlciBhbiBvcHRpb24uXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGdldEF1dGhUb2tlbigpOiBQcm9taXNlPHN0cmluZz4ge1xuICBpZiAoY2FjaGVkVG9rZW4gJiYgRGF0ZS5ub3coKSA8IGNhY2hlZFRva2VuRXhwaXJlc0F0KSByZXR1cm4gY2FjaGVkVG9rZW47XG5cbiAgY29uc3QgYmFzZVVybCA9IChwcm9jZXNzLmVudi5QTEFURk9STV9CQVNFX1VSTCB8fCAnJykucmVwbGFjZSgvXFwvKyQvLCAnJyk7XG4gIGlmICghYmFzZVVybCkgdGhyb3cgbmV3IEVycm9yKCdQTEFURk9STV9CQVNFX1VSTCBlbnZpcm9ubWVudCB2YXJpYWJsZSBpcyByZXF1aXJlZCcpO1xuICBjb25zdCBrZXkgPSBhd2FpdCBnZXRBY2Nlc3NLZXkoKTtcblxuICBjb25zdCBjb250cm9sbGVyID0gbmV3IEFib3J0Q29udHJvbGxlcigpO1xuICBjb25zdCB0aW1lciA9IHNldFRpbWVvdXQoKCkgPT4gY29udHJvbGxlci5hYm9ydCgpLCBFWENIQU5HRV9USU1FT1VUX01TKTtcbiAgbGV0IHJlczogUmVzcG9uc2U7XG4gIHRyeSB7XG4gICAgcmVzID0gYXdhaXQgZmV0Y2goYCR7YmFzZVVybH0vYXBpL2F1dGgvdG9rZW4vZXhjaGFuZ2VgLCB7XG4gICAgICBtZXRob2Q6ICdQT1NUJyxcbiAgICAgIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJywgJ0FjY2VwdCc6ICdhcHBsaWNhdGlvbi9qc29uJyB9LFxuICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoeyBrZXkgfSksXG4gICAgICBzaWduYWw6IGNvbnRyb2xsZXIuc2lnbmFsLFxuICAgIH0pO1xuICB9IGZpbmFsbHkge1xuICAgIGNsZWFyVGltZW91dCh0aW1lcik7XG4gIH1cblxuICBpZiAoIXJlcy5vaykge1xuICAgIC8vIEEgcmVqZWN0ZWQga2V5IGlzIHRoZSBvbmUgZmFpbHVyZSBhbiBvcGVyYXRvciBoYXMgdG8gYWN0IG9uLCBhbmQgcGxhdGZvcm1cbiAgICAvLyBkZWxpYmVyYXRlbHkgYW5zd2VycyBldmVyeSByZWZ1c2FsIHRoZSBzYW1lIHdheSDigJQgc28gc2F5IHdoYXQgdG8gY2hlY2tcbiAgICAvLyByYXRoZXIgdGhhbiBlY2hvaW5nIGFuIHVuZGlmZmVyZW50aWF0ZWQgNDAxLlxuICAgIGlmIChyZXMuc3RhdHVzID09PSA0MDEgfHwgcmVzLnN0YXR1cyA9PT0gNDAzKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgIGBBY2Nlc3Mta2V5IGV4Y2hhbmdlIHJlZnVzZWQgKCR7cmVzLnN0YXR1c30pIOKAlCB0aGUgc3RvcmVkIGtleSBpcyB1bmtub3duLCByZXZva2VkLCBleHBpcmVkLCBgXG4gICAgICAgICsgJ2l0cyBzZXJ2aWNlIGFjY291bnQgaXMgZGlzYWJsZWQsIG9yIHRoaXMgYWRkcmVzcyBpcyBvdXRzaWRlIHRoZSBrZXnigJlzIElQIGFsbG93bGlzdCcsXG4gICAgICApO1xuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoYEFjY2Vzcy1rZXkgZXhjaGFuZ2UgZmFpbGVkOiAke3Jlcy5zdGF0dXN9YCk7XG4gIH1cblxuICBjb25zdCBib2R5ID0gYXdhaXQgcmVzLmpzb24oKS5jYXRjaCgoKSA9PiAoe30pKSBhcyB7IGRhdGE/OiB7IGFjY2Vzc1Rva2VuPzogc3RyaW5nOyBleHBpcmVzSW4/OiBudW1iZXIgfSB9O1xuICBjb25zdCBhY2Nlc3NUb2tlbiA9IGJvZHkuZGF0YT8uYWNjZXNzVG9rZW47XG4gIGlmICghYWNjZXNzVG9rZW4pIHRocm93IG5ldyBFcnJvcignQWNjZXNzLWtleSBleGNoYW5nZSByZXR1cm5lZCBubyBhY2Nlc3MgdG9rZW4nKTtcblxuICBjb25zdCB0dGxNcyA9ICh0eXBlb2YgYm9keS5kYXRhPy5leHBpcmVzSW4gPT09ICdudW1iZXInICYmIGJvZHkuZGF0YS5leHBpcmVzSW4gPiAwID8gYm9keS5kYXRhLmV4cGlyZXNJbiA6IDMwMCkgKiAxMDAwO1xuICBjYWNoZWRUb2tlbiA9IGFjY2Vzc1Rva2VuO1xuICBjYWNoZWRUb2tlbkV4cGlyZXNBdCA9IERhdGUubm93KCkgKyBNYXRoLm1heChNSU5fVE9LRU5fQ0FDSEVfTVMsIHR0bE1zICogVE9LRU5fUkVGUkVTSF9GUkFDVElPTik7XG4gIGxvZy5pbmZvKCdFeGNoYW5nZWQgdGhlIHN0b3JlZCBzZXJ2aWNlLWFjY291bnQga2V5IGZvciBhIHBsYXRmb3JtIHRva2VuJyk7XG4gIHJldHVybiBhY2Nlc3NUb2tlbjtcbn1cblxuLyoqXG4gKiBEcm9wIGV2ZXJ5dGhpbmcgdGhlIGNvbnRhaW5lciBpcyBob2xkaW5nIGFib3V0IGl0cyBjcmVkZW50aWFsIGFmdGVyIGEgNDAxLzQwM1xuICogZnJvbSB0aGUgQVBJLiBUaGUgdG9rZW4gaXMgZHJvcHBlZCBmb3IgdGhlIG9idmlvdXMgcmVhc29uOyB0aGUgS0VZIGlzIGRyb3BwZWRcbiAqIGJlY2F1c2UgdGhlIHRva2VuLXJlbmV3IExhbWJkYSByb3RhdGVzIHRoZSBzZWNyZXQgdW5kZXJuZWF0aCB1cyDigJQgYSB3YXJtXG4gKiBjb250YWluZXIgdGhhdCBvbmx5IHJlLWV4Y2hhbmdlZCB3b3VsZCBrZWVwIHByZXNlbnRpbmcgdGhlIHJldGlyZWQga2V5XG4gKiBmb3JldmVyLiBUaGUgbmV4dCBjYWxsIHJlLXJlYWRzIHRoZSBzZWNyZXQgYW5kIHBpY2tzIHVwIHRoZSByZXBsYWNlbWVudC5cbiAqL1xuZnVuY3Rpb24gaW52YWxpZGF0ZUNyZWRlbnRpYWwoKTogdm9pZCB7XG4gIGNhY2hlZFRva2VuID0gbnVsbDtcbiAgY2FjaGVkVG9rZW5FeHBpcmVzQXQgPSAwO1xuICAvLyBPbmx5IHRoZSBTRUNSRVQgcGF0aCBjYW4gYmUgcmVmcmVzaGVkOyBhbiBlbnYtcHJvdmlkZWQga2V5IGNhbm5vdCBjaGFuZ2VcbiAgLy8gd2l0aG91dCBhIG5ldyBjb250YWluZXIsIHNvIGtlZXAgaXQgcmF0aGVyIHRoYW4gcmUtcmVhZGluZyBhbiBlbnYgdmFyLlxuICBpZiAoIXByb2Nlc3MuZW52LlBMQVRGT1JNX0FDQ0VTU19LRVkpIGNhY2hlZEtleSA9IG51bGw7XG59XG5cbi8vIOKUgOKUgOKUgCBQaGFzZSA0OiBpbi1hY2NvdW50IGNvbW1pdC1yYW5nZSByZXNvbHV0aW9uIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuLy8gRm9yIGEgc291cmNlL2RlcGxveSBldmVudCBjYXJyeWluZyBhIGNvbW1pdFNoYSB3ZSByZXNvbHZlIHRoZSBjb21taXQgdGltZXN0YW1wXG4vLyAoYW5kIGhvdyBtYW55IGNvbW1pdHMgc2hpcHBlZCkgZm9yIHRoZSByYW5nZSBzaW5jZSB0aGUgbGFzdCByZXNvbHZlZCBzaGEgb2YgdGhlXG4vLyBzYW1lIHBpcGVsaW5lIOKAlCBlbnRpcmVseSBJTi1BQ0NPVU5ULCBwZXIgc291cmNlIHR5cGU6XG4vLyAgIOKAoiBDb2RlQ29tbWl0ICDihpIgQVdTIFNESyBHZXRDb21taXQgKHdhbGsgZmlyc3QtcGFyZW50cyBiYWNrIHRvIHRoZSBib3VuZGFyeSkuXG4vLyAgIOKAoiBHaXRIdWIvQ29kZUNvbm5lY3Rpb25zLUdpdEh1YiDihpIgR2l0SHViIFJFU1QgY29tcGFyZS9jb21taXRzIEFQSS4gQXV0aGVudGljYXRlZFxuLy8gICAgIHdpdGggdGhlIG9yZydzIGBwaXBlbGluZS1idWlsZGVyL3tvcmdJZH0vZ2l0aHViLXRva2VuYCBzZWNyZXQgd2hlbiBwcmVzZW50XG4vLyAgICAgKGRlcml2ZWQgZnJvbSBQTEFURk9STV9TRUNSRVRfTkFNRSksIG90aGVyd2lzZSBiZXN0LWVmZm9ydCB1bmF1dGhlbnRpY2F0ZWQuXG4vLyAgIOKAoiBCaXRidWNrZXQgICDihpIgQml0YnVja2V0IFJFU1QgY29tbWl0IEFQSSAoc2luZ2xlLWNvbW1pdDsgcmFuZ2Ugd2FsayBvbWl0dGVkKS5cbi8vIFRoZSBvbGRlc3QgdW5zaGlwcGVkIGNvbW1pdCdzIHRpbWVzdGFtcCDihpIgYGNvbW1pdFRpbWVzdGFtcGA7IHRoZSBudW1iZXIgb2YgY29tbWl0c1xuLy8gaW4gdGhlIHJhbmdlIOKGkiBgY29tbWl0Q291bnRgLiBFdmVyeXRoaW5nIGhlcmUgaXMgQkVTVC1FRkZPUlQ6IG9uIGFueSBmYWlsdXJlIHRoZVxuLy8gZmllbGRzIGFyZSBzaW1wbHkgb21pdHRlZCAocmVwb3J0aW5nIGZhbGxzIGJhY2sgdG8gYHVua25vd25gKS4gSXQgbXVzdCBORVZFUiBmYWlsXG4vLyB0aGUgYmF0Y2guXG4vL1xuLy8gSUFNIHJlcXVpcmVkIGJ5IHRoaXMgcGF0aCAoZG9jdW1lbnQgb25seSDigJQgZ3JhbnRlZCBvbiB0aGUgZXZlbnRzIHN0YWNrKTpcbi8vICAg4oCiIGNvZGVjb21taXQ6R2V0Q29tbWl0ICAgICAgICAgICAgKENvZGVDb21taXQgc291cmNlcylcbi8vICAg4oCiIHNlY3JldHNtYW5hZ2VyOkdldFNlY3JldFZhbHVlIG9uIGBwaXBlbGluZS1idWlsZGVyLyovZ2l0aHViLXRva2VuYCAodG9rZW4gU0NNIHNvdXJjZXMpXG5cbnR5cGUgU291cmNlUHJvdmlkZXIgPSAnZ2l0aHViJyB8ICdiaXRidWNrZXQnIHwgJ2NvZGVjb21taXQnIHwgJ3Vua25vd24nO1xuaW50ZXJmYWNlIFNvdXJjZVJlZiB7XG4gIHNoYTogc3RyaW5nO1xuICBwcm92aWRlcjogU291cmNlUHJvdmlkZXI7XG4gIG93bmVyPzogc3RyaW5nO1xuICByZXBvPzogc3RyaW5nO1xuICByZXBvc2l0b3J5TmFtZT86IHN0cmluZztcbn1cbmludGVyZmFjZSBDb21taXRJbmZvIHsgY29tbWl0VGltZXN0YW1wPzogc3RyaW5nOyBjb21taXRDb3VudD86IG51bWJlciB9XG5cbi8vIFBlci0ocGlwZWxpbmUsc2hhKSByZXNvbHZlZCByZXN1bHQg4oCUIGNvbXB1dGVkIG9uY2UsIHJldXNlZCBhY3Jvc3MgdGhlIG1hbnkgZXZlbnRzXG4vLyAoUElQRUxJTkUvU1RBR0UvQUNUSU9OKSB0aGF0IHNoYXJlIGEgc291cmNlIHJldmlzaW9uIGFuZCBhY3Jvc3MgcmVkZWxpdmVyaWVzLlxuY29uc3QgY29tbWl0UmVzdWx0Q2FjaGUgPSBuZXcgTWFwPHN0cmluZywgQ29tbWl0SW5mbz4oKTtcbi8vIExhc3Qgc2hhIHdlIHN1Y2Nlc3NmdWxseSByZXNvbHZlZCBwZXIgcGlwZWxpbmUg4oCUIHRoZSBleGNsdXNpdmUgbG93ZXIgYm91bmQgZm9yIHRoZVxuLy8gbmV4dCByYW5nZSAoXCJjb21taXRzIHNpbmNlIHRoZSBsYXN0IGRlcGxveVwiKS4gSW4tbWVtb3J5IG9ubHk6IGEgY29sZCBzdGFydCByZXNldHNcbi8vIGl0LCBzbyB0aGUgZmlyc3QgcG9zdC1jb2xkIGV2ZW50IHJlc29sdmVzIGFzIGEgc2luZ2xlIGNvbW1pdCAoZXhwZWN0ZWQsIGRlZmVuc2l2ZSkuXG5jb25zdCBsYXN0U2hhQnlQaXBlbGluZSA9IG5ldyBNYXA8c3RyaW5nLCBzdHJpbmc+KCk7XG4vLyBQZXItc2hhIENvZGVDb21taXQgbWV0YWRhdGEgY2FjaGUgKGRhdGUgKyBmaXJzdC1wYXJlbnQpIGZvciByYW5nZSB3YWxrcy5cbmNvbnN0IGNvbW1pdE1ldGFDYWNoZSA9IG5ldyBNYXA8c3RyaW5nLCB7IGRhdGU/OiBzdHJpbmc7IHBhcmVudHM/OiBzdHJpbmdbXSB9PigpO1xuLy8gU0NNIHJhdGUtbGltaXQgY29vbGRvd246IGFmdGVyIGEgNDAzLzQyOSB3ZSBzdG9wIGNhbGxpbmcgdGhlIFNDTSBmb3IgYSB3aGlsZS5cbmxldCBzY21Db29sZG93blVudGlsID0gMDtcbmNvbnN0IFNDTV9DT09MRE9XTl9NUyA9IDIgKiA2MCAqIDEwMDA7XG4vLyBDYXAgdGhlIENvZGVDb21taXQgZmlyc3QtcGFyZW50IHdhbGsgc28gYSBsb25nIHJhbmdlIGNhbid0IGJsb3cgdGhlIExhbWJkYSB0aW1lb3V0LlxuY29uc3QgTUFYX0NPTU1JVF9XQUxLID0gNTA7XG4vLyBQZXItcmVxdWVzdCB0aW1lb3V0IGZvciBleHRlcm5hbCBTQ00gY2FsbHMgKGRlZmVuc2l2ZSBhZ2FpbnN0IHNsb3cvaHVuZyBob3N0cykuXG5jb25zdCBTQ01fRkVUQ0hfVElNRU9VVF9NUyA9IDMwMDA7XG5cbi8vIFRoZSBmb3J3YXJkZXIgaXMgYSBmbGVldCBMYW1iZGE6IG9uZSB3YXJtIGNvbnRhaW5lciByZXNvbHZlcyBldmVudHMgZm9yIE1BTllcbi8vIG9yZ3MsIHNvIHRoZSBHaXRIdWIgdG9rZW4gTVVTVCBiZSBrZXllZCBieSB0aGUgZXZlbnQncyByZXNvbHZlZCBvcmcgKG5vdCBhIHNpbmdsZVxuLy8gY29udGFpbmVyLWdsb2JhbCB0b2tlbikuIEVhY2ggb3JnJ3MgdG9rZW4gbGl2ZXMgYXQgYDxwcmVmaXg+LzxvcmdJZD4vZ2l0aHViLXRva2VuYCxcbi8vIGRlcml2ZWQgZnJvbSBQTEFURk9STV9TRUNSRVRfTkFNRSAoYDxwcmVmaXg+Lzxjb250YWluZXJPcmc+L3BsYXRmb3JtYCkuIG51bGwgY2FjaGVzXG4vLyBhIGRlZmluaXRpdmUgbWlzcyAobm8gc2VjcmV0IC8gcmVhZCBmYWlsZWQpIOKGkiB1bmF1dGhlbnRpY2F0ZWQgYmVzdC1lZmZvcnQuXG5jb25zdCBnaXRodWJUb2tlbkJ5T3JnID0gbmV3IE1hcDxzdHJpbmcsIHN0cmluZyB8IG51bGw+KCk7XG5cbi8qKlxuICogRGVyaXZlIGEgc3BlY2lmaWMgb3JnJ3MgZ2l0aHViLXRva2VuIHNlY3JldCBuYW1lIGZyb20gUExBVEZPUk1fU0VDUkVUX05BTUVcbiAqIChgPHByZWZpeD4vPGNvbnRhaW5lck9yZz4vcGxhdGZvcm1gKS4gV2l0aCBhbiBgb3JnSWRgIHdlIHN3YXAgdGhlIG9yZyBzZWdtZW50XG4gKiAoYDxwcmVmaXg+LzxvcmdJZD4vZ2l0aHViLXRva2VuYCk7IHdpdGhvdXQgb25lIChwaXBlbGluZSBjYXJyaWVkIG5vIE9yZ0lkIHRhZykgd2VcbiAqIGZhbGwgYmFjayB0byB0aGUgY29udGFpbmVyJ3Mgb3duIHNpYmxpbmcgc2VjcmV0LiBSZXR1cm5zIHVuZGVmaW5lZCB3aGVuXG4gKiBQTEFURk9STV9TRUNSRVRfTkFNRSBpc24ndCB0aGUgZXhwZWN0ZWQgYC4uLi9wbGF0Zm9ybWAgc2hhcGUuXG4gKi9cbmZ1bmN0aW9uIGdpdGh1YlRva2VuU2VjcmV0TmFtZShvcmdJZDogc3RyaW5nIHwgbnVsbCk6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIGNvbnN0IG5hbWUgPSBwcm9jZXNzLmVudi5QTEFURk9STV9TRUNSRVRfTkFNRTtcbiAgaWYgKCFuYW1lIHx8ICFuYW1lLmVuZHNXaXRoKCcvcGxhdGZvcm0nKSkgcmV0dXJuIHVuZGVmaW5lZDtcbiAgY29uc3QgYmFzZSA9IG5hbWUuc2xpY2UoMCwgLScvcGxhdGZvcm0nLmxlbmd0aCk7IC8vIDxwcmVmaXg+Lzxjb250YWluZXJPcmc+XG4gIGlmIChvcmdJZCkge1xuICAgIGNvbnN0IHNsYXNoID0gYmFzZS5sYXN0SW5kZXhPZignLycpO1xuICAgIGNvbnN0IHByZWZpeCA9IHNsYXNoID4gMCA/IGJhc2Uuc2xpY2UoMCwgc2xhc2gpIDogYmFzZTtcbiAgICByZXR1cm4gYCR7cHJlZml4fS8ke29yZ0lkfS9naXRodWItdG9rZW5gO1xuICB9XG4gIHJldHVybiBgJHtiYXNlfS9naXRodWItdG9rZW5gO1xufVxuXG4vKiogUmVhZCAoYW5kIGNhY2hlLCBwZXIgb3JnKSB0aGUgb3JnIGdpdGh1YiB0b2tlbjsgbnVsbCDihpIgdW5hdXRoZW50aWNhdGVkIGJlc3QtZWZmb3J0LiAqL1xuYXN5bmMgZnVuY3Rpb24gZ2V0R2l0SHViVG9rZW4ob3JnSWQ6IHN0cmluZyB8IG51bGwpOiBQcm9taXNlPHN0cmluZyB8IG51bGw+IHtcbiAgY29uc3Qga2V5ID0gb3JnSWQgPz8gJyc7XG4gIGNvbnN0IGNhY2hlZCA9IGdpdGh1YlRva2VuQnlPcmcuZ2V0KGtleSk7XG4gIGlmIChjYWNoZWQgIT09IHVuZGVmaW5lZCkgcmV0dXJuIGNhY2hlZDtcbiAgY29uc3Qgc2VjcmV0TmFtZSA9IGdpdGh1YlRva2VuU2VjcmV0TmFtZShvcmdJZCk7XG4gIGlmICghc2VjcmV0TmFtZSkgeyBnaXRodWJUb2tlbkJ5T3JnLnNldChrZXksIG51bGwpOyByZXR1cm4gbnVsbDsgfVxuICBsZXQgdG9rZW46IHN0cmluZyB8IG51bGw7XG4gIHRyeSB7XG4gICAgY29uc3QgY2xpZW50ID0gbmV3IFNlY3JldHNNYW5hZ2VyQ2xpZW50KHt9KTtcbiAgICBjb25zdCByZXMgPSBhd2FpdCBjbGllbnQuc2VuZChuZXcgR2V0U2VjcmV0VmFsdWVDb21tYW5kKHsgU2VjcmV0SWQ6IHNlY3JldE5hbWUgfSkpO1xuICAgIC8vIFRoZSBnaXRodWItdG9rZW4gc2VjcmV0IGlzIGEgUkFXIHRva2VuIHN0cmluZyAobm90IEpTT04g4oCUIENESyByZXNvbHZlcyBpdCB2aWFcbiAgICAvLyB0aGUgZGVmYXVsdCBTZWNyZXRTdHJpbmcgd2l0aCBubyBrZXkpLlxuICAgIHRva2VuID0gcmVzLlNlY3JldFN0cmluZyAmJiByZXMuU2VjcmV0U3RyaW5nLmxlbmd0aCA+IDAgPyByZXMuU2VjcmV0U3RyaW5nLnRyaW0oKSA6IG51bGw7XG4gIH0gY2F0Y2ggKGVycikge1xuICAgIGxvZy53YXJuKCdObyBnaXRodWItdG9rZW4gc2VjcmV0IOKAlCBHaXRIdWIgY29tbWl0IHJlc29sdXRpb24gd2lsbCBiZSB1bmF1dGhlbnRpY2F0ZWQnLCB7IG9yZ0lkLCBlcnJvcjogKGVyciBhcyB7IG5hbWU/OiBzdHJpbmcgfSk/Lm5hbWUgfSk7XG4gICAgdG9rZW4gPSBudWxsO1xuICB9XG4gIGdpdGh1YlRva2VuQnlPcmcuc2V0KGtleSwgdG9rZW4pO1xuICByZXR1cm4gdG9rZW47XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGZldGNoV2l0aFRpbWVvdXQodXJsOiBzdHJpbmcsIG9wdHM6IFJlY29yZDxzdHJpbmcsIHVua25vd24+KTogUHJvbWlzZTxSZXNwb25zZT4ge1xuICBjb25zdCBjb250cm9sbGVyID0gbmV3IEFib3J0Q29udHJvbGxlcigpO1xuICBjb25zdCB0aW1lciA9IHNldFRpbWVvdXQoKCkgPT4gY29udHJvbGxlci5hYm9ydCgpLCBTQ01fRkVUQ0hfVElNRU9VVF9NUyk7XG4gIHRyeSB7XG4gICAgcmV0dXJuIGF3YWl0IGZldGNoKHVybCwgeyAuLi5vcHRzLCBzaWduYWw6IGNvbnRyb2xsZXIuc2lnbmFsIH0pO1xuICB9IGZpbmFsbHkge1xuICAgIGNsZWFyVGltZW91dCh0aW1lcik7XG4gIH1cbn1cblxuLyoqIE5vcm1hbGl6ZSBhIGNvbW1pdCBkYXRlIHRvIElTTyA4NjAxLiBBY2NlcHRzIElTTyBzdHJpbmdzIGFuZCBDb2RlQ29tbWl0J3NcbiAqICBcIjxlcG9jaC1zZWNvbmRzPiArWlpaWlwiIGZvcm0uIFJldHVybnMgdW5kZWZpbmVkIHdoZW4gdW5wYXJzZWFibGUuICovXG5mdW5jdGlvbiB0b0lzbyh2YWx1ZTogdW5rbm93bik6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIGlmICh0eXBlb2YgdmFsdWUgIT09ICdzdHJpbmcnIHx8IHZhbHVlLmxlbmd0aCA9PT0gMCkgcmV0dXJuIHVuZGVmaW5lZDtcbiAgY29uc3QgZXBvY2ggPSAvXihcXGR7OSx9KVxcYi8uZXhlYyh2YWx1ZS50cmltKCkpO1xuICBpZiAoZXBvY2gpIHtcbiAgICBjb25zdCBkID0gbmV3IERhdGUoTnVtYmVyKGVwb2NoWzFdKSAqIDEwMDApO1xuICAgIHJldHVybiBOdW1iZXIuaXNOYU4oZC5nZXRUaW1lKCkpID8gdW5kZWZpbmVkIDogZC50b0lTT1N0cmluZygpO1xuICB9XG4gIGNvbnN0IGQgPSBuZXcgRGF0ZSh2YWx1ZSk7XG4gIHJldHVybiBOdW1iZXIuaXNOYU4oZC5nZXRUaW1lKCkpID8gdW5kZWZpbmVkIDogZC50b0lTT1N0cmluZygpO1xufVxuXG4vKiogQ2xhc3NpZnkgYSBzb3VyY2UtcmV2aXNpb24gVVJMIGludG8gYSBwcm92aWRlciArIHJlcG8gY29vcmRpbmF0ZXMuICovXG5mdW5jdGlvbiBjbGFzc2lmeVNvdXJjZSh1cmw6IHN0cmluZyB8IHVuZGVmaW5lZCk6IE9taXQ8U291cmNlUmVmLCAnc2hhJz4ge1xuICBpZiAoIXVybCkgcmV0dXJuIHsgcHJvdmlkZXI6ICd1bmtub3duJyB9O1xuICB0cnkge1xuICAgIGNvbnN0IHUgPSBuZXcgVVJMKHVybCk7XG4gICAgY29uc3QgaG9zdCA9IHUuaG9zdG5hbWUudG9Mb3dlckNhc2UoKTtcbiAgICBpZiAoaG9zdCA9PT0gJ2dpdGh1Yi5jb20nIHx8IGhvc3QuZW5kc1dpdGgoJy5naXRodWIuY29tJykpIHtcbiAgICAgIGNvbnN0IHBhcnRzID0gdS5wYXRobmFtZS5zcGxpdCgnLycpLmZpbHRlcihCb29sZWFuKTtcbiAgICAgIGlmIChwYXJ0cy5sZW5ndGggPj0gMikgcmV0dXJuIHsgcHJvdmlkZXI6ICdnaXRodWInLCBvd25lcjogcGFydHNbMF0sIHJlcG86IHBhcnRzWzFdLnJlcGxhY2UoL1xcLmdpdCQvLCAnJykgfTtcbiAgICB9XG4gICAgaWYgKGhvc3QgPT09ICdiaXRidWNrZXQub3JnJykge1xuICAgICAgY29uc3QgcGFydHMgPSB1LnBhdGhuYW1lLnNwbGl0KCcvJykuZmlsdGVyKEJvb2xlYW4pO1xuICAgICAgaWYgKHBhcnRzLmxlbmd0aCA+PSAyKSByZXR1cm4geyBwcm92aWRlcjogJ2JpdGJ1Y2tldCcsIG93bmVyOiBwYXJ0c1swXSwgcmVwbzogcGFydHNbMV0ucmVwbGFjZSgvXFwuZ2l0JC8sICcnKSB9O1xuICAgIH1cbiAgICBpZiAodS5wYXRobmFtZS50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzKCdjb2RlY29tbWl0JykgfHwgdS5oYXNoLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMoJ2NvZGVjb21taXQnKSB8fCB1LnBhdGhuYW1lLmluY2x1ZGVzKCcvcmVwb3NpdG9yaWVzLycpKSB7XG4gICAgICBjb25zdCBtID0gL3JlcG9zaXRvcig/Onl8aWVzKVxcLyhbXi8jP10rKS8uZXhlYyh1LnBhdGhuYW1lICsgdS5oYXNoKTtcbiAgICAgIGlmIChtKSByZXR1cm4geyBwcm92aWRlcjogJ2NvZGVjb21taXQnLCByZXBvc2l0b3J5TmFtZTogZGVjb2RlVVJJQ29tcG9uZW50KG1bMV0pIH07XG4gICAgfVxuICB9IGNhdGNoIHtcbiAgICAvLyBub3QgYSBVUkwg4oaSIHVua25vd25cbiAgfVxuICByZXR1cm4geyBwcm92aWRlcjogJ3Vua25vd24nIH07XG59XG5cbmxldCBjb2RlY29tbWl0Q2xpZW50c0J5UmVnaW9uOiBNYXA8c3RyaW5nLCB7IHNlbmQoY29tbWFuZDogdW5rbm93bik6IFByb21pc2U8dW5rbm93bj4gfT4gfCB1bmRlZmluZWQ7XG5pbnRlcmZhY2UgQ29kZUNvbW1pdE1vZHVsZSB7XG4gIENvZGVDb21taXRDbGllbnQ6IG5ldyAoY29uZmlnOiB7IHJlZ2lvbjogc3RyaW5nIH0pID0+IHsgc2VuZChjb21tYW5kOiB1bmtub3duKTogUHJvbWlzZTx1bmtub3duPiB9O1xuICBHZXRDb21taXRDb21tYW5kOiBuZXcgKGlucHV0OiB7IHJlcG9zaXRvcnlOYW1lOiBzdHJpbmc7IGNvbW1pdElkOiBzdHJpbmcgfSkgPT4gdW5rbm93bjtcbn1cbmxldCBjb2RlY29tbWl0TW9kOiBDb2RlQ29tbWl0TW9kdWxlIHwgdW5kZWZpbmVkO1xuYXN5bmMgZnVuY3Rpb24gY29kZUNvbW1pdENsaWVudChyZWdpb246IHN0cmluZyk6IFByb21pc2U8eyBzZW5kKGNvbW1hbmQ6IHVua25vd24pOiBQcm9taXNlPHVua25vd24+IH0+IHtcbiAgaWYgKCFjb2RlY29tbWl0TW9kKSBjb2RlY29tbWl0TW9kID0gYXdhaXQgbG9hZFNkazxDb2RlQ29tbWl0TW9kdWxlPignQGF3cy1zZGsvY2xpZW50LWNvZGVjb21taXQnKTtcbiAgaWYgKCFjb2RlY29tbWl0Q2xpZW50c0J5UmVnaW9uKSBjb2RlY29tbWl0Q2xpZW50c0J5UmVnaW9uID0gbmV3IE1hcCgpO1xuICBsZXQgY2xpZW50ID0gY29kZWNvbW1pdENsaWVudHNCeVJlZ2lvbi5nZXQocmVnaW9uKTtcbiAgaWYgKCFjbGllbnQpIHtcbiAgICBjbGllbnQgPSBuZXcgY29kZWNvbW1pdE1vZC5Db2RlQ29tbWl0Q2xpZW50KHsgcmVnaW9uIH0pO1xuICAgIGNvZGVjb21taXRDbGllbnRzQnlSZWdpb24uc2V0KHJlZ2lvbiwgY2xpZW50KTtcbiAgfVxuICByZXR1cm4gY2xpZW50O1xufVxuXG4vKiogRmV0Y2ggKGNhY2hlZCkgYSBzaW5nbGUgQ29kZUNvbW1pdCBjb21taXQncyBkYXRlICsgZmlyc3QgcGFyZW50cyB2aWEgR2V0Q29tbWl0LiAqL1xuYXN5bmMgZnVuY3Rpb24gY29kZUNvbW1pdE1ldGEocmVnaW9uOiBzdHJpbmcsIHJlcG9zaXRvcnlOYW1lOiBzdHJpbmcsIGNvbW1pdElkOiBzdHJpbmcpOiBQcm9taXNlPHsgZGF0ZT86IHN0cmluZzsgcGFyZW50cz86IHN0cmluZ1tdIH0gfCB1bmRlZmluZWQ+IHtcbiAgY29uc3QgY2FjaGVkID0gY29tbWl0TWV0YUNhY2hlLmdldChjb21taXRJZCk7XG4gIGlmIChjYWNoZWQpIHJldHVybiBjYWNoZWQ7XG4gIGlmICghY29kZWNvbW1pdE1vZCkgY29kZWNvbW1pdE1vZCA9IGF3YWl0IGxvYWRTZGs8Q29kZUNvbW1pdE1vZHVsZT4oJ0Bhd3Mtc2RrL2NsaWVudC1jb2RlY29tbWl0Jyk7XG4gIGNvbnN0IGNsaWVudCA9IGF3YWl0IGNvZGVDb21taXRDbGllbnQocmVnaW9uKTtcbiAgY29uc3Qgb3V0ID0gYXdhaXQgY2xpZW50LnNlbmQobmV3IGNvZGVjb21taXRNb2QuR2V0Q29tbWl0Q29tbWFuZCh7IHJlcG9zaXRvcnlOYW1lLCBjb21taXRJZCB9KSkgYXMge1xuICAgIGNvbW1pdD86IHsgY29tbWl0dGVyPzogeyBkYXRlPzogc3RyaW5nIH07IGF1dGhvcj86IHsgZGF0ZT86IHN0cmluZyB9OyBwYXJlbnRzPzogc3RyaW5nW10gfTtcbiAgfTtcbiAgY29uc3QgY29tbWl0ID0gb3V0LmNvbW1pdDtcbiAgY29uc3QgbWV0YSA9IHsgZGF0ZTogdG9Jc28oY29tbWl0Py5jb21taXR0ZXI/LmRhdGUgPz8gY29tbWl0Py5hdXRob3I/LmRhdGUpLCBwYXJlbnRzOiBjb21taXQ/LnBhcmVudHMgfTtcbiAgY29tbWl0TWV0YUNhY2hlLnNldChjb21taXRJZCwgbWV0YSk7XG4gIHJldHVybiBtZXRhO1xufVxuXG5hc3luYyBmdW5jdGlvbiByZXNvbHZlQ29kZUNvbW1pdChyZWdpb246IHN0cmluZywgcmVwb3NpdG9yeU5hbWU6IHN0cmluZywgc2hhOiBzdHJpbmcsIHNpbmNlU2hhPzogc3RyaW5nKTogUHJvbWlzZTxDb21taXRJbmZvPiB7XG4gIGxldCBjdXI6IHN0cmluZyB8IHVuZGVmaW5lZCA9IHNoYTtcbiAgbGV0IGNvdW50ID0gMDtcbiAgbGV0IG9sZGVzdDogc3RyaW5nIHwgdW5kZWZpbmVkO1xuICBsZXQgdHJ1bmNhdGVkID0gZmFsc2U7XG4gIGZvciAobGV0IGkgPSAwOyA7IGkrKykge1xuICAgIGlmICghY3VyKSBicmVhazsgLy8gcmVhY2hlZCBoaXN0b3J5IHJvb3Qg4oCUIHRoZSB3aG9sZSByYW5nZSBpcyBjb3ZlcmVkXG4gICAgaWYgKHNpbmNlU2hhICYmIGN1ciA9PT0gc2luY2VTaGEpIGJyZWFrOyAvLyByZWFjaGVkIHRoZSBleGNsdXNpdmUgbG93ZXIgYm91bmRcbiAgICBpZiAoaSA+PSBNQVhfQ09NTUlUX1dBTEspIHsgdHJ1bmNhdGVkID0gdHJ1ZTsgYnJlYWs7IH0gLy8gaGl0IHRoZSBjYXAgYmVmb3JlIHRoZSBib3VuZGFyeVxuICAgIGNvbnN0IG1ldGEgPSBhd2FpdCBjb2RlQ29tbWl0TWV0YShyZWdpb24sIHJlcG9zaXRvcnlOYW1lLCBjdXIpO1xuICAgIGlmICghbWV0YT8uZGF0ZSkgYnJlYWs7XG4gICAgY291bnQrKztcbiAgICBvbGRlc3QgPSBtZXRhLmRhdGU7XG4gICAgaWYgKCFzaW5jZVNoYSkgYnJlYWs7IC8vIG5vIHJhbmdlIOKGkiBzaW5nbGUgY29tbWl0XG4gICAgY3VyID0gbWV0YS5wYXJlbnRzPy5bMF07XG4gIH1cbiAgLy8gQSB0cnVuY2F0ZWQgd2FsayB5aWVsZHMgYSBjb21taXQgTkVXRVIgdGhhbiB0aGUgdHJ1ZSBvbGRlc3QtdW5zaGlwcGVkIGNvbW1pdCxcbiAgLy8gd2hpY2ggd291bGQgVU5ERVJTVEFURSBsZWFkIHRpbWUg4oCUIG9taXQgdGhlIGZpZWxkcyBlbnRpcmVseSByYXRoZXIgdGhhbiByZXBvcnQgYVxuICAvLyB3cm9uZyAodG9vLXNtYWxsKSB2YWx1ZS4gUmVwb3J0aW5nIGZhbGxzIGJhY2sgdG8gYHVua25vd25gLlxuICBpZiAodHJ1bmNhdGVkKSByZXR1cm4ge307XG4gIHJldHVybiBvbGRlc3QgPyB7IGNvbW1pdFRpbWVzdGFtcDogb2xkZXN0LCBjb21taXRDb3VudDogY291bnQgfSA6IHt9O1xufVxuXG5hc3luYyBmdW5jdGlvbiByZXNvbHZlR2l0SHViKG93bmVyOiBzdHJpbmcsIHJlcG86IHN0cmluZywgc2hhOiBzdHJpbmcsIHNpbmNlU2hhOiBzdHJpbmcgfCB1bmRlZmluZWQsIG9yZ0lkOiBzdHJpbmcgfCBudWxsKTogUHJvbWlzZTxDb21taXRJbmZvPiB7XG4gIGNvbnN0IHRva2VuID0gYXdhaXQgZ2V0R2l0SHViVG9rZW4ob3JnSWQpO1xuICBjb25zdCBoZWFkZXJzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge1xuICAgICdBY2NlcHQnOiAnYXBwbGljYXRpb24vdm5kLmdpdGh1Yitqc29uJyxcbiAgICAnVXNlci1BZ2VudCc6ICdwaXBlbGluZS1idWlsZGVyLWZvcndhcmRlcicsXG4gICAgJ1gtR2l0SHViLUFwaS1WZXJzaW9uJzogJzIwMjItMTEtMjgnLFxuICB9O1xuICBpZiAodG9rZW4pIGhlYWRlcnMuQXV0aG9yaXphdGlvbiA9IGBCZWFyZXIgJHt0b2tlbn1gO1xuICBjb25zdCByYXRlTGltaXRlZCA9IChyOiBSZXNwb25zZSkgPT4gci5zdGF0dXMgPT09IDQwMyB8fCByLnN0YXR1cyA9PT0gNDI5O1xuXG4gIGlmIChzaW5jZVNoYSkge1xuICAgIGNvbnN0IHIgPSBhd2FpdCBmZXRjaFdpdGhUaW1lb3V0KGBodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zLyR7b3duZXJ9LyR7cmVwb30vY29tcGFyZS8ke3NpbmNlU2hhfS4uLiR7c2hhfWAsIHsgaGVhZGVycyB9KTtcbiAgICBpZiAocmF0ZUxpbWl0ZWQocikpIHsgc2NtQ29vbGRvd25VbnRpbCA9IERhdGUubm93KCkgKyBTQ01fQ09PTERPV05fTVM7IHJldHVybiB7fTsgfVxuICAgIGlmIChyLm9rKSB7XG4gICAgICBjb25zdCBqID0gYXdhaXQgci5qc29uKCkgYXMgeyBjb21taXRzPzogQXJyYXk8eyBjb21taXQ/OiB7IGNvbW1pdHRlcj86IHsgZGF0ZT86IHN0cmluZyB9OyBhdXRob3I/OiB7IGRhdGU/OiBzdHJpbmcgfSB9IH0+IH07XG4gICAgICBjb25zdCBjb21taXRzID0gQXJyYXkuaXNBcnJheShqLmNvbW1pdHMpID8gai5jb21taXRzIDogW107XG4gICAgICBpZiAoY29tbWl0cy5sZW5ndGggPiAwKSB7XG4gICAgICAgIGNvbnN0IG9sZGVzdCA9IHRvSXNvKGNvbW1pdHNbMF0/LmNvbW1pdD8uY29tbWl0dGVyPy5kYXRlID8/IGNvbW1pdHNbMF0/LmNvbW1pdD8uYXV0aG9yPy5kYXRlKTtcbiAgICAgICAgaWYgKG9sZGVzdCkgcmV0dXJuIHsgY29tbWl0VGltZXN0YW1wOiBvbGRlc3QsIGNvbW1pdENvdW50OiBjb21taXRzLmxlbmd0aCB9O1xuICAgICAgfVxuICAgIH1cbiAgICAvLyBmYWxsIHRocm91Z2ggdG8gc2luZ2xlLWNvbW1pdCByZXNvbHV0aW9uXG4gIH1cbiAgY29uc3QgcjIgPSBhd2FpdCBmZXRjaFdpdGhUaW1lb3V0KGBodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zLyR7b3duZXJ9LyR7cmVwb30vY29tbWl0cy8ke3NoYX1gLCB7IGhlYWRlcnMgfSk7XG4gIGlmIChyYXRlTGltaXRlZChyMikpIHsgc2NtQ29vbGRvd25VbnRpbCA9IERhdGUubm93KCkgKyBTQ01fQ09PTERPV05fTVM7IHJldHVybiB7fTsgfVxuICBpZiAocjIub2spIHtcbiAgICBjb25zdCBqID0gYXdhaXQgcjIuanNvbigpIGFzIHsgY29tbWl0PzogeyBjb21taXR0ZXI/OiB7IGRhdGU/OiBzdHJpbmcgfTsgYXV0aG9yPzogeyBkYXRlPzogc3RyaW5nIH0gfSB9O1xuICAgIGNvbnN0IGQgPSB0b0lzbyhqLmNvbW1pdD8uY29tbWl0dGVyPy5kYXRlID8/IGouY29tbWl0Py5hdXRob3I/LmRhdGUpO1xuICAgIGlmIChkKSByZXR1cm4geyBjb21taXRUaW1lc3RhbXA6IGQsIGNvbW1pdENvdW50OiAxIH07XG4gIH1cbiAgcmV0dXJuIHt9O1xufVxuXG5hc3luYyBmdW5jdGlvbiByZXNvbHZlQml0YnVja2V0KG93bmVyOiBzdHJpbmcsIHJlcG86IHN0cmluZywgc2hhOiBzdHJpbmcpOiBQcm9taXNlPENvbW1pdEluZm8+IHtcbiAgY29uc3QgciA9IGF3YWl0IGZldGNoV2l0aFRpbWVvdXQoYGh0dHBzOi8vYXBpLmJpdGJ1Y2tldC5vcmcvMi4wL3JlcG9zaXRvcmllcy8ke293bmVyfS8ke3JlcG99L2NvbW1pdC8ke3NoYX1gLCB7XG4gICAgaGVhZGVyczogeyAnVXNlci1BZ2VudCc6ICdwaXBlbGluZS1idWlsZGVyLWZvcndhcmRlcicsICdBY2NlcHQnOiAnYXBwbGljYXRpb24vanNvbicgfSxcbiAgfSk7XG4gIGlmIChyLnN0YXR1cyA9PT0gNDI5KSB7IHNjbUNvb2xkb3duVW50aWwgPSBEYXRlLm5vdygpICsgU0NNX0NPT0xET1dOX01TOyByZXR1cm4ge307IH1cbiAgaWYgKHIub2spIHtcbiAgICBjb25zdCBqID0gYXdhaXQgci5qc29uKCkgYXMgeyBkYXRlPzogc3RyaW5nIH07XG4gICAgY29uc3QgZCA9IHRvSXNvKGouZGF0ZSk7XG4gICAgaWYgKGQpIHJldHVybiB7IGNvbW1pdFRpbWVzdGFtcDogZCwgY29tbWl0Q291bnQ6IDEgfTtcbiAgfVxuICByZXR1cm4ge307XG59XG5cbi8qKlxuICogUmVzb2x2ZSBgY29tbWl0VGltZXN0YW1wYCArIGBjb21taXRDb3VudGAgZm9yIGEgc291cmNlL2RlcGxveSBldmVudCwgaW4tYWNjb3VudCxcbiAqIGJlc3QtZWZmb3J0LiBDb21wdXRlZCBvbmNlIHBlciAocGlwZWxpbmVJZCwgc2hhKTsgYWR2YW5jZXMgdGhlIHBpcGVsaW5lJ3MgcmFuZ2VcbiAqIGJvdW5kYXJ5IG9ubHkgb24gc3VjY2Vzcy4gTmV2ZXIgdGhyb3dzIOKAlCByZXR1cm5zIHt9IG9uIGFueSBmYWlsdXJlLlxuICovXG5hc3luYyBmdW5jdGlvbiByZXNvbHZlQ29tbWl0SW5mbyhwaXBlbGluZUlkOiBzdHJpbmcsIG9yZ0lkOiBzdHJpbmcgfCBudWxsLCByZWdpb246IHN0cmluZywgc3JjOiBTb3VyY2VSZWYpOiBQcm9taXNlPENvbW1pdEluZm8+IHtcbiAgaWYgKHNyYy5wcm92aWRlciA9PT0gJ3Vua25vd24nKSByZXR1cm4ge307XG4gIGNvbnN0IHJrZXkgPSBgJHtwaXBlbGluZUlkfXwke3NyYy5zaGF9YDtcbiAgY29uc3QgY2FjaGVkID0gY29tbWl0UmVzdWx0Q2FjaGUuZ2V0KHJrZXkpO1xuICBpZiAoY2FjaGVkKSByZXR1cm4gY2FjaGVkO1xuXG4gIGNvbnN0IHByZXYgPSBsYXN0U2hhQnlQaXBlbGluZS5nZXQocGlwZWxpbmVJZCk7XG4gIGNvbnN0IHNpbmNlU2hhID0gcHJldiAmJiBwcmV2ICE9PSBzcmMuc2hhID8gcHJldiA6IHVuZGVmaW5lZDtcblxuICBsZXQgaW5mbzogQ29tbWl0SW5mbyA9IHt9O1xuICB0cnkge1xuICAgIGlmIChzcmMucHJvdmlkZXIgPT09ICdjb2RlY29tbWl0JyAmJiBzcmMucmVwb3NpdG9yeU5hbWUpIHtcbiAgICAgIGluZm8gPSBhd2FpdCByZXNvbHZlQ29kZUNvbW1pdChyZWdpb24sIHNyYy5yZXBvc2l0b3J5TmFtZSwgc3JjLnNoYSwgc2luY2VTaGEpO1xuICAgIH0gZWxzZSBpZiAoc3JjLnByb3ZpZGVyID09PSAnZ2l0aHViJyAmJiBzcmMub3duZXIgJiYgc3JjLnJlcG8pIHtcbiAgICAgIGlmIChEYXRlLm5vdygpID49IHNjbUNvb2xkb3duVW50aWwpIGluZm8gPSBhd2FpdCByZXNvbHZlR2l0SHViKHNyYy5vd25lciwgc3JjLnJlcG8sIHNyYy5zaGEsIHNpbmNlU2hhLCBvcmdJZCk7XG4gICAgfSBlbHNlIGlmIChzcmMucHJvdmlkZXIgPT09ICdiaXRidWNrZXQnICYmIHNyYy5vd25lciAmJiBzcmMucmVwbykge1xuICAgICAgaWYgKERhdGUubm93KCkgPj0gc2NtQ29vbGRvd25VbnRpbCkgaW5mbyA9IGF3YWl0IHJlc29sdmVCaXRidWNrZXQoc3JjLm93bmVyLCBzcmMucmVwbywgc3JjLnNoYSk7XG4gICAgfVxuICB9IGNhdGNoIChlcnIpIHtcbiAgICBsb2cud2FybignQ29tbWl0IHJlc29sdXRpb24gZmFpbGVkIChvbWl0dGluZyBjb21taXRUaW1lc3RhbXAvY29tbWl0Q291bnQpJywgeyBwcm92aWRlcjogc3JjLnByb3ZpZGVyLCBlcnJvcjogKGVyciBhcyB7IG5hbWU/OiBzdHJpbmcgfSk/Lm5hbWUgPz8gU3RyaW5nKGVycikgfSk7XG4gICAgaW5mbyA9IHt9O1xuICB9XG5cbiAgY29tbWl0UmVzdWx0Q2FjaGUuc2V0KHJrZXksIGluZm8pO1xuICBpZiAoaW5mby5jb21taXRUaW1lc3RhbXApIGxhc3RTaGFCeVBpcGVsaW5lLnNldChwaXBlbGluZUlkLCBzcmMuc2hhKTtcbiAgcmV0dXJuIGluZm87XG59XG5cbi8vIOKUgOKUgOKUgCBFdmVudCBQYXJzaW5nIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG5pbnRlcmZhY2UgUGFyc2VkRXZlbnQge1xuICAvKipcbiAgICogUmVzb2x2ZWQgcGxhdGZvcm0gb3JnSWQgKGZyb20gdGhlIHBpcGVsaW5lJ3MgT3JnSWQgdGFnOyBudWxsIHdoZW4gdW50YWdnZWQpLlxuICAgKiBJTlRFUk5BTCDigJQgdXNlZCBvbmx5IHRvIGtleSB0aGUgcGVyLW9yZyBHaXRIdWIgdG9rZW4gYW5kIHRvIGF0dHJpYnV0ZVxuICAgKiBpbmdlc3QtaGVhbHRoLiBTdHJpcHBlZCBmcm9tIHRoZSBwYXlsb2FkIFBPU1RlZCB0byAvcmVwb3J0cy9ldmVudHMgKHdoaWNoXG4gICAqIHJlc29sdmVzIG9yZyBmcm9tIHRoZSBwaXBlbGluZSByZWdpc3RyeSwgbm90IHRoZSBib2R5KS5cbiAgICovXG4gIG9yZ0lkOiBzdHJpbmcgfCBudWxsO1xuICBwaXBlbGluZUlkOiBzdHJpbmc7XG4gIGV2ZW50U291cmNlOiBzdHJpbmc7XG4gIGV2ZW50VHlwZTogc3RyaW5nO1xuICBzdGF0dXM6IHN0cmluZztcbiAgZXhlY3V0aW9uSWQ/OiBzdHJpbmc7XG4gIHN0YWdlTmFtZT86IHN0cmluZztcbiAgYWN0aW9uTmFtZT86IHN0cmluZztcbiAgLyoqIEh1bWFuLXJlYWRhYmxlIGZhaWx1cmUgcmVhc29uLCBmcm9tIGFuIEFjdGlvbiBldmVudCdzXG4gICAqICBgZXhlY3V0aW9uLXJlc3VsdC5leHRlcm5hbC1leGVjdXRpb24tc3VtbWFyeWAuIFRoZSBsb2cgVVJMICsgZXJyb3ItY29kZVxuICAgKiAgc3RheSBpbiBgZGV0YWlsYCBmb3IgZHJpbGwtZG93bi4gKi9cbiAgZXJyb3JNZXNzYWdlPzogc3RyaW5nO1xuICBzdGFydGVkQXQ/OiBzdHJpbmc7XG4gIGNvbXBsZXRlZEF0Pzogc3RyaW5nO1xuICBkdXJhdGlvbk1zPzogbnVtYmVyO1xuICAvKiogU291cmNlIGNvbW1pdCBpZCAoRE9SQSBkZXBsb3kgYXR0cmlidXRpb24pLiBVbmRlZmluZWQgd2hlbiB0aGUgZXZlbnRcbiAgICogIGNhcnJpZXMgbm8gc291cmNlIHJldmlzaW9uIOKAlCBzdG9yZWQgTlVMTCBkb3duc3RyZWFtLiAqL1xuICBjb21taXRTaGE/OiBzdHJpbmc7XG4gIC8qKiBTb3VyY2UgYnJhbmNoL3JlZiAoRE9SQSBkZXBsb3kgYXR0cmlidXRpb24pLiBVbmRlZmluZWQgd2hlbiB1bmF2YWlsYWJsZS4gKi9cbiAgY29tbWl0UmVmPzogc3RyaW5nO1xuICAvKiogT2xkZXN0LXVuc2hpcHBlZCBjb21taXQgdGltZXN0YW1wIChJU08gODYwMSkgZm9yIHRoZSByYW5nZSBzaW5jZSB0aGUgbGFzdFxuICAgKiAgZGVwbG95IOKAlCByZXNvbHZlZCBpbi1hY2NvdW50IChQaGFzZSA0KS4gVW5kZWZpbmVkIHdoZW4gdW5yZXNvbHZhYmxlLiAqL1xuICBjb21taXRUaW1lc3RhbXA/OiBzdHJpbmc7XG4gIC8qKiBOdW1iZXIgb2YgY29tbWl0cyBpbiB0aGUgcmVzb2x2ZWQgcmFuZ2UgKOKJpTEpLiBVbmRlZmluZWQgd2hlbiB1bnJlc29sdmFibGUuICovXG4gIGNvbW1pdENvdW50PzogbnVtYmVyO1xuICAvKiogRGVwbG95IGVudmlyb25tZW50IOKAlCBzZXQgb25seSB3aGVuIHRoZSBldmVudCdzIHN0YWdlIGlzIGxpc3RlZCBpbiB0aGVcbiAgICogIHBpcGVsaW5lJ3MgYHBiLmRlcGxveXNgIHRhZy4gVW5kZWZpbmVkIG90aGVyd2lzZSAo4oeSIG5vdCBhIGRlcGxveSkuICovXG4gIGVudmlyb25tZW50Pzogc3RyaW5nO1xuICBkZXRhaWw6IFJlY29yZDxzdHJpbmcsIHVua25vd24+O1xufVxuXG4vKiogQ2FwIG9uIHRoZSBzdG9yZWQgZmFpbHVyZSBzdW1tYXJ5IOKAlCBDb2RlQnVpbGQvRGVwbG95IHN1bW1hcmllcyBjYW4gYmUgbG9uZy4gKi9cbmNvbnN0IE1BWF9FUlJPUl9NRVNTQUdFID0gNDAwMDtcblxuLyoqIEZpcnN0IG5vbi1lbXB0eSBzdHJpbmcgYW1vbmcgdGhlIGNhbmRpZGF0ZXMsIGNhcHBlZCB0byB0aGUgaW5nZXN0IG1heC4gKi9cbmZ1bmN0aW9uIGZpcnN0QXR0ciguLi52YWxzOiB1bmtub3duW10pOiBzdHJpbmcgfCB1bmRlZmluZWQge1xuICBmb3IgKGNvbnN0IHYgb2YgdmFscykge1xuICAgIGlmICh0eXBlb2YgdiA9PT0gJ3N0cmluZycgJiYgdi5sZW5ndGggPiAwKSByZXR1cm4gdi5zbGljZSgwLCBNQVhfQVRUUik7XG4gIH1cbiAgcmV0dXJuIHVuZGVmaW5lZDtcbn1cblxuLyoqXG4gKiBCZXN0LWVmZm9ydCBleHRyYWN0aW9uIG9mIHRoZSBzb3VyY2UgY29tbWl0IGlkICsgcmVmIChhbmQgdGhlIHJldmlzaW9uIFVSTCwgdXNlZFxuICogb25seSBpbi1hY2NvdW50IHRvIGNsYXNzaWZ5IHRoZSBzb3VyY2UgdHlwZSBmb3IgY29tbWl0LXJhbmdlIHJlc29sdXRpb24pIGZyb20gYVxuICogQ29kZVBpcGVsaW5lIGV2ZW50IGRldGFpbC4gQ29kZVBpcGVsaW5lIHN1cmZhY2VzIHRoZSBzb3VyY2UgcmV2aXNpb24gdW5kZXIgYSBmZXdcbiAqIHNoYXBlcyBkZXBlbmRpbmcgb24gdGhlIGV2ZW50IHR5cGUgLyBzb3VyY2UtYWN0aW9uOyB3ZSBjaGVjayB0aGUga25vd24gb25lcyBhbmRcbiAqIGxlYXZlIHRoZSBmaWVsZHMgdW5kZWZpbmVkIHdoZW4gbm9uZSBhcmUgcHJlc2VudCAoTlVMTCBkb3duc3RyZWFtKS4gTmV2ZXIgcHVsbHMgYW5cbiAqIEFSTi9hY2NvdW50IGlkOiBvbmx5IHJldmlzaW9uL2JyYW5jaC91cmwgc3RyaW5nIGZpZWxkcyBhcmUgcmVhZDsgdGhlIHJldmlzaW9uVXJsXG4gKiBpcyBjb25zdW1lZCBpbi1hY2NvdW50IChzb3VyY2UgY2xhc3NpZmljYXRpb24pIGFuZCBuZXZlciBmb3J3YXJkZWQgb24gaXRzIG93bi5cbiAqL1xuZnVuY3Rpb24gZXh0cmFjdENvbW1pdChkZXRhaWw6IFJlY29yZDxzdHJpbmcsIHVua25vd24+KTogeyBjb21taXRTaGE/OiBzdHJpbmc7IGNvbW1pdFJlZj86IHN0cmluZzsgcmV2aXNpb25Vcmw/OiBzdHJpbmcgfSB7XG4gIC8vIGBzb3VyY2UtcmV2aXNpb25zYCAoa2ViYWIsIEV2ZW50QnJpZGdlKSAvIGBzb3VyY2VSZXZpc2lvbnNgIChjYW1lbCkg4oCUIGZpcnN0IGVudHJ5LlxuICBjb25zdCByZXZzID0gKGRldGFpbFsnc291cmNlLXJldmlzaW9ucyddID8/IGRldGFpbC5zb3VyY2VSZXZpc2lvbnMpIGFzXG4gICAgfCBBcnJheTxSZWNvcmQ8c3RyaW5nLCB1bmtub3duPj4gfCB1bmRlZmluZWQ7XG4gIGNvbnN0IGZpcnN0UmV2ID0gQXJyYXkuaXNBcnJheShyZXZzKSA/IHJldnNbMF0gOiB1bmRlZmluZWQ7XG4gIC8vIE9uIHNvdXJjZSBhY3Rpb25zIHRoZSByZXZpc2lvbiBpZCBjYW4gYWxzbyByaWRlIG9uIGBleGVjdXRpb24tcmVzdWx0YC5cbiAgY29uc3QgZXhlY1Jlc3VsdCA9IGRldGFpbFsnZXhlY3V0aW9uLXJlc3VsdCddIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+IHwgdW5kZWZpbmVkO1xuXG4gIGNvbnN0IGNvbW1pdFNoYSA9IGZpcnN0QXR0cihcbiAgICBkZXRhaWwuY29tbWl0SWQsXG4gICAgZGV0YWlsLnJldmlzaW9uSWQsXG4gICAgZmlyc3RSZXY/LnJldmlzaW9uSWQsXG4gICAgZXhlY1Jlc3VsdD8ucmV2aXNpb25JZCxcbiAgKTtcbiAgY29uc3QgY29tbWl0UmVmID0gZmlyc3RBdHRyKFxuICAgIGRldGFpbC5jb21taXRSZWYsXG4gICAgZGV0YWlsLmJyYW5jaCxcbiAgICBkZXRhaWwuc291cmNlQnJhbmNoLFxuICAgIGZpcnN0UmV2Py5icmFuY2hOYW1lLFxuICAgIGZpcnN0UmV2Py5icmFuY2gsXG4gICk7XG4gIGNvbnN0IHJldmlzaW9uVXJsID0gdHlwZW9mIGZpcnN0UmV2Py5yZXZpc2lvblVybCA9PT0gJ3N0cmluZycgPyBmaXJzdFJldi5yZXZpc2lvblVybCA6IHVuZGVmaW5lZDtcblxuICByZXR1cm4geyAuLi4oY29tbWl0U2hhICYmIHsgY29tbWl0U2hhIH0pLCAuLi4oY29tbWl0UmVmICYmIHsgY29tbWl0UmVmIH0pLCAuLi4ocmV2aXNpb25VcmwgJiYgeyByZXZpc2lvblVybCB9KSB9O1xufVxuXG5mdW5jdGlvbiBjbGFzc2lmeUV2ZW50KGRldGFpbFR5cGU6IHN0cmluZyk6IHsgZXZlbnRUeXBlOiBzdHJpbmc7IGV2ZW50U291cmNlOiBzdHJpbmcgfSB7XG4gIGlmIChkZXRhaWxUeXBlLmluY2x1ZGVzKCdQaXBlbGluZSBFeGVjdXRpb24nKSkgcmV0dXJuIHsgZXZlbnRUeXBlOiAnUElQRUxJTkUnLCBldmVudFNvdXJjZTogJ2NvZGVwaXBlbGluZScgfTtcbiAgaWYgKGRldGFpbFR5cGUuaW5jbHVkZXMoJ1N0YWdlIEV4ZWN1dGlvbicpKSByZXR1cm4geyBldmVudFR5cGU6ICdTVEFHRScsIGV2ZW50U291cmNlOiAnY29kZXBpcGVsaW5lJyB9O1xuICBpZiAoZGV0YWlsVHlwZS5pbmNsdWRlcygnQWN0aW9uIEV4ZWN1dGlvbicpKSByZXR1cm4geyBldmVudFR5cGU6ICdBQ1RJT04nLCBldmVudFNvdXJjZTogJ2NvZGVwaXBlbGluZScgfTtcbiAgLy8gQ29kZUJ1aWxkIFwiQnVpbGQgU3RhdGVcIiBldmVudHMgaWRlbnRpZnkgYSBidWlsZCAqcHJvamVjdCosIG5vdCBhIHBpcGVsaW5lLCBzb1xuICAvLyBwYXJzZVJlY29yZCBkcm9wcyB0aGVtIG9uIHRoZSBgZXZlbnRTb3VyY2UgIT09ICdjb2RlcGlwZWxpbmUnYCBjaGVjayBiZWxvdyDigJRcbiAgLy8gdGhlICdCVUlMRCcgZXZlbnRUeXBlIGlzIG5ldmVyIHBlcnNpc3RlZCBoZXJlIChwbHVnaW4gQlVJTEQgZXZlbnRzIGFyZVxuICAvLyByZWNvcmRlZCBkaXJlY3RseSBieSB0aGUgcGx1Z2luIHNlcnZpY2UpLiBUaGUgJ2NvZGVidWlsZCcgc291cmNlIGlzIHdoYXRcbiAgLy8gcm91dGVzIHRoZSBkcm9wOyBrZWVwIHRoaXMgYnJhbmNoIG9ubHkgZm9yIHRoYXQuXG4gIGlmIChkZXRhaWxUeXBlLmluY2x1ZGVzKCdCdWlsZCBTdGF0ZScpKSByZXR1cm4geyBldmVudFR5cGU6ICdCVUlMRCcsIGV2ZW50U291cmNlOiAnY29kZWJ1aWxkJyB9O1xuICByZXR1cm4geyBldmVudFR5cGU6ICdQSVBFTElORScsIGV2ZW50U291cmNlOiAnY29kZXBpcGVsaW5lJyB9O1xufVxuXG4vKiogUGFyc2UgKyByZXNvbHZlIG9uZSByZWNvcmQgdG8gYSByZXBvcnRhYmxlIGV2ZW50LCBvciBudWxsIHRvIHNraXAgaXQuICovXG5hc3luYyBmdW5jdGlvbiBwYXJzZVJlY29yZChyZWNvcmQ6IFNRU1JlY29yZCk6IFByb21pc2U8UGFyc2VkRXZlbnQgfCBudWxsPiB7XG4gIGxldCBldmVudDoge1xuICAgICdkZXRhaWwtdHlwZSc6IHN0cmluZztcbiAgICAnc291cmNlJzogc3RyaW5nO1xuICAgICdkZXRhaWwnOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPjtcbiAgICAndGltZSc6IHN0cmluZztcbiAgICAncmVnaW9uJzogc3RyaW5nO1xuICAgICdhY2NvdW50Jzogc3RyaW5nO1xuICB9O1xuICAvLyBBIG1hbGZvcm1lZCBib2R5IGlzIGEgZGVhZCBtZXNzYWdlIChyZXRyeWluZyB3b24ndCBoZWxwKSDigJQgbG9nICsgc2tpcCBpdCBzb1xuICAvLyBpdCBjYW4ndCBmYWlsIHRoZSB3aG9sZSBiYXRjaC4gUmVzb2x1dGlvbiBlcnJvcnMgYmVsb3cgKGUuZy4gQWNjZXNzRGVuaWVkKVxuICAvLyBhcmUgTk9UIGNhdWdodCBoZXJlOiB0aG9zZSBhcmUgdHJhbnNpZW50L2luZnJhIGFuZCBtdXN0IHByb3BhZ2F0ZSB0byByZXRyeS5cbiAgdHJ5IHtcbiAgICBldmVudCA9IEpTT04ucGFyc2UocmVjb3JkLmJvZHkpO1xuICB9IGNhdGNoIChlcnIpIHtcbiAgICBsb2cud2FybignU2tpcHBpbmcgU1FTIHJlY29yZCB3aXRoIHVucGFyc2VhYmxlIGJvZHknLCB7IG1lc3NhZ2VJZDogcmVjb3JkLm1lc3NhZ2VJZCwgZXJyb3I6IFN0cmluZyhlcnIpIH0pO1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgY29uc3QgeyBldmVudFR5cGUsIGV2ZW50U291cmNlIH0gPSBjbGFzc2lmeUV2ZW50KGV2ZW50WydkZXRhaWwtdHlwZSddKTtcbiAgY29uc3QgZGV0YWlsID0geyAuLi5ldmVudC5kZXRhaWwgfTtcbiAgLy8gVGhlIHJhdyBhY2NvdW50IG5ldmVyIG5lZWRzIHRvIGxlYXZlIEFXUyBub3cg4oCUIGRyb3AgaXQgZnJvbSB0aGUgcGF5bG9hZC5cbiAgZGVsZXRlIGRldGFpbC5hY2NvdW50O1xuXG4gIC8vIENvZGVCdWlsZCBcIkJ1aWxkIFN0YXRlXCIgZXZlbnRzIGlkZW50aWZ5IGEgYnVpbGQgKnByb2plY3QqLCBub3QgYSBwaXBlbGluZSxcbiAgLy8gYW5kIGEgcHJvamVjdCBjYW4gYmUgc2hhcmVkIGFjcm9zcyBwaXBlbGluZXMg4oCUIHRoZXJlJ3Mgbm8gY2xlYW4gMToxIG1hcHBpbmdcbiAgLy8gdG8gYSBwaXBlbGluZSBpZCwgc28gc2tpcCB0aGVtLlxuICBpZiAoZXZlbnRTb3VyY2UgIT09ICdjb2RlcGlwZWxpbmUnKSB7XG4gICAgbG9nLndhcm4oJ1NraXBwaW5nIG5vbi1Db2RlUGlwZWxpbmUgZXZlbnQgKG5vIHBpcGVsaW5lIHRhZyB0byByZXNvbHZlKScsIHsgZGV0YWlsVHlwZTogZXZlbnRbJ2RldGFpbC10eXBlJ10gfSk7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICBjb25zdCBwaXBlbGluZU5hbWUgPSBkZXRhaWwucGlwZWxpbmUgYXMgc3RyaW5nIHwgdW5kZWZpbmVkO1xuICBpZiAoIXBpcGVsaW5lTmFtZSkge1xuICAgIGxvZy53YXJuKCdDb2RlUGlwZWxpbmUgZXZlbnQgbWlzc2luZyBwaXBlbGluZSBuYW1lIOKAlCBza2lwcGluZycpO1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgLy8gUmVzb2x2ZSB0aGUgcGlwZWxpbmUncyBwYi5waXBlbGluZS1pZCAoPSBwbGF0Zm9ybSBwaXBlbGluZUlkKSArIHBiLmRlcGxveXMgbWFwLlxuICAvLyBUaGUgQVJOIGlzIG9ubHkgYSB0cmFuc2llbnQgaGFuZGxlIGZvciB0aGUgdGFnIGxvb2t1cDsgaXQgaXMgbmV2ZXIgc3RvcmVkLlxuICBjb25zdCBhcm4gPSBgYXJuOmF3czpjb2RlcGlwZWxpbmU6JHtldmVudC5yZWdpb259OiR7ZXZlbnQuYWNjb3VudH06JHtwaXBlbGluZU5hbWV9YDtcbiAgY29uc3QgeyBwaXBlbGluZUlkLCBvcmdJZCwgZGVwbG95cyB9ID0gYXdhaXQgcmVzb2x2ZVBpcGVsaW5lKGFybiwgZXZlbnQucmVnaW9uKTtcbiAgaWYgKCFwaXBlbGluZUlkKSByZXR1cm4gbnVsbDsgLy8gdW50YWdnZWQgLyB1bnJlZ2lzdGVyZWQg4oaSIHNraXBcblxuICBjb25zdCBzdGFydGVkQXQgPSAoZGV0YWlsWydzdGFydC10aW1lJ10gYXMgc3RyaW5nKSB8fCBldmVudC50aW1lO1xuICBjb25zdCBzdGF0ZSA9IGRldGFpbC5zdGF0ZSBhcyBzdHJpbmc7XG5cbiAgbGV0IGR1cmF0aW9uTXM6IG51bWJlciB8IHVuZGVmaW5lZDtcbiAgbGV0IGNvbXBsZXRlZEF0OiBzdHJpbmcgfCB1bmRlZmluZWQ7XG5cbiAgaWYgKFsnU1VDQ0VFREVEJywgJ0ZBSUxFRCcsICdDQU5DRUxFRCcsICdTVE9QUEVEJ10uaW5jbHVkZXMoc3RhdGUpKSB7XG4gICAgY29tcGxldGVkQXQgPSBldmVudC50aW1lO1xuICAgIGlmIChzdGFydGVkQXQgJiYgY29tcGxldGVkQXQpIHtcbiAgICAgIGNvbnN0IG1zID0gbmV3IERhdGUoY29tcGxldGVkQXQpLmdldFRpbWUoKSAtIG5ldyBEYXRlKHN0YXJ0ZWRBdCkuZ2V0VGltZSgpO1xuICAgICAgaWYgKG1zID49IDApIGR1cmF0aW9uTXMgPSBtcztcbiAgICB9XG4gIH1cblxuICAvLyBQcm9tb3RlIHRoZSBmYWlsdXJlIHJlYXNvbiB0byBhIHR5cGVkIGZpZWxkLiBPbiBBY3Rpb24gZXZlbnRzIEFXUyBwdXRzIHRoZVxuICAvLyBodW1hbi1yZWFkYWJsZSBzdW1tYXJ5IChhbmQgYSBsb2cgVVJMICsgZXJyb3ItY29kZSwgd2hpY2ggc3RheSBpbiBgZGV0YWlsYClcbiAgLy8gdW5kZXIgYGV4ZWN1dGlvbi1yZXN1bHRgLlxuICBjb25zdCByZXN1bHQgPSBkZXRhaWxbJ2V4ZWN1dGlvbi1yZXN1bHQnXSBhcyB7ICdleHRlcm5hbC1leGVjdXRpb24tc3VtbWFyeSc/OiB1bmtub3duIH0gfCB1bmRlZmluZWQ7XG4gIGNvbnN0IHN1bW1hcnkgPSByZXN1bHQ/LlsnZXh0ZXJuYWwtZXhlY3V0aW9uLXN1bW1hcnknXTtcbiAgY29uc3QgZXJyb3JNZXNzYWdlID0gdHlwZW9mIHN1bW1hcnkgPT09ICdzdHJpbmcnICYmIHN1bW1hcnkubGVuZ3RoID4gMFxuICAgID8gc3VtbWFyeS5zbGljZSgwLCBNQVhfRVJST1JfTUVTU0FHRSlcbiAgICA6IHVuZGVmaW5lZDtcblxuICAvLyBET1JBIGRlcGxveSBhdHRyaWJ1dGlvbiDigJQgYWxsIG9wdGlvbmFsOiBvbWl0dGVkICh1bmRlZmluZWQpIHdoZW4gdGhlXG4gIC8vIGV2ZW50L3BpcGVsaW5lIGRvZXNuJ3QgY2FycnkgdGhlbS5cbiAgY29uc3QgeyBjb21taXRTaGEsIGNvbW1pdFJlZiwgcmV2aXNpb25VcmwgfSA9IGV4dHJhY3RDb21taXQoZGV0YWlsKTtcblxuICBjb25zdCBleGVjdXRpb25JZCA9IGRldGFpbFsnZXhlY3V0aW9uLWlkJ10gYXMgc3RyaW5nIHwgdW5kZWZpbmVkO1xuICBjb25zdCBzdGFnZU5hbWUgPSBkZXRhaWwuc3RhZ2UgYXMgc3RyaW5nIHwgdW5kZWZpbmVkO1xuICBjb25zdCBhY3Rpb25OYW1lID0gZGV0YWlsLmFjdGlvbiBhcyBzdHJpbmcgfCB1bmRlZmluZWQ7XG5cbiAgLy8gRW52aXJvbm1lbnQgKOKHkiBpc0RlcGxveSkgaXMgc2V0IE9OTFkgd2hlbiB0aGlzIGV2ZW50J3Mgc3RhZ2UgaXMgbGlzdGVkIGluIHRoZVxuICAvLyBwaXBlbGluZSdzIHBiLmRlcGxveXMgdGFnLiBQSVBFTElORSBldmVudHMgaGF2ZSBubyBzdGFnZSDihpIgbmV2ZXIgYSBkZXBsb3kuXG4gIGNvbnN0IGVudmlyb25tZW50ID0gc3RhZ2VOYW1lID8gZGVwbG95cy5nZXQoc3RhZ2VOYW1lKSA6IHVuZGVmaW5lZDtcblxuICAvLyBQaGFzZSA0IOKAlCByZXNvbHZlIHRoZSBjb21taXQgcmFuZ2UgKG9sZGVzdC11bnNoaXBwZWQgdGltZXN0YW1wICsgY291bnQpIGZvciBhbnlcbiAgLy8gZXZlbnQgY2FycnlpbmcgYSBjb21taXQuIEJlc3QtZWZmb3J0L2luLWFjY291bnQ7IG9taXR0ZWQgb24gYW55IGZhaWx1cmUuXG4gIC8vIEdBVEVEIG9uIGBET1JBX0VOQUJMRURgIChzZXQgYnkgYHNldHVwLWV2ZW50cyAtLXdpdGgtZG9yYWApOiBET1JBIGxlYWQgdGltZSBpcyBhblxuICAvLyBhZHZhbmNlZF9yZXBvcnRpbmcgYWRkLW9uLCBzbyBvcmdzIHdpdGhvdXQgaXQgc2tpcCB0aGUgU0NNL3NlY3JldCBjb3N0IGVudGlyZWx5IOKAlFxuICAvLyBzdGFuZGFyZCByZXBvcnRpbmcgKGNvbW1pdFNoYSArIGFsbCBvdGhlciBmaWVsZHMpIHN0aWxsIGZvcndhcmRzIHJlZ2FyZGxlc3MuXG4gIGxldCBjb21taXRUaW1lc3RhbXA6IHN0cmluZyB8IHVuZGVmaW5lZDtcbiAgbGV0IGNvbW1pdENvdW50OiBudW1iZXIgfCB1bmRlZmluZWQ7XG4gIGlmIChjb21taXRTaGEgJiYgcHJvY2Vzcy5lbnYuRE9SQV9FTkFCTEVEID09PSAndHJ1ZScpIHtcbiAgICBjb25zdCBzcmMgPSBjbGFzc2lmeVNvdXJjZShyZXZpc2lvblVybCk7XG4gICAgY29uc3QgaW5mbyA9IGF3YWl0IHJlc29sdmVDb21taXRJbmZvKHBpcGVsaW5lSWQsIG9yZ0lkLCBldmVudC5yZWdpb24sIHsgc2hhOiBjb21taXRTaGEsIC4uLnNyYyB9KTtcbiAgICBjb21taXRUaW1lc3RhbXAgPSBpbmZvLmNvbW1pdFRpbWVzdGFtcDtcbiAgICBjb21taXRDb3VudCA9IGluZm8uY29tbWl0Q291bnQ7XG4gIH1cblxuICByZXR1cm4ge1xuICAgIG9yZ0lkLFxuICAgIHBpcGVsaW5lSWQsXG4gICAgZXZlbnRTb3VyY2UsXG4gICAgZXZlbnRUeXBlLFxuICAgIHN0YXR1czogc3RhdGUsXG4gICAgZXhlY3V0aW9uSWQsXG4gICAgc3RhZ2VOYW1lLFxuICAgIGFjdGlvbk5hbWUsXG4gICAgZXJyb3JNZXNzYWdlLFxuICAgIHN0YXJ0ZWRBdCxcbiAgICBjb21wbGV0ZWRBdCxcbiAgICBkdXJhdGlvbk1zLFxuICAgIC4uLihjb21taXRTaGEgJiYgeyBjb21taXRTaGEgfSksXG4gICAgLi4uKGNvbW1pdFJlZiAmJiB7IGNvbW1pdFJlZiB9KSxcbiAgICAuLi4oY29tbWl0VGltZXN0YW1wICYmIHsgY29tbWl0VGltZXN0YW1wIH0pLFxuICAgIC4uLihjb21taXRDb3VudCAmJiB7IGNvbW1pdENvdW50IH0pLFxuICAgIC4uLihlbnZpcm9ubWVudCAmJiB7IGVudmlyb25tZW50IH0pLFxuICAgIGRldGFpbCxcbiAgfTtcbn1cblxuLy8g4pSA4pSA4pSAIFBoYXNlIDM6IGRlbGl2ZXJ5IGhlYWx0aCArIHNlbGYtaGVhbGluZyBETFEgcmVkcml2ZSDilIBcbi8vIEFmdGVyIGEgU1VDQ0VTU0ZVTCBiYXRjaCBQT1NUIHdlICh0aHJvdHRsZWQsIGJlc3QtZWZmb3J0KTpcbi8vICAgMS4gUE9TVCBhbiBpbmdlc3QtaGVhbHRoIHNpZ25hbCB0byAvYXBpL3JlcG9ydHMvaW5nZXN0LWhlYWx0aCBzbyB0aGUgUmVwb3J0cyBVSVxuLy8gICAgICBjYW4gc2hvdyBmbG93aW5nIC8gc3RhbGUgLyBkcm9wcGluZy5cbi8vICAgMi4gSWYgdGhlIERMUSBpcyBub24tZW1wdHksIG5vIG1vdmUgdGFzayBpcyBhbHJlYWR5IHJ1bm5pbmcsIEFORCB0aGUgb2xkZXN0IERMUVxuLy8gICAgICBtZXNzYWdlIGlzIHlvdW5nZXIgdGhhbiB0aGUgcG9pc29uLWFnZSBjZWlsaW5nLCBraWNrIG9mZiBhbiBTUVNcbi8vICAgICAgTWVzc2FnZU1vdmVUYXNrIChETFEg4oaSIG1haW4gcXVldWUpIHNvIHJldHJ5YWJsZSBmYWlsdXJlcyBzZWxmLWhlYWwgd2l0aG91dCBhXG4vLyAgICAgIG5ldyBBV1Mgc2VydmljZS4gVGhlIHJlcG9ydGluZyBpbmdlc3QncyBwYXJ0aWFsLXVuaXF1ZSBpbmRleCBkZWR1cGVzIGFueVxuLy8gICAgICByZWRlbGl2ZXJlZCBldmVudCwgc28gYSByZWRyaXZlIGNhbid0IGRvdWJsZS1jb3VudC5cbi8vIEd1YXJkczogb25seSBvbiBzdWNjZXNzLCBvbmUtYXQtYS10aW1lIChMaXN0TWVzc2FnZU1vdmVUYXNrcyksIGEgcG9pc29uLWFnZSBjZWlsaW5nXG4vLyAoc28gYSBtZXNzYWdlIHRoYXQgYWx3YXlzIGZhaWxzIGNhbid0IGxvb3AgRExR4oaSbWFpbuKGkkRMUSBmb3JldmVyKSwgYW5kIHRocm90dGxlZCB2aWFcbi8vIG1vZHVsZSB0aW1lc3RhbXBzLiBORVZFUiB0aHJvd3Mg4oCUIGEgZmFpbHVyZSBoZXJlIG11c3Qgbm90IGZhaWwgdGhlIGJhdGNoLlxuLy9cbi8vIElBTSByZXF1aXJlZCAoZG9jdW1lbnQgb25seSDigJQgZ3JhbnRlZCBvbiB0aGUgZXZlbnRzIHN0YWNrKTpcbi8vICAgc3FzOkdldFF1ZXVlQXR0cmlidXRlcywgc3FzOkxpc3RNZXNzYWdlTW92ZVRhc2tzLCBzcXM6U3RhcnRNZXNzYWdlTW92ZVRhc2sgKG9uIHRoZSBETFEpXG5cbmludGVyZmFjZSBTcXNDbGllbnRMaWtlIHsgc2VuZChjb21tYW5kOiB1bmtub3duKTogUHJvbWlzZTx1bmtub3duPiB9XG5pbnRlcmZhY2UgU3FzTW9kdWxlIHtcbiAgU1FTQ2xpZW50OiBuZXcgKGNvbmZpZzogeyByZWdpb246IHN0cmluZyB9KSA9PiBTcXNDbGllbnRMaWtlO1xuICBHZXRRdWV1ZUF0dHJpYnV0ZXNDb21tYW5kOiBuZXcgKGlucHV0OiB7IFF1ZXVlVXJsOiBzdHJpbmc7IEF0dHJpYnV0ZU5hbWVzOiBzdHJpbmdbXSB9KSA9PiB1bmtub3duO1xuICBMaXN0TWVzc2FnZU1vdmVUYXNrc0NvbW1hbmQ6IG5ldyAoaW5wdXQ6IHsgU291cmNlQXJuOiBzdHJpbmcgfSkgPT4gdW5rbm93bjtcbiAgU3RhcnRNZXNzYWdlTW92ZVRhc2tDb21tYW5kOiBuZXcgKGlucHV0OiB7IFNvdXJjZUFybjogc3RyaW5nOyBEZXN0aW5hdGlvbkFybj86IHN0cmluZyB9KSA9PiB1bmtub3duO1xufVxubGV0IHNxc01vZDogU3FzTW9kdWxlIHwgdW5kZWZpbmVkO1xubGV0IHNxc0NsaWVudHNCeVJlZ2lvbjogTWFwPHN0cmluZywgU3FzQ2xpZW50TGlrZT4gfCB1bmRlZmluZWQ7XG5hc3luYyBmdW5jdGlvbiBzcXNDbGllbnQocmVnaW9uOiBzdHJpbmcpOiBQcm9taXNlPFNxc0NsaWVudExpa2U+IHtcbiAgaWYgKCFzcXNNb2QpIHNxc01vZCA9IGF3YWl0IGxvYWRTZGs8U3FzTW9kdWxlPignQGF3cy1zZGsvY2xpZW50LXNxcycpO1xuICBpZiAoIXNxc0NsaWVudHNCeVJlZ2lvbikgc3FzQ2xpZW50c0J5UmVnaW9uID0gbmV3IE1hcCgpO1xuICBsZXQgY2xpZW50ID0gc3FzQ2xpZW50c0J5UmVnaW9uLmdldChyZWdpb24pO1xuICBpZiAoIWNsaWVudCkge1xuICAgIGNsaWVudCA9IG5ldyBzcXNNb2QuU1FTQ2xpZW50KHsgcmVnaW9uIH0pO1xuICAgIHNxc0NsaWVudHNCeVJlZ2lvbi5zZXQocmVnaW9uLCBjbGllbnQpO1xuICB9XG4gIHJldHVybiBjbGllbnQ7XG59XG5cbi8vIERlbGl2ZXJ5LWhlYWx0aCBjb3VudGVycyBhcmUgYXR0cmlidXRlZCBQRVIgUkVTT0xWRUQgT1JHICh0aGUgZm9yd2FyZGVyIGlzIGEgZmxlZXRcbi8vIExhbWJkYSBzZXJ2aW5nIG1hbnkgb3JnczsgYSBzaW5nbGUgZ2xvYmFsIGNvdW50ZXIgd291bGQgbWlzLWF0dHJpYnV0ZSBvbmUgb3JnJ3Ncbi8vIHRocm91Z2hwdXQgdG8gYW5vdGhlcikuIEtleWVkIGJ5IG9yZ0lkICgnJyBidWNrZXQgPSBwaXBlbGluZSB3aXRoIG5vIE9yZ0lkIHRhZykuXG4vLyBUaGUgRExRIGRlcHRoIChgZHJvcHBlZGApIGlzIGdlbnVpbmVseSBmbGVldC1nbG9iYWwg4oCUIG9uZSBzaGFyZWQgRExRIOKAlCBzbyBpdCBpcyBhXG4vLyBzaW5nbGUgc25hcHNob3QgcG9zdGVkIGFsb25nc2lkZSBlYWNoIG9yZydzIGhlYWx0aCwgZG9jdW1lbnRlZCBhcyB0aGUgc2hhcmVkIGRlcHRoLlxuY29uc3QgZm9yd2FyZGVkQnlPcmcgPSBuZXcgTWFwPHN0cmluZywgbnVtYmVyPigpO1xuY29uc3QgbGFzdEV2ZW50QXRCeU9yZyA9IG5ldyBNYXA8c3RyaW5nLCBzdHJpbmc+KCk7XG5sZXQgbGFzdEhlYWx0aEF0ID0gMDtcbmxldCBsYXN0UmVkcml2ZUF0ID0gMDtcbmNvbnN0IEhFQUxUSF9USFJPVFRMRV9NUyA9IDYwICogMTAwMDtcbmNvbnN0IFJFRFJJVkVfVEhST1RUTEVfTVMgPSA1ICogNjAgKiAxMDAwO1xuLy8gUG9pc29uLW1lc3NhZ2UgY2VpbGluZzogaWYgdGhlIG9sZGVzdCBETFEgbWVzc2FnZSBpcyBvbGRlciB0aGFuIHRoaXMsIHdlIFNUT1Bcbi8vIHJlZHJpdmluZyBpdCAoRExR4oaSbWFpbuKGkmZhaWzihpJETFEgd291bGQgbG9vcCBmb3JldmVyKS4gU3VjaCBtZXNzYWdlcyBhcmUgbGVmdCBmb3IgYW5cbi8vIGFsYXJtIC8gbWFudWFsIGluc3BlY3Rpb24gaW5zdGVhZCBvZiBjaHVybmluZyB0aGUgZmxlZXQgaW5kZWZpbml0ZWx5LlxuY29uc3QgTUFYX0RMUV9SRURSSVZFX0FHRV9TRUMgPSA2ICogNjAgKiA2MDsgLy8gNiBob3Vyc1xuXG4vKiogUGFyc2UgdGhlIFNRUyB0cmlnZ2VyIEFSTiBhbmQgZGVyaXZlIHRoZSBtYWluICsgRExRIEFSTnMgYW5kIHRoZSBETFEgVVJMLiAqL1xuZnVuY3Rpb24gZGVyaXZlUXVldWVzKG1haW5RdWV1ZUFybjogc3RyaW5nIHwgdW5kZWZpbmVkKTogeyByZWdpb246IHN0cmluZzsgbWFpbkFybjogc3RyaW5nOyBkbHFBcm46IHN0cmluZzsgZGxxVXJsOiBzdHJpbmcgfSB8IHVuZGVmaW5lZCB7XG4gIGlmICghbWFpblF1ZXVlQXJuKSByZXR1cm4gdW5kZWZpbmVkO1xuICAvLyBhcm46YXdzOnNxczp7cmVnaW9ufTp7YWNjb3VudH06e25hbWV9XG4gIGNvbnN0IHBhcnRzID0gbWFpblF1ZXVlQXJuLnNwbGl0KCc6Jyk7XG4gIGlmIChwYXJ0cy5sZW5ndGggPCA2IHx8IHBhcnRzWzJdICE9PSAnc3FzJykgcmV0dXJuIHVuZGVmaW5lZDtcbiAgY29uc3QgWywgLCAsIHJlZ2lvbiwgYWNjb3VudCwgbmFtZV0gPSBwYXJ0cztcbiAgY29uc3QgZGxxQXJuID0gcHJvY2Vzcy5lbnYuRVZFTlRfRExRX0FSTiB8fCBgYXJuOmF3czpzcXM6JHtyZWdpb259OiR7YWNjb3VudH06JHtuYW1lfS1kbHFgO1xuICBjb25zdCBkbHFOYW1lID0gZGxxQXJuLnNwbGl0KCc6JykucG9wKCkgPz8gYCR7bmFtZX0tZGxxYDtcbiAgY29uc3QgZGxxVXJsID0gYGh0dHBzOi8vc3FzLiR7cmVnaW9ufS5hbWF6b25hd3MuY29tLyR7YWNjb3VudH0vJHtkbHFOYW1lfWA7XG4gIHJldHVybiB7IHJlZ2lvbiwgbWFpbkFybjogbWFpblF1ZXVlQXJuLCBkbHFBcm4sIGRscVVybCB9O1xufVxuXG4vKiogU25hcHNob3QgdGhlIERMUSBkZXB0aCArIHRoZSBhZ2UgKHNlY29uZHMpIG9mIGl0cyBvbGRlc3QgbWVzc2FnZSAoZm9yIHRoZVxuICogIHBvaXNvbi1tZXNzYWdlIHJlZHJpdmUgY2VpbGluZykuIE1pc3NpbmcvTmFOIGF0dHJpYnV0ZXMgZmFsbCBiYWNrIHRvIDAuICovXG5hc3luYyBmdW5jdGlvbiBkbHFTdGF0cyhyZWdpb246IHN0cmluZywgZGxxVXJsOiBzdHJpbmcpOiBQcm9taXNlPHsgZGVwdGg6IG51bWJlcjsgb2xkZXN0QWdlU2VjOiBudW1iZXIgfT4ge1xuICBpZiAoIXNxc01vZCkgc3FzTW9kID0gYXdhaXQgbG9hZFNkazxTcXNNb2R1bGU+KCdAYXdzLXNkay9jbGllbnQtc3FzJyk7XG4gIGNvbnN0IGNsaWVudCA9IGF3YWl0IHNxc0NsaWVudChyZWdpb24pO1xuICBjb25zdCBvdXQgPSBhd2FpdCBjbGllbnQuc2VuZChuZXcgc3FzTW9kLkdldFF1ZXVlQXR0cmlidXRlc0NvbW1hbmQoe1xuICAgIFF1ZXVlVXJsOiBkbHFVcmwsXG4gICAgQXR0cmlidXRlTmFtZXM6IFsnQXBwcm94aW1hdGVOdW1iZXJPZk1lc3NhZ2VzJywgJ0FwcHJveGltYXRlQWdlT2ZPbGRlc3RNZXNzYWdlJ10sXG4gIH0pKSBhcyB7IEF0dHJpYnV0ZXM/OiB7IEFwcHJveGltYXRlTnVtYmVyT2ZNZXNzYWdlcz86IHN0cmluZzsgQXBwcm94aW1hdGVBZ2VPZk9sZGVzdE1lc3NhZ2U/OiBzdHJpbmcgfSB9O1xuICBjb25zdCBuID0gTnVtYmVyKG91dC5BdHRyaWJ1dGVzPy5BcHByb3hpbWF0ZU51bWJlck9mTWVzc2FnZXMgPz8gJzAnKTtcbiAgY29uc3QgYWdlID0gTnVtYmVyKG91dC5BdHRyaWJ1dGVzPy5BcHByb3hpbWF0ZUFnZU9mT2xkZXN0TWVzc2FnZSA/PyAnMCcpO1xuICByZXR1cm4ge1xuICAgIGRlcHRoOiBOdW1iZXIuaXNGaW5pdGUobikgPyBuIDogMCxcbiAgICBvbGRlc3RBZ2VTZWM6IE51bWJlci5pc0Zpbml0ZShhZ2UpID8gYWdlIDogMCxcbiAgfTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gaGFzQWN0aXZlTW92ZVRhc2socmVnaW9uOiBzdHJpbmcsIGRscUFybjogc3RyaW5nKTogUHJvbWlzZTxib29sZWFuPiB7XG4gIGlmICghc3FzTW9kKSBzcXNNb2QgPSBhd2FpdCBsb2FkU2RrPFNxc01vZHVsZT4oJ0Bhd3Mtc2RrL2NsaWVudC1zcXMnKTtcbiAgY29uc3QgY2xpZW50ID0gYXdhaXQgc3FzQ2xpZW50KHJlZ2lvbik7XG4gIGNvbnN0IG91dCA9IGF3YWl0IGNsaWVudC5zZW5kKG5ldyBzcXNNb2QuTGlzdE1lc3NhZ2VNb3ZlVGFza3NDb21tYW5kKHsgU291cmNlQXJuOiBkbHFBcm4gfSkpIGFzIHtcbiAgICBSZXN1bHRzPzogQXJyYXk8eyBTdGF0dXM/OiBzdHJpbmcgfT47XG4gIH07XG4gIHJldHVybiAob3V0LlJlc3VsdHMgPz8gW10pLnNvbWUodCA9PiB0LlN0YXR1cyA9PT0gJ1JVTk5JTkcnKTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gc3RhcnRNb3ZlVGFzayhyZWdpb246IHN0cmluZywgZGxxQXJuOiBzdHJpbmcsIG1haW5Bcm46IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICBpZiAoIXNxc01vZCkgc3FzTW9kID0gYXdhaXQgbG9hZFNkazxTcXNNb2R1bGU+KCdAYXdzLXNkay9jbGllbnQtc3FzJyk7XG4gIGNvbnN0IGNsaWVudCA9IGF3YWl0IHNxc0NsaWVudChyZWdpb24pO1xuICBhd2FpdCBjbGllbnQuc2VuZChuZXcgc3FzTW9kLlN0YXJ0TWVzc2FnZU1vdmVUYXNrQ29tbWFuZCh7IFNvdXJjZUFybjogZGxxQXJuLCBEZXN0aW5hdGlvbkFybjogbWFpbkFybiB9KSk7XG59XG5cbi8qKiBQT1NUIG9uZSBvcmcncyBpbmdlc3QtaGVhbHRoIHNpZ25hbC4gUmV0dXJucyB0cnVlIG9ubHkgb24gYSBjb25maXJtZWQgMnh4IHNvIHRoZVxuICogIGNhbGxlciByZXNldHMgdGhhdCBvcmcncyBgZm9yd2FyZGVkYCBjb3VudGVyIE9OTFkgd2hlbiB0aGUgc2lnbmFsIHdhcyBhY2NlcHRlZFxuICogIChhIGRyb3BwZWQvZmFpbGVkIFBPU1QgbXVzdCBub3Qgc2lsZW50bHkgemVybyB0aGUgYWNjdW11bGF0ZWQgY291bnQpLiAqL1xuYXN5bmMgZnVuY3Rpb24gcG9zdEluZ2VzdEhlYWx0aChiYXNlVXJsOiBzdHJpbmcsIGJvZHk6IHsgb3JnSWQ/OiBzdHJpbmc7IGZvcndhcmRlZDogbnVtYmVyOyBkcm9wcGVkOiBudW1iZXI7IGxhc3RFdmVudEF0Pzogc3RyaW5nIH0pOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgdHJ5IHtcbiAgICBjb25zdCB0b2tlbiA9IGF3YWl0IGdldEF1dGhUb2tlbigpO1xuICAgIGNvbnN0IHJlcyA9IGF3YWl0IGZldGNoKGAke2Jhc2VVcmx9L2FwaS9yZXBvcnRzL2luZ2VzdC1oZWFsdGhgLCB7XG4gICAgICBtZXRob2Q6ICdQT1NUJyxcbiAgICAgIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJywgJ0F1dGhvcml6YXRpb24nOiBgQmVhcmVyICR7dG9rZW59YCB9LFxuICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoYm9keSksXG4gICAgfSk7XG4gICAgaWYgKCFyZXMub2spIHtcbiAgICAgIGxvZy53YXJuKCdpbmdlc3QtaGVhbHRoIFBPU1QgcmV0dXJuZWQgbm9uLTJ4eCAoaWdub3JlZCknLCB7IHN0YXR1czogcmVzLnN0YXR1cywgb3JnSWQ6IGJvZHkub3JnSWQgfSk7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIHJldHVybiB0cnVlO1xuICB9IGNhdGNoIChlcnIpIHtcbiAgICBsb2cud2FybignaW5nZXN0LWhlYWx0aCBQT1NUIGZhaWxlZCAoaWdub3JlZCknLCB7IGVycm9yOiBTdHJpbmcoZXJyKSwgb3JnSWQ6IGJvZHkub3JnSWQgfSk7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG59XG5cbi8qKlxuICogVGhyb3R0bGVkLCBiZXN0LWVmZm9ydCBwb3N0LXN1Y2Nlc3Mgd29yazogZW1pdCBwZXItb3JnIGluZ2VzdC1oZWFsdGggYW5kIHNlbGYtaGVhbFxuICogdGhlIERMUS4gTmV2ZXIgdGhyb3dzLiBgZm9yd2FyZGVkYC9gbGFzdEV2ZW50QXRgIGFjY3VtdWxhdGUgUEVSIE9SRyBhY3Jvc3MgYmF0Y2hlc1xuICogYmV0d2VlbiBoZWFsdGggcG9zdHM7IGBkcm9wcGVkYCBpcyB0aGUgc2hhcmVkIGZsZWV0IERMUSBkZXB0aCBzbmFwc2hvdC4gQSBwZXItb3JnXG4gKiBjb3VudGVyIGlzIHplcm9lZCBvbmx5IGFmdGVyIHRoYXQgb3JnJ3MgaGVhbHRoIFBPU1QgaXMgY29uZmlybWVkIDJ4eC5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gcmVwb3J0SGVhbHRoQW5kUmVkcml2ZShiYXNlVXJsOiBzdHJpbmcsIG1haW5RdWV1ZUFybjogc3RyaW5nIHwgdW5kZWZpbmVkKTogUHJvbWlzZTx2b2lkPiB7XG4gIGNvbnN0IG5vdyA9IERhdGUubm93KCk7XG4gIGlmIChub3cgLSBsYXN0SGVhbHRoQXQgPCBIRUFMVEhfVEhST1RUTEVfTVMpIHJldHVybjtcbiAgbGFzdEhlYWx0aEF0ID0gbm93O1xuICB0cnkge1xuICAgIGNvbnN0IHEgPSBkZXJpdmVRdWV1ZXMobWFpblF1ZXVlQXJuKTtcbiAgICBsZXQgZGVwdGggPSAwO1xuICAgIGxldCBvbGRlc3RBZ2VTZWMgPSAwO1xuICAgIGlmIChxKSB7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCBzdGF0cyA9IGF3YWl0IGRscVN0YXRzKHEucmVnaW9uLCBxLmRscVVybCk7XG4gICAgICAgIGRlcHRoID0gc3RhdHMuZGVwdGg7XG4gICAgICAgIG9sZGVzdEFnZVNlYyA9IHN0YXRzLm9sZGVzdEFnZVNlYztcbiAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICBsb2cud2FybignRExRIHN0YXRzIGNoZWNrIGZhaWxlZCAoaWdub3JlZCknLCB7IGVycm9yOiAoZXJyIGFzIHsgbmFtZT86IHN0cmluZyB9KT8ubmFtZSA/PyBTdHJpbmcoZXJyKSB9KTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBQb3N0IG9uZSBoZWFsdGggc2lnbmFsIHBlciByZXNvbHZlZCBvcmcsIGF0dHJpYnV0aW5nIGl0cyBvd24gZm9yd2FyZGVkIGNvdW50ICtcbiAgICAvLyBsYXN0IGV2ZW50IHRpbWUuIGBkcm9wcGVkYCAodGhlIHNoYXJlZCBmbGVldCBETFEgZGVwdGgpIHJpZGVzIGFsb25nIG9uIGVhY2guXG4gICAgLy8gU25hcHNob3QgdGhlIGtleXMgZmlyc3Qgc28gd2UgZG9uJ3QgbXV0YXRlIHRoZSBtYXAgd2hpbGUgaXRlcmF0aW5nLlxuICAgIGNvbnN0IG9yZ0tleXMgPSBuZXcgU2V0KFsuLi5mb3J3YXJkZWRCeU9yZy5rZXlzKCksIC4uLmxhc3RFdmVudEF0QnlPcmcua2V5cygpXSk7XG4gICAgaWYgKG9yZ0tleXMuc2l6ZSA9PT0gMCkgb3JnS2V5cy5hZGQoJycpOyAvLyBub3RoaW5nIGZvcndhcmRlZCB5ZXQg4oaSIHN0aWxsIHJlcG9ydCBkZXB0aFxuICAgIGZvciAoY29uc3Qga2V5IG9mIG9yZ0tleXMpIHtcbiAgICAgIGNvbnN0IGZvcndhcmRlZCA9IGZvcndhcmRlZEJ5T3JnLmdldChrZXkpID8/IDA7XG4gICAgICBjb25zdCBsYXN0RXZlbnRBdCA9IGxhc3RFdmVudEF0QnlPcmcuZ2V0KGtleSk7XG4gICAgICBjb25zdCBvayA9IGF3YWl0IHBvc3RJbmdlc3RIZWFsdGgoYmFzZVVybCwge1xuICAgICAgICAuLi4oa2V5ID8geyBvcmdJZDoga2V5IH0gOiB7fSksXG4gICAgICAgIGZvcndhcmRlZCxcbiAgICAgICAgZHJvcHBlZDogZGVwdGgsXG4gICAgICAgIGxhc3RFdmVudEF0LFxuICAgICAgfSk7XG4gICAgICAvLyBSZXNldCB0aGUgZm9yd2FyZGVkIGNvdW50ZXIgT05MWSBvbiBhIGNvbmZpcm1lZCAyeHgg4oCUIGEgZmFpbGVkL2Ryb3BwZWQgUE9TVFxuICAgICAgLy8ga2VlcHMgdGhlIGNvdW50IHNvIHRoZSBuZXh0IHRpY2sgcmUtcmVwb3J0cyBpdCBpbnN0ZWFkIG9mIGxvc2luZyBpdC5cbiAgICAgIGlmIChvaykgZm9yd2FyZGVkQnlPcmcuc2V0KGtleSwgMCk7XG4gICAgfVxuXG4gICAgaWYgKHEgJiYgZGVwdGggPiAwICYmIG5vdyAtIGxhc3RSZWRyaXZlQXQgPj0gUkVEUklWRV9USFJPVFRMRV9NUykge1xuICAgICAgLy8gUG9pc29uLW1lc3NhZ2UgY2VpbGluZzogbmV2ZXIgcmVkcml2ZSBhIERMUSB3aG9zZSBvbGRlc3QgbWVzc2FnZSBoYXMgYWdlZFxuICAgICAgLy8gcGFzdCB0aGUgY2VpbGluZyDigJQgdGhhdCBtZXNzYWdlIGtlZXBzIGZhaWxpbmcgYW5kIHdvdWxkIGxvb3AgRExR4oaSbWFpbuKGkkRMUS5cbiAgICAgIGlmIChvbGRlc3RBZ2VTZWMgPiBNQVhfRExRX1JFRFJJVkVfQUdFX1NFQykge1xuICAgICAgICBsb2cud2FybignRExRIG9sZGVzdCBtZXNzYWdlIGV4Y2VlZHMgcG9pc29uLWFnZSBjZWlsaW5nIOKAlCBza2lwcGluZyByZWRyaXZlIChuZWVkcyBtYW51YWwgYXR0ZW50aW9uKScsIHtcbiAgICAgICAgICBkbHFBcm46IHEuZGxxQXJuLCBkcm9wcGVkOiBkZXB0aCwgb2xkZXN0QWdlU2VjLCBjZWlsaW5nU2VjOiBNQVhfRExRX1JFRFJJVkVfQUdFX1NFQyxcbiAgICAgICAgfSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0cnkge1xuICAgICAgICAgIGlmICghKGF3YWl0IGhhc0FjdGl2ZU1vdmVUYXNrKHEucmVnaW9uLCBxLmRscUFybikpKSB7XG4gICAgICAgICAgICBsYXN0UmVkcml2ZUF0ID0gbm93O1xuICAgICAgICAgICAgYXdhaXQgc3RhcnRNb3ZlVGFzayhxLnJlZ2lvbiwgcS5kbHFBcm4sIHEubWFpbkFybik7XG4gICAgICAgICAgICBsb2cuaW5mbygnU3RhcnRlZCBzZWxmLWhlYWxpbmcgRExR4oaSbWFpbiByZWRyaXZlJywgeyBkbHFBcm46IHEuZGxxQXJuLCBkcm9wcGVkOiBkZXB0aCwgb2xkZXN0QWdlU2VjIH0pO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgbG9nLndhcm4oJ0RMUSByZWRyaXZlIGZhaWxlZCAoaWdub3JlZCknLCB7IGVycm9yOiAoZXJyIGFzIHsgbmFtZT86IHN0cmluZyB9KT8ubmFtZSA/PyBTdHJpbmcoZXJyKSB9KTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfSBjYXRjaCAoZXJyKSB7XG4gICAgbG9nLndhcm4oJ2hlYWx0aC9yZWRyaXZlIHN0ZXAgZmFpbGVkIChpZ25vcmVkKScsIHsgZXJyb3I6IFN0cmluZyhlcnIpIH0pO1xuICB9XG59XG5cbi8vIOKUgOKUgOKUgCBIYW5kbGVyIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG5leHBvcnQgY29uc3QgaGFuZGxlciA9IGFzeW5jIChldmVudDogU1FTRXZlbnQpOiBQcm9taXNlPHZvaWQ+ID0+IHtcbiAgY29uc3QgYmFzZVVybCA9IHByb2Nlc3MuZW52LlBMQVRGT1JNX0JBU0VfVVJMO1xuICBpZiAoIWJhc2VVcmwpIHRocm93IG5ldyBFcnJvcignUExBVEZPUk1fQkFTRV9VUkwgZW52aXJvbm1lbnQgdmFyaWFibGUgaXMgcmVxdWlyZWQnKTtcblxuICAvLyBQYXJzZSArIHJlc29sdmUgYWxsIHJlY29yZHMgKHRhZyArIGNvbW1pdCBsb29rdXBzIHJ1biBjb25jdXJyZW50bHkpOyBkcm9wXG4gIC8vIGludGVudGlvbmFsIHNraXBzIChudWxsKS4gQSBtYWxmb3JtZWQgcmVjb3JkIEJPRFkgaXMgaGFuZGxlZCBpbnNpZGUgcGFyc2VSZWNvcmRcbiAgLy8gKGxvZ2dlZCArIHNraXBwZWQpIHNvIG9uZSBiYWQgbWVzc2FnZSBjYW4ndCBmYWlsIHRoZSB3aG9sZSBiYXRjaCDigJQgd2hpbGUgZ2VudWluZVxuICAvLyBpbmZyYSBlcnJvcnMgKGUuZy4gQWNjZXNzRGVuaWVkIG9uIHRoZSB0YWcgbG9va3VwKSBzdGlsbCBwcm9wYWdhdGUgc28gYSBtaXNzaW5nXG4gIC8vIElBTSBncmFudCBzdXJmYWNlcyBhbmQgU1FTIHJldHJpZXMgdGhlIGJhdGNoLlxuICBjb25zdCBldmVudHMgPSAoYXdhaXQgUHJvbWlzZS5hbGwoZXZlbnQuUmVjb3Jkcy5tYXAocGFyc2VSZWNvcmQpKSlcbiAgICAuZmlsdGVyKChlKTogZSBpcyBQYXJzZWRFdmVudCA9PiBlICE9PSBudWxsKTtcbiAgaWYgKGV2ZW50cy5sZW5ndGggPT09IDApIHtcbiAgICBsb2cuaW5mbygnTm8gcmVzb2x2YWJsZSBDb2RlUGlwZWxpbmUgZXZlbnRzIGluIGJhdGNoJyk7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgLy8gUE9TVCBiYXRjaCB0byByZXBvcnRpbmcgc2VydmljZS4gT24gYSA0MDEvNDAzIHRoZSBjcmVkZW50aWFsIHRoaXMgY29udGFpbmVyXG4gIC8vIGhvbGRzIGlzIHN0YWxlIOKAlCBtb3N0IG9mdGVuIGJlY2F1c2UgdGhlIHRva2VuLXJlbmV3IExhbWJkYSByb3RhdGVkIHRoZSBrZXkgaW5cbiAgLy8gU2VjcmV0cyBNYW5hZ2VyIOKAlCBzbyBkcm9wIEJPVEggdGhlIGNhY2hlZCB0b2tlbiBhbmQgdGhlIGNhY2hlZCBrZXksIHJlLXJlYWQsXG4gIC8vIGFuZCByZXRyeSBPTkNFLiBgb3JnSWRgIGlzIElOVEVSTkFMICh0b2tlbi9oZWFsdGgga2V5aW5nKSDigJQgc3RyaXAgaXQgaGVyZSBzb1xuICAvLyBpdCBuZXZlciByaWRlcyB0aGUgd2lyZTsgdGhlIGluZ2VzdCByZXNvbHZlcyBvcmcgZnJvbSB0aGUgcGlwZWxpbmUgcmVnaXN0cnksXG4gIC8vIG5vdCB0aGUgYm9keS5cbiAgY29uc3QgcGF5bG9hZEV2ZW50cyA9IGV2ZW50cy5tYXAoKHsgb3JnSWQ6IF9vcmdJZCwgLi4ucmVzdCB9KSA9PiByZXN0KTtcbiAgY29uc3QgcG9zdCA9ICh0b2tlbjogc3RyaW5nKSA9PiBmZXRjaChgJHtiYXNlVXJsfS9hcGkvcmVwb3J0cy9ldmVudHNgLCB7XG4gICAgbWV0aG9kOiAnUE9TVCcsXG4gICAgaGVhZGVyczogeyAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nLCAnQXV0aG9yaXphdGlvbic6IGBCZWFyZXIgJHt0b2tlbn1gIH0sXG4gICAgYm9keTogSlNPTi5zdHJpbmdpZnkoeyBldmVudHM6IHBheWxvYWRFdmVudHMgfSksXG4gIH0pO1xuICBsZXQgcmVzID0gYXdhaXQgcG9zdChhd2FpdCBnZXRBdXRoVG9rZW4oKSk7XG4gIGlmIChyZXMuc3RhdHVzID09PSA0MDEgfHwgcmVzLnN0YXR1cyA9PT0gNDAzKSB7XG4gICAgbG9nLndhcm4oJ1JlcG9ydGluZyBBUEkgYXV0aCBmYWlsZWQ7IHJlLXJlYWRpbmcgdGhlIHN0b3JlZCBrZXkgYW5kIHJldHJ5aW5nIG9uY2UnLCB7IHN0YXR1czogcmVzLnN0YXR1cyB9KTtcbiAgICBpbnZhbGlkYXRlQ3JlZGVudGlhbCgpO1xuICAgIHJlcyA9IGF3YWl0IHBvc3QoYXdhaXQgZ2V0QXV0aFRva2VuKCkpO1xuICB9XG5cbiAgaWYgKCFyZXMub2spIHtcbiAgICBjb25zdCBib2R5ID0gYXdhaXQgcmVzLnRleHQoKS5jYXRjaCgoKSA9PiAnJyk7XG4gICAgbG9nLmVycm9yKGBSZXBvcnRpbmcgQVBJIHJldHVybmVkICR7cmVzLnN0YXR1c31gLCB7IGJvZHkgfSk7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBSZXBvcnRpbmcgQVBJIGZhaWxlZDogJHtyZXMuc3RhdHVzfWApO1xuICB9XG5cbiAgLy8gVGhlIGluc2VydCBhbHJlYWR5IHN1Y2NlZWRlZCAoMnh4KS4gVGhlIHBhcnNlZCBib2R5IGlzIG9ubHkgdXNlZCBmb3IgYSBsb2dcbiAgLy8gbGluZSwgc28gYSBub24tSlNPTi9lbXB0eSBib2R5IG11c3Qgbm90IHRocm93IGFuZCBmYWlsIHRoZSB3aG9sZSBiYXRjaCDigJRcbiAgLy8gdGhhdCB3b3VsZCB0cmlnZ2VyIGFuIFNRUyByZWRlbGl2ZXJ5IGFuZCByZS1QT1NUIChkdXBsaWNhdGUpIGFscmVhZHktaW5zZXJ0ZWRcbiAgLy8gZXZlbnRzLiBUaGUgaW5nZXN0J3MgcGFydGlhbC11bmlxdWUgaW5kZXggZGVkdXBlcywgYnV0IGF2b2lkIHRoZSBjaHVybiBhbnl3YXkuXG4gIGNvbnN0IHJlc3VsdCA9IGF3YWl0IHJlcy5qc29uKCkuY2F0Y2goKCkgPT4gKHt9KSkgYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG4gIGxvZy5pbmZvKGBJbmdlc3RlZCAke2V2ZW50cy5sZW5ndGh9IGV2ZW50c2AsIHsgaW5zZXJ0ZWQ6IHJlc3VsdC5kYXRhIH0pO1xuXG4gIC8vIEFjY3VtdWxhdGUgZGVsaXZlcnktaGVhbHRoIGNvdW50ZXJzIFBFUiBSRVNPTFZFRCBPUkcsIHRoZW4gcnVuIHRoZSB0aHJvdHRsZWRcbiAgLy8gcG9zdC1zdWNjZXNzIGhlYWx0aCArIHNlbGYtaGVhbGluZyBETFEgcmVkcml2ZSAoYmVzdC1lZmZvcnQsIG5ldmVyIGZhaWxzIGJhdGNoKS5cbiAgZm9yIChjb25zdCBlIG9mIGV2ZW50cykge1xuICAgIGNvbnN0IGtleSA9IGUub3JnSWQgPz8gJyc7XG4gICAgZm9yd2FyZGVkQnlPcmcuc2V0KGtleSwgKGZvcndhcmRlZEJ5T3JnLmdldChrZXkpID8/IDApICsgMSk7XG4gICAgY29uc3QgYXQgPSBlLmNvbXBsZXRlZEF0IHx8IGUuc3RhcnRlZEF0O1xuICAgIGlmIChhdCkge1xuICAgICAgY29uc3QgcHJldiA9IGxhc3RFdmVudEF0QnlPcmcuZ2V0KGtleSk7XG4gICAgICBpZiAoIXByZXYgfHwgYXQgPiBwcmV2KSBsYXN0RXZlbnRBdEJ5T3JnLnNldChrZXksIGF0KTtcbiAgICB9XG4gIH1cbiAgYXdhaXQgcmVwb3J0SGVhbHRoQW5kUmVkcml2ZShiYXNlVXJsLCBldmVudC5SZWNvcmRzWzBdPy5ldmVudFNvdXJjZUFSTik7XG59O1xuXG4vKipcbiAqIEBpbnRlcm5hbCBUZXN0LW9ubHk6IHJlc2V0IGFsbCBtb2R1bGUtbGV2ZWwgY2FjaGVzL3Rocm90dGxlIHN0YXRlIHNvIHRlc3RzIGFyZVxuICogaXNvbGF0ZWQgZnJvbSBvbmUgYW5vdGhlci4gU3RyaXBwZWQgZnJvbSB0aGUgcHVibGlzaGVkIC5kLnRzIChzdHJpcEludGVybmFsKS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIF9yZXNldEZvclRlc3RzKCk6IHZvaWQge1xuICByZXNvbHZlZEJ5QXJuLmNsZWFyKCk7XG4gIGNvbW1pdFJlc3VsdENhY2hlLmNsZWFyKCk7XG4gIGxhc3RTaGFCeVBpcGVsaW5lLmNsZWFyKCk7XG4gIGNvbW1pdE1ldGFDYWNoZS5jbGVhcigpO1xuICBzY21Db29sZG93blVudGlsID0gMDtcbiAgZ2l0aHViVG9rZW5CeU9yZy5jbGVhcigpO1xuICBmb3J3YXJkZWRCeU9yZy5jbGVhcigpO1xuICBsYXN0RXZlbnRBdEJ5T3JnLmNsZWFyKCk7XG4gIGxhc3RIZWFsdGhBdCA9IDA7XG4gIGxhc3RSZWRyaXZlQXQgPSAwO1xuICBjYWNoZWRLZXkgPSBudWxsO1xuICBjYWNoZWRUb2tlbiA9IG51bGw7XG4gIGNhY2hlZFRva2VuRXhwaXJlc0F0ID0gMDtcbn1cbiJdfQ==