Creative Commons License

This is part of the online course Proteomics Data Analysis (PDA)

Outline

  • Francisella tularensis Example
  • Hypothesis testing
  • Multiple testing
  • Moderated statistics
  • Experimental design

Note, that the R-code is included for learners who are aiming to develop R/markdown scripts to automate their quantitative proteomics data analyses. According to the target audience of the course we either work with a graphical user interface (GUI) in a R/shiny App msqrob2gui (e.g. Proteomics Bioinformatics course of the EBI and the Proteomics Data Analysis course at the Gulbenkian institute) or with R/markdowns scripts (e.g. Bioinformatics Summer School at UCLouvain or the Statistical Genomics Course at Ghent University).


1 Francisella tularensis experiment

  • Pathogen: causes tularemia
  • Metabolic adaptation key for intracellular life cycle of pathogenic microorganisms.
  • Upon entry into host cells quick phasomal escape and active multiplication in cytosolic compartment.
  • Franciscella is auxotroph for several amino acids, including arginine.
  • Inactivation of arginine transporter delayed bacterial phagosomal escape and intracellular multiplication.
  • Experiment to assess difference in proteome using 3 WT vs 3 ArgP KO mutants

1.1 Import the data in R

Click to see code

  1. Load libraries
library(tidyverse)
library(limma)
library(QFeatures)
library(msqrob2)
library(plotly)
library(ggplot2)
library(data.table)
  1. We use a peptides.txt file from MS-data quantified with maxquant that contains MS1 intensities summarized at the peptide level.
peptidesTable <- fread("https://raw.githubusercontent.com/statOmics/PDA/data/quantification/francisella/peptides.txt")
int64 <- which(sapply(peptidesTable,class) == "integer64")
for (j in int64) peptidesTable[[j]] <- as.numeric(peptidesTable[[j]])                      
  1. Maxquant stores the intensity data for the different samples in columnns that start with Intensity. We can retreive the column names with the intensity data with the code below:
quantCols <- grep("Intensity ", names(peptidesTable))
  1. Read the data and store it in QFeatures object
pe <- readQFeatures(
  assayData = peptidesTable,
  fnames = 1,
  quantCols =  quantCols,
  name = "peptideRaw")
## Checking arguments.
## Loading data as a 'SummarizedExperiment' object.
## Formatting sample annotations (colData).
## Formatting data as a 'QFeatures' object.
## Setting assay rownames.
rm(peptidesTable)
gc()
##            used  (Mb) gc trigger  (Mb) limit (Mb) max used  (Mb)
## Ncells  8795459 469.8   15503455 828.0         NA 15503455 828.0
## Vcells 34574492 263.8   81348285 620.7      16384 81348278 620.7
gc()
##            used  (Mb) gc trigger  (Mb) limit (Mb) max used  (Mb)
## Ncells  8795308 469.8   15503455 828.0         NA 15503455 828.0
## Vcells 34567854 263.8   81348285 620.7      16384 81348278 620.7
  1. Update data with information on design
colData(pe)$genotype <- pe[[1]] |> 
  colnames() |> 
  substr(12,13) |>
  as.factor() |> 
  relevel("WT")

pe |> colData()
## DataFrame with 6 rows and 1 column
##                          genotype
##                          <factor>
## Intensity 1WT_20_2h_n3_3       WT
## Intensity 1WT_20_2h_n4_3       WT
## Intensity 1WT_20_2h_n5_3       WT
## Intensity 3D8_20_2h_n3_3       D8
## Intensity 3D8_20_2h_n4_3       D8
## Intensity 3D8_20_2h_n5_3       D8

1.2 Preprocessing

Click to see code to log-transfrom the data

  1. Log transform
  • Peptides with zero intensities are missing peptides and should be represent with a NA value rather than 0.
pe <- zeroIsNA(pe, "peptideRaw") # convert 0 to NA
  • Logtransform data with base 2
pe <- logTransform(pe, base = 2, i = "peptideRaw", name = "peptideLog")
  1. Filtering
  • We remove peptides that could not be mapped to a protein or that map to multiple proteins (the protein identifier contains multiple identifiers separated by a ;).
pe <- filterFeatures(
    pe, ~ Proteins != "" & ## Remove failed protein inference
        !grepl(";", Proteins)) ## Remove protein groups
## 'Proteins' found in 2 out of 2 assay(s).
  • Remove reverse sequences (decoys) and contaminants. Note that this is indicated by the column names Reverse and depending on the version of maxQuant with Potential.contaminants or Contaminants.
pe <- filterFeatures(pe,~Reverse != "+")
## 'Reverse' found in 2 out of 2 assay(s).
pe <- filterFeatures(pe,~ Contaminant != "+")
## 'Contaminant' found in 2 out of 2 assay(s).
  • Drop peptides that were identified in less than three sample. We tolerate the following proportion of NAs: pNA = (n-3)/n. 
nObs <- 3
n <- ncol(pe[["peptideLog"]])
pNA <- (n-nObs)/n
pe <- filterNA(pe, pNA = pNA, i = "peptideLog")
nrow(pe[["peptideLog"]])
## [1] 5740

We keep 5740 peptides upon filtering.

  1. Normalization by median centering
pe <- normalize(pe, 
                i = "peptideLog", 
                name = "peptideNorm", 
                method = "center.median")
  1. Summarization. We use the standard sumarisation in aggregateFeatures, which is a robust summarisation method.
pe <- aggregateFeatures(pe,
    i = "peptideNorm", 
    fcol = "Proteins", 
    na.rm = TRUE,
    name = "protein")
## Your quantitative and row data contain missing values. Please read the
## relevant section(s) in the aggregateFeatures manual page regarding the
## effects of missing values on data aggregation.
## Aggregated: 1/1
  1. Filter proteins that contain many missing values

We want to have at least 4 observed proteins so that most proteins have at least 2 observations in each group.
So we tolerate a proportion of (n-4)/n NAs.

nObs <- 4
n <- ncol(pe[["protein"]])
pNA <- (n-nObs)/n
pe <- filterNA(pe, pNA = pNA, i = "protein")

Plot of preprocessed data

pe[["peptideNorm"]] |> 
  assay() |>
  as.data.frame() |>
  gather(sample, intensity) |> 
  mutate(genotype = colData(pe)[sample,"genotype"]) |>
  ggplot(aes(x = intensity,group = sample,color = genotype)) + 
    geom_density() +
    ggtitle("Peptide-level")
## Warning: Removed 4421 rows containing non-finite outside the scale range
## (`stat_density()`).

pe[["protein"]] |> 
  assay() |>
  as.data.frame() |>
  gather(sample, intensity) |> 
  mutate(genotype = colData(pe)[sample,"genotype"]) |>
  ggplot(aes(x = intensity,group = sample,color = genotype)) + 
    geom_density() +
    ggtitle("Protein-level")
## Warning: Removed 149 rows containing non-finite outside the scale range
## (`stat_density()`).

1.3 Summarized data structure

1.3.1 Design

pe |> 
  colData() |> 
  knitr::kable()
genotype
Intensity 1WT_20_2h_n3_3 WT
Intensity 1WT_20_2h_n4_3 WT
Intensity 1WT_20_2h_n5_3 WT
Intensity 3D8_20_2h_n3_3 D8
Intensity 3D8_20_2h_n4_3 D8
Intensity 3D8_20_2h_n5_3 D8
  • WT vs KO
  • 3 vs 3 repeats

1.3.2 Summarized intensity matrix

pe[["protein"]] |> 
  assay() |> 
  head() |> 
  knitr::kable()
Intensity 1WT_20_2h_n3_3 Intensity 1WT_20_2h_n4_3 Intensity 1WT_20_2h_n5_3 Intensity 3D8_20_2h_n3_3 Intensity 3D8_20_2h_n4_3 Intensity 3D8_20_2h_n5_3
WP_003013731 -0.2277864 -0.0333727 0.1810282 -0.1946484 0.1347441 0.0522102
WP_003013909 -0.7603080 -0.8862012 -0.8565867 -0.4951824 -0.6100506 -0.5490239
WP_003014068 0.5743424 0.7821886 1.0336891 0.5098828 0.8536632 0.5446984
WP_003014122 -0.8382825 -1.1131095 -0.9099676 -1.2104299 -1.2575807 -1.3093650
WP_003014123 -0.2803634 -0.4062057 -0.3159891 -0.4216095 -0.3453093 -0.6329528
WP_003014302 1.5696671 1.7486922 1.5869110 1.6919901 1.8142693 1.5398097
  • 1025 proteins

1.3.3 Hypothesis testing: a single protein

1.3.3.1 T-test

\[ \log_2 \text{FC} = \bar{y}_{p1}-\bar{y}_{p2} \]

\[ T_g=\frac{\log_2 \text{FC}}{\text{se}_{\log_2 \text{FC}}} \]

\[ T_g=\frac{\widehat{\text{signal}}}{\widehat{\text{Noise}}} \]

If we can assume equal variance in both treatment groups:

\[ \text{se}_{\log_2 \text{FC}}=\text{SD}\sqrt{\frac{1}{n_1}+\frac{1}{n_2}} \]

WP_003023392 <- data.frame(
    intensity = assay(pe[["protein"]]["WP_003023392",]) |> 
      c(), 
    genotype = colData(pe)[,1]) 

WP_003023392 |> 
  ggplot(aes(x=genotype,y=intensity)) + 
  geom_point() +
  ggtitle("Protein WP_003023392")

\[ t=\frac{\log_2\widehat{\text{FC}}}{\text{se}_{\log_2\widehat{\text{FC}}}}=\frac{-1.4}{0.0541}=-25.8 \]

  • Is t = -25.8 indicating that there is an effect?

  • How likely is it to observe t = -25.8 when there is no effect of the argP KO on the protein expression?

1.3.3.2 Null hypothesis (\(H_0\)) and alternative hypothesis (\(H_1\))

  • With data we can never prove a hypothesis (falsification principle of Popper)

  • With data we can only reject a hypothesis

  • In general we start from alternative hypothese \(H_1\): we want to show an effect of the KO on a protein

\(H_1\): On average the protein abundance in WT is different from that in KO
  • But, we will assess this by falsifying the opposite:
    \(H_0\): On average the protein abundance in WT is equal to that in KO<-
t.test(intensity ~ genotype, data = WP_003023392, var.equal=TRUE)
## 
##  Two Sample t-test
## 
## data:  intensity by genotype
## t = 25.828, df = 4, p-value = 1.335e-05
## alternative hypothesis: true difference in means between group WT and group D8 is not equal to 0
## 95 percent confidence interval:
##  1.246966 1.547353
## sample estimates:
## mean in group WT mean in group D8 
##       -0.2641239       -1.6612832
  • How likely is it to observe an equal or more extreme effect than the one observed in the sample when the null hypothesis is true?

  • When we make assumptions about the distribution of our test statistic we can quantify this probability: p-value. The p-value will only be calculated correctly if the underlying assumptions hold!

  • When we repeat the experiment, the probability to observe a fold change for this gene that is more extreme than a 2.63 fold (\(\log_2 FC=-1.4\)) down or up regulation by random change (if \(H_0\) is true) is 13 out of 1 000 000.

  • If the p-value is below a significance threshold \(\alpha\) we reject the null hypothesis. We control the probability on a false positive result at the \(\alpha\)-level (type I error)

  • Note, that the p-values are uniform under the null hypothesis, i.e. when \(H_0\) is true all p-values are equally likely.

1.4 Multiple hypothesis testing

  • Consider testing DA for all \(m=1066\) proteins simultaneously

  • What if we assess each individual test at level \(\alpha\)? \(\rightarrow\) Probability to have a false positive (FP) among all m simultatenous test \(>>> \alpha= 0.05\)

  • Indeed for each non DA protein we have a probability of 5% to return a FP.

  • In a typical experiment the majority of the proteins are non DA.

  • So an upperbound of the expected FP is \(m \times \alpha\) or \(1066 \times 0.05=53\).

\(\rightarrow\) Hence, we are bound to call many false positive proteins each time we run the experiment.

1.4.1 Multiple testing

1.4.1.1 Family-wise error rate

The family-wise error rate (FWER) addresses the multiple testing issue by no longer controlling the individual type I error for each protein, instead it controls:

\[ \text{FWER} = \text{P}\left[FP \geq 1 \right]. \]

The Bonferroni method is widely used to control the type I error:

  • assess each test at \[\alpha_\text{adj}=\frac{\alpha}{m}\]
  • or use adjusted p-values and compare them to \(\alpha\): \[p_\text{adj}=\text{min}\left(p \times m,1\right)\]

Problem, the method is very conservative!

1.4.1.2 False discovery rate

  • FDR: Expected proportion of false positives on the total number of positives you return.
  • An FDR of 1% means that on average we expect 1% false positive proteins in the list of proteins that are called significant.
  • Defined by Benjamini and Hochberg in their seminal paper Benjamini, Y. and Hochberg, Y. (1995). “Controlling the false discovery rate: a practical and powerful approach to multiple testing”. Journal of the Royal Statistical Society Series B, 57 (1): 289–300.

The False Discovery Proportion (FDP) is the fraction of false positives that are returned, i.e. 

\[ FDP = \frac{FP}{R} \]

  • However, this quantity cannot be observed because in practice we only know the number of proteins for which we rejected \(H_0\), \(R\).
  • But, we do not know the number of false positives, \(FP\).

Therefore, Benjamini and Hochberg, 1995, defined The False Discovery Rate (FDR) as \[ \text{FDR} = \text{E}\left[\frac{FP}{R}\right] =\text{E}\left[\text{FDP}\right] \] the expected FDP.

  • Controlling the FDR allows for more discoveries (i.e. longer lists with significant results), while the fraction of false discoveries among the significant results in well controlled on average. As a consequence, more of the true positive hypotheses will be detected.

1.4.1.3 Intuition of BH-FDR procedure

Consider \(m = 1000\) tests

  • Suppose that a researcher rejects all null hypotheses for which \(p < 0.01\).

  • If we use \(p < 0.01\), we expect \(0.01 \times m_0\) tests to return false positives.

  • A conservative estimate of the number of false positives that we can expect can be obtained by considering that the null hypotheses are true for all features, \(m_0 = m = 1000\).

  • We then would expect \(0.01 \times 1000 = 10\) false positives (\(FP=10\)).

  • Suppose that the researcher found 200 genes with \(p<0.01\) (\(R=200\)).

  • The proportion of false positive results (FDP = false positive proportion) among the list of \(R=200\) genes can then be estimated as \[ \widehat{\text{FDP}}=\frac{FP}{R}=\frac{10}{200}=\frac{0.01 \times 1000}{200} = 0.05. \]

1.4.1.4 Benjamini and Hochberg (1995) procedure for controlling the FDR at \(\alpha\)

  1. Let \(p_{(1)}\leq \ldots \leq p_{(m)}\) denote the ordered \(p\)-values.

  2. Find the largest integer \(k\) so that \[ \frac{p_{(k)} \times m}{k} \leq \alpha \] \[\text{or}\] \[ p_{(k)} \leq k \times \alpha/m \]

  3. If such a \(k\) exists, reject the \(k\) null hypotheses associated with \(p_{(1)}, \ldots, p_{(k)}\). Otherwise none of the null hypotheses is rejected.

The adjusted \(p\)-value (also known as the \(q\)-value in FDR literature): \[ q_{(i)}=\tilde{p}_{(i)} = \min\left[\min_{j=i,\ldots, m}\left(m p_{(j)}/j\right), 1 \right]. \] In the hypothetical example above: \(k=200\), \(p_{(k)}=0.01\), \(m=1000\) and \(\alpha=0.05\).

1.4.1.5 Francisella Example

Click to see code

ttestMx <- function(y,group) {
    test <- try(t.test(y[group],y[!group],var.equal=TRUE),silent=TRUE)
    if(is(test,"try-error")) {
      return(c(log2FC=NA,se=NA,tstat=NA,p=NA))
      } else {
      return(c(log2FC= (test$estimate%*%c(1,-1)),se=test$stderr,tstat=test$statistic,pval=test$p.value))
      }
 }
 
 res <- apply(
    assay(pe[["protein"]]), 
    1, 
    ttestMx,
    group = colData(pe)$genotype=="D8") |> 
  t() 
 
 colnames(res) <- c("logFC","se","tstat","pval")
 
 res <- res |> 
   as.data.frame() |> 
   na.exclude() |> 
   arrange(pval)
 
 res$adjPval <- p.adjust(res$pval, "fdr")
 
 alpha <- 0.05

 res$adjAlphaForm <- paste0(1:nrow(res)," x ",alpha,"/",nrow(res))

 res$adjAlpha <- alpha * (1:nrow(res))/nrow(res) 

 res$"pval < adjAlpha" <- res$pval < res$adjAlpha 

 res$"adjPval < alpha" <- res$adjPval < alpha 

FWER: Bonferroni method:\(\alpha_\text{adj} = \alpha/m = 0.05 / 1025= 5\times 10^{-5}\)

logFC pval adjPval adjAlphaForm adjAlpha pval < adjAlpha adjPval < alpha
WP_003023392 -1.3971592 0.0000134 0.0136840 1 x 0.05/1025 0.0000488 TRUE TRUE
WP_011733588 -0.4018518 0.0000418 0.0214166 2 x 0.05/1025 0.0000976 TRUE TRUE
WP_003038430 -0.4024957 0.0001814 0.0408656 3 x 0.05/1025 0.0001463 FALSE TRUE
WP_003026016 -1.0493276 0.0001834 0.0408656 4 x 0.05/1025 0.0001951 TRUE TRUE
WP_003014552 -0.9536835 0.0001993 0.0408656 5 x 0.05/1025 0.0002439 TRUE TRUE
WP_011733645 0.3838435 0.0002817 0.0481190 6 x 0.05/1025 0.0002927 TRUE TRUE
WP_003040849 -1.2473865 0.0004301 0.0629754 7 x 0.05/1025 0.0003415 FALSE FALSE
WP_003038940 -0.2386737 0.0005609 0.0718619 8 x 0.05/1025 0.0003902 FALSE FALSE
WP_003040562 0.0039480 0.9976429 0.9985797 1065 x 0.05/1066 0.0499531 FALSE FALSE
WP_003041130 0.0002941 0.9992812 0.9992812 1066 x 0.05/1066 0.05 FALSE FALSE

1.4.1.6 Results

Click to see code

volcanoT <- res |> 
  ggplot(aes(x = logFC, y = -log10(pval), color = adjPval < 0.05)) +
    geom_point(cex = 2.5) +
    scale_color_manual(values = alpha(c("black", "red"), 0.5)) +
    theme_minimal() 

volcanoT

1.5 Moderated Statistics

Problems with ordinary t-test

Click to see code

problemPlots <- list() 
problemPlots[[1]] <- res |> 
  ggplot(aes(x = logFC, y = se, color = adjPval < 0.05)) +
    geom_point(cex = 2.5) +
    scale_color_manual(values = alpha(c("black", "red"), 0.5)) +
    theme_minimal() 

i <- 1
problemPlots[[i+1]] <- colData(pe) |> 
    as.data.frame() |> 
    mutate(intensity = pe[["protein"]][rownames(res)[i],] |> 
             assay() |> 
             c()
           ) |> 
    ggplot(aes(x=genotype,y=intensity)) +
    geom_point() + 
    ylim(-3,0) +
    ggtitle(rownames(res)[i])


i <- 2
problemPlots[[i+1]] <- colData(pe) |> 
    as.data.frame() |> 
    mutate(intensity = pe[["protein"]][rownames(res)[i],] |> 
             assay() |> 
             c()
           ) |> 
    ggplot(aes(x=genotype,y=intensity)) +
    geom_point() + 
    ylim(0,3) +
    ggtitle(rownames(res)[i])

problemPlots
## [[1]]

## 
## [[2]]

## 
## [[3]]

A general class of moderated test statistics is given by \[ T_g^{mod} = \frac{\bar{Y}_{g1} - \bar{Y}_{g2}}{C \quad \tilde{S}_g} , \] where \(\tilde{S}_g\) is a moderated standard deviation estimate.

  • \(C\) is a constant depending on the design e.g. \(\sqrt{1/{n_1}+1/n_2}\) for a t-test and of another form for linear models.
  • \(\tilde{S}_g=S_g+S_0\): add small positive constant to denominator of t-statistic.
  • This can be adopted in Perseus.
Click to see code

simI<-sapply(res$se/sqrt(1/3+1/3),function(n,mean,sd) rnorm(n,mean,sd),n=6,mean=0) |> 
  t()

resSim <- apply(
    simI, 
    1, 
    ttestMx,
    group = colData(pe)$genotype=="D8") |> 
  t() 
 colnames(resSim) <- c("logFC","se","tstat","pval")
 resSim <- as.data.frame(resSim)
 tstatSimPlot <- resSim |> 
   ggplot(aes(x=tstat)) +
     geom_histogram(aes(y=..density.., fill=..count..),bins=30) +
     stat_function(fun=dt,
    color="red",
    args=list(df=4)) + 
   ylim(0,.6) +
   ggtitle("t-statistic")

 
 resSim$C <- sqrt(1/3+1/3) 
 resSim$sd <- resSim$se/resSim$C 
 tstatSimPerseus <- resSim |> 
   ggplot(aes(x=logFC/((sd+.1)*C))) +
     geom_histogram(aes(y=..density.., fill=..count..),bins=30) +
     stat_function(fun=dt,
                   color="red",
                  args=list(df=4)) + 
     ylim(0,.6) +
    ggtitle("Perseus")

gridExtra::grid.arrange(tstatSimPlot,tstatSimPerseus,nrow=1)
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).

  • The choice of \(S_0\) in Perseus is ad hoc and the t-statistic is no-longer t-distributed.
  • Permutation test, but is difficult for more complex designs.
  • Allows for Data Dredging because user can choose \(S_0\)

1.5.1 Empirical Bayes

Figure courtesy to Rafael Irizarry

\[ T_g^{mod} = \frac{\bar{Y}_{g1} - \bar{Y}_{g2}}{C \quad \tilde{S}_g} , \]

  • empirical Bayes theory provides formal framework for borrowing strength across proteins,
  • Implemented in popular bioconductor package limma and msqrob2

\[ \tilde{S}_g=\sqrt{\frac{d_gS_g^2+d_0S_0^2}{d_g+d_0}}, \]

  • \(S_0^2\): common variance (over all proteins)
  • Moderated t-statistic is t-distributed with \(d_0+d_g\) degrees of freedom.
  • Note that the degrees of freedom increase by borrowing strength across proteins!
Click to see the code

  1. We model the protein level expression values using the msqrob function. By default msqrob2 estimates the model parameters using robust regression.

We will model the data with a different group mean for every genotype. The group is incoded in the variable genotype of the colData. We can specify this model by using a formula with the factor genotype as its predictor: formula = ~genotype.

Note, that a formula always starts with a symbol ‘~’.

pe <- msqrob(object = pe, i = "protein", formula = ~genotype)
  1. Inference

We first explore the design of the model that we specified using the the package ExploreModelMatrix

library(ExploreModelMatrix)
VisualizeDesign(colData(pe),~genotype)$plotlist
## [[1]]

We have two model parameters, the (Intercept) and genotypeD8. This results in a model with two group means:

  1. For the wild type (WT) the expected value (mean) of the log2 transformed intensity y for a protein will be modelled using

\[\text{E}[Y\vert \text{genotype}=\text{WT}] = \text{(Intercept)}\]

  1. For the knockout genotype D8 the expected value (mean) of the log2 transformed intensity y for a protein will be modelled using

\[\text{E}[Y\vert \text{genotype}=\text{D8}] = \text{(Intercept)} + \text{genotypeD8}\]

The average log2FC between D8 and WT is thus \[\log_2\text{FC}_{D8-WT}= \text{E}[Y\vert \text{genotype}=\text{D8}] - \text{E}[Y\vert \text{genotype}=\text{WT}] = \text{genotypeD8} \]

Hence, assessing the null hypothesis that there is no differential abundance between D8 and WT can be reformulated as

\[H_0: \text{genotypeD8}=0\] We can implement a hypothesis test for each protein in msqrob2 using the code below:

L <- makeContrast("genotypeD8 = 0", parameterNames = c("genotypeD8"))
pe <- hypothesisTest(object = pe, i = "protein", contrast = L)

We can show the list with all significant DE proteins at the 5% FDR using

rowData(pe[["protein"]])$genotypeD8 |> 
  arrange(pval) |>
  filter(adjPval<0.05)
##                   logFC         se       df          t         pval     adjPval
## WP_003023392 -1.3971592 0.09349966 6.397042 -14.942933 3.254588e-06 0.003244824
## WP_003040849 -1.2473865 0.12404946 6.397042 -10.055557 3.736229e-05 0.015936958
## WP_003014552 -0.9536835 0.10126476 6.397042  -9.417724 5.550408e-05 0.015936958
## WP_003026016 -1.0283106 0.10528137 6.034774  -9.767261 6.393965e-05 0.015936958
## WP_003033719 -1.1740374 0.13543520 6.186857  -8.668628 1.098307e-04 0.021900233

We can also visualise the results using a volcanoplot

volcano <- ggplot(
    rowData(pe[["protein"]])$genotypeD8,
    aes(x = logFC, y = -log10(pval), color = adjPval < 0.05)
) +
    geom_point(cex = 2.5) +
    scale_color_manual(values = alpha(c("black", "red"), 0.5)) +
    theme_minimal() +
    ggtitle("msqrob2")

gridExtra::grid.arrange(
  volcanoT +    
    xlim(-3,3) +
  ggtitle("ordinary t-test"),
  volcano +     
    xlim(-3,3)
,nrow=2)
## Warning: Removed 28 rows containing missing values or values outside the scale range
## (`geom_point()`).

  • The volcano plot opens up when using the EB variance estimator

  • Borrowing strength to estimate the variance using empirical Bayes solves the issue of returning proteins with a low fold change as significant due to a low variance.

1.5.2 Shrinkage of the variance and moderated t-statistics

qplot(
  sapply(rowData(pe[["protein"]])$msqrobModels,getSigma),
  sapply(rowData(pe[["protein"]])$msqrobModels,getSigmaPosterior)) +
  xlab("SD") +
  ylab("moderated SD") +
  geom_abline(intercept = 0,slope = 1) +
  geom_hline(yintercept = ) 
## Warning: Removed 28 rows containing missing values or values outside the scale range
## (`geom_point()`).

  • Small variances are shrunken towards the common variance resulting in large EB variance estimates
  • Large variances are shrunken towards the common variance resulting in smaller EB variance estimates
  • Pooled degrees of freedom of the EB variance estimator are larger because information is borrowed across proteins to estimate the variance

1.6 Plots

sigNames <- rowData(pe[["protein"]])$genotypeD8 |>
    rownames_to_column("protein") |>
    filter(adjPval < 0.05) |>
    pull(protein)
heatmap(assay(pe[["protein"]])[sigNames, ])

for (protName in sigNames)
    {
        pePlot <- pe[protName, , c("peptideNorm", "protein")]
        pePlotDf <- data.frame(longForm(pePlot))
        pePlotDf$assay <- factor(pePlotDf$assay,
            levels = c("peptideNorm", "protein")
        )
        pePlotDf$genotype <- as.factor(colData(pePlot)[pePlotDf$colname, "genotype"])

        # plotting
        p1 <- ggplot(
            data = pePlotDf,
            aes(x = colname, y = value, group = rowname)
        ) +
            geom_line() +
            geom_point() +
            facet_grid(~assay) +
            theme(axis.text.x = element_text(angle = 70, hjust = 1, vjust = 0.5)) +
            ggtitle(protName)
        print(p1)

        # plotting 2
        p2 <- ggplot(pePlotDf, aes(x = colname, y = value, fill = genotype)) +
            geom_boxplot(outlier.shape = NA) +
            geom_point(
                position = position_jitter(width = .1),
                aes(shape = rowname)
            ) +
            scale_shape_manual(values = 1:nrow(pePlotDf)) +
            labs(title = protName, x = "sample", y = "peptide intensity (log2)") +
            theme(axis.text.x = element_text(angle = 70, hjust = 1, vjust = 0.5)) +
            facet_grid(~assay)
        print(p2)
}

2 Experimental Design

2.1 Sample size

\[ \log_2 \text{FC} = \bar{y}_{p1}-\bar{y}_{p2} \]

\[ T_g=\frac{\log_2 \text{FC}}{\text{se}_{\log_2 \text{FC}}} \]

\[ T_g=\frac{\widehat{\text{signal}}}{\widehat{\text{Noise}}} \]

If we can assume equal variance in both treatment groups:

\[ \text{se}_{\log_2 \text{FC}}=\text{SD}\sqrt{\frac{1}{n_1}+\frac{1}{n_2}} \]

\(\rightarrow\) if number of bio-repeats increases we have a higher power!

  • cfr. Study of tamoxifen treated Estrogen Recepter (ER) positive breast cancer patients

2.2 Randomized complete block designs

\[\sigma^2= \sigma^2_{bio}+\sigma^2_\text{lab} +\sigma^2_\text{extraction} + \sigma^2_\text{run} + \ldots\]

  • Biological: fluctuations in protein level between mice, fluctations in protein level between cells, …
  • Technical: cage effect, lab effect, week effect, plasma extraction, MS-run, …

2.2.1 Nature methods: Points of significance - Blocking

https://www.nature.com/articles/nmeth.3005.pdf

2.2.2 Mouse example

Duguet et al. (2017) MCP 16(8):1416-1432. doi: 10.1074/mcp.m116.062745

  • All treatments of interest are present within block!
  • We can estimate the effect of the treatment within block!

To illustrate the power of blocking we have subsetted the data of Duguet et al. in a

  • completely randomized design with

    • four mice for which we only have measurements on the ordinary T-cells
    • four mice for which we only have measurements on the regulatory T-cells
  • randomized complete block design with four mice for which we both have

    • measurements on ordinary T-cells as well as
    • measurements on regulatory T-cells

2.2.3 Data

Click to see code

peptidesTable <- fread("https://raw.githubusercontent.com/statOmics/PDA21/data/quantification/mouseTcell/peptidesRCB.txt")
int64 <- which(sapply(peptidesTable,class) == "integer64")
for (j in int64) peptidesTable[[j]] <- as.numeric(peptidesTable[[j]]) 

peptidesTable2 <- fread("https://raw.githubusercontent.com/statOmics/PDA21/data/quantification/mouseTcell/peptidesCRD.txt")
int64 <- which(sapply(peptidesTable2,class) == "integer64")
for (j in int64) peptidesTable2[[j]] <- as.numeric(peptidesTable2[[j]]) 

peptidesTable3 <- fread("https://raw.githubusercontent.com/statOmics/PDA21/data/quantification/mouseTcell/peptides.txt")
int64 <- which(sapply(peptidesTable3,class) == "integer64")
for (j in int64) peptidesTable3[[j]] <- as.numeric(peptidesTable3[[j]]) 

quantCols <- grep("Intensity ", names(peptidesTable))
pe <- readQFeatures(
  assayData = peptidesTable,
  fnames = 1,
  quantCols =  quantCols,
  name = "peptideRaw")
rm(peptidesTable)
gc()
##            used  (Mb) gc trigger  (Mb) limit (Mb) max used  (Mb)
## Ncells  8800206 470.0   15503455 828.0         NA 15503455 828.0
## Vcells 45274038 345.5   81348285 620.7      16384 81348278 620.7
gc()
##            used  (Mb) gc trigger  (Mb) limit (Mb) max used  (Mb)
## Ncells  8800197 470.0   15503455 828.0         NA 15503455 828.0
## Vcells 45271902 345.4   81348285 620.7      16384 81348278 620.7
quantCols2 <- grep("Intensity ", names(peptidesTable2))
pe2 <- readQFeatures(
  assayData = peptidesTable2,
  fnames = 1,
  quantCols =  quantCols2,
  name = "peptideRaw")
rm(peptidesTable2)
gc()
##            used  (Mb) gc trigger  (Mb) limit (Mb) max used  (Mb)
## Ncells  8712079 465.3   15503455 828.0         NA 15503455 828.0
## Vcells 38667083 295.1   81348285 620.7      16384 81348278 620.7
gc()
##            used  (Mb) gc trigger  (Mb) limit (Mb) max used  (Mb)
## Ncells  8712078 465.3   15503455 828.0         NA 15503455 828.0
## Vcells 38662800 295.0   81348285 620.7      16384 81348278 620.7
quantCols3 <- grep("Intensity ", names(peptidesTable3))
pe3 <- readQFeatures(
  assayData = peptidesTable3,
  fnames = 1,
  quantCols =  quantCols3,
  name = "peptideRaw")
rm(peptidesTable3)
gc()
##            used  (Mb) gc trigger  (Mb) limit (Mb) max used  (Mb)
## Ncells  8711667 465.3   15503455 828.0         NA 15503455 828.0
## Vcells 29978703 228.8   81348285 620.7      16384 81348278 620.7
gc()
##            used  (Mb) gc trigger  (Mb) limit (Mb) max used  (Mb)
## Ncells  8711592 465.3   15503455 828.0         NA 15503455 828.0
## Vcells 29974145 228.7   81348285 620.7      16384 81348278 620.7
### Design
colData(pe)$celltype <- substr(
  colnames(pe[["peptideRaw"]]),
  11,
  14) |>
  unlist() |>  
  as.factor()

colData(pe)$mouse <- pe[[1]] |>
  colnames() |>
  strsplit(split="[ ]")  |>
  sapply(function(x) x[3]) |>
  as.factor()

colData(pe2)$celltype <- substr(
  colnames(pe2[["peptideRaw"]]),
  11,
  14) |>
  unlist() |>  
  as.factor()

colData(pe2)$mouse <- pe2[[1]] |>
  colnames() |>
  strsplit(split="[ ]")  |>
  sapply(function(x) x[3]) |>
  as.factor()

colData(pe3)$celltype <- substr(
  colnames(pe3[["peptideRaw"]]),
  11,
  14) |>
  unlist() |>  
  as.factor()

colData(pe3)$mouse <- pe3[[1]] |>
  colnames() |>
  strsplit(split="[ ]")  |>
  sapply(function(x) x[3]) |>
  as.factor()

2.2.4 Preprocessing

2.2.4.1 Log-transform

Click to see code to log-transfrom the data

  • Peptides with zero intensities are missing peptides and should be represent with a NA value rather than 0.
pe <- zeroIsNA(pe, "peptideRaw") # convert 0 to NA

pe2 <- zeroIsNA(pe2, "peptideRaw") # convert 0 to NA

pe3 <- zeroIsNA(pe3, "peptideRaw") # convert 0 to NA
  • Logtransform data with base 2
pe <- logTransform(pe, base = 2, i = "peptideRaw", name = "peptideLog")

pe2 <- logTransform(pe2, base = 2, i = "peptideRaw", name = "peptideLog")

pe3 <- logTransform(pe3, base = 2, i = "peptideRaw", name = "peptideLog")

2.2.4.2 Filtering

Click to see details on filtering

  1. Remove peptides that map to multiple proteins

We remove PSMs that could not be mapped to a protein or that map to multiple proteins (the protein identifier contains multiple identifiers separated by a ;).

pe <- filterFeatures(
    pe, ~ Proteins != "" & ## Remove failed protein inference
        !grepl(";", Proteins)) ## Remove protein groups
## 'Proteins' found in 2 out of 2 assay(s).
pe2 <- filterFeatures(
    pe2, ~ Proteins != "" & ## Remove failed protein inference
        !grepl(";", Proteins)) ## Remove protein groups
## 'Proteins' found in 2 out of 2 assay(s).
pe3 <- filterFeatures(
    pe3, ~ Proteins != "" & ## Remove failed protein inference
        !grepl(";", Proteins)) ## Remove protein groups
## 'Proteins' found in 2 out of 2 assay(s).
  1. Remove reverse sequences (decoys) and contaminants

We now remove the contaminants, peptides that map to decoy sequences, and proteins which were only identified by peptides with modifications.

pe <- filterFeatures(pe,~Reverse != "+")
## 'Reverse' found in 2 out of 2 assay(s).
pe <- filterFeatures(pe,~ Potential.contaminant != "+")
## 'Potential.contaminant' found in 2 out of 2 assay(s).
pe2 <- filterFeatures(pe2,~Reverse != "+")
## 'Reverse' found in 2 out of 2 assay(s).
pe2 <- filterFeatures(pe2,~ Potential.contaminant != "+")
## 'Potential.contaminant' found in 2 out of 2 assay(s).
pe3 <- filterFeatures(pe3,~Reverse != "+")
## 'Reverse' found in 2 out of 2 assay(s).
pe3 <- filterFeatures(pe3,~ Potential.contaminant != "+")
## 'Potential.contaminant' found in 2 out of 2 assay(s).
  1. Drop peptides that were identified in less than three sample

We keep peptides that were observed at least three times.

nObs <- 3
n <- ncol(pe[["peptideLog"]])
pNA <- (n-nObs)/n
pe <- filterNA(pe, pNA = pNA, i = "peptideLog")
nrow(pe[["peptideLog"]])
## [1] 38782
n <- ncol(pe2[["peptideLog"]])
pNA <- (n-nObs)/n
pe2 <- filterNA(pe2, pNA = pNA, i = "peptideLog")
nrow(pe2[["peptideLog"]])
## [1] 37898
n <- ncol(pe3[["peptideLog"]])
pNA <- (n-nObs)/n
pe3 <- filterNA(pe3, pNA = pNA, i = "peptideLog")
nrow(pe3[["peptideLog"]])
## [1] 43984

2.2.4.3 Normalization

Click to see code to normalize the data

pe <- normalize(pe, 
                i = "peptideLog", 
                name = "peptideNorm", 
                method = "center.median")

pe2 <- normalize(pe2, 
                i = "peptideLog", 
                name = "peptideNorm", 
                method = "center.median")


pe3 <- normalize(pe3, 
                i = "peptideLog", 
                name = "peptideNorm", 
                method = "center.median")

2.2.4.4 Summarization

Click to see code to summarize the data

pe <- aggregateFeatures(pe,
 i = "peptideNorm",
 fcol = "Proteins",
 na.rm = TRUE,
 name = "protein")
## Your quantitative and row data contain missing values. Please read the
## relevant section(s) in the aggregateFeatures manual page regarding the
## effects of missing values on data aggregation.
## Aggregated: 1/1
pe2 <- aggregateFeatures(pe2,
 i = "peptideNorm",
 fcol = "Proteins",
 na.rm = TRUE,
 name = "protein")
## Your quantitative and row data contain missing values. Please read the
## relevant section(s) in the aggregateFeatures manual page regarding the
## effects of missing values on data aggregation.
## Aggregated: 1/1
pe3 <- aggregateFeatures(pe3,
 i = "peptideNorm",
 fcol = "Proteins",
 na.rm = TRUE,
 name = "protein")
## Your quantitative and row data contain missing values. Please read the
## relevant section(s) in the aggregateFeatures manual page regarding the
## effects of missing values on data aggregation.
## Aggregated: 1/1

2.2.4.5 Filtering proteins with too many missing values

We want to have at least two observed protein intensities for each group so we set the minimum number of observed values at 4. We still have to check for the observed proteins if that is the case.

For block design more clever filtering can be used. E.g. we could imply that we have both cell types in at least two animals…

nObs <- 4
n <- ncol(pe[["protein"]])
pNA <- (n-nObs)/n
pe<- filterNA(pe, pNA = pNA, i = "protein")

n <- ncol(pe2[["protein"]])
pNA <- (n-nObs)/n
pe2 <- filterNA(pe2, pNA = pNA, i = "protein")

n <- ncol(pe3[["protein"]])
pNA <- (n-nObs)/n
pe3 <- filterNA(pe3, pNA = pNA, i = "protein")

2.2.5 Data Exploration: what is impact of blocking?

Click to see code

levels(colData(pe3)$mouse) <- paste0("m",1:7)
mdsObj3 <- plotMDS(assay(pe3[["protein"]]), plot = FALSE)
mdsOrig <- colData(pe3) |>
  as.data.frame() |>
  mutate(mds1 = mdsObj3$x,
         mds2 = mdsObj3$y,
         lab = paste(mouse,celltype,sep="_")) |>
  ggplot(aes(x = mds1, y = mds2, label = lab, color = celltype, group = mouse)) +
  geom_text(show.legend = FALSE) +
  geom_point(shape = 21) +
  geom_line(color = "black", linetype = "dashed") +
  xlab(
    paste0(
      mdsObj3$axislabel,
      " ",
      1, 
      " (",
      paste0(
        round(mdsObj3$var.explained[1] *100,0),
        "%"
        ),
      ")"
      )
    ) +
  ylab(
    paste0(
      mdsObj3$axislabel,
      " ",
      2, 
      " (",
      paste0(
        round(mdsObj3$var.explained[2] *100,0),
        "%"
        ),
      ")"
      )
    ) +
  ggtitle("Original (RCB)")

levels(colData(pe)$mouse) <- paste0("m",1:4)
mdsObj <- plotMDS(assay(pe[["protein"]]), plot = FALSE)
mdsRCB <- colData(pe) |>
  as.data.frame() |>
  mutate(mds1 = mdsObj$x,
         mds2 = mdsObj$y,
         lab = paste(mouse,celltype,sep="_")) |>
  ggplot(aes(x = mds1, y = mds2, label = lab, color = celltype, group = mouse)) +
  geom_text(show.legend = FALSE) +
  geom_point(shape = 21) +
  geom_line(color = "black", linetype = "dashed") +
  xlab(
    paste0(
      mdsObj$axislabel,
      " ",
      1, 
      " (",
      paste0(
        round(mdsObj$var.explained[1] *100,0),
        "%"
        ),
      ")"
      )
    ) +
  ylab(
    paste0(
      mdsObj$axislabel,
      " ",
      2, 
      " (",
      paste0(
        round(mdsObj$var.explained[2] *100,0),
        "%"
        ),
      ")"
      )
    ) +
  ggtitle("Randomized Complete Block (RCB)")


levels(colData(pe2)$mouse) <- paste0("m",1:8)
mdsObj2 <- plotMDS(assay(pe2[["protein"]]), plot = FALSE)
mdsCRD <- colData(pe2) |>
  as.data.frame() |>
  mutate(mds1 = mdsObj2$x,
         mds2 = mdsObj2$y,
         lab = paste(mouse,celltype,sep="_")) |>
  ggplot(aes(x = mds1, y = mds2, label = lab, color = celltype, group = mouse)) +
  geom_text(show.legend = FALSE) +
  geom_point(shape = 21) +
  xlab(
    paste0(
      mdsObj$axislabel,
      " ",
      1, 
      " (",
      paste0(
        round(mdsObj2$var.explained[1] *100,0),
        "%"
        ),
      ")"
      )
    ) +
  ylab(
    paste0(
      mdsObj$axislabel,
      " ",
      2, 
      " (",
      paste0(
        round(mdsObj2$var.explained[2] *100,0),
        "%"
        ),
      ")"
      )
    ) +
  ggtitle("Completely Randomized Design (CRD)")

mdsOrig

mdsRCB

mdsCRD

  • We observe that the leading fold change is according to mouse

  • In the second dimension we see a separation according to cell-type

  • With the Randomized Complete Block design (RCB) we can remove the mouse effect from the analysis!

  • We can isolate the between block variability from the analysis using linear model:

    • Formula in R \[ y \sim \text{celltype} + \text{mouse} \]

    • Formula

\[ y_i = \beta_0 + \beta_\text{Treg} x_{i,\text{Treg}} + \beta_{m2}x_{i,m2} + \beta_{m3}x_{i,m3} + \beta_{m4}x_{i,m4} + \epsilon_i \]

with

  • \(x_{i,Treg}=\begin{cases} 1& \text{Treg}\\ 0& \text{Tcon} \end{cases}\)

  • \(x_{i,m2}=\begin{cases} 1& \text{m2}\\ 0& \text{otherwise} \end{cases}\)

  • \(x_{i,m3}=\begin{cases} 1& \text{m3}\\ 0& \text{otherwise} \end{cases}\)

  • \(x_{i,m4}=\begin{cases} 1& \text{m4}\\ 0& \text{otherwise} \end{cases}\)

  • Possible in msqrob2 and MSstats but not possible with Perseus!

2.2.6 Modeling and inference

2.2.6.1 RCB analysis

pe <- msqrob(
  object = pe,
  i = "protein",
  formula = ~ celltype + mouse)

2.2.6.2 CRD analysis

pe2 <- msqrob(
  object = pe2,
  i = "protein",
  formula = ~ celltype)

2.2.6.3 Estimation, effect size and inference

Effect size in RCB

library(ExploreModelMatrix)
VisualizeDesign(colData(pe),~ celltype + mouse)$plotlist
## [[1]]

Effect size in CRD

VisualizeDesign(colData(pe2),~ celltype)$plotlist
## [[1]]

Click to see code for statistical inference

L <- makeContrast("celltypeTreg = 0", parameterNames = c("celltypeTreg"))
pe <- hypothesisTest(object = pe, i = "protein", contrast = L)
pe2 <- hypothesisTest(object = pe2, i = "protein", contrast = L)

2.2.7 Comparison of results

Click to see code

2.2.8 Comparison of standard deviation

Click to see code

accessions <- rownames(pe[["protein"]])[rownames(pe[["protein"]])%in%rownames(pe2[["protein"]])]
dat <- data.frame(
sigmaRBC = sapply(rowData(pe[["protein"]])$msqrobModels[accessions], getSigmaPosterior),
sigmaCRD <- sapply(rowData(pe2[["protein"]])$msqrobModels[accessions], getSigmaPosterior)
)

plotRBCvsCRD <- ggplot(data = dat, aes(sigmaRBC, sigmaCRD)) +
    geom_point(alpha = 0.1, shape = 20) +
    scale_x_log10() +
    scale_y_log10() +
    geom_abline(intercept=0,slope=1)

  plotRBCvsCRD
## Warning: Removed 351 rows containing missing values or values outside the scale range
## (`geom_point()`).

  • We clearly observe that the standard deviation of the protein expression in the RCB is smaller for the majority of the proteins than that obtained with the CRD

  • Can you think of a reason why it would not be useful to block on a particular factor?

2.3 Pseudo-replication

  • The Francisella tularensis that we used before was a subset of the data of Ramond et al. 
  • The authors have run the sample for each bio-rep in technical triplicate on the mass-spectrometer.

2.3.1 Data

Click to see code to import data

  1. We use a peptides.txt file from MS-data quantified with maxquant that contains MS1 intensities summarized at the peptide level.
peptidesTable <- fread("https://raw.githubusercontent.com/statOmics/MSqRobSumPaper/refs/heads/master/Francisella/data/maxquant/peptides.txt")
int64 <- which(sapply(peptidesTable,class) == "integer64")
for (j in int64) peptidesTable[[j]] <- as.numeric(peptidesTable[[j]])                      
  1. Maxquant stores the intensity data for the different samples in columnns that start with Intensity. We can retreive the column names with the intensity data with the code below:
quantCols <- grep("Intensity ", names(peptidesTable))
  1. Read the data and store it in QFeatures object
pe <- readQFeatures(
  assayData = peptidesTable,
  fnames = 1,
  quantCols =  quantCols,
  name = "peptideRaw")
## Checking arguments.
## Loading data as a 'SummarizedExperiment' object.
## Formatting sample annotations (colData).
## Formatting data as a 'QFeatures' object.
## Setting assay rownames.
rm(peptidesTable)
gc()
##            used  (Mb) gc trigger  (Mb) limit (Mb) max used  (Mb)
## Ncells  8796340 469.8   15503455 828.0         NA 15503455 828.0
## Vcells 34773472 265.4   81348285 620.7      16384 81348285 620.7
gc()
##            used  (Mb) gc trigger  (Mb) limit (Mb) max used  (Mb)
## Ncells  8796246 469.8   15503455 828.0         NA 15503455 828.0
## Vcells 34766829 265.3   81348285 620.7      16384 81348285 620.7
  1. Update data with information on design
colData(pe)$genotype <- pe[[1]] |> 
  colnames() |> 
  substr(12,13) |>
  as.factor() |> 
  relevel("WT")

colData(pe)$biorep <- pe[[1]] |> 
  colnames() |> 
  substr(22,22)

colData(pe)$biorep <- paste(colData(pe)$genotype,colData(pe)$biorep,sep="_")
  
pe |> colData()
## DataFrame with 18 rows and 2 columns
##                          genotype      biorep
##                          <factor> <character>
## Intensity 1WT_20_2h_n3_1       WT        WT_3
## Intensity 1WT_20_2h_n3_2       WT        WT_3
## Intensity 1WT_20_2h_n3_3       WT        WT_3
## Intensity 1WT_20_2h_n4_1       WT        WT_4
## Intensity 1WT_20_2h_n4_2       WT        WT_4
## ...                           ...         ...
## Intensity 3D8_20_2h_n4_2       D8        D8_4
## Intensity 3D8_20_2h_n4_3       D8        D8_4
## Intensity 3D8_20_2h_n5_1       D8        D8_5
## Intensity 3D8_20_2h_n5_2       D8        D8_5
## Intensity 3D8_20_2h_n5_3       D8        D8_5

2.3.2 Preprocessing

Click to see code to log-transfrom the data

  1. Log transform
  • Peptides with zero intensities are missing peptides and should be represent with a NA value rather than 0.
pe <- zeroIsNA(pe, "peptideRaw") # convert 0 to NA
  • Logtransform data with base 2
pe <- logTransform(pe, base = 2, i = "peptideRaw", name = "peptideLog")
  1. Filtering
  • We remove PSMs that could not be mapped to a protein or that map to multiple proteins (the protein identifier contains multiple identifiers separated by a ;).
pe <- filterFeatures(
    pe, ~ Proteins != "" & ## Remove failed protein inference
        !grepl(";", Proteins)) ## Remove protein groups
## 'Proteins' found in 2 out of 2 assay(s).
  • Remove reverse sequences (decoys) and contaminants. Note that this is indicated by the column names Reverse and depending on the version of maxQuant with Potential.contaminants or Contaminants.
pe <- filterFeatures(pe,~Reverse != "+")
## 'Reverse' found in 2 out of 2 assay(s).
pe <- filterFeatures(pe,~ Contaminant != "+")
## 'Contaminant' found in 2 out of 2 assay(s).
  • Drop peptides that were identified in less than three sample. We tolerate the following proportion of NAs: pNA = (n-3)/n. 
nObs <- 3
n <- ncol(pe[["peptideLog"]])
pNA <- (n-nObs)/n
pe <- filterNA(pe, pNA = pNA, i = "peptideLog")
nrow(pe[["peptideLog"]])
## [1] 7244

We keep 7244 peptides upon filtering.

  1. Normalization by median centering
pe <- normalize(pe, 
                i = "peptideLog", 
                name = "peptideNorm", 
                method = "center.median")
  1. Summarization. We use the standard sumarisation in aggregateFeatures, which is a robust summarisation method.
pe <- aggregateFeatures(pe,
    i = "peptideNorm", 
    fcol = "Proteins", 
    na.rm = TRUE,
    name = "protein")
## Your quantitative and row data contain missing values. Please read the
## relevant section(s) in the aggregateFeatures manual page regarding the
## effects of missing values on data aggregation.
## Aggregated: 1/1

msqrob2gui:::plotPCA(pe, "protein", "biorep")

  • Response?
  • Experimental unit?
  • Observational unit?
  • Factors?
VisualizeDesign(colData(pe),~ genotype + biorep)$plotlist
## [[1]]

\(\rightarrow\) Pseudo-replication, randomisation to bio-repeat and each bio-repeat measured in technical triplicate. \(\rightarrow\) If we would analyse the data using a linear model based on each measured intensity, we would act as if we had sampled 18 bio-repeats. \(\rightarrow\) Effect of interest has to be assessed between bio-repeats. So block analysis is not possible (if we condition on bio-repeat, we condition on the genotype)!

2.3.3 Wrong analysis

pe <- msqrob(object = pe, i = "protein", formula = ~genotype, modelColumnName = "wrong", overwrite = TRUE)
L <- makeContrast("genotypeD8 = 0", parameterNames = c("genotypeD8"))
pe <- hypothesisTest(object = pe, i = "protein", contrast = L, modelColumn = "wrong",   resultsColumnNamePrefix = "wrong_", overwrite = TRUE)

volcanoWrong <- ggplot(
    rowData(pe[["protein"]])$wrong_genotypeD8,
    aes(x = logFC, y = -log10(pval), color = adjPval < 0.05)
) +
    geom_point(cex = 2.5) +
    scale_color_manual(values = alpha(c("black", "red"), 0.5)) +
    theme_minimal() +
    ggtitle(paste0("wrong analysis: ",sum(rowData(pe[["protein"]])$wrong_genotypeD8$adjPval <0.05, na.rm=TRUE)," DA"))

volcanoWrong

Note, that the analysis where we ignore that we have multiple technical repeats for each bio-repeat returns many significant DA proteins because we act as if we have much more independent observations.

2.3.4 Correct analysis

  • Mixed Models can model the correlation structure in the data

  • They can acknowledge that protein expression values from runs from the same biological repeat are more alike than protein expression values from runs of different biological repeats.

  • Mixed models can also be used when inference between and within blocks is needed.

  • For the francisella example the formula in msqrob2 becomes: (formula: ~ genotype + (1|biorep)).

\[ \left\{\begin{array}{rcl} y_{ir} &=& \beta_0 + \beta_{wt}X_{wt,i} + b_i + \epsilon_{ir}\\ b_i & \sim & N(0,\sigma_b^2)\\ \epsilon_{ir} &\sim& N(0,\sigma_\epsilon^2) \end{array}\right.\]

pe <- msqrob(object = pe, i = "protein", formula = ~genotype + (1|biorep), overwrite = TRUE)
## Warning: 'experiments' dropped; see 'drops()'
L <- makeContrast("genotypeD8 = 0", parameterNames = c("genotypeD8"))
pe <- hypothesisTest(object = pe, i = "protein", contrast = L, overwrite = TRUE)

volcanoMixed <- ggplot(
    rowData(pe[["protein"]])$genotypeD8,
    aes(x = logFC, y = -log10(pval), color = adjPval < 0.05)
) +
    geom_point(cex = 2.5) +
    scale_color_manual(values = alpha(c("black", "red"), 0.5)) +
    theme_minimal() +
    ggtitle(paste0("correct analysis: ",sum(rowData(pe[["protein"]])$genotypeD8$adjPval <0.05, na.rm=TRUE)," DA"))
volcanoMixed
## Warning: Removed 46 rows containing missing values or values outside the scale range
## (`geom_point()`).

  • Note, that the statistical inference with mixed models is still a bit too liberal because it:

    • hypothesis tests are only valid asymptotically (large number of biorepeats).
    • in small samples the between biorepeat variance is sometimes estimated to be zero due to estimation uncertainty and for these proteins the model still acts as if all 18 protein expression values are independent.
  • Mixed models are also very useful to analyse data from large labeled experiments (Vandenbulcke and Clement 2025).

  • But, they are beyond the scope of the lecture series.

CONSULT BIOSTATISTICIAN IN CASE OF EXPERIMENTS WITH PSEUDO-REPLICATES, TECHNICAL REPEATS, COMPLEX DESIGNS, …

3 Software & code

  • Our R/Bioconductor package msqrob2 can be used in R markdown scripts or with GUI/shinyApps QFeaturesGUI (+) and msqrob2gui (+: forked from the UCLouvain-CBIO lab).

  • GUIs are intended as a introduction to the key concepts of proteomics data analysis for users who have no experience in R.

  • However, learning how to code data analyses in R markdown scripts is key for open en reproducible science and for reporting your proteomics data analyses and interpretation in a reproducible way.

  • More information on our tools can be found in our papers (L. J. Goeminne, Gevaert, and Clement 2016), (L. J. E. Goeminne et al. 2020), (Sticker et al. 2020) and (Vandenbulcke and Clement 2025). Please refer to our work when using our tools.

References

Goeminne, L. J. E., A. Sticker, L. Martens, K. Gevaert, and L. Clement. 2020. MSqRob Takes the Missing Hurdle: Uniting Intensity- and Count-Based Proteomics.” Anal Chem 92 (9): 6278–87.
Goeminne, L. J., K. Gevaert, and L. Clement. 2016. Peptide-level Robust Ridge Regression Improves Estimation, Sensitivity, and Specificity in Data-dependent Quantitative Label-free Shotgun Proteomics.” Mol Cell Proteomics 15 (2): 657–68.
Sticker, A., L. Goeminne, L. Martens, and L. Clement. 2020. Robust Summarization and Inference in Proteome-wide Label-free Quantification.” Mol Cell Proteomics 19 (7): 1209–19.
Vandenbulcke, C., S. Vanderaa, and L. Clement. 2025. “msqrob2TMT: Robust Linear Mixed Models for Inferring Differential Signals in Tandem Mass Tag-Based Proteomics.” Molecular & Cellular Proteomics 24 (3): e10101–1. https://doi.org/10.1016/j.mcpro.2025.00101-X.
LS0tCnRpdGxlOiAiU3RhdGlzdGljYWwgTWV0aG9kcyBmb3IgUXVhbnRpdGF0aXZlIE1TLWJhc2VkIFByb3Rlb21pY3M6IFBhcnQgSUkuIERpZmZlcmVudGlhbCBBYnVuZGFuY2UgQW5hbHlzaXMiCmF1dGhvcjogIkxpZXZlbiBDbGVtZW50IgpkYXRlOiAiW3N0YXRPbWljc10oaHR0cHM6Ly9zdGF0b21pY3MuZ2l0aHViLmlvKSwgR2hlbnQgVW5pdmVyc2l0eSIKb3V0cHV0OgogICAgaHRtbF9kb2N1bWVudDoKICAgICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgICB0aGVtZTogZmxhdGx5CiAgICAgIHRvYzogdHJ1ZQogICAgICB0b2NfZmxvYXQ6IHRydWUKICAgICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHBkZl9kb2N1bWVudDoKICAgICAgdG9jOiB0cnVlCiAgICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQpsaW5rY29sb3I6IGJsdWUKdXJsY29sb3I6IGJsdWUKY2l0ZWNvbG9yOiBibHVlCgpiaWJsaW9ncmFwaHk6IG1zcXJvYjIuYmliCgotLS0KCjxhIHJlbD0ibGljZW5zZSIgaHJlZj0iaHR0cHM6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMCI+PGltZyBhbHQ9IkNyZWF0aXZlIENvbW1vbnMgTGljZW5zZSIgc3R5bGU9ImJvcmRlci13aWR0aDowIiBzcmM9Imh0dHBzOi8vaS5jcmVhdGl2ZWNvbW1vbnMub3JnL2wvYnktbmMtc2EvNC4wLzg4eDMxLnBuZyIgLz48L2E+CgpUaGlzIGlzIHBhcnQgb2YgdGhlIG9ubGluZSBjb3Vyc2UgW1Byb3Rlb21pY3MgRGF0YSBBbmFseXNpcyAoUERBKV0oaHR0cHM6Ly9zdGF0b21pY3MuZ2l0aHViLmlvL1BEQS8pCgo8aWZyYW1lIHdpZHRoPSI1NjAiIGhlaWdodD0iMzE1IgpzcmM9Imh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL2VtYmVkLzFtaGczQkN1RW04IgpmcmFtZWJvcmRlcj0iMCIKc3R5bGU9ImRpc3BsYXk6IGJsb2NrOyBtYXJnaW46IGF1dG87IgphbGxvdz0iYXV0b3BsYXk7IGVuY3J5cHRlZC1tZWRpYSIgYWxsb3dmdWxsc2NyZWVuPjwvaWZyYW1lPgoKLSBbUGxheWxpc3QgUERBIFByZXByb2Nlc3NpbmddKGh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9MW1oZzNCQ3VFbTgmbGlzdD1QTFpIMWhQOF9MYkpLcW5QU1M0aHhUa24tdFFvbFNDR2pQKQoKIyBPdXRsaW5lIHstfQoKLSBGcmFuY2lzZWxsYSB0dWxhcmVuc2lzIEV4YW1wbGUKLSBIeXBvdGhlc2lzIHRlc3RpbmcKLSBNdWx0aXBsZSB0ZXN0aW5nCi0gTW9kZXJhdGVkIHN0YXRpc3RpY3MKLSBFeHBlcmltZW50YWwgZGVzaWduCgoKTm90ZSwgdGhhdCB0aGUgUi1jb2RlIGlzIGluY2x1ZGVkIGZvciBsZWFybmVycyB3aG8gYXJlIGFpbWluZyB0byBkZXZlbG9wIFIvbWFya2Rvd24gc2NyaXB0cyB0byBhdXRvbWF0ZSB0aGVpciBxdWFudGl0YXRpdmUgcHJvdGVvbWljcyBkYXRhIGFuYWx5c2VzLgpBY2NvcmRpbmcgdG8gdGhlIHRhcmdldCBhdWRpZW5jZSBvZiB0aGUgY291cnNlIHdlIGVpdGhlciB3b3JrIHdpdGggYSBncmFwaGljYWwgdXNlciBpbnRlcmZhY2UgKEdVSSkgaW4gYSBSL3NoaW55IEFwcCBtc3Fyb2IyZ3VpIChlLmcuIFByb3Rlb21pY3MgQmlvaW5mb3JtYXRpY3MgY291cnNlIG9mIHRoZSBFQkkgYW5kIHRoZSBQcm90ZW9taWNzIERhdGEgQW5hbHlzaXMgY291cnNlIGF0IHRoZSBHdWxiZW5raWFuIGluc3RpdHV0ZSkgb3Igd2l0aCBSL21hcmtkb3ducyBzY3JpcHRzIChlLmcuIEJpb2luZm9ybWF0aWNzIFN1bW1lciBTY2hvb2wgYXQgVUNMb3V2YWluIG9yIHRoZSBTdGF0aXN0aWNhbCBHZW5vbWljcyBDb3Vyc2UgYXQgR2hlbnQgVW5pdmVyc2l0eSkuIAoKLS0tCgojIEZyYW5jaXNlbGxhIHR1bGFyZW5zaXMgZXhwZXJpbWVudAoKPGlmcmFtZSB3aWR0aD0iNTYwIiBoZWlnaHQ9IjMxNSIKc3JjPSJodHRwczovL3d3dy55b3V0dWJlLmNvbS9lbWJlZC9UVzVyazF5N2FPYyIKZnJhbWVib3JkZXI9IjAiCnN0eWxlPSJkaXNwbGF5OiBibG9jazsgbWFyZ2luOiBhdXRvOyIKYWxsb3c9ImF1dG9wbGF5OyBlbmNyeXB0ZWQtbWVkaWEiIGFsbG93ZnVsbHNjcmVlbj48L2lmcmFtZT4KCmBgYHtyIGVjaG89RkFMU0Usb3V0LndpZHRoPSI1MCUifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiLi9maWd1cmVzL2ZyYW5jaXNlbGxhLmpwZyIpCmBgYAoKYGBge3IgZWNobz1GQUxTRX0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIi4vZmlndXJlcy90dWxhcmVtaWFfbGVzaW9uLmpwZyIpCmBgYAoKLSBQYXRob2dlbjogY2F1c2VzIHR1bGFyZW1pYQotIE1ldGFib2xpYyBhZGFwdGF0aW9uIGtleSBmb3IgaW50cmFjZWxsdWxhciBsaWZlIGN5Y2xlIG9mIHBhdGhvZ2VuaWMgbWljcm9vcmdhbmlzbXMuIAotIFVwb24gZW50cnkgaW50byBob3N0IGNlbGxzIHF1aWNrIHBoYXNvbWFsIGVzY2FwZSBhbmQgYWN0aXZlIG11bHRpcGxpY2F0aW9uIGluIGN5dG9zb2xpYyBjb21wYXJ0bWVudC4KLSBGcmFuY2lzY2VsbGEgaXMgYXV4b3Ryb3BoIGZvciBzZXZlcmFsIGFtaW5vIGFjaWRzLCBpbmNsdWRpbmcgYXJnaW5pbmUuIAotIEluYWN0aXZhdGlvbiBvZiBhcmdpbmluZSB0cmFuc3BvcnRlciBkZWxheWVkIGJhY3RlcmlhbCBwaGFnb3NvbWFsIGVzY2FwZSBhbmQgaW50cmFjZWxsdWxhciBtdWx0aXBsaWNhdGlvbi4gCi0gRXhwZXJpbWVudCB0byBhc3Nlc3MgZGlmZmVyZW5jZSBpbiBwcm90ZW9tZSB1c2luZyAzIFdUIHZzIDMgQXJnUCBLTyBtdXRhbnRzCgoKIyMgSW1wb3J0IHRoZSBkYXRhIGluIFIgCgo8aWZyYW1lIHdpZHRoPSI1NjAiIGhlaWdodD0iMzE1IgpzcmM9Imh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL2VtYmVkLzJMamQ5NUlFX0FZIgpmcmFtZWJvcmRlcj0iMCIKc3R5bGU9ImRpc3BsYXk6IGJsb2NrOyBtYXJnaW46IGF1dG87IgphbGxvdz0iYXV0b3BsYXk7IGVuY3J5cHRlZC1tZWRpYSIgYWxsb3dmdWxsc2NyZWVuPjwvaWZyYW1lPgoKCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgY29kZSA8L3N1bW1hcnk+PHA+CjEuIExvYWQgbGlicmFyaWVzIAoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGxpbW1hKQpsaWJyYXJ5KFFGZWF0dXJlcykKbGlicmFyeShtc3Fyb2IyKQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGRhdGEudGFibGUpCmBgYAoKMi4gV2UgdXNlIGEgcGVwdGlkZXMudHh0IGZpbGUgZnJvbSBNUy1kYXRhIHF1YW50aWZpZWQgd2l0aCBtYXhxdWFudCB0aGF0IApjb250YWlucyBNUzEgaW50ZW5zaXRpZXMgc3VtbWFyaXplZCBhdCB0aGUgcGVwdGlkZSBsZXZlbC4gCmBgYHtyfQpwZXB0aWRlc1RhYmxlIDwtIGZyZWFkKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL1BEQS9kYXRhL3F1YW50aWZpY2F0aW9uL2ZyYW5jaXNlbGxhL3BlcHRpZGVzLnR4dCIpCmludDY0IDwtIHdoaWNoKHNhcHBseShwZXB0aWRlc1RhYmxlLGNsYXNzKSA9PSAiaW50ZWdlcjY0IikKZm9yIChqIGluIGludDY0KSBwZXB0aWRlc1RhYmxlW1tqXV0gPC0gYXMubnVtZXJpYyhwZXB0aWRlc1RhYmxlW1tqXV0pICAgICAgICAgICAgICAgICAgICAgIApgYGAKCjMuIE1heHF1YW50IHN0b3JlcyB0aGUgaW50ZW5zaXR5IGRhdGEgZm9yIHRoZSBkaWZmZXJlbnQgc2FtcGxlcyBpbiBjb2x1bW5ucyB0aGF0IHN0YXJ0IHdpdGggSW50ZW5zaXR5LiBXZSBjYW4gcmV0cmVpdmUgdGhlIGNvbHVtbiBuYW1lcyB3aXRoIHRoZSBpbnRlbnNpdHkgZGF0YSB3aXRoIHRoZSBjb2RlIGJlbG93OiAKCmBgYHtyfQpxdWFudENvbHMgPC0gZ3JlcCgiSW50ZW5zaXR5ICIsIG5hbWVzKHBlcHRpZGVzVGFibGUpKQpgYGAKCjQuIFJlYWQgdGhlIGRhdGEgYW5kIHN0b3JlIGl0IGluICBRRmVhdHVyZXMgb2JqZWN0IAoKYGBge3J9CnBlIDwtIHJlYWRRRmVhdHVyZXMoCiAgYXNzYXlEYXRhID0gcGVwdGlkZXNUYWJsZSwKICBmbmFtZXMgPSAxLAogIHF1YW50Q29scyA9ICBxdWFudENvbHMsCiAgbmFtZSA9ICJwZXB0aWRlUmF3IikKcm0ocGVwdGlkZXNUYWJsZSkKZ2MoKQpnYygpCmBgYAoKNS4gVXBkYXRlIGRhdGEgd2l0aCBpbmZvcm1hdGlvbiBvbiBkZXNpZ24KCmBgYHtyfQpjb2xEYXRhKHBlKSRnZW5vdHlwZSA8LSBwZVtbMV1dIHw+IAogIGNvbG5hbWVzKCkgfD4gCiAgc3Vic3RyKDEyLDEzKSB8PgogIGFzLmZhY3RvcigpIHw+IAogIHJlbGV2ZWwoIldUIikKCnBlIHw+IGNvbERhdGEoKQpgYGAKCjwvcD48L2RldGFpbHM+CgojIyBQcmVwcm9jZXNzaW5nCgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgdG8gbG9nLXRyYW5zZnJvbSB0aGUgZGF0YSA8L3N1bW1hcnk+PHA+CgoxLiBMb2cgdHJhbnNmb3JtCgogIC0gUGVwdGlkZXMgd2l0aCB6ZXJvIGludGVuc2l0aWVzIGFyZSBtaXNzaW5nIHBlcHRpZGVzIGFuZCBzaG91bGQgYmUgcmVwcmVzZW50CndpdGggYSBgTkFgIHZhbHVlIHJhdGhlciB0aGFuIGAwYC4KCmBgYHtyfQpwZSA8LSB6ZXJvSXNOQShwZSwgInBlcHRpZGVSYXciKSAjIGNvbnZlcnQgMCB0byBOQQpgYGAKCiAgLSBMb2d0cmFuc2Zvcm0gZGF0YSB3aXRoIGJhc2UgMgoKYGBge3J9CnBlIDwtIGxvZ1RyYW5zZm9ybShwZSwgYmFzZSA9IDIsIGkgPSAicGVwdGlkZVJhdyIsIG5hbWUgPSAicGVwdGlkZUxvZyIpCmBgYAoKMi4gRmlsdGVyaW5nCgogIC0gV2UgcmVtb3ZlIHBlcHRpZGVzIHRoYXQgY291bGQgbm90IGJlIG1hcHBlZCB0byBhIHByb3RlaW4gb3IgdGhhdCBtYXAgdG8gbXVsdGlwbGUgcHJvdGVpbnMgKHRoZSBwcm90ZWluIGlkZW50aWZpZXIgY29udGFpbnMgbXVsdGlwbGUgaWRlbnRpZmllcnMgc2VwYXJhdGVkIGJ5IGEgYDtgKS4KCmBgYHtyfQpwZSA8LSBmaWx0ZXJGZWF0dXJlcygKICAgIHBlLCB+IFByb3RlaW5zICE9ICIiICYgIyMgUmVtb3ZlIGZhaWxlZCBwcm90ZWluIGluZmVyZW5jZQogICAgICAgICFncmVwbCgiOyIsIFByb3RlaW5zKSkgIyMgUmVtb3ZlIHByb3RlaW4gZ3JvdXBzCmBgYAoKICAtIFJlbW92ZSByZXZlcnNlIHNlcXVlbmNlcyAoZGVjb3lzKSBhbmQgY29udGFtaW5hbnRzLiBOb3RlIHRoYXQgdGhpcyBpcyBpbmRpY2F0ZWQgYnkgdGhlIGNvbHVtbiBuYW1lcyBSZXZlcnNlIGFuZCBkZXBlbmRpbmcgb24gdGhlIHZlcnNpb24gb2YgbWF4UXVhbnQgd2l0aCBQb3RlbnRpYWwuY29udGFtaW5hbnRzIG9yIENvbnRhbWluYW50cy4KCgpgYGB7cn0KcGUgPC0gZmlsdGVyRmVhdHVyZXMocGUsflJldmVyc2UgIT0gIisiKQpwZSA8LSBmaWx0ZXJGZWF0dXJlcyhwZSx+IENvbnRhbWluYW50ICE9ICIrIikKYGBgCgogIC0gRHJvcCBwZXB0aWRlcyB0aGF0IHdlcmUgaWRlbnRpZmllZCBpbiBsZXNzIHRoYW4gdGhyZWUgc2FtcGxlLiBXZSB0b2xlcmF0ZSB0aGUgZm9sbG93aW5nIHByb3BvcnRpb24gb2YgTkFzOiBwTkEgPSAobi0zKS9uLiAKCmBgYHtyfQpuT2JzIDwtIDMKbiA8LSBuY29sKHBlW1sicGVwdGlkZUxvZyJdXSkKcE5BIDwtIChuLW5PYnMpL24KcGUgPC0gZmlsdGVyTkEocGUsIHBOQSA9IHBOQSwgaSA9ICJwZXB0aWRlTG9nIikKbnJvdyhwZVtbInBlcHRpZGVMb2ciXV0pCmBgYAoKV2Uga2VlcCBgciBucm93KHBlW1sicGVwdGlkZUxvZyJdXSlgIHBlcHRpZGVzIHVwb24gZmlsdGVyaW5nLgoKMy4gTm9ybWFsaXphdGlvbiBieSBtZWRpYW4gY2VudGVyaW5nCgpgYGB7cn0KcGUgPC0gbm9ybWFsaXplKHBlLCAKICAgICAgICAgICAgICAgIGkgPSAicGVwdGlkZUxvZyIsIAogICAgICAgICAgICAgICAgbmFtZSA9ICJwZXB0aWRlTm9ybSIsIAogICAgICAgICAgICAgICAgbWV0aG9kID0gImNlbnRlci5tZWRpYW4iKQpgYGAKCgo0LiBTdW1tYXJpemF0aW9uLiBXZSB1c2UgdGhlIHN0YW5kYXJkIHN1bWFyaXNhdGlvbiBpbiBhZ2dyZWdhdGVGZWF0dXJlcywgd2hpY2ggaXMgYQpyb2J1c3Qgc3VtbWFyaXNhdGlvbiBtZXRob2QuCgpgYGB7cix3YXJuaW5nPUZBTFNFfQpwZSA8LSBhZ2dyZWdhdGVGZWF0dXJlcyhwZSwKICAgIGkgPSAicGVwdGlkZU5vcm0iLCAKICAgIGZjb2wgPSAiUHJvdGVpbnMiLCAKICAgIG5hLnJtID0gVFJVRSwKICAgIG5hbWUgPSAicHJvdGVpbiIpCmBgYAoKNS4gRmlsdGVyIHByb3RlaW5zIHRoYXQgY29udGFpbiBtYW55IG1pc3NpbmcgdmFsdWVzIAoKV2Ugd2FudCB0byBoYXZlIGF0IGxlYXN0IDQgb2JzZXJ2ZWQgcHJvdGVpbnMgc28gdGhhdCBtb3N0IHByb3RlaW5zIGhhdmUgYXQgbGVhc3QgMiBvYnNlcnZhdGlvbnMgaW4gZWFjaCBncm91cC4gIApTbyB3ZSB0b2xlcmF0ZSBhIHByb3BvcnRpb24gb2YgKG4tNCkvbiBOQXMuIAoKCmBgYHtyfQpuT2JzIDwtIDQKbiA8LSBuY29sKHBlW1sicHJvdGVpbiJdXSkKcE5BIDwtIChuLW5PYnMpL24KcGUgPC0gZmlsdGVyTkEocGUsIHBOQSA9IHBOQSwgaSA9ICJwcm90ZWluIikKYGBgCgpQbG90IG9mIHByZXByb2Nlc3NlZCBkYXRhIAoKYGBge3J9CnBlW1sicGVwdGlkZU5vcm0iXV0gfD4gCiAgYXNzYXkoKSB8PgogIGFzLmRhdGEuZnJhbWUoKSB8PgogIGdhdGhlcihzYW1wbGUsIGludGVuc2l0eSkgfD4gCiAgbXV0YXRlKGdlbm90eXBlID0gY29sRGF0YShwZSlbc2FtcGxlLCJnZW5vdHlwZSJdKSB8PgogIGdncGxvdChhZXMoeCA9IGludGVuc2l0eSxncm91cCA9IHNhbXBsZSxjb2xvciA9IGdlbm90eXBlKSkgKyAKICAgIGdlb21fZGVuc2l0eSgpICsKICAgIGdndGl0bGUoIlBlcHRpZGUtbGV2ZWwiKQoKcGVbWyJwcm90ZWluIl1dIHw+IAogIGFzc2F5KCkgfD4KICBhcy5kYXRhLmZyYW1lKCkgfD4KICBnYXRoZXIoc2FtcGxlLCBpbnRlbnNpdHkpIHw+IAogIG11dGF0ZShnZW5vdHlwZSA9IGNvbERhdGEocGUpW3NhbXBsZSwiZ2Vub3R5cGUiXSkgfD4KICBnZ3Bsb3QoYWVzKHggPSBpbnRlbnNpdHksZ3JvdXAgPSBzYW1wbGUsY29sb3IgPSBnZW5vdHlwZSkpICsgCiAgICBnZW9tX2RlbnNpdHkoKSArCiAgICBnZ3RpdGxlKCJQcm90ZWluLWxldmVsIikKYGBgCjwvcD48L2RldGFpbHM+CgojIyBTdW1tYXJpemVkIGRhdGEgc3RydWN0dXJlCgojIyMgRGVzaWduCgpgYGB7cn0KcGUgfD4gCiAgY29sRGF0YSgpIHw+IAogIGtuaXRyOjprYWJsZSgpCmBgYAoKLSBXVCB2cyBLTyAKLSAzIHZzIDMgcmVwZWF0cyAKCiMjIyBTdW1tYXJpemVkIGludGVuc2l0eSBtYXRyaXgKCmBgYHtyfQpwZVtbInByb3RlaW4iXV0gfD4gCiAgYXNzYXkoKSB8PiAKICBoZWFkKCkgfD4gCiAga25pdHI6OmthYmxlKCkKYGBgCgotIGByIG5yb3cocGVbWyJwcm90ZWluIl1dKWAgcHJvdGVpbnMgCgojIyMgSHlwb3RoZXNpcyB0ZXN0aW5nOiBhIHNpbmdsZSBwcm90ZWluIAoKPGlmcmFtZSB3aWR0aD0iNTYwIiBoZWlnaHQ9IjMxNSIKc3JjPSJodHRwczovL3d3dy55b3V0dWJlLmNvbS9lbWJlZC9teVA2U1VsU3dzTSIKZnJhbWVib3JkZXI9IjAiCnN0eWxlPSJkaXNwbGF5OiBibG9jazsgbWFyZ2luOiBhdXRvOyIKYWxsb3c9ImF1dG9wbGF5OyBlbmNyeXB0ZWQtbWVkaWEiIGFsbG93ZnVsbHNjcmVlbj48L2lmcmFtZT4KCmBgYHtyIGVjaG89RkFMU0V9CmlmICgicGkiJWluJWxzKCkpIHJtKCJwaSIpCmtvcHZvZXRlcjwtZnVuY3Rpb24oeCx5LGFuZ2xlPTAsbD0uMixjZXguZG90PS41LHBjaD0xOSxjb2w9ImJsYWNrIikKewphbmdsZT1hbmdsZS8xODAqcGkKcG9pbnRzKHgseSxjZXg9Y2V4LmRvdCxwY2g9cGNoLGNvbD1jb2wpCmxpbmVzKGMoeCx4K2wqY29zKC1waS8yK2FuZ2xlKSksYyh5LHkrbCpzaW4oLXBpLzIrYW5nbGUpKSxjb2w9Y29sKQpsaW5lcyhjKHgrbC8yKmNvcygtcGkvMithbmdsZSkseCtsLzIqY29zKC1waS8yK2FuZ2xlKStsLzQqY29zKGFuZ2xlKSksYyh5K2wvMipzaW4oLXBpLzIrYW5nbGUpLHkrbC8yKnNpbigtcGkvMithbmdsZSkrbC80KnNpbihhbmdsZSkpLGNvbD1jb2wpCmxpbmVzKGMoeCtsLzIqY29zKC1waS8yK2FuZ2xlKSx4K2wvMipjb3MoLXBpLzIrYW5nbGUpK2wvNCpjb3MocGkrYW5nbGUpKSxjKHkrbC8yKnNpbigtcGkvMithbmdsZSkseStsLzIqc2luKC1waS8yK2FuZ2xlKStsLzQqc2luKHBpK2FuZ2xlKSksY29sPWNvbCkKbGluZXMoYyh4K2wqY29zKC1waS8yK2FuZ2xlKSx4K2wqY29zKC1waS8yK2FuZ2xlKStsLzIqY29zKC1waS8yK3BpLzQrYW5nbGUpKSxjKHkrbCpzaW4oLXBpLzIrYW5nbGUpLHkrbCpzaW4oLXBpLzIrYW5nbGUpK2wvMipzaW4oLXBpLzIrcGkvNCthbmdsZSkpLGNvbD1jb2wpCmxpbmVzKGMoeCtsKmNvcygtcGkvMithbmdsZSkseCtsKmNvcygtcGkvMithbmdsZSkrbC8yKmNvcygtcGkvMi1waS80K2FuZ2xlKSksYyh5K2wqc2luKC1waS8yK2FuZ2xlKSx5K2wqc2luKC1waS8yK2FuZ2xlKStsLzIqc2luKC1waS8yLXBpLzQrYW5nbGUpKSxjb2w9Y29sKQp9CgpwYXIobWFyPWMoMCwwLDAsMCksbWFpPWMoMCwwLDAsMCkpCnBsb3QoMCwwLHhsYWI9IiIseWxhYj0iIix4bGltPWMoMCwxMCkseWxpbT1jKDAsMTApLGNvbD0wLHhheHQ9Im5vbmUiLHlheHQ9Im5vbmUiLGF4ZXM9RkFMU0UpCnJlY3QoMCw2LDEwLDEwLGJvcmRlcj0icmVkIixsd2Q9MikKdGV4dCguNSw4LCJwb3B1bGF0aW9uIixzcnQ9OTAsY29sPSJyZWQiLGNleD0yKQpzeW1ib2xzICgzLCA4LCBjaXJjbGVzPTEuMiwgY29sPSJyZWQiLGFkZD1UUlVFLGZnPSJyZWQiLGluY2hlcz1GQUxTRSxsd2Q9MikKc2V0LnNlZWQoMzMwKQpncmlkPXNlcSgwLDEsLjAxKQoKZm9yIChpIGluIDE6NTApCnsKCWFuZ2xlMT1ydW5pZihuPTEsbWluPTAsbWF4PTM2MCkKCWFuZ2xlMj1ydW5pZihuPTEsbWluPTAsbWF4PTM2MCkKCXJhZGl1cz1zYW1wbGUoZ3JpZCxwcm9iPWdyaWReMipwaS9zdW0oZ3JpZF4yKnBpKSxzaXplPTEpCglrb3B2b2V0ZXIoMytyYWRpdXMqY29zKGFuZ2xlMS8xODAqcGkpLDgrcmFkaXVzKnNpbihhbmdsZTEvMTgwKnBpKSxhbmdsZT1hbmdsZTIpCn0KdGV4dCg3LjUsOCwiRWZmZWN0IG9mIGFyZ2luaW5lIGRlZi4gaW4gcG9wdWxhdGlvbiIsY29sPSJyZWQiLGNleD0xLjIpCgpyZWN0KDAsMCwxMCw0LGJvcmRlcj0iYmx1ZSIsbHdkPTIpCnRleHQoLjUsMiwic2FtcGxlIixzcnQ9OTAsY29sPSJibHVlIixjZXg9MikKc3ltYm9scyAoMywgMiwgY2lyY2xlcz0xLjIsIGNvbD0icmVkIixhZGQ9VFJVRSxmZz0iYmx1ZSIsaW5jaGVzPUZBTFNFLGx3ZD0yKQpmb3IgKGkgaW4gMDoxKQoJZm9yIChqIGluIDA6MikKewoKCWtvcHZvZXRlcigyLjUraiooMy45LTIuMSkvNCwxLjUraSkKfQp0ZXh0KDcuNSwyLCJFZmZlY3Qgb2YgYXJnaW5pbmUgZGVmLiBpbiBzYW1wbGUiLGNvbD0iYmx1ZSIsY2V4PTEuMikKCmFycm93cygzLDUuOSwzLDQuMSxjb2w9ImJsYWNrIixsd2Q9MykKYXJyb3dzKDcsNC4xLDcsNS45LGNvbD0iYmxhY2siLGx3ZD0zKQp0ZXh0KDEuNSw1LCJFeHAuIERlc2lnbiIsY29sPSJibGFjayIsY2V4PTEuMikKdGV4dCg4LjUsNSwiRXN0aW1hdGlvbiBcbiBJbmZlcmVuY2UgIixjb2w9ImJsYWNrIixjZXg9MS4yKQp0ZXh0KDcuNSwuNSwiRGF0YSBleHBsb3JhdGlvbiIsY29sPSJibGFjayIsY2V4PTEuMikKYGBgCgojIyMjIFQtdGVzdAoKPGlmcmFtZSB3aWR0aD0iNTYwIiBoZWlnaHQ9IjMxNSIKc3JjPSJodHRwczovL3d3dy55b3V0dWJlLmNvbS9lbWJlZC9jVnc1a2RTUlpDRSIKZnJhbWVib3JkZXI9IjAiCnN0eWxlPSJkaXNwbGF5OiBibG9jazsgbWFyZ2luOiBhdXRvOyIKYWxsb3c9ImF1dG9wbGF5OyBlbmNyeXB0ZWQtbWVkaWEiIGFsbG93ZnVsbHNjcmVlbj48L2lmcmFtZT4KCgokJAogXGxvZ18yIFx0ZXh0e0ZDfSA9IFxiYXJ7eX1fe3AxfS1cYmFye3l9X3twMn0KJCQKCiQkClRfZz1cZnJhY3tcbG9nXzIgXHRleHR7RkN9fXtcdGV4dHtzZX1fe1xsb2dfMiBcdGV4dHtGQ319fQokJAoKJCQKVF9nPVxmcmFje1x3aWRlaGF0e1x0ZXh0e3NpZ25hbH19fXtcd2lkZWhhdHtcdGV4dHtOb2lzZX19fQokJAoKSWYgd2UgY2FuIGFzc3VtZSBlcXVhbCB2YXJpYW5jZSBpbiBib3RoIHRyZWF0bWVudCBncm91cHM6CgokJApcdGV4dHtzZX1fe1xsb2dfMiBcdGV4dHtGQ319PVx0ZXh0e1NEfVxzcXJ0e1xmcmFjezF9e25fMX0rXGZyYWN7MX17bl8yfX0KJCQKCmBgYHtyfQpXUF8wMDMwMjMzOTIgPC0gZGF0YS5mcmFtZSgKICAgIGludGVuc2l0eSA9IGFzc2F5KHBlW1sicHJvdGVpbiJdXVsiV1BfMDAzMDIzMzkyIixdKSB8PiAKICAgICAgYygpLCAKICAgIGdlbm90eXBlID0gY29sRGF0YShwZSlbLDFdKSAKCldQXzAwMzAyMzM5MiB8PiAKICBnZ3Bsb3QoYWVzKHg9Z2Vub3R5cGUseT1pbnRlbnNpdHkpKSArIAogIGdlb21fcG9pbnQoKSArCiAgZ2d0aXRsZSgiUHJvdGVpbiBXUF8wMDMwMjMzOTIiKQpgYGAKCmBgYHtyIGVjaG89RkFMU0V9CmxtSGxwIDwtIGxtKGludGVuc2l0eSB+IGdlbm90eXBlLCBkYXRhID0gV1BfMDAzMDIzMzkyKQpgYGAKCiQkCnQ9XGZyYWN7XGxvZ18yXHdpZGVoYXR7XHRleHR7RkN9fX17XHRleHR7c2V9X3tcbG9nXzJcd2lkZWhhdHtcdGV4dHtGQ319fX09XGZyYWN7YHIgZm9ybWF0KHN1bW1hcnkobG1IbHApJGNvZWZbMiwxXSwgZGlnaXQgPSAzKWB9e2ByIGZvcm1hdChzdW1tYXJ5KGxtSGxwKSRjb2VmWzIsMl0sIGRpZ2l0ID0gMylgfT1gciBmb3JtYXQoc3VtbWFyeShsbUhscCkkY29lZlsyLDNdLCBkaWdpdCA9IDMpYAokJAoKLSBJcyB0ID0gYHIgZm9ybWF0KHN1bW1hcnkobG1IbHApJGNvZWZbMiwzXSwgZGlnaXQgPSAzKWAgaW5kaWNhdGluZyB0aGF0CnRoZXJlIGlzIGFuIGVmZmVjdD8KCi0gSG93IGxpa2VseSBpcyBpdCB0byBvYnNlcnZlCnQgPSBgciBmb3JtYXQoc3VtbWFyeShsbUhscCkkY29lZlsyLDNdLCBkaWdpdCA9IDMpYCB3aGVuIHRoZXJlIGlzIG5vIGVmZmVjdCBvZiB0aGUgYXJnUCBLTyBvbiB0aGUgcHJvdGVpbiBleHByZXNzaW9uPwoKIyMjIyBOdWxsIGh5cG90aGVzaXMgKCRIXzAkKSBhbmQgYWx0ZXJuYXRpdmUgaHlwb3RoZXNpcyAoJEhfMSQpCgotIFdpdGggZGF0YSB3ZSBjYW4gbmV2ZXIgcHJvdmUgYSBoeXBvdGhlc2lzIChmYWxzaWZpY2F0aW9uIHByaW5jaXBsZSBvZiBQb3BwZXIpCi0gV2l0aCBkYXRhIHdlIGNhbiBvbmx5IHJlamVjdCBhIGh5cG90aGVzaXMgCgotIEluIGdlbmVyYWwgd2Ugc3RhcnQgZnJvbSAqYWx0ZXJuYXRpdmUgaHlwb3RoZXNlKiAkSF8xJDogd2Ugd2FudCB0byBzaG93IGFuIGVmZmVjdCBvZiB0aGUgS08gb24gYSBwcm90ZWluCgo8Y2VudGVyPgokSF8xJDogT24gYXZlcmFnZSB0aGUgcHJvdGVpbiBhYnVuZGFuY2UgaW4gV1QgaXMgZGlmZmVyZW50IGZyb20gdGhhdCBpbiBLTwo8L2NlbnRlcj4KCi0gQnV0LCB3ZSB3aWxsIGFzc2VzcyB0aGlzIGJ5IGZhbHNpZnlpbmcgdGhlIG9wcG9zaXRlOiAKPGNlbnRlcj4KJEhfMCQ6IE9uIGF2ZXJhZ2UgdGhlIHByb3RlaW4gYWJ1bmRhbmNlIGluIFdUIGlzIGVxdWFsIHRvIHRoYXQgaW4gS088LQo8L2NlbnRlcj4KCgpgYGB7cn0KdC50ZXN0KGludGVuc2l0eSB+IGdlbm90eXBlLCBkYXRhID0gV1BfMDAzMDIzMzkyLCB2YXIuZXF1YWw9VFJVRSkKYGBgCgotIEhvdyBsaWtlbHkgaXMgaXQgdG8gb2JzZXJ2ZSBhbiBlcXVhbCBvciBtb3JlIGV4dHJlbWUgZWZmZWN0IHRoYW4gdGhlIG9uZSBvYnNlcnZlZCBpbiB0aGUgc2FtcGxlIHdoZW4gdGhlIG51bGwgaHlwb3RoZXNpcyBpcyB0cnVlPwotIFdoZW4gd2UgbWFrZSBhc3N1bXB0aW9ucyBhYm91dCB0aGUgZGlzdHJpYnV0aW9uIG9mIG91ciB0ZXN0IHN0YXRpc3RpYyB3ZSBjYW4gcXVhbnRpZnkgdGhpcyBwcm9iYWJpbGl0eTogKnAtdmFsdWUqLiAKVGhlIHAtdmFsdWUgd2lsbCBvbmx5IGJlIGNhbGN1bGF0ZWQgY29ycmVjdGx5IGlmIHRoZSB1bmRlcmx5aW5nIGFzc3VtcHRpb25zIGhvbGQhCi0gV2hlbiB3ZSByZXBlYXQgdGhlIGV4cGVyaW1lbnQsIHRoZSBwcm9iYWJpbGl0eSB0byBvYnNlcnZlIGEgZm9sZCBjaGFuZ2UgZm9yIHRoaXMgZ2VuZSB0aGF0IGlzIG1vcmUgZXh0cmVtZSB0aGFuIGEgYHIgZm9ybWF0KDJeYWJzKGxtSGxwJGNvZWZbMl0pLGRpZ2l0cz0zKWAgZm9sZCAoJFxsb2dfMiBGQz1gciBmb3JtYXQobG1IbHAkY29lZlsyXSxkaWdpdHM9MylgJCkgZG93biBvciB1cCByZWd1bGF0aW9uIGJ5IHJhbmRvbSBjaGFuZ2UgKGlmICRIXzAkIGlzIHRydWUpIGlzIGByIHJvdW5kKHN1bW1hcnkobG1IbHApJGNvZWZbMiw0XSoxZTYsMClgIG91dCBvZiAxIDAwMCAwMDAuICAKLSBJZiB0aGUgcC12YWx1ZSBpcyBiZWxvdyBhIHNpZ25pZmljYW5jZSB0aHJlc2hvbGQgJFxhbHBoYSQgd2UgcmVqZWN0IHRoZSBudWxsIGh5cG90aGVzaXMuICpXZSBjb250cm9sIHRoZSBwcm9iYWJpbGl0eSBvbiBhIGZhbHNlIHBvc2l0aXZlIHJlc3VsdCBhdCB0aGUgJFxhbHBoYSQtbGV2ZWwgKHR5cGUgSSBlcnJvcikqCgotIE5vdGUsIHRoYXQgdGhlIHAtdmFsdWVzIGFyZSB1bmlmb3JtIHVuZGVyIHRoZSBudWxsIGh5cG90aGVzaXMsIGkuZS4gd2hlbiAkSF8wJCBpcyB0cnVlIGFsbCBwLXZhbHVlcyBhcmUgZXF1YWxseSBsaWtlbHkuIAoKIyMgTXVsdGlwbGUgaHlwb3RoZXNpcyB0ZXN0aW5nCgo8aWZyYW1lIHdpZHRoPSI1NjAiIGhlaWdodD0iMzE1IgpzcmM9Imh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL2VtYmVkL2NMbi1DRnlBNnBzIgpmcmFtZWJvcmRlcj0iMCIKc3R5bGU9ImRpc3BsYXk6IGJsb2NrOyBtYXJnaW46IGF1dG87IgphbGxvdz0iYXV0b3BsYXk7IGVuY3J5cHRlZC1tZWRpYSIgYWxsb3dmdWxsc2NyZWVuPjwvaWZyYW1lPgoKLSBDb25zaWRlciB0ZXN0aW5nIERBIGZvciBhbGwgJG09MTA2NiQgcHJvdGVpbnMgc2ltdWx0YW5lb3VzbHkKLSBXaGF0IGlmIHdlIGFzc2VzcyBlYWNoIGluZGl2aWR1YWwgdGVzdCBhdCBsZXZlbCAkXGFscGhhJD8KJFxyaWdodGFycm93JCBQcm9iYWJpbGl0eSB0byBoYXZlIGEgZmFsc2UgcG9zaXRpdmUgKEZQKSBhbW9uZyBhbGwgbSBzaW11bHRhdGVub3VzCnRlc3QgJD4+PiAgXGFscGhhPSAwLjA1JAoKLSBJbmRlZWQgZm9yIGVhY2ggbm9uIERBIHByb3RlaW4gd2UgaGF2ZSBhIHByb2JhYmlsaXR5IG9mIDUlIHRvIHJldHVybiBhIEZQLgotIEluIGEgdHlwaWNhbCBleHBlcmltZW50IHRoZSBtYWpvcml0eSBvZiB0aGUgcHJvdGVpbnMgYXJlIG5vbiBEQS4gCi0gU28gYW4gdXBwZXJib3VuZCBvZiB0aGUgZXhwZWN0ZWQgRlAgaXMgJG0gXHRpbWVzIFxhbHBoYSQgb3IgJDEwNjYgXHRpbWVzIDAuMDU9YHIgcm91bmQoMTA2NiowLjA1LDApYCQuIAoKJFxyaWdodGFycm93JCBIZW5jZSwgd2UgYXJlIGJvdW5kIHRvIGNhbGwgbWFueSBmYWxzZSBwb3NpdGl2ZSBwcm90ZWlucyBlYWNoIHRpbWUgd2UgcnVuIHRoZSBleHBlcmltZW50LgoKIyMjIE11bHRpcGxlIHRlc3RpbmcKCiMjIyMgRmFtaWx5LXdpc2UgZXJyb3IgcmF0ZQoKPGlmcmFtZSB3aWR0aD0iNTYwIiBoZWlnaHQ9IjMxNSIKc3JjPSJodHRwczovL3d3dy55b3V0dWJlLmNvbS9lbWJlZC9JTF9lVVN5UkRSQSIKZnJhbWVib3JkZXI9IjAiCnN0eWxlPSJkaXNwbGF5OiBibG9jazsgbWFyZ2luOiBhdXRvOyIKYWxsb3c9ImF1dG9wbGF5OyBlbmNyeXB0ZWQtbWVkaWEiIGFsbG93ZnVsbHNjcmVlbj48L2lmcmFtZT4KClRoZSBmYW1pbHktd2lzZSBlcnJvciByYXRlIChGV0VSKSBhZGRyZXNzZXMgdGhlIG11bHRpcGxlIHRlc3RpbmcgaXNzdWUgYnkgbm8gbG9uZ2VyIGNvbnRyb2xsaW5nIHRoZSBpbmRpdmlkdWFsIHR5cGUgSSBlcnJvciBmb3IgZWFjaCBwcm90ZWluLCBpbnN0ZWFkIGl0IGNvbnRyb2xzOiAgCgpcWwogICBcdGV4dHtGV0VSfSA9IAogICBcdGV4dHtQfVxsZWZ0W0ZQIFxnZXEgMSBccmlnaHRdLgpcXQoKVGhlIEJvbmZlcnJvbmkgbWV0aG9kIGlzIHdpZGVseSB1c2VkIHRvIGNvbnRyb2wgdGhlIHR5cGUgSSBlcnJvcjogCgotIGFzc2VzcyBlYWNoIHRlc3QgYXQgClxbXGFscGhhX1x0ZXh0e2Fkan09XGZyYWN7XGFscGhhfXttfVxdCi0gb3IgdXNlIGFkanVzdGVkIHAtdmFsdWVzIGFuZCBjb21wYXJlIHRoZW0gdG8gJFxhbHBoYSQ6IApcW3BfXHRleHR7YWRqfT1cdGV4dHttaW59XGxlZnQocCBcdGltZXMgbSwxXHJpZ2h0KVxdCgpQcm9ibGVtLCB0aGUgbWV0aG9kIGlzIHZlcnkgY29uc2VydmF0aXZlISAKCiMjIyMgRmFsc2UgZGlzY292ZXJ5IHJhdGUKCjxpZnJhbWUgd2lkdGg9IjU2MCIgaGVpZ2h0PSIzMTUiCnNyYz0iaHR0cHM6Ly93d3cueW91dHViZS5jb20vZW1iZWQvZW52RHF2RXdSY2MiCmZyYW1lYm9yZGVyPSIwIgpzdHlsZT0iZGlzcGxheTogYmxvY2s7IG1hcmdpbjogYXV0bzsiCmFsbG93PSJhdXRvcGxheTsgZW5jcnlwdGVkLW1lZGlhIiBhbGxvd2Z1bGxzY3JlZW4+PC9pZnJhbWU+CgotIEZEUjogRXhwZWN0ZWQgcHJvcG9ydGlvbiBvZiBmYWxzZSBwb3NpdGl2ZXMgb24gdGhlIHRvdGFsIG51bWJlciBvZiBwb3NpdGl2ZXMgeW91IHJldHVybi4KLSBBbiBGRFIgb2YgMSUgbWVhbnMgdGhhdCBvbiBhdmVyYWdlIHdlIGV4cGVjdCAxJSBmYWxzZSBwb3NpdGl2ZSBwcm90ZWlucyBpbiB0aGUgbGlzdCBvZiBwcm90ZWlucyB0aGF0IGFyZSBjYWxsZWQgc2lnbmlmaWNhbnQuCi0gRGVmaW5lZCBieSBCZW5qYW1pbmkgYW5kIEhvY2hiZXJnIGluIHRoZWlyIHNlbWluYWwgcGFwZXIgQmVuamFtaW5pLCBZLiBhbmQgSG9jaGJlcmcsIFkuICgxOTk1KS4gIkNvbnRyb2xsaW5nIHRoZSBmYWxzZSBkaXNjb3ZlcnkgcmF0ZTogYSBwcmFjdGljYWwgYW5kIHBvd2VyZnVsIGFwcHJvYWNoIHRvIG11bHRpcGxlIHRlc3RpbmciLiBKb3VybmFsIG9mIHRoZSBSb3lhbCBTdGF0aXN0aWNhbCBTb2NpZXR5IFNlcmllcyBCLCA1NyAoMSk6IDI4OeKAkzMwMC4gCgpUaGUgKipGYWxzZSBEaXNjb3ZlcnkgUHJvcG9ydGlvbiAoRkRQKSoqIGlzIHRoZSBmcmFjdGlvbiBvZiBmYWxzZSBwb3NpdGl2ZXMgdGhhdCBhcmUgcmV0dXJuZWQsIGkuZS4gCgpcWwpGRFAgPSBcZnJhY3tGUH17Un0KXF0KCi0gSG93ZXZlciwgdGhpcyBxdWFudGl0eSBjYW5ub3QgYmUgb2JzZXJ2ZWQgYmVjYXVzZSBpbiBwcmFjdGljZSB3ZSBvbmx5IGtub3cgdGhlIG51bWJlciBvZiBwcm90ZWlucyBmb3Igd2hpY2ggd2UgcmVqZWN0ZWQgJEhfMCQsICRSJC4gCi0gQnV0LCB3ZSBkbyBub3Qga25vdyB0aGUgbnVtYmVyIG9mIGZhbHNlIHBvc2l0aXZlcywgJEZQJC4KClRoZXJlZm9yZSwgQmVuamFtaW5pIGFuZCBIb2NoYmVyZywgMTk5NSwgZGVmaW5lZCBUaGUgKipGYWxzZSBEaXNjb3ZlcnkgUmF0ZSAoRkRSKSoqIGFzClxbCiAgIFx0ZXh0e0ZEUn0gPSBcdGV4dHtFfVxsZWZ0W1xmcmFje0ZQfXtSfVxyaWdodF0gPVx0ZXh0e0V9XGxlZnRbXHRleHR7RkRQfVxyaWdodF0KXF0KdGhlIGV4cGVjdGVkIEZEUC4gCgotIENvbnRyb2xsaW5nIHRoZSBGRFIgYWxsb3dzIGZvciBtb3JlIGRpc2NvdmVyaWVzIChpLmUuIGxvbmdlciBsaXN0cyB3aXRoIHNpZ25pZmljYW50IHJlc3VsdHMpLCB3aGlsZSB0aGUgZnJhY3Rpb24gb2YgZmFsc2UgZGlzY292ZXJpZXMgYW1vbmcgdGhlIHNpZ25pZmljYW50IHJlc3VsdHMgaW4gd2VsbCBjb250cm9sbGVkIG9uIGF2ZXJhZ2UuIEFzIGEgY29uc2VxdWVuY2UsIG1vcmUgb2YgdGhlIHRydWUgcG9zaXRpdmUgaHlwb3RoZXNlcyB3aWxsIGJlIGRldGVjdGVkLgoKCiMjIyMgSW50dWl0aW9uIG9mIEJILUZEUiBwcm9jZWR1cmUKCkNvbnNpZGVyICRtID0gMTAwMCQgdGVzdHMKCi0gU3VwcG9zZSB0aGF0IGEgcmVzZWFyY2hlciByZWplY3RzIGFsbCBudWxsIGh5cG90aGVzZXMgZm9yIHdoaWNoICRwIDwgMC4wMSQuIAoKLSBJZiB3ZSB1c2UgJHAgPCAwLjAxJCwgd2UgZXhwZWN0ICQwLjAxIFx0aW1lcyBtXzAkIHRlc3RzIHRvIHJldHVybiBmYWxzZSBwb3NpdGl2ZXMuIAotIEEgY29uc2VydmF0aXZlIGVzdGltYXRlIG9mIHRoZSBudW1iZXIgb2YgZmFsc2UgcG9zaXRpdmVzIHRoYXQgd2UgY2FuIGV4cGVjdCBjYW4gYmUgb2J0YWluZWQgYnkgY29uc2lkZXJpbmcgdGhhdCB0aGUgbnVsbCBoeXBvdGhlc2VzIGFyZSB0cnVlIGZvciBhbGwgZmVhdHVyZXMsICRtXzAgPSBtID0gIDEwMDAkLiAKLSBXZSB0aGVuIHdvdWxkIGV4cGVjdCAkMC4wMSBcdGltZXMgMTAwMCA9IDEwJCBmYWxzZSBwb3NpdGl2ZXMgKCRGUD0xMCQpLgoKLSBTdXBwb3NlIHRoYXQgdGhlIHJlc2VhcmNoZXIgZm91bmQgMjAwIGdlbmVzIHdpdGggJHA8MC4wMSQgKCRSPTIwMCQpLgoKLSBUaGUgcHJvcG9ydGlvbiBvZiBmYWxzZSBwb3NpdGl2ZSByZXN1bHRzIChGRFAgPSBmYWxzZSBwb3NpdGl2ZSBwcm9wb3J0aW9uKSBhbW9uZyB0aGUgbGlzdCBvZiAkUj0yMDAkIGdlbmVzIGNhbiB0aGVuIGJlIGVzdGltYXRlZCBhcwogXFsKICAgXHdpZGVoYXR7XHRleHR7RkRQfX09XGZyYWN7RlB9e1J9PVxmcmFjezEwfXsyMDB9PVxmcmFjezAuMDEgXHRpbWVzIDEwMDB9ezIwMH0gPSAwLjA1LgogXF0KCgojIyMjIEJlbmphbWluaSBhbmQgSG9jaGJlcmcgKDE5OTUpIHByb2NlZHVyZSBmb3IgY29udHJvbGxpbmcgdGhlIEZEUiBhdCAkXGFscGhhJAoKMS4gTGV0ICRwX3soMSl9XGxlcSBcbGRvdHMgXGxlcSBwX3sobSl9JCBkZW5vdGUgdGhlIG9yZGVyZWQgJHAkLXZhbHVlcy4KCjIuIEZpbmQgdGhlIGxhcmdlc3QgaW50ZWdlciAkayQgc28gdGhhdCAKJCQKXGZyYWN7cF97KGspfSBcdGltZXMgbX17a30gXGxlcSBcYWxwaGEKJCQKJCRcdGV4dHtvcn0kJAokJApwX3soayl9IFxsZXEgayBcdGltZXMgXGFscGhhL20KJCQKCjMuIElmIHN1Y2ggYSAkayQgZXhpc3RzLCByZWplY3QgdGhlICRrJCBudWxsIGh5cG90aGVzZXMgYXNzb2NpYXRlZCB3aXRoICRwX3soMSl9LCBcbGRvdHMsIHBfeyhrKX0kLgpPdGhlcndpc2Ugbm9uZSBvZiB0aGUgbnVsbCBoeXBvdGhlc2VzIGlzIHJlamVjdGVkLgoKVGhlIGFkanVzdGVkICRwJC12YWx1ZSAoYWxzbyBrbm93biBhcyB0aGUgJHEkLXZhbHVlIGluIEZEUiBsaXRlcmF0dXJlKToKICQkCiAgIHFfeyhpKX09XHRpbGRle3B9X3soaSl9ID0gXG1pblxsZWZ0W1xtaW5fe2o9aSxcbGRvdHMsIG19XGxlZnQobSBwX3soail9L2pccmlnaHQpLCAxIFxyaWdodF0uCiAkJAogSW4gdGhlIGh5cG90aGV0aWNhbCBleGFtcGxlIGFib3ZlOiAkaz0yMDAkLCAkcF97KGspfT0wLjAxJCwgJG09MTAwMCQgYW5kICRcYWxwaGE9MC4wNSQuCgojIyMjIEZyYW5jaXNlbGxhIEV4YW1wbGUKCjxpZnJhbWUgd2lkdGg9IjU2MCIgaGVpZ2h0PSIzMTUiCnNyYz0iaHR0cHM6Ly93d3cueW91dHViZS5jb20vZW1iZWQvQjNCbW5PTGtZZzQiCmZyYW1lYm9yZGVyPSIwIgpzdHlsZT0iZGlzcGxheTogYmxvY2s7IG1hcmdpbjogYXV0bzsiCmFsbG93PSJhdXRvcGxheTsgZW5jcnlwdGVkLW1lZGlhIiBhbGxvd2Z1bGxzY3JlZW4+PC9pZnJhbWU+Cgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgPC9zdW1tYXJ5PjxwPgpgYGB7cn0KdHRlc3RNeCA8LSBmdW5jdGlvbih5LGdyb3VwKSB7CiAgICB0ZXN0IDwtIHRyeSh0LnRlc3QoeVtncm91cF0seVshZ3JvdXBdLHZhci5lcXVhbD1UUlVFKSxzaWxlbnQ9VFJVRSkKICAgIGlmKGlzKHRlc3QsInRyeS1lcnJvciIpKSB7CiAgICAgIHJldHVybihjKGxvZzJGQz1OQSxzZT1OQSx0c3RhdD1OQSxwPU5BKSkKICAgICAgfSBlbHNlIHsKICAgICAgcmV0dXJuKGMobG9nMkZDPSAodGVzdCRlc3RpbWF0ZSUqJWMoMSwtMSkpLHNlPXRlc3Qkc3RkZXJyLHRzdGF0PXRlc3Qkc3RhdGlzdGljLHB2YWw9dGVzdCRwLnZhbHVlKSkKICAgICAgfQogfQogCiByZXMgPC0gYXBwbHkoCiAgICBhc3NheShwZVtbInByb3RlaW4iXV0pLCAKICAgIDEsIAogICAgdHRlc3RNeCwKICAgIGdyb3VwID0gY29sRGF0YShwZSkkZ2Vub3R5cGU9PSJEOCIpIHw+IAogIHQoKSAKIAogY29sbmFtZXMocmVzKSA8LSBjKCJsb2dGQyIsInNlIiwidHN0YXQiLCJwdmFsIikKIAogcmVzIDwtIHJlcyB8PiAKICAgYXMuZGF0YS5mcmFtZSgpIHw+IAogICBuYS5leGNsdWRlKCkgfD4gCiAgIGFycmFuZ2UocHZhbCkKIAogcmVzJGFkalB2YWwgPC0gcC5hZGp1c3QocmVzJHB2YWwsICJmZHIiKQogCiBhbHBoYSA8LSAwLjA1CgogcmVzJGFkakFscGhhRm9ybSA8LSBwYXN0ZTAoMTpucm93KHJlcyksIiB4ICIsYWxwaGEsIi8iLG5yb3cocmVzKSkKCiByZXMkYWRqQWxwaGEgPC0gYWxwaGEgKiAoMTpucm93KHJlcykpL25yb3cocmVzKSAKCiByZXMkInB2YWwgPCBhZGpBbHBoYSIgPC0gcmVzJHB2YWwgPCByZXMkYWRqQWxwaGEgCgogcmVzJCJhZGpQdmFsIDwgYWxwaGEiIDwtIHJlcyRhZGpQdmFsIDwgYWxwaGEgCmBgYAo8L3A+PC9kZXRhaWxzPgoKRldFUjogQm9uZmVycm9uaSBtZXRob2Q6JFxhbHBoYV9cdGV4dHthZGp9ID0gXGFscGhhL20gPSAwLjA1IC8gYHIgbnJvdyhyZXMpYD0gYHIgcm91bmQoYWxwaGEvbnJvdyhyZXMpLDUpYCQKCmBgYHtyIGVjaG89RkFMU0V9IApoZWFkKHJlc1ssLWMoMjozKV0sc3VtKHJlcyRhZGpQdmFsIDwgYWxwaGEpKzIpIHw+IAogIGtuaXRyOjprYWJsZSgpCmBgYAp8IC4uLiB8IC4uLiB8IC4uLiB8IC4uLiB8IC4uLiB8IC4uLiB8IC4uLiB8IC4uLiB8CnxXUF8wMDMwNDA1NjIgfCAwLjAwMzk0ODB8IDAuOTk3NjQyOXwgMC45OTg1Nzk3fDEwNjUgeCAwLjA1LzEwNjYgfCAwLjA0OTk1MzF8RkFMU0UgICAgICAgICAgIHxGQUxTRSAgICAKfFdQXzAwMzA0MTEzMCB8IDAuMDAwMjk0MXwgMC45OTkyODEyfCAwLjk5OTI4MTJ8MTA2NiB4IDAuMDUvMTA2NiB8ICAgICAwLjA1fEZBTFNFICAgICAgICAgICB8RkFMU0UgICAgICAgICAgIHwKCgoKCiMjIyMgUmVzdWx0cwo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgPC9zdW1tYXJ5PjxwPgpgYGB7cn0Kdm9sY2Fub1QgPC0gcmVzIHw+IAogIGdncGxvdChhZXMoeCA9IGxvZ0ZDLCB5ID0gLWxvZzEwKHB2YWwpLCBjb2xvciA9IGFkalB2YWwgPCAwLjA1KSkgKwogICAgZ2VvbV9wb2ludChjZXggPSAyLjUpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBhbHBoYShjKCJibGFjayIsICJyZWQiKSwgMC41KSkgKwogICAgdGhlbWVfbWluaW1hbCgpIApgYGAKPC9wPjwvZGV0YWlscz4KCgpgYGB7cn0Kdm9sY2Fub1QKYGBgCgojIyBNb2RlcmF0ZWQgU3RhdGlzdGljcwoKPGlmcmFtZSB3aWR0aD0iNTYwIiBoZWlnaHQ9IjMxNSIKc3JjPSJodHRwczovL3d3dy55b3V0dWJlLmNvbS9lbWJlZC9fUTExTFhEeTB4VSIKZnJhbWVib3JkZXI9IjAiCnN0eWxlPSJkaXNwbGF5OiBibG9jazsgbWFyZ2luOiBhdXRvOyIKYWxsb3c9ImF1dG9wbGF5OyBlbmNyeXB0ZWQtbWVkaWEiIGFsbG93ZnVsbHNjcmVlbj48L2lmcmFtZT4KClByb2JsZW1zIHdpdGggb3JkaW5hcnkgdC10ZXN0Cgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgPC9zdW1tYXJ5PjxwPgpgYGB7cn0KcHJvYmxlbVBsb3RzIDwtIGxpc3QoKSAKcHJvYmxlbVBsb3RzW1sxXV0gPC0gcmVzIHw+IAogIGdncGxvdChhZXMoeCA9IGxvZ0ZDLCB5ID0gc2UsIGNvbG9yID0gYWRqUHZhbCA8IDAuMDUpKSArCiAgICBnZW9tX3BvaW50KGNleCA9IDIuNSkgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGFscGhhKGMoImJsYWNrIiwgInJlZCIpLCAwLjUpKSArCiAgICB0aGVtZV9taW5pbWFsKCkgCgppIDwtIDEKcHJvYmxlbVBsb3RzW1tpKzFdXSA8LSBjb2xEYXRhKHBlKSB8PiAKICAgIGFzLmRhdGEuZnJhbWUoKSB8PiAKICAgIG11dGF0ZShpbnRlbnNpdHkgPSBwZVtbInByb3RlaW4iXV1bcm93bmFtZXMocmVzKVtpXSxdIHw+IAogICAgICAgICAgICAgYXNzYXkoKSB8PiAKICAgICAgICAgICAgIGMoKQogICAgICAgICAgICkgfD4gCiAgICBnZ3Bsb3QoYWVzKHg9Z2Vub3R5cGUseT1pbnRlbnNpdHkpKSArCiAgICBnZW9tX3BvaW50KCkgKyAKICAgIHlsaW0oLTMsMCkgKwogICAgZ2d0aXRsZShyb3duYW1lcyhyZXMpW2ldKQoKCmkgPC0gMgpwcm9ibGVtUGxvdHNbW2krMV1dIDwtIGNvbERhdGEocGUpIHw+IAogICAgYXMuZGF0YS5mcmFtZSgpIHw+IAogICAgbXV0YXRlKGludGVuc2l0eSA9IHBlW1sicHJvdGVpbiJdXVtyb3duYW1lcyhyZXMpW2ldLF0gfD4gCiAgICAgICAgICAgICBhc3NheSgpIHw+IAogICAgICAgICAgICAgYygpCiAgICAgICAgICAgKSB8PiAKICAgIGdncGxvdChhZXMoeD1nZW5vdHlwZSx5PWludGVuc2l0eSkpICsKICAgIGdlb21fcG9pbnQoKSArIAogICAgeWxpbSgwLDMpICsKICAgIGdndGl0bGUocm93bmFtZXMocmVzKVtpXSkKYGBgCjwvcD48L2RldGFpbHM+CgpgYGB7cn0KcHJvYmxlbVBsb3RzCmBgYAoKQSBnZW5lcmFsIGNsYXNzIG9mIG1vZGVyYXRlZCB0ZXN0IHN0YXRpc3RpY3MgaXMgZ2l2ZW4gYnkKIFxbCiAgIFRfZ157bW9kfSA9IFxmcmFje1xiYXJ7WX1fe2cxfSAtIFxiYXJ7WX1fe2cyfX17QyBccXVhZCBcdGlsZGV7U31fZ30gLAogXF0KIHdoZXJlICRcdGlsZGV7U31fZyQgaXMgYSBtb2RlcmF0ZWQgc3RhbmRhcmQgZGV2aWF0aW9uIGVzdGltYXRlLiAKCi0gJEMkIGlzIGEgY29uc3RhbnQgZGVwZW5kaW5nIG9uIHRoZSBkZXNpZ24gZS5nLiAkXHNxcnR7MS97bl8xfSsxL25fMn0kIGZvciBhIHQtdGVzdCBhbmQgb2YgYW5vdGhlciBmb3JtIGZvciBsaW5lYXIgbW9kZWxzLgotICRcdGlsZGV7U31fZz1TX2crU18wJDogYWRkIHNtYWxsIHBvc2l0aXZlIGNvbnN0YW50IHRvIGRlbm9taW5hdG9yIG9mIHQtc3RhdGlzdGljLiAKLSBUaGlzIGNhbiBiZSBhZG9wdGVkIGluIFBlcnNldXMuIAoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlIDwvc3VtbWFyeT48cD4KYGBge3J9CnNpbUk8LXNhcHBseShyZXMkc2Uvc3FydCgxLzMrMS8zKSxmdW5jdGlvbihuLG1lYW4sc2QpIHJub3JtKG4sbWVhbixzZCksbj02LG1lYW49MCkgfD4gCiAgdCgpCgpyZXNTaW0gPC0gYXBwbHkoCiAgICBzaW1JLCAKICAgIDEsIAogICAgdHRlc3RNeCwKICAgIGdyb3VwID0gY29sRGF0YShwZSkkZ2Vub3R5cGU9PSJEOCIpIHw+IAogIHQoKSAKIGNvbG5hbWVzKHJlc1NpbSkgPC0gYygibG9nRkMiLCJzZSIsInRzdGF0IiwicHZhbCIpCiByZXNTaW0gPC0gYXMuZGF0YS5mcmFtZShyZXNTaW0pCiB0c3RhdFNpbVBsb3QgPC0gcmVzU2ltIHw+IAogICBnZ3Bsb3QoYWVzKHg9dHN0YXQpKSArCiAgICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHk9Li5kZW5zaXR5Li4sIGZpbGw9Li5jb3VudC4uKSxiaW5zPTMwKSArCiAgICAgc3RhdF9mdW5jdGlvbihmdW49ZHQsCiAgICBjb2xvcj0icmVkIiwKICAgIGFyZ3M9bGlzdChkZj00KSkgKyAKICAgeWxpbSgwLC42KSArCiAgIGdndGl0bGUoInQtc3RhdGlzdGljIikKCiAKIHJlc1NpbSRDIDwtIHNxcnQoMS8zKzEvMykgCiByZXNTaW0kc2QgPC0gcmVzU2ltJHNlL3Jlc1NpbSRDIAogdHN0YXRTaW1QZXJzZXVzIDwtIHJlc1NpbSB8PiAKICAgZ2dwbG90KGFlcyh4PWxvZ0ZDLygoc2QrLjEpKkMpKSkgKwogICAgIGdlb21faGlzdG9ncmFtKGFlcyh5PS4uZGVuc2l0eS4uLCBmaWxsPS4uY291bnQuLiksYmlucz0zMCkgKwogICAgIHN0YXRfZnVuY3Rpb24oZnVuPWR0LAogICAgICAgICAgICAgICAgICAgY29sb3I9InJlZCIsCiAgICAgICAgICAgICAgICAgIGFyZ3M9bGlzdChkZj00KSkgKyAKICAgICB5bGltKDAsLjYpICsKICAgIGdndGl0bGUoIlBlcnNldXMiKQpgYGAKCjwvcD48L2RldGFpbHM+CgpgYGB7cn0KZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UodHN0YXRTaW1QbG90LHRzdGF0U2ltUGVyc2V1cyxucm93PTEpCmBgYAoKLSBUaGUgY2hvaWNlIG9mICRTXzAkIGluIFBlcnNldXMgaXMgYWQgaG9jIGFuZCB0aGUgdC1zdGF0aXN0aWMgaXMgbm8tbG9uZ2VyIHQtZGlzdHJpYnV0ZWQuIAotIFBlcm11dGF0aW9uIHRlc3QsIGJ1dCBpcyBkaWZmaWN1bHQgZm9yIG1vcmUgY29tcGxleCBkZXNpZ25zLgotIEFsbG93cyBmb3IgRGF0YSBEcmVkZ2luZyBiZWNhdXNlIHVzZXIgY2FuIGNob29zZSAkU18wJCAKCgojIyMgRW1waXJpY2FsIEJheWVzIAoKCmBgYHtyIGVjaG89RkFMU0UsIG91dC53aWR0aD0iNTAlIn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIi4vZmlndXJlcy9saW1tYVNocmlua2FnZS5wbmciKQpgYGAKCkZpZ3VyZSBjb3VydGVzeSB0byBSYWZhZWwgSXJpemFycnkKCiQkCiAgIFRfZ157bW9kfSA9IFxmcmFje1xiYXJ7WX1fe2cxfSAtIFxiYXJ7WX1fe2cyfX17QyBccXVhZCBcdGlsZGV7U31fZ30gLAogJCQKCi0gKiplbXBpcmljYWwgQmF5ZXMqKiB0aGVvcnkgcHJvdmlkZXMgZm9ybWFsIGZyYW1ld29yayBmb3IgYm9ycm93aW5nIHN0cmVuZ3RoIGFjcm9zcyBwcm90ZWlucywKLSBJbXBsZW1lbnRlZCBpbiBwb3B1bGFyIGJpb2NvbmR1Y3RvciBwYWNrYWdlICoqbGltbWEqKiBhbmQgKiptc3Fyb2IyKioKCiQkCiAgXHRpbGRle1N9X2c9XHNxcnR7XGZyYWN7ZF9nU19nXjIrZF8wU18wXjJ9e2RfZytkXzB9fSwKJCQKCi0gJFNfMF4yJDogY29tbW9uIHZhcmlhbmNlIChvdmVyIGFsbCBwcm90ZWlucykgCi0gTW9kZXJhdGVkIHQtc3RhdGlzdGljIGlzIHQtZGlzdHJpYnV0ZWQgd2l0aCAkZF8wK2RfZyQgZGVncmVlcyBvZiBmcmVlZG9tLiAKLSBOb3RlIHRoYXQgdGhlIGRlZ3JlZXMgb2YgZnJlZWRvbSBpbmNyZWFzZSBieSBib3Jyb3dpbmcgc3RyZW5ndGggYWNyb3NzIHByb3RlaW5zIQoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSB0aGUgY29kZSA8L3N1bW1hcnk+PHA+ICAgCgoxLiBXZSBtb2RlbCB0aGUgcHJvdGVpbiBsZXZlbCBleHByZXNzaW9uIHZhbHVlcyB1c2luZyB0aGUgYG1zcXJvYmAgZnVuY3Rpb24uCkJ5IGRlZmF1bHQgYG1zcXJvYjJgIGVzdGltYXRlcyB0aGUgbW9kZWwgcGFyYW1ldGVycyB1c2luZyByb2J1c3QgcmVncmVzc2lvbi4KCldlIHdpbGwgbW9kZWwgdGhlIGRhdGEgd2l0aCBhIGRpZmZlcmVudCBncm91cCBtZWFuIGZvciBldmVyeSBnZW5vdHlwZS4gClRoZSBncm91cCBpcyBpbmNvZGVkIGluIHRoZSB2YXJpYWJsZSBgZ2Vub3R5cGVgIG9mIHRoZSBjb2xEYXRhLiAKV2UgY2FuIHNwZWNpZnkgdGhpcyBtb2RlbCBieSB1c2luZyBhIGZvcm11bGEgd2l0aCB0aGUgZmFjdG9yIGBnZW5vdHlwZWAgYXMgaXRzIHByZWRpY3RvcjogCmBmb3JtdWxhID0gfmdlbm90eXBlYC4KCk5vdGUsIHRoYXQgYSBmb3JtdWxhIGFsd2F5cyBzdGFydHMgd2l0aCBhIHN5bWJvbCAnficuCmBgYHtyIHdhcm5pbmc9RkFMU0V9CnBlIDwtIG1zcXJvYihvYmplY3QgPSBwZSwgaSA9ICJwcm90ZWluIiwgZm9ybXVsYSA9IH5nZW5vdHlwZSkKYGBgCgoyLiBJbmZlcmVuY2UgCgpXZSBmaXJzdCBleHBsb3JlIHRoZSBkZXNpZ24gb2YgdGhlIG1vZGVsIHRoYXQgd2Ugc3BlY2lmaWVkIHVzaW5nIHRoZSB0aGUgcGFja2FnZSBgRXhwbG9yZU1vZGVsTWF0cml4YCAKCmBgYHtyfQpsaWJyYXJ5KEV4cGxvcmVNb2RlbE1hdHJpeCkKVmlzdWFsaXplRGVzaWduKGNvbERhdGEocGUpLH5nZW5vdHlwZSkkcGxvdGxpc3QKYGBgCgpXZSBoYXZlIHR3byBtb2RlbCBwYXJhbWV0ZXJzLCB0aGUgKEludGVyY2VwdCkgYW5kIGdlbm90eXBlRDguIApUaGlzIHJlc3VsdHMgaW4gYSBtb2RlbCB3aXRoIHR3byBncm91cCBtZWFuczogCgoxLiBGb3IgdGhlIHdpbGQgdHlwZSAoV1QpIHRoZSBleHBlY3RlZCB2YWx1ZSAobWVhbikgb2YgdGhlIGxvZzIgdHJhbnNmb3JtZWQgaW50ZW5zaXR5IHkgZm9yIGEgcHJvdGVpbiB3aWxsIGJlIG1vZGVsbGVkIHVzaW5nIAoKJCRcdGV4dHtFfVtZXHZlcnQgXHRleHR7Z2Vub3R5cGV9PVx0ZXh0e1dUfV0gPSBcdGV4dHsoSW50ZXJjZXB0KX0kJCAKCjIuIEZvciB0aGUga25vY2tvdXQgZ2Vub3R5cGUgRDggdGhlIGV4cGVjdGVkIHZhbHVlIChtZWFuKSBvZiB0aGUgbG9nMiB0cmFuc2Zvcm1lZCBpbnRlbnNpdHkgeSBmb3IgYSBwcm90ZWluIHdpbGwgYmUgbW9kZWxsZWQgdXNpbmcgCgokJFx0ZXh0e0V9W1lcdmVydCBcdGV4dHtnZW5vdHlwZX09XHRleHR7RDh9XSA9IFx0ZXh0eyhJbnRlcmNlcHQpfSArIFx0ZXh0e2dlbm90eXBlRDh9JCQgCgpUaGUgYXZlcmFnZSBsb2cyRkMgYmV0d2VlbiBEOCBhbmQgV1QgaXMgdGh1cwokJFxsb2dfMlx0ZXh0e0ZDfV97RDgtV1R9PSBcdGV4dHtFfVtZXHZlcnQgXHRleHR7Z2Vub3R5cGV9PVx0ZXh0e0Q4fV0gLSBcdGV4dHtFfVtZXHZlcnQgXHRleHR7Z2Vub3R5cGV9PVx0ZXh0e1dUfV0gPSBcdGV4dHtnZW5vdHlwZUQ4fQokJAoKSGVuY2UsIGFzc2Vzc2luZyB0aGUgbnVsbCBoeXBvdGhlc2lzIHRoYXQgdGhlcmUgaXMgbm8gZGlmZmVyZW50aWFsIGFidW5kYW5jZSBiZXR3ZWVuIEQ4IGFuZCBXVCBjYW4gYmUgcmVmb3JtdWxhdGVkIGFzCgokJEhfMDogIFx0ZXh0e2dlbm90eXBlRDh9PTAkJApXZSBjYW4gaW1wbGVtZW50IGEgaHlwb3RoZXNpcyB0ZXN0IGZvciBlYWNoIHByb3RlaW4gaW4gbXNxcm9iMiB1c2luZyB0aGUgY29kZSBiZWxvdzogCgpgYGB7cn0KTCA8LSBtYWtlQ29udHJhc3QoImdlbm90eXBlRDggPSAwIiwgcGFyYW1ldGVyTmFtZXMgPSBjKCJnZW5vdHlwZUQ4IikpCnBlIDwtIGh5cG90aGVzaXNUZXN0KG9iamVjdCA9IHBlLCBpID0gInByb3RlaW4iLCBjb250cmFzdCA9IEwpCmBgYAoKV2UgY2FuIHNob3cgdGhlIGxpc3Qgd2l0aCBhbGwgc2lnbmlmaWNhbnQgREUgcHJvdGVpbnMgYXQgdGhlIDUlIEZEUiB1c2luZyAKYGBge3J9CnJvd0RhdGEocGVbWyJwcm90ZWluIl1dKSRnZW5vdHlwZUQ4IHw+IAogIGFycmFuZ2UocHZhbCkgfD4KICBmaWx0ZXIoYWRqUHZhbDwwLjA1KQpgYGAKCldlIGNhbiBhbHNvIHZpc3VhbGlzZSB0aGUgcmVzdWx0cyB1c2luZyBhIHZvbGNhbm9wbG90CgpgYGB7cn0Kdm9sY2FubyA8LSBnZ3Bsb3QoCiAgICByb3dEYXRhKHBlW1sicHJvdGVpbiJdXSkkZ2Vub3R5cGVEOCwKICAgIGFlcyh4ID0gbG9nRkMsIHkgPSAtbG9nMTAocHZhbCksIGNvbG9yID0gYWRqUHZhbCA8IDAuMDUpCikgKwogICAgZ2VvbV9wb2ludChjZXggPSAyLjUpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBhbHBoYShjKCJibGFjayIsICJyZWQiKSwgMC41KSkgKwogICAgdGhlbWVfbWluaW1hbCgpICsKICAgIGdndGl0bGUoIm1zcXJvYjIiKQpgYGAKPC9wPjwvZGV0YWlscz4KCmBgYHtyfQpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZSgKICB2b2xjYW5vVCArICAgIAogICAgeGxpbSgtMywzKSArCiAgZ2d0aXRsZSgib3JkaW5hcnkgdC10ZXN0IiksCiAgdm9sY2FubyArICAgICAKICAgIHhsaW0oLTMsMykKLG5yb3c9MikKYGBgCgotIFRoZSB2b2xjYW5vIHBsb3Qgb3BlbnMgdXAgd2hlbiB1c2luZyB0aGUgRUIgdmFyaWFuY2UgZXN0aW1hdG9yCgotICBCb3Jyb3dpbmcgc3RyZW5ndGggdG8gZXN0aW1hdGUgdGhlIHZhcmlhbmNlIHVzaW5nIGVtcGlyaWNhbCBCYXllcyBzb2x2ZXMgdGhlIGlzc3VlIG9mIHJldHVybmluZyBwcm90ZWlucyB3aXRoIGEgbG93IGZvbGQgY2hhbmdlIGFzIHNpZ25pZmljYW50IGR1ZSB0byBhIGxvdyB2YXJpYW5jZS4gCgoKIyMjIFNocmlua2FnZSBvZiB0aGUgIHZhcmlhbmNlIGFuZCBtb2RlcmF0ZWQgdC1zdGF0aXN0aWNzCgpgYGB7cn0KcXBsb3QoCiAgc2FwcGx5KHJvd0RhdGEocGVbWyJwcm90ZWluIl1dKSRtc3Fyb2JNb2RlbHMsZ2V0U2lnbWEpLAogIHNhcHBseShyb3dEYXRhKHBlW1sicHJvdGVpbiJdXSkkbXNxcm9iTW9kZWxzLGdldFNpZ21hUG9zdGVyaW9yKSkgKwogIHhsYWIoIlNEIikgKwogIHlsYWIoIm1vZGVyYXRlZCBTRCIpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLHNsb3BlID0gMSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9ICkgCmBgYAoKLSBTbWFsbCB2YXJpYW5jZXMgYXJlIHNocnVua2VuIHRvd2FyZHMgdGhlIGNvbW1vbiB2YXJpYW5jZSByZXN1bHRpbmcgaW4gbGFyZ2UgRUIgdmFyaWFuY2UgZXN0aW1hdGVzCi0gTGFyZ2UgdmFyaWFuY2VzIGFyZSBzaHJ1bmtlbiB0b3dhcmRzIHRoZSBjb21tb24gdmFyaWFuY2UgcmVzdWx0aW5nIGluIHNtYWxsZXIgRUIgdmFyaWFuY2UgZXN0aW1hdGVzIAotIFBvb2xlZCBkZWdyZWVzIG9mIGZyZWVkb20gb2YgdGhlIEVCIHZhcmlhbmNlIGVzdGltYXRvciBhcmUgbGFyZ2VyIGJlY2F1c2UgaW5mb3JtYXRpb24gaXMgYm9ycm93ZWQgYWNyb3NzIHByb3RlaW5zIHRvIGVzdGltYXRlIHRoZSB2YXJpYW5jZQoKIyMgUGxvdHMgCgpgYGB7cn0Kc2lnTmFtZXMgPC0gcm93RGF0YShwZVtbInByb3RlaW4iXV0pJGdlbm90eXBlRDggfD4KICAgIHJvd25hbWVzX3RvX2NvbHVtbigicHJvdGVpbiIpIHw+CiAgICBmaWx0ZXIoYWRqUHZhbCA8IDAuMDUpIHw+CiAgICBwdWxsKHByb3RlaW4pCmhlYXRtYXAoYXNzYXkocGVbWyJwcm90ZWluIl1dKVtzaWdOYW1lcywgXSkKYGBgCgoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmZvciAocHJvdE5hbWUgaW4gc2lnTmFtZXMpCiAgICB7CiAgICAgICAgcGVQbG90IDwtIHBlW3Byb3ROYW1lLCAsIGMoInBlcHRpZGVOb3JtIiwgInByb3RlaW4iKV0KICAgICAgICBwZVBsb3REZiA8LSBkYXRhLmZyYW1lKGxvbmdGb3JtKHBlUGxvdCkpCiAgICAgICAgcGVQbG90RGYkYXNzYXkgPC0gZmFjdG9yKHBlUGxvdERmJGFzc2F5LAogICAgICAgICAgICBsZXZlbHMgPSBjKCJwZXB0aWRlTm9ybSIsICJwcm90ZWluIikKICAgICAgICApCiAgICAgICAgcGVQbG90RGYkZ2Vub3R5cGUgPC0gYXMuZmFjdG9yKGNvbERhdGEocGVQbG90KVtwZVBsb3REZiRjb2xuYW1lLCAiZ2Vub3R5cGUiXSkKCiAgICAgICAgIyBwbG90dGluZwogICAgICAgIHAxIDwtIGdncGxvdCgKICAgICAgICAgICAgZGF0YSA9IHBlUGxvdERmLAogICAgICAgICAgICBhZXMoeCA9IGNvbG5hbWUsIHkgPSB2YWx1ZSwgZ3JvdXAgPSByb3duYW1lKQogICAgICAgICkgKwogICAgICAgICAgICBnZW9tX2xpbmUoKSArCiAgICAgICAgICAgIGdlb21fcG9pbnQoKSArCiAgICAgICAgICAgIGZhY2V0X2dyaWQofmFzc2F5KSArCiAgICAgICAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNzAsIGhqdXN0ID0gMSwgdmp1c3QgPSAwLjUpKSArCiAgICAgICAgICAgIGdndGl0bGUocHJvdE5hbWUpCiAgICAgICAgcHJpbnQocDEpCgogICAgICAgICMgcGxvdHRpbmcgMgogICAgICAgIHAyIDwtIGdncGxvdChwZVBsb3REZiwgYWVzKHggPSBjb2xuYW1lLCB5ID0gdmFsdWUsIGZpbGwgPSBnZW5vdHlwZSkpICsKICAgICAgICAgICAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGUgPSBOQSkgKwogICAgICAgICAgICBnZW9tX3BvaW50KAogICAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAuMSksCiAgICAgICAgICAgICAgICBhZXMoc2hhcGUgPSByb3duYW1lKQogICAgICAgICAgICApICsKICAgICAgICAgICAgc2NhbGVfc2hhcGVfbWFudWFsKHZhbHVlcyA9IDE6bnJvdyhwZVBsb3REZikpICsKICAgICAgICAgICAgbGFicyh0aXRsZSA9IHByb3ROYW1lLCB4ID0gInNhbXBsZSIsIHkgPSAicGVwdGlkZSBpbnRlbnNpdHkgKGxvZzIpIikgKwogICAgICAgICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDcwLCBoanVzdCA9IDEsIHZqdXN0ID0gMC41KSkgKwogICAgICAgICAgICBmYWNldF9ncmlkKH5hc3NheSkKICAgICAgICBwcmludChwMikKfQpgYGAKCiMgRXhwZXJpbWVudGFsIERlc2lnbgoKIyMgU2FtcGxlIHNpemUgCgo8aWZyYW1lIHdpZHRoPSI1NjAiIGhlaWdodD0iMzE1IgpzcmM9Imh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL2VtYmVkL3VFTGdrekRqVlJZIgpmcmFtZWJvcmRlcj0iMCIKc3R5bGU9ImRpc3BsYXk6IGJsb2NrOyBtYXJnaW46IGF1dG87IgphbGxvdz0iYXV0b3BsYXk7IGVuY3J5cHRlZC1tZWRpYSIgYWxsb3dmdWxsc2NyZWVuPjwvaWZyYW1lPgoKJCQKIFxsb2dfMiBcdGV4dHtGQ30gPSBcYmFye3l9X3twMX0tXGJhcnt5fV97cDJ9CiQkCgokJApUX2c9XGZyYWN7XGxvZ18yIFx0ZXh0e0ZDfX17XHRleHR7c2V9X3tcbG9nXzIgXHRleHR7RkN9fX0KJCQKCiQkClRfZz1cZnJhY3tcd2lkZWhhdHtcdGV4dHtzaWduYWx9fX17XHdpZGVoYXR7XHRleHR7Tm9pc2V9fX0KJCQKCklmIHdlIGNhbiBhc3N1bWUgZXF1YWwgdmFyaWFuY2UgaW4gYm90aCB0cmVhdG1lbnQgZ3JvdXBzOgoKJCQKXHRleHR7c2V9X3tcbG9nXzIgXHRleHR7RkN9fT1cdGV4dHtTRH1cc3FydHtcZnJhY3sxfXtuXzF9K1xmcmFjezF9e25fMn19CiQkCgokXHJpZ2h0YXJyb3ckIGlmIG51bWJlciBvZiBiaW8tcmVwZWF0cyBpbmNyZWFzZXMgd2UgaGF2ZSBhIGhpZ2hlciBwb3dlciEKCi0gY2ZyLiBTdHVkeSBvZiB0YW1veGlmZW4gdHJlYXRlZCBFc3Ryb2dlbiBSZWNlcHRlciAoRVIpIHBvc2l0aXZlIGJyZWFzdCBjYW5jZXIgcGF0aWVudHMKCiMjIFJhbmRvbWl6ZWQgY29tcGxldGUgYmxvY2sgZGVzaWducwoKPGlmcmFtZSB3aWR0aD0iNTYwIiBoZWlnaHQ9IjMxNSIKc3JjPSJodHRwczovL3d3dy55b3V0dWJlLmNvbS9lbWJlZC9IRTRLV2ZJT1ZwMCIKZnJhbWVib3JkZXI9IjAiCnN0eWxlPSJkaXNwbGF5OiBibG9jazsgbWFyZ2luOiBhdXRvOyIKYWxsb3c9ImF1dG9wbGF5OyBlbmNyeXB0ZWQtbWVkaWEiIGFsbG93ZnVsbHNjcmVlbj48L2lmcmFtZT4KCgpcW1xzaWdtYV4yPSBcc2lnbWFeMl97YmlvfStcc2lnbWFeMl9cdGV4dHtsYWJ9ICtcc2lnbWFeMl9cdGV4dHtleHRyYWN0aW9ufSArIFxzaWdtYV4yX1x0ZXh0e3J1bn0gKyBcbGRvdHNcXQoKLSBCaW9sb2dpY2FsOiBmbHVjdHVhdGlvbnMgaW4gcHJvdGVpbiBsZXZlbCBiZXR3ZWVuIG1pY2UsIGZsdWN0YXRpb25zIGluIHByb3RlaW4gbGV2ZWwgYmV0d2VlbiBjZWxscywgLi4uCi0gVGVjaG5pY2FsOiBjYWdlIGVmZmVjdCwgbGFiIGVmZmVjdCwgd2VlayBlZmZlY3QsIHBsYXNtYSBleHRyYWN0aW9uLCBNUy1ydW4sIC4uLgoKIyMjIE5hdHVyZSBtZXRob2RzOiBQb2ludHMgb2Ygc2lnbmlmaWNhbmNlIC0gQmxvY2tpbmcgCgpbaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9ubWV0aC4zMDA1LnBkZl0oaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9ubWV0aC4zMDA1LnBkZikKCgojIyMgTW91c2UgZXhhbXBsZSAKCgpgYGB7ciBlY2hvPUZBTFNFLCBvdXQud2lkdGg9IjUwJSJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCIuL2ZpZ3VyZXMvbW91c2VUY2VsbF9SQ0JfZGVzaWduLnBuZyIpCmBgYAoKRHVndWV0IGV0IGFsLiAoMjAxNykgTUNQIDE2KDgpOjE0MTYtMTQzMi4gZG9pOiAxMC4xMDc0L21jcC5tMTE2LjA2Mjc0NQoKLSBBbGwgdHJlYXRtZW50cyBvZiBpbnRlcmVzdCBhcmUgcHJlc2VudCB3aXRoaW4gYmxvY2shCi0gV2UgY2FuIGVzdGltYXRlIHRoZSBlZmZlY3Qgb2YgdGhlIHRyZWF0bWVudCB3aXRoaW4gYmxvY2shCgpUbyBpbGx1c3RyYXRlIHRoZSBwb3dlciBvZiBibG9ja2luZyB3ZSBoYXZlIHN1YnNldHRlZCB0aGUgZGF0YSBvZiBEdWd1ZXQgZXQgYWwuIGluIGEgCgotIGNvbXBsZXRlbHkgcmFuZG9taXplZCBkZXNpZ24gd2l0aCAKICAgIC0gZm91ciBtaWNlIGZvciB3aGljaCB3ZSBvbmx5IGhhdmUgbWVhc3VyZW1lbnRzIG9uIHRoZSBvcmRpbmFyeSBULWNlbGxzIAogICAgLSBmb3VyIG1pY2UgZm9yIHdoaWNoIHdlIG9ubHkgaGF2ZSBtZWFzdXJlbWVudHMgb24gdGhlIHJlZ3VsYXRvcnkgVC1jZWxscyAKICAgIAotIHJhbmRvbWl6ZWQgY29tcGxldGUgYmxvY2sgZGVzaWduIHdpdGggZm91ciBtaWNlIGZvciB3aGljaCB3ZSBib3RoIGhhdmUKICAgIAogICAgLSBtZWFzdXJlbWVudHMgb24gb3JkaW5hcnkgVC1jZWxscyBhcyB3ZWxsIGFzCiAgICAtIG1lYXN1cmVtZW50cyBvbiByZWd1bGF0b3J5IFQtY2VsbHMKCgoKCiMjIyBEYXRhIAo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgIDwvc3VtbWFyeT48cD4KYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CnBlcHRpZGVzVGFibGUgPC0gZnJlYWQoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvUERBMjEvZGF0YS9xdWFudGlmaWNhdGlvbi9tb3VzZVRjZWxsL3BlcHRpZGVzUkNCLnR4dCIpCmludDY0IDwtIHdoaWNoKHNhcHBseShwZXB0aWRlc1RhYmxlLGNsYXNzKSA9PSAiaW50ZWdlcjY0IikKZm9yIChqIGluIGludDY0KSBwZXB0aWRlc1RhYmxlW1tqXV0gPC0gYXMubnVtZXJpYyhwZXB0aWRlc1RhYmxlW1tqXV0pIAoKcGVwdGlkZXNUYWJsZTIgPC0gZnJlYWQoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvUERBMjEvZGF0YS9xdWFudGlmaWNhdGlvbi9tb3VzZVRjZWxsL3BlcHRpZGVzQ1JELnR4dCIpCmludDY0IDwtIHdoaWNoKHNhcHBseShwZXB0aWRlc1RhYmxlMixjbGFzcykgPT0gImludGVnZXI2NCIpCmZvciAoaiBpbiBpbnQ2NCkgcGVwdGlkZXNUYWJsZTJbW2pdXSA8LSBhcy5udW1lcmljKHBlcHRpZGVzVGFibGUyW1tqXV0pIAoKcGVwdGlkZXNUYWJsZTMgPC0gZnJlYWQoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvUERBMjEvZGF0YS9xdWFudGlmaWNhdGlvbi9tb3VzZVRjZWxsL3BlcHRpZGVzLnR4dCIpCmludDY0IDwtIHdoaWNoKHNhcHBseShwZXB0aWRlc1RhYmxlMyxjbGFzcykgPT0gImludGVnZXI2NCIpCmZvciAoaiBpbiBpbnQ2NCkgcGVwdGlkZXNUYWJsZTNbW2pdXSA8LSBhcy5udW1lcmljKHBlcHRpZGVzVGFibGUzW1tqXV0pIAoKcXVhbnRDb2xzIDwtIGdyZXAoIkludGVuc2l0eSAiLCBuYW1lcyhwZXB0aWRlc1RhYmxlKSkKcGUgPC0gcmVhZFFGZWF0dXJlcygKICBhc3NheURhdGEgPSBwZXB0aWRlc1RhYmxlLAogIGZuYW1lcyA9IDEsCiAgcXVhbnRDb2xzID0gIHF1YW50Q29scywKICBuYW1lID0gInBlcHRpZGVSYXciKQpybShwZXB0aWRlc1RhYmxlKQpnYygpCmdjKCkKCgpxdWFudENvbHMyIDwtIGdyZXAoIkludGVuc2l0eSAiLCBuYW1lcyhwZXB0aWRlc1RhYmxlMikpCnBlMiA8LSByZWFkUUZlYXR1cmVzKAogIGFzc2F5RGF0YSA9IHBlcHRpZGVzVGFibGUyLAogIGZuYW1lcyA9IDEsCiAgcXVhbnRDb2xzID0gIHF1YW50Q29sczIsCiAgbmFtZSA9ICJwZXB0aWRlUmF3IikKcm0ocGVwdGlkZXNUYWJsZTIpCmdjKCkKZ2MoKQoKcXVhbnRDb2xzMyA8LSBncmVwKCJJbnRlbnNpdHkgIiwgbmFtZXMocGVwdGlkZXNUYWJsZTMpKQpwZTMgPC0gcmVhZFFGZWF0dXJlcygKICBhc3NheURhdGEgPSBwZXB0aWRlc1RhYmxlMywKICBmbmFtZXMgPSAxLAogIHF1YW50Q29scyA9ICBxdWFudENvbHMzLAogIG5hbWUgPSAicGVwdGlkZVJhdyIpCnJtKHBlcHRpZGVzVGFibGUzKQpnYygpCmdjKCkKCiMjIyBEZXNpZ24KY29sRGF0YShwZSkkY2VsbHR5cGUgPC0gc3Vic3RyKAogIGNvbG5hbWVzKHBlW1sicGVwdGlkZVJhdyJdXSksCiAgMTEsCiAgMTQpIHw+CiAgdW5saXN0KCkgfD4gIAogIGFzLmZhY3RvcigpCgpjb2xEYXRhKHBlKSRtb3VzZSA8LSBwZVtbMV1dIHw+CiAgY29sbmFtZXMoKSB8PgogIHN0cnNwbGl0KHNwbGl0PSJbIF0iKSAgfD4KICBzYXBwbHkoZnVuY3Rpb24oeCkgeFszXSkgfD4KICBhcy5mYWN0b3IoKQoKY29sRGF0YShwZTIpJGNlbGx0eXBlIDwtIHN1YnN0cigKICBjb2xuYW1lcyhwZTJbWyJwZXB0aWRlUmF3Il1dKSwKICAxMSwKICAxNCkgfD4KICB1bmxpc3QoKSB8PiAgCiAgYXMuZmFjdG9yKCkKCmNvbERhdGEocGUyKSRtb3VzZSA8LSBwZTJbWzFdXSB8PgogIGNvbG5hbWVzKCkgfD4KICBzdHJzcGxpdChzcGxpdD0iWyBdIikgIHw+CiAgc2FwcGx5KGZ1bmN0aW9uKHgpIHhbM10pIHw+CiAgYXMuZmFjdG9yKCkKCmNvbERhdGEocGUzKSRjZWxsdHlwZSA8LSBzdWJzdHIoCiAgY29sbmFtZXMocGUzW1sicGVwdGlkZVJhdyJdXSksCiAgMTEsCiAgMTQpIHw+CiAgdW5saXN0KCkgfD4gIAogIGFzLmZhY3RvcigpCgpjb2xEYXRhKHBlMykkbW91c2UgPC0gcGUzW1sxXV0gfD4KICBjb2xuYW1lcygpIHw+CiAgc3Ryc3BsaXQoc3BsaXQ9IlsgXSIpICB8PgogIHNhcHBseShmdW5jdGlvbih4KSB4WzNdKSB8PgogIGFzLmZhY3RvcigpCmBgYAo8L3A+PC9kZXNpZ24+CgojIyMgUHJlcHJvY2Vzc2luZyAKCgojIyMjIExvZy10cmFuc2Zvcm0KCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgY29kZSB0byBsb2ctdHJhbnNmcm9tIHRoZSBkYXRhIDwvc3VtbWFyeT48cD4KCi0gUGVwdGlkZXMgd2l0aCB6ZXJvIGludGVuc2l0aWVzIGFyZSBtaXNzaW5nIHBlcHRpZGVzIGFuZCBzaG91bGQgYmUgcmVwcmVzZW50CndpdGggYSBgTkFgIHZhbHVlIHJhdGhlciB0aGFuIGAwYC4KCmBgYHtyfQpwZSA8LSB6ZXJvSXNOQShwZSwgInBlcHRpZGVSYXciKSAjIGNvbnZlcnQgMCB0byBOQQoKcGUyIDwtIHplcm9Jc05BKHBlMiwgInBlcHRpZGVSYXciKSAjIGNvbnZlcnQgMCB0byBOQQoKcGUzIDwtIHplcm9Jc05BKHBlMywgInBlcHRpZGVSYXciKSAjIGNvbnZlcnQgMCB0byBOQQpgYGAKCi0gTG9ndHJhbnNmb3JtIGRhdGEgd2l0aCBiYXNlIDIKCmBgYHtyfQpwZSA8LSBsb2dUcmFuc2Zvcm0ocGUsIGJhc2UgPSAyLCBpID0gInBlcHRpZGVSYXciLCBuYW1lID0gInBlcHRpZGVMb2ciKQoKcGUyIDwtIGxvZ1RyYW5zZm9ybShwZTIsIGJhc2UgPSAyLCBpID0gInBlcHRpZGVSYXciLCBuYW1lID0gInBlcHRpZGVMb2ciKQoKcGUzIDwtIGxvZ1RyYW5zZm9ybShwZTMsIGJhc2UgPSAyLCBpID0gInBlcHRpZGVSYXciLCBuYW1lID0gInBlcHRpZGVMb2ciKQpgYGAKPC9wPjwvZGV0YWlscz4KCgojIyMjIEZpbHRlcmluZwo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGRldGFpbHMgb24gZmlsdGVyaW5nIDwvc3VtbWFyeT48cD4KCjEuIFJlbW92ZSBwZXB0aWRlcyB0aGF0IG1hcCB0byBtdWx0aXBsZSBwcm90ZWlucwoKV2UgcmVtb3ZlIFBTTXMgdGhhdCBjb3VsZCBub3QgYmUgbWFwcGVkIHRvIGEgcHJvdGVpbiBvciB0aGF0IG1hcAp0byBtdWx0aXBsZSBwcm90ZWlucyAodGhlIHByb3RlaW4gaWRlbnRpZmllciBjb250YWlucyBtdWx0aXBsZQppZGVudGlmaWVycyBzZXBhcmF0ZWQgYnkgYSBgO2ApLgoKYGBge3J9CnBlIDwtIGZpbHRlckZlYXR1cmVzKAogICAgcGUsIH4gUHJvdGVpbnMgIT0gIiIgJiAjIyBSZW1vdmUgZmFpbGVkIHByb3RlaW4gaW5mZXJlbmNlCiAgICAgICAgIWdyZXBsKCI7IiwgUHJvdGVpbnMpKSAjIyBSZW1vdmUgcHJvdGVpbiBncm91cHMKCnBlMiA8LSBmaWx0ZXJGZWF0dXJlcygKICAgIHBlMiwgfiBQcm90ZWlucyAhPSAiIiAmICMjIFJlbW92ZSBmYWlsZWQgcHJvdGVpbiBpbmZlcmVuY2UKICAgICAgICAhZ3JlcGwoIjsiLCBQcm90ZWlucykpICMjIFJlbW92ZSBwcm90ZWluIGdyb3VwcwoKcGUzIDwtIGZpbHRlckZlYXR1cmVzKAogICAgcGUzLCB+IFByb3RlaW5zICE9ICIiICYgIyMgUmVtb3ZlIGZhaWxlZCBwcm90ZWluIGluZmVyZW5jZQogICAgICAgICFncmVwbCgiOyIsIFByb3RlaW5zKSkgIyMgUmVtb3ZlIHByb3RlaW4gZ3JvdXBzCmBgYAoyLiBSZW1vdmUgcmV2ZXJzZSBzZXF1ZW5jZXMgKGRlY295cykgYW5kIGNvbnRhbWluYW50cwoKV2Ugbm93IHJlbW92ZSB0aGUgY29udGFtaW5hbnRzLCBwZXB0aWRlcyB0aGF0IG1hcCB0byBkZWNveSBzZXF1ZW5jZXMsIGFuZCBwcm90ZWlucwp3aGljaCB3ZXJlIG9ubHkgaWRlbnRpZmllZCBieSBwZXB0aWRlcyB3aXRoIG1vZGlmaWNhdGlvbnMuCgpgYGB7cn0KcGUgPC0gZmlsdGVyRmVhdHVyZXMocGUsflJldmVyc2UgIT0gIisiKQpwZSA8LSBmaWx0ZXJGZWF0dXJlcyhwZSx+IFBvdGVudGlhbC5jb250YW1pbmFudCAhPSAiKyIpCgpwZTIgPC0gZmlsdGVyRmVhdHVyZXMocGUyLH5SZXZlcnNlICE9ICIrIikKcGUyIDwtIGZpbHRlckZlYXR1cmVzKHBlMix+IFBvdGVudGlhbC5jb250YW1pbmFudCAhPSAiKyIpCgpwZTMgPC0gZmlsdGVyRmVhdHVyZXMocGUzLH5SZXZlcnNlICE9ICIrIikKcGUzIDwtIGZpbHRlckZlYXR1cmVzKHBlMyx+IFBvdGVudGlhbC5jb250YW1pbmFudCAhPSAiKyIpCmBgYAoKCjMuIERyb3AgcGVwdGlkZXMgdGhhdCB3ZXJlIGlkZW50aWZpZWQgaW4gbGVzcyB0aGFuIHRocmVlIHNhbXBsZQoKV2Uga2VlcCBwZXB0aWRlcyB0aGF0IHdlcmUgb2JzZXJ2ZWQgYXQgbGVhc3QgdGhyZWUgdGltZXMuCgpgYGB7cn0Kbk9icyA8LSAzCm4gPC0gbmNvbChwZVtbInBlcHRpZGVMb2ciXV0pCnBOQSA8LSAobi1uT2JzKS9uCnBlIDwtIGZpbHRlck5BKHBlLCBwTkEgPSBwTkEsIGkgPSAicGVwdGlkZUxvZyIpCm5yb3cocGVbWyJwZXB0aWRlTG9nIl1dKQoKbiA8LSBuY29sKHBlMltbInBlcHRpZGVMb2ciXV0pCnBOQSA8LSAobi1uT2JzKS9uCnBlMiA8LSBmaWx0ZXJOQShwZTIsIHBOQSA9IHBOQSwgaSA9ICJwZXB0aWRlTG9nIikKbnJvdyhwZTJbWyJwZXB0aWRlTG9nIl1dKQoKbiA8LSBuY29sKHBlM1tbInBlcHRpZGVMb2ciXV0pCnBOQSA8LSAobi1uT2JzKS9uCnBlMyA8LSBmaWx0ZXJOQShwZTMsIHBOQSA9IHBOQSwgaSA9ICJwZXB0aWRlTG9nIikKbnJvdyhwZTNbWyJwZXB0aWRlTG9nIl1dKQpgYGAKCjwvcD48L2RldGFpbHM+CgojIyMjIE5vcm1hbGl6YXRpb24gCgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgdG8gbm9ybWFsaXplIHRoZSBkYXRhIDwvc3VtbWFyeT48cD4KYGBge3J9CnBlIDwtIG5vcm1hbGl6ZShwZSwgCiAgICAgICAgICAgICAgICBpID0gInBlcHRpZGVMb2ciLCAKICAgICAgICAgICAgICAgIG5hbWUgPSAicGVwdGlkZU5vcm0iLCAKICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJjZW50ZXIubWVkaWFuIikKCnBlMiA8LSBub3JtYWxpemUocGUyLCAKICAgICAgICAgICAgICAgIGkgPSAicGVwdGlkZUxvZyIsIAogICAgICAgICAgICAgICAgbmFtZSA9ICJwZXB0aWRlTm9ybSIsIAogICAgICAgICAgICAgICAgbWV0aG9kID0gImNlbnRlci5tZWRpYW4iKQoKCnBlMyA8LSBub3JtYWxpemUocGUzLCAKICAgICAgICAgICAgICAgIGkgPSAicGVwdGlkZUxvZyIsIAogICAgICAgICAgICAgICAgbmFtZSA9ICJwZXB0aWRlTm9ybSIsIAogICAgICAgICAgICAgICAgbWV0aG9kID0gImNlbnRlci5tZWRpYW4iKQpgYGAKCjwvcD48L2RldGFpbHM+CgojIyMjIFN1bW1hcml6YXRpb24KCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgY29kZSB0byBzdW1tYXJpemUgdGhlIGRhdGEgPC9zdW1tYXJ5PjxwPgoKYGBge3Isd2FybmluZz1GQUxTRX0KcGUgPC0gYWdncmVnYXRlRmVhdHVyZXMocGUsCiBpID0gInBlcHRpZGVOb3JtIiwKIGZjb2wgPSAiUHJvdGVpbnMiLAogbmEucm0gPSBUUlVFLAogbmFtZSA9ICJwcm90ZWluIikKCgpwZTIgPC0gYWdncmVnYXRlRmVhdHVyZXMocGUyLAogaSA9ICJwZXB0aWRlTm9ybSIsCiBmY29sID0gIlByb3RlaW5zIiwKIG5hLnJtID0gVFJVRSwKIG5hbWUgPSAicHJvdGVpbiIpCgpwZTMgPC0gYWdncmVnYXRlRmVhdHVyZXMocGUzLAogaSA9ICJwZXB0aWRlTm9ybSIsCiBmY29sID0gIlByb3RlaW5zIiwKIG5hLnJtID0gVFJVRSwKIG5hbWUgPSAicHJvdGVpbiIpCmBgYAoKIyMjIyBGaWx0ZXJpbmcgcHJvdGVpbnMgd2l0aCB0b28gbWFueSBtaXNzaW5nIHZhbHVlcyAKCldlIHdhbnQgdG8gaGF2ZSBhdCBsZWFzdCB0d28gb2JzZXJ2ZWQgcHJvdGVpbiBpbnRlbnNpdGllcyBmb3IgZWFjaCBncm91cCBzbyB3ZSBzZXQgdGhlIG1pbmltdW0gbnVtYmVyIG9mIG9ic2VydmVkIHZhbHVlcyBhdCA0LiAKV2Ugc3RpbGwgaGF2ZSB0byBjaGVjayBmb3IgdGhlIG9ic2VydmVkIHByb3RlaW5zIGlmIHRoYXQgaXMgdGhlIGNhc2UuIAoKRm9yIGJsb2NrIGRlc2lnbiBtb3JlIGNsZXZlciBmaWx0ZXJpbmcgY2FuIGJlIHVzZWQuIEUuZy4gd2UgY291bGQgaW1wbHkgdGhhdCB3ZSBoYXZlIGJvdGggY2VsbCB0eXBlcyBpbiBhdCBsZWFzdCB0d28gYW5pbWFscy4uLiAKCgpgYGB7cn0Kbk9icyA8LSA0Cm4gPC0gbmNvbChwZVtbInByb3RlaW4iXV0pCnBOQSA8LSAobi1uT2JzKS9uCnBlPC0gZmlsdGVyTkEocGUsIHBOQSA9IHBOQSwgaSA9ICJwcm90ZWluIikKCm4gPC0gbmNvbChwZTJbWyJwcm90ZWluIl1dKQpwTkEgPC0gKG4tbk9icykvbgpwZTIgPC0gZmlsdGVyTkEocGUyLCBwTkEgPSBwTkEsIGkgPSAicHJvdGVpbiIpCgpuIDwtIG5jb2wocGUzW1sicHJvdGVpbiJdXSkKcE5BIDwtIChuLW5PYnMpL24KcGUzIDwtIGZpbHRlck5BKHBlMywgcE5BID0gcE5BLCBpID0gInByb3RlaW4iKQpgYGAKCjwvcD48L2RldGFpbHM+CgojIyMgRGF0YSBFeHBsb3JhdGlvbjogd2hhdCBpcyBpbXBhY3Qgb2YgYmxvY2tpbmc/IAoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlIDwvc3VtbWFyeT48cD4KYGBge3J9CmxldmVscyhjb2xEYXRhKHBlMykkbW91c2UpIDwtIHBhc3RlMCgibSIsMTo3KQptZHNPYmozIDwtIHBsb3RNRFMoYXNzYXkocGUzW1sicHJvdGVpbiJdXSksIHBsb3QgPSBGQUxTRSkKbWRzT3JpZyA8LSBjb2xEYXRhKHBlMykgfD4KICBhcy5kYXRhLmZyYW1lKCkgfD4KICBtdXRhdGUobWRzMSA9IG1kc09iajMkeCwKICAgICAgICAgbWRzMiA9IG1kc09iajMkeSwKICAgICAgICAgbGFiID0gcGFzdGUobW91c2UsY2VsbHR5cGUsc2VwPSJfIikpIHw+CiAgZ2dwbG90KGFlcyh4ID0gbWRzMSwgeSA9IG1kczIsIGxhYmVsID0gbGFiLCBjb2xvciA9IGNlbGx0eXBlLCBncm91cCA9IG1vdXNlKSkgKwogIGdlb21fdGV4dChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZ2VvbV9wb2ludChzaGFwZSA9IDIxKSArCiAgZ2VvbV9saW5lKGNvbG9yID0gImJsYWNrIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIHhsYWIoCiAgICBwYXN0ZTAoCiAgICAgIG1kc09iajMkYXhpc2xhYmVsLAogICAgICAiICIsCiAgICAgIDEsIAogICAgICAiICgiLAogICAgICBwYXN0ZTAoCiAgICAgICAgcm91bmQobWRzT2JqMyR2YXIuZXhwbGFpbmVkWzFdICoxMDAsMCksCiAgICAgICAgIiUiCiAgICAgICAgKSwKICAgICAgIikiCiAgICAgICkKICAgICkgKwogIHlsYWIoCiAgICBwYXN0ZTAoCiAgICAgIG1kc09iajMkYXhpc2xhYmVsLAogICAgICAiICIsCiAgICAgIDIsIAogICAgICAiICgiLAogICAgICBwYXN0ZTAoCiAgICAgICAgcm91bmQobWRzT2JqMyR2YXIuZXhwbGFpbmVkWzJdICoxMDAsMCksCiAgICAgICAgIiUiCiAgICAgICAgKSwKICAgICAgIikiCiAgICAgICkKICAgICkgKwogIGdndGl0bGUoIk9yaWdpbmFsIChSQ0IpIikKCmxldmVscyhjb2xEYXRhKHBlKSRtb3VzZSkgPC0gcGFzdGUwKCJtIiwxOjQpCm1kc09iaiA8LSBwbG90TURTKGFzc2F5KHBlW1sicHJvdGVpbiJdXSksIHBsb3QgPSBGQUxTRSkKbWRzUkNCIDwtIGNvbERhdGEocGUpIHw+CiAgYXMuZGF0YS5mcmFtZSgpIHw+CiAgbXV0YXRlKG1kczEgPSBtZHNPYmokeCwKICAgICAgICAgbWRzMiA9IG1kc09iaiR5LAogICAgICAgICBsYWIgPSBwYXN0ZShtb3VzZSxjZWxsdHlwZSxzZXA9Il8iKSkgfD4KICBnZ3Bsb3QoYWVzKHggPSBtZHMxLCB5ID0gbWRzMiwgbGFiZWwgPSBsYWIsIGNvbG9yID0gY2VsbHR5cGUsIGdyb3VwID0gbW91c2UpKSArCiAgZ2VvbV90ZXh0KHNob3cubGVnZW5kID0gRkFMU0UpICsKICBnZW9tX3BvaW50KHNoYXBlID0gMjEpICsKICBnZW9tX2xpbmUoY29sb3IgPSAiYmxhY2siLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgeGxhYigKICAgIHBhc3RlMCgKICAgICAgbWRzT2JqJGF4aXNsYWJlbCwKICAgICAgIiAiLAogICAgICAxLCAKICAgICAgIiAoIiwKICAgICAgcGFzdGUwKAogICAgICAgIHJvdW5kKG1kc09iaiR2YXIuZXhwbGFpbmVkWzFdICoxMDAsMCksCiAgICAgICAgIiUiCiAgICAgICAgKSwKICAgICAgIikiCiAgICAgICkKICAgICkgKwogIHlsYWIoCiAgICBwYXN0ZTAoCiAgICAgIG1kc09iaiRheGlzbGFiZWwsCiAgICAgICIgIiwKICAgICAgMiwgCiAgICAgICIgKCIsCiAgICAgIHBhc3RlMCgKICAgICAgICByb3VuZChtZHNPYmokdmFyLmV4cGxhaW5lZFsyXSAqMTAwLDApLAogICAgICAgICIlIgogICAgICAgICksCiAgICAgICIpIgogICAgICApCiAgICApICsKICBnZ3RpdGxlKCJSYW5kb21pemVkIENvbXBsZXRlIEJsb2NrIChSQ0IpIikKCgpsZXZlbHMoY29sRGF0YShwZTIpJG1vdXNlKSA8LSBwYXN0ZTAoIm0iLDE6OCkKbWRzT2JqMiA8LSBwbG90TURTKGFzc2F5KHBlMltbInByb3RlaW4iXV0pLCBwbG90ID0gRkFMU0UpCm1kc0NSRCA8LSBjb2xEYXRhKHBlMikgfD4KICBhcy5kYXRhLmZyYW1lKCkgfD4KICBtdXRhdGUobWRzMSA9IG1kc09iajIkeCwKICAgICAgICAgbWRzMiA9IG1kc09iajIkeSwKICAgICAgICAgbGFiID0gcGFzdGUobW91c2UsY2VsbHR5cGUsc2VwPSJfIikpIHw+CiAgZ2dwbG90KGFlcyh4ID0gbWRzMSwgeSA9IG1kczIsIGxhYmVsID0gbGFiLCBjb2xvciA9IGNlbGx0eXBlLCBncm91cCA9IG1vdXNlKSkgKwogIGdlb21fdGV4dChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZ2VvbV9wb2ludChzaGFwZSA9IDIxKSArCiAgeGxhYigKICAgIHBhc3RlMCgKICAgICAgbWRzT2JqJGF4aXNsYWJlbCwKICAgICAgIiAiLAogICAgICAxLCAKICAgICAgIiAoIiwKICAgICAgcGFzdGUwKAogICAgICAgIHJvdW5kKG1kc09iajIkdmFyLmV4cGxhaW5lZFsxXSAqMTAwLDApLAogICAgICAgICIlIgogICAgICAgICksCiAgICAgICIpIgogICAgICApCiAgICApICsKICB5bGFiKAogICAgcGFzdGUwKAogICAgICBtZHNPYmokYXhpc2xhYmVsLAogICAgICAiICIsCiAgICAgIDIsIAogICAgICAiICgiLAogICAgICBwYXN0ZTAoCiAgICAgICAgcm91bmQobWRzT2JqMiR2YXIuZXhwbGFpbmVkWzJdICoxMDAsMCksCiAgICAgICAgIiUiCiAgICAgICAgKSwKICAgICAgIikiCiAgICAgICkKICAgICkgKwogIGdndGl0bGUoIkNvbXBsZXRlbHkgUmFuZG9taXplZCBEZXNpZ24gKENSRCkiKQpgYGAKPC9wPjwvZGV0YWlscz4KYGBge3J9Cm1kc09yaWcKbWRzUkNCCm1kc0NSRApgYGAKCi0gV2Ugb2JzZXJ2ZSB0aGF0IHRoZSBsZWFkaW5nIGZvbGQgY2hhbmdlIGlzIGFjY29yZGluZyB0byBtb3VzZQotIEluIHRoZSBzZWNvbmQgZGltZW5zaW9uIHdlIHNlZSBhIHNlcGFyYXRpb24gYWNjb3JkaW5nIHRvIGNlbGwtdHlwZSAKLSBXaXRoIHRoZSBSYW5kb21pemVkIENvbXBsZXRlIEJsb2NrIGRlc2lnbiAoUkNCKSB3ZSBjYW4gcmVtb3ZlIHRoZSBtb3VzZSBlZmZlY3QgZnJvbSB0aGUgYW5hbHlzaXMhCgoKCgotIFdlIGNhbiBpc29sYXRlIHRoZSBiZXR3ZWVuIGJsb2NrIHZhcmlhYmlsaXR5IGZyb20gdGhlIGFuYWx5c2lzIHVzaW5nIGxpbmVhciBtb2RlbDoKICAKICAtIEZvcm11bGEgaW4gUgokJCAKeSBcc2ltIFx0ZXh0e2NlbGx0eXBlfSArIFx0ZXh0e21vdXNlfQokJAogIAogIC0gRm9ybXVsYSAKCiQkCnlfaSA9IFxiZXRhXzAgKyBcYmV0YV9cdGV4dHtUcmVnfSB4X3tpLFx0ZXh0e1RyZWd9fSArIFxiZXRhX3ttMn14X3tpLG0yfSArIFxiZXRhX3ttM314X3tpLG0zfSArIFxiZXRhX3ttNH14X3tpLG00fSAgKyBcZXBzaWxvbl9pCiQkCgp3aXRoCgotICR4X3tpLFRyZWd9PVxiZWdpbntjYXNlc30KMSYgXHRleHR7VHJlZ31cXAowJiBcdGV4dHtUY29ufQpcZW5ke2Nhc2VzfSQKCi0gJHhfe2ksbTJ9PVxiZWdpbntjYXNlc30KMSYgXHRleHR7bTJ9XFwKMCYgXHRleHR7b3RoZXJ3aXNlfQpcZW5ke2Nhc2VzfSQKCi0gJHhfe2ksbTN9PVxiZWdpbntjYXNlc30KMSYgXHRleHR7bTN9XFwKMCYgXHRleHR7b3RoZXJ3aXNlfQpcZW5ke2Nhc2VzfSQKLSAkeF97aSxtNH09XGJlZ2lue2Nhc2VzfQoxJiBcdGV4dHttNH1cXAowJiBcdGV4dHtvdGhlcndpc2V9ClxlbmR7Y2FzZXN9JAotIFBvc3NpYmxlIGluIG1zcXJvYjIgYW5kIE1Tc3RhdHMgYnV0IG5vdCBwb3NzaWJsZSB3aXRoIFBlcnNldXMhCgoKCiMjIyBNb2RlbGluZyBhbmQgaW5mZXJlbmNlCgojIyMjIFJDQiBhbmFseXNpcwpgYGB7ciB3YXJuaW5nPUZBTFNFfQpwZSA8LSBtc3Fyb2IoCiAgb2JqZWN0ID0gcGUsCiAgaSA9ICJwcm90ZWluIiwKICBmb3JtdWxhID0gfiBjZWxsdHlwZSArIG1vdXNlKQpgYGAKCgojIyMjIENSRCBhbmFseXNpcyAKYGBge3Igd2FybmluZyA9IEZBTFNFfQpwZTIgPC0gbXNxcm9iKAogIG9iamVjdCA9IHBlMiwKICBpID0gInByb3RlaW4iLAogIGZvcm11bGEgPSB+IGNlbGx0eXBlKQpgYGAKCiMjIyMgRXN0aW1hdGlvbiwgZWZmZWN0IHNpemUgYW5kIGluZmVyZW5jZQoKRWZmZWN0IHNpemUgaW4gUkNCCmBgYHtyfQpsaWJyYXJ5KEV4cGxvcmVNb2RlbE1hdHJpeCkKVmlzdWFsaXplRGVzaWduKGNvbERhdGEocGUpLH4gY2VsbHR5cGUgKyBtb3VzZSkkcGxvdGxpc3QKYGBgCgpFZmZlY3Qgc2l6ZSBpbiBDUkQKYGBge3J9ClZpc3VhbGl6ZURlc2lnbihjb2xEYXRhKHBlMiksfiBjZWxsdHlwZSkkcGxvdGxpc3QKYGBgCgoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlIGZvciBzdGF0aXN0aWNhbCBpbmZlcmVuY2UgPC9zdW1tYXJ5PjxwPgpgYGB7cn0KTCA8LSBtYWtlQ29udHJhc3QoImNlbGx0eXBlVHJlZyA9IDAiLCBwYXJhbWV0ZXJOYW1lcyA9IGMoImNlbGx0eXBlVHJlZyIpKQpwZSA8LSBoeXBvdGhlc2lzVGVzdChvYmplY3QgPSBwZSwgaSA9ICJwcm90ZWluIiwgY29udHJhc3QgPSBMKQpwZTIgPC0gaHlwb3RoZXNpc1Rlc3Qob2JqZWN0ID0gcGUyLCBpID0gInByb3RlaW4iLCBjb250cmFzdCA9IEwpCmBgYAo8L3A+PC9kZXRhaWxzPgoKIyMjIENvbXBhcmlzb24gb2YgcmVzdWx0cwoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlIDwvc3VtbWFyeT48cD4KYGBge3Igd2FybmluZz1GQUxTRSxlY2hvPUZBTFNFfQp2b2xjYW5vUkNCIDwtIGdncGxvdCgKICAgIHJvd0RhdGEocGVbWyJwcm90ZWluIl1dKSRjZWxsdHlwZVRyZWcsCiAgICBhZXMoeCA9IGxvZ0ZDLCB5ID0gLWxvZzEwKHB2YWwpLCBjb2xvciA9IGFkalB2YWwgPCAwLjA1KQopICsKICAgIGdlb21fcG9pbnQoY2V4ID0gMi41KSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYWxwaGEoYygiYmxhY2siLCAicmVkIiksIDAuNSkpICsKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICBnZ3RpdGxlKHBhc3RlMCgiUkNCOiBcbiIsIAogICAgICAgICAgICAgICAgc3VtKHJvd0RhdGEocGVbWyJwcm90ZWluIl1dKSRjZWxsdHlwZVRyZWckYWRqUHZhbDwwLjA1LG5hLnJtPVRSVUUpLAogICAgICAgICAgICAiIHNpZ25pZmljYW50IikpCgp2b2xjYW5vQ1JEIDwtIGdncGxvdCgKICAgIHJvd0RhdGEocGUyW1sicHJvdGVpbiJdXSkkY2VsbHR5cGVUcmVnLAogICAgYWVzKHggPSBsb2dGQywgeSA9IC1sb2cxMChwdmFsKSwgY29sb3IgPSBhZGpQdmFsIDwgMC4wNSkKKSArCiAgICBnZW9tX3BvaW50KGNleCA9IDIuNSkgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGFscGhhKGMoImJsYWNrIiwgInJlZCIpLCAwLjUpKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgZ2d0aXRsZShwYXN0ZTAoIkNSRDogXG4iLCAKICAgICAgICAgICAgICAgIHN1bShyb3dEYXRhKHBlMltbInByb3RlaW4iXV0pJGNlbGx0eXBlVHJlZyRhZGpQdmFsPDAuMDUsbmEucm09VFJVRSksCiAgICAgICAgICAgICIgc2lnbmlmaWNhbnQiKSkKCnhsaW1zIDwtICgKICByYW5nZSgKICAgIHJvd0RhdGEocGVbWyJwcm90ZWluIl1dKSRjZWxsdHlwZVRyZWckbG9nRkMsCiAgICByb3dEYXRhKHBlMltbInByb3RlaW4iXV0pJGNlbGx0eXBlVHJlZyRsb2dGQywKICAgICAgICAgICAgICBuYS5ybT1UUlVFKSB8PiAKICAgIGFicygpIHw+IAogICAgbWF4KCkKICApICogYygtMSwxKQoKeWxpbXMgPC0gcmFuZ2UoLWxvZzEwKHJvd0RhdGEocGVbWyJwcm90ZWluIl1dKSRjZWxsdHlwZVRyZWckcHZhbCksIAogICAgICAgICAgICAgIC1sb2cxMChyb3dEYXRhKHBlMltbInByb3RlaW4iXV0pJGNlbGx0eXBlVHJlZyRsb2dGQyksIAogICAgICAgICAgICAgIG5hLnJtPVRSVUUpCmBgYAo8L3A+PC9kZXRhaWxzPgogIApgYGB7ciB3YXJuaW5nPUZBTFNFLGVjaG89RkFMU0V9CnZvbGNhbm9SQ0IgKyAKICB4bGltKHhsaW1zKSArIAogIHlsaW0oeWxpbXMpIAp2b2xjYW5vQ1JEICsgCiAgeGxpbSh4bGltcykgKyAKICB5bGltKHlsaW1zKQpgYGAKCiMjIyBDb21wYXJpc29uIG9mIHN0YW5kYXJkIGRldmlhdGlvbiAKCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgY29kZSA8L3N1bW1hcnk+PHA+CmBgYHtyfQphY2Nlc3Npb25zIDwtIHJvd25hbWVzKHBlW1sicHJvdGVpbiJdXSlbcm93bmFtZXMocGVbWyJwcm90ZWluIl1dKSVpbiVyb3duYW1lcyhwZTJbWyJwcm90ZWluIl1dKV0KZGF0IDwtIGRhdGEuZnJhbWUoCnNpZ21hUkJDID0gc2FwcGx5KHJvd0RhdGEocGVbWyJwcm90ZWluIl1dKSRtc3Fyb2JNb2RlbHNbYWNjZXNzaW9uc10sIGdldFNpZ21hUG9zdGVyaW9yKSwKc2lnbWFDUkQgPC0gc2FwcGx5KHJvd0RhdGEocGUyW1sicHJvdGVpbiJdXSkkbXNxcm9iTW9kZWxzW2FjY2Vzc2lvbnNdLCBnZXRTaWdtYVBvc3RlcmlvcikKKQoKcGxvdFJCQ3ZzQ1JEIDwtIGdncGxvdChkYXRhID0gZGF0LCBhZXMoc2lnbWFSQkMsIHNpZ21hQ1JEKSkgKwogICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSwgc2hhcGUgPSAyMCkgKwogICAgc2NhbGVfeF9sb2cxMCgpICsKICAgIHNjYWxlX3lfbG9nMTAoKSArCiAgICBnZW9tX2FibGluZShpbnRlcmNlcHQ9MCxzbG9wZT0xKQpgYGAKPC9wPjwvZGV0YWlscz4KCmBgYHtyfQogIHBsb3RSQkN2c0NSRApgYGAKCgotIFdlIGNsZWFybHkgb2JzZXJ2ZSB0aGF0IHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgdGhlIHByb3RlaW4gZXhwcmVzc2lvbiBpbiB0aGUgUkNCIGlzIHNtYWxsZXIgZm9yIHRoZSBtYWpvcml0eSBvZiB0aGUgcHJvdGVpbnMgdGhhbiB0aGF0IG9idGFpbmVkIHdpdGggdGhlIENSRAoKLSBDYW4geW91IHRoaW5rIG9mIGEgcmVhc29uIHdoeSBpdCB3b3VsZCBub3QgYmUgdXNlZnVsIHRvIGJsb2NrIG9uIGEgcGFydGljdWxhciBmYWN0b3I/IAoKIyMgUHNldWRvLXJlcGxpY2F0aW9uCgotIFRoZSBGcmFuY2lzZWxsYSB0dWxhcmVuc2lzIHRoYXQgd2UgdXNlZCBiZWZvcmUgd2FzIGEgc3Vic2V0IG9mIHRoZSBkYXRhIG9mIFJhbW9uZCBldCBhbC4gCi0gVGhlIGF1dGhvcnMgaGF2ZSBydW4gdGhlIHNhbXBsZSBmb3IgZWFjaCBiaW8tcmVwIGluIHRlY2huaWNhbCB0cmlwbGljYXRlIG9uIHRoZSBtYXNzLXNwZWN0cm9tZXRlci4KCiMjIyBEYXRhCgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgdG8gaW1wb3J0IGRhdGEgPC9zdW1tYXJ5PjxwPgoKMS4gV2UgdXNlIGEgcGVwdGlkZXMudHh0IGZpbGUgZnJvbSBNUy1kYXRhIHF1YW50aWZpZWQgd2l0aCBtYXhxdWFudCB0aGF0IApjb250YWlucyBNUzEgaW50ZW5zaXRpZXMgc3VtbWFyaXplZCBhdCB0aGUgcGVwdGlkZSBsZXZlbC4gCmBgYHtyfQpwZXB0aWRlc1RhYmxlIDwtIGZyZWFkKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL01TcVJvYlN1bVBhcGVyL3JlZnMvaGVhZHMvbWFzdGVyL0ZyYW5jaXNlbGxhL2RhdGEvbWF4cXVhbnQvcGVwdGlkZXMudHh0IikKaW50NjQgPC0gd2hpY2goc2FwcGx5KHBlcHRpZGVzVGFibGUsY2xhc3MpID09ICJpbnRlZ2VyNjQiKQpmb3IgKGogaW4gaW50NjQpIHBlcHRpZGVzVGFibGVbW2pdXSA8LSBhcy5udW1lcmljKHBlcHRpZGVzVGFibGVbW2pdXSkgICAgICAgICAgICAgICAgICAgICAgCmBgYAoKMi4gTWF4cXVhbnQgc3RvcmVzIHRoZSBpbnRlbnNpdHkgZGF0YSBmb3IgdGhlIGRpZmZlcmVudCBzYW1wbGVzIGluIGNvbHVtbm5zIHRoYXQgc3RhcnQgd2l0aCBJbnRlbnNpdHkuIFdlIGNhbiByZXRyZWl2ZSB0aGUgY29sdW1uIG5hbWVzIHdpdGggdGhlIGludGVuc2l0eSBkYXRhIHdpdGggdGhlIGNvZGUgYmVsb3c6IAoKYGBge3J9CnF1YW50Q29scyA8LSBncmVwKCJJbnRlbnNpdHkgIiwgbmFtZXMocGVwdGlkZXNUYWJsZSkpCmBgYAoKNC4gUmVhZCB0aGUgZGF0YSBhbmQgc3RvcmUgaXQgaW4gIFFGZWF0dXJlcyBvYmplY3QgCgpgYGB7cn0KcGUgPC0gcmVhZFFGZWF0dXJlcygKICBhc3NheURhdGEgPSBwZXB0aWRlc1RhYmxlLAogIGZuYW1lcyA9IDEsCiAgcXVhbnRDb2xzID0gIHF1YW50Q29scywKICBuYW1lID0gInBlcHRpZGVSYXciKQpybShwZXB0aWRlc1RhYmxlKQpnYygpCmdjKCkKYGBgCgo1LiBVcGRhdGUgZGF0YSB3aXRoIGluZm9ybWF0aW9uIG9uIGRlc2lnbgoKYGBge3J9CmNvbERhdGEocGUpJGdlbm90eXBlIDwtIHBlW1sxXV0gfD4gCiAgY29sbmFtZXMoKSB8PiAKICBzdWJzdHIoMTIsMTMpIHw+CiAgYXMuZmFjdG9yKCkgfD4gCiAgcmVsZXZlbCgiV1QiKQoKY29sRGF0YShwZSkkYmlvcmVwIDwtIHBlW1sxXV0gfD4gCiAgY29sbmFtZXMoKSB8PiAKICBzdWJzdHIoMjIsMjIpCgpjb2xEYXRhKHBlKSRiaW9yZXAgPC0gcGFzdGUoY29sRGF0YShwZSkkZ2Vub3R5cGUsY29sRGF0YShwZSkkYmlvcmVwLHNlcD0iXyIpCiAgCnBlIHw+IGNvbERhdGEoKQpgYGAKCjwvcD48L2RldGFpbHM+CgojIyMgUHJlcHJvY2Vzc2luZwoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlIHRvIGxvZy10cmFuc2Zyb20gdGhlIGRhdGEgPC9zdW1tYXJ5PjxwPgoKMS4gTG9nIHRyYW5zZm9ybQoKICAtIFBlcHRpZGVzIHdpdGggemVybyBpbnRlbnNpdGllcyBhcmUgbWlzc2luZyBwZXB0aWRlcyBhbmQgc2hvdWxkIGJlIHJlcHJlc2VudAp3aXRoIGEgYE5BYCB2YWx1ZSByYXRoZXIgdGhhbiBgMGAuCgpgYGB7cn0KcGUgPC0gemVyb0lzTkEocGUsICJwZXB0aWRlUmF3IikgIyBjb252ZXJ0IDAgdG8gTkEKYGBgCgogIC0gTG9ndHJhbnNmb3JtIGRhdGEgd2l0aCBiYXNlIDIKCmBgYHtyfQpwZSA8LSBsb2dUcmFuc2Zvcm0ocGUsIGJhc2UgPSAyLCBpID0gInBlcHRpZGVSYXciLCBuYW1lID0gInBlcHRpZGVMb2ciKQpgYGAKCjIuIEZpbHRlcmluZwoKICAtIFdlIHJlbW92ZSBQU01zIHRoYXQgY291bGQgbm90IGJlIG1hcHBlZCB0byBhIHByb3RlaW4gb3IgdGhhdCBtYXAgdG8gbXVsdGlwbGUgcHJvdGVpbnMgKHRoZSBwcm90ZWluIGlkZW50aWZpZXIgY29udGFpbnMgbXVsdGlwbGUgaWRlbnRpZmllcnMgc2VwYXJhdGVkIGJ5IGEgYDtgKS4KCmBgYHtyfQpwZSA8LSBmaWx0ZXJGZWF0dXJlcygKICAgIHBlLCB+IFByb3RlaW5zICE9ICIiICYgIyMgUmVtb3ZlIGZhaWxlZCBwcm90ZWluIGluZmVyZW5jZQogICAgICAgICFncmVwbCgiOyIsIFByb3RlaW5zKSkgIyMgUmVtb3ZlIHByb3RlaW4gZ3JvdXBzCmBgYAoKICAtIFJlbW92ZSByZXZlcnNlIHNlcXVlbmNlcyAoZGVjb3lzKSBhbmQgY29udGFtaW5hbnRzLiBOb3RlIHRoYXQgdGhpcyBpcyBpbmRpY2F0ZWQgYnkgdGhlIGNvbHVtbiBuYW1lcyBSZXZlcnNlIGFuZCBkZXBlbmRpbmcgb24gdGhlIHZlcnNpb24gb2YgbWF4UXVhbnQgd2l0aCBQb3RlbnRpYWwuY29udGFtaW5hbnRzIG9yIENvbnRhbWluYW50cy4KCgpgYGB7cn0KcGUgPC0gZmlsdGVyRmVhdHVyZXMocGUsflJldmVyc2UgIT0gIisiKQpwZSA8LSBmaWx0ZXJGZWF0dXJlcyhwZSx+IENvbnRhbWluYW50ICE9ICIrIikKYGBgCgogIC0gRHJvcCBwZXB0aWRlcyB0aGF0IHdlcmUgaWRlbnRpZmllZCBpbiBsZXNzIHRoYW4gdGhyZWUgc2FtcGxlLiBXZSB0b2xlcmF0ZSB0aGUgZm9sbG93aW5nIHByb3BvcnRpb24gb2YgTkFzOiBwTkEgPSAobi0zKS9uLiAKCmBgYHtyfQpuT2JzIDwtIDMKbiA8LSBuY29sKHBlW1sicGVwdGlkZUxvZyJdXSkKcE5BIDwtIChuLW5PYnMpL24KcGUgPC0gZmlsdGVyTkEocGUsIHBOQSA9IHBOQSwgaSA9ICJwZXB0aWRlTG9nIikKbnJvdyhwZVtbInBlcHRpZGVMb2ciXV0pCmBgYAoKV2Uga2VlcCBgciBucm93KHBlW1sicGVwdGlkZUxvZyJdXSlgIHBlcHRpZGVzIHVwb24gZmlsdGVyaW5nLgoKMy4gTm9ybWFsaXphdGlvbiBieSBtZWRpYW4gY2VudGVyaW5nCgpgYGB7cn0KcGUgPC0gbm9ybWFsaXplKHBlLCAKICAgICAgICAgICAgICAgIGkgPSAicGVwdGlkZUxvZyIsIAogICAgICAgICAgICAgICAgbmFtZSA9ICJwZXB0aWRlTm9ybSIsIAogICAgICAgICAgICAgICAgbWV0aG9kID0gImNlbnRlci5tZWRpYW4iKQpgYGAKCgo0LiBTdW1tYXJpemF0aW9uLiBXZSB1c2UgdGhlIHN0YW5kYXJkIHN1bWFyaXNhdGlvbiBpbiBhZ2dyZWdhdGVGZWF0dXJlcywgd2hpY2ggaXMgYQpyb2J1c3Qgc3VtbWFyaXNhdGlvbiBtZXRob2QuCgpgYGB7cix3YXJuaW5nPUZBTFNFfQpwZSA8LSBhZ2dyZWdhdGVGZWF0dXJlcyhwZSwKICAgIGkgPSAicGVwdGlkZU5vcm0iLCAKICAgIGZjb2wgPSAiUHJvdGVpbnMiLCAKICAgIG5hLnJtID0gVFJVRSwKICAgIG5hbWUgPSAicHJvdGVpbiIpCmBgYAoKPC9wPjwvZGV0YWlscz4KCmBgYHtyfQptc3Fyb2IyZ3VpOjo6cGxvdFBDQShwZSwgInByb3RlaW4iLCAiYmlvcmVwIikKYGBgCgoKLSBSZXNwb25zZT8KLSBFeHBlcmltZW50YWwgdW5pdD8KLSBPYnNlcnZhdGlvbmFsIHVuaXQ/Ci0gRmFjdG9ycz8KCmBgYHtyfQpWaXN1YWxpemVEZXNpZ24oY29sRGF0YShwZSksfiBnZW5vdHlwZSArIGJpb3JlcCkkcGxvdGxpc3QKYGBgCgokXHJpZ2h0YXJyb3ckIFBzZXVkby1yZXBsaWNhdGlvbiwgcmFuZG9taXNhdGlvbiB0byBiaW8tcmVwZWF0IGFuZCBlYWNoIGJpby1yZXBlYXQgbWVhc3VyZWQgaW4gdGVjaG5pY2FsIHRyaXBsaWNhdGUuCiRccmlnaHRhcnJvdyQgSWYgd2Ugd291bGQgYW5hbHlzZSB0aGUgZGF0YSB1c2luZyBhIGxpbmVhciBtb2RlbCBiYXNlZCBvbiBlYWNoIG1lYXN1cmVkIGludGVuc2l0eSwgd2Ugd291bGQgYWN0IGFzIGlmIHdlIGhhZCBzYW1wbGVkIDE4IGJpby1yZXBlYXRzLgokXHJpZ2h0YXJyb3ckIEVmZmVjdCBvZiBpbnRlcmVzdCBoYXMgdG8gYmUgYXNzZXNzZWQgYmV0d2VlbiBiaW8tcmVwZWF0cy4gU28gYmxvY2sgYW5hbHlzaXMgaXMgbm90IHBvc3NpYmxlIChpZiB3ZSBjb25kaXRpb24gb24gYmlvLXJlcGVhdCwgd2UgY29uZGl0aW9uIG9uIHRoZSBnZW5vdHlwZSkhCgoKIyMjIFdyb25nIGFuYWx5c2lzCgoKYGBge3Igd2FybmluZz1GQUxTRX0KcGUgPC0gbXNxcm9iKG9iamVjdCA9IHBlLCBpID0gInByb3RlaW4iLCBmb3JtdWxhID0gfmdlbm90eXBlLCBtb2RlbENvbHVtbk5hbWUgPSAid3JvbmciLCBvdmVyd3JpdGUgPSBUUlVFKQpMIDwtIG1ha2VDb250cmFzdCgiZ2Vub3R5cGVEOCA9IDAiLCBwYXJhbWV0ZXJOYW1lcyA9IGMoImdlbm90eXBlRDgiKSkKcGUgPC0gaHlwb3RoZXNpc1Rlc3Qob2JqZWN0ID0gcGUsIGkgPSAicHJvdGVpbiIsIGNvbnRyYXN0ID0gTCwgbW9kZWxDb2x1bW4gPSAid3JvbmciLCAgIHJlc3VsdHNDb2x1bW5OYW1lUHJlZml4ID0gIndyb25nXyIsIG92ZXJ3cml0ZSA9IFRSVUUpCgp2b2xjYW5vV3JvbmcgPC0gZ2dwbG90KAogICAgcm93RGF0YShwZVtbInByb3RlaW4iXV0pJHdyb25nX2dlbm90eXBlRDgsCiAgICBhZXMoeCA9IGxvZ0ZDLCB5ID0gLWxvZzEwKHB2YWwpLCBjb2xvciA9IGFkalB2YWwgPCAwLjA1KQopICsKICAgIGdlb21fcG9pbnQoY2V4ID0gMi41KSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYWxwaGEoYygiYmxhY2siLCAicmVkIiksIDAuNSkpICsKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICBnZ3RpdGxlKHBhc3RlMCgid3JvbmcgYW5hbHlzaXM6ICIsc3VtKHJvd0RhdGEocGVbWyJwcm90ZWluIl1dKSR3cm9uZ19nZW5vdHlwZUQ4JGFkalB2YWwgPDAuMDUsIG5hLnJtPVRSVUUpLCIgREEiKSkKCnZvbGNhbm9Xcm9uZwpgYGAKCgpOb3RlLCB0aGF0IHRoZSBhbmFseXNpcyB3aGVyZSB3ZSBpZ25vcmUgdGhhdCB3ZSBoYXZlIG11bHRpcGxlIHRlY2huaWNhbCByZXBlYXRzIGZvciBlYWNoIGJpby1yZXBlYXQgcmV0dXJucyBtYW55IHNpZ25pZmljYW50IERBIHByb3RlaW5zIGJlY2F1c2Ugd2UgYWN0IGFzIGlmIHdlIGhhdmUgbXVjaCBtb3JlIGluZGVwZW5kZW50IG9ic2VydmF0aW9ucy4KCiMjIyBDb3JyZWN0IGFuYWx5c2lzCgotIE1peGVkIE1vZGVscyBjYW4gbW9kZWwgdGhlIGNvcnJlbGF0aW9uIHN0cnVjdHVyZSBpbiB0aGUgZGF0YQoKLSBUaGV5IGNhbiBhY2tub3dsZWRnZSB0aGF0IHByb3RlaW4gZXhwcmVzc2lvbiB2YWx1ZXMgZnJvbSBydW5zIGZyb20gdGhlIHNhbWUgYmlvbG9naWNhbCByZXBlYXQgYXJlIG1vcmUgYWxpa2UgdGhhbiBwcm90ZWluIGV4cHJlc3Npb24gdmFsdWVzIGZyb20gcnVucyBvZiBkaWZmZXJlbnQgYmlvbG9naWNhbCByZXBlYXRzLiAKCi0gTWl4ZWQgbW9kZWxzIGNhbiBhbHNvIGJlIHVzZWQgd2hlbiBpbmZlcmVuY2UgYmV0d2VlbiBhbmQgd2l0aGluIGJsb2NrcyBpcyBuZWVkZWQuIAoKLSBGb3IgdGhlIGZyYW5jaXNlbGxhIGV4YW1wbGUgdGhlIGZvcm11bGEgaW4gbXNxcm9iMiBiZWNvbWVzOiAoZm9ybXVsYTogfiBnZW5vdHlwZSArICgxfGJpb3JlcCkpLiAKCiQkIFxsZWZ0XHtcYmVnaW57YXJyYXl9e3JjbH0KeV97aXJ9ICY9JiBcYmV0YV8wICsgXGJldGFfe3d0fVhfe3d0LGl9ICsgYl9pICsgXGVwc2lsb25fe2lyfVxcCmJfaSAmIFxzaW0gJiBOKDAsXHNpZ21hX2JeMilcXApcZXBzaWxvbl97aXJ9ICZcc2ltJiBOKDAsXHNpZ21hX1xlcHNpbG9uXjIpClxlbmR7YXJyYXl9XHJpZ2h0LiQkCgoKCmBgYHtyfQpwZSA8LSBtc3Fyb2Iob2JqZWN0ID0gcGUsIGkgPSAicHJvdGVpbiIsIGZvcm11bGEgPSB+Z2Vub3R5cGUgKyAoMXxiaW9yZXApLCBvdmVyd3JpdGUgPSBUUlVFKQpMIDwtIG1ha2VDb250cmFzdCgiZ2Vub3R5cGVEOCA9IDAiLCBwYXJhbWV0ZXJOYW1lcyA9IGMoImdlbm90eXBlRDgiKSkKcGUgPC0gaHlwb3RoZXNpc1Rlc3Qob2JqZWN0ID0gcGUsIGkgPSAicHJvdGVpbiIsIGNvbnRyYXN0ID0gTCwgb3ZlcndyaXRlID0gVFJVRSkKCnZvbGNhbm9NaXhlZCA8LSBnZ3Bsb3QoCiAgICByb3dEYXRhKHBlW1sicHJvdGVpbiJdXSkkZ2Vub3R5cGVEOCwKICAgIGFlcyh4ID0gbG9nRkMsIHkgPSAtbG9nMTAocHZhbCksIGNvbG9yID0gYWRqUHZhbCA8IDAuMDUpCikgKwogICAgZ2VvbV9wb2ludChjZXggPSAyLjUpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBhbHBoYShjKCJibGFjayIsICJyZWQiKSwgMC41KSkgKwogICAgdGhlbWVfbWluaW1hbCgpICsKICAgIGdndGl0bGUocGFzdGUwKCJjb3JyZWN0IGFuYWx5c2lzOiAiLHN1bShyb3dEYXRhKHBlW1sicHJvdGVpbiJdXSkkZ2Vub3R5cGVEOCRhZGpQdmFsIDwwLjA1LCBuYS5ybT1UUlVFKSwiIERBIikpCnZvbGNhbm9NaXhlZApgYGAKCi0gTm90ZSwgdGhhdCB0aGUgc3RhdGlzdGljYWwgaW5mZXJlbmNlIHdpdGggbWl4ZWQgbW9kZWxzIGlzIHN0aWxsIGEgYml0IHRvbyBsaWJlcmFsIGJlY2F1c2UgaXQ6IAogICAgLSBoeXBvdGhlc2lzIHRlc3RzIGFyZSBvbmx5IHZhbGlkIGFzeW1wdG90aWNhbGx5IChsYXJnZSBudW1iZXIgb2YgYmlvcmVwZWF0cykuCiAgICAtIGluIHNtYWxsIHNhbXBsZXMgdGhlIGJldHdlZW4gYmlvcmVwZWF0IHZhcmlhbmNlIGlzIHNvbWV0aW1lcyBlc3RpbWF0ZWQgdG8gYmUgemVybyBkdWUgdG8gZXN0aW1hdGlvbiB1bmNlcnRhaW50eSBhbmQgZm9yIHRoZXNlIHByb3RlaW5zIHRoZSBtb2RlbCBzdGlsbCBhY3RzIGFzIGlmIGFsbCAxOCBwcm90ZWluIGV4cHJlc3Npb24gdmFsdWVzIGFyZSBpbmRlcGVuZGVudC4gCiAgICAKLSBNaXhlZCBtb2RlbHMgYXJlIGFsc28gdmVyeSB1c2VmdWwgdG8gYW5hbHlzZSBkYXRhIGZyb20gbGFyZ2UgbGFiZWxlZCBleHBlcmltZW50cyBbQHZhbmRlbmJ1bGNrZTIwMjVdLiAKCi0gQnV0LCB0aGV5IGFyZSBiZXlvbmQgdGhlIHNjb3BlIG9mIHRoZSBsZWN0dXJlIHNlcmllcy4gCgpDT05TVUxUIEJJT1NUQVRJU1RJQ0lBTiBJTiBDQVNFIE9GIEVYUEVSSU1FTlRTIFdJVEggUFNFVURPLVJFUExJQ0FURVMsIFRFQ0hOSUNBTCBSRVBFQVRTLCBDT01QTEVYIERFU0lHTlMsIC4uLgoKIyBTb2Z0d2FyZSAmIGNvZGUKCi0gT3VyIFIvQmlvY29uZHVjdG9yIHBhY2thZ2UgW21zcXJvYjJdKGh0dHBzOi8vd3d3LmJpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvcmVsZWFzZS9iaW9jL2h0bWwvbXNxcm9iMi5odG1sKSBjYW4gYmUgdXNlZCBpbiBSIG1hcmtkb3duIHNjcmlwdHMgb3Igd2l0aCBHVUkvc2hpbnlBcHBzIFtRRmVhdHVyZXNHVUldKGh0dHBzOi8vZ2l0aHViLmNvbS9zdGF0T21pY3MvUUZlYXR1cmVzR1VJKSAoKykgYW5kIFttc3Fyb2IyZ3VpXShodHRwczovL2dpdGh1Yi5jb20vc3RhdE9taWNzL21zcXJvYjJndWkpICgrOiBmb3JrZWQgZnJvbSB0aGUgVUNMb3V2YWluLUNCSU8gbGFiKS4KCi0gR1VJcyBhcmUgaW50ZW5kZWQgYXMgYSBpbnRyb2R1Y3Rpb24gdG8gdGhlIGtleSBjb25jZXB0cyBvZiBwcm90ZW9taWNzIGRhdGEgYW5hbHlzaXMgZm9yIHVzZXJzIHdobyBoYXZlIG5vIGV4cGVyaWVuY2UgaW4gUi4gCgotIEhvd2V2ZXIsIGxlYXJuaW5nIGhvdyB0byBjb2RlIGRhdGEgYW5hbHlzZXMgaW4gUiBtYXJrZG93biBzY3JpcHRzIGlzIGtleSBmb3Igb3BlbiBlbiByZXByb2R1Y2libGUgc2NpZW5jZSBhbmQgZm9yIHJlcG9ydGluZyB5b3VyIHByb3Rlb21pY3MgZGF0YSBhbmFseXNlcyBhbmQgaW50ZXJwcmV0YXRpb24gaW4gYSByZXByb2R1Y2libGUgd2F5LiAKCgotIE1vcmUgaW5mb3JtYXRpb24gb24gb3VyIHRvb2xzIGNhbiBiZSBmb3VuZCBpbiBvdXIgcGFwZXJzIFtAZ29lbWlubmUyMDE2XSwgW0Bnb2VtaW5uZTIwMjBdLCBbQHN0aWNrZXIyMDIwXSBhbmQgW0B2YW5kZW5idWxja2UyMDI1XS4gUGxlYXNlIHJlZmVyIHRvIG91ciB3b3JrIHdoZW4gdXNpbmcgb3VyIHRvb2xzLiAKCjwhLS0KLSBDbGlwcyBvbiB0aGUgY29kZSBvbiBpbXBvcnRpbmcgdGhlIGRhdGEgYW5kIHByZXByb2Nlc3NpbmcgY2FuIGJlIGZvdW5kIGluIFtQYXJ0IEkgUHJlcHJvY2Vzc2luZ10oLi9wZGFfcXVhbnRpZmljYXRpb25fcHJlcHJvY2Vzc2luZy5odG1sKQoKLSBBIGNsaXAgb24gdGhlIGNvZGUgZm9yIG1vZGVsbGluZyBhbmQgc3RhdGlzdGljYWwgaW5mZXJlbmNlIHdpdGggbXNxcm9iMiBpcyBpbmNsdWRlZCBiZWxvdwoKPGlmcmFtZSB3aWR0aD0iNTYwIiBoZWlnaHQ9IjMxNSIKc3JjPSJodHRwczovL3d3dy55b3V0dWJlLmNvbS9lbWJlZC9lWHhJZHpHT1BnWSIKZnJhbWVib3JkZXI9IjAiCnN0eWxlPSJkaXNwbGF5OiBibG9jazsgbWFyZ2luOiBhdXRvOyIKYWxsb3c9ImF1dG9wbGF5OyBlbmNyeXB0ZWQtbWVkaWEiIGFsbG93ZnVsbHNjcmVlbj48L2lmcmFtZT4KLS0+IAoKIyBSZWZlcmVuY2VzCg==