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.

Known effects of MSC which may benefit organ transplants

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.

Structure of a SummarizedExperiment object

Extracting the data and metadata

Ok, let’s pull out the information that we want from this SummarizedExperiment.

First, the sample information:

sample_table <- as.data.frame(colData(sexp))

Next, the gene information:

## Gene metadata
gene_info <- as.data.frame(mcols(sexp))

And finally, the count matrix:

count_matrix <- assay(sexp)

Examine the sample_table, gene_info, and count_matrix objects. Try dim, class, nrow, ncol, and similar. Try viewing the first few rows or columns of each one.

  • What kind of object is each one?
  • What kind of data do they contain?
  • Which dimensions do they have in common? How do these dimensions match up to the dimension of the SummarizedExperiment?

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)))
}

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=