Creative Commons License


1 Why data exploration and descriptive statistics?

  1. Reporting on results: not useful to only report a table with the results for each subject.
  2. Letting the data speak by summarizing and visualizing it.
  3. Getting insight in the data.
  4. Discover errors, anomalies or even fraud.
  5. Check assumptions that are required for the downstream statistical inference, e.g. are the data Normally distributed.

2 Univariate exploration of quantitative variables

2.1 Histogram

A histogram is very useful to provide an estimate of the distribution of the data without making distributional assumptions.

For a histogram you typically require to have enough observations. A sample size of 30 observations is a bare minimum to construct a histogram.

NHANES %>%
  filter(Gender == "female") %>%
  ggplot(aes(x = DirectChol)) +
  geom_histogram(aes(y = ..density.., fill = ..count..), bins = 30) +
  geom_density(aes(y = ..density..))

  1. Select females and pipe results to ggplot.
NHANES %>% filter(Gender=="female")
  1. Select data to plot.
ggplot(aes(x=DirectChol)) +
  1. Equal bins for interpretation, the number of bins can be selected with the bins argument to the geom_hist.

  2. Relative frequenties to enable visual comparison between histograms.

geom_histogram(aes(y=..density.., fill=..count..)) +
  1. If we have enough observations we can use a kernel density estimator of f(x).
geom_density(aes(y=..density..))

2.2 Boxplot

  • A quantile, \(x_{a\%}\), is the value of the random variable that correspond to a certain probability \(F(x_{a\%})=P[X\leq x_{a\%}=a\%]\).

With ggplot we always have to define an x variable if we make a boxplot. If we use a string then all data is considered to originate from one category and one boxplot is constructed.

NHANES %>%
  filter(Gender == "female") %>%
  ggplot(aes(x = "", y = DirectChol)) +
  geom_boxplot()

So we can add a boxplot to a ggplot figure by using the geom_boxplot() function.

If the dataset is small to moderate in size we can also add the raw data to the plot with the geom_point() function and the position="jitter" argument. Note, that we then also set the outlier.shape argument in the geom_boxplot function on NA so that the outliers are not plotted twice.

Here, we will plot again the relative abundances of Staphylococcus from the armpit transplant experiment.

ap <- read_csv("https://raw.githubusercontent.com/GTPB/PSLS20/master/data/armpit.csv")
ap
ap %>%
  ggplot(aes(x = trt, y = rel)) +
  geom_boxplot(outlier.shape = NA) +
  geom_point(position = "jitter")

When we specify a factor variable for x, we get a boxplot for each treatment group.


2.3 Descriptive statistics

2.3.1 Central location: Mean or Median?

2.3.1.1 Mean

  • In a period of 30 years, males hope to have on average 64.3 partners and females 2.8 {(Miller and Fishkin, 1997)}.

2.3.1.2 Median

  • The median of the number of partners males and females want to have is both 1 {(Miller and Fishkin, 1997)}


2.3.1.3 What happens?

  • Mean is very sensitive towards outliers!


2.3.2 Geometric mean

\[\sqrt[n]{\prod\limits_{i=1}^n x_i} = \exp\left\{\frac{1}{n} \sum_{i=1}^n \log(x_i)\right\}\]

  • Geometric mean is closer to the median then the mean

  • log-transformation removes skewness

  • Often a more useful measure for the central location than median:

  1. Uses all observations: is more precise
  2. It is the ordinary mean on log-transformed data \(\rightarrow\) classical statistical methods can be directly applied, e.g. hypothesis tests and confidence intervals (see chapter 5)
  3. Useful for many biological characteristics e.g. concentrations that cannot be negative.
  4. Differences on a log scale have the interpretation of a log fold change:

\[\log (B) - \log(A)= \log(\frac{B}{A})=\log(FC_\text{B vs A})\]

In Genomics often the \(log_2\) transformation is used. A difference of 1 corresponds to a \(FC=2\).

logSummary <-
  NHANES %>%
  filter(Gender == "female") %>%
  summarize(logMean = mean(DirectChol %>% log2(), na.rm = TRUE), sd = sd(DirectChol %>% log2(), na.rm = TRUE), mean = mean(DirectChol, na.rm = TRUE), median = median(DirectChol, na.rm = TRUE)) %>%
  mutate(geoMean = 2^logMean)

NHANES %>%
  filter(Gender == "female") %>%
  ggplot(aes(x = DirectChol %>% log2())) +
  geom_histogram(aes(y = ..density.., fill = ..count..), bins = 30) +
  geom_density(aes(y = ..density..)) +
  stat_function(fun = dnorm, color = "red", args = list(mean = logSummary$logMean, sd = logSummary$sd))

logSummary
  • Indeed the mean is pulled to larger values by the skewed data.
  • The geometric mean is closer to the median.
  • The cholesterol data are much more symmetric upon log transformation and the approximation by a Normal distribution is good.

2.4 Descriptive Statistics for Variability

The variability around the central value is crucial:

  1. Biologists are often interested in how animals or plants are spread in the study region.
  2. Compare groups: the group effect is more clear when the response has less variability. Quantifying variability is crucial to distinguish between systematic and random patterns.
  • The response varies between and within individuals and is the reason why we need statistics.

  • Crucial to describe both the central location and the variability.

  • Which part of the variability can we explain (e.g. with characteristics treatment, age, etc,) and which part is unexplained?

2.4.1 Sample variance and sample standard deviation

  • Sample variance: \[ s_X^2= \sum\limits_{i=1}^n \frac{(X-\bar X)^2}{n-1} \]

  • Interpretation is often difficult because it is in another unit than the measurements.

  • Standard deviation: \[ s_X= \sqrt{s_x^2} \]

  • Very useful for Normal distributed observations:

    • 68% of the observations falls in the interval \(\bar{x} - s_x\) en \(\bar{x} + s_x\)
    • 95% of the observations falls in the interval \(\bar{x} - 2 s_x\) en \(\bar{x} + 2 s_x\).
  • These intervals are referred to as 68% en 95% reference-intervals.

  • If the data are not Normally distributed, reference intervals are not valid.


2.4.2 Interquartile range

For skewed data the standard deviation is not useful

  • It is very sensitive to outliers
  • Inter Quartile Range: Distance between first and third quartile
  • Width of the boxplot!
NHANES %>%
  filter(Gender == "female") %>%
  summarize(IQR = IQR(DirectChol, na.rm = TRUE))
NHANES %>%
  filter(Gender == "female") %>%
  ggplot(aes(x = "", y = DirectChol)) +
  geom_boxplot()


3 Normal approximation

  • Biological and chemical data are often Normally distributed upon transformation.

  • If this is the case we can get a lot of insight in the data using just two descriptive statistics: mean \(\mu\) and standard deviation \(\sigma\).


3.1 Evaluation with QQ-plots

If your analysis builds upon the assumption that the data are Normally distributed, it has to be verified.

We use QQ-plots or quantile-quantile plots.

  • Observed quantiles from the observations in the sample are plotted against quantiles from the Normal distribution.

  • If the data are Normally distributed both quantiles have to be in line.

  • Dots in the plot are expected on a straight line.

  • Systematic deviations of the straight line indicate that the data are not Normally distributed.

  • Note, that we will always observe some random deviations from the straight line in the plot because of random biological variability, which is not indicative for deviations from Normality.

  • So it is important to train yourself to learn to distinguish systematic from random deviations.


3.1.1 Normal data

  • We will first simulate data from the Normal distribution to show how the plots look like for data that is meeting the assumptions.

  • We will simulate data from 9 samples with a mean of 18 and standard deviation of 9.

n <- 20
mu <- 18
sigma <- 9
nSamp <- 9

normSim <- matrix(rnorm(n * nSamp, mean = mu, sd = sigma), nrow = n) %>% as.data.frame()

normSim %>%
  gather(samp, data) %>%
  ggplot(aes(x = data)) +
  geom_histogram(aes(y = ..density.., fill = ..count..), bins = 30) +
  geom_density(aes(y = ..density..)) +
  facet_wrap(~samp)

normSim %>%
  gather(samp, data) %>%
  ggplot(aes(sample = data)) +
  geom_qq() +
  geom_qq_line() +
  facet_wrap(~samp)

So even for Normal data we observe some deviations due to sampling variability!


3.1.2 Real data

NHANES %>%
  filter(Gender == "female" & !is.na(BMI)) %>%
  ggplot(aes(x = BMI)) +
  geom_histogram(aes(y = ..density.., fill = ..count..)) +
  xlab("BMI") +
  ggtitle("All females in study") +
  geom_density(aes(y = ..density..))

NHANES %>%
  filter(Gender == "female" & !is.na(BMI)) %>%
  ggplot(aes(sample = BMI)) +
  geom_qq() +
  geom_qq_line()

The QQ-plot shows that the quantiles of the data

  • are larger (above the line) than these from the Normal in the left tail: compression of the lower tail,
  • are larger (above the line) than these from the Normal in the right tail: long tail to the right.

We can clearly see that the data are right-skewed.


4 Two continuous variables: Correlation

  • NHANES study
  • Height and Weight Example for females
NHANES %>%
  filter(Age > 25 & Gender == "female") %>%
  ggplot(aes(x = Height, y = Weight)) +
  geom_point()

We observe an association between Weight and Height, but we also observe that the Weights tend to be right-skewed.

Lets look at the univariate distributions first.

NHANES %>%
  filter(Age > 25 & Gender == "female") %>%
  ggplot(aes(x = Height)) +
  geom_histogram(aes(y = ..density.., fill = ..count..)) +
  xlab("Height") +
  ggtitle("All females in study") +
  geom_density(aes(y = ..density..))

NHANES %>%
  filter(Age > 25 & Gender == "female") %>%
  ggplot(aes(sample = Height)) +
  geom_qq() +
  geom_qq_line()

NHANES %>%
  filter(Age > 25 & Gender == "female") %>%
  ggplot(aes(x = Weight)) +
  geom_histogram(aes(y = ..density.., fill = ..count..)) +
  xlab("Weight") +
  ggtitle("All females in study") +
  geom_density(aes(y = ..density..))

NHANES %>%
  filter(Age > 25 & Gender == "female") %>%
  ggplot(aes(sample = Weight)) +
  geom_qq() +
  geom_qq_line()

The weights are indeed skewed!

Upon log transformaton the Weights are less skewed, but still not Normally distributed.

NHANES %>%
  filter(Age > 25 & Gender == "female") %>%
  ggplot(aes(x = Weight %>% log2())) +
  geom_histogram(aes(y = ..density.., fill = ..count..)) +
  xlab("Weight (log2)") +
  ggtitle("All females in study") +
  geom_density(aes(y = ..density..))

NHANES %>%
  filter(Age > 25 & Gender == "female") %>%
  ggplot(aes(sample = Weight %>% log2())) +
  geom_qq() +
  geom_qq_line()

Skewness is still there but is reduced already.

NHANES %>%
  filter(Age > 25 & Gender == "female") %>%
  ggplot(aes(x = Height, y = Weight %>% log2())) +
  ylab("Weight (log2)") +
  geom_point()


4.1 Covariance and Correlation

  • Let X and Y be to continuous random variables, and for each subject i we observe \((X_i,Y_i)\).
  • Covariance: how deviate X_i and Y_i around their means?

\[\mbox{Covar}(X,Y)=E[(X-E[X])(Y-E[Y])]\]

  • Correlation: standardise the covariance according to the variability in each variable:

\[\mbox{Cor}(X,Y)=\frac{E[(X-E[X])(Y-E[Y])]}{\sqrt{E[(X-E[X])^2}\sqrt{E[(Y-E[Y])^2}}\]

4.2 Pearson Correlation

  • Association between two continuous covariate:

\[\mbox{Cor}(X,Y)=\frac{\sum_{i=1}^{n}(X_{i}-\bar{X})(Y_{i}-\bar{Y})}{(n-1)s_{X}s_{Y}} \]

  • Positive correlation: \(x \ \nearrow \ \Rightarrow \ y \ \nearrow\)

  • Negative correlation: \(x \ \nearrow \ \Rightarrow \ y \ \searrow\)

  • Correlation always between -1 en 1

means <- NHANES %>%
  filter(Age > 25 & Gender == "female") %>%
  select(Weight, Height) %>%
  mutate(log2Weight = Weight %>% log2()) %>%
  apply(., 2, mean, na.rm = TRUE)
ranges <- NHANES %>%
  filter(Age > 25 & Gender == "female") %>%
  select(Weight, Height) %>%
  mutate(log2Weight = Weight %>% log2()) %>%
  apply(., 2, range, na.rm = TRUE)

NHANES %>%
  filter(Age > 25 & Gender == "female") %>%
  ggplot(aes(x = Height, y = Weight %>% log2())) +
  ylab("Weight (log2)") +
  geom_point() +
  geom_hline(yintercept = means["log2Weight"], color = "red") +
  geom_vline(xintercept = means["Height"], color = "red") +
  annotate(
    geom = "text",
    x = c(ranges[1, "Height"], ranges[1, "Height"], ranges[2, "Height"], ranges[2, "Height"]),
    y = c(ranges[1, "log2Weight"], ranges[2, "log2Weight"], ranges[1, "log2Weight"], ranges[2, "log2Weight"]),
    label = c("+", "-", "-", "+"), color = "red", size = 10
  )

NHANES %>%
  filter(Age > 25 & Gender == "female") %>%
  ggplot(aes(x = Height, y = Weight)) +
  ylab("Weight (log2)") +
  geom_point() +
  geom_hline(yintercept = means["Weight"], color = "red") +
  geom_vline(xintercept = means["Height"], color = "red") +
  annotate(
    geom = "text",
    x = c(ranges[1, "Height"], ranges[1, "Height"], ranges[2, "Height"], ranges[2, "Height"]),
    y = c(ranges[1, "Weight"], ranges[2, "Weight"], ranges[1, "Weight"], ranges[2, "Weight"]),
    label = c("+", "-", "-", "+"), color = "red", size = 10
  )

NHANES %>%
  filter(Age > 25 & Gender == "female") %>%
  select(Weight, Height) %>%
  mutate(log2Weight = Weight %>% log2()) %>%
  na.exclude() %>%
  cor()
              Weight    Height log2Weight
Weight     1.0000000 0.3029834  0.9812646
Height     0.3029834 1.0000000  0.3240824
log2Weight 0.9812646 0.3240824  1.0000000
  • Note, that the correlation is lower when the data are not transformed.
  • The Pearson correlation is sensitive to outliers!
  • Do not use Pearson correlation for skewed distributions or for data with outliers.

4.2.1 Impact of outliers

Illustration with simulated data that has one outlier.

set.seed(100)
x <- rnorm(20)
simData <- data.frame(x = x, y = x * 2 + rnorm(length(x)))
simData %>% ggplot(aes(x = x, y = y)) +
  geom_point() +
  ggtitle(paste("cor =", cor(simData[, 1], simData[, 2]) %>% round(., 2)))

outlier <- rbind(simData, c(2, -4))
outlier %>% ggplot(aes(x = x, y = y)) +
  geom_point() +
  ggtitle(paste("cor =", cor(outlier[, 1], outlier[, 2]) %>% round(., 2)))

4.2.2 Only linear association

Note, that the Pearson correlation only captures linear association!

x <- rnorm(100)
quadratic <- data.frame(x = x, y = x^2 + rnorm(length(x)))
quadratic %>% ggplot(aes(x = x, y = y)) +
  geom_point() +
  ggtitle(paste("cor =", cor(quadratic[, 1], quadratic[, 2]) %>% round(., 2))) +
  geom_hline(yintercept = mean(quadratic[, 2]), col = "red") +
  geom_vline(xintercept = mean(quadratic[, 1]), col = "red")

4.3 Different magnitudes of correlation

set.seed(100)
x <- rnorm(100)
simData2 <- cbind(x, 1.5 * x, sapply(1:7, function(sd, x) 1.5 * x + rnorm(length(x), sd = sd), x = x), rnorm(length(x), sd = 7))

colnames(simData2)[-1] <- paste("cor", round(cor(simData2)[1, -1], 2), sep = "=")

simData2 %>%
  as.data.frame() %>%
  gather(cor, y, -x) %>%
  ggplot(aes(x = x, y = y)) +
  geom_point() +
  facet_wrap(~cor)

simData3 <- simData2
simData3[, -1] <- -simData2[, -1]
colnames(simData3)[-1] <- paste("cor", round(cor(simData3)[1, -1], 2), sep = "=")

simData3 %>%
  as.data.frame() %>%
  gather(cor, y, -x) %>%
  ggplot(aes(x = x, y = y)) +
  geom_point() +
  facet_wrap(~cor)


4.4 Spearman correlation

The Spearman correlation is the Pearson correlation after transforming the data to ranks.

  • Pearson correlation
cor(outlier)
          x         y
x 1.0000000 0.4682823
y 0.4682823 1.0000000
  • Spearman correlation
cor(outlier, method = "spearman")
          x         y
x 1.0000000 0.6571429
y 0.6571429 1.0000000
  • Spearman correlation is less sensitive to outliers.

  • Pearson correlation on ranks

rankData <- apply(outlier, 2, rank)
cor(rankData)
          x         y
x 1.0000000 0.6571429
y 0.6571429 1.0000000
  • NHANES example
NHANES %>%
  filter(Age > 25 & Gender == "female") %>%
  select(Weight, Height) %>%
  mutate(log2Weight = Weight %>% log2()) %>%
  na.exclude() %>%
  cor(method = "spearman")
              Weight    Height log2Weight
Weight     1.0000000 0.3039481  1.0000000
Height     0.3039481 1.0000000  0.3039481
log2Weight 1.0000000 0.3039481  1.0000000
LS0tCnRpdGxlOiAiNC4gRGF0YSBFeHBsb3JhdGlvbiIKYXV0aG9yOiAiTGlldmVuIENsZW1lbnQiCmRhdGU6ICJzdGF0T21pY3MsIEdoZW50IFVuaXZlcnNpdHkgKGh0dHBzOi8vc3RhdG9taWNzLmdpdGh1Yi5pbykiCi0tLQoKPGEgcmVsPSJsaWNlbnNlIiBocmVmPSJodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtc2EvNC4wIj48aW1nIGFsdD0iQ3JlYXRpdmUgQ29tbW9ucyBMaWNlbnNlIiBzdHlsZT0iYm9yZGVyLXdpZHRoOjAiIHNyYz0iaHR0cHM6Ly9pLmNyZWF0aXZlY29tbW9ucy5vcmcvbC9ieS1uYy1zYS80LjAvODh4MzEucG5nIiAvPjwvYT4KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFLCBjYWNoZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAogIGluY2x1ZGUgPSBUUlVFLCBjb21tZW50ID0gTkEsIGVjaG8gPSBUUlVFLAogIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBjYWNoZSA9IFRSVUUKKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShOSEFORVMpCmBgYAoKYGBge3IgcG9wMlNhbXAyUG9wLCBvdXQud2lkdGg9JzgwJScsZmlnLmFzcD0uOCwgZmlnLmFsaWduPSdjZW50ZXInLGVjaG89RkFMU0V9CmlmICgicGkiICVpbiUgbHMoKSkgcm0oInBpIikKa29wdm9ldGVyIDwtIGZ1bmN0aW9uKHgsIHksIGFuZ2xlID0gMCwgbCA9IC4yLCBjZXguZG90ID0gLjUsIHBjaCA9IDE5LCBjb2wgPSAiYmxhY2siKSB7CiAgYW5nbGUgPC0gYW5nbGUgLyAxODAgKiBwaQogIHBvaW50cyh4LCB5LCBjZXggPSBjZXguZG90LCBwY2ggPSBwY2gsIGNvbCA9IGNvbCkKICBsaW5lcyhjKHgsIHggKyBsICogY29zKC1waSAvIDIgKyBhbmdsZSkpLCBjKHksIHkgKyBsICogc2luKC1waSAvIDIgKyBhbmdsZSkpLCBjb2wgPSBjb2wpCiAgbGluZXMoYyh4ICsgbCAvIDIgKiBjb3MoLXBpIC8gMiArIGFuZ2xlKSwgeCArIGwgLyAyICogY29zKC1waSAvIDIgKyBhbmdsZSkgKyBsIC8gNCAqIGNvcyhhbmdsZSkpLCBjKHkgKyBsIC8gMiAqIHNpbigtcGkgLyAyICsgYW5nbGUpLCB5ICsgbCAvIDIgKiBzaW4oLXBpIC8gMiArIGFuZ2xlKSArIGwgLyA0ICogc2luKGFuZ2xlKSksIGNvbCA9IGNvbCkKICBsaW5lcyhjKHggKyBsIC8gMiAqIGNvcygtcGkgLyAyICsgYW5nbGUpLCB4ICsgbCAvIDIgKiBjb3MoLXBpIC8gMiArIGFuZ2xlKSArIGwgLyA0ICogY29zKHBpICsgYW5nbGUpKSwgYyh5ICsgbCAvIDIgKiBzaW4oLXBpIC8gMiArIGFuZ2xlKSwgeSArIGwgLyAyICogc2luKC1waSAvIDIgKyBhbmdsZSkgKyBsIC8gNCAqIHNpbihwaSArIGFuZ2xlKSksIGNvbCA9IGNvbCkKICBsaW5lcyhjKHggKyBsICogY29zKC1waSAvIDIgKyBhbmdsZSksIHggKyBsICogY29zKC1waSAvIDIgKyBhbmdsZSkgKyBsIC8gMiAqIGNvcygtcGkgLyAyICsgcGkgLyA0ICsgYW5nbGUpKSwgYyh5ICsgbCAqIHNpbigtcGkgLyAyICsgYW5nbGUpLCB5ICsgbCAqIHNpbigtcGkgLyAyICsgYW5nbGUpICsgbCAvIDIgKiBzaW4oLXBpIC8gMiArIHBpIC8gNCArIGFuZ2xlKSksIGNvbCA9IGNvbCkKICBsaW5lcyhjKHggKyBsICogY29zKC1waSAvIDIgKyBhbmdsZSksIHggKyBsICogY29zKC1waSAvIDIgKyBhbmdsZSkgKyBsIC8gMiAqIGNvcygtcGkgLyAyIC0gcGkgLyA0ICsgYW5nbGUpKSwgYyh5ICsgbCAqIHNpbigtcGkgLyAyICsgYW5nbGUpLCB5ICsgbCAqIHNpbigtcGkgLyAyICsgYW5nbGUpICsgbCAvIDIgKiBzaW4oLXBpIC8gMiAtIHBpIC8gNCArIGFuZ2xlKSksIGNvbCA9IGNvbCkKfQoKcGFyKG1hciA9IGMoMCwgMCwgMCwgMCksIG1haSA9IGMoMCwgMCwgMCwgMCkpCnBsb3QoMCwgMCwgeGxhYiA9ICIiLCB5bGFiID0gIiIsIHhsaW0gPSBjKDAsIDEwKSwgeWxpbSA9IGMoMCwgMTApLCBjb2wgPSAwLCB4YXh0ID0gIm5vbmUiLCB5YXh0ID0gIm5vbmUiLCBheGVzID0gRkFMU0UpCnJlY3QoMCwgNiwgMTAsIDEwLCBib3JkZXIgPSAicmVkIiwgbHdkID0gMikKdGV4dCguNSwgOCwgInBvcHVsYXRpb24iLCBzcnQgPSA5MCwgY29sID0gInJlZCIsIGNleCA9IDIpCnN5bWJvbHMoMywgOCwgY2lyY2xlcyA9IDEuNSwgY29sID0gInJlZCIsIGFkZCA9IFRSVUUsIGZnID0gInJlZCIsIGluY2hlcyA9IEZBTFNFLCBsd2QgPSAyKQpzZXQuc2VlZCgzMzApCmdyaWQgPC0gc2VxKDAsIDEuMywgLjAxKQoKZm9yIChpIGluIDE6NTApCnsKICBhbmdsZTEgPC0gcnVuaWYobiA9IDEsIG1pbiA9IDAsIG1heCA9IDM2MCkKICBhbmdsZTIgPC0gcnVuaWYobiA9IDEsIG1pbiA9IDAsIG1heCA9IDM2MCkKICByYWRpdXMgPC0gc2FtcGxlKGdyaWQsIHByb2IgPSBncmlkXjIgKiBwaSAvIHN1bShncmlkXjIgKiBwaSksIHNpemUgPSAxKQogIGtvcHZvZXRlcigzICsgcmFkaXVzICogY29zKGFuZ2xlMSAvIDE4MCAqIHBpKSwgOCArIHJhZGl1cyAqIHNpbihhbmdsZTEgLyAxODAgKiBwaSksIGFuZ2xlID0gYW5nbGUyKQp9CnRleHQoNy41LCA4LCAiQ2hvbGVzdGVyb2wgaW4gcG9wdWxhdGlvbiIsIGNvbCA9ICJyZWQiLCBjZXggPSAxLjIpCgpyZWN0KDAsIDAsIDEwLCA0LCBib3JkZXIgPSAiYmx1ZSIsIGx3ZCA9IDIpCnRleHQoLjUsIDIsICJzYW1wbGUiLCBzcnQgPSA5MCwgY29sID0gImJsdWUiLCBjZXggPSAyKQpzeW1ib2xzKDMsIDIsIGNpcmNsZXMgPSAxLjUsIGNvbCA9ICJyZWQiLCBhZGQgPSBUUlVFLCBmZyA9ICJibHVlIiwgaW5jaGVzID0gRkFMU0UsIGx3ZCA9IDIpCmZvciAoaSBpbiAwOjIpIHsKICBmb3IgKGogaW4gMDo0KQogIHsKICAgIGtvcHZvZXRlcigyLjEgKyBqICogKDMuOSAtIDIuMSkgLyA0LCAxLjEgKyBpKQogIH0KfQp0ZXh0KDcuNSwgMiwgIkNob2xlc3Rlcm9sIGluIHNhbXBsZSIsIGNvbCA9ICJibHVlIiwgY2V4ID0gMS4yKQoKYXJyb3dzKDMsIDUuOSwgMywgNC4xLCBjb2wgPSAiYmxhY2siLCBsd2QgPSAzKQphcnJvd3MoNywgNC4xLCA3LCA1LjksIGNvbCA9ICJibGFjayIsIGx3ZCA9IDMpCnRleHQoMS41LCA1LCAiRVhQLiBERVNJR04gKDEpIiwgY29sID0gImJsYWNrIiwgY2V4ID0gMS4yKQp0ZXh0KDguNSwgNSwgIkVTVElNQVRJT04gJlxuSU5GRVJFTkNFICgzKSIsIGNvbCA9ICJibGFjayIsIGNleCA9IDEuMikKdGV4dCg3LjUsIC41LCAiREFUQSBFWFBMT1JBVElPTiAmXG5ERVNDUklQVElWRSBTVEFUSVNUSUNTICgyKSIsIGNvbCA9ICJibGFjayIsIGNleCA9IDEuMikKYGBgCgotLS0KCiMgV2h5IGRhdGEgZXhwbG9yYXRpb24gYW5kIGRlc2NyaXB0aXZlIHN0YXRpc3RpY3M/CgoxLiBSZXBvcnRpbmcgb24gcmVzdWx0czogbm90IHVzZWZ1bCB0byBvbmx5IHJlcG9ydCBhIHRhYmxlIHdpdGggdGhlIHJlc3VsdHMgZm9yIGVhY2ggc3ViamVjdC4KMi4gTGV0dGluZyB0aGUgZGF0YSBzcGVhayBieSBzdW1tYXJpemluZyBhbmQgdmlzdWFsaXppbmcgaXQuCjMuIEdldHRpbmcgaW5zaWdodCBpbiB0aGUgZGF0YS4KNC4gRGlzY292ZXIgZXJyb3JzLCBhbm9tYWxpZXMgb3IgZXZlbiBmcmF1ZC4KNS4gQ2hlY2sgYXNzdW1wdGlvbnMgdGhhdCBhcmUgcmVxdWlyZWQgZm9yIHRoZSBkb3duc3RyZWFtIHN0YXRpc3RpY2FsIGluZmVyZW5jZSwgZS5nLiBhcmUgdGhlIGRhdGEgTm9ybWFsbHkgZGlzdHJpYnV0ZWQuCgotLS0KCiMgVW5pdmFyaWF0ZSBleHBsb3JhdGlvbiBvZiBxdWFudGl0YXRpdmUgdmFyaWFibGVzCgoKIyMgSGlzdG9ncmFtCgpBIGhpc3RvZ3JhbSBpcyB2ZXJ5IHVzZWZ1bCB0byBwcm92aWRlIGFuIGVzdGltYXRlIG9mIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGRhdGEgd2l0aG91dCBtYWtpbmcgZGlzdHJpYnV0aW9uYWwgYXNzdW1wdGlvbnMuIAoKRm9yIGEgaGlzdG9ncmFtIHlvdSB0eXBpY2FsbHkgcmVxdWlyZSB0byBoYXZlIGVub3VnaCBvYnNlcnZhdGlvbnMuCkEgc2FtcGxlIHNpemUgb2YgMzAgb2JzZXJ2YXRpb25zIGlzIGEgYmFyZSBtaW5pbXVtIHRvIGNvbnN0cnVjdCBhIGhpc3RvZ3JhbS4KCmBgYHtyLGVjaG89RkFMU0V9CmxpYnJhcnkoTkhBTkVTKQpgYGAKCmBgYHtyfQpOSEFORVMgJT4lCiAgZmlsdGVyKEdlbmRlciA9PSAiZmVtYWxlIikgJT4lCiAgZ2dwbG90KGFlcyh4ID0gRGlyZWN0Q2hvbCkpICsKICBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IC4uZGVuc2l0eS4uLCBmaWxsID0gLi5jb3VudC4uKSwgYmlucyA9IDMwKSArCiAgZ2VvbV9kZW5zaXR5KGFlcyh5ID0gLi5kZW5zaXR5Li4pKQpgYGAKCgoxLiBTZWxlY3QgZmVtYWxlcyBhbmQgcGlwZSByZXN1bHRzIHRvIGdncGxvdC4KYGBgCk5IQU5FUyAlPiUgZmlsdGVyKEdlbmRlcj09ImZlbWFsZSIpCmBgYAoKMi4gU2VsZWN0IGRhdGEgdG8gcGxvdC4KCmBgYApnZ3Bsb3QoYWVzKHg9RGlyZWN0Q2hvbCkpICsKYGBgCgozLiBFcXVhbCBiaW5zIGZvciBpbnRlcnByZXRhdGlvbiwgdGhlIG51bWJlciBvZiBiaW5zIGNhbiBiZSBzZWxlY3RlZCB3aXRoIHRoZSBiaW5zIGFyZ3VtZW50IHRvIHRoZSBnZW9tX2hpc3QuCgo0LiAqUmVsYXRpdmUqIGZyZXF1ZW50aWVzIHRvIGVuYWJsZSB2aXN1YWwgY29tcGFyaXNvbiBiZXR3ZWVuIGhpc3RvZ3JhbXMuCgpgYGAKZ2VvbV9oaXN0b2dyYW0oYWVzKHk9Li5kZW5zaXR5Li4sIGZpbGw9Li5jb3VudC4uKSkgKwpgYGAKCjUuIElmIHdlIGhhdmUgZW5vdWdoIG9ic2VydmF0aW9ucyB3ZSBjYW4gdXNlIGEga2VybmVsIGRlbnNpdHkgZXN0aW1hdG9yIG9mIGYoeCkuCgpgYGAKZ2VvbV9kZW5zaXR5KGFlcyh5PS4uZGVuc2l0eS4uKSkKYGBgCgotLS0KCiMjIEJveHBsb3QKCi0gQSBxdWFudGlsZSwgJHhfe2FcJX0kLCBpcyB0aGUgdmFsdWUgb2YgdGhlIHJhbmRvbSB2YXJpYWJsZSB0aGF0IGNvcnJlc3BvbmQgdG8gYSBjZXJ0YWluIHByb2JhYmlsaXR5ICRGKHhfe2FcJX0pPVBbWFxsZXEgeF97YVwlfT1hXCVdJC4KCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJyxlY2hvPUZBTFNFfQpmZW0gPC0gTkhBTkVTICU+JQogIGZpbHRlcihHZW5kZXIgPT0gImZlbWFsZSIgJiAhaXMubmEoRGlyZWN0Q2hvbCkpICU+JQogIHNlbGVjdChEaXJlY3RDaG9sKQpib3hwbG90KGZlbSREaXJlY3RDaG9sLCB5bGFiID0gIkRpcmVjdCBDaG9sZXN0ZXJvbCIsIGNleC5sYWIgPSAxLjUsIGNleC5heGlzID0gMS41LCBjZXgubWFpbiA9IDEuNSkKcmFuZ2VDbCA8LSBxdWFudGlsZShmZW0kRGlyZWN0Q2hvbCwgYyguMjUsIC43NSkpICsgYygtMSwgMSkgKiBkaWZmKHF1YW50aWxlKGZlbSREaXJlY3RDaG9sLCBjKC4yNSwgLjc1KSkpICogMS41CmJveFlzIDwtIGMocmFuZ2UoZmVtJERpcmVjdENob2xbZmVtJERpcmVjdENob2wgPD0gcmFuZ2VDbFsyXSAmIGZlbSREaXJlY3RDaG9sID49IHJhbmdlQ2xbMV1dKSwgcXVhbnRpbGUoZmVtJERpcmVjdENob2wsIGMoLjI1LCAuNSwgLjc1KSksIHJhbmdlQ2xbMl0gKyAobWF4KGZlbSREaXJlY3RDaG9sKSAtIHJhbmdlQ2xbMl0pIC8gMikKdGV4dCgxLjMsIGJveFlzLCBsYWJlbHMgPSBjKCJ3aXNrZXIiLCAid2lza2VyIiwgIngyNSIsICJtZWRpYW4iLCAieDc1IiwgIm91dGxpZXJzIiksIHBvcyA9IDQsIGNleCA9IDEuMykKbGluZXMoYygxLjEsIDEuMywgMS4zLCAxLjEpLCBjKHJhbmdlQ2xbMl0sIHJhbmdlQ2xbMl0gKyAobWF4KGZlbSREaXJlY3RDaG9sKSAtIHJhbmdlQ2xbMl0pIC8gMiwgcmFuZ2VDbFsyXSArIChtYXgoZmVtJERpcmVjdENob2wpIC0gcmFuZ2VDbFsyXSkgLyAyLCBtYXgoZmVtJERpcmVjdENob2wpKSwgbHR5ID0gMikKYGBgCgpXaXRoIGdncGxvdCB3ZSBhbHdheXMgaGF2ZSB0byBkZWZpbmUgYW4geCB2YXJpYWJsZSBpZiB3ZSBtYWtlIGEgYm94cGxvdC4gSWYgd2UgdXNlIGEgc3RyaW5nIHRoZW4gYWxsIGRhdGEgaXMgY29uc2lkZXJlZCB0byBvcmlnaW5hdGUgZnJvbSBvbmUgY2F0ZWdvcnkgYW5kIG9uZSBib3hwbG90IGlzIGNvbnN0cnVjdGVkLgoKYGBge3J9Ck5IQU5FUyAlPiUKICBmaWx0ZXIoR2VuZGVyID09ICJmZW1hbGUiKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSAiIiwgeSA9IERpcmVjdENob2wpKSArCiAgZ2VvbV9ib3hwbG90KCkKYGBgCgpTbyB3ZSBjYW4gYWRkIGEgYm94cGxvdCB0byBhIGdncGxvdCBmaWd1cmUgYnkgdXNpbmcgdGhlIGBnZW9tX2JveHBsb3QoKWAgZnVuY3Rpb24uCgpJZiB0aGUgZGF0YXNldCBpcyBzbWFsbCB0byBtb2RlcmF0ZSBpbiBzaXplIHdlIGNhbiBhbHNvIGFkZCB0aGUgcmF3IGRhdGEgdG8gdGhlIHBsb3Qgd2l0aCB0aGUgYGdlb21fcG9pbnQoKWAgZnVuY3Rpb24gYW5kIHRoZSBgcG9zaXRpb249ImppdHRlciJgIGFyZ3VtZW50LiBOb3RlLCB0aGF0IHdlIHRoZW4gYWxzbyBzZXQgdGhlIGBvdXRsaWVyLnNoYXBlYCBhcmd1bWVudCBpbiB0aGUgYGdlb21fYm94cGxvdGAgZnVuY3Rpb24gb24gTkEgc28gdGhhdCB0aGUgb3V0bGllcnMgYXJlIG5vdCBwbG90dGVkIHR3aWNlLgoKSGVyZSwgd2Ugd2lsbCBwbG90IGFnYWluIHRoZSByZWxhdGl2ZSBhYnVuZGFuY2VzIG9mICoqU3RhcGh5bG9jb2NjdXMqKiBmcm9tIHRoZSBhcm1waXQgdHJhbnNwbGFudCBleHBlcmltZW50LgoKYGBge3J9CmFwIDwtIHJlYWRfY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vR1RQQi9QU0xTMjAvbWFzdGVyL2RhdGEvYXJtcGl0LmNzdiIpCmFwCgphcCAlPiUKICBnZ3Bsb3QoYWVzKHggPSB0cnQsIHkgPSByZWwpKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGUgPSBOQSkgKwogIGdlb21fcG9pbnQocG9zaXRpb24gPSAiaml0dGVyIikKYGBgCgpXaGVuIHdlIHNwZWNpZnkgYSBmYWN0b3IgdmFyaWFibGUgZm9yIHgsIHdlIGdldCBhIGJveHBsb3QgZm9yIGVhY2ggdHJlYXRtZW50IGdyb3VwLgoKLS0tCgojIyBEZXNjcmlwdGl2ZSBzdGF0aXN0aWNzCgojIyMgQ2VudHJhbCBsb2NhdGlvbjogTWVhbiBvciBNZWRpYW4/CgojIyMjIE1lYW4KLSBJbiBhIHBlcmlvZCBvZiAzMCB5ZWFycywgbWFsZXMgaG9wZSB0byBoYXZlIG9uIGF2ZXJhZ2UgNjQuMyBwYXJ0bmVycyBhbmQgZmVtYWxlcyAyLjggIFx0aW55eyhNaWxsZXIgYW5kIEZpc2hraW4sIDE5OTcpfS5cTm9ybWFsc2l6ZQoKLS0tCgojIyMjIE1lZGlhbgoKPGRldGFpbHM+PHN1bW1hcnk+IDwvc3VtbWFyeT48cD4KLSBUaGUgbWVkaWFuIG9mIHRoZSBudW1iZXIgb2YgcGFydG5lcnMgbWFsZXMgYW5kIGZlbWFsZXMgd2FudCB0byBoYXZlIGlzIGJvdGggMSBcdGlueXsoTWlsbGVyIGFuZCBGaXNoa2luLCAxOTk3KX1cTm9ybWFsc2l6ZQo8L3A+CjwvZGV0YWlscz4KLS0tCgojIyMjIFdoYXQgaGFwcGVucz8KCjxkZXRhaWxzPjxzdW1tYXJ5PiA8L3N1bW1hcnk+PHA+CiFbXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vR1RQQi9QU0xTMjAvZ2gtcGFnZXMvYXNzZXRzL2ZpZ3MvcGFydG5lcnMucG5nKXt3aWR0aD03NSV9CgotIE1lYW4gaXMgdmVyeSBzZW5zaXRpdmUgdG93YXJkcyBvdXRsaWVycyEKPC9wPgo8L2RldGFpbHM+CgotLS0KCiMjIyBHZW9tZXRyaWMgbWVhbgoKJCRcc3FydFtuXXtccHJvZFxsaW1pdHNfe2k9MX1ebiB4X2l9ID0gXGV4cFxsZWZ0XHtcZnJhY3sxfXtufSBcc3VtX3tpPTF9Xm4gXGxvZyh4X2kpXHJpZ2h0XH0kJAoKLSBHZW9tZXRyaWMgbWVhbiBpcyBjbG9zZXIgdG8gdGhlIG1lZGlhbiB0aGVuIHRoZSBtZWFuCgotIGxvZy10cmFuc2Zvcm1hdGlvbiByZW1vdmVzIHNrZXduZXNzCgotIE9mdGVuIGEgbW9yZSB1c2VmdWwgbWVhc3VyZSBmb3IgdGhlIGNlbnRyYWwgbG9jYXRpb24gdGhhbiBtZWRpYW46CgoxLiBVc2VzIGFsbCBvYnNlcnZhdGlvbnM6IGlzIG1vcmUgcHJlY2lzZQoyLiBJdCBpcyB0aGUgb3JkaW5hcnkgbWVhbiBvbiBsb2ctdHJhbnNmb3JtZWQgZGF0YSAkXHJpZ2h0YXJyb3ckIGNsYXNzaWNhbCBzdGF0aXN0aWNhbCBtZXRob2RzIGNhbiBiZSBkaXJlY3RseSBhcHBsaWVkLCBlLmcuIGh5cG90aGVzaXMgdGVzdHMgYW5kIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIChzZWUgY2hhcHRlciA1KQozLiBVc2VmdWwgZm9yIG1hbnkgYmlvbG9naWNhbCBjaGFyYWN0ZXJpc3RpY3MgZS5nLiBjb25jZW50cmF0aW9ucyB0aGF0IGNhbm5vdCBiZSBuZWdhdGl2ZS4KNC4gRGlmZmVyZW5jZXMgb24gYSBsb2cgc2NhbGUgaGF2ZSB0aGUgaW50ZXJwcmV0YXRpb24gb2YgYSBsb2cgZm9sZCBjaGFuZ2U6CgokJFxsb2cgKEIpIC0gXGxvZyhBKT0gXGxvZyhcZnJhY3tCfXtBfSk9XGxvZyhGQ19cdGV4dHtCIHZzIEF9KSQkCgpJbiBHZW5vbWljcyBvZnRlbiB0aGUgJGxvZ18yJCB0cmFuc2Zvcm1hdGlvbiBpcyB1c2VkLiBBIGRpZmZlcmVuY2Ugb2YgMSBjb3JyZXNwb25kcyB0byBhICRGQz0yJC4KCmBgYHtyfQpsb2dTdW1tYXJ5IDwtCiAgTkhBTkVTICU+JQogIGZpbHRlcihHZW5kZXIgPT0gImZlbWFsZSIpICU+JQogIHN1bW1hcml6ZShsb2dNZWFuID0gbWVhbihEaXJlY3RDaG9sICU+JSBsb2cyKCksIG5hLnJtID0gVFJVRSksIHNkID0gc2QoRGlyZWN0Q2hvbCAlPiUgbG9nMigpLCBuYS5ybSA9IFRSVUUpLCBtZWFuID0gbWVhbihEaXJlY3RDaG9sLCBuYS5ybSA9IFRSVUUpLCBtZWRpYW4gPSBtZWRpYW4oRGlyZWN0Q2hvbCwgbmEucm0gPSBUUlVFKSkgJT4lCiAgbXV0YXRlKGdlb01lYW4gPSAyXmxvZ01lYW4pCgpOSEFORVMgJT4lCiAgZmlsdGVyKEdlbmRlciA9PSAiZmVtYWxlIikgJT4lCiAgZ2dwbG90KGFlcyh4ID0gRGlyZWN0Q2hvbCAlPiUgbG9nMigpKSkgKwogIGdlb21faGlzdG9ncmFtKGFlcyh5ID0gLi5kZW5zaXR5Li4sIGZpbGwgPSAuLmNvdW50Li4pLCBiaW5zID0gMzApICsKICBnZW9tX2RlbnNpdHkoYWVzKHkgPSAuLmRlbnNpdHkuLikpICsKICBzdGF0X2Z1bmN0aW9uKGZ1biA9IGRub3JtLCBjb2xvciA9ICJyZWQiLCBhcmdzID0gbGlzdChtZWFuID0gbG9nU3VtbWFyeSRsb2dNZWFuLCBzZCA9IGxvZ1N1bW1hcnkkc2QpKQoKbG9nU3VtbWFyeQpgYGAKCi0gSW5kZWVkIHRoZSBtZWFuIGlzIHB1bGxlZCB0byBsYXJnZXIgdmFsdWVzIGJ5IHRoZSBza2V3ZWQgZGF0YS4KLSBUaGUgZ2VvbWV0cmljIG1lYW4gaXMgY2xvc2VyIHRvIHRoZSBtZWRpYW4uCi0gVGhlIGNob2xlc3Rlcm9sIGRhdGEgYXJlIG11Y2ggbW9yZSBzeW1tZXRyaWMgdXBvbiBsb2cgdHJhbnNmb3JtYXRpb24gYW5kIHRoZSBhcHByb3hpbWF0aW9uIGJ5IGEgTm9ybWFsIGRpc3RyaWJ1dGlvbiBpcyBnb29kLgoKCgojIyBEZXNjcmlwdGl2ZSBTdGF0aXN0aWNzIGZvciBWYXJpYWJpbGl0eQoKVGhlIHZhcmlhYmlsaXR5IGFyb3VuZCB0aGUgY2VudHJhbCB2YWx1ZSBpcyBjcnVjaWFsOgoKMS4gQmlvbG9naXN0cyBhcmUgb2Z0ZW4gaW50ZXJlc3RlZCBpbiBob3cgYW5pbWFscyBvciBwbGFudHMgYXJlIHNwcmVhZCBpbiB0aGUgc3R1ZHkgcmVnaW9uLgoyLiBDb21wYXJlIGdyb3VwczogdGhlIGdyb3VwIGVmZmVjdCBpcyBtb3JlIGNsZWFyIHdoZW4gdGhlIHJlc3BvbnNlIGhhcyBsZXNzIHZhcmlhYmlsaXR5LiBRdWFudGlmeWluZyB2YXJpYWJpbGl0eSBpcyBjcnVjaWFsIHRvIGRpc3Rpbmd1aXNoIGJldHdlZW4gc3lzdGVtYXRpYyBhbmQgcmFuZG9tIHBhdHRlcm5zLgoKLSBUaGUgcmVzcG9uc2UgdmFyaWVzIGJldHdlZW4gYW5kIHdpdGhpbiBpbmRpdmlkdWFscyBhbmQgaXMgdGhlIHJlYXNvbiB3aHkgd2UgbmVlZCBzdGF0aXN0aWNzLgoKLSBDcnVjaWFsIHRvIGRlc2NyaWJlIGJvdGggdGhlIGNlbnRyYWwgbG9jYXRpb24gYW5kIHRoZSB2YXJpYWJpbGl0eS4KLSBXaGljaCBwYXJ0IG9mIHRoZSB2YXJpYWJpbGl0eSBjYW4gd2UgZXhwbGFpbiAoZS5nLiB3aXRoIGNoYXJhY3RlcmlzdGljcyB0cmVhdG1lbnQsIGFnZSwgZXRjLCkgYW5kIHdoaWNoIHBhcnQgaXMgdW5leHBsYWluZWQ/CgojIyMgU2FtcGxlIHZhcmlhbmNlIGFuZCBzYW1wbGUgc3RhbmRhcmQgZGV2aWF0aW9uCgotIFNhbXBsZSB2YXJpYW5jZTogJCQKc19YXjI9IFxzdW1cbGltaXRzX3tpPTF9Xm4gXGZyYWN7KFgtXGJhciBYKV4yfXtuLTF9CiQkCi0gSW50ZXJwcmV0YXRpb24gaXMgb2Z0ZW4gZGlmZmljdWx0IGJlY2F1c2UgaXQgaXMgaW4gYW5vdGhlciB1bml0IHRoYW4gdGhlIG1lYXN1cmVtZW50cy4KCi0gKlN0YW5kYXJkIGRldmlhdGlvbio6ICQkCnNfWD0gXHNxcnR7c194XjJ9CiQkCgotIFZlcnkgdXNlZnVsIGZvciBOb3JtYWwgZGlzdHJpYnV0ZWQgb2JzZXJ2YXRpb25zOgoKICAgIC0gNjglIG9mIHRoZSBvYnNlcnZhdGlvbnMgZmFsbHMgaW4gdGhlIGludGVydmFsICAkXGJhcnt4fSAtIHNfeCQgZW4gJFxiYXJ7eH0gKyBzX3gkCiAgICAtIDk1JSBvZiB0aGUgb2JzZXJ2YXRpb25zIGZhbGxzIGluIHRoZSBpbnRlcnZhbCAkXGJhcnt4fSAtIDIgc194JCBlbiAkXGJhcnt4fSArIDIgc194JC4KCi0gVGhlc2UgaW50ZXJ2YWxzIGFyZSByZWZlcnJlZCB0byBhcyA2OCUgZW4gOTUlICpyZWZlcmVuY2UtaW50ZXJ2YWxzKi4KCi0gSWYgdGhlIGRhdGEgYXJlIG5vdCBOb3JtYWxseSBkaXN0cmlidXRlZCwgcmVmZXJlbmNlIGludGVydmFscyBhcmUgbm90IHZhbGlkLgoKLS0tCgojIyMgSW50ZXJxdWFydGlsZSByYW5nZQoKRm9yIHNrZXdlZCBkYXRhIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gaXMgbm90IHVzZWZ1bAoKLSBJdCBpcyB2ZXJ5IHNlbnNpdGl2ZSB0byBvdXRsaWVycwotICpJbnRlciBRdWFydGlsZSBSYW5nZSo6IERpc3RhbmNlIGJldHdlZW4gZmlyc3QgYW5kIHRoaXJkIHF1YXJ0aWxlCi0gV2lkdGggb2YgdGhlIGJveHBsb3QhCgpgYGB7cn0KTkhBTkVTICU+JQogIGZpbHRlcihHZW5kZXIgPT0gImZlbWFsZSIpICU+JQogIHN1bW1hcml6ZShJUVIgPSBJUVIoRGlyZWN0Q2hvbCwgbmEucm0gPSBUUlVFKSkKYGBgCgoKYGBge3J9Ck5IQU5FUyAlPiUKICBmaWx0ZXIoR2VuZGVyID09ICJmZW1hbGUiKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSAiIiwgeSA9IERpcmVjdENob2wpKSArCiAgZ2VvbV9ib3hwbG90KCkKYGBgCgotLS0KCiMgTm9ybWFsIGFwcHJveGltYXRpb24KCgotIEJpb2xvZ2ljYWwgYW5kIGNoZW1pY2FsIGRhdGEgYXJlIG9mdGVuIE5vcm1hbGx5IGRpc3RyaWJ1dGVkIHVwb24gdHJhbnNmb3JtYXRpb24uCgotIElmIHRoaXMgaXMgdGhlIGNhc2Ugd2UgY2FuIGdldCBhIGxvdCBvZiBpbnNpZ2h0IGluIHRoZSBkYXRhIHVzaW5nIGp1c3QgdHdvIGRlc2NyaXB0aXZlIHN0YXRpc3RpY3M6IG1lYW4gJFxtdSQgYW5kIHN0YW5kYXJkIGRldmlhdGlvbiAkXHNpZ21hJC4KCi0tLQoKIyMgRXZhbHVhdGlvbiB3aXRoIFFRLXBsb3RzCgpJZiB5b3VyIGFuYWx5c2lzIGJ1aWxkcyB1cG9uIHRoZSBhc3N1bXB0aW9uIHRoYXQgdGhlIGRhdGEgYXJlIE5vcm1hbGx5IGRpc3RyaWJ1dGVkLCBpdCBoYXMgdG8gYmUgdmVyaWZpZWQuCgpXZSB1c2UgKlFRLXBsb3RzKiBvciAqcXVhbnRpbGUtcXVhbnRpbGUgcGxvdHMqLgoKLSBPYnNlcnZlZCBxdWFudGlsZXMgZnJvbSB0aGUgb2JzZXJ2YXRpb25zIGluIHRoZSBzYW1wbGUgYXJlIHBsb3R0ZWQgYWdhaW5zdCBxdWFudGlsZXMgZnJvbSB0aGUgTm9ybWFsIGRpc3RyaWJ1dGlvbi4KCi0gSWYgdGhlIGRhdGEgYXJlIE5vcm1hbGx5IGRpc3RyaWJ1dGVkIGJvdGggcXVhbnRpbGVzIGhhdmUgdG8gYmUgaW4gbGluZS4KCi0gRG90cyBpbiB0aGUgcGxvdCBhcmUgZXhwZWN0ZWQgb24gYSBzdHJhaWdodCBsaW5lLgoKLSBTeXN0ZW1hdGljIGRldmlhdGlvbnMgb2YgdGhlIHN0cmFpZ2h0IGxpbmUgaW5kaWNhdGUgdGhhdCB0aGUgZGF0YSBhcmUgbm90IE5vcm1hbGx5IGRpc3RyaWJ1dGVkLgoKLSBOb3RlLCB0aGF0IHdlIHdpbGwgYWx3YXlzIG9ic2VydmUgc29tZSByYW5kb20gZGV2aWF0aW9ucyBmcm9tIHRoZSBzdHJhaWdodCBsaW5lIGluIHRoZSBwbG90IGJlY2F1c2Ugb2YgcmFuZG9tIGJpb2xvZ2ljYWwgdmFyaWFiaWxpdHksIHdoaWNoIGlzIG5vdCBpbmRpY2F0aXZlIGZvciBkZXZpYXRpb25zIGZyb20gTm9ybWFsaXR5LgoKLSBTbyBpdCBpcyBpbXBvcnRhbnQgdG8gdHJhaW4geW91cnNlbGYgdG8gbGVhcm4gdG8gZGlzdGluZ3Vpc2ggc3lzdGVtYXRpYyBmcm9tIHJhbmRvbSBkZXZpYXRpb25zLgoKLS0tCgojIyMgTm9ybWFsIGRhdGEKLSBXZSB3aWxsIGZpcnN0IHNpbXVsYXRlIGRhdGEgZnJvbSB0aGUgTm9ybWFsIGRpc3RyaWJ1dGlvbiB0byBzaG93IGhvdyB0aGUgcGxvdHMgbG9vayBsaWtlIGZvciBkYXRhIHRoYXQgaXMgbWVldGluZyB0aGUgYXNzdW1wdGlvbnMuCgotIFdlIHdpbGwgc2ltdWxhdGUgZGF0YSBmcm9tIDkgc2FtcGxlcyB3aXRoIGEgbWVhbiBvZiAxOCBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIDkuCgpgYGB7cn0KbiA8LSAyMAptdSA8LSAxOApzaWdtYSA8LSA5Cm5TYW1wIDwtIDkKCm5vcm1TaW0gPC0gbWF0cml4KHJub3JtKG4gKiBuU2FtcCwgbWVhbiA9IG11LCBzZCA9IHNpZ21hKSwgbnJvdyA9IG4pICU+JSBhcy5kYXRhLmZyYW1lKCkKCm5vcm1TaW0gJT4lCiAgZ2F0aGVyKHNhbXAsIGRhdGEpICU+JQogIGdncGxvdChhZXMoeCA9IGRhdGEpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiwgZmlsbCA9IC4uY291bnQuLiksIGJpbnMgPSAzMCkgKwogIGdlb21fZGVuc2l0eShhZXMoeSA9IC4uZGVuc2l0eS4uKSkgKwogIGZhY2V0X3dyYXAofnNhbXApCgpub3JtU2ltICU+JQogIGdhdGhlcihzYW1wLCBkYXRhKSAlPiUKICBnZ3Bsb3QoYWVzKHNhbXBsZSA9IGRhdGEpKSArCiAgZ2VvbV9xcSgpICsKICBnZW9tX3FxX2xpbmUoKSArCiAgZmFjZXRfd3JhcCh+c2FtcCkKYGBgCgpTbyBldmVuIGZvciBOb3JtYWwgZGF0YSB3ZSBvYnNlcnZlIHNvbWUgZGV2aWF0aW9ucyBkdWUgdG8gc2FtcGxpbmcgdmFyaWFiaWxpdHkhCgotLS0KCiMjIyBSZWFsIGRhdGEKCmBgYHtyfQpOSEFORVMgJT4lCiAgZmlsdGVyKEdlbmRlciA9PSAiZmVtYWxlIiAmICFpcy5uYShCTUkpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBCTUkpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiwgZmlsbCA9IC4uY291bnQuLikpICsKICB4bGFiKCJCTUkiKSArCiAgZ2d0aXRsZSgiQWxsIGZlbWFsZXMgaW4gc3R1ZHkiKSArCiAgZ2VvbV9kZW5zaXR5KGFlcyh5ID0gLi5kZW5zaXR5Li4pKQoKTkhBTkVTICU+JQogIGZpbHRlcihHZW5kZXIgPT0gImZlbWFsZSIgJiAhaXMubmEoQk1JKSkgJT4lCiAgZ2dwbG90KGFlcyhzYW1wbGUgPSBCTUkpKSArCiAgZ2VvbV9xcSgpICsKICBnZW9tX3FxX2xpbmUoKQpgYGAKClRoZSBRUS1wbG90IHNob3dzIHRoYXQgdGhlIHF1YW50aWxlcyBvZiB0aGUgZGF0YQoKLSBhcmUgbGFyZ2VyIChhYm92ZSB0aGUgbGluZSkgdGhhbiB0aGVzZSBmcm9tIHRoZSBOb3JtYWwgaW4gdGhlIGxlZnQgdGFpbDogY29tcHJlc3Npb24gb2YgdGhlIGxvd2VyIHRhaWwsCi0gYXJlIGxhcmdlciAoYWJvdmUgdGhlIGxpbmUpIHRoYW4gdGhlc2UgZnJvbSB0aGUgTm9ybWFsIGluIHRoZSByaWdodCB0YWlsOiBsb25nIHRhaWwgdG8gdGhlIHJpZ2h0LgoKV2UgY2FuIGNsZWFybHkgc2VlIHRoYXQgdGhlIGRhdGEgYXJlIHJpZ2h0LXNrZXdlZC4KCi0tLQoKIyBUd28gY29udGludW91cyB2YXJpYWJsZXM6IENvcnJlbGF0aW9uCgotIE5IQU5FUyBzdHVkeQotIEhlaWdodCBhbmQgV2VpZ2h0IEV4YW1wbGUgZm9yIGZlbWFsZXMKCmBgYHtyfQpOSEFORVMgJT4lCiAgZmlsdGVyKEFnZSA+IDI1ICYgR2VuZGVyID09ICJmZW1hbGUiKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBIZWlnaHQsIHkgPSBXZWlnaHQpKSArCiAgZ2VvbV9wb2ludCgpCmBgYAoKV2Ugb2JzZXJ2ZSBhbiBhc3NvY2lhdGlvbiBiZXR3ZWVuIFdlaWdodCBhbmQgSGVpZ2h0LCBidXQgd2UgYWxzbyBvYnNlcnZlIHRoYXQgdGhlIFdlaWdodHMgdGVuZCB0byBiZSByaWdodC1za2V3ZWQuCgpMZXRzIGxvb2sgYXQgdGhlIHVuaXZhcmlhdGUgZGlzdHJpYnV0aW9ucyBmaXJzdC4KCmBgYHtyfQpOSEFORVMgJT4lCiAgZmlsdGVyKEFnZSA+IDI1ICYgR2VuZGVyID09ICJmZW1hbGUiKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBIZWlnaHQpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiwgZmlsbCA9IC4uY291bnQuLikpICsKICB4bGFiKCJIZWlnaHQiKSArCiAgZ2d0aXRsZSgiQWxsIGZlbWFsZXMgaW4gc3R1ZHkiKSArCiAgZ2VvbV9kZW5zaXR5KGFlcyh5ID0gLi5kZW5zaXR5Li4pKQoKTkhBTkVTICU+JQogIGZpbHRlcihBZ2UgPiAyNSAmIEdlbmRlciA9PSAiZmVtYWxlIikgJT4lCiAgZ2dwbG90KGFlcyhzYW1wbGUgPSBIZWlnaHQpKSArCiAgZ2VvbV9xcSgpICsKICBnZW9tX3FxX2xpbmUoKQpgYGAKCgpgYGB7cn0KTkhBTkVTICU+JQogIGZpbHRlcihBZ2UgPiAyNSAmIEdlbmRlciA9PSAiZmVtYWxlIikgJT4lCiAgZ2dwbG90KGFlcyh4ID0gV2VpZ2h0KSkgKwogIGdlb21faGlzdG9ncmFtKGFlcyh5ID0gLi5kZW5zaXR5Li4sIGZpbGwgPSAuLmNvdW50Li4pKSArCiAgeGxhYigiV2VpZ2h0IikgKwogIGdndGl0bGUoIkFsbCBmZW1hbGVzIGluIHN0dWR5IikgKwogIGdlb21fZGVuc2l0eShhZXMoeSA9IC4uZGVuc2l0eS4uKSkKCk5IQU5FUyAlPiUKICBmaWx0ZXIoQWdlID4gMjUgJiBHZW5kZXIgPT0gImZlbWFsZSIpICU+JQogIGdncGxvdChhZXMoc2FtcGxlID0gV2VpZ2h0KSkgKwogIGdlb21fcXEoKSArCiAgZ2VvbV9xcV9saW5lKCkKYGBgCgpUaGUgd2VpZ2h0cyBhcmUgaW5kZWVkIHNrZXdlZCEKClVwb24gbG9nIHRyYW5zZm9ybWF0b24gdGhlIFdlaWdodHMgYXJlIGxlc3Mgc2tld2VkLCBidXQgc3RpbGwgbm90IE5vcm1hbGx5IGRpc3RyaWJ1dGVkLgoKYGBge3J9Ck5IQU5FUyAlPiUKICBmaWx0ZXIoQWdlID4gMjUgJiBHZW5kZXIgPT0gImZlbWFsZSIpICU+JQogIGdncGxvdChhZXMoeCA9IFdlaWdodCAlPiUgbG9nMigpKSkgKwogIGdlb21faGlzdG9ncmFtKGFlcyh5ID0gLi5kZW5zaXR5Li4sIGZpbGwgPSAuLmNvdW50Li4pKSArCiAgeGxhYigiV2VpZ2h0IChsb2cyKSIpICsKICBnZ3RpdGxlKCJBbGwgZmVtYWxlcyBpbiBzdHVkeSIpICsKICBnZW9tX2RlbnNpdHkoYWVzKHkgPSAuLmRlbnNpdHkuLikpCgpOSEFORVMgJT4lCiAgZmlsdGVyKEFnZSA+IDI1ICYgR2VuZGVyID09ICJmZW1hbGUiKSAlPiUKICBnZ3Bsb3QoYWVzKHNhbXBsZSA9IFdlaWdodCAlPiUgbG9nMigpKSkgKwogIGdlb21fcXEoKSArCiAgZ2VvbV9xcV9saW5lKCkKYGBgCgpTa2V3bmVzcyBpcyBzdGlsbCB0aGVyZSBidXQgaXMgcmVkdWNlZCBhbHJlYWR5LgoKYGBge3J9Ck5IQU5FUyAlPiUKICBmaWx0ZXIoQWdlID4gMjUgJiBHZW5kZXIgPT0gImZlbWFsZSIpICU+JQogIGdncGxvdChhZXMoeCA9IEhlaWdodCwgeSA9IFdlaWdodCAlPiUgbG9nMigpKSkgKwogIHlsYWIoIldlaWdodCAobG9nMikiKSArCiAgZ2VvbV9wb2ludCgpCmBgYAoKLS0tCgojIyBDb3ZhcmlhbmNlIGFuZCBDb3JyZWxhdGlvbgoKLSBMZXQgWCBhbmQgWSBiZSB0byBjb250aW51b3VzIHJhbmRvbSB2YXJpYWJsZXMsIGFuZCBmb3IgZWFjaCBzdWJqZWN0IGkgd2Ugb2JzZXJ2ZSAkKFhfaSxZX2kpJC4KLSBDb3ZhcmlhbmNlOiBob3cgZGV2aWF0ZSBYX2kgYW5kIFlfaSBhcm91bmQgdGhlaXIgbWVhbnM/CgokJFxtYm94e0NvdmFyfShYLFkpPUVbKFgtRVtYXSkoWS1FW1ldKV0kJAoKLSBDb3JyZWxhdGlvbjogc3RhbmRhcmRpc2UgdGhlIGNvdmFyaWFuY2UgYWNjb3JkaW5nIHRvIHRoZSB2YXJpYWJpbGl0eSBpbiBlYWNoIHZhcmlhYmxlOgoKJCRcbWJveHtDb3J9KFgsWSk9XGZyYWN7RVsoWC1FW1hdKShZLUVbWV0pXX17XHNxcnR7RVsoWC1FW1hdKV4yfVxzcXJ0e0VbKFktRVtZXSleMn19JCQKCgojIyBQZWFyc29uIENvcnJlbGF0aW9uCgotIEFzc29jaWF0aW9uIGJldHdlZW4gdHdvIGNvbnRpbnVvdXMgY292YXJpYXRlOgoKXFtcbWJveHtDb3J9KFgsWSk9XGZyYWN7XHN1bV97aT0xfV57bn0oWF97aX0tXGJhcntYfSkoWV97aX0tXGJhcntZfSl9eyhuLTEpc197WH1zX3tZfX0KXF0KCi0gUG9zaXRpdmUgY29ycmVsYXRpb246ICR4IFwgXG5lYXJyb3cgXCBcUmlnaHRhcnJvdyBcIHkgXCBcbmVhcnJvdyQKCi0gTmVnYXRpdmUgY29ycmVsYXRpb246ICR4IFwgXG5lYXJyb3cgXCBcUmlnaHRhcnJvdyBcIHkgXCBcc2VhcnJvdyQKCi0gQ29ycmVsYXRpb24gYWx3YXlzIGJldHdlZW4gLTEgZW4gMQoKYGBge3J9Cm1lYW5zIDwtIE5IQU5FUyAlPiUKICBmaWx0ZXIoQWdlID4gMjUgJiBHZW5kZXIgPT0gImZlbWFsZSIpICU+JQogIHNlbGVjdChXZWlnaHQsIEhlaWdodCkgJT4lCiAgbXV0YXRlKGxvZzJXZWlnaHQgPSBXZWlnaHQgJT4lIGxvZzIoKSkgJT4lCiAgYXBwbHkoLiwgMiwgbWVhbiwgbmEucm0gPSBUUlVFKQpyYW5nZXMgPC0gTkhBTkVTICU+JQogIGZpbHRlcihBZ2UgPiAyNSAmIEdlbmRlciA9PSAiZmVtYWxlIikgJT4lCiAgc2VsZWN0KFdlaWdodCwgSGVpZ2h0KSAlPiUKICBtdXRhdGUobG9nMldlaWdodCA9IFdlaWdodCAlPiUgbG9nMigpKSAlPiUKICBhcHBseSguLCAyLCByYW5nZSwgbmEucm0gPSBUUlVFKQoKTkhBTkVTICU+JQogIGZpbHRlcihBZ2UgPiAyNSAmIEdlbmRlciA9PSAiZmVtYWxlIikgJT4lCiAgZ2dwbG90KGFlcyh4ID0gSGVpZ2h0LCB5ID0gV2VpZ2h0ICU+JSBsb2cyKCkpKSArCiAgeWxhYigiV2VpZ2h0IChsb2cyKSIpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IG1lYW5zWyJsb2cyV2VpZ2h0Il0sIGNvbG9yID0gInJlZCIpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBtZWFuc1siSGVpZ2h0Il0sIGNvbG9yID0gInJlZCIpICsKICBhbm5vdGF0ZSgKICAgIGdlb20gPSAidGV4dCIsCiAgICB4ID0gYyhyYW5nZXNbMSwgIkhlaWdodCJdLCByYW5nZXNbMSwgIkhlaWdodCJdLCByYW5nZXNbMiwgIkhlaWdodCJdLCByYW5nZXNbMiwgIkhlaWdodCJdKSwKICAgIHkgPSBjKHJhbmdlc1sxLCAibG9nMldlaWdodCJdLCByYW5nZXNbMiwgImxvZzJXZWlnaHQiXSwgcmFuZ2VzWzEsICJsb2cyV2VpZ2h0Il0sIHJhbmdlc1syLCAibG9nMldlaWdodCJdKSwKICAgIGxhYmVsID0gYygiKyIsICItIiwgIi0iLCAiKyIpLCBjb2xvciA9ICJyZWQiLCBzaXplID0gMTAKICApCmBgYAoKYGBge3J9Ck5IQU5FUyAlPiUKICBmaWx0ZXIoQWdlID4gMjUgJiBHZW5kZXIgPT0gImZlbWFsZSIpICU+JQogIGdncGxvdChhZXMoeCA9IEhlaWdodCwgeSA9IFdlaWdodCkpICsKICB5bGFiKCJXZWlnaHQgKGxvZzIpIikgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gbWVhbnNbIldlaWdodCJdLCBjb2xvciA9ICJyZWQiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gbWVhbnNbIkhlaWdodCJdLCBjb2xvciA9ICJyZWQiKSArCiAgYW5ub3RhdGUoCiAgICBnZW9tID0gInRleHQiLAogICAgeCA9IGMocmFuZ2VzWzEsICJIZWlnaHQiXSwgcmFuZ2VzWzEsICJIZWlnaHQiXSwgcmFuZ2VzWzIsICJIZWlnaHQiXSwgcmFuZ2VzWzIsICJIZWlnaHQiXSksCiAgICB5ID0gYyhyYW5nZXNbMSwgIldlaWdodCJdLCByYW5nZXNbMiwgIldlaWdodCJdLCByYW5nZXNbMSwgIldlaWdodCJdLCByYW5nZXNbMiwgIldlaWdodCJdKSwKICAgIGxhYmVsID0gYygiKyIsICItIiwgIi0iLCAiKyIpLCBjb2xvciA9ICJyZWQiLCBzaXplID0gMTAKICApCmBgYAoKCgpgYGB7cn0KTkhBTkVTICU+JQogIGZpbHRlcihBZ2UgPiAyNSAmIEdlbmRlciA9PSAiZmVtYWxlIikgJT4lCiAgc2VsZWN0KFdlaWdodCwgSGVpZ2h0KSAlPiUKICBtdXRhdGUobG9nMldlaWdodCA9IFdlaWdodCAlPiUgbG9nMigpKSAlPiUKICBuYS5leGNsdWRlKCkgJT4lCiAgY29yKCkKYGBgCgotIE5vdGUsIHRoYXQgdGhlIGNvcnJlbGF0aW9uIGlzIGxvd2VyIHdoZW4gdGhlIGRhdGEgYXJlIG5vdCB0cmFuc2Zvcm1lZC4KLSBUaGUgUGVhcnNvbiBjb3JyZWxhdGlvbiBpcyBzZW5zaXRpdmUgdG8gb3V0bGllcnMhCi0gRG8gbm90IHVzZSBQZWFyc29uIGNvcnJlbGF0aW9uIGZvciBza2V3ZWQgZGlzdHJpYnV0aW9ucyBvciBmb3IgZGF0YSB3aXRoIG91dGxpZXJzLgoKIyMjIEltcGFjdCBvZiBvdXRsaWVycwoKSWxsdXN0cmF0aW9uIHdpdGggc2ltdWxhdGVkIGRhdGEgdGhhdCBoYXMgb25lIG91dGxpZXIuCgpgYGB7cn0Kc2V0LnNlZWQoMTAwKQp4IDwtIHJub3JtKDIwKQpzaW1EYXRhIDwtIGRhdGEuZnJhbWUoeCA9IHgsIHkgPSB4ICogMiArIHJub3JtKGxlbmd0aCh4KSkpCnNpbURhdGEgJT4lIGdncGxvdChhZXMoeCA9IHgsIHkgPSB5KSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2d0aXRsZShwYXN0ZSgiY29yID0iLCBjb3Ioc2ltRGF0YVssIDFdLCBzaW1EYXRhWywgMl0pICU+JSByb3VuZCguLCAyKSkpCgpvdXRsaWVyIDwtIHJiaW5kKHNpbURhdGEsIGMoMiwgLTQpKQpvdXRsaWVyICU+JSBnZ3Bsb3QoYWVzKHggPSB4LCB5ID0geSkpICsKICBnZW9tX3BvaW50KCkgKwogIGdndGl0bGUocGFzdGUoImNvciA9IiwgY29yKG91dGxpZXJbLCAxXSwgb3V0bGllclssIDJdKSAlPiUgcm91bmQoLiwgMikpKQpgYGAKCiMjIyBPbmx5IGxpbmVhciBhc3NvY2lhdGlvbgoKTm90ZSwgdGhhdCB0aGUgUGVhcnNvbiBjb3JyZWxhdGlvbiBvbmx5IGNhcHR1cmVzIGxpbmVhciBhc3NvY2lhdGlvbiEKCmBgYHtyfQp4IDwtIHJub3JtKDEwMCkKcXVhZHJhdGljIDwtIGRhdGEuZnJhbWUoeCA9IHgsIHkgPSB4XjIgKyBybm9ybShsZW5ndGgoeCkpKQpxdWFkcmF0aWMgJT4lIGdncGxvdChhZXMoeCA9IHgsIHkgPSB5KSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2d0aXRsZShwYXN0ZSgiY29yID0iLCBjb3IocXVhZHJhdGljWywgMV0sIHF1YWRyYXRpY1ssIDJdKSAlPiUgcm91bmQoLiwgMikpKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gbWVhbihxdWFkcmF0aWNbLCAyXSksIGNvbCA9ICJyZWQiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gbWVhbihxdWFkcmF0aWNbLCAxXSksIGNvbCA9ICJyZWQiKQpgYGAKCgojIyBEaWZmZXJlbnQgbWFnbml0dWRlcyBvZiBjb3JyZWxhdGlvbgoKYGBge3J9CnNldC5zZWVkKDEwMCkKeCA8LSBybm9ybSgxMDApCnNpbURhdGEyIDwtIGNiaW5kKHgsIDEuNSAqIHgsIHNhcHBseSgxOjcsIGZ1bmN0aW9uKHNkLCB4KSAxLjUgKiB4ICsgcm5vcm0obGVuZ3RoKHgpLCBzZCA9IHNkKSwgeCA9IHgpLCBybm9ybShsZW5ndGgoeCksIHNkID0gNykpCgpjb2xuYW1lcyhzaW1EYXRhMilbLTFdIDwtIHBhc3RlKCJjb3IiLCByb3VuZChjb3Ioc2ltRGF0YTIpWzEsIC0xXSwgMiksIHNlcCA9ICI9IikKCnNpbURhdGEyICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBnYXRoZXIoY29yLCB5LCAteCkgJT4lCiAgZ2dwbG90KGFlcyh4ID0geCwgeSA9IHkpKSArCiAgZ2VvbV9wb2ludCgpICsKICBmYWNldF93cmFwKH5jb3IpCgpzaW1EYXRhMyA8LSBzaW1EYXRhMgpzaW1EYXRhM1ssIC0xXSA8LSAtc2ltRGF0YTJbLCAtMV0KY29sbmFtZXMoc2ltRGF0YTMpWy0xXSA8LSBwYXN0ZSgiY29yIiwgcm91bmQoY29yKHNpbURhdGEzKVsxLCAtMV0sIDIpLCBzZXAgPSAiPSIpCgpzaW1EYXRhMyAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgZ2F0aGVyKGNvciwgeSwgLXgpICU+JQogIGdncGxvdChhZXMoeCA9IHgsIHkgPSB5KSkgKwogIGdlb21fcG9pbnQoKSArCiAgZmFjZXRfd3JhcCh+Y29yKQpgYGAKCi0tLQoKIyMgU3BlYXJtYW4gY29ycmVsYXRpb24KClRoZSBTcGVhcm1hbiBjb3JyZWxhdGlvbiBpcyB0aGUgUGVhcnNvbiBjb3JyZWxhdGlvbiBhZnRlciB0cmFuc2Zvcm1pbmcgdGhlIGRhdGEgdG8gcmFua3MuCgotIFBlYXJzb24gY29ycmVsYXRpb24KYGBge3J9CmNvcihvdXRsaWVyKQpgYGAKCi0gU3BlYXJtYW4gY29ycmVsYXRpb24KYGBge3J9CmNvcihvdXRsaWVyLCBtZXRob2QgPSAic3BlYXJtYW4iKQpgYGAKCi0gU3BlYXJtYW4gY29ycmVsYXRpb24gaXMgbGVzcyBzZW5zaXRpdmUgdG8gb3V0bGllcnMuCgotIFBlYXJzb24gY29ycmVsYXRpb24gb24gcmFua3MKYGBge3J9CnJhbmtEYXRhIDwtIGFwcGx5KG91dGxpZXIsIDIsIHJhbmspCmNvcihyYW5rRGF0YSkKYGBgCgotIE5IQU5FUyBleGFtcGxlCgpgYGB7cn0KTkhBTkVTICU+JQogIGZpbHRlcihBZ2UgPiAyNSAmIEdlbmRlciA9PSAiZmVtYWxlIikgJT4lCiAgc2VsZWN0KFdlaWdodCwgSGVpZ2h0KSAlPiUKICBtdXRhdGUobG9nMldlaWdodCA9IFdlaWdodCAlPiUgbG9nMigpKSAlPiUKICBuYS5leGNsdWRlKCkgJT4lCiAgY29yKG1ldGhvZCA9ICJzcGVhcm1hbiIpCmBgYAo=