Introduction

In this tutorial we will demonstrate how to download data from Gene Expression Omnibus directly into R. Once loaded, we will perform some quality assessment, differential expression and downstream analysis such as clustering.

We will illustrate the main steps in the workflow. However, some steps may need adjusted for your particular analysis (e.g. changing the model for the differential expression).

You will need to install the following packages before starting:-

install.packages("BiocManager")
install.packages("forcats")
install.packages("stringr")
install.packages("ggplot2")
install.packages("ggrepel")
install.packages("readr")
install.packages("tidyr")
install.packages("survminer")
BiocManager::install("GEOquery")
BiocManager::install("limma")
BiocManager::install("pheatmap")
BiocManager::install("org.Hs.eg.db")

You will also need to be familiar with our introductory materials on the ggplot2 and dplyr packages

https://sbc.shef.ac.uk/workshops/2020-03-03-r/crash-course.nb.html#dealing_with_data

Importing the data

The data from this experiment comprises nine paired tumor/normal colon tissues on Illumina HT12_v3 gene expression Beadchips. We will assume that you already know the accession number (GSE….) for the dataset that you want to download.

The function to download a GEO dataset is getGEO from the GEOquery package. You have to specify the ID of the dataset that you want. To download your own data, replace GSE33126 with the ID that you’re interested in.

library(GEOquery)
## change my_id to be the dataset that you want.
my_id <- "GSE33126"
gse <- getGEO(my_id)
Found 1 file(s)
GSE33126_series_matrix.txt.gz
trying URL 'https://ftp.ncbi.nlm.nih.gov/geo/series/GSE33nnn/GSE33126/matrix/GSE33126_series_matrix.txt.gz'
Content type 'application/x-gzip' length 3658664 bytes (3.5 MB)
downloaded 3.5 MB

Parsed with column specification:
cols(
  ID_REF = col_character(),
  GSM820516 = col_double(),
  GSM820517 = col_double(),
  GSM820518 = col_double(),
  GSM820519 = col_double(),
  GSM820520 = col_double(),
  GSM820521 = col_double(),
  GSM820522 = col_double(),
  GSM820523 = col_double(),
  GSM820524 = col_double(),
  GSM820525 = col_double(),
  GSM820526 = col_double(),
  GSM820527 = col_double(),
  GSM820528 = col_double(),
  GSM820529 = col_double(),
  GSM820530 = col_double(),
  GSM820531 = col_double(),
  GSM820532 = col_double(),
  GSM820533 = col_double()
)
File stored at: 
C:\Users\mjdun\AppData\Local\Temp\Rtmp6fVGF5/GPL6947.soft

Some datasets on GEO may be derived from different microarray platforms. Therefore the object gse is a list of different datasets. You can find out how many were used by checking the length of the gse object. Usually there will only be one platform and the dataset we want to analyse will be the first object in the list (gse[[1]]).

## check how many platforms used
length(gse)
[1] 1
gse <- gse[[1]]
gse
ExpressionSet (storageMode: lockedEnvironment)
assayData: 48803 features, 18 samples 
  element names: exprs 
protocolData: none
phenoData
  sampleNames: GSM820516 GSM820517 ... GSM820533 (18 total)
  varLabels: title geo_accession ... tissue:ch1 (34 total)
  varMetadata: labelDescription
featureData
  featureNames: ILMN_1343291 ILMN_1343295 ... ILMN_2416019 (48803 total)
  fvarLabels: ID nuID ... GB_ACC (30 total)
  fvarMetadata: Column Description labelDescription
experimentData: use 'experimentData(object)'
  pubMedIds: 23028787 
Annotation: GPL6947 
## if more than one dataset is present, you can analyse the other dataset by changing the number inside the [[...]]
## e.g. gse <- gse[[2]]
pData(gse) ## print the sample information
fData(gse) ## print the gene annotation
exprs(gse) ## print the expression data

Check the normalisation and scales used

For visualisation and statistical analysis, we will inspect the data to discover what scale the data are presented in. The methods we will use assume the data are on a log\(_2\) scale; typically in the range of 0 to 16.

The exprs function can retrieve the expression values as a data frame; with one column per-sample and one row per-gene.

The summary function can then be used to print the distributions.

## exprs get the expression levels as a data frame and get the distribution
summary(exprs(gse))
   GSM820516         GSM820517         GSM820518         GSM820519         GSM820520         GSM820521         GSM820522      
 Min.   :  137.4   Min.   :  132.6   Min.   :  132.6   Min.   :  132.6   Min.   :  132.6   Min.   :  137.4   Min.   :  132.6  
 1st Qu.:  212.1   1st Qu.:  212.1   1st Qu.:  212.0   1st Qu.:  212.1   1st Qu.:  212.0   1st Qu.:  212.1   1st Qu.:  212.1  
 Median :  244.9   Median :  245.0   Median :  244.9   Median :  244.9   Median :  244.9   Median :  244.9   Median :  244.9  
 Mean   : 1247.1   Mean   : 1247.1   Mean   : 1247.0   Mean   : 1247.1   Mean   : 1247.1   Mean   : 1247.1   Mean   : 1247.1  
 3rd Qu.:  508.2   3rd Qu.:  508.2   3rd Qu.:  508.0   3rd Qu.:  508.2   3rd Qu.:  508.2   3rd Qu.:  508.2   3rd Qu.:  508.2  
 Max.   :71775.3   Max.   :71775.3   Max.   :71775.3   Max.   :71775.3   Max.   :71775.3   Max.   :71775.3   Max.   :71775.3  
   GSM820523         GSM820524         GSM820525         GSM820526         GSM820527         GSM820528         GSM820529      
 Min.   :  132.6   Min.   :  132.6   Min.   :  132.6   Min.   :  132.6   Min.   :  132.6   Min.   :  132.6   Min.   :  137.4  
 1st Qu.:  212.1   1st Qu.:  212.1   1st Qu.:  212.1   1st Qu.:  212.0   1st Qu.:  212.1   1st Qu.:  212.1   1st Qu.:  212.1  
 Median :  244.9   Median :  245.0   Median :  245.0   Median :  244.9   Median :  245.0   Median :  245.0   Median :  245.0  
 Mean   : 1247.1   Mean   : 1247.2   Mean   : 1247.2   Mean   : 1247.1   Mean   : 1247.2   Mean   : 1247.1   Mean   : 1247.1  
 3rd Qu.:  508.2   3rd Qu.:  508.2   3rd Qu.:  508.3   3rd Qu.:  508.3   3rd Qu.:  508.2   3rd Qu.:  508.3   3rd Qu.:  508.2  
 Max.   :71775.3   Max.   :71775.3   Max.   :71775.3   Max.   :71775.3   Max.   :71775.3   Max.   :71775.3   Max.   :71775.3  
   GSM820530         GSM820531         GSM820532         GSM820533      
 Min.   :  132.6   Min.   :  132.6   Min.   :  132.6   Min.   :  132.6  
 1st Qu.:  212.1   1st Qu.:  212.1   1st Qu.:  212.1   1st Qu.:  212.1  
 Median :  244.9   Median :  244.9   Median :  245.0   Median :  245.0  
 Mean   : 1247.0   Mean   : 1247.1   Mean   : 1247.1   Mean   : 1247.1  
 3rd Qu.:  507.7   3rd Qu.:  507.9   3rd Qu.:  508.1   3rd Qu.:  508.0  
 Max.   :71775.3   Max.   :71775.3   Max.   :71775.3   Max.   :71775.3  

From this output we clearly see that the values go beyond 16, so we will need to perform a \(log_2\) transformation. A boxplot can also be generated to see if the data have been normalised. If so, the distributions of each sample should be highly similar.

exprs(gse) <- log2(exprs(gse))
boxplot(exprs(gse),outline=FALSE)

Inspect the clinical variables

Data submitted to GEO contain sample labels assigned by the experimenters, and some information about the processing protocol. All these data can be extracted by the pData function.

For your own data, you will have to decide which columns will be useful in the analysis. This will include the column giving the main comparison(s) of interest and any potential confounding factors. In this particular dataset it looks like source_name_ch1 and characteristics_ch1.1.

We can use the select function from dplyr to display just these columns of interest. At this stage it will also be useful to rename the columns to something more convenient using the rename function.

library(dplyr)

Attaching package: 㤼㸱dplyr㤼㸲

The following object is masked from 㤼㸱package:Biobase㤼㸲:

    combine

The following objects are masked from 㤼㸱package:BiocGenerics㤼㸲:

    combine, intersect, setdiff, union

The following objects are masked from 㤼㸱package:stats㤼㸲:

    filter, lag

The following objects are masked from 㤼㸱package:base㤼㸲:

    intersect, setdiff, setequal, union
sampleInfo <- pData(gse)
sampleInfo

## source_name_ch1 and characteristics_ch1.1 seem to contain factors we might need for the analysis. Let's pick just those columns

sampleInfo <- select(sampleInfo, source_name_ch1,characteristics_ch1.1)

## Optionally, rename to more convenient column names
sampleInfo <- rename(sampleInfo,group = source_name_ch1, patient=characteristics_ch1.1)

Our sample information is therefore:-

sampleInfo

Sample clustering and Principal Components Analysis

Unsupervised analysis is a good way to get an understanding of the sources of variation in the data. It can also identify potential outlier samples.

The function cor can calculate the correlation (on scale 0 - 1) in a pairwise fashion between all samples. This can be then visualised on a heatmap. Among the many options for creating heatmaps in R, the pheatmap library is one of the more popular ones. The only argument it requires is a matrix of numerical values (such as the correlation matrix).

library(pheatmap)
## argument use="c" stops an error if there are any missing data points

corMatrix <- cor(exprs(gse),use="c")
pheatmap(corMatrix)                

We can incorporate sample information onto the plot to try and understand the clustering. We have already created such a data frame previously (sampleInfo). However, we need to take care that the rownames of these data match the columns of the correlation matrix.

## Print the rownames of the sample information and check it matches the correlation matrix
rownames(sampleInfo)
 [1] "GSM820516" "GSM820517" "GSM820518" "GSM820519" "GSM820520" "GSM820521" "GSM820522" "GSM820523" "GSM820524" "GSM820525" "GSM820526"
[12] "GSM820527" "GSM820528" "GSM820529" "GSM820530" "GSM820531" "GSM820532" "GSM820533"
colnames(corMatrix)
 [1] "GSM820516" "GSM820517" "GSM820518" "GSM820519" "GSM820520" "GSM820521" "GSM820522" "GSM820523" "GSM820524" "GSM820525" "GSM820526"
[12] "GSM820527" "GSM820528" "GSM820529" "GSM820530" "GSM820531" "GSM820532" "GSM820533"
## If not, force the rownames to match the columns

rownames(sampleInfo) <- colnames(corMatrix)
pheatmap(corMatrix,
         annotation_col=sampleInfo)    

Here we see that the main separation is due to normal vs tumours; as we hope.

A complementary approach is to use Principal Components Analysis (PCA). There is a nice explanation in this youtube video.

https://www.youtube.com/watch?v=0Jp4gsfOLMs

It is important to transpose the expression matrix, otherwise R will try and compute PCA on the genes (instead of samples) and quickly run out of memory.

As PCA is an unsupervised method, the known sample groups are not taken into account. However, we can add labels when we plot the results. The ggplot2 package is particularly convenient for this. The ggrepel package can be used to postion the text labels more cleverly so they can be read.

library(ggplot2)
Use suppressPackageStartupMessages() to eliminate package startup messages
library(ggrepel)
## MAKE SURE TO TRANSPOSE THE EXPRESSION MATRIX

pca <- prcomp(t(exprs(gse)))

## Join the PCs to the sample information
cbind(sampleInfo, pca$x) %>% 
ggplot(aes(x = PC1, y=PC2, col=group,label=paste("Patient", patient))) + geom_point() + geom_text_repel()

What happens if we spot a batch effect?

Nothing at this stage. Provided the experimental design is sensible (i.e. representatives from all samples groups are present in each batch) we can correct for batch when we run the differential expression analysis.

What happens if we detect outliers?

If we suspect some samples are outliers we can remove them for further analysis

### CODE ONLY FOR DEMONSTRATION ONLY

### lets' say are outliers are samples 1,2 and 3
## replace 1,2,3 with the outliers in your dataset
outlier_samples <- c(1,2,3)

gse <- gse[,-outlier_samples]

Exporting the data

We can export the expression data to a csv for inspection in Excel using the write_csv function from readr. The expression values themselves will probably not be very useful as they will be named according to manufacturer ID rather than gene name (for example). We can create a matrix by joining the expression matrix with the feature annotation.

library(readr)
full_output <- cbind(fData(gse),exprs(gse))
write_csv(full_output, path="gse_full_output.csv")

The annotation from GEO might contain lots of columns that we are not particularly interested in. To keep the data tidier we can use the select function to only print particular columns in the output.

features <- fData(gse)
View(features)
### Look at the features data frame and decide the names of the columns you want to keep
features <- select(features,Symbol,Entrez_Gene_ID,Chromosome,Cytoband)
full_output <- cbind(features,exprs(gse))
write_csv(full_output, path="gse_full_output.csv")

Differential Expression

By far the most-popular package for performing differential expression is limma. The user-guide is extensive and covers the theory behind the analysis and many use-cases (Chapters 9 and 17 for single-channel data such as Illumina and Affymetrix)

https://bioconductor.org/packages/release/bioc/vignettes/limma/inst/doc/usersguide.pdf

Crucially, we have to allocate the samples in our dataset to the sample groups of interest. A useful function is model.matrix, which will create a design matrix from one of the columns in your sampleInfo. Here I choose sampleInfo$group.

The design matrix is a matrix of 0 and 1s; one row for each sample and one column for each sample group. A 1 in a particular row and column indicates that a given sample (the row) belongs to a given group (column).

library(limma)
design <- model.matrix(~0+sampleInfo$group)
design
   sampleInfo$groupnormal sampleInfo$grouptumor
1                       0                     1
2                       1                     0
3                       0                     1
4                       1                     0
5                       0                     1
6                       1                     0
7                       0                     1
8                       1                     0
9                       0                     1
10                      1                     0
11                      0                     1
12                      1                     0
13                      0                     1
14                      1                     0
15                      0                     1
16                      1                     0
17                      0                     1
18                      1                     0
attr(,"assign")
[1] 1 1
attr(,"contrasts")
attr(,"contrasts")$`sampleInfo$group`
[1] "contr.treatment"
## the column names are a bit ugly, so we will rename
colnames(design) <- c("Normal","Tumour")

It has been demonstrated that our power to detect differential expression can be improved if we filter lowly-expressed genes prior to performing the analysis. Quite how one defines a gene being expressed may vary from experiment to experiment, so a cut-off that will work for all datasets is not feasible. Here we consider that aroudn 50% of our genes will not be expressed, and use the median expression level as a cut-off.

summary(exprs(gse))

## calculate median expression level
cutoff <- median(exprs(gse))

## TRUE or FALSE for whether each gene is "expressed" in each sample
is_expressed <- exprs(gse) > cutoff

## Identify genes expressed in more than 2 samples

keep <- rowSums(is_expressed) > 2

## check how many genes are removed / retained.
table(keep)

## subset to just those expressed genes
gse <- gse[keep,]

The lmFit function is used to fit the model to the data. The result of which is to estimate the expression level in each of the groups that we specified.

fit <- lmFit(exprs(gse), design)
head(fit$coefficients)
                Normal    Tumour
ILMN_1343291 16.087740 16.004200
ILMN_1343295 14.032346 14.699567
ILMN_1651199  7.786860  7.763617
ILMN_1651209  8.079803  8.002217
ILMN_1651210  7.555458  7.659205
ILMN_1651221  7.816231  7.801812

In order to perform the differential analysis, we have to define the contrast that we are interested in. In our case we only have two groups and one contrast of interest. Multiple contrasts can be defined in the makeContrasts function.

contrasts <- makeContrasts(Tumour - Normal, levels=design)

## can define multiple contrasts
## e.g. makeContrasts(Group1 - Group2, Group2 - Group3,....levels=design)

fit2 <- contrasts.fit(fit, contrasts)

Finally, apply the empirical Bayes’ step to get our differential expression statistics and p-values.

fit2 <- eBayes(fit2)

We usually get our first look at the results by using the topTable command

topTable(fit2)

The topTable function automatically displays the results for the first contrast. If you want to see results for other contrasts

topTable(fit2, coef=1)
### to see the results of the second contrast (if it exists)
## topTable(fit2, coef=2)

If we want to know how many genes are differentially-expressed overall we can use the decideTests function.

decideTests(fit2)
TestResults matrix
              Contrasts
               Tumour - Normal
  ILMN_1343291               0
  ILMN_1343295               0
  ILMN_1651199               0
  ILMN_1651209               0
  ILMN_1651210               0
48798 more rows ...
table(decideTests(fit2))

   -1     0     1 
 1097 46219  1487 

Coping with outliers

It is tempting to discard any arrays which seem to be outliers prior to differential expressions. However, this is done at the expense of sample-size which could be an issue for small experiments. A compromise, which has been shown to work well is to calculate weights to define the reliability of each sample.

Ritchie, M. E., Diyagama, D., Neilson, van Laar, R., J., Dobrovic, A., Holloway, A., and Smyth, G. K. (2006). Empirical array quality weights in the analysis of microarray data. BMC Bioinformatics 7, 261. http://www.biomedcentral.com/1471-2105/7/261

The arrayWeights function will assign a score to each sample; with a value of 1 implying equal weight. Samples with score less than 1 are down-weights, and samples with scores greater than 1 are up-weighted. Therefore no samples actually need to be removed.

## calculate relative array weights
aw <- arrayWeights(exprs(gse),design)
aw
        1         2         3         4         5         6         7         8         9        10        11        12        13        14 
1.0941904 0.7792316 0.8795979 0.8149897 1.1027797 1.1832412 1.0416964 0.9044246 0.9260245 0.9283017 0.8567824 1.3450707 1.3738622 1.1054466 
       15        16        17        18 
0.9272203 1.1598047 0.7926736 1.0376677 

The lmFit function can accept weights, and the rest of the code proceeds as above.

fit <- lmFit(exprs(gse), design,
             weights = aw)
contrasts <- makeContrasts(Tumour - Normal, levels=design)
fit2 <- contrasts.fit(fit, contrasts)
fit2 <- eBayes(fit2)

Further processing and visualisation of DE results

At the moment our results are not particularly easy to navigate as the only information to identify each gene is the identifier that the microarray manufacturer has assigned. Fortunately, the GEO entry contains extensive annotation that we can add. The annotation data can be retrieved with the fData function and we restrict to columns we are interested in using select.

For your own data, you will have to choose the columns that are of interest to you. You probably won’t have the same column headings used here.

Once an annotation data frame has been created, it can be assigned to our results.

anno <- fData(gse)
anno
anno <- select(anno,Symbol,Entrez_Gene_ID,Chromosome,Cytoband)
fit2$genes <- anno
topTable(fit2)

The “Volcano Plot” function is a common way of visualising the results of a DE analysis. The \(x\) axis shows the log-fold change and the \(y\) axis is some measure of statistical significance, which in this case is the log-odds, or “B” statistic. A characteristic “volcano” shape should be seen.

First we create a data frame that we can visualise in ggplot2. Specifying the number argument to topTable creates a table containing test results from all genes. We also put the probe IDs as a column rather than row names.

full_results <- topTable(fit2, number=Inf)
full_results <- tibble::rownames_to_column(full_results,"ID")

The basic plot is created as follows:-

## Make sure you have ggplot2 loaded
library(ggplot2)
ggplot(full_results,aes(x = logFC, y=B)) + geom_point()

The flexibility of ggplot2 allows us to automatically label points on the plot that might be of interest. For example, genes that meet a particular p-value and log fold-change cut-off. With the code below the values of p_cutoff and fc_cutoff can be changed as desired.

## change according to your needs
p_cutoff <- 0.05
fc_cutoff <- 1

full_results %>% 
  mutate(Significant = adj.P.Val < p_cutoff, abs(logFC) > fc_cutoff ) %>% 
  ggplot(aes(x = logFC, y = B, col=Significant)) + geom_point()

Furthermore, we can label the identity of some genes. Below we set a limit of the top “N” genes we want to label, and label each gene according to it’s Symbol.

library(ggrepel)
p_cutoff <- 0.05
fc_cutoff <- 1
topN <- 20

full_results %>% 
  mutate(Significant = adj.P.Val < p_cutoff, abs(logFC) > fc_cutoff ) %>% 
  mutate(Rank = 1:n(), Label = ifelse(Rank < topN, Symbol,"")) %>% 
  ggplot(aes(x = logFC, y = B, col=Significant,label=Label)) + geom_point() + geom_text_repel(col="black")

Filtering and exporting the results table

The filter function from dplyr gives a convenient way to interrogate the table of results.

## Get the results for particular gene of interest
filter(full_results, Symbol == "SMOX")
## Get results for genes with TP53 in the name
filter(full_results, grepl("TP53", Symbol))
## Get results for one chromosome
filter(full_results, Chromosome==20)

We can also filter according to p-value (adjusted) and fold-change cut-offs

p_cutoff <- 0.05
fc_cutoff <- 1

filter(full_results, adj.P.Val < 0.05, abs(logFC) > 1)

These results can be exported with the write_csv function.

library(readr)
filter(full_results, adj.P.Val < 0.05, abs(logFC) > 1) %>%
  write_csv(path="filtered_de_results.csv")

Further visualisation

Heatmaps of selected genes

R and Bioconductor have many packages for creating heatmaps. The most popular at the current time ComplexHeatmap and pheatmap (that we will use here).

Creating the heatmap is pretty straightforward. There is a pheatmap function within the pheatmap library, and it just needs to know the matrix of values that you want to plot (say gene_matrix):-

### example code
library(pheatmap)
pheatmap(gene_matrix)

However, there are many different ways of contrusting such a matrix depending on what you want to visualise in the plot. We will consider some options below.

Most differentially-expressed genes

We have already created a table of differential expression results, which is ranked according to statistical significance.

To visualise the most differentially-expressed genes, we first need to extract their ID. These IDs should correspond to rows in the expression matrix.

In the code below we introduce a new column to the results which just gives a row number to each gene. We then filter to return data for the top N results. The pull function is used to extract the ID column as a variable.

## Use to top 20 genes for illustration

topN <- 20
##
ids_of_interest <- mutate(full_results, Rank = 1:n()) %>% 
  filter(Rank < topN) %>% 
  pull(ID)

In order to label the heatmap in a useful manner we extract the corresponding gene symbols.

gene_names <- mutate(full_results, Rank = 1:n()) %>% 
  filter(Rank < topN) %>% 
  pull(Symbol) 

The expression values for the IDs we have retrieved can be obtained by using the [..] notation to index the expression matrix.

## Get the rows corresponding to ids_of_interest and all columns
gene_matrix <- exprs(gse)[ids_of_interest,]

We now make the heatmap. A default colour scheme is used, but can be changed via the arguments. Please don’t use red and green.

pheatmap(gene_matrix,
     labels_row = gene_names)

It is often preferable to scale each row to highlight the differences in each gene across the dataset.

pheatmap(gene_matrix,
     labels_row = gene_names,
     scale="row")

User-defined genes of interest

The procedure is similar to above if you have your own list of genes (e.g. genes from a previous study). The %in% function is used to identify rows whose Symbol matches any member of my_genes. Here we create my_genes manually. If you want to plot the genes belonging to a particular GO term, it might be more efficient to follow the section below.

Depending on the technology used, there might be multiple matches for a particular gene; so we could end up with more IDs than genes. Therefore we repeat the filtering put pull the Symbol column to make sure we can label the rows of the heatmap.


my_genes <- c("HIG2", "CA1","ETV4","FOXA1")
ids_of_interest <-  filter(full_results,Symbol %in% my_genes) %>% 
  pull(ID)

gene_names <-  filter(full_results,Symbol %in% my_genes) %>% 
  pull(Symbol)
gene_matrix <- exprs(gse)[ids_of_interest,]
pheatmap(gene_matrix,
         labels_row = gene_names,
         scale="row")

For a particular pathway

Bioconductor annotation packages exist for a number of organisms to allow easy conversion between different ID schemes. In this particular use-case we can retrieve the names of genes belonging to a given pathway.

You can check what packages are available from the Bioconductor page (look for the packages named org.XX.XX.db)

if (!requireNamespace("BiocManager", quietly = TRUE))
    install.packages("BiocManager")

BiocManager::install("org.Hs.eg.db")

Once installed, we load the package in the usual manner:-

library(org.Hs.eg.db)
Loading required package: AnnotationDbi
Loading required package: stats4
Loading required package: IRanges
Loading required package: S4Vectors

Attaching package: 㤼㸱S4Vectors㤼㸲

The following objects are masked from 㤼㸱package:dplyr㤼㸲:

    first, rename

The following object is masked from 㤼㸱package:base㤼㸲:

    expand.grid


Attaching package: 㤼㸱IRanges㤼㸲

The following objects are masked from 㤼㸱package:dplyr㤼㸲:

    collapse, desc, slice

The following object is masked from 㤼㸱package:grDevices㤼㸲:

    windows


Attaching package: 㤼㸱AnnotationDbi㤼㸲

The following object is masked from 㤼㸱package:dplyr㤼㸲:

    select

Each of these organism packages has a series of keytypes that can we can use to query:-

keytypes(org.Hs.eg.db)
 [1] "ACCNUM"       "ALIAS"        "ENSEMBL"      "ENSEMBLPROT"  "ENSEMBLTRANS" "ENTREZID"     "ENZYME"       "EVIDENCE"     "EVIDENCEALL" 
[10] "GENENAME"     "GO"           "GOALL"        "IPI"          "MAP"          "OMIM"         "ONTOLOGY"     "ONTOLOGYALL"  "PATH"        
[19] "PFAM"         "PMID"         "PROSITE"      "REFSEQ"       "SYMBOL"       "UCSCKG"       "UNIGENE"      "UNIPROT"     

and a series of columns of data that we can retrieve:-

columns(org.Hs.eg.db)
 [1] "ACCNUM"       "ALIAS"        "ENSEMBL"      "ENSEMBLPROT"  "ENSEMBLTRANS" "ENTREZID"     "ENZYME"       "EVIDENCE"     "EVIDENCEALL" 
[10] "GENENAME"     "GO"           "GOALL"        "IPI"          "MAP"          "OMIM"         "ONTOLOGY"     "ONTOLOGYALL"  "PATH"        
[19] "PFAM"         "PMID"         "PROSITE"      "REFSEQ"       "SYMBOL"       "UCSCKG"       "UNIGENE"      "UNIPROT"     

To make a query we need to specify a set of keys (the IDs that we want to map), what type these keys (must match something in the output of keytypes) and the columns (the additional data we want).

For illustration, we’ll use the same genes from above (of keytype SYMBOL) and retrieve their ENSEMBL ID and GO terms.

The function required to make the query is also called select, but different from the select function we have used from dplyr. To avoid confusion, we explictly tell R to use the select function from AnnotationDbi (the package used to query annotation databases automatically installed when we download a database package).

my_genes <- c("HIG2", "CA1","ETV4","FOXA1")

anno <- AnnotationDbi::select(org.Hs.eg.db, 
                              columns=c("ENSEMBL","GO"),
                              keys=my_genes,
                              keytype = "SYMBOL")
'select()' returned 1:many mapping between keys and columns
anno

We can use the same function to retrieve genes belonging to a particular pathway with appropriate adjustments to the columns, keys and keytype arguments:-

anno <- AnnotationDbi::select(org.Hs.eg.db,
                              columns="SYMBOL",
                              keys="GO:0006338",
                              keytype="GO")
'select()' returned 1:many mapping between keys and columns
anno
my_genes <- pull(anno, SYMBOL)
ids_of_interest <-  filter(full_results,Symbol %in% my_genes) %>% 
  pull(ID)

gene_names <-  filter(full_results,Symbol %in% my_genes) %>% 
  pull(Symbol)
gene_matrix <- exprs(gse)[ids_of_interest,]
pheatmap(gene_matrix,
         labels_row = gene_names,
         scale="row")

Survival Analysis

In this section we give a brief overview of how to perform a survival analysis from a published dataset. The example dataset in question, although quite old, is a useful example of predicting survival in breast cancer.

You will need to install an extra package, survminer for the survival analysis itself.

install.packages("survminer")

We will use the usual commands for importing the data.

library(GEOquery)
gse <- getGEO('GSE7390')[[1]]
Found 1 file(s)
GSE7390_series_matrix.txt.gz
trying URL 'https://ftp.ncbi.nlm.nih.gov/geo/series/GSE7nnn/GSE7390/matrix/GSE7390_series_matrix.txt.gz'
Content type 'application/x-gzip' length 24203878 bytes (23.1 MB)
downloaded 23.1 MB

Parsed with column specification:
cols(
  .default = col_double(),
  ID_REF = col_character()
)
See spec(...) for full column specifications.
File stored at: 
C:\Users\mjdun\AppData\Local\Temp\Rtmp6fVGF5/GPL96.soft
68 parsing failures.
  row     col           expected    actual         file
22216 SPOT_ID 1/0/T/F/TRUE/FALSE --Control literal data
22217 SPOT_ID 1/0/T/F/TRUE/FALSE --Control literal data
22218 SPOT_ID 1/0/T/F/TRUE/FALSE --Control literal data
22219 SPOT_ID 1/0/T/F/TRUE/FALSE --Control literal data
22220 SPOT_ID 1/0/T/F/TRUE/FALSE --Control literal data
..... ....... .................. ......... ............
See problems(...) for more details.

We are going to be interested in the phenotypic (/clinical) data stored with the dataset, which will unfortunately require some cleaning prior to analysis. This will be quite a laborious process as there are many variables of interest. For your own dataset, you may need to adapt the code accordingly.

To eye-ball the contents we can use the View command in RStudio.

View(pData(gse))

It seems that most of the useful columns are prefixed by characteristics, so we can use the convenient contains function to select these. characteristics_ch1 and characteristics_ch1.2 are probably not useful, so we will remove these.

library(dplyr)
s_data <- pData(gse) %>% 
  dplyr::select(geo_accession, contains("characteristics"), -characteristics_ch1.2, -characteristics_ch1, -characteristics_ch1.1)

None of the columns have very convenient names, so we will go ahead and rename them.

s_data <- s_data %>% 
  dplyr::rename(hospital = characteristics_ch1.3,
         age = characteristics_ch1.4,
         size = characteristics_ch1.5,
         surgery_type = characteristics_ch1.6,
         histtype = characteristics_ch1.7,
         angioinv = characteristics_ch1.8,
         lymp_infil = characteristics_ch1.9,
         node = characteristics_ch1.10,
         grade = characteristics_ch1.11,
         er = characteristics_ch1.12,
         t.rfs = characteristics_ch1.13,
         e.rfs = characteristics_ch1.14,
         t.os = characteristics_ch1.15,
         e.os = characteristics_ch1.16,
         t.dmfs = characteristics_ch1.17,
         e.dmfs =characteristics_ch1.18,
         t.tdm = characteristics_ch1.19,
         e.tdm = characteristics_ch1.20,
         risksg = characteristics_ch1.21,
         npi = characteristics_ch1.22,
         risknpi = characteristics_ch1.23,
         aol_os_10yr = characteristics_ch1.24,
         risk_aol = characteristics_ch1.25,
         veridex_risk = characteristics_ch1.26)

The columns themselves contain entries that are not particularly convenient for analysis. For example, in the age column we would expect to find the age of patients in years. Instead each entry is prefixed by the string age:, and the same is true for other columns of interest. We can fix this by a performing a global substituion in the offending columns; replacing the prefix with an empty string "". See the help on gsub for more information. The dplyr function mutate will save the update column in the data frame.

s_data <- s_data %>% 
  mutate(hospital = gsub("hospital: ", "", hospital, fixed=TRUE),
         age = as.numeric(gsub("age: ","", age, fixed=TRUE)),
         size = as.numeric(gsub("size: ","", size, fixed=TRUE)),
         surgery_type = gsub("Surgery_type: ","", surgery_type, fixed=TRUE),
         histtype = gsub("Histtype: ","", histtype, fixed=TRUE),
         angioinv = gsub("Angioinv: ","", angioinv, fixed=TRUE),
         lymp_infil = gsub("Lymp_infil: ","", lymp_infil, fixed=TRUE),
         node = gsub("node: ","", node, fixed=TRUE),
         grade = gsub("grade: ","", grade, fixed=TRUE),
         er = gsub("er: ","", er, fixed=TRUE),
         t.rfs = as.numeric(gsub("t.rfs: ","", t.rfs, fixed=TRUE)),
         e.rfs = as.numeric(gsub("e.rfs: ","", e.rfs, fixed=TRUE)),
         t.os = as.numeric(gsub("t.os: ","", t.os, fixed=TRUE)),
         e.os = as.numeric(gsub("e.os: ","", e.os, fixed=TRUE)),
         t.dmfs = as.numeric(gsub("t.dmfs: ","", t.dmfs, fixed=TRUE)),
         e.dmfs = as.numeric(gsub("e.dmfs: ","", e.dmfs, fixed=TRUE)),
         t.tdm = as.numeric(gsub("t.tdm: ","", t.tdm, fixed=TRUE)),
         e.tdm = as.numeric(gsub("e.tdm: ","", e.tdm, fixed=TRUE)),
         risksg = gsub("risksg: ","", risksg, fixed=TRUE),
         npi = as.numeric(gsub("NPI: ","", npi, fixed=TRUE)),
         risknpi = gsub("risknpi: ","", risknpi, fixed=TRUE),
         aol_os_10yr = as.numeric(gsub("AOL_os_10y: ","", aol_os_10yr, fixed=TRUE)),
         risk_aol = gsub("risk_AOL: ","", risk_aol, fixed=TRUE),
         veridex_risk = gsub("veridex_risk: ","", veridex_risk, fixed=TRUE)
         )
NAs introduced by coercion

Finally we can start to fit survival models to the data. For more detailed explanation of the survminer package, the project page has lots of useful information. It is well-known that the Estrogen Receptor (ER) status of a breast cancer is predictive of survival. We have this variable in the er column of s_data, so can fit the model and plot with the following:-

library(survival)
library(survminer)
package 㤼㸱survminer㤼㸲 was built under R version 4.0.2Loading required package: ggpubr
package 㤼㸱ggpubr㤼㸲 was built under R version 4.0.2Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
fit <- survfit(Surv(t.os, e.os) ~ er, data = s_data)
ggsurvplot(fit, data = s_data,pval = TRUE)

The p-value is significant, as we would hope, with patients having a negative ER status having poorer survival. Other variables can be tested by modifying the formula. For more information on survminer see the package documentation at:-

Testing Survival association with gene expression

The real utility of having the GEO dataset in a convenient format is to test for associations with the expression level of a particular gene; rather than a pre-defined clinical variable. Thus, we can validate if the genes we are interested in might be predictive of survival. As an example we will use the gene ESR1 (Estrogen Receptor 1).

We can get a look at the expression values by printing the first five rows and columns. It is quite common for microarray datasets to have each row labelled according to a particular microarray probe identifer (as we have here), so we will need a way of being able to identify genes by their more-common name.

exprs(gse)[1:5,1:5]
          GSM177885 GSM177886 GSM177887 GSM177888 GSM177889
1007_s_at 10.780787 11.335393 11.028074 11.847736 12.359239
1053_at    8.674201  9.354759  9.053889  9.139895  9.196708
117_at     7.738589  7.763657  6.327600  7.032598  7.873428
121_at     9.285511  9.025809  9.234409  9.656416  9.174188
1255_g_at  6.610484  4.809869  4.621973  5.589404  5.325173

More information about the probes is retrieved using the fData function.

features <- fData(gse)
View(features)

In this case, the columns of interest are probably ID (which should match the rows of our expression matrix) and Gene Symbol. For your own data, you will have to work out the names of columns you want to use.

features <- dplyr::select(features, ID, `Gene Symbol`)

We can also notice that some rows have multiple entries separated by the /// set of characters. We can split into multiple columns using the separate function from tidyr. You might not need to do this for your own dataset of interest.

features <- tidyr::separate(features,`Gene Symbol`, into = c("Symbol_1","Symbol_2"), sep=" /// ")
Expected 2 pieces. Additional pieces discarded in 388 rows [33, 39, 54, 59, 62, 101, 110, 150, 151, 179, 181, 183, 243, 244, 300, 328, 386, 402, 403, 440, ...].Expected 2 pieces. Missing pieces filled with `NA` in 20878 rows [2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 21, 22, 23, ...].

We can now join this to our expression matrix.

e_data <- exprs(gse) %>% 
  data.frame %>% 
  tibble::rownames_to_column("ID") %>% 
  left_join(features)
Joining, by = "ID"
e_data

Now that we have gene symbols in the same table as the expression data we can search according to the gene name. Depending on what gene is chosen you may get multiple probes. It looks like ESR1 has quite a few in this dataset.

In this step we can also choose to remove the symbol column as we won’t need it later on.

e_data <- filter(e_data,Symbol_1 %in% "ESR1") %>% 
  dplyr::select(-Symbol_1, -Symbol_2)
e_data

We would like to incorporate these values into our analysis, but at the moment the dimensions are not compatible with s_data; we need a data frame with one row for each sample and a column for each feature of interest (in this case a gene expression probe).

dim(s_data)
[1] 198  25
dim(e_data)
[1]   9 199

Essentially the data need to be transposed. Whilst R provides a function t for transposing, it doesn’t seem to work well (in my experience) for data frames. Instead we can use a two-step process of converting to a long data type (with more rows than columns) with gather from the tidyr package, and then making into a wide table with expression probes as columns with spread.

Some pictorial representation of gather and spread is provided on this cheatsheet

You will need to make sure you have installed tidyr. Once you have installed it, you won’t need to repeat this step

install.packages("tidyr")

Now for the transformation. When we gather the data we can choose the column names in the output data frame. I have chosen geo_accession because this will help later on when I want to merge with the survival data (which already has a column by this name).

e_data <- e_data %>% tidyr::gather(geo_accession, Expression,-ID) %>% 
  tidyr::spread(ID, Expression)
e_data
## TO-DO: replace with pivot_wider and pivot_longer at some point

The dimensions should now match our survival data. We can now join these using the left_join function from dplyr. This is possible because both s_data and e_data have a column (geo_accession) in common.

s_data <- left_join(s_data, e_data)
Joining, by = "geo_accession"

As an aside, we can do a quick sanity check as we expect ESR1 to be lowly-expressed in ER negative tumours.

ggplot(s_data, aes(x = er, y =`205225_at`)) + geom_boxplot()

As we saw before, the survival analysis requires a grouping variable to split our data (previously we used the er column). A convenient way to create a new grouping variable is to use the ifelse function. This will create a new variable of two values depending on a logical expression. In this case we can assign High or Low grouping depending on whether the expression values for a given probe (205225_at) exceed a cutoff or not. For this example we will use the 25th percentile (via the quantile function) as we know low expression is predictive

s_data <- mutate(s_data, Group = ifelse(`205225_at` > quantile(`205225_at`,0.25), "High","Low"))
fit <- survfit(Surv(t.os, e.os) ~ Group, data = s_data)
ggsurvplot(fit, data = s_data,pval = TRUE)

cutoff <- 0.5
s_data <- mutate(s_data, Group = ifelse(`205225_at` > quantile(`205225_at`,cutoff), "High","Low"))
fit <- survfit(Surv(t.os, e.os) ~ Group, data = s_data)
ggsurvplot(fit, data = s_data,pval = TRUE)

LS0tDQp0aXRsZTogIkFuYWx5c2luZyBkYXRhIGZyb20gR0VPIC0gV29yayBpbiBQcm9ncmVzcyINCmF1dGhvcjogIk1hcmsgRHVubmluZyINCmRhdGU6ICdgciBmb3JtYXQoU3lzLnRpbWUoKSwgIkxhc3QgbW9kaWZpZWQ6ICVkICViICVZIilgJw0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCi0tLQ0KDQojIEludHJvZHVjdGlvbg0KDQpJbiB0aGlzIHR1dG9yaWFsIHdlIHdpbGwgZGVtb25zdHJhdGUgaG93IHRvIGRvd25sb2FkIGRhdGEgZnJvbSBHZW5lIEV4cHJlc3Npb24gT21uaWJ1cyBkaXJlY3RseSBpbnRvIFIuIE9uY2UgbG9hZGVkLCB3ZSB3aWxsIHBlcmZvcm0gc29tZSBxdWFsaXR5IGFzc2Vzc21lbnQsIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuZCBkb3duc3RyZWFtIGFuYWx5c2lzIHN1Y2ggYXMgY2x1c3RlcmluZy4NCg0KV2Ugd2lsbCBpbGx1c3RyYXRlIHRoZSBtYWluIHN0ZXBzIGluIHRoZSB3b3JrZmxvdy4gSG93ZXZlciwgc29tZSBzdGVwcyBtYXkgbmVlZCBhZGp1c3RlZCBmb3IgeW91ciBwYXJ0aWN1bGFyIGFuYWx5c2lzIChlLmcuIGNoYW5naW5nIHRoZSBtb2RlbCBmb3IgdGhlIGRpZmZlcmVudGlhbCBleHByZXNzaW9uKS4NCg0KWW91IHdpbGwgbmVlZCB0byBpbnN0YWxsIHRoZSBmb2xsb3dpbmcgcGFja2FnZXMgYmVmb3JlIHN0YXJ0aW5nOi0NCg0KYGBge3IgZXZhbD1GQUxTRX0NCmluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikNCmluc3RhbGwucGFja2FnZXMoImZvcmNhdHMiKQ0KaW5zdGFsbC5wYWNrYWdlcygic3RyaW5nciIpDQppbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikNCmluc3RhbGwucGFja2FnZXMoImdncmVwZWwiKQ0KaW5zdGFsbC5wYWNrYWdlcygicmVhZHIiKQ0KaW5zdGFsbC5wYWNrYWdlcygidGlkeXIiKQ0KaW5zdGFsbC5wYWNrYWdlcygic3Vydm1pbmVyIikNCkJpb2NNYW5hZ2VyOjppbnN0YWxsKCJHRU9xdWVyeSIpDQpCaW9jTWFuYWdlcjo6aW5zdGFsbCgibGltbWEiKQ0KQmlvY01hbmFnZXI6Omluc3RhbGwoInBoZWF0bWFwIikNCkJpb2NNYW5hZ2VyOjppbnN0YWxsKCJvcmcuSHMuZWcuZGIiKQ0KYGBgDQoNCllvdSB3aWxsIGFsc28gbmVlZCB0byBiZSBmYW1pbGlhciB3aXRoIG91ciBpbnRyb2R1Y3RvcnkgbWF0ZXJpYWxzIG9uIHRoZSBgZ2dwbG90MmAgYW5kIGBkcGx5cmAgcGFja2FnZXMNCg0KaHR0cHM6Ly9zYmMuc2hlZi5hYy51ay93b3Jrc2hvcHMvMjAyMC0wMy0wMy1yL2NyYXNoLWNvdXJzZS5uYi5odG1sI2RlYWxpbmdfd2l0aF9kYXRhDQoNCiMgSW1wb3J0aW5nIHRoZSBkYXRhDQoNClRoZSBkYXRhIGZyb20gdGhpcyBleHBlcmltZW50IGNvbXByaXNlcyBuaW5lIHBhaXJlZCB0dW1vci9ub3JtYWwgY29sb24gdGlzc3VlcyBvbiBJbGx1bWluYSBIVDEyXF92MyBnZW5lIGV4cHJlc3Npb24gQmVhZGNoaXBzLiBXZSB3aWxsIGFzc3VtZSB0aGF0IHlvdSBhbHJlYWR5IGtub3cgdGhlIGFjY2Vzc2lvbiBudW1iZXIgKEdTRS4uLi4pIGZvciB0aGUgZGF0YXNldCB0aGF0IHlvdSB3YW50IHRvIGRvd25sb2FkLg0KDQoNCmBgYHtyIGVjaG89RkFMU0UsbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkoR0VPcXVlcnkpDQpsaWJyYXJ5KGxpbW1hKQ0KYGBgDQoNClRoZSBmdW5jdGlvbiB0byBkb3dubG9hZCBhIEdFTyBkYXRhc2V0IGlzIGBnZXRHRU9gIGZyb20gdGhlIGBHRU9xdWVyeWAgcGFja2FnZS4gWW91IGhhdmUgdG8gc3BlY2lmeSB0aGUgSUQgb2YgdGhlIGRhdGFzZXQgdGhhdCB5b3Ugd2FudC4gVG8gZG93bmxvYWQgeW91ciBvd24gZGF0YSwgcmVwbGFjZSBgR1NFMzMxMjZgIHdpdGggdGhlIElEIHRoYXQgeW91J3JlIGludGVyZXN0ZWQgaW4uDQoNCmBgYHtyIGNhY2hlPVRSVUV9DQpsaWJyYXJ5KEdFT3F1ZXJ5KQ0KIyMgY2hhbmdlIG15X2lkIHRvIGJlIHRoZSBkYXRhc2V0IHRoYXQgeW91IHdhbnQuDQpteV9pZCA8LSAiR1NFMzMxMjYiDQpnc2UgPC0gZ2V0R0VPKG15X2lkKQ0KYGBgDQoNClNvbWUgZGF0YXNldHMgb24gR0VPIG1heSBiZSBkZXJpdmVkIGZyb20gZGlmZmVyZW50IG1pY3JvYXJyYXkgcGxhdGZvcm1zLiBUaGVyZWZvcmUgdGhlIG9iamVjdCBgZ3NlYCBpcyBhIGxpc3Qgb2YgZGlmZmVyZW50IGRhdGFzZXRzLiBZb3UgY2FuIGZpbmQgb3V0IGhvdyBtYW55IHdlcmUgdXNlZCBieSBjaGVja2luZyB0aGUgbGVuZ3RoIG9mIHRoZSBgZ3NlYCBvYmplY3QuIFVzdWFsbHkgdGhlcmUgd2lsbCBvbmx5IGJlIG9uZSBwbGF0Zm9ybSBhbmQgdGhlIGRhdGFzZXQgd2Ugd2FudCB0byBhbmFseXNlIHdpbGwgYmUgdGhlIGZpcnN0IG9iamVjdCBpbiB0aGUgbGlzdCAoYGdzZVtbMV1dYCkuDQoNCmBgYHtyfQ0KIyMgY2hlY2sgaG93IG1hbnkgcGxhdGZvcm1zIHVzZWQNCmxlbmd0aChnc2UpDQpnc2UgPC0gZ3NlW1sxXV0NCmdzZQ0KDQojIyBpZiBtb3JlIHRoYW4gb25lIGRhdGFzZXQgaXMgcHJlc2VudCwgeW91IGNhbiBhbmFseXNlIHRoZSBvdGhlciBkYXRhc2V0IGJ5IGNoYW5naW5nIHRoZSBudW1iZXIgaW5zaWRlIHRoZSBbWy4uLl1dDQojIyBlLmcuIGdzZSA8LSBnc2VbWzJdXQ0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQpwRGF0YShnc2UpICMjIHByaW50IHRoZSBzYW1wbGUgaW5mb3JtYXRpb24NCmZEYXRhKGdzZSkgIyMgcHJpbnQgdGhlIGdlbmUgYW5ub3RhdGlvbg0KZXhwcnMoZ3NlKSAjIyBwcmludCB0aGUgZXhwcmVzc2lvbiBkYXRhDQpgYGANCg0KDQojIENoZWNrIHRoZSBub3JtYWxpc2F0aW9uIGFuZCBzY2FsZXMgdXNlZA0KDQpGb3IgdmlzdWFsaXNhdGlvbiBhbmQgc3RhdGlzdGljYWwgYW5hbHlzaXMsIHdlIHdpbGwgaW5zcGVjdCB0aGUgZGF0YSB0byBkaXNjb3ZlciB3aGF0ICpzY2FsZSogdGhlIGRhdGEgYXJlIHByZXNlbnRlZCBpbi4gVGhlIG1ldGhvZHMgd2Ugd2lsbCB1c2UgYXNzdW1lIHRoZSBkYXRhIGFyZSBvbiBhIGxvZyRfMiQgc2NhbGU7IHR5cGljYWxseSBpbiB0aGUgcmFuZ2Ugb2YgMCB0byAxNi4gDQoNClRoZSBgZXhwcnNgIGZ1bmN0aW9uIGNhbiByZXRyaWV2ZSB0aGUgZXhwcmVzc2lvbiB2YWx1ZXMgYXMgYSBkYXRhIGZyYW1lOyB3aXRoIG9uZSBjb2x1bW4gcGVyLXNhbXBsZSBhbmQgb25lIHJvdyBwZXItZ2VuZS4NCg0KVGhlIGBzdW1tYXJ5YCBmdW5jdGlvbiBjYW4gdGhlbiBiZSB1c2VkIHRvIHByaW50IHRoZSBkaXN0cmlidXRpb25zLg0KDQpgYGB7cn0NCiMjIGV4cHJzIGdldCB0aGUgZXhwcmVzc2lvbiBsZXZlbHMgYXMgYSBkYXRhIGZyYW1lIGFuZCBnZXQgdGhlIGRpc3RyaWJ1dGlvbg0Kc3VtbWFyeShleHBycyhnc2UpKQ0KYGBgDQoNCkZyb20gdGhpcyBvdXRwdXQgd2UgY2xlYXJseSBzZWUgdGhhdCB0aGUgdmFsdWVzIGdvIGJleW9uZCAxNiwgc28gd2Ugd2lsbCBuZWVkIHRvIHBlcmZvcm0gYSAkbG9nXzIkIHRyYW5zZm9ybWF0aW9uLiBBIGBib3hwbG90YCBjYW4gYWxzbyBiZSBnZW5lcmF0ZWQgdG8gc2VlIGlmIHRoZSBkYXRhIGhhdmUgYmVlbiBub3JtYWxpc2VkLiBJZiBzbywgdGhlIGRpc3RyaWJ1dGlvbnMgb2YgZWFjaCBzYW1wbGUgc2hvdWxkIGJlIGhpZ2hseSBzaW1pbGFyLg0KDQpgYGB7cn0NCmV4cHJzKGdzZSkgPC0gbG9nMihleHBycyhnc2UpKQ0KYm94cGxvdChleHBycyhnc2UpLG91dGxpbmU9RkFMU0UpDQpgYGANCg0KIyBJbnNwZWN0IHRoZSBjbGluaWNhbCB2YXJpYWJsZXMNCg0KRGF0YSBzdWJtaXR0ZWQgdG8gR0VPIGNvbnRhaW4gc2FtcGxlIGxhYmVscyBhc3NpZ25lZCBieSB0aGUgZXhwZXJpbWVudGVycywgYW5kIHNvbWUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHByb2Nlc3NpbmcgcHJvdG9jb2wuIEFsbCB0aGVzZSBkYXRhIGNhbiBiZSBleHRyYWN0ZWQgYnkgdGhlIGBwRGF0YWAgZnVuY3Rpb24uIA0KDQoqKkZvciB5b3VyIG93biBkYXRhLCB5b3Ugd2lsbCBoYXZlIHRvIGRlY2lkZSB3aGljaCBjb2x1bW5zIHdpbGwgYmUgdXNlZnVsIGluIHRoZSBhbmFseXNpcyoqLiBUaGlzIHdpbGwgaW5jbHVkZSB0aGUgY29sdW1uIGdpdmluZyB0aGUgbWFpbiBjb21wYXJpc29uKHMpIG9mIGludGVyZXN0IGFuZCBhbnkgcG90ZW50aWFsIGNvbmZvdW5kaW5nIGZhY3RvcnMuIEluIHRoaXMgcGFydGljdWxhciBkYXRhc2V0IGl0IGxvb2tzIGxpa2UgYHNvdXJjZV9uYW1lX2NoMWAgYW5kIGBjaGFyYWN0ZXJpc3RpY3NfY2gxLjFgLg0KDQpXZSBjYW4gdXNlIHRoZSBgc2VsZWN0YCBmdW5jdGlvbiBmcm9tIGBkcGx5cmAgdG8gZGlzcGxheSBqdXN0IHRoZXNlIGNvbHVtbnMgb2YgaW50ZXJlc3QuIEF0IHRoaXMgc3RhZ2UgaXQgd2lsbCBhbHNvIGJlIHVzZWZ1bCB0byByZW5hbWUgdGhlIGNvbHVtbnMgdG8gc29tZXRoaW5nIG1vcmUgY29udmVuaWVudCB1c2luZyB0aGUgYHJlbmFtZWAgZnVuY3Rpb24uDQoNCmBgYHtyfQ0KbGlicmFyeShkcGx5cikNCnNhbXBsZUluZm8gPC0gcERhdGEoZ3NlKQ0Kc2FtcGxlSW5mbw0KDQojIyBzb3VyY2VfbmFtZV9jaDEgYW5kIGNoYXJhY3RlcmlzdGljc19jaDEuMSBzZWVtIHRvIGNvbnRhaW4gZmFjdG9ycyB3ZSBtaWdodCBuZWVkIGZvciB0aGUgYW5hbHlzaXMuIExldCdzIHBpY2sganVzdCB0aG9zZSBjb2x1bW5zDQoNCnNhbXBsZUluZm8gPC0gc2VsZWN0KHNhbXBsZUluZm8sIHNvdXJjZV9uYW1lX2NoMSxjaGFyYWN0ZXJpc3RpY3NfY2gxLjEpDQoNCiMjIE9wdGlvbmFsbHksIHJlbmFtZSB0byBtb3JlIGNvbnZlbmllbnQgY29sdW1uIG5hbWVzDQpzYW1wbGVJbmZvIDwtIHJlbmFtZShzYW1wbGVJbmZvLGdyb3VwID0gc291cmNlX25hbWVfY2gxLCBwYXRpZW50PWNoYXJhY3RlcmlzdGljc19jaDEuMSkNCmBgYA0KDQpPdXIgc2FtcGxlIGluZm9ybWF0aW9uIGlzIHRoZXJlZm9yZTotDQoNCmBgYHtyfQ0Kc2FtcGxlSW5mbw0KYGBgDQoNCiMgU2FtcGxlIGNsdXN0ZXJpbmcgYW5kIFByaW5jaXBhbCBDb21wb25lbnRzIEFuYWx5c2lzDQoNClVuc3VwZXJ2aXNlZCBhbmFseXNpcyBpcyBhIGdvb2Qgd2F5IHRvIGdldCBhbiB1bmRlcnN0YW5kaW5nIG9mIHRoZSBzb3VyY2VzIG9mIHZhcmlhdGlvbiBpbiB0aGUgZGF0YS4gSXQgY2FuIGFsc28gaWRlbnRpZnkgcG90ZW50aWFsIG91dGxpZXIgc2FtcGxlcy4NCg0KVGhlIGZ1bmN0aW9uIGBjb3JgIGNhbiBjYWxjdWxhdGUgdGhlIGNvcnJlbGF0aW9uIChvbiBzY2FsZSAwIC0gMSkgaW4gYSBwYWlyd2lzZSBmYXNoaW9uIGJldHdlZW4gYWxsIHNhbXBsZXMuIFRoaXMgY2FuIGJlIHRoZW4gdmlzdWFsaXNlZCBvbiBhIGhlYXRtYXAuIEFtb25nIHRoZSBtYW55IG9wdGlvbnMgZm9yIGNyZWF0aW5nIGhlYXRtYXBzIGluIFIsIHRoZSBgcGhlYXRtYXBgIGxpYnJhcnkgaXMgb25lIG9mIHRoZSBtb3JlIHBvcHVsYXIgb25lcy4gVGhlIG9ubHkgYXJndW1lbnQgaXQgcmVxdWlyZXMgaXMgYSBtYXRyaXggb2YgbnVtZXJpY2FsIHZhbHVlcyAoc3VjaCBhcyB0aGUgY29ycmVsYXRpb24gbWF0cml4KS4NCg0KYGBge3J9DQpsaWJyYXJ5KHBoZWF0bWFwKQ0KIyMgYXJndW1lbnQgdXNlPSJjIiBzdG9wcyBhbiBlcnJvciBpZiB0aGVyZSBhcmUgYW55IG1pc3NpbmcgZGF0YSBwb2ludHMNCg0KY29yTWF0cml4IDwtIGNvcihleHBycyhnc2UpLHVzZT0iYyIpDQpwaGVhdG1hcChjb3JNYXRyaXgpICAgICAgICAgICAgICAgIA0KYGBgDQoNCldlIGNhbiBpbmNvcnBvcmF0ZSBzYW1wbGUgaW5mb3JtYXRpb24gb250byB0aGUgcGxvdCB0byB0cnkgYW5kIHVuZGVyc3RhbmQgdGhlIGNsdXN0ZXJpbmcuIFdlIGhhdmUgYWxyZWFkeSBjcmVhdGVkIHN1Y2ggYSBkYXRhIGZyYW1lIHByZXZpb3VzbHkgKGBzYW1wbGVJbmZvYCkuIEhvd2V2ZXIsIHdlIG5lZWQgdG8gdGFrZSBjYXJlIHRoYXQgdGhlIHJvd25hbWVzIG9mIHRoZXNlIGRhdGEgbWF0Y2ggdGhlIGNvbHVtbnMgb2YgdGhlIGNvcnJlbGF0aW9uIG1hdHJpeC4NCg0KYGBge3J9DQojIyBQcmludCB0aGUgcm93bmFtZXMgb2YgdGhlIHNhbXBsZSBpbmZvcm1hdGlvbiBhbmQgY2hlY2sgaXQgbWF0Y2hlcyB0aGUgY29ycmVsYXRpb24gbWF0cml4DQpyb3duYW1lcyhzYW1wbGVJbmZvKQ0KY29sbmFtZXMoY29yTWF0cml4KQ0KDQojIyBJZiBub3QsIGZvcmNlIHRoZSByb3duYW1lcyB0byBtYXRjaCB0aGUgY29sdW1ucw0KDQpyb3duYW1lcyhzYW1wbGVJbmZvKSA8LSBjb2xuYW1lcyhjb3JNYXRyaXgpDQpwaGVhdG1hcChjb3JNYXRyaXgsDQogICAgICAgICBhbm5vdGF0aW9uX2NvbD1zYW1wbGVJbmZvKSAgICANCmBgYA0KDQpIZXJlIHdlIHNlZSB0aGF0IHRoZSBtYWluIHNlcGFyYXRpb24gaXMgZHVlIHRvIG5vcm1hbCB2cyB0dW1vdXJzOyBhcyB3ZSBob3BlLg0KDQpBIGNvbXBsZW1lbnRhcnkgYXBwcm9hY2ggaXMgdG8gdXNlIFByaW5jaXBhbCBDb21wb25lbnRzIEFuYWx5c2lzIChQQ0EpLiBUaGVyZSBpcyBhIG5pY2UgZXhwbGFuYXRpb24gaW4gdGhpcyB5b3V0dWJlIHZpZGVvLg0KDQpodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PTBKcDRnc2ZPTE1zDQoNCkl0IGlzIGltcG9ydGFudCB0byAqdHJhbnNwb3NlKiB0aGUgZXhwcmVzc2lvbiBtYXRyaXgsIG90aGVyd2lzZSBSIHdpbGwgdHJ5IGFuZCBjb21wdXRlIFBDQSBvbiB0aGUgZ2VuZXMgKGluc3RlYWQgb2Ygc2FtcGxlcykgYW5kIHF1aWNrbHkgcnVuIG91dCBvZiBtZW1vcnkuDQoNCkFzIFBDQSBpcyBhbiB1bnN1cGVydmlzZWQgbWV0aG9kLCB0aGUga25vd24gc2FtcGxlIGdyb3VwcyBhcmUgbm90IHRha2VuIGludG8gYWNjb3VudC4gSG93ZXZlciwgd2UgY2FuIGFkZCBsYWJlbHMgd2hlbiB3ZSBwbG90IHRoZSByZXN1bHRzLiBUaGUgYGdncGxvdDJgIHBhY2thZ2UgaXMgcGFydGljdWxhcmx5IGNvbnZlbmllbnQgZm9yIHRoaXMuIFRoZSBgZ2dyZXBlbGAgcGFja2FnZSBjYW4gYmUgdXNlZCB0byBwb3N0aW9uIHRoZSB0ZXh0IGxhYmVscyBtb3JlIGNsZXZlcmx5IHNvIHRoZXkgY2FuIGJlIHJlYWQuDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShnZ3JlcGVsKQ0KIyMgTUFLRSBTVVJFIFRPIFRSQU5TUE9TRSBUSEUgRVhQUkVTU0lPTiBNQVRSSVgNCg0KcGNhIDwtIHByY29tcCh0KGV4cHJzKGdzZSkpKQ0KDQojIyBKb2luIHRoZSBQQ3MgdG8gdGhlIHNhbXBsZSBpbmZvcm1hdGlvbg0KY2JpbmQoc2FtcGxlSW5mbywgcGNhJHgpICU+JSANCmdncGxvdChhZXMoeCA9IFBDMSwgeT1QQzIsIGNvbD1ncm91cCxsYWJlbD1wYXN0ZSgiUGF0aWVudCIsIHBhdGllbnQpKSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX3RleHRfcmVwZWwoKQ0KYGBgDQoNCiMjIFdoYXQgaGFwcGVucyBpZiB3ZSBzcG90IGEgYmF0Y2ggZWZmZWN0Pw0KDQpOb3RoaW5nIGF0IHRoaXMgc3RhZ2UuIFByb3ZpZGVkIHRoZSBleHBlcmltZW50YWwgZGVzaWduIGlzIHNlbnNpYmxlIChpLmUuIHJlcHJlc2VudGF0aXZlcyBmcm9tIGFsbCBzYW1wbGVzIGdyb3VwcyBhcmUgcHJlc2VudCBpbiBlYWNoIGJhdGNoKSB3ZSBjYW4gY29ycmVjdCBmb3IgYmF0Y2ggd2hlbiB3ZSBydW4gdGhlIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2lzLg0KDQojIyBXaGF0IGhhcHBlbnMgaWYgd2UgZGV0ZWN0IG91dGxpZXJzPw0KDQpJZiB3ZSBzdXNwZWN0IHNvbWUgc2FtcGxlcyBhcmUgb3V0bGllcnMgd2UgY2FuIHJlbW92ZSB0aGVtIGZvciBmdXJ0aGVyIGFuYWx5c2lzDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQojIyMgQ09ERSBPTkxZIEZPUiBERU1PTlNUUkFUSU9OIE9OTFkNCg0KIyMjIGxldHMnIHNheSBhcmUgb3V0bGllcnMgYXJlIHNhbXBsZXMgMSwyIGFuZCAzDQojIyByZXBsYWNlIDEsMiwzIHdpdGggdGhlIG91dGxpZXJzIGluIHlvdXIgZGF0YXNldA0Kb3V0bGllcl9zYW1wbGVzIDwtIGMoMSwyLDMpDQoNCmdzZSA8LSBnc2VbLC1vdXRsaWVyX3NhbXBsZXNdDQoNCmBgYA0KDQojIEV4cG9ydGluZyB0aGUgZGF0YQ0KDQpXZSBjYW4gZXhwb3J0IHRoZSBleHByZXNzaW9uIGRhdGEgdG8gYSBgY3N2YCBmb3IgaW5zcGVjdGlvbiBpbiBFeGNlbCB1c2luZyB0aGUgYHdyaXRlX2NzdmAgZnVuY3Rpb24gZnJvbSBgcmVhZHJgLiBUaGUgZXhwcmVzc2lvbiB2YWx1ZXMgdGhlbXNlbHZlcyB3aWxsIHByb2JhYmx5IG5vdCBiZSB2ZXJ5IHVzZWZ1bCBhcyB0aGV5IHdpbGwgYmUgbmFtZWQgYWNjb3JkaW5nIHRvIG1hbnVmYWN0dXJlciBJRCByYXRoZXIgdGhhbiBnZW5lIG5hbWUgKGZvciBleGFtcGxlKS4gV2UgY2FuIGNyZWF0ZSBhIG1hdHJpeCBieSBqb2luaW5nIHRoZSBleHByZXNzaW9uIG1hdHJpeCB3aXRoIHRoZSBmZWF0dXJlIGFubm90YXRpb24uDQoNCmBgYHtyfQ0KbGlicmFyeShyZWFkcikNCmZ1bGxfb3V0cHV0IDwtIGNiaW5kKGZEYXRhKGdzZSksZXhwcnMoZ3NlKSkNCndyaXRlX2NzdihmdWxsX291dHB1dCwgcGF0aD0iZ3NlX2Z1bGxfb3V0cHV0LmNzdiIpDQpgYGANCg0KVGhlIGFubm90YXRpb24gZnJvbSBHRU8gbWlnaHQgY29udGFpbiBsb3RzIG9mIGNvbHVtbnMgdGhhdCB3ZSBhcmUgbm90IHBhcnRpY3VsYXJseSBpbnRlcmVzdGVkIGluLiBUbyBrZWVwIHRoZSBkYXRhIHRpZGllciB3ZSBjYW4gdXNlIHRoZSBgc2VsZWN0YCBmdW5jdGlvbiB0byBvbmx5IHByaW50IHBhcnRpY3VsYXIgY29sdW1ucyBpbiB0aGUgb3V0cHV0Lg0KDQpgYGB7cn0NCmZlYXR1cmVzIDwtIGZEYXRhKGdzZSkNClZpZXcoZmVhdHVyZXMpDQojIyMgTG9vayBhdCB0aGUgZmVhdHVyZXMgZGF0YSBmcmFtZSBhbmQgZGVjaWRlIHRoZSBuYW1lcyBvZiB0aGUgY29sdW1ucyB5b3Ugd2FudCB0byBrZWVwDQpmZWF0dXJlcyA8LSBzZWxlY3QoZmVhdHVyZXMsU3ltYm9sLEVudHJlel9HZW5lX0lELENocm9tb3NvbWUsQ3l0b2JhbmQpDQpmdWxsX291dHB1dCA8LSBjYmluZChmZWF0dXJlcyxleHBycyhnc2UpKQ0Kd3JpdGVfY3N2KGZ1bGxfb3V0cHV0LCBwYXRoPSJnc2VfZnVsbF9vdXRwdXQuY3N2IikNCg0KYGBgDQoNCg0KIyBEaWZmZXJlbnRpYWwgRXhwcmVzc2lvbg0KDQpCeSBmYXIgdGhlIG1vc3QtcG9wdWxhciBwYWNrYWdlIGZvciBwZXJmb3JtaW5nIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGlzIGBsaW1tYWAuIFRoZSB1c2VyLWd1aWRlIGlzIGV4dGVuc2l2ZSBhbmQgY292ZXJzIHRoZSB0aGVvcnkgYmVoaW5kIHRoZSBhbmFseXNpcyBhbmQgbWFueSB1c2UtY2FzZXMgKENoYXB0ZXJzIDkgYW5kIDE3IGZvciBzaW5nbGUtY2hhbm5lbCBkYXRhIHN1Y2ggYXMgSWxsdW1pbmEgYW5kIEFmZnltZXRyaXgpDQoNCmh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL2Jpb2MvdmlnbmV0dGVzL2xpbW1hL2luc3QvZG9jL3VzZXJzZ3VpZGUucGRmDQoNCkNydWNpYWxseSwgd2UgaGF2ZSB0byBhbGxvY2F0ZSB0aGUgc2FtcGxlcyBpbiBvdXIgZGF0YXNldCB0byB0aGUgc2FtcGxlIGdyb3VwcyBvZiBpbnRlcmVzdC4gQSB1c2VmdWwgZnVuY3Rpb24gaXMgIGBtb2RlbC5tYXRyaXhgLCB3aGljaCB3aWxsIGNyZWF0ZSBhICpkZXNpZ24gbWF0cml4KiBmcm9tIG9uZSBvZiB0aGUgY29sdW1ucyBpbiB5b3VyIGBzYW1wbGVJbmZvYC4gSGVyZSBJIGNob29zZSBgc2FtcGxlSW5mbyRncm91cGAuDQoNClRoZSBkZXNpZ24gbWF0cml4IGlzIGEgbWF0cml4IG9mIGAwYCBhbmQgYDFgczsgb25lIHJvdyBmb3IgZWFjaCBzYW1wbGUgYW5kIG9uZSBjb2x1bW4gZm9yIGVhY2ggc2FtcGxlIGdyb3VwLiBBIGAxYCBpbiBhIHBhcnRpY3VsYXIgcm93IGFuZCBjb2x1bW4gaW5kaWNhdGVzIHRoYXQgYSBnaXZlbiBzYW1wbGUgKHRoZSByb3cpIGJlbG9uZ3MgdG8gYSBnaXZlbiBncm91cCAoY29sdW1uKS4NCg0KYGBge3J9DQpsaWJyYXJ5KGxpbW1hKQ0KZGVzaWduIDwtIG1vZGVsLm1hdHJpeCh+MCtzYW1wbGVJbmZvJGdyb3VwKQ0KZGVzaWduDQojIyB0aGUgY29sdW1uIG5hbWVzIGFyZSBhIGJpdCB1Z2x5LCBzbyB3ZSB3aWxsIHJlbmFtZQ0KY29sbmFtZXMoZGVzaWduKSA8LSBjKCJOb3JtYWwiLCJUdW1vdXIiKQ0KYGBgDQoNCkl0IGhhcyBiZWVuIGRlbW9uc3RyYXRlZCB0aGF0IG91ciBwb3dlciB0byBkZXRlY3QgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gY2FuIGJlIGltcHJvdmVkIGlmIHdlIGZpbHRlciBsb3dseS1leHByZXNzZWQgZ2VuZXMgcHJpb3IgdG8gcGVyZm9ybWluZyB0aGUgYW5hbHlzaXMuIFF1aXRlIGhvdyBvbmUgZGVmaW5lcyBhIGdlbmUgYmVpbmcgZXhwcmVzc2VkIG1heSB2YXJ5IGZyb20gZXhwZXJpbWVudCB0byBleHBlcmltZW50LCBzbyBhIGN1dC1vZmYgdGhhdCB3aWxsIHdvcmsgZm9yIGFsbCBkYXRhc2V0cyBpcyBub3QgZmVhc2libGUuIEhlcmUgd2UgY29uc2lkZXIgdGhhdCBhcm91ZG4gNTAlIG9mIG91ciBnZW5lcyB3aWxsIG5vdCBiZSBleHByZXNzZWQsIGFuZCB1c2UgdGhlIG1lZGlhbiBleHByZXNzaW9uIGxldmVsIGFzIGEgY3V0LW9mZi4NCg0KYGBge3IgZXZhbD1GQUxTRX0NCnN1bW1hcnkoZXhwcnMoZ3NlKSkNCg0KIyMgY2FsY3VsYXRlIG1lZGlhbiBleHByZXNzaW9uIGxldmVsDQpjdXRvZmYgPC0gbWVkaWFuKGV4cHJzKGdzZSkpDQoNCiMjIFRSVUUgb3IgRkFMU0UgZm9yIHdoZXRoZXIgZWFjaCBnZW5lIGlzICJleHByZXNzZWQiIGluIGVhY2ggc2FtcGxlDQppc19leHByZXNzZWQgPC0gZXhwcnMoZ3NlKSA+IGN1dG9mZg0KDQojIyBJZGVudGlmeSBnZW5lcyBleHByZXNzZWQgaW4gbW9yZSB0aGFuIDIgc2FtcGxlcw0KDQprZWVwIDwtIHJvd1N1bXMoaXNfZXhwcmVzc2VkKSA+IDINCg0KIyMgY2hlY2sgaG93IG1hbnkgZ2VuZXMgYXJlIHJlbW92ZWQgLyByZXRhaW5lZC4NCnRhYmxlKGtlZXApDQoNCiMjIHN1YnNldCB0byBqdXN0IHRob3NlIGV4cHJlc3NlZCBnZW5lcw0KZ3NlIDwtIGdzZVtrZWVwLF0NCmBgYA0KDQoNCg0KVGhlIGBsbUZpdGAgZnVuY3Rpb24gaXMgdXNlZCB0byBmaXQgdGhlIG1vZGVsIHRvIHRoZSBkYXRhLiBUaGUgcmVzdWx0IG9mIHdoaWNoIGlzIHRvIGVzdGltYXRlIHRoZSBleHByZXNzaW9uIGxldmVsIGluIGVhY2ggb2YgdGhlIGdyb3VwcyB0aGF0IHdlIHNwZWNpZmllZC4NCg0KYGBge3J9DQpmaXQgPC0gbG1GaXQoZXhwcnMoZ3NlKSwgZGVzaWduKQ0KaGVhZChmaXQkY29lZmZpY2llbnRzKQ0KYGBgDQoNCkluIG9yZGVyIHRvIHBlcmZvcm0gdGhlICpkaWZmZXJlbnRpYWwqIGFuYWx5c2lzLCB3ZSBoYXZlIHRvIGRlZmluZSB0aGUgY29udHJhc3QgdGhhdCB3ZSBhcmUgaW50ZXJlc3RlZCBpbi4gSW4gb3VyIGNhc2Ugd2Ugb25seSBoYXZlIHR3byBncm91cHMgYW5kIG9uZSBjb250cmFzdCBvZiBpbnRlcmVzdC4gTXVsdGlwbGUgY29udHJhc3RzIGNhbiBiZSBkZWZpbmVkIGluIHRoZSBgbWFrZUNvbnRyYXN0c2AgZnVuY3Rpb24uDQoNCmBgYHtyfQ0KY29udHJhc3RzIDwtIG1ha2VDb250cmFzdHMoVHVtb3VyIC0gTm9ybWFsLCBsZXZlbHM9ZGVzaWduKQ0KDQojIyBjYW4gZGVmaW5lIG11bHRpcGxlIGNvbnRyYXN0cw0KIyMgZS5nLiBtYWtlQ29udHJhc3RzKEdyb3VwMSAtIEdyb3VwMiwgR3JvdXAyIC0gR3JvdXAzLC4uLi5sZXZlbHM9ZGVzaWduKQ0KDQpmaXQyIDwtIGNvbnRyYXN0cy5maXQoZml0LCBjb250cmFzdHMpDQpgYGANCg0KRmluYWxseSwgYXBwbHkgdGhlICplbXBpcmljYWwgQmF5ZXMnKiBzdGVwIHRvIGdldCBvdXIgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gc3RhdGlzdGljcyBhbmQgcC12YWx1ZXMuDQoNCmBgYHtyfQ0KZml0MiA8LSBlQmF5ZXMoZml0MikNCmBgYA0KDQpXZSB1c3VhbGx5IGdldCBvdXIgZmlyc3QgbG9vayBhdCB0aGUgcmVzdWx0cyBieSB1c2luZyB0aGUgYHRvcFRhYmxlYCBjb21tYW5kDQoNCmBgYHtyfQ0KdG9wVGFibGUoZml0MikNCmBgYA0KDQpUaGUgYHRvcFRhYmxlYCBmdW5jdGlvbiBhdXRvbWF0aWNhbGx5IGRpc3BsYXlzIHRoZSByZXN1bHRzIGZvciB0aGUgZmlyc3QgY29udHJhc3QuIElmIHlvdSB3YW50IHRvIHNlZSByZXN1bHRzIGZvciBvdGhlciBjb250cmFzdHMNCmBgYHtyfQ0KdG9wVGFibGUoZml0MiwgY29lZj0xKQ0KIyMjIHRvIHNlZSB0aGUgcmVzdWx0cyBvZiB0aGUgc2Vjb25kIGNvbnRyYXN0IChpZiBpdCBleGlzdHMpDQojIyB0b3BUYWJsZShmaXQyLCBjb2VmPTIpDQoNCmBgYA0KDQpJZiB3ZSB3YW50IHRvIGtub3cgaG93IG1hbnkgZ2VuZXMgYXJlIGRpZmZlcmVudGlhbGx5LWV4cHJlc3NlZCBvdmVyYWxsIHdlIGNhbiB1c2UgdGhlIGBkZWNpZGVUZXN0c2AgZnVuY3Rpb24uDQoNCmBgYHtyfQ0KZGVjaWRlVGVzdHMoZml0MikNCg0KdGFibGUoZGVjaWRlVGVzdHMoZml0MikpDQpgYGANCg0KIyMgQ29waW5nIHdpdGggb3V0bGllcnMNCg0KSXQgaXMgdGVtcHRpbmcgdG8gZGlzY2FyZCBhbnkgYXJyYXlzIHdoaWNoIHNlZW0gdG8gYmUgb3V0bGllcnMgcHJpb3IgdG8gZGlmZmVyZW50aWFsIGV4cHJlc3Npb25zLiBIb3dldmVyLCB0aGlzIGlzIGRvbmUgYXQgdGhlIGV4cGVuc2Ugb2Ygc2FtcGxlLXNpemUgd2hpY2ggY291bGQgYmUgYW4gaXNzdWUgZm9yIHNtYWxsIGV4cGVyaW1lbnRzLiBBIGNvbXByb21pc2UsIHdoaWNoIGhhcyBiZWVuIHNob3duIHRvIHdvcmsgd2VsbCBpcyB0byBjYWxjdWxhdGUgKndlaWdodHMqIHRvIGRlZmluZSB0aGUgcmVsaWFiaWxpdHkgb2YgZWFjaCBzYW1wbGUuDQoNClJpdGNoaWUsIE0uIEUuLCBEaXlhZ2FtYSwgRC4sIE5laWxzb24sIHZhbiBMYWFyLCBSLiwgSi4sIERvYnJvdmljLCBBLiwgSG9sbG93YXksIEEuLCBhbmQgU215dGgsIEcuIEsuICgyMDA2KS4gRW1waXJpY2FsIGFycmF5IHF1YWxpdHkgd2VpZ2h0cyBpbiB0aGUgYW5hbHlzaXMgb2YgbWljcm9hcnJheSBkYXRhLiBCTUMgQmlvaW5mb3JtYXRpY3MgNywgMjYxLiBodHRwOi8vd3d3LmJpb21lZGNlbnRyYWwuY29tLzE0NzEtMjEwNS83LzI2MQ0KDQpUaGUgYGFycmF5V2VpZ2h0c2AgZnVuY3Rpb24gd2lsbCBhc3NpZ24gYSBzY29yZSB0byBlYWNoIHNhbXBsZTsgd2l0aCBhIHZhbHVlIG9mIDEgaW1wbHlpbmcgZXF1YWwgd2VpZ2h0LiBTYW1wbGVzIHdpdGggc2NvcmUgbGVzcyB0aGFuIDEgYXJlIGRvd24td2VpZ2h0cywgYW5kIHNhbXBsZXMgd2l0aCBzY29yZXMgZ3JlYXRlciB0aGFuIDEgYXJlIHVwLXdlaWdodGVkLiBUaGVyZWZvcmUgbm8gc2FtcGxlcyBhY3R1YWxseSBuZWVkIHRvIGJlIHJlbW92ZWQuDQoNCmBgYHtyfQ0KIyMgY2FsY3VsYXRlIHJlbGF0aXZlIGFycmF5IHdlaWdodHMNCmF3IDwtIGFycmF5V2VpZ2h0cyhleHBycyhnc2UpLGRlc2lnbikNCmF3DQpgYGANCg0KVGhlIGBsbUZpdGAgZnVuY3Rpb24gY2FuIGFjY2VwdCB3ZWlnaHRzLCBhbmQgdGhlIHJlc3Qgb2YgdGhlIGNvZGUgcHJvY2VlZHMgYXMgYWJvdmUuDQoNCmBgYHtyfQ0KZml0IDwtIGxtRml0KGV4cHJzKGdzZSksIGRlc2lnbiwNCiAgICAgICAgICAgICB3ZWlnaHRzID0gYXcpDQpjb250cmFzdHMgPC0gbWFrZUNvbnRyYXN0cyhUdW1vdXIgLSBOb3JtYWwsIGxldmVscz1kZXNpZ24pDQpmaXQyIDwtIGNvbnRyYXN0cy5maXQoZml0LCBjb250cmFzdHMpDQpmaXQyIDwtIGVCYXllcyhmaXQyKQ0KYGBgDQoNCiMgRnVydGhlciBwcm9jZXNzaW5nIGFuZCB2aXN1YWxpc2F0aW9uIG9mIERFIHJlc3VsdHMNCg0KQXQgdGhlIG1vbWVudCBvdXIgcmVzdWx0cyBhcmUgbm90IHBhcnRpY3VsYXJseSBlYXN5IHRvIG5hdmlnYXRlIGFzIHRoZSBvbmx5IGluZm9ybWF0aW9uIHRvIGlkZW50aWZ5IGVhY2ggZ2VuZSBpcyB0aGUgaWRlbnRpZmllciB0aGF0IHRoZSBtaWNyb2FycmF5IG1hbnVmYWN0dXJlciBoYXMgYXNzaWduZWQuIEZvcnR1bmF0ZWx5LCB0aGUgR0VPIGVudHJ5IGNvbnRhaW5zIGV4dGVuc2l2ZSBhbm5vdGF0aW9uIHRoYXQgd2UgY2FuIGFkZC4gVGhlIGFubm90YXRpb24gZGF0YSBjYW4gYmUgcmV0cmlldmVkIHdpdGggdGhlIGBmRGF0YWAgZnVuY3Rpb24gYW5kIHdlIHJlc3RyaWN0IHRvIGNvbHVtbnMgd2UgYXJlIGludGVyZXN0ZWQgaW4gdXNpbmcgYHNlbGVjdGAuDQoNCioqRm9yIHlvdXIgb3duIGRhdGEsIHlvdSB3aWxsIGhhdmUgdG8gY2hvb3NlIHRoZSBjb2x1bW5zIHRoYXQgYXJlIG9mIGludGVyZXN0IHRvIHlvdS4gWW91IHByb2JhYmx5IHdvbid0IGhhdmUgdGhlIHNhbWUgY29sdW1uIGhlYWRpbmdzIHVzZWQgaGVyZSoqLg0KDQpPbmNlIGFuIGFubm90YXRpb24gZGF0YSBmcmFtZSBoYXMgYmVlbiBjcmVhdGVkLCBpdCBjYW4gYmUgYXNzaWduZWQgdG8gb3VyIHJlc3VsdHMuDQoNCmBgYHtyfQ0KYW5ubyA8LSBmRGF0YShnc2UpDQphbm5vDQphbm5vIDwtIHNlbGVjdChhbm5vLFN5bWJvbCxFbnRyZXpfR2VuZV9JRCxDaHJvbW9zb21lLEN5dG9iYW5kKQ0KZml0MiRnZW5lcyA8LSBhbm5vDQp0b3BUYWJsZShmaXQyKQ0KYGBgDQoNCg0KVGhlICIqVm9sY2FubyBQbG90KiIgZnVuY3Rpb24gaXMgYSBjb21tb24gd2F5IG9mIHZpc3VhbGlzaW5nIHRoZSByZXN1bHRzIG9mIGEgREUgYW5hbHlzaXMuIFRoZSAkeCQgYXhpcyBzaG93cyB0aGUgbG9nLWZvbGQgY2hhbmdlIGFuZCB0aGUgJHkkIGF4aXMgaXMgc29tZSBtZWFzdXJlIG9mIHN0YXRpc3RpY2FsIHNpZ25pZmljYW5jZSwgd2hpY2ggaW4gdGhpcyBjYXNlIGlzIHRoZSBsb2ctb2Rkcywgb3IgIkIiIHN0YXRpc3RpYy4gQSBjaGFyYWN0ZXJpc3RpYyAidm9sY2FubyIgc2hhcGUgc2hvdWxkIGJlIHNlZW4uDQoNCkZpcnN0IHdlIGNyZWF0ZSBhIGRhdGEgZnJhbWUgdGhhdCB3ZSBjYW4gdmlzdWFsaXNlIGluIGBnZ3Bsb3QyYC4gU3BlY2lmeWluZyB0aGUgYG51bWJlcmAgYXJndW1lbnQgdG8gYHRvcFRhYmxlYCBjcmVhdGVzIGEgdGFibGUgY29udGFpbmluZyB0ZXN0IHJlc3VsdHMgZnJvbSBhbGwgZ2VuZXMuIFdlIGFsc28gcHV0IHRoZSBwcm9iZSBJRHMgYXMgYSBjb2x1bW4gcmF0aGVyIHRoYW4gcm93IG5hbWVzLg0KDQpgYGB7cn0NCmZ1bGxfcmVzdWx0cyA8LSB0b3BUYWJsZShmaXQyLCBudW1iZXI9SW5mKQ0KZnVsbF9yZXN1bHRzIDwtIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKGZ1bGxfcmVzdWx0cywiSUQiKQ0KYGBgDQoNClRoZSBiYXNpYyBwbG90IGlzIGNyZWF0ZWQgYXMgZm9sbG93czotDQoNCmBgYHtyfQ0KIyMgTWFrZSBzdXJlIHlvdSBoYXZlIGdncGxvdDIgbG9hZGVkDQpsaWJyYXJ5KGdncGxvdDIpDQpnZ3Bsb3QoZnVsbF9yZXN1bHRzLGFlcyh4ID0gbG9nRkMsIHk9QikpICsgZ2VvbV9wb2ludCgpDQpgYGANCg0KVGhlIGZsZXhpYmlsaXR5IG9mIGBnZ3Bsb3QyYCBhbGxvd3MgdXMgdG8gYXV0b21hdGljYWxseSBsYWJlbCBwb2ludHMgb24gdGhlIHBsb3QgdGhhdCBtaWdodCBiZSBvZiBpbnRlcmVzdC4gRm9yIGV4YW1wbGUsIGdlbmVzIHRoYXQgbWVldCBhIHBhcnRpY3VsYXIgcC12YWx1ZSBhbmQgbG9nIGZvbGQtY2hhbmdlIGN1dC1vZmYuIFdpdGggdGhlIGNvZGUgYmVsb3cgdGhlIHZhbHVlcyBvZiBgcF9jdXRvZmZgIGFuZCBgZmNfY3V0b2ZmYCBjYW4gYmUgY2hhbmdlZCBhcyBkZXNpcmVkLg0KDQpgYGB7cn0NCiMjIGNoYW5nZSBhY2NvcmRpbmcgdG8geW91ciBuZWVkcw0KcF9jdXRvZmYgPC0gMC4wNQ0KZmNfY3V0b2ZmIDwtIDENCg0KZnVsbF9yZXN1bHRzICU+JSANCiAgbXV0YXRlKFNpZ25pZmljYW50ID0gYWRqLlAuVmFsIDwgcF9jdXRvZmYsIGFicyhsb2dGQykgPiBmY19jdXRvZmYgKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IGxvZ0ZDLCB5ID0gQiwgY29sPVNpZ25pZmljYW50KSkgKyBnZW9tX3BvaW50KCkNCmBgYA0KDQpGdXJ0aGVybW9yZSwgd2UgY2FuIGxhYmVsIHRoZSBpZGVudGl0eSBvZiBzb21lIGdlbmVzLiBCZWxvdyB3ZSBzZXQgYSBsaW1pdCBvZiB0aGUgdG9wICJOIiBnZW5lcyB3ZSB3YW50IHRvIGxhYmVsLCBhbmQgbGFiZWwgZWFjaCBnZW5lIGFjY29yZGluZyB0byBpdCdzIGBTeW1ib2xgLiANCg0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dyZXBlbCkNCnBfY3V0b2ZmIDwtIDAuMDUNCmZjX2N1dG9mZiA8LSAxDQp0b3BOIDwtIDIwDQoNCmZ1bGxfcmVzdWx0cyAlPiUgDQogIG11dGF0ZShTaWduaWZpY2FudCA9IGFkai5QLlZhbCA8IHBfY3V0b2ZmLCBhYnMobG9nRkMpID4gZmNfY3V0b2ZmICkgJT4lIA0KICBtdXRhdGUoUmFuayA9IDE6bigpLCBMYWJlbCA9IGlmZWxzZShSYW5rIDwgdG9wTiwgU3ltYm9sLCIiKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBsb2dGQywgeSA9IEIsIGNvbD1TaWduaWZpY2FudCxsYWJlbD1MYWJlbCkpICsgZ2VvbV9wb2ludCgpICsgZ2VvbV90ZXh0X3JlcGVsKGNvbD0iYmxhY2siKQ0KYGBgDQoNCiMgRmlsdGVyaW5nIGFuZCBleHBvcnRpbmcgdGhlIHJlc3VsdHMgdGFibGUNCg0KVGhlIGBmaWx0ZXJgIGZ1bmN0aW9uIGZyb20gYGRwbHlyYCBnaXZlcyBhIGNvbnZlbmllbnQgd2F5IHRvIGludGVycm9nYXRlIHRoZSB0YWJsZSBvZiByZXN1bHRzLg0KDQpgYGB7cn0NCiMjIEdldCB0aGUgcmVzdWx0cyBmb3IgcGFydGljdWxhciBnZW5lIG9mIGludGVyZXN0DQpmaWx0ZXIoZnVsbF9yZXN1bHRzLCBTeW1ib2wgPT0gIlNNT1giKQ0KIyMgR2V0IHJlc3VsdHMgZm9yIGdlbmVzIHdpdGggVFA1MyBpbiB0aGUgbmFtZQ0KZmlsdGVyKGZ1bGxfcmVzdWx0cywgZ3JlcGwoIlRQNTMiLCBTeW1ib2wpKQ0KIyMgR2V0IHJlc3VsdHMgZm9yIG9uZSBjaHJvbW9zb21lDQpmaWx0ZXIoZnVsbF9yZXN1bHRzLCBDaHJvbW9zb21lPT0yMCkNCmBgYA0KDQpXZSBjYW4gYWxzbyBmaWx0ZXIgYWNjb3JkaW5nIHRvIHAtdmFsdWUgKGFkanVzdGVkKSBhbmQgZm9sZC1jaGFuZ2UgY3V0LW9mZnMNCg0KYGBge3J9DQpwX2N1dG9mZiA8LSAwLjA1DQpmY19jdXRvZmYgPC0gMQ0KDQpmaWx0ZXIoZnVsbF9yZXN1bHRzLCBhZGouUC5WYWwgPCAwLjA1LCBhYnMobG9nRkMpID4gMSkNCmBgYA0KDQpUaGVzZSByZXN1bHRzIGNhbiBiZSBleHBvcnRlZCB3aXRoIHRoZSBgd3JpdGVfY3N2YCBmdW5jdGlvbi4NCg0KYGBge3J9DQpsaWJyYXJ5KHJlYWRyKQ0KZmlsdGVyKGZ1bGxfcmVzdWx0cywgYWRqLlAuVmFsIDwgMC4wNSwgYWJzKGxvZ0ZDKSA+IDEpICU+JQ0KICB3cml0ZV9jc3YocGF0aD0iZmlsdGVyZWRfZGVfcmVzdWx0cy5jc3YiKQ0KYGBgDQoNCiMgRnVydGhlciB2aXN1YWxpc2F0aW9uDQoNCiMjIEhlYXRtYXBzIG9mIHNlbGVjdGVkIGdlbmVzDQoNClIgYW5kIEJpb2NvbmR1Y3RvciBoYXZlIG1hbnkgcGFja2FnZXMgZm9yIGNyZWF0aW5nIGhlYXRtYXBzLiBUaGUgbW9zdCBwb3B1bGFyIGF0IHRoZSBjdXJyZW50IHRpbWUgYENvbXBsZXhIZWF0bWFwYCBhbmQgYHBoZWF0bWFwYCAodGhhdCB3ZSB3aWxsIHVzZSBoZXJlKS4NCg0KQ3JlYXRpbmcgdGhlIGhlYXRtYXAgaXMgcHJldHR5IHN0cmFpZ2h0Zm9yd2FyZC4gVGhlcmUgaXMgYSBgcGhlYXRtYXBgIGZ1bmN0aW9uIHdpdGhpbiB0aGUgYHBoZWF0bWFwYCBsaWJyYXJ5LCBhbmQgaXQganVzdCBuZWVkcyB0byBrbm93IHRoZSBtYXRyaXggb2YgdmFsdWVzIHRoYXQgeW91IHdhbnQgdG8gcGxvdCAoc2F5IGBnZW5lX21hdHJpeGApOi0NCg0KYGBge3IgZXZhbD1GQUxTRX0NCiMjIyBleGFtcGxlIGNvZGUNCmxpYnJhcnkocGhlYXRtYXApDQpwaGVhdG1hcChnZW5lX21hdHJpeCkNCmBgYA0KDQpIb3dldmVyLCB0aGVyZSBhcmUgbWFueSBkaWZmZXJlbnQgd2F5cyBvZiBjb250cnVzdGluZyBzdWNoIGEgbWF0cml4IGRlcGVuZGluZyBvbiB3aGF0IHlvdSB3YW50IHRvIHZpc3VhbGlzZSBpbiB0aGUgcGxvdC4gV2Ugd2lsbCBjb25zaWRlciBzb21lIG9wdGlvbnMgYmVsb3cuDQoNCiMjIyBNb3N0IGRpZmZlcmVudGlhbGx5LWV4cHJlc3NlZCBnZW5lcw0KDQpXZSBoYXZlIGFscmVhZHkgY3JlYXRlZCBhIHRhYmxlIG9mIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIHJlc3VsdHMsIHdoaWNoIGlzIHJhbmtlZCBhY2NvcmRpbmcgdG8gc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlLiANCg0KVG8gdmlzdWFsaXNlIHRoZSBtb3N0IGRpZmZlcmVudGlhbGx5LWV4cHJlc3NlZCBnZW5lcywgd2UgZmlyc3QgbmVlZCB0byBleHRyYWN0IHRoZWlyIGBJRGAuIFRoZXNlIElEcyBzaG91bGQgY29ycmVzcG9uZCB0byByb3dzIGluIHRoZSBleHByZXNzaW9uIG1hdHJpeC4NCg0KSW4gdGhlIGNvZGUgYmVsb3cgd2UgaW50cm9kdWNlIGEgbmV3IGNvbHVtbiB0byB0aGUgcmVzdWx0cyB3aGljaCBqdXN0IGdpdmVzIGEgcm93IG51bWJlciB0byBlYWNoIGdlbmUuIFdlIHRoZW4gZmlsdGVyIHRvIHJldHVybiBkYXRhIGZvciB0aGUgdG9wICpOKiByZXN1bHRzLiBUaGUgYHB1bGxgIGZ1bmN0aW9uIGlzIHVzZWQgdG8gZXh0cmFjdCB0aGUgYElEYCBjb2x1bW4gYXMgYSB2YXJpYWJsZS4NCg0KDQpgYGB7cn0NCiMjIFVzZSB0byB0b3AgMjAgZ2VuZXMgZm9yIGlsbHVzdHJhdGlvbg0KDQp0b3BOIDwtIDIwDQojIw0KaWRzX29mX2ludGVyZXN0IDwtIG11dGF0ZShmdWxsX3Jlc3VsdHMsIFJhbmsgPSAxOm4oKSkgJT4lIA0KICBmaWx0ZXIoUmFuayA8IHRvcE4pICU+JSANCiAgcHVsbChJRCkNCmBgYA0KDQpJbiBvcmRlciB0byBsYWJlbCB0aGUgaGVhdG1hcCBpbiBhIHVzZWZ1bCBtYW5uZXIgd2UgZXh0cmFjdCB0aGUgY29ycmVzcG9uZGluZyBnZW5lIHN5bWJvbHMuDQoNCmBgYHtyfQ0KZ2VuZV9uYW1lcyA8LSBtdXRhdGUoZnVsbF9yZXN1bHRzLCBSYW5rID0gMTpuKCkpICU+JSANCiAgZmlsdGVyKFJhbmsgPCB0b3BOKSAlPiUgDQogIHB1bGwoU3ltYm9sKSANCmBgYA0KDQpUaGUgZXhwcmVzc2lvbiB2YWx1ZXMgZm9yIHRoZSBJRHMgd2UgaGF2ZSByZXRyaWV2ZWQgY2FuIGJlIG9idGFpbmVkIGJ5IHVzaW5nIHRoZSBgWy4uXWAgbm90YXRpb24gdG8gaW5kZXggdGhlIGV4cHJlc3Npb24gbWF0cml4LiANCg0KYGBge3J9DQojIyBHZXQgdGhlIHJvd3MgY29ycmVzcG9uZGluZyB0byBpZHNfb2ZfaW50ZXJlc3QgYW5kIGFsbCBjb2x1bW5zDQpnZW5lX21hdHJpeCA8LSBleHBycyhnc2UpW2lkc19vZl9pbnRlcmVzdCxdDQpgYGANCg0KV2Ugbm93IG1ha2UgdGhlIGhlYXRtYXAuIEEgZGVmYXVsdCBjb2xvdXIgc2NoZW1lIGlzIHVzZWQsIGJ1dCBjYW4gYmUgY2hhbmdlZCB2aWEgdGhlIGFyZ3VtZW50cy4gKipQbGVhc2UgZG9uJ3QgdXNlIHJlZCBhbmQgZ3JlZW4uKioNCg0KYGBge3J9DQpwaGVhdG1hcChnZW5lX21hdHJpeCwNCiAgICAgbGFiZWxzX3JvdyA9IGdlbmVfbmFtZXMpDQpgYGANCg0KSXQgaXMgb2Z0ZW4gcHJlZmVyYWJsZSB0byBzY2FsZSBlYWNoIHJvdyB0byBoaWdobGlnaHQgdGhlIGRpZmZlcmVuY2VzIGluIGVhY2ggZ2VuZSBhY3Jvc3MgdGhlIGRhdGFzZXQuDQoNCmBgYHtyfQ0KcGhlYXRtYXAoZ2VuZV9tYXRyaXgsDQogICAgIGxhYmVsc19yb3cgPSBnZW5lX25hbWVzLA0KICAgICBzY2FsZT0icm93IikNCmBgYA0KDQoNCiMjIyBVc2VyLWRlZmluZWQgZ2VuZXMgb2YgaW50ZXJlc3QNCg0KVGhlIHByb2NlZHVyZSBpcyBzaW1pbGFyIHRvIGFib3ZlIGlmIHlvdSBoYXZlIHlvdXIgb3duIGxpc3Qgb2YgZ2VuZXMgKGUuZy4gZ2VuZXMgZnJvbSBhIHByZXZpb3VzIHN0dWR5KS4gVGhlIGAlaW4lYCBmdW5jdGlvbiBpcyB1c2VkIHRvIGlkZW50aWZ5IHJvd3Mgd2hvc2UgYFN5bWJvbGAgbWF0Y2hlcyBhbnkgbWVtYmVyIG9mIGBteV9nZW5lc2AuIEhlcmUgd2UgY3JlYXRlIGBteV9nZW5lc2AgbWFudWFsbHkuIElmIHlvdSB3YW50IHRvIHBsb3QgdGhlIGdlbmVzIGJlbG9uZ2luZyB0byBhIHBhcnRpY3VsYXIgKkdPKiB0ZXJtLCBpdCBtaWdodCBiZSBtb3JlIGVmZmljaWVudCB0byBmb2xsb3cgdGhlIHNlY3Rpb24gYmVsb3cuDQoNCkRlcGVuZGluZyBvbiB0aGUgdGVjaG5vbG9neSB1c2VkLCB0aGVyZSBtaWdodCBiZSBtdWx0aXBsZSBtYXRjaGVzIGZvciBhIHBhcnRpY3VsYXIgZ2VuZTsgc28gd2UgY291bGQgZW5kIHVwIHdpdGggbW9yZSBgSURgcyB0aGFuIGdlbmVzLiBUaGVyZWZvcmUgd2UgcmVwZWF0IHRoZSBmaWx0ZXJpbmcgcHV0IGBwdWxsYCB0aGUgYFN5bWJvbGAgY29sdW1uIHRvIG1ha2Ugc3VyZSB3ZSBjYW4gbGFiZWwgdGhlIHJvd3Mgb2YgdGhlIGhlYXRtYXAuDQoNCmBgYHtyfQ0KDQpteV9nZW5lcyA8LSBjKCJISUcyIiwgIkNBMSIsIkVUVjQiLCJGT1hBMSIpDQppZHNfb2ZfaW50ZXJlc3QgPC0gIGZpbHRlcihmdWxsX3Jlc3VsdHMsU3ltYm9sICVpbiUgbXlfZ2VuZXMpICU+JSANCiAgcHVsbChJRCkNCg0KZ2VuZV9uYW1lcyA8LSAgZmlsdGVyKGZ1bGxfcmVzdWx0cyxTeW1ib2wgJWluJSBteV9nZW5lcykgJT4lIA0KICBwdWxsKFN5bWJvbCkNCmBgYA0KDQpgYGB7cn0NCmdlbmVfbWF0cml4IDwtIGV4cHJzKGdzZSlbaWRzX29mX2ludGVyZXN0LF0NCnBoZWF0bWFwKGdlbmVfbWF0cml4LA0KICAgICAgICAgbGFiZWxzX3JvdyA9IGdlbmVfbmFtZXMsDQogICAgICAgICBzY2FsZT0icm93IikNCg0KYGBgDQoNCg0KIyMjIEZvciBhIHBhcnRpY3VsYXIgcGF0aHdheQ0KDQpCaW9jb25kdWN0b3IgYW5ub3RhdGlvbiBwYWNrYWdlcyBleGlzdCBmb3IgYSBudW1iZXIgb2Ygb3JnYW5pc21zIHRvIGFsbG93IGVhc3kgY29udmVyc2lvbiBiZXR3ZWVuIGRpZmZlcmVudCBJRCBzY2hlbWVzLiBJbiB0aGlzIHBhcnRpY3VsYXIgdXNlLWNhc2Ugd2UgY2FuIHJldHJpZXZlIHRoZSBuYW1lcyBvZiBnZW5lcyBiZWxvbmdpbmcgdG8gYSBnaXZlbiBwYXRod2F5Lg0KDQpZb3UgY2FuIGNoZWNrIHdoYXQgcGFja2FnZXMgYXJlIGF2YWlsYWJsZSBmcm9tIHRoZSBCaW9jb25kdWN0b3IgcGFnZSAobG9vayBmb3IgdGhlIHBhY2thZ2VzIG5hbWVkIGBvcmcuWFguWFguZGJgKQ0KDQotIFtsaXN0IG9mIEJpb2NvbmR1Y3RvciBvcmdhbmlzbSBwYWNrYWdlc10oaHR0cDovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvcmVsZWFzZS9CaW9jVmlld3MuaHRtbCNfX19PcmdhbmlzbSkNCg0KYGBge3IgZXZhbD1GQUxTRX0NCmlmICghcmVxdWlyZU5hbWVzcGFjZSgiQmlvY01hbmFnZXIiLCBxdWlldGx5ID0gVFJVRSkpDQogICAgaW5zdGFsbC5wYWNrYWdlcygiQmlvY01hbmFnZXIiKQ0KDQpCaW9jTWFuYWdlcjo6aW5zdGFsbCgib3JnLkhzLmVnLmRiIikNCg0KYGBgDQoNCk9uY2UgaW5zdGFsbGVkLCB3ZSBsb2FkIHRoZSBwYWNrYWdlIGluIHRoZSB1c3VhbCBtYW5uZXI6LQ0KDQpgYGB7cn0NCmxpYnJhcnkob3JnLkhzLmVnLmRiKQ0KYGBgDQoNCkVhY2ggb2YgdGhlc2Ugb3JnYW5pc20gcGFja2FnZXMgaGFzIGEgc2VyaWVzIG9mIGtleXR5cGVzIHRoYXQgY2FuIHdlIGNhbiB1c2UgdG8gcXVlcnk6LQ0KDQpgYGB7cn0NCmtleXR5cGVzKG9yZy5Icy5lZy5kYikNCmBgYA0KDQphbmQgYSBzZXJpZXMgb2YgY29sdW1ucyBvZiBkYXRhIHRoYXQgd2UgY2FuIHJldHJpZXZlOi0NCg0KYGBge3J9DQpjb2x1bW5zKG9yZy5Icy5lZy5kYikNCmBgYA0KDQpUbyBtYWtlIGEgcXVlcnkgd2UgbmVlZCB0byBzcGVjaWZ5IGEgc2V0IG9mICprZXlzKiAodGhlIElEcyB0aGF0IHdlIHdhbnQgdG8gbWFwKSwgd2hhdCAqdHlwZSogdGhlc2Uga2V5cyAobXVzdCBtYXRjaCBzb21ldGhpbmcgaW4gdGhlIG91dHB1dCBvZiBga2V5dHlwZXNgKSBhbmQgdGhlIGNvbHVtbnMgKHRoZSBhZGRpdGlvbmFsIGRhdGEgd2Ugd2FudCkuDQoNCkZvciBpbGx1c3RyYXRpb24sIHdlJ2xsIHVzZSB0aGUgc2FtZSBnZW5lcyBmcm9tIGFib3ZlIChvZiBrZXl0eXBlIGBTWU1CT0xgKSBhbmQgcmV0cmlldmUgdGhlaXIgYEVOU0VNQkxgIElEIGFuZCBgR09gIHRlcm1zLg0KDQpUaGUgZnVuY3Rpb24gcmVxdWlyZWQgdG8gbWFrZSB0aGUgcXVlcnkgaXMgYWxzbyBjYWxsZWQgYHNlbGVjdGAsIGJ1dCBkaWZmZXJlbnQgZnJvbSB0aGUgYHNlbGVjdGAgZnVuY3Rpb24gd2UgaGF2ZSB1c2VkIGZyb20gYGRwbHlyYC4gVG8gYXZvaWQgY29uZnVzaW9uLCB3ZSBleHBsaWN0bHkgdGVsbCBSIHRvIHVzZSB0aGUgYHNlbGVjdGAgZnVuY3Rpb24gZnJvbSBgQW5ub3RhdGlvbkRiaWAgKHRoZSBwYWNrYWdlIHVzZWQgdG8gcXVlcnkgYW5ub3RhdGlvbiBkYXRhYmFzZXMgYXV0b21hdGljYWxseSBpbnN0YWxsZWQgd2hlbiB3ZSBkb3dubG9hZCBhIGRhdGFiYXNlIHBhY2thZ2UpLg0KDQpgYGB7cn0NCm15X2dlbmVzIDwtIGMoIkhJRzIiLCAiQ0ExIiwiRVRWNCIsIkZPWEExIikNCg0KYW5ubyA8LSBBbm5vdGF0aW9uRGJpOjpzZWxlY3Qob3JnLkhzLmVnLmRiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbnM9YygiRU5TRU1CTCIsIkdPIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXlzPW15X2dlbmVzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2V5dHlwZSA9ICJTWU1CT0wiKQ0KYW5ubw0KYGBgDQoNCldlIGNhbiB1c2UgdGhlIHNhbWUgZnVuY3Rpb24gdG8gcmV0cmlldmUgZ2VuZXMgYmVsb25naW5nIHRvIGEgcGFydGljdWxhciBwYXRod2F5IHdpdGggYXBwcm9wcmlhdGUgYWRqdXN0bWVudHMgdG8gdGhlIGBjb2x1bW5zYCwgYGtleXNgIGFuZCBga2V5dHlwZWAgYXJndW1lbnRzOi0NCg0KYGBge3J9DQphbm5vIDwtIEFubm90YXRpb25EYmk6OnNlbGVjdChvcmcuSHMuZWcuZGIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW5zPSJTWU1CT0wiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2V5cz0iR086MDAwNjMzOCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXl0eXBlPSJHTyIpDQphbm5vDQpgYGANCg0KDQpgYGB7cn0NCm15X2dlbmVzIDwtIHB1bGwoYW5ubywgU1lNQk9MKQ0KaWRzX29mX2ludGVyZXN0IDwtICBmaWx0ZXIoZnVsbF9yZXN1bHRzLFN5bWJvbCAlaW4lIG15X2dlbmVzKSAlPiUgDQogIHB1bGwoSUQpDQoNCmdlbmVfbmFtZXMgPC0gIGZpbHRlcihmdWxsX3Jlc3VsdHMsU3ltYm9sICVpbiUgbXlfZ2VuZXMpICU+JSANCiAgcHVsbChTeW1ib2wpDQpgYGANCg0KYGBge3J9DQpnZW5lX21hdHJpeCA8LSBleHBycyhnc2UpW2lkc19vZl9pbnRlcmVzdCxdDQpwaGVhdG1hcChnZW5lX21hdHJpeCwNCiAgICAgICAgIGxhYmVsc19yb3cgPSBnZW5lX25hbWVzLA0KICAgICAgICAgc2NhbGU9InJvdyIpDQpgYGANCg0KIyBTdXJ2aXZhbCBBbmFseXNpcw0KDQpJbiB0aGlzIHNlY3Rpb24gd2UgZ2l2ZSBhIGJyaWVmIG92ZXJ2aWV3IG9mIGhvdyB0byBwZXJmb3JtIGEgc3Vydml2YWwgYW5hbHlzaXMgZnJvbSBhIHB1Ymxpc2hlZCBkYXRhc2V0LiBUaGUgZXhhbXBsZSBkYXRhc2V0IGluIHF1ZXN0aW9uLCBhbHRob3VnaCBxdWl0ZSBvbGQsIGlzIGEgdXNlZnVsIGV4YW1wbGUgb2YgcHJlZGljdGluZyBzdXJ2aXZhbCBpbiBicmVhc3QgY2FuY2VyLiANCg0KWW91IHdpbGwgbmVlZCB0byBpbnN0YWxsIGFuIGV4dHJhIHBhY2thZ2UsIGBzdXJ2bWluZXJgIGZvciB0aGUgc3Vydml2YWwgYW5hbHlzaXMgaXRzZWxmLg0KDQpgYGB7ciBldmFsPUZBTFNFfQ0KaW5zdGFsbC5wYWNrYWdlcygic3Vydm1pbmVyIikNCmBgYA0KDQpXZSB3aWxsIHVzZSB0aGUgdXN1YWwgY29tbWFuZHMgZm9yIGltcG9ydGluZyB0aGUgZGF0YS4NCg0KYGBge3J9DQpsaWJyYXJ5KEdFT3F1ZXJ5KQ0KZ3NlIDwtIGdldEdFTygnR1NFNzM5MCcpW1sxXV0NCmBgYA0KDQpXZSBhcmUgZ29pbmcgdG8gYmUgaW50ZXJlc3RlZCBpbiB0aGUgcGhlbm90eXBpYyAoL2NsaW5pY2FsKSBkYXRhIHN0b3JlZCB3aXRoIHRoZSBkYXRhc2V0LCB3aGljaCB3aWxsIHVuZm9ydHVuYXRlbHkgcmVxdWlyZSBzb21lIGNsZWFuaW5nIHByaW9yIHRvIGFuYWx5c2lzLiBUaGlzIHdpbGwgYmUgcXVpdGUgYSBsYWJvcmlvdXMgcHJvY2VzcyBhcyB0aGVyZSBhcmUgbWFueSB2YXJpYWJsZXMgb2YgaW50ZXJlc3QuIEZvciB5b3VyIG93biBkYXRhc2V0LCB5b3UgbWF5IG5lZWQgdG8gYWRhcHQgdGhlIGNvZGUgYWNjb3JkaW5nbHkuDQoNCg0KVG8gZXllLWJhbGwgdGhlIGNvbnRlbnRzIHdlIGNhbiB1c2UgdGhlIGBWaWV3YCBjb21tYW5kIGluIFJTdHVkaW8uDQoNCg0KYGBge3J9DQpWaWV3KHBEYXRhKGdzZSkpDQpgYGANCg0KSXQgc2VlbXMgdGhhdCBtb3N0IG9mIHRoZSB1c2VmdWwgY29sdW1ucyBhcmUgcHJlZml4ZWQgYnkgYGNoYXJhY3RlcmlzdGljc2AsIHNvIHdlIGNhbiB1c2UgdGhlIGNvbnZlbmllbnQgYGNvbnRhaW5zYCBmdW5jdGlvbiB0byBgc2VsZWN0YCB0aGVzZS4gYGNoYXJhY3RlcmlzdGljc19jaDFgIGFuZCBgY2hhcmFjdGVyaXN0aWNzX2NoMS4yYCBhcmUgcHJvYmFibHkgbm90IHVzZWZ1bCwgc28gd2Ugd2lsbCByZW1vdmUgdGhlc2UuIA0KDQpgYGB7cn0NCmxpYnJhcnkoZHBseXIpDQpzX2RhdGEgPC0gcERhdGEoZ3NlKSAlPiUgDQogIGRwbHlyOjpzZWxlY3QoZ2VvX2FjY2Vzc2lvbiwgY29udGFpbnMoImNoYXJhY3RlcmlzdGljcyIpLCAtY2hhcmFjdGVyaXN0aWNzX2NoMS4yLCAtY2hhcmFjdGVyaXN0aWNzX2NoMSwgLWNoYXJhY3RlcmlzdGljc19jaDEuMSkNCmBgYA0KDQpOb25lIG9mIHRoZSBjb2x1bW5zIGhhdmUgdmVyeSBjb252ZW5pZW50IG5hbWVzLCBzbyB3ZSB3aWxsIGdvIGFoZWFkIGFuZCBgcmVuYW1lYCB0aGVtLg0KDQpgYGB7cn0NCnNfZGF0YSA8LSBzX2RhdGEgJT4lIA0KICBkcGx5cjo6cmVuYW1lKGhvc3BpdGFsID0gY2hhcmFjdGVyaXN0aWNzX2NoMS4zLA0KICAgICAgICAgYWdlID0gY2hhcmFjdGVyaXN0aWNzX2NoMS40LA0KICAgICAgICAgc2l6ZSA9IGNoYXJhY3RlcmlzdGljc19jaDEuNSwNCiAgICAgICAgIHN1cmdlcnlfdHlwZSA9IGNoYXJhY3RlcmlzdGljc19jaDEuNiwNCiAgICAgICAgIGhpc3R0eXBlID0gY2hhcmFjdGVyaXN0aWNzX2NoMS43LA0KICAgICAgICAgYW5naW9pbnYgPSBjaGFyYWN0ZXJpc3RpY3NfY2gxLjgsDQogICAgICAgICBseW1wX2luZmlsID0gY2hhcmFjdGVyaXN0aWNzX2NoMS45LA0KICAgICAgICAgbm9kZSA9IGNoYXJhY3RlcmlzdGljc19jaDEuMTAsDQogICAgICAgICBncmFkZSA9IGNoYXJhY3RlcmlzdGljc19jaDEuMTEsDQogICAgICAgICBlciA9IGNoYXJhY3RlcmlzdGljc19jaDEuMTIsDQogICAgICAgICB0LnJmcyA9IGNoYXJhY3RlcmlzdGljc19jaDEuMTMsDQogICAgICAgICBlLnJmcyA9IGNoYXJhY3RlcmlzdGljc19jaDEuMTQsDQogICAgICAgICB0Lm9zID0gY2hhcmFjdGVyaXN0aWNzX2NoMS4xNSwNCiAgICAgICAgIGUub3MgPSBjaGFyYWN0ZXJpc3RpY3NfY2gxLjE2LA0KICAgICAgICAgdC5kbWZzID0gY2hhcmFjdGVyaXN0aWNzX2NoMS4xNywNCiAgICAgICAgIGUuZG1mcyA9Y2hhcmFjdGVyaXN0aWNzX2NoMS4xOCwNCiAgICAgICAgIHQudGRtID0gY2hhcmFjdGVyaXN0aWNzX2NoMS4xOSwNCiAgICAgICAgIGUudGRtID0gY2hhcmFjdGVyaXN0aWNzX2NoMS4yMCwNCiAgICAgICAgIHJpc2tzZyA9IGNoYXJhY3RlcmlzdGljc19jaDEuMjEsDQogICAgICAgICBucGkgPSBjaGFyYWN0ZXJpc3RpY3NfY2gxLjIyLA0KICAgICAgICAgcmlza25waSA9IGNoYXJhY3RlcmlzdGljc19jaDEuMjMsDQogICAgICAgICBhb2xfb3NfMTB5ciA9IGNoYXJhY3RlcmlzdGljc19jaDEuMjQsDQogICAgICAgICByaXNrX2FvbCA9IGNoYXJhY3RlcmlzdGljc19jaDEuMjUsDQogICAgICAgICB2ZXJpZGV4X3Jpc2sgPSBjaGFyYWN0ZXJpc3RpY3NfY2gxLjI2KQ0KDQpgYGANCg0KVGhlIGNvbHVtbnMgdGhlbXNlbHZlcyBjb250YWluIGVudHJpZXMgdGhhdCBhcmUgbm90IHBhcnRpY3VsYXJseSBjb252ZW5pZW50IGZvciBhbmFseXNpcy4gRm9yIGV4YW1wbGUsIGluIHRoZSBgYWdlYCBjb2x1bW4gd2Ugd291bGQgZXhwZWN0IHRvIGZpbmQgdGhlIGFnZSBvZiBwYXRpZW50cyBpbiB5ZWFycy4gSW5zdGVhZCBlYWNoIGVudHJ5IGlzIHByZWZpeGVkIGJ5IHRoZSBzdHJpbmcgYGFnZTogYCwgYW5kIHRoZSBzYW1lIGlzIHRydWUgZm9yIG90aGVyIGNvbHVtbnMgb2YgaW50ZXJlc3QuIFdlIGNhbiBmaXggdGhpcyBieSBhIHBlcmZvcm1pbmcgYSAqZypsb2JhbCAqc3ViKnN0aXR1aW9uIGluIHRoZSBvZmZlbmRpbmcgY29sdW1uczsgcmVwbGFjaW5nIHRoZSBwcmVmaXggd2l0aCBhbiBlbXB0eSBzdHJpbmcgYCIiYC4gU2VlIHRoZSBoZWxwIG9uIGBnc3ViYCBmb3IgbW9yZSBpbmZvcm1hdGlvbi4gVGhlIGBkcGx5cmAgZnVuY3Rpb24gYG11dGF0ZWAgd2lsbCBzYXZlIHRoZSB1cGRhdGUgY29sdW1uIGluIHRoZSBkYXRhIGZyYW1lLg0KDQpgYGB7cn0NCnNfZGF0YSA8LSBzX2RhdGEgJT4lIA0KICBtdXRhdGUoaG9zcGl0YWwgPSBnc3ViKCJob3NwaXRhbDogIiwgIiIsIGhvc3BpdGFsLCBmaXhlZD1UUlVFKSwNCiAgICAgICAgIGFnZSA9IGFzLm51bWVyaWMoZ3N1YigiYWdlOiAiLCIiLCBhZ2UsIGZpeGVkPVRSVUUpKSwNCiAgICAgICAgIHNpemUgPSBhcy5udW1lcmljKGdzdWIoInNpemU6ICIsIiIsIHNpemUsIGZpeGVkPVRSVUUpKSwNCiAgICAgICAgIHN1cmdlcnlfdHlwZSA9IGdzdWIoIlN1cmdlcnlfdHlwZTogIiwiIiwgc3VyZ2VyeV90eXBlLCBmaXhlZD1UUlVFKSwNCiAgICAgICAgIGhpc3R0eXBlID0gZ3N1YigiSGlzdHR5cGU6ICIsIiIsIGhpc3R0eXBlLCBmaXhlZD1UUlVFKSwNCiAgICAgICAgIGFuZ2lvaW52ID0gZ3N1YigiQW5naW9pbnY6ICIsIiIsIGFuZ2lvaW52LCBmaXhlZD1UUlVFKSwNCiAgICAgICAgIGx5bXBfaW5maWwgPSBnc3ViKCJMeW1wX2luZmlsOiAiLCIiLCBseW1wX2luZmlsLCBmaXhlZD1UUlVFKSwNCiAgICAgICAgIG5vZGUgPSBnc3ViKCJub2RlOiAiLCIiLCBub2RlLCBmaXhlZD1UUlVFKSwNCiAgICAgICAgIGdyYWRlID0gZ3N1YigiZ3JhZGU6ICIsIiIsIGdyYWRlLCBmaXhlZD1UUlVFKSwNCiAgICAgICAgIGVyID0gZ3N1YigiZXI6ICIsIiIsIGVyLCBmaXhlZD1UUlVFKSwNCiAgICAgICAgIHQucmZzID0gYXMubnVtZXJpYyhnc3ViKCJ0LnJmczogIiwiIiwgdC5yZnMsIGZpeGVkPVRSVUUpKSwNCiAgICAgICAgIGUucmZzID0gYXMubnVtZXJpYyhnc3ViKCJlLnJmczogIiwiIiwgZS5yZnMsIGZpeGVkPVRSVUUpKSwNCiAgICAgICAgIHQub3MgPSBhcy5udW1lcmljKGdzdWIoInQub3M6ICIsIiIsIHQub3MsIGZpeGVkPVRSVUUpKSwNCiAgICAgICAgIGUub3MgPSBhcy5udW1lcmljKGdzdWIoImUub3M6ICIsIiIsIGUub3MsIGZpeGVkPVRSVUUpKSwNCiAgICAgICAgIHQuZG1mcyA9IGFzLm51bWVyaWMoZ3N1YigidC5kbWZzOiAiLCIiLCB0LmRtZnMsIGZpeGVkPVRSVUUpKSwNCiAgICAgICAgIGUuZG1mcyA9IGFzLm51bWVyaWMoZ3N1YigiZS5kbWZzOiAiLCIiLCBlLmRtZnMsIGZpeGVkPVRSVUUpKSwNCiAgICAgICAgIHQudGRtID0gYXMubnVtZXJpYyhnc3ViKCJ0LnRkbTogIiwiIiwgdC50ZG0sIGZpeGVkPVRSVUUpKSwNCiAgICAgICAgIGUudGRtID0gYXMubnVtZXJpYyhnc3ViKCJlLnRkbTogIiwiIiwgZS50ZG0sIGZpeGVkPVRSVUUpKSwNCiAgICAgICAgIHJpc2tzZyA9IGdzdWIoInJpc2tzZzogIiwiIiwgcmlza3NnLCBmaXhlZD1UUlVFKSwNCiAgICAgICAgIG5waSA9IGFzLm51bWVyaWMoZ3N1YigiTlBJOiAiLCIiLCBucGksIGZpeGVkPVRSVUUpKSwNCiAgICAgICAgIHJpc2tucGkgPSBnc3ViKCJyaXNrbnBpOiAiLCIiLCByaXNrbnBpLCBmaXhlZD1UUlVFKSwNCiAgICAgICAgIGFvbF9vc18xMHlyID0gYXMubnVtZXJpYyhnc3ViKCJBT0xfb3NfMTB5OiAiLCIiLCBhb2xfb3NfMTB5ciwgZml4ZWQ9VFJVRSkpLA0KICAgICAgICAgcmlza19hb2wgPSBnc3ViKCJyaXNrX0FPTDogIiwiIiwgcmlza19hb2wsIGZpeGVkPVRSVUUpLA0KICAgICAgICAgdmVyaWRleF9yaXNrID0gZ3N1YigidmVyaWRleF9yaXNrOiAiLCIiLCB2ZXJpZGV4X3Jpc2ssIGZpeGVkPVRSVUUpDQogICAgICAgICApDQpgYGANCkZpbmFsbHkgd2UgY2FuIHN0YXJ0IHRvIGZpdCBzdXJ2aXZhbCBtb2RlbHMgdG8gdGhlIGRhdGEuIEZvciBtb3JlIGRldGFpbGVkIGV4cGxhbmF0aW9uIG9mIHRoZSBgc3Vydm1pbmVyYCBwYWNrYWdlLCB0aGUgcHJvamVjdCBwYWdlIGhhcyBsb3RzIG9mIHVzZWZ1bCBpbmZvcm1hdGlvbi4gSXQgaXMgd2VsbC1rbm93biB0aGF0IHRoZSBFc3Ryb2dlbiBSZWNlcHRvciAoRVIpIHN0YXR1cyBvZiBhIGJyZWFzdCBjYW5jZXIgaXMgcHJlZGljdGl2ZSBvZiBzdXJ2aXZhbC4gV2UgaGF2ZSB0aGlzIHZhcmlhYmxlIGluIHRoZSBgZXJgIGNvbHVtbiBvZiBgc19kYXRhYCwgc28gY2FuIGZpdCB0aGUgbW9kZWwgYW5kIHBsb3Qgd2l0aCB0aGUgZm9sbG93aW5nOi0NCg0KYGBge3J9DQpsaWJyYXJ5KHN1cnZpdmFsKQ0KbGlicmFyeShzdXJ2bWluZXIpDQpmaXQgPC0gc3VydmZpdChTdXJ2KHQub3MsIGUub3MpIH4gZXIsIGRhdGEgPSBzX2RhdGEpDQpnZ3N1cnZwbG90KGZpdCwgZGF0YSA9IHNfZGF0YSxwdmFsID0gVFJVRSkNCmBgYA0KDQoNClRoZSBwLXZhbHVlIGlzIHNpZ25pZmljYW50LCBhcyB3ZSB3b3VsZCBob3BlLCB3aXRoIHBhdGllbnRzIGhhdmluZyBhIG5lZ2F0aXZlIEVSIHN0YXR1cyBoYXZpbmcgcG9vcmVyIHN1cnZpdmFsLiBPdGhlciB2YXJpYWJsZXMgY2FuIGJlIHRlc3RlZCBieSBtb2RpZnlpbmcgdGhlIGZvcm11bGEuIEZvciBtb3JlIGluZm9ybWF0aW9uIG9uIGBzdXJ2bWluZXJgIHNlZSB0aGUgcGFja2FnZSBkb2N1bWVudGF0aW9uIGF0Oi0NCg0KLSBbaHR0cHM6Ly9naXRodWIuY29tL2thc3NhbWJhcmEvc3Vydm1pbmVyXShodHRwczovL2dpdGh1Yi5jb20va2Fzc2FtYmFyYS9zdXJ2bWluZXIpDQoNCiMjIFRlc3RpbmcgU3Vydml2YWwgYXNzb2NpYXRpb24gd2l0aCBnZW5lIGV4cHJlc3Npb24NCg0KVGhlIHJlYWwgdXRpbGl0eSBvZiBoYXZpbmcgdGhlIEdFTyBkYXRhc2V0IGluIGEgY29udmVuaWVudCBmb3JtYXQgaXMgdG8gdGVzdCBmb3IgYXNzb2NpYXRpb25zIHdpdGggdGhlIGV4cHJlc3Npb24gbGV2ZWwgb2YgYSBwYXJ0aWN1bGFyIGdlbmU7IHJhdGhlciB0aGFuIGEgcHJlLWRlZmluZWQgY2xpbmljYWwgdmFyaWFibGUuIFRodXMsIHdlIGNhbiB2YWxpZGF0ZSBpZiB0aGUgZ2VuZXMgd2UgYXJlIGludGVyZXN0ZWQgaW4gbWlnaHQgYmUgcHJlZGljdGl2ZSBvZiBzdXJ2aXZhbC4gQXMgYW4gZXhhbXBsZSB3ZSB3aWxsIHVzZSB0aGUgZ2VuZSBgRVNSMWAgKEVzdHJvZ2VuIFJlY2VwdG9yIDEpLg0KDQpXZSBjYW4gZ2V0IGEgbG9vayBhdCB0aGUgZXhwcmVzc2lvbiB2YWx1ZXMgYnkgcHJpbnRpbmcgdGhlIGZpcnN0IGZpdmUgcm93cyBhbmQgY29sdW1ucy4gSXQgaXMgcXVpdGUgY29tbW9uIGZvciBtaWNyb2FycmF5IGRhdGFzZXRzIHRvIGhhdmUgZWFjaCByb3cgbGFiZWxsZWQgYWNjb3JkaW5nIHRvIGEgcGFydGljdWxhciBtaWNyb2FycmF5IHByb2JlIGlkZW50aWZlciAoYXMgd2UgaGF2ZSBoZXJlKSwgc28gd2Ugd2lsbCBuZWVkIGEgd2F5IG9mIGJlaW5nIGFibGUgdG8gaWRlbnRpZnkgZ2VuZXMgYnkgdGhlaXIgbW9yZS1jb21tb24gbmFtZS4NCg0KYGBge3J9DQpleHBycyhnc2UpWzE6NSwxOjVdDQpgYGANCg0KTW9yZSBpbmZvcm1hdGlvbiBhYm91dCB0aGUgcHJvYmVzIGlzIHJldHJpZXZlZCB1c2luZyB0aGUgYGZEYXRhYCBmdW5jdGlvbi4NCg0KYGBge3J9DQpmZWF0dXJlcyA8LSBmRGF0YShnc2UpDQpWaWV3KGZlYXR1cmVzKQ0KYGBgDQoNCkluIHRoaXMgY2FzZSwgdGhlIGNvbHVtbnMgb2YgaW50ZXJlc3QgYXJlIHByb2JhYmx5IGBJRGAgKHdoaWNoIHNob3VsZCBtYXRjaCB0aGUgcm93cyBvZiBvdXIgZXhwcmVzc2lvbiBtYXRyaXgpIGFuZCBgR2VuZSBTeW1ib2xgLiBGb3IgeW91ciBvd24gZGF0YSwgeW91IHdpbGwgaGF2ZSB0byB3b3JrIG91dCB0aGUgbmFtZXMgb2YgY29sdW1ucyB5b3Ugd2FudCB0byB1c2UuDQoNCg0KYGBge3J9DQpmZWF0dXJlcyA8LSBkcGx5cjo6c2VsZWN0KGZlYXR1cmVzLCBJRCwgYEdlbmUgU3ltYm9sYCkNCmBgYA0KDQoNCldlIGNhbiBhbHNvIG5vdGljZSB0aGF0IHNvbWUgcm93cyBoYXZlIG11bHRpcGxlIGVudHJpZXMgc2VwYXJhdGVkIGJ5IHRoZSBgLy8vYCBzZXQgb2YgY2hhcmFjdGVycy4gV2UgY2FuIHNwbGl0IGludG8gbXVsdGlwbGUgY29sdW1ucyB1c2luZyB0aGUgYHNlcGFyYXRlYCBmdW5jdGlvbiBmcm9tIGB0aWR5cmAuIFlvdSBtaWdodCBub3QgbmVlZCB0byBkbyB0aGlzIGZvciB5b3VyIG93biBkYXRhc2V0IG9mIGludGVyZXN0Lg0KDQpgYGB7cn0NCmZlYXR1cmVzIDwtIHRpZHlyOjpzZXBhcmF0ZShmZWF0dXJlcyxgR2VuZSBTeW1ib2xgLCBpbnRvID0gYygiU3ltYm9sXzEiLCJTeW1ib2xfMiIpLCBzZXA9IiAvLy8gIikNCmBgYA0KDQpXZSBjYW4gbm93IGpvaW4gdGhpcyB0byBvdXIgZXhwcmVzc2lvbiBtYXRyaXguDQoNCmBgYHtyfQ0KZV9kYXRhIDwtIGV4cHJzKGdzZSkgJT4lIA0KICBkYXRhLmZyYW1lICU+JSANCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oIklEIikgJT4lIA0KICBsZWZ0X2pvaW4oZmVhdHVyZXMpDQplX2RhdGENCmBgYA0KTm93IHRoYXQgd2UgaGF2ZSBnZW5lIHN5bWJvbHMgaW4gdGhlIHNhbWUgdGFibGUgYXMgdGhlIGV4cHJlc3Npb24gZGF0YSB3ZSBjYW4gc2VhcmNoIGFjY29yZGluZyB0byB0aGUgZ2VuZSBuYW1lLiBEZXBlbmRpbmcgb24gd2hhdCBnZW5lIGlzIGNob3NlbiB5b3UgbWF5IGdldCBtdWx0aXBsZSBwcm9iZXMuIEl0IGxvb2tzIGxpa2UgYEVTUjFgIGhhcyBxdWl0ZSBhIGZldyBpbiB0aGlzIGRhdGFzZXQuDQoNCkluIHRoaXMgc3RlcCB3ZSBjYW4gYWxzbyBjaG9vc2UgdG8gcmVtb3ZlIHRoZSBzeW1ib2wgY29sdW1uIGFzIHdlIHdvbid0IG5lZWQgaXQgbGF0ZXIgb24uDQoNCmBgYHtyfQ0KZV9kYXRhIDwtIGZpbHRlcihlX2RhdGEsU3ltYm9sXzEgJWluJSAiRVNSMSIpICU+JSANCiAgZHBseXI6OnNlbGVjdCgtU3ltYm9sXzEsIC1TeW1ib2xfMikNCmVfZGF0YQ0KYGBgDQoNCldlIHdvdWxkIGxpa2UgdG8gaW5jb3Jwb3JhdGUgdGhlc2UgdmFsdWVzIGludG8gb3VyIGFuYWx5c2lzLCBidXQgYXQgdGhlIG1vbWVudCB0aGUgZGltZW5zaW9ucyBhcmUgbm90IGNvbXBhdGlibGUgd2l0aCBgc19kYXRhYDsgd2UgbmVlZCBhIGRhdGEgZnJhbWUgd2l0aCBvbmUgcm93IGZvciBlYWNoIHNhbXBsZSBhbmQgYSBjb2x1bW4gZm9yIGVhY2ggZmVhdHVyZSBvZiBpbnRlcmVzdCAoaW4gdGhpcyBjYXNlIGEgZ2VuZSBleHByZXNzaW9uIHByb2JlKS4NCg0KYGBge3J9DQpkaW0oc19kYXRhKQ0KZGltKGVfZGF0YSkNCmBgYA0KDQoNCkVzc2VudGlhbGx5IHRoZSBkYXRhIG5lZWQgdG8gYmUgKnRyYW5zcG9zZWQqLiBXaGlsc3QgUiBwcm92aWRlcyBhIGZ1bmN0aW9uIGB0YCBmb3IgdHJhbnNwb3NpbmcsIGl0IGRvZXNuJ3Qgc2VlbSB0byB3b3JrIHdlbGwgKGluIG15IGV4cGVyaWVuY2UpIGZvciBkYXRhIGZyYW1lcy4gIEluc3RlYWQgd2UgY2FuIHVzZSBhIHR3by1zdGVwIHByb2Nlc3Mgb2YgY29udmVydGluZyB0byBhICpsb25nKiBkYXRhIHR5cGUgKHdpdGggbW9yZSByb3dzIHRoYW4gY29sdW1ucykgd2l0aCBgZ2F0aGVyYCBmcm9tIHRoZSBgdGlkeXJgIHBhY2thZ2UsIGFuZCB0aGVuIG1ha2luZyBpbnRvIGEgd2lkZSB0YWJsZSB3aXRoIGV4cHJlc3Npb24gcHJvYmVzIGFzIGNvbHVtbnMgd2l0aCBgc3ByZWFkYC4gDQoNClNvbWUgcGljdG9yaWFsIHJlcHJlc2VudGF0aW9uIG9mIGBnYXRoZXJgIGFuZCBgc3ByZWFkYCBpcyBwcm92aWRlZCBvbiBbdGhpcyBjaGVhdHNoZWV0XShodHRwczovL3JzdHVkaW8uY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE1LzAyL2RhdGEtd3JhbmdsaW5nLWNoZWF0c2hlZXQucGRmKQ0KDQpZb3Ugd2lsbCBuZWVkIHRvIG1ha2Ugc3VyZSB5b3UgaGF2ZSBpbnN0YWxsZWQgYHRpZHlyYC4gT25jZSB5b3UgaGF2ZSBpbnN0YWxsZWQgaXQsIHlvdSB3b24ndCBuZWVkIHRvIHJlcGVhdCB0aGlzIHN0ZXANCg0KYGBge3IgZXZhbD1GQUxTRX0NCmluc3RhbGwucGFja2FnZXMoInRpZHlyIikNCmBgYA0KDQpOb3cgZm9yIHRoZSB0cmFuc2Zvcm1hdGlvbi4gV2hlbiB3ZSBgZ2F0aGVyYCB0aGUgZGF0YSB3ZSBjYW4gY2hvb3NlIHRoZSBjb2x1bW4gbmFtZXMgaW4gdGhlIG91dHB1dCBkYXRhIGZyYW1lLiBJIGhhdmUgY2hvc2VuIGBnZW9fYWNjZXNzaW9uYCBiZWNhdXNlIHRoaXMgd2lsbCBoZWxwIGxhdGVyIG9uIHdoZW4gSSB3YW50IHRvIG1lcmdlIHdpdGggdGhlIHN1cnZpdmFsIGRhdGEgKHdoaWNoIGFscmVhZHkgaGFzIGEgY29sdW1uIGJ5IHRoaXMgbmFtZSkuIA0KDQpgYGB7cn0NCmVfZGF0YSA8LSBlX2RhdGEgJT4lIHRpZHlyOjpnYXRoZXIoZ2VvX2FjY2Vzc2lvbiwgRXhwcmVzc2lvbiwtSUQpICU+JSANCiAgdGlkeXI6OnNwcmVhZChJRCwgRXhwcmVzc2lvbikNCmVfZGF0YQ0KIyMgVE8tRE86IHJlcGxhY2Ugd2l0aCBwaXZvdF93aWRlciBhbmQgcGl2b3RfbG9uZ2VyIGF0IHNvbWUgcG9pbnQNCmBgYA0KDQpUaGUgZGltZW5zaW9ucyBzaG91bGQgbm93IG1hdGNoIG91ciBzdXJ2aXZhbCBkYXRhLiBXZSBjYW4gbm93IGpvaW4gdGhlc2UgdXNpbmcgdGhlIGBsZWZ0X2pvaW5gIGZ1bmN0aW9uIGZyb20gYGRwbHlyYC4gVGhpcyBpcyBwb3NzaWJsZSBiZWNhdXNlIGJvdGggYHNfZGF0YWAgYW5kIGBlX2RhdGFgIGhhdmUgYSBjb2x1bW4gKGBnZW9fYWNjZXNzaW9uYCkgaW4gY29tbW9uLg0KDQpgYGB7cn0NCnNfZGF0YSA8LSBsZWZ0X2pvaW4oc19kYXRhLCBlX2RhdGEpDQpgYGANCg0KQXMgYW4gYXNpZGUsIHdlIGNhbiBkbyBhIHF1aWNrIHNhbml0eSBjaGVjayBhcyB3ZSBleHBlY3QgKipFU1IxKiogdG8gYmUgbG93bHktZXhwcmVzc2VkIGluIEVSIG5lZ2F0aXZlIHR1bW91cnMuDQoNCmBgYHtyfQ0KZ2dwbG90KHNfZGF0YSwgYWVzKHggPSBlciwgeSA9YDIwNTIyNV9hdGApKSArIGdlb21fYm94cGxvdCgpDQpgYGANCg0KQXMgd2Ugc2F3IGJlZm9yZSwgdGhlIHN1cnZpdmFsIGFuYWx5c2lzIHJlcXVpcmVzIGEgZ3JvdXBpbmcgdmFyaWFibGUgdG8gc3BsaXQgb3VyIGRhdGEgKHByZXZpb3VzbHkgd2UgdXNlZCB0aGUgYGVyYCBjb2x1bW4pLiBBIGNvbnZlbmllbnQgd2F5IHRvIGNyZWF0ZSBhIG5ldyBncm91cGluZyB2YXJpYWJsZSBpcyB0byB1c2UgdGhlIGBpZmVsc2VgIGZ1bmN0aW9uLiBUaGlzIHdpbGwgY3JlYXRlIGEgbmV3IHZhcmlhYmxlIG9mIHR3byB2YWx1ZXMgZGVwZW5kaW5nIG9uIGEgbG9naWNhbCBleHByZXNzaW9uLiBJbiB0aGlzIGNhc2Ugd2UgY2FuIGFzc2lnbiBgSGlnaGAgb3IgYExvd2AgZ3JvdXBpbmcgZGVwZW5kaW5nIG9uIHdoZXRoZXIgdGhlIGV4cHJlc3Npb24gdmFsdWVzIGZvciBhIGdpdmVuIHByb2JlIChgMjA1MjI1X2F0YCkgZXhjZWVkIGEgY3V0b2ZmIG9yIG5vdC4gRm9yIHRoaXMgZXhhbXBsZSB3ZSB3aWxsIHVzZSB0aGUgMjV0aCBwZXJjZW50aWxlICh2aWEgdGhlIGBxdWFudGlsZWAgZnVuY3Rpb24pIGFzIHdlIGtub3cgbG93IGV4cHJlc3Npb24gaXMgcHJlZGljdGl2ZQ0KDQoNCmBgYHtyfQ0Kc19kYXRhIDwtIG11dGF0ZShzX2RhdGEsIEdyb3VwID0gaWZlbHNlKGAyMDUyMjVfYXRgID4gcXVhbnRpbGUoYDIwNTIyNV9hdGAsMC4yNSksICJIaWdoIiwiTG93IikpDQpgYGANCg0KYGBge3J9DQpmaXQgPC0gc3VydmZpdChTdXJ2KHQub3MsIGUub3MpIH4gR3JvdXAsIGRhdGEgPSBzX2RhdGEpDQpnZ3N1cnZwbG90KGZpdCwgZGF0YSA9IHNfZGF0YSxwdmFsID0gVFJVRSkNCmBgYA0KYGBge3J9DQpjdXRvZmYgPC0gMC41DQpzX2RhdGEgPC0gbXV0YXRlKHNfZGF0YSwgR3JvdXAgPSBpZmVsc2UoYDIwNTIyNV9hdGAgPiBxdWFudGlsZShgMjA1MjI1X2F0YCxjdXRvZmYpLCAiSGlnaCIsIkxvdyIpKQ0KYGBgDQoNCmBgYHtyfQ0KZml0IDwtIHN1cnZmaXQoU3Vydih0Lm9zLCBlLm9zKSB+IEdyb3VwLCBkYXRhID0gc19kYXRhKQ0KZ2dzdXJ2cGxvdChmaXQsIGRhdGEgPSBzX2RhdGEscHZhbCA9IFRSVUUpDQpgYGANCg==