Workflow Overview


wayfinder

Introduction

One of our goals in a single-cell analysis is to generate clusters that reasonably approximate cell-types or sub-types of interest in our samples before determining if there are differences in the proportions of these populations or differences in gene expression within these populations between experimental conditions.

A. The prior step used the filtered, normalized counts as input to a prinicpal component analysis. The top principle components (PCs) are the input to the clustering step.
B. Cells are plotted in a high-dimensional space (where the num dimesnions = num of included PCs, e.g. 15 dimensions); cells with similar gene expression profiles are closer to each other and are clustered together.
C. The cells clusters are projected from the high-dimensional space (e.g. 15 dimensions) down to two-dimensional space for visualization.


In this section, we will demonstrate how to generate clusters using Seurat’s graph based clustering approach and visualize those clustering assignments via a lower-dimensional projection of the full dataset.

Like other steps in our analysis, multiple parameters may need to be tested and evaluated while we would expect that only the final would be reported. Clustering is considered part of data exploration so an iterative approach is reasonable, and often expected (source).

Objectives

  • Understand the clustering process and input parameters
  • Generate initial clusters using ``
  • Visualize our clustering results

Clustering and projection

Now that we selected a number of PCs that we think are likely to represent biological variation and integrated our data across samples/batches, our next task is clustering.

An important aspect of parameter selection for clustering is to understand the “resolution” of the underlying biology and your experimental design:

  • Is answering your biological question dependent on identifying rarer cell types or specific subtypes?
  • Or are broader cell-types more relevant to address your biological question?

The OSCA book has a helpful analogy comparing clustering to microscopy and points out that “asking for an unqualified ‘best’ clustering is akin to asking for the best magnification on a microscope without any context”.

To generate clusters, we will generate “communities” of cells using the PCs we selected, before choosing a resolution parameter to divide those communities into discrete clusters.

Clustering

Seurat uses a graph-based clustering approach to assign cells to clusters using a distance metric based on the previously generated PCs, with improvements based on work by (Xu and Su 2015) and CyTOF data (Levine et al. 2015) implemented in Seurat v3 and v5 and building on the initial strategies for droplet-based single-cell technology (Macosko et al. 2015) (source). A key aspect of this process is that while the clusters are based on similarity of expression between the cells, the clustering is based on the selected PCs and not the full data set.

Image: kNN example - section on graph based clustering (from Cambridge Bioinformatics course)
Image: kNN example - section on graph based clustering (from Cambridge Bioinformatics course)

To briefly summarize, cells are embedded in a k-nearest neighbors (kNN) graph (illustrated above) based on “the euclidean distance in PCA space” between the cells and the edge weights between any two cells (e.g. their “closeness”) is refined based on Jaccard similarity (source).

Additional context and sources for graph-based clustering Cambridge Bioinformatics’ Analysis of single cell RNA-seq data course materials, the source of the image above, delves into kNN and other graph based clustering methods in much greater detail, including outlining possible downsides for these methods. To described kNN, we have also drawn from the Ho Lab’s description of this process for Seurat v3 as well as the HBC materials on clustering and the OSCA book’s more general overview of graph based clustering, which also describes the drawbacks for these methods.


This process is performed with the FindNeighbors() command, using the number of principal components we selected in the previous section.

The second step is to iteratively partition the kNN graph into “cliques” or clusters using the Louvain modularity optimization algorithm (for the default parameters), with the “granularity” of the clusters set by a resolution parameter (source).

Image: K-means clustering example (from Cambridge Bioinformatics course)
Image: K-means clustering example (from Cambridge Bioinformatics course)

We’ll use the FindClusters() function, selecting a resolution of 0.4 to start, although we could also add other resolutions at this stage to look at in later steps. See Waltman and Jan van Eck (2013) for the underlying algorithms.

Again, how a “cell type” or “subtype” should be defined for your data is important to consider in selecting a resolution - we’d start with a higher resolution for smaller/more rare clusters and a lower resolution for larger/more general clusters.

Then, when we look at the meta data we should see that cluster labels have been added for each cell:

# Cluster PCAs ------------------------------------------------------------
# Create KNN graph with `FindNeighbors()`
geo_so = FindNeighbors(geo_so, dims = 1:pcs, reduction = 'integrated.sct.rpca')

# generate clusters
geo_so = FindClusters(geo_so, resolution = 0.4, cluster.name = 'integrated.sct.rpca.clusters')

# look at meta.data to see cluster labels
head(geo_so@meta.data)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 31560
Number of edges: 1047628

Running Louvain algorithm...
Maximum modularity in 10 random starts: 0.9428
Number of communities: 20
Elapsed time: 9 seconds
                                            orig.ident nCount_RNA nFeature_RNA  day  replicate percent.mt nCount_SCT nFeature_SCT integrated.sct.rpca.clusters seurat_clusters
HODay0replicate1_AAACCTGAGAGAACAG-1 HO.Day0.replicate1      10234         3226 Day0 replicate1   1.240962       6062         2867                            1               1
HODay0replicate1_AAACCTGGTCATGCAT-1 HO.Day0.replicate1       3158         1499 Day0 replicate1   7.536415       4607         1509                            1               1
HODay0replicate1_AAACCTGTCAGAGCTT-1 HO.Day0.replicate1      13464         4102 Day0 replicate1   3.112002       5314         2370                            6               6
HODay0replicate1_AAACGGGAGAGACTTA-1 HO.Day0.replicate1        577          346 Day0 replicate1   1.559792       3877         1031                           10              10
HODay0replicate1_AAACGGGAGGCCCGTT-1 HO.Day0.replicate1       1189          629 Day0 replicate1   3.700589       4166          915                            0               0
HODay0replicate1_AAACGGGCAACTGGCC-1 HO.Day0.replicate1       7726         2602 Day0 replicate1   2.938131       5865         2588                            1               1

The result of FindNeighbors() adds graph information to the graphs slot:

Image: Schematic after FindNeighbors().
Image: Schematic after FindNeighbors().


The result of FindClusters() adds two columns to the meta.data table and changes the active.ident to the “seurat_clusters” column. In other words, the cells now belong to clusters rather than to their orig.ident.

Image: Schematic after FindClusters().
Image: Schematic after FindClusters().

Generally it’s preferable to err on the side of too many clusters, as they can be combined manually in later steps. In our experience, this is another parameter that often needs to be iteratively revised and reviewed.

Resolution parameters recommendations

More details on choosing a clustering resolution The Seurat clustering tutorial recommends selecting a resolution between 0.4 - 1.2 for datasets of approximately 3k cells, while the HBC course recommends 0.4-1.4 for 3k-5k cells. However, in our experience reasonable starting resolutions can be very dataset dependent.


Cluster plots

To visualize the cell clusters, we can use dimensionality reduction techniques to visualize and explore our large, high-dimensional dataset. Two popular methods that are supported by Seurat are t-distributed stochastic neighbor embedding (t-SNE) and Uniform Manifold Approximation and Projection (UMAP) techniques. These techniques allow us to visualize our high-dimensional single-cell data in 2D space and see if cells grouped together within graph-based clusters co-localize in these representations (source).

While we unfortunately don’t have time to compare and contrast tSNE, and UMAP, we would highly recommend this blog post contrasting tSNE and UMAP for illustrative examples. The Seurat authors additionally caution that while these methods are useful for data exploration, to avoid drawing biological conclusions solely based on these visualizations (source).

To start this process, we’ll use the RunUMAP() function to calculate the UMAP reduction for our data. Notice how the previous dimensionality choices carry through the downstream analysis and that the number of PCs selected in the previous steps are included as an argument.

# Create UMAP reduction ---------------------------------------------------
geo_so = RunUMAP(geo_so, dims = 1:pcs, reduction = 'integrated.sct.rpca', reduction.name = 'umap.integrated.sct.rpca')
geo_so # Notice a third reduction has been added: `umap.integrated.sct.rpca`
An object of class Seurat 
46955 features across 31560 samples within 2 assays 
Active assay: SCT (20466 features, 3000 variable features)
 3 layers present: counts, data, scale.data
 1 other assay present: RNA
 3 dimensional reductions calculated: unintegrated.sct.pca, integrated.sct.rpca, umap.integrated.sct.rpca

The resulting Seurat object now has an additional umap.integrated.sct.rpca in the reduction slot:

Image: Schematic after RunUMAP().
Image: Schematic after RunUMAP().

Visualizing and evaluating clustering

After we generate the UMAP reduction, we can then visualize the results using the DimPlot() function, labeling our plot by the auto generated seurat_clusters that correspond to the most recent clustering results generated.

At this stage, we want to determine if the clusters look fairly well separated, if they seem to correspond to how cells are grouped in the UMAP, and if the number of clusters is generally aligned with the resolution of our biological question. Again, if there are “too many” clusters that’s not necessarily a problem.

We can also look at the same UMAP labeled by day to visually inspect if the UMAP structure corresponds to the day.

# Visualize UMAP cluster --------------------------------------------------
# cluster ID labels
post_integration_umap_plot_clusters = DimPlot(geo_so, group.by = 'seurat_clusters', label = TRUE, reduction = 'umap.integrated.sct.rpca') + NoLegend()
post_integration_umap_plot_clusters

ggsave(filename = 'results/figures/umap_integrated_sct_clusters.png', plot = post_integration_umap_plot_clusters, width = 6, height = 6, units = 'in')

# clusters with labels, split by condition
post_integration_umap_plot_split_clusters = DimPlot(geo_so, group.by = 'seurat_clusters', split.by = 'day', label = TRUE, reduction = 'umap.integrated.sct.rpca') + NoLegend()
post_integration_umap_plot_split_clusters

ggsave(filename = 'results/figures/umap_integrated_sct_split_clusters.png', plot = post_integration_umap_plot_clusters, width = 14, height = 6, units = 'in')

# UMAP with day labels (note - we added this column to the meta-data yesterday)
post_integration_umap_plot_day = DimPlot(geo_so, group.by = 'day', label = FALSE, reduction = 'umap.integrated.sct.rpca')
post_integration_umap_plot_day

ggsave(filename = 'results/figures/umap_integrated_sct_day.png', plot = post_integration_umap_plot_day, width = 8, height = 6, units = 'in')

Similar to the PCA plots, the day labeled UMAP can tell us if technical sources of variation might be driving or stratifying the clusters, which would suggest that the normalization and integration steps should be revisted before proceeding.

Another approach is to evaluate the number of cells per cluster using the table() function, split by day or split by orig.ident to see if the individual samples are driving any of the UMAP structure:

# Make tables of cell counts ----------------------------------------------
# cells per cluster, split by condition
table(geo_so@meta.data$day, geo_so@meta.data$integrated.sct.rpca.clusters)

# cells per cluster per sample
table(geo_so@meta.data$orig.ident, geo_so@meta.data$integrated.sct.rpca.clusters)
       
           0    1    2    3    4    5    6    7    8    9   10   11   12   13   14   15   16   17   18   19
  Day0   119  946   25  213   44  190  240  789  186   92  121   71   30  270    1   76  138  161  133   30
  Day7  3561 1325 2919 2443 2693 1684 1237  133  597  552  542  684  650  351  570  334   97  285   93  105
  Day21 1502 1320   39  325  148  484  375  449  453  452  266  166   68  123    2  135  271   40  194   18
                     
                         0    1    2    3    4    5    6    7    8    9   10   11   12   13   14   15   16   17   18   19
  HO.Day0.replicate1    31  279   10   72   20   58   64  196   50   18   23   22   11   71    0   14   22   48   34   10
  HO.Day0.replicate2    15  132    8   33    7   35   24  165   29   24   20    8    7   52    1   15   25   13   13    3
  HO.Day0.replicate3    48  329    2   66    4   56   90  221   64   26   36   20    5   75    0   25   41   59   52    3
  HO.Day0.replicate4    25  206    5   42   13   41   62  207   43   24   42   21    7   72    0   22   50   41   34   14
  HO.Day7.replicate1   521  343 1089  572  898  446  307   49  208  106    0  191  214   29    0   76   41   57   29   39
  HO.Day7.replicate2  1269  285  600  658  552  522  276   12  153  163    0  197  159  242    0  101   18   87   15   15
  HO.Day7.replicate3   990  530  767  759  565  454  422   63  145  187    0  194  158   57    0  129   28   96   37   45
  HO.Day7.replicate4   781  167  463  454  678  262  232    9   91   96  542  102  119   23  570   28   10   45   12    6
  HO.Day21.replicate1  382  415   13   85   50  152  140  131  134  131   68   56   16   47    0   40   71   13   58    2
  HO.Day21.replicate2  198  233    7   64   25   85   51  140   71   68   56   25   14   24    1   33   53    5   28    5
  HO.Day21.replicate3  218  199    4   65   23   97   57  117   85   78   44   24   18   20    0   22   56    7   38    6
  HO.Day21.replicate4  704  473   15  111   50  150  127   61  163  175   98   61   20   32    1   40   91   15   70    5

Comparing to unintegrated data

If we had proceeded with our filtered data and only normalized our data without doing any integration, including through the dimensionality reduction and clustering steps and then labeled the cells with their sample of origin, then we would see the following for our data:

Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 31560
Number of edges: 998270

Running Louvain algorithm...
Maximum modularity in 10 random starts: 0.9475
Number of communities: 22
Elapsed time: 9 seconds

In the plot at left, we see that while there are distinct clusters, those clusters seem to stratified by day. This suggests that without integration, these batch effects could skew the biological variability in our data. While on the right, we see little stratification within our clusters which means the integration seems to have removed those batch effects.

Rewind: Pre-integration evaluation clustering and visualization (code)

Prior to integration, we could follow the same steps we’ve just run for the integrated to see if the resulting clusters tend to be determined by sample or condition (in this case, the day):

geo_so = FindNeighbors(geo_so, dims = 1:pcs, assay = 'RNA', reduction = 'unintegrated.sct.pca', graph.name = c('RNA_nn', 'RNA_snn'))
geo_so = FindClusters(geo_so, resolution = 0.4, graph.name = 'RNA_snn', cluster.name = 'unintegrated.sct.clusters')
geo_so = RunUMAP(geo_so, dims = 1:pcs, reduction = 'unintegrated.sct.pca', reduction.name = 'umap.unintegrated.sct.pca')

The plots above were generated with:

# Code block - show unintegrated
pre_integration_umap_plot_orig.ident = DimPlot(geo_so, group.by = 'orig.ident', label = FALSE, reduction = 'umap.unintegrated.sct.pca')
ggsave(filename = 'results/figures/umap_unintegrated_sct_orig.ident.png', plot = pre_integration_umap_plot_orig.ident, width = 8, height = 6, units = 'in')

pre_integration_umap_plot_day = DimPlot(geo_so, group.by = 'day', label = FALSE, reduction = 'umap.unintegrated.sct.pca')
ggsave(filename = 'results/figures/umap_unintegrated_sct_day.png', plot = pre_integration_umap_plot_day, width = 8, height = 6, units = 'in')



Alternative clustering resolutions

While we show a single resolution, we can generate and plot multiple resolutions iteratively and compare between them before selecting a clustering result for the next steps:

resolutions = c(0.4, 0.8)

for(res in resolutions) {
    message(res)

    cluster_column = sprintf('SCT_snn_res.%s', res)
    umap_file = sprintf('results/figures/umap_integrated_sct_%s.png', res)

    geo_so = FindClusters(geo_so, resolution = res)

    DimPlot(geo_so, group.by = cluster_column, label = TRUE, reduction = 'umap.integrated.sct.rpca') + NoLegend()
    ggsave(filename = umap_file, width = 8, height = 7, units = 'in')
}

In the results, we’ll see multiple resolutions should now be added to the metadata slot.

head(geo_so@meta.data)



Save our progress

Before moving on to our next section, we will output our updated Seurat object to file:

# Save Seurat object ------------------------------------------------------
saveRDS(geo_so, file = 'results/rdata/geo_so_sct_clustered.rds')

Summary

In this section we:

  • Generated cluster assignments for our cells using FindNeighbors() and FindClusters()
  • Evaluated our initial clusters using RunUMAP dimensional reduction and visualization

Next steps: Marker genes


These materials have been adapted and extended from materials listed above. These are open access materials distributed under the terms of the Creative Commons Attribution license (CC BY 4.0), which permits unrestricted use, distribution, and reproduction in any medium, provided the original author and source are credited.




Previous lesson Top of this lesson Next lesson
LS0tCnRpdGxlOiAiQ2x1c3RlcmluZyBhbmQgUHJvamVjdGlvbiIKYXV0aG9yOiAiVU0gQmlvaW5mb3JtYXRpY3MgQ29yZSIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgICAgICAgaHRtbF9kb2N1bWVudDoKICAgICAgICAgICAgaW5jbHVkZXM6CiAgICAgICAgICAgICAgICBpbl9oZWFkZXI6IGhlYWRlci5odG1sCiAgICAgICAgICAgIHRoZW1lOiBwYXBlcgogICAgICAgICAgICB0b2M6IHRydWUKICAgICAgICAgICAgdG9jX2RlcHRoOiA0CiAgICAgICAgICAgIHRvY19mbG9hdDogdHJ1ZQogICAgICAgICAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlCiAgICAgICAgICAgIGZpZ19jYXB0aW9uOiB0cnVlCiAgICAgICAgICAgIG1hcmtkb3duOiBHRk0KICAgICAgICAgICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CmJvZHksIHRkIHsKICAgZm9udC1zaXplOiAxOHB4Owp9CmNvZGUucnsKICBmb250LXNpemU6IDEycHg7Cn0KcHJlIHsKICBmb250LXNpemU6IDEycHgKfQoKdGFibGUuZmlnLCB0aC5maWcsIHRkLmZpZyB7CiAgYm9yZGVyOiAxcHggc29saWQgYmxhY2s7CiAgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsKICBwYWRkaW5nOiAxNXB4Owp9Cgp0YWJsZS5maWcsIHRoLmZpZywgdGQuZmlnIHsKICBib3JkZXI6IDFweCBzb2xpZCBibGFjazsKICBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlOwogIHBhZGRpbmc6IDE1cHg7Cn0KPC9zdHlsZT4KCmBgYHtyIGtsaXBweSwgZWNobz1GQUxTRSwgaW5jbHVkZT1UUlVFfQprbGlwcHk6OmtsaXBweShsYW5nID0gYygiciIsICJtYXJrZG93biIsICJiYXNoIiksIHBvc2l0aW9uID0gYygidG9wIiwgInJpZ2h0IikpCmBgYAoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0Kc291cmNlKCIuLi9iaW4vY2h1bmstb3B0aW9ucy5SIikKa25pdHJfZmlnX3BhdGgoIjA1LVByb2plY3Rpb25BbmRDbHVzdGVyaW5nLzA1LSIpCmBgYAoKIyBXb3JrZmxvdyBPdmVydmlldyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQoKPGJyLz4KPGltZyBzcmM9ImltYWdlcy93YXlmaW5kZXIvd2F5ZmluZGVyLnBuZyIgYWx0PSJ3YXlmaW5kZXIiIHN0eWxlPSJoZWlnaHQ6IDQwMHB4OyIvPgo8YnIvPgo8YnIvPgoKIyBJbnRyb2R1Y3Rpb24KCjwhLS0tIEdlbmVyYWwgZ29hbDogdG8gZ2VuZXJhdGUgY2x1c3RlcnMgdGhhdCByZWFzb25hYmx5IGFwcHJveGltYXRlIGNlbGwtdHlwZXMgb3Igc3ViLXR5cGVzIG9mIGludGVyZXN0IC0tLT4KT25lIG9mIG91ciBnb2FscyBpbiBhIHNpbmdsZS1jZWxsIGFuYWx5c2lzIGlzIHRvIGdlbmVyYXRlIGNsdXN0ZXJzIHRoYXQgcmVhc29uYWJseSBhcHByb3hpbWF0ZSBjZWxsLXR5cGVzIG9yIHN1Yi10eXBlcyBvZiBpbnRlcmVzdCBpbiBvdXIgc2FtcGxlcyBiZWZvcmUgZGV0ZXJtaW5pbmcgaWYgdGhlcmUgYXJlIGRpZmZlcmVuY2VzIGluIHRoZSBwcm9wb3J0aW9ucyBvZiB0aGVzZSBwb3B1bGF0aW9ucyBvciBkaWZmZXJlbmNlcyBpbiBnZW5lIGV4cHJlc3Npb24gd2l0aGluIHRoZXNlIHBvcHVsYXRpb25zIGJldHdlZW4gZXhwZXJpbWVudGFsIGNvbmRpdGlvbnMuCgo8dGFibGUgY2xhc3M9J2ZpZyc+Cjx0ciBjbGFzcz0nZmlnJz48dGQgY2xhc3M9J2ZpZyc+IVtdKGltYWdlcy9ncmFwaGljYWxfYWJzdHJhY3RzL2dyYXBoaWNhbF9hYnN0cmFjdF9jbHVzdGVyX3Byb2plY3Rpb24ucG5nKTwvdGQ+PC90cj4KPHRyIGNsYXNzPSdmaWcnPjx0ZCBjbGFzcz0nZmlnJz5BLiBUaGUgcHJpb3Igc3RlcCB1c2VkIHRoZSBmaWx0ZXJlZCwgbm9ybWFsaXplZCBjb3VudHMgYXMgaW5wdXQgdG8gYSBwcmluaWNwYWwgY29tcG9uZW50IGFuYWx5c2lzLiBUaGUgdG9wIHByaW5jaXBsZSBjb21wb25lbnRzIChQQ3MpIGFyZSB0aGUgaW5wdXQgdG8gdGhlIGNsdXN0ZXJpbmcgc3RlcC4gPGJyLz4KQi4gQ2VsbHMgYXJlIHBsb3R0ZWQgaW4gYSBoaWdoLWRpbWVuc2lvbmFsIHNwYWNlICh3aGVyZSB0aGUgbnVtIGRpbWVzbmlvbnMgPSBudW0gb2YgaW5jbHVkZWQgUENzLCBlLmcuIDE1IGRpbWVuc2lvbnMpOyBjZWxscyB3aXRoIHNpbWlsYXIgZ2VuZSBleHByZXNzaW9uIHByb2ZpbGVzIGFyZSBjbG9zZXIgdG8gZWFjaCBvdGhlciBhbmQgYXJlIGNsdXN0ZXJlZCB0b2dldGhlci4gPGJyLz4KQy4gVGhlIGNlbGxzIGNsdXN0ZXJzIGFyZSBwcm9qZWN0ZWQgZnJvbSB0aGUgaGlnaC1kaW1lbnNpb25hbCBzcGFjZSAoZS5nLiAxNSBkaW1lbnNpb25zKSBkb3duIHRvIHR3by1kaW1lbnNpb25hbCBzcGFjZSBmb3IgdmlzdWFsaXphdGlvbi4KPC90ZD48L3RyPgo8L3RhYmxlPgo8YnIvPgoKCkluIHRoaXMgc2VjdGlvbiwgd2Ugd2lsbCBkZW1vbnN0cmF0ZSBob3cgdG8gZ2VuZXJhdGUgY2x1c3RlcnMgdXNpbmcgU2V1cmF0J3MgZ3JhcGggYmFzZWQgY2x1c3RlcmluZyBhcHByb2FjaCBhbmQgdmlzdWFsaXplIHRob3NlIGNsdXN0ZXJpbmcgYXNzaWdubWVudHMgdmlhIGEgbG93ZXItZGltZW5zaW9uYWwgcHJvamVjdGlvbiBvZiB0aGUgZnVsbCBkYXRhc2V0LgoKTGlrZSBvdGhlciBzdGVwcyBpbiBvdXIgYW5hbHlzaXMsIG11bHRpcGxlIHBhcmFtZXRlcnMgbWF5IG5lZWQgdG8gYmUgdGVzdGVkIGFuZCBldmFsdWF0ZWQgd2hpbGUgd2Ugd291bGQgZXhwZWN0IHRoYXQgb25seSB0aGUgZmluYWwgd291bGQgYmUgcmVwb3J0ZWQuIENsdXN0ZXJpbmcgaXMgY29uc2lkZXJlZCBwYXJ0IG9mIGRhdGEgZXhwbG9yYXRpb24gc28gYW4gaXRlcmF0aXZlIGFwcHJvYWNoIGlzIHJlYXNvbmFibGUsIGFuZCBvZnRlbiBleHBlY3RlZCAoW3NvdXJjZV0oaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL2Jvb2tzLzMuMTUvT1NDQS5iYXNpYy9jbHVzdGVyaW5nLmh0bWwpKS4gCgoKIyMgT2JqZWN0aXZlcwoKLSBVbmRlcnN0YW5kIHRoZSBjbHVzdGVyaW5nIHByb2Nlc3MgYW5kIGlucHV0IHBhcmFtZXRlcnMKLSBHZW5lcmF0ZSBpbml0aWFsIGNsdXN0ZXJzIHVzaW5nIGBgIAotIFZpc3VhbGl6ZSBvdXIgY2x1c3RlcmluZyByZXN1bHRzCgotLS0KCmBgYHtyLCByZWFkX3Jkc19oaWRkZW4sIGVjaG8gPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmlmKCFleGlzdHMoJ2dlb19zbycpKSB7CiAgbGlicmFyeShTZXVyYXQpCiAgbGlicmFyeShCUENlbGxzKQogIGxpYnJhcnkodGlkeXZlcnNlKQoKICBvcHRpb25zKGZ1dHVyZS5nbG9iYWxzLm1heFNpemUgPSAxZTkpCgogIGdlb19zbyA9IHJlYWRSRFMoJ3Jlc3VsdHMvcmRhdGEvZ2VvX3NvX3NjdF9pbnRlZ3JhdGVkLnJkcycpCn0KYGBgCgojIENsdXN0ZXJpbmcgYW5kIHByb2plY3Rpb24KCk5vdyB0aGF0IHdlIHNlbGVjdGVkIGEgbnVtYmVyIG9mIFBDcyB0aGF0IHdlIHRoaW5rIGFyZSBsaWtlbHkgdG8gcmVwcmVzZW50IGJpb2xvZ2ljYWwgdmFyaWF0aW9uIGFuZCBpbnRlZ3JhdGVkIG91ciBkYXRhIGFjcm9zcyBzYW1wbGVzL2JhdGNoZXMsIG91ciBuZXh0IHRhc2sgaXMgY2x1c3RlcmluZy4gCgoKQW4gaW1wb3J0YW50IGFzcGVjdCBvZiBwYXJhbWV0ZXIgc2VsZWN0aW9uIGZvciBjbHVzdGVyaW5nIGlzIHRvIHVuZGVyc3RhbmQgdGhlICJyZXNvbHV0aW9uIiBvZiB0aGUgdW5kZXJseWluZyBiaW9sb2d5IGFuZCB5b3VyIGV4cGVyaW1lbnRhbCBkZXNpZ246ICAgCgotIElzIGFuc3dlcmluZyB5b3VyIGJpb2xvZ2ljYWwgcXVlc3Rpb24gZGVwZW5kZW50IG9uIGlkZW50aWZ5aW5nIHJhcmVyIGNlbGwgdHlwZXMgb3Igc3BlY2lmaWMgc3VidHlwZXM/ICAgCi0gT3IgYXJlIGJyb2FkZXIgY2VsbC10eXBlcyBtb3JlIHJlbGV2YW50IHRvIGFkZHJlc3MgeW91ciBiaW9sb2dpY2FsIHF1ZXN0aW9uPyAgIAoKVGhlIE9TQ0EgYm9vayBoYXMgYSBbaGVscGZ1bCBhbmFsb2d5IGNvbXBhcmluZyBjbHVzdGVyaW5nIHRvIG1pY3Jvc2NvcHldKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9ib29rcy8zLjE1L09TQ0EuYmFzaWMvY2x1c3RlcmluZy5odG1sI292ZXJ2aWV3LTEpIGFuZCBwb2ludHMgb3V0IHRoYXQgImFza2luZyBmb3IgYW4gdW5xdWFsaWZpZWQgJ2Jlc3QnIGNsdXN0ZXJpbmcgaXMgYWtpbiB0byBhc2tpbmcgZm9yIHRoZSBiZXN0IG1hZ25pZmljYXRpb24gb24gYSBtaWNyb3Njb3BlIHdpdGhvdXQgYW55IGNvbnRleHQiLiAKClRvIGdlbmVyYXRlIGNsdXN0ZXJzLCB3ZSB3aWxsIGdlbmVyYXRlICJjb21tdW5pdGllcyIgb2YgY2VsbHMgdXNpbmcgdGhlIFBDcyB3ZSBzZWxlY3RlZCwgYmVmb3JlIGNob29zaW5nIGEgcmVzb2x1dGlvbiBwYXJhbWV0ZXIgdG8gZGl2aWRlIHRob3NlIGNvbW11bml0aWVzIGludG8gZGlzY3JldGUgY2x1c3RlcnMuCgo8IS0tLSBDb250cmFzdCB0aGUgcHJldmlvdXMgZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uIHZlcnN1cyBuZWFyZXN0IG5laWdoYm9ycyBjbHVzdGVyaW5nIGFuZCBwbG90dGluZyB0aGUgY2VsbHMgaW4gbG93ZXIgZGltZW5zaW9uYWxpdHkgd2l0aCB0aGUgY2x1c3RlciBsYWJlbHM/IC0tLT4KCiMjIENsdXN0ZXJpbmcKClNldXJhdCB1c2VzIGEgZ3JhcGgtYmFzZWQgY2x1c3RlcmluZyBhcHByb2FjaCB0byBhc3NpZ24gY2VsbHMgdG8gY2x1c3RlcnMgdXNpbmcgYSBkaXN0YW5jZSBtZXRyaWMgYmFzZWQgb24gdGhlIHByZXZpb3VzbHkgZ2VuZXJhdGVkIFBDcywgd2l0aCBpbXByb3ZlbWVudHMgYmFzZWQgb24gd29yayBieSAoW1h1IGFuZCBTdSAyMDE1XShodHRwczovL2FjYWRlbWljLm91cC5jb20vYmlvaW5mb3JtYXRpY3MvYXJ0aWNsZS8zMS8xMi8xOTc0LzIxNDUwNSkpIGFuZCBDeVRPRiBkYXRhIChbTGV2aW5lIGV0IGFsLiAyMDE1XShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUM0NTA4NzU3LykpIGltcGxlbWVudGVkIGluIFNldXJhdCB2MyBhbmQgdjUgYW5kIGJ1aWxkaW5nIG9uIHRoZSBpbml0aWFsIHN0cmF0ZWdpZXMgZm9yIGRyb3BsZXQtYmFzZWQgc2luZ2xlLWNlbGwgdGVjaG5vbG9neSAoW01hY29za28gZXQgYWwuIDIwMTVdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzQ0ODExMzkvKSkgIChbc291cmNlXShodHRwczovL3NhdGlqYWxhYi5vcmcvc2V1cmF0L2FydGljbGVzL3BibWMza190dXRvcmlhbC5odG1sKSkuIEEga2V5IGFzcGVjdCBvZiB0aGlzIHByb2Nlc3MgaXMgdGhhdCB3aGlsZSB0aGUgY2x1c3RlcnMgYXJlIGJhc2VkIG9uIHNpbWlsYXJpdHkgb2YgZXhwcmVzc2lvbiBiZXR3ZWVuIHRoZSBjZWxscywgdGhlIGNsdXN0ZXJpbmcgaXMgYmFzZWQgb24gdGhlIHNlbGVjdGVkIFBDcyBhbmQgbm90IHRoZSBmdWxsIGRhdGEgc2V0LiAKCjxjZW50ZXI+CiFbSW1hZ2U6IGtOTiBleGFtcGxlIC0gc2VjdGlvbiBvbiBncmFwaCBiYXNlZCBjbHVzdGVyaW5nIChmcm9tIENhbWJyaWRnZSBCaW9pbmZvcm1hdGljcyBjb3Vyc2UpXSguL2ltYWdlcy9jdXJyaWN1bHVtLzA1LVByb2plY3Rpb25BbmRDbHVzdGVyaW5nL0Jpb0NlbGxHZW5lLU5lYXJlc3ROZWlnaGJvck5ldHdvcmtzLnBuZykKPC9jZW50ZXI+CgpUbyBicmllZmx5IHN1bW1hcml6ZSwgY2VsbHMgYXJlIGVtYmVkZGVkIGluIGEgay1uZWFyZXN0IG5laWdoYm9ycyAoa05OKSBncmFwaCAoaWxsdXN0cmF0ZWQgYWJvdmUpIGJhc2VkIG9uICJ0aGUgZXVjbGlkZWFuIGRpc3RhbmNlIGluIFBDQSBzcGFjZSIgYmV0d2VlbiB0aGUgY2VsbHMgYW5kIHRoZSBlZGdlIHdlaWdodHMgYmV0d2VlbiBhbnkgdHdvIGNlbGxzIChlLmcuIHRoZWlyICJjbG9zZW5lc3MiKSBpcyByZWZpbmVkIGJhc2VkIG9uIEphY2NhcmQgc2ltaWxhcml0eSAoW3NvdXJjZV0oaHR0cHM6Ly9oYmN0cmFpbmluZy5naXRodWIuaW8vc2NSTkEtc2VxX29ubGluZS9sZXNzb25zLzA3X1NDX2NsdXN0ZXJpbmdfY2VsbHNfU0NULmh0bWwpKS4KCjxkZXRhaWxzPgogICAgPHN1bW1hcnk+KkFkZGl0aW9uYWwgY29udGV4dCBhbmQgc291cmNlcyBmb3IgZ3JhcGgtYmFzZWQgY2x1c3RlcmluZyo8L3N1bW1hcnk+CiAgICBbQ2FtYnJpZGdlIEJpb2luZm9ybWF0aWNzJyBBbmFseXNpcyBvZiBzaW5nbGUgY2VsbCBSTkEtc2VxIGRhdGEgY291cnNlIG1hdGVyaWFsc10oaHR0cHM6Ly9iaW9jZWxsZ2VuLXB1YmxpYy5zdmkuZWR1LmF1L21pZ18yMDE5X3Njcm5hc2VxLXdvcmtzaG9wL2NsdXN0ZXJpbmctYW5kLWNlbGwtYW5ub3RhdGlvbi5odG1sKSwgdGhlIHNvdXJjZSBvZiB0aGUgaW1hZ2UgYWJvdmUsIGRlbHZlcyBpbnRvIGtOTiBhbmQgb3RoZXIgZ3JhcGggYmFzZWQgY2x1c3RlcmluZyBtZXRob2RzIGluIG11Y2ggZ3JlYXRlciBkZXRhaWwsIGluY2x1ZGluZyBvdXRsaW5pbmcgcG9zc2libGUgZG93bnNpZGVzIGZvciB0aGVzZSBtZXRob2RzLiAKICAgICBUbyBkZXNjcmliZWQga05OLCB3ZSBoYXZlIGFsc28gZHJhd24gZnJvbSB0aGUgW0hvIExhYidzIGRlc2NyaXB0aW9uIG9mIHRoaXMgcHJvY2VzcyBmb3IgU2V1cmF0IHYzXShodHRwczovL2hvbGFiLWhrdS5naXRodWIuaW8vRnVuZGFtZW50YWwtc2NSTkEvZG93bnN0cmVhbS5odG1sI3BlcmZvcm0tbGluZWFyLWRpbWVuc2lvbmFsLXJlZHVjdGlvbikgYXMgd2VsbCBhcyB0aGUgW0hCQyBtYXRlcmlhbHMgb24gY2x1c3RlcmluZ10oaHR0cHM6Ly9oYmN0cmFpbmluZy5naXRodWIuaW8vc2NSTkEtc2VxX29ubGluZS9sZXNzb25zLzA3X1NDX2NsdXN0ZXJpbmdfY2VsbHNfU0NULmh0bWwpIGFuZCB0aGUgW09TQ0EgYm9vaydzIG1vcmUgZ2VuZXJhbCBvdmVydmlldyBvZiBncmFwaCBiYXNlZCBjbHVzdGVyaW5nXShodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvYm9va3MvMy4xNS9PU0NBLmJhc2ljL2NsdXN0ZXJpbmcuaHRtbCNjbHVzdGVyaW5nLWdyYXBoKSwgd2hpY2ggYWxzbyBkZXNjcmliZXMgdGhlIGRyYXdiYWNrcyBmb3IgdGhlc2UgbWV0aG9kcy4KPC9kZXRhaWxzPgo8YnI+CgpUaGlzIHByb2Nlc3MgaXMgcGVyZm9ybWVkIHdpdGggdGhlIGBGaW5kTmVpZ2hib3JzKClgIFtjb21tYW5kXShodHRwczovL3NhdGlqYWxhYi5vcmcvc2V1cmF0L3JlZmVyZW5jZS9maW5kbmVpZ2hib3JzKSwgdXNpbmcgdGhlIG51bWJlciBvZiBwcmluY2lwYWwgY29tcG9uZW50cyB3ZSBzZWxlY3RlZCBpbiB0aGUgcHJldmlvdXMgc2VjdGlvbi4KClRoZSBzZWNvbmQgc3RlcCBpcyB0byBpdGVyYXRpdmVseSBwYXJ0aXRpb24gdGhlIGtOTiBncmFwaCBpbnRvICJjbGlxdWVzIiBvciBjbHVzdGVycyB1c2luZyB0aGUgTG91dmFpbiBtb2R1bGFyaXR5IG9wdGltaXphdGlvbiBhbGdvcml0aG0gKGZvciB0aGUgZGVmYXVsdCBwYXJhbWV0ZXJzKSwgd2l0aCB0aGUgImdyYW51bGFyaXR5IiBvZiB0aGUgY2x1c3RlcnMgc2V0IGJ5IGEgYHJlc29sdXRpb25gIHBhcmFtZXRlciAoW3NvdXJjZV0oaHR0cHM6Ly9zYXRpamFsYWIub3JnL3NldXJhdC9hcnRpY2xlcy9wYm1jM2tfdHV0b3JpYWwuaHRtbCkpLgoKIVtJbWFnZTogSy1tZWFucyBjbHVzdGVyaW5nIGV4YW1wbGUgKGZyb20gQ2FtYnJpZGdlIEJpb2luZm9ybWF0aWNzIGNvdXJzZSldKC4vaW1hZ2VzL2N1cnJpY3VsdW0vMDUtUHJvamVjdGlvbkFuZENsdXN0ZXJpbmcvSEJDLTA3LWstbWVhbnNGaWd1cmUucG5nKQoKV2UnbGwgdXNlIHRoZSBgRmluZENsdXN0ZXJzKClgIFtmdW5jdGlvbl0oaHR0cHM6Ly9zYXRpamFsYWIub3JnL3NldXJhdC9yZWZlcmVuY2UvZmluZGNsdXN0ZXJzKSwgc2VsZWN0aW5nIGEgcmVzb2x1dGlvbiBvZiBgMC40YCB0byBzdGFydCwgYWx0aG91Z2ggd2UgY291bGQgYWxzbyBhZGQgb3RoZXIgcmVzb2x1dGlvbnMgYXQgdGhpcyBzdGFnZSB0byBsb29rIGF0IGluIGxhdGVyIHN0ZXBzLiBTZWUgW1dhbHRtYW4gYW5kIEphbiB2YW4gRWNrICgyMDEzKV0oaHR0cHM6Ly9saW5rLnNwcmluZ2VyLmNvbS9hcnRpY2xlLzEwLjExNDAvZXBqYi9lMjAxMy00MDgyOS0wKSBmb3IgdGhlIHVuZGVybHlpbmcgYWxnb3JpdGhtcy4KCkFnYWluLCBob3cgYSDigJxjZWxsIHR5cGXigJ0gb3Ig4oCcc3VidHlwZeKAnSBzaG91bGQgYmUgZGVmaW5lZCBmb3IgeW91ciBkYXRhIGlzIGltcG9ydGFudCB0byBjb25zaWRlciBpbiBzZWxlY3RpbmcgYSByZXNvbHV0aW9uIC0gd2UnZCBzdGFydCB3aXRoIGEgaGlnaGVyIHJlc29sdXRpb24gZm9yIHNtYWxsZXIvbW9yZSByYXJlIGNsdXN0ZXJzIGFuZCBhIGxvd2VyIHJlc29sdXRpb24gZm9yIGxhcmdlci9tb3JlIGdlbmVyYWwgY2x1c3RlcnMuICAKClRoZW4sIHdoZW4gd2UgbG9vayBhdCB0aGUgbWV0YSBkYXRhIHdlIHNob3VsZCBzZWUgdGhhdCBjbHVzdGVyIGxhYmVscyBoYXZlIGJlZW4gYWRkZWQgZm9yIGVhY2ggY2VsbDoKCmBgYHtyLCBmaW5kX25laWdoYm9ycywgZXZhbCA9IEZBTFNFfQojIENsdXN0ZXIgUENBcyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBDcmVhdGUgS05OIGdyYXBoIHdpdGggYEZpbmROZWlnaGJvcnMoKWAKZ2VvX3NvID0gRmluZE5laWdoYm9ycyhnZW9fc28sIGRpbXMgPSAxOnBjcywgcmVkdWN0aW9uID0gJ2ludGVncmF0ZWQuc2N0LnJwY2EnKQoKIyBnZW5lcmF0ZSBjbHVzdGVycwpnZW9fc28gPSBGaW5kQ2x1c3RlcnMoZ2VvX3NvLCByZXNvbHV0aW9uID0gMC40LCBjbHVzdGVyLm5hbWUgPSAnaW50ZWdyYXRlZC5zY3QucnBjYS5jbHVzdGVycycpCgojIGxvb2sgYXQgbWV0YS5kYXRhIHRvIHNlZSBjbHVzdGVyIGxhYmVscwpoZWFkKGdlb19zb0BtZXRhLmRhdGEpCmBgYAoKYGBge3IsIGZpbmRfbmVpZ2hib3JzX2hpZGRlbiwgY2FjaGUgPSBUUlVFLCBjYWNoZS5sYXp5ID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBlY2hvPUZBTFNFfQojIENsdXN0ZXIgUENBcyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBDcmVhdGUgS05OIGdyYXBoIHdpdGggYEZpbmROZWlnaGJvcnMoKWAKZ2VvX3NvID0gRmluZE5laWdoYm9ycyhnZW9fc28sIGRpbXMgPSAxOnBjcywgcmVkdWN0aW9uID0gJ2ludGVncmF0ZWQuc2N0LnJwY2EnKQoKIyBnZW5lcmF0ZSBjbHVzdGVycwpnZW9fc28gPSBGaW5kQ2x1c3RlcnMoZ2VvX3NvLCByZXNvbHV0aW9uID0gMC40LCBjbHVzdGVyLm5hbWUgPSAnaW50ZWdyYXRlZC5zY3QucnBjYS5jbHVzdGVycycpCgojIGxvb2sgYXQgbWV0YS5kYXRhIHRvIHNlZSBjbHVzdGVyIGxhYmVscwpoZWFkKGdlb19zb0BtZXRhLmRhdGEpCmBgYAoKVGhlIHJlc3VsdCBvZiBgRmluZE5laWdoYm9ycygpYCBhZGRzIGdyYXBoIGluZm9ybWF0aW9uIHRvIHRoZSBgZ3JhcGhzYCBzbG90OgoKIVtJbWFnZTogU2NoZW1hdGljIGFmdGVyIEZpbmROZWlnaGJvcnMoKS5dKGltYWdlcy9zZXVyYXRfc2NoZW1hdGljL1NsaWRlOC5wbmcpCgo8YnI+CgpUaGUgcmVzdWx0IG9mIGBGaW5kQ2x1c3RlcnMoKWAgYWRkcyB0d28gY29sdW1ucyB0byB0aGUgYG1ldGEuZGF0YWAgdGFibGUgYW5kIGNoYW5nZXMgdGhlIGBhY3RpdmUuaWRlbnRgIHRvIHRoZSAic2V1cmF0X2NsdXN0ZXJzIiBjb2x1bW4uIEluIG90aGVyIHdvcmRzLCB0aGUgY2VsbHMgbm93IGJlbG9uZyB0byBjbHVzdGVycyByYXRoZXIgdGhhbiB0byB0aGVpciBgb3JpZy5pZGVudGAuCgohW0ltYWdlOiBTY2hlbWF0aWMgYWZ0ZXIgRmluZENsdXN0ZXJzKCkuXShpbWFnZXMvc2V1cmF0X3NjaGVtYXRpYy9TbGlkZTkucG5nKQoKR2VuZXJhbGx5IGl0J3MgcHJlZmVyYWJsZSB0byBlcnIgb24gdGhlIHNpZGUgb2YgdG9vIG1hbnkgY2x1c3RlcnMsIGFzIHRoZXkgY2FuIGJlIGNvbWJpbmVkIG1hbnVhbGx5IGluIGxhdGVyIHN0ZXBzLiBJbiBvdXIgZXhwZXJpZW5jZSwgdGhpcyBpcyBhbm90aGVyIHBhcmFtZXRlciB0aGF0IG9mdGVuIG5lZWRzIHRvIGJlIGl0ZXJhdGl2ZWx5IHJldmlzZWQgYW5kIHJldmlld2VkLiAKCiMjIyBSZXNvbHV0aW9uIHBhcmFtZXRlcnMgcmVjb21tZW5kYXRpb25zCjxkZXRhaWxzPgogICAgPHN1bW1hcnk+Kk1vcmUgZGV0YWlscyBvbiBjaG9vc2luZyBhIGNsdXN0ZXJpbmcgcmVzb2x1dGlvbio8L3N1bW1hcnk+CiAgICBUaGUgW1NldXJhdCBjbHVzdGVyaW5nIHR1dG9yaWFsXShodHRwczovL2hvbGFiLWhrdS5naXRodWIuaW8vRnVuZGFtZW50YWwtc2NSTkEvZG93bnN0cmVhbS5odG1sI3BlcmZvcm0tbGluZWFyLWRpbWVuc2lvbmFsLXJlZHVjdGlvbikgcmVjb21tZW5kcyBzZWxlY3RpbmcgYSByZXNvbHV0aW9uIGJldHdlZW4gMC40IC0gMS4yIGZvciBkYXRhc2V0cyBvZiBhcHByb3hpbWF0ZWx5IDNrIGNlbGxzLCB3aGlsZSB0aGUgW0hCQyBjb3Vyc2VdKGh0dHBzOi8vaGJjdHJhaW5pbmcuZ2l0aHViLmlvL3NjUk5BLXNlcV9vbmxpbmUvbGVzc29ucy8wN19TQ19jbHVzdGVyaW5nX2NlbGxzX1NDVC5odG1sKSByZWNvbW1lbmRzIDAuNC0xLjQgZm9yIDNrLTVrIGNlbGxzLiBIb3dldmVyLCBpbiBvdXIgZXhwZXJpZW5jZSByZWFzb25hYmxlIHN0YXJ0aW5nIHJlc29sdXRpb25zIGNhbiBiZSB2ZXJ5IGRhdGFzZXQgZGVwZW5kZW50Lgo8L2RldGFpbHM+Cjxicj4KCiMgQ2x1c3RlciBwbG90cyAKClRvIHZpc3VhbGl6ZSB0aGUgY2VsbCBjbHVzdGVycywgd2UgY2FuIHVzZSBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gdGVjaG5pcXVlcyB0byB2aXN1YWxpemUgYW5kIGV4cGxvcmUgb3VyIGxhcmdlLCBoaWdoLWRpbWVuc2lvbmFsIGRhdGFzZXQuIFR3byBwb3B1bGFyIG1ldGhvZHMgdGhhdCBhcmUgc3VwcG9ydGVkIGJ5IFNldXJhdCBhcmUgdC1kaXN0cmlidXRlZCBzdG9jaGFzdGljIG5laWdoYm9yIGVtYmVkZGluZyAodC1TTkUpIGFuZCBVbmlmb3JtIE1hbmlmb2xkIEFwcHJveGltYXRpb24gYW5kIFByb2plY3Rpb24gKFVNQVApIHRlY2huaXF1ZXMuIFRoZXNlIHRlY2huaXF1ZXMgYWxsb3cgdXMgdG8gdmlzdWFsaXplIG91ciBoaWdoLWRpbWVuc2lvbmFsIHNpbmdsZS1jZWxsIGRhdGEgaW4gMkQgc3BhY2UgYW5kIHNlZSBpZiBjZWxscyBncm91cGVkIHRvZ2V0aGVyIHdpdGhpbiBncmFwaC1iYXNlZCBjbHVzdGVycyBjby1sb2NhbGl6ZSBpbiB0aGVzZSByZXByZXNlbnRhdGlvbnMgKFtzb3VyY2VdKGh0dHBzOi8vc2F0aWphbGFiLm9yZy9zZXVyYXQvYXJ0aWNsZXMvcGJtYzNrX3R1dG9yaWFsLmh0bWwjcnVuLW5vbi1saW5lYXItZGltZW5zaW9uYWwtcmVkdWN0aW9uLXVtYXB0c25lKSkuCgpXaGlsZSB3ZSB1bmZvcnR1bmF0ZWx5IGRvbid0IGhhdmUgdGltZSB0byBjb21wYXJlIGFuZCBjb250cmFzdCB0U05FLCBhbmQgVU1BUCwgd2Ugd291bGQgaGlnaGx5IHJlY29tbWVuZCBbdGhpcyBibG9nIHBvc3QgY29udHJhc3RpbmcgdFNORSBhbmQgVU1BUF0oaHR0cHM6Ly9wYWlyLWNvZGUuZ2l0aHViLmlvL3VuZGVyc3RhbmRpbmctdW1hcC8pIGZvciBpbGx1c3RyYXRpdmUgZXhhbXBsZXMuIFRoZSBTZXVyYXQgYXV0aG9ycyBhZGRpdGlvbmFsbHkgY2F1dGlvbiB0aGF0IHdoaWxlIHRoZXNlIG1ldGhvZHMgYXJlIHVzZWZ1bCBmb3IgZGF0YSBleHBsb3JhdGlvbiwgdG8gYXZvaWQgZHJhd2luZyBiaW9sb2dpY2FsIGNvbmNsdXNpb25zIHNvbGVseSBiYXNlZCBvbiB0aGVzZSB2aXN1YWxpemF0aW9ucyAoW3NvdXJjZV0oaHR0cHM6Ly9zYXRpamFsYWIub3JnL3NldXJhdC9hcnRpY2xlcy9wYm1jM2tfdHV0b3JpYWwuaHRtbCNydW4tbm9uLWxpbmVhci1kaW1lbnNpb25hbC1yZWR1Y3Rpb24tdW1hcHRzbmUpKS4KClRvIHN0YXJ0IHRoaXMgcHJvY2Vzcywgd2UnbGwgdXNlIHRoZSBgUnVuVU1BUCgpYCBbZnVuY3Rpb25dKGh0dHBzOi8vc2F0aWphbGFiLm9yZy9zZXVyYXQvcmVmZXJlbmNlL3J1bnVtYXApIHRvIGNhbGN1bGF0ZSB0aGUgVU1BUCByZWR1Y3Rpb24gZm9yIG91ciBkYXRhLiBOb3RpY2UgaG93IHRoZSBwcmV2aW91cyBkaW1lbnNpb25hbGl0eSBjaG9pY2VzIGNhcnJ5IHRocm91Z2ggdGhlIGRvd25zdHJlYW0gYW5hbHlzaXMgYW5kIHRoYXQgdGhlIG51bWJlciBvZiBQQ3Mgc2VsZWN0ZWQgaW4gdGhlIHByZXZpb3VzIHN0ZXBzIGFyZSBpbmNsdWRlZCBhcyBhbiBhcmd1bWVudC4KCmBgYHtyLCBydW5fdW1hcCwgY2FjaGUgPSBUUlVFLCBjYWNoZS5sYXp5ID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQojIENyZWF0ZSBVTUFQIHJlZHVjdGlvbiAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KZ2VvX3NvID0gUnVuVU1BUChnZW9fc28sIGRpbXMgPSAxOnBjcywgcmVkdWN0aW9uID0gJ2ludGVncmF0ZWQuc2N0LnJwY2EnLCByZWR1Y3Rpb24ubmFtZSA9ICd1bWFwLmludGVncmF0ZWQuc2N0LnJwY2EnKQpnZW9fc28gIyBOb3RpY2UgYSB0aGlyZCByZWR1Y3Rpb24gaGFzIGJlZW4gYWRkZWQ6IGB1bWFwLmludGVncmF0ZWQuc2N0LnJwY2FgCmBgYAoKVGhlIHJlc3VsdGluZyBTZXVyYXQgb2JqZWN0IG5vdyBoYXMgYW4gYWRkaXRpb25hbCBgdW1hcC5pbnRlZ3JhdGVkLnNjdC5ycGNhYCBpbiB0aGUgYHJlZHVjdGlvbmAgc2xvdDoKCiFbSW1hZ2U6IFNjaGVtYXRpYyBhZnRlciBSdW5VTUFQKCkuXShpbWFnZXMvc2V1cmF0X3NjaGVtYXRpYy9TbGlkZTEwLnBuZykKCiMgVmlzdWFsaXppbmcgYW5kIGV2YWx1YXRpbmcgY2x1c3RlcmluZwoKPCEtLS0gSG93IG1hbnkgY2x1c3RlcnMgc2hvdWxkIEkgZ2V0IGFuZCBob3cgZG8gSSBhZGp1c3QgdGhlIG51bWJlcj8gLS0tPgoKPCEtLS0gQWRkIGV4YW1wbGUgb2YgY2hhbmdpbmcgcmVzb2x1dGlvbj8tLS0+CgpBZnRlciB3ZSBnZW5lcmF0ZSB0aGUgVU1BUCByZWR1Y3Rpb24sIHdlIGNhbiB0aGVuIHZpc3VhbGl6ZSB0aGUgcmVzdWx0cyB1c2luZyB0aGUgYERpbVBsb3QoKWAgW2Z1bmN0aW9uXShodHRwczovL3NhdGlqYWxhYi5vcmcvc2V1cmF0L3JlZmVyZW5jZS9kaW1wbG90KSwgbGFiZWxpbmcgb3VyIHBsb3QgYnkgdGhlIGF1dG8gZ2VuZXJhdGVkIGBzZXVyYXRfY2x1c3RlcnNgIHRoYXQgY29ycmVzcG9uZCB0byB0aGUgbW9zdCByZWNlbnQgY2x1c3RlcmluZyByZXN1bHRzIGdlbmVyYXRlZC4KCkF0IHRoaXMgc3RhZ2UsIHdlIHdhbnQgdG8gZGV0ZXJtaW5lIGlmIHRoZSBjbHVzdGVycyBsb29rIGZhaXJseSB3ZWxsIHNlcGFyYXRlZCwgaWYgdGhleSBzZWVtIHRvIGNvcnJlc3BvbmQgdG8gaG93IGNlbGxzIGFyZSBncm91cGVkIGluIHRoZSBVTUFQLCBhbmQgaWYgdGhlIG51bWJlciBvZiBjbHVzdGVycyBpcyBnZW5lcmFsbHkgYWxpZ25lZCB3aXRoIHRoZSByZXNvbHV0aW9uIG9mIG91ciBiaW9sb2dpY2FsIHF1ZXN0aW9uLiBBZ2FpbiwgaWYgdGhlcmUgYXJlICJ0b28gbWFueSIgY2x1c3RlcnMgdGhhdCdzIG5vdCBuZWNlc3NhcmlseSBhIHByb2JsZW0uCgpXZSBjYW4gYWxzbyBsb29rIGF0IHRoZSBzYW1lIFVNQVAgbGFiZWxlZCBieSBgZGF5YCB0byB2aXN1YWxseSBpbnNwZWN0IGlmIHRoZSBVTUFQIHN0cnVjdHVyZSBjb3JyZXNwb25kcyB0byB0aGUgYGRheWAuCgpgYGB7ciwgdW1hcF9ieV9jbHVzdGVycywgZmlnLnNob3c9J2hvbGQnfQojIFZpc3VhbGl6ZSBVTUFQIGNsdXN0ZXIgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBjbHVzdGVyIElEIGxhYmVscwpwb3N0X2ludGVncmF0aW9uX3VtYXBfcGxvdF9jbHVzdGVycyA9IERpbVBsb3QoZ2VvX3NvLCBncm91cC5ieSA9ICdzZXVyYXRfY2x1c3RlcnMnLCBsYWJlbCA9IFRSVUUsIHJlZHVjdGlvbiA9ICd1bWFwLmludGVncmF0ZWQuc2N0LnJwY2EnKSArIE5vTGVnZW5kKCkKcG9zdF9pbnRlZ3JhdGlvbl91bWFwX3Bsb3RfY2x1c3RlcnMKCmdnc2F2ZShmaWxlbmFtZSA9ICdyZXN1bHRzL2ZpZ3VyZXMvdW1hcF9pbnRlZ3JhdGVkX3NjdF9jbHVzdGVycy5wbmcnLCBwbG90ID0gcG9zdF9pbnRlZ3JhdGlvbl91bWFwX3Bsb3RfY2x1c3RlcnMsIHdpZHRoID0gNiwgaGVpZ2h0ID0gNiwgdW5pdHMgPSAnaW4nKQoKIyBjbHVzdGVycyB3aXRoIGxhYmVscywgc3BsaXQgYnkgY29uZGl0aW9uCnBvc3RfaW50ZWdyYXRpb25fdW1hcF9wbG90X3NwbGl0X2NsdXN0ZXJzID0gRGltUGxvdChnZW9fc28sIGdyb3VwLmJ5ID0gJ3NldXJhdF9jbHVzdGVycycsIHNwbGl0LmJ5ID0gJ2RheScsIGxhYmVsID0gVFJVRSwgcmVkdWN0aW9uID0gJ3VtYXAuaW50ZWdyYXRlZC5zY3QucnBjYScpICsgTm9MZWdlbmQoKQpwb3N0X2ludGVncmF0aW9uX3VtYXBfcGxvdF9zcGxpdF9jbHVzdGVycwoKZ2dzYXZlKGZpbGVuYW1lID0gJ3Jlc3VsdHMvZmlndXJlcy91bWFwX2ludGVncmF0ZWRfc2N0X3NwbGl0X2NsdXN0ZXJzLnBuZycsIHBsb3QgPSBwb3N0X2ludGVncmF0aW9uX3VtYXBfcGxvdF9jbHVzdGVycywgd2lkdGggPSAxNCwgaGVpZ2h0ID0gNiwgdW5pdHMgPSAnaW4nKQoKIyBVTUFQIHdpdGggZGF5IGxhYmVscyAobm90ZSAtIHdlIGFkZGVkIHRoaXMgY29sdW1uIHRvIHRoZSBtZXRhLWRhdGEgeWVzdGVyZGF5KQpwb3N0X2ludGVncmF0aW9uX3VtYXBfcGxvdF9kYXkgPSBEaW1QbG90KGdlb19zbywgZ3JvdXAuYnkgPSAnZGF5JywgbGFiZWwgPSBGQUxTRSwgcmVkdWN0aW9uID0gJ3VtYXAuaW50ZWdyYXRlZC5zY3QucnBjYScpCnBvc3RfaW50ZWdyYXRpb25fdW1hcF9wbG90X2RheQoKZ2dzYXZlKGZpbGVuYW1lID0gJ3Jlc3VsdHMvZmlndXJlcy91bWFwX2ludGVncmF0ZWRfc2N0X2RheS5wbmcnLCBwbG90ID0gcG9zdF9pbnRlZ3JhdGlvbl91bWFwX3Bsb3RfZGF5LCB3aWR0aCA9IDgsIGhlaWdodCA9IDYsIHVuaXRzID0gJ2luJykKYGBgCgpTaW1pbGFyIHRvIHRoZSBQQ0EgcGxvdHMsIHRoZSBgZGF5YCBsYWJlbGVkIFVNQVAgY2FuIHRlbGwgdXMgaWYgdGVjaG5pY2FsIHNvdXJjZXMgb2YgdmFyaWF0aW9uIG1pZ2h0IGJlIGRyaXZpbmcgb3Igc3RyYXRpZnlpbmcgdGhlIGNsdXN0ZXJzLCB3aGljaCB3b3VsZCBzdWdnZXN0IHRoYXQgdGhlIG5vcm1hbGl6YXRpb24gYW5kIGludGVncmF0aW9uIHN0ZXBzIHNob3VsZCBiZSByZXZpc3RlZCBiZWZvcmUgcHJvY2VlZGluZy4KCkFub3RoZXIgYXBwcm9hY2ggaXMgdG8gZXZhbHVhdGUgdGhlIG51bWJlciBvZiBjZWxscyBwZXIgY2x1c3RlciB1c2luZyB0aGUgYHRhYmxlKClgIGZ1bmN0aW9uLCBzcGxpdCBieSBgZGF5YCBvciBzcGxpdCBieSBgb3JpZy5pZGVudGAgdG8gc2VlIGlmIHRoZSBpbmRpdmlkdWFsIHNhbXBsZXMgYXJlIGRyaXZpbmcgYW55IG9mIHRoZSBVTUFQIHN0cnVjdHVyZToKCmBgYHtyLCB0YWJsZV9ieV9kYXksIGV2YWwgPSBGQUxTRX0KIyBNYWtlIHRhYmxlcyBvZiBjZWxsIGNvdW50cyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgY2VsbHMgcGVyIGNsdXN0ZXIsIHNwbGl0IGJ5IGNvbmRpdGlvbgp0YWJsZShnZW9fc29AbWV0YS5kYXRhJGRheSwgZ2VvX3NvQG1ldGEuZGF0YSRpbnRlZ3JhdGVkLnNjdC5ycGNhLmNsdXN0ZXJzKQoKIyBjZWxscyBwZXIgY2x1c3RlciBwZXIgc2FtcGxlCnRhYmxlKGdlb19zb0BtZXRhLmRhdGEkb3JpZy5pZGVudCwgZ2VvX3NvQG1ldGEuZGF0YSRpbnRlZ3JhdGVkLnNjdC5ycGNhLmNsdXN0ZXJzKQpgYGAKCmBgYHtyLCB0YWJsZV9ieV9kYXlfaGlkZGVuLCBlY2hvID0gRkFMU0V9CiMgTWFrZSB0YWJsZXMgb2YgY2VsbCBjb3VudHMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIGNlbGxzIHBlciBjbHVzdGVyLCBzcGxpdCBieSBjb25kaXRpb24KdGFibGUoZ2VvX3NvQG1ldGEuZGF0YSRkYXksIGdlb19zb0BtZXRhLmRhdGEkaW50ZWdyYXRlZC5zY3QucnBjYS5jbHVzdGVycykKCiMgY2VsbHMgcGVyIGNsdXN0ZXIgcGVyIHNhbXBsZQp0YWJsZShnZW9fc29AbWV0YS5kYXRhJG9yaWcuaWRlbnQsIGdlb19zb0BtZXRhLmRhdGEkaW50ZWdyYXRlZC5zY3QucnBjYS5jbHVzdGVycykKYGBgCgo8IS0tIGNvbnNpZGVyIGFkZGluZyBsYXRlciAKQWRkIERpbVBsb3QgZm9yICUgbWl0b2Nob25kcmlhIHRvIHNlZSBpZiBoaWdoICUgYXJlIGFjcm9zcyBkYXRhc2V0IG9yIGxpbWl0ZWQgdG8gb25lL2ZldyBjbHVzdGVycyAocmVsYXRlZCB0byB3aGF0IE9saXZpYSB0YWxrZWQgYWJvdXQgZHVyaW5nIERheSAxKQoKIyBPdGhlciBhcHByb2FjaGVzIGZvciB2aXN1YWxpemluZyBzY1JOQS1zcSBkYXRhCmUuZy4gY29kZSBmb3IgdFNORSB2aXN1YWxpemF0aW9uIAotLT4KCiMgQ29tcGFyaW5nIHRvIHVuaW50ZWdyYXRlZCBkYXRhCgpJZiB3ZSBoYWQgcHJvY2VlZGVkIHdpdGggb3VyIGZpbHRlcmVkIGRhdGEgYW5kIG9ubHkgbm9ybWFsaXplZCBvdXIgZGF0YSB3aXRob3V0IGRvaW5nIGFueSBpbnRlZ3JhdGlvbiwgaW5jbHVkaW5nIHRocm91Z2ggdGhlIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiBhbmQgY2x1c3RlcmluZyBzdGVwcyBhbmQgdGhlbiBsYWJlbGVkIHRoZSBjZWxscyB3aXRoIHRoZWlyIHNhbXBsZSBvZiBvcmlnaW4sIHRoZW4gd2Ugd291bGQgc2VlIHRoZSBmb2xsb3dpbmcgZm9yIG91ciBkYXRhOgoKPCEtLUFkZCB1cGRhdGVkIGV4YW1wbGUgb2YgVU1BUCAvIHNhbXBsZSBsYWJlbHMgZm9yIHVuaW50ZWdyYXRlZCBkYXRhIC0gbmVlZCB0byBjaGVjayB3aXRoIFJheW1vbmQgcmVnYXJkaW5nIGhvdyBiZXN0IHRvIGRvIHRoaXMgaW4gcGxhY2UgLS0+IAoKYGBge3IsIHJ1bl91bWFwX3VuaW50ZWdyYXRlZCwgZWNobyA9IEZBTFNFLCBjYWNoZSA9IFRSVUUsIGNhY2hlLmxhenkgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIG91dC5oZWlnaHQ9JzYwJSd9CiMgQ3JlYXRlIEtOTiBncmFwaCB3aXRoIGBGaW5kTmVpZ2hib3JzKClgIGZvciB1bmludGVncmF0ZWQgZGF0YQpnZW9fc28gPSBGaW5kTmVpZ2hib3JzKGdlb19zbywgZGltcyA9IDE6cGNzLCByZWR1Y3Rpb24gPSAndW5pbnRlZ3JhdGVkLnNjdC5wY2EnLCB2ZXJib3NlPUZBTFNFKQoKIyBnZW5lcmF0ZSBjbHVzdGVycyBmb3IgdW5pbnRlZ3JhdGVkIGRhdGEKZ2VvX3NvID0gRmluZENsdXN0ZXJzKGdlb19zbywgcmVzb2x1dGlvbiA9IDAuNCwgY2x1c3Rlci5uYW1lID0gJ3VuaW50ZWdyYXRlZC5zY3QucGNhLmNsdXN0ZXJzJykKCmdlb19zbyA9IFJ1blVNQVAoZ2VvX3NvLCBkaW1zID0gMTpwY3MsIHJlZHVjdGlvbiA9ICd1bmludGVncmF0ZWQuc2N0LnBjYScsIHJlZHVjdGlvbi5uYW1lID0gJ3VtYXAudW5pbnRlZ3JhdGVkLnNjdC5wY2EnKQoKcHJlX2ludGVncmF0aW9uX3VtYXBfcGxvdF9kYXkgPSBEaW1QbG90KGdlb19zbywgZ3JvdXAuYnkgPSAnZGF5JywgbGFiZWwgPSBGQUxTRSwgcmVkdWN0aW9uID0gJ3VtYXAudW5pbnRlZ3JhdGVkLnNjdC5wY2EnKQoKcHJpbnQocHJlX2ludGVncmF0aW9uX3VtYXBfcGxvdF9kYXkgKyBwb3N0X2ludGVncmF0aW9uX3VtYXBfcGxvdF9kYXkpCmBgYAoKSW4gdGhlIHBsb3QgYXQgbGVmdCwgd2Ugc2VlIHRoYXQgd2hpbGUgdGhlcmUgYXJlIGRpc3RpbmN0IGNsdXN0ZXJzLCB0aG9zZSBjbHVzdGVycyBzZWVtIHRvIHN0cmF0aWZpZWQgYnkgZGF5LiBUaGlzIHN1Z2dlc3RzIHRoYXQgd2l0aG91dCBpbnRlZ3JhdGlvbiwgdGhlc2UgYmF0Y2ggZWZmZWN0cyBjb3VsZCBza2V3IHRoZSBiaW9sb2dpY2FsIHZhcmlhYmlsaXR5IGluIG91ciBkYXRhLiBXaGlsZSBvbiB0aGUgcmlnaHQsIHdlIHNlZSBsaXR0bGUgc3RyYXRpZmljYXRpb24gd2l0aGluIG91ciBjbHVzdGVycyB3aGljaCBtZWFucyB0aGUgaW50ZWdyYXRpb24gc2VlbXMgdG8gaGF2ZSByZW1vdmVkIHRob3NlIGJhdGNoIGVmZmVjdHMuCgo8IS0tIEVkaXQgc2VjdGlvbiB0byBpbmNvcnBvcmF0ZSAtLS0+Cgo8ZGV0YWlscz4KPHN1bW1hcnk+KipSZXdpbmQ6IFByZS1pbnRlZ3JhdGlvbiBldmFsdWF0aW9uIGNsdXN0ZXJpbmcgYW5kIHZpc3VhbGl6YXRpb24gKGNvZGUpKio8L3N1bW1hcnk+ClByaW9yIHRvIGludGVncmF0aW9uLCB3ZSBjb3VsZCBmb2xsb3cgdGhlIHNhbWUgc3RlcHMgd2UndmUganVzdCBydW4gZm9yIHRoZSBpbnRlZ3JhdGVkIHRvIHNlZSBpZiB0aGUgcmVzdWx0aW5nIGNsdXN0ZXJzIHRlbmQgdG8gYmUgZGV0ZXJtaW5lZCBieSBzYW1wbGUgb3IgY29uZGl0aW9uIChpbiB0aGlzIGNhc2UsIHRoZSBkYXkpOgoKYGBge3IsIGV2YWw9RkFMU0V9Cmdlb19zbyA9IEZpbmROZWlnaGJvcnMoZ2VvX3NvLCBkaW1zID0gMTpwY3MsIGFzc2F5ID0gJ1JOQScsIHJlZHVjdGlvbiA9ICd1bmludGVncmF0ZWQuc2N0LnBjYScsIGdyYXBoLm5hbWUgPSBjKCdSTkFfbm4nLCAnUk5BX3NubicpKQpnZW9fc28gPSBGaW5kQ2x1c3RlcnMoZ2VvX3NvLCByZXNvbHV0aW9uID0gMC40LCBncmFwaC5uYW1lID0gJ1JOQV9zbm4nLCBjbHVzdGVyLm5hbWUgPSAndW5pbnRlZ3JhdGVkLnNjdC5jbHVzdGVycycpCmdlb19zbyA9IFJ1blVNQVAoZ2VvX3NvLCBkaW1zID0gMTpwY3MsIHJlZHVjdGlvbiA9ICd1bmludGVncmF0ZWQuc2N0LnBjYScsIHJlZHVjdGlvbi5uYW1lID0gJ3VtYXAudW5pbnRlZ3JhdGVkLnNjdC5wY2EnKQpgYGAKCQpUaGUgcGxvdHMgYWJvdmUgd2VyZSBnZW5lcmF0ZWQgd2l0aDoKCmBgYHtyLCBldmFsPUZBTFNFfQojIENvZGUgYmxvY2sgLSBzaG93IHVuaW50ZWdyYXRlZApwcmVfaW50ZWdyYXRpb25fdW1hcF9wbG90X29yaWcuaWRlbnQgPSBEaW1QbG90KGdlb19zbywgZ3JvdXAuYnkgPSAnb3JpZy5pZGVudCcsIGxhYmVsID0gRkFMU0UsIHJlZHVjdGlvbiA9ICd1bWFwLnVuaW50ZWdyYXRlZC5zY3QucGNhJykKZ2dzYXZlKGZpbGVuYW1lID0gJ3Jlc3VsdHMvZmlndXJlcy91bWFwX3VuaW50ZWdyYXRlZF9zY3Rfb3JpZy5pZGVudC5wbmcnLCBwbG90ID0gcHJlX2ludGVncmF0aW9uX3VtYXBfcGxvdF9vcmlnLmlkZW50LCB3aWR0aCA9IDgsIGhlaWdodCA9IDYsIHVuaXRzID0gJ2luJykKCnByZV9pbnRlZ3JhdGlvbl91bWFwX3Bsb3RfZGF5ID0gRGltUGxvdChnZW9fc28sIGdyb3VwLmJ5ID0gJ2RheScsIGxhYmVsID0gRkFMU0UsIHJlZHVjdGlvbiA9ICd1bWFwLnVuaW50ZWdyYXRlZC5zY3QucGNhJykKZ2dzYXZlKGZpbGVuYW1lID0gJ3Jlc3VsdHMvZmlndXJlcy91bWFwX3VuaW50ZWdyYXRlZF9zY3RfZGF5LnBuZycsIHBsb3QgPSBwcmVfaW50ZWdyYXRpb25fdW1hcF9wbG90X2RheSwgd2lkdGggPSA4LCBoZWlnaHQgPSA2LCB1bml0cyA9ICdpbicpCmBgYAo8L2RldGFpbHM+Cjxicj4KPGJyPgoKPGRldGFpbHM+CjxzdW1tYXJ5PioqQWx0ZXJuYXRpdmUgY2x1c3RlcmluZyByZXNvbHV0aW9ucyoqPC9zdW1tYXJ5PgpXaGlsZSB3ZSBzaG93IGEgc2luZ2xlIHJlc29sdXRpb24sIHdlIGNhbiBnZW5lcmF0ZSBhbmQgcGxvdCBtdWx0aXBsZSByZXNvbHV0aW9ucyBpdGVyYXRpdmVseSBhbmQgY29tcGFyZSBiZXR3ZWVuIHRoZW0gYmVmb3JlIHNlbGVjdGluZyBhIGNsdXN0ZXJpbmcgcmVzdWx0IGZvciB0aGUgbmV4dCBzdGVwczoKCmBgYHtyLCBldmFsPUZBTFNFfQpyZXNvbHV0aW9ucyA9IGMoMC40LCAwLjgpCgpmb3IocmVzIGluIHJlc29sdXRpb25zKSB7CiAgICBtZXNzYWdlKHJlcykKCiAgICBjbHVzdGVyX2NvbHVtbiA9IHNwcmludGYoJ1NDVF9zbm5fcmVzLiVzJywgcmVzKQogICAgdW1hcF9maWxlID0gc3ByaW50ZigncmVzdWx0cy9maWd1cmVzL3VtYXBfaW50ZWdyYXRlZF9zY3RfJXMucG5nJywgcmVzKQoKICAgIGdlb19zbyA9IEZpbmRDbHVzdGVycyhnZW9fc28sIHJlc29sdXRpb24gPSByZXMpCgogICAgRGltUGxvdChnZW9fc28sIGdyb3VwLmJ5ID0gY2x1c3Rlcl9jb2x1bW4sIGxhYmVsID0gVFJVRSwgcmVkdWN0aW9uID0gJ3VtYXAuaW50ZWdyYXRlZC5zY3QucnBjYScpICsgTm9MZWdlbmQoKQogICAgZ2dzYXZlKGZpbGVuYW1lID0gdW1hcF9maWxlLCB3aWR0aCA9IDgsIGhlaWdodCA9IDcsIHVuaXRzID0gJ2luJykKfQpgYGAKCkluIHRoZSByZXN1bHRzLCB3ZSdsbCBzZWUgbXVsdGlwbGUgcmVzb2x1dGlvbnMgc2hvdWxkIG5vdyBiZSBhZGRlZCB0byB0aGUgbWV0YWRhdGEgc2xvdC4KCmBgYHtyLCBldmFsPUZBTFNFfQpoZWFkKGdlb19zb0BtZXRhLmRhdGEpCmBgYAo8L2RldGFpbHM+Cjxicj4KPGJyPgoKIyBTYXZlIG91ciBwcm9ncmVzcwoKQmVmb3JlIG1vdmluZyBvbiB0byBvdXIgbmV4dCBzZWN0aW9uLCB3ZSB3aWxsIG91dHB1dCBvdXIgdXBkYXRlZCBTZXVyYXQgb2JqZWN0IHRvIGZpbGU6CgpgYGB7ciwgc2F2ZV9yZHNfaGlkZGVuLCBlY2hvID0gRkFMU0V9CmlmKCFmaWxlLmV4aXN0cygncmVzdWx0cy9yZGF0YS9nZW9fc29fc2N0X2NsdXN0ZXJlZC5yZHMnKSkgewogIHNhdmVSRFMoZ2VvX3NvLCBmaWxlID0gJ3Jlc3VsdHMvcmRhdGEvZ2VvX3NvX3NjdF9jbHVzdGVyZWQucmRzJykKfQpgYGAKCmBgYHtyLCBldmFsPUZBTFNFfQojIFNhdmUgU2V1cmF0IG9iamVjdCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0Kc2F2ZVJEUyhnZW9fc28sIGZpbGUgPSAncmVzdWx0cy9yZGF0YS9nZW9fc29fc2N0X2NsdXN0ZXJlZC5yZHMnKQpgYGAKCiMgU3VtbWFyeQoKSW4gdGhpcyBzZWN0aW9uIHdlOgoKLSBHZW5lcmF0ZWQgY2x1c3RlciBhc3NpZ25tZW50cyBmb3Igb3VyIGNlbGxzIHVzaW5nIGBGaW5kTmVpZ2hib3JzKClgIGFuZCBgRmluZENsdXN0ZXJzKClgCi0gRXZhbHVhdGVkIG91ciBpbml0aWFsIGNsdXN0ZXJzIHVzaW5nIGBSdW5VTUFQYCBkaW1lbnNpb25hbCByZWR1Y3Rpb24gYW5kIHZpc3VhbGl6YXRpb24KCk5leHQgc3RlcHM6IE1hcmtlciBnZW5lcwoKLS0tLQoKVGhlc2UgbWF0ZXJpYWxzIGhhdmUgYmVlbiBhZGFwdGVkIGFuZCBleHRlbmRlZCBmcm9tIG1hdGVyaWFscyBsaXN0ZWQgYWJvdmUuIFRoZXNlIGFyZSBvcGVuIGFjY2VzcyBtYXRlcmlhbHMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBbQ3JlYXRpdmUgQ29tbW9ucyBBdHRyaWJ1dGlvbiBsaWNlbnNlIChDQyBCWSA0LjApXShodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9saWNlbnNlcy9ieS80LjAvKSwgd2hpY2ggcGVybWl0cyB1bnJlc3RyaWN0ZWQgdXNlLCBkaXN0cmlidXRpb24sIGFuZCByZXByb2R1Y3Rpb24gaW4gYW55IG1lZGl1bSwgcHJvdmlkZWQgdGhlIG9yaWdpbmFsIGF1dGhvciBhbmQgc291cmNlIGFyZSBjcmVkaXRlZC4KCjxici8+Cjxici8+Cjxoci8+CnwgW1ByZXZpb3VzIGxlc3Nvbl0oMDQtUENBYW5kSW50ZWdyYXRpb24uaHRtbCkgfCBbVG9wIG9mIHRoaXMgbGVzc29uXSgjdG9wKSB8IFtOZXh0IGxlc3Nvbl0oMDYtTWFya2VyVmlzdWFsaXphdGlvbi5odG1sKSB8CnwgOi0tLSB8IDotLS0tOiB8IC0tLTogfAo=