Introduction

In order to aid the interpretation of the clustering results, it is helpful to identify genes that are contributing to the separation of cells into those clusters.

The main approach to achieve this, is to identify genes that are differently expressed between clusters. These may be, for example, exclusively expressed in a single cluster or perhaps differentiate between a few different clusters. There are different methods to identify differentially expressed genes, focusing on differences between the mean expression level, the differences in the ranking of genes by expression between clusters or the differences between clusters of the proportions of cells expressing a gene.

Our main objective in this section is to cover some of the methods that can be used to achieve this goal, and obtain a summary table of results.

As always, the OSCA chapter on marker detection contains additional detail and advice.

Setup

Before we start, let’s load our packages and read our data in.

library(scater)
library(scran)
library(pheatmap)
library(tidyverse)

We will load two SingleCellExperiment objects generated in previous sections:

uncorrected <- readRDS("../Robjects/DataIntegration_all_sce_dimred.Rds")
rownames(uncorrected) <- uniquifyFeatureNames(rownames(uncorrected), rowData(uncorrected)$Symbol)

# data corrected using batch integration with mutual nearest neighbours
corrected <- readRDS("../Robjects/Caron_clustering_material.rds")
rownames(corrected) <- uniquifyFeatureNames(rownames(corrected), rowData(corrected)$Symbol)

Note that we also replace the gene names (rownames) of our objects to use common gene symbols instead of Ensembl IDs. Using the uniquifyFeatureNames() function makes this safely, avoiding duplicated gene symbols.

As a reminder, the corrected object contains the cell cluster assignments obtained in a colData column called “label”:

# visualise cluster assignments on the corrected data
plotTSNE(corrected, 
         colour_by = "label", 
         text_by = "label")

It will also be helpful for data visualisation purposes to copy the cluster assignment in the corrected data to our uncorrected (normalised) SCE object:

# first make sure that the cell names are in the same order
all(colnames(uncorrected) == colnames(corrected))
[1] TRUE
colLabels(uncorrected) <- colLabels(corrected)

Identifying cluster marker genes

Although we have defined our clusters based on the batch-corrected expression values, these should not be used for for gene-based analyses like marker gene detection.

Instead, we should use the uncorrected (normalised) expression values for differential expression between clusters.

The reason for this is that batch correction algorithms do not necessarily preserve the magnitude (or even direction) of the differences in gene expression when attempting to integrate multiple batches. For example, the cosine normalization used by the “Mutual Nearest Neighbours” algorithm (fastMNN()) shrinks the magnitude of the expression values so that the computed log-fold changes have no obvious interpretation. Of greater concern is the possibility that the correction introduces artificial agreement across batches.

In our case, the valid assay to use in differential analysis tests is the logcounts obtained from the deconvolution method (using computePooledFactors & logNormCounts).

Pairwise comparisons for scoring potential marker genes

The basic approach for marker gene identification across clusters is to perform statistical tests for each gene between every pair of clusters based on gene expression. We use scoreMarkers() function to do this. In addition to computing various scores, the function can take account of known batch effects. The function outputs a list of DataFrames, with one DataFrame of scores for each cluster. However, note that the blocking assumes that each pair of clusters is present in at least one of the blocks. If there are two clusters which are not both present in at least one block (in our cases Samples), then that pairwise comparison will by necessity be omitted.

markers <- scoreMarkers(uncorrected, 
                        groups = factor(uncorrected$label), 
                        block =uncorrected$SampleName)

head(markers[["8"]], n=2)
DataFrame with 2 rows and 19 columns
           self.average other.average self.detected other.detected mean.logFC.cohen min.logFC.cohen median.logFC.cohen
              <numeric>     <numeric>     <numeric>      <numeric>        <numeric>       <numeric>          <numeric>
AL627309.5 -0.000708272  -0.000842074     0.0000000    2.14173e-05        0.0419331      -0.0412962          0.0271537
LINC01409   0.017808816   0.025250072     0.0311542    2.70209e-02        0.0251980      -0.1367201          0.0221337
           max.logFC.cohen rank.logFC.cohen  mean.AUC   min.AUC median.AUC   max.AUC  rank.AUC mean.logFC.detected
                 <numeric>        <integer> <numeric> <numeric>  <numeric> <numeric> <integer>           <numeric>
AL627309.5        0.185322             4024  0.503696  0.499629   0.502047  0.513969      4750           0.1162902
LINC01409         0.258749             4376  0.504467  0.486847   0.505058  0.520581      6109           0.0536382
           min.logFC.detected median.logFC.detected max.logFC.detected rank.logFC.detected
                    <numeric>             <numeric>          <numeric>           <integer>
AL627309.5          -0.152179             0.0369594           0.614644                4438
LINC01409           -0.907950             0.0942543           0.538028                4999

This DataFrame contains the results for cluster 8. The first four columns contain summary statistics:

  • self.average - the mean log-expression in the cluster of interest
  • other.average - the grand mean across all other clusters
  • self.detected - the proportion of cells with detected expression in the cluster of interest
  • other.detected - the mean detected proportion across all other clusters.

The remaining columns contain summaries of three scores from the pairwise comparisons. The three scores are:

  • logFC.cohen - “Cohen’s d” - this is the log fold change of mean gene expression that has been standardized by scaling by the average of the standard deviation across the groups. This can be interpreted in a similar way to log fold change in that a positive value indicates upregulation in the cluster of interest.
  • AUC - “Area Under the Curve” - this quantifies the ability to distinguish between two gene expression distributions. It can be interpreted as the likelihood that any random cell in the cluster of interest will have a higher expression of the gene than any random cell in the other cluster. It ranges from 0 to 1, where 1 can be interpreted as upregulation, 0 downregulation, and 0.5 as no difference.
  • logFC.detected - this is the log fold change in the proportion of cells in which the gene is detected in the cluster of interest versus the proportion of cells in which the gene is detected in the other cluster. Positive values indicate that the gene is detected in a more cells in the cluster of interest than the other cluster. Note this takes no account of the magnitude of the gene expression.

More detail on the differences between these effect size scores can be found in the “Advanced” Marker detection chapter of the OSCA book.

Whilst all the pairwise scores can be retrieved by adding the argument full.stats=TRUE to the scoreMarkers command, by default scoreMarkers returns 5 summary statistics for each score:

  • mean.X - this is the mean of the score across all pairwise comparisons. It gives the relative expression of the gene versus the average of the other clusters.
  • min.X - this is the minimum score obtained across all pairwise comparisons. This is the most stringent summary statistic for detecting upregulated genes, if the score is high, then the gene is upregulated in the cluster of interest relative to all other clusters. Conversely, if the minimum is low, then the gene is downregulated relative to at least one other cluster.
  • median.X - this is the median of the score across all pairwise comparisons. It is more robust to outliers than the mean. Whilst this is beneficial in avoiding the situation where the effect size is very large in only a small number of comparisons, it may conversely be detrimental to marker gene selection if there are small number of clusters with very similar effect sizes, as these will be effectively ignored.
  • max.X - this is the maximum score obtained across all pairwise comparisons. This is the least stringent summary statistic for detecting upregulated genes as a high score only indicates that the gene is upregulated in the cluster of interest relative to at least one other clusters. Conversely, if the maximum is low, then the gene is downregulated relative to all other clusters.
  • rank.X - This is the minimum ranking (“min-rank”) of that gene by that score across all clusters. For each pairwise comparison the genes are ranked according to the score, this summary provides the lowest rank for that gene across all comparisons. Essentially, a gene with a low “min-rank” will be one of the best genes according to that score in at least one comparison.

The choice of the summary used for ranking will effect the stringency of the selection. See the the OSCA books “Basic” chapter on Marker gene detection for further discussion of these different summaries. In general the mean and median make reasonable defaults for most applications. In practice, the minimum and maximum are most helpful for diagnosing discrepancies between the mean and median, rather than being used directly for ranking.

Selecting genes based on a given min-rank, say 5, is useful as it will generate a list of genes that is the union of genes with a rank of 5 or less for any pairwise comparison.This will ensure we get at least 5 genes that distinguish the cluster of interest from all other clusters.

For example using the min-rank for Cohen’s d on cluster 11 yields 19 marker genes:

topRanked <- markers[["11"]] %>%
  as.data.frame() %>% 
  select(contains("cohen")) %>% 
  filter(rank.logFC.cohen <= 5) %>% 
  arrange(rank.logFC.cohen)
topRanked

We can then plot these on a heatmap to visualize the gene expression differences:

plotGroupedHeatmap(uncorrected, 
                   features=rownames(topRanked), 
                   group="label", 
                   block="SampleName",
                   center=TRUE, 
                   zlim=c(-3, 3))

We can plot the distribution of expression of specific genes as we have previously, e.g. FCGR3A (CD16), a monocyte marker:

plotTSNE(corrected, 
         by_exprs_values = "reconstructed", 
         colour_by = "FCGR3A",
         text_by = "label")

Adjusting the log-fold change

The AUC and Cohen’s d scores incorporate both the gene expression differences between the clusters and the variance in gene expression scores within each cluster. If a gene with low variance it is possible that it will be ranked highly even if the magnitude of the difference between the clusters is low. These genes will not necessarily make good marker genes. It may therefore be desirable to favour the detection of genes with larger log-fold changes. A log-fold change threshold can be set using the lfc= argument in score markers.

Consider for example cluster 6. The gene RPS18, a ribosomal protein gene, is identified as a good marker for this clusters based on a min-rank for Cohen’s d of 2.

topRanked <- markers[["6"]] %>%
  as.data.frame() %>% 
  select(contains("cohen")) %>% 
  filter(rank.logFC.cohen <= 5) %>% 
  arrange(rank.logFC.cohen)
topRanked

However, if we are interested in finding strong positive markers, a look at the expression shows that it would not really be a good positive marker as fold-change difference between it and other clusters is not great.

plotExpression(uncorrected, 
               features = "RPS18", 
               x = "label")

We can select for strong positive markers by setting an lfc threshold of 2:

markerslfc <- scoreMarkers(uncorrected, 
                        groups = factor(uncorrected$label), 
                        block =uncorrected$SampleName,
                        lfc=2)

RPS18 no longer appears in the candidate cluster marker genes list by min-rank of Cohen’s d.

topRanked <- markerslfc[["6"]] %>%
  as.data.frame() %>% 
  select(contains("cohen")) %>% 
  filter(rank.logFC.cohen <= 5) %>% 
  arrange(rank.logFC.cohen)
topRanked

In fact it’s min-rank for Cohen’s d has dropped to:

markerslfc[["6"]]["RPS18", "rank.logFC.cohen"]
[1] 57

From the OSCA book:

Given that scoreMarkers() already reports effect sizes, it is tempting to take the next step and obtain p-values for the pairwise comparisons. Unfortunately, the p-values from the relevant tests cannot be reliably used to reject the null hypothesis. This is because DE analysis is performed on the same data used to obtain the clusters, which represents “data dredging” (also known as fishing or data snooping). The hypothesis of interest - are there differences between clusters? - is formulated from the data, so we are more likely to get a positive result when we re-use the data set to test that hypothesis.

More details here

Session information

sessionInfo()
sessionInfo()
R version 4.1.1 (2021-08-10)
Platform: x86_64-apple-darwin17.0 (64-bit)
Running under: macOS Big Sur 11.5.2

Matrix products: default
LAPACK: /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRlapack.dylib

locale:
[1] en_GB.UTF-8/en_GB.UTF-8/en_GB.UTF-8/C/en_GB.UTF-8/en_GB.UTF-8

attached base packages:
[1] parallel  stats4    stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] forcats_0.5.1               stringr_1.4.0               dplyr_1.0.8                 purrr_0.3.4                
 [5] readr_2.1.2                 tidyr_1.2.0                 tibble_3.1.6                tidyverse_1.3.1            
 [9] patchwork_1.1.1             pheatmap_1.0.12             igraph_1.2.11               cluster_2.1.2              
[13] bluster_1.2.1               scran_1.20.1                scater_1.20.1               ggplot2_3.3.5              
[17] scuttle_1.2.1               SingleCellExperiment_1.14.1 SummarizedExperiment_1.22.0 Biobase_2.52.0             
[21] GenomicRanges_1.44.0        GenomeInfoDb_1.28.4         IRanges_2.26.0              S4Vectors_0.30.2           
[25] BiocGenerics_0.38.0         MatrixGenerics_1.4.3        matrixStats_0.61.0          DT_0.21                    
[29] knitr_1.37                 

loaded via a namespace (and not attached):
 [1] ggbeeswarm_0.6.0          colorspace_2.0-3          ellipsis_0.3.2            XVector_0.32.0           
 [5] BiocNeighbors_1.10.0      fs_1.5.2                  rstudioapi_0.13           farver_2.1.0             
 [9] fansi_1.0.2               lubridate_1.8.0           xml2_1.3.3                sparseMatrixStats_1.4.2  
[13] jsonlite_1.8.0            broom_0.7.12              dbplyr_2.1.1              compiler_4.1.1           
[17] httr_1.4.2                dqrng_0.3.0               backports_1.4.1           assertthat_0.2.1         
[21] Matrix_1.4-0              fastmap_1.1.0             limma_3.48.3              cli_3.2.0                
[25] BiocSingular_1.8.1        htmltools_0.5.2           tools_4.1.1               rsvd_1.0.5               
[29] gtable_0.3.0              glue_1.6.2                GenomeInfoDbData_1.2.6    Rcpp_1.0.8               
[33] cellranger_1.1.0          jquerylib_0.1.4           vctrs_0.3.8               crosstalk_1.2.0          
[37] DelayedMatrixStats_1.14.3 xfun_0.30                 beachmat_2.8.1            rvest_1.0.2              
[41] lifecycle_1.0.1           irlba_2.3.5               statmod_1.4.36            edgeR_3.34.1             
[45] zlibbioc_1.38.0           scales_1.1.1              hms_1.1.1                 RColorBrewer_1.1-2       
[49] yaml_2.3.5                gridExtra_2.3             sass_0.4.0                stringi_1.7.6            
[53] ScaledMatrix_1.0.0        BiocParallel_1.26.2       rlang_1.0.2               pkgconfig_2.0.3          
[57] bitops_1.0-7              evaluate_0.15             lattice_0.20-45           labeling_0.4.2           
[61] htmlwidgets_1.5.4         cowplot_1.1.1             tidyselect_1.1.2          magrittr_2.0.2           
[65] R6_2.5.1                  generics_0.1.2            metapod_1.0.0             DelayedArray_0.18.0      
[69] DBI_1.1.2                 pillar_1.7.0              haven_2.4.3               withr_2.5.0              
[73] RCurl_1.98-1.6            modelr_0.1.8              crayon_1.5.0              utf8_1.2.2               
[77] rmarkdown_2.12            tzdb_0.2.0                viridis_0.6.2             locfit_1.5-9.5           
[81] grid_4.1.1                readxl_1.3.1              reprex_2.0.1              digest_0.6.29            
[85] munsell_0.5.0             beeswarm_0.4.0            viridisLite_0.4.0         vipor_0.4.5              
[89] bslib_0.3.1              
LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIHNpbmdsZS1jZWxsIFJOQS1zZXEgYW5hbHlzaXMiCnN1YnRpdGxlOiAiQ2x1c3RlciBtYXJrZXIgZ2VuZXMiCmRhdGU6ICJNYXkgMjAyMSIKYXV0aG9yOiAiQXNobGV5IFNhd2xlLCBTdGVwaGFuZSBCYWxsZXJlYXUsIFpleW5lcCBLYWxlbmRlciBBdGFrLCBIdWdvIFRhdmFyZXMiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogMgogICAgY3NzOiAuLi9jc3MvYm94ZXMuY3NzCi0tLQoKCmBgYHtyIHNldHVwLCBlY2hvPUZBTFNFLCBpbmNsdWRlPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBwdXJsPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZXJyb3I9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUUpCiMga25pdHI6Om9wdHNfY2h1bmskc2V0KGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTcpCiMga25pdHI6Om9wdHNfY2h1bmskc2V0KGRldj0iQ2Fpcm9QTkciKQpzZXQuc2VlZCgxMjMpCmBgYAoKCiMgSW50cm9kdWN0aW9uCgpJbiBvcmRlciB0byBhaWQgdGhlIGludGVycHJldGF0aW9uIG9mIHRoZSBjbHVzdGVyaW5nIHJlc3VsdHMsIGl0IGlzIGhlbHBmdWwgdG8KaWRlbnRpZnkgZ2VuZXMgdGhhdCBhcmUgY29udHJpYnV0aW5nIHRvIHRoZSBzZXBhcmF0aW9uIG9mIGNlbGxzIGludG8gdGhvc2UKY2x1c3RlcnMuCgpUaGUgbWFpbiBhcHByb2FjaCB0byBhY2hpZXZlIHRoaXMsIGlzIHRvIGlkZW50aWZ5IGdlbmVzIHRoYXQgYXJlIGRpZmZlcmVudGx5CmV4cHJlc3NlZCBiZXR3ZWVuIGNsdXN0ZXJzLiBUaGVzZSBtYXkgYmUsIGZvciBleGFtcGxlLCBleGNsdXNpdmVseSBleHByZXNzZWQgaW4KYSBzaW5nbGUgY2x1c3RlciBvciBwZXJoYXBzIGRpZmZlcmVudGlhdGUgYmV0d2VlbiBhIGZldyBkaWZmZXJlbnQgY2x1c3RlcnMuClRoZXJlIGFyZSBkaWZmZXJlbnQgbWV0aG9kcyB0byBpZGVudGlmeSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMsIGZvY3VzaW5nCm9uIGRpZmZlcmVuY2VzIGJldHdlZW4gdGhlIG1lYW4gZXhwcmVzc2lvbiBsZXZlbCwgdGhlIGRpZmZlcmVuY2VzIGluIHRoZSByYW5raW5nCm9mIGdlbmVzIGJ5IGV4cHJlc3Npb24gYmV0d2VlbiBjbHVzdGVycyBvciB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBjbHVzdGVycyBvZgp0aGUgcHJvcG9ydGlvbnMgb2YgY2VsbHMgZXhwcmVzc2luZyBhIGdlbmUuCgpPdXIgbWFpbiBvYmplY3RpdmUgaW4gdGhpcyBzZWN0aW9uIGlzIHRvIGNvdmVyIHNvbWUgb2YgdGhlIG1ldGhvZHMgdGhhdCBjYW4gYmUKdXNlZCB0byBhY2hpZXZlIHRoaXMgZ29hbCwgYW5kIG9idGFpbiBhIHN1bW1hcnkgdGFibGUgb2YgcmVzdWx0cy4KCkFzIGFsd2F5cywgdGhlIFtPU0NBIGNoYXB0ZXIgb24gbWFya2VyIGRldGVjdGlvbl0oaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL2Jvb2tzL3JlbGVhc2UvT1NDQS5iYXNpYy9tYXJrZXItZGV0ZWN0aW9uLmh0bWwpIApjb250YWlucyBhZGRpdGlvbmFsIGRldGFpbCBhbmQgYWR2aWNlLgoKIyBTZXR1cAoKQmVmb3JlIHdlIHN0YXJ0LCBsZXQncyBsb2FkIG91ciBwYWNrYWdlcyBhbmQgcmVhZCBvdXIgZGF0YSBpbi4gCgpgYGB7ciBwYWNrYWdlcywgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoc2NhdGVyKQpsaWJyYXJ5KHNjcmFuKQpsaWJyYXJ5KHBoZWF0bWFwKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgpXZSB3aWxsIGxvYWQgdHdvIF9TaW5nbGVDZWxsRXhwZXJpbWVudF8gb2JqZWN0cyBnZW5lcmF0ZWQgaW4gcHJldmlvdXMgc2VjdGlvbnM6CgoqIFRoZSBTQ0Ugd2l0aCBsb2djb3VudHMgbm9ybWFsaXNlZCB3aXRoaW4gZWFjaCBiYXRjaCBnZW5lcmF0ZWQgaW4gdGhlIFtEYXRhc2V0CkludGVncmF0aW9uIGFuZCBCYXRjaCBDb3JyZWN0aW9uXSgwN19EYXRhc2V0SW50ZWdyYXRpb24uaHRtbCkgc2VjdGlvbgoqIFRoZSBTQ0Ugd2l0aCBiYXRjaC1jb3JyZWN0ZWQgZGF0YSBpbnRlZ3JhdGlvbiB1c2luZyB0aGUgYGJhdGNoZWxvcjo6ZmFzdE1OTigpYAphbGdvcml0aG0sIGluY2x1ZGluZyBjbHVzdGVycyBjb3ZlcmVkIGluIHRoZSBbQ2x1c3RlcmluZ10oMDhfQ2x1c3RlcmluZ19NYXRlcmlhbHMubmIuaHRtbCkKc2VjdGlvbi4gIAoKYGBge3J9CnVuY29ycmVjdGVkIDwtIHJlYWRSRFMoIi4uL1JvYmplY3RzL0RhdGFJbnRlZ3JhdGlvbl9hbGxfc2NlX2RpbXJlZC5SZHMiKQpyb3duYW1lcyh1bmNvcnJlY3RlZCkgPC0gdW5pcXVpZnlGZWF0dXJlTmFtZXMocm93bmFtZXModW5jb3JyZWN0ZWQpLCByb3dEYXRhKHVuY29ycmVjdGVkKSRTeW1ib2wpCgojIGRhdGEgY29ycmVjdGVkIHVzaW5nIGJhdGNoIGludGVncmF0aW9uIHdpdGggbXV0dWFsIG5lYXJlc3QgbmVpZ2hib3Vycwpjb3JyZWN0ZWQgPC0gcmVhZFJEUygiLi4vUm9iamVjdHMvQ2Fyb25fY2x1c3RlcmluZ19tYXRlcmlhbC5yZHMiKQpyb3duYW1lcyhjb3JyZWN0ZWQpIDwtIHVuaXF1aWZ5RmVhdHVyZU5hbWVzKHJvd25hbWVzKGNvcnJlY3RlZCksIHJvd0RhdGEoY29ycmVjdGVkKSRTeW1ib2wpCmBgYAoKTm90ZSB0aGF0IHdlIGFsc28gcmVwbGFjZSB0aGUgZ2VuZSBuYW1lcyAocm93bmFtZXMpIG9mIG91ciBvYmplY3RzIHRvIHVzZSBjb21tb24KZ2VuZSBzeW1ib2xzIGluc3RlYWQgb2YgRW5zZW1ibCBJRHMuIFVzaW5nIHRoZSBgdW5pcXVpZnlGZWF0dXJlTmFtZXMoKWAgZnVuY3Rpb24KbWFrZXMgdGhpcyBzYWZlbHksIGF2b2lkaW5nIGR1cGxpY2F0ZWQgZ2VuZSBzeW1ib2xzLgoKQXMgYSByZW1pbmRlciwgdGhlIGBjb3JyZWN0ZWRgIG9iamVjdCBjb250YWlucyB0aGUgY2VsbCBjbHVzdGVyIGFzc2lnbm1lbnRzCm9idGFpbmVkIGluIGEgYGNvbERhdGFgIGNvbHVtbiBjYWxsZWQgImxhYmVsIjoKCmBgYHtyfQojIHZpc3VhbGlzZSBjbHVzdGVyIGFzc2lnbm1lbnRzIG9uIHRoZSBjb3JyZWN0ZWQgZGF0YQpwbG90VFNORShjb3JyZWN0ZWQsIAogICAgICAgICBjb2xvdXJfYnkgPSAibGFiZWwiLCAKICAgICAgICAgdGV4dF9ieSA9ICJsYWJlbCIpCmBgYAoKSXQgd2lsbCBhbHNvIGJlIGhlbHBmdWwgZm9yIGRhdGEgdmlzdWFsaXNhdGlvbiBwdXJwb3NlcyB0byBjb3B5IHRoZSBjbHVzdGVyCmFzc2lnbm1lbnQgaW4gdGhlIGNvcnJlY3RlZCBkYXRhIHRvIG91ciB1bmNvcnJlY3RlZCAobm9ybWFsaXNlZCkgU0NFIG9iamVjdDoKCmBgYHtyfQojIGZpcnN0IG1ha2Ugc3VyZSB0aGF0IHRoZSBjZWxsIG5hbWVzIGFyZSBpbiB0aGUgc2FtZSBvcmRlcgphbGwoY29sbmFtZXModW5jb3JyZWN0ZWQpID09IGNvbG5hbWVzKGNvcnJlY3RlZCkpCmNvbExhYmVscyh1bmNvcnJlY3RlZCkgPC0gY29sTGFiZWxzKGNvcnJlY3RlZCkKYGBgCgoKIyBJZGVudGlmeWluZyBjbHVzdGVyIG1hcmtlciBnZW5lcwoKQWx0aG91Z2ggd2UgaGF2ZSBkZWZpbmVkIG91ciBjbHVzdGVycyBiYXNlZCBvbiB0aGUgYmF0Y2gtY29ycmVjdGVkIGV4cHJlc3Npb24KdmFsdWVzLCB0aGVzZSBzaG91bGQgKipub3QqKiBiZSB1c2VkIGZvciBmb3IgZ2VuZS1iYXNlZCBhbmFseXNlcyBsaWtlIG1hcmtlcgpnZW5lIGRldGVjdGlvbi4KCkluc3RlYWQsIHdlIHNob3VsZCB1c2UgdGhlIHVuY29ycmVjdGVkIChub3JtYWxpc2VkKSBleHByZXNzaW9uIHZhbHVlcyBmb3IKZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYmV0d2VlbiBjbHVzdGVycy4KClRoZSByZWFzb24gZm9yIHRoaXMgaXMgdGhhdCBiYXRjaCBjb3JyZWN0aW9uIGFsZ29yaXRobXMgZG8gbm90IG5lY2Vzc2FyaWx5CnByZXNlcnZlIHRoZSBtYWduaXR1ZGUgKG9yIGV2ZW4gZGlyZWN0aW9uKSBvZiB0aGUgZGlmZmVyZW5jZXMgaW4gZ2VuZSBleHByZXNzaW9uCndoZW4gYXR0ZW1wdGluZyB0byBpbnRlZ3JhdGUgbXVsdGlwbGUgYmF0Y2hlcy4gRm9yIGV4YW1wbGUsIHRoZSBjb3NpbmUKbm9ybWFsaXphdGlvbiB1c2VkIGJ5IHRoZSAiTXV0dWFsIE5lYXJlc3QgTmVpZ2hib3VycyIgYWxnb3JpdGhtIChgZmFzdE1OTigpYCkKc2hyaW5rcyB0aGUgbWFnbml0dWRlIG9mIHRoZSBleHByZXNzaW9uIHZhbHVlcyBzbyB0aGF0IHRoZSBjb21wdXRlZCBsb2ctZm9sZApjaGFuZ2VzIGhhdmUgbm8gb2J2aW91cyBpbnRlcnByZXRhdGlvbi4gT2YgZ3JlYXRlciBjb25jZXJuIGlzIHRoZSBwb3NzaWJpbGl0eQp0aGF0IHRoZSBjb3JyZWN0aW9uIGludHJvZHVjZXMgYXJ0aWZpY2lhbCBhZ3JlZW1lbnQgYWNyb3NzIGJhdGNoZXMuCgpJbiBvdXIgY2FzZSwgdGhlIHZhbGlkIGFzc2F5IHRvIHVzZSBpbiBkaWZmZXJlbnRpYWwgYW5hbHlzaXMgdGVzdHMgaXMgdGhlCmxvZ2NvdW50cyBvYnRhaW5lZCBmcm9tIHRoZSBkZWNvbnZvbHV0aW9uIG1ldGhvZCAodXNpbmcgYGNvbXB1dGVQb29sZWRGYWN0b3JzYCAmCmBsb2dOb3JtQ291bnRzYCkuCgojIyBQYWlyd2lzZSBjb21wYXJpc29ucyBmb3Igc2NvcmluZyBwb3RlbnRpYWwgbWFya2VyIGdlbmVzCgpUaGUgYmFzaWMgYXBwcm9hY2ggZm9yIG1hcmtlciBnZW5lIGlkZW50aWZpY2F0aW9uIGFjcm9zcyBjbHVzdGVycyBpcyB0byBwZXJmb3JtCnN0YXRpc3RpY2FsIHRlc3RzIGZvciBlYWNoIGdlbmUgYmV0d2VlbiBldmVyeSBwYWlyIG9mIGNsdXN0ZXJzIGJhc2VkIG9uIGdlbmUKZXhwcmVzc2lvbi4gV2UgdXNlIGBzY29yZU1hcmtlcnMoKWAgZnVuY3Rpb24gdG8gZG8gdGhpcy4gSW4gYWRkaXRpb24gdG8KY29tcHV0aW5nIHZhcmlvdXMgc2NvcmVzLCB0aGUgZnVuY3Rpb24gY2FuIHRha2UgYWNjb3VudCBvZiBrbm93biBiYXRjaCBlZmZlY3RzLgpUaGUgZnVuY3Rpb24gb3V0cHV0cyBhIGxpc3Qgb2YgRGF0YUZyYW1lcywgd2l0aCBvbmUgRGF0YUZyYW1lIG9mIHNjb3JlcyBmb3IgZWFjaApjbHVzdGVyLiBIb3dldmVyLCBub3RlIHRoYXQgdGhlIGJsb2NraW5nIGFzc3VtZXMgdGhhdCBlYWNoIHBhaXIgb2YgY2x1c3RlcnMgaXMgCnByZXNlbnQgaW4gYXQgbGVhc3Qgb25lIG9mIHRoZSBibG9ja3MuIElmIHRoZXJlIGFyZSB0d28gY2x1c3RlcnMgd2hpY2ggYXJlIG5vdApib3RoIHByZXNlbnQgaW4gYXQgbGVhc3Qgb25lIGJsb2NrIChpbiBvdXIgY2FzZXMgU2FtcGxlcyksIHRoZW4gdGhhdCBwYWlyd2lzZQpjb21wYXJpc29uIHdpbGwgYnkgbmVjZXNzaXR5IGJlIG9taXR0ZWQuCgpgYGB7cn0KbWFya2VycyA8LSBzY29yZU1hcmtlcnModW5jb3JyZWN0ZWQsIAogICAgICAgICAgICAgICAgICAgICAgICBncm91cHMgPSBmYWN0b3IodW5jb3JyZWN0ZWQkbGFiZWwpLCAKICAgICAgICAgICAgICAgICAgICAgICAgYmxvY2sgPXVuY29ycmVjdGVkJFNhbXBsZU5hbWUpCgpoZWFkKG1hcmtlcnNbWyI4Il1dLCBuPTIpCmBgYAoKVGhpcyBEYXRhRnJhbWUgY29udGFpbnMgdGhlIHJlc3VsdHMgZm9yIGNsdXN0ZXIgOC4gVGhlIGZpcnN0IGZvdXIgY29sdW1ucyBjb250YWluIHN1bW1hcnkgCnN0YXRpc3RpY3M6CgoqICoqc2VsZi5hdmVyYWdlKiogLSB0aGUgbWVhbiBsb2ctZXhwcmVzc2lvbiBpbiB0aGUgY2x1c3RlciBvZiBpbnRlcmVzdCAgCiogKipvdGhlci5hdmVyYWdlKiogLSB0aGUgZ3JhbmQgbWVhbiBhY3Jvc3MgYWxsIG90aGVyIGNsdXN0ZXJzICAKKiAqKnNlbGYuZGV0ZWN0ZWQqKiAtIHRoZSBwcm9wb3J0aW9uIG9mIGNlbGxzIHdpdGggZGV0ZWN0ZWQgZXhwcmVzc2lvbiBpbiB0aGUgY2x1c3RlciBvZiBpbnRlcmVzdCAgCiogKipvdGhlci5kZXRlY3RlZCoqIC0gdGhlIG1lYW4gZGV0ZWN0ZWQgcHJvcG9ydGlvbiBhY3Jvc3MgYWxsIG90aGVyIGNsdXN0ZXJzLiAgCgpUaGUgcmVtYWluaW5nIGNvbHVtbnMgY29udGFpbiBzdW1tYXJpZXMgb2YgdGhyZWUgc2NvcmVzIGZyb20gdGhlIHBhaXJ3aXNlIGNvbXBhcmlzb25zLiBUaGUgdGhyZWUgc2NvcmVzIGFyZToKCiogKipsb2dGQy5jb2hlbioqIC0gIkNvaGVuJ3MgKmQqIiAtIHRoaXMgaXMgdGhlIGxvZyBmb2xkIGNoYW5nZSBvZiBtZWFuIGdlbmUKZXhwcmVzc2lvbiB0aGF0IGhhcyBiZWVuIHN0YW5kYXJkaXplZCBieSBzY2FsaW5nIGJ5IHRoZSBhdmVyYWdlIG9mIHRoZSBzdGFuZGFyZApkZXZpYXRpb24gYWNyb3NzIHRoZSBncm91cHMuIFRoaXMgY2FuIGJlIGludGVycHJldGVkIGluIGEgc2ltaWxhciB3YXkgdG8gbG9nCmZvbGQgY2hhbmdlIGluIHRoYXQgYSBwb3NpdGl2ZSB2YWx1ZSBpbmRpY2F0ZXMgdXByZWd1bGF0aW9uIGluIHRoZSBjbHVzdGVyIG9mCmludGVyZXN0LiAgICAKKiAqKkFVQyoqIC0gIkFyZWEgVW5kZXIgdGhlIEN1cnZlIiAtIHRoaXMgcXVhbnRpZmllcyB0aGUgYWJpbGl0eSB0byBkaXN0aW5ndWlzaApiZXR3ZWVuIHR3byBnZW5lIGV4cHJlc3Npb24gZGlzdHJpYnV0aW9ucy4gSXQgY2FuIGJlIGludGVycHJldGVkIGFzIHRoZQpsaWtlbGlob29kIHRoYXQgYW55IHJhbmRvbSBjZWxsIGluIHRoZSBjbHVzdGVyIG9mIGludGVyZXN0IHdpbGwgaGF2ZSBhIGhpZ2hlcgpleHByZXNzaW9uIG9mIHRoZSBnZW5lIHRoYW4gYW55IHJhbmRvbSBjZWxsIGluIHRoZSBvdGhlciBjbHVzdGVyLiBJdCByYW5nZXMgZnJvbQowIHRvIDEsIHdoZXJlIDEgY2FuIGJlIGludGVycHJldGVkIGFzIHVwcmVndWxhdGlvbiwgMCBkb3ducmVndWxhdGlvbiwgYW5kIDAuNSBhcwpubyBkaWZmZXJlbmNlLgoqICoqbG9nRkMuZGV0ZWN0ZWQqKiAtIHRoaXMgaXMgdGhlIGxvZyBmb2xkIGNoYW5nZSBpbiB0aGUgcHJvcG9ydGlvbiBvZiBjZWxscyBpbgp3aGljaCB0aGUgZ2VuZSBpcyBkZXRlY3RlZCBpbiB0aGUgY2x1c3RlciBvZiBpbnRlcmVzdCB2ZXJzdXMgdGhlIHByb3BvcnRpb24gb2YKY2VsbHMgaW4gd2hpY2ggdGhlIGdlbmUgaXMgZGV0ZWN0ZWQgaW4gdGhlIG90aGVyIGNsdXN0ZXIuIFBvc2l0aXZlIHZhbHVlcwppbmRpY2F0ZSB0aGF0IHRoZSBnZW5lIGlzIGRldGVjdGVkIGluIGEgbW9yZSBjZWxscyBpbiB0aGUgY2x1c3RlciBvZiBpbnRlcmVzdAp0aGFuIHRoZSBvdGhlciBjbHVzdGVyLiBOb3RlIHRoaXMgdGFrZXMgbm8gYWNjb3VudCBvZiB0aGUgbWFnbml0dWRlIG9mIHRoZSBnZW5lCmV4cHJlc3Npb24uCgpNb3JlIGRldGFpbCBvbiB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGVzZSBlZmZlY3Qgc2l6ZSBzY29yZXMgY2FuIGJlIGZvdW5kIGluCnRoZSBbIkFkdmFuY2VkIiBNYXJrZXIgZGV0ZWN0aW9uIGNoYXB0ZXIgb2YgdGhlIE9TQ0EKYm9va10oaHR0cDovL2Jpb2NvbmR1Y3Rvci5vcmcvYm9va3MvMy4xNS9PU0NBLmFkdmFuY2VkL21hcmtlci1kZXRlY3Rpb24tcmVkdXguaHRtbCNwcm9wZXJ0aWVzLW9mLWVhY2gtZWZmZWN0LXNpemUpLgoKV2hpbHN0IGFsbCB0aGUgcGFpcndpc2Ugc2NvcmVzIGNhbiBiZSByZXRyaWV2ZWQgYnkgYWRkaW5nIHRoZSBhcmd1bWVudApgZnVsbC5zdGF0cz1UUlVFYCB0byB0aGUgYHNjb3JlTWFya2Vyc2AgY29tbWFuZCwgYnkgZGVmYXVsdCBgc2NvcmVNYXJrZXJzYApyZXR1cm5zIDUgc3VtbWFyeSBzdGF0aXN0aWNzIGZvciBlYWNoIHNjb3JlOgoKKiAqKm1lYW4uWCoqIC0gdGhpcyBpcyB0aGUgbWVhbiBvZiB0aGUgc2NvcmUgYWNyb3NzIGFsbCBwYWlyd2lzZSBjb21wYXJpc29ucy4gSXQKZ2l2ZXMgdGhlIHJlbGF0aXZlIGV4cHJlc3Npb24gb2YgdGhlIGdlbmUgdmVyc3VzIHRoZSBhdmVyYWdlIG9mIHRoZSBvdGhlcgpjbHVzdGVycy4gIAoqICoqbWluLlgqKiAtIHRoaXMgaXMgdGhlIG1pbmltdW0gc2NvcmUgb2J0YWluZWQgYWNyb3NzIGFsbCBwYWlyd2lzZQpjb21wYXJpc29ucy4gVGhpcyBpcyB0aGUgbW9zdCBzdHJpbmdlbnQgc3VtbWFyeSBzdGF0aXN0aWMgZm9yIGRldGVjdGluZwp1cHJlZ3VsYXRlZCBnZW5lcywgaWYgdGhlIHNjb3JlIGlzIGhpZ2gsIHRoZW4gdGhlIGdlbmUgaXMgdXByZWd1bGF0ZWQgaW4gdGhlCmNsdXN0ZXIgb2YgaW50ZXJlc3QgcmVsYXRpdmUgdG8gKmFsbCogb3RoZXIgY2x1c3RlcnMuIENvbnZlcnNlbHksIGlmIHRoZSBtaW5pbXVtCmlzIGxvdywgdGhlbiB0aGUgZ2VuZSBpcyBkb3ducmVndWxhdGVkIHJlbGF0aXZlIHRvICphdCBsZWFzdCBvbmUqIG90aGVyIGNsdXN0ZXIuICAKKiAqKm1lZGlhbi5YKiogLSB0aGlzIGlzIHRoZSBtZWRpYW4gb2YgdGhlIHNjb3JlIGFjcm9zcyBhbGwgcGFpcndpc2UKY29tcGFyaXNvbnMuIEl0IGlzIG1vcmUgcm9idXN0IHRvIG91dGxpZXJzIHRoYW4gdGhlIG1lYW4uIFdoaWxzdCB0aGlzIGlzCmJlbmVmaWNpYWwgaW4gYXZvaWRpbmcgdGhlIHNpdHVhdGlvbiB3aGVyZSB0aGUgZWZmZWN0IHNpemUgaXMgdmVyeSBsYXJnZSBpbiBvbmx5CmEgc21hbGwgbnVtYmVyIG9mIGNvbXBhcmlzb25zLCBpdCBtYXkgY29udmVyc2VseSBiZSBkZXRyaW1lbnRhbCB0byBtYXJrZXIgZ2VuZQpzZWxlY3Rpb24gaWYgdGhlcmUgYXJlIHNtYWxsIG51bWJlciBvZiBjbHVzdGVycyB3aXRoIHZlcnkgc2ltaWxhciBlZmZlY3Qgc2l6ZXMsCmFzIHRoZXNlIHdpbGwgYmUgZWZmZWN0aXZlbHkgaWdub3JlZC4KKiAqKm1heC5YKiogLSB0aGlzIGlzIHRoZSBtYXhpbXVtIHNjb3JlIG9idGFpbmVkIGFjcm9zcyBhbGwgcGFpcndpc2UKY29tcGFyaXNvbnMuIFRoaXMgaXMgdGhlIGxlYXN0IHN0cmluZ2VudCBzdW1tYXJ5IHN0YXRpc3RpYyBmb3IgZGV0ZWN0aW5nCnVwcmVndWxhdGVkIGdlbmVzIGFzIGEgaGlnaCBzY29yZSBvbmx5IGluZGljYXRlcyB0aGF0IHRoZSBnZW5lIGlzIHVwcmVndWxhdGVkIGluCnRoZSBjbHVzdGVyIG9mIGludGVyZXN0IHJlbGF0aXZlIHRvICphdCBsZWFzdCBvbmUqIG90aGVyIGNsdXN0ZXJzLiBDb252ZXJzZWx5LAppZiB0aGUgbWF4aW11bSBpcyBsb3csIHRoZW4gdGhlIGdlbmUgaXMgZG93bnJlZ3VsYXRlZCByZWxhdGl2ZSB0byAqYWxsKiBvdGhlcgpjbHVzdGVycy4KKiAqKnJhbmsuWCoqIC0gVGhpcyBpcyB0aGUgbWluaW11bSByYW5raW5nICgibWluLXJhbmsiKSBvZiB0aGF0IGdlbmUgYnkgdGhhdApzY29yZSBhY3Jvc3MgYWxsIGNsdXN0ZXJzLiBGb3IgZWFjaCBwYWlyd2lzZSBjb21wYXJpc29uIHRoZSBnZW5lcyBhcmUgcmFua2VkCmFjY29yZGluZyB0byB0aGUgc2NvcmUsIHRoaXMgc3VtbWFyeSBwcm92aWRlcyB0aGUgbG93ZXN0IHJhbmsgZm9yIHRoYXQgZ2VuZQphY3Jvc3MgYWxsIGNvbXBhcmlzb25zLiBFc3NlbnRpYWxseSwgYSBnZW5lIHdpdGggYSBsb3cgIm1pbi1yYW5rIiB3aWxsIGJlIG9uZSBvZgp0aGUgYmVzdCBnZW5lcyBhY2NvcmRpbmcgdG8gdGhhdCBzY29yZSBpbiBhdCBsZWFzdCBvbmUgY29tcGFyaXNvbi4KClRoZSBjaG9pY2Ugb2YgdGhlIHN1bW1hcnkgdXNlZCBmb3IgcmFua2luZyB3aWxsIGVmZmVjdCB0aGUgc3RyaW5nZW5jeSBvZiB0aGUKc2VsZWN0aW9uLiBTZWUgdGhlIFt0aGUgT1NDQSBib29rcyAiQmFzaWMiIGNoYXB0ZXIgb24gTWFya2VyIGdlbmUKZGV0ZWN0aW9uXShodHRwOi8vYmlvY29uZHVjdG9yLm9yZy9ib29rcy8zLjE1L09TQ0EuYmFzaWMvbWFya2VyLWRldGVjdGlvbi5odG1sI3N1bW1hcml6aW5nLXBhaXJ3aXNlLWVmZmVjdHMpCmZvciBmdXJ0aGVyIGRpc2N1c3Npb24gb2YgdGhlc2UgZGlmZmVyZW50IHN1bW1hcmllcy4gSW4gZ2VuZXJhbCB0aGUgbWVhbiBhbmQKbWVkaWFuIG1ha2UgcmVhc29uYWJsZSBkZWZhdWx0cyBmb3IgbW9zdCBhcHBsaWNhdGlvbnMuIEluIHByYWN0aWNlLCB0aGUgbWluaW11bQphbmQgbWF4aW11bSBhcmUgbW9zdCBoZWxwZnVsIGZvciBkaWFnbm9zaW5nIGRpc2NyZXBhbmNpZXMgYmV0d2VlbiB0aGUgbWVhbiBhbmQKbWVkaWFuLCByYXRoZXIgdGhhbiBiZWluZyB1c2VkIGRpcmVjdGx5IGZvciByYW5raW5nLgoKU2VsZWN0aW5nIGdlbmVzIGJhc2VkIG9uIGEgZ2l2ZW4gbWluLXJhbmssIHNheSAqNSosIGlzIHVzZWZ1bCBhcyBpdCB3aWxsCmdlbmVyYXRlIGEgbGlzdCBvZiBnZW5lcyB0aGF0IGlzIHRoZSB1bmlvbiBvZiBnZW5lcyB3aXRoIGEgcmFuayBvZiA1IG9yIGxlc3MgZm9yCmFueSBwYWlyd2lzZSBjb21wYXJpc29uLlRoaXMgd2lsbCBlbnN1cmUgd2UgZ2V0IGF0IGxlYXN0IDUgZ2VuZXMgdGhhdApkaXN0aW5ndWlzaCB0aGUgY2x1c3RlciBvZiBpbnRlcmVzdCBmcm9tIGFsbCBvdGhlciBjbHVzdGVycy4KCkZvciBleGFtcGxlIHVzaW5nIHRoZSBtaW4tcmFuayBmb3IgQ29oZW4ncyAqZCogb24gY2x1c3RlciAxMSB5aWVsZHMgMTkgbWFya2VyCmdlbmVzOgoKYGBge3J9CnRvcFJhbmtlZCA8LSBtYXJrZXJzW1siMTEiXV0gJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JSAKICBzZWxlY3QoY29udGFpbnMoImNvaGVuIikpICU+JSAKICBmaWx0ZXIocmFuay5sb2dGQy5jb2hlbiA8PSA1KSAlPiUgCiAgYXJyYW5nZShyYW5rLmxvZ0ZDLmNvaGVuKQp0b3BSYW5rZWQKYGBgCgpXZSBjYW4gdGhlbiBwbG90IHRoZXNlIG9uIGEgaGVhdG1hcCB0byB2aXN1YWxpemUgdGhlIGdlbmUgZXhwcmVzc2lvbiBkaWZmZXJlbmNlczoKCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTZ9CnBsb3RHcm91cGVkSGVhdG1hcCh1bmNvcnJlY3RlZCwgCiAgICAgICAgICAgICAgICAgICBmZWF0dXJlcz1yb3duYW1lcyh0b3BSYW5rZWQpLCAKICAgICAgICAgICAgICAgICAgIGdyb3VwPSJsYWJlbCIsIAogICAgICAgICAgICAgICAgICAgYmxvY2s9IlNhbXBsZU5hbWUiLAogICAgICAgICAgICAgICAgICAgY2VudGVyPVRSVUUsIAogICAgICAgICAgICAgICAgICAgemxpbT1jKC0zLCAzKSkKYGBgCgpXZSBjYW4gcGxvdCB0aGUgZGlzdHJpYnV0aW9uIG9mICBleHByZXNzaW9uIG9mIHNwZWNpZmljIGdlbmVzIGFzIHdlIGhhdmUKcHJldmlvdXNseSwgZS5nLiBGQ0dSM0EgKENEMTYpLCBhIG1vbm9jeXRlIG1hcmtlcjoKCmBgYHtyfQpwbG90VFNORShjb3JyZWN0ZWQsIAogICAgICAgICBieV9leHByc192YWx1ZXMgPSAicmVjb25zdHJ1Y3RlZCIsIAogICAgICAgICBjb2xvdXJfYnkgPSAiRkNHUjNBIiwKICAgICAgICAgdGV4dF9ieSA9ICJsYWJlbCIpCmBgYAoKIyBBZGp1c3RpbmcgdGhlIGxvZy1mb2xkIGNoYW5nZQoKVGhlIEFVQyBhbmQgQ29oZW4ncyAqZCogc2NvcmVzIGluY29ycG9yYXRlIGJvdGggdGhlIGdlbmUgZXhwcmVzc2lvbiBkaWZmZXJlbmNlcwpiZXR3ZWVuIHRoZSBjbHVzdGVycyBhbmQgdGhlIHZhcmlhbmNlIGluIGdlbmUgZXhwcmVzc2lvbiBzY29yZXMgd2l0aGluIGVhY2gKY2x1c3Rlci4gSWYgYSBnZW5lIHdpdGggbG93IHZhcmlhbmNlIGl0IGlzIHBvc3NpYmxlIHRoYXQgaXQgd2lsbCBiZSByYW5rZWQKaGlnaGx5IGV2ZW4gaWYgdGhlIG1hZ25pdHVkZSBvZiB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBjbHVzdGVycyBpcyBsb3cuClRoZXNlIGdlbmVzIHdpbGwgbm90IG5lY2Vzc2FyaWx5IG1ha2UgZ29vZCBtYXJrZXIgZ2VuZXMuIEl0IG1heSB0aGVyZWZvcmUgYmUKZGVzaXJhYmxlIHRvIGZhdm91ciB0aGUgZGV0ZWN0aW9uIG9mIGdlbmVzIHdpdGggbGFyZ2VyIGxvZy1mb2xkIGNoYW5nZXMuIEEKbG9nLWZvbGQgY2hhbmdlIHRocmVzaG9sZCBjYW4gYmUgc2V0IHVzaW5nIHRoZSBgbGZjPWAgYXJndW1lbnQgaW4gc2NvcmUgbWFya2Vycy4KCkNvbnNpZGVyIGZvciBleGFtcGxlIGNsdXN0ZXIgNi4gVGhlIGdlbmUgUlBTMTgsIGEgcmlib3NvbWFsIHByb3RlaW4gZ2VuZSwgaXMKaWRlbnRpZmllZCBhcyBhIGdvb2QgbWFya2VyIGZvciB0aGlzIGNsdXN0ZXJzIGJhc2VkIG9uIGEgbWluLXJhbmsgZm9yIENvaGVuJ3MKKmQqIG9mIDIuCgpgYGB7cn0KdG9wUmFua2VkIDwtIG1hcmtlcnNbWyI2Il1dICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgc2VsZWN0KGNvbnRhaW5zKCJjb2hlbiIpKSAlPiUgCiAgZmlsdGVyKHJhbmsubG9nRkMuY29oZW4gPD0gNSkgJT4lIAogIGFycmFuZ2UocmFuay5sb2dGQy5jb2hlbikKdG9wUmFua2VkCmBgYAoKSG93ZXZlciwgaWYgd2UgYXJlIGludGVyZXN0ZWQgaW4gZmluZGluZyBzdHJvbmcgcG9zaXRpdmUgbWFya2VycywgYSBsb29rIGF0IHRoZQpleHByZXNzaW9uIHNob3dzIHRoYXQgaXQgd291bGQgbm90IHJlYWxseSBiZSBhIGdvb2QgcG9zaXRpdmUgbWFya2VyIGFzCmZvbGQtY2hhbmdlIGRpZmZlcmVuY2UgYmV0d2VlbiBpdCBhbmQgb3RoZXIgY2x1c3RlcnMgaXMgbm90IGdyZWF0LgoKYGBge3J9CnBsb3RFeHByZXNzaW9uKHVuY29ycmVjdGVkLCAKICAgICAgICAgICAgICAgZmVhdHVyZXMgPSAiUlBTMTgiLCAKICAgICAgICAgICAgICAgeCA9ICJsYWJlbCIpCmBgYApXZSBjYW4gc2VsZWN0IGZvciBzdHJvbmcgcG9zaXRpdmUgbWFya2VycyBieSBzZXR0aW5nIGFuIGBsZmNgIHRocmVzaG9sZCBvZiAyOgoKYGBge3J9Cm1hcmtlcnNsZmMgPC0gc2NvcmVNYXJrZXJzKHVuY29ycmVjdGVkLCAKICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBzID0gZmFjdG9yKHVuY29ycmVjdGVkJGxhYmVsKSwgCiAgICAgICAgICAgICAgICAgICAgICAgIGJsb2NrID11bmNvcnJlY3RlZCRTYW1wbGVOYW1lLAogICAgICAgICAgICAgICAgICAgICAgICBsZmM9MikKYGBgCgpSUFMxOCBubyBsb25nZXIgYXBwZWFycyBpbiB0aGUgY2FuZGlkYXRlIGNsdXN0ZXIgbWFya2VyIGdlbmVzIGxpc3QgYnkgbWluLXJhbmsgb2YgQ29oZW4ncyAqZCouCgpgYGB7cn0KdG9wUmFua2VkIDwtIG1hcmtlcnNsZmNbWyI2Il1dICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgc2VsZWN0KGNvbnRhaW5zKCJjb2hlbiIpKSAlPiUgCiAgZmlsdGVyKHJhbmsubG9nRkMuY29oZW4gPD0gNSkgJT4lIAogIGFycmFuZ2UocmFuay5sb2dGQy5jb2hlbikKdG9wUmFua2VkCmBgYAoKSW4gZmFjdCBpdCdzIG1pbi1yYW5rIGZvciBDb2hlbidzICpkKiBoYXMgZHJvcHBlZCB0bzoKCmBgYHtyfQptYXJrZXJzbGZjW1siNiJdXVsiUlBTMTgiLCAicmFuay5sb2dGQy5jb2hlbiJdCmBgYAoKCjo6Om5vdGUKCkZyb20gdGhlIE9TQ0EgYm9vazogIAoKPiBHaXZlbiB0aGF0IHNjb3JlTWFya2VycygpIGFscmVhZHkgcmVwb3J0cyBlZmZlY3Qgc2l6ZXMsIGl0IGlzIHRlbXB0aW5nIHRvIHRha2UKdGhlIG5leHQgc3RlcCBhbmQgb2J0YWluIHAtdmFsdWVzIGZvciB0aGUgcGFpcndpc2UgY29tcGFyaXNvbnMuIFVuZm9ydHVuYXRlbHksCnRoZSBwLXZhbHVlcyBmcm9tIHRoZSByZWxldmFudCB0ZXN0cyBjYW5ub3QgYmUgcmVsaWFibHkgdXNlZCB0byByZWplY3QgdGhlIG51bGwKaHlwb3RoZXNpcy4gVGhpcyBpcyBiZWNhdXNlIERFIGFuYWx5c2lzIGlzIHBlcmZvcm1lZCBvbiB0aGUgc2FtZSBkYXRhIHVzZWQgdG8Kb2J0YWluIHRoZSBjbHVzdGVycywgd2hpY2ggcmVwcmVzZW50cyDigJxkYXRhIGRyZWRnaW5n4oCdIChhbHNvIGtub3duIGFzIGZpc2hpbmcgb3IKZGF0YSBzbm9vcGluZykuIFRoZSBoeXBvdGhlc2lzIG9mIGludGVyZXN0IC0gYXJlIHRoZXJlIGRpZmZlcmVuY2VzIGJldHdlZW4KY2x1c3RlcnM/IC0gaXMgZm9ybXVsYXRlZCBmcm9tIHRoZSBkYXRhLCBzbyB3ZSBhcmUgbW9yZSBsaWtlbHkgdG8gZ2V0IGEgcG9zaXRpdmUKcmVzdWx0IHdoZW4gd2UgcmUtdXNlIHRoZSBkYXRhIHNldCB0byB0ZXN0IHRoYXQgaHlwb3RoZXNpcy4KCk1vcmUgZGV0YWlscyBbaGVyZV0oaHR0cDovL2Jpb2NvbmR1Y3Rvci5vcmcvYm9va3MvMy4xNS9PU0NBLmFkdmFuY2VkL21hcmtlci1kZXRlY3Rpb24tcmVkdXguaHRtbCNmcm9tLWRhdGEtc25vb3BpbmcpCgo6OjoKCiMgU2Vzc2lvbiBpbmZvcm1hdGlvbgoKPGRldGFpbHM+PHN1bW1hcnk+YHNlc3Npb25JbmZvKClgPC9zdW1tYXJ5PgoKYGBge3IsIHB1cmw9RkFMU0V9CnNlc3Npb25JbmZvKCkKYGBgCgo8L2RldGFpbHM+