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 1
s; 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 ID
s 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==