gx
chenyc
2025-06-12 7b72ac13a83764a662159d4a49b7fffb90476ecb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
/**
 * @license
 * Copyright 2018 Google LLC. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * =============================================================================
 */
import * as tf from '../index';
import { BROWSER_ENVS, describeWithFlags, runWithLock } from '../jasmine_util';
import { arrayBufferToBase64String, base64StringToArrayBuffer } from './io_utils';
import { browserLocalStorage, BrowserLocalStorage, BrowserLocalStorageManager, localStorageRouter, purgeLocalStorageArtifacts } from './local_storage';
describeWithFlags('LocalStorage', BROWSER_ENVS, () => {
    // Test data.
    const modelTopology1 = {
        'class_name': 'Sequential',
        'keras_version': '2.1.4',
        'config': [{
                'class_name': 'Dense',
                'config': {
                    'kernel_initializer': {
                        'class_name': 'VarianceScaling',
                        'config': {
                            'distribution': 'uniform',
                            'scale': 1.0,
                            'seed': null,
                            'mode': 'fan_avg'
                        }
                    },
                    'name': 'dense',
                    'kernel_constraint': null,
                    'bias_regularizer': null,
                    'bias_constraint': null,
                    'dtype': 'float32',
                    'activation': 'linear',
                    'trainable': true,
                    'kernel_regularizer': null,
                    'bias_initializer': { 'class_name': 'Zeros', 'config': {} },
                    'units': 1,
                    'batch_input_shape': [null, 3],
                    'use_bias': true,
                    'activity_regularizer': null
                }
            }],
        'backend': 'tensorflow'
    };
    const weightSpecs1 = [
        {
            name: 'dense/kernel',
            shape: [3, 1],
            dtype: 'float32',
        },
        {
            name: 'dense/bias',
            shape: [1],
            dtype: 'float32',
        }
    ];
    const weightData1 = new ArrayBuffer(16);
    const trainingConfig1 = {
        loss: 'categorical_crossentropy',
        metrics: ['accuracy'],
        optimizer_config: { class_name: 'SGD', config: { learningRate: 0.1 } }
    };
    const artifacts1 = {
        modelTopology: modelTopology1,
        weightSpecs: weightSpecs1,
        weightData: weightData1,
        format: 'layers-model',
        generatedBy: 'TensorFlow.js v0.0.0',
        convertedBy: '1.13.1',
        signature: null,
        userDefinedMetadata: {},
        modelInitializer: {},
        initializerSignature: null,
        trainingConfig: trainingConfig1,
    };
    const artifactsV0 = {
        modelTopology: modelTopology1,
        weightSpecs: weightSpecs1,
        weightData: weightData1
    };
    function findOverflowingByteSize() {
        const LS = window.localStorage;
        const probeKey = `tfjs_test_probe_values_${new Date().getTime()}_${Math.random()}`;
        const minKilobytes = 200;
        const stepKilobytes = 200;
        const maxKilobytes = 40000;
        for (let kilobytes = minKilobytes; kilobytes < maxKilobytes; kilobytes += stepKilobytes) {
            const bytes = kilobytes * 1024;
            const data = new ArrayBuffer(bytes);
            try {
                const encoded = arrayBufferToBase64String(data);
                LS.setItem(probeKey, encoded);
            }
            catch (err) {
                return bytes;
            }
            LS.removeItem(probeKey);
        }
        throw new Error(`Unable to determined overflowing byte size up to ${maxKilobytes} kB.`);
    }
    beforeEach(() => {
        purgeLocalStorageArtifacts();
    });
    afterEach(() => {
        purgeLocalStorageArtifacts();
    });
    it('Save artifacts succeeds', runWithLock(async () => {
        const testStartDate = new Date();
        const handler = tf.io.getSaveHandlers('localstorage://foo/FooModel')[0];
        const saveResult = await handler.save(artifacts1);
        expect(saveResult.modelArtifactsInfo.dateSaved.getTime())
            .toBeGreaterThanOrEqual(testStartDate.getTime());
        // Note: The following two assertions work only because there is no
        //   non-ASCII characters in `modelTopology1` and `weightSpecs1`.
        expect(saveResult.modelArtifactsInfo.modelTopologyBytes)
            .toEqual(JSON.stringify(modelTopology1).length);
        expect(saveResult.modelArtifactsInfo.weightSpecsBytes)
            .toEqual(JSON.stringify(weightSpecs1).length);
        expect(saveResult.modelArtifactsInfo.weightDataBytes).toEqual(16);
        // Check the content of the saved items in local storage.
        const LS = window.localStorage;
        const info = JSON.parse(LS.getItem('tensorflowjs_models/foo/FooModel/info'));
        expect(Date.parse(info.dateSaved))
            .toEqual(saveResult.modelArtifactsInfo.dateSaved.getTime());
        expect(info.modelTopologyBytes)
            .toEqual(saveResult.modelArtifactsInfo.modelTopologyBytes);
        expect(info.weightSpecsBytes)
            .toEqual(saveResult.modelArtifactsInfo.weightSpecsBytes);
        expect(info.weightDataBytes)
            .toEqual(saveResult.modelArtifactsInfo.weightDataBytes);
        const topologyString = LS.getItem('tensorflowjs_models/foo/FooModel/model_topology');
        expect(JSON.stringify(modelTopology1)).toEqual(topologyString);
        const weightSpecsString = LS.getItem('tensorflowjs_models/foo/FooModel/weight_specs');
        expect(JSON.stringify(weightSpecs1)).toEqual(weightSpecsString);
        const weightDataBase64String = LS.getItem('tensorflowjs_models/foo/FooModel/weight_data');
        expect(base64StringToArrayBuffer(weightDataBase64String))
            .toEqual(weightData1);
    }));
    it('Save-load round trip succeeds', runWithLock(async () => {
        const handler1 = tf.io.getSaveHandlers('localstorage://FooModel')[0];
        await handler1.save(artifacts1);
        const handler2 = tf.io.getLoadHandlers('localstorage://FooModel')[0];
        const loaded = await handler2.load();
        expect(loaded.modelTopology).toEqual(modelTopology1);
        expect(loaded.weightSpecs).toEqual(weightSpecs1);
        expect(loaded.weightData).toEqual(weightData1);
        expect(loaded.format).toEqual('layers-model');
        expect(loaded.generatedBy).toEqual('TensorFlow.js v0.0.0');
        expect(loaded.convertedBy).toEqual('1.13.1');
        expect(loaded.userDefinedMetadata).toEqual({});
        expect(loaded.modelInitializer).toEqual({});
        expect(loaded.initializerSignature).toBeUndefined();
        expect(loaded.trainingConfig).toEqual(trainingConfig1);
    }));
    it('Save-load round trip succeeds: v0 format', runWithLock(async () => {
        const handler1 = tf.io.getSaveHandlers('localstorage://FooModel')[0];
        await handler1.save(artifactsV0);
        const handler2 = tf.io.getLoadHandlers('localstorage://FooModel')[0];
        const loaded = await handler2.load();
        expect(loaded.modelTopology).toEqual(modelTopology1);
        expect(loaded.weightSpecs).toEqual(weightSpecs1);
        expect(loaded.weightData).toEqual(weightData1);
        expect(loaded.format).toBeUndefined();
        expect(loaded.generatedBy).toBeUndefined();
        expect(loaded.convertedBy).toBeUndefined();
        expect(loaded.userDefinedMetadata).toBeUndefined();
        expect(loaded.trainingConfig).toBeUndefined();
        expect(loaded.modelInitializer).toBeUndefined();
        expect(loaded.initializerSignature).toBeUndefined();
        expect(loaded.trainingConfig).toBeUndefined();
    }));
    it('Loading nonexistent model fails.', runWithLock(async () => {
        const handler = tf.io.getSaveHandlers('localstorage://NonexistentModel')[0];
        try {
            await handler.load();
        }
        catch (err) {
            expect(err.message)
                .toEqual('In local storage, there is no model with name ' +
                '\'NonexistentModel\'');
            return; // Success
        }
        fail('Loading nonexistent model succeeded unexpectedly.');
    }));
    it('Loading model with missing topology fails.', runWithLock(async () => {
        const handler1 = tf.io.getSaveHandlers('localstorage://FooModel')[0];
        await handler1.save(artifacts1);
        // Manually remove the topology item from local storage.
        window.localStorage.removeItem('tensorflowjs_models/FooModel/model_topology');
        const handler2 = tf.io.getLoadHandlers('localstorage://FooModel')[0];
        try {
            await handler2.load();
        }
        catch (err) {
            expect(err.message)
                .toEqual('In local storage, the topology of model ' +
                '\'FooModel\' is missing.');
            return; // Success
        }
        fail('Loading of model with missing topology succeeded unexpectedly.');
    }));
    it('Loading model with missing weight specs fails.', runWithLock(async () => {
        const handler1 = tf.io.getSaveHandlers('localstorage://FooModel')[0];
        await handler1.save(artifacts1);
        // Manually remove the weight specs item from local storage.
        window.localStorage.removeItem('tensorflowjs_models/FooModel/weight_specs');
        const handler2 = tf.io.getLoadHandlers('localstorage://FooModel')[0];
        try {
            await handler2.load();
        }
        catch (err) {
            expect(err.message)
                .toEqual('In local storage, the weight specs of model ' +
                '\'FooModel\' are missing.');
            return; // Success
        }
        fail('Loading of model with missing weight specs ' +
            'succeeded unexpectedly.');
    }));
    it('Loading model with missing weight data fails.', runWithLock(async () => {
        const handler1 = tf.io.getSaveHandlers('localstorage://FooModel')[0];
        await handler1.save(artifacts1);
        // Manually remove the weight data item from local storage.
        window.localStorage.removeItem('tensorflowjs_models/FooModel/weight_data');
        const handler2 = tf.io.getLoadHandlers('localstorage://FooModel')[0];
        try {
            await handler2.load();
            fail('Loading of model with missing weight data ' +
                'succeeded unexpectedly.');
        }
        catch (err) {
            expect(err.message)
                .toEqual('In local storage, the binary weight values of model ' +
                '\'FooModel\' are missing.');
        }
    }));
    it('Data size too large leads to error thrown', runWithLock(async () => {
        const overflowByteSize = findOverflowingByteSize();
        const overflowArtifacts = {
            modelTopology: modelTopology1,
            weightSpecs: weightSpecs1,
            weightData: new ArrayBuffer(overflowByteSize),
        };
        const handler1 = tf.io.getSaveHandlers('localstorage://FooModel')[0];
        try {
            await handler1.save(overflowArtifacts);
            fail('Saving of model of overflowing-size weight data succeeded ' +
                'unexpectedly.');
        }
        catch (err) {
            expect(err.message
                .indexOf('Failed to save model \'FooModel\' to local storage'))
                .toEqual(0);
        }
    }));
    it('Null, undefined or empty modelPath throws Error', () => {
        expect(() => browserLocalStorage(null))
            .toThrowError(/local storage, modelPath must not be null, undefined or empty/);
        expect(() => browserLocalStorage(undefined))
            .toThrowError(/local storage, modelPath must not be null, undefined or empty/);
        expect(() => browserLocalStorage(''))
            .toThrowError(/local storage, modelPath must not be null, undefined or empty./);
    });
    it('router', () => {
        expect(localStorageRouter('localstorage://bar') instanceof BrowserLocalStorage)
            .toEqual(true);
        expect(localStorageRouter('indexeddb://bar')).toBeNull();
        expect(localStorageRouter('qux')).toBeNull();
    });
    it('Manager: List models: 0 result', runWithLock(async () => {
        // Before any model is saved, listModels should return empty result.
        const out = await new BrowserLocalStorageManager().listModels();
        expect(out).toEqual({});
    }));
    it('Manager: List models: 1 result', runWithLock(async () => {
        const handler = tf.io.getSaveHandlers('localstorage://baz/QuxModel')[0];
        const saveResult = await handler.save(artifacts1);
        // After successful saving, there should be one model.
        const out = await new BrowserLocalStorageManager().listModels();
        if (Object.keys(out).length !== 1) {
            console.log(JSON.stringify(out, null, 2));
        }
        expect(Object.keys(out).length).toEqual(1);
        expect(out['baz/QuxModel'].modelTopologyType)
            .toEqual(saveResult.modelArtifactsInfo.modelTopologyType);
        expect(out['baz/QuxModel'].modelTopologyBytes)
            .toEqual(saveResult.modelArtifactsInfo.modelTopologyBytes);
        expect(out['baz/QuxModel'].weightSpecsBytes)
            .toEqual(saveResult.modelArtifactsInfo.weightSpecsBytes);
        expect(out['baz/QuxModel'].weightDataBytes)
            .toEqual(saveResult.modelArtifactsInfo.weightDataBytes);
    }));
    it('Manager: List models: 2 results', runWithLock(async () => {
        // First, save a model.
        const handler1 = tf.io.getSaveHandlers('localstorage://QuxModel')[0];
        const saveResult1 = await handler1.save(artifacts1);
        // Then, save the model under another path.
        const handler2 = tf.io.getSaveHandlers('localstorage://repeat/QuxModel')[0];
        const saveResult2 = await handler2.save(artifacts1);
        // After successful saving, there should be two models.
        const out = await new BrowserLocalStorageManager().listModels();
        if (Object.keys(out).length !== 2) {
            console.log(JSON.stringify(out, null, 2));
        }
        expect(Object.keys(out).length).toEqual(2);
        expect(out['QuxModel'].modelTopologyType)
            .toEqual(saveResult1.modelArtifactsInfo.modelTopologyType);
        expect(out['QuxModel'].modelTopologyBytes)
            .toEqual(saveResult1.modelArtifactsInfo.modelTopologyBytes);
        expect(out['QuxModel'].weightSpecsBytes)
            .toEqual(saveResult1.modelArtifactsInfo.weightSpecsBytes);
        expect(out['QuxModel'].weightDataBytes)
            .toEqual(saveResult1.modelArtifactsInfo.weightDataBytes);
        expect(out['repeat/QuxModel'].modelTopologyType)
            .toEqual(saveResult2.modelArtifactsInfo.modelTopologyType);
        expect(out['repeat/QuxModel'].modelTopologyBytes)
            .toEqual(saveResult2.modelArtifactsInfo.modelTopologyBytes);
        expect(out['repeat/QuxModel'].weightSpecsBytes)
            .toEqual(saveResult2.modelArtifactsInfo.weightSpecsBytes);
        expect(out['repeat/QuxModel'].weightDataBytes)
            .toEqual(saveResult2.modelArtifactsInfo.weightDataBytes);
    }));
    it('Manager: Successful deleteModel', runWithLock(async () => {
        // First, save a model.
        const handler1 = tf.io.getSaveHandlers('localstorage://QuxModel')[0];
        await handler1.save(artifacts1);
        // Then, save the model under another path.
        const handler2 = tf.io.getSaveHandlers('localstorage://repeat/QuxModel')[0];
        await handler2.save(artifacts1);
        // After successful saving, delete the first save, and then
        // `listModel` should give only one result.
        const manager = new BrowserLocalStorageManager();
        await manager.removeModel('QuxModel');
        const out = await manager.listModels();
        expect(Object.keys(out)).toEqual(['repeat/QuxModel']);
    }));
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9jYWxfc3RvcmFnZV90ZXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vdGZqcy1jb3JlL3NyYy9pby9sb2NhbF9zdG9yYWdlX3Rlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7OztHQWVHO0FBRUgsT0FBTyxLQUFLLEVBQUUsTUFBTSxVQUFVLENBQUM7QUFDL0IsT0FBTyxFQUFDLFlBQVksRUFBRSxpQkFBaUIsRUFBRSxXQUFXLEVBQUMsTUFBTSxpQkFBaUIsQ0FBQztBQUM3RSxPQUFPLEVBQUMseUJBQXlCLEVBQUUseUJBQXlCLEVBQUMsTUFBTSxZQUFZLENBQUM7QUFDaEYsT0FBTyxFQUFDLG1CQUFtQixFQUFFLG1CQUFtQixFQUFFLDBCQUEwQixFQUFFLGtCQUFrQixFQUFFLDBCQUEwQixFQUFDLE1BQU0saUJBQWlCLENBQUM7QUFFckosaUJBQWlCLENBQUMsY0FBYyxFQUFFLFlBQVksRUFBRSxHQUFHLEVBQUU7SUFDbkQsYUFBYTtJQUNiLE1BQU0sY0FBYyxHQUFPO1FBQ3pCLFlBQVksRUFBRSxZQUFZO1FBQzFCLGVBQWUsRUFBRSxPQUFPO1FBQ3hCLFFBQVEsRUFBRSxDQUFDO2dCQUNULFlBQVksRUFBRSxPQUFPO2dCQUNyQixRQUFRLEVBQUU7b0JBQ1Isb0JBQW9CLEVBQUU7d0JBQ3BCLFlBQVksRUFBRSxpQkFBaUI7d0JBQy9CLFFBQVEsRUFBRTs0QkFDUixjQUFjLEVBQUUsU0FBUzs0QkFDekIsT0FBTyxFQUFFLEdBQUc7NEJBQ1osTUFBTSxFQUFFLElBQUk7NEJBQ1osTUFBTSxFQUFFLFNBQVM7eUJBQ2xCO3FCQUNGO29CQUNELE1BQU0sRUFBRSxPQUFPO29CQUNmLG1CQUFtQixFQUFFLElBQUk7b0JBQ3pCLGtCQUFrQixFQUFFLElBQUk7b0JBQ3hCLGlCQUFpQixFQUFFLElBQUk7b0JBQ3ZCLE9BQU8sRUFBRSxTQUFTO29CQUNsQixZQUFZLEVBQUUsUUFBUTtv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UUFDckUsTUFBTSxRQUFRLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBRWhDLDJDQUEyQztRQUMzQyxNQUFNLFFBQVEsR0FDVixFQUFFLENBQUMsRUFBRSxDQUFDLGVBQWUsQ0FBQyxnQ0FBZ0MsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQy9ELE1BQU0sUUFBUSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUVoQywyREFBMkQ7UUFDM0QsMkNBQTJDO1FBQzNDLE1BQU0sT0FBTyxHQUFHLElBQUksMEJBQTBCLEVBQUUsQ0FBQztRQUNqRCxNQUFNLE9BQU8sQ0FBQyxXQUFXLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDdEMsTUFBTSxHQUFHLEdBQUcsTUFBTSxPQUFPLENBQUMsVUFBVSxFQUFFLENBQUM7UUFDdkMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDLENBQUM7SUFDeEQsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNULENBQUMsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBAbGljZW5zZVxuICogQ29weXJpZ2h0IDIwMTggR29vZ2xlIExMQy4gQWxsIFJpZ2h0cyBSZXNlcnZlZC5cbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAnTGljZW5zZScpO1xuICogeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLlxuICogWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0XG4gKlxuICogaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wXG4gKlxuICogVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZVxuICogZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gJ0FTIElTJyBCQVNJUyxcbiAqIFdJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLlxuICogU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZFxuICogbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuXG4gKiA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuICovXG5cbmltcG9ydCAqIGFzIHRmIGZyb20gJy4uL2luZGV4JztcbmltcG9ydCB7QlJPV1NFUl9FTlZTLCBkZXNjcmliZVdpdGhGbGFncywgcnVuV2l0aExvY2t9IGZyb20gJy4uL2phc21pbmVfdXRpbCc7XG5pbXBvcnQge2FycmF5QnVmZmVyVG9CYXNlNjRTdHJpbmcsIGJhc2U2NFN0cmluZ1RvQXJyYXlCdWZmZXJ9IGZyb20gJy4vaW9fdXRpbHMnO1xuaW1wb3J0IHticm93c2VyTG9jYWxTdG9yYWdlLCBCcm93c2VyTG9jYWxTdG9yYWdlLCBCcm93c2VyTG9jYWxTdG9yYWdlTWFuYWdlciwgbG9jYWxTdG9yYWdlUm91dGVyLCBwdXJnZUxvY2FsU3RvcmFnZUFydGlmYWN0c30gZnJvbSAnLi9sb2NhbF9zdG9yYWdlJztcblxuZGVzY3JpYmVXaXRoRmxhZ3MoJ0xvY2FsU3RvcmFnZScsIEJST1dTRVJfRU5WUywgKCkgPT4ge1xuICAvLyBUZXN0IGRhdGEuXG4gIGNvbnN0IG1vZGVsVG9wb2xvZ3kxOiB7fSA9IHtcbiAgICAnY2xhc3NfbmFtZSc6ICdTZXF1ZW50aWFsJyxcbiAgICAna2VyYXNfdmVyc2lvbic6ICcyLjEuNCcsXG4gICAgJ2NvbmZpZyc6IFt7XG4gICAgICAnY2xhc3NfbmFtZSc6ICdEZW5zZScsXG4gICAgICAnY29uZmlnJzoge1xuICAgICAgICAna2VybmVsX2luaXRpYWxpemVyJzoge1xuICAgICAgICAgICdjbGFzc19uYW1lJzogJ1ZhcmlhbmNlU2NhbGluZycsXG4gICAgICAgICAgJ2NvbmZpZyc6IHtcbiAgICAgICAgICAgICdkaXN0cmlidXRpb24nOiAndW5pZm9ybScsXG4gICAgICAgICAgICAnc2NhbGUnOiAxLjAsXG4gICAgICAgICAgICAnc2VlZCc6IG51bGwsXG4gICAgICAgICAgICAnbW9kZSc6ICdmYW5fYXZnJ1xuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgJ25hbWUnOiAnZGVuc2UnLFxuICAgICAgICAna2VybmVsX2NvbnN0cmFpbnQnOiBudWxsLFxuICAgICAgICAnYmlhc19yZWd1bGFyaXplcic6IG51bGwsXG4gICAgICAgICdiaWFzX2NvbnN0cmFpbnQnOiBudWxsLFxuICAgICAgICAnZHR5cGUnOiAnZmxvYXQzMicsXG4gICAgICAgICdhY3RpdmF0aW9uJzogJ2xpbmVhcicsXG4gICAgICAgICd0cmFpbmFibGUnOiB0cnVlLFxuICAgICAgICAna2VybmVsX3JlZ3VsYXJpemVyJzogbnVsbCxcbiAgICAgICAgJ2JpYXNfaW5pdGlhbGl6ZXInOiB7J2NsYXNzX25hbWUnOiAnWmVyb3MnLCAnY29uZmlnJzoge319LFxuICAgICAgICAndW5pdHMnOiAxLFxuICAgICAgICAnYmF0Y2hfaW5wdXRfc2hhcGUnOiBbbnVsbCwgM10sXG4gICAgICAgICd1c2VfYmlhcyc6IHRydWUsXG4gICAgICAgICdhY3Rpdml0eV9yZWd1bGFyaXplcic6IG51bGxcbiAgICAgIH1cbiAgICB9XSxcbiAgICAnYmFja2VuZCc6ICd0ZW5zb3JmbG93J1xuICB9O1xuICBjb25zdCB3ZWlnaHRTcGVjczE6IHRmLmlvLldlaWdodHNNYW5pZmVzdEVudHJ5W10gPSBbXG4gICAge1xuICAgICAgbmFtZTogJ2RlbnNlL2tlcm5lbCcsXG4gICAgICBzaGFwZTogWzMsIDFdLFxuICAgICAgZHR5cGU6ICdmbG9hdDMyJyxcbiAgICB9LFxuICAgIHtcbiAgICAgIG5hbWU6ICdkZW5zZS9iaWFzJyxcbiAgICAgIHNoYXBlOiBbMV0sXG4gICAgICBkdHlwZTogJ2Zsb2F0MzInLFxuICAgIH1cbiAgXTtcbiAgY29uc3Qgd2VpZ2h0RGF0YTEgPSBuZXcgQXJyYXlCdWZmZXIoMTYpO1xuICBjb25zdCB0cmFpbmluZ0NvbmZpZzE6IHRmLmlvLlRyYWluaW5nQ29uZmlnID0ge1xuICAgIGxvc3M6ICdjYXRlZ29yaWNhbF9jcm9zc2VudHJvcHknLFxuICAgIG1ldHJpY3M6IFsnYWNjdXJhY3knXSxcbiAgICBvcHRpbWl6ZXJfY29uZmlnOiB7Y2xhc3NfbmFtZTogJ1NHRCcsIGNvbmZpZzoge2xlYXJuaW5nUmF0ZTogMC4xfX1cbiAgfTtcblxuICBjb25zdCBhcnRpZmFjdHMxOiB0Zi5pby5Nb2RlbEFydGlmYWN0cyA9IHtcbiAgICBtb2RlbFRvcG9sb2d5OiBtb2RlbFRvcG9sb2d5MSxcbiAgICB3ZWlnaHRTcGVjczogd2VpZ2h0U3BlY3MxLFxuICAgIHdlaWdodERhdGE6IHdlaWdodERhdGExLFxuICAgIGZvcm1hdDogJ2xheWVycy1tb2RlbCcsXG4gICAgZ2VuZXJhdGVkQnk6ICdUZW5zb3JGbG93LmpzIHYwLjAuMCcsXG4gICAgY29udmVydGVkQnk6ICcxLjEzLjEnLFxuICAgIHNpZ25hdHVyZTogbnVsbCxcbiAgICB1c2VyRGVmaW5lZE1ldGFkYXRhOiB7fSxcbiAgICBtb2RlbEluaXRpYWxpemVyOiB7fSxcbiAgICBpbml0aWFsaXplclNpZ25hdHVyZTogbnVsbCxcbiAgICB0cmFpbmluZ0NvbmZpZzogdHJhaW5pbmdDb25maWcxLFxuICB9O1xuXG4gIGNvbnN0IGFydGlmYWN0c1YwOiB0Zi5pby5Nb2RlbEFydGlmYWN0cyA9IHtcbiAgICBtb2RlbFRvcG9sb2d5OiBtb2RlbFRvcG9sb2d5MSxcbiAgICB3ZWlnaHRTcGVjczogd2VpZ2h0U3BlY3MxLFxuICAgIHdlaWdodERhdGE6IHdlaWdodERhdGExXG4gIH07XG5cbiAgZnVuY3Rpb24gZmluZE92ZXJmbG93aW5nQnl0ZVNpemUoKTogbnVtYmVyIHtcbiAgICBjb25zdCBMUyA9IHdpbmRvdy5sb2NhbFN0b3JhZ2U7XG4gICAgY29uc3QgcHJvYmVLZXkgPVxuICAgICAgICBgdGZqc190ZXN0X3Byb2JlX3ZhbHVlc18ke25ldyBEYXRlKCkuZ2V0VGltZSgpfV8ke01hdGgucmFuZG9tKCl9YDtcbiAgICBjb25zdCBtaW5LaWxvYnl0ZXMgPSAyMDA7XG4gICAgY29uc3Qgc3RlcEtpbG9ieXRlcyA9IDIwMDtcbiAgICBjb25zdCBtYXhLaWxvYnl0ZXMgPSA0MDAwMDtcbiAgICBmb3IgKGxldCBraWxvYnl0ZXMgPSBtaW5LaWxvYnl0ZXM7IGtpbG9ieXRlcyA8IG1heEtpbG9ieXRlcztcbiAgICAgICAgIGtpbG9ieXRlcyArPSBzdGVwS2lsb2J5dGVzKSB7XG4gICAgICBjb25zdCBieXRlcyA9IGtpbG9ieXRlcyAqIDEwMjQ7XG4gICAgICBjb25zdCBkYXRhID0gbmV3IEFycmF5QnVmZmVyKGJ5dGVzKTtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IGVuY29kZWQgPSBhcnJheUJ1ZmZlclRvQmFzZTY0U3RyaW5nKGRhdGEpO1xuICAgICAgICBMUy5zZXRJdGVtKHByb2JlS2V5LCBlbmNvZGVkKTtcbiAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICByZXR1cm4gYnl0ZXM7XG4gICAgICB9XG4gICAgICBMUy5yZW1vdmVJdGVtKHByb2JlS2V5KTtcbiAgICB9XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICBgVW5hYmxlIHRvIGRldGVybWluZWQgb3ZlcmZsb3dpbmcgYnl0ZSBzaXplIHVwIHRvICR7bWF4S2lsb2J5dGVzfSBrQi5gKTtcbiAgfVxuXG4gIGJlZm9yZUVhY2goKCkgPT4ge1xuICAgIHB1cmdlTG9jYWxTdG9yYWdlQXJ0aWZhY3RzKCk7XG4gIH0pO1xuXG4gIGFmdGVyRWFjaCgoKSA9PiB7XG4gICAgcHVyZ2VMb2NhbFN0b3JhZ2VBcnRpZmFjdHMoKTtcbiAgfSk7XG5cbiAgaXQoJ1NhdmUgYXJ0aWZhY3RzIHN1Y2NlZWRzJywgcnVuV2l0aExvY2soYXN5bmMgKCkgPT4ge1xuICAgICAgIGNvbnN0IHRlc3RTdGFydERhdGUgPSBuZXcgRGF0ZSgpO1xuICAgICAgIGNvbnN0IGhhbmRsZXIgPSB0Zi5pby5nZXRTYXZlSGFuZGxlcnMoJ2xvY2Fsc3RvcmFnZTovL2Zvby9Gb29Nb2RlbCcpWzBdO1xuICAgICAgIGNvbnN0IHNhdmVSZXN1bHQgPSBhd2FpdCBoYW5kbGVyLnNhdmUoYXJ0aWZhY3RzMSk7XG5cbiAgICAgICBleHBlY3Qoc2F2ZVJlc3VsdC5tb2RlbEFydGlmYWN0c0luZm8uZGF0ZVNhdmVkLmdldFRpbWUoKSlcbiAgICAgICAgICAgLnRvQmVHcmVhdGVyVGhhbk9yRXF1YWwodGVzdFN0YXJ0RGF0ZS5nZXRUaW1lKCkpO1xuICAgICAgIC8vIE5vdGU6IFRoZSBmb2xsb3dpbmcgdHdvIGFzc2VydGlvbnMgd29yayBvbmx5IGJlY2F1c2UgdGhlcmUgaXMgbm9cbiAgICAgICAvLyAgIG5vbi1BU0NJSSBjaGFyYWN0ZXJzIGluIGBtb2RlbFRvcG9sb2d5MWAgYW5kIGB3ZWlnaHRTcGVjczFgLlxuICAgICAgIGV4cGVjdChzYXZlUmVzdWx0Lm1vZGVsQXJ0aWZhY3RzSW5mby5tb2RlbFRvcG9sb2d5Qnl0ZXMpXG4gICAgICAgICAgIC50b0VxdWFsKEpTT04uc3RyaW5naWZ5KG1vZGVsVG9wb2xvZ3kxKS5sZW5ndGgpO1xuICAgICAgIGV4cGVjdChzYXZlUmVzdWx0Lm1vZGVsQXJ0aWZhY3RzSW5mby53ZWlnaHRTcGVjc0J5dGVzKVxuICAgICAgICAgICAudG9FcXVhbChKU09OLnN0cmluZ2lmeSh3ZWlnaHRTcGVjczEpLmxlbmd0aCk7XG4gICAgICAgZXhwZWN0KHNhdmVSZXN1bHQubW9kZWxBcnRpZmFjdHNJbmZvLndlaWdodERhdGFCeXRlcykudG9FcXVhbCgxNik7XG5cbiAgICAgICAvLyBDaGVjayB0aGUgY29udGVudCBvZiB0aGUgc2F2ZWQgaXRlbXMgaW4gbG9jYWwgc3RvcmFnZS5cbiAgICAgICBjb25zdCBMUyA9IHdpbmRvdy5sb2NhbFN0b3JhZ2U7XG4gICAgICAgY29uc3QgaW5mbyA9XG4gICAgICAgICAgIEpTT04ucGFyc2UoTFMuZ2V0SXRlbSgndGVuc29yZmxvd2pzX21vZGVscy9mb28vRm9vTW9kZWwvaW5mbycpKTtcbiAgICAgICBleHBlY3QoRGF0ZS5wYXJzZShpbmZvLmRhdGVTYXZlZCkpXG4gICAgICAgICAgIC50b0VxdWFsKHNhdmVSZXN1bHQubW9kZWxBcnRpZmFjdHNJbmZvLmRhdGVTYXZlZC5nZXRUaW1lKCkpO1xuICAgICAgIGV4cGVjdChpbmZvLm1vZGVsVG9wb2xvZ3lCeXRlcylcbiAgICAgICAgICAgLnRvRXF1YWwoc2F2ZVJlc3VsdC5tb2RlbEFydGlmYWN0c0luZm8ubW9kZWxUb3BvbG9neUJ5dGVzKTtcbiAgICAgICBleHBlY3QoaW5mby53ZWlnaHRTcGVjc0J5dGVzKVxuICAgICAgICAgICAudG9FcXVhbChzYXZlUmVzdWx0Lm1vZGVsQXJ0aWZhY3RzSW5mby53ZWlnaHRTcGVjc0J5dGVzKTtcbiAgICAgICBleHBlY3QoaW5mby53ZWlnaHREYXRhQnl0ZXMpXG4gICAgICAgICAgIC50b0VxdWFsKHNhdmVSZXN1bHQubW9kZWxBcnRpZmFjdHNJbmZvLndlaWdodERhdGFCeXRlcyk7XG5cbiAgICAgICBjb25zdCB0b3BvbG9neVN0cmluZyA9XG4gICAgICAgICAgIExTLmdldEl0ZW0oJ3RlbnNvcmZsb3dqc19tb2RlbHMvZm9vL0Zvb01vZGVsL21vZGVsX3RvcG9sb2d5Jyk7XG4gICAgICAgZXhwZWN0KEpTT04uc3RyaW5naWZ5KG1vZGVsVG9wb2xvZ3kxKSkudG9FcXVhbCh0b3BvbG9neVN0cmluZyk7XG5cbiAgICAgICBjb25zdCB3ZWlnaHRTcGVjc1N0cmluZyA9XG4gICAgICAgICAgIExTLmdldEl0ZW0oJ3RlbnNvcmZsb3dqc19tb2RlbHMvZm9vL0Zvb01vZGVsL3dlaWdodF9zcGVjcycpO1xuICAgICAgIGV4cGVjdChKU09OLnN0cmluZ2lmeSh3ZWlnaHRTcGVjczEpKS50b0VxdWFsKHdlaWdodFNwZWNzU3RyaW5nKTtcblxuICAgICAgIGNvbnN0IHdlaWdodERhdGFCYXNlNjRTdHJpbmcgPVxuICAgICAgICAgICBMUy5nZXRJdGVtKCd0ZW5zb3JmbG93anNfbW9kZWxzL2Zvby9Gb29Nb2RlbC93ZWlnaHRfZGF0YScpO1xuICAgICAgIGV4cGVjdChiYXNlNjRTdHJpbmdUb0FycmF5QnVmZmVyKHdlaWdodERhdGFCYXNlNjRTdHJpbmcpKVxuICAgICAgICAgICAudG9FcXVhbCh3ZWlnaHREYXRhMSk7XG4gICAgIH0pKTtcblxuICBpdCgnU2F2ZS1sb2FkIHJvdW5kIHRyaXAgc3VjY2VlZHMnLCBydW5XaXRoTG9jayhhc3luYyAoKSA9PiB7XG4gICAgICAgY29uc3QgaGFuZGxlcjEgPSB0Zi5pby5nZXRTYXZlSGFuZGxlcnMoJ2xvY2Fsc3RvcmFnZTovL0Zvb01vZGVsJylbMF07XG5cbiAgICAgICBhd2FpdCBoYW5kbGVyMS5zYXZlKGFydGlmYWN0czEpO1xuICAgICAgIGNvbnN0IGhhbmRsZXIyID0gdGYuaW8uZ2V0TG9hZEhhbmRsZXJzKCdsb2NhbHN0b3JhZ2U6Ly9Gb29Nb2RlbCcpWzBdO1xuICAgICAgIGNvbnN0IGxvYWRlZCA9IGF3YWl0IGhhbmRsZXIyLmxvYWQoKTtcbiAgICAgICBleHBlY3QobG9hZGVkLm1vZGVsVG9wb2xvZ3kpLnRvRXF1YWwobW9kZWxUb3BvbG9neTEpO1xuICAgICAgIGV4cGVjdChsb2FkZWQud2VpZ2h0U3BlY3MpLnRvRXF1YWwod2VpZ2h0U3BlY3MxKTtcbiAgICAgICBleHBlY3QobG9hZGVkLndlaWdodERhdGEpLnRvRXF1YWwod2VpZ2h0RGF0YTEpO1xuICAgICAgIGV4cGVjdChsb2FkZWQuZm9ybWF0KS50b0VxdWFsKCdsYXllcnMtbW9kZWwnKTtcbiAgICAgICBleHBlY3QobG9hZGVkLmdlbmVyYXRlZEJ5KS50b0VxdWFsKCdUZW5zb3JGbG93LmpzIHYwLjAuMCcpO1xuICAgICAgIGV4cGVjdChsb2FkZWQuY29udmVydGVkQnkpLnRvRXF1YWwoJzEuMTMuMScpO1xuICAgICAgIGV4cGVjdChsb2FkZWQudXNlckRlZmluZWRNZXRhZGF0YSkudG9FcXVhbCh7fSk7XG4gICAgICAgZXhwZWN0KGxvYWRlZC5tb2RlbEluaXRpYWxpemVyKS50b0VxdWFsKHt9KTtcbiAgICAgICBleHBlY3QobG9hZGVkLmluaXRpYWxpemVyU2lnbmF0dXJlKS50b0JlVW5kZWZpbmVkKCk7XG4gICAgICAgZXhwZWN0KGxvYWRlZC50cmFpbmluZ0NvbmZpZykudG9FcXVhbCh0cmFpbmluZ0NvbmZpZzEpO1xuICAgICB9KSk7XG5cbiAgaXQoJ1NhdmUtbG9hZCByb3VuZCB0cmlwIHN1Y2NlZWRzOiB2MCBmb3JtYXQnLCBydW5XaXRoTG9jayhhc3luYyAoKSA9PiB7XG4gICAgICAgY29uc3QgaGFuZGxlcjEgPSB0Zi5pby5nZXRTYXZlSGFuZGxlcnMoJ2xvY2Fsc3RvcmFnZTovL0Zvb01vZGVsJylbMF07XG5cbiAgICAgICBhd2FpdCBoYW5kbGVyMS5zYXZlKGFydGlmYWN0c1YwKTtcbiAgICAgICBjb25zdCBoYW5kbGVyMiA9IHRmLmlvLmdldExvYWRIYW5kbGVycygnbG9jYWxzdG9yYWdlOi8vRm9vTW9kZWwnKVswXTtcbiAgICAgICBjb25zdCBsb2FkZWQgPSBhd2FpdCBoYW5kbGVyMi5sb2FkKCk7XG4gICAgICAgZXhwZWN0KGxvYWRlZC5tb2RlbFRvcG9sb2d5KS50b0VxdWFsKG1vZGVsVG9wb2xvZ3kxKTtcbiAgICAgICBleHBlY3QobG9hZGVkLndlaWdodFNwZWNzKS50b0VxdWFsKHdlaWdodFNwZWNzMSk7XG4gICAgICAgZXhwZWN0KGxvYWRlZC53ZWlnaHREYXRhKS50b0VxdWFsKHdlaWdodERhdGExKTtcbiAgICAgICBleHBlY3QobG9hZGVkLmZvcm1hdCkudG9CZVVuZGVmaW5lZCgpO1xuICAgICAgIGV4cGVjdChsb2FkZWQuZ2VuZXJhdGVkQnkpLnRvQmVVbmRlZmluZWQoKTtcbiAgICAgICBleHBlY3QobG9hZGVkLmNvbnZlcnRlZEJ5KS50b0JlVW5kZWZpbmVkKCk7XG4gICAgICAgZXhwZWN0KGxvYWRlZC51c2VyRGVmaW5lZE1ldGFkYXRhKS50b0JlVW5kZWZpbmVkKCk7XG4gICAgICAgZXhwZWN0KGxvYWRlZC50cmFpbmluZ0NvbmZpZykudG9CZVVuZGVmaW5lZCgpO1xuICAgICAgIGV4cGVjdChsb2FkZWQubW9kZWxJbml0aWFsaXplcikudG9CZVVuZGVmaW5lZCgpO1xuICAgICAgIGV4cGVjdChsb2FkZWQuaW5pdGlhbGl6ZXJTaWduYXR1cmUpLnRvQmVVbmRlZmluZWQoKTtcbiAgICAgICBleHBlY3QobG9hZGVkLnRyYWluaW5nQ29uZmlnKS50b0JlVW5kZWZpbmVkKCk7XG4gICAgIH0pKTtcblxuICBpdCgnTG9hZGluZyBub25leGlzdGVudCBtb2RlbCBmYWlscy4nLCBydW5XaXRoTG9jayhhc3luYyAoKSA9PiB7XG4gICAgICAgY29uc3QgaGFuZGxlciA9XG4gICAgICAgICAgIHRmLmlvLmdldFNhdmVIYW5kbGVycygnbG9jYWxzdG9yYWdlOi8vTm9uZXhpc3RlbnRNb2RlbCcpWzBdO1xuICAgICAgIHRyeSB7XG4gICAgICAgICBhd2FpdCBoYW5kbGVyLmxvYWQoKTtcbiAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgIGV4cGVjdChlcnIubWVzc2FnZSlcbiAgICAgICAgICAgICAudG9FcXVhbChcbiAgICAgICAgICAgICAgICAgJ0luIGxvY2FsIHN0b3JhZ2UsIHRoZXJlIGlzIG5vIG1vZGVsIHdpdGggbmFtZSAnICtcbiAgICAgICAgICAgICAgICAgJ1xcJ05vbmV4aXN0ZW50TW9kZWxcXCcnKTtcbiAgICAgICAgIHJldHVybjsgIC8vIFN1Y2Nlc3NcbiAgICAgICB9XG4gICAgICAgZmFpbCgnTG9hZGluZyBub25leGlzdGVudCBtb2RlbCBzdWNjZWVkZWQgdW5leHBlY3RlZGx5LicpO1xuICAgICB9KSk7XG5cbiAgaXQoJ0xvYWRpbmcgbW9kZWwgd2l0aCBtaXNzaW5nIHRvcG9sb2d5IGZhaWxzLicsIHJ1bldpdGhMb2NrKGFzeW5jICgpID0+IHtcbiAgICAgICBjb25zdCBoYW5kbGVyMSA9IHRmLmlvLmdldFNhdmVIYW5kbGVycygnbG9jYWxzdG9yYWdlOi8vRm9vTW9kZWwnKVswXTtcbiAgICAgICBhd2FpdCBoYW5kbGVyMS5zYXZlKGFydGlmYWN0czEpO1xuICAgICAgIC8vIE1hbnVhbGx5IHJlbW92ZSB0aGUgdG9wb2xvZ3kgaXRlbSBmcm9tIGxvY2FsIHN0b3JhZ2UuXG4gICAgICAgd2luZG93LmxvY2FsU3RvcmFnZS5yZW1vdmVJdGVtKFxuICAgICAgICAgICAndGVuc29yZmxvd2pzX21vZGVscy9Gb29Nb2RlbC9tb2RlbF90b3BvbG9neScpO1xuXG4gICAgICAgY29uc3QgaGFuZGxlcjIgPSB0Zi5pby5nZXRMb2FkSGFuZGxlcnMoJ2xvY2Fsc3RvcmFnZTovL0Zvb01vZGVsJylbMF07XG4gICAgICAgdHJ5IHtcbiAgICAgICAgIGF3YWl0IGhhbmRsZXIyLmxvYWQoKTtcbiAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgIGV4cGVjdChlcnIubWVzc2FnZSlcbiAgICAgICAgICAgICAudG9FcXVhbChcbiAgICAgICAgICAgICAgICAgJ0luIGxvY2FsIHN0b3JhZ2UsIHRoZSB0b3BvbG9neSBvZiBtb2RlbCAnICtcbiAgICAgICAgICAgICAgICAgJ1xcJ0Zvb01vZGVsXFwnIGlzIG1pc3NpbmcuJyk7XG4gICAgICAgICByZXR1cm47ICAvLyBTdWNjZXNzXG4gICAgICAgfVxuICAgICAgIGZhaWwoJ0xvYWRpbmcgb2YgbW9kZWwgd2l0aCBtaXNzaW5nIHRvcG9sb2d5IHN1Y2NlZWRlZCB1bmV4cGVjdGVkbHkuJyk7XG4gICAgIH0pKTtcblxuICBpdCgnTG9hZGluZyBtb2RlbCB3aXRoIG1pc3Npbmcgd2VpZ2h0IHNwZWNzIGZhaWxzLicsIHJ1bldpdGhMb2NrKGFzeW5jICgpID0+IHtcbiAgICAgICBjb25zdCBoYW5kbGVyMSA9IHRmLmlvLmdldFNhdmVIYW5kbGVycygnbG9jYWxzdG9yYWdlOi8vRm9vTW9kZWwnKVswXTtcbiAgICAgICBhd2FpdCBoYW5kbGVyMS5zYXZlKGFydGlmYWN0czEpO1xuICAgICAgIC8vIE1hbnVhbGx5IHJlbW92ZSB0aGUgd2VpZ2h0IHNwZWNzIGl0ZW0gZnJvbSBsb2NhbCBzdG9yYWdlLlxuICAgICAgIHdpbmRvdy5sb2NhbFN0b3JhZ2UucmVtb3ZlSXRlbShcbiAgICAgICAgICAgJ3RlbnNvcmZsb3dqc19tb2RlbHMvRm9vTW9kZWwvd2VpZ2h0X3NwZWNzJyk7XG5cbiAgICAgICBjb25zdCBoYW5kbGVyMiA9IHRmLmlvLmdldExvYWRIYW5kbGVycygnbG9jYWxzdG9yYWdlOi8vRm9vTW9kZWwnKVswXTtcbiAgICAgICB0cnkge1xuICAgICAgICAgYXdhaXQgaGFuZGxlcjIubG9hZCgpO1xuICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgZXhwZWN0KGVyci5tZXNzYWdlKVxuICAgICAgICAgICAgIC50b0VxdWFsKFxuICAgICAgICAgICAgICAgICAnSW4gbG9jYWwgc3RvcmFnZSwgdGhlIHdlaWdodCBzcGVjcyBvZiBtb2RlbCAnICtcbiAgICAgICAgICAgICAgICAgJ1xcJ0Zvb01vZGVsXFwnIGFyZSBtaXNzaW5nLicpO1xuICAgICAgICAgcmV0dXJuOyAgLy8gU3VjY2Vzc1xuICAgICAgIH1cbiAgICAgICBmYWlsKFxuICAgICAgICAgICAnTG9hZGluZyBvZiBtb2RlbCB3aXRoIG1pc3Npbmcgd2VpZ2h0IHNwZWNzICcgK1xuICAgICAgICAgICAnc3VjY2VlZGVkIHVuZXhwZWN0ZWRseS4nKTtcbiAgICAgfSkpO1xuXG4gIGl0KCdMb2FkaW5nIG1vZGVsIHdpdGggbWlzc2luZyB3ZWlnaHQgZGF0YSBmYWlscy4nLCBydW5XaXRoTG9jayhhc3luYyAoKSA9PiB7XG4gICAgICAgY29uc3QgaGFuZGxlcjEgPSB0Zi5pby5nZXRTYXZlSGFuZGxlcnMoJ2xvY2Fsc3RvcmFnZTovL0Zvb01vZGVsJylbMF07XG4gICAgICAgYXdhaXQgaGFuZGxlcjEuc2F2ZShhcnRpZmFjdHMxKTtcblxuICAgICAgIC8vIE1hbnVhbGx5IHJlbW92ZSB0aGUgd2VpZ2h0IGRhdGEgaXRlbSBmcm9tIGxvY2FsIHN0b3JhZ2UuXG4gICAgICAgd2luZG93LmxvY2FsU3RvcmFnZS5yZW1vdmVJdGVtKFxuICAgICAgICAgICAndGVuc29yZmxvd2pzX21vZGVscy9Gb29Nb2RlbC93ZWlnaHRfZGF0YScpO1xuXG4gICAgICAgY29uc3QgaGFuZGxlcjIgPSB0Zi5pby5nZXRMb2FkSGFuZGxlcnMoJ2xvY2Fsc3RvcmFnZTovL0Zvb01vZGVsJylbMF07XG4gICAgICAgdHJ5IHtcbiAgICAgICAgIGF3YWl0IGhhbmRsZXIyLmxvYWQoKTtcbiAgICAgICAgIGZhaWwoXG4gICAgICAgICAgICAgJ0xvYWRpbmcgb2YgbW9kZWwgd2l0aCBtaXNzaW5nIHdlaWdodCBkYXRhICcgK1xuICAgICAgICAgICAgICdzdWNjZWVkZWQgdW5leHBlY3RlZGx5LicpO1xuICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgZXhwZWN0KGVyci5tZXNzYWdlKVxuICAgICAgICAgICAgIC50b0VxdWFsKFxuICAgICAgICAgICAgICAgICAnSW4gbG9jYWwgc3RvcmFnZSwgdGhlIGJpbmFyeSB3ZWlnaHQgdmFsdWVzIG9mIG1vZGVsICcgK1xuICAgICAgICAgICAgICAgICAnXFwnRm9vTW9kZWxcXCcgYXJlIG1pc3NpbmcuJyk7XG4gICAgICAgfVxuICAgICB9KSk7XG5cbiAgaXQoJ0RhdGEgc2l6ZSB0b28gbGFyZ2UgbGVhZHMgdG8gZXJyb3IgdGhyb3duJywgcnVuV2l0aExvY2soYXN5bmMgKCkgPT4ge1xuICAgICAgIGNvbnN0IG92ZXJmbG93Qnl0ZVNpemUgPSBmaW5kT3ZlcmZsb3dpbmdCeXRlU2l6ZSgpO1xuICAgICAgIGNvbnN0IG92ZXJmbG93QXJ0aWZhY3RzOiB0Zi5pby5Nb2RlbEFydGlmYWN0cyA9IHtcbiAgICAgICAgIG1vZGVsVG9wb2xvZ3k6IG1vZGVsVG9wb2xvZ3kxLFxuICAgICAgICAgd2VpZ2h0U3BlY3M6IHdlaWdodFNwZWNzMSxcbiAgICAgICAgIHdlaWdodERhdGE6IG5ldyBBcnJheUJ1ZmZlcihvdmVyZmxvd0J5dGVTaXplKSxcbiAgICAgICB9O1xuICAgICAgIGNvbnN0IGhhbmRsZXIxID0gdGYuaW8uZ2V0U2F2ZUhhbmRsZXJzKCdsb2NhbHN0b3JhZ2U6Ly9Gb29Nb2RlbCcpWzBdO1xuICAgICAgIHRyeSB7XG4gICAgICAgICBhd2FpdCBoYW5kbGVyMS5zYXZlKG92ZXJmbG93QXJ0aWZhY3RzKTtcbiAgICAgICAgIGZhaWwoXG4gICAgICAgICAgICAgJ1NhdmluZyBvZiBtb2RlbCBvZiBvdmVyZmxvd2luZy1zaXplIHdlaWdodCBkYXRhIHN1Y2NlZWRlZCAnICtcbiAgICAgICAgICAgICAndW5leHBlY3RlZGx5LicpO1xuICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgZXhwZWN0KFxuICAgICAgICAgICAgIChlcnIubWVzc2FnZSBhcyBzdHJpbmcpXG4gICAgICAgICAgICAgICAgIC5pbmRleE9mKCdGYWlsZWQgdG8gc2F2ZSBtb2RlbCBcXCdGb29Nb2RlbFxcJyB0byBsb2NhbCBzdG9yYWdlJykpXG4gICAgICAgICAgICAgLnRvRXF1YWwoMCk7XG4gICAgICAgfVxuICAgICB9KSk7XG5cbiAgaXQoJ051bGwsIHVuZGVmaW5lZCBvciBlbXB0eSBtb2RlbFBhdGggdGhyb3dzIEVycm9yJywgKCkgPT4ge1xuICAgIGV4cGVjdCgoKSA9PiBicm93c2VyTG9jYWxTdG9yYWdlKG51bGwpKVxuICAgICAgICAudG9UaHJvd0Vycm9yKFxuICAgICAgICAgICAgL2xvY2FsIHN0b3JhZ2UsIG1vZGVsUGF0aCBtdXN0IG5vdCBiZSBudWxsLCB1bmRlZmluZWQgb3IgZW1wdHkvKTtcbiAgICBleHBlY3QoKCkgPT4gYnJvd3NlckxvY2FsU3RvcmFnZSh1bmRlZmluZWQpKVxuICAgICAgICAudG9UaHJvd0Vycm9yKFxuICAgICAgICAgICAgL2xvY2FsIHN0b3JhZ2UsIG1vZGVsUGF0aCBtdXN0IG5vdCBiZSBudWxsLCB1bmRlZmluZWQgb3IgZW1wdHkvKTtcbiAgICBleHBlY3QoKCkgPT4gYnJvd3NlckxvY2FsU3RvcmFnZSgnJykpXG4gICAgICAgIC50b1Rocm93RXJyb3IoXG4gICAgICAgICAgICAvbG9jYWwgc3RvcmFnZSwgbW9kZWxQYXRoIG11c3Qgbm90IGJlIG51bGwsIHVuZGVmaW5lZCBvciBlbXB0eS4vKTtcbiAgfSk7XG5cbiAgaXQoJ3JvdXRlcicsICgpID0+IHtcbiAgICBleHBlY3QoXG4gICAgICAgIGxvY2FsU3RvcmFnZVJvdXRlcignbG9jYWxzdG9yYWdlOi8vYmFyJykgaW5zdGFuY2VvZiBCcm93c2VyTG9jYWxTdG9yYWdlKVxuICAgICAgICAudG9FcXVhbCh0cnVlKTtcbiAgICBleHBlY3QobG9jYWxTdG9yYWdlUm91dGVyKCdpbmRleGVkZGI6Ly9iYXInKSkudG9CZU51bGwoKTtcbiAgICBleHBlY3QobG9jYWxTdG9yYWdlUm91dGVyKCdxdXgnKSkudG9CZU51bGwoKTtcbiAgfSk7XG5cbiAgaXQoJ01hbmFnZXI6IExpc3QgbW9kZWxzOiAwIHJlc3VsdCcsIHJ1bldpdGhMb2NrKGFzeW5jICgpID0+IHtcbiAgICAgICAvLyBCZWZvcmUgYW55IG1vZGVsIGlzIHNhdmVkLCBsaXN0TW9kZWxzIHNob3VsZCByZXR1cm4gZW1wdHkgcmVzdWx0LlxuICAgICAgIGNvbnN0IG91dCA9IGF3YWl0IG5ldyBCcm93c2VyTG9jYWxTdG9yYWdlTWFuYWdlcigpLmxpc3RNb2RlbHMoKTtcbiAgICAgICBleHBlY3Qob3V0KS50b0VxdWFsKHt9KTtcbiAgICAgfSkpO1xuXG4gIGl0KCdNYW5hZ2VyOiBMaXN0IG1vZGVsczogMSByZXN1bHQnLCBydW5XaXRoTG9jayhhc3luYyAoKSA9PiB7XG4gICAgICAgY29uc3QgaGFuZGxlciA9IHRmLmlvLmdldFNhdmVIYW5kbGVycygnbG9jYWxzdG9yYWdlOi8vYmF6L1F1eE1vZGVsJylbMF07XG4gICAgICAgY29uc3Qgc2F2ZVJlc3VsdCA9IGF3YWl0IGhhbmRsZXIuc2F2ZShhcnRpZmFjdHMxKTtcblxuICAgICAgIC8vIEFmdGVyIHN1Y2Nlc3NmdWwgc2F2aW5nLCB0aGVyZSBzaG91bGQgYmUgb25lIG1vZGVsLlxuICAgICAgIGNvbnN0IG91dCA9IGF3YWl0IG5ldyBCcm93c2VyTG9jYWxTdG9yYWdlTWFuYWdlcigpLmxpc3RNb2RlbHMoKTtcbiAgICAgICBpZiAoT2JqZWN0LmtleXMob3V0KS5sZW5ndGggIT09IDEpIHtcbiAgICAgICAgIGNvbnNvbGUubG9nKEpTT04uc3RyaW5naWZ5KG91dCwgbnVsbCwgMikpO1xuICAgICAgIH1cblxuICAgICAgIGV4cGVjdChPYmplY3Qua2V5cyhvdXQpLmxlbmd0aCkudG9FcXVhbCgxKTtcbiAgICAgICBleHBlY3Qob3V0WydiYXovUXV4TW9kZWwnXS5tb2RlbFRvcG9sb2d5VHlwZSlcbiAgICAgICAgICAgLnRvRXF1YWwoc2F2ZVJlc3VsdC5tb2RlbEFydGlmYWN0c0luZm8ubW9kZWxUb3BvbG9neVR5cGUpO1xuICAgICAgIGV4cGVjdChvdXRbJ2Jhei9RdXhNb2RlbCddLm1vZGVsVG9wb2xvZ3lCeXRlcylcbiAgICAgICAgICAgLnRvRXF1YWwoc2F2ZVJlc3VsdC5tb2RlbEFydGlmYWN0c0luZm8ubW9kZWxUb3BvbG9neUJ5dGVzKTtcbiAgICAgICBleHBlY3Qob3V0WydiYXovUXV4TW9kZWwnXS53ZWlnaHRTcGVjc0J5dGVzKVxuICAgICAgICAgICAudG9FcXVhbChzYXZlUmVzdWx0Lm1vZGVsQXJ0aWZhY3RzSW5mby53ZWlnaHRTcGVjc0J5dGVzKTtcbiAgICAgICBleHBlY3Qob3V0WydiYXovUXV4TW9kZWwnXS53ZWlnaHREYXRhQnl0ZXMpXG4gICAgICAgICAgIC50b0VxdWFsKHNhdmVSZXN1bHQubW9kZWxBcnRpZmFjdHNJbmZvLndlaWdodERhdGFCeXRlcyk7XG4gICAgIH0pKTtcblxuICBpdCgnTWFuYWdlcjogTGlzdCBtb2RlbHM6IDIgcmVzdWx0cycsIHJ1bldpdGhMb2NrKGFzeW5jICgpID0+IHtcbiAgICAgICAvLyBGaXJzdCwgc2F2ZSBhIG1vZGVsLlxuICAgICAgIGNvbnN0IGhhbmRsZXIxID0gdGYuaW8uZ2V0U2F2ZUhhbmRsZXJzKCdsb2NhbHN0b3JhZ2U6Ly9RdXhNb2RlbCcpWzBdO1xuICAgICAgIGNvbnN0IHNhdmVSZXN1bHQxID0gYXdhaXQgaGFuZGxlcjEuc2F2ZShhcnRpZmFjdHMxKTtcblxuICAgICAgIC8vIFRoZW4sIHNhdmUgdGhlIG1vZGVsIHVuZGVyIGFub3RoZXIgcGF0aC5cbiAgICAgICBjb25zdCBoYW5kbGVyMiA9XG4gICAgICAgICAgIHRmLmlvLmdldFNhdmVIYW5kbGVycygnbG9jYWxzdG9yYWdlOi8vcmVwZWF0L1F1eE1vZGVsJylbMF07XG4gICAgICAgY29uc3Qgc2F2ZVJlc3VsdDIgPSBhd2FpdCBoYW5kbGVyMi5zYXZlKGFydGlmYWN0czEpO1xuXG4gICAgICAgLy8gQWZ0ZXIgc3VjY2Vzc2Z1bCBzYXZpbmcsIHRoZXJlIHNob3VsZCBiZSB0d28gbW9kZWxzLlxuICAgICAgIGNvbnN0IG91dCA9IGF3YWl0IG5ldyBCcm93c2VyTG9jYWxTdG9yYWdlTWFuYWdlcigpLmxpc3RNb2RlbHMoKTtcbiAgICAgICBpZiAoT2JqZWN0LmtleXMob3V0KS5sZW5ndGggIT09IDIpIHtcbiAgICAgICAgIGNvbnNvbGUubG9nKEpTT04uc3RyaW5naWZ5KG91dCwgbnVsbCwgMikpO1xuICAgICAgIH1cbiAgICAgICBleHBlY3QoT2JqZWN0LmtleXMob3V0KS5sZW5ndGgpLnRvRXF1YWwoMik7XG4gICAgICAgZXhwZWN0KG91dFsnUXV4TW9kZWwnXS5tb2RlbFRvcG9sb2d5VHlwZSlcbiAgICAgICAgICAgLnRvRXF1YWwoc2F2ZVJlc3VsdDEubW9kZWxBcnRpZmFjdHNJbmZvLm1vZGVsVG9wb2xvZ3lUeXBlKTtcbiAgICAgICBleHBlY3Qob3V0WydRdXhNb2RlbCddLm1vZGVsVG9wb2xvZ3lCeXRlcylcbiAgICAgICAgICAgLnRvRXF1YWwoc2F2ZVJlc3VsdDEubW9kZWxBcnRpZmFjdHNJbmZvLm1vZGVsVG9wb2xvZ3lCeXRlcyk7XG4gICAgICAgZXhwZWN0KG91dFsnUXV4TW9kZWwnXS53ZWlnaHRTcGVjc0J5dGVzKVxuICAgICAgICAgICAudG9FcXVhbChzYXZlUmVzdWx0MS5tb2RlbEFydGlmYWN0c0luZm8ud2VpZ2h0U3BlY3NCeXRlcyk7XG4gICAgICAgZXhwZWN0KG91dFsnUXV4TW9kZWwnXS53ZWlnaHREYXRhQnl0ZXMpXG4gICAgICAgICAgIC50b0VxdWFsKHNhdmVSZXN1bHQxLm1vZGVsQXJ0aWZhY3RzSW5mby53ZWlnaHREYXRhQnl0ZXMpO1xuICAgICAgIGV4cGVjdChvdXRbJ3JlcGVhdC9RdXhNb2RlbCddLm1vZGVsVG9wb2xvZ3lUeXBlKVxuICAgICAgICAgICAudG9FcXVhbChzYXZlUmVzdWx0Mi5tb2RlbEFydGlmYWN0c0luZm8ubW9kZWxUb3BvbG9neVR5cGUpO1xuICAgICAgIGV4cGVjdChvdXRbJ3JlcGVhdC9RdXhNb2RlbCddLm1vZGVsVG9wb2xvZ3lCeXRlcylcbiAgICAgICAgICAgLnRvRXF1YWwoc2F2ZVJlc3VsdDIubW9kZWxBcnRpZmFjdHNJbmZvLm1vZGVsVG9wb2xvZ3lCeXRlcyk7XG4gICAgICAgZXhwZWN0KG91dFsncmVwZWF0L1F1eE1vZGVsJ10ud2VpZ2h0U3BlY3NCeXRlcylcbiAgICAgICAgICAgLnRvRXF1YWwoc2F2ZVJlc3VsdDIubW9kZWxBcnRpZmFjdHNJbmZvLndlaWdodFNwZWNzQnl0ZXMpO1xuICAgICAgIGV4cGVjdChvdXRbJ3JlcGVhdC9RdXhNb2RlbCddLndlaWdodERhdGFCeXRlcylcbiAgICAgICAgICAgLnRvRXF1YWwoc2F2ZVJlc3VsdDIubW9kZWxBcnRpZmFjdHNJbmZvLndlaWdodERhdGFCeXRlcyk7XG4gICAgIH0pKTtcblxuICBpdCgnTWFuYWdlcjogU3VjY2Vzc2Z1bCBkZWxldGVNb2RlbCcsIHJ1bldpdGhMb2NrKGFzeW5jICgpID0+IHtcbiAgICAgICAvLyBGaXJzdCwgc2F2ZSBhIG1vZGVsLlxuICAgICAgIGNvbnN0IGhhbmRsZXIxID0gdGYuaW8uZ2V0U2F2ZUhhbmRsZXJzKCdsb2NhbHN0b3JhZ2U6Ly9RdXhNb2RlbCcpWzBdO1xuICAgICAgIGF3YWl0IGhhbmRsZXIxLnNhdmUoYXJ0aWZhY3RzMSk7XG5cbiAgICAgICAvLyBUaGVuLCBzYXZlIHRoZSBtb2RlbCB1bmRlciBhbm90aGVyIHBhdGguXG4gICAgICAgY29uc3QgaGFuZGxlcjIgPVxuICAgICAgICAgICB0Zi5pby5nZXRTYXZlSGFuZGxlcnMoJ2xvY2Fsc3RvcmFnZTovL3JlcGVhdC9RdXhNb2RlbCcpWzBdO1xuICAgICAgIGF3YWl0IGhhbmRsZXIyLnNhdmUoYXJ0aWZhY3RzMSk7XG5cbiAgICAgICAvLyBBZnRlciBzdWNjZXNzZnVsIHNhdmluZywgZGVsZXRlIHRoZSBmaXJzdCBzYXZlLCBhbmQgdGhlblxuICAgICAgIC8vIGBsaXN0TW9kZWxgIHNob3VsZCBnaXZlIG9ubHkgb25lIHJlc3VsdC5cbiAgICAgICBjb25zdCBtYW5hZ2VyID0gbmV3IEJyb3dzZXJMb2NhbFN0b3JhZ2VNYW5hZ2VyKCk7XG4gICAgICAgYXdhaXQgbWFuYWdlci5yZW1vdmVNb2RlbCgnUXV4TW9kZWwnKTtcbiAgICAgICBjb25zdCBvdXQgPSBhd2FpdCBtYW5hZ2VyLmxpc3RNb2RlbHMoKTtcbiAgICAgICBleHBlY3QoT2JqZWN0LmtleXMob3V0KSkudG9FcXVhbChbJ3JlcGVhdC9RdXhNb2RlbCddKTtcbiAgICAgfSkpO1xufSk7XG4iXX0=