@pipeline-builder/api-core 0.0.1

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.
Files changed (184) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +98 -0
  3. package/lib/constants/ai-providers.d.ts +52 -0
  4. package/lib/constants/ai-providers.js +129 -0
  5. package/lib/constants/http-status.d.ts +20 -0
  6. package/lib/constants/http-status.js +26 -0
  7. package/lib/constants/index.d.ts +3 -0
  8. package/lib/constants/index.js +6 -0
  9. package/lib/constants/time.d.ts +10 -0
  10. package/lib/constants/time.js +13 -0
  11. package/lib/errors/app-errors.d.ts +30 -0
  12. package/lib/errors/app-errors.js +53 -0
  13. package/lib/errors/index.d.ts +1 -0
  14. package/lib/errors/index.js +4 -0
  15. package/lib/helpers/access-helpers.d.ts +63 -0
  16. package/lib/helpers/access-helpers.js +82 -0
  17. package/lib/helpers/crud-helpers.d.ts +9 -0
  18. package/lib/helpers/crud-helpers.js +23 -0
  19. package/lib/helpers/index.d.ts +6 -0
  20. package/lib/helpers/index.js +9 -0
  21. package/lib/helpers/org-hierarchy-http.d.ts +74 -0
  22. package/lib/helpers/org-hierarchy-http.js +144 -0
  23. package/lib/helpers/org-hierarchy.d.ts +57 -0
  24. package/lib/helpers/org-hierarchy.js +110 -0
  25. package/lib/helpers/restore-helpers.d.ts +49 -0
  26. package/lib/helpers/restore-helpers.js +78 -0
  27. package/lib/helpers/sse-helpers.d.ts +20 -0
  28. package/lib/helpers/sse-helpers.js +45 -0
  29. package/lib/index.d.ts +58 -0
  30. package/lib/index.js +71 -0
  31. package/lib/middleware/auth.d.ts +208 -0
  32. package/lib/middleware/auth.js +588 -0
  33. package/lib/middleware/index.d.ts +3 -0
  34. package/lib/middleware/index.js +6 -0
  35. package/lib/middleware/mongo-sanitize.d.ts +32 -0
  36. package/lib/middleware/mongo-sanitize.js +100 -0
  37. package/lib/middleware/step-up.d.ts +45 -0
  38. package/lib/middleware/step-up.js +138 -0
  39. package/lib/openapi/extend-zod.d.ts +1 -0
  40. package/lib/openapi/extend-zod.js +6 -0
  41. package/lib/openapi/index.d.ts +2 -0
  42. package/lib/openapi/index.js +5 -0
  43. package/lib/openapi/registry.d.ts +17 -0
  44. package/lib/openapi/registry.js +38 -0
  45. package/lib/openapi/routes/ask-routes.d.ts +7 -0
  46. package/lib/openapi/routes/ask-routes.js +62 -0
  47. package/lib/openapi/routes/billing-routes.d.ts +1 -0
  48. package/lib/openapi/routes/billing-routes.js +66 -0
  49. package/lib/openapi/routes/image-registry-routes.d.ts +7 -0
  50. package/lib/openapi/routes/image-registry-routes.js +98 -0
  51. package/lib/openapi/routes/index.d.ts +5 -0
  52. package/lib/openapi/routes/index.js +28 -0
  53. package/lib/openapi/routes/message-routes.d.ts +1 -0
  54. package/lib/openapi/routes/message-routes.js +105 -0
  55. package/lib/openapi/routes/pipeline-routes.d.ts +1 -0
  56. package/lib/openapi/routes/pipeline-routes.js +105 -0
  57. package/lib/openapi/routes/pipeline-template-routes.d.ts +1 -0
  58. package/lib/openapi/routes/pipeline-template-routes.js +61 -0
  59. package/lib/openapi/routes/plugin-routes.d.ts +1 -0
  60. package/lib/openapi/routes/plugin-routes.js +96 -0
  61. package/lib/openapi/routes/quota-routes.d.ts +1 -0
  62. package/lib/openapi/routes/quota-routes.js +62 -0
  63. package/lib/openapi/schema-registry.d.ts +25 -0
  64. package/lib/openapi/schema-registry.js +91 -0
  65. package/lib/routes/health.d.ts +41 -0
  66. package/lib/routes/health.js +82 -0
  67. package/lib/routes/index.d.ts +1 -0
  68. package/lib/routes/index.js +4 -0
  69. package/lib/services/audit-spool.d.ts +80 -0
  70. package/lib/services/audit-spool.js +172 -0
  71. package/lib/services/cache-service.d.ts +124 -0
  72. package/lib/services/cache-service.js +276 -0
  73. package/lib/services/circuit-breaker.d.ts +45 -0
  74. package/lib/services/circuit-breaker.js +138 -0
  75. package/lib/services/compliance-client.d.ts +46 -0
  76. package/lib/services/compliance-client.js +128 -0
  77. package/lib/services/compliance-event-subscriber.d.ts +16 -0
  78. package/lib/services/compliance-event-subscriber.js +72 -0
  79. package/lib/services/durable-event-bus.d.ts +96 -0
  80. package/lib/services/durable-event-bus.js +204 -0
  81. package/lib/services/entity-events.d.ts +48 -0
  82. package/lib/services/entity-events.js +79 -0
  83. package/lib/services/env-redis.d.ts +44 -0
  84. package/lib/services/env-redis.js +94 -0
  85. package/lib/services/http-client.d.ts +143 -0
  86. package/lib/services/http-client.js +335 -0
  87. package/lib/services/index.d.ts +18 -0
  88. package/lib/services/index.js +20 -0
  89. package/lib/services/leader-lock.d.ts +45 -0
  90. package/lib/services/leader-lock.js +85 -0
  91. package/lib/services/quota.d.ts +130 -0
  92. package/lib/services/quota.js +264 -0
  93. package/lib/services/remote-audit-client.d.ts +160 -0
  94. package/lib/services/remote-audit-client.js +390 -0
  95. package/lib/services/retry-strategy.d.ts +74 -0
  96. package/lib/services/retry-strategy.js +118 -0
  97. package/lib/services/scheduler.d.ts +41 -0
  98. package/lib/services/scheduler.js +86 -0
  99. package/lib/services/service-boot.d.ts +20 -0
  100. package/lib/services/service-boot.js +28 -0
  101. package/lib/services/sse-ticket-store.d.ts +51 -0
  102. package/lib/services/sse-ticket-store.js +130 -0
  103. package/lib/services/token-revocation.d.ts +52 -0
  104. package/lib/services/token-revocation.js +120 -0
  105. package/lib/testing/tier-mock.d.ts +44 -0
  106. package/lib/testing/tier-mock.js +65 -0
  107. package/lib/types/audit-events.d.ts +69 -0
  108. package/lib/types/audit-events.js +4 -0
  109. package/lib/types/catalog-metadata.d.ts +36 -0
  110. package/lib/types/catalog-metadata.js +21 -0
  111. package/lib/types/common.d.ts +255 -0
  112. package/lib/types/common.js +41 -0
  113. package/lib/types/error-codes.d.ts +54 -0
  114. package/lib/types/error-codes.js +107 -0
  115. package/lib/types/feature-flags.d.ts +36 -0
  116. package/lib/types/feature-flags.js +128 -0
  117. package/lib/types/http.d.ts +27 -0
  118. package/lib/types/http.js +4 -0
  119. package/lib/types/index.d.ts +11 -0
  120. package/lib/types/index.js +14 -0
  121. package/lib/types/permissions.d.ts +65 -0
  122. package/lib/types/permissions.js +131 -0
  123. package/lib/types/pipeline-template.d.ts +30 -0
  124. package/lib/types/pipeline-template.js +13 -0
  125. package/lib/types/pipeline.d.ts +59 -0
  126. package/lib/types/pipeline.js +31 -0
  127. package/lib/types/quota-tiers.d.ts +79 -0
  128. package/lib/types/quota-tiers.js +221 -0
  129. package/lib/types/visibility.d.ts +20 -0
  130. package/lib/types/visibility.js +22 -0
  131. package/lib/utils/alias-resolver.d.ts +37 -0
  132. package/lib/utils/alias-resolver.js +68 -0
  133. package/lib/utils/audit.d.ts +15 -0
  134. package/lib/utils/audit.js +39 -0
  135. package/lib/utils/aws-scrub.d.ts +11 -0
  136. package/lib/utils/aws-scrub.js +67 -0
  137. package/lib/utils/compliance-attributes.d.ts +6 -0
  138. package/lib/utils/compliance-attributes.js +69 -0
  139. package/lib/utils/concurrency.d.ts +9 -0
  140. package/lib/utils/concurrency.js +29 -0
  141. package/lib/utils/crash-handlers.d.ts +18 -0
  142. package/lib/utils/crash-handlers.js +36 -0
  143. package/lib/utils/env.d.ts +28 -0
  144. package/lib/utils/env.js +54 -0
  145. package/lib/utils/headers.d.ts +18 -0
  146. package/lib/utils/headers.js +21 -0
  147. package/lib/utils/identity.d.ts +40 -0
  148. package/lib/utils/identity.js +51 -0
  149. package/lib/utils/index.d.ts +17 -0
  150. package/lib/utils/index.js +20 -0
  151. package/lib/utils/logger.d.ts +38 -0
  152. package/lib/utils/logger.js +174 -0
  153. package/lib/utils/metric-emitter.d.ts +36 -0
  154. package/lib/utils/metric-emitter.js +32 -0
  155. package/lib/utils/object.d.ts +13 -0
  156. package/lib/utils/object.js +18 -0
  157. package/lib/utils/params.d.ts +150 -0
  158. package/lib/utils/params.js +206 -0
  159. package/lib/utils/response.d.ts +117 -0
  160. package/lib/utils/response.js +223 -0
  161. package/lib/utils/safe-require.d.ts +13 -0
  162. package/lib/utils/safe-require.js +19 -0
  163. package/lib/utils/secret-encryption.d.ts +176 -0
  164. package/lib/utils/secret-encryption.js +374 -0
  165. package/lib/utils/ssrf.d.ts +30 -0
  166. package/lib/utils/ssrf.js +106 -0
  167. package/lib/validation/ai-schemas.d.ts +56 -0
  168. package/lib/validation/ai-schemas.js +78 -0
  169. package/lib/validation/common-schemas.d.ts +133 -0
  170. package/lib/validation/common-schemas.js +94 -0
  171. package/lib/validation/index.d.ts +7 -0
  172. package/lib/validation/index.js +10 -0
  173. package/lib/validation/message-schemas.d.ts +160 -0
  174. package/lib/validation/message-schemas.js +156 -0
  175. package/lib/validation/middleware.d.ts +72 -0
  176. package/lib/validation/middleware.js +65 -0
  177. package/lib/validation/pipeline-schemas.d.ts +181 -0
  178. package/lib/validation/pipeline-schemas.js +101 -0
  179. package/lib/validation/pipeline-template-schemas.d.ts +155 -0
  180. package/lib/validation/pipeline-template-schemas.js +66 -0
  181. package/lib/validation/plugin-schemas.d.ts +171 -0
  182. package/lib/validation/plugin-schemas.js +81 -0
  183. package/openapi.yaml +292 -0
  184. package/package.json +167 -0
@@ -0,0 +1,172 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { createEnvRedisClient } from './env-redis.js';
4
+ import { createLogger } from '../utils/logger.js';
5
+ import { emitCounter } from '../utils/metric-emitter.js';
6
+ const logger = createLogger('audit-spool');
7
+ const DEFAULT_SPOOL_KEY = 'audit:spool';
8
+ const DEFAULT_MAX_DEPTH = 10_000;
9
+ /** Guard so a pathological in-progress list can't spin `recover`/`take` forever. */
10
+ const MAX_MOVE_ITERATIONS = 100_000;
11
+ function errMsg(err) {
12
+ return err instanceof Error ? err.message : String(err);
13
+ }
14
+ function safeParse(raw) {
15
+ try {
16
+ const parsed = JSON.parse(raw);
17
+ return parsed && parsed.event && parsed.serviceName ? parsed : null;
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ /**
24
+ * Redis-LIST-backed spool. Durable across process restarts (unlike an in-memory
25
+ * or per-pod-disk buffer), shared by all replicas of a service, and bounded via
26
+ * a trim-to-tail on overflow (drops the OLDEST, emitting `audit_spool_dropped_total`).
27
+ */
28
+ export function createRedisAuditSpool(redis, opts = {}) {
29
+ const key = opts.key ?? DEFAULT_SPOOL_KEY;
30
+ // The in-progress "processing" list — a taken batch lives here until it is
31
+ // acked (delivered) or requeued (still failing). It is a security log's
32
+ // durability net: a crash between take and ack/requeue leaves the batch here,
33
+ // and `recover` reclaims it on restart instead of silently losing it.
34
+ const inProgressKey = `${key}:inflight`;
35
+ const maxDepth = Math.max(1, opts.maxDepth ?? DEFAULT_MAX_DEPTH);
36
+ // The EXACT serialized string each returned entry was parsed from, so `ack` /
37
+ // `requeue` can LREM the precise value off the in-progress list. Keyed by the
38
+ // returned object identity (the drain consumer passes the SAME objects back),
39
+ // so it needs no stable re-serialization. WeakMap → entries GC naturally.
40
+ const rawByEntry = new WeakMap();
41
+ const rawOf = (entry) => rawByEntry.get(entry) ?? JSON.stringify(entry);
42
+ return {
43
+ async enqueue(entry) {
44
+ try {
45
+ const len = await redis.rpush(key, JSON.stringify(entry));
46
+ emitCounter('audit_spool_enqueued_total', { service: entry.serviceName });
47
+ if (len > maxDepth) {
48
+ // Keep only the newest `maxDepth`; drop the overflow at the head.
49
+ const dropped = len - maxDepth;
50
+ await redis.ltrim(key, dropped, -1);
51
+ emitCounter('audit_spool_dropped_total', { service: entry.serviceName }, dropped);
52
+ logger.warn('Audit spool overflow — dropped oldest buffered events', { dropped, maxDepth });
53
+ }
54
+ }
55
+ catch (err) {
56
+ logger.warn('Audit spool enqueue failed (event lost)', { error: errMsg(err) });
57
+ }
58
+ },
59
+ async take(max) {
60
+ try {
61
+ const out = [];
62
+ const limit = Math.max(1, max);
63
+ for (let i = 0; i < limit; i++) {
64
+ // Atomically MOVE head→in-progress-tail so the entry is never in limbo:
65
+ // it is on exactly one list at all times, recoverable after a crash.
66
+ const raw = await redis.lmove(key, inProgressKey, 'LEFT', 'RIGHT');
67
+ if (raw === null || raw === undefined)
68
+ break;
69
+ const parsed = safeParse(raw);
70
+ if (parsed) {
71
+ rawByEntry.set(parsed, raw);
72
+ out.push(parsed);
73
+ }
74
+ else {
75
+ // Corrupt payload — drop it off the in-progress list so it can't wedge
76
+ // the drain, and account for it.
77
+ await redis.lrem(inProgressKey, 1, raw);
78
+ emitCounter('audit_spool_dropped_total', { service: 'unknown' });
79
+ }
80
+ }
81
+ return out;
82
+ }
83
+ catch (err) {
84
+ logger.warn('Audit spool take failed', { error: errMsg(err) });
85
+ return [];
86
+ }
87
+ },
88
+ async ack(entries) {
89
+ if (entries.length === 0)
90
+ return;
91
+ try {
92
+ for (const entry of entries) {
93
+ await redis.lrem(inProgressKey, 1, rawOf(entry));
94
+ rawByEntry.delete(entry);
95
+ }
96
+ }
97
+ catch (err) {
98
+ logger.warn('Audit spool ack failed (may re-deliver on recover)', { error: errMsg(err) });
99
+ }
100
+ },
101
+ async requeue(entries) {
102
+ if (entries.length === 0)
103
+ return;
104
+ try {
105
+ // Put failed re-deliveries back at the HEAD so they're retried first and
106
+ // rough emission order is preserved across the outage — and clear them
107
+ // from the in-progress list so `recover` doesn't double them. Iterate in
108
+ // reverse so lpush restores the batch's original order at the head.
109
+ //
110
+ // ORDERING INVARIANT (at-least-once): lpush to the main list FIRST, THEN
111
+ // lrem from in-progress. A crash between the two leaves the entry on BOTH
112
+ // lists → harmless double-delivery (deduped downstream by idempotency
113
+ // reuse), never a LOSS. The reverse order (lrem then lpush) would drop the
114
+ // event outright on a crash in the gap, which a security log must not do.
115
+ for (let i = entries.length - 1; i >= 0; i--) {
116
+ const raw = rawOf(entries[i]);
117
+ await redis.lpush(key, raw);
118
+ await redis.lrem(inProgressKey, 1, raw);
119
+ rawByEntry.delete(entries[i]);
120
+ }
121
+ }
122
+ catch (err) {
123
+ logger.warn('Audit spool requeue failed (events lost)', { error: errMsg(err) });
124
+ }
125
+ },
126
+ async recover() {
127
+ try {
128
+ let reclaimed = 0;
129
+ for (let i = 0; i < MAX_MOVE_ITERATIONS; i++) {
130
+ // Move in-progress-tail → main-head: an older stranded entry (nearer the
131
+ // in-progress head) ends up ahead of a newer one, preserving order.
132
+ const raw = await redis.lmove(inProgressKey, key, 'RIGHT', 'LEFT');
133
+ if (raw === null || raw === undefined)
134
+ break;
135
+ reclaimed++;
136
+ }
137
+ if (reclaimed > 0) {
138
+ emitCounter('audit_spool_recovered_total', {}, reclaimed);
139
+ logger.warn('Audit spool recovered stranded in-progress events', { reclaimed });
140
+ }
141
+ return reclaimed;
142
+ }
143
+ catch (err) {
144
+ logger.warn('Audit spool recover failed', { error: errMsg(err) });
145
+ return 0;
146
+ }
147
+ },
148
+ async depth() {
149
+ try {
150
+ return await redis.llen(key);
151
+ }
152
+ catch {
153
+ return 0;
154
+ }
155
+ },
156
+ };
157
+ }
158
+ /**
159
+ * Build a Redis-backed audit spool from the ambient `REDIS_URL` / `REDIS_HOST`
160
+ * env via the shared `createEnvRedisClient`. Returns `null` when Redis is not
161
+ * configured or the client can't be constructed — the caller then runs without a
162
+ * spool (pre-spool best-effort behavior), never crashing. The shared helper loads
163
+ * ioredis via `createRequire`, so this stays importable where Redis isn't present.
164
+ */
165
+ export function createEnvRedisAuditSpool(opts = {}) {
166
+ const inst = createEnvRedisClient('audit-spool');
167
+ if (!inst)
168
+ return null;
169
+ logger.info('Redis audit spool initialized');
170
+ return createRedisAuditSpool(inst, opts);
171
+ }
172
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXVkaXQtc3Bvb2wuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZXMvYXVkaXQtc3Bvb2wudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0QyxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUV0RCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDbEQsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBRXpELE1BQU0sTUFBTSxHQUFHLFlBQVksQ0FBQyxhQUFhLENBQUMsQ0FBQztBQStEM0MsTUFBTSxpQkFBaUIsR0FBRyxhQUFhLENBQUM7QUFDeEMsTUFBTSxpQkFBaUIsR0FBRyxNQUFNLENBQUM7QUFDakMsb0ZBQW9GO0FBQ3BGLE1BQU0sbUJBQW1CLEdBQUcsT0FBTyxDQUFDO0FBRXBDLFNBQVMsTUFBTSxDQUFDLEdBQVk7SUFDMUIsT0FBTyxHQUFHLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7QUFDMUQsQ0FBQztBQUVELFNBQVMsU0FBUyxDQUFDLEdBQVc7SUFDNUIsSUFBSSxDQUFDO1FBQ0gsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQW9CLENBQUM7UUFDbEQsT0FBTyxNQUFNLElBQUksTUFBTSxDQUFDLEtBQUssSUFBSSxNQUFNLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztJQUN0RSxDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxNQUFNLFVBQVUscUJBQXFCLENBQ25DLEtBQXNCLEVBQ3RCLElBQUksR0FBd0Ms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LENBQUM7Z0JBQ0QsSUFBSSxTQUFTLEdBQUcsQ0FBQyxFQUFFLENBQUM7b0JBQ2xCLFdBQVcsQ0FBQyw2QkFBNkIsRUFBRSxFQUFFLEVBQUUsU0FBUyxDQUFDLENBQUM7b0JBQzFELE1BQU0sQ0FBQyxJQUFJLENBQUMsbURBQW1ELEVBQUUsRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDO2dCQUNsRixDQUFDO2dCQUNELE9BQU8sU0FBUyxDQUFDO1lBQ25CLENBQUM7WUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO2dCQUNiLE1BQU0sQ0FBQyxJQUFJLENBQUMsNEJBQTRCLEVBQUUsRUFBRSxLQUFLLEVBQUUsTUFBTSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztnQkFDbEUsT0FBTyxDQUFDLENBQUM7WUFDWCxDQUFDO1FBQ0gsQ0FBQztRQUVELEtBQUssQ0FBQyxLQUFLO1lBQ1QsSUFBSSxDQUFDO2dCQUNILE9BQU8sTUFBTSxLQUFLLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQy9CLENBQUM7WUFBQyxNQUFNLENBQUM7Z0JBQ1AsT0FBTyxDQUFDLENBQUM7WUFDWCxDQUFDO1FBQ0gsQ0FBQztLQUNGLENBQUM7QUFDSixDQUFDO0FBRUQ7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLHdCQUF3QixDQUFDLElBQUksR0FBd0MsRUFBRTtJQUNyRixNQUFNLElBQUksR0FBRyxvQkFBb0IsQ0FBa0IsYUFBYSxDQUFDLENBQUM7SUFDbEUsSUFBSSxDQUFDLElBQUk7UUFBRSxPQUFPLElBQUksQ0FBQztJQUN2QixNQUFNLENBQUMsSUFBSSxDQUFDLCtCQUErQixDQUFDLENBQUM7SUFDN0MsT0FBTyxxQkFBcUIsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDM0MsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgeyBjcmVhdGVFbnZSZWRpc0NsaWVudCB9IGZyb20gJy4vZW52LXJlZGlzLmpzJztcbmltcG9ydCB0eXBlIHsgUmVtb3RlQXVkaXRFdmVudCB9IGZyb20gJy4vcmVtb3RlLWF1ZGl0LWNsaWVudC5qcyc7XG5pbXBvcnQgeyBjcmVhdGVMb2dnZXIgfSBmcm9tICcuLi91dGlscy9sb2dnZXIuanMnO1xuaW1wb3J0IHsgZW1pdENvdW50ZXIgfSBmcm9tICcuLi91dGlscy9tZXRyaWMtZW1pdHRlci5qcyc7XG5cbmNvbnN0IGxvZ2dlciA9IGNyZWF0ZUxvZ2dlcignYXVkaXQtc3Bvb2wnKTtcblxuLyoqIEEgYnVmZmVyZWQgZW1pc3Npb246IHRoZSBldmVudCBwbHVzIHdoaWNoIHNlcnZpY2UgbXVzdCByZS1kZWxpdmVyIGl0LiAqL1xuZXhwb3J0IGludGVyZmFjZSBBdWRpdFNwb29sRW50cnkge1xuICBldmVudDogUmVtb3RlQXVkaXRFdmVudDtcbiAgc2VydmljZU5hbWU6IHN0cmluZztcbn1cblxuLyoqXG4gKiBEdXJhYmxlIGJ1ZmZlciBmb3IgYXVkaXQgZXZlbnRzIHRoYXQgZmFpbGVkIExJVkUgZGVsaXZlcnkgdG8gdGhlIHBsYXRmb3JtXG4gKiAocmV0cmllcyBleGhhdXN0ZWQgZHVyaW5nIGEgc3VzdGFpbmVkIG91dGFnZSkuIFdpdGhvdXQgdGhpcywgdGhlIGF1ZGl0IHRyYWlsIOKAlFxuICogYSBzZWN1cml0eSBsb2cg4oCUIGlzIHNpbGVudGx5IGxvc3QgdGhlIG1vbWVudCB0aGUgcGxhdGZvcm0gaXMgZG93biBmb3IgbG9uZ2VyXG4gKiB0aGFuIHRoZSBjbGllbnQncyBzbWFsbCByZXRyeSBidWRnZXQuXG4gKlxuICogQ29udHJhY3Q6XG4gKiAtIEJPVU5ERUQuIEEgc2VjdXJpdHkgbG9nIG11c3QgZGVncmFkZSB0byBcImRyb3BwZWQgdGhlIG9sZGVzdCwgbG91ZGx5XCIgKGFcbiAqICAgbWV0cmljKSByYXRoZXIgdGhhbiBncm93IHdpdGhvdXQgbGltaXQgYW5kIE9PTSB0aGUgZW1pdHRlci5cbiAqIC0gRkFJTC1TQUZFLiBFdmVyeSBtZXRob2Qgc3dhbGxvd3MgaXRzIG93biBlcnJvcnMg4oCUIGEgYnJva2VuL3VucmVhY2hhYmxlIHNwb29sXG4gKiAgIG11c3QgbmV2ZXIgYnJlYWsgb3IgYmxvY2sgdGhlIGF1ZGl0IGVtaXNzaW9uIHBhdGggKHdoaWNoIGlzIGl0c2VsZlxuICogICBmaXJlLWFuZC1mb3JnZXQpLiBBIHNwb29sIHByb2JsZW0gZGVncmFkZXMgdG8gdGhlIHByZS1zcG9vbCBiZWhhdmlvci5cbiAqIC0gT3JkZXJpbmcgaXMgYmVzdC1lZmZvcnQuIFJlLWRlbGl2ZXJlZCBldmVudHMgY2hhaW4gYXQgcmUtZGVsaXZlcnkgdGltZVxuICogICAodGhhdCdzIHdoZW4gdGhlIHBsYXRmb3JtIGluc2VydHMgdGhlbSksIHNvIGVhY2ggZXZlbnQgYWxzbyBjYXJyaWVzIGFuXG4gKiAgIGBvY2N1cnJlZEF0YCBzdGFtcGVkIGF0IGVtaXNzaW9uIGZvciByZXZpZXdlcnMg4oCUIHNlZSBSZW1vdGVBdWRpdEV2ZW50LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIEF1ZGl0U3Bvb2wge1xuICAvKiogQnVmZmVyIGFuIGV2ZW50IHRoYXQgZmFpbGVkIGxpdmUgZGVsaXZlcnkuICovXG4gIGVucXVldWUoZW50cnk6IEF1ZGl0U3Bvb2xFbnRyeSk6IFByb21pc2U8dm9pZD47XG4gIC8qKlxuICAgKiBBdG9taWNhbGx5IE1PVkUgdXAgdG8gYG1heGAgYnVmZmVyZWQgZW50cmllcyBvbnRvIGFuIGluLXByb2dyZXNzIGxpc3QgYW5kXG4gICAqIHJldHVybiB0aGVtLiBCZWNhdXNlIHRoZSBlbnRyaWVzIGFyZSBtb3ZlZCAobm90IHBvcHBlZC1hbmQtZHJvcHBlZCksIGEgY3Jhc2hcbiAgICogbWlkLWRyYWluIGxlYXZlcyB0aGVtIHJlY292ZXJhYmxlIG9uIHRoZSBpbi1wcm9ncmVzcyBsaXN0IOKAlCBzZWUgYHJlY292ZXJgLlxuICAgKiBFYWNoIHRha2VuIGVudHJ5IE1VU1QgYmUgcmVzb2x2ZWQgd2l0aCBleGFjdGx5IG9uZSBvZiBgYWNrYCAoZGVsaXZlcmVkKSBvclxuICAgKiBgcmVxdWV1ZWAgKHN0aWxsIGZhaWxpbmcpLCBvciBpdCBsaW5nZXJzIG9uIHRoZSBpbi1wcm9ncmVzcyBsaXN0IHVudGlsIHRoZVxuICAgKiBuZXh0IGByZWNvdmVyYC5cbiAgICovXG4gIHRha2UobWF4OiBudW1iZXIpOiBQcm9taXNlPEF1ZGl0U3Bvb2xFbnRyeVtdPjtcbiAgLyoqIEFja25vd2xlZGdlIHN1Y2Nlc3NmdWxseSByZS1kZWxpdmVyZWQgZW50cmllcyDigJQgcmVtb3ZlcyB0aGVtIGZyb20gdGhlXG4gICAqICBpbi1wcm9ncmVzcyBsaXN0IHNvIHRoZXkgYXJlIG5vdCByZWNvdmVyZWQgYWdhaW4uICovXG4gIGFjayhlbnRyaWVzOiBBdWRpdFNwb29sRW50cnlbXSk6IFByb21pc2U8dm9pZD47XG4gIC8qKiBSZXR1cm4gZW50cmllcyB3aG9zZSByZS1kZWxpdmVyeSBmYWlsZWQgdG8gdGhlIEhFQUQgb2YgdGhlIGJ1ZmZlciAocmV0cmllZFxuICAgKiAgZmlyc3QpIGFuZCBjbGVhciB0aGVtIGZyb20gdGhlIGluLXByb2dyZXNzIGxpc3QuIEJlc3QtZWZmb3J0LiAqL1xuICByZXF1ZXVlKGVudHJpZXM6IEF1ZGl0U3Bvb2xFbnRyeVtdKTogUHJvbWlzZTx2b2lkPjtcbiAgLyoqIFJlY2xhaW0gYW55IGVudHJpZXMgc3RyYW5kZWQgb24gdGhlIGluLXByb2dyZXNzIGxpc3QgYnkgYSBwcmlvciBjcmFzaCxcbiAgICogIG1vdmluZyB0aGVtIGJhY2sgdG8gdGhlIGhlYWQgb2YgdGhlIG1haW4gYnVmZmVyLiBDYWxsIG9uY2UgYXQgc3RhcnR1cC5cbiAgICogIFJldHVybnMgdGhlIG51bWJlciByZWNsYWltZWQuICovXG4gIHJlY292ZXIoKTogUHJvbWlzZTxudW1iZXI+O1xuICAvKiogQXBwcm94aW1hdGUgY3VycmVudCBkZXB0aCAoZm9yIG1ldHJpY3MgLyBkcmFpbiBkZWNpc2lvbnMpLiAqL1xuICBkZXB0aCgpOiBQcm9taXNlPG51bWJlcj47XG59XG5cbi8qKiBNaW5pbWFsIFJlZGlzIExJU1Qgc3VyZmFjZSB0aGUgc3Bvb2wgbmVlZHMgKGEgc3Vic2V0IG9mIGlvcmVkaXMpLiAqL1xuaW50ZXJmYWNlIFJlZGlzTGlzdENsaWVudCB7XG4gIHJwdXNoKGtleTogc3RyaW5nLCAuLi52YWx1ZXM6IHN0cmluZ1tdKTogUHJvbWlzZTxudW1iZXI+O1xuICBscHVzaChrZXk6IHN0cmluZywgLi4udmFsdWVzOiBzdHJpbmdbXSk6IFByb21pc2U8bnVtYmVyPjtcbiAgbHBvcChrZXk6IHN0cmluZywgY291bnQ6IG51bWJlcik6IFByb21pc2U8c3RyaW5nW10gfCBudWxsPjtcbiAgbHRyaW0oa2V5OiBzdHJpbmcsIHN0YXJ0OiBudW1iZXIsIHN0b3A6IG51bWJlcik6IFByb21pc2U8dW5rbm93bj47XG4gIGxsZW4oa2V5OiBzdHJpbmcpOiBQcm9taXNlPG51bWJlcj47XG4gIC8qKiBBdG9taWNhbGx5IHBvcCBmcm9tIG9uZSBsaXN0IGVuZCBhbmQgcHVzaCB0byBhbm90aGVyIChSZWRpcyDiiaUgNi4yKS4gKi9cbiAgbG1vdmUoc291cmNlOiBzdHJpbmcsIGRlc3RpbmF0aW9uOiBzdHJpbmcsIGZyb206ICdMRUZUJyB8ICdSSUdIVCcsIHRvOiAnTEVGVCcgfCAnUklHSFQnKTogUHJvbWlzZTxzdHJpbmcgfCBudWxsPjtcbiAgLyoqIFJlbW92ZSBgY291bnRgIG9jY3VycmVuY2VzIG9mIGB2YWx1ZWAgZnJvbSBhIGxpc3QuICovXG4gIGxyZW0oa2V5OiBzdHJpbmcsIGNvdW50OiBudW1iZXIsIHZhbHVlOiBzdHJpbmcpOiBQcm9taXNlPG51bWJlcj47XG59XG5cbmNvbnN0IERFRkFVTFRfU1BPT0xfS0VZID0gJ2F1ZGl0OnNwb29sJztcbmNvbnN0IERFRkFVTFRfTUFYX0RFUFRIID0gMTBfMDAwO1xuLyoqIEd1YXJkIHNvIGEgcGF0aG9sb2dpY2FsIGluLXByb2dyZXNzIGxpc3QgY2FuJ3Qgc3BpbiBgcmVjb3ZlcmAvYHRha2VgIGZvcmV2ZXIuICovXG5jb25zdCBNQVhfTU9WRV9JVEVSQVRJT05TID0gMTAwXzAwMDtcblxuZnVuY3Rpb24gZXJyTXNnKGVycjogdW5rbm93bik6IHN0cmluZyB7XG4gIHJldHVybiBlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogU3RyaW5nKGVycik7XG59XG5cbmZ1bmN0aW9uIHNhZmVQYXJzZShyYXc6IHN0cmluZyk6IEF1ZGl0U3Bvb2xFbnRyeSB8IG51bGwge1xuICB0cnkge1xuICAgIGNvbnN0IHBhcnNlZCA9IEpTT04ucGFyc2UocmF3KSBhcyBBdWRpdFNwb29sRW50cnk7XG4gICAgcmV0dXJuIHBhcnNlZCAmJiBwYXJzZWQuZXZlbnQgJiYgcGFyc2VkLnNlcnZpY2VOYW1lID8gcGFyc2VkIDogbnVsbDtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbn1cblxuLyoqXG4gKiBSZWRpcy1MSVNULWJhY2tlZCBzcG9vbC4gRHVyYWJsZSBhY3Jvc3MgcHJvY2VzcyByZXN0YXJ0cyAodW5saWtlIGFuIGluLW1lbW9yeVxuICogb3IgcGVyLXBvZC1kaXNrIGJ1ZmZlciksIHNoYXJlZCBieSBhbGwgcmVwbGljYXMgb2YgYSBzZXJ2aWNlLCBhbmQgYm91bmRlZCB2aWFcbiAqIGEgdHJpbS10by10YWlsIG9uIG92ZXJmbG93IChkcm9wcyB0aGUgT0xERVNULCBlbWl0dGluZyBgYXVkaXRfc3Bvb2xfZHJvcHBlZF90b3RhbGApLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlUmVkaXNBdWRpdFNwb29sKFxuICByZWRpczogUmVkaXNMaXN0Q2xpZW50LFxuICBvcHRzOiB7IG1heERlcHRoPzogbnVtYmVyOyBrZXk/OiBzdHJpbmcgfSA9IHt9LFxuKTogQXVkaXRTcG9vbCB7XG4gIGNvbnN0IGtleSA9IG9wdHMua2V5ID8/IERFRkFVTFRfU1BPT0xfS0VZO1xuICAvLyBUaGUgaW4tcHJvZ3Jlc3MgXCJwcm9jZXNzaW5nXCIgbGlzdCDigJQgYSB0YWtlbiBiYXRjaCBsaXZlcyBoZXJlIHVudGlsIGl0IGlzXG4gIC8vIGFja2VkIChkZWxpdmVyZWQpIG9yIHJlcXVldWVkIChzdGlsbCBmYWlsaW5nKS4gSXQgaXMgYSBzZWN1cml0eSBsb2cnc1xuICAvLyBkdXJhYmlsaXR5IG5ldDogYSBjcmFzaCBiZXR3ZWVuIHRha2UgYW5kIGFjay9yZXF1ZXVlIGxlYXZlcyB0aGUgYmF0Y2ggaGVyZSxcbiAgLy8gYW5kIGByZWNvdmVyYCByZWNsYWltcyBpdCBvbiByZXN0YXJ0IGluc3RlYWQgb2Ygc2lsZW50bHkgbG9zaW5nIGl0LlxuICBjb25zdCBpblByb2dyZXNzS2V5ID0gYCR7a2V5fTppbmZsaWdodGA7XG4gIGNvbnN0IG1heERlcHRoID0gTWF0aC5tYXgoMSwgb3B0cy5tYXhEZXB0aCA/PyBERUZBVUxUX01BWF9ERVBUSCk7XG5cbiAgLy8gVGhlIEVYQUNUIHNlcmlhbGl6ZWQgc3RyaW5nIGVhY2ggcmV0dXJuZWQgZW50cnkgd2FzIHBhcnNlZCBmcm9tLCBzbyBgYWNrYCAvXG4gIC8vIGByZXF1ZXVlYCBjYW4gTFJFTSB0aGUgcHJlY2lzZSB2YWx1ZSBvZmYgdGhlIGluLXByb2dyZXNzIGxpc3QuIEtleWVkIGJ5IHRoZVxuICAvLyByZXR1cm5lZCBvYmplY3QgaWRlbnRpdHkgKHRoZSBkcmFpbiBjb25zdW1lciBwYXNzZXMgdGhlIFNBTUUgb2JqZWN0cyBiYWNrKSxcbiAgLy8gc28gaXQgbmVlZHMgbm8gc3RhYmxlIHJlLXNlcmlhbGl6YXRpb24uIFdlYWtNYXAg4oaSIGVudHJpZXMgR0MgbmF0dXJhbGx5LlxuICBjb25zdCByYXdCeUVudHJ5ID0gbmV3IFdlYWtNYXA8QXVkaXRTcG9vbEVudHJ5LCBzdHJpbmc+KCk7XG4gIGNvbnN0IHJhd09mID0gKGVudHJ5OiBBdWRpdFNwb29sRW50cnkpOiBzdHJpbmcgPT4gcmF3QnlFbnRyeS5nZXQoZW50cnkpID8/IEpTT04uc3RyaW5naWZ5KGVudHJ5KTtcblxuICByZXR1cm4ge1xuICAgIGFzeW5jIGVucXVldWUoZW50cnkpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IGxlbiA9IGF3YWl0IHJlZGlzLnJwdXNoKGtleSwgSlNPTi5zdHJpbmdpZnkoZW50cnkpKTtcbiAgICAgICAgZW1pdENvdW50ZXIoJ2F1ZGl0X3Nwb29sX2VucXVldWVkX3RvdGFsJywgeyBzZXJ2aWNlOiBlbnRyeS5zZXJ2aWNlTmFtZSB9KTtcbiAgICAgICAgaWYgKGxlbiA+IG1heERlcHRoKSB7XG4gICAgICAgICAgLy8gS2VlcCBvbmx5IHRoZSBuZXdlc3QgYG1heERlcHRoYDsgZHJvcCB0aGUgb3ZlcmZsb3cgYXQgdGhlIGhlYWQuXG4gICAgICAgICAgY29uc3QgZHJvcHBlZCA9IGxlbiAtIG1heERlcHRoO1xuICAgICAgICAgIGF3YWl0IHJlZGlzLmx0cmltKGtleSwgZHJvcHBlZCwgLTEpO1xuICAgICAgICAgIGVtaXRDb3VudGVyKCdhdWRpdF9zcG9vbF9kcm9wcGVkX3RvdGFsJywgeyBzZXJ2aWNlOiBlbnRyeS5zZXJ2aWNlTmFtZSB9LCBkcm9wcGVkKTtcbiAgICAgICAgICBsb2dnZXIud2FybignQXVkaXQgc3Bvb2wgb3ZlcmZsb3cg4oCUIGRyb3BwZWQgb2xkZXN0IGJ1ZmZlcmVkIGV2ZW50cycsIHsgZHJvcHBlZCwgbWF4RGVwdGggfSk7XG4gICAgICAgIH1cbiAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICBsb2dnZXIud2FybignQXVkaXQgc3Bvb2wgZW5xdWV1ZSBmYWlsZWQgKGV2ZW50IGxvc3QpJywgeyBlcnJvcjogZXJyTXNnKGVycikgfSk7XG4gICAgICB9XG4gICAgfSxcblxuICAgIGFzeW5jIHRha2UobWF4KSB7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCBvdXQ6IEF1ZGl0U3Bvb2xFbnRyeVtdID0gW107XG4gICAgICAgIGNvbnN0IGxpbWl0ID0gTWF0aC5tYXgoMSwgbWF4KTtcbiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBsaW1pdDsgaSsrKSB7XG4gICAgICAgICAgLy8gQXRvbWljYWxseSBNT1ZFIGhlYWTihpJpbi1wcm9ncmVzcy10YWlsIHNvIHRoZSBlbnRyeSBpcyBuZXZlciBpbiBsaW1ibzpcbiAgICAgICAgICAvLyBpdCBpcyBvbiBleGFjdGx5IG9uZSBsaXN0IGF0IGFsbCB0aW1lcywgcmVjb3ZlcmFibGUgYWZ0ZXIgYSBjcmFzaC5cbiAgICAgICAgICBjb25zdCByYXcgPSBhd2FpdCByZWRpcy5sbW92ZShrZXksIGluUHJvZ3Jlc3NLZXksICdMRUZUJywgJ1JJR0hUJyk7XG4gICAgICAgICAgaWYgKHJhdyA9PT0gbnVsbCB8fCByYXcgPT09IHVuZGVmaW5lZCkgYnJlYWs7XG4gICAgICAgICAgY29uc3QgcGFyc2VkID0gc2FmZVBhcnNlKHJhdyk7XG4gICAgICAgICAgaWYgKHBhcnNlZCkge1xuICAgICAgICAgICAgcmF3QnlFbnRyeS5zZXQocGFyc2VkLCByYXcpO1xuICAgICAgICAgICAgb3V0LnB1c2gocGFyc2VkKTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgLy8gQ29ycnVwdCBwYXlsb2FkIOKAlCBkcm9wIGl0IG9mZiB0aGUgaW4tcHJvZ3Jlc3MgbGlzdCBzbyBpdCBjYW4ndCB3ZWRnZVxuICAgICAgICAgICAgLy8gdGhlIGRyYWluLCBhbmQgYWNjb3VudCBmb3IgaXQuXG4gICAgICAgICAgICBhd2FpdCByZWRpcy5scmVtKGluUHJvZ3Jlc3NLZXksIDEsIHJhdyk7XG4gICAgICAgICAgICBlbWl0Q291bnRlcignYXVkaXRfc3Bvb2xfZHJvcHBlZF90b3RhbCcsIHsgc2VydmljZTogJ3Vua25vd24nIH0pO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gb3V0O1xuICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgIGxvZ2dlci53YXJuKCdBdWRpdCBzcG9vbCB0YWtlIGZhaWxlZCcsIHsgZXJyb3I6IGVyck1zZyhlcnIpIH0pO1xuICAgICAgICByZXR1cm4gW107XG4gICAgICB9XG4gICAgfSxcblxuICAgIGFzeW5jIGFjayhlbnRyaWVzKSB7XG4gICAgICBpZiAoZW50cmllcy5sZW5ndGggPT09IDApIHJldHVybjtcbiAgICAgIHRyeSB7XG4gICAgICAgIGZvciAoY29uc3QgZW50cnkgb2YgZW50cmllcykge1xuICAgICAgICAgIGF3YWl0IHJlZGlzLmxyZW0oaW5Qcm9ncmVzc0tleSwgMSwgcmF3T2YoZW50cnkpKTtcbiAgICAgICAgICByYXdCeUVudHJ5LmRlbGV0ZShlbnRyeSk7XG4gICAgICAgIH1cbiAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICBsb2dnZXIud2FybignQXVkaXQgc3Bvb2wgYWNrIGZhaWxlZCAobWF5IHJlLWRlbGl2ZXIgb24gcmVjb3ZlciknLCB7IGVycm9yOiBlcnJNc2coZXJyKSB9KTtcbiAgICAgIH1cbiAgICB9LFxuXG4gICAgYXN5bmMgcmVxdWV1ZShlbnRyaWVzKSB7XG4gICAgICBpZiAoZW50cmllcy5sZW5ndGggPT09IDApIHJldHVybjtcbiAgICAgIHRyeSB7XG4gICAgICAgIC8vIFB1dCBmYWlsZWQgcmUtZGVsaXZlcmllcyBiYWNrIGF0IHRoZSBIRUFEIHNvIHRoZXkncmUgcmV0cmllZCBmaXJzdCBhbmRcbiAgICAgICAgLy8gcm91Z2ggZW1pc3Npb24gb3JkZXIgaXMgcHJlc2VydmVkIGFjcm9zcyB0aGUgb3V0YWdlIOKAlCBhbmQgY2xlYXIgdGhlbVxuICAgICAgICAvLyBmcm9tIHRoZSBpbi1wcm9ncmVzcyBsaXN0IHNvIGByZWNvdmVyYCBkb2Vzbid0IGRvdWJsZSB0aGVtLiBJdGVyYXRlIGluXG4gICAgICAgIC8vIHJldmVyc2Ugc28gbHB1c2ggcmVzdG9yZXMgdGhlIGJhdGNoJ3Mgb3JpZ2luYWwgb3JkZXIgYXQgdGhlIGhlYWQuXG4gICAgICAgIC8vXG4gICAgICAgIC8vIE9SREVSSU5HIElOVkFSSUFOVCAoYXQtbGVhc3Qtb25jZSk6IGxwdXNoIHRvIHRoZSBtYWluIGxpc3QgRklSU1QsIFRIRU5cbiAgICAgICAgLy8gbHJlbSBmcm9tIGluLXByb2dyZXNzLiBBIGNyYXNoIGJldHdlZW4gdGhlIHR3byBsZWF2ZXMgdGhlIGVudHJ5IG9uIEJPVEhcbiAgICAgICAgLy8gbGlzdHMg4oaSIGhhcm1sZXNzIGRvdWJsZS1kZWxpdmVyeSAoZGVkdXBlZCBkb3duc3RyZWFtIGJ5IGlkZW1wb3RlbmN5XG4gICAgICAgIC8vIHJldXNlKSwgbmV2ZXIgYSBMT1NTLiBUaGUgcmV2ZXJzZSBvcmRlciAobHJlbSB0aGVuIGxwdXNoKSB3b3VsZCBkcm9wIHRoZVxuICAgICAgICAvLyBldmVudCBvdXRyaWdodCBvbiBhIGNyYXNoIGluIHRoZSBnYXAsIHdoaWNoIGEgc2VjdXJpdHkgbG9nIG11c3Qgbm90IGRvLlxuICAgICAgICBmb3IgKGxldCBpID0gZW50cmllcy5sZW5ndGggLSAxOyBpID49IDA7IGktLSkge1xuICAgICAgICAgIGNvbnN0IHJhdyA9IHJhd09mKGVudHJpZXNbaV0pO1xuICAgICAgICAgIGF3YWl0IHJlZGlzLmxwdXNoKGtleSwgcmF3KTtcbiAgICAgICAgICBhd2FpdCByZWRpcy5scmVtKGluUHJvZ3Jlc3NLZXksIDEsIHJhdyk7XG4gICAgICAgICAgcmF3QnlFbnRyeS5kZWxldGUoZW50cmllc1tpXSk7XG4gICAgICAgIH1cbiAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICBsb2dnZXIud2FybignQXVkaXQgc3Bvb2wgcmVxdWV1ZSBmYWlsZWQgKGV2ZW50cyBsb3N0KScsIHsgZXJyb3I6IGVyck1zZyhlcnIpIH0pO1xuICAgICAgfVxuICAgIH0sXG5cbiAgICBhc3luYyByZWNvdmVyKCkge1xuICAgICAgdHJ5IHtcbiAgICAgICAgbGV0IHJlY2xhaW1lZCA9IDA7XG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgTUFYX01PVkVfSVRFUkFUSU9OUzsgaSsrKSB7XG4gICAgICAgICAgLy8gTW92ZSBpbi1wcm9ncmVzcy10YWlsIOKGkiBtYWluLWhlYWQ6IGFuIG9sZGVyIHN0cmFuZGVkIGVudHJ5IChuZWFyZXIgdGhlXG4gICAgICAgICAgLy8gaW4tcHJvZ3Jlc3MgaGVhZCkgZW5kcyB1cCBhaGVhZCBvZiBhIG5ld2VyIG9uZSwgcHJlc2VydmluZyBvcmRlci5cbiAgICAgICAgICBjb25zdCByYXcgPSBhd2FpdCByZWRpcy5sbW92ZShpblByb2dyZXNzS2V5LCBrZXksICdSSUdIVCcsICdMRUZUJyk7XG4gICAgICAgICAgaWYgKHJhdyA9PT0gbnVsbCB8fCByYXcgPT09IHVuZGVmaW5lZCkgYnJlYWs7XG4gICAgICAgICAgcmVjbGFpbWVkKys7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHJlY2xhaW1lZCA+IDApIHtcbiAgICAgICAgICBlbWl0Q291bnRlcignYXVkaXRfc3Bvb2xfcmVjb3ZlcmVkX3RvdGFsJywge30sIHJlY2xhaW1lZCk7XG4gICAgICAgICAgbG9nZ2VyLndhcm4oJ0F1ZGl0IHNwb29sIHJlY292ZXJlZCBzdHJhbmRlZCBpbi1wcm9ncmVzcyBldmVudHMnLCB7IHJlY2xhaW1lZCB9KTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gcmVjbGFpbWVkO1xuICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgIGxvZ2dlci53YXJuKCdBdWRpdCBzcG9vbCByZWNvdmVyIGZhaWxlZCcsIHsgZXJyb3I6IGVyck1zZyhlcnIpIH0pO1xuICAgICAgICByZXR1cm4gMDtcbiAgICAgIH1cbiAgICB9LFxuXG4gICAgYXN5bmMgZGVwdGgoKSB7XG4gICAgICB0cnkge1xuICAgICAgICByZXR1cm4gYXdhaXQgcmVkaXMubGxlbihrZXkpO1xuICAgICAgfSBjYXRjaCB7XG4gICAgICAgIHJldHVybiAwO1xuICAgICAgfVxuICAgIH0sXG4gIH07XG59XG5cbi8qKlxuICogQnVpbGQgYSBSZWRpcy1iYWNrZWQgYXVkaXQgc3Bvb2wgZnJvbSB0aGUgYW1iaWVudCBgUkVESVNfVVJMYCAvIGBSRURJU19IT1NUYFxuICogZW52IHZpYSB0aGUgc2hhcmVkIGBjcmVhdGVFbnZSZWRpc0NsaWVudGAuIFJldHVybnMgYG51bGxgIHdoZW4gUmVkaXMgaXMgbm90XG4gKiBjb25maWd1cmVkIG9yIHRoZSBjbGllbnQgY2FuJ3QgYmUgY29uc3RydWN0ZWQg4oCUIHRoZSBjYWxsZXIgdGhlbiBydW5zIHdpdGhvdXQgYVxuICogc3Bvb2wgKHByZS1zcG9vbCBiZXN0LWVmZm9ydCBiZWhhdmlvciksIG5ldmVyIGNyYXNoaW5nLiBUaGUgc2hhcmVkIGhlbHBlciBsb2Fkc1xuICogaW9yZWRpcyB2aWEgYGNyZWF0ZVJlcXVpcmVgLCBzbyB0aGlzIHN0YXlzIGltcG9ydGFibGUgd2hlcmUgUmVkaXMgaXNuJ3QgcHJlc2VudC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUVudlJlZGlzQXVkaXRTcG9vbChvcHRzOiB7IG1heERlcHRoPzogbnVtYmVyOyBrZXk/OiBzdHJpbmcgfSA9IHt9KTogQXVkaXRTcG9vbCB8IG51bGwge1xuICBjb25zdCBpbnN0ID0gY3JlYXRlRW52UmVkaXNDbGllbnQ8UmVkaXNMaXN0Q2xpZW50PignYXVkaXQtc3Bvb2wnKTtcbiAgaWYgKCFpbnN0KSByZXR1cm4gbnVsbDtcbiAgbG9nZ2VyLmluZm8oJ1JlZGlzIGF1ZGl0IHNwb29sIGluaXRpYWxpemVkJyk7XG4gIHJldHVybiBjcmVhdGVSZWRpc0F1ZGl0U3Bvb2woaW5zdCwgb3B0cyk7XG59XG4iXX0=
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Minimal Redis-like client interface (subset of ioredis).
3
+ * Services pass their own Redis client instance.
4
+ */
5
+ export interface RedisCacheClient {
6
+ get(key: string): Promise<string | null>;
7
+ set(key: string, value: string, ...args: unknown[]): Promise<unknown>;
8
+ del(...keys: string[]): Promise<number>;
9
+ keys(pattern: string): Promise<string[]>;
10
+ /** SCAN-based iteration (preferred over KEYS for production). */
11
+ scanStream?(options: {
12
+ match: string;
13
+ count?: number;
14
+ }): NodeJS.ReadableStream;
15
+ }
16
+ export interface CacheConfig {
17
+ /** Key prefix for namespace isolation (e.g., 'plugin:', 'compliance:') */
18
+ prefix: string;
19
+ /** Default TTL in seconds */
20
+ defaultTtlSeconds: number;
21
+ /** Max entries for in-memory cache (default 1000) */
22
+ maxEntries?: number;
23
+ /** Optional Redis client — uses in-memory cache if not provided */
24
+ redis?: RedisCacheClient;
25
+ }
26
+ /**
27
+ * Cache service with get/set/del operations and automatic TTL expiry.
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const cache = new CacheService({ prefix: 'plugin:', defaultTtlSeconds: 300 });
32
+ *
33
+ * // Set with default TTL
34
+ * await cache.set('org123:list', plugins);
35
+ *
36
+ * // Get (returns null on miss)
37
+ * const cached = await cache.get<Plugin[]>('org123:list');
38
+ *
39
+ * // Invalidate
40
+ * await cache.del('org123:list');
41
+ *
42
+ * // Invalidate by pattern
43
+ * await cache.invalidatePattern('org123:*');
44
+ * ```
45
+ */
46
+ export declare class CacheService {
47
+ private memory;
48
+ /**
49
+ * In-flight `getOrSet` factory promises, keyed by full cache key. Coalesces
50
+ * concurrent cold callers onto a single `factory()` invocation (single-flight
51
+ * / stampede protection); entries are cleared once the promise settles.
52
+ */
53
+ private inflight;
54
+ private readonly prefix;
55
+ private readonly defaultTtlMs;
56
+ private readonly maxEntries;
57
+ private readonly redis?;
58
+ /** Cache metrics — tracks hits, misses, and invalidations. */
59
+ readonly metrics: {
60
+ hits: number;
61
+ misses: number;
62
+ sets: number;
63
+ invalidations: number;
64
+ };
65
+ constructor(config: CacheConfig);
66
+ private fullKey;
67
+ /**
68
+ * Get a cached value. Returns null on miss or error.
69
+ *
70
+ * The returned value is an INDEPENDENT deep copy — mutating it never affects
71
+ * the cached entry (the in-memory backend clones on read for parity with
72
+ * Redis, which returns a fresh JSON clone). Callers may still treat results as
73
+ * immutable, but doing so is no longer load-bearing for cache integrity.
74
+ */
75
+ get<T>(key: string): Promise<T | null>;
76
+ /**
77
+ * Set a cached value with optional TTL override.
78
+ *
79
+ * @param key - Cache key (prefix is added automatically)
80
+ * @param value - Value to cache (must be JSON-serializable for Redis)
81
+ * @param ttlSeconds - TTL override (uses default if not provided)
82
+ */
83
+ set<T>(key: string, value: T, ttlSeconds?: number): Promise<void>;
84
+ /**
85
+ * Delete a cached value.
86
+ */
87
+ del(key: string): Promise<void>;
88
+ /**
89
+ * Invalidate all keys matching a pattern (e.g., 'org123:*').
90
+ * Uses SCAN for Redis (non-blocking) with KEYS fallback, or regex for in-memory.
91
+ */
92
+ invalidatePattern(pattern: string): Promise<number>;
93
+ /** Collect keys via Redis SCAN (non-blocking alternative to KEYS). */
94
+ private scanKeys;
95
+ /**
96
+ * Get or compute: returns cached value if available, otherwise calls the
97
+ * factory function, caches the result, and returns it.
98
+ *
99
+ * Single-flight / stampede protection: when N callers miss concurrently for
100
+ * the same key, only ONE `factory()` runs — the rest await its shared promise.
101
+ * The in-flight entry is cleared once it settles (success or failure), so a
102
+ * failed factory doesn't poison later calls.
103
+ *
104
+ * @param key - Cache key
105
+ * @param factory - Async function to compute the value on cache miss
106
+ * @param ttlSeconds - Optional TTL override
107
+ * @returns The cached or computed value
108
+ */
109
+ getOrSet<T>(key: string, factory: () => Promise<T>, ttlSeconds?: number): Promise<T>;
110
+ /**
111
+ * Clear all entries (useful for testing).
112
+ */
113
+ clear(): Promise<void>;
114
+ /** Current in-memory cache size (for diagnostics). */
115
+ get size(): number;
116
+ }
117
+ /**
118
+ * Create a cache service instance.
119
+ *
120
+ * @param prefix - Namespace prefix (e.g., 'compliance:', 'plugin:')
121
+ * @param defaultTtlSeconds - Default TTL in seconds (default 300 = 5 min)
122
+ * @param redis - Optional Redis client for cross-process caching
123
+ */
124
+ export declare function createCacheService(prefix: string, defaultTtlSeconds?: number, redis?: RedisCacheClient): CacheService;
@@ -0,0 +1,276 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Deep-clone a value so the in-memory backend hands back an INDEPENDENT copy on
5
+ * every read — matching the Redis backend, which returns a fresh `JSON.parse`
6
+ * clone each time. Without this the in-memory cache returns the same stored
7
+ * object reference, so a consumer mutating a "cached" object silently corrupts
8
+ * the shared cache (a footgun that only bites under the memory backend).
9
+ *
10
+ * Primitives are returned as-is. Prefers the structured-clone algorithm; falls
11
+ * back to a JSON round-trip on older runtimes or exotic values.
12
+ */
13
+ function cloneValue(value) {
14
+ if (value === null || typeof value !== 'object')
15
+ return value;
16
+ try {
17
+ return structuredClone(value);
18
+ }
19
+ catch {
20
+ return JSON.parse(JSON.stringify(value));
21
+ }
22
+ }
23
+ /**
24
+ * Cache service with get/set/del operations and automatic TTL expiry.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const cache = new CacheService({ prefix: 'plugin:', defaultTtlSeconds: 300 });
29
+ *
30
+ * // Set with default TTL
31
+ * await cache.set('org123:list', plugins);
32
+ *
33
+ * // Get (returns null on miss)
34
+ * const cached = await cache.get<Plugin[]>('org123:list');
35
+ *
36
+ * // Invalidate
37
+ * await cache.del('org123:list');
38
+ *
39
+ * // Invalidate by pattern
40
+ * await cache.invalidatePattern('org123:*');
41
+ * ```
42
+ */
43
+ export class CacheService {
44
+ memory = new Map();
45
+ /**
46
+ * In-flight `getOrSet` factory promises, keyed by full cache key. Coalesces
47
+ * concurrent cold callers onto a single `factory()` invocation (single-flight
48
+ * / stampede protection); entries are cleared once the promise settles.
49
+ */
50
+ inflight = new Map();
51
+ prefix;
52
+ defaultTtlMs;
53
+ maxEntries;
54
+ redis;
55
+ /** Cache metrics — tracks hits, misses, and invalidations. */
56
+ metrics = { hits: 0, misses: 0, sets: 0, invalidations: 0 };
57
+ constructor(config) {
58
+ this.prefix = config.prefix;
59
+ this.defaultTtlMs = config.defaultTtlSeconds * 1000;
60
+ this.maxEntries = config.maxEntries ?? 1000;
61
+ this.redis = config.redis;
62
+ }
63
+ fullKey(key) {
64
+ return `${this.prefix}${key}`;
65
+ }
66
+ /**
67
+ * Get a cached value. Returns null on miss or error.
68
+ *
69
+ * The returned value is an INDEPENDENT deep copy — mutating it never affects
70
+ * the cached entry (the in-memory backend clones on read for parity with
71
+ * Redis, which returns a fresh JSON clone). Callers may still treat results as
72
+ * immutable, but doing so is no longer load-bearing for cache integrity.
73
+ */
74
+ async get(key) {
75
+ const fk = this.fullKey(key);
76
+ try {
77
+ if (this.redis) {
78
+ const raw = await this.redis.get(fk);
79
+ if (!raw) {
80
+ this.metrics.misses++;
81
+ return null;
82
+ }
83
+ this.metrics.hits++;
84
+ return JSON.parse(raw);
85
+ }
86
+ // In-memory
87
+ const entry = this.memory.get(fk);
88
+ if (!entry) {
89
+ this.metrics.misses++;
90
+ return null;
91
+ }
92
+ if (Date.now() > entry.expiresAt) {
93
+ this.memory.delete(fk);
94
+ this.metrics.misses++;
95
+ return null;
96
+ }
97
+ // LRU touch: delete + re-insert moves this key to the newest position
98
+ // (Map preserves insertion order), so `set`'s oldest-first eviction is a
99
+ // true least-recently-USED eviction rather than first-inserted (FIFO).
100
+ this.memory.delete(fk);
101
+ this.memory.set(fk, entry);
102
+ this.metrics.hits++;
103
+ // Clone so a caller mutating the result can't corrupt the shared entry.
104
+ return cloneValue(entry.value);
105
+ }
106
+ catch {
107
+ this.metrics.misses++;
108
+ return null;
109
+ }
110
+ }
111
+ /**
112
+ * Set a cached value with optional TTL override.
113
+ *
114
+ * @param key - Cache key (prefix is added automatically)
115
+ * @param value - Value to cache (must be JSON-serializable for Redis)
116
+ * @param ttlSeconds - TTL override (uses default if not provided)
117
+ */
118
+ async set(key, value, ttlSeconds) {
119
+ const fk = this.fullKey(key);
120
+ const ttl = ttlSeconds ?? this.defaultTtlMs / 1000;
121
+ try {
122
+ this.metrics.sets++;
123
+ if (this.redis) {
124
+ await this.redis.set(fk, JSON.stringify(value), 'EX', ttl);
125
+ return;
126
+ }
127
+ // In-memory — a write counts as a use, so drop any existing entry first
128
+ // and re-insert below as the newest (Map preserves insertion order). This
129
+ // keeps the Map ordered oldest-USED → newest-USED so the eviction below is
130
+ // true LRU, and avoids evicting a victim when merely updating a key.
131
+ this.memory.delete(fk);
132
+ if (this.memory.size >= this.maxEntries) {
133
+ const firstKey = this.memory.keys().next().value;
134
+ if (firstKey)
135
+ this.memory.delete(firstKey);
136
+ }
137
+ this.memory.set(fk, {
138
+ value,
139
+ expiresAt: Date.now() + ttl * 1000,
140
+ });
141
+ }
142
+ catch {
143
+ // Cache set failure is non-fatal
144
+ }
145
+ }
146
+ /**
147
+ * Delete a cached value.
148
+ */
149
+ async del(key) {
150
+ const fk = this.fullKey(key);
151
+ try {
152
+ if (this.redis) {
153
+ await this.redis.del(fk);
154
+ return;
155
+ }
156
+ this.memory.delete(fk);
157
+ }
158
+ catch {
159
+ // Cache delete failure is non-fatal
160
+ }
161
+ }
162
+ /**
163
+ * Invalidate all keys matching a pattern (e.g., 'org123:*').
164
+ * Uses SCAN for Redis (non-blocking) with KEYS fallback, or regex for in-memory.
165
+ */
166
+ async invalidatePattern(pattern) {
167
+ const fp = this.fullKey(pattern);
168
+ try {
169
+ if (this.redis) {
170
+ const keys = this.redis.scanStream
171
+ ? await this.scanKeys(fp)
172
+ : await this.redis.keys(fp);
173
+ if (keys.length > 0) {
174
+ await this.redis.del(...keys);
175
+ this.metrics.invalidations += keys.length;
176
+ }
177
+ return keys.length;
178
+ }
179
+ // In-memory — match glob-style pattern. Escape regex metacharacters first
180
+ // so a literal `.`/`(`/`[` in a key prefix (e.g. `org:v1.2:*`) can't
181
+ // over-match or throw (a throw here is swallowed → nothing invalidated →
182
+ // stale reads); only `*` is treated as a wildcard.
183
+ const regex = new RegExp('^' + fp.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\\\*/g, '.*') + '$');
184
+ let deleted = 0;
185
+ for (const key of this.memory.keys()) {
186
+ if (regex.test(key)) {
187
+ this.memory.delete(key);
188
+ deleted++;
189
+ }
190
+ }
191
+ this.metrics.invalidations += deleted;
192
+ return deleted;
193
+ }
194
+ catch {
195
+ return 0;
196
+ }
197
+ }
198
+ /** Collect keys via Redis SCAN (non-blocking alternative to KEYS). */
199
+ scanKeys(pattern) {
200
+ return new Promise((resolve, reject) => {
201
+ const stream = this.redis.scanStream({ match: pattern, count: 100 });
202
+ const keys = [];
203
+ stream.on('data', (batch) => keys.push(...batch));
204
+ stream.once('end', () => resolve(keys));
205
+ stream.once('error', reject);
206
+ });
207
+ }
208
+ /**
209
+ * Get or compute: returns cached value if available, otherwise calls the
210
+ * factory function, caches the result, and returns it.
211
+ *
212
+ * Single-flight / stampede protection: when N callers miss concurrently for
213
+ * the same key, only ONE `factory()` runs — the rest await its shared promise.
214
+ * The in-flight entry is cleared once it settles (success or failure), so a
215
+ * failed factory doesn't poison later calls.
216
+ *
217
+ * @param key - Cache key
218
+ * @param factory - Async function to compute the value on cache miss
219
+ * @param ttlSeconds - Optional TTL override
220
+ * @returns The cached or computed value
221
+ */
222
+ async getOrSet(key, factory, ttlSeconds) {
223
+ const cached = await this.get(key);
224
+ if (cached !== null)
225
+ return cached;
226
+ const fk = this.fullKey(key);
227
+ // Coalesce concurrent cold callers onto a single factory() invocation. The
228
+ // in-flight promise resolves to the RAW value; each awaiter (the primary
229
+ // caller below AND every coalesced caller here) clones it INDEPENDENTLY.
230
+ // Cloning inside the flight instead would hand one shared clone to all N
231
+ // coalesced callers, so one caller mutating its "independent" result would
232
+ // corrupt what the others read — the exact footgun get()'s clone-on-read
233
+ // prevents. (Redis backend is unaffected: set serializes, get JSON.parses.)
234
+ const existing = this.inflight.get(fk);
235
+ if (existing)
236
+ return cloneValue(await existing);
237
+ const flight = (async () => {
238
+ const value = await factory();
239
+ await this.set(key, value, ttlSeconds);
240
+ return value;
241
+ })();
242
+ this.inflight.set(fk, flight);
243
+ try {
244
+ return cloneValue(await flight);
245
+ }
246
+ finally {
247
+ this.inflight.delete(fk);
248
+ }
249
+ }
250
+ /**
251
+ * Clear all entries (useful for testing).
252
+ */
253
+ async clear() {
254
+ if (this.redis) {
255
+ await this.invalidatePattern('*');
256
+ }
257
+ else {
258
+ this.memory.clear();
259
+ }
260
+ }
261
+ /** Current in-memory cache size (for diagnostics). */
262
+ get size() {
263
+ return this.memory.size;
264
+ }
265
+ }
266
+ /**
267
+ * Create a cache service instance.
268
+ *
269
+ * @param prefix - Namespace prefix (e.g., 'compliance:', 'plugin:')
270
+ * @param defaultTtlSeconds - Default TTL in seconds (default 300 = 5 min)
271
+ * @param redis - Optional Redis client for cross-process caching
272
+ */
273
+ export function createCacheService(prefix, defaultTtlSeconds = 300, redis) {
274
+ return new CacheService({ prefix, defaultTtlSeconds, redis });
275
+ }
276
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2FjaGUtc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXJ2aWNlcy9jYWNoZS1zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUEwQnRDOzs7Ozs7Ozs7R0FTRztBQUNILFNBQVMsVUFBVSxDQUFJLEtBQVE7SUFDN0IsSUFBSSxLQUFLLEtBQUssSUFBSSxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVE7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUM5RCxJQUFJLENBQUM7UUFDSCxPQUFPLGVBQWUsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUNoQyxDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQU0sQ0FBQztJQUNoRCxDQUFDO0FBQ0gsQ0FBQztBQTBCRDs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW1CRztBQUNILE1BQU0sT0FBTyxZQUFZO0lBQ2YsTUFBTSxHQUFHLElBQUksR0FBRyxFQUErQixDQUFDO0lBQ3hEOzs7O09BSUc7SUFDSyxRQUFRLEdBQUcsSUFBSSxHQUFHLEVBQTRCLENBQUM7SUFDdEMsTUFBTSxDQUFTO0lBQ2YsWUFBWSxDQUFTO0lBQ3JCLFVBQVUsQ0FBUztJQUNuQixLQUFLLENBQW9CO0lBRTFDLDhEQUE4RDtJQUNyRCxPQUFPLEdBQUcsRUFBRSxJQUFJLEVBQUUsQ0FBQyxFQUFFLE1BQU0sRUFBRSxDQUFDLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBRSxhQUFhLEVBQUUsQ0FBQyxFQUFFLENBQUM7SUFFckUsWUFBWSxNQUFtQjtRQUM3QixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7UUFDNUIsSUFBSSxDQUFDLFlBQVksR0FBRyxNQUFNLENBQUMsaUJBQWlCLEdBQUcsSUFBSSxDQUFDO1FBQ3BELElBQUksQ0FBQyxVQUFVLEdBQUcsTUFBTSxDQUFDLFVBQVUsSUFBSSxJQUFJLENBQUM7UUFDNUMsSUFBSSxDQUFDLEtBQUssR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDO0lBQzVCLENBQUM7SUFFTyxPQUFPLENBQUMsR0FBVztRQUN6QixPQUFPLEdBQUcsSUFBSSxDQUFDLE1BQU0sR0FBRyxHQUFHLEVBQUUsQ0FBQztJQUNoQyxDQUFDO0lBRUQ7Ozs7Ozs7T0FPRztJQUNILEtBQUssQ0FBQyxHQUFHLENBQUksR0FBVztRQUN0QixNQUFNLEVBQUUsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBRTdCLElBQUksQ0FBQztZQUNILElBQUksSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO2dCQUNmLE1BQU0sR0FBRyxHQUFHLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUM7Z0JBQ3JDLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztvQkFBQyxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFDO29CQUFDLE9BQU8sSUFBSSxDQUFDO2dCQUFDLENBQUM7Z0JBQ2pELElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFFLENBQUM7Z0JBQ3BCLE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQU0sQ0FBQztZQUM5QixDQUFDO1lBRUQsWUFBWTtZQUNaLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDO1lBQ2xDLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztnQkFBQyxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFDO2dCQUFDLE9BQU8sSUFBSSxDQUFDO1lBQUMsQ0FBQztZQUNuRCxJQUFJLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxLQUFLLENBQUMsU0FBUyxFQUFFLENBQUM7Z0JBQ2pDLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxDQUFDO2dCQUN2QixJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFDO2dCQUN0QixPQUFPLElBQUksQ0FBQztZQUNkLENBQUM7WUFDRCxzRUFBc0U7WUFDdEUseUVBQXlFO1lBQ3pFLHVFQUF1RTtZQUN2RSxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUN2QixJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxFQUFFLEVBQUUsS0FBSyxDQUFDLENBQUM7WUFDM0IsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUNwQix3RUFBd0U7WUFDeEUsT0FBTyxVQUFVLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBTSxDQUFDO1FBQ3RDLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ3RCLE9BQU8sSUFBSSxDQUFDO1FBQ2QsQ0FBQztJQUNILENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSCxLQUFLLENBQUMsR0FBRyxDQUFJLEdBQVcsRUFBRSxLQUFRLEVBQUUsVUFBbUI7UUFDckQsTUFBTSxFQUFFLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUM3QixNQUFNLEdBQUcsR0FBRyxVQUFVLElBQUksSUFBSSxDQUFDLFlBQVksR0FBRyxJQUFJLENBQUM7UUFFbkQsSUFBSSxDQUFDO1lBQ0gsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUNwQixJQUFJLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztnQkFDZixNQUFNLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLEVBQUUsRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxFQUFFLElBQUksRUFBRSxHQUFHLENBQUMsQ0FBQztnQkFDM0QsT0FBTztZQUNULENBQUM7WUFFRCx3RUFBd0U7WUFDeEUsMEVBQTBFO1lBQzFFLDJFQUEyRTtZQUMzRSxxRUFBcUU7WUFDckUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLENBQUM7WUFDdkIsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksSUFBSSxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUM7Z0JBQ3hDLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxFQUFFLENBQUMsSUFBSSxFQUFFLENBQUMsS0FBSyxDQUFDO2dCQUNqRCxJQUFJLFFBQVE7b0JBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7WUFDN0MsQ0FBQztZQUVELElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLEVBQUUsRUFBRTtnQkFDbEIsS0FBSztnQkFDTCxTQUFTLEVBQUUsSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLEdBQUcsR0FBRyxJQUFJO2FBQ25DLENBQUMsQ0FBQztRQUNMLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxpQ0FBaUM7UUFDbkMsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyxHQUFHLENBQUMsR0FBVztRQUNuQixNQUFNLEVBQUUsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBRTdCLElBQUksQ0FBQztZQUNILElBQUksSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO2dCQUNmLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUM7Z0JBQ3pCLE9BQU87WUFDVCxDQUFDO1lBQ0QsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDekIsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLG9DQUFvQztRQUN0QyxDQUFDO0lBQ0gsQ0FBQztJQUVEOzs7T0FHRztJQUNILEtBQUssQ0FBQyxpQkFBaUIsQ0FBQyxPQUFlO1FBQ3JDLE1BQU0sRUFBRSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUM7UUFFakMsSUFBSSxDQUFDO1lBQ0gsSUFBSSxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7Z0JBQ2YsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxVQUFVO29CQUNoQyxDQUFDLENBQUMsTUFBTSxJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztvQkFDekIsQ0FBQyxDQUFDLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUM7Z0JBQzlCLElBQUksSUFBSSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztvQkFDcEIsTUFBTSxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxHQUFHLElBQUksQ0FBQyxDQUFDO29CQUM5QixJQUFJLENBQUMsT0FBTyxDQUFDLGFBQWEsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDO2dCQUM1QyxDQUFDO2dCQUNELE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQztZQUNyQixDQUFDO1lBRUQsMEVBQTBFO1lBQzFFLHFFQUFxRTtZQUNyRSx5RUFBeUU7WUFDekUsbURBQW1EO1lBQ25ELE1BQU0sS0FBSyxHQUFHLElBQUksTUFBTSxDQUFDLEdBQUcsR0FBRyxFQUFFLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLE1BQU0sQ0FBQyxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLEdBQUcsR0FBRyxDQUFDLENBQUM7WUFDdkcsSUFBSSxPQUFPLEdBQUcsQ0FBQyxDQUFDO1lBQ2hCLEtBQUssTUFBTSxHQUFHLElBQUksSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDO2dCQUNyQyxJQUFJLEtBQUssQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztvQkFDcEIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7b0JBQ3hCLE9BQU8sRUFBRSxDQUFDO2dCQUNaLENBQUM7WUFDSCxDQUFDO1lBQ0QsSUFBSSxDQUFDLE9BQU8sQ0FBQyxhQUFhLElBQUksT0FBTyxDQUFDO1lBQ3RDLE9BQU8sT0FBTyxDQUFDO1FBQ2pCLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxPQUFPLENBQUMsQ0FBQztRQUNYLENBQUM7SUFDSCxDQUFDO0lBRUQsc0VBQXNFO0lBQzlELFFBQVEsQ0FBQyxPQUFlO1FBQzlCLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7WUFDckMsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLEtBQU0sQ0FBQyxVQUFXLENBQUMsRUFBRSxLQUFLLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDO1lBQ3ZFLE1BQU0sSUFBSSxHQUFhLEVBQUUsQ0FBQztZQUMxQixNQUFNLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxDQUFDLEtBQWUsRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUM7WUFDNUQsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLEVBQUUsR0FBRyxFQUFFLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7WUFDeEMsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFDL0IsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7Ozs7Ozs7Ozs7Ozs7T0FhRztJQUNILEtBQUssQ0FBQyxRQUFRLENBQUksR0FBVyxFQUFFLE9BQXlCLEVBQUUsVUFBbUI7UUFDM0UsTUFBTSxNQUFNLEdBQUcsTUFBTSxJQUFJLENBQUMsR0FBRyxDQUFJLEdBQUcsQ0FBQyxDQUFDO1FBQ3RDLElBQUksTUFBTSxLQUFLLElBQUk7WUFBRSxPQUFPLE1BQU0sQ0FBQztRQUVuQyxNQUFNLEVBQUUsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQzdCLDJFQUEyRTtRQUMzRSx5RUFBeUU7UUFDekUseUVBQXlFO1FBQ3pFLHlFQUF5RTtRQUN6RSwyRUFBMkU7UUFDM0UseUVBQXlFO1FBQ3pFLDRFQUE0RTtRQUM1RSxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQTJCLENBQUM7UUFDakUsSUFBSSxRQUFRO1lBQUUsT0FBTyxVQUFVLENBQUMsTUFBTSxRQUFRLENBQUMsQ0FBQztRQUVoRCxNQUFNLE1BQU0sR0FBRyxDQUFDLEtBQUssSUFBSSxFQUFFO1lBQ3pCLE1BQU0sS0FBSyxHQUFHLE1BQU0sT0FBTyxFQUFFLENBQUM7WUFDOUIsTUFBTSxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxLQUFLLEVBQUUsVUFBVSxDQUFDLENBQUM7WUFDdkMsT0FBTyxLQUFLLENBQUM7UUFDZixDQUFDLENBQUMsRUFBRSxDQUFDO1FBQ0wsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsRUFBRSxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQzlCLElBQUksQ0FBQztZQUNILE9BQU8sVUFBVSxDQUFDLE1BQU0sTUFBTSxDQUFDLENBQUM7UUFDbEMsQ0FBQztnQkFBUyxDQUFDO1lBQ1QsSUFBSSxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDM0IsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyxLQUFLO1FBQ1QsSUFBSSxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7WUFDZixNQUFNLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNwQyxDQUFDO2FBQU0sQ0FBQztZQUNOLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDdEIsQ0FBQztJQUNILENBQUM7SUFFRCxzREFBc0Q7SUFDdEQsSUFBSSxJQUFJO1FBQ04sT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQztJQUMxQixDQUFDO0NBQ0Y7QUFFRDs7Ozs7O0dBTUc7QUFDSCxNQUFNLFVBQVUsa0JBQWtCLENBQ2hDLE1BQWMsRUFDZCxpQkFBaUIsR0FBRyxHQUFHLEVBQ3ZCLEtBQXdCO0lBRXhCLE9BQU8sSUFBSSxZQUFZLENBQUMsRUFBRSxNQUFNLEVBQUUsaUJBQWlCLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQztBQUNoRSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbi8qKlxuICogTGlnaHR3ZWlnaHQgY2FjaGluZyBzZXJ2aWNlIHdpdGggVFRMIHN1cHBvcnQuXG4gKlxuICogVHdvIGltcGxlbWVudGF0aW9uczpcbiAqIC0gSW4tbWVtb3J5IChkZWZhdWx0KTogdHJ1ZS1MUlUgTWFwIGNhY2hlLCBubyBleHRlcm5hbCBkZXBlbmRlbmNpZXNcbiAqIC0gUmVkaXM6IFdoZW4gYSBSZWRpcyBjbGllbnQgaXMgcHJvdmlkZWQsIHVzZXMgUmVkaXMgZm9yIGNyb3NzLXByb2Nlc3MgY2FjaGluZ1xuICpcbiAqIERlc2lnbjpcbiAqIC0gQWxsIG9wZXJhdGlvbnMgYXJlIGZhaWwtc2FmZTogY2FjaGUgbWlzc2VzL2Vycm9ycyByZXR1cm4gbnVsbCwgbmV2ZXIgdGhyb3dcbiAqIC0gSlNPTiBzZXJpYWxpemF0aW9uIGZvciBSZWRpczsgdGhlIGluLW1lbW9yeSBiYWNrZW5kIGRlZXAtY2xvbmVzIG9uIHJlYWQgc29cbiAqICAgYm90aCBiYWNrZW5kcyByZXR1cm4gYW4gaW5kZXBlbmRlbnQgY29weSAobXV0YXRpbmcgYSByZXN1bHQgaXMgYWx3YXlzIHNhZmUpXG4gKiAtIGBnZXRPclNldGAgaXMgc2luZ2xlLWZsaWdodDogY29uY3VycmVudCBtaXNzZXMgc2hhcmUgb25lIGZhY3RvcnkoKSBjYWxsXG4gKiAtIEtleSBuYW1lc3BhY2UgcHJlZml4aW5nIHRvIGF2b2lkIGNvbGxpc2lvbnMgYmV0d2VlbiBzZXJ2aWNlc1xuICovXG5cblxuLyoqXG4gKiBDYWNoZSBlbnRyeSB3aXRoIHZhbHVlIGFuZCBleHBpcmF0aW9uIHRpbWUuXG4gKi9cbmludGVyZmFjZSBDYWNoZUVudHJ5PFQ+IHtcbiAgdmFsdWU6IFQ7XG4gIGV4cGlyZXNBdDogbnVtYmVyOyAvLyBVbml4IHRpbWVzdGFtcCBpbiBtc1xufVxuXG4vKipcbiAqIERlZXAtY2xvbmUgYSB2YWx1ZSBzbyB0aGUgaW4tbWVtb3J5IGJhY2tlbmQgaGFuZHMgYmFjayBhbiBJTkRFUEVOREVOVCBjb3B5IG9uXG4gKiBldmVyeSByZWFkIOKAlCBtYXRjaGluZyB0aGUgUmVkaXMgYmFja2VuZCwgd2hpY2ggcmV0dXJucyBhIGZyZXNoIGBKU09OLnBhcnNlYFxuICogY2xvbmUgZWFjaCB0aW1lLiBXaXRob3V0IHRoaXMgdGhlIGluLW1lbW9yeSBjYWNoZSByZXR1cm5zIHRoZSBzYW1lIHN0b3JlZFxuICogb2JqZWN0IHJlZmVyZW5jZSwgc28gYSBjb25zdW1lciBtdXRhdGluZyBhIFwiY2FjaGVkXCIgb2JqZWN0IHNpbGVudGx5IGNvcnJ1cHRzXG4gKiB0aGUgc2hhcmVkIGNhY2hlIChhIGZvb3RndW4gdGhhdCBvbmx5IGJpdGVzIHVuZGVyIHRoZSBtZW1vcnkgYmFja2VuZCkuXG4gKlxuICogUHJpbWl0aXZlcyBhcmUgcmV0dXJuZWQgYXMtaXMuIFByZWZlcnMgdGhlIHN0cnVjdHVyZWQtY2xvbmUgYWxnb3JpdGhtOyBmYWxsc1xuICogYmFjayB0byBhIEpTT04gcm91bmQtdHJpcCBvbiBvbGRlciBydW50aW1lcyBvciBleG90aWMgdmFsdWVzLlxuICovXG5mdW5jdGlvbiBjbG9uZVZhbHVlPFQ+KHZhbHVlOiBUKTogVCB7XG4gIGlmICh2YWx1ZSA9PT0gbnVsbCB8fCB0eXBlb2YgdmFsdWUgIT09ICdvYmplY3QnKSByZXR1cm4gdmFsdWU7XG4gIHRyeSB7XG4gICAgcmV0dXJuIHN0cnVjdHVyZWRDbG9uZSh2YWx1ZSk7XG4gIH0gY2F0Y2gge1xuICAgIHJldHVybiBKU09OLnBhcnNlKEpTT04uc3RyaW5naWZ5KHZhbHVlKSkgYXMgVDtcbiAgfVxufVxuXG4vKipcbiAqIE1pbmltYWwgUmVkaXMtbGlrZSBjbGllbnQgaW50ZXJmYWNlIChzdWJzZXQgb2YgaW9yZWRpcykuXG4gKiBTZXJ2aWNlcyBwYXNzIHRoZWlyIG93biBSZWRpcyBjbGllbnQgaW5zdGFuY2UuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUmVkaXNDYWNoZUNsaWVudCB7XG4gIGdldChrZXk6IHN0cmluZyk6IFByb21pc2U8c3RyaW5nIHwgbnVsbD47XG4gIHNldChrZXk6IHN0cmluZywgdmFsdWU6IHN0cmluZywgLi4uYXJnczogdW5rbm93bltdKTogUHJvbWlzZTx1bmtub3duPjtcbiAgZGVsKC4uLmtleXM6IHN0cmluZ1tdKTogUHJvbWlzZTxudW1iZXI+O1xuICBrZXlzKHBhdHRlcm46IHN0cmluZyk6IFByb21pc2U8c3RyaW5nW10+O1xuICAvKiogU0NBTi1iYXNlZCBpdGVyYXRpb24gKHByZWZlcnJlZCBvdmVyIEtFWVMgZm9yIHByb2R1Y3Rpb24pLiAqL1xuICBzY2FuU3RyZWFtPyhvcHRpb25zOiB7IG1hdGNoOiBzdHJpbmc7IGNvdW50PzogbnVtYmVyIH0pOiBOb2RlSlMuUmVhZGFibGVTdHJlYW07XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgQ2FjaGVDb25maWcge1xuICAvKiogS2V5IHByZWZpeCBmb3IgbmFtZXNwYWNlIGlzb2xhdGlvbiAoZS5nLiwgJ3BsdWdpbjonLCAnY29tcGxpYW5jZTonKSAqL1xuICBwcmVmaXg6IHN0cmluZztcbiAgLyoqIERlZmF1bHQgVFRMIGluIHNlY29uZHMgKi9cbiAgZGVmYXVsdFR0bFNlY29uZHM6IG51bWJlcjtcbiAgLyoqIE1heCBlbnRyaWVzIGZvciBpbi1tZW1vcnkgY2FjaGUgKGRlZmF1bHQgMTAwMCkgKi9cbiAgbWF4RW50cmllcz86IG51bWJlcjtcbiAgLyoqIE9wdGlvbmFsIFJlZGlzIGNsaWVudCDigJQgdXNlcyBpbi1tZW1vcnkgY2FjaGUgaWYgbm90IHByb3ZpZGVkICovXG4gIHJlZGlzPzogUmVkaXNDYWNoZUNsaWVudDtcbn1cblxuLyoqXG4gKiBDYWNoZSBzZXJ2aWNlIHdpdGggZ2V0L3NldC9kZWwgb3BlcmF0aW9ucyBhbmQgYXV0b21hdGljIFRUTCBleHBpcnkuXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIGNvbnN0IGNhY2hlID0gbmV3IENhY2hlU2VydmljZSh7IHByZWZpeDogJ3BsdWdpbjonLCBkZWZhdWx0VHRsU2Vjb25kczogMzAwIH0pO1xuICpcbiAqIC8vIFNldCB3aXRoIGRlZmF1bHQgVFRMXG4gKiBhd2FpdCBjYWNoZS5zZXQoJ29yZzEyMzpsaXN0JywgcGx1Z2lucyk7XG4gKlxuICogLy8gR2V0IChyZXR1cm5zIG51bGwgb24gbWlzcylcbiAqIGNvbnN0IGNhY2hlZCA9IGF3YWl0IGNhY2hlLmdldDxQbHVnaW5bXT4oJ29yZzEyMzpsaXN0Jyk7XG4gKlxuICogLy8gSW52YWxpZGF0ZVxuICogYXdhaXQgY2FjaGUuZGVsKCdvcmcxMjM6bGlzdCcpO1xuICpcbiAqIC8vIEludmFsaWRhdGUgYnkgcGF0dGVyblxuICogYXdhaXQgY2FjaGUuaW52YWxpZGF0ZVBhdHRlcm4oJ29yZzEyMzoqJyk7XG4gKiBgYGBcbiAqL1xuZXhwb3J0IGNsYXNzIENhY2hlU2VydmljZSB7XG4gIHByaXZhdGUgbWVtb3J5ID0gbmV3IE1hcDxzdHJpbmcsIENhY2hlRW50cnk8dW5rbm93bj4+KCk7XG4gIC8qKlxuICAgKiBJbi1mbGlnaHQgYGdldE9yU2V0YCBmYWN0b3J5IHByb21pc2VzLCBrZXllZCBieSBmdWxsIGNhY2hlIGtleS4gQ29hbGVzY2VzXG4gICAqIGNvbmN1cnJlbnQgY29sZCBjYWxsZXJzIG9udG8gYSBzaW5nbGUgYGZhY3RvcnkoKWAgaW52b2NhdGlvbiAoc2luZ2xlLWZsaWdodFxuICAgKiAvIHN0YW1wZWRlIHByb3RlY3Rpb24pOyBlbnRyaWVzIGFyZSBjbGVhcmVkIG9uY2UgdGhlIHByb21pc2Ugc2V0dGxlcy5cbiAgICovXG4gIHByaXZhdGUgaW5mbGlnaHQgPSBuZXcgTWFwPHN0cmluZywgUHJvbWlzZTx1bmtub3duPj4oKTtcbiAgcHJpdmF0ZSByZWFkb25seSBwcmVmaXg6IHN0cmluZztcbiAgcHJpdmF0ZSByZWFkb25seSBkZWZhdWx0VHRsTXM6IG51bWJlcjtcbiAgcHJpdmF0ZSByZWFkb25seSBtYXhFbnRyaWVzOiBudW1iZXI7XG4gIHByaXZhdGUgcmVhZG9ubHkgcmVkaXM/OiBSZWRpc0NhY2hlQ2xpZW50O1xuXG4gIC8qKiBDYWNoZSBtZXRyaWNzIOKAlCB0cmFja3MgaGl0cywgbWlzc2VzLCBhbmQgaW52YWxpZGF0aW9ucy4gKi9cbiAgcmVhZG9ubHkgbWV0cmljcyA9IHsgaGl0czogMCwgbWlzc2VzOiAwLCBzZXRzOiAwLCBpbnZhbGlkYXRpb25zOiAwIH07XG5cbiAgY29uc3RydWN0b3IoY29uZmlnOiBDYWNoZUNvbmZpZykge1xuICAgIHRoaXMucHJlZml4ID0gY29uZmlnLnByZWZpeDtcbiAgICB0aGlzLmRlZmF1bHRUdGxNcyA9IGNvbmZpZy5kZWZhdWx0VHRsU2Vjb25kcyAqIDEwMDA7XG4gICAgdGhpcy5tYXhFbnRyaWVzID0gY29uZmlnLm1heEVudHJpZXMgPz8gMTAwMDtcbiAgICB0aGlzLnJlZGlzID0gY29uZmlnLnJlZGlzO1xuICB9XG5cbiAgcHJpdmF0ZSBmdWxsS2V5KGtleTogc3RyaW5nKTogc3RyaW5nIHtcbiAgICByZXR1cm4gYCR7dGhpcy5wcmVmaXh9JHtrZXl9YDtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgYSBjYWNoZWQgdmFsdWUuIFJldHVybnMgbnVsbCBvbiBtaXNzIG9yIGVycm9yLlxuICAgKlxuICAgKiBUaGUgcmV0dXJuZWQgdmFsdWUgaXMgYW4gSU5ERVBFTkRFTlQgZGVlcCBjb3B5IOKAlCBtdXRhdGluZyBpdCBuZXZlciBhZmZlY3RzXG4gICAqIHRoZSBjYWNoZWQgZW50cnkgKHRoZSBpbi1tZW1vcnkgYmFja2VuZCBjbG9uZXMgb24gcmVhZCBmb3IgcGFyaXR5IHdpdGhcbiAgICogUmVkaXMsIHdoaWNoIHJldHVybnMgYSBmcmVzaCBKU09OIGNsb25lKS4gQ2FsbGVycyBtYXkgc3RpbGwgdHJlYXQgcmVzdWx0cyBhc1xuICAgKiBpbW11dGFibGUsIGJ1dCBkb2luZyBzbyBpcyBubyBsb25nZXIgbG9hZC1iZWFyaW5nIGZvciBjYWNoZSBpbnRlZ3JpdHkuXG4gICAqL1xuICBhc3luYyBnZXQ8VD4oa2V5OiBzdHJpbmcpOiBQcm9taXNlPFQgfCBudWxsPiB7XG4gICAgY29uc3QgZmsgPSB0aGlzLmZ1bGxLZXkoa2V5KTtcblxuICAgIHRyeSB7XG4gICAgICBpZiAodGhpcy5yZWRpcykge1xuICAgICAgICBjb25zdCByYXcgPSBhd2FpdCB0aGlzLnJlZGlzLmdldChmayk7XG4gICAgICAgIGlmICghcmF3KSB7IHRoaXMubWV0cmljcy5taXNzZXMrKzsgcmV0dXJuIG51bGw7IH1cbiAgICAgICAgdGhpcy5tZXRyaWNzLmhpdHMrKztcbiAgICAgICAgcmV0dXJuIEpTT04ucGFyc2UocmF3KSBhcyBUO1xuICAgICAgfVxuXG4gICAgICAvLyBJbi1tZW1vcnlcbiAgICAgIGNvbnN0IGVudHJ5ID0gdGhpcy5tZW1vcnkuZ2V0KGZrKTtcbiAgICAgIGlmICghZW50cnkpIHsgdGhpcy5tZXRyaWNzLm1pc3NlcysrOyByZXR1cm4gbnVsbDsgfVxuICAgICAgaWYgKERhdGUubm93KCkgPiBlbnRyeS5leHBpcmVzQXQpIHtcbiAgICAgICAgdGhpcy5tZW1vcnkuZGVsZXRlKGZrKTtcbiAgICAgICAgdGhpcy5tZXRyaWNzLm1pc3NlcysrO1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgIH1cbiAgICAgIC8vIExSVSB0b3VjaDogZGVsZXRlICsgcmUtaW5zZXJ0IG1vdmVzIHRoaXMga2V5IHRvIHRoZSBuZXdlc3QgcG9zaXRpb25cbiAgICAgIC8vIChNYXAgcHJlc2VydmVzIGluc2VydGlvbiBvcmRlciksIHNvIGBzZXRgJ3Mgb2xkZXN0LWZpcnN0IGV2aWN0aW9uIGlzIGFcbiAgICAgIC8vIHRydWUgbGVhc3QtcmVjZW50bHktVVNFRCBldmljdGlvbiByYXRoZXIgdGhhbiBmaXJzdC1pbnNlcnRlZCAoRklGTykuXG4gICAgICB0aGlzLm1lbW9yeS5kZWxldGUoZmspO1xuICAgICAgdGhpcy5tZW1vcnkuc2V0KGZrLCBlbnRyeSk7XG4gICAgICB0aGlzLm1ldHJpY3MuaGl0cysrO1xuICAgICAgLy8gQ2xvbmUgc28gYSBjYWxsZXIgbXV0YXRpbmcgdGhlIHJlc3VsdCBjYW4ndCBjb3JydXB0IHRoZSBzaGFyZWQgZW50cnkuXG4gICAgICByZXR1cm4gY2xvbmVWYWx1ZShlbnRyeS52YWx1ZSkgYXMgVDtcbiAgICB9IGNhdGNoIHtcbiAgICAgIHRoaXMubWV0cmljcy5taXNzZXMrKztcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBTZXQgYSBjYWNoZWQgdmFsdWUgd2l0aCBvcHRpb25hbCBUVEwgb3ZlcnJpZGUuXG4gICAqXG4gICAqIEBwYXJhbSBrZXkgLSBDYWNoZSBrZXkgKHByZWZpeCBpcyBhZGRlZCBhdXRvbWF0aWNhbGx5KVxuICAgKiBAcGFyYW0gdmFsdWUgLSBWYWx1ZSB0byBjYWNoZSAobXVzdCBiZSBKU09OLXNlcmlhbGl6YWJsZSBmb3IgUmVkaXMpXG4gICAqIEBwYXJhbSB0dGxTZWNvbmRzIC0gVFRMIG92ZXJyaWRlICh1c2VzIGRlZmF1bHQgaWYgbm90IHByb3ZpZGVkKVxuICAgKi9cbiAgYXN5bmMgc2V0PFQ+KGtleTogc3RyaW5nLCB2YWx1ZTogVCwgdHRsU2Vjb25kcz86IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IGZrID0gdGhpcy5mdWxsS2V5KGtleSk7XG4gICAgY29uc3QgdHRsID0gdHRsU2Vjb25kcyA/PyB0aGlzLmRlZmF1bHRUdGxNcyAvIDEwMDA7XG5cbiAgICB0cnkge1xuICAgICAgdGhpcy5tZXRyaWNzLnNldHMrKztcbiAgICAgIGlmICh0aGlzLnJlZGlzKSB7XG4gICAgICAgIGF3YWl0IHRoaXMucmVkaXMuc2V0KGZrLCBKU09OLnN0cmluZ2lmeSh2YWx1ZSksICdFWCcsIHR0bCk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cblxuICAgICAgLy8gSW4tbWVtb3J5IOKAlCBhIHdyaXRlIGNvdW50cyBhcyBhIHVzZSwgc28gZHJvcCBhbnkgZXhpc3RpbmcgZW50cnkgZmlyc3RcbiAgICAgIC8vIGFuZCByZS1pbnNlcnQgYmVsb3cgYXMgdGhlIG5ld2VzdCAoTWFwIHByZXNlcnZlcyBpbnNlcnRpb24gb3JkZXIpLiBUaGlzXG4gICAgICAvLyBrZWVwcyB0aGUgTWFwIG9yZGVyZWQgb2xkZXN0LVVTRUQg4oaSIG5ld2VzdC1VU0VEIHNvIHRoZSBldmljdGlvbiBiZWxvdyBpc1xuICAgICAgLy8gdHJ1ZSBMUlUsIGFuZCBhdm9pZHMgZXZpY3RpbmcgYSB2aWN0aW0gd2hlbiBtZXJlbHkgdXBkYXRpbmcgYSBrZXkuXG4gICAgICB0aGlzLm1lbW9yeS5kZWxldGUoZmspO1xuICAgICAgaWYgKHRoaXMubWVtb3J5LnNpemUgPj0gdGhpcy5tYXhFbnRyaWVzKSB7XG4gICAgICAgIGNvbnN0IGZpcnN0S2V5ID0gdGhpcy5tZW1vcnkua2V5cygpLm5leHQoKS52YWx1ZTtcbiAgICAgICAgaWYgKGZpcnN0S2V5KSB0aGlzLm1lbW9yeS5kZWxldGUoZmlyc3RLZXkpO1xuICAgICAgfVxuXG4gICAgICB0aGlzLm1lbW9yeS5zZXQoZmssIHtcbiAgICAgICAgdmFsdWUsXG4gICAgICAgIGV4cGlyZXNBdDogRGF0ZS5ub3coKSArIHR0bCAqIDEwMDAsXG4gICAgICB9KTtcbiAgICB9IGNhdGNoIHtcbiAgICAgIC8vIENhY2hlIHNldCBmYWlsdXJlIGlzIG5vbi1mYXRhbFxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBEZWxldGUgYSBjYWNoZWQgdmFsdWUuXG4gICAqL1xuICBhc3luYyBkZWwoa2V5OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCBmayA9IHRoaXMuZnVsbEtleShrZXkpO1xuXG4gICAgdHJ5IHtcbiAgICAgIGlmICh0aGlzLnJlZGlzKSB7XG4gICAgICAgIGF3YWl0IHRoaXMucmVkaXMuZGVsKGZrKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgdGhpcy5tZW1vcnkuZGVsZXRlKGZrKTtcbiAgICB9IGNhdGNoIHtcbiAgICAgIC8vIENhY2hlIGRlbGV0ZSBmYWlsdXJlIGlzIG5vbi1mYXRhbFxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBJbnZhbGlkYXRlIGFsbCBrZXlzIG1hdGNoaW5nIGEgcGF0dGVybiAoZS5nLiwgJ29yZzEyMzoqJykuXG4gICAqIFVzZXMgU0NBTiBmb3IgUmVkaXMgKG5vbi1ibG9ja2luZykgd2l0aCBLRVlTIGZhbGxiYWNrLCBvciByZWdleCBmb3IgaW4tbWVtb3J5LlxuICAgKi9cbiAgYXN5bmMgaW52YWxpZGF0ZVBhdHRlcm4ocGF0dGVybjogc3RyaW5nKTogUHJvbWlzZTxudW1iZXI+IHtcbiAgICBjb25zdCBmcCA9IHRoaXMuZnVsbEtleShwYXR0ZXJuKTtcblxuICAgIHRyeSB7XG4gICAgICBpZiAodGhpcy5yZWRpcykge1xuICAgICAgICBjb25zdCBrZXlzID0gdGhpcy5yZWRpcy5zY2FuU3RyZWFtXG4gICAgICAgICAgPyBhd2FpdCB0aGlzLnNjYW5LZXlzKGZwKVxuICAgICAgICAgIDogYXdhaXQgdGhpcy5yZWRpcy5rZXlzKGZwKTtcbiAgICAgICAgaWYgKGtleXMubGVuZ3RoID4gMCkge1xuICAgICAgICAgIGF3YWl0IHRoaXMucmVkaXMuZGVsKC4uLmtleXMpO1xuICAgICAgICAgIHRoaXMubWV0cmljcy5pbnZhbGlkYXRpb25zICs9IGtleXMubGVuZ3RoO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBrZXlzLmxlbmd0aDtcbiAgICAgIH1cblxuICAgICAgLy8gSW4tbWVtb3J5IOKAlCBtYXRjaCBnbG9iLXN0eWxlIHBhdHRlcm4uIEVzY2FwZSByZWdleCBtZXRhY2hhcmFjdGVycyBmaXJzdFxuICAgICAgLy8gc28gYSBsaXRlcmFsIGAuYC9gKGAvYFtgIGluIGEga2V5IHByZWZpeCAoZS5nLiBgb3JnOnYxLjI6KmApIGNhbid0XG4gICAgICAvLyBvdmVyLW1hdGNoIG9yIHRocm93IChhIHRocm93IGhlcmUgaXMgc3dhbGxvd2VkIOKGkiBub3RoaW5nIGludmFsaWRhdGVkIOKGklxuICAgICAgLy8gc3RhbGUgcmVhZHMpOyBvbmx5IGAqYCBpcyB0cmVhdGVkIGFzIGEgd2lsZGNhcmQuXG4gICAgICBjb25zdCByZWdleCA9IG5ldyBSZWdFeHAoJ14nICsgZnAucmVwbGFjZSgvWy4qKz9eJHt9KCl8W1xcXVxcXFxdL2csICdcXFxcJCYnKS5yZXBsYWNlKC9cXFxcXFwqL2csICcuKicpICsgJyQnKTtcbiAgICAgIGxldCBkZWxldGVkID0gMDtcbiAgICAgIGZvciAoY29uc3Qga2V5IG9mIHRoaXMubWVtb3J5LmtleXMoKSkge1xuICAgICAgICBpZiAocmVnZXgudGVzdChrZXkpKSB7XG4gICAgICAgICAgdGhpcy5tZW1vcnkuZGVsZXRlKGtleSk7XG4gICAgICAgICAgZGVsZXRlZCsrO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICB0aGlzLm1ldHJpY3MuaW52YWxpZGF0aW9ucyArPSBkZWxldGVkO1xuICAgICAgcmV0dXJuIGRlbGV0ZWQ7XG4gICAgfSBjYXRjaCB7XG4gICAgICByZXR1cm4gMDtcbiAgICB9XG4gIH1cblxuICAvKiogQ29sbGVjdCBrZXlzIHZpYSBSZWRpcyBTQ0FOIChub24tYmxvY2tpbmcgYWx0ZXJuYXRpdmUgdG8gS0VZUykuICovXG4gIHByaXZhdGUgc2NhbktleXMocGF0dGVybjogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmdbXT4ge1xuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICBjb25zdCBzdHJlYW0gPSB0aGlzLnJlZGlzIS5zY2FuU3RyZWFtISh7IG1hdGNoOiBwYXR0ZXJuLCBjb3VudDogMTAwIH0pO1xuICAgICAgY29uc3Qga2V5czogc3RyaW5nW10gPSBbXTtcbiAgICAgIHN0cmVhbS5vbignZGF0YScsIChiYXRjaDogc3RyaW5nW10pID0+IGtleXMucHVzaCguLi5iYXRjaCkpO1xuICAgICAgc3RyZWFtLm9uY2UoJ2VuZCcsICgpID0+IHJlc29sdmUoa2V5cykpO1xuICAgICAgc3RyZWFtLm9uY2UoJ2Vycm9yJywgcmVqZWN0KTtcbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgb3IgY29tcHV0ZTogcmV0dXJucyBjYWNoZWQgdmFsdWUgaWYgYXZhaWxhYmxlLCBvdGhlcndpc2UgY2FsbHMgdGhlXG4gICAqIGZhY3RvcnkgZnVuY3Rpb24sIGNhY2hlcyB0aGUgcmVzdWx0LCBhbmQgcmV0dXJucyBpdC5cbiAgICpcbiAgICogU2luZ2xlLWZsaWdodCAvIHN0YW1wZWRlIHByb3RlY3Rpb246IHdoZW4gTiBjYWxsZXJzIG1pc3MgY29uY3VycmVudGx5IGZvclxuICAgKiB0aGUgc2FtZSBrZXksIG9ubHkgT05FIGBmYWN0b3J5KClgIHJ1bnMg4oCUIHRoZSByZXN0IGF3YWl0IGl0cyBzaGFyZWQgcHJvbWlzZS5cbiAgICogVGhlIGluLWZsaWdodCBlbnRyeSBpcyBjbGVhcmVkIG9uY2UgaXQgc2V0dGxlcyAoc3VjY2VzcyBvciBmYWlsdXJlKSwgc28gYVxuICAgKiBmYWlsZWQgZmFjdG9yeSBkb2Vzbid0IHBvaXNvbiBsYXRlciBjYWxscy5cbiAgICpcbiAgICogQHBhcmFtIGtleSAtIENhY2hlIGtleVxuICAgKiBAcGFyYW0gZmFjdG9yeSAtIEFzeW5jIGZ1bmN0aW9uIHRvIGNvbXB1dGUgdGhlIHZhbHVlIG9uIGNhY2hlIG1pc3NcbiAgICogQHBhcmFtIHR0bFNlY29uZHMgLSBPcHRpb25hbCBUVEwgb3ZlcnJpZGVcbiAgICogQHJldHVybnMgVGhlIGNhY2hlZCBvciBjb21wdXRlZCB2YWx1ZVxuICAgKi9cbiAgYXN5bmMgZ2V0T3JTZXQ8VD4oa2V5OiBzdHJpbmcsIGZhY3Rvcnk6ICgpID0+IFByb21pc2U8VD4sIHR0bFNlY29uZHM/OiBudW1iZXIpOiBQcm9taXNlPFQ+IHtcbiAgICBjb25zdCBjYWNoZWQgPSBhd2FpdCB0aGlzLmdldDxUPihrZXkpO1xuICAgIGlmIChjYWNoZWQgIT09IG51bGwpIHJldHVybiBjYWNoZWQ7XG5cbiAgICBjb25zdCBmayA9IHRoaXMuZnVsbEtleShrZXkpO1xuICAgIC8vIENvYWxlc2NlIGNvbmN1cnJlbnQgY29sZCBjYWxsZXJzIG9udG8gYSBzaW5nbGUgZmFjdG9yeSgpIGludm9jYXRpb24uIFRoZVxuICAgIC8vIGluLWZsaWdodCBwcm9taXNlIHJlc29sdmVzIHRvIHRoZSBSQVcgdmFsdWU7IGVhY2ggYXdhaXRlciAodGhlIHByaW1hcnlcbiAgICAvLyBjYWxsZXIgYmVsb3cgQU5EIGV2ZXJ5IGNvYWxlc2NlZCBjYWxsZXIgaGVyZSkgY2xvbmVzIGl0IElOREVQRU5ERU5UTFkuXG4gICAgLy8gQ2xvbmluZyBpbnNpZGUgdGhlIGZsaWdodCBpbnN0ZWFkIHdvdWxkIGhhbmQgb25lIHNoYXJlZCBjbG9uZSB0byBhbGwgTlxuICAgIC8vIGNvYWxlc2NlZCBjYWxsZXJzLCBzbyBvbmUgY2FsbGVyIG11dGF0aW5nIGl0cyBcImluZGVwZW5kZW50XCIgcmVzdWx0IHdvdWxkXG4gICAgLy8gY29ycnVwdCB3aGF0IHRoZSBvdGhlcnMgcmVhZCDigJQgdGhlIGV4YWN0IGZvb3RndW4gZ2V0KCkncyBjbG9uZS1vbi1yZWFkXG4gICAgLy8gcHJldmVudHMuIChSZWRpcyBiYWNrZW5kIGlzIHVuYWZmZWN0ZWQ6IHNldCBzZXJpYWxpemVzLCBnZXQgSlNPTi5wYXJzZXMuKVxuICAgIGNvbnN0IGV4aXN0aW5nID0gdGhpcy5pbmZsaWdodC5nZXQoZmspIGFzIFByb21pc2U8VD4gfCB1bmRlZmluZWQ7XG4gICAgaWYgKGV4aXN0aW5nKSByZXR1cm4gY2xvbmVWYWx1ZShhd2FpdCBleGlzdGluZyk7XG5cbiAgICBjb25zdCBmbGlnaHQgPSAoYXN5bmMgKCkgPT4ge1xuICAgICAgY29uc3QgdmFsdWUgPSBhd2FpdCBmYWN0b3J5KCk7XG4gICAgICBhd2FpdCB0aGlzLnNldChrZXksIHZhbHVlLCB0dGxTZWNvbmRzKTtcbiAgICAgIHJldHVybiB2YWx1ZTtcbiAgICB9KSgpO1xuICAgIHRoaXMuaW5mbGlnaHQuc2V0KGZrLCBmbGlnaHQpO1xuICAgIHRyeSB7XG4gICAgICByZXR1cm4gY2xvbmVWYWx1ZShhd2FpdCBmbGlnaHQpO1xuICAgIH0gZmluYWxseSB7XG4gICAgICB0aGlzLmluZmxpZ2h0LmRlbGV0ZShmayk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIENsZWFyIGFsbCBlbnRyaWVzICh1c2VmdWwgZm9yIHRlc3RpbmcpLlxuICAgKi9cbiAgYXN5bmMgY2xlYXIoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgaWYgKHRoaXMucmVkaXMpIHtcbiAgICAgIGF3YWl0IHRoaXMuaW52YWxpZGF0ZVBhdHRlcm4oJyonKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5tZW1vcnkuY2xlYXIoKTtcbiAgICB9XG4gIH1cblxuICAvKiogQ3VycmVudCBpbi1tZW1vcnkgY2FjaGUgc2l6ZSAoZm9yIGRpYWdub3N0aWNzKS4gKi9cbiAgZ2V0IHNpemUoKTogbnVtYmVyIHtcbiAgICByZXR1cm4gdGhpcy5tZW1vcnkuc2l6ZTtcbiAgfVxufVxuXG4vKipcbiAqIENyZWF0ZSBhIGNhY2hlIHNlcnZpY2UgaW5zdGFuY2UuXG4gKlxuICogQHBhcmFtIHByZWZpeCAtIE5hbWVzcGFjZSBwcmVmaXggKGUuZy4sICdjb21wbGlhbmNlOicsICdwbHVnaW46JylcbiAqIEBwYXJhbSBkZWZhdWx0VHRsU2Vjb25kcyAtIERlZmF1bHQgVFRMIGluIHNlY29uZHMgKGRlZmF1bHQgMzAwID0gNSBtaW4pXG4gKiBAcGFyYW0gcmVkaXMgLSBPcHRpb25hbCBSZWRpcyBjbGllbnQgZm9yIGNyb3NzLXByb2Nlc3MgY2FjaGluZ1xuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlQ2FjaGVTZXJ2aWNlKFxuICBwcmVmaXg6IHN0cmluZyxcbiAgZGVmYXVsdFR0bFNlY29uZHMgPSAzMDAsXG4gIHJlZGlzPzogUmVkaXNDYWNoZUNsaWVudCxcbik6IENhY2hlU2VydmljZSB7XG4gIHJldHVybiBuZXcgQ2FjaGVTZXJ2aWNlKHsgcHJlZml4LCBkZWZhdWx0VHRsU2Vjb25kcywgcmVkaXMgfSk7XG59XG4iXX0=