In this lecture we will start working with a real bulk RNA-seq dataset from Haglund et al. (2012). After importing the data, we will be working our way through four major challenges which, together, will form a full RNA-seq differential expression (DE) analysis pipeline where the result of our analysis will be a(n ordered) list of genes that we find to be differently expressed between our conditions of interest. The four main challenges we will look into are

  • Choice of modeling assumptions (distribution).
  • Normalization.
  • Parameter estimation under a limited information setting.
  • Statistical inference under high dimensionality (many genes).

1 Experimental design, data import and data exploration

1.1 Experimental design

Let’s try to work out the experimental design using the following paragraph from the Methods section of the paper.

Figure: A paragraph from the Methods section.

Figure: A paragraph from the Methods section.

1.2 Data import and exploration

We will be importing the dataset using the parathyroidSE data package from Bioconductor.

if (!requireNamespace("BiocManager", quietly = TRUE)){
  install.packages("BiocManager")
}
if(!"SummarizedExperiment" %in% installed.packages()[,1]){
  BiocManager::install("SummarizedExperiment")
} 
# install package if not installed.
if(!"parathyroidSE" %in% installed.packages()[,1]) BiocManager::install("parathyroidSE")

suppressPackageStartupMessages({
  library(parathyroidSE)
  library(SummarizedExperiment)
})

# import data
data("parathyroidGenesSE", package="parathyroidSE")
# rename for convenience
se1 <- parathyroidGenesSE
rm(parathyroidGenesSE)

# three treatments
treatment1 <- colData(se1)$treatment
table(treatment1)
## treatment1
## Control     DPN     OHT 
##       7      10      10
# two timepoints
time1 <- colData(se1)$time
table(time1)
## time1
## 24h 48h 
##  13  14
# four donor patients
patient1 <- colData(se1)$patient
table(patient1)
## patient1
## 1 2 3 4 
## 6 8 6 7
table(patient1, treatment1, time1)
## , , time1 = 24h
## 
##         treatment1
## patient1 Control DPN OHT
##        1       1   1   1
##        2       1   2   2
##        3       1   1   1
##        4       0   1   1
## 
## , , time1 = 48h
## 
##         treatment1
## patient1 Control DPN OHT
##        1       1   1   1
##        2       1   1   1
##        3       1   1   1
##        4       1   2   2
  • We observe that the number of samples that we are observing here is larger than what is described in the paper. As also described in the parathyroidSE vignette, some samples were spread over multiple sequencing runs (i.e., the same sample being sequenced repeatedly) and therefore constitute technical replication, rather than biological replication.
  • We have previously seen that technical replicates can be considered to be distributed according to a Poisson distribution. One important property of Poisson random variables is that a sum of Poisson random variables still follow a Poisson distribution. Indeed, if \(X \sim Poi(\mu_X)\) and \(Y \sim Poi(\mu_Y)\), then \(X + Y = Z \sim Poi(\mu_X + \mu_Y)\).
  • For this reason, it is often suggested to sum technical replicates rather than, for example, averaging, which does not retain the Poisson property (try for yourself!). We’ll therefore first sum the technical replicates.
dupExps <- as.character(colData(se1)$experiment[duplicated(colData(se1)$experiment)])
dupExps
## [1] "SRX140511" "SRX140513" "SRX140523" "SRX140525"
counts <- assays(se1)$counts
newCounts <- counts
cd <- colData(se1)
for(ss in 1:length(dupExps)){
  # check which samples are duplicates
  relevantId <- which(colData(se1)$experiment == dupExps[ss])
  # sum counts
  newCounts[,relevantId[1]] <- rowSums(counts[,relevantId])
  # keep which columns / rows to remove.
  if(ss == 1){
    toRemove <- relevantId[2]
  } else {
    toRemove <- c(toRemove, relevantId[2])
  }
}

# remove after summing counts (otherwise IDs get mixed up)
newCounts <- newCounts[,-toRemove]
newCD <- cd[-toRemove,]

# Create new SummarizedExperiment
se <- SummarizedExperiment(assays = list("counts" = newCounts),
                            colData = newCD,
                            metadata = metadata(se1))

treatment <- colData(se)$treatment
table(treatment)
## treatment
## Control     DPN     OHT 
##       7       8       8
time <- colData(se)$time
table(time)
## time
## 24h 48h 
##  11  12
patient <- colData(se)$patient
table(patient)
## patient
## 1 2 3 4 
## 6 6 6 5
table(patient, treatment, time) # agrees with paper.
## , , time = 24h
## 
##        treatment
## patient Control DPN OHT
##       1       1   1   1
##       2       1   1   1
##       3       1   1   1
##       4       0   1   1
## 
## , , time = 48h
## 
##        treatment
## patient Control DPN OHT
##       1       1   1   1
##       2       1   1   1
##       3       1   1   1
##       4       1   1   1
  • After summing the technical replicates and appropriately updating the sample information, we again create a SummarizedExperiment object, which is essentially a data container that contains all relevant information about your experiment. Please see the vignette for more information on how to use this class.
  • By directly matching columns (samples) and rows (genes) to their relevant metadata, the SummarizedExperiment class avoids mistakes by mis-matching columns and rows with each other (provided you haven’t mismatched them when you create the object).
  • The SummarizedExperiment class is modular and extendable, and extensions exist for example for the analysis of single-cell RNA-seq data, i.e., the SingleCellExperiment class.
  • Due to their convenient organization and widely supported usage within Bioconductor, we will typically work with such classes in the analysis of RNA-seq data.

1.3 Independent filtering

Independent filtering is a strategy to remove features (in this case, genes) prior to the analysis. Removal of these features may lower the multiple testing correction for other genes that pass the filter. We try to remove genes that have a low power to be found statistically significant, and/or that are biologically less relevant. A common filtering strategy is to remove genes with a generally low expression, as low counts have lower relative uncertainty (hence lower statistical power), and may be considered biologically less relevant.

suppressPackageStartupMessages({
  library(limma)
  library(edgeR)
})

keep <- rowSums(cpm(se) > 2) >= 3
table(keep)
## keep
## FALSE  TRUE 
## 47837 15356
se <- se[keep,]

1.4 Data exploration

# library size distribution
hist(colSums(assays(se)$counts)/1e6, breaks=10)

boxplot(colSums(assays(se)$counts)/1e6 ~ treatment)

boxplot(colSums(assays(se)$counts)/1e6 ~ time)

boxplot(colSums(assays(se)$counts)/1e6 ~ patient)

boxplot(colSums(assays(se)$counts)/1e6 ~ interaction(treatment, time))

# MDS plot
plotMDS(se, 
        labels = treatment, 
        col=as.numeric(patient))

## hard to see influence of experimental factors due to large between-patient variation
## we could also make an MDS plot per patient to take a look.
for(kk in 1:4){
  id <- which(patient == kk)
  plotMDS(se[,id], 
        labels = paste0(treatment[id],"_",time[id]), 
        col=as.numeric(time[id]))
}

Observations based on MDS plot:

  • There is a very large between-patient variability, which is the major source of variation in this dataset. The samples from each patient cluster together tightly.
  • Within patient, time consistently explains more variation than the treatments.
  • Relative to patients and time, the treatment seems to have a fairly small effect.

2 Challenge I: Choice of modeling assumptions

When working with a GLM, as part of the choices of modeling assumptions, we need to pick an appropriate distribution for the expression counts. Below we perform some exploratory analyses to investigate.

y <- assays(se)$counts[1,]
hist(y, breaks = 40,
     xlab = "Gene expression",
     xaxt = "n", yaxt = "n",
     main = "Data for the first gene")
axis(1, at = seq(200, 1200, by=200))
axis(2, at = 0:3)

# Mean-variance trend within each experimental condition
cont24ID <- which(treatment == "Control" & time == "24h")
DPN24ID <- which(treatment == "DPN" & time == "24h")
OHT24ID <- which(treatment == "OHT" & time == "24h")
cont48ID <- which(treatment == "Control" & time == "48h")
DPN48ID <- which(treatment == "DPN" & time == "48h")
OHT48ID <- which(treatment == "OHT" & time == "48h")
idList <- list(cont24ID, DPN24ID, OHT24ID, 
               cont48ID, DPN48ID, OHT48ID)
names(idList) <- paste0(rep(levels(treatment),2), rep(levels(time), each=3))

par(mfrow=c(3,2), mar=c(2,2,2,1))
for(kk in 1:length(idList)){
  # extract counts for each condition
  curCounts <- assays(se)$counts[,idList[[kk]]]
  plot(x = rowMeans(curCounts)+1,
       y = rowVars(curCounts)+1,
       pch = 16, cex=1/2,
       xlab = "Mean", ylab="Variance",
       main = names(idList)[kk],
       log="xy")
  abline(0,1, col="red")
  lw1 <- loess((rowVars(curCounts)+1) ~ (rowMeans(curCounts)+1), span=1/4, lwd=3)
  oo <- order(rowMeans(curCounts)+1)
  lines(rowMeans(curCounts)[oo]+1, lw1$fitted[oo], col="orange")

  smoothScatter(x = log1p(rowMeans(curCounts)),
       y = log1p(rowVars(curCounts)),
       pch = 16, cex=1/2,
       xlab = "Mean", ylab="Variance")
  abline(0,1, col="red")
  lines(log(rowMeans(curCounts)[oo]+1), log(lw1$fitted[oo]+1), col="orange")
}

  • Having data on thousands of genes provides the opportunity to empirically assess the mean-variance relationship.
  • It is clear that the data is overdispersed with respect to the Poisson distribution (red \(y=x\) line). There also seems to be a quadratic trend of the variance as a function of the mean. This has motivated the negative binomial distribution as the most popular choice to model (bulk) RNA-seq gene expression data.

  • The negative binomial distribution is also referred to as the Gamma-Poisson distribution as it can be formulated as such. Indeed, if \[ \lambda \sim \Gamma(\alpha, \beta) \\ Y | \lambda \sim Poi(\lambda), \] then this is equivalent to \[ Y \sim NB(\mu = \alpha / \beta, \phi = 1/\alpha). \]
  • This can be shown analytically, but is considered out of the scope of this course. Below, we show it empirically using simulation.
  • This theoretical result has got some practical consequences. The Gamma-Poisson formulation makes it clear why we can sum technical replicates as the sum of Poisson random variables is again a Poisson random variable.
  • The Poisson statement can thus be considered as capturing technical variation, while the Gamma statement can be considered to capture biological variation, i.e., variation in the mean expression across biological replicates.
alpha <- 20
beta <- 10
lambda <- rgamma(n = 1e5, shape = alpha, rate = beta)
y1 <- rpois(n = 1e5, lambda = lambda)

# note phi = 1 / size
y2 <- rnbinom(n=1e5, mu=alpha / (beta), size=alpha)


plot(density(y1))
lines(density(y2), col="steelblue")

3 Challenge II: Normalization

Normalization is necessary to correct for several sources of technical variation:

  • Differences in sequencing depth between samples. Some samples get sequenced deeper in the sense that they consist of more (mapped) reads and therefore can be considered to contain a higher amount of information, which we should be taking into account. In addition, if a sample is sequenced deeper, it is natural that the counts for each gene will be higher, jeopardizing a direct comparison of the expression counts.
  • Differences in RNA population composition between samples. As an extreme example, suppose that two samples have been sequenced to the exact same depth. One sample is contaminated and has a very high concentration of the contaminant cDNA being sequenced, but otherwise the two samples are identical. Since the contaminant will be taking up a significant proportion of the reads being sequenced, the counts will not be directly comparable between the samples. Hence, we may also want to correct for differences in the composition of the RNA population of the samples.
  • Other technical variation such as sample-specific GC-content or transcript length effects may also be accounted for.

Let’s take a look at how comparable different replicates are in the Control condition at 48h in our dataset. We will investigate this using MD-plots (mean-difference plots as introduced by Dudoit et al. (2002)), also sometimes referred to as MA-plots.

cont48ID # relevant samples
## [1]  2  8 14 19
colSums(assays(se)$counts[,cont48ID]) / 1e6
## [1] 10.801193  6.828259  8.038079  7.678421
combs <- combn(cont48ID, m=2) #pairwise combinations between samples

par(mfrow=c(3,2), mar=c(4,4,2,1))
for(cc in 1:ncol(combs)){
  curSamples <- combs[,cc]
  M <- rowMeans(assays(se)$counts[,curSamples])
  D <- assays(se)$counts[,curSamples[2]] / assays(se)$counts[,curSamples[1]]
  plot(x = log(M), y = log2(D),
       pch = 16, cex=1/3,
       main = paste0("Sample ", curSamples[2], " vs sample ", curSamples[1]),
       xlab = "Log mean", ylab = "Log2 fold-change",
       bty = 'l')
  abline(h = 0, col="orange", lwd=2)
}

  • We see clear bias for some pairwise comparisons. For example, in the first plot comparing sample 8 versus sample 2, the log fold-changes are biased downwards. This means that, on average, a gene is lower expressed in sample 8 versus sample 2. Looking at the library sizes, we can indeed see that the library size for sample 2 is about \(11 \times 10^6\) while it is only about \(7 \times 10^6\) for sample 8! This is a clear library size effect that we should take into account.

3.1 Count scaling versus GLM offsets

  • We have previously discussed count scaling transformations such as CPM and TPM.
  • A more appropriate and natural way when working with GLMs is through the use of offsets. The general use of an offset is to account for the ‘effort’ performed in order to gather that observation of the response variable. Two examples:
    1. A biologist studying whale migration has one fixed spot where, in the migration season, she counts migrating whales day after day, over several years. For each day she records the number of spotted whales. Of course, the time spent whale-watching may differ from day to day and it is natural that you are more likely to spot more whales if you spend more time looking for them. The time spent spotting whales can then be used as an offset.
    2. In our case, a sample being sequenced deeper contains more information, i.e., more ‘effort’ has been performed, as compared to a sample being sequenced relatively shallow. We have more confidence of a count from a deeply sequenced sample than from a shallowly sequenced sample. We can therefore use the sequencing depth \(N_i = \sum_g Y_{gi}\) as offset in the model.
  • Adding an offset to the model is different from adding a new variable to the model. For each new variable we add, we will estimate its average effect \(\beta\) on the response variable. When adding an offset, however, we are implicitly assuming that \(\beta=1\).
  • Offsets are typically added on the scale of the linear predictor. Suppose we have a gene \(g\) and sample \(i\) specific offset \(O_{gi}\), then we can define a negative binomial GLM including the offset as \[ \left\{ \begin{array}{ccc} Y_{gi} & \sim & NB(\mu_{gi}, \phi_g) \\ \log \mu_{gi} & = & \eta_{gi} \\ \eta_{gi} & = & \mathbf{X}^T_i \beta_g + \log(O_{gi}). \\ \end{array} \right. \]
  • Please read this page for an intuitive reasoning as to why offsets are preferred over count scaling.

3.2 How to normalize?

Many approaches are available for normalizing RNA-seq data, and we will review a couple of these. Most methods calculate an offset that is used in the GLM used to model gene expression. One notable method, full-quantile normalization, does not calculate an offset, and rather normalizes counts directly, immediately enforcing the same sequencing depth for all samples.

3.2.1 TMM method (default of edgeR)

The trimmed mean of M-values (TMM) method introduced by Robinson & Oshlack (2010) is a normalization procedure that calculates a single normalization factor for each sample. As the name suggests, it is based on a trimmed mean of fold-changes (\(M\)-values) as the scaling factor. A trimmed mean is an average after removing a set of ‘extreme’ values. Specifically, TMM calculates a normalization factor \(F_i^{(r)}\) across genes \(g\) for each sample \(i\) as compared to a reference sample \(r\), \[ \log_2(F_i^{(r)}) = \frac{\sum_{g \in {\cal G}^*} w_{gi}^r M_{gi}^r}{\sum_{g \in {\cal G}^*} w_{gi}^r}, \] where \(M_{gi}^r\) represents the \(\log_2\)-fold-change of the gene expression fraction as compared to a reference sample \(r\), i.e., \[ M_{gi}^r = \log_2\left( \frac{Y_{gi} / N_i}{ Y_{gr} / N_r} \right), \] and \(w_{gi}^r\) represents a precision weight calculated as \[ w_{gi}^r = \frac{N_i - Y_{gi}}{N_i Y_{gi}} + \frac{N_r - Y_{gr}}{N_r Y_{gr}}, \] and \({\cal G}^*\) represents the set of genes after trimming those with the most extreme average expression and fold-change. The weights serve to account for the fact that fold-changes for genes with lower read counts are more variable.

The procedure only takes genes into account where both \(Y_{gi}>0\) and \(Y_{gr}>0\). By default, TMM trims genes with the \(30\%\) most extreme \(M\)-values and \(5\%\) most extreme average gene expression, and chooses as reference \(r\) the sample whose upper-quartile is closest to the across-sample average upper-quartile. The normalization factor is then used in conjunction with the library size to calculate an effective library size \[ N_i^{eff} = N_i F_i^{(r)} \] which is used as offset in the GLM. The normalized counts may be given by \(\tilde{Y}_{gi} = Y_{gi} / N_i^s\), with size factor \[N_i^s = \frac{N_i F_i^{(r)}}{\frac{1}{n}\sum_{i=1}^n N_i F_i^{(r)}}.\]

TMM normalization may be performed from the calcNormFactors function implemented in edgeR:

dge <- edgeR::calcNormFactors(se)
dge$samples #normalization factors added to colData

Let’s check how our MD-plots look like after normalization. Note that, we can rewrite the GLM as \[ \log\left( \frac{\mu_{gi}}{N_i^s} \right) = \mathbf{X}_i^T \beta_g \] and so \(\frac{\mu_{gi}}{N_i^s}\) can be considered as an ‘offset-corrected average count’.

We see that all MD-plots are now nicely centered around a log-fold-change of zero!

## normalize
effLibSize <- dge$samples$lib.size * dge$samples$norm.factors
normCountTMM <- sweep(assays(se)$counts, 2, FUN="/", effLibSize)


par(mfrow=c(3,2), mar=c(4,4,2,1))
for(cc in 1:ncol(combs)){
  curSamples <- combs[,cc]
  M <- rowMeans(normCountTMM[,curSamples])
  D <- normCountTMM[,curSamples[2]] / normCountTMM[,curSamples[1]]
  plot(x = log(M), y = log2(D),
       pch = 16, cex=1/3,
       main = paste0("Sample ", curSamples[2], " vs sample ", curSamples[1]),
       xlab = "Log mean", ylab = "Log2 fold-change",
       bty = 'l')
  abline(h = 0, col="orange", lwd=2)
}

3.3 Median-of-ratios method (default of DESeq2)

The median-of-ratios method is used in DESeq2 as described in Love et al. (2014). It assumes that the expected value \(\mu_{gi} = E(Y_{gi})\) is proportional to the true expression of the gene, \(q_{gi}\), scaled by a size factor \(s_{i}\) for each sample, \[ \mu_{gi} = s_{i}q_{gi}. \]

The size factor \(s_{i}\) is then estimated using the median-of-ratios method compared to a synthetic reference sample \(r\) defined based on geometric means of counts across samples \[ s_i = \text{median}_{\{{g:Y^{*}_{gr} \ne 0}\}} \frac{Y_{gi}}{Y^{*}_{gr}}, \] with \[ Y^{*}_{gr} = \left( \prod_{i=1}^n Y_{gi} \right)^{1/n}. \]

We can then use the size factors \(s_i\) as offsets to the GLM.

Question. Do you see any issues with the procedure described as such?

Answer.

The procedure relies on genes being expressed in all samples. As sample size increases, the number of genes with this property steadily decreases. This will become crucial in single-cell RNA-seq data analysis.

Median-of-ratios normalization is implemented in the DESeq2 package:

dds <- DESeq2::DESeqDataSetFromMatrix(countData = assays(se)$counts,
                                      colData = colData(se),
                                      design = ~ 1) #just add intercept to showcase normalization
## converting counts to integer mode
dds <- DESeq2::estimateSizeFactors(dds)
sizeFactors(dds)
##  [1] 0.9166999 1.0640199 0.5224518 0.9806075 0.5664996 0.7802413 0.8282554
##  [8] 0.6592136 2.3949029 0.7898898 2.2781598 0.7858861 0.7775688 0.8399826
## [15] 1.3311498 1.6977165 2.4949218 0.7875376 0.8106158 0.7607974 1.4476678
## [22] 0.6529423 1.6653456

You may also want to check out the StatQuest video on DESeq2 normalization.

3.3.1 Comparing TMM with DESeq2 normalization

We can compare the size factors for both normalizations to verify if they agree on the normalization factors. Note we need to scale the effective library sizes from edgeR to enforce a similar scale as the size factors from DESeq2. While below we are using an arithmetic mean, a geometric mean may be used as well, which will be more robust to outlying effective library sizes.

plot(effLibSize / mean(effLibSize), sizeFactors(dds),
     xlab = "edgeR size factor",
     ylab = "DESeq2 size factor")

3.4 Full-quantile (FQ) normalization

In full-quantile normalization, originally introduced in the context of microarrays by Bolstad et al. (2003), the samples are forced to each have a distribution identical to the distribution of the median/average of the quantiles across samples. In practice, we can implement full-quantile normalization using the following procedure

  1. Given a data matrix \(\mathbf{Y}_{G \times n}\) for \(G\) genes (rows) and \(n\) samples (columns),
  2. sort each column to get \(\mathbf{Y}^S\),
  3. replace all elements of each row by the median (or average) for that row,
  4. obtain the normalized counts \(\tilde{\mathbf{Y}}\) by re-arranging (i.e., unsorting) each column.

3.5 Uncertainty in normalization

  • The offsets that are being cacluated for normalization purposes are estimates.
  • The downstream analyses incorporates these estimates as if they are known, i.e., conditions on the estimates.
  • The analysis therefore ignores the uncertainty in their estimation.
Figure: Box 1 from Vallejos et al. (2017).

Figure: Box 1 from Vallejos et al. (2017).

4 Challenge III: Parameter estimation (under limited information setting)

There are two challenges to be overcome here:

  • First, we need to get the structure of our mean model right, this is, which covariates to include, and how to include them, such that we are capable of capturing important sources of variation in our experiment, in order to derive a correct interpretation of the data in terms of our research question.
  • Second, we need to be able to estimate the parameters of our model in an efficient way, while having limited information (i.e., we are often confronted with only a small number of replicates).

4.1 Defining the model for the mean

Let’s first check how the authors of the original study parameterized the mean model.

Figure: Another paragraph from the Methods section.

Figure: Another paragraph from the Methods section.

The authors write that they are “employing treatment type, time point and sample ID as factors in the model”. Concerning experimental variables, this suggests they have added covariates defining the treatment and time point for each sample. The sample ID in the text refers to the original tissue sample and therefore corresponds to the donor patient. While there is also a variable called sample in the colData, this is not what the authors refer to. Note the ambiguity here and since the authors didn’t share their code, this is hard to check! But, more on reproducibility later…

Question. What are the authors assuming when using this structure for the mean model? Do you think that there are extensions or simplifications of the mean model that would be relevant?

Answer.

The authors are acknowledging the relatedness of samples derived from the same donor patient by adding it as a fixed effect to the model, which is great. This blocking strategy has been extensively discussed in the proteomics part of this course. However, by only adding a main effect for treatment and time, they are assuming that the effect of time is identical for all treatments, i.e., the average gene expression in-/decrease at 48h versus 24h is identical for the DPN, OHT or the control samples, which seems like a quite stringent assumption. We can make the model more flexible by allowing for a treatment * time interaction.

4.2 Parameter estimation and empirical Bayes

  • Even in limited sample size settings, the parameters \(\beta\) of the mean model may be estimated reasonably efficiently, and we have previously discussed the IRLS algorithm to do so.

  • However, estimating parameters for the variance (this is, the dispersion parameter \(\phi\) from the negative binomial or the variance parameter \(\sigma\) from the Gaussian) is typically quite a bit harder.

  • In genomics, we often take advantage of the parallel structure of the thousands of regression models (one for each gene) to borrow information across genes in a procedure called empirical Bayes, as also seen in the proteomics part of this course.

  • In the Bayesian setting, we use not only the data, but also a prior distribution, to derive our parameter estimates. In a traditional Bayesian analysis, one assumes an a priori known prior distribution, which reflects our prior belief into all possible values of the parameter. This prior distribution is completely independent of the observed data and the idea is that one specifies the prior distribution before observing the data. One can then use the data and prior distribution to derive a posterior distribution for the parameter(s) \(\theta\) of interest through Bayes rule \[ p(\theta | \mathbf{Y}) = \frac{p(\mathbf{Y} | \theta) p(\theta)}{\int_{\theta \in \Theta} p(\mathbf{Y} | \theta) p(\theta) d\theta}. \] Here, the posterior distribution \(p(\theta | \mathbf{Y})\) is calculated using the data likelihood \(p(\mathbf{Y} | \theta)\), prior distribution \(p(\theta)\) and the ‘marginal likelihood’ \(\int_{\theta \in \Theta} p(\mathbf{Y} | \theta) p(\theta) d\theta\), where \(\Theta\) denotes the parameter space of \(\theta\). We can see that the posterior probability for a specific value of the parameter \(\theta\) will be high if both the data likelihood as well as prior probability are high.

  • In empirical Bayes, we basically take a semi-Bayesian approach to parameter estimation. Indeed, we do not assume a known prior distribution, but estimate it empirically using the data. This empirically estimated prior \(\hat{p}(\theta)\) is then used to calculate the posterior distribution.

  • While in some settings one can easily calculate the posterior distribution, sometimes it can be a hard problem. In such cases, it may be useful to restrict ourselves to calculating the maximum a posteriori (MAP) estimate, which corresponds to the mode of the posterior distribution. This can be considered to be analogous to point estimation in the frequentist setting.

  • In our setting, we use genes with a similar average expression to moderate the dispersion estimate for a particular gene. The basic assumption for this to make sense is that genes with similar means might have similar dispersion parameters (or variances), owing to the mean-variance trend.


  • Once initial estimates for the gene-wise dispersions have been derived (\(\hat{\Phi}_g^{ML}\) in the figure), we use a parametric model to estimate its distribution (typically as a function of the mean) across genes. This distribution is the prior distribution.
  • Then, each initial estimate is shrunken towards that empirically estimated prior distribution. The amount of shrinkage being performed is data-driven, and depends on the data, taking into account the precision of our initial estimate (i.e., shape of the likelihood) and the variability of the prior distribution.
  • These strategies result in impressive performance gains in terms of differential expression analysis and are implemented in all popular differential expression analysis software packages (though in slightly differing ways) like limma, edgeR and DESeq2.
Figure 8 from Van den Berge *et al.* (2019).

Figure 8 from Van den Berge et al. (2019).

The blog post on understanding empirical Bayes estimation using baseball statistics is a great primer for further reading, as well as the accompanying book by David Robinson.

4.3 In practice

Let’s fit the model using edgeR.

design <- model.matrix(~ treatment*time + patient, data=colData(se))


dge <- calcNormFactors(se)
dge <- estimateDisp(dge, design) # estimate dispersion estimates
plotBCV(dge)

fit <- glmFit(dge, design)
head(fit$coefficients)
##                 (Intercept) treatmentDPN treatmentOHT     time48h   patient2
## ENSG00000000003   -9.332660   0.11215980   0.08617711  0.13277338 -0.5185631
## ENSG00000000419  -10.374585  -0.05233370  -0.03544105 -0.09301681  0.1376257
## ENSG00000000457  -10.935441  -0.12815863  -0.13380545 -0.07347351  0.4574027
## ENSG00000000460  -10.098383   0.08875322   0.13754925 -0.89357390  0.5407554
## ENSG00000000971  -13.689209   0.29526136   0.26736723  0.49224506 -0.1468142
## ENSG00000001036   -8.280283   0.02211192  -0.02416563 -0.08638397 -0.4676508
##                    patient3   patient4 treatmentDPN:time48h
## ENSG00000000003 -0.82994270 -0.6245673          -0.12278804
## ENSG00000000419  0.10515134  0.1023421           0.04810092
## ENSG00000000457 -0.05731193  0.2089687           0.06405330
## ENSG00000000460 -0.33459640 -0.1321134           0.15904868
## ENSG00000000971  0.93857349 -0.2676066          -0.60487781
## ENSG00000001036 -1.52141542 -1.0046446           0.04238597
##                 treatmentOHT:time48h
## ENSG00000000003          -0.11955914
## ENSG00000000419           0.11561301
## ENSG00000000457           0.13101511
## ENSG00000000460           0.18156925
## ENSG00000000971          -0.71520710
## ENSG00000001036           0.08331487

5 Challenge IV: Statistical inference across many genes

5.1 Contrasts on the treatment effects

We will first derive all contrasts that are also investigated in the original manuscript, using our extended model where we are allowing for an interaction effect between treatment and time.

The mean model is \[ \log(\mu_{gi}) = \beta_{g0} + \beta_{g1} x_{DPN} + \beta_{g2} x_{OHT} + \beta_{g3} x_{48h} + \beta_{g4} x_{pat2} + \beta_{g5} x_{pat3} + \beta_{g6} x_{pat4} + \\ \beta_{g7} x_{DPN:48h} + \beta_{g8} x_{OHT:48h}. \]

The intercept corresponds to the log average gene expression in the control group at 24h for patient 1.

DPN 24h vs control 24h. The respective means are \[\log \mu_{g,DPN,24h} = \beta_{g0} + \beta_{g1},\] \[\log \mu_{g,con,24h} = \beta_{g0}.\] And their difference is \[ \delta_g = \beta_{g1}. \]

DPN 48h vs control 48h. The respective means are \[\log \mu_{g,DPN,48h} = \beta_{g0} + \beta_{g1} + \beta_{g3} + \beta_{g7},\] \[\log \mu_{g,con,48h} = \beta_{g0} + \beta_{g3}.\] And their difference is \[ \delta_g = \beta_{g1} + \beta_{g7}. \]

OHT 24h vs control 24h. The respective means are \[\log \mu_{g,OHT,24h} = \beta_{g0} + \beta_{g2} ,\] \[\log \mu_{g,con,24h} = \beta_{g0}.\] And their difference is \[ \delta_g = \beta_{g2}. \]

OHT 48h vs control 48h. The respective means are \[\log \mu_{g,OHT,48h} = \beta_{g0} + \beta_{g2} + \beta_{g3} + \beta_{g8},\] \[\log \mu_{g,con,48h} = \beta_{g0} + \beta_{g3}.\] And their difference is \[ \delta_g = \beta_{g2} + \beta_{g8}. \]

However, we can also assess the interaction effects: is the time effect different between DPN and OHT treatment versus the control? And how about the DPN vs OHT treatments?

DPN vs control interaction. The time effect for each condition is \[ \delta_{DPN} = \log \mu_{g,DPN,48h} - \log \mu_{g,DPN,24h} = \beta_{g3} + \beta_{g7},\] \[ \delta_{con} = \log \mu_{g,con,48h} - \log \mu_{g,con,24h} = \beta_{g3}. \] So the interaction effect is \[\delta_{DPN-con} = \beta_{g7}.\]

OHT vs control interaction. The time effect for each condition is \[ \delta_{OHT} = \log \mu_{g,OHT,48h} - \log \mu_{g,OHT,24h} = \beta_{g3} + \beta_{g8},\] \[ \delta_{con} = \log \mu_{g,con,48h} - \log \mu_{g,con,24h} = \beta_{g3}. \] So the interaction effect is \[\delta_{DPN-con} = \beta_{g8}.\]

OHT vs DPN interaction. The time effect for each condition is \[ \delta_{OHT} = \log \mu_{g,OHT,48h} - \log \mu_{g,OHT,24h} = \beta_{g3} + \beta_{g8},\] \[ \delta_{DPN} = \log \mu_{g,DPN,48h} - \log \mu_{g,DPN,24h} = \beta_{g3} + \beta_{g7},\] So the interaction effect is \[\delta_{OHT-DPN} = \beta_{g8} - \beta_{g7}.\]

Let’s implement all of these in a contrast matrix.

L <- matrix(0, nrow = ncol(fit$coefficients), ncol = 7)
rownames(L) <- colnames(fit$coefficients)
colnames(L) <- c("DPNvsCON24", "DPNvsCON48",
                 "OHTvsCON24", "OHTvsCON48",
                 "DPNvsCONInt", "OHTvsCONInt",
                 "OHTvsDPNInt")
# DPN vs control at 24h
L[2,"DPNvsCON24"] <- 1
# DPN vs control at 48h
L[c(2,8),"DPNvsCON48"] <- 1
# OHT vs control at 24h
L[3,"OHTvsCON24"] <- 1
# OHT vs control at 48h
L[c(3,9),"OHTvsCON48"] <- 1
# DPN control interaction
L[8,"DPNvsCONInt"] <- 1
# OHT control interaction
L[9,"OHTvsCONInt"] <- 1
# OHT DPN interaction
L[c(9,8),"OHTvsDPNInt"] <- c(1, -1)

L
##                      DPNvsCON24 DPNvsCON48 OHTvsCON24 OHTvsCON48 DPNvsCONInt
## (Intercept)                   0          0          0          0           0
## treatmentDPN                  1          1          0          0           0
## treatmentOHT                  0          0          1          1           0
## time48h                       0          0          0          0           0
## patient2                      0          0          0          0           0
## patient3                      0          0          0          0           0
## patient4                      0          0          0          0           0
## treatmentDPN:time48h          0          1          0          0           1
## treatmentOHT:time48h          0          0          0          1           0
##                      OHTvsCONInt OHTvsDPNInt
## (Intercept)                    0           0
## treatmentDPN                   0           0
## treatmentOHT                   0           0
## time48h                        0           0
## patient2                       0           0
## patient3                       0           0
## patient4                       0           0
## treatmentDPN:time48h           0          -1
## treatmentOHT:time48h           1           1

And, finally, we can assess each hypothesis using the glmLRT function implemented in edgeR. We can assess each hypothesis separately by looping over the contrasts.

lrtList <- list() #list of results
for(cc in 1:ncol(L)) lrtList[[cc]] <- glmLRT(fit, contrast = L[,cc])

# p-value histograms
pvalList <- lapply(lrtList, function(x) x$table$PValue)
pvalMat <- do.call(cbind,  pvalList)
colnames(pvalMat) <- colnames(L)
par(mfrow=c(3,3))
sapply(1:ncol(pvalMat), function(ii) hist(pvalMat[,ii], 
                                          main = colnames(pvalMat)[ii],
                                          xlab = "p-value"))
##          [,1]            [,2]            [,3]            [,4]           
## breaks   numeric,21      numeric,21      numeric,21      numeric,21     
## counts   integer,20      integer,20      integer,20      integer,20     
## density  numeric,20      numeric,20      numeric,20      numeric,20     
## mids     numeric,20      numeric,20      numeric,20      numeric,20     
## xname    "pvalMat[, ii]" "pvalMat[, ii]" "pvalMat[, ii]" "pvalMat[, ii]"
## equidist TRUE            TRUE            TRUE            TRUE           
##          [,5]            [,6]            [,7]           
## breaks   numeric,21      numeric,21      numeric,21     
## counts   integer,20      integer,20      integer,20     
## density  numeric,20      numeric,20      numeric,20     
## mids     numeric,20      numeric,20      numeric,20     
## xname    "pvalMat[, ii]" "pvalMat[, ii]" "pvalMat[, ii]"
## equidist TRUE            TRUE            TRUE

5.1.1 Multiple testing

# number of DE genes
padjMat <- apply(pvalMat, 2, p.adjust, method="fdr")
colSums(padjMat <= 0.05 )
##  DPNvsCON24  DPNvsCON48  OHTvsCON24  OHTvsCON48 DPNvsCONInt OHTvsCONInt 
##           2          62           0          22           0           0 
## OHTvsDPNInt 
##           0

We are finding low numbers of DE genes between treatments at a 5% FDR level. This was already reflected in the the MDS plots.

5.1.2 Visualization

Let’s visualize some results for the DPN vs control at 48h contrast.

library(scales) # for scales::alpha()
deGenes <- p.adjust(lrtList[[2]]$table$PValue, "fdr") <= 0.05

## volcano plot
plot(x = lrtList[[2]]$table$logFC,
     y = -log10(lrtList[[2]]$table$PValue),
     xlab = "log Fold-change",
     ylab = "-log10 P-value",
     pch = 16, col = alpha(deGenes+1, .4), 
     cex=2/3, bty='l')
legend("topright", c("DE", "not DE"),
       col = 2:1, pch=16, bty='n')

## MD-plot
plot(x = lrtList[[2]]$table$logCPM,
     y = lrtList[[2]]$table$logFC,
     xlab = "Average log CPM",
     ylab = "Log fold-change",
     pch = 16, col = alpha(deGenes+1, .4), 
     cex=2/3, bty='l')
legend("topright", c("DE", "not DE"),
       col = 2:1, pch=16, bty='n')
abline(h=0, col="orange", lwd=2, lty=2)


# extract all DE genes
deGenes <- rownames(lrtList[[2]]$table)[p.adjust(lrtList[[2]]$table$PValue, "fdr") <= 0.05]
deGenes
##  [1] "ENSG00000035928" "ENSG00000044574" "ENSG00000070882" "ENSG00000075790"
##  [5] "ENSG00000091137" "ENSG00000092621" "ENSG00000099864" "ENSG00000099875"
##  [9] "ENSG00000100219" "ENSG00000100867" "ENSG00000101255" "ENSG00000101974"
## [13] "ENSG00000102547" "ENSG00000103064" "ENSG00000103257" "ENSG00000103449"
## [17] "ENSG00000107796" "ENSG00000111181" "ENSG00000111790" "ENSG00000119242"
## [21] "ENSG00000120129" "ENSG00000120217" "ENSG00000133935" "ENSG00000135473"
## [25] "ENSG00000135541" "ENSG00000138685" "ENSG00000143127" "ENSG00000145050"
## [29] "ENSG00000145244" "ENSG00000149428" "ENSG00000149485" "ENSG00000152952"
## [33] "ENSG00000155111" "ENSG00000155330" "ENSG00000155660" "ENSG00000164597"
## [37] "ENSG00000166813" "ENSG00000167608" "ENSG00000167703" "ENSG00000168014"
## [41] "ENSG00000169174" "ENSG00000169239" "ENSG00000169762" "ENSG00000170122"
## [45] "ENSG00000170837" "ENSG00000171798" "ENSG00000173210" "ENSG00000175198"
## [49] "ENSG00000181790" "ENSG00000182704" "ENSG00000182836" "ENSG00000183401"
## [53] "ENSG00000185818" "ENSG00000187908" "ENSG00000188783" "ENSG00000197976"
## [57] "ENSG00000219481" "ENSG00000226887" "ENSG00000233705" "ENSG00000234431"
## [61] "ENSG00000236044" "ENSG00000243927"
# order according to absolute fold-change
orderedDEGenes <- deGenes[order(abs(lrtList[[2]]$table[deGenes, "logFC"]), decreasing = TRUE)]

par(mfrow=c(3,3))
for(kk in 1:9){
  boxplot(log1p(assays(se)$counts[orderedDEGenes[kk],]) ~ interaction(treatment, time))
  boxplot(fit$fitted.values[orderedDEGenes[kk],] ~ interaction(treatment, time))
}

5.2 Contrast on the time effect

Based on the MDS plot, we can expect comparatively more DE genes for the time effect. For didactic purposes, here we assess an average time effect across the three treatments. The analysis shows how flexible one can be when using contrasts.

Mean of time 24h: \[\log \mu_{g,24h} = \frac{1}{3} \left\{ \underbrace{(\beta_{g0})}_\text{Control, 24h} + \underbrace{(\beta_{g0} + \beta_{g1})}_\text{DPN, 24h} + \underbrace{(\beta_{g0} + \beta_{g2})}_\text{OHT, 24h} \right\}\]

Mean of time 48h: \[\log \mu_{g,48h} = \frac{1}{3} \left\{ \underbrace{(\beta_{g0} + \beta_{g3})}_\text{Control, 48h} + \underbrace{(\beta_{g0} + \beta_{g1} + \beta_{g3} + \beta_{g7})}_\text{DPN, 48h} + \underbrace{(\beta_{g0} + \beta_{g2}+ \beta_{g3} + \beta_{g8})}_\text{OHT, 48h} \right\}\]

Difference: \[ \log \left( \frac{\mu_{g,48h}}{\mu_{g,24h}} \right) = \beta_{g3} + \frac{1}{3}(\beta_{g7} + \beta_{g8}) \]

Ltime <- matrix(0, nrow = ncol(fit$coefficients), ncol = 1)
rownames(Ltime) <- colnames(fit$coefficients)
Ltime[c("time48h", "treatmentDPN:time48h", "treatmentOHT:time48h"),1] <- c(1, 1/3, 1/3)

lrtTime <- glmLRT(fit, contrast=Ltime)
hist(lrtTime$table$PValue) # very different p-value distribution

sum(p.adjust(lrtTime$table$PValue, "fdr") <= 0.05) # many DE genes
## [1] 5938

6 Alternative parameterizations

While our design matrix here was parameterized as ~ treatment*time + patient alternative, equivalent parameterizations are also possible. Below, we demonstrate another parameterization that could work, too, and can be more intuitive. In this parameterization, we estimate a mean for each experimental condition, without an intercept, which can be convenient to think about how to set up contrasts.

treatTime <- as.factor(paste0(treatment, time))
table(treatTime)
## treatTime
## Control24h Control48h     DPN24h     DPN48h     OHT24h     OHT48h 
##          3          4          4          4          4          4
design2 <- model.matrix(~ 0 + treatTime + patient)

dge2 <- calcNormFactors(se)
dge2 <- estimateDisp(dge2, design2)
plotBCV(dge2)

fit2 <- glmFit(dge2, design2)
head(fit2$coefficients)
##                 treatTimeControl24h treatTimeControl48h treatTimeDPN24h
## ENSG00000000003           -9.332660           -9.199887       -9.220500
## ENSG00000000419          -10.374585          -10.467601      -10.426918
## ENSG00000000457          -10.935441          -11.008915      -11.063600
## ENSG00000000460          -10.098383          -10.991957      -10.009630
## ENSG00000000971          -13.689209          -13.196964      -13.393947
## ENSG00000001036           -8.280283           -8.366667       -8.258171
##                 treatTimeDPN48h treatTimeOHT24h treatTimeOHT48h   patient2
## ENSG00000000003       -9.210515       -9.246483       -9.233269 -0.5185631
## ENSG00000000419      -10.471834      -10.410026      -10.387429  0.1376257
## ENSG00000000457      -11.073020      -11.069247      -11.011705  0.4574027
## ENSG00000000460      -10.744155       -9.960834      -10.672839  0.5407554
## ENSG00000000971      -13.506580      -13.421841      -13.644803 -0.1468142
## ENSG00000001036       -8.302169       -8.304449       -8.307518 -0.4676508
##                    patient3   patient4
## ENSG00000000003 -0.82994270 -0.6245673
## ENSG00000000419  0.10515134  0.1023421
## ENSG00000000457 -0.05731193  0.2089687
## ENSG00000000460 -0.33459640 -0.1321134
## ENSG00000000971  0.93857349 -0.2676066
## ENSG00000001036 -1.52141542 -1.0046446
## for example: the estimate for the DPN24h vs control 24h is still the same,
## but requires a different combination of parameters
plot(fit$coefficients[,"treatmentDPN"], 
     fit2$coefficients[,"treatTimeDPN24h"] - fit2$coefficients[,"treatTimeControl24h"],
     xlab="Intercept model estimate", ylab="No intercept model estimate")

# Let's implement the DPNvsCON48 contrast
L2 <- matrix(0, nrow = ncol(fit2$coefficients), ncol = 1)
rownames(L2) <- colnames(fit2$coefficients)
L2[c("treatTimeDPN48h", "treatTimeControl48h"),1] <- c(1, -1)
lrt2 <- glmLRT(fit2, contrast=L2[,1])
hist(lrt2$table$PValue)

plot(x=lrt2$table$PValue, y=lrtList[[2]]$table$PValue,
     xlab="No intercept model p-value",
     ylab="Intercept model p-value")

7 Additional Challenge (Opportunity?): The importance of reproducible analysis

Finally, it is crucial to make your analysis reproducible using tools such as RMarkdown and GitHub. Please sit back and watch this amazing lecture from Professor Keith Baggerly on "The Importance of Reproducible Research in High-Throughput Biology.

LS0tCnRpdGxlOiAnU2VxdWVuY2luZzogUk5BLXNlcSBkYXRhIGludHJvJwphdXRob3I6ICJLb2VuIFZhbiBkZW4gQmVyZ2UiCmRhdGU6ICJMYXN0IGNvbXBpbGVkIG9uIGByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJUIsICVZJylgIgpvdXRwdXQ6IAogIHBkZl9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICBsYXRleF9lbmdpbmU6IHhlbGF0ZXgKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKLS0tCgpgYGB7ciBmdW5jdGlvbnMsIGluY2x1ZGU9RkFMU0V9CiMgQSBmdW5jdGlvbiBmb3IgY2FwdGlvbmluZyBhbmQgcmVmZXJlbmNpbmcgaW1hZ2VzCmZpZyA8LSBsb2NhbCh7CiAgICBpIDwtIDAKICAgIHJlZiA8LSBsaXN0KCkKICAgIGxpc3QoCiAgICAgICAgY2FwPWZ1bmN0aW9uKHJlZk5hbWUsIHRleHQpIHsKICAgICAgICAgICAgaSA8PC0gaSArIDEKICAgICAgICAgICAgcmVmW1tyZWZOYW1lXV0gPDwtIGkKICAgICAgICAgICAgcGFzdGUoIkZpZ3VyZSAiLCBpLCAiOiAiLCB0ZXh0LCBzZXA9IiIpCiAgICAgICAgfSwKICAgICAgICByZWY9ZnVuY3Rpb24ocmVmTmFtZSkgewogICAgICAgICAgICByZWZbW3JlZk5hbWVdXQogICAgICAgIH0pCn0pCmBgYCAKCmBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBldmFsPVRSVUV9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeShrbml0cikKICBsaWJyYXJ5KHJtYXJrZG93bikKICBsaWJyYXJ5KGdncGxvdDIpCn0pCmlmKCEiQmlvY01hbmFnZXIiICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKClbLDFdKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJCaW9jTWFuYWdlciIpCn0KaWYoISJsaW1tYSIgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKVssMV0pewogIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCJsaW1tYSIpCn0KaWYoISJlZGdlUiIgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKVssMV0pewogIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCJlZGdlUiIpCn0KaWYoISJERVNlcTIiICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKClbLDFdKXsKICBCaW9jTWFuYWdlcjo6aW5zdGFsbCgiREVTZXEyIikKfQppZighInNjYWxlcyIgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKVssMV0pewogIGluc3RhbGwucGFja2FnZXMoInNjYWxlcyIpCn0KYGBgCgpJbiB0aGlzIGxlY3R1cmUgd2Ugd2lsbCBzdGFydCB3b3JraW5nIHdpdGggYSByZWFsIGJ1bGsgUk5BLXNlcSBkYXRhc2V0IGZyb20gW0hhZ2x1bmQgKmV0IGFsLiogKDIwMTIpXShodHRwczovL2FjYWRlbWljLm91cC5jb20vamNlbS9hcnRpY2xlLzk3LzEyLzQ2MzEvMjUzNjU3MykuIEFmdGVyIGltcG9ydGluZyB0aGUgZGF0YSwgd2Ugd2lsbCBiZSB3b3JraW5nIG91ciB3YXkgdGhyb3VnaCBmb3VyIG1ham9yIGNoYWxsZW5nZXMgd2hpY2gsIHRvZ2V0aGVyLCB3aWxsIGZvcm0gYSBmdWxsIFJOQS1zZXEgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gKERFKSBhbmFseXNpcyBwaXBlbGluZSB3aGVyZSB0aGUgcmVzdWx0IG9mIG91ciBhbmFseXNpcyB3aWxsIGJlIGEobiBvcmRlcmVkKSBsaXN0IG9mIGdlbmVzIHRoYXQgd2UgZmluZCB0byBiZSBkaWZmZXJlbnRseSBleHByZXNzZWQgYmV0d2VlbiBvdXIgY29uZGl0aW9ucyBvZiBpbnRlcmVzdC4gVGhlIGZvdXIgbWFpbiBjaGFsbGVuZ2VzIHdlIHdpbGwgbG9vayBpbnRvIGFyZSAKCiAtIENob2ljZSBvZiBtb2RlbGluZyBhc3N1bXB0aW9ucyAoZGlzdHJpYnV0aW9uKS4KIC0gTm9ybWFsaXphdGlvbi4KIC0gUGFyYW1ldGVyIGVzdGltYXRpb24gdW5kZXIgYSBsaW1pdGVkIGluZm9ybWF0aW9uIHNldHRpbmcuCiAtIFN0YXRpc3RpY2FsIGluZmVyZW5jZSB1bmRlciBoaWdoIGRpbWVuc2lvbmFsaXR5IChtYW55IGdlbmVzKS4KCiMgRXhwZXJpbWVudGFsIGRlc2lnbiwgZGF0YSBpbXBvcnQgYW5kIGRhdGEgZXhwbG9yYXRpb24KCiMjIEV4cGVyaW1lbnRhbCBkZXNpZ24KCkxldCdzIHRyeSB0byB3b3JrIG91dCB0aGUgZXhwZXJpbWVudGFsIGRlc2lnbiB1c2luZyB0aGUgZm9sbG93aW5nIHBhcmFncmFwaCBmcm9tIHRoZSBNZXRob2RzIHNlY3Rpb24gb2YgdGhlIHBhcGVyLgoKYGBge3IsIGVjaG89RkFMU0UsIGZpZy5jYXA9cGFzdGUoIkZpZ3VyZTogQSBwYXJhZ3JhcGggZnJvbSB0aGUgTWV0aG9kcyBzZWN0aW9uLiIpfQojIEFsbCBkZWZhdWx0cwppbmNsdWRlX2dyYXBoaWNzKCIuL2ltYWdlc19zZXF1ZW5jaW5nL2V4cERlc2lnbl9wYXJhLnBuZyIpCmBgYAoKIyMgRGF0YSBpbXBvcnQgYW5kIGV4cGxvcmF0aW9uCgpXZSB3aWxsIGJlIGltcG9ydGluZyB0aGUgZGF0YXNldCB1c2luZyB0aGUgW3BhcmF0aHlyb2lkU0VdKGh0dHBzOi8vd3d3LmJpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvcmVsZWFzZS9kYXRhL2V4cGVyaW1lbnQvaHRtbC9wYXJhdGh5cm9pZFNFLmh0bWwpIGRhdGEgcGFja2FnZSBmcm9tIFtCaW9jb25kdWN0b3JdKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy8pLgoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGV2YWw9VFJVRX0KaWYgKCFyZXF1aXJlTmFtZXNwYWNlKCJCaW9jTWFuYWdlciIsIHF1aWV0bHkgPSBUUlVFKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiQmlvY01hbmFnZXIiKQp9CmlmKCEiU3VtbWFyaXplZEV4cGVyaW1lbnQiICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKClbLDFdKXsKICBCaW9jTWFuYWdlcjo6aW5zdGFsbCgiU3VtbWFyaXplZEV4cGVyaW1lbnQiKQp9IAojIGluc3RhbGwgcGFja2FnZSBpZiBub3QgaW5zdGFsbGVkLgppZighInBhcmF0aHlyb2lkU0UiICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKClbLDFdKSBCaW9jTWFuYWdlcjo6aW5zdGFsbCgicGFyYXRoeXJvaWRTRSIpCgpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkocGFyYXRoeXJvaWRTRSkKICBsaWJyYXJ5KFN1bW1hcml6ZWRFeHBlcmltZW50KQp9KQoKIyBpbXBvcnQgZGF0YQpkYXRhKCJwYXJhdGh5cm9pZEdlbmVzU0UiLCBwYWNrYWdlPSJwYXJhdGh5cm9pZFNFIikKIyByZW5hbWUgZm9yIGNvbnZlbmllbmNlCnNlMSA8LSBwYXJhdGh5cm9pZEdlbmVzU0UKcm0ocGFyYXRoeXJvaWRHZW5lc1NFKQoKIyB0aHJlZSB0cmVhdG1lbnRzCnRyZWF0bWVudDEgPC0gY29sRGF0YShzZTEpJHRyZWF0bWVudAp0YWJsZSh0cmVhdG1lbnQxKQojIHR3byB0aW1lcG9pbnRzCnRpbWUxIDwtIGNvbERhdGEoc2UxKSR0aW1lCnRhYmxlKHRpbWUxKQojIGZvdXIgZG9ub3IgcGF0aWVudHMKcGF0aWVudDEgPC0gY29sRGF0YShzZTEpJHBhdGllbnQKdGFibGUocGF0aWVudDEpCgp0YWJsZShwYXRpZW50MSwgdHJlYXRtZW50MSwgdGltZTEpCmBgYAoKLSBXZSBvYnNlcnZlIHRoYXQgdGhlIG51bWJlciBvZiBzYW1wbGVzIHRoYXQgd2UgYXJlIG9ic2VydmluZyBoZXJlIGlzIGxhcmdlciB0aGFuIHdoYXQgaXMgZGVzY3JpYmVkIGluIHRoZSBwYXBlci4gQXMgYWxzbyBkZXNjcmliZWQgaW4gdGhlIFtwYXJhdGh5cm9pZFNFIHZpZ25ldHRlXShodHRwczovL3d3dy5iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvZGF0YS9leHBlcmltZW50L3ZpZ25ldHRlcy9wYXJhdGh5cm9pZFNFL2luc3QvZG9jL3BhcmF0aHlyb2lkU0UucGRmKSwgc29tZSBzYW1wbGVzIHdlcmUgc3ByZWFkIG92ZXIgbXVsdGlwbGUgc2VxdWVuY2luZyBydW5zIChpLmUuLCB0aGUgc2FtZSBzYW1wbGUgYmVpbmcgc2VxdWVuY2VkIHJlcGVhdGVkbHkpIGFuZCB0aGVyZWZvcmUgY29uc3RpdHV0ZSAqKnRlY2huaWNhbCByZXBsaWNhdGlvbioqLCByYXRoZXIgdGhhbiBiaW9sb2dpY2FsIHJlcGxpY2F0aW9uLgotIFdlIGhhdmUgcHJldmlvdXNseSBzZWVuIHRoYXQgdGVjaG5pY2FsIHJlcGxpY2F0ZXMgY2FuIGJlIGNvbnNpZGVyZWQgdG8gYmUgZGlzdHJpYnV0ZWQgYWNjb3JkaW5nIHRvIGEgUG9pc3NvbiBkaXN0cmlidXRpb24uIE9uZSBpbXBvcnRhbnQgcHJvcGVydHkgb2YgUG9pc3NvbiByYW5kb20gdmFyaWFibGVzIGlzIHRoYXQgYSBzdW0gb2YgUG9pc3NvbiByYW5kb20gdmFyaWFibGVzIHN0aWxsIGZvbGxvdyBhIFBvaXNzb24gZGlzdHJpYnV0aW9uLiBJbmRlZWQsIGlmICRYIFxzaW0gUG9pKFxtdV9YKSQgYW5kICRZIFxzaW0gUG9pKFxtdV9ZKSQsIHRoZW4gJFggKyBZID0gWiBcc2ltIFBvaShcbXVfWCArIFxtdV9ZKSQuCi0gRm9yIHRoaXMgcmVhc29uLCBpdCBpcyBvZnRlbiBzdWdnZXN0ZWQgdG8gc3VtIHRlY2huaWNhbCByZXBsaWNhdGVzIHJhdGhlciB0aGFuLCBmb3IgZXhhbXBsZSwgYXZlcmFnaW5nLCB3aGljaCBkb2VzIG5vdCByZXRhaW4gdGhlIFBvaXNzb24gcHJvcGVydHkgKHRyeSBmb3IgeW91cnNlbGYhKS4gV2UnbGwgdGhlcmVmb3JlIGZpcnN0IHN1bSB0aGUgdGVjaG5pY2FsIHJlcGxpY2F0ZXMuCgpgYGB7cn0KZHVwRXhwcyA8LSBhcy5jaGFyYWN0ZXIoY29sRGF0YShzZTEpJGV4cGVyaW1lbnRbZHVwbGljYXRlZChjb2xEYXRhKHNlMSkkZXhwZXJpbWVudCldKQpkdXBFeHBzCgpjb3VudHMgPC0gYXNzYXlzKHNlMSkkY291bnRzCm5ld0NvdW50cyA8LSBjb3VudHMKY2QgPC0gY29sRGF0YShzZTEpCmZvcihzcyBpbiAxOmxlbmd0aChkdXBFeHBzKSl7CiAgIyBjaGVjayB3aGljaCBzYW1wbGVzIGFyZSBkdXBsaWNhdGVzCiAgcmVsZXZhbnRJZCA8LSB3aGljaChjb2xEYXRhKHNlMSkkZXhwZXJpbWVudCA9PSBkdXBFeHBzW3NzXSkKICAjIHN1bSBjb3VudHMKICBuZXdDb3VudHNbLHJlbGV2YW50SWRbMV1dIDwtIHJvd1N1bXMoY291bnRzWyxyZWxldmFudElkXSkKICAjIGtlZXAgd2hpY2ggY29sdW1ucyAvIHJvd3MgdG8gcmVtb3ZlLgogIGlmKHNzID09IDEpewogICAgdG9SZW1vdmUgPC0gcmVsZXZhbnRJZFsyXQogIH0gZWxzZSB7CiAgICB0b1JlbW92ZSA8LSBjKHRvUmVtb3ZlLCByZWxldmFudElkWzJdKQogIH0KfQoKIyByZW1vdmUgYWZ0ZXIgc3VtbWluZyBjb3VudHMgKG90aGVyd2lzZSBJRHMgZ2V0IG1peGVkIHVwKQpuZXdDb3VudHMgPC0gbmV3Q291bnRzWywtdG9SZW1vdmVdCm5ld0NEIDwtIGNkWy10b1JlbW92ZSxdCgojIENyZWF0ZSBuZXcgU3VtbWFyaXplZEV4cGVyaW1lbnQKc2UgPC0gU3VtbWFyaXplZEV4cGVyaW1lbnQoYXNzYXlzID0gbGlzdCgiY291bnRzIiA9IG5ld0NvdW50cyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xEYXRhID0gbmV3Q0QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRhZGF0YSA9IG1ldGFkYXRhKHNlMSkpCgp0cmVhdG1lbnQgPC0gY29sRGF0YShzZSkkdHJlYXRtZW50CnRhYmxlKHRyZWF0bWVudCkKdGltZSA8LSBjb2xEYXRhKHNlKSR0aW1lCnRhYmxlKHRpbWUpCnBhdGllbnQgPC0gY29sRGF0YShzZSkkcGF0aWVudAp0YWJsZShwYXRpZW50KQoKdGFibGUocGF0aWVudCwgdHJlYXRtZW50LCB0aW1lKSAjIGFncmVlcyB3aXRoIHBhcGVyLgoKYGBgCgogLSBBZnRlciBzdW1taW5nIHRoZSB0ZWNobmljYWwgcmVwbGljYXRlcyBhbmQgYXBwcm9wcmlhdGVseSB1cGRhdGluZyB0aGUgc2FtcGxlIGluZm9ybWF0aW9uLCB3ZSBhZ2FpbiBjcmVhdGUgYSBgU3VtbWFyaXplZEV4cGVyaW1lbnRgIG9iamVjdCwgd2hpY2ggaXMgZXNzZW50aWFsbHkgYSBkYXRhIGNvbnRhaW5lciB0aGF0IGNvbnRhaW5zIGFsbCByZWxldmFudCBpbmZvcm1hdGlvbiBhYm91dCB5b3VyIGV4cGVyaW1lbnQuIFBsZWFzZSBzZWUgdGhlIFt2aWduZXR0ZV0oaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvYmlvYy92aWduZXR0ZXMvU3VtbWFyaXplZEV4cGVyaW1lbnQvaW5zdC9kb2MvU3VtbWFyaXplZEV4cGVyaW1lbnQuaHRtbCkgZm9yIG1vcmUgaW5mb3JtYXRpb24gb24gaG93IHRvIHVzZSB0aGlzIGNsYXNzLgogLSBCeSBkaXJlY3RseSBtYXRjaGluZyBjb2x1bW5zIChzYW1wbGVzKSBhbmQgcm93cyAoZ2VuZXMpIHRvIHRoZWlyIHJlbGV2YW50IG1ldGFkYXRhLCB0aGUgYFN1bW1hcml6ZWRFeHBlcmltZW50YCBjbGFzcyBhdm9pZHMgbWlzdGFrZXMgYnkgbWlzLW1hdGNoaW5nIGNvbHVtbnMgYW5kIHJvd3Mgd2l0aCBlYWNoIG90aGVyIChwcm92aWRlZCB5b3UgaGF2ZW4ndCBtaXNtYXRjaGVkIHRoZW0gd2hlbiB5b3UgY3JlYXRlIHRoZSBvYmplY3QpLgogLSBUaGUgYFN1bW1hcml6ZWRFeHBlcmltZW50YCBjbGFzcyBpcyBtb2R1bGFyIGFuZCBleHRlbmRhYmxlLCBhbmQgZXh0ZW5zaW9ucyBleGlzdCBmb3IgZXhhbXBsZSBmb3IgdGhlIGFuYWx5c2lzIG9mIHNpbmdsZS1jZWxsIFJOQS1zZXEgZGF0YSwgaS5lLiwgdGhlIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgY2xhc3MuCiAtIER1ZSB0byB0aGVpciBjb252ZW5pZW50IG9yZ2FuaXphdGlvbiBhbmQgd2lkZWx5IHN1cHBvcnRlZCB1c2FnZSB3aXRoaW4gQmlvY29uZHVjdG9yLCB3ZSB3aWxsIHR5cGljYWxseSB3b3JrIHdpdGggc3VjaCBjbGFzc2VzIGluIHRoZSBhbmFseXNpcyBvZiBSTkEtc2VxIGRhdGEuCiAKIyMgSW5kZXBlbmRlbnQgZmlsdGVyaW5nCgpJbmRlcGVuZGVudCBmaWx0ZXJpbmcgaXMgYSBzdHJhdGVneSB0byByZW1vdmUgZmVhdHVyZXMgKGluIHRoaXMgY2FzZSwgZ2VuZXMpIHByaW9yIHRvIHRoZSBhbmFseXNpcy4gUmVtb3ZhbCBvZiB0aGVzZSBmZWF0dXJlcyBtYXkgbG93ZXIgdGhlIG11bHRpcGxlIHRlc3RpbmcgY29ycmVjdGlvbiBmb3Igb3RoZXIgZ2VuZXMgdGhhdCBwYXNzIHRoZSBmaWx0ZXIuIFdlIHRyeSB0byByZW1vdmUgZ2VuZXMgdGhhdCBoYXZlIGEgbG93IHBvd2VyIHRvIGJlIGZvdW5kIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQsIGFuZC9vciB0aGF0IGFyZSBiaW9sb2dpY2FsbHkgbGVzcyByZWxldmFudC4gCkEgY29tbW9uIGZpbHRlcmluZyBzdHJhdGVneSBpcyB0byByZW1vdmUgZ2VuZXMgd2l0aCBhIGdlbmVyYWxseSBsb3cgZXhwcmVzc2lvbiwgYXMgbG93IGNvdW50cyBoYXZlIGxvd2VyIHJlbGF0aXZlIHVuY2VydGFpbnR5IChoZW5jZSBsb3dlciBzdGF0aXN0aWNhbCBwb3dlciksIGFuZCBtYXkgYmUgY29uc2lkZXJlZCBiaW9sb2dpY2FsbHkgbGVzcyByZWxldmFudC4KCmBgYHtyfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkobGltbWEpCiAgbGlicmFyeShlZGdlUikKfSkKCmtlZXAgPC0gcm93U3VtcyhjcG0oc2UpID4gMikgPj0gMwp0YWJsZShrZWVwKQpzZSA8LSBzZVtrZWVwLF0KYGBgCgojIyBEYXRhIGV4cGxvcmF0aW9uCgpgYGB7cn0KIyBsaWJyYXJ5IHNpemUgZGlzdHJpYnV0aW9uCmhpc3QoY29sU3Vtcyhhc3NheXMoc2UpJGNvdW50cykvMWU2LCBicmVha3M9MTApCmJveHBsb3QoY29sU3Vtcyhhc3NheXMoc2UpJGNvdW50cykvMWU2IH4gdHJlYXRtZW50KQpib3hwbG90KGNvbFN1bXMoYXNzYXlzKHNlKSRjb3VudHMpLzFlNiB+IHRpbWUpCmJveHBsb3QoY29sU3Vtcyhhc3NheXMoc2UpJGNvdW50cykvMWU2IH4gcGF0aWVudCkKYm94cGxvdChjb2xTdW1zKGFzc2F5cyhzZSkkY291bnRzKS8xZTYgfiBpbnRlcmFjdGlvbih0cmVhdG1lbnQsIHRpbWUpKQoKIyBNRFMgcGxvdApwbG90TURTKHNlLCAKICAgICAgICBsYWJlbHMgPSB0cmVhdG1lbnQsIAogICAgICAgIGNvbD1hcy5udW1lcmljKHBhdGllbnQpKQoKIyMgaGFyZCB0byBzZWUgaW5mbHVlbmNlIG9mIGV4cGVyaW1lbnRhbCBmYWN0b3JzIGR1ZSB0byBsYXJnZSBiZXR3ZWVuLXBhdGllbnQgdmFyaWF0aW9uCiMjIHdlIGNvdWxkIGFsc28gbWFrZSBhbiBNRFMgcGxvdCBwZXIgcGF0aWVudCB0byB0YWtlIGEgbG9vay4KZm9yKGtrIGluIDE6NCl7CiAgaWQgPC0gd2hpY2gocGF0aWVudCA9PSBraykKICBwbG90TURTKHNlWyxpZF0sIAogICAgICAgIGxhYmVscyA9IHBhc3RlMCh0cmVhdG1lbnRbaWRdLCJfIix0aW1lW2lkXSksIAogICAgICAgIGNvbD1hcy5udW1lcmljKHRpbWVbaWRdKSkKfQpgYGAKCmBgYHtyLCBldmFsPUZBTFNFLCBlY2hvPUZBTFNFfQojIEV4cGxhaW4gY29uY2VwdCBvZiBNRFM6IHByZXNlcnZlIEV1Y2xpZGVhbiBkaXN0YW5jZSBmcm9tIGhpZ2ggdG8gbG93IGRpbS4KYGBgCgpPYnNlcnZhdGlvbnMgYmFzZWQgb24gTURTIHBsb3Q6CgogLSBUaGVyZSBpcyBhIHZlcnkgbGFyZ2UgYmV0d2Vlbi1wYXRpZW50IHZhcmlhYmlsaXR5LCB3aGljaCBpcyB0aGUgbWFqb3Igc291cmNlIG9mIHZhcmlhdGlvbiBpbiB0aGlzIGRhdGFzZXQuIFRoZSBzYW1wbGVzIGZyb20gZWFjaCBwYXRpZW50IGNsdXN0ZXIgdG9nZXRoZXIgdGlnaHRseS4KIC0gV2l0aGluIHBhdGllbnQsIHRpbWUgY29uc2lzdGVudGx5IGV4cGxhaW5zIG1vcmUgdmFyaWF0aW9uIHRoYW4gdGhlIHRyZWF0bWVudHMuCiAtIFJlbGF0aXZlIHRvIHBhdGllbnRzIGFuZCB0aW1lLCB0aGUgdHJlYXRtZW50IHNlZW1zIHRvIGhhdmUgYSBmYWlybHkgc21hbGwgZWZmZWN0LgoKIyBDaGFsbGVuZ2UgSTogQ2hvaWNlIG9mIG1vZGVsaW5nIGFzc3VtcHRpb25zCgpXaGVuIHdvcmtpbmcgd2l0aCBhIEdMTSwgYXMgcGFydCBvZiB0aGUgY2hvaWNlcyBvZiBtb2RlbGluZyBhc3N1bXB0aW9ucywgd2UgbmVlZCB0byBwaWNrIGFuIGFwcHJvcHJpYXRlIGRpc3RyaWJ1dGlvbiBmb3IgdGhlIGV4cHJlc3Npb24gY291bnRzLiBCZWxvdyB3ZSBwZXJmb3JtIHNvbWUgZXhwbG9yYXRvcnkgYW5hbHlzZXMgdG8gaW52ZXN0aWdhdGUuCgpgYGB7ciwgZXZhbD1UUlVFfQp5IDwtIGFzc2F5cyhzZSkkY291bnRzWzEsXQpoaXN0KHksIGJyZWFrcyA9IDQwLAogICAgIHhsYWIgPSAiR2VuZSBleHByZXNzaW9uIiwKICAgICB4YXh0ID0gIm4iLCB5YXh0ID0gIm4iLAogICAgIG1haW4gPSAiRGF0YSBmb3IgdGhlIGZpcnN0IGdlbmUiKQpheGlzKDEsIGF0ID0gc2VxKDIwMCwgMTIwMCwgYnk9MjAwKSkKYXhpcygyLCBhdCA9IDA6MykKCiMgTWVhbi12YXJpYW5jZSB0cmVuZCB3aXRoaW4gZWFjaCBleHBlcmltZW50YWwgY29uZGl0aW9uCmNvbnQyNElEIDwtIHdoaWNoKHRyZWF0bWVudCA9PSAiQ29udHJvbCIgJiB0aW1lID09ICIyNGgiKQpEUE4yNElEIDwtIHdoaWNoKHRyZWF0bWVudCA9PSAiRFBOIiAmIHRpbWUgPT0gIjI0aCIpCk9IVDI0SUQgPC0gd2hpY2godHJlYXRtZW50ID09ICJPSFQiICYgdGltZSA9PSAiMjRoIikKY29udDQ4SUQgPC0gd2hpY2godHJlYXRtZW50ID09ICJDb250cm9sIiAmIHRpbWUgPT0gIjQ4aCIpCkRQTjQ4SUQgPC0gd2hpY2godHJlYXRtZW50ID09ICJEUE4iICYgdGltZSA9PSAiNDhoIikKT0hUNDhJRCA8LSB3aGljaCh0cmVhdG1lbnQgPT0gIk9IVCIgJiB0aW1lID09ICI0OGgiKQppZExpc3QgPC0gbGlzdChjb250MjRJRCwgRFBOMjRJRCwgT0hUMjRJRCwgCiAgICAgICAgICAgICAgIGNvbnQ0OElELCBEUE40OElELCBPSFQ0OElEKQpuYW1lcyhpZExpc3QpIDwtIHBhc3RlMChyZXAobGV2ZWxzKHRyZWF0bWVudCksMiksIHJlcChsZXZlbHModGltZSksIGVhY2g9MykpCgpwYXIobWZyb3c9YygzLDIpLCBtYXI9YygyLDIsMiwxKSkKZm9yKGtrIGluIDE6bGVuZ3RoKGlkTGlzdCkpewogICMgZXh0cmFjdCBjb3VudHMgZm9yIGVhY2ggY29uZGl0aW9uCiAgY3VyQ291bnRzIDwtIGFzc2F5cyhzZSkkY291bnRzWyxpZExpc3RbW2trXV1dCiAgcGxvdCh4ID0gcm93TWVhbnMoY3VyQ291bnRzKSsxLAogICAgICAgeSA9IHJvd1ZhcnMoY3VyQ291bnRzKSsxLAogICAgICAgcGNoID0gMTYsIGNleD0xLzIsCiAgICAgICB4bGFiID0gIk1lYW4iLCB5bGFiPSJWYXJpYW5jZSIsCiAgICAgICBtYWluID0gbmFtZXMoaWRMaXN0KVtra10sCiAgICAgICBsb2c9Inh5IikKICBhYmxpbmUoMCwxLCBjb2w9InJlZCIpCiAgbHcxIDwtIGxvZXNzKChyb3dWYXJzKGN1ckNvdW50cykrMSkgfiAocm93TWVhbnMoY3VyQ291bnRzKSsxKSwgc3Bhbj0xLzQsIGx3ZD0zKQogIG9vIDwtIG9yZGVyKHJvd01lYW5zKGN1ckNvdW50cykrMSkKICBsaW5lcyhyb3dNZWFucyhjdXJDb3VudHMpW29vXSsxLCBsdzEkZml0dGVkW29vXSwgY29sPSJvcmFuZ2UiKQoKICBzbW9vdGhTY2F0dGVyKHggPSBsb2cxcChyb3dNZWFucyhjdXJDb3VudHMpKSwKICAgICAgIHkgPSBsb2cxcChyb3dWYXJzKGN1ckNvdW50cykpLAogICAgICAgcGNoID0gMTYsIGNleD0xLzIsCiAgICAgICB4bGFiID0gIk1lYW4iLCB5bGFiPSJWYXJpYW5jZSIpCiAgYWJsaW5lKDAsMSwgY29sPSJyZWQiKQogIGxpbmVzKGxvZyhyb3dNZWFucyhjdXJDb3VudHMpW29vXSsxKSwgbG9nKGx3MSRmaXR0ZWRbb29dKzEpLCBjb2w9Im9yYW5nZSIpCn0KYGBgCgogLSBIYXZpbmcgZGF0YSBvbiB0aG91c2FuZHMgb2YgZ2VuZXMgcHJvdmlkZXMgdGhlIG9wcG9ydHVuaXR5IHRvIGVtcGlyaWNhbGx5IGFzc2VzcyB0aGUgbWVhbi12YXJpYW5jZSByZWxhdGlvbnNoaXAuCiAtIEl0IGlzIGNsZWFyIHRoYXQgdGhlIGRhdGEgaXMgb3ZlcmRpc3BlcnNlZCB3aXRoIHJlc3BlY3QgdG8gdGhlIFBvaXNzb24gZGlzdHJpYnV0aW9uIChyZWQgJHk9eCQgbGluZSkuIFRoZXJlIGFsc28gc2VlbXMgdG8gYmUgYSBxdWFkcmF0aWMgdHJlbmQgb2YgdGhlIHZhcmlhbmNlIGFzIGEgZnVuY3Rpb24gb2YgdGhlIG1lYW4uIFRoaXMgaGFzIG1vdGl2YXRlZCB0aGUgKipuZWdhdGl2ZSBiaW5vbWlhbCBkaXN0cmlidXRpb24gYXMgdGhlIG1vc3QgcG9wdWxhciBjaG9pY2UgdG8gbW9kZWwgKGJ1bGspIFJOQS1zZXEgZ2VuZSBleHByZXNzaW9uIGRhdGEqKi4KIAogLS0tCiAKIC0gVGhlIG5lZ2F0aXZlIGJpbm9taWFsIGRpc3RyaWJ1dGlvbiBpcyBhbHNvIHJlZmVycmVkIHRvIGFzIHRoZSBHYW1tYS1Qb2lzc29uIGRpc3RyaWJ1dGlvbiBhcyBpdCBjYW4gYmUgZm9ybXVsYXRlZCBhcyBzdWNoLiBJbmRlZWQsIGlmIAogJCQKIFxsYW1iZGEgXHNpbSBcR2FtbWEoXGFscGhhLCBcYmV0YSkgXFwKIFkgfCBcbGFtYmRhIFxzaW0gUG9pKFxsYW1iZGEpLAogJCQKIHRoZW4gdGhpcyBpcyBlcXVpdmFsZW50IHRvCiAkJCBZIFxzaW0gTkIoXG11ID0gXGFscGhhIC8gXGJldGEsIFxwaGkgPSAxL1xhbHBoYSkuICQkCiAtIFRoaXMgY2FuIGJlIHNob3duIGFuYWx5dGljYWxseSwgYnV0IGlzIGNvbnNpZGVyZWQgb3V0IG9mIHRoZSBzY29wZSBvZiB0aGlzIGNvdXJzZS4gQmVsb3csIHdlIHNob3cgaXQgZW1waXJpY2FsbHkgdXNpbmcgc2ltdWxhdGlvbi4KIC0gVGhpcyB0aGVvcmV0aWNhbCByZXN1bHQgaGFzIGdvdCBzb21lIHByYWN0aWNhbCBjb25zZXF1ZW5jZXMuIFRoZSBHYW1tYS1Qb2lzc29uIGZvcm11bGF0aW9uIG1ha2VzIGl0IGNsZWFyIHdoeSB3ZSBjYW4gc3VtIHRlY2huaWNhbCByZXBsaWNhdGVzIGFzIHRoZSBzdW0gb2YgUG9pc3NvbiByYW5kb20gdmFyaWFibGVzIGlzIGFnYWluIGEgUG9pc3NvbiByYW5kb20gdmFyaWFibGUuIAogLSBUaGUgUG9pc3NvbiBzdGF0ZW1lbnQgY2FuIHRodXMgYmUgY29uc2lkZXJlZCBhcyBjYXB0dXJpbmcgdGVjaG5pY2FsIHZhcmlhdGlvbiwgd2hpbGUgdGhlIEdhbW1hIHN0YXRlbWVudCBjYW4gYmUgY29uc2lkZXJlZCB0byBjYXB0dXJlIGJpb2xvZ2ljYWwgdmFyaWF0aW9uLCBpLmUuLCB2YXJpYXRpb24gaW4gdGhlIG1lYW4gZXhwcmVzc2lvbiBhY3Jvc3MgYmlvbG9naWNhbCByZXBsaWNhdGVzLgoKYGBge3J9CmFscGhhIDwtIDIwCmJldGEgPC0gMTAKbGFtYmRhIDwtIHJnYW1tYShuID0gMWU1LCBzaGFwZSA9IGFscGhhLCByYXRlID0gYmV0YSkKeTEgPC0gcnBvaXMobiA9IDFlNSwgbGFtYmRhID0gbGFtYmRhKQoKIyBub3RlIHBoaSA9IDEgLyBzaXplCnkyIDwtIHJuYmlub20obj0xZTUsIG11PWFscGhhIC8gKGJldGEpLCBzaXplPWFscGhhKQoKCnBsb3QoZGVuc2l0eSh5MSkpCmxpbmVzKGRlbnNpdHkoeTIpLCBjb2w9InN0ZWVsYmx1ZSIpCmBgYAoKCiMgQ2hhbGxlbmdlIElJOiBOb3JtYWxpemF0aW9uCgpOb3JtYWxpemF0aW9uIGlzIG5lY2Vzc2FyeSB0byBjb3JyZWN0IGZvciBzZXZlcmFsIHNvdXJjZXMgb2YgdGVjaG5pY2FsIHZhcmlhdGlvbjoKCiAtICoqRGlmZmVyZW5jZXMgaW4gc2VxdWVuY2luZyBkZXB0aCoqIGJldHdlZW4gc2FtcGxlcy4gU29tZSBzYW1wbGVzIGdldCBzZXF1ZW5jZWQgZGVlcGVyIGluIHRoZSBzZW5zZSB0aGF0IHRoZXkgY29uc2lzdCBvZiBtb3JlIChtYXBwZWQpIHJlYWRzIGFuZCB0aGVyZWZvcmUgY2FuIGJlIGNvbnNpZGVyZWQgdG8gY29udGFpbiBhIGhpZ2hlciBhbW91bnQgb2YgaW5mb3JtYXRpb24sIHdoaWNoIHdlIHNob3VsZCBiZSB0YWtpbmcgaW50byBhY2NvdW50LiBJbiBhZGRpdGlvbiwgaWYgYSBzYW1wbGUgaXMgc2VxdWVuY2VkIGRlZXBlciwgaXQgaXMgbmF0dXJhbCB0aGF0IHRoZSBjb3VudHMgZm9yIGVhY2ggZ2VuZSB3aWxsIGJlIGhpZ2hlciwgamVvcGFyZGl6aW5nIGEgZGlyZWN0IGNvbXBhcmlzb24gb2YgdGhlIGV4cHJlc3Npb24gY291bnRzLgogLSAqKkRpZmZlcmVuY2VzIGluIFJOQSBwb3B1bGF0aW9uIGNvbXBvc2l0aW9uKiogYmV0d2VlbiBzYW1wbGVzLiBBcyBhbiBleHRyZW1lIGV4YW1wbGUsIHN1cHBvc2UgdGhhdCB0d28gc2FtcGxlcyBoYXZlIGJlZW4gc2VxdWVuY2VkIHRvIHRoZSBleGFjdCBzYW1lIGRlcHRoLiBPbmUgc2FtcGxlIGlzIGNvbnRhbWluYXRlZCBhbmQgaGFzIGEgdmVyeSBoaWdoIGNvbmNlbnRyYXRpb24gb2YgdGhlIGNvbnRhbWluYW50IGNETkEgYmVpbmcgc2VxdWVuY2VkLCBidXQgb3RoZXJ3aXNlIHRoZSB0d28gc2FtcGxlcyBhcmUgaWRlbnRpY2FsLiBTaW5jZSB0aGUgY29udGFtaW5hbnQgd2lsbCBiZSB0YWtpbmcgdXAgYSBzaWduaWZpY2FudCBwcm9wb3J0aW9uIG9mIHRoZSByZWFkcyBiZWluZyBzZXF1ZW5jZWQsIHRoZSBjb3VudHMgd2lsbCBub3QgYmUgZGlyZWN0bHkgY29tcGFyYWJsZSBiZXR3ZWVuIHRoZSBzYW1wbGVzLiBIZW5jZSwgd2UgbWF5IGFsc28gd2FudCB0byBjb3JyZWN0IGZvciBkaWZmZXJlbmNlcyBpbiB0aGUgY29tcG9zaXRpb24gb2YgdGhlIFJOQSBwb3B1bGF0aW9uIG9mIHRoZSBzYW1wbGVzLgogLSAqKk90aGVyIHRlY2huaWNhbCB2YXJpYXRpb24qKiBzdWNoIGFzIHNhbXBsZS1zcGVjaWZpYyBHQy1jb250ZW50IG9yIHRyYW5zY3JpcHQgbGVuZ3RoIGVmZmVjdHMgbWF5IGFsc28gYmUgYWNjb3VudGVkIGZvci4KIAogLS0tCiAKTGV0J3MgdGFrZSBhIGxvb2sgYXQgaG93IGNvbXBhcmFibGUgZGlmZmVyZW50IHJlcGxpY2F0ZXMgYXJlIGluIHRoZSBDb250cm9sIGNvbmRpdGlvbiBhdCA0OGggaW4gb3VyIGRhdGFzZXQuIFdlIHdpbGwgaW52ZXN0aWdhdGUgdGhpcyB1c2luZyBNRC1wbG90cyAobWVhbi1kaWZmZXJlbmNlIHBsb3RzIGFzIGludHJvZHVjZWQgYnkgW0R1ZG9pdCAqZXQgYWwuKiAoMjAwMildKGh0dHBzOi8vd3d3LmpzdG9yLm9yZy9zdGFibGUvMjQzMDcwMzgpKSwgYWxzbyBzb21ldGltZXMgcmVmZXJyZWQgdG8gYXMgTUEtcGxvdHMuCgpgYGB7cn0KY29udDQ4SUQgIyByZWxldmFudCBzYW1wbGVzCmNvbFN1bXMoYXNzYXlzKHNlKSRjb3VudHNbLGNvbnQ0OElEXSkgLyAxZTYKY29tYnMgPC0gY29tYm4oY29udDQ4SUQsIG09MikgI3BhaXJ3aXNlIGNvbWJpbmF0aW9ucyBiZXR3ZWVuIHNhbXBsZXMKCnBhcihtZnJvdz1jKDMsMiksIG1hcj1jKDQsNCwyLDEpKQpmb3IoY2MgaW4gMTpuY29sKGNvbWJzKSl7CiAgY3VyU2FtcGxlcyA8LSBjb21ic1ssY2NdCiAgTSA8LSByb3dNZWFucyhhc3NheXMoc2UpJGNvdW50c1ssY3VyU2FtcGxlc10pCiAgRCA8LSBhc3NheXMoc2UpJGNvdW50c1ssY3VyU2FtcGxlc1syXV0gLyBhc3NheXMoc2UpJGNvdW50c1ssY3VyU2FtcGxlc1sxXV0KICBwbG90KHggPSBsb2coTSksIHkgPSBsb2cyKEQpLAogICAgICAgcGNoID0gMTYsIGNleD0xLzMsCiAgICAgICBtYWluID0gcGFzdGUwKCJTYW1wbGUgIiwgY3VyU2FtcGxlc1syXSwgIiB2cyBzYW1wbGUgIiwgY3VyU2FtcGxlc1sxXSksCiAgICAgICB4bGFiID0gIkxvZyBtZWFuIiwgeWxhYiA9ICJMb2cyIGZvbGQtY2hhbmdlIiwKICAgICAgIGJ0eSA9ICdsJykKICBhYmxpbmUoaCA9IDAsIGNvbD0ib3JhbmdlIiwgbHdkPTIpCn0KYGBgCiAKIC0gV2Ugc2VlIGNsZWFyIGJpYXMgZm9yIHNvbWUgcGFpcndpc2UgY29tcGFyaXNvbnMuIEZvciBleGFtcGxlLCBpbiB0aGUgZmlyc3QgcGxvdCBjb21wYXJpbmcgc2FtcGxlIDggdmVyc3VzIHNhbXBsZSAyLCB0aGUgbG9nIGZvbGQtY2hhbmdlcyBhcmUgYmlhc2VkIGRvd253YXJkcy4gVGhpcyBtZWFucyB0aGF0LCBvbiBhdmVyYWdlLCBhIGdlbmUgaXMgbG93ZXIgZXhwcmVzc2VkIGluIHNhbXBsZSA4IHZlcnN1cyBzYW1wbGUgMi4gTG9va2luZyBhdCB0aGUgbGlicmFyeSBzaXplcywgd2UgY2FuIGluZGVlZCBzZWUgdGhhdCB0aGUgbGlicmFyeSBzaXplIGZvciBzYW1wbGUgMiBpcyBhYm91dCAkMTEgXHRpbWVzIDEwXjYkIHdoaWxlIGl0IGlzIG9ubHkgYWJvdXQgJDcgXHRpbWVzIDEwXjYkIGZvciBzYW1wbGUgOCEgVGhpcyBpcyBhIGNsZWFyIGxpYnJhcnkgc2l6ZSBlZmZlY3QgdGhhdCB3ZSBzaG91bGQgdGFrZSBpbnRvIGFjY291bnQuCiAKIyMgQ291bnQgc2NhbGluZyB2ZXJzdXMgR0xNIG9mZnNldHMKCiAtIFdlIGhhdmUgcHJldmlvdXNseSBkaXNjdXNzZWQgY291bnQgc2NhbGluZyB0cmFuc2Zvcm1hdGlvbnMgc3VjaCBhcyBDUE0gYW5kIFRQTS4KIC0gQSBtb3JlIGFwcHJvcHJpYXRlIGFuZCBuYXR1cmFsIHdheSB3aGVuIHdvcmtpbmcgd2l0aCBHTE1zIGlzIHRocm91Z2ggdGhlIHVzZSBvZiAqKm9mZnNldHMqKi4gVGhlIGdlbmVyYWwgdXNlIG9mIGFuIG9mZnNldCBpcyB0byBhY2NvdW50IGZvciB0aGUgJ2VmZm9ydCcgcGVyZm9ybWVkIGluIG9yZGVyIHRvIGdhdGhlciB0aGF0IG9ic2VydmF0aW9uIG9mIHRoZSByZXNwb25zZSB2YXJpYWJsZS4gVHdvIGV4YW1wbGVzOgogICAxLiBBIGJpb2xvZ2lzdCBzdHVkeWluZyB3aGFsZSBtaWdyYXRpb24gaGFzIG9uZSBmaXhlZCBzcG90IHdoZXJlLCBpbiB0aGUgbWlncmF0aW9uIHNlYXNvbiwgc2hlIGNvdW50cyBtaWdyYXRpbmcgd2hhbGVzIGRheSBhZnRlciBkYXksIG92ZXIgc2V2ZXJhbCB5ZWFycy4gRm9yIGVhY2ggZGF5IHNoZSByZWNvcmRzIHRoZSBudW1iZXIgb2Ygc3BvdHRlZCB3aGFsZXMuIE9mIGNvdXJzZSwgdGhlIHRpbWUgc3BlbnQgd2hhbGUtd2F0Y2hpbmcgbWF5IGRpZmZlciBmcm9tIGRheSB0byBkYXkgYW5kIGl0IGlzIG5hdHVyYWwgdGhhdCB5b3UgYXJlIG1vcmUgbGlrZWx5IHRvIHNwb3QgbW9yZSB3aGFsZXMgaWYgeW91IHNwZW5kIG1vcmUgdGltZSBsb29raW5nIGZvciB0aGVtLiBUaGUgdGltZSBzcGVudCBzcG90dGluZyB3aGFsZXMgY2FuIHRoZW4gYmUgdXNlZCBhcyBhbiBvZmZzZXQuCiAgIDIuIEluIG91ciBjYXNlLCBhIHNhbXBsZSBiZWluZyBzZXF1ZW5jZWQgZGVlcGVyIGNvbnRhaW5zIG1vcmUgaW5mb3JtYXRpb24sIGkuZS4sIG1vcmUgJ2VmZm9ydCcgaGFzIGJlZW4gcGVyZm9ybWVkLCBhcyBjb21wYXJlZCB0byBhIHNhbXBsZSBiZWluZyBzZXF1ZW5jZWQgcmVsYXRpdmVseSBzaGFsbG93LiBXZSBoYXZlIG1vcmUgY29uZmlkZW5jZSBvZiBhIGNvdW50IGZyb20gYSBkZWVwbHkgc2VxdWVuY2VkIHNhbXBsZSB0aGFuIGZyb20gYSBzaGFsbG93bHkgc2VxdWVuY2VkIHNhbXBsZS4gV2UgY2FuIHRoZXJlZm9yZSB1c2UgdGhlIHNlcXVlbmNpbmcgZGVwdGggJE5faSA9IFxzdW1fZyBZX3tnaX0kIGFzIG9mZnNldCBpbiB0aGUgbW9kZWwuCiAgLSBBZGRpbmcgYW4gb2Zmc2V0IHRvIHRoZSBtb2RlbCBpcyBkaWZmZXJlbnQgZnJvbSBhZGRpbmcgYSBuZXcgdmFyaWFibGUgdG8gdGhlIG1vZGVsLiBGb3IgZWFjaCBuZXcgdmFyaWFibGUgd2UgYWRkLCB3ZSB3aWxsIGVzdGltYXRlIGl0cyBhdmVyYWdlIGVmZmVjdCAkXGJldGEkIG9uIHRoZSByZXNwb25zZSB2YXJpYWJsZS4gV2hlbiBhZGRpbmcgYW4gb2Zmc2V0LCBob3dldmVyLCB3ZSBhcmUgaW1wbGljaXRseSBhc3N1bWluZyB0aGF0ICRcYmV0YT0xJC4KICAtIE9mZnNldHMgYXJlIHR5cGljYWxseSBhZGRlZCBvbiB0aGUgc2NhbGUgb2YgdGhlIGxpbmVhciBwcmVkaWN0b3IuIFN1cHBvc2Ugd2UgaGF2ZSBhIGdlbmUgJGckIGFuZCBzYW1wbGUgJGkkIHNwZWNpZmljIG9mZnNldCAkT197Z2l9JCwgdGhlbiB3ZSBjYW4gZGVmaW5lIGEgbmVnYXRpdmUgYmlub21pYWwgR0xNIGluY2x1ZGluZyB0aGUgb2Zmc2V0IGFzCiAgJCQKICBcbGVmdFx7CiAgXGJlZ2lue2FycmF5fXtjY2N9CiAgWV97Z2l9ICYgXHNpbSAmIE5CKFxtdV97Z2l9LCBccGhpX2cpIFxcCiAgXGxvZyBcbXVfe2dpfSAmID0gJiBcZXRhX3tnaX0gXFwKICBcZXRhX3tnaX0gJiA9ICYgXG1hdGhiZntYfV5UX2kgXGJldGFfZyArIFxsb2coT197Z2l9KS4gXFwKICBcZW5ke2FycmF5fQogIFxyaWdodC4KICAkJAogLSBQbGVhc2UgW3JlYWQgdGhpcyBwYWdlXShodHRwczovL3N0YXRvbWljcy5naXRodWIuaW8vU0dBMjEvc2VxdWVuY2luZ19zY2FsaW5nTm9ybWFsaXphdGlvbi5odG1sKSBmb3IgYW4gaW50dWl0aXZlIHJlYXNvbmluZyBhcyB0byB3aHkgb2Zmc2V0cyBhcmUgcHJlZmVycmVkIG92ZXIgY291bnQgc2NhbGluZy4KICAKIyMgSG93IHRvIG5vcm1hbGl6ZT8KCk1hbnkgYXBwcm9hY2hlcyBhcmUgYXZhaWxhYmxlIGZvciBub3JtYWxpemluZyBSTkEtc2VxIGRhdGEsIGFuZCB3ZSB3aWxsIHJldmlldyBhIGNvdXBsZSBvZiB0aGVzZS4gTW9zdCBtZXRob2RzIGNhbGN1bGF0ZSBhbiBvZmZzZXQgdGhhdCBpcyB1c2VkIGluIHRoZSBHTE0gdXNlZCB0byBtb2RlbCBnZW5lIGV4cHJlc3Npb24uIE9uZSBub3RhYmxlIG1ldGhvZCwgZnVsbC1xdWFudGlsZSBub3JtYWxpemF0aW9uLCBkb2VzIG5vdCBjYWxjdWxhdGUgYW4gb2Zmc2V0LCBhbmQgcmF0aGVyIG5vcm1hbGl6ZXMgY291bnRzIGRpcmVjdGx5LCBpbW1lZGlhdGVseSBlbmZvcmNpbmcgdGhlIHNhbWUgc2VxdWVuY2luZyBkZXB0aCBmb3IgYWxsIHNhbXBsZXMuCiAKIyMjIFRNTSBtZXRob2QgKGRlZmF1bHQgb2YgYGVkZ2VSYCkKClRoZSB0cmltbWVkIG1lYW4gb2YgTS12YWx1ZXMgKFRNTSkgbWV0aG9kIGludHJvZHVjZWQgYnkgW1JvYmluc29uICYgT3NobGFjayAoMjAxMCldKGh0dHBzOi8vZ2Vub21lYmlvbG9neS5iaW9tZWRjZW50cmFsLmNvbS9hcnRpY2xlcy8xMC4xMTg2L2diLTIwMTAtMTEtMy1yMjUpIGlzIGEgbm9ybWFsaXphdGlvbiBwcm9jZWR1cmUgdGhhdCBjYWxjdWxhdGVzIGEgc2luZ2xlIG5vcm1hbGl6YXRpb24gZmFjdG9yIGZvciBlYWNoIHNhbXBsZS4gQXMgdGhlIG5hbWUgc3VnZ2VzdHMsIGl0IGlzIGJhc2VkIG9uIGEgdHJpbW1lZCBtZWFuIG9mIGZvbGQtY2hhbmdlcyAoJE0kLXZhbHVlcykgYXMgdGhlIHNjYWxpbmcgZmFjdG9yLiBBIHRyaW1tZWQgbWVhbiBpcyBhbiBhdmVyYWdlIGFmdGVyIHJlbW92aW5nIGEgc2V0IG9mICdleHRyZW1lJyB2YWx1ZXMuIApTcGVjaWZpY2FsbHksIFRNTSBjYWxjdWxhdGVzIGEgbm9ybWFsaXphdGlvbiBmYWN0b3IgJEZfaV57KHIpfSQgYWNyb3NzIGdlbmVzICRnJCBmb3IgZWFjaCBzYW1wbGUgJGkkIGFzIGNvbXBhcmVkIHRvIGEgcmVmZXJlbmNlIHNhbXBsZSAkciQsCiQkClxsb2dfMihGX2leeyhyKX0pID0gXGZyYWN7XHN1bV97ZyBcaW4ge1xjYWwgR31eKn0gd197Z2l9XnIgTV97Z2l9XnJ9e1xzdW1fe2cgXGluIHtcY2FsIEd9Xip9IHdfe2dpfV5yfSwKJCQKd2hlcmUgJE1fe2dpfV5yJCByZXByZXNlbnRzIHRoZSAkXGxvZ18yJC1mb2xkLWNoYW5nZSBvZiB0aGUgZ2VuZSBleHByZXNzaW9uIGZyYWN0aW9uIGFzIGNvbXBhcmVkIHRvIGEgcmVmZXJlbmNlIHNhbXBsZSAkciQsIGkuZS4sClxbIE1fe2dpfV5yID0gXGxvZ18yXGxlZnQoIFxmcmFje1lfe2dpfSAvIE5faX17IFlfe2dyfSAvIE5fcn0gXHJpZ2h0KSwgXF0KYW5kICR3X3tnaX1eciQgcmVwcmVzZW50cyBhIHByZWNpc2lvbiB3ZWlnaHQgY2FsY3VsYXRlZCBhcwokJAogd197Z2l9XnIgPSBcZnJhY3tOX2kgLSBZX3tnaX19e05faSBZX3tnaX19ICsgXGZyYWN7Tl9yIC0gWV97Z3J9fXtOX3IgWV97Z3J9fSwKJCQKYW5kICR7XGNhbCBHfV4qJCByZXByZXNlbnRzIHRoZSBzZXQgb2YgZ2VuZXMgYWZ0ZXIgdHJpbW1pbmcgdGhvc2Ugd2l0aCB0aGUgbW9zdCBleHRyZW1lIGF2ZXJhZ2UgZXhwcmVzc2lvbiBhbmQgZm9sZC1jaGFuZ2UuIFRoZSB3ZWlnaHRzIHNlcnZlIHRvIGFjY291bnQgZm9yIHRoZSBmYWN0IHRoYXQgZm9sZC1jaGFuZ2VzIGZvciBnZW5lcyB3aXRoIGxvd2VyIHJlYWQgY291bnRzIGFyZSBtb3JlIHZhcmlhYmxlLgoKVGhlIHByb2NlZHVyZSBvbmx5IHRha2VzIGdlbmVzIGludG8gYWNjb3VudCB3aGVyZSBib3RoICRZX3tnaX0+MCQgYW5kICRZX3tncn0+MCQuIEJ5IGRlZmF1bHQsIFRNTSB0cmltcyBnZW5lcyB3aXRoIHRoZSAkMzBcJSQgbW9zdCBleHRyZW1lICRNJC12YWx1ZXMgYW5kICQ1XCUkIG1vc3QgZXh0cmVtZSBhdmVyYWdlIGdlbmUgZXhwcmVzc2lvbiwgYW5kIGNob29zZXMgYXMgcmVmZXJlbmNlICRyJCB0aGUgc2FtcGxlIHdob3NlIHVwcGVyLXF1YXJ0aWxlIGlzIGNsb3Nlc3QgdG8gdGhlIGFjcm9zcy1zYW1wbGUgYXZlcmFnZSB1cHBlci1xdWFydGlsZS4gVGhlIG5vcm1hbGl6YXRpb24gZmFjdG9yIGlzIHRoZW4gdXNlZCBpbiBjb25qdW5jdGlvbiB3aXRoIHRoZSBsaWJyYXJ5IHNpemUgdG8gY2FsY3VsYXRlIGFuICoqZWZmZWN0aXZlIGxpYnJhcnkgc2l6ZSoqCiQkCk5faV57ZWZmfSA9IE5faSBGX2leeyhyKX0KJCQKd2hpY2ggaXMgdXNlZCBhcyBvZmZzZXQgaW4gdGhlIEdMTS4KVGhlIG5vcm1hbGl6ZWQgY291bnRzIG1heSBiZSBnaXZlbiBieSAkXHRpbGRle1l9X3tnaX0gPSBZX3tnaX0gLyBOX2lecyQsIHdpdGggc2l6ZSBmYWN0b3IgCiQkTl9pXnMgPSBcZnJhY3tOX2kgRl9pXnsocil9fXtcZnJhY3sxfXtufVxzdW1fe2k9MX1ebiBOX2kgRl9pXnsocil9fS4kJAoKVE1NIG5vcm1hbGl6YXRpb24gbWF5IGJlIHBlcmZvcm1lZCBmcm9tIHRoZSBgY2FsY05vcm1GYWN0b3JzYCBmdW5jdGlvbiBpbXBsZW1lbnRlZCBpbiBgZWRnZVJgOgoKYGBge3J9CmRnZSA8LSBlZGdlUjo6Y2FsY05vcm1GYWN0b3JzKHNlKQpkZ2Ukc2FtcGxlcyAjbm9ybWFsaXphdGlvbiBmYWN0b3JzIGFkZGVkIHRvIGNvbERhdGEKYGBgCgpMZXQncyBjaGVjayBob3cgb3VyIE1ELXBsb3RzIGxvb2sgbGlrZSBhZnRlciBub3JtYWxpemF0aW9uLiBOb3RlIHRoYXQsIHdlIGNhbiByZXdyaXRlIHRoZSBHTE0gYXMKJCQgXGxvZ1xsZWZ0KCBcZnJhY3tcbXVfe2dpfX17Tl9pXnN9IFxyaWdodCkgPSBcbWF0aGJme1h9X2leVCBcYmV0YV9nICQkCmFuZCBzbyAkXGZyYWN7XG11X3tnaX19e05faV5zfSQgY2FuIGJlIGNvbnNpZGVyZWQgYXMgYW4gJ29mZnNldC1jb3JyZWN0ZWQgYXZlcmFnZSBjb3VudCcuCgpXZSBzZWUgdGhhdCBhbGwgTUQtcGxvdHMgYXJlIG5vdyBuaWNlbHkgY2VudGVyZWQgYXJvdW5kIGEgbG9nLWZvbGQtY2hhbmdlIG9mIHplcm8hCgpgYGB7cn0KIyMgbm9ybWFsaXplCmVmZkxpYlNpemUgPC0gZGdlJHNhbXBsZXMkbGliLnNpemUgKiBkZ2Ukc2FtcGxlcyRub3JtLmZhY3RvcnMKbm9ybUNvdW50VE1NIDwtIHN3ZWVwKGFzc2F5cyhzZSkkY291bnRzLCAyLCBGVU49Ii8iLCBlZmZMaWJTaXplKQoKCnBhcihtZnJvdz1jKDMsMiksIG1hcj1jKDQsNCwyLDEpKQpmb3IoY2MgaW4gMTpuY29sKGNvbWJzKSl7CiAgY3VyU2FtcGxlcyA8LSBjb21ic1ssY2NdCiAgTSA8LSByb3dNZWFucyhub3JtQ291bnRUTU1bLGN1clNhbXBsZXNdKQogIEQgPC0gbm9ybUNvdW50VE1NWyxjdXJTYW1wbGVzWzJdXSAvIG5vcm1Db3VudFRNTVssY3VyU2FtcGxlc1sxXV0KICBwbG90KHggPSBsb2coTSksIHkgPSBsb2cyKEQpLAogICAgICAgcGNoID0gMTYsIGNleD0xLzMsCiAgICAgICBtYWluID0gcGFzdGUwKCJTYW1wbGUgIiwgY3VyU2FtcGxlc1syXSwgIiB2cyBzYW1wbGUgIiwgY3VyU2FtcGxlc1sxXSksCiAgICAgICB4bGFiID0gIkxvZyBtZWFuIiwgeWxhYiA9ICJMb2cyIGZvbGQtY2hhbmdlIiwKICAgICAgIGJ0eSA9ICdsJykKICBhYmxpbmUoaCA9IDAsIGNvbD0ib3JhbmdlIiwgbHdkPTIpCn0KYGBgCgoKIyMgTWVkaWFuLW9mLXJhdGlvcyBtZXRob2QgKGRlZmF1bHQgb2YgYERFU2VxMmApCgpUaGUgbWVkaWFuLW9mLXJhdGlvcyBtZXRob2QgaXMgdXNlZCBpbiBgREVTZXEyYCBhcyBkZXNjcmliZWQgaW4gW0xvdmUgKmV0IGFsLiogKDIwMTQpXShodHRwczovL2dlbm9tZWJpb2xvZ3kuYmlvbWVkY2VudHJhbC5jb20vYXJ0aWNsZXMvMTAuMTE4Ni9zMTMwNTktMDE0LTA1NTAtOCkuCkl0IGFzc3VtZXMgdGhhdCB0aGUgZXhwZWN0ZWQgdmFsdWUgJFxtdV97Z2l9ID0gRShZX3tnaX0pJCBpcyBwcm9wb3J0aW9uYWwgdG8gdGhlIHRydWUgZXhwcmVzc2lvbiBvZiB0aGUgZ2VuZSwgJHFfe2dpfSQsIHNjYWxlZCBieSBhIHNpemUgZmFjdG9yICRzX3tpfSQgZm9yIGVhY2ggc2FtcGxlLApcWyBcbXVfe2dpfSA9IHNfe2l9cV97Z2l9LiBcXQoKVGhlIHNpemUgZmFjdG9yICRzX3tpfSQgaXMgdGhlbiBlc3RpbWF0ZWQgdXNpbmcgdGhlIG1lZGlhbi1vZi1yYXRpb3MgbWV0aG9kIGNvbXBhcmVkIHRvIGEgc3ludGhldGljIHJlZmVyZW5jZSBzYW1wbGUgJHIkIGRlZmluZWQgYmFzZWQgb24gZ2VvbWV0cmljIG1lYW5zIG9mIGNvdW50cyBhY3Jvc3Mgc2FtcGxlcwokJApzX2kgPSBcdGV4dHttZWRpYW59X3tce3tnOlleeyp9X3tncn0gXG5lIDB9XH19IFxmcmFje1lfe2dpfX17WV57Kn1fe2dyfX0sCiQkCndpdGggClxbIFleeyp9X3tncn0gPSBcbGVmdCggXHByb2Rfe2k9MX1ebiBZX3tnaX0gXHJpZ2h0KV57MS9ufS4gXF0KCldlIGNhbiB0aGVuIHVzZSB0aGUgc2l6ZSBmYWN0b3JzICRzX2kkIGFzIG9mZnNldHMgdG8gdGhlIEdMTS4KCioqUXVlc3Rpb24qKi4gRG8geW91IHNlZSBhbnkgaXNzdWVzIHdpdGggdGhlIHByb2NlZHVyZSBkZXNjcmliZWQgYXMgc3VjaD8KCjxkZXRhaWxzPjxzdW1tYXJ5PiBBbnN3ZXIuIDwvc3VtbWFyeT48cD4KVGhlIHByb2NlZHVyZSByZWxpZXMgb24gZ2VuZXMgYmVpbmcgZXhwcmVzc2VkIGluIGFsbCBzYW1wbGVzLgpBcyBzYW1wbGUgc2l6ZSBpbmNyZWFzZXMsIHRoZSBudW1iZXIgb2YgZ2VuZXMgd2l0aCB0aGlzIHByb3BlcnR5IHN0ZWFkaWx5IGRlY3JlYXNlcy4KVGhpcyB3aWxsIGJlY29tZSBjcnVjaWFsIGluIHNpbmdsZS1jZWxsIFJOQS1zZXEgZGF0YSBhbmFseXNpcy4KPC9wPjwvZGV0YWlscz4KCk1lZGlhbi1vZi1yYXRpb3Mgbm9ybWFsaXphdGlvbiBpcyBpbXBsZW1lbnRlZCBpbiB0aGUgYERFU2VxMmAgcGFja2FnZToKCmBgYHtyfQpkZHMgPC0gREVTZXEyOjpERVNlcURhdGFTZXRGcm9tTWF0cml4KGNvdW50RGF0YSA9IGFzc2F5cyhzZSkkY291bnRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbERhdGEgPSBjb2xEYXRhKHNlKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXNpZ24gPSB+IDEpICNqdXN0IGFkZCBpbnRlcmNlcHQgdG8gc2hvd2Nhc2Ugbm9ybWFsaXphdGlvbgpkZHMgPC0gREVTZXEyOjplc3RpbWF0ZVNpemVGYWN0b3JzKGRkcykKc2l6ZUZhY3RvcnMoZGRzKQpgYGAKCllvdSBtYXkgYWxzbyB3YW50IHRvIGNoZWNrIG91dCB0aGUgW1N0YXRRdWVzdCB2aWRlbyBvbiBERVNlcTIgbm9ybWFsaXphdGlvbl0oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1VRkI5OTN4dWZVVSkuCgojIyMgQ29tcGFyaW5nIFRNTSB3aXRoIERFU2VxMiBub3JtYWxpemF0aW9uCgpXZSBjYW4gY29tcGFyZSB0aGUgc2l6ZSBmYWN0b3JzIGZvciBib3RoIG5vcm1hbGl6YXRpb25zIHRvIHZlcmlmeSBpZiB0aGV5IGFncmVlIG9uIHRoZSBub3JtYWxpemF0aW9uIGZhY3RvcnMuIE5vdGUgd2UgbmVlZCB0byBzY2FsZSB0aGUgZWZmZWN0aXZlIGxpYnJhcnkgc2l6ZXMgZnJvbSBgZWRnZVJgIHRvIGVuZm9yY2UgYSBzaW1pbGFyIHNjYWxlIGFzIHRoZSBzaXplIGZhY3RvcnMgZnJvbSBgREVTZXEyYC4gV2hpbGUgYmVsb3cgd2UgYXJlIHVzaW5nIGFuIGFyaXRobWV0aWMgbWVhbiwgYSBnZW9tZXRyaWMgbWVhbiBtYXkgYmUgdXNlZCBhcyB3ZWxsLCB3aGljaCB3aWxsIGJlIG1vcmUgcm9idXN0IHRvIG91dGx5aW5nIGVmZmVjdGl2ZSBsaWJyYXJ5IHNpemVzLgoKYGBge3J9CnBsb3QoZWZmTGliU2l6ZSAvIG1lYW4oZWZmTGliU2l6ZSksIHNpemVGYWN0b3JzKGRkcyksCiAgICAgeGxhYiA9ICJlZGdlUiBzaXplIGZhY3RvciIsCiAgICAgeWxhYiA9ICJERVNlcTIgc2l6ZSBmYWN0b3IiKQpgYGAKCgojIyBGdWxsLXF1YW50aWxlIChGUSkgbm9ybWFsaXphdGlvbgoKSW4gZnVsbC1xdWFudGlsZSBub3JtYWxpemF0aW9uLCBvcmlnaW5hbGx5IGludHJvZHVjZWQgaW4gdGhlIGNvbnRleHQgb2YgbWljcm9hcnJheXMgYnkgW0JvbHN0YWQgKmV0IGFsLiogKDIwMDMpXShodHRwczovL2FjYWRlbWljLm91cC5jb20vYmlvaW5mb3JtYXRpY3MvYXJ0aWNsZS8xOS8yLzE4NS8zNzI2NjQpLCB0aGUgc2FtcGxlcyBhcmUgZm9yY2VkIHRvIGVhY2ggaGF2ZSBhIGRpc3RyaWJ1dGlvbiBpZGVudGljYWwgdG8gdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgbWVkaWFuL2F2ZXJhZ2Ugb2YgdGhlIHF1YW50aWxlcyBhY3Jvc3Mgc2FtcGxlcy4KSW4gcHJhY3RpY2UsIHdlIGNhbiBpbXBsZW1lbnQgZnVsbC1xdWFudGlsZSBub3JtYWxpemF0aW9uIHVzaW5nIHRoZSBmb2xsb3dpbmcgcHJvY2VkdXJlCgogMS4gR2l2ZW4gYSBkYXRhIG1hdHJpeCAkXG1hdGhiZntZfV97RyBcdGltZXMgbn0kIGZvciAkRyQgZ2VuZXMgKHJvd3MpIGFuZCAkbiQgc2FtcGxlcyAoY29sdW1ucyksCiAyLiBzb3J0IGVhY2ggY29sdW1uIHRvIGdldCAkXG1hdGhiZntZfV5TJCwKIDMuIHJlcGxhY2UgYWxsIGVsZW1lbnRzIG9mIGVhY2ggcm93IGJ5IHRoZSBtZWRpYW4gKG9yIGF2ZXJhZ2UpIGZvciB0aGF0IHJvdywKIDQuIG9idGFpbiB0aGUgbm9ybWFsaXplZCBjb3VudHMgJFx0aWxkZXtcbWF0aGJme1l9fSQgYnkgcmUtYXJyYW5naW5nIChpLmUuLCB1bnNvcnRpbmcpIGVhY2ggY29sdW1uLgoKIyMgVW5jZXJ0YWludHkgaW4gbm9ybWFsaXphdGlvbgoKIC0gVGhlIG9mZnNldHMgdGhhdCBhcmUgYmVpbmcgY2FjbHVhdGVkIGZvciBub3JtYWxpemF0aW9uIHB1cnBvc2VzIGFyZSBlc3RpbWF0ZXMuCiAtIFRoZSBkb3duc3RyZWFtIGFuYWx5c2VzIGluY29ycG9yYXRlcyB0aGVzZSBlc3RpbWF0ZXMgYXMgaWYgdGhleSBhcmUga25vd24sIGkuZS4sIGNvbmRpdGlvbnMgb24gdGhlIGVzdGltYXRlcy4KIC0gVGhlIGFuYWx5c2lzIHRoZXJlZm9yZSAqKmlnbm9yZXMgdGhlIHVuY2VydGFpbnR5KiogaW4gdGhlaXIgZXN0aW1hdGlvbi4KIApgYGB7ciwgZWNobz1GQUxTRSwgZmlnLmNhcD1wYXN0ZSgiRmlndXJlOiBCb3ggMSBmcm9tIFZhbGxlam9zIGV0IGFsLiAoMjAxNykuIil9CiMgQWxsIGRlZmF1bHRzCmluY2x1ZGVfZ3JhcGhpY3MoIi4vaW1hZ2VzX3NlcXVlbmNpbmcvbm9ybWFsaXphdGlvblVuY2VydGFpbnR5LnBuZyIpCmBgYAoKCiMgQ2hhbGxlbmdlIElJSTogUGFyYW1ldGVyIGVzdGltYXRpb24gKHVuZGVyIGxpbWl0ZWQgaW5mb3JtYXRpb24gc2V0dGluZykKClRoZXJlIGFyZSB0d28gY2hhbGxlbmdlcyB0byBiZSBvdmVyY29tZSBoZXJlOgoKIC0gRmlyc3QsIHdlIG5lZWQgdG8gZ2V0IHRoZSBzdHJ1Y3R1cmUgb2Ygb3VyIG1lYW4gbW9kZWwgcmlnaHQsIHRoaXMgaXMsIHdoaWNoIGNvdmFyaWF0ZXMgdG8gaW5jbHVkZSwgYW5kIGhvdyB0byBpbmNsdWRlIHRoZW0sIHN1Y2ggdGhhdCB3ZSBhcmUgY2FwYWJsZSBvZiBjYXB0dXJpbmcgaW1wb3J0YW50IHNvdXJjZXMgb2YgdmFyaWF0aW9uIGluIG91ciBleHBlcmltZW50LCBpbiBvcmRlciB0byBkZXJpdmUgYSBjb3JyZWN0IGludGVycHJldGF0aW9uIG9mIHRoZSBkYXRhIGluIHRlcm1zIG9mIG91ciByZXNlYXJjaCBxdWVzdGlvbi4KIC0gU2Vjb25kLCB3ZSBuZWVkIHRvIGJlIGFibGUgdG8gZXN0aW1hdGUgdGhlIHBhcmFtZXRlcnMgb2Ygb3VyIG1vZGVsIGluIGFuIGVmZmljaWVudCB3YXksIHdoaWxlIGhhdmluZyBsaW1pdGVkIGluZm9ybWF0aW9uIChpLmUuLCB3ZSBhcmUgb2Z0ZW4gY29uZnJvbnRlZCB3aXRoIG9ubHkgYSBzbWFsbCBudW1iZXIgb2YgcmVwbGljYXRlcykuCgojIyBEZWZpbmluZyB0aGUgbW9kZWwgZm9yIHRoZSBtZWFuCgpMZXQncyBmaXJzdCBjaGVjayBob3cgdGhlIGF1dGhvcnMgb2YgdGhlIG9yaWdpbmFsIHN0dWR5IHBhcmFtZXRlcml6ZWQgdGhlIG1lYW4gbW9kZWwuCgpgYGB7ciwgZWNobz1GQUxTRSwgZmlnLmNhcD1wYXN0ZSgiRmlndXJlOiBBbm90aGVyIHBhcmFncmFwaCBmcm9tIHRoZSBNZXRob2RzIHNlY3Rpb24uIil9CiMgQWxsIGRlZmF1bHRzCmluY2x1ZGVfZ3JhcGhpY3MoIi4vaW1hZ2VzX3NlcXVlbmNpbmcvZGVBbmFseXNpc19wYXJhLnBuZyIpCmBgYAoKVGhlIGF1dGhvcnMgd3JpdGUgdGhhdCB0aGV5IGFyZSAiZW1wbG95aW5nIHRyZWF0bWVudCB0eXBlLCB0aW1lIHBvaW50IGFuZCBzYW1wbGUgSUQgYXMgZmFjdG9ycyBpbiB0aGUgbW9kZWwiLiBDb25jZXJuaW5nIGV4cGVyaW1lbnRhbCB2YXJpYWJsZXMsIHRoaXMgc3VnZ2VzdHMgdGhleSBoYXZlIGFkZGVkIGNvdmFyaWF0ZXMgZGVmaW5pbmcgdGhlIHRyZWF0bWVudCBhbmQgdGltZSBwb2ludCBmb3IgZWFjaCBzYW1wbGUuIFRoZSBzYW1wbGUgSUQgaW4gdGhlIHRleHQgcmVmZXJzIHRvIHRoZSBvcmlnaW5hbCB0aXNzdWUgc2FtcGxlIGFuZCB0aGVyZWZvcmUgY29ycmVzcG9uZHMgdG8gdGhlIGRvbm9yIHBhdGllbnQuIFdoaWxlIHRoZXJlIGlzIGFsc28gYSB2YXJpYWJsZSBjYWxsZWQgYHNhbXBsZWAgaW4gdGhlIGBjb2xEYXRhYCwgdGhpcyBpcyBub3Qgd2hhdCB0aGUgYXV0aG9ycyByZWZlciB0by4gTm90ZSB0aGUgYW1iaWd1aXR5IGhlcmUgYW5kIHNpbmNlIHRoZSBhdXRob3JzIGRpZG4ndCBzaGFyZSB0aGVpciBjb2RlLCB0aGlzIGlzIGhhcmQgdG8gY2hlY2shIEJ1dCwgbW9yZSBvbiByZXByb2R1Y2liaWxpdHkgbGF0ZXIuLi4KCioqUXVlc3Rpb24qKi4gV2hhdCBhcmUgdGhlIGF1dGhvcnMgYXNzdW1pbmcgd2hlbiB1c2luZyB0aGlzIHN0cnVjdHVyZSBmb3IgdGhlIG1lYW4gbW9kZWw/IERvIHlvdSB0aGluayB0aGF0IHRoZXJlIGFyZSBleHRlbnNpb25zIG9yIHNpbXBsaWZpY2F0aW9ucyBvZiB0aGUgbWVhbiBtb2RlbCB0aGF0IHdvdWxkIGJlIHJlbGV2YW50PwoKPGRldGFpbHM+PHN1bW1hcnk+IEFuc3dlci4gPC9zdW1tYXJ5PjxwPgpUaGUgYXV0aG9ycyBhcmUgYWNrbm93bGVkZ2luZyB0aGUgcmVsYXRlZG5lc3Mgb2Ygc2FtcGxlcyBkZXJpdmVkIGZyb20gdGhlIHNhbWUgZG9ub3IgcGF0aWVudCBieSBhZGRpbmcgaXQgYXMgYSBmaXhlZCBlZmZlY3QgdG8gdGhlIG1vZGVsLCB3aGljaCBpcyBncmVhdC4gVGhpcyBibG9ja2luZyBzdHJhdGVneSBoYXMgYmVlbiBleHRlbnNpdmVseSBkaXNjdXNzZWQgaW4gdGhlIHByb3Rlb21pY3MgcGFydCBvZiB0aGlzIGNvdXJzZS4KSG93ZXZlciwgYnkgb25seSBhZGRpbmcgYSBtYWluIGVmZmVjdCBmb3IgdHJlYXRtZW50IGFuZCB0aW1lLCB0aGV5IGFyZSBhc3N1bWluZyB0aGF0IHRoZSBlZmZlY3Qgb2YgdGltZSBpcyBpZGVudGljYWwgZm9yIGFsbCB0cmVhdG1lbnRzLCBpLmUuLCB0aGUgYXZlcmFnZSBnZW5lIGV4cHJlc3Npb24gaW4tL2RlY3JlYXNlIGF0IDQ4aCB2ZXJzdXMgMjRoIGlzIGlkZW50aWNhbCBmb3IgdGhlIERQTiwgT0hUIG9yIHRoZSBjb250cm9sIHNhbXBsZXMsIHdoaWNoIHNlZW1zIGxpa2UgYSBxdWl0ZSBzdHJpbmdlbnQgYXNzdW1wdGlvbi4KV2UgY2FuIG1ha2UgdGhlIG1vZGVsIG1vcmUgZmxleGlibGUgYnkgYWxsb3dpbmcgZm9yIGEgYHRyZWF0bWVudCAqIHRpbWVgIGludGVyYWN0aW9uLgo8L3A+PC9kZXRhaWxzPgoKIyMgUGFyYW1ldGVyIGVzdGltYXRpb24gYW5kIGVtcGlyaWNhbCBCYXllcwoKIC0gRXZlbiBpbiBsaW1pdGVkIHNhbXBsZSBzaXplIHNldHRpbmdzLCB0aGUgcGFyYW1ldGVycyAkXGJldGEkIG9mIHRoZSBtZWFuIG1vZGVsIG1heSBiZSBlc3RpbWF0ZWQgcmVhc29uYWJseSBlZmZpY2llbnRseSwgYW5kIHdlIGhhdmUgcHJldmlvdXNseSBkaXNjdXNzZWQgdGhlIElSTFMgYWxnb3JpdGhtIHRvIGRvIHNvLiAKIC0gSG93ZXZlciwgZXN0aW1hdGluZyBwYXJhbWV0ZXJzIGZvciB0aGUgdmFyaWFuY2UgKHRoaXMgaXMsIHRoZSBkaXNwZXJzaW9uIHBhcmFtZXRlciAkXHBoaSQgZnJvbSB0aGUgbmVnYXRpdmUgYmlub21pYWwgb3IgdGhlIHZhcmlhbmNlIHBhcmFtZXRlciAkXHNpZ21hJCBmcm9tIHRoZSBHYXVzc2lhbikgaXMgdHlwaWNhbGx5IHF1aXRlIGEgYml0IGhhcmRlci4KIC0gSW4gZ2Vub21pY3MsIHdlIG9mdGVuIHRha2UgYWR2YW50YWdlIG9mIHRoZSBwYXJhbGxlbCBzdHJ1Y3R1cmUgb2YgdGhlIHRob3VzYW5kcyBvZiByZWdyZXNzaW9uIG1vZGVscyAob25lIGZvciBlYWNoIGdlbmUpIHRvICoqYm9ycm93IGluZm9ybWF0aW9uIGFjcm9zcyBnZW5lcyoqIGluIGEgcHJvY2VkdXJlIGNhbGxlZCAqKmVtcGlyaWNhbCBCYXllcyoqLCBhcyBhbHNvIHNlZW4gaW4gdGhlIHByb3Rlb21pY3MgcGFydCBvZiB0aGlzIGNvdXJzZS4gCiAtIEluIHRoZSBCYXllc2lhbiBzZXR0aW5nLCB3ZSB1c2Ugbm90IG9ubHkgdGhlIGRhdGEsIGJ1dCBhbHNvIGEgcHJpb3IgZGlzdHJpYnV0aW9uLCB0byBkZXJpdmUgb3VyIHBhcmFtZXRlciBlc3RpbWF0ZXMuIEluIGEgdHJhZGl0aW9uYWwgQmF5ZXNpYW4gYW5hbHlzaXMsIG9uZSBhc3N1bWVzIGFuICphIHByaW9yaSoga25vd24gcHJpb3IgZGlzdHJpYnV0aW9uLCB3aGljaCByZWZsZWN0cyBvdXIgcHJpb3IgYmVsaWVmIGludG8gYWxsIHBvc3NpYmxlIHZhbHVlcyBvZiB0aGUgcGFyYW1ldGVyLiBUaGlzIHByaW9yIGRpc3RyaWJ1dGlvbiBpcyBjb21wbGV0ZWx5IGluZGVwZW5kZW50IG9mIHRoZSBvYnNlcnZlZCBkYXRhIGFuZCB0aGUgaWRlYSBpcyB0aGF0IG9uZSBzcGVjaWZpZXMgdGhlIHByaW9yIGRpc3RyaWJ1dGlvbiBiZWZvcmUgb2JzZXJ2aW5nIHRoZSBkYXRhLiBPbmUgY2FuIHRoZW4gdXNlIHRoZSBkYXRhIGFuZCBwcmlvciBkaXN0cmlidXRpb24gdG8gZGVyaXZlIGEgKipwb3N0ZXJpb3IgZGlzdHJpYnV0aW9uKiogZm9yIHRoZSBwYXJhbWV0ZXIocykgJFx0aGV0YSQgb2YgaW50ZXJlc3QgdGhyb3VnaCBCYXllcyBydWxlCiAkJAogcChcdGhldGEgfCBcbWF0aGJme1l9KSA9IFxmcmFje3AoXG1hdGhiZntZfSB8IFx0aGV0YSkgcChcdGhldGEpfXtcaW50X3tcdGhldGEgXGluIFxUaGV0YX0gcChcbWF0aGJme1l9IHwgXHRoZXRhKSBwKFx0aGV0YSkgZFx0aGV0YX0uCiAkJAogICAgSGVyZSwgdGhlIHBvc3RlcmlvciBkaXN0cmlidXRpb24gJHAoXHRoZXRhIHwgXG1hdGhiZntZfSkkIGlzIGNhbGN1bGF0ZWQgdXNpbmcgdGhlIGRhdGEgbGlrZWxpaG9vZCAkcChcbWF0aGJme1l9IHwgXHRoZXRhKSQsIHByaW9yIGRpc3RyaWJ1dGlvbiAkcChcdGhldGEpJCBhbmQgdGhlICdtYXJnaW5hbCBsaWtlbGlob29kJyAkXGludF97XHRoZXRhIFxpbiBcVGhldGF9IHAoXG1hdGhiZntZfSB8IFx0aGV0YSkgcChcdGhldGEpIGRcdGhldGEkLCB3aGVyZSAkXFRoZXRhJCBkZW5vdGVzIHRoZSBwYXJhbWV0ZXIgc3BhY2Ugb2YgJFx0aGV0YSQuIFdlIGNhbiBzZWUgdGhhdCB0aGUgcG9zdGVyaW9yIHByb2JhYmlsaXR5IGZvciBhIHNwZWNpZmljIHZhbHVlIG9mIHRoZSBwYXJhbWV0ZXIgJFx0aGV0YSQgd2lsbCBiZSBoaWdoIGlmIGJvdGggdGhlIGRhdGEgbGlrZWxpaG9vZCBhcyB3ZWxsIGFzIHByaW9yIHByb2JhYmlsaXR5IGFyZSBoaWdoLgogICAgCiAtIEluIGVtcGlyaWNhbCBCYXllcywgd2UgYmFzaWNhbGx5IHRha2UgYSBzZW1pLUJheWVzaWFuIGFwcHJvYWNoIHRvIHBhcmFtZXRlciBlc3RpbWF0aW9uLiBJbmRlZWQsIHdlIGRvIG5vdCBhc3N1bWUgYSBrbm93biBwcmlvciBkaXN0cmlidXRpb24sIGJ1dCBlc3RpbWF0ZSBpdCBlbXBpcmljYWxseSB1c2luZyB0aGUgZGF0YS4gVGhpcyBlbXBpcmljYWxseSBlc3RpbWF0ZWQgcHJpb3IgJFxoYXR7cH0oXHRoZXRhKSQgaXMgdGhlbiB1c2VkIHRvIGNhbGN1bGF0ZSB0aGUgcG9zdGVyaW9yIGRpc3RyaWJ1dGlvbi4KIC0gV2hpbGUgaW4gc29tZSBzZXR0aW5ncyBvbmUgY2FuIGVhc2lseSBjYWxjdWxhdGUgdGhlIHBvc3RlcmlvciBkaXN0cmlidXRpb24sIHNvbWV0aW1lcyBpdCBjYW4gYmUgYSBoYXJkIHByb2JsZW0uIEluIHN1Y2ggY2FzZXMsIGl0IG1heSBiZSB1c2VmdWwgdG8gcmVzdHJpY3Qgb3Vyc2VsdmVzIHRvIGNhbGN1bGF0aW5nIHRoZSAqKm1heGltdW0gYSBwb3N0ZXJpb3JpIChNQVApKiogZXN0aW1hdGUsIHdoaWNoIGNvcnJlc3BvbmRzIHRvIHRoZSBtb2RlIG9mIHRoZSBwb3N0ZXJpb3IgZGlzdHJpYnV0aW9uLiBUaGlzIGNhbiBiZSBjb25zaWRlcmVkIHRvIGJlIGFuYWxvZ291cyB0byBwb2ludCBlc3RpbWF0aW9uIGluIHRoZSBmcmVxdWVudGlzdCBzZXR0aW5nLgogLSBJbiBvdXIgc2V0dGluZywgd2UgdXNlIGdlbmVzIHdpdGggYSBzaW1pbGFyIGF2ZXJhZ2UgZXhwcmVzc2lvbiB0byBtb2RlcmF0ZSB0aGUgZGlzcGVyc2lvbiBlc3RpbWF0ZSBmb3IgYSBwYXJ0aWN1bGFyIGdlbmUuIFRoZSBiYXNpYyBhc3N1bXB0aW9uIGZvciB0aGlzIHRvIG1ha2Ugc2Vuc2UgaXMgdGhhdCBnZW5lcyB3aXRoIHNpbWlsYXIgbWVhbnMgbWlnaHQgaGF2ZSBzaW1pbGFyIGRpc3BlcnNpb24gcGFyYW1ldGVycyAob3IgdmFyaWFuY2VzKSwgb3dpbmcgdG8gdGhlIG1lYW4tdmFyaWFuY2UgdHJlbmQuIAoKIC0tLQogCiAtIE9uY2UgaW5pdGlhbCBlc3RpbWF0ZXMgZm9yIHRoZSBnZW5lLXdpc2UgZGlzcGVyc2lvbnMgaGF2ZSBiZWVuIGRlcml2ZWQgKCRcaGF0e1xQaGl9X2dee01MfSQgaW4gdGhlIGZpZ3VyZSksIHdlIHVzZSBhIHBhcmFtZXRyaWMgbW9kZWwgdG8gZXN0aW1hdGUgaXRzIGRpc3RyaWJ1dGlvbiAodHlwaWNhbGx5IGFzIGEgZnVuY3Rpb24gb2YgdGhlIG1lYW4pIGFjcm9zcyBnZW5lcy4gVGhpcyBkaXN0cmlidXRpb24gaXMgdGhlICoqcHJpb3IgZGlzdHJpYnV0aW9uKiouCiAtIFRoZW4sIGVhY2ggaW5pdGlhbCBlc3RpbWF0ZSBpcyBzaHJ1bmtlbiB0b3dhcmRzIHRoYXQgZW1waXJpY2FsbHkgZXN0aW1hdGVkIHByaW9yIGRpc3RyaWJ1dGlvbi4gVGhlIGFtb3VudCBvZiBzaHJpbmthZ2UgYmVpbmcgcGVyZm9ybWVkIGlzIGRhdGEtZHJpdmVuLCBhbmQgZGVwZW5kcyBvbiB0aGUgZGF0YSwgdGFraW5nIGludG8gYWNjb3VudCB0aGUgcHJlY2lzaW9uIG9mIG91ciBpbml0aWFsIGVzdGltYXRlIChpLmUuLCBzaGFwZSBvZiB0aGUgbGlrZWxpaG9vZCkgYW5kIHRoZSB2YXJpYWJpbGl0eSBvZiB0aGUgcHJpb3IgZGlzdHJpYnV0aW9uLgogLSBUaGVzZSBzdHJhdGVnaWVzIHJlc3VsdCBpbiBpbXByZXNzaXZlIHBlcmZvcm1hbmNlIGdhaW5zIGluIHRlcm1zIG9mIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2lzIGFuZCBhcmUgaW1wbGVtZW50ZWQgaW4gYWxsIHBvcHVsYXIgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgc29mdHdhcmUgcGFja2FnZXMgKHRob3VnaCBpbiBzbGlnaHRseSBkaWZmZXJpbmcgd2F5cykgbGlrZSBgbGltbWFgLCBgZWRnZVJgIGFuZCBgREVTZXEyYC4KCmBgYHtyLCBlY2hvPUZBTFNFLCBmaWcuY2FwPXBhc3RlKCJGaWd1cmUgOCBmcm9tIFZhbiBkZW4gQmVyZ2UgKmV0IGFsLiogKDIwMTkpLiIpfQojIEFsbCBkZWZhdWx0cwppbmNsdWRlX2dyYXBoaWNzKCIuL2ltYWdlc19zZXF1ZW5jaW5nL2VtcGlyaWNhbEJheWVzLnBuZyIpCmBgYAoKVGhlIGJsb2cgcG9zdCBvbiBbdW5kZXJzdGFuZGluZyBlbXBpcmljYWwgQmF5ZXMgZXN0aW1hdGlvbiB1c2luZyBiYXNlYmFsbCBzdGF0aXN0aWNzXShodHRwOi8vdmFyaWFuY2VleHBsYWluZWQub3JnL3IvZW1waXJpY2FsX2JheWVzX2Jhc2ViYWxsLykgaXMgYSBncmVhdCBwcmltZXIgZm9yIGZ1cnRoZXIgcmVhZGluZywgYXMgd2VsbCBhcyB0aGUgW2FjY29tcGFueWluZyBib29rXShodHRwczovL2Ryb2IuZ3Vtcm9hZC5jb20vbC9lbXBpcmljYWwtYmF5ZXMpIGJ5IERhdmlkIFJvYmluc29uLgoKIyMgSW4gcHJhY3RpY2UKCkxldCdzIGZpdCB0aGUgbW9kZWwgdXNpbmcgYGVkZ2VSYC4KCmBgYHtyfQpkZXNpZ24gPC0gbW9kZWwubWF0cml4KH4gdHJlYXRtZW50KnRpbWUgKyBwYXRpZW50LCBkYXRhPWNvbERhdGEoc2UpKQoKCmRnZSA8LSBjYWxjTm9ybUZhY3RvcnMoc2UpCmRnZSA8LSBlc3RpbWF0ZURpc3AoZGdlLCBkZXNpZ24pICMgZXN0aW1hdGUgZGlzcGVyc2lvbiBlc3RpbWF0ZXMKcGxvdEJDVihkZ2UpCmZpdCA8LSBnbG1GaXQoZGdlLCBkZXNpZ24pCmhlYWQoZml0JGNvZWZmaWNpZW50cykKCgpgYGAKCiMgQ2hhbGxlbmdlIElWOiBTdGF0aXN0aWNhbCBpbmZlcmVuY2UgYWNyb3NzIG1hbnkgZ2VuZXMKCiMjIENvbnRyYXN0cyBvbiB0aGUgdHJlYXRtZW50IGVmZmVjdHMKCldlIHdpbGwgZmlyc3QgZGVyaXZlIGFsbCBjb250cmFzdHMgdGhhdCBhcmUgYWxzbyBpbnZlc3RpZ2F0ZWQgaW4gdGhlIG9yaWdpbmFsIG1hbnVzY3JpcHQsIHVzaW5nIG91ciBleHRlbmRlZCBtb2RlbCB3aGVyZSB3ZSBhcmUgYWxsb3dpbmcgZm9yIGFuIGludGVyYWN0aW9uIGVmZmVjdCBiZXR3ZWVuIHRyZWF0bWVudCBhbmQgdGltZS4KClRoZSBtZWFuIG1vZGVsIGlzCiQkIFxsb2coXG11X3tnaX0pID0gXGJldGFfe2cwfSArIFxiZXRhX3tnMX0geF97RFBOfSArIFxiZXRhX3tnMn0geF97T0hUfSArIFxiZXRhX3tnM30geF97NDhofSArIFxiZXRhX3tnNH0geF97cGF0Mn0gKyBcYmV0YV97ZzV9IHhfe3BhdDN9ICsgXGJldGFfe2c2fSB4X3twYXQ0fSArIFxcIFxiZXRhX3tnN30geF97RFBOOjQ4aH0gKyBcYmV0YV97Zzh9IHhfe09IVDo0OGh9LiAkJAoKVGhlIGludGVyY2VwdCBjb3JyZXNwb25kcyB0byB0aGUgbG9nIGF2ZXJhZ2UgZ2VuZSBleHByZXNzaW9uIGluIHRoZSBjb250cm9sIGdyb3VwIGF0IDI0aCBmb3IgcGF0aWVudCAxLgoKKipEUE4gMjRoIHZzIGNvbnRyb2wgMjRoLioqClRoZSByZXNwZWN0aXZlIG1lYW5zIGFyZQokJFxsb2cgXG11X3tnLERQTiwyNGh9ID0gXGJldGFfe2cwfSArIFxiZXRhX3tnMX0sJCQKJCRcbG9nIFxtdV97Zyxjb24sMjRofSA9IFxiZXRhX3tnMH0uJCQKQW5kIHRoZWlyIGRpZmZlcmVuY2UgaXMKJCQgXGRlbHRhX2cgPSBcYmV0YV97ZzF9LiAkJAoKKipEUE4gNDhoIHZzIGNvbnRyb2wgNDhoLioqClRoZSByZXNwZWN0aXZlIG1lYW5zIGFyZQokJFxsb2cgXG11X3tnLERQTiw0OGh9ID0gXGJldGFfe2cwfSArIFxiZXRhX3tnMX0gKyBcYmV0YV97ZzN9ICsgXGJldGFfe2c3fSwkJAokJFxsb2cgXG11X3tnLGNvbiw0OGh9ID0gXGJldGFfe2cwfSArIFxiZXRhX3tnM30uJCQKQW5kIHRoZWlyIGRpZmZlcmVuY2UgaXMKJCQgXGRlbHRhX2cgPSBcYmV0YV97ZzF9ICsgXGJldGFfe2c3fS4gJCQKCioqT0hUIDI0aCB2cyBjb250cm9sIDI0aC4qKgpUaGUgcmVzcGVjdGl2ZSBtZWFucyBhcmUKJCRcbG9nIFxtdV97ZyxPSFQsMjRofSA9IFxiZXRhX3tnMH0gKyBcYmV0YV97ZzJ9ICwkJAokJFxsb2cgXG11X3tnLGNvbiwyNGh9ID0gXGJldGFfe2cwfS4kJApBbmQgdGhlaXIgZGlmZmVyZW5jZSBpcwokJCBcZGVsdGFfZyA9IFxiZXRhX3tnMn0uICQkCgoqKk9IVCA0OGggdnMgY29udHJvbCA0OGguKioKVGhlIHJlc3BlY3RpdmUgbWVhbnMgYXJlCiQkXGxvZyBcbXVfe2csT0hULDQ4aH0gPSBcYmV0YV97ZzB9ICsgXGJldGFfe2cyfSArIFxiZXRhX3tnM30gKyBcYmV0YV97Zzh9LCQkCiQkXGxvZyBcbXVfe2csY29uLDQ4aH0gPSBcYmV0YV97ZzB9ICsgXGJldGFfe2czfS4kJApBbmQgdGhlaXIgZGlmZmVyZW5jZSBpcwokJCBcZGVsdGFfZyA9IFxiZXRhX3tnMn0gKyBcYmV0YV97Zzh9LiAkJAoKSG93ZXZlciwgd2UgY2FuIGFsc28gYXNzZXNzIHRoZSBpbnRlcmFjdGlvbiBlZmZlY3RzOiBpcyB0aGUgdGltZSBlZmZlY3QgZGlmZmVyZW50IGJldHdlZW4gRFBOIGFuZCBPSFQgdHJlYXRtZW50IHZlcnN1cyB0aGUgY29udHJvbD8gQW5kIGhvdyBhYm91dCB0aGUgRFBOIHZzIE9IVCB0cmVhdG1lbnRzPwoKKipEUE4gdnMgY29udHJvbCBpbnRlcmFjdGlvbi4qKgpUaGUgdGltZSBlZmZlY3QgZm9yIGVhY2ggY29uZGl0aW9uIGlzCiQkIFxkZWx0YV97RFBOfSA9IFxsb2cgXG11X3tnLERQTiw0OGh9IC0gXGxvZyBcbXVfe2csRFBOLDI0aH0gPSBcYmV0YV97ZzN9ICsgXGJldGFfe2c3fSwkJAokJCBcZGVsdGFfe2Nvbn0gPSBcbG9nIFxtdV97Zyxjb24sNDhofSAtIFxsb2cgXG11X3tnLGNvbiwyNGh9ID0gXGJldGFfe2czfS4gJCQKU28gdGhlIGludGVyYWN0aW9uIGVmZmVjdCBpcwokJFxkZWx0YV97RFBOLWNvbn0gPSBcYmV0YV97Zzd9LiQkCgoqKk9IVCB2cyBjb250cm9sIGludGVyYWN0aW9uLioqClRoZSB0aW1lIGVmZmVjdCBmb3IgZWFjaCBjb25kaXRpb24gaXMKJCQgXGRlbHRhX3tPSFR9ID0gXGxvZyBcbXVfe2csT0hULDQ4aH0gLSBcbG9nIFxtdV97ZyxPSFQsMjRofSA9IFxiZXRhX3tnM30gKyBcYmV0YV97Zzh9LCQkCiQkIFxkZWx0YV97Y29ufSA9IFxsb2cgXG11X3tnLGNvbiw0OGh9IC0gXGxvZyBcbXVfe2csY29uLDI0aH0gPSBcYmV0YV97ZzN9LiAkJApTbyB0aGUgaW50ZXJhY3Rpb24gZWZmZWN0IGlzCiQkXGRlbHRhX3tEUE4tY29ufSA9IFxiZXRhX3tnOH0uJCQKCioqT0hUIHZzIERQTiBpbnRlcmFjdGlvbi4qKgpUaGUgdGltZSBlZmZlY3QgZm9yIGVhY2ggY29uZGl0aW9uIGlzCiQkIFxkZWx0YV97T0hUfSA9IFxsb2cgXG11X3tnLE9IVCw0OGh9IC0gXGxvZyBcbXVfe2csT0hULDI0aH0gPSBcYmV0YV97ZzN9ICsgXGJldGFfe2c4fSwkJAokJCBcZGVsdGFfe0RQTn0gPSBcbG9nIFxtdV97ZyxEUE4sNDhofSAtIFxsb2cgXG11X3tnLERQTiwyNGh9ID0gXGJldGFfe2czfSArIFxiZXRhX3tnN30sJCQKU28gdGhlIGludGVyYWN0aW9uIGVmZmVjdCBpcwokJFxkZWx0YV97T0hULURQTn0gPSBcYmV0YV97Zzh9IC0gXGJldGFfe2c3fS4kJAoKTGV0J3MgaW1wbGVtZW50IGFsbCBvZiB0aGVzZSBpbiBhIGNvbnRyYXN0IG1hdHJpeC4KCmBgYHtyfQpMIDwtIG1hdHJpeCgwLCBucm93ID0gbmNvbChmaXQkY29lZmZpY2llbnRzKSwgbmNvbCA9IDcpCnJvd25hbWVzKEwpIDwtIGNvbG5hbWVzKGZpdCRjb2VmZmljaWVudHMpCmNvbG5hbWVzKEwpIDwtIGMoIkRQTnZzQ09OMjQiLCAiRFBOdnNDT040OCIsCiAgICAgICAgICAgICAgICAgIk9IVHZzQ09OMjQiLCAiT0hUdnNDT040OCIsCiAgICAgICAgICAgICAgICAgIkRQTnZzQ09OSW50IiwgIk9IVHZzQ09OSW50IiwKICAgICAgICAgICAgICAgICAiT0hUdnNEUE5JbnQiKQojIERQTiB2cyBjb250cm9sIGF0IDI0aApMWzIsIkRQTnZzQ09OMjQiXSA8LSAxCiMgRFBOIHZzIGNvbnRyb2wgYXQgNDhoCkxbYygyLDgpLCJEUE52c0NPTjQ4Il0gPC0gMQojIE9IVCB2cyBjb250cm9sIGF0IDI0aApMWzMsIk9IVHZzQ09OMjQiXSA8LSAxCiMgT0hUIHZzIGNvbnRyb2wgYXQgNDhoCkxbYygzLDkpLCJPSFR2c0NPTjQ4Il0gPC0gMQojIERQTiBjb250cm9sIGludGVyYWN0aW9uCkxbOCwiRFBOdnNDT05JbnQiXSA8LSAxCiMgT0hUIGNvbnRyb2wgaW50ZXJhY3Rpb24KTFs5LCJPSFR2c0NPTkludCJdIDwtIDEKIyBPSFQgRFBOIGludGVyYWN0aW9uCkxbYyg5LDgpLCJPSFR2c0RQTkludCJdIDwtIGMoMSwgLTEpCgpMCmBgYAoKQW5kLCBmaW5hbGx5LCB3ZSBjYW4gYXNzZXNzIGVhY2ggaHlwb3RoZXNpcyB1c2luZyB0aGUgYGdsbUxSVGAgZnVuY3Rpb24gaW1wbGVtZW50ZWQgaW4gYGVkZ2VSYC4gV2UgY2FuIGFzc2VzcyBlYWNoIGh5cG90aGVzaXMgc2VwYXJhdGVseSBieSBsb29waW5nIG92ZXIgdGhlIGNvbnRyYXN0cy4KCmBgYHtyfQpscnRMaXN0IDwtIGxpc3QoKSAjbGlzdCBvZiByZXN1bHRzCmZvcihjYyBpbiAxOm5jb2woTCkpIGxydExpc3RbW2NjXV0gPC0gZ2xtTFJUKGZpdCwgY29udHJhc3QgPSBMWyxjY10pCgojIHAtdmFsdWUgaGlzdG9ncmFtcwpwdmFsTGlzdCA8LSBsYXBwbHkobHJ0TGlzdCwgZnVuY3Rpb24oeCkgeCR0YWJsZSRQVmFsdWUpCnB2YWxNYXQgPC0gZG8uY2FsbChjYmluZCwgIHB2YWxMaXN0KQpjb2xuYW1lcyhwdmFsTWF0KSA8LSBjb2xuYW1lcyhMKQpwYXIobWZyb3c9YygzLDMpKQpzYXBwbHkoMTpuY29sKHB2YWxNYXQpLCBmdW5jdGlvbihpaSkgaGlzdChwdmFsTWF0WyxpaV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYWluID0gY29sbmFtZXMocHZhbE1hdClbaWldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB4bGFiID0gInAtdmFsdWUiKSkKYGBgCgojIyMgTXVsdGlwbGUgdGVzdGluZwoKYGBge3J9CiMgbnVtYmVyIG9mIERFIGdlbmVzCnBhZGpNYXQgPC0gYXBwbHkocHZhbE1hdCwgMiwgcC5hZGp1c3QsIG1ldGhvZD0iZmRyIikKY29sU3VtcyhwYWRqTWF0IDw9IDAuMDUgKQpgYGAKCldlIGFyZSBmaW5kaW5nIGxvdyBudW1iZXJzIG9mIERFIGdlbmVzIGJldHdlZW4gdHJlYXRtZW50cyBhdCBhIDVcJSBGRFIgbGV2ZWwuIFRoaXMgd2FzIGFscmVhZHkgcmVmbGVjdGVkIGluIHRoZSB0aGUgTURTIHBsb3RzLgoKIyMjIFZpc3VhbGl6YXRpb24KCkxldCdzIHZpc3VhbGl6ZSBzb21lIHJlc3VsdHMgZm9yIHRoZSBEUE4gdnMgY29udHJvbCBhdCA0OGggY29udHJhc3QuCgpgYGB7cn0KbGlicmFyeShzY2FsZXMpICMgZm9yIHNjYWxlczo6YWxwaGEoKQpkZUdlbmVzIDwtIHAuYWRqdXN0KGxydExpc3RbWzJdXSR0YWJsZSRQVmFsdWUsICJmZHIiKSA8PSAwLjA1CgojIyB2b2xjYW5vIHBsb3QKcGxvdCh4ID0gbHJ0TGlzdFtbMl1dJHRhYmxlJGxvZ0ZDLAogICAgIHkgPSAtbG9nMTAobHJ0TGlzdFtbMl1dJHRhYmxlJFBWYWx1ZSksCiAgICAgeGxhYiA9ICJsb2cgRm9sZC1jaGFuZ2UiLAogICAgIHlsYWIgPSAiLWxvZzEwIFAtdmFsdWUiLAogICAgIHBjaCA9IDE2LCBjb2wgPSBhbHBoYShkZUdlbmVzKzEsIC40KSwgCiAgICAgY2V4PTIvMywgYnR5PSdsJykKbGVnZW5kKCJ0b3ByaWdodCIsIGMoIkRFIiwgIm5vdCBERSIpLAogICAgICAgY29sID0gMjoxLCBwY2g9MTYsIGJ0eT0nbicpCgojIyBNRC1wbG90CnBsb3QoeCA9IGxydExpc3RbWzJdXSR0YWJsZSRsb2dDUE0sCiAgICAgeSA9IGxydExpc3RbWzJdXSR0YWJsZSRsb2dGQywKICAgICB4bGFiID0gIkF2ZXJhZ2UgbG9nIENQTSIsCiAgICAgeWxhYiA9ICJMb2cgZm9sZC1jaGFuZ2UiLAogICAgIHBjaCA9IDE2LCBjb2wgPSBhbHBoYShkZUdlbmVzKzEsIC40KSwgCiAgICAgY2V4PTIvMywgYnR5PSdsJykKbGVnZW5kKCJ0b3ByaWdodCIsIGMoIkRFIiwgIm5vdCBERSIpLAogICAgICAgY29sID0gMjoxLCBwY2g9MTYsIGJ0eT0nbicpCmFibGluZShoPTAsIGNvbD0ib3JhbmdlIiwgbHdkPTIsIGx0eT0yKQoKYGBgCgogLS0tCgpgYGB7cn0KIyBleHRyYWN0IGFsbCBERSBnZW5lcwpkZUdlbmVzIDwtIHJvd25hbWVzKGxydExpc3RbWzJdXSR0YWJsZSlbcC5hZGp1c3QobHJ0TGlzdFtbMl1dJHRhYmxlJFBWYWx1ZSwgImZkciIpIDw9IDAuMDVdCmRlR2VuZXMKIyBvcmRlciBhY2NvcmRpbmcgdG8gYWJzb2x1dGUgZm9sZC1jaGFuZ2UKb3JkZXJlZERFR2VuZXMgPC0gZGVHZW5lc1tvcmRlcihhYnMobHJ0TGlzdFtbMl1dJHRhYmxlW2RlR2VuZXMsICJsb2dGQyJdKSwgZGVjcmVhc2luZyA9IFRSVUUpXQoKcGFyKG1mcm93PWMoMywzKSkKZm9yKGtrIGluIDE6OSl7CiAgYm94cGxvdChsb2cxcChhc3NheXMoc2UpJGNvdW50c1tvcmRlcmVkREVHZW5lc1tra10sXSkgfiBpbnRlcmFjdGlvbih0cmVhdG1lbnQsIHRpbWUpKQogIGJveHBsb3QoZml0JGZpdHRlZC52YWx1ZXNbb3JkZXJlZERFR2VuZXNba2tdLF0gfiBpbnRlcmFjdGlvbih0cmVhdG1lbnQsIHRpbWUpKQp9CmBgYAoKCiMjIENvbnRyYXN0IG9uIHRoZSB0aW1lIGVmZmVjdAoKQmFzZWQgb24gdGhlIE1EUyBwbG90LCB3ZSBjYW4gZXhwZWN0IGNvbXBhcmF0aXZlbHkgbW9yZSBERSBnZW5lcyBmb3IgdGhlIHRpbWUgZWZmZWN0LiBGb3IgZGlkYWN0aWMgcHVycG9zZXMsIGhlcmUgd2UgYXNzZXNzIGFuICoqYXZlcmFnZSB0aW1lIGVmZmVjdCBhY3Jvc3MgdGhlIHRocmVlIHRyZWF0bWVudHMqKi4gVGhlIGFuYWx5c2lzIHNob3dzIGhvdyBmbGV4aWJsZSBvbmUgY2FuIGJlIHdoZW4gdXNpbmcgY29udHJhc3RzLgoKTWVhbiBvZiB0aW1lIDI0aDoKJCRcbG9nIFxtdV97ZywyNGh9ID0gXGZyYWN7MX17M30gXGxlZnRceyBcdW5kZXJicmFjZXsoXGJldGFfe2cwfSl9X1x0ZXh0e0NvbnRyb2wsIDI0aH0gKyBcdW5kZXJicmFjZXsoXGJldGFfe2cwfSArIFxiZXRhX3tnMX0pfV9cdGV4dHtEUE4sIDI0aH0gKyBcdW5kZXJicmFjZXsoXGJldGFfe2cwfSArIFxiZXRhX3tnMn0pfV9cdGV4dHtPSFQsIDI0aH0gXHJpZ2h0XH0kJAoKTWVhbiBvZiB0aW1lIDQ4aDoKJCRcbG9nIFxtdV97Zyw0OGh9ID0gXGZyYWN7MX17M30gXGxlZnRceyBcdW5kZXJicmFjZXsoXGJldGFfe2cwfSArIFxiZXRhX3tnM30pfV9cdGV4dHtDb250cm9sLCA0OGh9ICsgXHVuZGVyYnJhY2V7KFxiZXRhX3tnMH0gKyBcYmV0YV97ZzF9ICsgXGJldGFfe2czfSArIFxiZXRhX3tnN30pfV9cdGV4dHtEUE4sIDQ4aH0gKyBcdW5kZXJicmFjZXsoXGJldGFfe2cwfSArIFxiZXRhX3tnMn0rIFxiZXRhX3tnM30gKyBcYmV0YV97Zzh9KX1fXHRleHR7T0hULCA0OGh9IFxyaWdodFx9JCQKCkRpZmZlcmVuY2U6CiQkIFxsb2cgXGxlZnQoIFxmcmFje1xtdV97Zyw0OGh9fXtcbXVfe2csMjRofX0gXHJpZ2h0KSA9IFxiZXRhX3tnM30gKyBcZnJhY3sxfXszfShcYmV0YV97Zzd9ICsgXGJldGFfe2c4fSkgJCQKCgpgYGB7cn0KTHRpbWUgPC0gbWF0cml4KDAsIG5yb3cgPSBuY29sKGZpdCRjb2VmZmljaWVudHMpLCBuY29sID0gMSkKcm93bmFtZXMoTHRpbWUpIDwtIGNvbG5hbWVzKGZpdCRjb2VmZmljaWVudHMpCkx0aW1lW2MoInRpbWU0OGgiLCAidHJlYXRtZW50RFBOOnRpbWU0OGgiLCAidHJlYXRtZW50T0hUOnRpbWU0OGgiKSwxXSA8LSBjKDEsIDEvMywgMS8zKQoKbHJ0VGltZSA8LSBnbG1MUlQoZml0LCBjb250cmFzdD1MdGltZSkKaGlzdChscnRUaW1lJHRhYmxlJFBWYWx1ZSkgIyB2ZXJ5IGRpZmZlcmVudCBwLXZhbHVlIGRpc3RyaWJ1dGlvbgoKc3VtKHAuYWRqdXN0KGxydFRpbWUkdGFibGUkUFZhbHVlLCAiZmRyIikgPD0gMC4wNSkgIyBtYW55IERFIGdlbmVzCmBgYAoKCiMgQWx0ZXJuYXRpdmUgcGFyYW1ldGVyaXphdGlvbnMKCldoaWxlIG91ciBkZXNpZ24gbWF0cml4IGhlcmUgd2FzIHBhcmFtZXRlcml6ZWQgYXMgYH4gdHJlYXRtZW50KnRpbWUgKyBwYXRpZW50YCBhbHRlcm5hdGl2ZSwgZXF1aXZhbGVudCBwYXJhbWV0ZXJpemF0aW9ucyBhcmUgYWxzbyBwb3NzaWJsZS4KQmVsb3csIHdlIGRlbW9uc3RyYXRlIGFub3RoZXIgcGFyYW1ldGVyaXphdGlvbiB0aGF0IGNvdWxkIHdvcmssIHRvbywgYW5kIGNhbiBiZSBtb3JlIGludHVpdGl2ZS4gSW4gdGhpcyBwYXJhbWV0ZXJpemF0aW9uLCB3ZSBlc3RpbWF0ZSBhIG1lYW4gZm9yIGVhY2ggZXhwZXJpbWVudGFsIGNvbmRpdGlvbiwgd2l0aG91dCBhbiBpbnRlcmNlcHQsIHdoaWNoIGNhbiBiZSBjb252ZW5pZW50IHRvIHRoaW5rIGFib3V0IGhvdyB0byBzZXQgdXAgY29udHJhc3RzLgoKYGBge3J9CnRyZWF0VGltZSA8LSBhcy5mYWN0b3IocGFzdGUwKHRyZWF0bWVudCwgdGltZSkpCnRhYmxlKHRyZWF0VGltZSkKCmRlc2lnbjIgPC0gbW9kZWwubWF0cml4KH4gMCArIHRyZWF0VGltZSArIHBhdGllbnQpCgpkZ2UyIDwtIGNhbGNOb3JtRmFjdG9ycyhzZSkKZGdlMiA8LSBlc3RpbWF0ZURpc3AoZGdlMiwgZGVzaWduMikKcGxvdEJDVihkZ2UyKQpmaXQyIDwtIGdsbUZpdChkZ2UyLCBkZXNpZ24yKQpoZWFkKGZpdDIkY29lZmZpY2llbnRzKQoKIyMgZm9yIGV4YW1wbGU6IHRoZSBlc3RpbWF0ZSBmb3IgdGhlIERQTjI0aCB2cyBjb250cm9sIDI0aCBpcyBzdGlsbCB0aGUgc2FtZSwKIyMgYnV0IHJlcXVpcmVzIGEgZGlmZmVyZW50IGNvbWJpbmF0aW9uIG9mIHBhcmFtZXRlcnMKcGxvdChmaXQkY29lZmZpY2llbnRzWywidHJlYXRtZW50RFBOIl0sIAogICAgIGZpdDIkY29lZmZpY2llbnRzWywidHJlYXRUaW1lRFBOMjRoIl0gLSBmaXQyJGNvZWZmaWNpZW50c1ssInRyZWF0VGltZUNvbnRyb2wyNGgiXSwKICAgICB4bGFiPSJJbnRlcmNlcHQgbW9kZWwgZXN0aW1hdGUiLCB5bGFiPSJObyBpbnRlcmNlcHQgbW9kZWwgZXN0aW1hdGUiKQoKIyBMZXQncyBpbXBsZW1lbnQgdGhlIERQTnZzQ09ONDggY29udHJhc3QKTDIgPC0gbWF0cml4KDAsIG5yb3cgPSBuY29sKGZpdDIkY29lZmZpY2llbnRzKSwgbmNvbCA9IDEpCnJvd25hbWVzKEwyKSA8LSBjb2xuYW1lcyhmaXQyJGNvZWZmaWNpZW50cykKTDJbYygidHJlYXRUaW1lRFBONDhoIiwgInRyZWF0VGltZUNvbnRyb2w0OGgiKSwxXSA8LSBjKDEsIC0xKQpscnQyIDwtIGdsbUxSVChmaXQyLCBjb250cmFzdD1MMlssMV0pCmhpc3QobHJ0MiR0YWJsZSRQVmFsdWUpCgoKcGxvdCh4PWxydDIkdGFibGUkUFZhbHVlLCB5PWxydExpc3RbWzJdXSR0YWJsZSRQVmFsdWUsCiAgICAgeGxhYj0iTm8gaW50ZXJjZXB0IG1vZGVsIHAtdmFsdWUiLAogICAgIHlsYWI9IkludGVyY2VwdCBtb2RlbCBwLXZhbHVlIikKYGBgCgoKIyBBZGRpdGlvbmFsIENoYWxsZW5nZSAoT3Bwb3J0dW5pdHk/KTogVGhlIGltcG9ydGFuY2Ugb2YgcmVwcm9kdWNpYmxlIGFuYWx5c2lzCgpGaW5hbGx5LCBpdCBpcyBjcnVjaWFsIHRvIG1ha2UgeW91ciBhbmFseXNpcyByZXByb2R1Y2libGUgdXNpbmcgdG9vbHMgc3VjaCBhcyBSTWFya2Rvd24gYW5kIEdpdEh1Yi4gUGxlYXNlIHNpdCBiYWNrIGFuZCB3YXRjaCB0aGlzIFthbWF6aW5nIGxlY3R1cmUgZnJvbSBQcm9mZXNzb3IgS2VpdGggQmFnZ2VybHkgb24gIlRoZSBJbXBvcnRhbmNlIG9mIFJlcHJvZHVjaWJsZSBSZXNlYXJjaCBpbiBIaWdoLVRocm91Z2hwdXQgQmlvbG9neV0oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj04UUpmTlM3WFh3QSkuCgo=