library(biomaRt)
library(DESeq2)
library(tidyverse)
Before starting this section, we will make sure we have all the relevant objects from the Differential Expression analysis.
load("Robjects/DE.RData")
Overview
- Getting annotation
- Visualising DE results
Adding annotation to the DESeq2 results
We have a list of significantly differentially expressed genes, but the only annotation we can see is the Ensembl Gene ID, which is not very informative.
There are a number of ways to add annotation. One method is to do this using the org.Mm.eg.db package. This package is one of several organism-level packages which are re-built every 6 months. These packages are listed on the annotation section of the Bioconductor, and are installed in the same way as regular Bioconductor packages.
An alternative approach is to use biomaRt
, an interface to the BioMart resource. This is the method we will use today.
Select BioMart database and dataset
The first step is to select the Biomart database we are going to access and which data set we are going to use.
| | | | |
---|
ENSEMBL_MART_ENSEMBL | Ensembl Genes 95 | | | |
ENSEMBL_MART_MOUSE | Mouse strains 95 | | | |
ENSEMBL_MART_SNP | Ensembl Variation 95 | | | |
ENSEMBL_MART_FUNCGEN | Ensembl Regulation 95 | | | |
ensembl=useMart("ENSEMBL_MART_ENSEMBL")
listDatasets(ensembl) %>%
filter(str_detect(description, "Mouse"))
| | | | |
---|
mmurinus_gene_ensembl | Mouse Lemur genes (Mmur_3.0) | Mmur_3.0 | | |
mmusculus_gene_ensembl | Mouse genes (GRCm38.p6) | GRCm38.p6 | | |
ensembl = useDataset("mmusculus_gene_ensembl", mart=ensembl)
Query the database
Now we need to set up a query. For this we need to specify three things:
- What type of information we are going to search the dataset on - called filters. In our case this is Ensembl Gene IDs
- A vector of the values for our filter - the Ensembl Gene IDs from our DE results table
- What columns (attributes) of the dataset we want returned.
Returning data from Biomart can take time, so it’s always a good idea to test your query on a small list of values first to make sure it is doing what you want. We’ll just use the first 1000 genes for now.
listFilters(ensembl) %>%
filter(str_detect(name, "ensembl"))
| |
---|
with_clone_based_ensembl_gene | |
with_clone_based_ensembl_transcript | |
ensembl_gene_id | |
ensembl_gene_id_version | |
ensembl_transcript_id | |
ensembl_transcript_id_version | |
ensembl_peptide_id | |
ensembl_peptide_id_version | |
ensembl_exon_id | |
clone_based_ensembl_gene | |
ourFilterType <- "ensembl_gene_id"
filterValues <- rownames(resLvV)[1:1000]
listAttributes(ensembl) %>%
head(20)
| | | |
---|
1 | ensembl_gene_id | Gene stable ID | feature_page |
2 | ensembl_gene_id_version | Gene stable ID version | feature_page |
3 | ensembl_transcript_id | Transcript stable ID | feature_page |
4 | ensembl_transcript_id_version | Transcript stable ID version | feature_page |
5 | ensembl_peptide_id | Protein stable ID | feature_page |
6 | ensembl_peptide_id_version | Protein stable ID version | feature_page |
7 | ensembl_exon_id | Exon stable ID | feature_page |
8 | description | Gene description | feature_page |
9 | chromosome_name | Chromosome/scaffold name | feature_page |
10 | start_position | Gene start (bp) | feature_page |
attributeNames <- c('ensembl_gene_id', 'entrezgene', 'external_gene_name')
annot <- getBM(attributes=attributeNames,
filters = ourFilterType,
values = filterValues,
mart = ensembl)
Batch submitting query [==============================>---------------] 67% eta: 0s
Batch submitting query [==============================================] 100% eta: 0s
One-to-many relationships
Let’s inspect the annotation.
| | | | |
---|
1 | ENSMUSG00000001138 | 94218 | Cnnm3 | |
2 | ENSMUSG00000001143 | 214895 | Lman2l | |
3 | ENSMUSG00000002459 | 58175 | Rgs20 | |
4 | ENSMUSG00000002881 | 17936 | Nab1 | |
5 | ENSMUSG00000003134 | 54610 | Tbc1d8 | |
6 | ENSMUSG00000003135 | 52846 | Cnot11 | |
[1] 1001 3
length(unique(annot$ensembl_gene_id))
[1] 999
isDup <- duplicated(annot$ensembl_gene_id)
dup <- annot$ensembl_gene_id[isDup]
annot[annot$ensembl_gene_id%in%dup,]
| | | | |
---|
483 | ENSMUSG00000044783 | 381280 | Hjurp | |
484 | ENSMUSG00000044783 | 212427 | Hjurp | |
867 | ENSMUSG00000070645 | 19701 | Ren1 | |
868 | ENSMUSG00000070645 | 19702 | Ren1 | |
There are a couple of genes that have multiple entries in the retrieved annotation. This is becaues there are multiple Entrez IDs for a single Ensembl gene. These one-to-many relationships come up frequently in genomic databases, it is important to be aware of them and check when necessary.
We will need to do a little work before adding the annotation to out results table. We could decide to discard one or both of the Entrez ID mappings, or we could concatenate the Entrez IDs so that we don’t lose information.
Retrieve full annotation
Challenge 1
That was just 1000 genes. We need annotations for the entire results table. Also, there may be some other interesting columns in BioMart that we wish to retrieve.
- Search the attributes and add the following to our list of attributes:
- The gene description
- The gene biotype
Query BioMart using all of the genes in our results table (resLvV
)
- How many Ensembl genes have multipe Entrez IDs associated with them?
How many Ensembl genes in resLvV
don’t have any annotation? Why is this?
Add annotation to the results table
We can now add the annotation to the results table and then save the results using the write_tsv
function, which writes the results out to a tab separated file. To save time we have created an annotation table in which we have modified the cumbersome Biomart column names, added median transcript length (we’ll need this in a later session), and dealt with the one-to-many issues for Entrez IDs.
load("Robjects/Ensembl_annotations.RData")
colnames(ensemblAnnot)
[1] "GeneID" "Entrez" "Symbol" "Description" "Biotype"
[6] "Chr" "Start" "End" "Strand" "medianTxLength"
annotLvV <- as.data.frame(resLvV) %>%
rownames_to_column("GeneID") %>%
left_join(ensemblAnnot, "GeneID") %>%
rename(logFC=log2FoldChange, FDR=padj)
Finally we can output the annotation DE results using write_csv
.
write_tsv(annotLvV, "data/VirginVsLactating_Results_Annotated.txt")
Visualisation
DESeq2
provides a functon called lfcShrink
that shrinks log-Fold Change (LFC) estimates towards zero using and empirical Bayes procedure. The reason for doing this is that there is high variance in the LFC estimates when counts are low and this results in lowly expressed genes appearing to be show greater differences between groups that highly expressed genes. The lfcShrink
method compensates for this and allows better visualisation and ranking of genes. We will use it for our visualisations of the data.
ddsShrink <- lfcShrink(ddsObj, coef="Status_lactate_vs_virgin")
shrinkLvV <- as.data.frame(ddsShrink) %>%
rownames_to_column("GeneID") %>%
left_join(ensemblAnnot, "GeneID") %>%
rename(logFC=log2FoldChange, FDR=padj)
P-value histogram
A quick and easy “sanity check” for our DE results is to generate a p-value histogram. What we should see is a high bar in the 0 - 0.05
and then a roughly uniform tail to the right of this. There is a nice explanation of other possible patterns in the histogram and what to when you see them in this post.
MA plots
MA plots are a common way to visualize the results of a differential analysis. We met them briefly towards the end of Session 2. This plot shows the log-Fold Change for each gene against its average expression across all samples in the two conditions being contrasted.
DESeq2
has a handy function for plotting this…
plotMA(ddsShrink, alpha=0.05)
…this is fine for a quick look, but it is not easy to make changes to the way it looks or add things such as gene labels. Perhaps we would like to add labels for the top 20 most significantly differentially expressed genes. Let’s use the package ggplot2
instead.
A Brief Introduction to ggplot2
The ggplot2
package has emerged as an attractive alternative to the traditional plots provided by base R. A full overview of all capabilities of the package is available from the cheatsheet.
In brief:-
shrinkLvV
is our data frame containing the variables we wish to plot
aes
creates a mapping between the variables in our data frame to the aesthetic proprties of the plot:
- the x-axis will be mapped to log2(
baseMean
)
- the y-axis will be mapped to the
logFC
geom_point
specifies the particular type of plot we want (in this case a bar plot)
geom_text
allows us to add labels to some or all of the points
The real advantage of ggplot2
is the ability to change the appearance of our plot by mapping other variables to aspects of the plot. For example, we could colour the points based on a the sample group. To this we can add metadata from the sampleinfo
table to the data. The colours are automatically chosen by ggplot2
, but we can specifiy particular values.
cutoff <- sort(shrinkLvV$pvalue)[10]
shrinkLvV <- shrinkLvV %>%
mutate(TopGeneLabel=ifelse(pvalue<=cutoff, Symbol, ""))
ggplot(shrinkLvV, aes(x = log2(baseMean), y=logFC)) +
geom_point(aes(colour=FDR < 0.05), shape=20, size=0.5) +
geom_text(aes(label=TopGeneLabel)) +
labs(x="mean of normalised counts", y="log fold change")
Volcano plot
Another common visualisation is the volcano plot which displays a measure of significance on the y-axis and fold-change on the x-axis.
Challenge 2
Use the log2 fold change (logFC
) on the x-axis, and use -log10(pvalue)
on the y-axis. (This >-log10
transformation is commonly used for p-values as it means that more significant genes have a >higher scale)
Create a column of -log10(pvalue) values
Create a plot with points coloured by if pvalue < 0.05
An example of what your plot should look like:
Strip chart for gene expression
Before following up on the DE genes with further lab work, a recommended sanity check is to have a look at the expression levels of the individual samples for the genes of interest. We can quickly look at grouped expression by using plotCounts
function of DESeq2
to retrieve the normalised expression values from the ddsObj
object and then plotting with ggplot2
.
topgene <- filter(shrinkLvV, Symbol=="Wap")
geneID <- topgene$GeneID
plotCounts(ddsObj, gene = geneID, intgroup = c("CellType", "Status"),
returnData = T) %>%
ggplot(aes(x=Status, y=log2(count))) +
geom_point(aes(fill=Status), shape=21, size=2) +
facet_wrap(~CellType) +
expand_limits(y=0)
Interactive StripChart with Glimma
An interactive version of the volcano plot above that includes the raw per sample values in a separate panel is possible via the glXYPlot
function in the Glimma package.
library(Glimma)
group <- str_remove_all(sampleinfo$Group, "[aeiou]")
de <- as.integer(shrinkLvV$FDR <= 0.05 & !is.na(shrinkLvV$FDR))
normCounts <- log2(counts(ddsObj))
glXYPlot(
x = shrinkLvV$logFC,
y = -log10(shrinkLvV$pvalue),
xlab = "logFC",
ylab = "FDR",
main = "Lactating v Virgin",
counts = normCounts,
groups = group,
status = de,
anno = shrinkLvV[, c("GeneID", "Symbol", "Description")],
folder = "volcano"
)
This function creates an html page (./volcano/XY-Plot.html) with a volcano plot on the left and a plot showing the log-CPM per sample for a selected gene on the right. A search bar is available to search for genes of interest.
Heatmap
We’re going to use the package ComplexHeatmap
(Z. Gu, Eils, and Schlesner 2016). We’ll also use circlize
to generate a colour scale (Z. Gu et al. 2014).
Loading required package: grid
========================================
ComplexHeatmap version 1.18.1
Bioconductor page: http://bioconductor.org/packages/ComplexHeatmap/
Github page: https://github.com/jokergoo/ComplexHeatmap
Documentation: http://bioconductor.org/packages/ComplexHeatmap/
If you use it in published research, please cite:
Gu, Z. Complex heatmaps reveal patterns and correlations in multidimensional
genomic data. Bioinformatics 2016.
========================================
========================================
circlize version 0.4.4
CRAN page: https://cran.r-project.org/package=circlize
Github page: https://github.com/jokergoo/circlize
Documentation: http://jokergoo.github.io/circlize_book/book/
If you use it in published research, please cite:
Gu, Z. circlize implements and enhances circular visualization
in R. Bioinformatics 2014.
========================================
We can’t plot the entire data set, let’s just select the top 150 by FDR. We’ll also z-transform the counts.
sigGenes <- as.data.frame(shrinkLvV) %>%
top_n(150, wt=-FDR) %>%
pull("GeneID")
plotDat <- vst(ddsObj)[sigGenes,] %>%
assay()
z.mat <- t(scale(t(plotDat), center=TRUE, scale=TRUE))
myPalette <- c("red3", "ivory", "blue3")
myRamp = colorRamp2(c(-2, 0, 2), myPalette)
Heatmap(z.mat, name = "z-score",
col = myRamp,
show_row_names = FALSE,
cluster_columns = FALSE)
we can also split the heat map into clusters and add some annotation.
hcDat <- hclust(dist(z.mat))
cutGroups <- cutree(hcDat, h=4)
ha1 = HeatmapAnnotation(df = colData(ddsObj)[,c("CellType", "Status")])
Heatmap(z.mat, name = "z-score",
col = myRamp,
show_row_name = FALSE,
cluster_columns = FALSE,
split=cutGroups,
rect_gp = gpar(col = "darkgrey", lwd=0.5),
top_annotation = ha1)
save(annotLvV, shrinkLvV, file="results/Annotated_Results_LvV.RData")
Additional Material
There is additional material for you to work through in the Supplementary Materials directory. Details include using genomic ranges, retrieving gene models, exporting browser tracks and some extra useful plots like the one below.
References
Gu, Zuguang, Roland Eils, and Matthias Schlesner. 2016. “Complex Heatmaps Reveal Patterns and Correlations in Multidimensional Genomic Data.” Bioinformatics.
Gu, Zuguang, Lei Gu, Roland Eils, Matthias Schlesner, and Benedikt Brors. 2014. “Circlize Implements and Enhances Circular Visualization in R.” Bioinformatics 30 (19): 2811–2.
LS0tCnRpdGxlOiAiUk5BLXNlcSBBbmFseXNpcyBpbiBSIgpzdWJ0aXRsZTogIkFubm90YXRpb24gYW5kIFZpc3VhbGlzYXRpb24gb2YgUk5BLXNlcSByZXN1bHRzIgphdXRob3I6ICJTdGVwaGFuZSBCYWxsZXJlYXUsIE1hcmsgRHVubmluZywgQWJiaSBFZHdhcmRzLCBPc2NhciBSdWVkYSwgQXNobGV5IFNhd2xlIgpkYXRlOiAnYHIgZm9ybWF0KFN5cy50aW1lKCksICJMYXN0IG1vZGlmaWVkOiAlZCAlYiAlWSIpYCcKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwptaW51dGVzOiAzMDAKbGF5b3V0OiBwYWdlCmJpYmxpb2dyYXBoeTogcmVmLmJpYgplZGl0b3Jfb3B0aW9uczogCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQotLS0KCmBgYHtyIHNldHVwLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KGJpb21hUnQpCmxpYnJhcnkoREVTZXEyKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgpCZWZvcmUgc3RhcnRpbmcgdGhpcyBzZWN0aW9uLCB3ZSB3aWxsIG1ha2Ugc3VyZSB3ZSBoYXZlIGFsbCB0aGUgcmVsZXZhbnQgb2JqZWN0cwpmcm9tIHRoZSBEaWZmZXJlbnRpYWwgRXhwcmVzc2lvbiBhbmFseXNpcy4KCmBgYHtyIGxvYWREYXRhfQpsb2FkKCJSb2JqZWN0cy9ERS5SRGF0YSIpCmBgYAoKIyBPdmVydmlldwoKLSBHZXR0aW5nIGFubm90YXRpb24KLSBWaXN1YWxpc2luZyBERSByZXN1bHRzCgojIEFkZGluZyBhbm5vdGF0aW9uIHRvIHRoZSBERVNlcTIgcmVzdWx0cwoKV2UgaGF2ZSBhIGxpc3Qgb2Ygc2lnbmlmaWNhbnRseSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMsIGJ1dCB0aGUgb25seQphbm5vdGF0aW9uIHdlIGNhbiBzZWUgaXMgdGhlIEVuc2VtYmwgR2VuZSBJRCwgd2hpY2ggaXMgbm90IHZlcnkgaW5mb3JtYXRpdmUuIAoKVGhlcmUgYXJlIGEgbnVtYmVyIG9mIHdheXMgdG8gYWRkIGFubm90YXRpb24uIE9uZSBtZXRob2QgaXMgdG8gZG8gdGhpcyB1c2luZyB0aGUKKm9yZy5NbS5lZy5kYiogcGFja2FnZS4gVGhpcyBwYWNrYWdlIGlzIG9uZSBvZiBzZXZlcmFsICpvcmdhbmlzbS1sZXZlbCogcGFja2FnZXMKd2hpY2ggYXJlIHJlLWJ1aWx0IGV2ZXJ5IDYgbW9udGhzLiBUaGVzZSBwYWNrYWdlcyBhcmUgbGlzdGVkIG9uIHRoZSBbYW5ub3RhdGlvbiAKc2VjdGlvbl0oaHR0cDovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvcmVsZWFzZS9CaW9jVmlld3MuaHRtbCNfX19Bbm5vdGF0aW9uRGF0YSkgCm9mIHRoZSBCaW9jb25kdWN0b3IsIGFuZCBhcmUgaW5zdGFsbGVkIGluIHRoZSBzYW1lIHdheSBhcyByZWd1bGFyIEJpb2NvbmR1Y3RvciAKcGFja2FnZXMuIAoKQW4gYWx0ZXJuYXRpdmUgYXBwcm9hY2ggaXMgdG8gdXNlIGBiaW9tYVJ0YCwgYW4gaW50ZXJmYWNlIHRvIHRoZSAKW0Jpb01hcnRdKGh0dHA6Ly93d3cuYmlvbWFydC5vcmcvKSByZXNvdXJjZS4gVGhpcyBpcyB0aGUgbWV0aG9kIHdlIHdpbGwgdXNlIAp0b2RheS4KCiMjIFNlbGVjdCBCaW9NYXJ0IGRhdGFiYXNlIGFuZCBkYXRhc2V0CgpUaGUgZmlyc3Qgc3RlcCBpcyB0byBzZWxlY3QgdGhlIEJpb21hcnQgZGF0YWJhc2Ugd2UgYXJlIGdvaW5nIHRvIGFjY2VzcyBhbmQgCndoaWNoIGRhdGEgc2V0IHdlIGFyZSBnb2luZyB0byB1c2UuCgpgYGB7ciBjb25uZWN0fQojIHZpZXcgdGhlIGF2YWlsYWJsZSBkYXRhYmFzZXMKbGlzdE1hcnRzKCkKIyMgc2V0IHVwIGNvbm5lY3Rpb24gdG8gZW5zZW1ibCBkYXRhYmFzZQplbnNlbWJsPXVzZU1hcnQoIkVOU0VNQkxfTUFSVF9FTlNFTUJMIikKCiMgbGlzdCB0aGUgYXZhaWxhYmxlIGRhdGFzZXRzIChzcGVjaWVzKQpsaXN0RGF0YXNldHMoZW5zZW1ibCkgJT4lIAogICAgZmlsdGVyKHN0cl9kZXRlY3QoZGVzY3JpcHRpb24sICJNb3VzZSIpKQoKIyBzcGVjaWZ5IGEgZGF0YSBzZXQgdG8gdXNlCmVuc2VtYmwgPSB1c2VEYXRhc2V0KCJtbXVzY3VsdXNfZ2VuZV9lbnNlbWJsIiwgbWFydD1lbnNlbWJsKQpgYGAKCiMjIFF1ZXJ5IHRoZSBkYXRhYmFzZQoKTm93IHdlIG5lZWQgdG8gc2V0IHVwIGEgcXVlcnkuIEZvciB0aGlzIHdlIG5lZWQgdG8gc3BlY2lmeSB0aHJlZSB0aGluZ3M6IAoKKGEpIFdoYXQgdHlwZSBvZiBpbmZvcm1hdGlvbiB3ZSBhcmUgZ29pbmcgdG8gc2VhcmNoIHRoZSBkYXRhc2V0IG9uIC0gY2FsbGVkCioqZmlsdGVycyoqLiBJbiBvdXIgY2FzZSB0aGlzIGlzIEVuc2VtYmwgR2VuZSBJRHMKKGIpIEEgdmVjdG9yIG9mIHRoZSAqKnZhbHVlcyoqIGZvciBvdXIgZmlsdGVyIC0gdGhlIEVuc2VtYmwgR2VuZSBJRHMgZnJvbSBvdXIgREUgCnJlc3VsdHMgdGFibGUKKGMpIFdoYXQgY29sdW1ucyAoKiphdHRyaWJ1dGVzKiopIG9mIHRoZSBkYXRhc2V0IHdlIHdhbnQgcmV0dXJuZWQuCgpSZXR1cm5pbmcgZGF0YSBmcm9tIEJpb21hcnQgY2FuIHRha2UgdGltZSwgc28gaXQncyBhbHdheXMgYSBnb29kIGlkZWEgdG8gdGVzdCAKeW91ciBxdWVyeSBvbiBhIHNtYWxsIGxpc3Qgb2YgdmFsdWVzIGZpcnN0IHRvIG1ha2Ugc3VyZSBpdCBpcyBkb2luZyB3aGF0IHlvdQp3YW50LiBXZSdsbCBqdXN0IHVzZSB0aGUgZmlyc3QgMTAwMCBnZW5lcyBmb3Igbm93LgoKYGBge3IgcXVlcnlCaW9NYXJ0LCBtZXNzYWdlPUZ9CgojIGNoZWNrIHRoZSBhdmFpbGFibGUgImZpbHRlcnMiIC0gdGhpbmdzIHlvdSBjYW4gZmlsdGVyIGZvcgpsaXN0RmlsdGVycyhlbnNlbWJsKSAlPiUgCiAgICBmaWx0ZXIoc3RyX2RldGVjdChuYW1lLCAiZW5zZW1ibCIpKQojIFNldCB0aGUgZmlsdGVyIHR5cGUgYW5kIHZhbHVlcwpvdXJGaWx0ZXJUeXBlIDwtICJlbnNlbWJsX2dlbmVfaWQiCmZpbHRlclZhbHVlcyA8LSByb3duYW1lcyhyZXNMdlYpWzE6MTAwMF0KCiMgY2hlY2sgdGhlIGF2YWlsYWJsZSAiYXR0cmlidXRlcyIgLSB0aGluZ3MgeW91IGNhbiByZXRyZWl2ZQpsaXN0QXR0cmlidXRlcyhlbnNlbWJsKSAlPiUgCiAgICBoZWFkKDIwKQojIFNldCB0aGUgbGlzdCBvZiBhdHRyaWJ1dGVzCmF0dHJpYnV0ZU5hbWVzIDwtIGMoJ2Vuc2VtYmxfZ2VuZV9pZCcsICdlbnRyZXpnZW5lJywgJ2V4dGVybmFsX2dlbmVfbmFtZScpCgojIHJ1biB0aGUgcXVlcnkKYW5ub3QgPC0gZ2V0Qk0oYXR0cmlidXRlcz1hdHRyaWJ1dGVOYW1lcywgCiAgICAgICAgICAgICAgIGZpbHRlcnMgPSBvdXJGaWx0ZXJUeXBlLCAKICAgICAgICAgICAgICAgdmFsdWVzID0gZmlsdGVyVmFsdWVzLCAKICAgICAgICAgICAgICAgbWFydCA9IGVuc2VtYmwpCmBgYAoKCiMjIyBPbmUtdG8tbWFueSByZWxhdGlvbnNoaXBzCgpMZXQncyBpbnNwZWN0IHRoZSBhbm5vdGF0aW9uLgoKYGBge3IgaW5zcGVjdEFubm90fQpoZWFkKGFubm90KQpkaW0oYW5ub3QpICMgd2h5IGFyZSB0aGVyZSBtb3JlIHRoYW4gMTAwMCByb3dzPwpsZW5ndGgodW5pcXVlKGFubm90JGVuc2VtYmxfZ2VuZV9pZCkpICMgd2h5IGFyZSB0aGVyZSBsZXNzIHRoYW4gMTAwMCBHZW5lIGlkcz8KCmlzRHVwIDwtIGR1cGxpY2F0ZWQoYW5ub3QkZW5zZW1ibF9nZW5lX2lkKQpkdXAgPC0gYW5ub3QkZW5zZW1ibF9nZW5lX2lkW2lzRHVwXQphbm5vdFthbm5vdCRlbnNlbWJsX2dlbmVfaWQlaW4lZHVwLF0KYGBgCgpUaGVyZSBhcmUgYSBjb3VwbGUgb2YgZ2VuZXMgdGhhdCBoYXZlIG11bHRpcGxlIGVudHJpZXMgaW4gdGhlIHJldHJpZXZlZCAKYW5ub3RhdGlvbi4gVGhpcyBpcyBiZWNhdWVzIHRoZXJlIGFyZSBtdWx0aXBsZSBFbnRyZXogSURzIGZvciBhIHNpbmdsZSBFbnNlbWJsIApnZW5lLiBUaGVzZSBvbmUtdG8tbWFueSByZWxhdGlvbnNoaXBzIGNvbWUgdXAgZnJlcXVlbnRseSBpbiBnZW5vbWljIGRhdGFiYXNlcywgCml0IGlzIGltcG9ydGFudCB0byBiZSBhd2FyZSBvZiB0aGVtIGFuZCBjaGVjayB3aGVuIG5lY2Vzc2FyeS4gCgpXZSB3aWxsIG5lZWQgdG8gZG8gYSBsaXR0bGUgd29yayBiZWZvcmUgYWRkaW5nIHRoZSBhbm5vdGF0aW9uIHRvIG91dCByZXN1bHRzIAp0YWJsZS4gV2UgY291bGQgZGVjaWRlIHRvIGRpc2NhcmQgb25lIG9yIGJvdGggb2YgdGhlIEVudHJleiBJRCBtYXBwaW5ncywgb3Igd2UgCmNvdWxkIGNvbmNhdGVuYXRlIHRoZSBFbnRyZXogSURzIHNvIHRoYXQgd2UgZG9uJ3QgbG9zZSBpbmZvcm1hdGlvbi4gCgojIyBSZXRyaWV2ZSBmdWxsIGFubm90YXRpb24KCj4gIyMjIENoYWxsZW5nZSAxIHsuY2hhbGxlbmdlfQo+IFRoYXQgd2FzIGp1c3QgMTAwMCBnZW5lcy4gV2UgbmVlZCBhbm5vdGF0aW9ucyBmb3IgdGhlIGVudGlyZSByZXN1bHRzIHRhYmxlLgo+IEFsc28sIHRoZXJlIG1heSBiZSBzb21lIG90aGVyIGludGVyZXN0aW5nIGNvbHVtbnMgaW4gQmlvTWFydCB0aGF0IHdlIHdpc2ggdG8KPiByZXRyaWV2ZS4gIAo+Cj4gKGEpIFNlYXJjaCB0aGUgYXR0cmlidXRlcyBhbmQgYWRkIHRoZSBmb2xsb3dpbmcgdG8gb3VyIGxpc3Qgb2YgYXR0cmlidXRlczogIAo+ICAgICAgIChpKSBUaGUgZ2VuZSBkZXNjcmlwdGlvbiAgIAo+ICAgICAgIChpaSkgVGhlIGdlbmUgYmlvdHlwZSAgCj4gKGIpIFF1ZXJ5IEJpb01hcnQgdXNpbmcgYWxsIG9mIHRoZSBnZW5lcyBpbiBvdXIgcmVzdWx0cyB0YWJsZSAoYHJlc0x2VmApICAKPgo+IChjKSBIb3cgbWFueSBFbnNlbWJsIGdlbmVzIGhhdmUgbXVsdGlwZSBFbnRyZXogSURzIGFzc29jaWF0ZWQgd2l0aCB0aGVtPyAgCj4gKGQpIEhvdyBtYW55IEVuc2VtYmwgZ2VuZXMgaW4gYHJlc0x2VmAgZG9uJ3QgaGF2ZSBhbnkgYW5ub3RhdGlvbj8gV2h5IGlzIHRoaXM/CgpgYGB7ciBzb2x1dGlvbkNoYWxsZW5nZTF9CmBgYAoKIyMjIEFkZCBhbm5vdGF0aW9uIHRvIHRoZSByZXN1bHRzIHRhYmxlCgpXZSBjYW4gbm93IGFkZCB0aGUgYW5ub3RhdGlvbiB0byB0aGUgcmVzdWx0cyB0YWJsZSBhbmQgdGhlbiBzYXZlIHRoZSByZXN1bHRzIAp1c2luZyB0aGUgYHdyaXRlX3RzdmAgZnVuY3Rpb24sIHdoaWNoIHdyaXRlcyB0aGUgcmVzdWx0cyBvdXQgdG8gYSB0YWIKc2VwYXJhdGVkIGZpbGUuClRvIHNhdmUgdGltZSB3ZSBoYXZlIGNyZWF0ZWQgYW4gYW5ub3RhdGlvbiB0YWJsZSBpbiB3aGljaCB3ZSBoYXZlIG1vZGlmaWVkIHRoZSAKY3VtYmVyc29tZSBCaW9tYXJ0IGNvbHVtbiBuYW1lcywgYWRkZWQgbWVkaWFuIHRyYW5zY3JpcHQgbGVuZ3RoICh3ZSdsbCBuZWVkIHRoaXMKaW4gYSBsYXRlciBzZXNzaW9uKSwgYW5kIGRlYWx0IHdpdGggdGhlIG9uZS10by1tYW55IGlzc3VlcyBmb3IgRW50cmV6IElEcy4KCmBgYHtyIGFkZEFubm90YXRpb24sIG1lc3NhZ2U9RkFMU0V9CmxvYWQoIlJvYmplY3RzL0Vuc2VtYmxfYW5ub3RhdGlvbnMuUkRhdGEiKQpjb2xuYW1lcyhlbnNlbWJsQW5ub3QpCmFubm90THZWIDwtIGFzLmRhdGEuZnJhbWUocmVzTHZWKSAlPiUgCiAgICByb3duYW1lc190b19jb2x1bW4oIkdlbmVJRCIpICU+JSAKICAgIGxlZnRfam9pbihlbnNlbWJsQW5ub3QsICJHZW5lSUQiKSAlPiUgCiAgICByZW5hbWUobG9nRkM9bG9nMkZvbGRDaGFuZ2UsIEZEUj1wYWRqKQpgYGAKCkZpbmFsbHkgd2UgY2FuIG91dHB1dCB0aGUgYW5ub3RhdGlvbiBERSByZXN1bHRzIHVzaW5nIGB3cml0ZV9jc3ZgLgoKYGBge3Igb3V0cHV0REV0YWJsZXMsIGV2YWw9Rn0Kd3JpdGVfdHN2KGFubm90THZWLCAiZGF0YS9WaXJnaW5Wc0xhY3RhdGluZ19SZXN1bHRzX0Fubm90YXRlZC50eHQiKQpgYGAKCiMgVmlzdWFsaXNhdGlvbgoKYERFU2VxMmAgcHJvdmlkZXMgYSBmdW5jdG9uIGNhbGxlZCBgbGZjU2hyaW5rYCB0aGF0IHNocmlua3MgbG9nLUZvbGQgQ2hhbmdlIAooTEZDKSBlc3RpbWF0ZXMgdG93YXJkcyB6ZXJvIHVzaW5nIGFuZCBlbXBpcmljYWwgQmF5ZXMgcHJvY2VkdXJlLiBUaGUgcmVhc29uIGZvcgpkb2luZyB0aGlzIGlzIHRoYXQgdGhlcmUgaXMgaGlnaCB2YXJpYW5jZSBpbiB0aGUgTEZDIGVzdGltYXRlcyB3aGVuIGNvdW50cyBhcmUgCmxvdyBhbmQgdGhpcyByZXN1bHRzIGluIGxvd2x5IGV4cHJlc3NlZCBnZW5lcyBhcHBlYXJpbmcgdG8gYmUgc2hvdyBncmVhdGVyCmRpZmZlcmVuY2VzIGJldHdlZW4gZ3JvdXBzIHRoYXQgaGlnaGx5IGV4cHJlc3NlZCBnZW5lcy4gVGhlIGBsZmNTaHJpbmtgIG1ldGhvZApjb21wZW5zYXRlcyBmb3IgdGhpcyBhbmQgYWxsb3dzIGJldHRlciB2aXN1YWxpc2F0aW9uIGFuZCByYW5raW5nIG9mIGdlbmVzLiBXZSAKd2lsbCB1c2UgaXQgZm9yIG91ciB2aXN1YWxpc2F0aW9ucyBvZiB0aGUgZGF0YS4KCmBgYHtyIHNocmlua0xGQ30KZGRzU2hyaW5rIDwtIGxmY1NocmluayhkZHNPYmosIGNvZWY9IlN0YXR1c19sYWN0YXRlX3ZzX3ZpcmdpbiIpCnNocmlua0x2ViA8LSBhcy5kYXRhLmZyYW1lKGRkc1NocmluaykgJT4lCiAgICByb3duYW1lc190b19jb2x1bW4oIkdlbmVJRCIpICU+JSAKICAgIGxlZnRfam9pbihlbnNlbWJsQW5ub3QsICJHZW5lSUQiKSAlPiUgCiAgICByZW5hbWUobG9nRkM9bG9nMkZvbGRDaGFuZ2UsIEZEUj1wYWRqKQpgYGAKCiMjIFAtdmFsdWUgaGlzdG9ncmFtCgpBIHF1aWNrIGFuZCBlYXN5ICJzYW5pdHkgY2hlY2siIGZvciBvdXIgREUgcmVzdWx0cyBpcyB0byBnZW5lcmF0ZSBhIHAtdmFsdWUgCmhpc3RvZ3JhbS4gV2hhdCB3ZSBzaG91bGQgc2VlIGlzIGEgaGlnaCBiYXIgaW4gdGhlIGAwIC0gMC4wNWAgYW5kIHRoZW4gYSByb3VnaGx5CnVuaWZvcm0gdGFpbCB0byB0aGUgcmlnaHQgb2YgdGhpcy4gVGhlcmUgaXMgYSBuaWNlIGV4cGxhbmF0aW9uIG9mIG90aGVyIHBvc3NpYmxlCnBhdHRlcm5zIGluIHRoZSBoaXN0b2dyYW0gYW5kIHdoYXQgdG8gd2hlbiB5b3Ugc2VlIHRoZW0gaW4gW3RoaXMgCnBvc3RdKGh0dHA6Ly92YXJpYW5jZWV4cGxhaW5lZC5vcmcvc3RhdGlzdGljcy9pbnRlcnByZXRpbmctcHZhbHVlLWhpc3RvZ3JhbS8pLgoKYGBge3IgcHZhbEhpc3QsIGZpZy5hbGlnbj0iY2VudGVyIn0KaGlzdChzaHJpbmtMdlYkcHZhbHVlKQpgYGAKCiMjIE1BIHBsb3RzCgpNQSBwbG90cyBhcmUgYSBjb21tb24gd2F5IHRvIHZpc3VhbGl6ZSB0aGUgcmVzdWx0cyBvZiBhIGRpZmZlcmVudGlhbCBhbmFseXNpcy4gCldlIG1ldCB0aGVtIGJyaWVmbHkgdG93YXJkcyB0aGUgZW5kIG9mIFtTZXNzaW9uIAoyXSgwMl9QcmVwcm9jZXNzaW5nX0RhdGEubmIuaHRtbCkuIFRoaXMgcGxvdCBzaG93cyB0aGUgbG9nLUZvbGQgQ2hhbmdlIGZvciBlYWNoIApnZW5lIGFnYWluc3QgaXRzIGF2ZXJhZ2UgZXhwcmVzc2lvbiBhY3Jvc3MgYWxsIHNhbXBsZXMgaW4gdGhlIHR3byBjb25kaXRpb25zCmJlaW5nIGNvbnRyYXN0ZWQuCgpgREVTZXEyYCBoYXMgYSBoYW5keSBmdW5jdGlvbiBmb3IgcGxvdHRpbmcgdGhpcy4uLgoKYGBge3IgbWFQbG90REVTZXEyLCBmaWcuYWxpZ249ImNlbnRlciIsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTV9CnBsb3RNQShkZHNTaHJpbmssIGFscGhhPTAuMDUpCmBgYAoKLi4udGhpcyBpcyBmaW5lIGZvciBhIHF1aWNrIGxvb2ssIGJ1dCBpdCBpcyBub3QgZWFzeSB0byBtYWtlIGNoYW5nZXMgdG8gdGhlIHdheQppdCBsb29rcyBvciBhZGQgdGhpbmdzIHN1Y2ggYXMgZ2VuZSBsYWJlbHMuIFBlcmhhcHMgd2Ugd291bGQgbGlrZSB0byBhZGQgbGFiZWxzCmZvciB0aGUgdG9wIDIwIG1vc3Qgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMuIExldCdzIHVzZSAKdGhlIHBhY2thZ2UgYGdncGxvdDJgIGluc3RlYWQuCgojIyMgQSBCcmllZiBJbnRyb2R1Y3Rpb24gdG8gYGdncGxvdDJgCgpUaGUgW2BnZ3Bsb3QyYF0oaHR0cDovL2dncGxvdDIudGlkeXZlcnNlLm9yZy8pIHBhY2thZ2UgaGFzIGVtZXJnZWQgYXMgYW4gCmF0dHJhY3RpdmUgYWx0ZXJuYXRpdmUgdG8gdGhlIHRyYWRpdGlvbmFsIHBsb3RzIHByb3ZpZGVkIGJ5IGJhc2UgUi4gQSBmdWxsIApvdmVydmlldyBvZiBhbGwgY2FwYWJpbGl0aWVzIG9mIHRoZSBwYWNrYWdlIGlzIGF2YWlsYWJsZSBmcm9tIHRoZSAKW2NoZWF0c2hlZXRdKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE1LzAzL2dncGxvdDItY2hlYXRzaGVldC5wZGYpLgoKSW4gYnJpZWY6LQoKLSBgc2hyaW5rTHZWYCBpcyBvdXIgZGF0YSBmcmFtZSBjb250YWluaW5nIHRoZSB2YXJpYWJsZXMgd2Ugd2lzaCB0byBwbG90Ci0gYGFlc2AgY3JlYXRlcyBhIG1hcHBpbmcgYmV0d2VlbiB0aGUgdmFyaWFibGVzIGluIG91ciBkYXRhIGZyYW1lIHRvIHRoZSAKKmFlcyp0aGV0aWMgcHJvcHJ0aWVzIG9mIHRoZSBwbG90OgogICAgKyB0aGUgeC1heGlzIHdpbGwgYmUgbWFwcGVkIHRvIGxvZzIoYGJhc2VNZWFuYCkKICAgICsgdGhlIHktYXhpcyB3aWxsIGJlIG1hcHBlZCB0byB0aGUgYGxvZ0ZDYAotIGBnZW9tX3BvaW50YCBzcGVjaWZpZXMgdGhlIHBhcnRpY3VsYXIgdHlwZSBvZiBwbG90IHdlIHdhbnQgKGluIHRoaXMgY2FzZSBhIGJhciAKcGxvdCkKLSBgZ2VvbV90ZXh0YCBhbGxvd3MgdXMgdG8gYWRkIGxhYmVscyB0byBzb21lIG9yIGFsbCBvZiB0aGUgcG9pbnRzCiAgICArIHNlZSAKICAgIFt0aGUgY2hlYXRzaGVldF0oaHR0cHM6Ly93d3cucnN0dWRpby5jb20vd3AtY29udGVudC91cGxvYWRzLzIwMTUvMDMvZ2dwbG90Mi1jaGVhdHNoZWV0LnBkZikgCiAgICBmb3Igb3RoZXIgcGxvdCB0eXBlcwoKVGhlIHJlYWwgYWR2YW50YWdlIG9mIGBnZ3Bsb3QyYCBpcyB0aGUgYWJpbGl0eSB0byBjaGFuZ2UgdGhlIGFwcGVhcmFuY2Ugb2Ygb3VyIApwbG90IGJ5IG1hcHBpbmcgb3RoZXIgdmFyaWFibGVzIHRvIGFzcGVjdHMgb2YgdGhlIHBsb3QuIEZvciBleGFtcGxlLCB3ZSBjb3VsZCAKY29sb3VyIHRoZSBwb2ludHMgYmFzZWQgb24gYSB0aGUgc2FtcGxlIGdyb3VwLiBUbyB0aGlzIHdlIGNhbiBhZGQgbWV0YWRhdGEgZnJvbQp0aGUgYHNhbXBsZWluZm9gIHRhYmxlIHRvIHRoZSBkYXRhLiBUaGUgY29sb3VycyBhcmUgYXV0b21hdGljYWxseSBjaG9zZW4gYnkKYGdncGxvdDJgLCBidXQgd2UgY2FuIHNwZWNpZml5IHBhcnRpY3VsYXIgdmFsdWVzLgoKCmBgYHtyIG1hUGxvdCwgZmlnLmFsaWduPSJjZW50ZXIiLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01fQojIGFkZCBhIGNvbHVtbiB3aXRoIHRoZSBuYW1lcyBvZiBvbmx5IHRoZSB0b3AgMTAgZ2VuZXMKY3V0b2ZmIDwtIHNvcnQoc2hyaW5rTHZWJHB2YWx1ZSlbMTBdCnNocmlua0x2ViA8LSBzaHJpbmtMdlYgJT4lIAogICAgbXV0YXRlKFRvcEdlbmVMYWJlbD1pZmVsc2UocHZhbHVlPD1jdXRvZmYsIFN5bWJvbCwgIiIpKQoKZ2dwbG90KHNocmlua0x2ViwgYWVzKHggPSBsb2cyKGJhc2VNZWFuKSwgeT1sb2dGQykpICsgCiAgICBnZW9tX3BvaW50KGFlcyhjb2xvdXI9RkRSIDwgMC4wNSksIHNoYXBlPTIwLCBzaXplPTAuNSkgKwogICAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1Ub3BHZW5lTGFiZWwpKSArCiAgICBsYWJzKHg9Im1lYW4gb2Ygbm9ybWFsaXNlZCBjb3VudHMiLCB5PSJsb2cgZm9sZCBjaGFuZ2UiKQpgYGAKCiMjIFZvbGNhbm8gcGxvdApBbm90aGVyIGNvbW1vbiB2aXN1YWxpc2F0aW9uIGlzIHRoZSAKWyp2b2xjYW5vIHBsb3QqXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Wb2xjYW5vX3Bsb3RfKHN0YXRpc3RpY3MpKSB3aGljaCAKZGlzcGxheXMgYSBtZWFzdXJlIG9mIHNpZ25pZmljYW5jZSBvbiB0aGUgeS1heGlzIGFuZCBmb2xkLWNoYW5nZSBvbiB0aGUgeC1heGlzLgoKPiAjIyMgQ2hhbGxlbmdlIDIgey5jaGFsbGVuZ2V9Cj4gVXNlIHRoZSBsb2cyIGZvbGQgY2hhbmdlIChgbG9nRkNgKSBvbiB0aGUgeC1heGlzLCBhbmQgdXNlIGAtbG9nMTAocHZhbHVlKWAgb24gdGhlIHktYXhpcy4gKFRoaXMgPmAtbG9nMTBgIHRyYW5zZm9ybWF0aW9uIGlzIGNvbW1vbmx5IHVzZWQgZm9yIHAtdmFsdWVzIGFzIGl0IG1lYW5zIHRoYXQgbW9yZSBzaWduaWZpY2FudCBnZW5lcyBoYXZlIGEgPmhpZ2hlciBzY2FsZSkgCj4KPiAoYSkgQ3JlYXRlIGEgY29sdW1uIG9mIC1sb2cxMChwdmFsdWUpIHZhbHVlcwo+Cj4gKGIpIENyZWF0ZSBhIHBsb3Qgd2l0aCBwb2ludHMgY29sb3VyZWQgYnkgaWYgcHZhbHVlIDwgMC4wNQoKQW4gZXhhbXBsZSBvZiB3aGF0IHlvdXIgcGxvdCBzaG91bGQgbG9vayBsaWtlOgohW10oLi4vaW1hZ2VzL1ZvbGNhbm8ucG5nKSAgIAoKIyMgU3RyaXAgY2hhcnQgZm9yIGdlbmUgZXhwcmVzc2lvbgoKQmVmb3JlIGZvbGxvd2luZyB1cCBvbiB0aGUgREUgZ2VuZXMgd2l0aCBmdXJ0aGVyIGxhYiB3b3JrLCBhIHJlY29tbWVuZGVkICpzYW5pdHkKY2hlY2sqIGlzIHRvIGhhdmUgYSBsb29rIGF0IHRoZSBleHByZXNzaW9uIGxldmVscyBvZiB0aGUgaW5kaXZpZHVhbCBzYW1wbGVzIGZvciAKdGhlIGdlbmVzIG9mIGludGVyZXN0LiBXZSBjYW4gcXVpY2tseSBsb29rIGF0IGdyb3VwZWQgZXhwcmVzc2lvbiBieSB1c2luZyAKYHBsb3RDb3VudHNgIGZ1bmN0aW9uIG9mIGBERVNlcTJgIHRvICByZXRyaWV2ZSB0aGUgbm9ybWFsaXNlZCBleHByZXNzaW9uIHZhbHVlcyAKZnJvbSB0aGUgYGRkc09iamAgb2JqZWN0IGFuZCB0aGVuIHBsb3R0aW5nIHdpdGggIGBnZ3Bsb3QyYC4KCmBgYHtyIHBsb3RHZW5lQ291bnRzfQojIExldCdzIGxvb2sgYXQgdGhlIG1vc3Qgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZQp0b3BnZW5lIDwtIGZpbHRlcihzaHJpbmtMdlYsIFN5bWJvbD09IldhcCIpCmdlbmVJRCA8LSB0b3BnZW5lJEdlbmVJRAoKcGxvdENvdW50cyhkZHNPYmosIGdlbmUgPSBnZW5lSUQsIGludGdyb3VwID0gYygiQ2VsbFR5cGUiLCAiU3RhdHVzIiksCiAgICAgICAgICAgcmV0dXJuRGF0YSA9IFQpICU+JSAKICAgIGdncGxvdChhZXMoeD1TdGF0dXMsIHk9bG9nMihjb3VudCkpKSArCiAgICAgIGdlb21fcG9pbnQoYWVzKGZpbGw9U3RhdHVzKSwgc2hhcGU9MjEsIHNpemU9MikgKwogICAgICBmYWNldF93cmFwKH5DZWxsVHlwZSkgKwogICAgICBleHBhbmRfbGltaXRzKHk9MCkKYGBgCgoKIyMjIEludGVyYWN0aXZlIFN0cmlwQ2hhcnQgd2l0aCBHbGltbWEKCkFuIGludGVyYWN0aXZlIHZlcnNpb24gb2YgdGhlIHZvbGNhbm8gcGxvdCBhYm92ZSB0aGF0IGluY2x1ZGVzIHRoZSByYXcgcGVyIApzYW1wbGUgdmFsdWVzIGluIGEgc2VwYXJhdGUgcGFuZWwgaXMgcG9zc2libGUgdmlhIHRoZSBgZ2xYWVBsb3RgIGZ1bmN0aW9uIGluIHRoZQoqR2xpbW1hKiBwYWNrYWdlLgoKCmBgYHtyIEdsaW1tYSwgZXZhbD1GQUxTRX0KbGlicmFyeShHbGltbWEpCgpncm91cCA8LSBzdHJfcmVtb3ZlX2FsbChzYW1wbGVpbmZvJEdyb3VwLCAiW2FlaW91XSIpCgpkZSA8LSBhcy5pbnRlZ2VyKHNocmlua0x2ViRGRFIgPD0gMC4wNSAmICFpcy5uYShzaHJpbmtMdlYkRkRSKSkKCm5vcm1Db3VudHMgPC0gbG9nMihjb3VudHMoZGRzT2JqKSkKCmdsWFlQbG90KAogIHggPSBzaHJpbmtMdlYkbG9nRkMsCiAgeSA9IC1sb2cxMChzaHJpbmtMdlYkcHZhbHVlKSwKICB4bGFiID0gImxvZ0ZDIiwKICB5bGFiID0gIkZEUiIsCiAgbWFpbiA9ICJMYWN0YXRpbmcgdiBWaXJnaW4iLAogIGNvdW50cyA9IG5vcm1Db3VudHMsCiAgZ3JvdXBzID0gZ3JvdXAsCiAgc3RhdHVzID0gZGUsCiAgYW5ubyA9IHNocmlua0x2VlssIGMoIkdlbmVJRCIsICJTeW1ib2wiLCAiRGVzY3JpcHRpb24iKV0sCiAgZm9sZGVyID0gInZvbGNhbm8iCikKYGBgCgpUaGlzIGZ1bmN0aW9uIGNyZWF0ZXMgYW4gaHRtbCBwYWdlICguL3ZvbGNhbm8vWFktUGxvdC5odG1sKSB3aXRoIGEgdm9sY2FubyBwbG90IApvbiB0aGUgbGVmdCBhbmQgYSBwbG90IHNob3dpbmcgdGhlIGxvZy1DUE0gcGVyIHNhbXBsZSBmb3IgYSBzZWxlY3RlZCBnZW5lIG9uIHRoZQpyaWdodC4gQSBzZWFyY2ggYmFyIGlzIGF2YWlsYWJsZSB0byBzZWFyY2ggZm9yIGdlbmVzIG9mIGludGVyZXN0LgoKIyMgSGVhdG1hcAoKV2UncmUgZ29pbmcgdG8gdXNlIHRoZSBwYWNrYWdlIGBDb21wbGV4SGVhdG1hcGAgW0BHdTIwMTZdLiBXZSdsbCBhbHNvIHVzZQpgY2lyY2xpemVgIHRvIGdlbmVyYXRlIGEgY29sb3VyIHNjYWxlIFtAR3UyMDE0XS4KCmBgYHtyIGNvbXBsZXhIZWF0bWFwLCBtZXNzYWdlPUZ9CmxpYnJhcnkoQ29tcGxleEhlYXRtYXApCmxpYnJhcnkoY2lyY2xpemUpCmBgYAoKV2UgY2FuJ3QgcGxvdCB0aGUgZW50aXJlIGRhdGEgc2V0LCBsZXQncyBqdXN0IHNlbGVjdCB0aGUgdG9wIDE1MCBieSBGRFIuIFdlJ2xsCmFsc28gei10cmFuc2Zvcm0gdGhlIGNvdW50cy4KCmBgYHtyIHNlbGVjdEdlbmVzfQojIGdldCB0aGUgdG9wIGdlbmVzCnNpZ0dlbmVzIDwtIGFzLmRhdGEuZnJhbWUoc2hyaW5rTHZWKSAlPiUgCiAgICB0b3BfbigxNTAsIHd0PS1GRFIpICU+JSAKICAgIHB1bGwoIkdlbmVJRCIpCgojIGZpbHRlciB0aGUgZGF0YSBmb3IgdGhlIHRvcCAyMDAgYnkgcGFkaiBpbiB0aGUgTFJUIHRlc3QKcGxvdERhdCA8LSB2c3QoZGRzT2JqKVtzaWdHZW5lcyxdICU+JSAKICAgIGFzc2F5KCkKei5tYXQgPC0gdChzY2FsZSh0KHBsb3REYXQpLCBjZW50ZXI9VFJVRSwgc2NhbGU9VFJVRSkpCmBgYAoKYGBge3IgY29sb3VyU2NhbGV9CiMgY29sb3VyIHBhbGV0dGUKbXlQYWxldHRlIDwtIGMoInJlZDMiLCAiaXZvcnkiLCAiYmx1ZTMiKQpteVJhbXAgPSBjb2xvclJhbXAyKGMoLTIsIDAsIDIpLCBteVBhbGV0dGUpCmBgYAoKYGBge3IgaGVhdG1hcCwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9OH0KSGVhdG1hcCh6Lm1hdCwgbmFtZSA9ICJ6LXNjb3JlIiwKICAgICAgICBjb2wgPSBteVJhbXAsICAgICAgICAgICAgCiAgICAgICAgc2hvd19yb3dfbmFtZXMgPSBGQUxTRSwKICAgICAgICBjbHVzdGVyX2NvbHVtbnMgPSBGQUxTRSkKYGBgCgp3ZSBjYW4gYWxzbyBzcGxpdCB0aGUgaGVhdCBtYXAgaW50byBjbHVzdGVycyBhbmQgYWRkIHNvbWUgYW5ub3RhdGlvbi4KCmBgYHtyIHNwbGl0SGVhdG1hcCwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9OH0KIyBjbHVzdGVyIHRoZSBkYXRhIGFuZCBzcGxpdCB0aGUgdHJlZQpoY0RhdCA8LSBoY2x1c3QoZGlzdCh6Lm1hdCkpCmN1dEdyb3VwcyA8LSBjdXRyZWUoaGNEYXQsIGg9NCkKCmhhMSA9IEhlYXRtYXBBbm5vdGF0aW9uKGRmID0gY29sRGF0YShkZHNPYmopWyxjKCJDZWxsVHlwZSIsICJTdGF0dXMiKV0pCgpIZWF0bWFwKHoubWF0LCBuYW1lID0gInotc2NvcmUiLAogICAgICAgIGNvbCA9IG15UmFtcCwgICAgICAgICAgICAKICAgICAgICBzaG93X3Jvd19uYW1lID0gRkFMU0UsCiAgICAgICAgY2x1c3Rlcl9jb2x1bW5zID0gRkFMU0UsCiAgICAgICAgc3BsaXQ9Y3V0R3JvdXBzLAogICAgICAgIHJlY3RfZ3AgPSBncGFyKGNvbCA9ICJkYXJrZ3JleSIsIGx3ZD0wLjUpLAogICAgICAgIHRvcF9hbm5vdGF0aW9uID0gaGExKQpgYGAKCgpgYGB7ciBzYXZlRW52aXJvbm1lbnQsIGV2YWw9RkFMU0V9CnNhdmUoYW5ub3RMdlYsIHNocmlua0x2ViwgZmlsZT0icmVzdWx0cy9Bbm5vdGF0ZWRfUmVzdWx0c19MdlYuUkRhdGEiKQpgYGAKCgojIEFkZGl0aW9uYWwgTWF0ZXJpYWwKVGhlcmUgaXMgYWRkaXRpb25hbCBtYXRlcmlhbCBmb3IgeW91IHRvIHdvcmsgdGhyb3VnaCBpbiB0aGUgW1N1cHBsZW1lbnRhcnkgTWF0ZXJpYWxzXSguLi9TdXBwbGVtZW50YXJ5X01hdGVyaWFscy9TM19Bbm5vdGF0aW9uX2FuZF9WaXN1YWxpc2F0aW9uLm5iLmh0bWwpIGRpcmVjdG9yeS4gRGV0YWlscyBpbmNsdWRlIHVzaW5nIGdlbm9taWMgcmFuZ2VzLCByZXRyaWV2aW5nIGdlbmUgbW9kZWxzLCBleHBvcnRpbmcgYnJvd3NlciB0cmFja3MgYW5kIHNvbWUgZXh0cmEgdXNlZnVsIHBsb3RzIGxpa2UgdGhlIG9uZSBiZWxvdy4KCgohW10oLi4vaW1hZ2VzL0NvdmVyYWdlLnBuZykKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyBSZWZlcmVuY2VzCg==