1 Motivation

1.1 Brain imaging study

  • Diffusion Tensor Imaging (DTI) data
  • DTI measures fluid flows in the brain
  • Comparing brain activity of six dyslexic children versus six normal controls
  • From each child, DTI produced observations on 15443 voxels (voxel = small volume at a particular (x, y, x) coordinate)
  • For each voxel, a two-sided two-sample t-test has been performed, resulting in a z-value (15443 z-values) for fractional anisotropy.
  • Low values for FA indicate diffusion in all directions, high values indicates directional diffusion.
  • Research question: at what brain locations (voxels) show dyslexic children a different brain activity as compared to children without dyslexia?

For each voxel separately, this is a simple problem, but the large scale of the problem (15443 simultaneous hypothesis tests) causes the problem of multiplicity.

1.1.1 Data Exploration

The dataset dti contains

  • Spatial location (x, y, z) of each voxel
  • z-statistic for assessing differential brain activity between dyslexic and non-dyslexic children
library(tidyverse)
library(locfdr)
library(gganimate)

dti <- read_csv("https://raw.githubusercontent.com/statOmics/HDA2020/data/dti.csv",
                col_types = cols())
pZ <- dti %>%
  ggplot(
    aes(
      coord.y,
      coord.x,
      color=z.value)
    ) +
  geom_point() +
  scale_colour_gradient2(low = "blue",mid="white",high="red") +
  transition_manual(coord.z) +
  labs(title = "transection z = {frame}") +
  theme_grey()

We will now plot the animated graph

pZ

WARNING: The animated graph will only be visible in the HTML output, not in PDF format. If you’re reading the PDF version, check online for the animated graph.

We visualised the test-statistic of each test per voxel!

Note, that it is difficult to see structure in the data.

1.1.2 Inference

We can convert the z-statistic in a two-sided p-value for each voxel to assess

\[H_0: \text{There is on average no difference in brain activity in voxel xyz between dyslexic and non-dyslexic children}\] \[\mu_d=\mu_{nd}\]

vs

\[H_0: \text{There is on average a difference in brain activity in voxel xyz between dyslexic and non-dyslexic children}\] \[\mu_d\neq\mu_{nd}\]

Below, we calculate the p-values and a variable zP for which we keep the z-value if it is statistical significant at the 5% level otherwise we set it equal to zP=0.

dti <- dti %>%
  mutate(
    p.value = pnorm(abs(z.value),lower=FALSE)*2,
    zP = (p.value < 0.05) * z.value)

pPval <- dti %>%
  ggplot(
    aes(
      coord.y,
      coord.x,
      color=zP)
    ) +
  geom_point() +
  scale_colour_gradient2(low = "blue",mid="white",high="red") +
  transition_manual(coord.z) +
  labs(title = "transection z = {frame}") +
  theme_grey()

We will now plot the animated graph

pPval

It is much more easy to observe patterns of activity.

Note, however that

  • Higher average FA (z > 0 and p < 0.05) in dyslexic children is appearing in spatial patterns in some locations.
  • Lower average FA (z < 0 and p > 0.05) in dyslexic children is scattered throughout the brain.
  • Multiple testing problem.
  • If there would be no association between brain activity and dyslexia we can expect on average \(15443\times\alpha=772\) false positive voxels at the 5% level of significance.
  • Note, that only 1241 were significant at the 5% significance level, so we can expect that the majority of the returned voxels are false positives.
FPexpected  <- nrow(dti) * 0.05
Preported <- sum(dti$p.value < 0.05)

FPexpected
#> [1] 772.15
Preported
#> [1] 1241

1.2 Challenges

Large Scale Inference implies

  • Many hypothesis to be evaluated
  • Huge multiple testing problem
  • Many false positives can be expected if we do not correct for multiple testing

Issue is widespread in many disciplines

  • genomics
  • transcriptomics
  • proteomics
  • brain imaging
  • high throughput single cell technologies
  • detection of anomalous events: e.g. credit card fraud
  • evaluation of trading rules
  • academic performance of schools

1.3 Multiplicity Problem

Suppose only a single hypothesis test is required for answering the research question. A statistical test controls the probability of making a type I error (type I error rate), \[ \alpha =\text{P}\left[\text{reject }H_0 \mid H_0\right] . \] The type I error is also known as a false positive (i.e. \(H_0\) expresses an negative result, and \(H_1\) a positive result): \(\alpha=\text{P}\left[\text{false positive}\right]\).

An important property:

When \(H_0\) is true, and the assumptions underlying the test hold true, then \[ P \sim U[0,1] . \] Hence, for any \(0<\alpha<1\), \[ \text{P}\left[\text{reject }H_0 \mid H_0\right] = \text{P}\left[P<\alpha \mid H_0\right] = \alpha. \]

The distribution of the z-statistic and the p-values under \(H_0\) are illustrated below:

library(gridExtra)

simData <- tibble(
  z.value = rnorm(20000)
  )

simData <- simData %>% mutate(p.value = 2*(1-pnorm(abs(z.value))))

p1 <- simData %>%
  ggplot(aes(x = z.value)) +
  geom_histogram(
    aes(y=..density..),
    color = "black") +
  stat_function(fun = dnorm, args=list(mean=0, sd=1))

p2 <- simData %>%
  ggplot(aes(x = p.value)) +
  geom_histogram(color = "black", breaks = seq(0,1,.05))

grid.arrange(p1, p2, ncol=2)

We indeed observe that the p-values are uniform under the null hypothesis. So statistical hypothesis testing provides a uniform testing strategy.

1.3.1 Notation

In the multiple testing literature the number of features that for which a test is conducted is denoted by \(m\) instead of \(p\) to avoid confusion with the symbol for a p-value.

Consider testing all \(m=15443\) voxels simultaneously

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

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

  • In a typical experiment the majority of the voxel are non differential.

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

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

1.3.2 Familywise error rate

Suppose that \(m\) hypotheses have to be tested simultaneously for answering a single research question.

Let \(H_{0i}\) denote the \(i\)th null hypothesis (\(i=1,\ldots, m\)) and let \(H_0\) denote the intersection of all these partial null hypotheses.

In this case the type I error rate is no longer relevant. Instead one may consider the Familywise Error Rate (FWER) \[ \text{FWER}=\text{P}\left[\text{reject at least one }H_{0i} \mid H_0\right]. \]

Assuming independence among the \(m\) tests and assuming that all individual tests are performed at the \(\alpha\) level of significance, the FWER can be computed as

\[ \begin{array}{rcl} \text{FWER} &=& \text{P}\left[\text{reject at least one }H_{0i} \mid H_0\right] \\ &=& 1 - \text{P}\left[\text{reject no }H_{0i} \mid H_0\right] \\ &=& 1- \text{P}\left[\text{not reject }H_{01}\text{ and }\ldots\text{ and not reject }H_{0m} \mid H_0\right] \\ &=& 1- \prod_{i=1}^m \text{P}\left[\text{not reject }H_{0i} \mid H_0\right] \\ &=& 1- (1-\alpha)^m . \end{array} \]

Examples:

\(\alpha=0.05\) and \(m=5\): FWER\(=0.23\)

\(\alpha=0.05\) and \(m=100\): FWER\(=0.99\)

\(\alpha=0.05\) and \(m=15443\): FWER\(\approx 1\).


These calculations illustrate the problem of multiplicity: the more tests that are performed, the larger the probability that at least one false positive conclusion is obtained. Thus if all significant results are listed, and suppose that all null hypotheses hold true, then the FWER is the probability that at least one of the listed positive results is a false positive. Sometimes, a list of significant results represent the “discoveries” from the study, and therefore a false positive result is often also referred to as a false discovery.

For example, with \(m=100\) and \(\alpha=0.05\) the chance that at least one of the “discoveries” is false, is about \(99\%\). Even worse, with \(m\approx 15000\) the FWER increases to virtually \(100\%\). In general we also expect that lists of significant results (discoveries) get longer with increasing \(m\).

Many researchers, however, when presented a long list of significant results (or discoveries), would not mind too much if one or a few false discoveries appear in the list. Hence, the FWER is not the most relevant risk measure, as the FWER is allowed to be \(100\%\) in case researchers do not mind to have a few false discoveries among the (perhaps many) positive results in the list of discoveries. A better solution will be given later, but first we continue with the use of FWER.


1.3.3 Method of Sidàk: invert FWER to significant level for individual test

The identity FWER\(=1- (1-\alpha)^m\) may be inverted to find the significance level at which each individual test should be tested to attain the nominal familywise error rate at FWER, \[ \alpha = 1-(1-\text{FWER})^{1/m} \] so that the simultaneous testing procedure controls the FWER at the desired level (method of Sidàk).

Examples:

FWER\(=0.05\) and \(m=5\): \(\alpha=0.0102\)

FWER\(=0.05\) and \(m=100\): \(\alpha=0.00051\)

FWER\(=0.05\) and \(m=15443\): \(\alpha=0.0000033\).

We will argue that this procedure is too stringent for large \(m\).

1.3.4 Bonferroni method

The Bonferroni method is another method that is widely used to control the FWER:

  • assess each test at \[\alpha_\text{adj}=\frac{\alpha}{m}\]

  • The method does not assume independence of the test statistics.

  • Again, the method is very conservative!


To attain the familywise error rate at level FWER the individual hypotheses should be tested at very stringent significance levels when \(m\) is large. The consequence of testing at a small significance level \(\alpha\) is that it is hard to find significant results, and thus the lists of significant results (discoveries) is likely to be short. Controlling the FWER means that the chance is small that these lists contain one or more false positives. A negative consequence, however, is that many of the true positive hypothesis (i.e. \(H_1\) is true) will not appear in these short lists. Hence, the “power” is small (power is not well defined in this multiple testing setting – extensions of the concept are possible). Thus, avoiding false positives by controlling the FWER comes at a price: many of the true positive hypothesis may be missed.


1.3.5 Adjusted p-value

First we give a very general definition of an adjusted \(p\)-value.

Define the adjusted \(p\)-value as \[ \tilde{p}_i = \{\inf \alpha\in[0,1]: \text{ reject }H_{0i} \text{ at FWER } \alpha\} . \] With these adjusted \(p\)-value, the \(i\)th partial null hypothesis may be rejected when \[ \tilde{p}_i < \alpha \] while controlling the FWER at \(\alpha\).

The corrected \(p\)-value should be reported. It accounts for the multiplicity problem and it can be compared directly to the nominal FWER level to make calls at the FWER level.

  • adjusted p-values for Bonferroni method: \[p_\text{adj}=\text{min}\left(p \times m,1\right)\]

2 False Discovery Rate

2.1 Introduction

In large scale inference it would be more interesting to tolerate a few false positives as long as they do not dominate the toplist

We first introduce some notation:

The table shows the results of \(m\) hypothesis tests in a single experiment.

accept \(H_{0i}\) reject \(H_{0i}\) Total
null TN FP \(m_0\)
non-null FN TP \(m_1\)
Total NR R m
  • \(TN\): number of true negative: random and unobserved
  • \(FP\): number of false positives: random and unobserved
  • \(FN\): number of false negatives: random and unobserved
  • \(TP\): number of true positives: random and unobserved
  • \(NR\): number of acceptances (negative results): random and observed
  • \(R\): number of rejections (positive results): random and observed
  • \(m_0\) and \(m_1\): fixed and unobserved
  • \(m\): fixed and observed

  • Note that the table is not completely observable.
  • Indeed, we can only observe the bottom row!
  • The table is introduced to better understand the concept of FWER and to introduce the concept of the false discovery rate (FDR).

accept \(H_{0i}\) reject \(H_{0i}\) Total
null TN FP \(m_0\)
non-null FN TP \(m_1\)
Total NR R m

The FWER can now be reexpressed as \[ \text{FWER}=\text{P}\left[\text{reject at least one }H_{0i} \mid H_0\right] = \text{P}\left[FP>0\right] . \]

  • However, we know that the FWER is very conservative in large scale inference problems.
  • Therefore it would be more interesting to tolerate a few false positives as long as they do not dominate the toplist

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

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

  • However, this quantity cannot be observed because in practice we only know the number of voxels for which we rejected \(H_0\), \(R\).

  • But, we do not know the number of false positives, \(FP\).

Therefore, Benjamini and Hochberg, 1995, defined The False Discovery Rate (FDR) as \[ \text{FDR} = \text{E}\left[\frac{FP}{R}\right] =\text{E}\left[\text{FDP}\right] \] the expected FDP, in their seminal paper Benjamini, Y. and Hochberg, Y. (1995). “Controlling the false discovery rate: a practical and powerful approach to multiple testing”. Journal of the Royal Statistical Society Series B, 57 (1): 289–300.

  • An FDR of 1% means that on average we expect 1% false positive voxels in the list of voxels that are called significant.

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

2.2 Intuition of BH-FDR procedure

Consider \(m = 1000\) voxels

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

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

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

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

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

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

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

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

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

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

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


2.4 Brain Example

dti %>%
  ggplot(aes(x = p.value)) +
  geom_histogram(color = "black",breaks = seq(0,1,.05))

  • The graph shows the histogram of the \(m=15443\) \(p\)-values. It shows a distribution which is close to a uniform distribution for the larger p-values, but with more small \(p\)-values than expected under a uniform distribution.

  • This is a trend that would arise if most of the hypotheses are nulls (resulting in \(p\)-values from a uniform distribution), but some are non-nulls (more likely to result in small \(p\)-values).


dti <- dti %>%
  mutate(
    padj = p.adjust(p.value, method="fdr"),
    zFDR = (padj < 0.05) * z.value)

pPadj <- dti %>%
  ggplot(aes(p.value,padj)) +
  geom_point() +
  geom_segment(x=0,y=0,xend=1,yend=1) +
  ylab("adjusted p-value (BH, 1995)")

grid.arrange(pPadj,
  pPadj + ylim(c(0,0.05)),
  ncol=2)

# BH corrected p-values
table(dti$padj < 0.05)
#> 
#> FALSE  TRUE 
#> 15411    32
# uncorrected p-values
table(dti$p.value < 0.05)
#> 
#> FALSE  TRUE 
#> 14202  1241

At the 5% FDR, 32 voxels are returned as significantly differentially active between dyslexic and non-dyslexic children.

2.4.1 Ordered table of results to explain the method

  • Bonferroni: \(\alpha_\text{adj}=3.2e-06 \rightarrow\) 0 voxels are significant at the Bonferroni FWER

  • BH-FDR:

  1. ordered \(p\)-values.

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

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

z.value p.value padj padjNonMonoForm padjNonMono adjAlphaForm adjAlpha pval < adjAlpha padj < alpha
4.399743 1.08e-05 0.0437969 15443 x pval /1 0.1673701 1 x 0.05/15443 3.20e-06 FALSE TRUE
4.336268 1.45e-05 0.0437969 15443 x pval /2 0.1119018 2 x 0.05/15443 6.50e-06 FALSE TRUE
4.322818 1.54e-05 0.0437969 15443 x pval /3 0.0792992 3 x 0.05/15443 9.70e-06 FALSE TRUE
4.290481 1.78e-05 0.0437969 15443 x pval /4 0.0688318 4 x 0.05/15443 1.30e-05 FALSE TRUE
4.273432 1.92e-05 0.0437969 15443 x pval /5 0.0594516 5 x 0.05/15443 1.62e-05 FALSE TRUE
4.211374 2.54e-05 0.0437969 15443 x pval /6 0.0653297 6 x 0.05/15443 1.94e-05 FALSE TRUE
4.200357 2.66e-05 0.0437969 15443 x pval /7 0.0587925 7 x 0.05/15443 2.27e-05 FALSE TRUE
4.116663 3.84e-05 0.0437969 15443 x pval /8 0.0742030 8 x 0.05/15443 2.59e-05 FALSE TRUE
4.100929 4.11e-05 0.0437969 15443 x pval /9 0.0706081 9 x 0.05/15443 2.91e-05 FALSE TRUE
4.093178 4.26e-05 0.0437969 15443 x pval /10 0.0657102 10 x 0.05/15443 3.24e-05 FALSE TRUE
z.value p.value padj padjNonMonoForm padjNonMono adjAlphaForm adjAlpha pval < adjAlpha padj < alpha
4.077959 4.54e-05 0.0437969 15443 x pval /11 0.0637835 11 x 0.05/15443 3.56e-05 FALSE TRUE
4.077082 4.56e-05 0.0437969 15443 x pval /12 0.0586891 12 x 0.05/15443 3.89e-05 FALSE TRUE
4.062125 4.86e-05 0.0437969 15443 x pval /13 0.0577663 13 x 0.05/15443 4.21e-05 FALSE TRUE
4.047767 5.17e-05 0.0437969 15443 x pval /14 0.0570383 14 x 0.05/15443 4.53e-05 FALSE TRUE
4.034977 5.46e-05 0.0437969 15443 x pval /15 0.0562204 15 x 0.05/15443 4.86e-05 FALSE TRUE
4.017347 5.89e-05 0.0437969 15443 x pval /16 0.0568081 16 x 0.05/15443 5.18e-05 FALSE TRUE
4.010879 6.05e-05 0.0437969 15443 x pval /17 0.0549527 17 x 0.05/15443 5.50e-05 FALSE TRUE
4.009839 6.08e-05 0.0437969 15443 x pval /18 0.0521289 18 x 0.05/15443 5.83e-05 FALSE TRUE
-4.000404 6.32e-05 0.0437969 15443 x pval /19 0.0513964 19 x 0.05/15443 6.15e-05 FALSE TRUE
-4.000404 6.32e-05 0.0437969 15443 x pval /20 0.0488265 20 x 0.05/15443 6.48e-05 TRUE TRUE
z.value p.value padj padjNonMonoForm padjNonMono adjAlphaForm adjAlpha pval < adjAlpha padj < alpha
3.992576 6.54e-05 0.0437969 15443 x pval /21 0.0480641 21 x 0.05/15443 6.80e-05 TRUE TRUE
3.977098 6.98e-05 0.0437969 15443 x pval /22 0.0489694 22 x 0.05/15443 7.12e-05 TRUE TRUE
3.969507 7.20e-05 0.0437969 15443 x pval /23 0.0483578 23 x 0.05/15443 7.45e-05 TRUE TRUE
3.954553 7.67e-05 0.0437969 15443 x pval /24 0.0493390 24 x 0.05/15443 7.77e-05 TRUE TRUE
3.950404 7.80e-05 0.0437969 15443 x pval /25 0.0481941 25 x 0.05/15443 8.09e-05 TRUE TRUE
3.947772 7.89e-05 0.0437969 15443 x pval /26 0.0468528 26 x 0.05/15443 8.42e-05 TRUE TRUE
3.947240 7.91e-05 0.0437969 15443 x pval /27 0.0452178 27 x 0.05/15443 8.74e-05 TRUE TRUE
3.946177 7.94e-05 0.0437969 15443 x pval /28 0.0437969 28 x 0.05/15443 9.07e-05 TRUE TRUE
3.923751 8.72e-05 0.0454537 15443 x pval /29 0.0464252 29 x 0.05/15443 9.39e-05 TRUE TRUE
3.920680 8.83e-05 0.0454537 15443 x pval /30 0.0454537 30 x 0.05/15443 9.71e-05 TRUE TRUE
z.value p.value padj padjNonMonoForm padjNonMono adjAlphaForm adjAlpha pval < adjAlpha padj < alpha
3.899404 0.0000964 0.0478784 15443 x pval /31 0.0480376 31 x 0.05/15443 0.0001004 TRUE TRUE
3.892514 0.0000992 0.0478784 15443 x pval /32 0.0478784 32 x 0.05/15443 0.0001036 TRUE TRUE
3.863097 0.0001120 0.0523932 15443 x pval /33 0.0523932 33 x 0.05/15443 0.0001068 FALSE FALSE
3.847472 0.0001193 0.0527813 15443 x pval /34 0.0542062 34 x 0.05/15443 0.0001101 FALSE FALSE
3.846897 0.0001196 0.0527813 15443 x pval /35 0.0527813 35 x 0.05/15443 0.0001133 FALSE FALSE
z.value p.value padj padjNonMonoForm padjNonMono adjAlphaForm adjAlpha pval < adjAlpha padj < alpha
0.0003165 0.9997475 0.9999417 15443 x pval /15440 0.9999417 15440 x 0.05/15443 0.0499903 FALSE FALSE
-0.0002325 0.9998145 0.9999440 15443 x pval /15441 0.9999440 15441 x 0.05/15443 0.0499935 FALSE FALSE
-0.0000953 0.9999240 0.9999665 15443 x pval /15442 0.9999887 15442 x 0.05/15443 0.0499968 FALSE FALSE
0.0000420 0.9999665 0.9999665 15443 x pval /15443 0.9999665 15443 x 0.05/15443 0.0500000 FALSE FALSE
pFDR <- dti %>%
  ggplot(
    aes(
      coord.y,
      coord.x,
      color=zFDR)
    ) +
  geom_point() +
  scale_colour_gradient2(low = "blue",mid="white",high="red") +
  transition_manual(coord.z) +
  labs(title = "transection z = {frame}") +
  theme_grey()

2.4.2 Visualisation of significant differences in brain activity at the 5% FDR


2.5 Comments and Extensions

  • Benjamini and Hochberg published their method in 1995; it was one of the first FDR control methods.
  • The same authors published later yet other FDR control methods.
  • For this reason their 1995 method is often referred to as the Benjamini and Hochberg 1995 method, or BH95.
  • As input the method only needs the \(p\)-values from the \(m\) hypotheses tests.
  • When controlling FDR, the adjusted \(p\)-values are often referred to as \(q\)-values.

  • It is a linear step-up procedure : it starts from the least significant result (largest p-value) and steps-up to more significant results (lower p-values).
  • In FDR terminology the adjusted \(p\)-value is often referred to as a \(q\)-value.
  • The BH95 method assumes that all tests are mutually independent (or at least a particular form of positive dependence between the p-values).
  • When the assumptions hold, it guarantees \[ \text{FDR}=\text{E}\left[TP/R\right]=\text{E}\left[\text{FDP}\right] \leq \frac{m_0}{m} \alpha \leq \alpha . \]

2.5.1 Extension

Thus, if we knew \(m_0\) (the number of true nulls), we could improve the method by applying it to the level \(\alpha m/m_0\) (cfr. Bonferroni).

\(\longrightarrow\) many FDR methods consist in estimating \(m_0\) or the fraction of null genes \(m_0/m\).

The inequality \[ \text{FDR} \leq \frac{m_0}{m} \alpha \leq \alpha \] shows that BH1995 is a conservative method, i.e. it controls the FDR at the safe side, i.e. when one is prepared to control the FDR at the nominal level \(\alpha\), the BH95 will guarantee that the true FDR is not larger than the nominal level (when the assumptions hold).

  • More interestingly is that \(\frac{m_0}{m} \alpha\) is in between the true FDR and the nominal FDR.

  • Suppose that \(m_0\) were known and that the BH95 method were applied at the nominal FDR level of \(\alpha=m/m_0 \alpha^*\), in which \(\alpha^*\) is the FDR level we want to control. Then the inequality gives \[ \text{FDR} \leq \frac{m_0}{m} \alpha = \frac{m_0}{m} \frac{m}{m_0}\alpha^* = \alpha^* , \] and hence BH95 would better control the FDR at \(\alpha^*\).

  • Note that \(\alpha=m/m_0 \alpha^*>\alpha^*\) and hence the results is less conservative than the original BH95 method.


The above reasoning implies a generalized adaptive linear step-up procedure:

  • estimate \(m_0\): \(\hat{m}_0\)
  • of \(\hat{m}_0=0\), reject all null hypotheses; otherwise, apply the step-up procedure of BH 95 at the level \(\alpha=m \alpha^*/\hat{m}_0\) to control the FDR at \(\alpha^*\).

The adjusted \(p\)-values (=\(q\)-values) are obtained as \[ \tilde{p}_{(i)} = \frac{\hat{m}_0}{m} \min\left\{\min_{j=i,\ldots, m}\{m p_{(j)}/j\} ,1 \right\}. \]

  • Many FDR procedures can be fit into this definition (e.g. Benjamini and Hochberg (2000) and Tibshirani (2003)).
  • We do not give details on the methods for estimating \(m_0\), but some of them are implemented in the R software. On the next page we illustrate with simulated data that BH can be improved with estimated \(m_0\).

2.5.2 Other important considerations

  • It can be shown that the BH-FDR method weakly controls the FWER, i.e. it controls the FWER if all features are false (\(m_0=m\)).

  • The BH-FDR is derived under the assumption of independence of the features and has been shown to be only valid under special forms of dependence between the features.

3 local fdr

3.1 Introduction

Suppose that the test statistic for testing \(H_{0i}\) is denoted by \(z_i\), and that the test statistics have a \(N(0,1)\) null distribution.

If all \(m\) null hypotheses are true, the histogram of the \(m\) test statistics should approximate the theoretical null distribution (density \(f_0(z)\)).

Assuming that the test statistic has a standard normal null distribution is not restrictive. For example, suppose that \(t\)-tests have been applied and that the null distribution is \(t_d\), with \(d\) representing the degrees of freedom. Let \(F_{td}\) denote the distribution function of \(t_d\) and let \(\Phi\) denote the distribution function of the standard normal distribution. If \(T\) denotes the \(t\)-test statistic, then, under the null hypothesis, \[ T \sim t_d \] and hence \[ F_{td}(T) \sim U[0,1] \] and \[ Z = \Phi^{-1}(F_{td}(T)) \sim N(0,1). \] If all \(m\) null hypotheses are true, then each of the \(Z_i\) is \(N(0,1)\) and the set of \(m\) calculated \(z_i\) test statistics may be considered as a sample from \(N(0,1)\). Hence, under these conditions we expect the histogram of the \(m\) \(z_i\)’s to look like the density of the null distribution.

3.2 Two group model

  • Suppose that under the alternative hypothesis the test statistic has density function \(f_1(z)\).

  • We use the term “null” to refer to a case \(i\) for which \(H_{0i}\) is true, and “non-null” for a case \(i\) for which \(H_{0i}\) is not true.

  • Consider the prior probabilities \[ \pi_0 = \text{P}\left[\text{null}\right] \text{ and } \pi_1=\text{P}\left[\text{non-null}\right] = 1-\pi_0. \]

  • The marginal distribution of the \(m\) test statistics is then given by the mixture distribution

\[ f(z) = \pi_0 f_0(z) + \pi_1 f_1(z) \]

3.2.1 Examples of mixture distributions

We have already explored mixture distributions in detail in the paper reading session on model based clustering.

  • blue: \(f_0\): \(N(0,1)\), red: \(f_1\): \(N(1,1)\)
components <- tibble(z = seq(-6,6,.01)) %>%
  mutate(
    f0 = dnorm(z),
    f1 = dnorm(z, mean = 1))

components %>%
  gather(component, density, -z) %>%
  ggplot(aes(z,density,color = component)) +
  geom_line() +
  scale_color_manual(values=c("blue","red"))

The graphs shows the two component distributions separately.


  • blue: \(\pi_0 \times f_0\) with \(\pi_0=0.9\) and \(f_0 = N(0,1)\)
  • red: \(\pi_1\times f_1\) with \(\pi_1=1-\pi_0=0.1\) and \(f_1 = N(1,1)\)
p0 <- 0.9
p1 <- 1-p0
mu1 <- 1
scaledComponents <- tibble(z = seq(-6,6,.01)) %>%
  mutate(
    p0xf0 = dnorm(z) * p0,
    p1xf1 = dnorm(z, mean = mu1)*p1
    )

scaledComponents %>%
  gather(component, density, -z) %>%
  ggplot(aes(z,density,color = component)) +
  geom_line() +
  scale_color_manual(values=c("blue","red")) +
  ggtitle("Scaled components")


Mixture distribution

  • blue: \(\pi_0 \times f_0\) with \(\pi_0=0.9\) and \(f_0 = N(0,1)\)
  • red: \(\pi_1\times f_1\) with \(\pi_1=1-\pi_0=0.1\) and \(f_1 = N(1,1)\)
  • black: \(f=\pi_0 f_0 + \pi_1 f_1\)
scaledComponents %>%
  mutate(f=p0xf0+p1xf1) %>%
  gather(component, density, -z) %>%
  ggplot(aes(z,density,color = component)) +
  geom_line() +
  scale_color_manual(values=c("black","blue","red")) +
  ggtitle("Mixture and scaled components")


Mixture \(\pi_0 f_0(z)+\pi_1 f_1(z)\) with \(\pi_0=0.65\) and \(f_1= N(2,1)\) and \(f_0 = N(0,1)\)

p0 <- 0.65
p1 <- 1-p0
mu1 <- 2
scaledComponents <- tibble(z = seq(-6,6,.01)) %>%
  mutate(
    p0xf0 = dnorm(z) * p0,
    p1xf1 = dnorm(z, mean = mu1)*p1)

scaledComponents %>%
  mutate(f=p0xf0+p1xf1) %>%
  gather(component, density, -z) %>%
  ggplot(aes(z,density,color = component)) +
  geom_line() +
  scale_color_manual(values=c("black","blue","red")) +
  ggtitle("Mixture and scaled components (p0 = 0.35)")

3.2.2 simulations

Simulated data: 20000 \(z\)-statistics with \(\pi_1=0.10\) non-nulls with \(f_1=N(1,1)\).

p0 <- .9
p1 <- 1-p0
mu1 <- 1
m <- 20000

zSim <- c(
  rnorm(m * p0),
  rnorm(m * p1, mean=mu1)
  )

zSim %>%
  as_tibble %>%
  ggplot(aes(x = zSim)) +
  geom_histogram(
    aes(y=..density..),
    color = "black") +
  stat_function(fun = dnorm,
    args = list(
      mean = 0,
      sd=1),
    color="blue")

It is hard to see the difference between the histogram and the density function of the null distribution (blue curve), because the mean of \(f_1\) is not much larger than 0 and because only \(\pi_1=10\%\) non-nulls are included and because the alternative is not far from the null distribution. However, this is not an unrealistic setting.

Note, that in most settings the non-null features will originate from a mixture of multiple distributions with positive and negative means. Fortunately, the local fdr method does not require us to estimate \(f_1\) as we will see further.


3.3 local fdr

We can now calculate the probability that a case is a null given the observed \(z\), \[ \text{P}\left[\text{null}\mid z\right] = \frac{\pi_0 f_0(z)}{f(z)} . \] This probability is referred to as the local false discovery rate, and denoted by fdr\((z)\).

If for an observed \(z\), fdr\((z)\) is sufficiently small, one may believe that the case is a true discovery (i.e. \(H_{0i}\) may be rejected).

3.3.2 Estimation of fdr\((z)=\frac{\pi_0 f_0(z)}{f(z)}\)

  • \(f(z)\) can be estimated by nonparametric density estimation methods (\(f(z)\) is the marginal distribution of the test statistics; no knowledge about null / non-null is needed)

  • \(f_0(z)\) is known or can be estimated from the data

  • \(\pi_0\) can be estimated once \(f(z)\) and \(f_0(z)\) are estimated for all \(z\).


3.3.3 Brainscan example

library(locfdr)
lfdr <- locfdr(dti$z.value, nulltype = 0)

  • In the brainscan example the test statistics are supposed to be \(N(0,1)\) distributed under the null hypothesis. Tests are performed two-sided.

  • The argument nulltype=0 specifies that the null distribution (\(f_0\)) is \(N(0,1)\).

  • The dashed blue line gives \(f_0\) and the solid green line is the nonparametric estimate of the marginal density function \(f\). The two densities do not coincide and hence we may anticipate that some of the voxels show differential brain activity.

  • The purple bars indicate the estimated number of non-nulls (among the hypotheses/voxels for a given \(z\)-value). The plots shows that more non-nulls are expected for the negative \(z\)-values than for the positive \(z\)-values (sign of \(z\) corresponds to more or less brain activity in normal versus dyslectic children).

3.3.4 Problems?

Note, however, that

  • we typically expect that the majority of the test statistics follow the null distribution.
  • that the null distribution in the plot is rescaled
  • So, we would expect that the two distributions to overlay in the middle part.
  • However, we observe a shift.

In practise it often happens that the theoretical null distribution is not valid.

This can happen due to

  1. Failed mathematical assumptions: null distribution is incorrect
  2. Correlation between the samples
  3. Correlation between the features
  4. Confounding that is not corrected for.

3.4 Advantage of having a massive parallel data structure

The massive parallel data structure enables us

  • to spot deviations from the theoretical null distribution.
  • to estimate the null distribution by using all features.

Efron relaxes the local fdr method by assuming that the null distribution is a Normal distribution but with a mean and variance that can be estimated empirically (based on all the features).

This can be done by setting the argument nulltype in the locfdr function equal to nulltype = 1, which is the default or be setting nulltype = 2.

The locfdr method then uses

  1. nulltype = 1 maximum likelihood to estimate the null by only considering the middle part in the distribution of the test statistics (MLE) or
  2. nulltype = 2 a geometric method that places the best fitting normal under the peak of the estimate of f(z). (CME)

3.4.1 Brainscan example

lfdr <- locfdr(dti$z.value)

The plot shows that the null distribution is shifted to negative values and has a standard deviation that remains close to 1.

  • This often happens if there is correlation between the features.

  • Spatial correlation can be expected in the brain, so voxels that are close to each-other typically will be correlated.

  • The dashed blue line gives \(f_0\) and the solid green line is the nonparametric estimate of the marginal density function \(f\). The two densities do not coincide and hence we may anticipate that some of the voxels show differential brain activity.

  • The purple bars indicate the estimated number of non-nulls (among the hypotheses/voxels for a given \(z\)-value). The plots shows that only non-nulls for positive \(z\)-values are expected (sign of \(z\) corresponds to more or less brain activity in normal versus dyslectic children).


lfdr <- locfdr(dti$z.value, plot=2)

  • The plot at the left is the same as on the previous page.

  • The plot at the right shows the local fdr as the black solid line. Close to \(z=0\) the fdr is about 1 (i.e. if those hypotheses would be rejected, the probability of a false positive is about \(100\%\)). When moving away from \(z=0\) to larger values the fdr drops.

  • This means that we can only discover convincingly differential brain activity for large positive \(z\). Rejecting null hypotheses with large negative \(z\) would still be risky: large chance of false discovery.

  • The reason can be read from the first graph: for negative \(z\) the ratio \(f_0(z)/f(z)\) is almost 1, whereas for large positive \(z\) the ratio \(f_0(z)/f(z)\) becomes small.

  • Note, that the result is atypically. In most applications we typically pick-up both downregulated (negative z) and upregulated (positive z) features.


dti <- dti %>%
  mutate(
    lfdr = lfdr$fdr,
    zfdr = (lfdr<0.2) * z.value)

pfdr <- dti %>%
  ggplot(
    aes(
      coord.y,
      coord.x,
      color=zfdr)
    ) +
  geom_point() +
  scale_colour_gradient2(low = "blue",mid="white",high="red") +
  transition_manual(coord.z) +
  labs(title = "transection z = {frame}") +
  theme_grey()

Note, that the local fdr method allows us to detect differential brain activity in a specific region in the front part of the brain for which a larger fractional anisotropy is observed on average for childeren having dyslexia.

We can also estimate the FDR of the set that we return as the average local fdr in this set.

dti %>%
  filter(lfdr < 0.2) %>%
  pull(lfdr) %>%
  mean
#> [1] 0.1034925

3.5 Power

The local false discovery rate may also be used to get power diagnostics.

General idea: for \(z\)’s supported by the alternative hypothesis (i.e. large \(f_1(z)\)), we hope to see small fdr\((z)\).

The expected fdr is an appropriate summary measure: \[ \text{Efdr} = \text{E}_{f1}\left[\text{fdr}(Z)\right] = \int_{-\infty}^{+\infty} \text{fdr}(z) f_1(z) dz. \]

With estimates of fdr\((z)\) and \(f_1(z)\), the Efdr can be computed.

A small Efdr is an indication of a powerful study.

lfdr <- locfdr(dti$z.value, plot = 3)

With \(\alpha\) the nominal local fdr level, the vertical axis gives \[ \text{E}_{f_1}\left[\text{fdr}(Z)<\alpha\right]. \]

where \(Z\) is the test statistic distributed under the alternative hypothesis (\(f_1\)).

  • This probability \(\text{P}_{f_1}\left[\text{fdr}(Z)<\alpha\right]\) is a kind of extension of the definition of the power of a test: it is the probability that a non-null can be detected when the nominal local fdr is set at \(\alpha\).

  • The graph shows, for examples, that with \(\alpha=0.20\) we only have \(\text{P}_{f_1}\left[\text{fdr}(Z)<\alpha\right] =0.24\), i.e. only \(24\%\) of the non-nulls are expected to be discovered.

  • At the bottom of the graph we read Efdr\(=0.486\). Hence, the local fdr for a typical non-null feature is expected to be 48.6% which is rather large. The study is not well powered!

3.6 Comparison with gene expression study

  • HIV dataset: 7680 z-values, each relating to a two-sample t-test comparing gene expression of 4 normal to 4 HIV patients.
data(hivdata)
res <- locfdr(hivdata,plot=4)

mean(res$fdr[res$fdr<0.2])
#> [1] 0.07539095

Session info

Session info
#> [1] "2024-10-07 12:47:13 CEST"
#> ─ Session info ───────────────────────────────────────────────────────────────
#>  setting  value
#>  version  R version 4.4.0 RC (2024-04-16 r86468)
#>  os       macOS Big Sur 11.6
#>  system   aarch64, darwin20
#>  ui       X11
#>  language (EN)
#>  collate  en_US.UTF-8
#>  ctype    en_US.UTF-8
#>  tz       Europe/Brussels
#>  date     2024-10-07
#>  pandoc   3.1.1 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/ (via rmarkdown)
#> 
#> ─ Packages ───────────────────────────────────────────────────────────────────
#>  package     * version date (UTC) lib source
#>  bit           4.5.0   2024-09-20 [1] CRAN (R 4.4.1)
#>  bit64         4.5.2   2024-09-22 [1] CRAN (R 4.4.1)
#>  bookdown      0.40    2024-07-02 [1] CRAN (R 4.4.0)
#>  bslib         0.8.0   2024-07-29 [1] CRAN (R 4.4.0)
#>  cachem        1.1.0   2024-05-16 [1] CRAN (R 4.4.0)
#>  cli           3.6.3   2024-06-21 [1] CRAN (R 4.4.0)
#>  colorspace    2.1-1   2024-07-26 [1] CRAN (R 4.4.0)
#>  crayon        1.5.3   2024-06-20 [1] CRAN (R 4.4.0)
#>  curl          5.2.3   2024-09-20 [1] CRAN (R 4.4.1)
#>  digest        0.6.37  2024-08-19 [1] CRAN (R 4.4.1)
#>  dplyr       * 1.1.4   2023-11-17 [1] CRAN (R 4.4.0)
#>  evaluate      1.0.0   2024-09-17 [1] CRAN (R 4.4.1)
#>  fansi         1.0.6   2023-12-08 [1] CRAN (R 4.4.0)
#>  farver        2.1.2   2024-05-13 [1] CRAN (R 4.4.0)
#>  fastmap       1.2.0   2024-05-15 [1] CRAN (R 4.4.0)
#>  forcats     * 1.0.0   2023-01-29 [1] CRAN (R 4.4.0)
#>  generics      0.1.3   2022-07-05 [1] CRAN (R 4.4.0)
#>  gganimate   * 1.0.9   2024-02-27 [1] CRAN (R 4.4.0)
#>  ggplot2     * 3.5.1   2024-04-23 [1] CRAN (R 4.4.0)
#>  glue          1.8.0   2024-09-30 [1] CRAN (R 4.4.1)
#>  gridExtra   * 2.3     2017-09-09 [1] CRAN (R 4.4.0)
#>  gtable        0.3.5   2024-04-22 [1] CRAN (R 4.4.0)
#>  highr         0.11    2024-05-26 [1] CRAN (R 4.4.0)
#>  hms           1.1.3   2023-03-21 [1] CRAN (R 4.4.0)
#>  htmltools     0.5.8.1 2024-04-04 [1] CRAN (R 4.4.0)
#>  jquerylib     0.1.4   2021-04-26 [1] CRAN (R 4.4.0)
#>  jsonlite      1.8.9   2024-09-20 [1] CRAN (R 4.4.1)
#>  knitr         1.48    2024-07-07 [1] CRAN (R 4.4.0)
#>  labeling      0.4.3   2023-08-29 [1] CRAN (R 4.4.0)
#>  lifecycle     1.0.4   2023-11-07 [1] CRAN (R 4.4.0)
#>  locfdr      * 1.1-8   2015-07-15 [1] CRAN (R 4.4.0)
#>  lubridate   * 1.9.3   2023-09-27 [1] CRAN (R 4.4.0)
#>  magick      * 2.8.5   2024-09-20 [1] CRAN (R 4.4.1)
#>  magrittr      2.0.3   2022-03-30 [1] CRAN (R 4.4.0)
#>  munsell       0.5.1   2024-04-01 [1] CRAN (R 4.4.0)
#>  pillar        1.9.0   2023-03-22 [1] CRAN (R 4.4.0)
#>  pkgconfig     2.0.3   2019-09-22 [1] CRAN (R 4.4.0)
#>  prettyunits   1.2.0   2023-09-24 [1] CRAN (R 4.4.0)
#>  progress      1.2.3   2023-12-06 [1] CRAN (R 4.4.0)
#>  purrr       * 1.0.2   2023-08-10 [1] CRAN (R 4.4.0)
#>  R6            2.5.1   2021-08-19 [1] CRAN (R 4.4.0)
#>  Rcpp          1.0.13  2024-07-17 [1] CRAN (R 4.4.0)
#>  readr       * 2.1.5   2024-01-10 [1] CRAN (R 4.4.0)
#>  rlang         1.1.4   2024-06-04 [1] CRAN (R 4.4.0)
#>  rmarkdown     2.28    2024-08-17 [1] CRAN (R 4.4.0)
#>  rstudioapi    0.16.0  2024-03-24 [1] CRAN (R 4.4.0)
#>  sass          0.4.9   2024-03-15 [1] CRAN (R 4.4.0)
#>  scales        1.3.0   2023-11-28 [1] CRAN (R 4.4.0)
#>  sessioninfo   1.2.2   2021-12-06 [1] CRAN (R 4.4.0)
#>  stringi       1.8.4   2024-05-06 [1] CRAN (R 4.4.0)
#>  stringr     * 1.5.1   2023-11-14 [1] CRAN (R 4.4.0)
#>  tibble      * 3.2.1   2023-03-20 [1] CRAN (R 4.4.0)
#>  tidyr       * 1.3.1   2024-01-24 [1] CRAN (R 4.4.0)
#>  tidyselect    1.2.1   2024-03-11 [1] CRAN (R 4.4.0)
#>  tidyverse   * 2.0.0   2023-02-22 [1] CRAN (R 4.4.0)
#>  timechange    0.3.0   2024-01-18 [1] CRAN (R 4.4.0)
#>  tweenr        2.0.3   2024-02-26 [1] CRAN (R 4.4.0)
#>  tzdb          0.4.0   2023-05-12 [1] CRAN (R 4.4.0)
#>  utf8          1.2.4   2023-10-22 [1] CRAN (R 4.4.0)
#>  vctrs         0.6.5   2023-12-01 [1] CRAN (R 4.4.0)
#>  vroom         1.6.5   2023-12-05 [1] CRAN (R 4.4.0)
#>  withr         3.0.1   2024-07-31 [1] CRAN (R 4.4.0)
#>  xfun          0.47    2024-08-17 [1] CRAN (R 4.4.0)
#>  yaml          2.3.10  2024-07-26 [1] CRAN (R 4.4.0)
#> 
#>  [1] /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/library
#> 
#> ──────────────────────────────────────────────────────────────────────────────
LS0tCnRpdGxlOiAiTGFyZ2UgU2NhbGUgSW5mZXJlbmNlIgphdXRob3I6ICJMaWV2ZW4gQ2xlbWVudCIKZGF0ZTogInN0YXRPbWljcywgR2hlbnQgVW5pdmVyc2l0eSAoaHR0cHM6Ly9zdGF0b21pY3MuZ2l0aHViLmlvKSIKb3V0cHV0OgogIGJvb2tkb3duOjpwZGZfZG9jdW1lbnQyOgogICAgdG9jOiB0cnVlCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIGxhdGV4X2VuZ2luZTogeGVsYXRleAotLS0KCmBgYHtyLCBjaGlsZD0iX3NldHVwLlJtZCJ9CmBgYAoKIyBNb3RpdmF0aW9uCgojIyBCcmFpbiBpbWFnaW5nIHN0dWR5CgoKYGBge3IgZmlnLmFsaWduPSJjZW50ZXIiLCBvdXQud2lkdGggPSAnODAlJywgZWNobz1GQUxTRX0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIi4vZmlndXJlcy9EVElDb2xvci5qcGciKQpsaWJyYXJ5KCJtYWdpY2siKQpgYGAKCi0gRGlmZnVzaW9uIFRlbnNvciBJbWFnaW5nIChEVEkpIGRhdGEKLSBEVEkgbWVhc3VyZXMgZmx1aWQgZmxvd3MgaW4gdGhlIGJyYWluCi0gQ29tcGFyaW5nIGJyYWluIGFjdGl2aXR5IG9mIHNpeCBkeXNsZXhpYyBjaGlsZHJlbiB2ZXJzdXMgc2l4IG5vcm1hbCBjb250cm9scwotIEZyb20gZWFjaCBjaGlsZCwgRFRJIHByb2R1Y2VkIG9ic2VydmF0aW9ucyBvbiAxNTQ0MyB2b3hlbHMgKHZveGVsID0gc21hbGwgdm9sdW1lIGF0IGEgcGFydGljdWxhciAoeCwgeSwgeCkgY29vcmRpbmF0ZSkKLSBGb3IgZWFjaCB2b3hlbCwgYSB0d28tc2lkZWQgdHdvLXNhbXBsZSB0LXRlc3QgaGFzIGJlZW4gcGVyZm9ybWVkLCByZXN1bHRpbmcgaW4gYSB6LXZhbHVlICgxNTQ0MyB6LXZhbHVlcykgZm9yIGZyYWN0aW9uYWwgYW5pc290cm9weS4KLSAgTG93IHZhbHVlcyBmb3IgRkEgaW5kaWNhdGUgZGlmZnVzaW9uIGluIGFsbCBkaXJlY3Rpb25zLCBoaWdoIHZhbHVlcyBpbmRpY2F0ZXMgZGlyZWN0aW9uYWwgZGlmZnVzaW9uLgotIFJlc2VhcmNoIHF1ZXN0aW9uOiBhdCB3aGF0IGJyYWluIGxvY2F0aW9ucyAodm94ZWxzKSBzaG93IGR5c2xleGljIGNoaWxkcmVuIGEgZGlmZmVyZW50IGJyYWluIGFjdGl2aXR5IGFzIGNvbXBhcmVkIHRvIGNoaWxkcmVuIHdpdGhvdXQgZHlzbGV4aWE/CgpGb3IgZWFjaCB2b3hlbCBzZXBhcmF0ZWx5LCB0aGlzIGlzIGEgc2ltcGxlIHByb2JsZW0sIGJ1dCB0aGUgbGFyZ2Ugc2NhbGUgb2YgdGhlIHByb2JsZW0gKDE1NDQzIHNpbXVsdGFuZW91cyBoeXBvdGhlc2lzIHRlc3RzKSBjYXVzZXMgdGhlIHByb2JsZW0gb2YgbXVsdGlwbGljaXR5LgoKIyMjIERhdGEgRXhwbG9yYXRpb24KClRoZSBkYXRhc2V0IGBkdGlgIGNvbnRhaW5zCgotIFNwYXRpYWwgbG9jYXRpb24gKHgsIHksIHopIG9mIGVhY2ggdm94ZWwKLSB6LXN0YXRpc3RpYyBmb3IgYXNzZXNzaW5nIGRpZmZlcmVudGlhbCBicmFpbiBhY3Rpdml0eSBiZXR3ZWVuIGR5c2xleGljIGFuZCBub24tZHlzbGV4aWMgY2hpbGRyZW4KCgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGxvY2ZkcikKbGlicmFyeShnZ2FuaW1hdGUpCgpkdGkgPC0gcmVhZF9jc3YoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvSERBMjAyMC9kYXRhL2R0aS5jc3YiLAogICAgICAgICAgICAgICAgY29sX3R5cGVzID0gY29scygpKQpgYGAKCmBgYHtyfQpwWiA8LSBkdGkgJT4lCiAgZ2dwbG90KAogICAgYWVzKAogICAgICBjb29yZC55LAogICAgICBjb29yZC54LAogICAgICBjb2xvcj16LnZhbHVlKQogICAgKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV9jb2xvdXJfZ3JhZGllbnQyKGxvdyA9ICJibHVlIixtaWQ9IndoaXRlIixoaWdoPSJyZWQiKSArCiAgdHJhbnNpdGlvbl9tYW51YWwoY29vcmQueikgKwogIGxhYnModGl0bGUgPSAidHJhbnNlY3Rpb24geiA9IHtmcmFtZX0iKSArCiAgdGhlbWVfZ3JleSgpCmBgYAoKV2Ugd2lsbCBub3cgcGxvdCB0aGUgYW5pbWF0ZWQgZ3JhcGgKCmBgYHtyIGV2YWw9RkFMU0V9CnBaCmBgYAoKCl9fV0FSTklOR19fOiBUaGUgYW5pbWF0ZWQgZ3JhcGggd2lsbCBvbmx5IGJlIHZpc2libGUgaW4gdGhlIEhUTUwgb3V0cHV0LCBub3QgaW4gUERGIGZvcm1hdC4KSWYgeW91J3JlIHJlYWRpbmcgdGhlIFBERiB2ZXJzaW9uLCBjaGVjayBbb25saW5lXShodHRwczovL3N0YXRvbWljcy5naXRodWIuaW8vSEREQS9sc2kuaHRtbCMxMTFfRGF0YV9FeHBsb3JhdGlvbikKZm9yIHRoZSBhbmltYXRlZCBncmFwaC4KCmBgYHtyIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGV2YWw9a25pdHI6OmlzX2h0bWxfb3V0cHV0KCl9CmFuaW1hdGUocFosIG5mcmFtZXMgPSAxMDMsIGVuZF9wYXVzZSA9IDMpCmBgYAoKV2UgdmlzdWFsaXNlZCB0aGUgdGVzdC1zdGF0aXN0aWMgb2YgZWFjaCB0ZXN0IHBlciB2b3hlbCEKCk5vdGUsIHRoYXQgaXQgaXMgZGlmZmljdWx0IHRvIHNlZSBzdHJ1Y3R1cmUgaW4gdGhlIGRhdGEuCgojIyMgSW5mZXJlbmNlCgpXZSBjYW4gY29udmVydCB0aGUgei1zdGF0aXN0aWMgaW4gYSB0d28tc2lkZWQgcC12YWx1ZSBmb3IgZWFjaCB2b3hlbCB0byBhc3Nlc3MKClxbSF8wOiBcdGV4dHtUaGVyZSBpcyBvbiBhdmVyYWdlIG5vIGRpZmZlcmVuY2UgaW4gYnJhaW4gYWN0aXZpdHkgaW4gdm94ZWwgeHl6IGJldHdlZW4gZHlzbGV4aWMgYW5kIG5vbi1keXNsZXhpYyBjaGlsZHJlbn1cXQogIFxbXG11X2Q9XG11X3tuZH1cXQoKdnMKClxbSF8wOiBcdGV4dHtUaGVyZSBpcyBvbiBhdmVyYWdlIGEgZGlmZmVyZW5jZSBpbiBicmFpbiBhY3Rpdml0eSBpbiB2b3hlbCB4eXogYmV0d2VlbiBkeXNsZXhpYyBhbmQgbm9uLWR5c2xleGljIGNoaWxkcmVufVxdCiAgXFtcbXVfZFxuZXFcbXVfe25kfVxdCgoKQmVsb3csIHdlIGNhbGN1bGF0ZSB0aGUgcC12YWx1ZXMgYW5kIGEgdmFyaWFibGUgelAgZm9yIHdoaWNoIHdlIGtlZXAgdGhlIHotdmFsdWUgaWYgaXQgaXMgc3RhdGlzdGljYWwgc2lnbmlmaWNhbnQgYXQgdGhlIDUlIGxldmVsIG90aGVyd2lzZSB3ZSBzZXQgaXQgZXF1YWwgdG8gelA9MC4KCmBgYHtyfQpkdGkgPC0gZHRpICU+JQogIG11dGF0ZSgKICAgIHAudmFsdWUgPSBwbm9ybShhYnMoei52YWx1ZSksbG93ZXI9RkFMU0UpKjIsCiAgICB6UCA9IChwLnZhbHVlIDwgMC4wNSkgKiB6LnZhbHVlKQoKcFB2YWwgPC0gZHRpICU+JQogIGdncGxvdCgKICAgIGFlcygKICAgICAgY29vcmQueSwKICAgICAgY29vcmQueCwKICAgICAgY29sb3I9elApCiAgICApICsKICBnZW9tX3BvaW50KCkgKwogIHNjYWxlX2NvbG91cl9ncmFkaWVudDIobG93ID0gImJsdWUiLG1pZD0id2hpdGUiLGhpZ2g9InJlZCIpICsKICB0cmFuc2l0aW9uX21hbnVhbChjb29yZC56KSArCiAgbGFicyh0aXRsZSA9ICJ0cmFuc2VjdGlvbiB6ID0ge2ZyYW1lfSIpICsKICB0aGVtZV9ncmV5KCkKYGBgCgoKV2Ugd2lsbCBub3cgcGxvdCB0aGUgYW5pbWF0ZWQgZ3JhcGgKCmBgYHtyIGV2YWw9RkFMU0V9CnBQdmFsCmBgYAoKYGBge3IgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgZXZhbD1rbml0cjo6aXNfaHRtbF9vdXRwdXQoKX0KYW5pbWF0ZShwUHZhbCwgbmZyYW1lcyA9IDEwMywgZW5kX3BhdXNlID0gMykKYGBgCgpJdCBpcyBtdWNoIG1vcmUgZWFzeSB0byBvYnNlcnZlIHBhdHRlcm5zIG9mIGFjdGl2aXR5LgoKTm90ZSwgaG93ZXZlciB0aGF0CgotIEhpZ2hlciBhdmVyYWdlIEZBICh6ID4gMCBhbmQgcCA8IDAuMDUpIGluIGR5c2xleGljIGNoaWxkcmVuIGlzIGFwcGVhcmluZyBpbiBzcGF0aWFsIHBhdHRlcm5zIGluIHNvbWUgbG9jYXRpb25zLgotIExvd2VyIGF2ZXJhZ2UgRkEgKHogPCAwIGFuZCBwID4gMC4wNSkgaW4gZHlzbGV4aWMgY2hpbGRyZW4gaXMgc2NhdHRlcmVkIHRocm91Z2hvdXQgdGhlIGJyYWluLgotIE11bHRpcGxlIHRlc3RpbmcgcHJvYmxlbS4KLSBJZiB0aGVyZSB3b3VsZCBiZSBubyBhc3NvY2lhdGlvbiBiZXR3ZWVuIGJyYWluIGFjdGl2aXR5IGFuZCBkeXNsZXhpYSB3ZSBjYW4gZXhwZWN0IG9uIGF2ZXJhZ2UgJGByIG5yb3coZHRpKWBcdGltZXNcYWxwaGE9YHIgcm91bmQobnJvdyhkdGkpICogMC4wNSwwKWAkIGZhbHNlIHBvc2l0aXZlIHZveGVscyBhdCB0aGUgNSUgbGV2ZWwgb2Ygc2lnbmlmaWNhbmNlLgotIE5vdGUsIHRoYXQgb25seSBgciBzdW0oZHRpJHAudmFsdWUgPCAwLjA1KWAgd2VyZSBzaWduaWZpY2FudCBhdCB0aGUgNSUgc2lnbmlmaWNhbmNlIGxldmVsLCBzbyB3ZSBjYW4gZXhwZWN0IHRoYXQgdGhlIG1ham9yaXR5IG9mIHRoZSByZXR1cm5lZCB2b3hlbHMgYXJlIGZhbHNlIHBvc2l0aXZlcy4KCmBgYHtyfQpGUGV4cGVjdGVkICA8LSBucm93KGR0aSkgKiAwLjA1ClByZXBvcnRlZCA8LSBzdW0oZHRpJHAudmFsdWUgPCAwLjA1KQoKRlBleHBlY3RlZApQcmVwb3J0ZWQKYGBgCgojIyBDaGFsbGVuZ2VzCgpMYXJnZSBTY2FsZSBJbmZlcmVuY2UgaW1wbGllcwoKLSBNYW55IGh5cG90aGVzaXMgdG8gYmUgZXZhbHVhdGVkCi0gSHVnZSBtdWx0aXBsZSB0ZXN0aW5nIHByb2JsZW0KLSBNYW55IGZhbHNlIHBvc2l0aXZlcyBjYW4gYmUgZXhwZWN0ZWQgaWYgd2UgZG8gbm90IGNvcnJlY3QgZm9yIG11bHRpcGxlIHRlc3RpbmcKCklzc3VlIGlzIHdpZGVzcHJlYWQgaW4gbWFueSBkaXNjaXBsaW5lcwoKLSBnZW5vbWljcwotIHRyYW5zY3JpcHRvbWljcwotIHByb3Rlb21pY3MKLSBicmFpbiBpbWFnaW5nCi0gaGlnaCB0aHJvdWdocHV0IHNpbmdsZSBjZWxsIHRlY2hub2xvZ2llcwotIGRldGVjdGlvbiBvZiBhbm9tYWxvdXMgZXZlbnRzOiBlLmcuIGNyZWRpdCBjYXJkIGZyYXVkCi0gZXZhbHVhdGlvbiBvZiB0cmFkaW5nIHJ1bGVzCi0gYWNhZGVtaWMgcGVyZm9ybWFuY2Ugb2Ygc2Nob29scwoKIyMgTXVsdGlwbGljaXR5IFByb2JsZW0KClN1cHBvc2Ugb25seSBhIHNpbmdsZSBoeXBvdGhlc2lzIHRlc3QgaXMgcmVxdWlyZWQgZm9yIGFuc3dlcmluZyB0aGUgcmVzZWFyY2ggcXVlc3Rpb24uIEEgc3RhdGlzdGljYWwgdGVzdCBjb250cm9scyB0aGUgcHJvYmFiaWxpdHkgb2YgbWFraW5nIGEgKip0eXBlIEkgZXJyb3IqKiAodHlwZSBJIGVycm9yIHJhdGUpLApcWwogICBcYWxwaGEgPVx0ZXh0e1B9XGxlZnRbXHRleHR7cmVqZWN0IH1IXzAgXG1pZCBIXzBccmlnaHRdIC4KXF0KVGhlIHR5cGUgSSBlcnJvciBpcyBhbHNvIGtub3duIGFzIGEgKipmYWxzZSBwb3NpdGl2ZSoqIChpLmUuICRIXzAkIGV4cHJlc3NlcyBhbiBuZWdhdGl2ZSByZXN1bHQsIGFuZCAkSF8xJCBhIHBvc2l0aXZlIHJlc3VsdCk6ICRcYWxwaGE9XHRleHR7UH1cbGVmdFtcdGV4dHtmYWxzZSBwb3NpdGl2ZX1ccmlnaHRdJC4KCkFuIGltcG9ydGFudCBwcm9wZXJ0eToKCldoZW4gJEhfMCQgaXMgdHJ1ZSwgYW5kIHRoZSBhc3N1bXB0aW9ucyB1bmRlcmx5aW5nIHRoZSB0ZXN0IGhvbGQgdHJ1ZSwgdGhlbgpcWwogIFAgXHNpbSBVWzAsMV0gLgpcXQpIZW5jZSwgZm9yIGFueSAkMDxcYWxwaGE8MSQsClxbCiAgXHRleHR7UH1cbGVmdFtcdGV4dHtyZWplY3QgfUhfMCBcbWlkIEhfMFxyaWdodF0gPSBcdGV4dHtQfVxsZWZ0W1A8XGFscGhhIFxtaWQgSF8wXHJpZ2h0XSA9IFxhbHBoYS4KXF0KClRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHotc3RhdGlzdGljIGFuZCB0aGUgcC12YWx1ZXMgdW5kZXIgJEhfMCQgYXJlIGlsbHVzdHJhdGVkIGJlbG93OgoKYGBge3J9CmxpYnJhcnkoZ3JpZEV4dHJhKQoKc2ltRGF0YSA8LSB0aWJibGUoCiAgei52YWx1ZSA9IHJub3JtKDIwMDAwKQogICkKCnNpbURhdGEgPC0gc2ltRGF0YSAlPiUgbXV0YXRlKHAudmFsdWUgPSAyKigxLXBub3JtKGFicyh6LnZhbHVlKSkpKQoKcDEgPC0gc2ltRGF0YSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB6LnZhbHVlKSkgKwogIGdlb21faGlzdG9ncmFtKAogICAgYWVzKHk9Li5kZW5zaXR5Li4pLAogICAgY29sb3IgPSAiYmxhY2siKSArCiAgc3RhdF9mdW5jdGlvbihmdW4gPSBkbm9ybSwgYXJncz1saXN0KG1lYW49MCwgc2Q9MSkpCgpwMiA8LSBzaW1EYXRhICU+JQogIGdncGxvdChhZXMoeCA9IHAudmFsdWUpKSArCiAgZ2VvbV9oaXN0b2dyYW0oY29sb3IgPSAiYmxhY2siLCBicmVha3MgPSBzZXEoMCwxLC4wNSkpCgpncmlkLmFycmFuZ2UocDEsIHAyLCBuY29sPTIpCmBgYAoKV2UgaW5kZWVkIG9ic2VydmUgdGhhdCB0aGUgcC12YWx1ZXMgYXJlIHVuaWZvcm0gdW5kZXIgdGhlIG51bGwgaHlwb3RoZXNpcy4gU28gc3RhdGlzdGljYWwgaHlwb3RoZXNpcyB0ZXN0aW5nIHByb3ZpZGVzIGEgdW5pZm9ybSB0ZXN0aW5nIHN0cmF0ZWd5LgoKCiMjIyBOb3RhdGlvbgoKSW4gdGhlIG11bHRpcGxlIHRlc3RpbmcgbGl0ZXJhdHVyZSB0aGUgbnVtYmVyIG9mIGZlYXR1cmVzIHRoYXQgZm9yIHdoaWNoIGEgdGVzdCBpcyBjb25kdWN0ZWQgaXMgZGVub3RlZCBieSAkbSQgaW5zdGVhZCBvZiAkcCQgdG8gYXZvaWQgY29uZnVzaW9uIHdpdGggdGhlIHN5bWJvbCBmb3IgYSBwLXZhbHVlLgoKCkNvbnNpZGVyIHRlc3RpbmcgYWxsICRtPTE1NDQzJCB2b3hlbHMgc2ltdWx0YW5lb3VzbHkKCi0gV2hhdCBpZiB3ZSBhc3Nlc3MgZWFjaCBpbmRpdmlkdWFsIHRlc3QgYXQgbGV2ZWwgJFxhbHBoYSQ/CiRccmlnaHRhcnJvdyQgUHJvYmFiaWxpdHkgdG8gaGF2ZSBhIGZhbHNlIHBvc2l0aXZlIChGUCkgYW1vbmcgYWxsIG0gc2ltdWx0YXRlbm91cwp0ZXN0ICQ+Pj4gIFxhbHBoYT0gMC4wNSQKCi0gSW5kZWVkIGZvciBlYWNoIG5vbiBkaWZmZXJlbnRpYWwgdm94ZWwgd2UgaGF2ZSBhIHByb2JhYmlsaXR5IG9mIDUlIHRvIHJldHVybiBhIEZQLgotIEluIGEgdHlwaWNhbCBleHBlcmltZW50IHRoZSBtYWpvcml0eSBvZiB0aGUgdm94ZWwgYXJlIG5vbiBkaWZmZXJlbnRpYWwuCi0gU28gYW4gdXBwZXJib3VuZCBvZiB0aGUgZXhwZWN0ZWQgRlAgaXMgJG0gXHRpbWVzIFxhbHBoYSQgb3IgJDE1NDQzIFx0aW1lcyAwLjA1PWByIHJvdW5kKDE1NDQzKjAuMDUsMClgJC4KCiRccmlnaHRhcnJvdyQgSGVuY2UsIHdlIGFyZSBib3VuZCB0byBjYWxsIG1hbnkgZmFsc2UgcG9zaXRpdmUgdm94ZWxzIGVhY2ggdGltZSB3ZSBydW4gdGhlIGV4cGVyaW1lbnQuCgoKIyMjICBGYW1pbHl3aXNlIGVycm9yIHJhdGUKClN1cHBvc2UgdGhhdCAkbSQgaHlwb3RoZXNlcyBoYXZlIHRvIGJlIHRlc3RlZCBzaW11bHRhbmVvdXNseSBmb3IgYW5zd2VyaW5nIGEgc2luZ2xlIHJlc2VhcmNoIHF1ZXN0aW9uLgoKTGV0ICRIX3swaX0kIGRlbm90ZSB0aGUgJGkkdGggbnVsbCBoeXBvdGhlc2lzICgkaT0xLFxsZG90cywgbSQpIGFuZCBsZXQgJEhfMCQgZGVub3RlIHRoZSBpbnRlcnNlY3Rpb24gb2YgYWxsIHRoZXNlIHBhcnRpYWwgbnVsbCBoeXBvdGhlc2VzLgoKIEluIHRoaXMgY2FzZSB0aGUgdHlwZSBJIGVycm9yIHJhdGUgaXMgbm8gbG9uZ2VyIHJlbGV2YW50LiBJbnN0ZWFkIG9uZSBtYXkgY29uc2lkZXIgdGhlICoqRmFtaWx5d2lzZSBFcnJvciBSYXRlIChGV0VSKSoqCiBcWwogICBcdGV4dHtGV0VSfT1cdGV4dHtQfVxsZWZ0W1x0ZXh0e3JlamVjdCBhdCBsZWFzdCBvbmUgfUhfezBpfSBcbWlkIEhfMFxyaWdodF0uCiBcXQoKCkFzc3VtaW5nIGluZGVwZW5kZW5jZSBhbW9uZyB0aGUgJG0kIHRlc3RzIGFuZCBhc3N1bWluZyB0aGF0IGFsbCBpbmRpdmlkdWFsIHRlc3RzIGFyZSBwZXJmb3JtZWQgYXQgdGhlICRcYWxwaGEkIGxldmVsIG9mIHNpZ25pZmljYW5jZSwgdGhlIEZXRVIgY2FuIGJlIGNvbXB1dGVkIGFzCgpcWwpcYmVnaW57YXJyYXl9e3JjbH0KXHRleHR7RldFUn0KJj0mIFx0ZXh0e1B9XGxlZnRbXHRleHR7cmVqZWN0IGF0IGxlYXN0IG9uZSB9SF97MGl9IFxtaWQgSF8wXHJpZ2h0XSBcXAomPSYgMSAtIFx0ZXh0e1B9XGxlZnRbXHRleHR7cmVqZWN0IG5vIH1IX3swaX0gXG1pZCBIXzBccmlnaHRdIFxcCiY9JiAxLSBcdGV4dHtQfVxsZWZ0W1x0ZXh0e25vdCByZWplY3QgfUhfezAxfVx0ZXh0eyBhbmQgfVxsZG90c1x0ZXh0eyBhbmQgbm90IHJlamVjdCB9SF97MG19IFxtaWQgSF8wXHJpZ2h0XSBcXAomPSYgMS0gXHByb2Rfe2k9MX1ebSBcdGV4dHtQfVxsZWZ0W1x0ZXh0e25vdCByZWplY3QgfUhfezBpfSBcbWlkIEhfMFxyaWdodF0gXFwKJj0mIDEtICgxLVxhbHBoYSlebSAuClxlbmR7YXJyYXl9ClxdCgogRXhhbXBsZXM6CgogICRcYWxwaGE9MC4wNSQgYW5kICRtPTUkOiBGV0VSJD0wLjIzJAoKICRcYWxwaGE9MC4wNSQgYW5kICRtPTEwMCQ6IEZXRVIkPTAuOTkkCgogJFxhbHBoYT0wLjA1JCBhbmQgJG09MTU0NDMkOiBGV0VSJFxhcHByb3ggMSQuCgotLS0KCiBUaGVzZSBjYWxjdWxhdGlvbnMgaWxsdXN0cmF0ZSB0aGUgcHJvYmxlbSBvZiBtdWx0aXBsaWNpdHk6IHRoZSBtb3JlIHRlc3RzIHRoYXQgYXJlIHBlcmZvcm1lZCwgdGhlIGxhcmdlciB0aGUgcHJvYmFiaWxpdHkgdGhhdCBhdCBsZWFzdCBvbmUgZmFsc2UgcG9zaXRpdmUgY29uY2x1c2lvbiBpcyBvYnRhaW5lZC4gVGh1cyBpZiBhbGwgc2lnbmlmaWNhbnQgcmVzdWx0cyBhcmUgbGlzdGVkLCBhbmQgc3VwcG9zZSB0aGF0IGFsbCBudWxsIGh5cG90aGVzZXMgaG9sZCB0cnVlLCB0aGVuIHRoZSBGV0VSIGlzIHRoZSBwcm9iYWJpbGl0eSB0aGF0IGF0IGxlYXN0IG9uZSBvZiB0aGUgbGlzdGVkIHBvc2l0aXZlIHJlc3VsdHMgaXMgYSBmYWxzZSBwb3NpdGl2ZS4gU29tZXRpbWVzLCBhIGxpc3Qgb2Ygc2lnbmlmaWNhbnQgcmVzdWx0cyByZXByZXNlbnQgdGhlICJkaXNjb3ZlcmllcyIgZnJvbSB0aGUgc3R1ZHksIGFuZCB0aGVyZWZvcmUgYSBmYWxzZSBwb3NpdGl2ZSByZXN1bHQgaXMgb2Z0ZW4gYWxzbyByZWZlcnJlZCB0byBhcyBhIGZhbHNlIGRpc2NvdmVyeS4KCkZvciBleGFtcGxlLCB3aXRoICRtPTEwMCQgYW5kICRcYWxwaGE9MC4wNSQgdGhlIGNoYW5jZSB0aGF0IGF0IGxlYXN0IG9uZSBvZiB0aGUgImRpc2NvdmVyaWVzIiBpcyBmYWxzZSwgaXMgYWJvdXQgJDk5XCUkLiBFdmVuIHdvcnNlLCB3aXRoICRtXGFwcHJveCAxNTAwMCQgdGhlIEZXRVIgaW5jcmVhc2VzIHRvIHZpcnR1YWxseSAkMTAwXCUkLiBJbiBnZW5lcmFsIHdlIGFsc28gZXhwZWN0IHRoYXQgbGlzdHMgb2Ygc2lnbmlmaWNhbnQgcmVzdWx0cyAoZGlzY292ZXJpZXMpIGdldCBsb25nZXIgd2l0aCBpbmNyZWFzaW5nICRtJC4KCk1hbnkgcmVzZWFyY2hlcnMsIGhvd2V2ZXIsIHdoZW4gcHJlc2VudGVkIGEgbG9uZyBsaXN0IG9mIHNpZ25pZmljYW50IHJlc3VsdHMgKG9yIGRpc2NvdmVyaWVzKSwgd291bGQgbm90IG1pbmQgdG9vIG11Y2ggaWYgb25lIG9yIGEgZmV3IGZhbHNlIGRpc2NvdmVyaWVzIGFwcGVhciBpbiB0aGUgbGlzdC4gSGVuY2UsIHRoZSBGV0VSIGlzIG5vdCB0aGUgbW9zdCByZWxldmFudCByaXNrIG1lYXN1cmUsIGFzIHRoZSBGV0VSIGlzIGFsbG93ZWQgdG8gYmUgJDEwMFwlJCBpbiBjYXNlIHJlc2VhcmNoZXJzIGRvIG5vdCBtaW5kIHRvIGhhdmUgYSBmZXcgZmFsc2UgZGlzY292ZXJpZXMgYW1vbmcgdGhlIChwZXJoYXBzIG1hbnkpIHBvc2l0aXZlIHJlc3VsdHMgaW4gdGhlIGxpc3Qgb2YgZGlzY292ZXJpZXMuIEEgYmV0dGVyIHNvbHV0aW9uIHdpbGwgYmUgZ2l2ZW4gbGF0ZXIsIGJ1dCBmaXJzdCB3ZSBjb250aW51ZSB3aXRoIHRoZSB1c2Ugb2YgRldFUi4KCi0tLQoKIyMjIE1ldGhvZCBvZiBTaWTDoGs6IGludmVydCBGV0VSIHRvIHNpZ25pZmljYW50IGxldmVsIGZvciBpbmRpdmlkdWFsIHRlc3QKClRoZSBpZGVudGl0eSBGV0VSJD0xLSAoMS1cYWxwaGEpXm0kIG1heSBiZSBpbnZlcnRlZCB0byBmaW5kIHRoZSBzaWduaWZpY2FuY2UgbGV2ZWwgYXQgd2hpY2ggZWFjaCBpbmRpdmlkdWFsIHRlc3Qgc2hvdWxkIGJlIHRlc3RlZCB0byBhdHRhaW4gdGhlIG5vbWluYWwgZmFtaWx5d2lzZSBlcnJvciByYXRlIGF0IEZXRVIsClxbCiAgIFxhbHBoYSA9IDEtKDEtXHRleHR7RldFUn0pXnsxL219ClxdCnNvIHRoYXQgdGhlIHNpbXVsdGFuZW91cyB0ZXN0aW5nIHByb2NlZHVyZSBjb250cm9scyB0aGUgRldFUiBhdCB0aGUgZGVzaXJlZCBsZXZlbCAobWV0aG9kIG9mIFNpZMOgaykuCgpFeGFtcGxlczoKCkZXRVIkPTAuMDUkIGFuZCAkbT01JDogJFxhbHBoYT0wLjAxMDIkCgpGV0VSJD0wLjA1JCBhbmQgJG09MTAwJDogJFxhbHBoYT0wLjAwMDUxJAoKRldFUiQ9MC4wNSQgYW5kICRtPTE1NDQzJDogJFxhbHBoYT0wLjAwMDAwMzMkLgoKV2Ugd2lsbCBhcmd1ZSB0aGF0IHRoaXMgcHJvY2VkdXJlIGlzIHRvbyBzdHJpbmdlbnQgZm9yIGxhcmdlICRtJC4KCiMjIyBCb25mZXJyb25pIG1ldGhvZAoKVGhlIEJvbmZlcnJvbmkgbWV0aG9kIGlzIGFub3RoZXIgbWV0aG9kIHRoYXQgaXMgd2lkZWx5IHVzZWQgdG8gY29udHJvbCB0aGUgRldFUjoKCi0gYXNzZXNzIGVhY2ggdGVzdCBhdApcW1xhbHBoYV9cdGV4dHthZGp9PVxmcmFje1xhbHBoYX17bX1cXQoKLSBUaGUgbWV0aG9kIGRvZXMgbm90IGFzc3VtZSBpbmRlcGVuZGVuY2Ugb2YgdGhlIHRlc3Qgc3RhdGlzdGljcy4KLSBBZ2FpbiwgdGhlIG1ldGhvZCBpcyB2ZXJ5IGNvbnNlcnZhdGl2ZSEKCi0tLQoKVG8gYXR0YWluIHRoZSBmYW1pbHl3aXNlIGVycm9yIHJhdGUgYXQgbGV2ZWwgRldFUiB0aGUgaW5kaXZpZHVhbCBoeXBvdGhlc2VzIHNob3VsZCBiZSB0ZXN0ZWQgYXQgdmVyeSBzdHJpbmdlbnQgc2lnbmlmaWNhbmNlIGxldmVscyB3aGVuICRtJCBpcyBsYXJnZS4gVGhlIGNvbnNlcXVlbmNlIG9mIHRlc3RpbmcgYXQgYSBzbWFsbCBzaWduaWZpY2FuY2UgbGV2ZWwgJFxhbHBoYSQgaXMgdGhhdCBpdCBpcyBoYXJkIHRvIGZpbmQgc2lnbmlmaWNhbnQgcmVzdWx0cywgYW5kIHRodXMgdGhlIGxpc3RzIG9mIHNpZ25pZmljYW50IHJlc3VsdHMgKGRpc2NvdmVyaWVzKSBpcyBsaWtlbHkgdG8gYmUgc2hvcnQuIENvbnRyb2xsaW5nIHRoZSBGV0VSIG1lYW5zIHRoYXQgdGhlIGNoYW5jZSBpcyBzbWFsbCB0aGF0IHRoZXNlIGxpc3RzIGNvbnRhaW4gb25lIG9yIG1vcmUgZmFsc2UgcG9zaXRpdmVzLiBBIG5lZ2F0aXZlIGNvbnNlcXVlbmNlLCBob3dldmVyLCBpcyB0aGF0IG1hbnkgb2YgdGhlIHRydWUgcG9zaXRpdmUgaHlwb3RoZXNpcyAoaS5lLiAkSF8xJCBpcyB0cnVlKSB3aWxsIG5vdCBhcHBlYXIgaW4gdGhlc2Ugc2hvcnQgbGlzdHMuIEhlbmNlLCB0aGUgInBvd2VyIiBpcyBzbWFsbCAocG93ZXIgaXMgbm90IHdlbGwgZGVmaW5lZCBpbiB0aGlzIG11bHRpcGxlIHRlc3Rpbmcgc2V0dGluZyAtLSBleHRlbnNpb25zIG9mIHRoZSBjb25jZXB0IGFyZSBwb3NzaWJsZSkuIFRodXMsIGF2b2lkaW5nIGZhbHNlIHBvc2l0aXZlcyBieSBjb250cm9sbGluZyB0aGUgRldFUiBjb21lcyBhdCBhIHByaWNlOiBtYW55IG9mIHRoZSB0cnVlIHBvc2l0aXZlIGh5cG90aGVzaXMgbWF5IGJlIG1pc3NlZC4KCi0tLQoKIyMjIEFkanVzdGVkIHAtdmFsdWUKCkZpcnN0IHdlIGdpdmUgYSB2ZXJ5IGdlbmVyYWwgZGVmaW5pdGlvbiBvZiBhbiAqKmFkanVzdGVkICRwJC12YWx1ZSoqLgoKIERlZmluZSB0aGUgYWRqdXN0ZWQgJHAkLXZhbHVlIGFzCiBcWwogICBcdGlsZGV7cH1faSA9IFx7XGluZiBcYWxwaGFcaW5bMCwxXTogXHRleHR7IHJlamVjdCB9SF97MGl9IFx0ZXh0eyBhdCBGV0VSIH0gXGFscGhhXH0gLgogXF0KIFdpdGggdGhlc2UgYWRqdXN0ZWQgJHAkLXZhbHVlLCB0aGUgJGkkdGggcGFydGlhbCBudWxsIGh5cG90aGVzaXMgbWF5ICBiZSByZWplY3RlZCB3aGVuCiBcWwogICBcdGlsZGV7cH1faSA8IFxhbHBoYQogXF0KIHdoaWxlIGNvbnRyb2xsaW5nIHRoZSBGV0VSIGF0ICRcYWxwaGEkLgoKIFRoZSBjb3JyZWN0ZWQgJHAkLXZhbHVlIHNob3VsZCBiZSByZXBvcnRlZC4gSXQgYWNjb3VudHMgZm9yIHRoZSBtdWx0aXBsaWNpdHkgcHJvYmxlbSBhbmQgaXQgY2FuIGJlIGNvbXBhcmVkIGRpcmVjdGx5IHRvIHRoZSBub21pbmFsIEZXRVIgbGV2ZWwgdG8gbWFrZSBjYWxscyBhdCB0aGUgRldFUiBsZXZlbC4KCi0gYWRqdXN0ZWQgcC12YWx1ZXMgZm9yIEJvbmZlcnJvbmkgbWV0aG9kOgpcW3BfXHRleHR7YWRqfT1cdGV4dHttaW59XGxlZnQocCBcdGltZXMgbSwxXHJpZ2h0KVxdCgotLS0KCiMgRmFsc2UgRGlzY292ZXJ5IFJhdGUKCiMjIEludHJvZHVjdGlvbgoKSW4gbGFyZ2Ugc2NhbGUgaW5mZXJlbmNlIGl0IHdvdWxkIGJlIG1vcmUgaW50ZXJlc3RpbmcgdG8gdG9sZXJhdGUgYSBmZXcgZmFsc2UgcG9zaXRpdmVzIGFzIGxvbmcgYXMgdGhleSBkbyBub3QgZG9taW5hdGUgdGhlIHRvcGxpc3QKCgpXZSBmaXJzdCBpbnRyb2R1Y2Ugc29tZSBub3RhdGlvbjoKClRoZSB0YWJsZSBzaG93cyB0aGUgcmVzdWx0cyBvZiAkbSQgaHlwb3RoZXNpcyB0ZXN0cyBpbiBhIHNpbmdsZSBleHBlcmltZW50LgoKfCAgICAgICAgICAgICAgICAgICAgICAgICB8IGFjY2VwdCAkSF97MGl9JCB8IHJlamVjdCAkSF97MGl9JCB8IFRvdGFsIHwKfDotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18Oi0tLS0tLS0tLS0tLS0tLTp8Oi0tLS0tLS0tLS0tLS0tLTp8Oi0tLS0tOnwKfCBudWxsIHwgVE4gfCBGUCB8ICRtXzAkIHwKfCBub24tbnVsbCB8IEZOIHwgVFAgfCAkbV8xJCB8CnwgVG90YWwgfCBOUiB8IFIgfCBtIHwKCgotICRUTiQ6IG51bWJlciBvZiB0cnVlIG5lZ2F0aXZlOiByYW5kb20gYW5kIHVub2JzZXJ2ZWQKLSAkRlAkOiBudW1iZXIgb2YgZmFsc2UgcG9zaXRpdmVzOiByYW5kb20gYW5kIHVub2JzZXJ2ZWQKLSAkRk4kOiBudW1iZXIgb2YgZmFsc2UgbmVnYXRpdmVzOiByYW5kb20gYW5kIHVub2JzZXJ2ZWQKLSAkVFAkOiBudW1iZXIgb2YgdHJ1ZSBwb3NpdGl2ZXM6IHJhbmRvbSBhbmQgdW5vYnNlcnZlZAotICROUiQ6IG51bWJlciBvZiBhY2NlcHRhbmNlcyAobmVnYXRpdmUgcmVzdWx0cyk6IHJhbmRvbSBhbmQgb2JzZXJ2ZWQKLSAkUiQ6IG51bWJlciBvZiByZWplY3Rpb25zIChwb3NpdGl2ZSByZXN1bHRzKTogcmFuZG9tIGFuZCBvYnNlcnZlZAotICRtXzAkIGFuZCAkbV8xJDogZml4ZWQgYW5kIHVub2JzZXJ2ZWQKLSAkbSQ6IGZpeGVkIGFuZCBvYnNlcnZlZAoKLS0tCgotIE5vdGUgdGhhdCB0aGUgdGFibGUgaXMgbm90IGNvbXBsZXRlbHkgb2JzZXJ2YWJsZS4KLSBJbmRlZWQsIHdlIGNhbiBvbmx5IG9ic2VydmUgdGhlIGJvdHRvbSByb3chCi0gVGhlIHRhYmxlIGlzIGludHJvZHVjZWQgdG8gYmV0dGVyIHVuZGVyc3RhbmQgdGhlIGNvbmNlcHQgb2YgRldFUiBhbmQgdG8gaW50cm9kdWNlIHRoZSBjb25jZXB0IG9mIHRoZSBmYWxzZSBkaXNjb3ZlcnkgcmF0ZSAoRkRSKS4KCi0tLQoKfCAgICAgICAgICAgICAgICAgICAgICAgICB8IGFjY2VwdCAkSF97MGl9JCB8IHJlamVjdCAkSF97MGl9JCB8IFRvdGFsIHwKfDotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18Oi0tLS0tLS0tLS0tLS0tLTp8Oi0tLS0tLS0tLS0tLS0tLTp8Oi0tLS0tOnwKfCBudWxsIHwgVE4gfCBGUCB8ICRtXzAkIHwKfCBub24tbnVsbCB8IEZOIHwgVFAgfCAkbV8xJCB8CnwgVG90YWwgfCBOUiB8IFIgfCBtIHwKClRoZSBGV0VSIGNhbiBub3cgYmUgcmVleHByZXNzZWQgYXMKIFxbCiAgIFx0ZXh0e0ZXRVJ9PVx0ZXh0e1B9XGxlZnRbXHRleHR7cmVqZWN0IGF0IGxlYXN0IG9uZSB9SF97MGl9IFxtaWQgSF8wXHJpZ2h0XSA9IFx0ZXh0e1B9XGxlZnRbRlA+MFxyaWdodF0gLgogXF0KCgotIEhvd2V2ZXIsIHdlIGtub3cgdGhhdCB0aGUgRldFUiBpcyB2ZXJ5IGNvbnNlcnZhdGl2ZSBpbiBsYXJnZSBzY2FsZSBpbmZlcmVuY2UgcHJvYmxlbXMuCi0gVGhlcmVmb3JlIGl0IHdvdWxkIGJlIG1vcmUgaW50ZXJlc3RpbmcgdG8gdG9sZXJhdGUgYSBmZXcgZmFsc2UgcG9zaXRpdmVzIGFzIGxvbmcgYXMgdGhleSBkbyBub3QgZG9taW5hdGUgdGhlIHRvcGxpc3QKClRoZSAqKkZhbHNlIERpc2NvdmVyeSBQcm9wb3J0aW9uIChGRFApKiogaXMgdGhlIGZyYWN0aW9uIG9mIGZhbHNlIHBvc2l0aXZlcyB0aGF0IGFyZSByZXR1cm5lZCwgaS5lLgoKXFsKRkRQID0gXGZyYWN7RlB9e1J9ClxdCgotIEhvd2V2ZXIsIHRoaXMgcXVhbnRpdHkgY2Fubm90IGJlIG9ic2VydmVkIGJlY2F1c2UgaW4gcHJhY3RpY2Ugd2Ugb25seSBrbm93IHRoZSBudW1iZXIgb2Ygdm94ZWxzIGZvciB3aGljaCB3ZSByZWplY3RlZCAkSF8wJCwgJFIkLgoKLSBCdXQsIHdlIGRvIG5vdCBrbm93IHRoZSBudW1iZXIgb2YgZmFsc2UgcG9zaXRpdmVzLCAkRlAkLgoKVGhlcmVmb3JlLCBCZW5qYW1pbmkgYW5kIEhvY2hiZXJnLCAxOTk1LCBkZWZpbmVkIFRoZSAqKkZhbHNlIERpc2NvdmVyeSBSYXRlIChGRFIpKiogYXMKXFsKICAgXHRleHR7RkRSfSA9IFx0ZXh0e0V9XGxlZnRbXGZyYWN7RlB9e1J9XHJpZ2h0XSA9XHRleHR7RX1cbGVmdFtcdGV4dHtGRFB9XHJpZ2h0XQpcXQp0aGUgZXhwZWN0ZWQgRkRQLCBpbiB0aGVpciBzZW1pbmFsIHBhcGVyIEJlbmphbWluaSwgWS4gYW5kIEhvY2hiZXJnLCBZLiAoMTk5NSkuICJDb250cm9sbGluZyB0aGUgZmFsc2UgZGlzY292ZXJ5IHJhdGU6IGEgcHJhY3RpY2FsIGFuZCBwb3dlcmZ1bCBhcHByb2FjaCB0byBtdWx0aXBsZSB0ZXN0aW5nIi4gSm91cm5hbCBvZiB0aGUgUm95YWwgU3RhdGlzdGljYWwgU29jaWV0eSBTZXJpZXMgQiwgNTcgKDEpOiAyODnigJMzMDAuCgotIEFuIEZEUiBvZiAxJSBtZWFucyB0aGF0IG9uIGF2ZXJhZ2Ugd2UgZXhwZWN0IDElIGZhbHNlIHBvc2l0aXZlIHZveGVscyBpbiB0aGUgbGlzdCBvZiB2b3hlbHMgdGhhdCBhcmUgY2FsbGVkIHNpZ25pZmljYW50LgoKLSBDb250cm9sbGluZyB0aGUgRkRSIGFsbG93cyBmb3IgbW9yZSBkaXNjb3ZlcmllcyAoaS5lLiBsb25nZXIgbGlzdHMgd2l0aCBzaWduaWZpY2FudCByZXN1bHRzKSwgd2hpbGUgdGhlIGZyYWN0aW9uIG9mIGZhbHNlIGRpc2NvdmVyaWVzIGFtb25nIHRoZSBzaWduaWZpY2FudCByZXN1bHRzIGluIHdlbGwgY29udHJvbGxlZCBvbiBhdmVyYWdlLiBBcyBhIGNvbnNlcXVlbmNlLCBtb3JlIG9mIHRoZSB0cnVlIHBvc2l0aXZlIGh5cG90aGVzZXMgd2lsbCBiZSBkZXRlY3RlZC4KCiMjIEludHVpdGlvbiBvZiBCSC1GRFIgcHJvY2VkdXJlCgpDb25zaWRlciAkbSA9IDEwMDAkIHZveGVscwoKLSBTdXBwb3NlIHRoYXQgYSByZXNlYXJjaGVyIHJlamVjdHMgYWxsIG51bGwgaHlwb3RoZXNlcyBmb3Igd2hpY2ggJHAgPCAwLjAxJC4KCi0gSWYgd2UgdXNlICRwIDwgMC4wMSQsIHdlIGV4cGVjdCAkMC4wMSBcdGltZXMgbV8wJCB0ZXN0cyB0byByZXR1cm4gZmFsc2UgcG9zaXRpdmVzLgotIEEgY29uc2VydmF0aXZlIGVzdGltYXRlIG9mIHRoZSBudW1iZXIgb2YgZmFsc2UgcG9zaXRpdmVzIHRoYXQgd2UgY2FuIGV4cGVjdCBjYW4gYmUgb2J0YWluZWQgYnkgY29uc2lkZXJpbmcgdGhhdCB0aGUgbnVsbCBoeXBvdGhlc2VzIGFyZSB0cnVlIGZvciBhbGwgZmVhdHVyZXMsICRtXzAgPSBtID0gIDEwMDAkLgotIFdlIHRoZW4gd291bGQgZXhwZWN0ICQwLjAxIFx0aW1lcyAxMDAwID0gMTAkIGZhbHNlIHBvc2l0aXZlcyAoJEZQPTEwJCkuCgotIFN1cHBvc2UgdGhhdCB0aGUgcmVzZWFyY2hlciBmb3VuZCAyMDAgdm94ZWxzIHdpdGggJHA8MC4wMSQgKCRSPTIwMCQpLgoKLSBUaGUgcHJvcG9ydGlvbiBvZiBmYWxzZSBwb3NpdGl2ZSByZXN1bHRzIChGRFAgPSBmYWxzZSBwb3NpdGl2ZSBwcm9wb3J0aW9uKSBhbW9uZyB0aGUgbGlzdCBvZiAkUj0yMDAkIGdlbmVzIGNhbiB0aGVuIGJlIGVzdGltYXRlZCBhcwogXFsKICAgXHdpZGVoYXR7XHRleHR7RkRQfX09XGZyYWN7RlB9e1J9PVxmcmFjezEwfXsyMDB9PVxmcmFjezAuMDEgXHRpbWVzIDEwMDB9ezIwMH0gPSAwLjA1LgogXF0KCgojIyBCZW5qYW1pbmkgYW5kIEhvY2hiZXJnICgxOTk1KSBwcm9jZWR1cmUgZm9yIGNvbnRyb2xsaW5nIHRoZSBGRFIgYXQgJFxhbHBoYSQKCjEuIExldCAkcF97KDEpfVxsZXEgXGxkb3RzIFxsZXEgcF97KG0pfSQgZGVub3RlIHRoZSBvcmRlcmVkICRwJC12YWx1ZXMuCgoyLiBGaW5kIHRoZSBsYXJnZXN0IGludGVnZXIgJGskIHNvIHRoYXQKJCQKXGZyYWN7cF97KGspfSBcdGltZXMgbX17a30gXGxlcSBcYWxwaGEKJCQKJCRcdGV4dHtvcn0kJAokJApwX3soayl9IFxsZXEgayBcdGltZXMgXGFscGhhL20KJCQKCjMuIElmIHN1Y2ggYSAkayQgZXhpc3RzLCByZWplY3QgdGhlICRrJCBudWxsIGh5cG90aGVzZXMgYXNzb2NpYXRlZCB3aXRoICRwX3soMSl9LCBcbGRvdHMsIHBfeyhrKX0kLgpPdGhlcndpc2Ugbm9uZSBvZiB0aGUgbnVsbCBoeXBvdGhlc2VzIGlzIHJlamVjdGVkLgoKVGhlIGFkanVzdGVkICRwJC12YWx1ZSAoYWxzbyBrbm93biBhcyB0aGUgJHEkLXZhbHVlIGluIEZEUiBsaXRlcmF0dXJlKToKICQkCiAgIHFfeyhpKX09XHRpbGRle3B9X3soaSl9ID0gXG1pblxsZWZ0W1xtaW5fe2o9aSxcbGRvdHMsIG19XGxlZnQobSBwX3soail9L2pccmlnaHQpLCAxIFxyaWdodF0uCiAkJAogSW4gdGhlIGh5cG90aGV0aWNhbCBleGFtcGxlIGFib3ZlOiAkaz0yMDAkLCAkcF97KGspfT0wLjAxJCwgJG09MTAwMCQgYW5kICRcYWxwaGE9MC4wNSQuCgoKLS0tCgojIyBCcmFpbiBFeGFtcGxlCgpgYGB7cn0KZHRpICU+JQogIGdncGxvdChhZXMoeCA9IHAudmFsdWUpKSArCiAgZ2VvbV9oaXN0b2dyYW0oY29sb3IgPSAiYmxhY2siLGJyZWFrcyA9IHNlcSgwLDEsLjA1KSkKYGBgCgotIFRoZSBncmFwaCBzaG93cyB0aGUgaGlzdG9ncmFtIG9mIHRoZSAkbT0xNTQ0MyQgJHAkLXZhbHVlcy4gSXQgc2hvd3MgYSBkaXN0cmlidXRpb24gd2hpY2ggaXMgY2xvc2UgdG8gYSB1bmlmb3JtIGRpc3RyaWJ1dGlvbiBmb3IgdGhlIGxhcmdlciBwLXZhbHVlcywgYnV0IHdpdGggbW9yZSBzbWFsbCAkcCQtdmFsdWVzIHRoYW4gZXhwZWN0ZWQgdW5kZXIgYSB1bmlmb3JtIGRpc3RyaWJ1dGlvbi4KCi0gVGhpcyBpcyBhIHRyZW5kIHRoYXQgd291bGQgYXJpc2UgaWYgbW9zdCBvZiB0aGUgaHlwb3RoZXNlcyBhcmUgbnVsbHMgKHJlc3VsdGluZyBpbiAkcCQtdmFsdWVzIGZyb20gYSB1bmlmb3JtIGRpc3RyaWJ1dGlvbiksIGJ1dCBzb21lIGFyZSBub24tbnVsbHMgKG1vcmUgbGlrZWx5IHRvIHJlc3VsdCBpbiBzbWFsbCAkcCQtdmFsdWVzKS4KCi0tLQoKCmBgYHtyfQpkdGkgPC0gZHRpICU+JQogIG11dGF0ZSgKICAgIHBhZGogPSBwLmFkanVzdChwLnZhbHVlLCBtZXRob2Q9ImZkciIpLAogICAgekZEUiA9IChwYWRqIDwgMC4wNSkgKiB6LnZhbHVlKQoKcFBhZGogPC0gZHRpICU+JQogIGdncGxvdChhZXMocC52YWx1ZSxwYWRqKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9zZWdtZW50KHg9MCx5PTAseGVuZD0xLHllbmQ9MSkgKwogIHlsYWIoImFkanVzdGVkIHAtdmFsdWUgKEJILCAxOTk1KSIpCgpncmlkLmFycmFuZ2UocFBhZGosCiAgcFBhZGogKyB5bGltKGMoMCwwLjA1KSksCiAgbmNvbD0yKQoKIyBCSCBjb3JyZWN0ZWQgcC12YWx1ZXMKdGFibGUoZHRpJHBhZGogPCAwLjA1KQoKIyB1bmNvcnJlY3RlZCBwLXZhbHVlcwp0YWJsZShkdGkkcC52YWx1ZSA8IDAuMDUpCgpgYGAKCkF0IHRoZSA1JSBGRFIsIGByIHN1bShkdGkkcGFkaiA8IDAuMDUpYCB2b3hlbHMgYXJlIHJldHVybmVkIGFzIHNpZ25pZmljYW50bHkgZGlmZmVyZW50aWFsbHkgYWN0aXZlIGJldHdlZW4gZHlzbGV4aWMgYW5kIG5vbi1keXNsZXhpYyBjaGlsZHJlbi4KCiMjIyBPcmRlcmVkIHRhYmxlIG9mIHJlc3VsdHMgdG8gZXhwbGFpbiB0aGUgbWV0aG9kCgotIEJvbmZlcnJvbmk6ICRcYWxwaGFfXHRleHR7YWRqfT1gciBmb3JtYXQoMC4wNS9ucm93KGR0aSksZGlnaXRzPTIpYCBccmlnaHRhcnJvdyQgIGByIHN1bShkdGkkcC52YWx1ZTwoMC4wNS9ucm93KGR0aSkpKWAgdm94ZWxzIGFyZSBzaWduaWZpY2FudCBhdCB0aGUgQm9uZmVycm9uaSBGV0VSCgotIEJILUZEUjoKCjEuIG9yZGVyZWQgJHAkLXZhbHVlcy4KCjIuIEZpbmQgdGhlIGxhcmdlc3QgaW50ZWdlciAkayQgc28gdGhhdAokJApcZnJhY3twX3soayl9IFx0aW1lcyBtfXtrfSBcbGVxIFxhbHBoYQokJAokJFx0ZXh0e29yfSQkCiQkCnBfeyhrKX0gXGxlcSBrIFx0aW1lcyBcYWxwaGEvbQokJAoKMy4gSWYgc3VjaCBhICRrJCBleGlzdHMsIHJlamVjdCB0aGUgJGskIG51bGwgaHlwb3RoZXNlcyBhc3NvY2lhdGVkIHdpdGggJHBfeygxKX0sIFxsZG90cywgcF97KGspfSQuCk90aGVyd2lzZSBub25lIG9mIHRoZSBudWxsIGh5cG90aGVzZXMgaXMgcmVqZWN0ZWQuCgoKYGBge3IgZWNobz1GQUxTRX0KYWxwaGEgPC0gMC4wNQpyZXMgPC0gIGR0aSAlPiUKICBzZWxlY3QoInoudmFsdWUiLCJwLnZhbHVlIiwicGFkaiIpICU+JQogIGFycmFuZ2UocC52YWx1ZSkKcmVzJHBhZGpOb25Nb25vRm9ybSAgPC0gcGFzdGUwKG5yb3cocmVzKSwiIHggcHZhbCAvIiwxOm5yb3cocmVzKSkKcmVzJHBhZGpOb25Nb25vIDwtIHJlcyRwLnZhbHVlICpucm93KHJlcykgLygxOm5yb3cocmVzKSkKcmVzJGFkakFscGhhRm9ybSA8LSBwYXN0ZTAoMTpucm93KHJlcyksIiB4ICIsYWxwaGEsIi8iLG5yb3cocmVzKSkKcmVzJGFkakFscGhhIDwtIGFscGhhICogKDE6bnJvdyhyZXMpKS9ucm93KHJlcykKcmVzJCJwdmFsIDwgYWRqQWxwaGEiIDwtIHJlcyRwLnZhbHVlIDwgcmVzJGFkakFscGhhCnJlcyQicGFkaiA8IGFscGhhIiA8LSByZXMkcGFkaiA8IGFscGhhCnJlc1sxOjEwLF0gJT4lIGtuaXRyOjprYWJsZSgpCnJlc1sxMToyMCxdICU+JSBrbml0cjo6a2FibGUoKQpyZXNbMjE6MzAsXSAlPiUga25pdHI6OmthYmxlKCkKcmVzWzMxOjM1LF0gJT4lIGtuaXRyOjprYWJsZSgpCmBgYAp8IC4uLiB8IC4uLiB8IC4uLiB8IC4uLiB8IC4uLiB8IC4uLiB8IC4uLiB8IC4uLiB8IC4uLiB8CmBgYHtyIGVjaG89RkFMU0V9CnJlc1tucm93KHJlcyktKDM6MCksXSAlPiUga25pdHI6OmthYmxlKCkKYGBgCgpgYGB7cn0KcEZEUiA8LSBkdGkgJT4lCiAgZ2dwbG90KAogICAgYWVzKAogICAgICBjb29yZC55LAogICAgICBjb29yZC54LAogICAgICBjb2xvcj16RkRSKQogICAgKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV9jb2xvdXJfZ3JhZGllbnQyKGxvdyA9ICJibHVlIixtaWQ9IndoaXRlIixoaWdoPSJyZWQiKSArCiAgdHJhbnNpdGlvbl9tYW51YWwoY29vcmQueikgKwogIGxhYnModGl0bGUgPSAidHJhbnNlY3Rpb24geiA9IHtmcmFtZX0iKSArCiAgdGhlbWVfZ3JleSgpCmBgYAoKIyMjIFZpc3VhbGlzYXRpb24gb2Ygc2lnbmlmaWNhbnQgZGlmZmVyZW5jZXMgaW4gYnJhaW4gYWN0aXZpdHkgYXQgdGhlIDUlIEZEUgoKYGBge3IgZWNobyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGV2YWw9a25pdHI6OmlzX2h0bWxfb3V0cHV0KCl9CmFuaW1hdGUocEZEUiwgbmZyYW1lcyA9IDEwMywgZW5kX3BhdXNlID0gMykKYGBgCgotLS0KCiMjIENvbW1lbnRzIGFuZCBFeHRlbnNpb25zCgotIEJlbmphbWluaSBhbmQgSG9jaGJlcmcgcHVibGlzaGVkIHRoZWlyIG1ldGhvZCBpbiAxOTk1OyBpdCB3YXMgb25lIG9mIHRoZSBmaXJzdCBGRFIgY29udHJvbCBtZXRob2RzLgotIFRoZSBzYW1lIGF1dGhvcnMgcHVibGlzaGVkIGxhdGVyIHlldCBvdGhlciBGRFIgY29udHJvbCBtZXRob2RzLgotIEZvciB0aGlzIHJlYXNvbiB0aGVpciAxOTk1IG1ldGhvZCBpcyBvZnRlbiByZWZlcnJlZCB0byBhcyB0aGUgQmVuamFtaW5pIGFuZCBIb2NoYmVyZyAxOTk1IG1ldGhvZCwgb3IgQkg5NS4KLSBBcyBpbnB1dCB0aGUgbWV0aG9kIG9ubHkgbmVlZHMgdGhlICRwJC12YWx1ZXMgZnJvbSB0aGUgJG0kIGh5cG90aGVzZXMgdGVzdHMuCi0gV2hlbiBjb250cm9sbGluZyBGRFIsIHRoZSBhZGp1c3RlZCAkcCQtdmFsdWVzIGFyZSBvZnRlbiByZWZlcnJlZCB0byBhcyAkcSQtdmFsdWVzLgoKLS0tCgotIEl0IGlzIGEgKipsaW5lYXIgc3RlcC11cCBwcm9jZWR1cmUqKiA6IGl0IHN0YXJ0cyBmcm9tIHRoZSBsZWFzdCBzaWduaWZpY2FudCByZXN1bHQgKGxhcmdlc3QgcC12YWx1ZSkgYW5kIHN0ZXBzLXVwIHRvIG1vcmUgc2lnbmlmaWNhbnQgcmVzdWx0cyAobG93ZXIgcC12YWx1ZXMpLgotIEluIEZEUiB0ZXJtaW5vbG9neSB0aGUgYWRqdXN0ZWQgJHAkLXZhbHVlIGlzIG9mdGVuIHJlZmVycmVkIHRvIGFzIGEgJHEkLXZhbHVlLgotIFRoZSBCSDk1IG1ldGhvZCBhc3N1bWVzIHRoYXQgYWxsIHRlc3RzIGFyZSBtdXR1YWxseSBpbmRlcGVuZGVudCAob3IgYXQgbGVhc3QgYSBwYXJ0aWN1bGFyIGZvcm0gb2YgcG9zaXRpdmUgZGVwZW5kZW5jZSBiZXR3ZWVuIHRoZSBwLXZhbHVlcykuCi0gV2hlbiB0aGUgYXNzdW1wdGlvbnMgaG9sZCwgaXQgZ3VhcmFudGVlcwogIFxbCiAgICBcdGV4dHtGRFJ9PVx0ZXh0e0V9XGxlZnRbVFAvUlxyaWdodF09XHRleHR7RX1cbGVmdFtcdGV4dHtGRFB9XHJpZ2h0XSBcbGVxIFxmcmFje21fMH17bX0gXGFscGhhIFxsZXEgXGFscGhhIC4KICBcXQoKLS0tCgojIyMgRXh0ZW5zaW9uCgpUaHVzLCBpZiB3ZSBrbmV3ICRtXzAkICh0aGUgbnVtYmVyIG9mIHRydWUgbnVsbHMpLCB3ZSBjb3VsZCBpbXByb3ZlIHRoZSBtZXRob2QgYnkgYXBwbHlpbmcgaXQgdG8gdGhlIGxldmVsICRcYWxwaGEgbS9tXzAkIChjZnIuIEJvbmZlcnJvbmkpLgoKICRcbG9uZ3JpZ2h0YXJyb3ckIG1hbnkgRkRSIG1ldGhvZHMgY29uc2lzdCBpbiBlc3RpbWF0aW5nICRtXzAkIG9yIHRoZSBmcmFjdGlvbiBvZiBudWxsIGdlbmVzICRtXzAvbSQuCgoKVGhlIGluZXF1YWxpdHkKXFsKICBcdGV4dHtGRFJ9IFxsZXEgXGZyYWN7bV8wfXttfSBcYWxwaGEgXGxlcSBcYWxwaGEKXF0Kc2hvd3MgdGhhdCBCSDE5OTUgaXMgYSBjb25zZXJ2YXRpdmUgbWV0aG9kLCBpLmUuIGl0IGNvbnRyb2xzIHRoZSBGRFIgYXQgdGhlIHNhZmUgc2lkZSwgaS5lLiB3aGVuIG9uZSBpcyBwcmVwYXJlZCB0byBjb250cm9sIHRoZSBGRFIgYXQgdGhlIG5vbWluYWwgbGV2ZWwgJFxhbHBoYSQsIHRoZSBCSDk1IHdpbGwgZ3VhcmFudGVlIHRoYXQgdGhlIHRydWUgRkRSIGlzIG5vdCBsYXJnZXIgdGhhbiB0aGUgbm9taW5hbCBsZXZlbCAod2hlbiB0aGUgYXNzdW1wdGlvbnMgaG9sZCkuCgotIE1vcmUgaW50ZXJlc3RpbmdseSBpcyB0aGF0ICRcZnJhY3ttXzB9e219IFxhbHBoYSQgaXMgaW4gYmV0d2VlbiB0aGUgdHJ1ZSBGRFIgYW5kIHRoZSBub21pbmFsIEZEUi4KCi0gU3VwcG9zZSB0aGF0ICRtXzAkIHdlcmUga25vd24gYW5kIHRoYXQgdGhlIEJIOTUgbWV0aG9kIHdlcmUgYXBwbGllZCBhdCB0aGUgbm9taW5hbCBGRFIgbGV2ZWwgb2YgJFxhbHBoYT1tL21fMCBcYWxwaGFeKiQsIGluIHdoaWNoICRcYWxwaGFeKiQgaXMgdGhlIEZEUiBsZXZlbCB3ZSB3YW50IHRvIGNvbnRyb2wuIFRoZW4gdGhlIGluZXF1YWxpdHkgZ2l2ZXMKXFsKICBcdGV4dHtGRFJ9IFxsZXEgXGZyYWN7bV8wfXttfSBcYWxwaGEgPSBcZnJhY3ttXzB9e219IFxmcmFje219e21fMH1cYWxwaGFeKiA9IFxhbHBoYV4qICwKXF0KYW5kIGhlbmNlIEJIOTUgd291bGQgYmV0dGVyIGNvbnRyb2wgdGhlIEZEUiAgYXQgJFxhbHBoYV4qJC4KCi0gTm90ZSB0aGF0ICRcYWxwaGE9bS9tXzAgXGFscGhhXio+XGFscGhhXiokIGFuZCBoZW5jZSB0aGUgcmVzdWx0cyBpcyBsZXNzIGNvbnNlcnZhdGl2ZSB0aGFuIHRoZSBvcmlnaW5hbCBCSDk1IG1ldGhvZC4KCi0tLQoKVGhlIGFib3ZlIHJlYXNvbmluZyBpbXBsaWVzIGEgKipnZW5lcmFsaXplZCBhZGFwdGl2ZSBsaW5lYXIgc3RlcC11cCBwcm9jZWR1cmUqKjoKCi0gZXN0aW1hdGUgJG1fMCQ6ICRcaGF0e219XzAkCi0gb2YgJFxoYXR7bX1fMD0wJCwgcmVqZWN0IGFsbCBudWxsIGh5cG90aGVzZXM7CiBvdGhlcndpc2UsIGFwcGx5IHRoZSBzdGVwLXVwIHByb2NlZHVyZSBvZiBCSCA5NSBhdCB0aGUgbGV2ZWwgJFxhbHBoYT1tIFxhbHBoYV4qL1xoYXR7bX1fMCQgdG8gY29udHJvbCB0aGUgRkRSIGF0ICRcYWxwaGFeKiQuCgpUaGUgYWRqdXN0ZWQgJHAkLXZhbHVlcyAoPSRxJC12YWx1ZXMpIGFyZSBvYnRhaW5lZCBhcwpcWwogIFx0aWxkZXtwfV97KGkpfSA9IFxmcmFje1xoYXR7bX1fMH17bX0gXG1pblxsZWZ0XHtcbWluX3tqPWksXGxkb3RzLCBtfVx7bSBwX3soail9L2pcfSAsMSBccmlnaHRcfS4KXF0KCi0gTWFueSBGRFIgcHJvY2VkdXJlcyBjYW4gYmUgZml0IGludG8gdGhpcyBkZWZpbml0aW9uIChlLmcuIEJlbmphbWluaSBhbmQgSG9jaGJlcmcgKDIwMDApIGFuZCBUaWJzaGlyYW5pICgyMDAzKSkuCi0gV2UgZG8gbm90IGdpdmUgZGV0YWlscyBvbiB0aGUgbWV0aG9kcyBmb3IgZXN0aW1hdGluZyAkbV8wJCwgYnV0IHNvbWUgb2YgdGhlbSBhcmUgaW1wbGVtZW50ZWQgaW4gdGhlIFIgc29mdHdhcmUuIE9uIHRoZSBuZXh0IHBhZ2Ugd2UgaWxsdXN0cmF0ZSB3aXRoIHNpbXVsYXRlZCBkYXRhIHRoYXQgQkggY2FuIGJlIGltcHJvdmVkIHdpdGggZXN0aW1hdGVkICRtXzAkLgoKLS0tCgojIyMgT3RoZXIgaW1wb3J0YW50IGNvbnNpZGVyYXRpb25zCgotIEl0IGNhbiBiZSBzaG93biAgdGhhdCB0aGUgQkgtRkRSIG1ldGhvZCB3ZWFrbHkgY29udHJvbHMgdGhlIEZXRVIsIGkuZS4gaXQgY29udHJvbHMgdGhlIEZXRVIgaWYgYWxsIGZlYXR1cmVzIGFyZSBmYWxzZSAoJG1fMD1tJCkuCgotIFRoZSBCSC1GRFIgaXMgZGVyaXZlZCB1bmRlciB0aGUgYXNzdW1wdGlvbiBvZiBpbmRlcGVuZGVuY2Ugb2YgdGhlIGZlYXR1cmVzIGFuZCBoYXMgYmVlbiBzaG93biB0byBiZSBvbmx5IHZhbGlkIHVuZGVyIHNwZWNpYWwgZm9ybXMgb2YgZGVwZW5kZW5jZSBiZXR3ZWVuIHRoZSBmZWF0dXJlcy4KCgojIGxvY2FsIGZkcgoKIyMgSW50cm9kdWN0aW9uCgpTdXBwb3NlIHRoYXQgdGhlIHRlc3Qgc3RhdGlzdGljIGZvciB0ZXN0aW5nICRIX3swaX0kIGlzIGRlbm90ZWQgYnkgJHpfaSQsIGFuZCB0aGF0IHRoZSB0ZXN0IHN0YXRpc3RpY3MgaGF2ZSBhICROKDAsMSkkIG51bGwgZGlzdHJpYnV0aW9uLgoKSWYgYWxsICRtJCBudWxsIGh5cG90aGVzZXMgYXJlIHRydWUsIHRoZSBoaXN0b2dyYW0gb2YgdGhlICRtJCB0ZXN0IHN0YXRpc3RpY3Mgc2hvdWxkIGFwcHJveGltYXRlIHRoZSB0aGVvcmV0aWNhbCBudWxsIGRpc3RyaWJ1dGlvbiAoZGVuc2l0eSAkZl8wKHopJCkuCgpgYGB7ciBlY2hvPUZBTFNFfQpwMQpgYGAKCkFzc3VtaW5nIHRoYXQgdGhlIHRlc3Qgc3RhdGlzdGljIGhhcyBhIHN0YW5kYXJkIG5vcm1hbCBudWxsIGRpc3RyaWJ1dGlvbiBpcyBub3QgcmVzdHJpY3RpdmUuIEZvciBleGFtcGxlLCBzdXBwb3NlIHRoYXQgJHQkLXRlc3RzIGhhdmUgYmVlbiBhcHBsaWVkIGFuZCB0aGF0IHRoZSBudWxsIGRpc3RyaWJ1dGlvbiBpcyAkdF9kJCwgd2l0aCAkZCQgcmVwcmVzZW50aW5nIHRoZSBkZWdyZWVzIG9mIGZyZWVkb20uIExldCAkRl97dGR9JCBkZW5vdGUgdGhlIGRpc3RyaWJ1dGlvbiBmdW5jdGlvbiBvZiAkdF9kJCBhbmQgbGV0ICRcUGhpJCBkZW5vdGUgdGhlIGRpc3RyaWJ1dGlvbiBmdW5jdGlvbiBvZiB0aGUgc3RhbmRhcmQgbm9ybWFsIGRpc3RyaWJ1dGlvbi4gSWYgJFQkIGRlbm90ZXMgdGhlICR0JC10ZXN0IHN0YXRpc3RpYywgdGhlbiwgdW5kZXIgdGhlIG51bGwgaHlwb3RoZXNpcywKXFsKICBUIFxzaW0gdF9kClxdCmFuZCBoZW5jZQpcWwogIEZfe3RkfShUKSBcc2ltIFVbMCwxXQpcXQphbmQKXFsKICBaID0gXFBoaV57LTF9KEZfe3RkfShUKSkgXHNpbSBOKDAsMSkuClxdCklmIGFsbCAkbSQgbnVsbCBoeXBvdGhlc2VzIGFyZSB0cnVlLCB0aGVuIGVhY2ggb2YgdGhlICRaX2kkIGlzICROKDAsMSkkIGFuZCB0aGUgc2V0IG9mICRtJCBjYWxjdWxhdGVkICR6X2kkIHRlc3Qgc3RhdGlzdGljcyBtYXkgYmUgY29uc2lkZXJlZCBhcyBhIHNhbXBsZSBmcm9tICROKDAsMSkkLiBIZW5jZSwgdW5kZXIgdGhlc2UgY29uZGl0aW9ucyB3ZSBleHBlY3QgdGhlIGhpc3RvZ3JhbSBvZiB0aGUgJG0kICR6X2kkJ3MgdG8gbG9vayBsaWtlIHRoZSBkZW5zaXR5IG9mIHRoZSBudWxsIGRpc3RyaWJ1dGlvbi4KCiMjIFR3byBncm91cCBtb2RlbAoKLSBTdXBwb3NlIHRoYXQgdW5kZXIgdGhlIGFsdGVybmF0aXZlIGh5cG90aGVzaXMgdGhlIHRlc3Qgc3RhdGlzdGljIGhhcyBkZW5zaXR5IGZ1bmN0aW9uICRmXzEoeikkLgoKLSBXZSB1c2UgdGhlIHRlcm0gIm51bGwiIHRvIHJlZmVyIHRvIGEgY2FzZSAkaSQgZm9yIHdoaWNoICRIX3swaX0kIGlzIHRydWUsIGFuZCAibm9uLW51bGwiIGZvciBhIGNhc2UgJGkkIGZvciB3aGljaCAkSF97MGl9JCBpcyBub3QgdHJ1ZS4KCgotIENvbnNpZGVyIHRoZSAqKnByaW9yIHByb2JhYmlsaXRpZXMqKgpcWwogIFxwaV8wID0gXHRleHR7UH1cbGVmdFtcdGV4dHtudWxsfVxyaWdodF0gXHRleHR7IGFuZCB9IFxwaV8xPVx0ZXh0e1B9XGxlZnRbXHRleHR7bm9uLW51bGx9XHJpZ2h0XSA9IDEtXHBpXzAuClxdCgotIFRoZSBtYXJnaW5hbCBkaXN0cmlidXRpb24gb2YgdGhlICRtJCB0ZXN0IHN0YXRpc3RpY3MgaXMgdGhlbiBnaXZlbiBieSB0aGUgKiptaXh0dXJlIGRpc3RyaWJ1dGlvbioqCgpcWwogIGYoeikgPSBccGlfMCBmXzAoeikgKyBccGlfMSBmXzEoeikKXF0KCiMjIyBFeGFtcGxlcyBvZiBtaXh0dXJlIGRpc3RyaWJ1dGlvbnMKCldlIGhhdmUgYWxyZWFkeSBleHBsb3JlZCBtaXh0dXJlIGRpc3RyaWJ1dGlvbnMgaW4gZGV0YWlsIGluIHRoZSBwYXBlciByZWFkaW5nIHNlc3Npb24gb24gbW9kZWwgYmFzZWQgY2x1c3RlcmluZy4KCi0gYmx1ZTogJGZfMCQ6ICROKDAsMSkkLCByZWQ6ICRmXzEkOiAkTigxLDEpJAoKYGBge3J9CmNvbXBvbmVudHMgPC0gdGliYmxlKHogPSBzZXEoLTYsNiwuMDEpKSAlPiUKICBtdXRhdGUoCiAgICBmMCA9IGRub3JtKHopLAogICAgZjEgPSBkbm9ybSh6LCBtZWFuID0gMSkpCgpjb21wb25lbnRzICU+JQogIGdhdGhlcihjb21wb25lbnQsIGRlbnNpdHksIC16KSAlPiUKICBnZ3Bsb3QoYWVzKHosZGVuc2l0eSxjb2xvciA9IGNvbXBvbmVudCkpICsKICBnZW9tX2xpbmUoKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJibHVlIiwicmVkIikpCmBgYAoKVGhlIGdyYXBocyBzaG93cyB0aGUgdHdvIGNvbXBvbmVudCBkaXN0cmlidXRpb25zIHNlcGFyYXRlbHkuCgoKLS0tCgotIGJsdWU6ICRccGlfMCBcdGltZXMgZl8wJCB3aXRoICRccGlfMD0wLjkkIGFuZCAkZl8wID0gTigwLDEpJAotIHJlZDogJFxwaV8xXHRpbWVzIGZfMSQgd2l0aCAkXHBpXzE9MS1ccGlfMD0wLjEkIGFuZCAkZl8xID0gTigxLDEpJAoKYGBge3J9CnAwIDwtIDAuOQpwMSA8LSAxLXAwCm11MSA8LSAxCnNjYWxlZENvbXBvbmVudHMgPC0gdGliYmxlKHogPSBzZXEoLTYsNiwuMDEpKSAlPiUKICBtdXRhdGUoCiAgICBwMHhmMCA9IGRub3JtKHopICogcDAsCiAgICBwMXhmMSA9IGRub3JtKHosIG1lYW4gPSBtdTEpKnAxCiAgICApCgpzY2FsZWRDb21wb25lbnRzICU+JQogIGdhdGhlcihjb21wb25lbnQsIGRlbnNpdHksIC16KSAlPiUKICBnZ3Bsb3QoYWVzKHosZGVuc2l0eSxjb2xvciA9IGNvbXBvbmVudCkpICsKICBnZW9tX2xpbmUoKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJibHVlIiwicmVkIikpICsKICBnZ3RpdGxlKCJTY2FsZWQgY29tcG9uZW50cyIpCmBgYAoKLS0tCgpNaXh0dXJlIGRpc3RyaWJ1dGlvbgoKLSBibHVlOiAkXHBpXzAgXHRpbWVzIGZfMCQgd2l0aCAkXHBpXzA9MC45JCBhbmQgJGZfMCA9IE4oMCwxKSQKLSByZWQ6ICRccGlfMVx0aW1lcyBmXzEkIHdpdGggJFxwaV8xPTEtXHBpXzA9MC4xJCBhbmQgJGZfMSA9IE4oMSwxKSQKLSBibGFjazogJGY9XHBpXzAgZl8wICsgXHBpXzEgZl8xJAoKYGBge3J9CnNjYWxlZENvbXBvbmVudHMgJT4lCiAgbXV0YXRlKGY9cDB4ZjArcDF4ZjEpICU+JQogIGdhdGhlcihjb21wb25lbnQsIGRlbnNpdHksIC16KSAlPiUKICBnZ3Bsb3QoYWVzKHosZGVuc2l0eSxjb2xvciA9IGNvbXBvbmVudCkpICsKICBnZW9tX2xpbmUoKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJibGFjayIsImJsdWUiLCJyZWQiKSkgKwogIGdndGl0bGUoIk1peHR1cmUgYW5kIHNjYWxlZCBjb21wb25lbnRzIikKYGBgCgotLS0KCk1peHR1cmUgJFxwaV8wIGZfMCh6KStccGlfMSBmXzEoeikkIHdpdGggJFxwaV8wPTAuNjUkIGFuZCAkZl8xPSBOKDIsMSkkIGFuZCAkZl8wID0gTigwLDEpJAoKCmBgYHtyfQpwMCA8LSAwLjY1CnAxIDwtIDEtcDAKbXUxIDwtIDIKc2NhbGVkQ29tcG9uZW50cyA8LSB0aWJibGUoeiA9IHNlcSgtNiw2LC4wMSkpICU+JQogIG11dGF0ZSgKICAgIHAweGYwID0gZG5vcm0oeikgKiBwMCwKICAgIHAxeGYxID0gZG5vcm0oeiwgbWVhbiA9IG11MSkqcDEpCgpzY2FsZWRDb21wb25lbnRzICU+JQogIG11dGF0ZShmPXAweGYwK3AxeGYxKSAlPiUKICBnYXRoZXIoY29tcG9uZW50LCBkZW5zaXR5LCAteikgJT4lCiAgZ2dwbG90KGFlcyh6LGRlbnNpdHksY29sb3IgPSBjb21wb25lbnQpKSArCiAgZ2VvbV9saW5lKCkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiYmxhY2siLCJibHVlIiwicmVkIikpICsKICBnZ3RpdGxlKCJNaXh0dXJlIGFuZCBzY2FsZWQgY29tcG9uZW50cyAocDAgPSAwLjM1KSIpCmBgYAoKIyMjIHNpbXVsYXRpb25zCgpTaW11bGF0ZWQgZGF0YTogMjAwMDAgJHokLXN0YXRpc3RpY3Mgd2l0aCAkXHBpXzE9MC4xMCQgbm9uLW51bGxzIHdpdGggJGZfMT1OKDEsMSkkLgoKYGBge3J9CnAwIDwtIC45CnAxIDwtIDEtcDAKbXUxIDwtIDEKbSA8LSAyMDAwMAoKelNpbSA8LSBjKAogIHJub3JtKG0gKiBwMCksCiAgcm5vcm0obSAqIHAxLCBtZWFuPW11MSkKICApCgp6U2ltICU+JQogIGFzX3RpYmJsZSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB6U2ltKSkgKwogIGdlb21faGlzdG9ncmFtKAogICAgYWVzKHk9Li5kZW5zaXR5Li4pLAogICAgY29sb3IgPSAiYmxhY2siKSArCiAgc3RhdF9mdW5jdGlvbihmdW4gPSBkbm9ybSwKICAgIGFyZ3MgPSBsaXN0KAogICAgICBtZWFuID0gMCwKICAgICAgc2Q9MSksCiAgICBjb2xvcj0iYmx1ZSIpCmBgYAoKSXQgaXMgaGFyZCB0byBzZWUgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgaGlzdG9ncmFtIGFuZCB0aGUgZGVuc2l0eSBmdW5jdGlvbiBvZiB0aGUgbnVsbCBkaXN0cmlidXRpb24gKGJsdWUgY3VydmUpLCBiZWNhdXNlIHRoZSBtZWFuIG9mICRmXzEkIGlzIG5vdCBtdWNoIGxhcmdlciB0aGFuIDAgYW5kIGJlY2F1c2Ugb25seSAkXHBpXzE9MTBcJSQgbm9uLW51bGxzIGFyZSBpbmNsdWRlZCBhbmQgYmVjYXVzZSB0aGUgYWx0ZXJuYXRpdmUgaXMgbm90IGZhciBmcm9tIHRoZSBudWxsIGRpc3RyaWJ1dGlvbi4gSG93ZXZlciwgdGhpcyBpcyBub3QgYW4gdW5yZWFsaXN0aWMgc2V0dGluZy4KCk5vdGUsIHRoYXQgaW4gbW9zdCBzZXR0aW5ncyB0aGUgbm9uLW51bGwgZmVhdHVyZXMgd2lsbCBvcmlnaW5hdGUgZnJvbSBhIG1peHR1cmUgb2YgbXVsdGlwbGUgZGlzdHJpYnV0aW9ucyB3aXRoIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSBtZWFucy4KRm9ydHVuYXRlbHksIHRoZSBsb2NhbCBmZHIgbWV0aG9kIGRvZXMgbm90IHJlcXVpcmUgdXMgdG8gZXN0aW1hdGUgJGZfMSQgYXMgd2Ugd2lsbCBzZWUgZnVydGhlci4KCi0tLQoKIyMgbG9jYWwgZmRyCgpXZSBjYW4gbm93IGNhbGN1bGF0ZSB0aGUgcHJvYmFiaWxpdHkgdGhhdCBhIGNhc2UgaXMgYSBudWxsIGdpdmVuIHRoZSBvYnNlcnZlZCAkeiQsClxbCiAgXHRleHR7UH1cbGVmdFtcdGV4dHtudWxsfVxtaWQgelxyaWdodF0gPSBcZnJhY3tccGlfMCBmXzAoeil9e2Yoeil9IC4KXF0KVGhpcyBwcm9iYWJpbGl0eSBpcyByZWZlcnJlZCB0byBhcyB0aGUgKipsb2NhbCBmYWxzZSBkaXNjb3ZlcnkgcmF0ZSoqLCBhbmQgZGVub3RlZCBieSBmZHIkKHopJC4KCklmIGZvciBhbiBvYnNlcnZlZCAkeiQsIGZkciQoeikkIGlzIHN1ZmZpY2llbnRseSBzbWFsbCwgb25lIG1heSBiZWxpZXZlIHRoYXQgdGhlIGNhc2UgaXMgYSB0cnVlIGRpc2NvdmVyeSAoaS5lLiAkSF97MGl9JCBtYXkgYmUgcmVqZWN0ZWQpLgoKIyMjIExpbmsgd2l0aCBGRFIKClJlY2FsbCB0aGUgZGVmaW5pdGlvbiBvZiB0aGUgRkRSLApcYmVnaW57ZXFuYXJyYXl9Clx0ZXh0e0ZEUn0KJj0mIFx0ZXh0e0V9XGxlZnRbRlAvUlxyaWdodF0gXFwKJj0mIFx0ZXh0e0V9XGxlZnRbXHRleHR7bnVtYmVyIG9mIG51bGxzIGFtb25nIHJlamVjdGVkfSAvIFx0ZXh0e251bWJlciBvZiByZWplY3RlZH1ccmlnaHRdIFxcCiY9JiBcdGV4dHtQfVxsZWZ0W1x0ZXh0e251bGx9IFxtaWQgXHRleHR7cmVqZWN0ZWR9XHJpZ2h0XQpcZW5ke2VxbmFycmF5fQoKLS0tCgoKLSBUaGUgRkRSIGlzIHRvIGJlIGludGVycHJldGVkIGFzIGFuIG92ZXJhbGwgcmlzazogKmFtb25nIGFsbCByZWplY3RlZCBoeXBvdGhlc2VzKiAoZGlzY292ZXJpZXMpIGl0IGdpdmVzIHRoZSBleHBlY3RlZCBmcmFjdGlvbiAob3IgcHJvYmFiaWxpdHkpIG9mIGEgbnVsbCAoZmFsc2UgZGlzY292ZXJ5KS4KCi0gVGhlIGxvY2FsIGZkciwgb24gdGhlIG90aGVyIGhhbmQsIGlzIHRvIGJlIGludGVycHJldGVkIGFzIGEgcmlzayBmb3IgYSBzcGVjaWZpYyBkZWNpc2lvbjogaWYgYSBudWxsIGh5cG90aGVzaXMgaXMgcmVqZWN0ZWQgYmFzZWQgb24gYSB0ZXN0IHN0YXRpc3RpYyB2YWx1ZSBvZiAkeiQsIHRoZW4gdGhlIGxvY2FsIGZkciBnaXZlcyB0aGUgcHJvYmFiaWxpdHkgb2YgdGhhdCBzaW5nbGUgZGlzY292ZXJ5IGJlaW5nIGEgZmFsc2UgZGlzY292ZXJ5LgoKLSBTaW5jZSB0aGUgbG9jYWwgZmRyIGhhcyBhIGNsZWFyIGludGVycHJldGF0aW9uIHRoYXQgYXBwbGllcyB0byBhbiBpbmRpdmlkdWFsIGh5cG90aGVzaXMgdGVzdCwgaXQgY2FuIGJlIHVzZWQgdG8gZGVjaWRlIHdoZXRoZXIgb3Igbm90IHRvIHJlamVjdCBhIG51bGwgaHlwb3RoZXNpcy4KCi0gSW4gcGFydGljdWxhciwgcmVqZWN0IGEgbnVsbCBoeXBvdGhlc2lzICRIX3swaX0kIGlmIGZkciQoeik8XGFscGhhJCwgd2hlcmUgJFxhbHBoYSQgaXMgdGhlIG5vbWluYWwgbG9jYWwgZmRyIGxldmVsIGF0IHdoaWNoIHRoZSBtdWx0aXBsZSB0ZXN0aW5nIHByb2JsZW0gbmVlZCB0byBiZSBjb250cm9sbGVkIGF0LgoKLSBUaGUgbG9jYWwgZmRyIG1ldGhvZCBjYW4gb25seSBiZSBhcHBsaWVkIGlmICRccGlfMCQgYW5kICRmJCBjYW4gYmUgZXN0aW1hdGVkIGZyb20gdGhlIGRhdGEgKHNlZSBsYXRlcikuICBUaGUgZGVuc2l0eSAkZl8wJCBjYW4gYmUgZWl0aGVyIGtub3duIChudWxsIGRpc3RyaWJ1dGlvbiBvZiB0aGUgdGVzdCBzdGF0aXN0aWMpIG9yIGl0IGNhbiBiZSBlc3RpbWF0ZWQgZnJvbSB0aGUgb2JzZXJ2ZWQgJG0kIHRlc3Qgc3RhdGlzdGljcy4KCi0tLQoKRm9yIHRoZSBzYWtlIG9mIHNpbXBsaWNpdHksIHN1cHBvc2UgdGhhdCAkSF97MGl9JCBpcyB0ZXN0ZWQgYWdhaW5zdCBhIG9uZS1zaWRlZCBhbHRlcm5hdGl2ZSBhbmQgdGhhdCAkSF97MGl9JCBpcyByZWplY3RlZCBmb3Igc21hbGwgJHokLCBpLmUuCgpcW0hfMDogeiA9IDAgXHRleHR7IHZzIH0gSF8xOiB6IDwgMFxdCgpTdXBwb3NlIHRoYXQgYWxsICRIX3swaX0kIGFyZSByZWplY3RlZCBmb3Igd2hpY2ggdGhlIG9ic2VydmVkIHRlc3Qgc3RhdGlzdGljIGlzIGF0IG1vc3QgJHokLCB0aGVuIHdlIGNhbiB3cml0ZQoKXGJlZ2lue2VxbmFycmF5fQpcdGV4dHtGRFJ9KHopCiY9JiBcdGV4dHtQfVxsZWZ0W1x0ZXh0e251bGx9IFxtaWQgXHRleHR7cmVqZWN0ZWR9XHJpZ2h0XSBcXFxcCiY9JiBcdGV4dHtQfVxsZWZ0W1x0ZXh0e251bGx9IFxtaWQgWlxsZXEgelxyaWdodF0gXFxcXAomPSYgXHRleHR7RX1fe1p9XGxlZnRce1x0ZXh0e1B9XGxlZnRbXHRleHR7bnVsbH0gXG1pZCBaXHJpZ2h0XSBcbWlkIFpcbGVxIHpccmlnaHRcfSBcXFxcCiY9JiBcdGV4dHtFfV97Wn1cbGVmdFtcdGV4dHtmZHJ9KFopIFxtaWQgWlxsZXEgelxyaWdodF0gXFxcXAomPSYgXGZyYWN7XGludF97LVxpbmZ0eX1eeiBcdGV4dHtmZHJ9KHUpIGYodSkgZHV9e1xpbnRfey1caW5mdHl9XnogZih1KSBkdX0gXFxcXAomPSYgXGZyYWN7XHBpXzBcaW50X3stXGluZnR5fV56ICBmXzAodSkgZHV9e0Yoeil9IFxcXFwKJj0mIFxmcmFje1xwaV8wIEZfMCh6KX17Rih6KX0gLgpcZW5ke2VxbmFycmF5fQoKVGhpcyBzaG93cyB0aGF0IGZkciQoeik9XGZyYWN7XHBpXzAgZl8wKHopfXtmKHopfSQgYW5kICRcdGV4dHtGRFJ9KHopPVxmcmFje1xwaV8wIEZfMCh6KX17Rih6KX0kIGhhdmUgc2ltaWxhciBleHByZXNzaW9uLiBUaGUgZm9ybWVyIGlzIGV4cHJlc3NlZCBpbiB0ZXJtcyBvZiBkZW5zaXR5IGZ1bmN0aW9ucywgYW5kIHRoZSBsYXR0ZXIgaW4gdGVybXMgb2YgdGhlIGNvcnJlc3BvbmRpbmcgY3VtdWxhdGl2ZSBkaXN0cmlidXRpb24gZnVuY3Rpb25zLgoKRnJvbSB0aGUgZXF1YWxpdHkKXFsKICBcdGV4dHtGRFJ9KHopID0gIFxmcmFje1xpbnRfey1caW5mdHl9XnogXHRleHR7ZmRyfSh1KSBmKHUpIGR1fXtcaW50X3stXGluZnR5fV56IGYodSkgZHV9ClxdCgp3ZSBsZWFybiB0aGF0IHRoZSBwcm9iYWJpbGl0eSBmb3IgYSBmYWxzZSBkaXNjb3ZlcnkgYW1vbmcgaHlwb3RoZXNlcyByZWplY3RlZCBieSB1c2luZyB0aHJlc2hvbGQgJHokLCBlcXVhbHMgdGhlIGF2ZXJhZ2Ugb2YgdGhlIGxvY2FsIGZhbHNlIGRpc2NvdmVyeSByYXRlcyBmZHIkKHUpJCBvZiB0aGUgZGlzY292ZXJpZXMgKCR1XGxlcSB6JCBoZXJlKS4KCk5vdGUsIHRoYXQgdGhlIEJILUZEUiBhZG9wdHMKCi0gJFxwaV8wPTEkLCB3aGljaCBpcyBhIGNvbnNlcnZhdGl2ZSBlc3RpbWF0ZQotIHVzZXMgdGhlIHRoZW9yZXRpY2FsIG51bGwgZm9yICRwPUZfMCh6KSQKLSB1c2VzIHRoZSBlbXBpcmljYWwgY3VtdWxhdGl2ZSBkaXN0cmlidXRpb24gZnVuY3Rpb24KICRcYmFyIEYoeikgPSBcZnJhY3tcI1ogPCB6fXttfSQgdG8gZXN0aW1hdGUgJEYoeikkLgoKQSBzaW1pbGFyIGlkZW50aXR5IGNhbiBiZSBlYXNpbHkgc2hvd24gZm9yIHR3by1zaWRlZCB0ZXN0cy4KCiMjIyAgIEVzdGltYXRpb24gb2YgZmRyJCh6KT1cZnJhY3tccGlfMCBmXzAoeil9e2Yoeil9JAoKLSAkZih6KSQgY2FuIGJlIGVzdGltYXRlZCBieSBub25wYXJhbWV0cmljIGRlbnNpdHkgZXN0aW1hdGlvbiBtZXRob2RzICgkZih6KSQgaXMgdGhlIG1hcmdpbmFsIGRpc3RyaWJ1dGlvbiBvZiB0aGUgdGVzdCBzdGF0aXN0aWNzOyBubyBrbm93bGVkZ2UgYWJvdXQgbnVsbCAvIG5vbi1udWxsIGlzIG5lZWRlZCkKCi0gJGZfMCh6KSQgaXMga25vd24gb3IgY2FuIGJlIGVzdGltYXRlZCBmcm9tIHRoZSBkYXRhCgotICAkXHBpXzAkIGNhbiBiZSBlc3RpbWF0ZWQgb25jZSAkZih6KSQgYW5kICRmXzAoeikkIGFyZSBlc3RpbWF0ZWQgZm9yIGFsbCAkeiQuCgotLS0KCiMjIyBCcmFpbnNjYW4gZXhhbXBsZQoKYGBge3J9CmxpYnJhcnkobG9jZmRyKQpsZmRyIDwtIGxvY2ZkcihkdGkkei52YWx1ZSwgbnVsbHR5cGUgPSAwKQpgYGAKCi0gSW4gdGhlIGJyYWluc2NhbiBleGFtcGxlIHRoZSB0ZXN0IHN0YXRpc3RpY3MgYXJlIHN1cHBvc2VkIHRvIGJlICROKDAsMSkkIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBudWxsIGh5cG90aGVzaXMuIFRlc3RzIGFyZSBwZXJmb3JtZWQgdHdvLXNpZGVkLgoKLSBUaGUgYXJndW1lbnQgYG51bGx0eXBlPTBgIHNwZWNpZmllcyB0aGF0IHRoZSBudWxsIGRpc3RyaWJ1dGlvbiAoJGZfMCQpIGlzICROKDAsMSkkLgoKLSBUaGUgZGFzaGVkIGJsdWUgbGluZSBnaXZlcyAkZl8wJCBhbmQgdGhlIHNvbGlkIGdyZWVuIGxpbmUgaXMgdGhlIG5vbnBhcmFtZXRyaWMgZXN0aW1hdGUgb2YgdGhlIG1hcmdpbmFsIGRlbnNpdHkgZnVuY3Rpb24gJGYkLiBUaGUgdHdvIGRlbnNpdGllcyBkbyBub3QgY29pbmNpZGUgYW5kIGhlbmNlIHdlIG1heSBhbnRpY2lwYXRlIHRoYXQgc29tZSBvZiB0aGUgdm94ZWxzIHNob3cgZGlmZmVyZW50aWFsIGJyYWluIGFjdGl2aXR5LgoKLSBUaGUgcHVycGxlIGJhcnMgaW5kaWNhdGUgdGhlIGVzdGltYXRlZCBudW1iZXIgb2Ygbm9uLW51bGxzIChhbW9uZyB0aGUgaHlwb3RoZXNlcy92b3hlbHMgZm9yIGEgZ2l2ZW4gJHokLXZhbHVlKS4gVGhlIHBsb3RzIHNob3dzIHRoYXQgbW9yZSBub24tbnVsbHMgYXJlIGV4cGVjdGVkIGZvciB0aGUgbmVnYXRpdmUgJHokLXZhbHVlcyB0aGFuIGZvciB0aGUgcG9zaXRpdmUgJHokLXZhbHVlcyAoc2lnbiBvZiAkeiQgY29ycmVzcG9uZHMgdG8gbW9yZSBvciBsZXNzIGJyYWluIGFjdGl2aXR5IGluIG5vcm1hbCB2ZXJzdXMgZHlzbGVjdGljIGNoaWxkcmVuKS4KCiMjIyBQcm9ibGVtcz8KCk5vdGUsIGhvd2V2ZXIsIHRoYXQKCi0gd2UgdHlwaWNhbGx5IGV4cGVjdCB0aGF0IHRoZSBtYWpvcml0eSBvZiB0aGUgdGVzdCBzdGF0aXN0aWNzIGZvbGxvdyB0aGUgbnVsbCBkaXN0cmlidXRpb24uCi0gdGhhdCB0aGUgbnVsbCBkaXN0cmlidXRpb24gaW4gdGhlIHBsb3QgaXMgcmVzY2FsZWQKLSBTbywgd2Ugd291bGQgZXhwZWN0IHRoYXQgdGhlIHR3byBkaXN0cmlidXRpb25zIHRvIG92ZXJsYXkgaW4gdGhlIG1pZGRsZSBwYXJ0LgotIEhvd2V2ZXIsIHdlIG9ic2VydmUgYSBzaGlmdC4KCkluIHByYWN0aXNlIGl0IG9mdGVuIGhhcHBlbnMgdGhhdCB0aGUgdGhlb3JldGljYWwgbnVsbCBkaXN0cmlidXRpb24gaXMgbm90IHZhbGlkLgoKVGhpcyBjYW4gaGFwcGVuIGR1ZSB0bwoKMS4gRmFpbGVkIG1hdGhlbWF0aWNhbCBhc3N1bXB0aW9uczogbnVsbCBkaXN0cmlidXRpb24gaXMgaW5jb3JyZWN0CjIuIENvcnJlbGF0aW9uIGJldHdlZW4gdGhlIHNhbXBsZXMKMy4gQ29ycmVsYXRpb24gYmV0d2VlbiB0aGUgZmVhdHVyZXMKNC4gQ29uZm91bmRpbmcgdGhhdCBpcyBub3QgY29ycmVjdGVkIGZvci4KCiMjIEFkdmFudGFnZSBvZiBoYXZpbmcgYSBtYXNzaXZlIHBhcmFsbGVsIGRhdGEgc3RydWN0dXJlCgpUaGUgbWFzc2l2ZSBwYXJhbGxlbCBkYXRhIHN0cnVjdHVyZSBlbmFibGVzIHVzCgotIHRvIHNwb3QgZGV2aWF0aW9ucyBmcm9tIHRoZSB0aGVvcmV0aWNhbCBudWxsIGRpc3RyaWJ1dGlvbi4KLSB0byBlc3RpbWF0ZSB0aGUgbnVsbCBkaXN0cmlidXRpb24gYnkgdXNpbmcgYWxsIGZlYXR1cmVzLgoKRWZyb24gcmVsYXhlcyB0aGUgbG9jYWwgZmRyIG1ldGhvZCBieSBhc3N1bWluZyB0aGF0IHRoZSBudWxsIGRpc3RyaWJ1dGlvbiBpcyBhIE5vcm1hbCBkaXN0cmlidXRpb24gYnV0IHdpdGggYSBtZWFuIGFuZCB2YXJpYW5jZSB0aGF0IGNhbiBiZSBlc3RpbWF0ZWQgZW1waXJpY2FsbHkgKGJhc2VkIG9uIGFsbCB0aGUgZmVhdHVyZXMpLgoKVGhpcyBjYW4gYmUgZG9uZSBieSBzZXR0aW5nIHRoZSBhcmd1bWVudCBgbnVsbHR5cGVgIGluIHRoZSBsb2NmZHIgZnVuY3Rpb24gZXF1YWwgdG8gYG51bGx0eXBlID0gMWAsIHdoaWNoIGlzIHRoZSBkZWZhdWx0IG9yIGJlIHNldHRpbmcgYG51bGx0eXBlID0gMmAuCgpUaGUgbG9jZmRyIG1ldGhvZCB0aGVuIHVzZXMKCjEuIGBudWxsdHlwZSA9IDFgIG1heGltdW0gbGlrZWxpaG9vZCB0byBlc3RpbWF0ZSB0aGUgbnVsbCBieSBvbmx5IGNvbnNpZGVyaW5nIHRoZSBtaWRkbGUgcGFydCBpbiB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSB0ZXN0IHN0YXRpc3RpY3MgKE1MRSkgb3IKMi4gYG51bGx0eXBlID0gMmAgYSBnZW9tZXRyaWMgbWV0aG9kIHRoYXQgcGxhY2VzIHRoZSBiZXN0IGZpdHRpbmcgbm9ybWFsIHVuZGVyIHRoZSBwZWFrIG9mIHRoZSBlc3RpbWF0ZSBvZiBmKHopLiAoQ01FKQoKIyMjIEJyYWluc2NhbiBleGFtcGxlCgpgYGB7cn0KbGZkciA8LSBsb2NmZHIoZHRpJHoudmFsdWUpCmBgYAoKVGhlIHBsb3Qgc2hvd3MgdGhhdCB0aGUgbnVsbCBkaXN0cmlidXRpb24gaXMgc2hpZnRlZCB0byBuZWdhdGl2ZSB2YWx1ZXMgYW5kIGhhcyBhIHN0YW5kYXJkIGRldmlhdGlvbiB0aGF0IHJlbWFpbnMgY2xvc2UgdG8gMS4KCi0gVGhpcyBvZnRlbiBoYXBwZW5zIGlmIHRoZXJlIGlzIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIGZlYXR1cmVzLgoKLSBTcGF0aWFsIGNvcnJlbGF0aW9uIGNhbiBiZSBleHBlY3RlZCBpbiB0aGUgYnJhaW4sIHNvIHZveGVscyB0aGF0IGFyZSBjbG9zZSB0byBlYWNoLW90aGVyIHR5cGljYWxseSB3aWxsIGJlIGNvcnJlbGF0ZWQuCgotIFRoZSBkYXNoZWQgYmx1ZSBsaW5lIGdpdmVzICRmXzAkIGFuZCB0aGUgc29saWQgZ3JlZW4gbGluZSBpcyB0aGUgbm9ucGFyYW1ldHJpYyBlc3RpbWF0ZSBvZiB0aGUgbWFyZ2luYWwgZGVuc2l0eSBmdW5jdGlvbiAkZiQuIFRoZSB0d28gZGVuc2l0aWVzIGRvIG5vdCBjb2luY2lkZSBhbmQgaGVuY2Ugd2UgbWF5IGFudGljaXBhdGUgdGhhdCBzb21lIG9mIHRoZSB2b3hlbHMgc2hvdyBkaWZmZXJlbnRpYWwgYnJhaW4gYWN0aXZpdHkuCgotIFRoZSBwdXJwbGUgYmFycyBpbmRpY2F0ZSB0aGUgZXN0aW1hdGVkIG51bWJlciBvZiBub24tbnVsbHMgKGFtb25nIHRoZSBoeXBvdGhlc2VzL3ZveGVscyBmb3IgYSBnaXZlbiAkeiQtdmFsdWUpLiBUaGUgcGxvdHMgc2hvd3MgdGhhdCBvbmx5IG5vbi1udWxscyBmb3IgcG9zaXRpdmUgJHokLXZhbHVlcyBhcmUgZXhwZWN0ZWQgKHNpZ24gb2YgJHokIGNvcnJlc3BvbmRzIHRvIG1vcmUgb3IgbGVzcyBicmFpbiBhY3Rpdml0eSBpbiBub3JtYWwgdmVyc3VzIGR5c2xlY3RpYyBjaGlsZHJlbikuCgotLS0KCmBgYHtyfQpsZmRyIDwtIGxvY2ZkcihkdGkkei52YWx1ZSwgcGxvdD0yKQpgYGAKCi0gVGhlIHBsb3QgYXQgdGhlIGxlZnQgaXMgdGhlIHNhbWUgYXMgb24gdGhlIHByZXZpb3VzIHBhZ2UuCgotIFRoZSBwbG90IGF0IHRoZSByaWdodCBzaG93cyB0aGUgbG9jYWwgZmRyIGFzIHRoZSBibGFjayBzb2xpZCBsaW5lLiBDbG9zZSB0byAkej0wJCB0aGUgZmRyIGlzIGFib3V0IDEgKGkuZS4gaWYgdGhvc2UgaHlwb3RoZXNlcyB3b3VsZCBiZSByZWplY3RlZCwgdGhlIHByb2JhYmlsaXR5IG9mIGEgZmFsc2UgcG9zaXRpdmUgaXMgYWJvdXQgJDEwMFwlJCkuIFdoZW4gbW92aW5nIGF3YXkgZnJvbSAkej0wJCB0byBsYXJnZXIgdmFsdWVzIHRoZSBmZHIgZHJvcHMuCgotIFRoaXMgbWVhbnMgdGhhdCB3ZSBjYW4gb25seSBkaXNjb3ZlciBjb252aW5jaW5nbHkgZGlmZmVyZW50aWFsIGJyYWluIGFjdGl2aXR5IGZvciBsYXJnZSBwb3NpdGl2ZSAkeiQuIFJlamVjdGluZyBudWxsIGh5cG90aGVzZXMgd2l0aCBsYXJnZSBuZWdhdGl2ZSAkeiQgd291bGQgc3RpbGwgYmUgcmlza3k6IGxhcmdlIGNoYW5jZSBvZiBmYWxzZSBkaXNjb3ZlcnkuCgotIFRoZSByZWFzb24gY2FuIGJlIHJlYWQgZnJvbSB0aGUgZmlyc3QgZ3JhcGg6IGZvciBuZWdhdGl2ZSAkeiQgdGhlIHJhdGlvICRmXzAoeikvZih6KSQgaXMgYWxtb3N0IDEsIHdoZXJlYXMgZm9yIGxhcmdlIHBvc2l0aXZlICR6JCB0aGUgcmF0aW8gJGZfMCh6KS9mKHopJCBiZWNvbWVzIHNtYWxsLgoKLSBOb3RlLCB0aGF0IHRoZSByZXN1bHQgaXMgYXR5cGljYWxseS4gSW4gbW9zdCBhcHBsaWNhdGlvbnMgd2UgdHlwaWNhbGx5IHBpY2stdXAgYm90aCBkb3ducmVndWxhdGVkIChuZWdhdGl2ZSB6KSBhbmQgdXByZWd1bGF0ZWQgKHBvc2l0aXZlIHopIGZlYXR1cmVzLgoKLS0tCgpgYGB7cn0KZHRpIDwtIGR0aSAlPiUKICBtdXRhdGUoCiAgICBsZmRyID0gbGZkciRmZHIsCiAgICB6ZmRyID0gKGxmZHI8MC4yKSAqIHoudmFsdWUpCgpwZmRyIDwtIGR0aSAlPiUKICBnZ3Bsb3QoCiAgICBhZXMoCiAgICAgIGNvb3JkLnksCiAgICAgIGNvb3JkLngsCiAgICAgIGNvbG9yPXpmZHIpCiAgICApICsKICBnZW9tX3BvaW50KCkgKwogIHNjYWxlX2NvbG91cl9ncmFkaWVudDIobG93ID0gImJsdWUiLG1pZD0id2hpdGUiLGhpZ2g9InJlZCIpICsKICB0cmFuc2l0aW9uX21hbnVhbChjb29yZC56KSArCiAgbGFicyh0aXRsZSA9ICJ0cmFuc2VjdGlvbiB6ID0ge2ZyYW1lfSIpICsKICB0aGVtZV9ncmV5KCkKYGBgCgpgYGB7ciBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBldmFsPWtuaXRyOjppc19odG1sX291dHB1dCgpfQphbmltYXRlKHBmZHIsIG5mcmFtZXMgPSAxMDMsIGVuZF9wYXVzZSA9IDMpCmBgYAoKTm90ZSwgdGhhdCB0aGUgbG9jYWwgZmRyIG1ldGhvZCBhbGxvd3MgdXMgdG8gZGV0ZWN0IGRpZmZlcmVudGlhbCBicmFpbiBhY3Rpdml0eSBpbiBhIHNwZWNpZmljIHJlZ2lvbiBpbiB0aGUgZnJvbnQgcGFydCBvZiB0aGUgYnJhaW4gZm9yIHdoaWNoIGEgbGFyZ2VyIGZyYWN0aW9uYWwgYW5pc290cm9weSBpcyBvYnNlcnZlZCBvbiBhdmVyYWdlIGZvciBjaGlsZGVyZW4gaGF2aW5nIGR5c2xleGlhLgoKV2UgY2FuIGFsc28gZXN0aW1hdGUgdGhlIEZEUiBvZiB0aGUgc2V0IHRoYXQgd2UgcmV0dXJuIGFzIHRoZSBhdmVyYWdlIGxvY2FsIGZkciBpbiB0aGlzIHNldC4KCmBgYHtyfQpkdGkgJT4lCiAgZmlsdGVyKGxmZHIgPCAwLjIpICU+JQogIHB1bGwobGZkcikgJT4lCiAgbWVhbgpgYGAKCiMjIFBvd2VyCgoKVGhlIGxvY2FsIGZhbHNlIGRpc2NvdmVyeSByYXRlIG1heSBhbHNvIGJlIHVzZWQgdG8gZ2V0ICoqcG93ZXIgZGlhZ25vc3RpY3MqKi4KCkdlbmVyYWwgaWRlYTogZm9yICR6JCdzIHN1cHBvcnRlZCBieSB0aGUgYWx0ZXJuYXRpdmUgaHlwb3RoZXNpcyAoaS5lLiBsYXJnZSAkZl8xKHopJCksIHdlIGhvcGUgdG8gc2VlIHNtYWxsIGZkciQoeikkLgoKVGhlICoqZXhwZWN0ZWQgZmRyKiogaXMgYW4gYXBwcm9wcmlhdGUgc3VtbWFyeSBtZWFzdXJlOgpcWwogIFx0ZXh0e0VmZHJ9ID0gXHRleHR7RX1fe2YxfVxsZWZ0W1x0ZXh0e2Zkcn0oWilccmlnaHRdID0gXGludF97LVxpbmZ0eX1eeytcaW5mdHl9IFx0ZXh0e2Zkcn0oeikgZl8xKHopIGR6LgpcXQoKV2l0aCBlc3RpbWF0ZXMgb2YgZmRyJCh6KSQgYW5kICRmXzEoeikkLCB0aGUgRWZkciBjYW4gYmUgY29tcHV0ZWQuCgpBIHNtYWxsIEVmZHIgaXMgYW4gaW5kaWNhdGlvbiBvZiBhIHBvd2VyZnVsIHN0dWR5LgoKYGBge3J9CmxmZHIgPC0gbG9jZmRyKGR0aSR6LnZhbHVlLCBwbG90ID0gMykKYGBgCgpXaXRoICRcYWxwaGEkIHRoZSBub21pbmFsIGxvY2FsIGZkciBsZXZlbCwgdGhlIHZlcnRpY2FsIGF4aXMgZ2l2ZXMKXFsKICBcdGV4dHtFfV97Zl8xfVxsZWZ0W1x0ZXh0e2Zkcn0oWik8XGFscGhhXHJpZ2h0XS4KXF0KCndoZXJlICRaJCBpcyB0aGUgdGVzdCBzdGF0aXN0aWMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIGFsdGVybmF0aXZlIGh5cG90aGVzaXMgKCRmXzEkKS4KCi0gVGhpcyBwcm9iYWJpbGl0eSAkXHRleHR7UH1fe2ZfMX1cbGVmdFtcdGV4dHtmZHJ9KFopPFxhbHBoYVxyaWdodF0kIGlzIGEga2luZCBvZiBleHRlbnNpb24gb2YgdGhlIGRlZmluaXRpb24gb2YgdGhlIHBvd2VyIG9mIGEgdGVzdDogaXQgaXMgdGhlIHByb2JhYmlsaXR5IHRoYXQgYSBub24tbnVsbCBjYW4gYmUgZGV0ZWN0ZWQgd2hlbiB0aGUgbm9taW5hbCBsb2NhbCBmZHIgaXMgc2V0IGF0ICRcYWxwaGEkLgoKLSBUaGUgZ3JhcGggc2hvd3MsIGZvciBleGFtcGxlcywgdGhhdCB3aXRoICRcYWxwaGE9MC4yMCQgd2Ugb25seSBoYXZlICRcdGV4dHtQfV97Zl8xfVxsZWZ0W1x0ZXh0e2Zkcn0oWik8XGFscGhhXHJpZ2h0XSA9MC4yNCQsIGkuZS4gb25seSAkMjRcJSQgb2YgdGhlIG5vbi1udWxscyBhcmUgZXhwZWN0ZWQgdG8gYmUgZGlzY292ZXJlZC4KCi0gQXQgdGhlIGJvdHRvbSBvZiB0aGUgZ3JhcGggd2UgcmVhZCBFZmRyJD0wLjQ4NiQuIEhlbmNlLCB0aGUgbG9jYWwgZmRyIGZvciBhIHR5cGljYWwgbm9uLW51bGwgZmVhdHVyZSBpcyBleHBlY3RlZCB0byBiZSA0OC42JSB3aGljaCBpcyByYXRoZXIgbGFyZ2UuIFRoZSBzdHVkeSBpcyBub3Qgd2VsbCBwb3dlcmVkIQoKIyMgQ29tcGFyaXNvbiB3aXRoIGdlbmUgZXhwcmVzc2lvbiBzdHVkeQoKLSBISVYgZGF0YXNldDogNzY4MCB6LXZhbHVlcywgZWFjaCByZWxhdGluZyB0byBhIHR3by1zYW1wbGUgdC10ZXN0IGNvbXBhcmluZyBnZW5lIGV4cHJlc3Npb24gb2YgNCBub3JtYWwgdG8gNCBISVYgcGF0aWVudHMuCgpgYGB7cn0KZGF0YShoaXZkYXRhKQpyZXMgPC0gbG9jZmRyKGhpdmRhdGEscGxvdD00KQpgYGAKCmBgYHtyfQptZWFuKHJlcyRmZHJbcmVzJGZkcjwwLjJdKQpgYGAKCmBgYHtyLCBjaGlsZD0iX3Nlc3Npb24taW5mby5SbWQifQpgYGAK