# Install and load all necessary packages
list.of.packages <- c("tidyverse", "broom", "mosaic", "ggExtra")
new.packages <- list.of.packages[!(list.of.packages %in% installed.packages()[,"Package"])]
if(length(new.packages)) install.packages(new.packages)
library(tidyverse)
library(mosaic)
library(broom)
library(modelr)
library(ggExtra)

Contest

The class will choose the criteria we use to declare the winner.


Celebrity Guess Actual ???
1. Oprah
2. Tom Hanks
3. Betty White
4. Pelé
5. Michael Jordan
6. Taylor Swift
7. Will Smith
8. George Washington
9. (students’ choice)
10. (students’ choice)
score = __________
  1. What was the point of that?

Scenario: College admissions

Suppose you work in the admissions office and are tasked with admitting students who are predicted to be successful at St. Ambrose.


  1. How do you define success at St. Ambrose? What information do you think you would need to predict a student’s success? Which factor do you think best predicts success?
  1. Click the tabs to see plots of data from first-year students at St. Ambrose from 2011-2015. On each plot, I’ve sketched the line that best fits the data. Interpret the slope and y-intercept of the line in the first plot. Then, use that plot to predict the first-semester GPA for a student with a high school GPA of 3.0.

High school GPAs

# Read data
sau <- read.csv("http://www.bradthiessen.com/html5/data/sau.csv")
# Convert female and minority variables to factors
sau <- sau %>%
  mutate(female = factor(female, labels = c("male", "female")),
         minority = factor(minority, labels = c("white", "minority")))
  
p <- ggplot(sau, aes(x = hsgpa, y=fallgpa)) +
  geom_point(alpha=.3, color="steelblue") +
  geom_smooth(method = "lm", se=FALSE, color="black") + 
  scale_y_continuous(limits = c(0.5,4),breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  scale_x_continuous(limits = c(0.5,4), breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  theme(legend.position="none") +
  labs(
    x = "High School GPA",
    y = "Fall semester GPA at St. Ambrose"
  ) +
  annotate("text", x=1.1, y=1.5, label="y = 0.058 + 0.826x")
# Use library(ggExtra) to plot marginal histograms
ggMarginal(p, type="histogram", fill="steelblue", color="white")

ACT Composite

p <- ggplot(sau, aes(x = jitter(ACTtotal), y=fallgpa)) +
  geom_point(alpha=.3, color="steelblue") +
  geom_smooth(method = "lm", se=FALSE, color="black") + 
  scale_y_continuous(limits = c(0.5,4),breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  scale_x_continuous(limits = c(15,36), breaks=seq(16, 36, 2), minor_breaks=seq(15, 35, 2)) +
  theme(legend.position="none") +
  labs(
    x = "ACT Composite",
    y = "Fall semester GPA at St. Ambrose"
  )

# Use library(ggExtra) to plot marginal histograms
ggMarginal(p, type="histogram", fill="steelblue", color="white")

ACT Math

p <- ggplot(sau, aes(x = jitter(ACTmath), y=fallgpa)) +
  geom_point(alpha=.3, color="steelblue") +
  geom_smooth(method = "lm", se=FALSE, color="black") + 
  scale_y_continuous(limits = c(0.5,4),breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  scale_x_continuous(limits = c(14,36), breaks=seq(14, 36, 2), minor_breaks=seq(15, 35, 2)) +
  theme(legend.position="none") +
  labs(
    x = "ACT Math",
    y = "Fall semester GPA at St. Ambrose"
  )
ggMarginal(p, type="histogram", fill="steelblue", color="white")

HSGPA and Retention

ggplot(sau, aes(x = hsgpa, y=jitter(retention, .1))) +
  geom_point(alpha=.3, color="steelblue") +
  geom_smooth(method = "lm", se=FALSE, color="black") + 
  scale_y_continuous(limits = c(0,1), breaks=seq(0, 1, 1), minor_breaks=NULL) +
  theme(legend.position="none") +
  labs(
    x = "high school GPA",
    y = "1 = student returned for sophomore year"
  )

Simple linear model

Here’s some simulated data showing the relationship between ACT scores and 1st-semester college GPAs.

# Simulate ACT and first-semester GPA data
set.seed(123)
firstyear <- tibble(
  act = c(rep(c(14, 16, 18, 20, 22, 24, 26, 28, 30, 32),3)),
  gpa = round((act/9) + rnorm(30, 0, .3),2),
  act2 = act + rnorm(30,0,.1)
)

# Plot the data
ggplot(data = firstyear, aes(x = act, y = gpa)) +
  geom_point() +
  scale_y_continuous(breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  scale_x_continuous(breaks=seq(12, 36, 2), minor_breaks=NULL)


  1. Describe and parameterize a model of GPAs as a function of ACT scores.

Let’s randomly generate 300 lines of the form \(y = b_0+b_1x\) to display on our data:


# Looks linear.  Randomly choose 250 slopes and y-intercepts. 
set.seed(123)
models <- tibble(
  b0 = runif(300, -2, 3.5),
  b1 = runif(300, -.2, .2)
)

# Plot all 300 models on top of the data
ggplot(data = firstyear, aes(x = act, y = gpa)) +
  geom_abline(aes(intercept = b0, slope = b1), data = models, alpha = 0.25) +
  geom_point() +
  geom_abline(aes(intercept = 0.0638, slope = .1077), data = models, alpha = 0.25) +
  scale_y_continuous(breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  scale_x_continuous(breaks=seq(12, 36, 2), minor_breaks=NULL)


Choosing the best line

  1. How can we determine which line best fits our data? Interpret total (absolute) distance and root mean square deviation on the plots displayed below.
Model #1
# Copy our data to new data frame
withpredictions <- firstyear

# Choose a slope and y-intercept
b00 <- 42/24
b10 <- 1/24
# Predict GPAs with this slope and y-intercept
withpredictions$pred2 <- b00 + (b10 * withpredictions$act)
# Add errors (vertical distances)
withpredictions$resid2 <- withpredictions$gpa - ( b00 + (b10 * withpredictions$act) )


ggplot(data = withpredictions, aes(x = act2, y = gpa)) +
  geom_point() +
  geom_abline(aes(intercept = b00, slope = b10), color="blue", size=1) + 
  annotate("segment", x=withpredictions$act2, xend=withpredictions$act2, 
           y=withpredictions$gpa, yend=withpredictions$pred2, color="red") +
  annotate("text", x = 27, y = 1.6, label=paste("root mean square deviation =", 
                                                round( sqrt(mean(withpredictions$resid2^2)), 3))) +
  annotate("text", x = 27, y = 1.8, label=paste("total (absolute) distance =", 
                                                round( sum(abs(withpredictions$resid2)), 3))) +
  scale_y_continuous(breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  scale_x_continuous(breaks=seq(12, 36, 2), minor_breaks=NULL)
Model #2
# Choose a slope and y-intercept
b01 <- 3/50
b11 <- 1/9
# Predict GPAs with this slope and y-intercept
withpredictions$pred1 <- b01 + (b11 * withpredictions$act)
# Add errors (vertical distances)
withpredictions$resid1 <- withpredictions$gpa - ( b01 + (b11 * withpredictions$act) )


ggplot(data = withpredictions, aes(x = act2, y = gpa)) +
  geom_point() +
  geom_abline(aes(intercept = b01, slope = b11), color="blue", size=1) + 
  annotate("segment", x=withpredictions$act2, xend=withpredictions$act2, 
           y=withpredictions$gpa, yend=withpredictions$pred1, color="red") +
  annotate("text", x = 27, y = 1.6, label=paste("root mean square deviation =", 
                                                round( sqrt(mean(withpredictions$resid1^2)), 3))) +
  annotate("text", x = 27, y = 1.8, label=paste("total (absolute) distance =", 
                                                round( sum(abs(withpredictions$resid1)), 3))) +
  scale_y_continuous(breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  scale_x_continuous(breaks=seq(12, 36, 2), minor_breaks=NULL)
Model #3
# Choose a slope and y-intercept
b02 <- 3.5
b12 <- -1/42
# Predict GPAs with this slope and y-intercept
withpredictions$pred3 <- b02 + (b12 * withpredictions$act)
# Add errors (vertical distances)
withpredictions$resid3 <- withpredictions$gpa - ( b02 + (b12 * withpredictions$act) )


ggplot(data = withpredictions, aes(x = act2, y = gpa)) +
  geom_point() +
  geom_abline(aes(intercept = b02, slope = b12), color="blue", size=1) + 
  annotate("segment", x=withpredictions$act2, xend=withpredictions$act2, 
           y=withpredictions$gpa, yend=withpredictions$pred3, color="red") +
  annotate("text", x = 27, y = 1.6, label=paste("root mean square deviation =", 
                                                round( sqrt(mean(withpredictions$resid3^2)), 3))) +
  annotate("text", x = 27, y = 1.8, label=paste("total (absolute) distance =", 
                                                round( sum(abs(withpredictions$resid3)), 3))) +
  scale_y_continuous(breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  scale_x_continuous(breaks=seq(12, 36, 2), minor_breaks=NULL)
Finding smallest RMSE through iteration

We can calculate this root mean square deviation for all 300 lines we plotted earlier. Let’s display the ten best models (with the smallest RMSE).

# Don't worry about this code.
# Function to calculate predictions based on slope and y-intercept
model1 <- function(a, data) {
  a[1] + data$act * a[2]
}

# Function to calculate vertical error distances
measure_distance <- function(mod, data) {
  diff <- data$gpa - model1(mod, data)
  sqrt(mean(diff ^ 2))
}

# Compute distance for all 300 models
sim1_dist <- function(b0, b1) {
  measure_distance(c(b0, b1), firstyear)
}

# Add distances to the models data frame
models <- models %>% 
  mutate(dist = purrr::map2_dbl(b0, b1, sim1_dist))

# Find 10 best models (with smallest rmse) and plot on data
ggplot(firstyear, aes(act, gpa)) + 
  geom_point(size = 2, colour = "black") + 
  geom_abline(
    aes(intercept = b0, slope = b1, colour = -dist), 
    data = filter(models, rank(dist) <= 10)
  ) +
  labs(title = "10 best models (with smallest RMSE)")


Each of the ten lines displayed above has a y-intercept and a slope, so we can think of each model as a point: \((b_0, \ b_1)\). Let’s plot these “points” for all 300 linear models:

# Plot the slopes and y-intercepts of all 300 models
# Highlight best combinations of slope and intercept with red circles
ggplot(models, aes(b0, b1)) +
  geom_point(data = filter(models, rank(dist) <= 10), size = 4, colour = "red") +
  geom_point(aes(colour = -dist))


The points with lighter shades of blue represent models with smaller amounts of error. The red circles highlight the 10 best models that were displayed earlier.


  1. How could you use this plot to identify the single best model (which might not be any of the 300 points appearing on the plot)?

Let’s try a grid search to find the best combination of slope and y-intercept.

# Create the grid by selecting ranges for b0 and b1
grid <- expand.grid(
  b0 = seq(-1, 1, length = 25),
  b1 = seq(0.05, .15, length = 25)
  ) %>% 
  mutate(dist = purrr::map2_dbl(b0, b1, sim1_dist))

# Zoom and enhance
grid %>% 
  ggplot(aes(b0, b1)) +
  geom_point(data = filter(grid, rank(dist) <= 10), size = 4, colour = "red") +
  geom_point(aes(colour = -dist)) 

Let’s take these 10 best models and display them back on the data.

ggplot(firstyear, aes(act, gpa)) + 
  geom_point(size = 2, colour = "grey30") + 
  geom_abline(
    aes(intercept = b0, slope = b1, colour = -dist), 
    data = filter(grid, rank(dist) <= 10)
  )


We could continue this zoom-and-enhance method until we really narrowed down our best y-intercept and slope. Thankfully, there’s an easier way.


  1. We want to find the model that minimizes the distances from each point to the line. Think back to your previous math courses. What methods have you used to find minimums (or to minimize functions)?
  1. Why might we be more interested in vertical errors than horizontal or perpendicular errors?
Vertical errors
# Choose a slope and y-intercept
b0 <- 0.06384
b1 <- 0.10772
# Predict GPAs with this slope and y-intercept
withpredictions$pred <- b0 + (b1 * withpredictions$act)
# Add errors (vertical distances)
withpredictions$resid <- withpredictions$gpa - ( b0 + (b1 * withpredictions$act) )

# Plot vertical errors
ggplot(data = withpredictions, aes(x = act2, y = gpa)) +
  geom_point() +
  geom_abline(aes(intercept = b0, slope = b1), color="blue", size=1) + 
  annotate("segment", x=withpredictions$act2, xend=withpredictions$act2, 
           y=withpredictions$gpa, yend=withpredictions$pred, color="red") +
  scale_y_continuous(breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  scale_x_continuous(breaks=seq(12, 36, 2), minor_breaks=NULL) +
  labs(x = "ACT", y = "GPA")
Horizontal
# Plot horizontal errors
ggplot(data = withpredictions, aes(x = act2, y = gpa)) +
  geom_point() +
  geom_abline(aes(intercept = b0, slope = b1), color="blue", size=1) + 
  annotate("segment", x=withpredictions$act2, xend= ( (withpredictions$gpa - b0) / b1), 
           y=withpredictions$gpa, yend=withpredictions$gpa, color="red") +
  scale_y_continuous(breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  scale_x_continuous(breaks=seq(12, 36, 2), minor_breaks=NULL) +
  labs(x = "ACT", y = "GPA")
Perpendicular
# Plot horizontal errors
ggplot(data = withpredictions, aes(x = act2, y = gpa)) +
  geom_point() +
  geom_abline(aes(intercept = b0, slope = b1), color="blue", size=1) + 
  annotate("segment", x=withpredictions$act2, xend= ((withpredictions$act2 + b1 * withpredictions$gpa - b0 * b1) / (1 + b1^2)), 
           y=withpredictions$gpa, yend = (b0 + b1 * withpredictions$act2), color="red") +
  scale_y_continuous(breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  scale_x_continuous(breaks=seq(12, 36, 2), minor_breaks=NULL) + 
  geom_abline(aes(intercept = 200, slope=-3)) + coord_fixed(ratio = 1, xlim=c(18,26)) +
    labs(x = "ACT", y=NULL)
Squared (vertical)
# Plot squared errors
ggplot(data = withpredictions, aes(x = act2, y = gpa)) +
  geom_point() +
  geom_abline(aes(intercept = b0, slope = b1), color="blue", size=1) + 
  annotate("rect", xmin=withpredictions$act2, xmax=withpredictions$act2 + (withpredictions$gpa - withpredictions$pred),
           ymin=withpredictions$gpa, ymax=withpredictions$pred, alpha=.3, fill="red") +
  scale_y_continuous(breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  scale_x_continuous(breaks=seq(12, 36, 2), minor_breaks=NULL) + coord_fixed(ratio = 1, xlim=c(18,26)) +
    labs(x = "ACT", y=NULL)


  1. Before we derive the formula for the line of best fit, why should we be willing to accept any amount of error? Why use a line when we can connect-the-dots or use a curve?
Connect-the-dots
ggplot(data = withpredictions, aes(x = act, y = gpa)) +
  geom_point() +
  geom_line(color="blue", size=1) +
  scale_y_continuous(breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  scale_x_continuous(breaks=seq(12, 36, 2), minor_breaks=NULL) +
  labs(x = "ACT", y = "GPA")
Curve
ggplot(data = withpredictions, aes(x = act, y = gpa)) +
  geom_point() +
  geom_smooth(method="loess", se=FALSE, span=.4) +
  scale_y_continuous(breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  scale_x_continuous(breaks=seq(12, 36, 2), minor_breaks=NULL) +
  labs(x = "ACT", y = "GPA")
Line
ggplot(data = withpredictions, aes(x = act, y = gpa)) +
  geom_point() +
  geom_smooth(method="lm", se=FALSE) +
  scale_y_continuous(breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  scale_x_continuous(breaks=seq(12, 36, 2), minor_breaks=NULL) +
  labs(x = "ACT", y = "GPA")

Deriving the least squares regression line

We want to minimize the squared vertical distances from predictions made by our model, \(\hat{y}_i=b_0+b_1x_i\) to each observed data point, \((x_i, y_i)\).

Each of these vertical distances can be written as: \(e_i = y_i-(b_0+b_1x_i)=y_i-b_0-b_1x_i\)



Our goal is to find values for the y-intercept \((b_0)\) and slope \((b_1)\) that minimize: \(Q =\sum_{i=1}^{n}(y_i-b_0-b_1x_i)^2\).


To minimize a function, we can set its first derivative equal to zero and solve (and verify the second derivative is positive at that value). Since we have two variables in \(Q\), we’ll need to take partial derivatives:


Partial derivative of Q with respect to b0

\(\frac{\partial Q }{\partial b_0}=\frac{\partial \sum(y_i-b_0-b_1x_i)^2 }{\partial b_0}=2\sum(y_i-b_0-b_1x_i)\frac{\partial \sum(y_i-b_0-b_1x_i) }{\partial b_0}=-2\sum(y_i-b_0-b_1x_i)\)


Set this partial derivative equal to zero:

\(-2\sum(y_i-b_0-b_1x_i)=0\)

\(\sum(y_i-b_0-b_1x_i)=0\)

\(\sum{y_i}=nb_0+b_1\sum{x_i}\)


Partial derivative of Q with respect to b1

\(\frac{\partial Q }{\partial b_1}=\frac{\partial \sum(y_i-b_0-b_1x_i)^2 }{\partial b_1}=2\sum(y_i-b_0-b_1x_i)\frac{\partial \sum(y_i-b_0-b_1x_i) }{\partial b_0}=-2\sum{x_i}(y_i-b_0-b_1x_i)\)


Set this partial derivative equal to zero:

\(-2\sum{x_i}(y_i-b_0-b_1x_i)=0\)

\(\sum{x_i}(y_i-b_0-b_1x_i)=0\)

\(\sum{x_iy_i}-b_0\sum{x_i}-b_1\sum{x_i^2}=0\)

\(\sum{x_iy_i}=b_0\sum{x_i}+b_1\sum{x_i^2}=0\)


Solve this system of normal equations

\(\sum{y_i}=nb_0+b_1\sum{x_i}\)

\(\sum{x_iy_i}=b_0\sum{x_i}+b_1\sum{x_i^2}\)


We can solve this system to get:

\(b_1=\frac{n\sum{x_iy_i}-\sum{x_i}\sum{y_i}}{n\sum{x_i^2} \ - (\sum{x_i})^2}\)

and

\(b_0=\frac{\sum{x_i^2}\sum{y_i}-\sum{x_i}\sum{x_iy_i}}{n\sum{x_i^2} \ - (\sum{x_i})^2}=\frac{\sum{y_i}}{n}-b_1\frac{\sum{x_i}}{n}=\overline{y}-b_1\overline{x}\)


Using formulas for variance and covariance, we can rewrite \(b_1\) as:

\(b_1=\frac{n\sum{x_iy_i}-\sum{x_i}\sum{y_i}}{n\sum{x_i^2} \ - (\sum{x_i})^2}=\frac{s_{xy}}{s_x^2}=r\frac{s_y}{s_x}\)



The line that minimizes the sum of squared errors has:

  • \(\textrm{slope}=b_1=r\frac{s_y}{s_x}\)
  • \(\textrm{y-intercept}=b_0=\overline{y}-b_1\overline{x}\)

  1. For this sample of 30 observations:

    \(\overline{Y}=\overline{GPA}=2.541\), \(s_y=s_{GPA}=0.694\)

    \(\overline{X}=\overline{ACT}=23.0\), \(s_x=s_{ACT}=5.843\)

    \(n = 30\), \(r_{y,x}=r_{GPA,ACT}=0.906\)

    Calculate the slope and y-intercept for the least squares regression line.
# Get summary statistics
firstyear %>%
  summarize(meanx = mean(act), sdx = sd(act),
            meany = mean(gpa), sdy = sd(gpa),
            n = n(), cor = cor(act, gpa))

# Calculate b1
firstyear %>%
  summarize(b1 = cor(gpa, act) * sd(gpa) / sd(act),
            b0 = mean(gpa) - (b1 * mean(act)) )

Fitting linear models in R

In R, we can use the lm() function to fit linear models. Expand the code –>

# lm = linear model
# lm(y ~ x, data)
lm(gpa ~ act, data = firstyear)
#
# The output shows coefficients
# It's typically more useful to store the model
model1 <- lm(gpa ~ act, data = firstyear)
#
# We can then investigate this model
# Summary statistics
summary(model1)
#
# Coefficients
coef(model1)
#
# Broom package:  tidy(), glance(), augment() functions
# Tidy the model coefficients, standard errors, and p-values
tidy(model1)
#
# Glance at summary statistics for the model
glance(model1)
#
# Augment the data with predictions and residuals 
head(augment(model1))
#
#
# Modelr package: add_predictions() function
# Add predictions to our dataset
firstyear <- firstyear %>%
  add_predictions(model1)
#
# Now we can plot these predictions as a line
ggplot(data = firstyear, aes(x = act, y = gpa)) +
  geom_point() +
  geom_line(aes(y = pred), color="blue", size=1) +
  scale_y_continuous(breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  scale_x_continuous(breaks=seq(12, 36, 2), minor_breaks=NULL)



Is the best line any good?

Just because we’re able to calculate the best line, it doesn’t mean that line is meaningful. For example, here’s the line that best fits this data:

# Simulate data forming a parabola
sim_data <- tibble(
  x = c(-5:5),
  y = x^2
)

# Plot the data
# geom_smooth(method = "lm") plots the line of best fit
ggplot(data = sim_data, aes(x = x, y = y)) +
  geom_point(size=2) +
  geom_smooth(method="lm", se=FALSE)


It looks like we’ll need some way of measuring just how well a model (a line) fits a given dataset. Let’s derive some of these measures for three different datasets:

  • One in which the best line fits the data perfectly
  • One for our sample data (where the best line seems to fit pretty well)
  • One in which the best line does not fit the data at all

We’ll complete the following table to see the value of each measure in each case:


Measures of fit

SSE

  1. We already know the best line is the one that minimizes the sum of squared errors (SSE). Can we use SSE to evaluate how well a line fits the data?

    What would SSE equal if a line perfectly fit the data?

    Now let’s consider a situation where the best line doesn’t fit the data at all. In this situation, X and Y would be uncorrelated – knowing the value of X would not tell you anything about the value of Y. To find the best fitting line, what value of \(a\) would minimize: \(\Sigma{(Y-a)^2}\).

    If you substitute your answer for \(a\), what does this expression represent? What’s the maximum value for SSE?
# Later, we'll see what this ANOVA table represents
# For now, it gives us SSE (Sum Sq Residuals)
anova(model1)

# We can also calculate this directly with the residuals
sum(residuals(model1)^2)
  1. What will happen to the value of SSE if we add any additional point to our data? Why is this a problem?

MSE, RSE, and RMSE

Perhaps a better measure of fit would be the average squared error (the average squared distance from observation to the prediction line). One simple estimate of this average squared error would be MSE:

\(s_{y|x}^2=\frac{SS_E}{df_E}=\frac{\Sigma(y_i-\hat{y})^2}{n-2}\)

  1. What would \(s_{y|x}^2\) be in a situation with perfect fit? worst fit? What’s the maximum value of \(s_{y|x}^2\)?
# MSE is displayed in the ANOVA summary table.  Calculate directly with:
sum(residuals(model1)^2) / model1$df.residual
  1. It would be easier to interpret if our measure were not in squared units. Suppose we calculate RSE (residual standard error): \(RSE=s_{y|x}=\sqrt{s_{y|x}^2}=\sqrt\frac{SS_E}{df_E}=\sqrt\frac{\Sigma(y_i-\hat{y})^2}{n-2}\). What would RSE be in a situation with perfect fit? worst fit?
# We can calculate it directly with...
sqrt( sum(residuals(model1)^2) / model1$df.residual )

# We can also glance at it (sigma) with the broom package
glance(model1)
  1. The \(n-2\) in the denominator is used to provide an unbiased estimate. Another popular measure of model fit is RMSE (root mean square error): \(RMSE=\sqrt\frac{\Sigma(y_i-\hat{y})^2}{n}\). Sketch a rough representation for what RMSE represents. The code calculates RMSE for our sample data.
# The modelr package has a rmse() function
rmse(model1, firstyear)

# Calculate it directly.  length = sample size
sqrt( sum(residuals(model1)^2) / length(firstyear$act) )
  1. So far, all our measures of fit are unbounded. Ideally, we’d might like a measure that has a known minimum and maximum. Suppose we took our total sums of squares (the total variation in Y) and partitioned it. Explain what the following formula represents, then calculate its value in the perfect and worst-fit scenarios:

    \(\frac{SS_E}{SS_Y}=\frac{\Sigma(y_i-\overline{Y})^2}{\Sigma(y_i-\hat{Y})^2}=1-R^2\)
# The modelr package has an rsquare() function
1 - rsquare(model1, firstyear)
  1. That seems backwards – the measure should be zero when we have no fit and 1.0 when we have perfect fit. Let’s try this:

    \(R^2=\frac{SS_Y-SS_E}{SS_Y}=\frac{SS_{reg}}{SS_Y}=\frac{\Sigma(\hat{y}-\overline{Y})^2}{\Sigma(y_i-\hat{Y})^2}\)

    This is the coefficient of determination (which has the same interpretation as eta-squared in ANOVA). Fill-in-the-blanks to show the value of \(R^2\) in perfect fit and worst fit scenarios.
# The modelr package has an rsquare() function
rsquare(model1, firstyear)

# We can glance at it with the broom package
glance(model1)

# It also appears in the summary
summary(model1)

# We can also get it from the ANOVA table
anova(model1)
11.487/(11.487+2.4982)
  1. As we’ll see later, none of these measures are perfect. R-squared and RMSE seem to be the most widely used, though. Identify at least one advantage each measure has over the other.

Conditions of linear regression

  • Validity: The data we’re analyzing maps to the research question we are trying to answer

  • Additivity and Linearity: The deterministic component of the model is a linear function of the predictors.
  • Diagnosis: Look at plots of observed vs predicted or residuals vs predicted values. The points should be symmetrically distributed around a diagonal line in the former plot or around horizontal line in the latter plot, with a roughly constant variance.

  • Independent errors: No correlation among errors
  • Diagnosis: If you have time series data, be careful that consecutive errors are not related.

  • Equal variance of errors (homoscedasticity): The variance in the errors is the same across all levels of X.
  • Diagnosis: Look at the plot of residuals vs predicted values. If the residuals grow larger as a function of X, you have a problem.

  • Normality of errors: The variance in the errors is the same across all levels of X.
  • Diagnosis: Look at a P-P or Q-Q plot of the residuals. The residuals should fall near the diagonal line. You could also run a test for normality, like the Shapiro-Wilk or Kologorov-Smirnov tests. Note that the dependent and independent variables in a regression model do not need to be normally distributed by themselves–only the prediction errors need to be normally distributed


  1. The plot() function displays some diagnostic plots for our model. Evaluate the conditions listed above based on our dataset, question of interest, and the diagnostic plots.
plot(model1)

Regression: Inference on slope

Let’s finally use our real dataset. To change things up a bit, let’s regress Fall semester GPAs on high school GPAs. Make sure you can interpret all this output:

full_model <- lm(fallgpa ~ hsgpa, data = sau)
summary(full_model)

rmse(full_model, data = sau)

plot(full_model)


Earlier, we learned that we always expect to get a non-zero correlation in any sample of data. Similarly, we expect linear models with non-zero slopes in any sample of data (even if we have reason to believe the variables are uncorrelated).

Our model to predict fall semester GPAs is: FallGPA = 0.058 + 0.926(hsGPA)

Does the magnitude of this slope (0.926) imply high school and college GPAs have a relationship for our population of interest or could we have obtained a slope of this magnitude even if the variables are uncorrelated?

  1. Explain how we’ll use randomization-based methods to test a null hypothesis that the slope of our linear model is zero. If it helps, explain what the two randomizations displayed below represent.
  1. Explain how we’ll use randomization-based methods to test a null hypothesis that the slope of our linear model is zero.
# Store our observed slope of 0826
observed_slope <- full_model$coefficients[2]

# Run 10,000 randomizations, shuffling the high school GPA
rand_slopes <- Do(10000) * lm(fallgpa ~ shuffle(hsgpa), data = sau)

# The slopes are stored in the "hsgpa" variable.  Let's plot and estimate the p-value.
ggplot(data = rand_slopes, aes(x = hsgpa)) +
  geom_histogram(fill="lightblue", color="white", alpha = 0.8) +
  labs(
      title = "Randomized slopes",
      x = "Slope"
      ) +
  scale_x_continuous(breaks=seq(-.1, .1, .025), minor_breaks=NULL) +
    theme(
    axis.text.x = element_text(size = 11, color="grey10"),
    legend.position = "none",
    panel.grid.major.y = element_line(colour = "white"),
    panel.grid.major.x = element_line(colour = "white", size=.15),
    panel.grid.minor = element_blank(),
    panel.background = element_rect(fill = "grey93")
  ) +
  annotate("text", x = .075, y = 600, label = paste("p = 0"))

Bootstrap confidence interval for the slope

  1. Explain how the following bootstrap confidence interval for the slope was constructed.
boot <- Do(10000) * lm(fallgpa ~ hsgpa, data = sample(sau, replace=TRUE))

bootstrapCI <- confint(boot$hsgpa, level = 0.95, method = "quantile")
lower <- as.numeric(bootstrapCI[1]) # Store lower CI bound
upper <- as.numeric(bootstrapCI[2]) # Store upper CI bound

# Density plot
ggplot(data = boot, aes(x = hsgpa)) +
  geom_density(fill="lightblue", color="white", alpha = 0.8) +
  labs(
      title = "Bootstrap distribution of correlations",
      x = "bootstrap correlations"
      ) +
  scale_x_continuous(breaks=seq(.7, 1, 0.05), minor_breaks=NULL) +
    theme(
    axis.text.x = element_text(size = 11, color="grey10"),
    legend.position = "none",
    panel.grid.major.y = element_line(colour = "white"),
    panel.grid.major.x = element_line(colour = "white", size=.15),
    panel.grid.minor = element_blank(),
    panel.background = element_rect(fill = "grey93")
  ) +
  annotate("text", x = lower, y = 5, label = round(lower,3)) +
  annotate("text", x = upper, y = 5, label = round(upper,3)) +
  annotate("text", x = median(boot$hsgpa), y = 6, label = paste("95%")) +
  annotate("segment", x = lower, xend = upper, y = 4, yend = 4, color = "red")

Model selection

  1. Two trees have fallen down during a windy night. Which of the following two possible explanations do you think is better? Why?

    a) The wind has blown them down.

    b) Two meteorites have each taken one tree down, and after that hit each other and removed any trace of themselves.

In the 14th century, William of Ockham wrote Entia non sunt multiplicanda praeter necessitatem, which translates to: More things should not be used than are necessary.

When comparing two competing theories, we might use Ockham’s Razor and prefer theories that are simpler (more parsimonious in that they require fewer assumptions or are easier to explain). On the other hand, we might prefer more complex theories if they more accurately predict future events.

When we compare competing statistical models, we’ll have to trade-off accuracy and simplicity. We’ll have to learn to navigate between:

  • Overfitting which leads to poor prediction by learning too much from our sample data

and

  • Underfitting which leads to poor prediction by learning too little from our sample data
  1. You’ve already dealt with this issue once. Explain, again, why you might prefer a linear model to a curve or a connect-the-dots model.

If we’re interested in the best prediction of fall semester GPAs, one approach we might take is:

  • Start with a basic model with one predictor: \(y = b_0 + b_1\textrm{(hsGPA)}\). Calculate a measure of model fit.
  • Add a second predictor: \(y = b_0 + b_1\textrm{(hsGPA)} + b_2\textrm{(ACT)}\). Calculate a measure of model fit. If this model fits noticeably better, keep this more complicated model.
  • Add a third predictor: \(y = b_0 + b_1\textrm{(hsGPA)} + b_2\textrm{(ACT)} + b_3\textrm{(female)}\). Calculate a measure of model fit. If this model fits noticeably better than the previous model, keep this more complicated model.

We could also start with a complicated model, remove a predictor, and determine if the fit worsens noticeably.


  1. When comparing models, it’s helpful to write out the full (more complicated) model and the reduced model. Write out the full and reduced models if I want to know whether ACT scores predict fall semester GPAs.
  1. Below, I’ve sketched the full and reduced models on our (simulated) dataset. What does SSE represent in our reduced model? Will SSE always get smaller as we add more predictor variables?


Reduced Model
ggplot(data = withpredictions, aes(x = act2, y = gpa)) +
  geom_point() +
  geom_abline(aes(intercept = mean(gpa), slope = 0), color="blue", size=1) + 
  annotate("rect", xmin=withpredictions$act2, xmax=withpredictions$act2 + (withpredictions$gpa - mean(withpredictions$gpa)),
           ymin=mean(withpredictions$gpa), ymax=withpredictions$gpa, alpha=.3, fill="red") +
  scale_y_continuous(breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  scale_x_continuous(breaks=seq(12, 36, 2), minor_breaks=NULL) + coord_fixed(ratio = 1, xlim=c(18,26)) +
    labs(x = "ACT", y=NULL) +
    labs(title = paste("Reduced model SSE =", round(anova(lm(gpa ~ act, data = firstyear))[2,2]+anova(lm(gpa ~ act, data = firstyear))[1,2],5)))
Full Model
ggplot(data = withpredictions, aes(x = act2, y = gpa)) +
  geom_point() +
  geom_abline(aes(intercept = b0, slope = b1), color="blue", size=1) + 
  annotate("rect", xmin=withpredictions$act2, xmax=withpredictions$act2 + (withpredictions$gpa - withpredictions$pred),
           ymin=withpredictions$gpa, ymax=withpredictions$pred, alpha=.3, fill="red") +
  scale_y_continuous(breaks=seq(0, 4, 1), minor_breaks=seq(0.5, 3.5, .5)) +
  scale_x_continuous(breaks=seq(12, 36, 2), minor_breaks=NULL) + coord_fixed(ratio = 1, xlim=c(18,26)) +
    labs(x = "ACT", y=NULL) +
  labs(title = paste("Full model SSE =", round(anova(lm(gpa ~ act, data = firstyear))[2,2],5)))
.
  1. Recall that for this sample of data:

    \(\overline{Y}=\overline{GPA}=2.541\), \(s_y=s_{GPA}=0.694\)

    \(\overline{X}=\overline{ACT}=23.0\), \(s_x=s_{ACT}=5.843\)

    \(n = 30\), \(r_{y,x}=r_{GPA,ACT}=0.906\)

    Complete the ANOVA summary table to compare our full and reduced models? Explain what each value represents. From this, what could we conclude about our models? You can estimate the p-value with the F-distribution applet at http://lock5stat.com/statkey/theoretical_distribution/theoretical_distribution.html#F
anova(lm(gpa ~ act, data = firstyear))

t-test for slope

The only difference between our full and reduced models is the \(b_1\) coefficient (the slope). If \(b_1=0\), the full model would be the same as our reduced model. Another way, then, to compare our full and reduced models would be to test the hypothesis: \(H_0:b_1=0\). We can test this hypothesis with a t-test.

  1. Explain what the following derivation shows. Then, conduct this t-test. Compare the value of the t-statistic to the value of the MSR (F) you calculated in the ANOVA summary table.

    Finally, conduct a test of the hypothesis: \(H_0:r_{\textrm{GPA,ACT}}=0\)
t <- cor(firstyear$gpa, firstyear$act) / sqrt( (1-cor(firstyear$gpa, firstyear$act)^2) / (length(firstyear$act)-2))
t
t^2

Omnibus F-test

  1. We can also calculate our MSR (F-statistic) with formula displayed below. Verify this yields the same value you calculated in the ANOVA summary table. In the code, I’ll calculate this manually and then use the anova() function.
# Manual calculation
Rf <- cor(firstyear$gpa, firstyear$act)
Rr <- 0
N <- length(firstyear$gpa)
kf <- 1
kr <- 0
F <- ( ((Rf^2 - Rr^2) / (kf - kr)) / ((1-Rf^2)/(N-kf-1)) )
F

#
# ANOVA function
fullmodel <- lm(gpa ~ act, data=firstyear)
reducedmodel <- lm(gpa ~ 1, data=firstyear)
anova(reducedmodel, fullmodel)

Other criteria for model selection

As we’ll soon see, R-squared and p-values resulting from the omnibus F-test aren’t the best measures to use when comparing models. Here are a couple alternatives we’ll build upon later:

Likelihood

The likelihood of a model is the probability it produces our data given its parameter estimates. If we assume all our observations are independent, then we can write our likelihood function as:

We want to calculate \(P(\textrm{observation 1} \ \cap \ \textrm{observation 2} \ \cap \ ...)\). When we assume independence, we can calculate this as: \(P(\textrm{observation 1})P(\textrm{observation 2})...\).

Given our model with \(b_0\) and \(b_1\) – along with our assumption that errors are normally distributed – we can use a normal distribution to find \(P(\textrm{observation 1})\). We then simply have to multiply these probabilities for each observation in our dataset.


As an example, our first observation is: ACT = 14 with GPA = 1.39. Recall our model has \(b_0=0.06384\), \(b_1=0.10772\), and \(RSE=\sqrt{MS_E}=\sqrt{0.0892}=0.2987\).


For all students with an ACT score of 14, our model expects a GPA of: \(\hat{y}=0.06384+0.10772(14)=1.5719\).


Given that expectation, we can find the probability of actually observing a GPA of 1.39:

\(P[y=1.39 \ | \ y \sim \textrm{ND}(\mu=1.5719, \sigma=0.2987)]\)

Expand the code to see how to calculate this manually in R.

# We'll use dnorm to calculate the likelihood of each observation
# Here's the likelihood of the first observation:
dnorm(1.39, mean = (b0 + (b1 * 14)), sd = 0.2987)

# Let's calculate the likelihood for ALL observations
# and multiply those together to get the model likelihood
# We'll also calculate the log-likelihood (which we'll see in a bit)
firstyear %>%
  mutate(likelihood = dnorm(gpa, mean = (b0 + (b1 * act)), sd = 0.2987)) %>%
  summarize(L = round(prod(likelihood),3 ),
            logL = round( log(L),2 ))


Typically, it’s easier to work with the natural log of the likelihood. This log-likelihood will always be negative, with values closer to zero indicating better model fit.

We can use log-likelihood to compare full and reduced models (by calculating the likelihood ratio):

If the full model is much better than the reduced model, we would expect:

  • \(L(\textrm{reduced})\) to be small
  • \(L(\textrm{full})\) to be large
  • \(\frac{L(\textrm{reduced})}{L(\textrm{full})}\) to be small
  • \(ln(\frac{L(\textrm{reduced})}{L(\textrm{full})})\) to be large and negative.
  • \(-2ln(\frac{L(\textrm{reduced})}{L(\textrm{full})})\) to be large and positive

To estimate a p-value from this likelihood ratio, we can use a chi-square distribution with \(df = df_{\textrm{full}} - df_{\textrm{reduced}}\)

Expand the code to see this calculation in R:

# Let's first calculate log-likelihood
# We can glance at the with the broom package
glance(fullmodel)

# We can calculate it directly with logLik()
logLik(fullmodel)
logLik(reducedmodel)

# Calculate the likelihood ratio
LR <- 2 * (logLik(fullmodel) - logLik(reducedmodel))
LR

# Calculate the p-value
pchisq(LR, df=1, lower.tail=FALSE)

# If you install the lmtest package, you can use lrtest
library(lmtest)
lrtest(reducedmodel, fullmodel)

AIC

Akaike’s Information Criterion is another measure to compare competing models. AIC seeks to find the model that has a good fit to the data with relatively few parameters (predictors). It’s defined as:

where b = the number of coefficients estimated in our model (slope(s) and intercept) and p = the number of predictors in our model. When comparing models, we prefer the model that produces the smaller AIC value.

Expand the code to see the AIC() function in action:

AIC(reducedmodel, fullmodel)
anova(fullmodel)



Example: Musical neurons

  1. Explain how we could use ANOVA to determine if an increase in neural activity is associated with playing stringed instruments. Expand the code and interpret the output. Also, calculate the omnibus F-statistic.
neuron <- read.csv("http://www.bradthiessen.com/html5/data/violin.csv")

# Convert years played to a factor variable.  I'll create a new variable.
neuron <- neuron %>%
  mutate(years_group = case_when(
    .$years == 0 ~ "a) 0 years",
    .$years > 0 & .$years < 10 ~ "b) <10 years",
    .$years > 10 ~ "c) >10 years"),
    years_group = as.factor(years_group))

anova(aov(neural ~ years_group, data = neuron))
  1. Write out the full and reduced models to determine if years playing a stringed instrument is associated with neural activity.
  1. Expand the code and interpret the output. Verify calculations as necessary, especially those in the ANOVA summary table. Interpret all plots.


The Prestige


  1. The linear model predicting prestige from income is displayed below. Interpret the slope and y-intercept. Expand the code and interpret the output from R.
prestige_data <- read.csv("http://www.bradthiessen.com/html5/data/prestige.csv")

ggplot(data = prestige_data, aes(x = income, y = prestige)) +
  geom_point(color="steelblue") +
  geom_smooth(method="lm", se=FALSE) +
  annotate("text", x=15000, y=35, label="R-squared = 0.511") +
  annotate("text", x=15000, y=28, label="y = 27.141 + 0.0029x")

prestige_model <- lm(prestige ~ income, data=prestige_data)

tidy(prestige_model)
  1. Notice the t-statistic for the slope estimate is 10.224. Write out full and reduced models and complete the ANOVA summary table. Verify the MSR using both the omnibus F-statistic and the t-statistic of 10.224.?
tidy(anova(prestige_model))
  1. For this model, RMSE = 11.97. Interpret this value.

Confidence intervals for predictions

  1. We can easily predict the average prestige of all jobs that pay an income of 7,000: \(27.14 + 0.0029(7000)=47.4\). We know that prediction won’t be perfect, so it might make sense to construct a confidence interval around that prediction. Interpret the confidence interval displayed below:

Confidence interval for predictions: \(\hat{y} \ \pm \ t_{n-2}^{\alpha/2}s_{y|x}\sqrt{\frac{1}{n}+\frac{(x_0-\overline{X})^2}{(n-1)s_x^2}}\) where \(s_{y|x}=\sqrt{MS_E}\)

A 95% confidence interval for our prediction of 7000 is, then: \(7000 \ \pm \ 2\sqrt{146.16}\sqrt{\frac{1}{102}+\frac{(7000-6797.9)^2}{(102-1)(s_x^2)4245.92^2}}=47.4 \ \pm \ 2.38\)

  1. A 95% confidence interval is displayed over our data. Why does the width of the interval differ across values of X?
ggplot(data = prestige_data, aes(x = income, y = prestige)) +
  geom_point(color="steelblue") +
  geom_smooth(method = "lm", se=TRUE, color="red")
  1. If you interpreted the confidence interval correctly, it probably didn’t give you what you wanted. If you want an interval to predict the prestige of individual jobs with specific incomes (and not the average of all jobs with that income), you’ll need to use a prediction interval. Expand the code to see how this was constructed. Why are prediction intervals wider than confidence intervals?

Prediction Interval: \(\hat{y} \ \pm \ t_{n-2}^{\alpha/2}s_{y|x}\sqrt{1+\frac{1}{n}+\frac{(x_0-\overline{X})^2}{(n-1)s_x^2}}\)

A 95% prediction interval for our prediction of 7000 is, then: \(7000 \ \pm \ 2\sqrt{146.16}\sqrt{1+\frac{1}{102}+\frac{(7000-6797.9)^2}{(102-1)(s_x^2)4245.92^2}}=47.4 \ \pm \ 24.10\)

We can get a rough estimate of this interval by simply taking: \(\hat{y} \ \pm \ 2\sqrt{MS_E}\)

# Get predictions (actual and lower/upper confidence)
predictions <- predict(prestige_model, interval="prediction")

# Add them to data frame
prestige_data <- cbind(prestige_data, predictions)

# Plot
ggplot(data = prestige_data, aes(x = income, y = prestige)) +
  geom_ribbon(aes(ymin = lwr, ymax = upr), fill="grey70", alpha=.5) +
  geom_point(color="steelblue") +
  geom_smooth(method="lm", se=FALSE)
  

Other topics (time permitting)

Lowess regression

Robust regression

Box-Cox transforms

Entropy

Sources

This document is released under a Creative Commons Attribution-ShareAlike 3.0 Unported license.

LS0tCnRpdGxlOiAiTGluZWFyIFJlZ3Jlc3Npb24iCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICBjc3M6IGh0dHA6Ly93d3cuYnJhZHRoaWVzc2VuLmNvbS9iYXRsYWIzLmNzcwogICAgZGZfcHJpbnQ6IHRpYmJsZQogICAgZmlnX2hlaWdodDogMy45CiAgICBmaWdfd2lkdGg6IDYuMwogICAgaGlnaGxpZ2h0OiBweWdtZW50cwogICAgdGhlbWU6IHNwYWNlbGFiCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICBjc3M6IGh0dHA6Ly93d3cuYnJhZHRoaWVzc2VuLmNvbS9iYXRsYWIzLmNzcwogICAgZmlnX2hlaWdodDogMy45CiAgICBmaWdfd2lkdGg6IDYuMwogICAgaGlnaGxpZ2h0OiBweWdtZW50cwogICAgdGhlbWU6IHNwYWNlbGFiCiAgICB0b2M6IHllcwotLS0KCmBgYHtyICdnbG9iYWwgb3B0aW9ucycsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgY29tbWVudCA9ICIjICAgIiwKICBjb2xsYXBzZSA9IFRSVUUsCiAgZmlnLmhlaWdodCA9IDMuOSwKICBmaWcud2lkdGggPSA2LjMKKQpgYGAKCmBgYHtyICdwcmVyZXFzJywgbWVzc2FnZT1GQUxTRX0KIyBJbnN0YWxsIGFuZCBsb2FkIGFsbCBuZWNlc3NhcnkgcGFja2FnZXMKbGlzdC5vZi5wYWNrYWdlcyA8LSBjKCJ0aWR5dmVyc2UiLCAiYnJvb20iLCAibW9zYWljIiwgImdnRXh0cmEiKQpuZXcucGFja2FnZXMgPC0gbGlzdC5vZi5wYWNrYWdlc1shKGxpc3Qub2YucGFja2FnZXMgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKVssIlBhY2thZ2UiXSldCmlmKGxlbmd0aChuZXcucGFja2FnZXMpKSBpbnN0YWxsLnBhY2thZ2VzKG5ldy5wYWNrYWdlcykKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobW9zYWljKQpsaWJyYXJ5KGJyb29tKQpsaWJyYXJ5KG1vZGVscikKbGlicmFyeShnZ0V4dHJhKQoKYGBgCgoqKioqKgoKIyBDb250ZXN0CgpUaGUgY2xhc3Mgd2lsbCBjaG9vc2UgdGhlIGNyaXRlcmlhIHdlIHVzZSB0byBkZWNsYXJlIHRoZSB3aW5uZXIuCgo8YnIgLz4KCgp8IENlbGVicml0eSAgICAgICAgICAgICAgfCBHdWVzcyB8IEFjdHVhbCB8ID8/PyAgICAgICAgICAgICAgICAgICAgfAp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfDotLS0tLS18Oi0tLS0tLTp8Oi0tLS0tLS0tLS0tLS0tLS0tLS0tLS06fAp8IDEuIE9wcmFoICAgICAgICAgICAgICAgfCAgICAgICB8ICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgfAp8IDIuIFRvbSBIYW5rcyAgICAgICAgICAgfCAgICAgICB8ICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgfAp8IDMuIEJldHR5IFdoaXRlICAgICAgICAgfCAgICAgICB8ICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgfAp8IDQuIFBlbMOpICAgICAgICAgICAgICAgIHwgICAgICAgfCAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgIHwKfCA1LiBNaWNoYWVsIEpvcmRhbiAgICAgIHwgICAgICAgfCAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgIHwKfCA2LiBUYXlsb3IgU3dpZnQgICAgICAgIHwgICAgICAgfCAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgIHwKfCA3LiBXaWxsIFNtaXRoICAgICAgICAgIHwgICAgICAgfCAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgIHwKfCA4LiBHZW9yZ2UgV2FzaGluZ3RvbiAgIHwgICAgICAgfCAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgIHwKfCA5LiAoc3R1ZGVudHMnIGNob2ljZSkgIHwgICAgICAgfCAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgIHwKfCAxMC4gKHN0dWRlbnRzJyBjaG9pY2UpIHwgICAgICAgfCAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgIHwKfCAgICAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgfCAgICAgICAgfCAqKnNjb3JlID0gX19fX19fX19fXyoqIHwKCjEuIFdoYXQgd2FzIHRoZSBwb2ludCBvZiB0aGF0PwoKPGRpdiBzdHlsZT0id2lkdGg6MzUwcHg7IGhlaWdodDo3NXB4Ij4hW10oVHJhbnNwYXJlbnQuZ2lmKTwvZGl2PgoKKioqKioKCiMgU2NlbmFyaW86ICBDb2xsZWdlIGFkbWlzc2lvbnMKClN1cHBvc2UgeW91IHdvcmsgaW4gdGhlIGFkbWlzc2lvbnMgb2ZmaWNlIGFuZCBhcmUgdGFza2VkIHdpdGggYWRtaXR0aW5nIHN0dWRlbnRzIHdobyBhcmUgcHJlZGljdGVkIHRvIGJlIHN1Y2Nlc3NmdWwgYXQgU3QuIEFtYnJvc2UuIAoKIVtdKHNhdS5qcGcpCgo8YnIgLz4KCjIuIEhvdyBkbyB5b3UgZGVmaW5lICpzdWNjZXNzKiBhdCBTdC4gQW1icm9zZT8gIFdoYXQgaW5mb3JtYXRpb24gZG8geW91IHRoaW5rIHlvdSB3b3VsZCBuZWVkIHRvIHByZWRpY3QgYSBzdHVkZW50J3Mgc3VjY2Vzcz8gIFdoaWNoIGZhY3RvciBkbyB5b3UgdGhpbmsgKmJlc3QqIHByZWRpY3RzIHN1Y2Nlc3M/Cgo8ZGl2IHN0eWxlPSJ3aWR0aDozNTBweDsgaGVpZ2h0OjEwMHB4Ij4hW10oVHJhbnNwYXJlbnQuZ2lmKTwvZGl2PgoKMy4gQ2xpY2sgdGhlIHRhYnMgdG8gc2VlIHBsb3RzIG9mIGRhdGEgZnJvbSBmaXJzdC15ZWFyIHN0dWRlbnRzIGF0IFN0LiBBbWJyb3NlIGZyb20gMjAxMS0yMDE1LiAgT24gZWFjaCBwbG90LCBJJ3ZlIHNrZXRjaGVkIHRoZSBsaW5lIHRoYXQgKmJlc3QqIGZpdHMgdGhlIGRhdGEuICBJbnRlcnByZXQgdGhlIHNsb3BlIGFuZCB5LWludGVyY2VwdCBvZiB0aGUgbGluZSBpbiB0aGUgZmlyc3QgcGxvdC4gIFRoZW4sIHVzZSB0aGF0IHBsb3QgdG8gcHJlZGljdCB0aGUgZmlyc3Qtc2VtZXN0ZXIgR1BBIGZvciBhIHN0dWRlbnQgd2l0aCBhIGhpZ2ggc2Nob29sIEdQQSBvZiAzLjAuCgo8ZGl2IHN0eWxlPSJ3aWR0aDozNTBweDsgaGVpZ2h0OjIwMHB4Ij4hW10oVHJhbnNwYXJlbnQuZ2lmKTwvZGl2PgoKIyMjIHsudGFic2V0IC50YWJzZXQtZmFkZX0KCiMjIyMgSGlnaCBzY2hvb2wgR1BBcwoKYGBge3IgJ2hzZ3BhLXNjYXR0ZXJwbG90JywgbWVzc2FnZT1GQUxTRSwgZmlnLmhlaWdodCA9IDMuOSwgZmlnLndpZHRoID0gNC41fQojIFJlYWQgZGF0YQpzYXUgPC0gcmVhZC5jc3YoImh0dHA6Ly93d3cuYnJhZHRoaWVzc2VuLmNvbS9odG1sNS9kYXRhL3NhdS5jc3YiKQojIENvbnZlcnQgZmVtYWxlIGFuZCBtaW5vcml0eSB2YXJpYWJsZXMgdG8gZmFjdG9ycwpzYXUgPC0gc2F1ICU+JQogIG11dGF0ZShmZW1hbGUgPSBmYWN0b3IoZmVtYWxlLCBsYWJlbHMgPSBjKCJtYWxlIiwgImZlbWFsZSIpKSwKICAgICAgICAgbWlub3JpdHkgPSBmYWN0b3IobWlub3JpdHksIGxhYmVscyA9IGMoIndoaXRlIiwgIm1pbm9yaXR5IikpKQogIApwIDwtIGdncGxvdChzYXUsIGFlcyh4ID0gaHNncGEsIHk9ZmFsbGdwYSkpICsKICBnZW9tX3BvaW50KGFscGhhPS4zLCBjb2xvcj0ic3RlZWxibHVlIikgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlPUZBTFNFLCBjb2xvcj0iYmxhY2siKSArIAogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAuNSw0KSxicmVha3M9c2VxKDAsIDQsIDEpLCBtaW5vcl9icmVha3M9c2VxKDAuNSwgMy41LCAuNSkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLjUsNCksIGJyZWFrcz1zZXEoMCwgNCwgMSksIG1pbm9yX2JyZWFrcz1zZXEoMC41LCAzLjUsIC41KSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpICsKICBsYWJzKAogICAgeCA9ICJIaWdoIFNjaG9vbCBHUEEiLAogICAgeSA9ICJGYWxsIHNlbWVzdGVyIEdQQSBhdCBTdC4gQW1icm9zZSIKICApICsKICBhbm5vdGF0ZSgidGV4dCIsIHg9MS4xLCB5PTEuNSwgbGFiZWw9InkgPSAwLjA1OCArIDAuODI2eCIpCiMgVXNlIGxpYnJhcnkoZ2dFeHRyYSkgdG8gcGxvdCBtYXJnaW5hbCBoaXN0b2dyYW1zCmdnTWFyZ2luYWwocCwgdHlwZT0iaGlzdG9ncmFtIiwgZmlsbD0ic3RlZWxibHVlIiwgY29sb3I9IndoaXRlIikKCmBgYAoKIyMjIyBBQ1QgQ29tcG9zaXRlCgpgYGB7ciAnYWN0LXNjYXR0ZXJwbG90JywgbWVzc2FnZT1GQUxTRSwgZmlnLmhlaWdodCA9IDMuOSwgZmlnLndpZHRoID0gNC41fQpwIDwtIGdncGxvdChzYXUsIGFlcyh4ID0gaml0dGVyKEFDVHRvdGFsKSwgeT1mYWxsZ3BhKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9LjMsIGNvbG9yPSJzdGVlbGJsdWUiKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2U9RkFMU0UsIGNvbG9yPSJibGFjayIpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMC41LDQpLGJyZWFrcz1zZXEoMCwgNCwgMSksIG1pbm9yX2JyZWFrcz1zZXEoMC41LCAzLjUsIC41KSkgKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDE1LDM2KSwgYnJlYWtzPXNlcSgxNiwgMzYsIDIpLCBtaW5vcl9icmVha3M9c2VxKDE1LCAzNSwgMikpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSArCiAgbGFicygKICAgIHggPSAiQUNUIENvbXBvc2l0ZSIsCiAgICB5ID0gIkZhbGwgc2VtZXN0ZXIgR1BBIGF0IFN0LiBBbWJyb3NlIgogICkKCiMgVXNlIGxpYnJhcnkoZ2dFeHRyYSkgdG8gcGxvdCBtYXJnaW5hbCBoaXN0b2dyYW1zCmdnTWFyZ2luYWwocCwgdHlwZT0iaGlzdG9ncmFtIiwgZmlsbD0ic3RlZWxibHVlIiwgY29sb3I9IndoaXRlIikKCmBgYAoKIyMjIyBBQ1QgTWF0aAoKYGBge3IgJ2FjdG1hdGgtc2NhdHRlcnBsb3QnLCBtZXNzYWdlPUZBTFNFLCBmaWcuaGVpZ2h0ID0gMy45LCBmaWcud2lkdGggPSA0LjV9CnAgPC0gZ2dwbG90KHNhdSwgYWVzKHggPSBqaXR0ZXIoQUNUbWF0aCksIHk9ZmFsbGdwYSkpICsKICBnZW9tX3BvaW50KGFscGhhPS4zLCBjb2xvcj0ic3RlZWxibHVlIikgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlPUZBTFNFLCBjb2xvcj0iYmxhY2siKSArIAogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAuNSw0KSxicmVha3M9c2VxKDAsIDQsIDEpLCBtaW5vcl9icmVha3M9c2VxKDAuNSwgMy41LCAuNSkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygxNCwzNiksIGJyZWFrcz1zZXEoMTQsIDM2LCAyKSwgbWlub3JfYnJlYWtzPXNlcSgxNSwgMzUsIDIpKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikgKwogIGxhYnMoCiAgICB4ID0gIkFDVCBNYXRoIiwKICAgIHkgPSAiRmFsbCBzZW1lc3RlciBHUEEgYXQgU3QuIEFtYnJvc2UiCiAgKQpnZ01hcmdpbmFsKHAsIHR5cGU9Imhpc3RvZ3JhbSIsIGZpbGw9InN0ZWVsYmx1ZSIsIGNvbG9yPSJ3aGl0ZSIpCgoKYGBgCgojIyMjIEhTR1BBIGFuZCBSZXRlbnRpb24KCmBgYHtyICdyZXRlbnRpb24tc2NhdHRlcnBsb3QnLCBtZXNzYWdlPUZBTFNFLCBmaWcuaGVpZ2h0ID0gMy45LCBmaWcud2lkdGggPSA0LjV9CmdncGxvdChzYXUsIGFlcyh4ID0gaHNncGEsIHk9aml0dGVyKHJldGVudGlvbiwgLjEpKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9LjMsIGNvbG9yPSJzdGVlbGJsdWUiKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2U9RkFMU0UsIGNvbG9yPSJibGFjayIpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwxKSwgYnJlYWtzPXNlcSgwLCAxLCAxKSwgbWlub3JfYnJlYWtzPU5VTEwpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSArCiAgbGFicygKICAgIHggPSAiaGlnaCBzY2hvb2wgR1BBIiwKICAgIHkgPSAiMSA9IHN0dWRlbnQgcmV0dXJuZWQgZm9yIHNvcGhvbW9yZSB5ZWFyIgogICkKCmBgYAoKCiMjIFNpbXBsZSBsaW5lYXIgbW9kZWwKCkhlcmUncyBzb21lIHNpbXVsYXRlZCBkYXRhIHNob3dpbmcgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuICoqQUNUIHNjb3JlcyoqIGFuZCAxc3Qtc2VtZXN0ZXIgKipjb2xsZWdlIEdQQXMqKi4KCmBgYHtyICdzaW11bGF0ZS1kYXRhJ30KIyBTaW11bGF0ZSBBQ1QgYW5kIGZpcnN0LXNlbWVzdGVyIEdQQSBkYXRhCnNldC5zZWVkKDEyMykKZmlyc3R5ZWFyIDwtIHRpYmJsZSgKICBhY3QgPSBjKHJlcChjKDE0LCAxNiwgMTgsIDIwLCAyMiwgMjQsIDI2LCAyOCwgMzAsIDMyKSwzKSksCiAgZ3BhID0gcm91bmQoKGFjdC85KSArIHJub3JtKDMwLCAwLCAuMyksMiksCiAgYWN0MiA9IGFjdCArIHJub3JtKDMwLDAsLjEpCikKCiMgUGxvdCB0aGUgZGF0YQpnZ3Bsb3QoZGF0YSA9IGZpcnN0eWVhciwgYWVzKHggPSBhY3QsIHkgPSBncGEpKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgwLCA0LCAxKSwgbWlub3JfYnJlYWtzPXNlcSgwLjUsIDMuNSwgLjUpKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMTIsIDM2LCAyKSwgbWlub3JfYnJlYWtzPU5VTEwpCmBgYAoKPGJyIC8+Cgo0LiBEZXNjcmliZSBhbmQgcGFyYW1ldGVyaXplIGEgbW9kZWwgb2YgR1BBcyBhcyBhIGZ1bmN0aW9uIG9mIEFDVCBzY29yZXMuCgo8ZGl2IHN0eWxlPSJ3aWR0aDozNTBweDsgaGVpZ2h0OjEwMHB4Ij4hW10oVHJhbnNwYXJlbnQuZ2lmKTwvZGl2PgoKTGV0J3MgcmFuZG9tbHkgZ2VuZXJhdGUgMzAwIGxpbmVzIG9mIHRoZSBmb3JtICR5ID0gYl8wK2JfMXgkIHRvIGRpc3BsYXkgb24gb3VyIGRhdGE6CgpgYGB7ciAnc2ltdWxhdGUtbGluZXMnfQoKIyBMb29rcyBsaW5lYXIuICBSYW5kb21seSBjaG9vc2UgMjUwIHNsb3BlcyBhbmQgeS1pbnRlcmNlcHRzLiAKc2V0LnNlZWQoMTIzKQptb2RlbHMgPC0gdGliYmxlKAogIGIwID0gcnVuaWYoMzAwLCAtMiwgMy41KSwKICBiMSA9IHJ1bmlmKDMwMCwgLS4yLCAuMikKKQoKIyBQbG90IGFsbCAzMDAgbW9kZWxzIG9uIHRvcCBvZiB0aGUgZGF0YQpnZ3Bsb3QoZGF0YSA9IGZpcnN0eWVhciwgYWVzKHggPSBhY3QsIHkgPSBncGEpKSArCiAgZ2VvbV9hYmxpbmUoYWVzKGludGVyY2VwdCA9IGIwLCBzbG9wZSA9IGIxKSwgZGF0YSA9IG1vZGVscywgYWxwaGEgPSAwLjI1KSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2FibGluZShhZXMoaW50ZXJjZXB0ID0gMC4wNjM4LCBzbG9wZSA9IC4xMDc3KSwgZGF0YSA9IG1vZGVscywgYWxwaGEgPSAwLjI1KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1zZXEoMCwgNCwgMSksIG1pbm9yX2JyZWFrcz1zZXEoMC41LCAzLjUsIC41KSkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKDEyLCAzNiwgMiksIG1pbm9yX2JyZWFrcz1OVUxMKQpgYGAKCjxiciAvPgoKIyMjIENob29zaW5nIHRoZSAqYmVzdCogbGluZQoKNS4gSG93IGNhbiB3ZSBkZXRlcm1pbmUgd2hpY2ggbGluZSAqYmVzdCogZml0cyBvdXIgZGF0YT8gIEludGVycHJldCAqdG90YWwgKGFic29sdXRlKSBkaXN0YW5jZSogYW5kICpyb290IG1lYW4gc3F1YXJlIGRldmlhdGlvbiogb24gdGhlIHBsb3RzIGRpc3BsYXllZCBiZWxvdy4KCjxkaXYgc3R5bGU9IndpZHRoOjM1MHB4OyBoZWlnaHQ6MjAwcHgiPiFbXShUcmFuc3BhcmVudC5naWYpPC9kaXY+CgojIyMjIyB7LnRhYnNldCAudGFic2V0LWZhZGV9CgojIyMjIyMgTW9kZWwgIzEKCmBgYHtyICdkaXNwbGF5LW1vZGVsLXdpdGgtZXJyb3InfQojIENvcHkgb3VyIGRhdGEgdG8gbmV3IGRhdGEgZnJhbWUKd2l0aHByZWRpY3Rpb25zIDwtIGZpcnN0eWVhcgoKIyBDaG9vc2UgYSBzbG9wZSBhbmQgeS1pbnRlcmNlcHQKYjAwIDwtIDQyLzI0CmIxMCA8LSAxLzI0CiMgUHJlZGljdCBHUEFzIHdpdGggdGhpcyBzbG9wZSBhbmQgeS1pbnRlcmNlcHQKd2l0aHByZWRpY3Rpb25zJHByZWQyIDwtIGIwMCArIChiMTAgKiB3aXRocHJlZGljdGlvbnMkYWN0KQojIEFkZCBlcnJvcnMgKHZlcnRpY2FsIGRpc3RhbmNlcykKd2l0aHByZWRpY3Rpb25zJHJlc2lkMiA8LSB3aXRocHJlZGljdGlvbnMkZ3BhIC0gKCBiMDAgKyAoYjEwICogd2l0aHByZWRpY3Rpb25zJGFjdCkgKQoKCmdncGxvdChkYXRhID0gd2l0aHByZWRpY3Rpb25zLCBhZXMoeCA9IGFjdDIsIHkgPSBncGEpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2FibGluZShhZXMoaW50ZXJjZXB0ID0gYjAwLCBzbG9wZSA9IGIxMCksIGNvbG9yPSJibHVlIiwgc2l6ZT0xKSArIAogIGFubm90YXRlKCJzZWdtZW50IiwgeD13aXRocHJlZGljdGlvbnMkYWN0MiwgeGVuZD13aXRocHJlZGljdGlvbnMkYWN0MiwgCiAgICAgICAgICAgeT13aXRocHJlZGljdGlvbnMkZ3BhLCB5ZW5kPXdpdGhwcmVkaWN0aW9ucyRwcmVkMiwgY29sb3I9InJlZCIpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSAyNywgeSA9IDEuNiwgbGFiZWw9cGFzdGUoInJvb3QgbWVhbiBzcXVhcmUgZGV2aWF0aW9uID0iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoIHNxcnQobWVhbih3aXRocHJlZGljdGlvbnMkcmVzaWQyXjIpKSwgMykpKSArCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gMjcsIHkgPSAxLjgsIGxhYmVsPXBhc3RlKCJ0b3RhbCAoYWJzb2x1dGUpIGRpc3RhbmNlID0iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoIHN1bShhYnMod2l0aHByZWRpY3Rpb25zJHJlc2lkMikpLCAzKSkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgwLCA0LCAxKSwgbWlub3JfYnJlYWtzPXNlcSgwLjUsIDMuNSwgLjUpKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMTIsIDM2LCAyKSwgbWlub3JfYnJlYWtzPU5VTEwpCgpgYGAKCiMjIyMjIyBNb2RlbCAjMgoKYGBge3IgJ2Rpc3BsYXktbW9kZWwtMi13aXRoLWVycm9yJ30KIyBDaG9vc2UgYSBzbG9wZSBhbmQgeS1pbnRlcmNlcHQKYjAxIDwtIDMvNTAKYjExIDwtIDEvOQojIFByZWRpY3QgR1BBcyB3aXRoIHRoaXMgc2xvcGUgYW5kIHktaW50ZXJjZXB0CndpdGhwcmVkaWN0aW9ucyRwcmVkMSA8LSBiMDEgKyAoYjExICogd2l0aHByZWRpY3Rpb25zJGFjdCkKIyBBZGQgZXJyb3JzICh2ZXJ0aWNhbCBkaXN0YW5jZXMpCndpdGhwcmVkaWN0aW9ucyRyZXNpZDEgPC0gd2l0aHByZWRpY3Rpb25zJGdwYSAtICggYjAxICsgKGIxMSAqIHdpdGhwcmVkaWN0aW9ucyRhY3QpICkKCgpnZ3Bsb3QoZGF0YSA9IHdpdGhwcmVkaWN0aW9ucywgYWVzKHggPSBhY3QyLCB5ID0gZ3BhKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9hYmxpbmUoYWVzKGludGVyY2VwdCA9IGIwMSwgc2xvcGUgPSBiMTEpLCBjb2xvcj0iYmx1ZSIsIHNpemU9MSkgKyAKICBhbm5vdGF0ZSgic2VnbWVudCIsIHg9d2l0aHByZWRpY3Rpb25zJGFjdDIsIHhlbmQ9d2l0aHByZWRpY3Rpb25zJGFjdDIsIAogICAgICAgICAgIHk9d2l0aHByZWRpY3Rpb25zJGdwYSwgeWVuZD13aXRocHJlZGljdGlvbnMkcHJlZDEsIGNvbG9yPSJyZWQiKSArCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gMjcsIHkgPSAxLjYsIGxhYmVsPXBhc3RlKCJyb290IG1lYW4gc3F1YXJlIGRldmlhdGlvbiA9IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKCBzcXJ0KG1lYW4od2l0aHByZWRpY3Rpb25zJHJlc2lkMV4yKSksIDMpKSkgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IDI3LCB5ID0gMS44LCBsYWJlbD1wYXN0ZSgidG90YWwgKGFic29sdXRlKSBkaXN0YW5jZSA9IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKCBzdW0oYWJzKHdpdGhwcmVkaWN0aW9ucyRyZXNpZDEpKSwgMykpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1zZXEoMCwgNCwgMSksIG1pbm9yX2JyZWFrcz1zZXEoMC41LCAzLjUsIC41KSkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKDEyLCAzNiwgMiksIG1pbm9yX2JyZWFrcz1OVUxMKQpgYGAKCgojIyMjIyMgTW9kZWwgIzMKCmBgYHtyICdkaXNwbGF5LW1vZGVsLTMtd2l0aC1lcnJvcid9CiMgQ2hvb3NlIGEgc2xvcGUgYW5kIHktaW50ZXJjZXB0CmIwMiA8LSAzLjUKYjEyIDwtIC0xLzQyCiMgUHJlZGljdCBHUEFzIHdpdGggdGhpcyBzbG9wZSBhbmQgeS1pbnRlcmNlcHQKd2l0aHByZWRpY3Rpb25zJHByZWQzIDwtIGIwMiArIChiMTIgKiB3aXRocHJlZGljdGlvbnMkYWN0KQojIEFkZCBlcnJvcnMgKHZlcnRpY2FsIGRpc3RhbmNlcykKd2l0aHByZWRpY3Rpb25zJHJlc2lkMyA8LSB3aXRocHJlZGljdGlvbnMkZ3BhIC0gKCBiMDIgKyAoYjEyICogd2l0aHByZWRpY3Rpb25zJGFjdCkgKQoKCmdncGxvdChkYXRhID0gd2l0aHByZWRpY3Rpb25zLCBhZXMoeCA9IGFjdDIsIHkgPSBncGEpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2FibGluZShhZXMoaW50ZXJjZXB0ID0gYjAyLCBzbG9wZSA9IGIxMiksIGNvbG9yPSJibHVlIiwgc2l6ZT0xKSArIAogIGFubm90YXRlKCJzZWdtZW50IiwgeD13aXRocHJlZGljdGlvbnMkYWN0MiwgeGVuZD13aXRocHJlZGljdGlvbnMkYWN0MiwgCiAgICAgICAgICAgeT13aXRocHJlZGljdGlvbnMkZ3BhLCB5ZW5kPXdpdGhwcmVkaWN0aW9ucyRwcmVkMywgY29sb3I9InJlZCIpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSAyNywgeSA9IDEuNiwgbGFiZWw9cGFzdGUoInJvb3QgbWVhbiBzcXVhcmUgZGV2aWF0aW9uID0iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoIHNxcnQobWVhbih3aXRocHJlZGljdGlvbnMkcmVzaWQzXjIpKSwgMykpKSArCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gMjcsIHkgPSAxLjgsIGxhYmVsPXBhc3RlKCJ0b3RhbCAoYWJzb2x1dGUpIGRpc3RhbmNlID0iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoIHN1bShhYnMod2l0aHByZWRpY3Rpb25zJHJlc2lkMykpLCAzKSkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgwLCA0LCAxKSwgbWlub3JfYnJlYWtzPXNlcSgwLjUsIDMuNSwgLjUpKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMTIsIDM2LCAyKSwgbWlub3JfYnJlYWtzPU5VTEwpCmBgYAoKCiMjIyMjIEZpbmRpbmcgc21hbGxlc3QgUk1TRSB0aHJvdWdoIGl0ZXJhdGlvbgoKV2UgY2FuIGNhbGN1bGF0ZSB0aGlzICpyb290IG1lYW4gc3F1YXJlIGRldmlhdGlvbiogZm9yIGFsbCAzMDAgbGluZXMgd2UgcGxvdHRlZCBlYXJsaWVyLiAgTGV0J3MgZGlzcGxheSB0aGUgdGVuIGJlc3QgbW9kZWxzICh3aXRoIHRoZSBzbWFsbGVzdCBSTVNFKS4KCmBgYHtyICdybXNlLWZvci1hbGwtMzAwLWxpbmVzJ30KIyBEb24ndCB3b3JyeSBhYm91dCB0aGlzIGNvZGUuCiMgRnVuY3Rpb24gdG8gY2FsY3VsYXRlIHByZWRpY3Rpb25zIGJhc2VkIG9uIHNsb3BlIGFuZCB5LWludGVyY2VwdAptb2RlbDEgPC0gZnVuY3Rpb24oYSwgZGF0YSkgewogIGFbMV0gKyBkYXRhJGFjdCAqIGFbMl0KfQoKIyBGdW5jdGlvbiB0byBjYWxjdWxhdGUgdmVydGljYWwgZXJyb3IgZGlzdGFuY2VzCm1lYXN1cmVfZGlzdGFuY2UgPC0gZnVuY3Rpb24obW9kLCBkYXRhKSB7CiAgZGlmZiA8LSBkYXRhJGdwYSAtIG1vZGVsMShtb2QsIGRhdGEpCiAgc3FydChtZWFuKGRpZmYgXiAyKSkKfQoKIyBDb21wdXRlIGRpc3RhbmNlIGZvciBhbGwgMzAwIG1vZGVscwpzaW0xX2Rpc3QgPC0gZnVuY3Rpb24oYjAsIGIxKSB7CiAgbWVhc3VyZV9kaXN0YW5jZShjKGIwLCBiMSksIGZpcnN0eWVhcikKfQoKIyBBZGQgZGlzdGFuY2VzIHRvIHRoZSBtb2RlbHMgZGF0YSBmcmFtZQptb2RlbHMgPC0gbW9kZWxzICU+JSAKICBtdXRhdGUoZGlzdCA9IHB1cnJyOjptYXAyX2RibChiMCwgYjEsIHNpbTFfZGlzdCkpCgojIEZpbmQgMTAgYmVzdCBtb2RlbHMgKHdpdGggc21hbGxlc3Qgcm1zZSkgYW5kIHBsb3Qgb24gZGF0YQpnZ3Bsb3QoZmlyc3R5ZWFyLCBhZXMoYWN0LCBncGEpKSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDIsIGNvbG91ciA9ICJibGFjayIpICsgCiAgZ2VvbV9hYmxpbmUoCiAgICBhZXMoaW50ZXJjZXB0ID0gYjAsIHNsb3BlID0gYjEsIGNvbG91ciA9IC1kaXN0KSwgCiAgICBkYXRhID0gZmlsdGVyKG1vZGVscywgcmFuayhkaXN0KSA8PSAxMCkKICApICsKICBsYWJzKHRpdGxlID0gIjEwIGJlc3QgbW9kZWxzICh3aXRoIHNtYWxsZXN0IFJNU0UpIikKCmBgYAoKPGJyIC8+CgpFYWNoIG9mIHRoZSB0ZW4gbGluZXMgZGlzcGxheWVkIGFib3ZlIGhhcyBhIHktaW50ZXJjZXB0IGFuZCBhIHNsb3BlLCBzbyB3ZSBjYW4gdGhpbmsgb2YgZWFjaCBtb2RlbCBhcyBhIHBvaW50OiAkKGJfMCwgXCBiXzEpJC4gIExldCdzIHBsb3QgdGhlc2UgInBvaW50cyIgZm9yIGFsbCAzMDAgbGluZWFyIG1vZGVsczogCgpgYGB7ciAnYmxhaCd9CiMgUGxvdCB0aGUgc2xvcGVzIGFuZCB5LWludGVyY2VwdHMgb2YgYWxsIDMwMCBtb2RlbHMKIyBIaWdobGlnaHQgYmVzdCBjb21iaW5hdGlvbnMgb2Ygc2xvcGUgYW5kIGludGVyY2VwdCB3aXRoIHJlZCBjaXJjbGVzCmdncGxvdChtb2RlbHMsIGFlcyhiMCwgYjEpKSArCiAgZ2VvbV9wb2ludChkYXRhID0gZmlsdGVyKG1vZGVscywgcmFuayhkaXN0KSA8PSAxMCksIHNpemUgPSA0LCBjb2xvdXIgPSAicmVkIikgKwogIGdlb21fcG9pbnQoYWVzKGNvbG91ciA9IC1kaXN0KSkKCmBgYAoKPGJyIC8+CgpUaGUgcG9pbnRzIHdpdGggbGlnaHRlciBzaGFkZXMgb2YgYmx1ZSByZXByZXNlbnQgbW9kZWxzIHdpdGggc21hbGxlciBhbW91bnRzIG9mIGVycm9yLiAgVGhlIHJlZCBjaXJjbGVzIGhpZ2hsaWdodCB0aGUgMTAgYmVzdCBtb2RlbHMgdGhhdCB3ZXJlIGRpc3BsYXllZCBlYXJsaWVyLgoKPGJyIC8+Cgo2LiBIb3cgY291bGQgeW91IHVzZSB0aGlzIHBsb3QgdG8gaWRlbnRpZnkgdGhlIHNpbmdsZSAqYmVzdCogbW9kZWwgKHdoaWNoIG1pZ2h0IG5vdCBiZSBhbnkgb2YgdGhlIDMwMCBwb2ludHMgYXBwZWFyaW5nIG9uIHRoZSBwbG90KT8KCjxkaXYgc3R5bGU9IndpZHRoOjM1MHB4OyBoZWlnaHQ6MTAwcHgiPiFbXShUcmFuc3BhcmVudC5naWYpPC9kaXY+CgpMZXQncyB0cnkgYSAqZ3JpZCBzZWFyY2gqIHRvIGZpbmQgdGhlIGJlc3QgY29tYmluYXRpb24gb2Ygc2xvcGUgYW5kIHktaW50ZXJjZXB0LgoKYGBge3IgJ2dyaWQtc2VhcmNoJ30KIyBDcmVhdGUgdGhlIGdyaWQgYnkgc2VsZWN0aW5nIHJhbmdlcyBmb3IgYjAgYW5kIGIxCmdyaWQgPC0gZXhwYW5kLmdyaWQoCiAgYjAgPSBzZXEoLTEsIDEsIGxlbmd0aCA9IDI1KSwKICBiMSA9IHNlcSgwLjA1LCAuMTUsIGxlbmd0aCA9IDI1KQogICkgJT4lIAogIG11dGF0ZShkaXN0ID0gcHVycnI6Om1hcDJfZGJsKGIwLCBiMSwgc2ltMV9kaXN0KSkKCiMgWm9vbSBhbmQgZW5oYW5jZQpncmlkICU+JSAKICBnZ3Bsb3QoYWVzKGIwLCBiMSkpICsKICBnZW9tX3BvaW50KGRhdGEgPSBmaWx0ZXIoZ3JpZCwgcmFuayhkaXN0KSA8PSAxMCksIHNpemUgPSA0LCBjb2xvdXIgPSAicmVkIikgKwogIGdlb21fcG9pbnQoYWVzKGNvbG91ciA9IC1kaXN0KSkgCgpgYGAKCkxldCdzIHRha2UgdGhlc2UgMTAgYmVzdCBtb2RlbHMgYW5kIGRpc3BsYXkgdGhlbSBiYWNrIG9uIHRoZSBkYXRhLgoKYGBge3IgJ3Bsb3QtMTAtYmVzdCd9CmdncGxvdChmaXJzdHllYXIsIGFlcyhhY3QsIGdwYSkpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMiwgY29sb3VyID0gImdyZXkzMCIpICsgCiAgZ2VvbV9hYmxpbmUoCiAgICBhZXMoaW50ZXJjZXB0ID0gYjAsIHNsb3BlID0gYjEsIGNvbG91ciA9IC1kaXN0KSwgCiAgICBkYXRhID0gZmlsdGVyKGdyaWQsIHJhbmsoZGlzdCkgPD0gMTApCiAgKQoKYGBgCgo8YnIgLz4KCldlIGNvdWxkIGNvbnRpbnVlIHRoaXMgem9vbS1hbmQtZW5oYW5jZSBtZXRob2QgdW50aWwgd2UgcmVhbGx5IG5hcnJvd2VkIGRvd24gb3VyIGJlc3QgeS1pbnRlcmNlcHQgYW5kIHNsb3BlLiAgVGhhbmtmdWxseSwgdGhlcmUncyBhbiBlYXNpZXIgd2F5LgoKPGJyIC8+Cgo3LiBXZSB3YW50IHRvIGZpbmQgdGhlIG1vZGVsIHRoYXQgKiptaW5pbWl6ZXMqKiB0aGUgZGlzdGFuY2VzIGZyb20gZWFjaCBwb2ludCB0byB0aGUgbGluZS4gIFRoaW5rIGJhY2sgdG8geW91ciBwcmV2aW91cyBtYXRoIGNvdXJzZXMuICBXaGF0IG1ldGhvZHMgaGF2ZSB5b3UgdXNlZCB0byBmaW5kIG1pbmltdW1zIChvciB0byBtaW5pbWl6ZSBmdW5jdGlvbnMpPwoKPGRpdiBzdHlsZT0id2lkdGg6MzUwcHg7IGhlaWdodDoxMDBweCI+IVtdKFRyYW5zcGFyZW50LmdpZik8L2Rpdj4KCjguIFdoeSBtaWdodCB3ZSBiZSBtb3JlIGludGVyZXN0ZWQgaW4gKnZlcnRpY2FsKiBlcnJvcnMgdGhhbiBob3Jpem9udGFsIG9yIHBlcnBlbmRpY3VsYXIgZXJyb3JzPwoKPGRpdiBzdHlsZT0id2lkdGg6MzUwcHg7IGhlaWdodDo3NXB4Ij4hW10oVHJhbnNwYXJlbnQuZ2lmKTwvZGl2PgoKIyMjIyMgey50YWJzZXQgLnRhYnNldC1mYWRlfQoKIyMjIyMjIFZlcnRpY2FsIGVycm9ycwoKYGBge3IgJ3ZlcnRpY2FsLWVycm9ycyd9CiMgQ2hvb3NlIGEgc2xvcGUgYW5kIHktaW50ZXJjZXB0CmIwIDwtIDAuMDYzODQKYjEgPC0gMC4xMDc3MgojIFByZWRpY3QgR1BBcyB3aXRoIHRoaXMgc2xvcGUgYW5kIHktaW50ZXJjZXB0CndpdGhwcmVkaWN0aW9ucyRwcmVkIDwtIGIwICsgKGIxICogd2l0aHByZWRpY3Rpb25zJGFjdCkKIyBBZGQgZXJyb3JzICh2ZXJ0aWNhbCBkaXN0YW5jZXMpCndpdGhwcmVkaWN0aW9ucyRyZXNpZCA8LSB3aXRocHJlZGljdGlvbnMkZ3BhIC0gKCBiMCArIChiMSAqIHdpdGhwcmVkaWN0aW9ucyRhY3QpICkKCiMgUGxvdCB2ZXJ0aWNhbCBlcnJvcnMKZ2dwbG90KGRhdGEgPSB3aXRocHJlZGljdGlvbnMsIGFlcyh4ID0gYWN0MiwgeSA9IGdwYSkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fYWJsaW5lKGFlcyhpbnRlcmNlcHQgPSBiMCwgc2xvcGUgPSBiMSksIGNvbG9yPSJibHVlIiwgc2l6ZT0xKSArIAogIGFubm90YXRlKCJzZWdtZW50IiwgeD13aXRocHJlZGljdGlvbnMkYWN0MiwgeGVuZD13aXRocHJlZGljdGlvbnMkYWN0MiwgCiAgICAgICAgICAgeT13aXRocHJlZGljdGlvbnMkZ3BhLCB5ZW5kPXdpdGhwcmVkaWN0aW9ucyRwcmVkLCBjb2xvcj0icmVkIikgKwogIHNjYWxlX3lfY29udGludW91cyhicmVha3M9c2VxKDAsIDQsIDEpLCBtaW5vcl9icmVha3M9c2VxKDAuNSwgMy41LCAuNSkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgxMiwgMzYsIDIpLCBtaW5vcl9icmVha3M9TlVMTCkgKwogIGxhYnMoeCA9ICJBQ1QiLCB5ID0gIkdQQSIpCgpgYGAKCiMjIyMjIyBIb3Jpem9udGFsCgpgYGB7ciAnaG9yaXpvbnRhbC1lcnJvcnMnfQojIFBsb3QgaG9yaXpvbnRhbCBlcnJvcnMKZ2dwbG90KGRhdGEgPSB3aXRocHJlZGljdGlvbnMsIGFlcyh4ID0gYWN0MiwgeSA9IGdwYSkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fYWJsaW5lKGFlcyhpbnRlcmNlcHQgPSBiMCwgc2xvcGUgPSBiMSksIGNvbG9yPSJibHVlIiwgc2l6ZT0xKSArIAogIGFubm90YXRlKCJzZWdtZW50IiwgeD13aXRocHJlZGljdGlvbnMkYWN0MiwgeGVuZD0gKCAod2l0aHByZWRpY3Rpb25zJGdwYSAtIGIwKSAvIGIxKSwgCiAgICAgICAgICAgeT13aXRocHJlZGljdGlvbnMkZ3BhLCB5ZW5kPXdpdGhwcmVkaWN0aW9ucyRncGEsIGNvbG9yPSJyZWQiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1zZXEoMCwgNCwgMSksIG1pbm9yX2JyZWFrcz1zZXEoMC41LCAzLjUsIC41KSkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKDEyLCAzNiwgMiksIG1pbm9yX2JyZWFrcz1OVUxMKSArCiAgbGFicyh4ID0gIkFDVCIsIHkgPSAiR1BBIikKCmBgYAoKCiMjIyMjIyBQZXJwZW5kaWN1bGFyCgpgYGB7ciAncGVycGVuZGljdWxhci1lcnJvcnMnfQojIFBsb3QgaG9yaXpvbnRhbCBlcnJvcnMKZ2dwbG90KGRhdGEgPSB3aXRocHJlZGljdGlvbnMsIGFlcyh4ID0gYWN0MiwgeSA9IGdwYSkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fYWJsaW5lKGFlcyhpbnRlcmNlcHQgPSBiMCwgc2xvcGUgPSBiMSksIGNvbG9yPSJibHVlIiwgc2l6ZT0xKSArIAogIGFubm90YXRlKCJzZWdtZW50IiwgeD13aXRocHJlZGljdGlvbnMkYWN0MiwgeGVuZD0gKCh3aXRocHJlZGljdGlvbnMkYWN0MiArIGIxICogd2l0aHByZWRpY3Rpb25zJGdwYSAtIGIwICogYjEpIC8gKDEgKyBiMV4yKSksIAogICAgICAgICAgIHk9d2l0aHByZWRpY3Rpb25zJGdwYSwgeWVuZCA9IChiMCArIGIxICogd2l0aHByZWRpY3Rpb25zJGFjdDIpLCBjb2xvcj0icmVkIikgKwogIHNjYWxlX3lfY29udGludW91cyhicmVha3M9c2VxKDAsIDQsIDEpLCBtaW5vcl9icmVha3M9c2VxKDAuNSwgMy41LCAuNSkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgxMiwgMzYsIDIpLCBtaW5vcl9icmVha3M9TlVMTCkgKyAKICBnZW9tX2FibGluZShhZXMoaW50ZXJjZXB0ID0gMjAwLCBzbG9wZT0tMykpICsgY29vcmRfZml4ZWQocmF0aW8gPSAxLCB4bGltPWMoMTgsMjYpKSArCiAgICBsYWJzKHggPSAiQUNUIiwgeT1OVUxMKQoKCmBgYAoKIyMjIyMjIFNxdWFyZWQgKHZlcnRpY2FsKQoKYGBge3IgJ3NxdWFyZWQtZXJyb3JzJ30KIyBQbG90IHNxdWFyZWQgZXJyb3JzCmdncGxvdChkYXRhID0gd2l0aHByZWRpY3Rpb25zLCBhZXMoeCA9IGFjdDIsIHkgPSBncGEpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2FibGluZShhZXMoaW50ZXJjZXB0ID0gYjAsIHNsb3BlID0gYjEpLCBjb2xvcj0iYmx1ZSIsIHNpemU9MSkgKyAKICBhbm5vdGF0ZSgicmVjdCIsIHhtaW49d2l0aHByZWRpY3Rpb25zJGFjdDIsIHhtYXg9d2l0aHByZWRpY3Rpb25zJGFjdDIgKyAod2l0aHByZWRpY3Rpb25zJGdwYSAtIHdpdGhwcmVkaWN0aW9ucyRwcmVkKSwKICAgICAgICAgICB5bWluPXdpdGhwcmVkaWN0aW9ucyRncGEsIHltYXg9d2l0aHByZWRpY3Rpb25zJHByZWQsIGFscGhhPS4zLCBmaWxsPSJyZWQiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1zZXEoMCwgNCwgMSksIG1pbm9yX2JyZWFrcz1zZXEoMC41LCAzLjUsIC41KSkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKDEyLCAzNiwgMiksIG1pbm9yX2JyZWFrcz1OVUxMKSArIGNvb3JkX2ZpeGVkKHJhdGlvID0gMSwgeGxpbT1jKDE4LDI2KSkgKwogICAgbGFicyh4ID0gIkFDVCIsIHk9TlVMTCkKCmBgYAoKPGJyIC8+Cgo5LiBCZWZvcmUgd2UgZGVyaXZlIHRoZSBmb3JtdWxhIGZvciB0aGUgbGluZSBvZiBiZXN0IGZpdCwgd2h5IHNob3VsZCB3ZSBiZSB3aWxsaW5nIHRvIGFjY2VwdCAqYW55KiBhbW91bnQgb2YgZXJyb3I/ICBXaHkgdXNlIGEgKmxpbmUqIHdoZW4gd2UgY2FuIGNvbm5lY3QtdGhlLWRvdHMgb3IgdXNlIGEgY3VydmU/Cgo8ZGl2IHN0eWxlPSJ3aWR0aDozNTBweDsgaGVpZ2h0Ojc1cHgiPiFbXShUcmFuc3BhcmVudC5naWYpPC9kaXY+CgojIyMjIyB7LnRhYnNldCAudGFic2V0LWZhZGV9CgojIyMjIyMgQ29ubmVjdC10aGUtZG90cwoKYGBge3IgJ2Nvbm5lY3QtdGhlLWRvdHMnfQpnZ3Bsb3QoZGF0YSA9IHdpdGhwcmVkaWN0aW9ucywgYWVzKHggPSBhY3QsIHkgPSBncGEpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2xpbmUoY29sb3I9ImJsdWUiLCBzaXplPTEpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgwLCA0LCAxKSwgbWlub3JfYnJlYWtzPXNlcSgwLjUsIDMuNSwgLjUpKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMTIsIDM2LCAyKSwgbWlub3JfYnJlYWtzPU5VTEwpICsKICBsYWJzKHggPSAiQUNUIiwgeSA9ICJHUEEiKQoKYGBgCgojIyMjIyMgQ3VydmUKCmBgYHtyICdjdXJ2ZSd9CmdncGxvdChkYXRhID0gd2l0aHByZWRpY3Rpb25zLCBhZXMoeCA9IGFjdCwgeSA9IGdwYSkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fc21vb3RoKG1ldGhvZD0ibG9lc3MiLCBzZT1GQUxTRSwgc3Bhbj0uNCkgKwogIHNjYWxlX3lfY29udGludW91cyhicmVha3M9c2VxKDAsIDQsIDEpLCBtaW5vcl9icmVha3M9c2VxKDAuNSwgMy41LCAuNSkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgxMiwgMzYsIDIpLCBtaW5vcl9icmVha3M9TlVMTCkgKwogIGxhYnMoeCA9ICJBQ1QiLCB5ID0gIkdQQSIpCgpgYGAKCgojIyMjIyMgTGluZQoKYGBge3IgJ2xpbmUnfQpnZ3Bsb3QoZGF0YSA9IHdpdGhwcmVkaWN0aW9ucywgYWVzKHggPSBhY3QsIHkgPSBncGEpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIiwgc2U9RkFMU0UpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgwLCA0LCAxKSwgbWlub3JfYnJlYWtzPXNlcSgwLjUsIDMuNSwgLjUpKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMTIsIDM2LCAyKSwgbWlub3JfYnJlYWtzPU5VTEwpICsKICBsYWJzKHggPSAiQUNUIiwgeSA9ICJHUEEiKQpgYGAKCgojIyBEZXJpdmluZyB0aGUgbGVhc3Qgc3F1YXJlcyByZWdyZXNzaW9uIGxpbmUKCldlIHdhbnQgdG8gbWluaW1pemUgdGhlIHNxdWFyZWQgdmVydGljYWwgZGlzdGFuY2VzIGZyb20gcHJlZGljdGlvbnMgbWFkZSBieSBvdXIgbW9kZWwsICRcaGF0e3l9X2k9Yl8wK2JfMXhfaSQgdG8gZWFjaCBvYnNlcnZlZCBkYXRhIHBvaW50LCAkKHhfaSwgeV9pKSQuCgpFYWNoIG9mIHRoZXNlIHZlcnRpY2FsIGRpc3RhbmNlcyBjYW4gYmUgd3JpdHRlbiBhczogICRlX2kgPSB5X2ktKGJfMCtiXzF4X2kpPXlfaS1iXzAtYl8xeF9pJAoKPGJyIC8+CgohW10obHMucG5nKQoKPGJyIC8+CgpPdXIgZ29hbCBpcyB0byBmaW5kIHZhbHVlcyBmb3IgdGhlIHktaW50ZXJjZXB0ICQoYl8wKSQgYW5kIHNsb3BlICQoYl8xKSQgdGhhdCBtaW5pbWl6ZTogJFEgPVxzdW1fe2k9MX1ee259KHlfaS1iXzAtYl8xeF9pKV4yJC4KCjxiciAvPgoKVG8gbWluaW1pemUgYSBmdW5jdGlvbiwgd2UgY2FuIHNldCBpdHMgZmlyc3QgZGVyaXZhdGl2ZSBlcXVhbCB0byB6ZXJvIGFuZCBzb2x2ZSAoYW5kIHZlcmlmeSB0aGUgc2Vjb25kIGRlcml2YXRpdmUgaXMgcG9zaXRpdmUgYXQgdGhhdCB2YWx1ZSkuICBTaW5jZSB3ZSBoYXZlIHR3byB2YXJpYWJsZXMgaW4gJFEkLCB3ZSdsbCBuZWVkIHRvIHRha2UgcGFydGlhbCBkZXJpdmF0aXZlczoKCjxiciAvPgoKIyMjIyBQYXJ0aWFsIGRlcml2YXRpdmUgb2YgUSB3aXRoIHJlc3BlY3QgdG8gYjAKCiRcZnJhY3tccGFydGlhbCBRIH17XHBhcnRpYWwgYl8wfT1cZnJhY3tccGFydGlhbCBcc3VtKHlfaS1iXzAtYl8xeF9pKV4yIH17XHBhcnRpYWwgYl8wfT0yXHN1bSh5X2ktYl8wLWJfMXhfaSlcZnJhY3tccGFydGlhbCBcc3VtKHlfaS1iXzAtYl8xeF9pKSB9e1xwYXJ0aWFsIGJfMH09LTJcc3VtKHlfaS1iXzAtYl8xeF9pKSQKCjxiciAvPgoKU2V0IHRoaXMgcGFydGlhbCBkZXJpdmF0aXZlIGVxdWFsIHRvIHplcm86CgokLTJcc3VtKHlfaS1iXzAtYl8xeF9pKT0wJAoKJFxzdW0oeV9pLWJfMC1iXzF4X2kpPTAkCgokXHN1bXt5X2l9PW5iXzArYl8xXHN1bXt4X2l9JAoKPGJyIC8+CgojIyMjIFBhcnRpYWwgZGVyaXZhdGl2ZSBvZiBRIHdpdGggcmVzcGVjdCB0byBiMQoKJFxmcmFje1xwYXJ0aWFsIFEgfXtccGFydGlhbCBiXzF9PVxmcmFje1xwYXJ0aWFsIFxzdW0oeV9pLWJfMC1iXzF4X2kpXjIgfXtccGFydGlhbCBiXzF9PTJcc3VtKHlfaS1iXzAtYl8xeF9pKVxmcmFje1xwYXJ0aWFsIFxzdW0oeV9pLWJfMC1iXzF4X2kpIH17XHBhcnRpYWwgYl8wfT0tMlxzdW17eF9pfSh5X2ktYl8wLWJfMXhfaSkkCgo8YnIgLz4KClNldCB0aGlzIHBhcnRpYWwgZGVyaXZhdGl2ZSBlcXVhbCB0byB6ZXJvOgoKJC0yXHN1bXt4X2l9KHlfaS1iXzAtYl8xeF9pKT0wJAoKJFxzdW17eF9pfSh5X2ktYl8wLWJfMXhfaSk9MCQKCiRcc3Vte3hfaXlfaX0tYl8wXHN1bXt4X2l9LWJfMVxzdW17eF9pXjJ9PTAkCgokXHN1bXt4X2l5X2l9PWJfMFxzdW17eF9pfStiXzFcc3Vte3hfaV4yfT0wJAoKPGJyIC8+CgojIyMjIFNvbHZlIHRoaXMgc3lzdGVtIG9mIG5vcm1hbCBlcXVhdGlvbnMKCiRcc3Vte3lfaX09bmJfMCtiXzFcc3Vte3hfaX0kCgokXHN1bXt4X2l5X2l9PWJfMFxzdW17eF9pfStiXzFcc3Vte3hfaV4yfSQKCjxiciAvPgoKV2UgY2FuIHNvbHZlIHRoaXMgc3lzdGVtIHRvIGdldDoKCiRiXzE9XGZyYWN7blxzdW17eF9peV9pfS1cc3Vte3hfaX1cc3Vte3lfaX19e25cc3Vte3hfaV4yfSBcIC0gKFxzdW17eF9pfSleMn0kCgphbmQKCiRiXzA9XGZyYWN7XHN1bXt4X2leMn1cc3Vte3lfaX0tXHN1bXt4X2l9XHN1bXt4X2l5X2l9fXtuXHN1bXt4X2leMn0gXCAtIChcc3Vte3hfaX0pXjJ9PVxmcmFje1xzdW17eV9pfX17bn0tYl8xXGZyYWN7XHN1bXt4X2l9fXtufT1cb3ZlcmxpbmV7eX0tYl8xXG92ZXJsaW5le3h9JAoKPGJyIC8+CgpVc2luZyBmb3JtdWxhcyBmb3IgdmFyaWFuY2UgYW5kIGNvdmFyaWFuY2UsIHdlIGNhbiByZXdyaXRlICRiXzEkIGFzOgoKJGJfMT1cZnJhY3tuXHN1bXt4X2l5X2l9LVxzdW17eF9pfVxzdW17eV9pfX17blxzdW17eF9pXjJ9IFwgLSAoXHN1bXt4X2l9KV4yfT1cZnJhY3tzX3t4eX19e3NfeF4yfT1yXGZyYWN7c195fXtzX3h9JAoKPGJyIC8+CgoqKioqKgoKIyMjIyBUaGUgbGluZSB0aGF0IG1pbmltaXplcyB0aGUgc3VtIG9mIHNxdWFyZWQgZXJyb3JzIGhhczoKCi0gJFx0ZXh0cm17c2xvcGV9PWJfMT1yXGZyYWN7c195fXtzX3h9JAotICRcdGV4dHJte3ktaW50ZXJjZXB0fT1iXzA9XG92ZXJsaW5le3l9LWJfMVxvdmVybGluZXt4fSQKCioqKioqCgoxMC4gRm9yIHRoaXMgc2FtcGxlIG9mIDMwIG9ic2VydmF0aW9uczo8YnIgLz48YnIgLz4kXG92ZXJsaW5le1l9PVxvdmVybGluZXtHUEF9PTIuNTQxJCwgJHNfeT1zX3tHUEF9PTAuNjk0JDxiciAvPjxiciAvPiRcb3ZlcmxpbmV7WH09XG92ZXJsaW5le0FDVH09MjMuMCQsICRzX3g9c197QUNUfT01Ljg0MyQ8YnIgLz48YnIgLz4kbiA9IDMwJCwgJHJfe3kseH09cl97R1BBLEFDVH09MC45MDYkPGJyIC8+PGJyIC8+Q2FsY3VsYXRlIHRoZSBzbG9wZSBhbmQgeS1pbnRlcmNlcHQgZm9yIHRoZSBsZWFzdCBzcXVhcmVzIHJlZ3Jlc3Npb24gbGluZS4KCmBgYHtyICdjYWxjdWxhdGUtT0xTLWZvci1zYW1wbGUtZGF0YSd9CiMgR2V0IHN1bW1hcnkgc3RhdGlzdGljcwpmaXJzdHllYXIgJT4lCiAgc3VtbWFyaXplKG1lYW54ID0gbWVhbihhY3QpLCBzZHggPSBzZChhY3QpLAogICAgICAgICAgICBtZWFueSA9IG1lYW4oZ3BhKSwgc2R5ID0gc2QoZ3BhKSwKICAgICAgICAgICAgbiA9IG4oKSwgY29yID0gY29yKGFjdCwgZ3BhKSkKCiMgQ2FsY3VsYXRlIGIxCmZpcnN0eWVhciAlPiUKICBzdW1tYXJpemUoYjEgPSBjb3IoZ3BhLCBhY3QpICogc2QoZ3BhKSAvIHNkKGFjdCksCiAgICAgICAgICAgIGIwID0gbWVhbihncGEpIC0gKGIxICogbWVhbihhY3QpKSApCgpgYGAKCjxkaXYgc3R5bGU9IndpZHRoOjM1MHB4OyBoZWlnaHQ6MTUwcHgiPiFbXShUcmFuc3BhcmVudC5naWYpPC9kaXY+CgoqKioqKgoKIyMgRml0dGluZyBsaW5lYXIgbW9kZWxzIGluIFIKCkluIFIsIHdlIGNhbiB1c2UgdGhlIGBsbSgpYCBmdW5jdGlvbiB0byBmaXQgbGluZWFyIG1vZGVscy4gIEV4cGFuZCB0aGUgY29kZSAtLT4KCmBgYHtyICdsbS1mdW5jdGlvbid9CiMgbG0gPSBsaW5lYXIgbW9kZWwKIyBsbSh5IH4geCwgZGF0YSkKbG0oZ3BhIH4gYWN0LCBkYXRhID0gZmlyc3R5ZWFyKQojCiMgVGhlIG91dHB1dCBzaG93cyBjb2VmZmljaWVudHMKIyBJdCdzIHR5cGljYWxseSBtb3JlIHVzZWZ1bCB0byBzdG9yZSB0aGUgbW9kZWwKbW9kZWwxIDwtIGxtKGdwYSB+IGFjdCwgZGF0YSA9IGZpcnN0eWVhcikKIwojIFdlIGNhbiB0aGVuIGludmVzdGlnYXRlIHRoaXMgbW9kZWwKIyBTdW1tYXJ5IHN0YXRpc3RpY3MKc3VtbWFyeShtb2RlbDEpCiMKIyBDb2VmZmljaWVudHMKY29lZihtb2RlbDEpCiMKIyBCcm9vbSBwYWNrYWdlOiAgdGlkeSgpLCBnbGFuY2UoKSwgYXVnbWVudCgpIGZ1bmN0aW9ucwojIFRpZHkgdGhlIG1vZGVsIGNvZWZmaWNpZW50cywgc3RhbmRhcmQgZXJyb3JzLCBhbmQgcC12YWx1ZXMKdGlkeShtb2RlbDEpCiMKIyBHbGFuY2UgYXQgc3VtbWFyeSBzdGF0aXN0aWNzIGZvciB0aGUgbW9kZWwKZ2xhbmNlKG1vZGVsMSkKIwojIEF1Z21lbnQgdGhlIGRhdGEgd2l0aCBwcmVkaWN0aW9ucyBhbmQgcmVzaWR1YWxzIApoZWFkKGF1Z21lbnQobW9kZWwxKSkKIwojCiMgTW9kZWxyIHBhY2thZ2U6IGFkZF9wcmVkaWN0aW9ucygpIGZ1bmN0aW9uCiMgQWRkIHByZWRpY3Rpb25zIHRvIG91ciBkYXRhc2V0CmZpcnN0eWVhciA8LSBmaXJzdHllYXIgJT4lCiAgYWRkX3ByZWRpY3Rpb25zKG1vZGVsMSkKIwojIE5vdyB3ZSBjYW4gcGxvdCB0aGVzZSBwcmVkaWN0aW9ucyBhcyBhIGxpbmUKZ2dwbG90KGRhdGEgPSBmaXJzdHllYXIsIGFlcyh4ID0gYWN0LCB5ID0gZ3BhKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9saW5lKGFlcyh5ID0gcHJlZCksIGNvbG9yPSJibHVlIiwgc2l6ZT0xKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1zZXEoMCwgNCwgMSksIG1pbm9yX2JyZWFrcz1zZXEoMC41LCAzLjUsIC41KSkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKDEyLCAzNiwgMiksIG1pbm9yX2JyZWFrcz1OVUxMKQoKCmBgYAoKPGJyIC8+CgoqKioqKgoKIyMgSXMgdGhlICpiZXN0KiBsaW5lIGFueSBnb29kPwoKSnVzdCBiZWNhdXNlIHdlJ3JlIGFibGUgdG8gY2FsY3VsYXRlIHRoZSAqYmVzdCogbGluZSwgaXQgZG9lc24ndCBtZWFuIHRoYXQgbGluZSBpcyBtZWFuaW5nZnVsLiAgRm9yIGV4YW1wbGUsIGhlcmUncyB0aGUgbGluZSB0aGF0IGJlc3QgZml0cyB0aGlzIGRhdGE6CgpgYGB7ciAnYmVzdC1saW5lLWlzbnQtZ29vZCd9CiMgU2ltdWxhdGUgZGF0YSBmb3JtaW5nIGEgcGFyYWJvbGEKc2ltX2RhdGEgPC0gdGliYmxlKAogIHggPSBjKC01OjUpLAogIHkgPSB4XjIKKQoKIyBQbG90IHRoZSBkYXRhCiMgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikgcGxvdHMgdGhlIGxpbmUgb2YgYmVzdCBmaXQKZ2dwbG90KGRhdGEgPSBzaW1fZGF0YSwgYWVzKHggPSB4LCB5ID0geSkpICsKICBnZW9tX3BvaW50KHNpemU9MikgKwogIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iLCBzZT1GQUxTRSkKCmBgYAoKPGJyIC8+CgpJdCBsb29rcyBsaWtlIHdlJ2xsIG5lZWQgc29tZSB3YXkgb2YgbWVhc3VyaW5nIGp1c3QgaG93IHdlbGwgYSBtb2RlbCAoYSBsaW5lKSBmaXRzIGEgZ2l2ZW4gZGF0YXNldC4gIExldCdzIGRlcml2ZSBzb21lIG9mIHRoZXNlIG1lYXN1cmVzIGZvciB0aHJlZSBkaWZmZXJlbnQgZGF0YXNldHM6CgotIE9uZSBpbiB3aGljaCB0aGUgYmVzdCBsaW5lIGZpdHMgdGhlIGRhdGEgcGVyZmVjdGx5Ci0gT25lIGZvciBvdXIgc2FtcGxlIGRhdGEgKHdoZXJlIHRoZSBiZXN0IGxpbmUgc2VlbXMgdG8gZml0IHByZXR0eSB3ZWxsKQotIE9uZSBpbiB3aGljaCB0aGUgYmVzdCBsaW5lIGRvZXMgbm90IGZpdCB0aGUgZGF0YSBhdCBhbGwKCldlJ2xsIGNvbXBsZXRlIHRoZSBmb2xsb3dpbmcgdGFibGUgdG8gc2VlIHRoZSB2YWx1ZSBvZiBlYWNoIG1lYXN1cmUgaW4gZWFjaCBjYXNlOgoKPGJyIC8+CgoKIyMjIE1lYXN1cmVzIG9mIGZpdAoKIVtdKGZpdGluZGljZXMucG5nKQoKIyMjIyBTU0UKCjExLiBXZSBhbHJlYWR5IGtub3cgdGhlICpiZXN0KiBsaW5lIGlzIHRoZSBvbmUgdGhhdCBtaW5pbWl6ZXMgdGhlIHN1bSBvZiBzcXVhcmVkIGVycm9ycyAoU1NFKS4gIENhbiB3ZSB1c2UgU1NFIHRvIGV2YWx1YXRlIGhvdyB3ZWxsIGEgbGluZSBmaXRzIHRoZSBkYXRhPzxiciAvPjxiciAvPldoYXQgd291bGQgU1NFIGVxdWFsIGlmIGEgbGluZSBwZXJmZWN0bHkgZml0IHRoZSBkYXRhPzxiciAvPjxiciAvPk5vdyBsZXQncyBjb25zaWRlciBhIHNpdHVhdGlvbiB3aGVyZSB0aGUgYmVzdCBsaW5lIGRvZXNuJ3QgZml0IHRoZSBkYXRhIGF0IGFsbC4gIEluIHRoaXMgc2l0dWF0aW9uLCBYIGFuZCBZIHdvdWxkIGJlIHVuY29ycmVsYXRlZCAtLSBrbm93aW5nIHRoZSB2YWx1ZSBvZiBYIHdvdWxkIG5vdCB0ZWxsIHlvdSBhbnl0aGluZyBhYm91dCB0aGUgdmFsdWUgb2YgWS4gIFRvIGZpbmQgdGhlIGJlc3QgZml0dGluZyBsaW5lLCB3aGF0IHZhbHVlIG9mICRhJCB3b3VsZCBtaW5pbWl6ZTogJFxTaWdtYXsoWS1hKV4yfSQuPGJyIC8+PGJyIC8+SWYgeW91IHN1YnN0aXR1dGUgeW91ciBhbnN3ZXIgZm9yICRhJCwgd2hhdCBkb2VzIHRoaXMgZXhwcmVzc2lvbiByZXByZXNlbnQ/ICBXaGF0J3MgdGhlIG1heGltdW0gdmFsdWUgZm9yIFNTRT8gCgpgYGB7ciAnU1NFLWZyb20tbGluZWFyLW1vZGVsJ30KIyBMYXRlciwgd2UnbGwgc2VlIHdoYXQgdGhpcyBBTk9WQSB0YWJsZSByZXByZXNlbnRzCiMgRm9yIG5vdywgaXQgZ2l2ZXMgdXMgU1NFIChTdW0gU3EgUmVzaWR1YWxzKQphbm92YShtb2RlbDEpCgojIFdlIGNhbiBhbHNvIGNhbGN1bGF0ZSB0aGlzIGRpcmVjdGx5IHdpdGggdGhlIHJlc2lkdWFscwpzdW0ocmVzaWR1YWxzKG1vZGVsMSleMikKYGBgCgo8ZGl2IHN0eWxlPSJ3aWR0aDozNTBweDsgaGVpZ2h0OjMwMHB4Ij4hW10oVHJhbnNwYXJlbnQuZ2lmKTwvZGl2PgoKMTIuIFdoYXQgd2lsbCBoYXBwZW4gdG8gdGhlIHZhbHVlIG9mIFNTRSBpZiB3ZSBhZGQgKmFueSogYWRkaXRpb25hbCBwb2ludCB0byBvdXIgZGF0YT8gIFdoeSBpcyB0aGlzIGEgcHJvYmxlbT8gCgo8ZGl2IHN0eWxlPSJ3aWR0aDozNTBweDsgaGVpZ2h0OjEwMHB4Ij4hW10oVHJhbnNwYXJlbnQuZ2lmKTwvZGl2PgoKIyMjIyBNU0UsIFJTRSwgYW5kIFJNU0UKClBlcmhhcHMgYSBiZXR0ZXIgbWVhc3VyZSBvZiBmaXQgd291bGQgYmUgdGhlICphdmVyYWdlKiBzcXVhcmVkIGVycm9yICh0aGUgYXZlcmFnZSBzcXVhcmVkIGRpc3RhbmNlIGZyb20gb2JzZXJ2YXRpb24gdG8gdGhlIHByZWRpY3Rpb24gbGluZSkuICBPbmUgc2ltcGxlIGVzdGltYXRlIG9mIHRoaXMgYXZlcmFnZSBzcXVhcmVkIGVycm9yIHdvdWxkIGJlIE1TRToKCiRzX3t5fHh9XjI9XGZyYWN7U1NfRX17ZGZfRX09XGZyYWN7XFNpZ21hKHlfaS1caGF0e3l9KV4yfXtuLTJ9JAoKCjEzLiBXaGF0IHdvdWxkICRzX3t5fHh9XjIkIGJlIGluIGEgc2l0dWF0aW9uIHdpdGggcGVyZmVjdCBmaXQ/ICB3b3JzdCBmaXQ/ICBXaGF0J3MgdGhlIG1heGltdW0gdmFsdWUgb2YgJHNfe3l8eH1eMiQ/ICAKCmBgYHtyICdzLXktZ2l2ZW4teC1zcXVhcmVkJ30KIyBNU0UgaXMgZGlzcGxheWVkIGluIHRoZSBBTk9WQSBzdW1tYXJ5IHRhYmxlLiAgQ2FsY3VsYXRlIGRpcmVjdGx5IHdpdGg6CnN1bShyZXNpZHVhbHMobW9kZWwxKV4yKSAvIG1vZGVsMSRkZi5yZXNpZHVhbApgYGAKCjxkaXYgc3R5bGU9IndpZHRoOjM1MHB4OyBoZWlnaHQ6MzAwcHgiPiFbXShUcmFuc3BhcmVudC5naWYpPC9kaXY+CgoKMTQuIEl0IHdvdWxkIGJlIGVhc2llciB0byBpbnRlcnByZXQgaWYgb3VyIG1lYXN1cmUgd2VyZSBub3QgaW4gKnNxdWFyZWQqIHVuaXRzLiAgU3VwcG9zZSB3ZSBjYWxjdWxhdGUgUlNFIChyZXNpZHVhbCBzdGFuZGFyZCBlcnJvcik6ICAkUlNFPXNfe3l8eH09XHNxcnR7c197eXx4fV4yfT1cc3FydFxmcmFje1NTX0V9e2RmX0V9PVxzcXJ0XGZyYWN7XFNpZ21hKHlfaS1caGF0e3l9KV4yfXtuLTJ9JC4gIFdoYXQgd291bGQgUlNFIGJlIGluIGEgc2l0dWF0aW9uIHdpdGggcGVyZmVjdCBmaXQ/ICB3b3JzdCBmaXQ/ICAKCmBgYHtyICdSU0UnfQojIFdlIGNhbiBjYWxjdWxhdGUgaXQgZGlyZWN0bHkgd2l0aC4uLgpzcXJ0KCBzdW0ocmVzaWR1YWxzKG1vZGVsMSleMikgLyBtb2RlbDEkZGYucmVzaWR1YWwgKQoKIyBXZSBjYW4gYWxzbyBnbGFuY2UgYXQgaXQgKHNpZ21hKSB3aXRoIHRoZSBicm9vbSBwYWNrYWdlCmdsYW5jZShtb2RlbDEpCgpgYGAKCjxkaXYgc3R5bGU9IndpZHRoOjM1MHB4OyBoZWlnaHQ6MjUwcHgiPiFbXShUcmFuc3BhcmVudC5naWYpPC9kaXY+CgoKMTUuIFRoZSAkbi0yJCBpbiB0aGUgZGVub21pbmF0b3IgaXMgdXNlZCB0byBwcm92aWRlIGFuIHVuYmlhc2VkIGVzdGltYXRlLiAgQW5vdGhlciBwb3B1bGFyIG1lYXN1cmUgb2YgbW9kZWwgZml0IGlzIFJNU0UgKHJvb3QgbWVhbiBzcXVhcmUgZXJyb3IpOiAgJFJNU0U9XHNxcnRcZnJhY3tcU2lnbWEoeV9pLVxoYXR7eX0pXjJ9e259JC4gIFNrZXRjaCBhIHJvdWdoIHJlcHJlc2VudGF0aW9uIGZvciB3aGF0IFJNU0UgcmVwcmVzZW50cy4gIFRoZSBjb2RlIGNhbGN1bGF0ZXMgUk1TRSBmb3Igb3VyIHNhbXBsZSBkYXRhLgoKYGBge3IgJ1JNU0UnfQojIFRoZSBtb2RlbHIgcGFja2FnZSBoYXMgYSBybXNlKCkgZnVuY3Rpb24Kcm1zZShtb2RlbDEsIGZpcnN0eWVhcikKCiMgQ2FsY3VsYXRlIGl0IGRpcmVjdGx5LiAgbGVuZ3RoID0gc2FtcGxlIHNpemUKc3FydCggc3VtKHJlc2lkdWFscyhtb2RlbDEpXjIpIC8gbGVuZ3RoKGZpcnN0eWVhciRhY3QpICkKCmBgYAoKPGRpdiBzdHlsZT0id2lkdGg6MzUwcHg7IGhlaWdodDoyMDBweCI+IVtdKFRyYW5zcGFyZW50LmdpZik8L2Rpdj4KCgoxNi4gU28gZmFyLCBhbGwgb3VyIG1lYXN1cmVzIG9mIGZpdCBhcmUgdW5ib3VuZGVkLiAgSWRlYWxseSwgd2UnZCBtaWdodCBsaWtlIGEgbWVhc3VyZSB0aGF0IGhhcyBhIGtub3duIG1pbmltdW0gYW5kIG1heGltdW0uICBTdXBwb3NlIHdlIHRvb2sgb3VyIHRvdGFsIHN1bXMgb2Ygc3F1YXJlcyAodGhlIHRvdGFsIHZhcmlhdGlvbiBpbiBZKSBhbmQgKnBhcnRpdGlvbmVkKiBpdC4gIEV4cGxhaW4gd2hhdCB0aGUgZm9sbG93aW5nIGZvcm11bGEgcmVwcmVzZW50cywgdGhlbiBjYWxjdWxhdGUgaXRzIHZhbHVlIGluIHRoZSBwZXJmZWN0IGFuZCB3b3JzdC1maXQgc2NlbmFyaW9zOjxiciAvPjxiciAvPiRcZnJhY3tTU19FfXtTU19ZfT1cZnJhY3tcU2lnbWEoeV9pLVxvdmVybGluZXtZfSleMn17XFNpZ21hKHlfaS1caGF0e1l9KV4yfT0xLVJeMiQKCmBgYHtyICcxLW1pbnVzLXJzcXVhcmUnfQojIFRoZSBtb2RlbHIgcGFja2FnZSBoYXMgYW4gcnNxdWFyZSgpIGZ1bmN0aW9uCjEgLSByc3F1YXJlKG1vZGVsMSwgZmlyc3R5ZWFyKQoKYGBgCgo8ZGl2IHN0eWxlPSJ3aWR0aDozNTBweDsgaGVpZ2h0OjMwMHB4Ij4hW10oVHJhbnNwYXJlbnQuZ2lmKTwvZGl2PgoKCjE3LiBUaGF0IHNlZW1zIGJhY2t3YXJkcyAtLSB0aGUgbWVhc3VyZSBzaG91bGQgYmUgemVybyB3aGVuIHdlIGhhdmUgbm8gZml0IGFuZCAxLjAgd2hlbiB3ZSBoYXZlIHBlcmZlY3QgZml0LiAgTGV0J3MgdHJ5IHRoaXM6PGJyIC8+PGJyIC8+JFJeMj1cZnJhY3tTU19ZLVNTX0V9e1NTX1l9PVxmcmFje1NTX3tyZWd9fXtTU19ZfT1cZnJhY3tcU2lnbWEoXGhhdHt5fS1cb3ZlcmxpbmV7WX0pXjJ9e1xTaWdtYSh5X2ktXGhhdHtZfSleMn0kPGJyIC8+PGJyIC8+VGhpcyBpcyB0aGUgKipjb2VmZmljaWVudCBvZiBkZXRlcm1pbmF0aW9uKiogKHdoaWNoIGhhcyB0aGUgc2FtZSBpbnRlcnByZXRhdGlvbiBhcyBldGEtc3F1YXJlZCBpbiBBTk9WQSkuICBGaWxsLWluLXRoZS1ibGFua3MgdG8gc2hvdyB0aGUgdmFsdWUgb2YgJFJeMiQgaW4gcGVyZmVjdCBmaXQgYW5kIHdvcnN0IGZpdCBzY2VuYXJpb3MuCgpgYGB7ciAncnF1YXJlJ30KIyBUaGUgbW9kZWxyIHBhY2thZ2UgaGFzIGFuIHJzcXVhcmUoKSBmdW5jdGlvbgpyc3F1YXJlKG1vZGVsMSwgZmlyc3R5ZWFyKQoKIyBXZSBjYW4gZ2xhbmNlIGF0IGl0IHdpdGggdGhlIGJyb29tIHBhY2thZ2UKZ2xhbmNlKG1vZGVsMSkKCiMgSXQgYWxzbyBhcHBlYXJzIGluIHRoZSBzdW1tYXJ5CnN1bW1hcnkobW9kZWwxKQoKIyBXZSBjYW4gYWxzbyBnZXQgaXQgZnJvbSB0aGUgQU5PVkEgdGFibGUKYW5vdmEobW9kZWwxKQoxMS40ODcvKDExLjQ4NysyLjQ5ODIpCmBgYAoKPGRpdiBzdHlsZT0id2lkdGg6MzUwcHg7IGhlaWdodDozMDBweCI+IVtdKFRyYW5zcGFyZW50LmdpZik8L2Rpdj4KCgoxOC4gQXMgd2UnbGwgc2VlIGxhdGVyLCBub25lIG9mIHRoZXNlIG1lYXN1cmVzIGFyZSBwZXJmZWN0LiAgUi1zcXVhcmVkIGFuZCBSTVNFIHNlZW0gdG8gYmUgdGhlIG1vc3Qgd2lkZWx5IHVzZWQsIHRob3VnaC4gIElkZW50aWZ5IGF0IGxlYXN0IG9uZSBhZHZhbnRhZ2UgZWFjaCBtZWFzdXJlIGhhcyBvdmVyIHRoZSBvdGhlci4gIAoKCjxkaXYgc3R5bGU9IndpZHRoOjM1MHB4OyBoZWlnaHQ6MjUwcHgiPiFbXShUcmFuc3BhcmVudC5naWYpPC9kaXY+CgoqKioqKgoKIyMgQ29uZGl0aW9ucyBvZiBsaW5lYXIgcmVncmVzc2lvbgoKLSAqKlZhbGlkaXR5Kio6IFRoZSBkYXRhIHdlJ3JlIGFuYWx5emluZyBtYXBzIHRvIHRoZSByZXNlYXJjaCBxdWVzdGlvbiB3ZSBhcmUgdHJ5aW5nIHRvIGFuc3dlcgoKLSAqKkFkZGl0aXZpdHkgYW5kIExpbmVhcml0eSoqOiBUaGUgZGV0ZXJtaW5pc3RpYyBjb21wb25lbnQgb2YgdGhlIG1vZGVsIGlzIGEgbGluZWFyIGZ1bmN0aW9uIG9mIHRoZSBwcmVkaWN0b3JzLgogIC0gRGlhZ25vc2lzOiBMb29rIGF0IHBsb3RzIG9mIG9ic2VydmVkIHZzIHByZWRpY3RlZCBvciByZXNpZHVhbHMgdnMgcHJlZGljdGVkIHZhbHVlcy4gVGhlIHBvaW50cyBzaG91bGQgYmUgc3ltbWV0cmljYWxseSBkaXN0cmlidXRlZCBhcm91bmQgYSBkaWFnb25hbCBsaW5lIGluIHRoZSBmb3JtZXIgcGxvdCBvciBhcm91bmQgaG9yaXpvbnRhbCBsaW5lIGluIHRoZSBsYXR0ZXIgcGxvdCwgd2l0aCBhIHJvdWdobHkgY29uc3RhbnQgdmFyaWFuY2UuCgotICoqSW5kZXBlbmRlbnQgZXJyb3JzKio6IE5vIGNvcnJlbGF0aW9uIGFtb25nIGVycm9ycwogIC0gRGlhZ25vc2lzOiBJZiB5b3UgaGF2ZSB0aW1lIHNlcmllcyBkYXRhLCBiZSBjYXJlZnVsIHRoYXQgY29uc2VjdXRpdmUgZXJyb3JzIGFyZSBub3QgcmVsYXRlZC4KCi0gKipFcXVhbCB2YXJpYW5jZSBvZiBlcnJvcnMgKGhvbW9zY2VkYXN0aWNpdHkpKio6IFRoZSB2YXJpYW5jZSBpbiB0aGUgZXJyb3JzIGlzIHRoZSBzYW1lIGFjcm9zcyBhbGwgbGV2ZWxzIG9mIFguCiAgLSBEaWFnbm9zaXM6IExvb2sgYXQgdGhlIHBsb3Qgb2YgcmVzaWR1YWxzIHZzIHByZWRpY3RlZCB2YWx1ZXMuIElmIHRoZSByZXNpZHVhbHMgZ3JvdyBsYXJnZXIgYXMgYSBmdW5jdGlvbiBvZgpYLCB5b3UgaGF2ZSBhIHByb2JsZW0uCgotICoqTm9ybWFsaXR5IG9mIGVycm9ycyoqOiBUaGUgdmFyaWFuY2UgaW4gdGhlIGVycm9ycyBpcyB0aGUgc2FtZSBhY3Jvc3MgYWxsIGxldmVscyBvZiBYLgogIC0gRGlhZ25vc2lzOiBMb29rIGF0IGEgUC1QIG9yIFEtUSBwbG90IG9mIHRoZSByZXNpZHVhbHMuIFRoZSByZXNpZHVhbHMgc2hvdWxkIGZhbGwgbmVhciB0aGUgZGlhZ29uYWwgbGluZS4KWW91IGNvdWxkIGFsc28gcnVuIGEgdGVzdCBmb3Igbm9ybWFsaXR5LCBsaWtlIHRoZSBTaGFwaXJvLVdpbGsgb3IgS29sb2dvcm92LVNtaXJub3YgdGVzdHMuIE5vdGUgdGhhdCB0aGUgZGVwZW5kZW50IGFuZCBpbmRlcGVuZGVudCB2YXJpYWJsZXMgaW4gYSByZWdyZXNzaW9uIG1vZGVsIGRvIG5vdCBuZWVkIHRvIGJlIG5vcm1hbGx5IGRpc3RyaWJ1dGVkIGJ5IHRoZW1zZWx2ZXMtLW9ubHkgdGhlIHByZWRpY3Rpb24gZXJyb3JzIG5lZWQgdG8gYmUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQKCjxiciAvPgoKMTkuIFRoZSBgcGxvdCgpYCBmdW5jdGlvbiBkaXNwbGF5cyBzb21lIGRpYWdub3N0aWMgcGxvdHMgZm9yIG91ciBtb2RlbC4gIEV2YWx1YXRlIHRoZSBjb25kaXRpb25zIGxpc3RlZCBhYm92ZSBiYXNlZCBvbiBvdXIgZGF0YXNldCwgcXVlc3Rpb24gb2YgaW50ZXJlc3QsIGFuZCB0aGUgZGlhZ25vc3RpYyBwbG90cy4gIAoKCmBgYHtyICdwbG90LW1vZGVsLWRpYWdub3N0aWNzJ30KcGxvdChtb2RlbDEpCmBgYAoKPGRpdiBzdHlsZT0id2lkdGg6MzUwcHg7IGhlaWdodDozMDBweCI+IVtdKFRyYW5zcGFyZW50LmdpZik8L2Rpdj4KCioqKioqCgojIyBSZWdyZXNzaW9uOiBJbmZlcmVuY2Ugb24gc2xvcGUKCkxldCdzIGZpbmFsbHkgdXNlIG91ciByZWFsIGRhdGFzZXQuICBUbyBjaGFuZ2UgdGhpbmdzIHVwIGEgYml0LCBsZXQncyByZWdyZXNzICoqRmFsbCBzZW1lc3RlciBHUEFzKiogb24gKipoaWdoIHNjaG9vbCBHUEFzKiouICBNYWtlIHN1cmUgeW91IGNhbiBpbnRlcnByZXQgYWxsIHRoaXMgb3V0cHV0OgoKYGBge3IgJ2xpbmVhci1tb2RlbC1mb3ItYWxsLWRhdGEnfQpmdWxsX21vZGVsIDwtIGxtKGZhbGxncGEgfiBoc2dwYSwgZGF0YSA9IHNhdSkKc3VtbWFyeShmdWxsX21vZGVsKQoKcm1zZShmdWxsX21vZGVsLCBkYXRhID0gc2F1KQoKcGxvdChmdWxsX21vZGVsKQoKYGBgCgo8YnIgLz4KCkVhcmxpZXIsIHdlIGxlYXJuZWQgdGhhdCB3ZSBhbHdheXMgZXhwZWN0IHRvIGdldCBhIG5vbi16ZXJvIGNvcnJlbGF0aW9uIGluICphbnkqIHNhbXBsZSBvZiBkYXRhLiAgU2ltaWxhcmx5LCB3ZSBleHBlY3QgbGluZWFyIG1vZGVscyB3aXRoIG5vbi16ZXJvIHNsb3BlcyBpbiBhbnkgc2FtcGxlIG9mIGRhdGEgKGV2ZW4gaWYgd2UgaGF2ZSByZWFzb24gdG8gYmVsaWV2ZSB0aGUgdmFyaWFibGVzIGFyZSB1bmNvcnJlbGF0ZWQpLiAgCgpPdXIgbW9kZWwgdG8gcHJlZGljdCBmYWxsIHNlbWVzdGVyIEdQQXMgaXM6ICBgRmFsbEdQQSA9IDAuMDU4ICsgMC45MjYoaHNHUEEpYAoKRG9lcyB0aGUgbWFnbml0dWRlIG9mIHRoaXMgc2xvcGUgKDAuOTI2KSBpbXBseSBoaWdoIHNjaG9vbCBhbmQgY29sbGVnZSBHUEFzIGhhdmUgYSByZWxhdGlvbnNoaXAgZm9yIG91ciBwb3B1bGF0aW9uIG9mIGludGVyZXN0IG9yIGNvdWxkIHdlIGhhdmUgb2J0YWluZWQgYSBzbG9wZSBvZiB0aGlzIG1hZ25pdHVkZSBldmVuIGlmIHRoZSB2YXJpYWJsZXMgYXJlIHVuY29ycmVsYXRlZD8KCjIwLiBFeHBsYWluIGhvdyB3ZSdsbCB1c2UgcmFuZG9taXphdGlvbi1iYXNlZCBtZXRob2RzIHRvIHRlc3QgYSBudWxsIGh5cG90aGVzaXMgdGhhdCB0aGUgc2xvcGUgb2Ygb3VyIGxpbmVhciBtb2RlbCBpcyB6ZXJvLiAgSWYgaXQgaGVscHMsIGV4cGxhaW4gd2hhdCB0aGUgdHdvIHJhbmRvbWl6YXRpb25zIGRpc3BsYXllZCBiZWxvdyByZXByZXNlbnQuCgohW10ocmFuZHNsb3Blcy5wbmcpCgo8ZGl2IHN0eWxlPSJ3aWR0aDozNTBweDsgaGVpZ2h0OjIwMHB4Ij4hW10oVHJhbnNwYXJlbnQuZ2lmKTwvZGl2PgoKMjEuIEV4cGxhaW4gaG93IHdlJ2xsIHVzZSByYW5kb21pemF0aW9uLWJhc2VkIG1ldGhvZHMgdG8gdGVzdCBhIG51bGwgaHlwb3RoZXNpcyB0aGF0IHRoZSBzbG9wZSBvZiBvdXIgbGluZWFyIG1vZGVsIGlzIHplcm8uICAKCmBgYHtyICdyYW5kb21pemVkLXNsb3Blcyd9CiMgU3RvcmUgb3VyIG9ic2VydmVkIHNsb3BlIG9mIDA4MjYKb2JzZXJ2ZWRfc2xvcGUgPC0gZnVsbF9tb2RlbCRjb2VmZmljaWVudHNbMl0KCiMgUnVuIDEwLDAwMCByYW5kb21pemF0aW9ucywgc2h1ZmZsaW5nIHRoZSBoaWdoIHNjaG9vbCBHUEEKcmFuZF9zbG9wZXMgPC0gRG8oMTAwMDApICogbG0oZmFsbGdwYSB+IHNodWZmbGUoaHNncGEpLCBkYXRhID0gc2F1KQoKIyBUaGUgc2xvcGVzIGFyZSBzdG9yZWQgaW4gdGhlICJoc2dwYSIgdmFyaWFibGUuICBMZXQncyBwbG90IGFuZCBlc3RpbWF0ZSB0aGUgcC12YWx1ZS4KZ2dwbG90KGRhdGEgPSByYW5kX3Nsb3BlcywgYWVzKHggPSBoc2dwYSkpICsKICBnZW9tX2hpc3RvZ3JhbShmaWxsPSJsaWdodGJsdWUiLCBjb2xvcj0id2hpdGUiLCBhbHBoYSA9IDAuOCkgKwogIGxhYnMoCiAgICAgIHRpdGxlID0gIlJhbmRvbWl6ZWQgc2xvcGVzIiwKICAgICAgeCA9ICJTbG9wZSIKICAgICAgKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoLS4xLCAuMSwgLjAyNSksIG1pbm9yX2JyZWFrcz1OVUxMKSArCiAgICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSwgY29sb3I9ImdyZXkxMCIpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKGNvbG91ciA9ICJ3aGl0ZSIpLAogICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9saW5lKGNvbG91ciA9ICJ3aGl0ZSIsIHNpemU9LjE1KSwKICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAiZ3JleTkzIikKICApICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSAuMDc1LCB5ID0gNjAwLCBsYWJlbCA9IHBhc3RlKCJwID0gMCIpKQoKYGBgCgo8ZGl2IHN0eWxlPSJ3aWR0aDozNTBweDsgaGVpZ2h0OjMwMHB4Ij4hW10oVHJhbnNwYXJlbnQuZ2lmKTwvZGl2PgoKIyMjIEJvb3RzdHJhcCBjb25maWRlbmNlIGludGVydmFsIGZvciB0aGUgc2xvcGUKCjIwLiBFeHBsYWluIGhvdyB0aGUgZm9sbG93aW5nIGJvb3RzdHJhcCBjb25maWRlbmNlIGludGVydmFsIGZvciB0aGUgc2xvcGUgd2FzIGNvbnN0cnVjdGVkLgoKYGBge3IgJ2Jvb3RzdHJhcC1jaS1mb3Itc2xvcGUnLCBtZXNzYWdlPUZBTFNFfQpib290IDwtIERvKDEwMDAwKSAqIGxtKGZhbGxncGEgfiBoc2dwYSwgZGF0YSA9IHNhbXBsZShzYXUsIHJlcGxhY2U9VFJVRSkpCgpib290c3RyYXBDSSA8LSBjb25maW50KGJvb3QkaHNncGEsIGxldmVsID0gMC45NSwgbWV0aG9kID0gInF1YW50aWxlIikKbG93ZXIgPC0gYXMubnVtZXJpYyhib290c3RyYXBDSVsxXSkgIyBTdG9yZSBsb3dlciBDSSBib3VuZAp1cHBlciA8LSBhcy5udW1lcmljKGJvb3RzdHJhcENJWzJdKSAjIFN0b3JlIHVwcGVyIENJIGJvdW5kCgojIERlbnNpdHkgcGxvdApnZ3Bsb3QoZGF0YSA9IGJvb3QsIGFlcyh4ID0gaHNncGEpKSArCiAgZ2VvbV9kZW5zaXR5KGZpbGw9ImxpZ2h0Ymx1ZSIsIGNvbG9yPSJ3aGl0ZSIsIGFscGhhID0gMC44KSArCiAgbGFicygKICAgICAgdGl0bGUgPSAiQm9vdHN0cmFwIGRpc3RyaWJ1dGlvbiBvZiBjb3JyZWxhdGlvbnMiLAogICAgICB4ID0gImJvb3RzdHJhcCBjb3JyZWxhdGlvbnMiCiAgICAgICkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKC43LCAxLCAwLjA1KSwgbWlub3JfYnJlYWtzPU5VTEwpICsKICAgIHRoZW1lKAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDExLCBjb2xvcj0iZ3JleTEwIiksCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gIndoaXRlIiksCiAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2xpbmUoY29sb3VyID0gIndoaXRlIiwgc2l6ZT0uMTUpLAogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJncmV5OTMiKQogICkgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IGxvd2VyLCB5ID0gNSwgbGFiZWwgPSByb3VuZChsb3dlciwzKSkgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IHVwcGVyLCB5ID0gNSwgbGFiZWwgPSByb3VuZCh1cHBlciwzKSkgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IG1lZGlhbihib290JGhzZ3BhKSwgeSA9IDYsIGxhYmVsID0gcGFzdGUoIjk1JSIpKSArCiAgYW5ub3RhdGUoInNlZ21lbnQiLCB4ID0gbG93ZXIsIHhlbmQgPSB1cHBlciwgeSA9IDQsIHllbmQgPSA0LCBjb2xvciA9ICJyZWQiKQpgYGAKCgo8ZGl2IHN0eWxlPSJ3aWR0aDozNTBweDsgaGVpZ2h0OjIwMHB4Ij4hW10oVHJhbnNwYXJlbnQuZ2lmKTwvZGl2PgoKKioqKioKCiMjIE1vZGVsIHNlbGVjdGlvbgoKMjEuIFR3byB0cmVlcyBoYXZlIGZhbGxlbiBkb3duIGR1cmluZyBhIHdpbmR5IG5pZ2h0LiAgV2hpY2ggb2YgdGhlIGZvbGxvd2luZyB0d28gcG9zc2libGUgZXhwbGFuYXRpb25zIGRvIHlvdSB0aGluayBpcyBiZXR0ZXI/ICBXaHk/PGJyIC8+PGJyIC8+YSkgVGhlIHdpbmQgaGFzIGJsb3duIHRoZW0gZG93bi48YnIgLz48YnIgLz5iKSBUd28gbWV0ZW9yaXRlcyBoYXZlIGVhY2ggdGFrZW4gb25lIHRyZWUgZG93biwgYW5kIGFmdGVyIHRoYXQgaGl0IGVhY2ggb3RoZXIgYW5kIHJlbW92ZWQgYW55IHRyYWNlIG9mIHRoZW1zZWx2ZXMuCgo8ZGl2IHN0eWxlPSJ3aWR0aDozNTBweDsgaGVpZ2h0Ojc1cHgiPiFbXShUcmFuc3BhcmVudC5naWYpPC9kaXY+CgpJbiB0aGUgMTR0aCBjZW50dXJ5LCBbV2lsbGlhbSBvZiBPY2toYW1dKGh0dHBzOi8vc2ltcGxlLndpa2lwZWRpYS5vcmcvd2lraS9PY2NhbSUyN3NfcmF6b3IpIHdyb3RlICpFbnRpYSBub24gc3VudCBtdWx0aXBsaWNhbmRhIHByYWV0ZXIgbmVjZXNzaXRhdGVtKiwgd2hpY2ggdHJhbnNsYXRlcyB0bzogICpNb3JlIHRoaW5ncyBzaG91bGQgbm90IGJlIHVzZWQgdGhhbiBhcmUgbmVjZXNzYXJ5LioKCldoZW4gY29tcGFyaW5nIHR3byBjb21wZXRpbmcgdGhlb3JpZXMsIHdlIG1pZ2h0IHVzZSAqKk9ja2hhbSdzIFJhem9yKiogYW5kIHByZWZlciB0aGVvcmllcyB0aGF0IGFyZSBzaW1wbGVyIChtb3JlICpwYXJzaW1vbmlvdXMqIGluIHRoYXQgdGhleSByZXF1aXJlIGZld2VyIGFzc3VtcHRpb25zIG9yIGFyZSBlYXNpZXIgdG8gZXhwbGFpbikuICBPbiB0aGUgb3RoZXIgaGFuZCwgd2UgbWlnaHQgcHJlZmVyIG1vcmUgY29tcGxleCB0aGVvcmllcyBpZiB0aGV5IG1vcmUgYWNjdXJhdGVseSBwcmVkaWN0IGZ1dHVyZSBldmVudHMuCgpXaGVuIHdlIGNvbXBhcmUgY29tcGV0aW5nIHN0YXRpc3RpY2FsIG1vZGVscywgd2UnbGwgaGF2ZSB0byB0cmFkZS1vZmYgKiphY2N1cmFjeSoqIGFuZCAqKnNpbXBsaWNpdHkqKi4gIFdlJ2xsIGhhdmUgdG8gbGVhcm4gdG8gbmF2aWdhdGUgYmV0d2VlbjoKCi0gKipPdmVyZml0dGluZyoqIHdoaWNoIGxlYWRzIHRvIHBvb3IgcHJlZGljdGlvbiBieSBsZWFybmluZyB0b28gbXVjaCBmcm9tIG91ciBzYW1wbGUgZGF0YQoKYW5kCgotICoqVW5kZXJmaXR0aW5nKiogd2hpY2ggbGVhZHMgdG8gcG9vciBwcmVkaWN0aW9uIGJ5IGxlYXJuaW5nIHRvbyBsaXR0bGUgZnJvbSBvdXIgc2FtcGxlIGRhdGEKCiAgCgoyMi4gWW91J3ZlIGFscmVhZHkgZGVhbHQgd2l0aCB0aGlzIGlzc3VlIG9uY2UuICBFeHBsYWluLCBhZ2Fpbiwgd2h5IHlvdSBtaWdodCBwcmVmZXIgYSBsaW5lYXIgbW9kZWwgdG8gYSBjdXJ2ZSBvciBhIGNvbm5lY3QtdGhlLWRvdHMgbW9kZWwuCgo8ZGl2IHN0eWxlPSJ3aWR0aDozNTBweDsgaGVpZ2h0OjEwMHB4Ij4hW10oVHJhbnNwYXJlbnQuZ2lmKTwvZGl2PgoKCklmIHdlJ3JlIGludGVyZXN0ZWQgaW4gdGhlICpiZXN0KiBwcmVkaWN0aW9uIG9mIGZhbGwgc2VtZXN0ZXIgR1BBcywgb25lIGFwcHJvYWNoIHdlIG1pZ2h0IHRha2UgaXM6CgotIFN0YXJ0IHdpdGggYSBiYXNpYyBtb2RlbCB3aXRoIG9uZSBwcmVkaWN0b3I6ICAkeSA9IGJfMCArIGJfMVx0ZXh0cm17KGhzR1BBKX0kLiAgQ2FsY3VsYXRlIGEgbWVhc3VyZSBvZiBtb2RlbCBmaXQuCi0gQWRkIGEgc2Vjb25kIHByZWRpY3RvcjogICR5ID0gYl8wICsgYl8xXHRleHRybXsoaHNHUEEpfSArIGJfMlx0ZXh0cm17KEFDVCl9JC4gIENhbGN1bGF0ZSBhIG1lYXN1cmUgb2YgbW9kZWwgZml0LiAgSWYgdGhpcyBtb2RlbCBmaXRzIG5vdGljZWFibHkgYmV0dGVyLCBrZWVwIHRoaXMgbW9yZSBjb21wbGljYXRlZCBtb2RlbC4KLSBBZGQgYSB0aGlyZCBwcmVkaWN0b3I6ICAkeSA9IGJfMCArIGJfMVx0ZXh0cm17KGhzR1BBKX0gKyBiXzJcdGV4dHJteyhBQ1QpfSArIGJfM1x0ZXh0cm17KGZlbWFsZSl9JC4gIENhbGN1bGF0ZSBhIG1lYXN1cmUgb2YgbW9kZWwgZml0LiAgSWYgdGhpcyBtb2RlbCBmaXRzIG5vdGljZWFibHkgYmV0dGVyIHRoYW4gdGhlIHByZXZpb3VzIG1vZGVsLCBrZWVwIHRoaXMgbW9yZSBjb21wbGljYXRlZCBtb2RlbC4KCldlIGNvdWxkIGFsc28gc3RhcnQgd2l0aCBhIGNvbXBsaWNhdGVkIG1vZGVsLCByZW1vdmUgYSBwcmVkaWN0b3IsIGFuZCBkZXRlcm1pbmUgaWYgdGhlIGZpdCB3b3JzZW5zIG5vdGljZWFibHkuCgo8YnIgLz4KCgoyMy4gV2hlbiBjb21wYXJpbmcgbW9kZWxzLCBpdCdzIGhlbHBmdWwgdG8gd3JpdGUgb3V0IHRoZSAqKmZ1bGwgKG1vcmUgY29tcGxpY2F0ZWQpIG1vZGVsKiogYW5kIHRoZSAqKnJlZHVjZWQgbW9kZWwqKi4gIFdyaXRlIG91dCB0aGUgZnVsbCBhbmQgcmVkdWNlZCBtb2RlbHMgaWYgSSB3YW50IHRvIGtub3cgd2hldGhlciBBQ1Qgc2NvcmVzIHByZWRpY3QgZmFsbCBzZW1lc3RlciBHUEFzLgoKPGRpdiBzdHlsZT0id2lkdGg6MzUwcHg7IGhlaWdodDoyMDBweCI+IVtdKFRyYW5zcGFyZW50LmdpZik8L2Rpdj4KCjI0LiBCZWxvdywgSSd2ZSBza2V0Y2hlZCB0aGUgZnVsbCBhbmQgcmVkdWNlZCBtb2RlbHMgb24gb3VyIChzaW11bGF0ZWQpIGRhdGFzZXQuICBXaGF0IGRvZXMgU1NFIHJlcHJlc2VudCBpbiBvdXIgcmVkdWNlZCBtb2RlbD8gIFdpbGwgU1NFIGFsd2F5cyBnZXQgc21hbGxlciBhcyB3ZSBhZGQgbW9yZSBwcmVkaWN0b3IgdmFyaWFibGVzPwoKPGJyIC8+CgojIyMjIyB7LnRhYnNldCAudGFic2V0LWZhZGV9CgojIyMjIyMgUmVkdWNlZCBNb2RlbAoKYGBge3IgJ3JlZHVjZWQtbW9kZWwtcGxvdCcsIG1lc3NhZ2U9RkFMU0UsIGZpZy5oZWlnaHQgPSAzLjksIGZpZy53aWR0aCA9IDQuNX0KZ2dwbG90KGRhdGEgPSB3aXRocHJlZGljdGlvbnMsIGFlcyh4ID0gYWN0MiwgeSA9IGdwYSkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fYWJsaW5lKGFlcyhpbnRlcmNlcHQgPSBtZWFuKGdwYSksIHNsb3BlID0gMCksIGNvbG9yPSJibHVlIiwgc2l6ZT0xKSArIAogIGFubm90YXRlKCJyZWN0IiwgeG1pbj13aXRocHJlZGljdGlvbnMkYWN0MiwgeG1heD13aXRocHJlZGljdGlvbnMkYWN0MiArICh3aXRocHJlZGljdGlvbnMkZ3BhIC0gbWVhbih3aXRocHJlZGljdGlvbnMkZ3BhKSksCiAgICAgICAgICAgeW1pbj1tZWFuKHdpdGhwcmVkaWN0aW9ucyRncGEpLCB5bWF4PXdpdGhwcmVkaWN0aW9ucyRncGEsIGFscGhhPS4zLCBmaWxsPSJyZWQiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1zZXEoMCwgNCwgMSksIG1pbm9yX2JyZWFrcz1zZXEoMC41LCAzLjUsIC41KSkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKDEyLCAzNiwgMiksIG1pbm9yX2JyZWFrcz1OVUxMKSArIGNvb3JkX2ZpeGVkKHJhdGlvID0gMSwgeGxpbT1jKDE4LDI2KSkgKwogICAgbGFicyh4ID0gIkFDVCIsIHk9TlVMTCkgKwogICAgbGFicyh0aXRsZSA9IHBhc3RlKCJSZWR1Y2VkIG1vZGVsIFNTRSA9Iiwgcm91bmQoYW5vdmEobG0oZ3BhIH4gYWN0LCBkYXRhID0gZmlyc3R5ZWFyKSlbMiwyXSthbm92YShsbShncGEgfiBhY3QsIGRhdGEgPSBmaXJzdHllYXIpKVsxLDJdLDUpKSkKCgpgYGAKCiMjIyMjIyBGdWxsIE1vZGVsCgpgYGB7ciAnZnVsbC1tb2RlbC1wbG90JywgbWVzc2FnZT1GQUxTRSwgZmlnLmhlaWdodCA9IDMuOSwgZmlnLndpZHRoID0gNC41fQpnZ3Bsb3QoZGF0YSA9IHdpdGhwcmVkaWN0aW9ucywgYWVzKHggPSBhY3QyLCB5ID0gZ3BhKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9hYmxpbmUoYWVzKGludGVyY2VwdCA9IGIwLCBzbG9wZSA9IGIxKSwgY29sb3I9ImJsdWUiLCBzaXplPTEpICsgCiAgYW5ub3RhdGUoInJlY3QiLCB4bWluPXdpdGhwcmVkaWN0aW9ucyRhY3QyLCB4bWF4PXdpdGhwcmVkaWN0aW9ucyRhY3QyICsgKHdpdGhwcmVkaWN0aW9ucyRncGEgLSB3aXRocHJlZGljdGlvbnMkcHJlZCksCiAgICAgICAgICAgeW1pbj13aXRocHJlZGljdGlvbnMkZ3BhLCB5bWF4PXdpdGhwcmVkaWN0aW9ucyRwcmVkLCBhbHBoYT0uMywgZmlsbD0icmVkIikgKwogIHNjYWxlX3lfY29udGludW91cyhicmVha3M9c2VxKDAsIDQsIDEpLCBtaW5vcl9icmVha3M9c2VxKDAuNSwgMy41LCAuNSkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgxMiwgMzYsIDIpLCBtaW5vcl9icmVha3M9TlVMTCkgKyBjb29yZF9maXhlZChyYXRpbyA9IDEsIHhsaW09YygxOCwyNikpICsKICAgIGxhYnMoeCA9ICJBQ1QiLCB5PU5VTEwpICsKICBsYWJzKHRpdGxlID0gcGFzdGUoIkZ1bGwgbW9kZWwgU1NFID0iLCByb3VuZChhbm92YShsbShncGEgfiBhY3QsIGRhdGEgPSBmaXJzdHllYXIpKVsyLDJdLDUpKSkKCgpgYGAKIAojIyMjIyAuCgo8ZGl2IHN0eWxlPSJ3aWR0aDozNTBweDsgaGVpZ2h0OjEwMHB4Ij4hW10oVHJhbnNwYXJlbnQuZ2lmKTwvZGl2PgoKMjUuIFJlY2FsbCB0aGF0IGZvciB0aGlzIHNhbXBsZSBvZiBkYXRhOjxiciAvPjxiciAvPiRcb3ZlcmxpbmV7WX09XG92ZXJsaW5le0dQQX09Mi41NDEkLCAkc195PXNfe0dQQX09MC42OTQkPGJyIC8+PGJyIC8+JFxvdmVybGluZXtYfT1cb3ZlcmxpbmV7QUNUfT0yMy4wJCwgJHNfeD1zX3tBQ1R9PTUuODQzJDxiciAvPjxiciAvPiRuID0gMzAkLCAkcl97eSx4fT1yX3tHUEEsQUNUfT0wLjkwNiQ8YnIgLz48YnIgLz5Db21wbGV0ZSB0aGUgQU5PVkEgc3VtbWFyeSB0YWJsZSB0byBjb21wYXJlIG91ciBmdWxsIGFuZCByZWR1Y2VkIG1vZGVscz8gIEV4cGxhaW4gd2hhdCBlYWNoIHZhbHVlIHJlcHJlc2VudHMuICBGcm9tIHRoaXMsIHdoYXQgY291bGQgd2UgY29uY2x1ZGUgYWJvdXQgb3VyIG1vZGVscz8gIFlvdSBjYW4gZXN0aW1hdGUgdGhlIHAtdmFsdWUgd2l0aCB0aGUgW0YtZGlzdHJpYnV0aW9uIGFwcGxldF0oaHR0cDovL2xvY2s1c3RhdC5jb20vc3RhdGtleS90aGVvcmV0aWNhbF9kaXN0cmlidXRpb24vdGhlb3JldGljYWxfZGlzdHJpYnV0aW9uLmh0bWwjRikgYXQgaHR0cDovL2xvY2s1c3RhdC5jb20vc3RhdGtleS90aGVvcmV0aWNhbF9kaXN0cmlidXRpb24vdGhlb3JldGljYWxfZGlzdHJpYnV0aW9uLmh0bWwjRgoKYGBge3IgJ2Fub3ZhLXN1bW1hcnktdGFibGUnfQphbm92YShsbShncGEgfiBhY3QsIGRhdGEgPSBmaXJzdHllYXIpKQoKYGBgCgohW10oYW5vdmF0YWJsZS5wbmcpCgo8ZGl2IHN0eWxlPSJ3aWR0aDozNTBweDsgaGVpZ2h0OjUwMHB4Ij4hW10oVHJhbnNwYXJlbnQuZ2lmKTwvZGl2PgoKIyMjIHQtdGVzdCBmb3Igc2xvcGUKClRoZSBvbmx5IGRpZmZlcmVuY2UgYmV0d2VlbiBvdXIgZnVsbCBhbmQgcmVkdWNlZCBtb2RlbHMgaXMgdGhlICRiXzEkIGNvZWZmaWNpZW50ICh0aGUgc2xvcGUpLiBJZiAkYl8xPTAkLCB0aGUgZnVsbCBtb2RlbCB3b3VsZCBiZSB0aGUgc2FtZSBhcyBvdXIgcmVkdWNlZCBtb2RlbC4gQW5vdGhlciB3YXksIHRoZW4sIHRvIGNvbXBhcmUgb3VyIGZ1bGwgYW5kIHJlZHVjZWQgbW9kZWxzIHdvdWxkIGJlIHRvIHRlc3QgdGhlIGh5cG90aGVzaXM6ICRIXzA6Yl8xPTAkLiBXZSBjYW4gdGVzdCB0aGlzIGh5cG90aGVzaXMgd2l0aCBhIHQtdGVzdC4KCjI2LiBFeHBsYWluIHdoYXQgdGhlIGZvbGxvd2luZyBkZXJpdmF0aW9uIHNob3dzLiAgVGhlbiwgY29uZHVjdCB0aGlzIHQtdGVzdC4gIENvbXBhcmUgdGhlIHZhbHVlIG9mIHRoZSB0LXN0YXRpc3RpYyB0byB0aGUgdmFsdWUgb2YgdGhlIE1TUiAoRikgeW91IGNhbGN1bGF0ZWQgaW4gdGhlIEFOT1ZBIHN1bW1hcnkgdGFibGUuPGJyIC8+PGJyIC8+RmluYWxseSwgY29uZHVjdCBhIHRlc3Qgb2YgdGhlIGh5cG90aGVzaXM6ICRIXzA6cl97XHRleHRybXtHUEEsQUNUfX09MCQKCmBgYHtyICd0dGVzdC1mb3Itc2xvcGUnfQp0IDwtIGNvcihmaXJzdHllYXIkZ3BhLCBmaXJzdHllYXIkYWN0KSAvIHNxcnQoICgxLWNvcihmaXJzdHllYXIkZ3BhLCBmaXJzdHllYXIkYWN0KV4yKSAvIChsZW5ndGgoZmlyc3R5ZWFyJGFjdCktMikpCnQKdF4yCmBgYAoKCiFbXSh0dGVzdC5wbmcpCgo8ZGl2IHN0eWxlPSJ3aWR0aDozNTBweDsgaGVpZ2h0OjIwMHB4Ij4hW10oVHJhbnNwYXJlbnQuZ2lmKTwvZGl2PgoKIyMjIE9tbmlidXMgRi10ZXN0CgoyNy4gV2UgY2FuIGFsc28gY2FsY3VsYXRlIG91ciBNU1IgKEYtc3RhdGlzdGljKSB3aXRoIGZvcm11bGEgZGlzcGxheWVkIGJlbG93LiAgVmVyaWZ5IHRoaXMgeWllbGRzIHRoZSBzYW1lIHZhbHVlIHlvdSBjYWxjdWxhdGVkIGluIHRoZSBBTk9WQSBzdW1tYXJ5IHRhYmxlLiAgSW4gdGhlIGNvZGUsIEknbGwgY2FsY3VsYXRlIHRoaXMgbWFudWFsbHkgYW5kIHRoZW4gdXNlIHRoZSBgYW5vdmEoKWAgZnVuY3Rpb24uCgohW10ob21uaWJ1c0YucG5nKQoKYGBge3IgJ29tbmlidXNGJ30KIyBNYW51YWwgY2FsY3VsYXRpb24KUmYgPC0gY29yKGZpcnN0eWVhciRncGEsIGZpcnN0eWVhciRhY3QpClJyIDwtIDAKTiA8LSBsZW5ndGgoZmlyc3R5ZWFyJGdwYSkKa2YgPC0gMQprciA8LSAwCkYgPC0gKCAoKFJmXjIgLSBScl4yKSAvIChrZiAtIGtyKSkgLyAoKDEtUmZeMikvKE4ta2YtMSkpICkKRgoKIwojIEFOT1ZBIGZ1bmN0aW9uCmZ1bGxtb2RlbCA8LSBsbShncGEgfiBhY3QsIGRhdGE9Zmlyc3R5ZWFyKQpyZWR1Y2VkbW9kZWwgPC0gbG0oZ3BhIH4gMSwgZGF0YT1maXJzdHllYXIpCmFub3ZhKHJlZHVjZWRtb2RlbCwgZnVsbG1vZGVsKQpgYGAKPGRpdiBzdHlsZT0id2lkdGg6MzUwcHg7IGhlaWdodDoxNTBweCI+IVtdKFRyYW5zcGFyZW50LmdpZik8L2Rpdj4KCioqKioqCgojIyBPdGhlciBjcml0ZXJpYSBmb3IgbW9kZWwgc2VsZWN0aW9uCgpBcyB3ZSdsbCBzb29uIHNlZSwgUi1zcXVhcmVkIGFuZCBwLXZhbHVlcyByZXN1bHRpbmcgZnJvbSB0aGUgb21uaWJ1cyBGLXRlc3QgYXJlbid0IHRoZSBiZXN0IG1lYXN1cmVzIHRvIHVzZSB3aGVuIGNvbXBhcmluZyBtb2RlbHMuICBIZXJlIGFyZSBhIGNvdXBsZSBhbHRlcm5hdGl2ZXMgd2UnbGwgYnVpbGQgdXBvbiBsYXRlcjoKCiMjIyMgTGlrZWxpaG9vZAoKVGhlICoqbGlrZWxpaG9vZCoqIG9mIGEgbW9kZWwgaXMgdGhlIHByb2JhYmlsaXR5IGl0IHByb2R1Y2VzIG91ciBkYXRhIGdpdmVuIGl0cyBwYXJhbWV0ZXIgZXN0aW1hdGVzLiBJZiB3ZSBhc3N1bWUgYWxsIG91ciBvYnNlcnZhdGlvbnMgYXJlIGluZGVwZW5kZW50LCB0aGVuIHdlIGNhbiB3cml0ZSBvdXIgbGlrZWxpaG9vZCBmdW5jdGlvbiBhczoKCiFbXShMZXF1YXRpb24ucG5nKQoKPGRpdiBpZCA9ICJsaWNlbnNlIj4KV2Ugd2FudCB0byBjYWxjdWxhdGUgJFAoXHRleHRybXtvYnNlcnZhdGlvbiAxfSBcICBcY2FwIFwgXHRleHRybXtvYnNlcnZhdGlvbiAyfSBcICBcY2FwIFwgLi4uKSQuICBXaGVuIHdlIGFzc3VtZSBpbmRlcGVuZGVuY2UsIHdlIGNhbiBjYWxjdWxhdGUgdGhpcyBhczogJFAoXHRleHRybXtvYnNlcnZhdGlvbiAxfSlQKFx0ZXh0cm17b2JzZXJ2YXRpb24gMn0pLi4uJC4KCkdpdmVuIG91ciBtb2RlbCB3aXRoICRiXzAkIGFuZCAkYl8xJCAtLSBhbG9uZyB3aXRoIG91ciBhc3N1bXB0aW9uIHRoYXQgZXJyb3JzIGFyZSBub3JtYWxseSBkaXN0cmlidXRlZCAtLSB3ZSBjYW4gdXNlIGEgbm9ybWFsIGRpc3RyaWJ1dGlvbiB0byBmaW5kICRQKFx0ZXh0cm17b2JzZXJ2YXRpb24gMX0pJC4gIFdlIHRoZW4gc2ltcGx5IGhhdmUgdG8gbXVsdGlwbHkgdGhlc2UgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBvYnNlcnZhdGlvbiBpbiBvdXIgZGF0YXNldC4KCjxiciAvPgoKQXMgYW4gZXhhbXBsZSwgb3VyIGZpcnN0IG9ic2VydmF0aW9uIGlzOiBgQUNUID0gMTRgIHdpdGggYEdQQSA9IDEuMzlgLiAgUmVjYWxsIG91ciBtb2RlbCBoYXMgJGJfMD0wLjA2Mzg0JCwgJGJfMT0wLjEwNzcyJCwgYW5kICRSU0U9XHNxcnR7TVNfRX09XHNxcnR7MC4wODkyfT0wLjI5ODckLgoKPGJyIC8+CgpGb3IgYWxsIHN0dWRlbnRzIHdpdGggYW4gQUNUIHNjb3JlIG9mIDE0LCBvdXIgbW9kZWwgZXhwZWN0cyBhIEdQQSBvZjogICRcaGF0e3l9PTAuMDYzODQrMC4xMDc3MigxNCk9MS41NzE5JC4KCjxiciAvPgoKR2l2ZW4gdGhhdCBleHBlY3RhdGlvbiwgd2UgY2FuIGZpbmQgdGhlIHByb2JhYmlsaXR5IG9mIGFjdHVhbGx5IG9ic2VydmluZyBhIEdQQSBvZiAxLjM5OgoKJFBbeT0xLjM5IFwgfCBcIHkgXHNpbSBcdGV4dHJte05EfShcbXU9MS41NzE5LCBcc2lnbWE9MC4yOTg3KV0kCgpFeHBhbmQgdGhlIGNvZGUgdG8gc2VlIGhvdyB0byBjYWxjdWxhdGUgdGhpcyBtYW51YWxseSBpbiBSLgoKPC9kaXY+CgoKYGBge3IgJ2xpa2VsaWhvb2QtbWFudWFsLWNhbGN1bGF0aW9uJ30KIyBXZSdsbCB1c2UgZG5vcm0gdG8gY2FsY3VsYXRlIHRoZSBsaWtlbGlob29kIG9mIGVhY2ggb2JzZXJ2YXRpb24KIyBIZXJlJ3MgdGhlIGxpa2VsaWhvb2Qgb2YgdGhlIGZpcnN0IG9ic2VydmF0aW9uOgpkbm9ybSgxLjM5LCBtZWFuID0gKGIwICsgKGIxICogMTQpKSwgc2QgPSAwLjI5ODcpCgojIExldCdzIGNhbGN1bGF0ZSB0aGUgbGlrZWxpaG9vZCBmb3IgQUxMIG9ic2VydmF0aW9ucwojIGFuZCBtdWx0aXBseSB0aG9zZSB0b2dldGhlciB0byBnZXQgdGhlIG1vZGVsIGxpa2VsaWhvb2QKIyBXZSdsbCBhbHNvIGNhbGN1bGF0ZSB0aGUgbG9nLWxpa2VsaWhvb2QgKHdoaWNoIHdlJ2xsIHNlZSBpbiBhIGJpdCkKZmlyc3R5ZWFyICU+JQogIG11dGF0ZShsaWtlbGlob29kID0gZG5vcm0oZ3BhLCBtZWFuID0gKGIwICsgKGIxICogYWN0KSksIHNkID0gMC4yOTg3KSkgJT4lCiAgc3VtbWFyaXplKEwgPSByb3VuZChwcm9kKGxpa2VsaWhvb2QpLDMgKSwKICAgICAgICAgICAgbG9nTCA9IHJvdW5kKCBsb2coTCksMiApKQoKYGBgCgo8YnIgLz4KClR5cGljYWxseSwgaXQncyBlYXNpZXIgdG8gd29yayB3aXRoIHRoZSAqbmF0dXJhbCBsb2cqIG9mIHRoZSBsaWtlbGlob29kLiAgVGhpcyAqKmxvZy1saWtlbGlob29kKiogd2lsbCBhbHdheXMgYmUgbmVnYXRpdmUsIHdpdGggdmFsdWVzIGNsb3NlciB0byB6ZXJvIGluZGljYXRpbmcgYmV0dGVyIG1vZGVsIGZpdC4KCldlIGNhbiB1c2UgbG9nLWxpa2VsaWhvb2QgdG8gY29tcGFyZSBmdWxsIGFuZCByZWR1Y2VkIG1vZGVscyAoYnkgY2FsY3VsYXRpbmcgdGhlICoqbGlrZWxpaG9vZCByYXRpbyoqKToKCiFbXShMTC5wbmcpCgpJZiB0aGUgZnVsbCBtb2RlbCBpcyBtdWNoIGJldHRlciB0aGFuIHRoZSByZWR1Y2VkIG1vZGVsLCB3ZSB3b3VsZCBleHBlY3Q6CgotICRMKFx0ZXh0cm17cmVkdWNlZH0pJCB0byBiZSBzbWFsbAotICRMKFx0ZXh0cm17ZnVsbH0pJCB0byBiZSBsYXJnZQotICRcZnJhY3tMKFx0ZXh0cm17cmVkdWNlZH0pfXtMKFx0ZXh0cm17ZnVsbH0pfSQgdG8gYmUgc21hbGwKLSAkbG4oXGZyYWN7TChcdGV4dHJte3JlZHVjZWR9KX17TChcdGV4dHJte2Z1bGx9KX0pJCB0byBiZSBsYXJnZSBhbmQgbmVnYXRpdmUuCi0gJC0ybG4oXGZyYWN7TChcdGV4dHJte3JlZHVjZWR9KX17TChcdGV4dHJte2Z1bGx9KX0pJCB0byBiZSBsYXJnZSBhbmQgcG9zaXRpdmUKClRvIGVzdGltYXRlIGEgcC12YWx1ZSBmcm9tIHRoaXMgbGlrZWxpaG9vZCByYXRpbywgd2UgY2FuIHVzZSBhIGNoaS1zcXVhcmUgZGlzdHJpYnV0aW9uIHdpdGggJGRmID0gZGZfe1x0ZXh0cm17ZnVsbH19IC0gZGZfe1x0ZXh0cm17cmVkdWNlZH19JAoKRXhwYW5kIHRoZSBjb2RlIHRvIHNlZSB0aGlzIGNhbGN1bGF0aW9uIGluIFI6CgpgYGB7ciAnbG9nLWxpa2VsaWhvb2QnfQojIExldCdzIGZpcnN0IGNhbGN1bGF0ZSBsb2ctbGlrZWxpaG9vZAojIFdlIGNhbiBnbGFuY2UgYXQgdGhlIHdpdGggdGhlIGJyb29tIHBhY2thZ2UKZ2xhbmNlKGZ1bGxtb2RlbCkKCiMgV2UgY2FuIGNhbGN1bGF0ZSBpdCBkaXJlY3RseSB3aXRoIGxvZ0xpaygpCmxvZ0xpayhmdWxsbW9kZWwpCmxvZ0xpayhyZWR1Y2VkbW9kZWwpCgojIENhbGN1bGF0ZSB0aGUgbGlrZWxpaG9vZCByYXRpbwpMUiA8LSAyICogKGxvZ0xpayhmdWxsbW9kZWwpIC0gbG9nTGlrKHJlZHVjZWRtb2RlbCkpCkxSCgojIENhbGN1bGF0ZSB0aGUgcC12YWx1ZQpwY2hpc3EoTFIsIGRmPTEsIGxvd2VyLnRhaWw9RkFMU0UpCgojIElmIHlvdSBpbnN0YWxsIHRoZSBsbXRlc3QgcGFja2FnZSwgeW91IGNhbiB1c2UgbHJ0ZXN0CmxpYnJhcnkobG10ZXN0KQpscnRlc3QocmVkdWNlZG1vZGVsLCBmdWxsbW9kZWwpCgpgYGAKPGRpdiBzdHlsZT0id2lkdGg6MzUwcHg7IGhlaWdodDoyMDBweCI+IVtdKFRyYW5zcGFyZW50LmdpZik8L2Rpdj4KCgojIyMjIEFJQwoKKipBa2Fpa2UncyBJbmZvcm1hdGlvbiBDcml0ZXJpb24qKiBpcyBhbm90aGVyIG1lYXN1cmUgdG8gY29tcGFyZSBjb21wZXRpbmcgbW9kZWxzLiAgQUlDIHNlZWtzIHRvIGZpbmQgdGhlIG1vZGVsIHRoYXQgaGFzIGEgZ29vZCBmaXQgdG8gdGhlIGRhdGEgd2l0aCAqKnJlbGF0aXZlbHkgZmV3IHBhcmFtZXRlcnMgKHByZWRpY3RvcnMpKiouIEl04oCZcyBkZWZpbmVkIGFzOgoKIVtdKEFJQy5wbmcpCgp3aGVyZSBgYiA9IHRoZSBudW1iZXIgb2YgY29lZmZpY2llbnRzIGVzdGltYXRlZCBpbiBvdXIgbW9kZWwgKHNsb3BlKHMpIGFuZCBpbnRlcmNlcHQpYCBhbmQgYHAgPSB0aGUgbnVtYmVyIG9mIHByZWRpY3RvcnMgaW4gb3VyIG1vZGVsYC4gV2hlbiBjb21wYXJpbmcgbW9kZWxzLCB3ZSAqKnByZWZlciB0aGUgbW9kZWwgdGhhdCBwcm9kdWNlcyB0aGUgc21hbGxlciBBSUMgdmFsdWUqKi4gCgpFeHBhbmQgdGhlIGNvZGUgdG8gc2VlIHRoZSBBSUMoKSBmdW5jdGlvbiBpbiBhY3Rpb246CgpgYGB7ciAnQUlDJ30KQUlDKHJlZHVjZWRtb2RlbCwgZnVsbG1vZGVsKQphbm92YShmdWxsbW9kZWwpCmBgYAoKPGJyIC8+CgoqKioqKgoKIyMgRXhhbXBsZTogTXVzaWNhbCBuZXVyb25zCgohW10obmV1cm9ucy5wbmcpCgoKCgoyOC4gRXhwbGFpbiBob3cgd2UgY291bGQgdXNlIEFOT1ZBIHRvIGRldGVybWluZSBpZiBhbiBpbmNyZWFzZSBpbiBuZXVyYWwgYWN0aXZpdHkgaXMgYXNzb2NpYXRlZCB3aXRoIHBsYXlpbmcgc3RyaW5nZWQgaW5zdHJ1bWVudHMuICBFeHBhbmQgdGhlIGNvZGUgYW5kIGludGVycHJldCB0aGUgb3V0cHV0LiAgQWxzbywgY2FsY3VsYXRlIHRoZSBvbW5pYnVzIEYtc3RhdGlzdGljLgoKYGBge3IgJ2Fub3ZhJ30KbmV1cm9uIDwtIHJlYWQuY3N2KCJodHRwOi8vd3d3LmJyYWR0aGllc3Nlbi5jb20vaHRtbDUvZGF0YS92aW9saW4uY3N2IikKCiMgQ29udmVydCB5ZWFycyBwbGF5ZWQgdG8gYSBmYWN0b3IgdmFyaWFibGUuICBJJ2xsIGNyZWF0ZSBhIG5ldyB2YXJpYWJsZS4KbmV1cm9uIDwtIG5ldXJvbiAlPiUKICBtdXRhdGUoeWVhcnNfZ3JvdXAgPSBjYXNlX3doZW4oCiAgICAuJHllYXJzID09IDAgfiAiYSkgMCB5ZWFycyIsCiAgICAuJHllYXJzID4gMCAmIC4keWVhcnMgPCAxMCB+ICJiKSA8MTAgeWVhcnMiLAogICAgLiR5ZWFycyA+IDEwIH4gImMpID4xMCB5ZWFycyIpLAogICAgeWVhcnNfZ3JvdXAgPSBhcy5mYWN0b3IoeWVhcnNfZ3JvdXApKQoKYW5vdmEoYW92KG5ldXJhbCB+IHllYXJzX2dyb3VwLCBkYXRhID0gbmV1cm9uKSkKCmBgYAoKPGRpdiBzdHlsZT0id2lkdGg6MzUwcHg7IGhlaWdodDoxMDBweCI+IVtdKFRyYW5zcGFyZW50LmdpZik8L2Rpdj4KCgoyOS4gV3JpdGUgb3V0IHRoZSBmdWxsIGFuZCByZWR1Y2VkIG1vZGVscyB0byBkZXRlcm1pbmUgaWYgeWVhcnMgcGxheWluZyBhIHN0cmluZ2VkIGluc3RydW1lbnQgaXMgYXNzb2NpYXRlZCB3aXRoIG5ldXJhbCBhY3Rpdml0eS4KCjxkaXYgc3R5bGU9IndpZHRoOjM1MHB4OyBoZWlnaHQ6MjAwcHgiPiFbXShUcmFuc3BhcmVudC5naWYpPC9kaXY+CgoKMzAuIEV4cGFuZCB0aGUgY29kZSBhbmQgaW50ZXJwcmV0IHRoZSBvdXRwdXQuICBWZXJpZnkgY2FsY3VsYXRpb25zIGFzIG5lY2Vzc2FyeSwgZXNwZWNpYWxseSB0aG9zZSBpbiB0aGUgQU5PVkEgc3VtbWFyeSB0YWJsZS4gIEludGVycHJldCBhbGwgcGxvdHMuCgpgYGB7cn0KIyBDcmVhdGUgdGhlIG1vZGVscwpmdWxsX25ldXJvbiA8LSBsbShuZXVyYWwgfiB5ZWFycywgZGF0YSA9IG5ldXJvbikKcmVkdWNlZF9uZXVyb24gPC0gbG0obmV1cmFsIH4gMSwgZGF0YSA9IG5ldXJvbikKCiMgU3VtbWFyaXplIHRoZSBmdWxsIG1vZGVsCnN1bW1hcnkoZnVsbF9uZXVyb24pCgojIEFOT1ZBIHN1bW1hcnkgdGFibGUKYW5vdmEoZnVsbF9uZXVyb24pCgojIE1vZGVsIGNvbXBhcmlzb24KQUlDKGZ1bGxfbmV1cm9uLCByZWR1Y2VkX25ldXJvbikKbHJ0ZXN0KHJlZHVjZWRtb2RlbCwgZnVsbG1vZGVsKQoKIyBHZXQgcHJlZGljdGVkIHZhbHVlcyBhbmQgcmVzaWR1YWxzCm5ldXJvbiA8LSBuZXVyb24gJT4lIGFkZF9wcmVkaWN0aW9ucyhmdWxsX25ldXJvbikKbmV1cm9uIDwtIG5ldXJvbiAlPiUgYWRkX3Jlc2lkdWFscyhmdWxsX25ldXJvbikKCiMgUGxvdCBtb2RlbCBvbiBkYXRhCmdncGxvdChkYXRhID0gbmV1cm9uLCBhZXMoeCA9IHllYXJzLCB5ID0gbmV1cmFsKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9saW5lKGFlcyh5ID0gcHJlZCksIGNvbG9yPSJibHVlIiwgc2l6ZT0xKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1zZXEoMCwgMzAsIDEwKSwgbWlub3JfYnJlYWtzPXNlcSg1LCAyNSwgNSkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgwLCAyMCwgNSksIG1pbm9yX2JyZWFrcz1OVUxMKQoKIyBQbG90IHJlc2lkdWFscyB2cy4gcHJlZGljdGVkIHZhbHVlcwpnZ3Bsb3QoZGF0YSA9IG5ldXJvbiwgYWVzKHggPSBwcmVkLCB5ID0gcmVzaWQpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwKQoKYGBgCgoKPGRpdiBzdHlsZT0id2lkdGg6MzUwcHg7IGhlaWdodDozMDBweCI+IVtdKFRyYW5zcGFyZW50LmdpZik8L2Rpdj4KCgoqKioqKgoKIyMgW1RoZSBQcmVzdGlnZV0oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1vNGdIQ21UUURWSSkKCiFbXShwcmVzdGlnZTEucG5nKQohW10ocHJlc3RpZ2UyLnBuZykKCjxiciAvPgoKMzEuIFRoZSBsaW5lYXIgbW9kZWwgcHJlZGljdGluZyBwcmVzdGlnZSBmcm9tIGluY29tZSBpcyBkaXNwbGF5ZWQgYmVsb3cuICBJbnRlcnByZXQgdGhlIHNsb3BlIGFuZCB5LWludGVyY2VwdC4gIEV4cGFuZCB0aGUgY29kZSBhbmQgaW50ZXJwcmV0IHRoZSBvdXRwdXQgZnJvbSBSLgoKYGBge3IgJ3RoZS1wcmVzdGlnZSd9CnByZXN0aWdlX2RhdGEgPC0gcmVhZC5jc3YoImh0dHA6Ly93d3cuYnJhZHRoaWVzc2VuLmNvbS9odG1sNS9kYXRhL3ByZXN0aWdlLmNzdiIpCgpnZ3Bsb3QoZGF0YSA9IHByZXN0aWdlX2RhdGEsIGFlcyh4ID0gaW5jb21lLCB5ID0gcHJlc3RpZ2UpKSArCiAgZ2VvbV9wb2ludChjb2xvcj0ic3RlZWxibHVlIikgKwogIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iLCBzZT1GQUxTRSkgKwogIGFubm90YXRlKCJ0ZXh0IiwgeD0xNTAwMCwgeT0zNSwgbGFiZWw9IlItc3F1YXJlZCA9IDAuNTExIikgKwogIGFubm90YXRlKCJ0ZXh0IiwgeD0xNTAwMCwgeT0yOCwgbGFiZWw9InkgPSAyNy4xNDEgKyAwLjAwMjl4IikKCnByZXN0aWdlX21vZGVsIDwtIGxtKHByZXN0aWdlIH4gaW5jb21lLCBkYXRhPXByZXN0aWdlX2RhdGEpCgp0aWR5KHByZXN0aWdlX21vZGVsKQpgYGAKCgo8ZGl2IHN0eWxlPSJ3aWR0aDozNTBweDsgaGVpZ2h0OjIwMHB4Ij4hW10oVHJhbnNwYXJlbnQuZ2lmKTwvZGl2PgoKCjMyLiBOb3RpY2UgdGhlIHQtc3RhdGlzdGljIGZvciB0aGUgc2xvcGUgZXN0aW1hdGUgaXMgMTAuMjI0LiAgV3JpdGUgb3V0IGZ1bGwgYW5kIHJlZHVjZWQgbW9kZWxzIGFuZCBjb21wbGV0ZSB0aGUgQU5PVkEgc3VtbWFyeSB0YWJsZS4gIFZlcmlmeSB0aGUgTVNSIHVzaW5nIGJvdGggdGhlIG9tbmlidXMgRi1zdGF0aXN0aWMgYW5kIHRoZSB0LXN0YXRpc3RpYyBvZiAxMC4yMjQuPwoKYGBge3IgJ3RoZS1wcmVzdGlnZS0yJ30KdGlkeShhbm92YShwcmVzdGlnZV9tb2RlbCkpCmBgYAoKIVtdKHByZXN0aWdlYW5vdmEucG5nKQoKPGRpdiBzdHlsZT0id2lkdGg6MzUwcHg7IGhlaWdodDoyMDBweCI+IVtdKFRyYW5zcGFyZW50LmdpZik8L2Rpdj4KCjMzLiBGb3IgdGhpcyBtb2RlbCwgUk1TRSA9IGByIHJvdW5kKHJtc2UocHJlc3RpZ2VfbW9kZWwsIHByZXN0aWdlX2RhdGEpLDIpYC4gIEludGVycHJldCB0aGlzIHZhbHVlLgoKPGRpdiBzdHlsZT0id2lkdGg6MzUwcHg7IGhlaWdodDoxMDBweCI+IVtdKFRyYW5zcGFyZW50LmdpZik8L2Rpdj4KCgojIyMgQ29uZmlkZW5jZSBpbnRlcnZhbHMgZm9yIHByZWRpY3Rpb25zCgozNC4gV2UgY2FuIGVhc2lseSBwcmVkaWN0IHRoZSBhdmVyYWdlIHByZXN0aWdlIG9mIGFsbCBqb2JzIHRoYXQgcGF5IGFuIGluY29tZSBvZiA3LDAwMDogICQyNy4xNCArIDAuMDAyOSg3MDAwKT00Ny40JC4gIFdlIGtub3cgdGhhdCBwcmVkaWN0aW9uIHdvbid0IGJlIHBlcmZlY3QsIHNvIGl0IG1pZ2h0IG1ha2Ugc2Vuc2UgdG8gY29uc3RydWN0IGEgY29uZmlkZW5jZSBpbnRlcnZhbCBhcm91bmQgdGhhdCBwcmVkaWN0aW9uLiAgSW50ZXJwcmV0IHRoZSBjb25maWRlbmNlIGludGVydmFsIGRpc3BsYXllZCBiZWxvdzoKCioqQ29uZmlkZW5jZSBpbnRlcnZhbCBmb3IgcHJlZGljdGlvbnMqKjogICRcaGF0e3l9IFwgXHBtIFwgdF97bi0yfV57XGFscGhhLzJ9c197eXx4fVxzcXJ0e1xmcmFjezF9e259K1xmcmFjeyh4XzAtXG92ZXJsaW5le1h9KV4yfXsobi0xKXNfeF4yfX0kIHdoZXJlICRzX3t5fHh9PVxzcXJ0e01TX0V9JAoKQSA5NSUgY29uZmlkZW5jZSBpbnRlcnZhbCBmb3Igb3VyIHByZWRpY3Rpb24gb2YgNzAwMCBpcywgdGhlbjogICQ3MDAwIFwgXHBtIFwgMlxzcXJ0ezE0Ni4xNn1cc3FydHtcZnJhY3sxfXsxMDJ9K1xmcmFjeyg3MDAwLTY3OTcuOSleMn17KDEwMi0xKShzX3heMik0MjQ1LjkyXjJ9fT00Ny40IFwgXHBtIFwgMi4zOCQKCjxkaXYgc3R5bGU9IndpZHRoOjM1MHB4OyBoZWlnaHQ6MTAwcHgiPiFbXShUcmFuc3BhcmVudC5naWYpPC9kaXY+CgozNS4gQSA5NSUgY29uZmlkZW5jZSBpbnRlcnZhbCBpcyBkaXNwbGF5ZWQgb3ZlciBvdXIgZGF0YS4gIFdoeSBkb2VzIHRoZSB3aWR0aCBvZiB0aGUgaW50ZXJ2YWwgZGlmZmVyIGFjcm9zcyB2YWx1ZXMgb2YgWD8KCgpgYGB7ciAnQ0ktcGxvdCd9CmdncGxvdChkYXRhID0gcHJlc3RpZ2VfZGF0YSwgYWVzKHggPSBpbmNvbWUsIHkgPSBwcmVzdGlnZSkpICsKICBnZW9tX3BvaW50KGNvbG9yPSJzdGVlbGJsdWUiKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2U9VFJVRSwgY29sb3I9InJlZCIpCgpgYGAKCjxkaXYgc3R5bGU9IndpZHRoOjM1MHB4OyBoZWlnaHQ6MTAwcHgiPiFbXShUcmFuc3BhcmVudC5naWYpPC9kaXY+CgozNi4gSWYgeW91IGludGVycHJldGVkIHRoZSBjb25maWRlbmNlIGludGVydmFsIGNvcnJlY3RseSwgaXQgcHJvYmFibHkgZGlkbid0IGdpdmUgeW91IHdoYXQgeW91IHdhbnRlZC4gIElmIHlvdSB3YW50IGFuIGludGVydmFsIHRvIHByZWRpY3QgdGhlIHByZXN0aWdlIG9mICppbmRpdmlkdWFsKiBqb2JzIHdpdGggc3BlY2lmaWMgaW5jb21lcyAoYW5kIG5vdCB0aGUgYXZlcmFnZSBvZiBhbGwgam9icyB3aXRoIHRoYXQgaW5jb21lKSwgeW91J2xsIG5lZWQgdG8gdXNlIGEgcHJlZGljdGlvbiBpbnRlcnZhbC4gIEV4cGFuZCB0aGUgY29kZSB0byBzZWUgaG93IHRoaXMgd2FzIGNvbnN0cnVjdGVkLiAgV2h5IGFyZSBwcmVkaWN0aW9uIGludGVydmFscyB3aWRlciB0aGFuIGNvbmZpZGVuY2UgaW50ZXJ2YWxzPwoKKipQcmVkaWN0aW9uIEludGVydmFsKio6ICRcaGF0e3l9IFwgXHBtIFwgdF97bi0yfV57XGFscGhhLzJ9c197eXx4fVxzcXJ0ezErXGZyYWN7MX17bn0rXGZyYWN7KHhfMC1cb3ZlcmxpbmV7WH0pXjJ9eyhuLTEpc194XjJ9fSQKCkEgOTUlIHByZWRpY3Rpb24gaW50ZXJ2YWwgZm9yIG91ciBwcmVkaWN0aW9uIG9mIDcwMDAgaXMsIHRoZW46ICAkNzAwMCBcIFxwbSBcIDJcc3FydHsxNDYuMTZ9XHNxcnR7MStcZnJhY3sxfXsxMDJ9K1xmcmFjeyg3MDAwLTY3OTcuOSleMn17KDEwMi0xKShzX3heMik0MjQ1LjkyXjJ9fT00Ny40IFwgXHBtIFwgMjQuMTAkCgpXZSBjYW4gZ2V0IGEgcm91Z2ggZXN0aW1hdGUgb2YgdGhpcyBpbnRlcnZhbCBieSBzaW1wbHkgdGFraW5nOiAkXGhhdHt5fSBcIFxwbSBcIDJcc3FydHtNU19FfSQKCmBgYHtyICdwcmVkaWN0aW9uLWludGVydmFsJ30KIyBHZXQgcHJlZGljdGlvbnMgKGFjdHVhbCBhbmQgbG93ZXIvdXBwZXIgY29uZmlkZW5jZSkKcHJlZGljdGlvbnMgPC0gcHJlZGljdChwcmVzdGlnZV9tb2RlbCwgaW50ZXJ2YWw9InByZWRpY3Rpb24iKQoKIyBBZGQgdGhlbSB0byBkYXRhIGZyYW1lCnByZXN0aWdlX2RhdGEgPC0gY2JpbmQocHJlc3RpZ2VfZGF0YSwgcHJlZGljdGlvbnMpCgojIFBsb3QKZ2dwbG90KGRhdGEgPSBwcmVzdGlnZV9kYXRhLCBhZXMoeCA9IGluY29tZSwgeSA9IHByZXN0aWdlKSkgKwogIGdlb21fcmliYm9uKGFlcyh5bWluID0gbHdyLCB5bWF4ID0gdXByKSwgZmlsbD0iZ3JleTcwIiwgYWxwaGE9LjUpICsKICBnZW9tX3BvaW50KGNvbG9yPSJzdGVlbGJsdWUiKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSJsbSIsIHNlPUZBTFNFKQogIApgYGAKCgo8ZGl2IHN0eWxlPSJ3aWR0aDozNTBweDsgaGVpZ2h0OjIwMHB4Ij4hW10oVHJhbnNwYXJlbnQuZ2lmKTwvZGl2PgoKKioqKioKCiMjIE90aGVyIHRvcGljcyAodGltZSBwZXJtaXR0aW5nKQoKTG93ZXNzIHJlZ3Jlc3Npb24KClJvYnVzdCByZWdyZXNzaW9uCgpCb3gtQ294IHRyYW5zZm9ybXMKCkVudHJvcHkKCgo8ZGl2IGlkPSJsaWNlbnNlIj4KKipTb3VyY2VzKioKClRoaXMgZG9jdW1lbnQgaXMgcmVsZWFzZWQgdW5kZXIgYSBbQ3JlYXRpdmUgQ29tbW9ucyBBdHRyaWJ1dGlvbi1TaGFyZUFsaWtlIDMuMCBVbnBvcnRlZF0oaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktc2EvMy4wKSBsaWNlbnNlLgo8L2Rpdj4KCgoK