Introduction
Today we’re going to look at a data set from my work. This data comes from menchymnal stem cells (MSCs) cultured from 9 cynomolgus monkeys (a closely related species to rhesus macaques). MSCs are known to have immune modulatory effects, and the main goal of the project is to test whether these stem cells, when activated with interferon gamma (IFNg), can help suppress the body’s immune response against an organ transplant.
The purpose of this specific data set is simply to determine which genes’ expression is affected when MSCs are treated with IFNg. As such, we have both untreated (Control) samples and IFNg-treated samples from 3 different passages of the cell cultures. So, we’ll be using limma to test each gene for differential expression between Control and IFNg samples.
Preliminary setup
First, let’s install all the packages we’ll need.
if (!requireNamespace("BiocManager", quietly = TRUE))
install.packages("BiocManager")
already_installed_packages <- rownames(installed.packages())
needed_packages <- c("edgeR", "limma", "SummarizedExperiment", "ggplot2", "locfit", "dplyr", "statmod")
need_to_install <- setdiff(needed_packages, already_installed_packages)
if (length(need_to_install) > 0) {
## http://bioconductor.org/install/
BiocManager::install(need_to_install)
}
Then we’ll load those packages.
library(SummarizedExperiment)
library(edgeR)
library(limma)
library(ggplot2)
library(dplyr)
Initial data loading and exploration
Let’s load the data file. This uses the readRDS
function, which reads a single R object from a file and returns it. We assign the result to a variable.
sexp <- readRDS(gzcon(url("https://darwinawardwinner.github.io/resume/examples/Salomon/Teaching/Cyno-RNASeq-SummarizedExperiment.RDS")))
print(sexp)
class: RangedSummarizedExperiment
dim: 20416 54
metadata(0):
assays(1): counts
rownames(20416): ENSP00000346839 ENSP00000260356 ... ENSP00000420502 ENSP00000420636
rowData names(8): symbol ensembl_gene_id ... entrezgene source_species
colnames(54): LabA.R104.6C4_65M.P4.Control LabA.R104.6C4_65M.P4.IFNg ...
LabB.R95.CN8351.P6.Control LabB.R95.CN8351.P6.IFNg
colData names(6): Sample.ID Animal.ID ... Lab Treatment
This object is called a SummarizedExperiment, and its purpose is to hold the experimental data, sample metadata, gene metadata, and any other relevant information for the experiment all in one place. This makes it an excellent starting point for any analysis. In our case, the experimental data consists of a matrix of counts for each gene in each sample. This count matrix was generated by aligning all the RNA sequence reads in each sample to the cyno genome and then counting the number of uniquely-mapped reads that overlap each gene. I have done the aligning and counting process for you and provided the count matrix, since the alignment and counting would take many hours to commplete.
Look at the output of the print statement above. The RNA-Seq read counts are contained in the assays
slot. The colData
slot contains information on the samples, while the rowRanges
slot contains information on the genes. The rows have “ranges” instead of just “data” because in addition to things like the gene symbol and description, the rowRanges
slot also contains the genomic coordinates of each gene, that is, the chromosome, start/end positions of each exon, and the strand. This is the same information that was used to count the overlapping reads for each gene, but we won’t be needing it today.
You can read more about SummarizedExperiment objects here.
- How many samples does this SummarizedExperiment object contain? How many genes does it have? Where did you find this information?
- What happens when you try to select a subset of rows or columns?
Learning about the experimental design from the sample table
Before we get to the analysis, we need to have a closer look at the sample table, to gain a better understanding of the experimental design.
names(sample_table)
[1] "Sample.ID" "Animal.ID" "Passage" "Run" "Lab" "Treatment"
head(sample_table)
- How many Labs/Runs/Animals/Passages/Treatments are there in this experiment?
- How many samples are in each one?
(You may find the unique
and table
functions useful for answering these questions.)
- How many Animals are there in each Lab?
- Are there Animals with samples from both Labs?
table(sample_table$Animal.ID, sample_table$Lab)
A B
6C4_65M 6 0
6C63 6 0
6C7 6 0
6C84 6 0
7C37 6 0
8C8 6 0
CN7314 0 6
CN7875 0 6
CN8351 0 6
- How many sequencing Runs are there in each Lab?
- How many Animals are there in each sequencing Run?
- Are there Animals with samples from multiple sequencing Runs?
- Is it better to put all of each animal’s samples in the same run, or is it better to distribute each animal’s samples across multiple runs?
Performing a basic limma analysis
Filtering non-expressed genes
Our count matrix contains information on every known gene in the cyno genome, but we only want to look at the genes that are expressed in MSCs. Many genes will not be expressed at all and will have zero or very few counts in all samples, and the statistical method breaks down when it is fed all zeros. So we need to decide on a threshold of gene detection and filter out all the genes whose average expression does not reach this threshold. Let’s see how a threshold of logCPM = 1 looks on a histogram and a QQ plot against normal distribution quantiles.
mean_log_cpm <- aveLogCPM(count_matrix)
filter_threshold <- 1
ggplot() + aes(x=mean_log_cpm) +
geom_histogram(binwidth=0.2) +
geom_vline(xintercept=filter_threshold) +
ggtitle("Histogram of mean expression values")

qqnorm(mean_log_cpm); abline(h=filter_threshold)

- What do the shapes of these two plots indicate?
- How can we justify our choice of threshold using these plots?
- Would this same detection threshold be appropriate for other experiments?
- What biological or experimental factors might influence the choice of threshold?
Having chosen our threshold, let’s pick the subset of genes whose average expression passes that threshold.
keep_genes <- mean_log_cpm >= 1
filtered_count_matrix <- count_matrix[keep_genes,]
filtered_gene_info <- gene_info[keep_genes,]
nrow(filtered_count_matrix)
[1] 11907
- What fraction of the genes in the genome are considered expressed according to our threshold?
- Is this number sensitive to our choice of threshold? How much of a difference does it make if we use a threshold of 0.5 or 1.5 instead?
Normalization
Ok, now that we have some understanding of the experimental design, let’s find some differentially expressed genes! We’ll start by computing the total counts in each sample and then normalizing these total counts using the Trimmed Mean of M-values (TMM) method, provided by the calcNormFactors
function.
total_counts <- colSums(count_matrix)
nf <- calcNormFactors(count_matrix, lib.size=total_counts, method="TMM")
print(nf)
LabA.R104.6C4_65M.P4.Control LabA.R104.6C4_65M.P4.IFNg LabA.R104.6C4_65M.P5.Control
0.8800745 0.9898919 0.8203199
LabA.R104.6C4_65M.P5.IFNg LabA.R104.6C4_65M.P6.Control LabA.R104.6C4_65M.P6.IFNg
0.9952668 0.8838364 1.0259290
LabA.R107.6C63.P4.Control LabA.R107.6C63.P4.IFNg LabA.R107.6C63.P5.Control
1.1900295 1.3016013 1.1399379
LabA.R107.6C63.P5.IFNg LabA.R107.6C63.P6.Control LabA.R107.6C63.P6.IFNg
1.1169711 1.0104253 1.2073377
LabA.R107.6C7.P4.Control LabA.R107.6C7.P4.IFNg LabA.R107.6C7.P5.Control
1.0517275 1.0998364 1.0280885
LabA.R107.6C7.P5.IFNg LabA.R107.6C7.P6.Control LabA.R107.6C7.P6.IFNg
1.0526623 0.8782156 1.0749254
LabA.R104.6C84.P4.Control LabA.R104.6C84.P4.IFNg LabA.R104.6C84.P5.Control
0.6720892 0.9333467 0.6241975
LabA.R104.6C84.P5.IFNg LabA.R104.6C84.P6.Control LabA.R104.6C84.P6.IFNg
0.8950369 0.6706472 0.9438313
LabA.R107.7C37.P4.Control LabA.R107.7C37.P4.IFNg LabA.R107.7C37.P5.Control
1.0769607 1.1242476 1.0746693
LabA.R107.7C37.P5.IFNg LabA.R107.7C37.P6.Control LabA.R107.7C37.P6.IFNg
1.0230317 1.1620610 1.0660020
LabA.R104.8C8.P4.Control LabA.R104.8C8.P4.IFNg LabA.R104.8C8.P5.Control
0.8419412 1.0470961 0.7138150
LabA.R104.8C8.P5.IFNg LabA.R104.8C8.P6.Control LabA.R104.8C8.P6.IFNg
1.0934070 0.8403635 1.1033499
LabB.R95.CN7314.P4.Control LabB.R95.CN7314.P4.IFNg LabB.R95.CN7314.P5.Control
0.9509877 1.0742606 0.9817360
LabB.R95.CN7314.P5.IFNg LabB.R95.CN7314.P6.Control LabB.R95.CN7314.P6.IFNg
1.0921636 1.2666923 1.0758749
LabB.R95.CN7875.P4.Control LabB.R95.CN7875.P4.IFNg LabB.R95.CN7875.P5.Control
0.9013256 1.0833068 1.0313982
LabB.R95.CN7875.P5.IFNg LabB.R95.CN7875.P6.Control LabB.R95.CN7875.P6.IFNg
1.1667053 1.1918567 1.3519535
LabB.R95.CN8351.P4.Control LabB.R95.CN8351.P4.IFNg LabB.R95.CN8351.P5.Control
0.9827153 0.9560459 1.0029934
LabB.R95.CN8351.P5.IFNg LabB.R95.CN8351.P6.Control LabB.R95.CN8351.P6.IFNg
0.9652303 0.9766554 0.9307331
normalized_total_counts <- total_counts * nf
- What is the range of total_counts/normalization factors? (Try the
summary
function)
- What is the average total count per sample? Does this seem high or low? How might we expect this to affect our results?
Fitting the linear models
Now that we have computed our normalization and filtered out non-expressed genes, it’s time to fit our linear models. The basic model fitting function provided by limma is lmFit
, which is essentially a shortcut for running lm
once for each gene, using the same model formula each time.
First, we will construct our design matrix by selecting an appropriate model formula. This step is performed automatically when you run lm
, but for lmFit
we must do it manually. For our first model, we’ll keep it simple and use Treatment as the only covariate.
design <- model.matrix(~ Treatment, data=sample_table)
head(design)
(Intercept) TreatmentIFNg
LabA.R104.6C4_65M.P4.Control 1 0
LabA.R104.6C4_65M.P4.IFNg 1 1
LabA.R104.6C4_65M.P5.Control 1 0
LabA.R104.6C4_65M.P5.IFNg 1 1
LabA.R104.6C4_65M.P6.Control 1 0
LabA.R104.6C4_65M.P6.IFNg 1 1
Note that the design has two columns, representing the two coefficients in our model. The first one, named (Intercept)
, represents the expression level of the Control samples, while the second coefficient represents the difference between Control and IFNg treatments. This is the coefficient that we will test for differential expression once we have fit our model. This model will be equivalent to a simple two-sample t-test.
Before we fit our model, we have to run voom
to compensate for the heteroskedasticity of the counts. (While we’re at it, we also insert the gene metadata into the resulting object. This will allow limma to include the gene metadata its result tables automatically.)
v <- voom(filtered_count_matrix, design, lib.size=normalized_total_counts, plot=TRUE)

v$genes <- filtered_gene_info
Running voom
with plot=TRUE
produces a diagnostic plot showing the empirical relationship between log count and variance. The fitted curve is a running average of the cloud of points, and this curve is what voom uses to assign a weight to each observed count.
- According to the plot, which count would have the highest weight (i.e. the lowest expected variance): 4, 30, or 30000? (Remember the log2 scale)
- Which would have the lowest weight?
- Is the relationship between log count and variance monotonic? Why might it not be monotonic, if higher counts are supposed to be more precise?
- Why does
voom
need to know our experimental design?
The object returned by voom
is an EList
, which is a complex object similar in spirit to a SummarizedExperiment. You don’t need to know anything about its internals. Just know that it contains both the matrix of log2(CPM) values and the corresponding matrix of weights. It also contains the gene info, which we added manually. Together with the design matrix, this is everything we need to fit our linear models.
fit <- lmFit(v, design)
fit <- eBayes(fit, robust=TRUE)
Recall that the eBayes
function is responsible for squeezing each gene’s sample variance toward the overall mean variance of the whole data set, in the process trading a bit of bias for stability.
Getting our results
Anyway, now we can perform a “moderated t-test”. “Moderated” means that we are substituting the empirical Bayes squeezed variance for the sample variance in the formula for the t statistic (and also adjusting the degrees of freedom term accordingly). To get our results, we call topTable
, telling it which coefficient we wish to test, along with which multiple testing correction to use on the p-values. The n=Inf
tells it to give us the results for all the genes in the data set.
results <- topTable(fit, coef="TreatmentIFNg", adjust.method="BH", n=Inf)
head(results)
- What is the estimated FDR for the most significant gene (the FDR is stored in the
adj.P.Val
column)?
- How many genes are significantly differentially expressed at a threshold of 10% FDR?
- Do you see any genes that make biological sense in the top few results?
Now let’s inspect the p-value histogram. For reference, we’ll add in a horizontal line indicating what a uniform distribution would look like.
ggplot(results) +
aes(x=P.Value) +
geom_histogram(aes(y=..density..), binwidth=0.025, boundary=0) +
geom_hline(yintercept=1) +
ggtitle("P-value distribution for Control vs Treatment")

- What can we conclude from this histogram?
Lastly, we can generate an MA plot showing the log2 fold change vs the log2 CPM for each gene:
ggplot(arrange(results, desc(P.Value))) +
aes(x=AveExpr, y=logFC,
color=ifelse(adj.P.Val <= 0.1, "FDR <= 10%", "FDR > 10%")) +
geom_point(size=0.1) +
scale_color_hue(name="Significance") +
theme(legend.justification=c(1,1), legend.position=c(1,1)) +
ggtitle("MA Plot, IFNg vs Control")

Another common plot is the volcano plot, which plots significance vs log fold change:
ggplot(arrange(results, desc(P.Value))) +
aes(x=logFC, y=-log10(P.Value),
color=ifelse(adj.P.Val <= 0.1, "FDR <= 10%", "FDR > 10%")) +
geom_point(size=0.1) +
scale_color_hue(name="Significance") +
theme(legend.justification=c(1,1), legend.position=c(1,1)) +
ggtitle("Volcano Plot, IFNg vs Control")

- Based on these plots, are the changes balanced between up and down, or is there a bias toward a certain direction of change? Does this make sense for the biology of interferon treatment?
Improving the analysis by exploring the data
We got pretty good results above, but how do we know that we analyzed the data correctly? Are there any important covariates that we should have included in our model? How can we figure out which covariates are important? One way is to do a PCA plot. Limma actually provides something called an MDS or PCoA plot, which is slightly different from a PCA plot but serves the same purpose.
mds <- data.frame(plotMDS(v)[c("x", "y")])

Limma’s plotMDS
function creates an MDS plot using the sample labels, but this results in a very crowded plot. Luckily, it also returns the x and y coordinates of the plot, which we can use to make our own. Because we want to see how each covariate relates to the principal coordinates, let’s make several versions of our MDS plot, colored by each covariate.
mds <- cbind(mds, sample_table)
p <- ggplot(mds) +
aes(x=x, y=y) +
xlab("PC1") + ylab("PC2") +
geom_point(size=3) +
coord_fixed(ratio=1) +
ggtitle("Sample MDS Plot")
for (i in c("Lab", "Run", "Animal.ID", "Passage", "Treatment")) {
print(p + aes_string(color=i) +
ggtitle(paste("Sample MDS Plot Colored by", i)))
}





- Based on the MDS plots, which variables seem to be important, and which do not?
Investigating an outlier
You might have noticed that a few of the IFNg-treated samples cluster with the Control samples. These look like possible outlier samples, and we should investigate them, since they could interfere with our model fit. Let’s try coloring by Animal ID and using a different shape for the IFNg samples.
p + aes(color=Animal.ID, shape=Treatment)

- Can you identify the misbehaving Animal?
Let’s remove the samples for this animal from the data set and repeat the whole limma analysis.
bad_animal <- "CN8351"
selected_samples <- !(sample_table$Animal.ID %in% bad_animal)
good_sample_table <- droplevels(sample_table[selected_samples,])
good_filtered_count_matrix <- count_matrix[keep_genes,selected_samples]
good_normalized_total_counts <- normalized_total_counts[selected_samples]
design <- model.matrix(~ Treatment, data=good_sample_table)
v <- voom(good_filtered_count_matrix, design, lib.size=good_normalized_total_counts)
v$genes <- filtered_gene_info
fit <- lmFit(v, design)
fit <- eBayes(fit, robust=TRUE)
results <- topTable(fit, coef="TreatmentIFNg", adjust.method="BH", n=Inf)
table(results$adj.P.Val <= 0.1)
FALSE TRUE
6788 5119
- How did removing the outlier animal affect the number of differentially expressed genes?
Hopefully this demonstrates the importance of properly exploring your data before deciding on a model. You can always fit any model to the data, but the model will give you some kind of answer, sometimes even a plausible-looking one, whether or not that model is a good fit for the data. By eliminating an outlier, we significantly increased our ability to detect differential expression. Similarly, the focus of the homework will be to explore the consequences of adding additional covariates into the model formula.
Homework: Basic Model Selection
Try fitting models with Animal.ID, Passage, or both in addition to Treatment. In other words, try all four of the following model formulae:
~ Treatment # (This is the model we just fit in class)
~ Animal.ID + Treatment
~ Passage + Treatment
~ Animal.ID + Passage + Treatment
Use the filtered dataset with the outlier animal samples removed. In addition to testing Treatment for differential expression in each model, also test the other covariates for differential expression by passing a different value for the coef
argument to topTable
. You will need to pass a vector of all the columns of the design matrix relevant to the covariate. Below, I have included an example of how to fit the model and test for differential expression for the formula ~Passage + Treatment
. (Hint: use colnames(design)
to help you figure out which coefficients to test.)
design <- model.matrix(~ Passage + Treatment, data=good_sample_table)
v <- voom(good_filtered_count_matrix, design, lib.size=good_normalized_total_counts)
v$genes <- filtered_gene_info
fit <- lmFit(v, design)
fit <- eBayes(fit, robust=TRUE)
results <- topTable(fit, coef="TreatmentIFNg", adjust.method="BH", n=Inf)
table(results$adj.P.Val <= 0.1)
passage.results <- topTable(fit, coef=c("PassageP5", "PassageP6"), n=Inf)
table(passage.results$adj.P.Val <= 0.1)
Based on your results for all four models, decide which model best fits the data. Justify your choice with MDS plots and p-value distribution plots that show why you are including or excluding each of Animal.ID and Passage from your model. How do your results for Treatment change when you include your chosen covariates? Does your model give more differentially expressed genes than the one we fit in class? If so, how many more? How many genes are differentially expressed with respect to your chosen covariates? Do these results still hold when using a different signifcance threshold, such as 5% or 1% FDR instead of 10%?
For help fitting models with multiple covariates, see the Limma User’s Guide section 9.4, “Additive Models and Blocking”.
Homework shortcut: selectModel()
designList <- list(
TrtOnly=model.matrix(~ Treatment, data=good_sample_table),
TrtPass=model.matrix(~ Treatment + Passage, data=good_sample_table),
TrtAnimal=model.matrix(~ Treatment + Animal.ID, data=good_sample_table),
TrtPassAnimal=model.matrix(~ Treatment + Animal.ID + Passage, data=good_sample_table))
v <- voom(good_filtered_count_matrix, designList$TrtPassAnimal, lib.size=good_normalized_total_counts)
sm <- selectModel(v, designlist = designList, criterion = "aic")
table(sm$pref)
TrtOnly TrtPass TrtAnimal TrtPassAnimal
418 65 7888 3536
LS0tCnRpdGxlOiBSTkEtc2VxIHN0dWR5IG9mIGN5bm9tb2xndXMgbW9ua2V5IG1lc2VuY2h5bWFsIHN0ZW0gY2VsbHMgdHJlYXRlZCB3aXRoIGludGVyZmVyb24KICBnYW1tYQphdXRob3I6ICJSeWFuIEMuIFRob21wc29uIgpkYXRlOiAiTWF5IDgsIDIwMTgiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgZmlnX2NhcHRpb246IHllcwogIHBkZl9kb2N1bWVudDoKICAgIGZpZ19jYXB0aW9uOiB5ZXMKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCByZXRpbmE9MiwgY2FjaGU9VFJVRSwgYXV0b2RlcD1UUlVFKQpgYGAKCiMgSW50cm9kdWN0aW9uCgpUb2RheSB3ZSdyZSBnb2luZyB0byBsb29rIGF0IGEgZGF0YSBzZXQgZnJvbSBteSB3b3JrLiBUaGlzIGRhdGEgY29tZXMKZnJvbSBtZW5jaHltbmFsIHN0ZW0gY2VsbHMgKE1TQ3MpIGN1bHR1cmVkIGZyb20gOSBjeW5vbW9sZ3VzIG1vbmtleXMKKGEgY2xvc2VseSByZWxhdGVkIHNwZWNpZXMgdG8gcmhlc3VzIG1hY2FxdWVzKS4gTVNDcyBhcmUga25vd24gdG8gaGF2ZQppbW11bmUgbW9kdWxhdG9yeSBlZmZlY3RzLCBhbmQgdGhlIG1haW4gZ29hbCBvZiB0aGUgcHJvamVjdCBpcyB0byB0ZXN0CndoZXRoZXIgdGhlc2Ugc3RlbSBjZWxscywgd2hlbiBhY3RpdmF0ZWQgd2l0aCBpbnRlcmZlcm9uIGdhbW1hIChJRk5nKSwKY2FuIGhlbHAgc3VwcHJlc3MgdGhlIGJvZHkncyBpbW11bmUgcmVzcG9uc2UgYWdhaW5zdCBhbiBvcmdhbgp0cmFuc3BsYW50LgoKIVtLbm93biBlZmZlY3RzIG9mIE1TQyB3aGljaCBtYXkgYmVuZWZpdCBvcmdhbiB0cmFuc3BsYW50c10oaW1hZ2VzL21zYzEtY3JvcHBlZC5wbmcpCgpUaGUgcHVycG9zZSBvZiB0aGlzIHNwZWNpZmljIGRhdGEgc2V0IGlzIHNpbXBseSB0byBkZXRlcm1pbmUgd2hpY2gKZ2VuZXMnIGV4cHJlc3Npb24gaXMgYWZmZWN0ZWQgd2hlbiBNU0NzIGFyZSB0cmVhdGVkIHdpdGggSUZOZy4gQXMKc3VjaCwgd2UgaGF2ZSBib3RoIHVudHJlYXRlZCAoQ29udHJvbCkgc2FtcGxlcyBhbmQgSUZOZy10cmVhdGVkCnNhbXBsZXMgZnJvbSAzIGRpZmZlcmVudCBwYXNzYWdlcyBvZiB0aGUgY2VsbCBjdWx0dXJlcy4gU28sIHdlJ2xsIGJlCnVzaW5nIGxpbW1hIHRvIHRlc3QgZWFjaCBnZW5lIGZvciBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBiZXR3ZWVuCkNvbnRyb2wgYW5kIElGTmcgc2FtcGxlcy4KCiMgUHJlbGltaW5hcnkgc2V0dXAKCkZpcnN0LCBsZXQncyBpbnN0YWxsIGFsbCB0aGUgcGFja2FnZXMgd2UnbGwgbmVlZC4KCmBgYHtyIGluc3RhbGxfcGtncywgZXZhbD1GQUxTRX0KaWYgKCFyZXF1aXJlTmFtZXNwYWNlKCJCaW9jTWFuYWdlciIsIHF1aWV0bHkgPSBUUlVFKSkKICAgIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikKYWxyZWFkeV9pbnN0YWxsZWRfcGFja2FnZXMgPC0gcm93bmFtZXMoaW5zdGFsbGVkLnBhY2thZ2VzKCkpCm5lZWRlZF9wYWNrYWdlcyA8LSBjKCJlZGdlUiIsICJsaW1tYSIsICJTdW1tYXJpemVkRXhwZXJpbWVudCIsICJnZ3Bsb3QyIiwgImxvY2ZpdCIsICJkcGx5ciIsICJzdGF0bW9kIikKbmVlZF90b19pbnN0YWxsIDwtIHNldGRpZmYobmVlZGVkX3BhY2thZ2VzLCBhbHJlYWR5X2luc3RhbGxlZF9wYWNrYWdlcykKaWYgKGxlbmd0aChuZWVkX3RvX2luc3RhbGwpID4gMCkgewogICAgIyMgaHR0cDovL2Jpb2NvbmR1Y3Rvci5vcmcvaW5zdGFsbC8KICAgIEJpb2NNYW5hZ2VyOjppbnN0YWxsKG5lZWRfdG9faW5zdGFsbCkKfQpgYGAKClRoZW4gd2UnbGwgbG9hZCB0aG9zZSBwYWNrYWdlcy4KCmBgYHtyIGxvYWRfcGtncywgcmVzdWx0cz0iaGlkZSIsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoU3VtbWFyaXplZEV4cGVyaW1lbnQpCmxpYnJhcnkoZWRnZVIpCmxpYnJhcnkobGltbWEpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShkcGx5cikKYGBgCgojIEluaXRpYWwgZGF0YSBsb2FkaW5nIGFuZCBleHBsb3JhdGlvbgoKTGV0J3MgbG9hZCB0aGUgZGF0YSBmaWxlLiBUaGlzIHVzZXMgdGhlIGByZWFkUkRTYCBmdW5jdGlvbiwgd2hpY2gKcmVhZHMgYSBzaW5nbGUgUiBvYmplY3QgZnJvbSBhIGZpbGUgYW5kIHJldHVybnMgaXQuIFdlIGFzc2lnbiB0aGUKcmVzdWx0IHRvIGEgdmFyaWFibGUuCgpgYGB7ciBsb2FkaW5nX2RhdGF9CnNleHAgPC0gcmVhZFJEUyhnemNvbih1cmwoImh0dHBzOi8vZGFyd2luYXdhcmR3aW5uZXIuZ2l0aHViLmlvL3Jlc3VtZS9leGFtcGxlcy9TYWxvbW9uL1RlYWNoaW5nL0N5bm8tUk5BU2VxLVN1bW1hcml6ZWRFeHBlcmltZW50LlJEUyIpKSkKcHJpbnQoc2V4cCkKYGBgCgpUaGlzIG9iamVjdCBpcyBjYWxsZWQgYSBTdW1tYXJpemVkRXhwZXJpbWVudCwgYW5kIGl0cyBwdXJwb3NlIGlzIHRvCmhvbGQgdGhlIGV4cGVyaW1lbnRhbCBkYXRhLCBzYW1wbGUgbWV0YWRhdGEsIGdlbmUgbWV0YWRhdGEsIGFuZCBhbnkKb3RoZXIgcmVsZXZhbnQgaW5mb3JtYXRpb24gZm9yIHRoZSBleHBlcmltZW50IGFsbCBpbiBvbmUgcGxhY2UuIFRoaXMKbWFrZXMgaXQgYW4gZXhjZWxsZW50IHN0YXJ0aW5nIHBvaW50IGZvciBhbnkgYW5hbHlzaXMuIEluIG91ciBjYXNlLAp0aGUgZXhwZXJpbWVudGFsIGRhdGEgY29uc2lzdHMgb2YgYSBtYXRyaXggb2YgY291bnRzIGZvciBlYWNoIGdlbmUgaW4KZWFjaCBzYW1wbGUuIFRoaXMgY291bnQgbWF0cml4IHdhcyBnZW5lcmF0ZWQgYnkgYWxpZ25pbmcgYWxsIHRoZSBSTkEKc2VxdWVuY2UgcmVhZHMgaW4gZWFjaCBzYW1wbGUgdG8gdGhlCltjeW5vIGdlbm9tZV0oaHR0cDovL3d3dy5uY2JpLm5sbS5uaWguZ292L2dlbm9tZS83NzYpIGFuZCB0aGVuCmNvdW50aW5nIHRoZSBudW1iZXIgb2YgdW5pcXVlbHktbWFwcGVkIHJlYWRzIHRoYXQgb3ZlcmxhcCBlYWNoIGdlbmUuIEkKaGF2ZSBkb25lIHRoZSBhbGlnbmluZyBhbmQgY291bnRpbmcgcHJvY2VzcyBmb3IgeW91IGFuZCBwcm92aWRlZCB0aGUKY291bnQgbWF0cml4LCBzaW5jZSB0aGUgYWxpZ25tZW50IGFuZCBjb3VudGluZyB3b3VsZCB0YWtlIG1hbnkgaG91cnMKdG8gY29tbXBsZXRlLgoKTG9vayBhdCB0aGUgb3V0cHV0IG9mIHRoZSBwcmludCBzdGF0ZW1lbnQgYWJvdmUuIFRoZSBSTkEtU2VxIHJlYWQKY291bnRzIGFyZSBjb250YWluZWQgaW4gdGhlIGBhc3NheXNgIHNsb3QuIFRoZSBgY29sRGF0YWAgc2xvdCBjb250YWlucwppbmZvcm1hdGlvbiBvbiB0aGUgc2FtcGxlcywgd2hpbGUgdGhlIGByb3dSYW5nZXNgIHNsb3QgY29udGFpbnMKaW5mb3JtYXRpb24gb24gdGhlIGdlbmVzLiBUaGUgcm93cyBoYXZlICJyYW5nZXMiIGluc3RlYWQgb2YganVzdAoiZGF0YSIgYmVjYXVzZSBpbiBhZGRpdGlvbiB0byB0aGluZ3MgbGlrZSB0aGUgZ2VuZSBzeW1ib2wgYW5kCmRlc2NyaXB0aW9uLCB0aGUgYHJvd1Jhbmdlc2Agc2xvdCBhbHNvIGNvbnRhaW5zIHRoZSBnZW5vbWljCmNvb3JkaW5hdGVzIG9mIGVhY2ggZ2VuZSwgdGhhdCBpcywgdGhlIGNocm9tb3NvbWUsIHN0YXJ0L2VuZCBwb3NpdGlvbnMKb2YgZWFjaCBleG9uLCBhbmQgdGhlIHN0cmFuZC4gVGhpcyBpcyB0aGUgc2FtZSBpbmZvcm1hdGlvbiB0aGF0IHdhcwp1c2VkIHRvIGNvdW50IHRoZSBvdmVybGFwcGluZyByZWFkcyBmb3IgZWFjaCBnZW5lLCBidXQgd2Ugd29uJ3QgYmUKbmVlZGluZyBpdCB0b2RheS4KCllvdSBjYW4gcmVhZCBtb3JlIGFib3V0IFN1bW1hcml6ZWRFeHBlcmltZW50IG9iamVjdHMKW2hlcmVdKGh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvYmlvYy92aWduZXR0ZXMvU3VtbWFyaXplZEV4cGVyaW1lbnQvaW5zdC9kb2MvU3VtbWFyaXplZEV4cGVyaW1lbnQuaHRtbCkuCgohW1N0cnVjdHVyZSBvZiBhIFN1bW1hcml6ZWRFeHBlcmltZW50IG9iamVjdF0oaW1hZ2VzL1NFWFAuc3ZnKQoKKiBIb3cgbWFueSBzYW1wbGVzIGRvZXMgdGhpcyBTdW1tYXJpemVkRXhwZXJpbWVudCBvYmplY3QgY29udGFpbj8gSG93CiAgbWFueSBnZW5lcyBkb2VzIGl0IGhhdmU/IFdoZXJlIGRpZCB5b3UgZmluZCB0aGlzIGluZm9ybWF0aW9uPwoqIFdoYXQgaGFwcGVucyB3aGVuIHlvdSB0cnkgdG8gc2VsZWN0IGEgc3Vic2V0IG9mIHJvd3Mgb3IgY29sdW1ucz8KCiMjIEV4dHJhY3RpbmcgdGhlIGRhdGEgYW5kIG1ldGFkYXRhCgpPaywgbGV0J3MgcHVsbCBvdXQgdGhlIGluZm9ybWF0aW9uIHRoYXQgd2Ugd2FudCBmcm9tIHRoaXMKU3VtbWFyaXplZEV4cGVyaW1lbnQuCgpGaXJzdCwgdGhlIHNhbXBsZSBpbmZvcm1hdGlvbjoKCmBgYHtyIGV4dHJhY3Rfc2FtcGxldGFibGV9CnNhbXBsZV90YWJsZSA8LSBhcy5kYXRhLmZyYW1lKGNvbERhdGEoc2V4cCkpCmBgYAoKTmV4dCwgdGhlIGdlbmUgaW5mb3JtYXRpb246CgpgYGB7ciBleHRyYWN0X2dlbmVpbmZvfQojIyBHZW5lIG1ldGFkYXRhCmdlbmVfaW5mbyA8LSBhcy5kYXRhLmZyYW1lKG1jb2xzKHNleHApKQpgYGAKCkFuZCBmaW5hbGx5LCB0aGUgY291bnQgbWF0cml4OgoKYGBge3IgZXh0cmFjdF9jb3VudHN9CmNvdW50X21hdHJpeCA8LSBhc3NheShzZXhwKQpgYGAKCkV4YW1pbmUgdGhlIGBzYW1wbGVfdGFibGVgLCBgZ2VuZV9pbmZvYCwgYW5kIGBjb3VudF9tYXRyaXhgIG9iamVjdHMuClRyeSBgZGltYCwgYGNsYXNzYCwgYG5yb3dgLCBgbmNvbGAsIGFuZCBzaW1pbGFyLiBUcnkgdmlld2luZyB0aGUgZmlyc3QKZmV3IHJvd3Mgb3IgY29sdW1ucyBvZiBlYWNoIG9uZS4KCiogV2hhdCBraW5kIG9mIG9iamVjdCBpcyBlYWNoIG9uZT8KKiBXaGF0IGtpbmQgb2YgZGF0YSBkbyB0aGV5IGNvbnRhaW4/CiogV2hpY2ggZGltZW5zaW9ucyBkbyB0aGV5IGhhdmUgaW4gY29tbW9uPyBIb3cgZG8gdGhlc2UgZGltZW5zaW9ucwogIG1hdGNoIHVwIHRvIHRoZSBkaW1lbnNpb24gb2YgdGhlIFN1bW1hcml6ZWRFeHBlcmltZW50PwoKYGBge3IgZXhhbXBsZV9zZXhwX2NvbXBvbmVudHMsIGluY2x1ZGU9RkFMU0V9CmRpbShzZXhwKQpkaW0oc2FtcGxlX3RhYmxlKQpkaW0oZ2VuZV9pbmZvKQpkaW0oY291bnRfbWF0cml4KQoKaGVhZChzYW1wbGVfdGFibGUpCmhlYWQoZ2VuZV9pbmZvKQojIyBGaW5kIHRoZSBJRk5HIGdlbmUKZ2VuZV9pbmZvW3doaWNoKGdlbmVfaW5mbyRzeW1ib2wgPT0gIklGTkciKSxdCmNvdW50X21hdHJpeFsxOjEwLDE6Nl0KYGBgCgojIyBMZWFybmluZyBhYm91dCB0aGUgZXhwZXJpbWVudGFsIGRlc2lnbiBmcm9tIHRoZSBzYW1wbGUgdGFibGUKCkJlZm9yZSB3ZSBnZXQgdG8gdGhlIGFuYWx5c2lzLCB3ZSBuZWVkIHRvIGhhdmUgYSBjbG9zZXIgbG9vayBhdCB0aGUKc2FtcGxlIHRhYmxlLCB0byBnYWluIGEgYmV0dGVyIHVuZGVyc3RhbmRpbmcgb2YgdGhlIGV4cGVyaW1lbnRhbApkZXNpZ24uCgpgYGB7ciBoZWFkX3NhbXBsZV90YWJsZX0KbmFtZXMoc2FtcGxlX3RhYmxlKQpoZWFkKHNhbXBsZV90YWJsZSkKYGBgCgoqIEhvdyBtYW55IExhYnMvUnVucy9BbmltYWxzL1Bhc3NhZ2VzL1RyZWF0bWVudHMgYXJlIHRoZXJlIGluIHRoaXMKICBleHBlcmltZW50PwoqIEhvdyBtYW55IHNhbXBsZXMgYXJlIGluIGVhY2ggb25lPwoKKFlvdSBtYXkgZmluZCB0aGUgYHVuaXF1ZWAgYW5kIGB0YWJsZWAgZnVuY3Rpb25zIHVzZWZ1bCBmb3IgYW5zd2VyaW5nCnRoZXNlIHF1ZXN0aW9ucy4pCgpgYGB7ciB0YWJ1bGF0ZV9zYW1wbGVzLCBpbmNsdWRlPUZBTFNFfQpsYXBwbHkoc2FtcGxlX3RhYmxlWy0xXSwgdW5pcXVlKQpsYXBwbHkoc2FtcGxlX3RhYmxlWy0xXSwgdGFibGUpCmBgYAoqIEhvdyBtYW55IEFuaW1hbHMgYXJlIHRoZXJlIGluIGVhY2ggTGFiPwoqIEFyZSB0aGVyZSBBbmltYWxzIHdpdGggc2FtcGxlcyBmcm9tIGJvdGggTGFicz8KCmBgYHtyIHRhYnVsYXRlX2FuaW1hbHNfaW5fbGFic30KdGFibGUoc2FtcGxlX3RhYmxlJEFuaW1hbC5JRCwgc2FtcGxlX3RhYmxlJExhYikKYGBgCgoqIEhvdyBtYW55IHNlcXVlbmNpbmcgUnVucyBhcmUgdGhlcmUgaW4gZWFjaCBMYWI/CiogSG93IG1hbnkgQW5pbWFscyBhcmUgdGhlcmUgaW4gZWFjaCBzZXF1ZW5jaW5nIFJ1bj8KKiBBcmUgdGhlcmUgQW5pbWFscyB3aXRoIHNhbXBsZXMgZnJvbSBtdWx0aXBsZSBzZXF1ZW5jaW5nIFJ1bnM/CiogSXMgaXQgYmV0dGVyIHRvIHB1dCBhbGwgb2YgZWFjaCBhbmltYWwncyBzYW1wbGVzIGluIHRoZSBzYW1lIHJ1biwgb3IKICBpcyBpdCBiZXR0ZXIgdG8gZGlzdHJpYnV0ZSBlYWNoIGFuaW1hbCdzIHNhbXBsZXMgYWNyb3NzIG11bHRpcGxlCiAgcnVucz8KCmBgYHtyIHRhYnVsYXRlX21vcmVfdGhpbmdzLCBpbmNsdWRlPUZBTFNFfQp0YWJsZShzYW1wbGVfdGFibGUkQW5pbWFsLklELCBzYW1wbGVfdGFibGUkUnVuKQp0YWJsZShzYW1wbGVfdGFibGUkUnVuLCBzYW1wbGVfdGFibGUkTGFiKQpgYGAKCiMgUGVyZm9ybWluZyBhIGJhc2ljIGxpbW1hIGFuYWx5c2lzICMKCiMjIEZpbHRlcmluZyBub24tZXhwcmVzc2VkIGdlbmVzICMjCgpPdXIgY291bnQgbWF0cml4IGNvbnRhaW5zIGluZm9ybWF0aW9uIG9uIGV2ZXJ5IGtub3duIGdlbmUgaW4gdGhlIGN5bm8KZ2Vub21lLCBidXQgd2Ugb25seSB3YW50IHRvIGxvb2sgYXQgdGhlIGdlbmVzIHRoYXQgYXJlIGV4cHJlc3NlZCBpbgpNU0NzLiBNYW55IGdlbmVzIHdpbGwgbm90IGJlIGV4cHJlc3NlZCBhdCBhbGwgYW5kIHdpbGwgaGF2ZSB6ZXJvIG9yCnZlcnkgZmV3IGNvdW50cyBpbiBhbGwgc2FtcGxlcywgYW5kIHRoZSBzdGF0aXN0aWNhbCBtZXRob2QgYnJlYWtzIGRvd24Kd2hlbiBpdCBpcyBmZWQgYWxsIHplcm9zLiBTbyB3ZSBuZWVkIHRvIGRlY2lkZSBvbiBhIHRocmVzaG9sZCBvZiBnZW5lCmRldGVjdGlvbiBhbmQgZmlsdGVyIG91dCBhbGwgdGhlIGdlbmVzIHdob3NlIGF2ZXJhZ2UgZXhwcmVzc2lvbiBkb2VzCm5vdCByZWFjaCB0aGlzIHRocmVzaG9sZC4gTGV0J3Mgc2VlIGhvdyBhIHRocmVzaG9sZCBvZiBsb2dDUE0gPSAxCmxvb2tzIG9uIGEgaGlzdG9ncmFtIGFuZCBhIFFRIHBsb3QgYWdhaW5zdCBub3JtYWwgZGlzdHJpYnV0aW9uCnF1YW50aWxlcy4KCmBgYHtyIHBsb3RfY3BtX3RocmVzaG9sZCwgZmlnLmNhcD0ibG9nQ1BNIGhpc3RvZ3JhbSB3aXRoIHRocmVzaG9sZCBsaW5lIn0KbWVhbl9sb2dfY3BtIDwtIGF2ZUxvZ0NQTShjb3VudF9tYXRyaXgpCmZpbHRlcl90aHJlc2hvbGQgPC0gMQpnZ3Bsb3QoKSArIGFlcyh4PW1lYW5fbG9nX2NwbSkgKwogICAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGg9MC4yKSArCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9ZmlsdGVyX3RocmVzaG9sZCkgKwogICAgZ2d0aXRsZSgiSGlzdG9ncmFtIG9mIG1lYW4gZXhwcmVzc2lvbiB2YWx1ZXMiKQpgYGAKCmBgYHtyIHBsb3RfY3BtX3FxLCBmaWcuY2FwPSJsb2dDUE0gUVEgcGxvdCB3aXRoIHRocmVzaG9sZCBsaW5lIn0KcXFub3JtKG1lYW5fbG9nX2NwbSk7IGFibGluZShoPWZpbHRlcl90aHJlc2hvbGQpCmBgYAoKKiBXaGF0IGRvIHRoZSBzaGFwZXMgb2YgdGhlc2UgdHdvIHBsb3RzIGluZGljYXRlPwoqIEhvdyBjYW4gd2UganVzdGlmeSBvdXIgY2hvaWNlIG9mIHRocmVzaG9sZCB1c2luZyB0aGVzZSBwbG90cz8KKiBXb3VsZCB0aGlzIHNhbWUgZGV0ZWN0aW9uIHRocmVzaG9sZCBiZSBhcHByb3ByaWF0ZSBmb3Igb3RoZXIKICBleHBlcmltZW50cz8KKiBXaGF0IGJpb2xvZ2ljYWwgb3IgZXhwZXJpbWVudGFsIGZhY3RvcnMgbWlnaHQgaW5mbHVlbmNlIHRoZSBjaG9pY2UKICBvZiB0aHJlc2hvbGQ/CgpIYXZpbmcgY2hvc2VuIG91ciB0aHJlc2hvbGQsIGxldCdzIHBpY2sgdGhlIHN1YnNldCBvZiBnZW5lcyB3aG9zZQphdmVyYWdlIGV4cHJlc3Npb24gcGFzc2VzIHRoYXQgdGhyZXNob2xkLgoKYGBge3IgZmlsdGVyX2dlbmVzfQprZWVwX2dlbmVzIDwtIG1lYW5fbG9nX2NwbSA+PSAxCmZpbHRlcmVkX2NvdW50X21hdHJpeCA8LSBjb3VudF9tYXRyaXhba2VlcF9nZW5lcyxdCmZpbHRlcmVkX2dlbmVfaW5mbyA8LSBnZW5lX2luZm9ba2VlcF9nZW5lcyxdCm5yb3coZmlsdGVyZWRfY291bnRfbWF0cml4KQpgYGAKCiogV2hhdCBmcmFjdGlvbiBvZiB0aGUgZ2VuZXMgaW4gdGhlIGdlbm9tZSBhcmUgY29uc2lkZXJlZCBleHByZXNzZWQKICBhY2NvcmRpbmcgdG8gb3VyIHRocmVzaG9sZD8KKiBJcyB0aGlzIG51bWJlciBzZW5zaXRpdmUgdG8gb3VyIGNob2ljZSBvZiB0aHJlc2hvbGQ/IEhvdyBtdWNoIG9mIGEKICBkaWZmZXJlbmNlIGRvZXMgaXQgbWFrZSBpZiB3ZSB1c2UgYSB0aHJlc2hvbGQgb2YgMC41IG9yIDEuNSBpbnN0ZWFkPwoKYGBge3IgdGhyZXNob2xkX3NlbnNpdGl2aXR5LCBpbmNsdWRlPUZBTFNFfQptZWFuKGtlZXBfZ2VuZXMpCm1lYW4obWVhbl9sb2dfY3BtID49IDEuNSkKbWVhbihtZWFuX2xvZ19jcG0gPj0gMC41KQpgYGAKCiMjIE5vcm1hbGl6YXRpb24gIyMKCk9rLCBub3cgdGhhdCB3ZSBoYXZlIHNvbWUgdW5kZXJzdGFuZGluZyBvZiB0aGUgZXhwZXJpbWVudGFsIGRlc2lnbiwKbGV0J3MgZmluZCBzb21lIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcyEgV2UnbGwgc3RhcnQgYnkKY29tcHV0aW5nIHRoZSB0b3RhbCBjb3VudHMgaW4gZWFjaCBzYW1wbGUgYW5kIHRoZW4gbm9ybWFsaXppbmcgdGhlc2UKdG90YWwgY291bnRzIHVzaW5nIHRoZSBUcmltbWVkIE1lYW4gb2YgTS12YWx1ZXMgKFRNTSkgbWV0aG9kLCBwcm92aWRlZApieSB0aGUgYGNhbGNOb3JtRmFjdG9yc2AgZnVuY3Rpb24uCgpgYGB7ciBjYWxjTm9ybUZhY3RvcnN9CnRvdGFsX2NvdW50cyA8LSBjb2xTdW1zKGNvdW50X21hdHJpeCkKbmYgPC0gY2FsY05vcm1GYWN0b3JzKGNvdW50X21hdHJpeCwgbGliLnNpemU9dG90YWxfY291bnRzLCBtZXRob2Q9IlRNTSIpCnByaW50KG5mKQpub3JtYWxpemVkX3RvdGFsX2NvdW50cyA8LSB0b3RhbF9jb3VudHMgKiBuZgpgYGAKCiogV2hhdCBpcyB0aGUgcmFuZ2Ugb2YgdG90YWxfY291bnRzL25vcm1hbGl6YXRpb24gZmFjdG9ycz8gKFRyeSB0aGUKICBgc3VtbWFyeWAgZnVuY3Rpb24pCiogV2hhdCBpcyB0aGUgYXZlcmFnZSB0b3RhbCBjb3VudCBwZXIgc2FtcGxlPyBEb2VzIHRoaXMgc2VlbSBoaWdoIG9yCiAgbG93PyBIb3cgbWlnaHQgd2UgZXhwZWN0IHRoaXMgdG8gYWZmZWN0IG91ciByZXN1bHRzPwoKYGBge3IgaW5zcGVjdF9uZiwgaW5jbHVkZT1GQUxTRX0Kc3VtbWFyeSh0b3RhbF9jb3VudHMpCnN1bW1hcnkobmYpCmBgYAoKIyMgRml0dGluZyB0aGUgbGluZWFyIG1vZGVscyAjIwoKTm93IHRoYXQgd2UgaGF2ZSBjb21wdXRlZCBvdXIgbm9ybWFsaXphdGlvbiBhbmQgZmlsdGVyZWQgb3V0Cm5vbi1leHByZXNzZWQgZ2VuZXMsIGl0J3MgdGltZSB0byBmaXQgb3VyIGxpbmVhciBtb2RlbHMuIFRoZSBiYXNpYwptb2RlbCBmaXR0aW5nIGZ1bmN0aW9uIHByb3ZpZGVkIGJ5IGxpbW1hIGlzIGBsbUZpdGAsIHdoaWNoIGlzCmVzc2VudGlhbGx5IGEgc2hvcnRjdXQgZm9yIHJ1bm5pbmcgYGxtYCBvbmNlIGZvciBlYWNoIGdlbmUsIHVzaW5nIHRoZQpzYW1lIG1vZGVsIGZvcm11bGEgZWFjaCB0aW1lLgoKRmlyc3QsIHdlIHdpbGwgY29uc3RydWN0IG91ciBkZXNpZ24gbWF0cml4IGJ5IHNlbGVjdGluZyBhbiBhcHByb3ByaWF0ZQptb2RlbCBmb3JtdWxhLiBUaGlzIHN0ZXAgaXMgcGVyZm9ybWVkIGF1dG9tYXRpY2FsbHkgd2hlbiB5b3UgcnVuIGBsbWAsCmJ1dCBmb3IgYGxtRml0YCB3ZSBtdXN0IGRvIGl0IG1hbnVhbGx5LiBGb3Igb3VyIGZpcnN0IG1vZGVsLCB3ZSdsbAprZWVwIGl0IHNpbXBsZSBhbmQgdXNlIFRyZWF0bWVudCBhcyB0aGUgb25seSBjb3ZhcmlhdGUuCgpgYGB7ciBkZXNpZ259CmRlc2lnbiA8LSBtb2RlbC5tYXRyaXgofiBUcmVhdG1lbnQsIGRhdGE9c2FtcGxlX3RhYmxlKQpoZWFkKGRlc2lnbikKYGBgCgpOb3RlIHRoYXQgdGhlIGRlc2lnbiBoYXMgdHdvIGNvbHVtbnMsIHJlcHJlc2VudGluZyB0aGUgdHdvCmNvZWZmaWNpZW50cyBpbiBvdXIgbW9kZWwuIFRoZSBmaXJzdCBvbmUsIG5hbWVkIGAoSW50ZXJjZXB0KWAsCnJlcHJlc2VudHMgdGhlIGV4cHJlc3Npb24gbGV2ZWwgb2YgdGhlIENvbnRyb2wgc2FtcGxlcywgd2hpbGUgdGhlCnNlY29uZCBjb2VmZmljaWVudCByZXByZXNlbnRzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gQ29udHJvbCBhbmQgSUZOZwp0cmVhdG1lbnRzLiBUaGlzIGlzIHRoZSBjb2VmZmljaWVudCB0aGF0IHdlIHdpbGwgdGVzdCBmb3IgZGlmZmVyZW50aWFsCmV4cHJlc3Npb24gb25jZSB3ZSBoYXZlIGZpdCBvdXIgbW9kZWwuIFRoaXMgbW9kZWwgd2lsbCBiZSBlcXVpdmFsZW50CnRvIGEgc2ltcGxlIHR3by1zYW1wbGUgdC10ZXN0LgoKQmVmb3JlIHdlIGZpdCBvdXIgbW9kZWwsIHdlIGhhdmUgdG8gcnVuIGB2b29tYCB0byBjb21wZW5zYXRlIGZvciB0aGUKaGV0ZXJvc2tlZGFzdGljaXR5IG9mIHRoZSBjb3VudHMuIChXaGlsZSB3ZSdyZSBhdCBpdCwgd2UgYWxzbyBpbnNlcnQKdGhlIGdlbmUgbWV0YWRhdGEgaW50byB0aGUgcmVzdWx0aW5nIG9iamVjdC4gVGhpcyB3aWxsIGFsbG93IGxpbW1hIHRvCmluY2x1ZGUgdGhlIGdlbmUgbWV0YWRhdGEgaXRzIHJlc3VsdCB0YWJsZXMgYXV0b21hdGljYWxseS4pCgpgYGB7ciB2b29tLCBmaWcuY2FwPSJWb29tIGRpYWdub3N0aWMgcGxvdCJ9CnYgPC0gdm9vbShmaWx0ZXJlZF9jb3VudF9tYXRyaXgsIGRlc2lnbiwgbGliLnNpemU9bm9ybWFsaXplZF90b3RhbF9jb3VudHMsIHBsb3Q9VFJVRSkKdiRnZW5lcyA8LSBmaWx0ZXJlZF9nZW5lX2luZm8KYGBgCgpSdW5uaW5nIGB2b29tYCB3aXRoIGBwbG90PVRSVUVgIHByb2R1Y2VzIGEgZGlhZ25vc3RpYyBwbG90IHNob3dpbmcgdGhlCmVtcGlyaWNhbCByZWxhdGlvbnNoaXAgYmV0d2VlbiBsb2cgY291bnQgYW5kIHZhcmlhbmNlLiBUaGUgZml0dGVkCmN1cnZlIGlzIGEgcnVubmluZyBhdmVyYWdlIG9mIHRoZSBjbG91ZCBvZiBwb2ludHMsIGFuZCB0aGlzIGN1cnZlIGlzCndoYXQgdm9vbSB1c2VzIHRvIGFzc2lnbiBhIHdlaWdodCB0byBlYWNoIG9ic2VydmVkIGNvdW50LgoKKiBBY2NvcmRpbmcgdG8gdGhlIHBsb3QsIHdoaWNoIGNvdW50IHdvdWxkIGhhdmUgdGhlIGhpZ2hlc3Qgd2VpZ2h0CiAgKGkuZS4gdGhlIGxvd2VzdCBleHBlY3RlZCB2YXJpYW5jZSk6IDQsIDMwLCBvciAzMDAwMD8gKFJlbWVtYmVyIHRoZQogIGxvZzIgc2NhbGUpCiogV2hpY2ggd291bGQgaGF2ZSB0aGUgbG93ZXN0IHdlaWdodD8KKiBJcyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gbG9nIGNvdW50IGFuZCB2YXJpYW5jZSBtb25vdG9uaWM/IFdoeQogIG1pZ2h0IGl0IG5vdCBiZSBtb25vdG9uaWMsIGlmIGhpZ2hlciBjb3VudHMgYXJlIHN1cHBvc2VkIHRvIGJlIG1vcmUKICBwcmVjaXNlPwoqIFdoeSBkb2VzIGB2b29tYCBuZWVkIHRvIGtub3cgb3VyIGV4cGVyaW1lbnRhbCBkZXNpZ24/CgpUaGUgb2JqZWN0IHJldHVybmVkIGJ5IGB2b29tYCBpcyBhbiBgRUxpc3RgLCB3aGljaCBpcyBhIGNvbXBsZXggb2JqZWN0CnNpbWlsYXIgaW4gc3Bpcml0IHRvIGEgU3VtbWFyaXplZEV4cGVyaW1lbnQuIFlvdSBkb24ndCBuZWVkIHRvIGtub3cKYW55dGhpbmcgYWJvdXQgaXRzIGludGVybmFscy4gSnVzdCBrbm93IHRoYXQgaXQgY29udGFpbnMgYm90aCB0aGUKbWF0cml4IG9mIGxvZzIoQ1BNKSB2YWx1ZXMgYW5kIHRoZSBjb3JyZXNwb25kaW5nIG1hdHJpeCBvZiB3ZWlnaHRzLiBJdAphbHNvIGNvbnRhaW5zIHRoZSBnZW5lIGluZm8sIHdoaWNoIHdlIGFkZGVkIG1hbnVhbGx5LiBUb2dldGhlciB3aXRoCnRoZSBkZXNpZ24gbWF0cml4LCB0aGlzIGlzIGV2ZXJ5dGhpbmcgd2UgbmVlZCB0byBmaXQgb3VyIGxpbmVhcgptb2RlbHMuCgpgYGB7ciBsbWZpdH0KZml0IDwtIGxtRml0KHYsIGRlc2lnbikKZml0IDwtIGVCYXllcyhmaXQsIHJvYnVzdD1UUlVFKQpgYGAKClJlY2FsbCB0aGF0IHRoZSBgZUJheWVzYCBmdW5jdGlvbiBpcyByZXNwb25zaWJsZSBmb3Igc3F1ZWV6aW5nIGVhY2gKZ2VuZSdzIHNhbXBsZSB2YXJpYW5jZSB0b3dhcmQgdGhlIG92ZXJhbGwgbWVhbiB2YXJpYW5jZSBvZiB0aGUgd2hvbGUKZGF0YSBzZXQsIGluIHRoZSBwcm9jZXNzIHRyYWRpbmcgYSBiaXQgb2YgYmlhcyBmb3Igc3RhYmlsaXR5LgoKIyMgR2V0dGluZyBvdXIgcmVzdWx0cyAjIwoKQW55d2F5LCBub3cgd2UgY2FuIHBlcmZvcm0gYSAibW9kZXJhdGVkIHQtdGVzdCIuICJNb2RlcmF0ZWQiIG1lYW5zCnRoYXQgd2UgYXJlIHN1YnN0aXR1dGluZyB0aGUgZW1waXJpY2FsIEJheWVzIHNxdWVlemVkIHZhcmlhbmNlIGZvciB0aGUKc2FtcGxlIHZhcmlhbmNlIGluIHRoZSBmb3JtdWxhIGZvciB0aGUgdCBzdGF0aXN0aWMgKGFuZCBhbHNvIGFkanVzdGluZwp0aGUgZGVncmVlcyBvZiBmcmVlZG9tIHRlcm0gYWNjb3JkaW5nbHkpLiBUbyBnZXQgb3VyIHJlc3VsdHMsIHdlIGNhbGwKYHRvcFRhYmxlYCwgdGVsbGluZyBpdCB3aGljaCBjb2VmZmljaWVudCB3ZSB3aXNoIHRvIHRlc3QsIGFsb25nIHdpdGgKd2hpY2ggbXVsdGlwbGUgdGVzdGluZyBjb3JyZWN0aW9uIHRvIHVzZSBvbiB0aGUgcC12YWx1ZXMuIFRoZSBgbj1JbmZgCnRlbGxzIGl0IHRvIGdpdmUgdXMgdGhlIHJlc3VsdHMgZm9yIGFsbCB0aGUgZ2VuZXMgaW4gdGhlIGRhdGEgc2V0LgoKYGBge3IgcmVzdWx0c30KcmVzdWx0cyA8LSB0b3BUYWJsZShmaXQsIGNvZWY9IlRyZWF0bWVudElGTmciLCBhZGp1c3QubWV0aG9kPSJCSCIsIG49SW5mKQpoZWFkKHJlc3VsdHMpCmBgYAoKKiBXaGF0IGlzIHRoZSBlc3RpbWF0ZWQgRkRSIGZvciB0aGUgbW9zdCBzaWduaWZpY2FudCBnZW5lICh0aGUgRkRSIGlzCiAgc3RvcmVkIGluIHRoZSBgYWRqLlAuVmFsYCBjb2x1bW4pPwoqIEhvdyBtYW55IGdlbmVzIGFyZSBzaWduaWZpY2FudGx5IGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBhdCBhCiAgdGhyZXNob2xkIG9mIDEwJSBGRFI/CiogRG8geW91IHNlZSBhbnkgZ2VuZXMgdGhhdCBtYWtlIGJpb2xvZ2ljYWwgc2Vuc2UgaW4gdGhlIHRvcCBmZXcKICByZXN1bHRzPwoKYGBge3IgbnVtX3NpZywgaW5jbHVkZT1GQUxTRX0KcmVzdWx0cyRhZGouUC5WYWxbMV0KdGFibGUocmVzdWx0cyRhZGouUC5WYWwgPD0gMC4xKQpgYGAKCk5vdyBsZXQncyBpbnNwZWN0IHRoZSBwLXZhbHVlIGhpc3RvZ3JhbS4gRm9yIHJlZmVyZW5jZSwgd2UnbGwgYWRkIGluIGEKaG9yaXpvbnRhbCBsaW5lIGluZGljYXRpbmcgd2hhdCBhIHVuaWZvcm0gZGlzdHJpYnV0aW9uIHdvdWxkIGxvb2sKbGlrZS4KCmBgYHtyIHB2YWxfaGlzdCwgZmlnLmNhcD0iUC12YWx1ZSBoaXN0b2dyYW0gZm9yIHRoZSBmaXJzdCBhbmFseXNpcyJ9CmdncGxvdChyZXN1bHRzKSArCiAgICBhZXMoeD1QLlZhbHVlKSArCiAgICBnZW9tX2hpc3RvZ3JhbShhZXMoeT0uLmRlbnNpdHkuLiksIGJpbndpZHRoPTAuMDI1LCBib3VuZGFyeT0wKSArCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9MSkgKwogICAgZ2d0aXRsZSgiUC12YWx1ZSBkaXN0cmlidXRpb24gZm9yIENvbnRyb2wgdnMgVHJlYXRtZW50IikKYGBgCgoqIFdoYXQgY2FuIHdlIGNvbmNsdWRlIGZyb20gdGhpcyBoaXN0b2dyYW0/CgpMYXN0bHksIHdlIGNhbiBnZW5lcmF0ZSBhbiBNQSBwbG90IHNob3dpbmcgdGhlIGxvZzIgZm9sZCBjaGFuZ2UgdnMgdGhlCmxvZzIgQ1BNIGZvciBlYWNoIGdlbmU6CgpgYGB7ciBtYXBsb3R9CmdncGxvdChhcnJhbmdlKHJlc3VsdHMsIGRlc2MoUC5WYWx1ZSkpKSArCiAgICBhZXMoeD1BdmVFeHByLCB5PWxvZ0ZDLAogICAgICAgICBjb2xvcj1pZmVsc2UoYWRqLlAuVmFsIDw9IDAuMSwgIkZEUiA8PSAxMCUiLCAiRkRSID4gMTAlIikpICsKICAgIGdlb21fcG9pbnQoc2l6ZT0wLjEpICsKICAgIHNjYWxlX2NvbG9yX2h1ZShuYW1lPSJTaWduaWZpY2FuY2UiKSArCiAgICB0aGVtZShsZWdlbmQuanVzdGlmaWNhdGlvbj1jKDEsMSksIGxlZ2VuZC5wb3NpdGlvbj1jKDEsMSkpICsKICAgIGdndGl0bGUoIk1BIFBsb3QsIElGTmcgdnMgQ29udHJvbCIpCmBgYAoKQW5vdGhlciBjb21tb24gcGxvdCBpcyB0aGUgdm9sY2FubyBwbG90LCB3aGljaCBwbG90cyBzaWduaWZpY2FuY2UgdnMKbG9nIGZvbGQgY2hhbmdlOgoKYGBge3Igdm9sY2Fub3Bsb3R9CmdncGxvdChhcnJhbmdlKHJlc3VsdHMsIGRlc2MoUC5WYWx1ZSkpKSArCiAgICBhZXMoeD1sb2dGQywgeT0tbG9nMTAoUC5WYWx1ZSksCiAgICAgICAgIGNvbG9yPWlmZWxzZShhZGouUC5WYWwgPD0gMC4xLCAiRkRSIDw9IDEwJSIsICJGRFIgPiAxMCUiKSkgKwogICAgZ2VvbV9wb2ludChzaXplPTAuMSkgKwogICAgc2NhbGVfY29sb3JfaHVlKG5hbWU9IlNpZ25pZmljYW5jZSIpICsKICAgIHRoZW1lKGxlZ2VuZC5qdXN0aWZpY2F0aW9uPWMoMSwxKSwgbGVnZW5kLnBvc2l0aW9uPWMoMSwxKSkgKwogICAgZ2d0aXRsZSgiVm9sY2FubyBQbG90LCBJRk5nIHZzIENvbnRyb2wiKQpgYGAKCiogQmFzZWQgb24gdGhlc2UgcGxvdHMsIGFyZSB0aGUgY2hhbmdlcyBiYWxhbmNlZCBiZXR3ZWVuIHVwIGFuZCBkb3duLAogIG9yIGlzIHRoZXJlIGEgYmlhcyB0b3dhcmQgYSBjZXJ0YWluIGRpcmVjdGlvbiBvZiBjaGFuZ2U/IERvZXMgdGhpcwogIG1ha2Ugc2Vuc2UgZm9yIHRoZSBiaW9sb2d5IG9mIGludGVyZmVyb24gdHJlYXRtZW50PwoKCiMgSW1wcm92aW5nIHRoZSBhbmFseXNpcyBieSBleHBsb3JpbmcgdGhlIGRhdGEKCldlIGdvdCBwcmV0dHkgZ29vZCByZXN1bHRzIGFib3ZlLCBidXQgaG93IGRvIHdlIGtub3cgdGhhdCB3ZSBhbmFseXplZAp0aGUgZGF0YSBjb3JyZWN0bHk/IEFyZSB0aGVyZSBhbnkgaW1wb3J0YW50IGNvdmFyaWF0ZXMgdGhhdCB3ZSBzaG91bGQKaGF2ZSBpbmNsdWRlZCBpbiBvdXIgbW9kZWw/IEhvdyBjYW4gd2UgZmlndXJlIG91dCB3aGljaCBjb3ZhcmlhdGVzIGFyZQppbXBvcnRhbnQ/IE9uZSB3YXkgaXMgdG8gZG8gYSBQQ0EgcGxvdC4gTGltbWEgYWN0dWFsbHkgcHJvdmlkZXMKc29tZXRoaW5nIGNhbGxlZCBhbiBNRFMgb3IgUENvQSBwbG90LCB3aGljaCBpcyBzbGlnaHRseSBkaWZmZXJlbnQgZnJvbQphIFBDQSBwbG90IGJ1dCBzZXJ2ZXMgdGhlIHNhbWUgcHVycG9zZS4KCmBgYHtyIG1kc19pbml0LCBmaWcuY2FwPSJCYXNpYyBNRFMgcGxvdCBmcm9tIGxpbW1hIn0KbWRzIDwtIGRhdGEuZnJhbWUocGxvdE1EUyh2KVtjKCJ4IiwgInkiKV0pCmBgYAoKTGltbWEncyBgcGxvdE1EU2AgZnVuY3Rpb24gY3JlYXRlcyBhbiBNRFMgcGxvdCB1c2luZyB0aGUgc2FtcGxlCmxhYmVscywgYnV0IHRoaXMgcmVzdWx0cyBpbiBhIHZlcnkgY3Jvd2RlZCBwbG90LiBMdWNraWx5LCBpdCBhbHNvCnJldHVybnMgdGhlIHggYW5kIHkgY29vcmRpbmF0ZXMgb2YgdGhlIHBsb3QsIHdoaWNoIHdlIGNhbiB1c2UgdG8gbWFrZQpvdXIgb3duLiBCZWNhdXNlIHdlIHdhbnQgdG8gc2VlIGhvdyBlYWNoIGNvdmFyaWF0ZSByZWxhdGVzIHRvIHRoZQpwcmluY2lwYWwgY29vcmRpbmF0ZXMsIGxldCdzIG1ha2Ugc2V2ZXJhbCB2ZXJzaW9ucyBvZiBvdXIgTURTIHBsb3QsCmNvbG9yZWQgYnkgZWFjaCBjb3ZhcmlhdGUuCgpgYGB7ciBtZHMsIHJlc3VsdHM9ImhpZGUifQptZHMgPC0gY2JpbmQobWRzLCBzYW1wbGVfdGFibGUpCnAgPC0gZ2dwbG90KG1kcykgKwogICAgYWVzKHg9eCwgeT15KSArCiAgICB4bGFiKCJQQzEiKSArIHlsYWIoIlBDMiIpICsKICAgIGdlb21fcG9pbnQoc2l6ZT0zKSArCiAgICBjb29yZF9maXhlZChyYXRpbz0xKSArCiAgICBnZ3RpdGxlKCJTYW1wbGUgTURTIFBsb3QiKQpmb3IgKGkgaW4gYygiTGFiIiwgIlJ1biIsICJBbmltYWwuSUQiLCAiUGFzc2FnZSIsICJUcmVhdG1lbnQiKSkgewogICAgcHJpbnQocCArIGFlc19zdHJpbmcoY29sb3I9aSkgKwogICAgICAgICAgZ2d0aXRsZShwYXN0ZSgiU2FtcGxlIE1EUyBQbG90IENvbG9yZWQgYnkiLCBpKSkpCn0KYGBgCgoqIEJhc2VkIG9uIHRoZSBNRFMgcGxvdHMsIHdoaWNoIHZhcmlhYmxlcyBzZWVtIHRvIGJlIGltcG9ydGFudCwgYW5kCiAgd2hpY2ggZG8gbm90PwoKIyMgSW52ZXN0aWdhdGluZyBhbiBvdXRsaWVyCgpZb3UgbWlnaHQgaGF2ZSBub3RpY2VkIHRoYXQgYSBmZXcgb2YgdGhlIElGTmctdHJlYXRlZCBzYW1wbGVzIGNsdXN0ZXIKd2l0aCB0aGUgQ29udHJvbCBzYW1wbGVzLiBUaGVzZSBsb29rIGxpa2UgcG9zc2libGUgb3V0bGllciBzYW1wbGVzLAphbmQgd2Ugc2hvdWxkIGludmVzdGlnYXRlIHRoZW0sIHNpbmNlIHRoZXkgY291bGQgaW50ZXJmZXJlIHdpdGggb3VyCm1vZGVsIGZpdC4gTGV0J3MgdHJ5IGNvbG9yaW5nIGJ5IEFuaW1hbCBJRCBhbmQgdXNpbmcgYSBkaWZmZXJlbnQgc2hhcGUKZm9yIHRoZSBJRk5nIHNhbXBsZXMuCgpgYGB7ciBpZF9vdXRsaWVyLCBmaWcuY2FwPSJNRFMgcGxvdCBmb3IgaWRlbnRpZnlpbmcgdGhlIG91dGxpZXIgYW5pbWFsIn0KcCArIGFlcyhjb2xvcj1BbmltYWwuSUQsIHNoYXBlPVRyZWF0bWVudCkKYGBgCgoqIENhbiB5b3UgaWRlbnRpZnkgdGhlIG1pc2JlaGF2aW5nIEFuaW1hbD8KCkxldCdzIHJlbW92ZSB0aGUgc2FtcGxlcyBmb3IgdGhpcyBhbmltYWwgZnJvbSB0aGUgZGF0YSBzZXQgYW5kIHJlcGVhdAp0aGUgd2hvbGUgbGltbWEgYW5hbHlzaXMuCgpgYGB7ciBmaWx0ZXJfb3V0bGllcl9hbmltYWx9CmJhZF9hbmltYWwgPC0gIkNOODM1MSIKc2VsZWN0ZWRfc2FtcGxlcyA8LSAhKHNhbXBsZV90YWJsZSRBbmltYWwuSUQgJWluJSBiYWRfYW5pbWFsKQoKZ29vZF9zYW1wbGVfdGFibGUgPC0gZHJvcGxldmVscyhzYW1wbGVfdGFibGVbc2VsZWN0ZWRfc2FtcGxlcyxdKQpnb29kX2ZpbHRlcmVkX2NvdW50X21hdHJpeCA8LSBjb3VudF9tYXRyaXhba2VlcF9nZW5lcyxzZWxlY3RlZF9zYW1wbGVzXQpnb29kX25vcm1hbGl6ZWRfdG90YWxfY291bnRzIDwtIG5vcm1hbGl6ZWRfdG90YWxfY291bnRzW3NlbGVjdGVkX3NhbXBsZXNdCgpkZXNpZ24gPC0gbW9kZWwubWF0cml4KH4gVHJlYXRtZW50LCBkYXRhPWdvb2Rfc2FtcGxlX3RhYmxlKQp2IDwtIHZvb20oZ29vZF9maWx0ZXJlZF9jb3VudF9tYXRyaXgsIGRlc2lnbiwgbGliLnNpemU9Z29vZF9ub3JtYWxpemVkX3RvdGFsX2NvdW50cykKdiRnZW5lcyA8LSBmaWx0ZXJlZF9nZW5lX2luZm8KZml0IDwtIGxtRml0KHYsIGRlc2lnbikKZml0IDwtIGVCYXllcyhmaXQsIHJvYnVzdD1UUlVFKQpyZXN1bHRzIDwtIHRvcFRhYmxlKGZpdCwgY29lZj0iVHJlYXRtZW50SUZOZyIsIGFkanVzdC5tZXRob2Q9IkJIIiwgbj1JbmYpCnRhYmxlKHJlc3VsdHMkYWRqLlAuVmFsIDw9IDAuMSkKYGBgCgoqIEhvdyBkaWQgcmVtb3ZpbmcgdGhlIG91dGxpZXIgYW5pbWFsIGFmZmVjdCB0aGUgbnVtYmVyIG9mCiAgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGdlbmVzPwoKSG9wZWZ1bGx5IHRoaXMgZGVtb25zdHJhdGVzIHRoZSBpbXBvcnRhbmNlIG9mIHByb3Blcmx5IGV4cGxvcmluZyB5b3VyCmRhdGEgYmVmb3JlIGRlY2lkaW5nIG9uIGEgbW9kZWwuIFlvdSBjYW4gYWx3YXlzIGZpdCBhbnkgbW9kZWwgdG8gdGhlCmRhdGEsIGJ1dCB0aGUgbW9kZWwgd2lsbCBnaXZlIHlvdSBzb21lIGtpbmQgb2YgYW5zd2VyLCBzb21ldGltZXMgZXZlbgphIHBsYXVzaWJsZS1sb29raW5nIG9uZSwgd2hldGhlciBvciBub3QgdGhhdCBtb2RlbCBpcyBhIGdvb2QgZml0IGZvcgp0aGUgZGF0YS4gQnkgZWxpbWluYXRpbmcgYW4gb3V0bGllciwgd2Ugc2lnbmlmaWNhbnRseSBpbmNyZWFzZWQgb3VyCmFiaWxpdHkgdG8gZGV0ZWN0IGRpZmZlcmVudGlhbCBleHByZXNzaW9uLiBTaW1pbGFybHksIHRoZSBmb2N1cyBvZiB0aGUKaG9tZXdvcmsgd2lsbCBiZSB0byBleHBsb3JlIHRoZSBjb25zZXF1ZW5jZXMgb2YgYWRkaW5nIGFkZGl0aW9uYWwKY292YXJpYXRlcyBpbnRvIHRoZSBtb2RlbCBmb3JtdWxhLgoKIyBIb21ld29yazogQmFzaWMgTW9kZWwgU2VsZWN0aW9uCgpUcnkgZml0dGluZyBtb2RlbHMgd2l0aCBBbmltYWwuSUQsIFBhc3NhZ2UsIG9yIGJvdGggaW4gYWRkaXRpb24gdG8KVHJlYXRtZW50LiBJbiBvdGhlciB3b3JkcywgdHJ5IGFsbCBmb3VyIG9mIHRoZSBmb2xsb3dpbmcgbW9kZWwKZm9ybXVsYWU6CgpgYGB7ciBtb2RlbF9vcHRpb25zLCBldmFsPUZBTFNFfQp+IFRyZWF0bWVudCAjIChUaGlzIGlzIHRoZSBtb2RlbCB3ZSBqdXN0IGZpdCBpbiBjbGFzcykKfiBBbmltYWwuSUQgKyBUcmVhdG1lbnQKfiBQYXNzYWdlICsgVHJlYXRtZW50Cn4gQW5pbWFsLklEICsgUGFzc2FnZSArIFRyZWF0bWVudApgYGAKClVzZSB0aGUgZmlsdGVyZWQgZGF0YXNldCB3aXRoIHRoZSBvdXRsaWVyIGFuaW1hbCBzYW1wbGVzIHJlbW92ZWQuIEluCmFkZGl0aW9uIHRvIHRlc3RpbmcgVHJlYXRtZW50IGZvciBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBpbiBlYWNoCm1vZGVsLCBhbHNvIHRlc3QgdGhlIG90aGVyIGNvdmFyaWF0ZXMgZm9yIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGJ5CnBhc3NpbmcgYSBkaWZmZXJlbnQgdmFsdWUgZm9yIHRoZSBgY29lZmAgYXJndW1lbnQgdG8gYHRvcFRhYmxlYC4gWW91CndpbGwgbmVlZCB0byBwYXNzIGEgdmVjdG9yIG9mIGFsbCB0aGUgY29sdW1ucyBvZiB0aGUgZGVzaWduIG1hdHJpeApyZWxldmFudCB0byB0aGUgY292YXJpYXRlLiBCZWxvdywgSSBoYXZlIGluY2x1ZGVkIGFuIGV4YW1wbGUgb2YgaG93IHRvCmZpdCB0aGUgbW9kZWwgYW5kIHRlc3QgZm9yIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGZvciB0aGUgZm9ybXVsYQpgflBhc3NhZ2UgKyBUcmVhdG1lbnRgLiAoSGludDogdXNlIGBjb2xuYW1lcyhkZXNpZ24pYCB0byBoZWxwIHlvdQpmaWd1cmUgb3V0IHdoaWNoIGNvZWZmaWNpZW50cyB0byB0ZXN0LikKCmBgYHtyIGhvbWV3b3JrX2V4YW1wbGUsIGV2YWw9RkFMU0V9CmRlc2lnbiA8LSBtb2RlbC5tYXRyaXgofiBQYXNzYWdlICsgVHJlYXRtZW50LCBkYXRhPWdvb2Rfc2FtcGxlX3RhYmxlKQp2IDwtIHZvb20oZ29vZF9maWx0ZXJlZF9jb3VudF9tYXRyaXgsIGRlc2lnbiwgbGliLnNpemU9Z29vZF9ub3JtYWxpemVkX3RvdGFsX2NvdW50cykKdiRnZW5lcyA8LSBmaWx0ZXJlZF9nZW5lX2luZm8KZml0IDwtIGxtRml0KHYsIGRlc2lnbikKZml0IDwtIGVCYXllcyhmaXQsIHJvYnVzdD1UUlVFKQpyZXN1bHRzIDwtIHRvcFRhYmxlKGZpdCwgY29lZj0iVHJlYXRtZW50SUZOZyIsIGFkanVzdC5tZXRob2Q9IkJIIiwgbj1JbmYpCnRhYmxlKHJlc3VsdHMkYWRqLlAuVmFsIDw9IDAuMSkKcGFzc2FnZS5yZXN1bHRzIDwtIHRvcFRhYmxlKGZpdCwgY29lZj1jKCJQYXNzYWdlUDUiLCAiUGFzc2FnZVA2IiksIG49SW5mKQp0YWJsZShwYXNzYWdlLnJlc3VsdHMkYWRqLlAuVmFsIDw9IDAuMSkKYGBgCgpCYXNlZCBvbiB5b3VyIHJlc3VsdHMgZm9yIGFsbCBmb3VyIG1vZGVscywgZGVjaWRlIHdoaWNoIG1vZGVsIGJlc3QKZml0cyB0aGUgZGF0YS4gSnVzdGlmeSB5b3VyIGNob2ljZSB3aXRoIE1EUyBwbG90cyBhbmQgcC12YWx1ZQpkaXN0cmlidXRpb24gcGxvdHMgdGhhdCBzaG93IHdoeSB5b3UgYXJlIGluY2x1ZGluZyBvciBleGNsdWRpbmcgZWFjaApvZiBBbmltYWwuSUQgYW5kIFBhc3NhZ2UgZnJvbSB5b3VyIG1vZGVsLiBIb3cgZG8geW91ciByZXN1bHRzIGZvcgpUcmVhdG1lbnQgY2hhbmdlIHdoZW4geW91IGluY2x1ZGUgeW91ciBjaG9zZW4gY292YXJpYXRlcz8gRG9lcyB5b3VyCm1vZGVsIGdpdmUgbW9yZSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMgdGhhbiB0aGUgb25lIHdlIGZpdCBpbgpjbGFzcz8gSWYgc28sIGhvdyBtYW55IG1vcmU/IEhvdyBtYW55IGdlbmVzIGFyZSBkaWZmZXJlbnRpYWxseQpleHByZXNzZWQgd2l0aCByZXNwZWN0IHRvIHlvdXIgY2hvc2VuIGNvdmFyaWF0ZXM/IERvIHRoZXNlIHJlc3VsdHMKc3RpbGwgaG9sZCB3aGVuIHVzaW5nIGEgZGlmZmVyZW50IHNpZ25pZmNhbmNlIHRocmVzaG9sZCwgc3VjaCBhcyA1JSBvcgoxJSBGRFIgaW5zdGVhZCBvZiAxMCU/CgpGb3IgaGVscCBmaXR0aW5nIG1vZGVscyB3aXRoIG11bHRpcGxlIGNvdmFyaWF0ZXMsIHNlZSB0aGUKW0xpbW1hIFVzZXIncyBHdWlkZV0oaHR0cHM6Ly93d3cuYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL2Jpb2MvdmlnbmV0dGVzL2xpbW1hL2luc3QvZG9jL3VzZXJzZ3VpZGUucGRmKQpzZWN0aW9uIDkuNCwgIkFkZGl0aXZlIE1vZGVscyBhbmQgQmxvY2tpbmciLgoKIyBIb21ld29yayBzaG9ydGN1dDogYHNlbGVjdE1vZGVsKClgCgpgYGB7ciBob21ld29ya19zaG9ydGN1dH0KZGVzaWduTGlzdCA8LSBsaXN0KAogIFRydE9ubHk9bW9kZWwubWF0cml4KH4gVHJlYXRtZW50LCBkYXRhPWdvb2Rfc2FtcGxlX3RhYmxlKSwKICBUcnRQYXNzPW1vZGVsLm1hdHJpeCh+IFRyZWF0bWVudCArIFBhc3NhZ2UsIGRhdGE9Z29vZF9zYW1wbGVfdGFibGUpLAogIFRydEFuaW1hbD1tb2RlbC5tYXRyaXgofiBUcmVhdG1lbnQgKyBBbmltYWwuSUQsIGRhdGE9Z29vZF9zYW1wbGVfdGFibGUpLAogIFRydFBhc3NBbmltYWw9bW9kZWwubWF0cml4KH4gVHJlYXRtZW50ICsgQW5pbWFsLklEICsgUGFzc2FnZSwgZGF0YT1nb29kX3NhbXBsZV90YWJsZSkpCnYgPC0gdm9vbShnb29kX2ZpbHRlcmVkX2NvdW50X21hdHJpeCwgZGVzaWduTGlzdCRUcnRQYXNzQW5pbWFsLCBsaWIuc2l6ZT1nb29kX25vcm1hbGl6ZWRfdG90YWxfY291bnRzKQpzbSA8LSBzZWxlY3RNb2RlbCh2LCBkZXNpZ25saXN0ID0gZGVzaWduTGlzdCwgY3JpdGVyaW9uID0gImFpYyIpCnRhYmxlKHNtJHByZWYpCmBgYAo=