Introduction

This kernel’s goal is to establish an advanced regression model to predict housing price in Melbourne, Australia. The data is a snapshot in September 2017 of Melbourne housing clearance data created by Tony Pino. A well-tuned model can help discover a reasonably priced property.

Data Exploration

Data Structure

melb_house <- read_csv("melb_data.csv", na = "")
melb_house$Date <- as.Date(melb_house$Date, format = "%d/%m/%Y") # format date column
glimpse(melb_house)
Rows: 13,580
Columns: 21
$ Suburb        <chr> "Abbotsford", "Abbotsford", "Abbotsford", "Abbotsfo…
$ Address       <chr> "85 Turner St", "25 Bloomburg St", "5 Charles St", …
$ Rooms         <dbl> 2, 2, 3, 3, 4, 2, 3, 2, 1, 2, 2, 3, 2, 2, 1, 2, 3, …
$ Type          <chr> "h", "h", "h", "h", "h", "h", "h", "h", "u", "h", "…
$ Price         <dbl> 1480000, 1035000, 1465000, 850000, 1600000, 941000,…
$ Method        <chr> "S", "S", "SP", "PI", "VB", "S", "S", "S", "S", "S"…
$ SellerG       <chr> "Biggin", "Biggin", "Biggin", "Biggin", "Nelson", "…
$ Date          <date> 2016-12-03, 2016-02-04, 2017-03-04, 2017-03-04, 20…
$ Distance      <dbl> 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2…
$ Postcode      <dbl> 3067, 3067, 3067, 3067, 3067, 3067, 3067, 3067, 306…
$ Bedroom2      <dbl> 2, 2, 3, 3, 3, 2, 4, 2, 1, 3, 2, 3, 2, 2, 1, 2, 3, …
$ Bathroom      <dbl> 1, 1, 2, 2, 1, 1, 2, 1, 1, 1, 2, 2, 2, 1, 1, 1, 2, …
$ Car           <dbl> 1, 0, 0, 1, 2, 0, 0, 2, 1, 2, 1, 2, 1, 1, 1, 2, 1, …
$ Landsize      <dbl> 202, 156, 134, 94, 120, 181, 245, 256, 0, 220, 0, 2…
$ BuildingArea  <dbl> NA, 79, 150, NA, 142, NA, 210, 107, NA, 75, NA, 190…
$ YearBuilt     <dbl> NA, 1900, 1900, NA, 2014, NA, 1910, 1890, NA, 1900,…
$ CouncilArea   <chr> "Yarra", "Yarra", "Yarra", "Yarra", "Yarra", "Yarra…
$ Lattitude     <dbl> -37.7996, -37.8079, -37.8093, -37.7969, -37.8072, -…
$ Longtitude    <dbl> 144.9984, 144.9934, 144.9944, 144.9969, 144.9941, 1…
$ Regionname    <chr> "Northern Metropolitan", "Northern Metropolitan", "…
$ Propertycount <dbl> 4019, 4019, 4019, 4019, 4019, 4019, 4019, 4019, 401…
print(paste("The start date is", min(melb_house$Date)))
[1] "The start date is 2016-01-28"
print(paste("The end date is", max(melb_house$Date)))
[1] "The end date is 2017-09-23"

The dataset contains 13580 properties with 21 attributes. It covers the period from 28/01/2016 to 23/09/2017.

Target Variable

As we want to predict the housing price, Price will be the target variable.

summary(melb_house$Price)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  85000  650000  903000 1075684 1330000 9000000 
ggplot(melb_house, aes(Price)) +
  geom_histogram(fill = "deepskyblue3", binwidth = 100000)

The sale prices are right skewed, with some outliers in the highly priced range.

Feature Selection

Handle Missing Data

First we plot all the variables that have missing values.

plot_missing(melb_house, missing_only = TRUE)

There are 4 variables with missing values. The BuildingArea has almost half of the values are NAs, therefore it will be removed in the analysis. For the variable YearBuilt, there are 39.58% NAs. Because this variable may imply the house’s condition, we will replace the NAs with Not Available and try binning the others into groups. Lets take a look at the quantile of those values:

quantile(melb_house$YearBuilt, na.rm = TRUE)
  0%  25%  50%  75% 100% 
1196 1940 1970 1999 2018 

There seems to be really old properties dating back to the year 1196. Looking at the quantile we propose 4 approximately similar groups of house based on age: Older than 80 years, 50-80 years, 20-50 years, Less than 20 years.

melb_house_prep <- melb_house %>% 
  mutate(Age = if_else(is.na(YearBuilt), "Not Available"
           , if_else(YearBuilt < 1937, "Older than 80 years" 
                       , if_else(YearBuilt < 1967, "50-80 years"
                               , if_else(YearBuilt < 1997, "20-50 years"
                                       , "Less than 20 years"))))
         )
ggplot(melb_house_prep, aes(Age)) +
  geom_bar(fill = "deepskyblue3")

For the Car variable, there are only 0.46% NAs where the number of car slots are not informed. We will consider them as no parking slots.

melb_house_prep$Car[is.na(melb_house_prep$Car)] <- 0
ggplot(melb_house_prep, aes(Car)) +
  geom_histogram(fill = "deepskyblue3", binwidth = 0.5) +
  scale_x_continuous(breaks = seq(0, 10, 1))

For CouncilArea, there are other variables that refer to location, such as Suburb and Postcode. Let us consider their correlation using chi-squared test and Cramer’s V value:

melb_house$Suburb <- factor(melb_house$Suburb)
melb_house$Postcode <- factor(melb_house$Postcode)
df <- melb_house %>% select(Suburb, Postcode, CouncilArea)
df$CouncilArea <- df$CouncilArea %>% replace_na("Not Available")
df$CouncilArea <- factor(df$CouncilArea)

# function to get chi square p value and Cramers V
f <- function(x,y) {
  tbl <- df %>% select(x,y) %>% table()
  chisq_pval <- round(chisq.test(tbl)$p.value, 4)
  cramV <- round(cramersV(tbl), 4) 
  data.frame(x, y, chisq_pval, cramV) }

# create unique combinations of column names
# sorting will help getting a better plot (upper triangular)
df_comb <- data.frame(t(combn(sort(names(df)), 2)), stringsAsFactors = F)

# apply function to each variable combination
df_res <- map2_df(df_comb$X1, df_comb$X2, f)
df_res %>%
  ggplot(aes(x, y, fill = chisq_pval))+
  geom_tile()+
  geom_text(aes(x,y,label=cramV))+
  scale_fill_gradient(low="red", high="yellow")+
  theme_classic()

There are strong correlations between each of the 3 categorical variables based on the high Cramer’s V and the p-value of chi-squared test close to 0. Therefore, we will only use Suburb in the prediction model and drop the other 2 variables as there will be little information loss. For the same reason, Address, Lattitude and Longtitude can also be dropped as they only contain unique information about location.

Transformation of Date variable

The date that the house is sold could be of some values, even though it is not really informative with just the exact date. We will separate the variable Date into MonthYear indicating the month and the year it is in.

melb_house_prep <- melb_house_prep %>% 
  mutate(MonthYear = format(as.Date(Date), "%Y-%b"))
melb_house_prep$Suburb <- factor(melb_house_prep$Suburb)
melb_house_prep$Type <- factor(melb_house_prep$Type)
melb_house_prep$Method <- factor(melb_house_prep$Method)
melb_house_prep$SellerG <- factor(melb_house_prep$SellerG)
melb_house_prep$Regionname <- factor(melb_house_prep$Regionname)
melb_house_prep$Age <- factor(melb_house_prep$Age)
# Order month name
monthlevel <- paste0(rep(2016:2017, each = 12), "-", month.abb)
melb_house_prep$MonthYear <- factor(melb_house_prep$MonthYear
                                    , levels = monthlevel)

Correlation of continuous variables

Next we will check the correlation plot. There are 7 continuous predictor variables remained. Let us check if they are correlated with Price sold.

# Reduce the variables in the dataset
melb_house_prep <- melb_house_prep %>% 
  select(Price, Suburb, Rooms, Type, Method, SellerG, Distance, Bedroom2
         , Bathroom, Car, Landsize, Regionname, Propertycount, Age, MonthYear)
# Index vector numeric variables
numericVars <- which(sapply(melb_house_prep, is.numeric))
numericVarNames <- names(numericVars) #saving names vector for use later on
# Correlations of all numeric variables
all_numVar <- melb_house_prep[, numericVars]
cor_numVar <- cor(all_numVar, use = "pairwise.complete.obs")
corrplot.mixed(cor_numVar, tl.col="black", tl.pos = "lt")

The number of rooms, bedrooms and bathrooms seems to be correlated with the price of the house. Rooms and Bedrooms are also highly correlated, so we can drop Bedrooms in the modelling process. We will also omit PropertyCount because it has an one on one representation of each Suburb.

melb_house_prep <- melb_house_prep %>%
  select(-Bedroom2, -Propertycount)

Data Pre-processing

After transforming and reducing the number of predictors:

newnumericVars <- melb_house_prep %>% 
  select(where(is.numeric))
nonnumericVars <- melb_house_prep %>% 
  select(!where(is.numeric))
cat('There are', length(newnumericVars) - 1, 'continuous predictor variables'
    , 'and', length(nonnumericVars), 'categorical predictor variables.')
There are 5 continuous predictor variables and 7 categorical predictor variables.

Continuous variables

plot_histogram(newnumericVars, ncol = 3L)

The continous variables need to be normalized to alleviate skewness. The histogram shows some abnormal pattern in Landsize. A better look at its boxplot shows an extreme outlier. We shall remove that instance for better model prediction.

ggplot(melb_house_prep, aes(Landsize)) +
  geom_boxplot()+
  coord_flip()

melb_house_prep <- melb_house_prep[-which(melb_house_prep$Landsize == max(melb_house_prep$Landsize)), ]

Categorical variables

Now looking at the categorical variables, Suburb has 314 categories and SellerG has 268 categories, which will need binning.

# Plot the categorical variables
plot_bar(nonnumericVars, ncol = 3L)
2 columns ignored with more than 50 categories.
Suburb: 314 categories
SellerG: 268 categories

The contingency table for MonthYear shows that there are very few observations in 2016-Jan and 2016-Feb, and there were even no house sale in 2016-Mar and 2017-Jan (there were no problem in the contingency table for other categorical variables).

table(melb_house_prep$MonthYear)

2016-Jan 2016-Feb 2016-Mar 2016-Apr 2016-May 2016-Jun 2016-Jul 2016-Aug 
       2       26        0      318      899      732      446      716 
2016-Sep 2016-Oct 2016-Nov 2016-Dec 2017-Jan 2017-Feb 2017-Mar 2017-Apr 
     925      551     1114      607        0      417      678      631 
2017-May 2017-Jun 2017-Jul 2017-Aug 2017-Sep 2017-Oct 2017-Nov 2017-Dec 
    1130     1098     1536      833      920        0        0        0 

To prevent overfitting, we will bin 2016-Jan and 2016-Feb together.

melb_house_prep <- melb_house_prep %>% 
  mutate(MonthYearNew = ifelse(MonthYear %in% c("2016-Jan", "2016-Feb"),
                               "2016-JanFeb", as.character(MonthYear))) %>% 
  select(-MonthYear)

Sampling and data partitioning

We split the data to begin the modelling process by performing stratified sampling to keep the similar target distribution. The training set will keep 70% of the observations and the test set keeps 30%.

# Stratified sampling with the rsample package
set.seed(1235)
split_strat  <- initial_split(melb_house_prep, prop = 0.7
                              , strata = "Price")
melb_train  <- training(split_strat)
melb_test   <- testing(split_strat)

Feature engineering

Examining the categorical variables, there are no near-zero variance variables.

# Check for near-zero variances
nearZeroVar(melb_train, saveMetrics = TRUE) %>% 
  tibble::rownames_to_column() %>% 
  filter(nzv)

In the next step we execute normalization to minimize the skewness of numerical variables. We use log transform for the target variable Price and Yeo-Johnson transformation for the predictors since the values are not strictly positive. We also standardize the numerical predictors so that they have the same magnitude in the models. Then, as said above, we will reduce the number of categories in Suburb and SellerG. All levels that are observed in less than 0.5% of the training sample will be collapsed into an Other category. We have to carefully choose a small percentage because of possible information loss. Lastly, we use dummy encoding to transform the categorical variables into numeric representations. The feature engineering steps in summary are: 1. Normalize numeric features. 2. Standardize numeric predictor features. 3. Binning Suburb and SellerG. 4. Dummy encode categorical features.

# Create recipe for pre-processing
blueprint_all <- recipe(Price ~ ., data = melb_train) %>%
  step_log(all_outcomes()) %>% 
  step_YeoJohnson(all_numeric(), -all_outcomes()) %>%
  step_center(all_numeric(), -all_outcomes()) %>% 
  step_scale(all_numeric(), -all_outcomes()) %>%
  step_other(Suburb, threshold = 0.005, other = "Other") %>% 
  step_other(SellerG, threshold = 0.005, other = "Other") %>% 
  step_dummy(all_nominal())
prepare <- prep(blueprint_all, training = melb_train)
baked_train <- bake(prepare, new_data = melb_train) # df after preprocess
#plot_bar(baked_train) # check the bar plot

Modelling

We are going to test 3 types of regression models on the data: a simple linear regression model, a regularized model and a light gradient boosting model. 10-fold cross-validation sampling is used for evaluation.

# Define a cross validation method
tr_control <- trainControl(method = "cv"
                           , number = 10
                           , savePredictions = TRUE)

Linear model and Regularized model

Using the caret package, the linear model and the regularized model can be pre-processed and trained in the same fashion.

set.seed(1235)  # for reproducibility
cv_lm <- train(
  blueprint_all
  , data = melb_train
  , method = "lm"
  , trControl = tr_control
)

The regularized model, or elastic net, has 2 hyperparameters alpha and lambda. We set tunelength = 10 to do a grid search of the 10 default values of each parameter.

set.seed(1235)
cv_en <- train(
  blueprint_all
  , data = melb_train
  , method = "glmnet"
  , trControl = tr_control
  , tuneLength = 10
)

Based on the plot of the performance measures, there are no significant differences between the linear model and the regularized model. In this case, the linear model might be a better choice because of its interpretability.

resamps <- resamples(list(
  LM = cv_lm
  , GLMnet = cv_en
))
summary(resamps)

Call:
summary.resamples(object = resamps)

Models: LM, GLMnet 
Number of resamples: 10 

MAE 
            Min.   1st Qu.    Median      Mean   3rd Qu.      Max. NA's
LM     0.1821066 0.1872720 0.1905543 0.1901902 0.1937587 0.1979388    0
GLMnet 0.1821618 0.1874446 0.1906834 0.1902476 0.1938839 0.1979971    0

RMSE 
            Min.   1st Qu.    Median      Mean   3rd Qu.      Max. NA's
LM     0.2345404 0.2452400 0.2470586 0.2498114 0.2544200 0.2652404    0
GLMnet 0.2345297 0.2454189 0.2470686 0.2498076 0.2543727 0.2650484    0

Rsquared 
            Min.   1st Qu.    Median      Mean   3rd Qu.      Max. NA's
LM     0.7439817 0.7734255 0.7778464 0.7780264 0.7880779 0.8061021    0
GLMnet 0.7443194 0.7732747 0.7776512 0.7780218 0.7878595 0.8061277    0
trellis.par.set(caretTheme())
Warning: Note: The default device has been opened to honour attempt to modify trellis settings
bwplot(resamps)

The RMSE is after log transformation of the target variable. Reversing it to normal scale can help with practical reasoning.

rmset <- function(model){
  y = log(melb_train$Price)
  y.pred = predict(model, melb_train)
  y <- exp(y)
  y.pred <- exp(y.pred)
  return(sqrt(mean((y - y.pred)^2)))
}

paste("The RMSE of the linear model is:", rmset(cv_lm))
[1] "The RMSE of the linear model is: 346096.995987272"

Light gradient boosting machine

Gradient Boosting Machine is a modern machine learning technique that makes use of an ensemble of weak learners, typically decision trees. Light GBM (by Microsoft) is a high-performance framework that offers significantly fast training speed and accuracy thanks to leaf-wise growth and other features. To fit the model, first we need to convert the training data into a matrix that is supported by lightgbm package.


# Convert into lgb dataset
new_melb <- lgb.convert_with_rules(melb_train %>% select(-Price))
train_label <- melb_train[, "Price"]
train_matrix <- lgb.Dataset(data = as.matrix(new_melb$data)
                            , label = as.matrix(train_label))

There are a variety of hyperparameters in lightgbm. We will perform tuning for 4 important hyperparameters: 1. num_leaves: the maximum number of leaves that each learners has. 2. max_depth: the maximum depth of each trained tree. 3. bagging_fraction: the percentage of rows used per iteration. 4. feature_fraction: the percentage of random subset of features per iteration.

Hyperparameter tuning will be carried out by Bayesian Optimization, which uses the information from prior model evaluations to guide future parameter searches. In this method, the prior response distribution is iteratively updated based on our best guess of where the best parameters are. The package used is ParBayesianOptimization.

# Define scoring function for BayesOpt
scoringFunction <- function(num_leaves, max_depth
                            , bagging_fraction, feature_fraction) {
  
  Pars <- list(
    objective = "regression"
    , metric = "rmse"
    , num_iterations = 1000
    , early_stopping_rounds = 50
    , learning_rate = 0.01
    , num_leaves = num_leaves
    , max_depth = max_depth
    , bagging_fraction = bagging_fraction
    , bagging_freq = 1
    , feature_fraction = feature_fraction
  )
  
  lgbcv <- lgb.cv(
    params = Pars
    , data = train_matrix
    , nfold = 5L
    , stratified = FALSE
  )
  
  return(list(Score = -1*lgbcv$best_score
              , nrounds = lgbcv$best_iter
  )
  )
}

# Define the search boundaries
bounds <- list(
  num_leaves = c(5L, 30L)
  , max_depth = c(2L, 5L)
  , bagging_fraction = c(0.4, 1)
  , feature_fraction = c(0.4, 1)
)

We define a scoring function to optimize. Then, we set the search boundaries for each hyperparameter. BayesOpt then try find parameters that maximize the scoring function based on some initial scoring.

start.time <- Sys.time()
set.seed(1235)

optObj <- bayesOpt(
  FUN = scoringFunction
  , bounds = bounds
  , initPoints = 5
  , iters.n = 20
  , iters.k = 1
)
end.time <- Sys.time()
time.taken <- end.time - start.time
time.taken

The results of 20 iterations are as below.

optObj$scoreSummary

The best tuned hyperparameters are num_leaves = 21, max_depth = 5, bagging_fraction = 0.741 and feature_fraction = 0.658.

getBestPars(optObj)
$num_leaves
[1] 21

$max_depth
[1] 5

$bagging_fraction
[1] 0.740829

$feature_fraction
[1] 0.6581308

The RMSE of the Light GBM model is better than the Linear model. For prediction purpose we proceed with the Light GBM model with the best hyperparameters, although we can choose the linear model if the goal is interpretability.

-1*max(optObj$scoreSummary$Score)
[1] 301154.3

Final model performance

We use the chosen hyperparameters to train a Light GBM model with higher iterations.


# Hyperparameter
params <- list(
  objective = "regression"
  , metric = "RMSE"
  , num_iterations = 5000
  , num_threads = 2
  , learning_rate = 0.01
  , num_leaves = 21
  , max_depth = 5
  , seed = 1235
  , bagging_fraction = 0.741
  , bagging_freq = 1
  , feature_fraction = 0.658
)

# Train lgb model
lgb_fin <- lgb.train(params = params, data = train_matrix)

Applying the final model to the unseen test set yields the following results:

# Convert test set into lgb dataset
new_melb_test <- lgb.convert_with_rules(melb_test %>% select(-Price))
test_label <- melb_test$Price
# Predict test data
preds <- predict(lgb_fin, data = as.matrix(new_melb_test$data))

# Accuracy check
mse <- mean((test_label - preds)^2)
mae <- caret::MAE(test_label, preds)
rmse <- caret::RMSE(test_label, preds)
rsq <- function (x, y) cor(x, y) ^ 2
r_squared <- rsq(test_label, preds)

cat("MSE: ", mse, "\nMAE: ", mae, "\nRMSE: ", rmse, "\nR-squared: ", r_squared)
MSE:  76403505601 
MAE:  167015.6 
RMSE:  276411.8 
R-squared:  0.8018303

The plot between actual and predicted values is as follow:

df_visual <- data.frame(test_label, preds)
df_visual$observations <- 1:nrow(df_visual)
ggplot(data = df_visual) + 
  geom_line(aes(x = observations, y = test_label, color = 'test')) + 
  geom_line(aes(x = observations, y = preds, color = 'preds')) +
  ggtitle("Melbourne housing test data prediction") +
  theme(plot.title = element_text(hjust = 0.5)) +
  ylab('Price')

By plotting the feature importance chart of the model, we can see that Distance has the biggest effect on predicting Price. Other important features in the top 5 are Regionname, Rooms, Landsize and Bathroom. It seems like price is affected the most by the location of the house (closeness to the central business district) and the size of the house (rooms, landsize, bathroom).

tree_imp <- lgb.importance(lgb_fin, percentage = TRUE)
lgb.plot.importance(tree_imp, top_n = 10L, measure = "Gain")

Conclusion

The kernel has shown steps of applying advanced machine learning techniques to a regression problem, particularly predicting Melbourne house price. After getting the dataset, exploration and feature engineering should be done thoroughly to help with model building. The Light Gradient Boosting model (Light GBM) performs the best at prediction and shows some notable factors that can affect pricing a house. This framework can be applied to other data problems, although more feature availability or broader hyperparameter tuning can help increase performance.

LS0tDQp0aXRsZTogIk1lbGJvdXJuZSBob3VzaW5nIHByaWNlIHByZWRpY3Rpb24iDQphdXRob3I6ICJQaG9uZyBWaWV0IE5ndXllbiINCmRhdGU6ICIyMDIyLTA2LTEyIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogeWVzDQotLS0NCg0KYGBge3IgTG9hZCBwYWNrYWdlcywgZWNobz1GQUxTRX0NCnBhY21hbjo6cF9sb2FkKA0KICAidGlkeXZlcnNlIiAgICAgICMgZGF0YSBtYW5pcHVsYXRpb24NCiAgLCAiY2FyZXQiICAgICAgICAjIG1vZGVsIHRyYWluaW5nDQogICwgInJlY2lwZXMiICAgICAgIyBwcmUtcHJvY2Vzc2luZw0KICAsICJyc2FtcGxlIiAgICAgICMgZGF0YSBzcGxpdHRpbmcNCiAgLCAiRGF0YUV4cGxvcmVyIiAjIEVEQQ0KICAsICJjb3JycGxvdCIgICAgICMgY29ycmVsYXRpb24gcGxvdA0KICAsICJnbG1uZXQiICAgICAgICMgZWxhc3RpYyBuZXQgbW9kZWxsaW5nDQogICwgIk1MbWV0cmljcyIgICAgIyBtb2RlbCBtZXRyaWNzDQogICwgInZpcCIgICAgICAgICAgIyB2YXJpYWJsZSBpbXBvcnRhbmNlDQogICwgInBkcCIgICAgICAgICAgIyBwYXJ0aWFsIGRlcGVuZGVuY2UgcGxvdA0KICAsICJmb3JjYXRzIg0KICAsICJsaWdodGdibSINCiAgLCAibHNyIiAgICAgICAgICMgZm9yIGNoaSBzcXVhcmVkIGZ1bmN0aW9uDQogICwgIlBhckJheWVzaWFuT3B0aW1pemF0aW9uIg0KKQ0KYGBgDQoNCg0KIyBJbnRyb2R1Y3Rpb24NCg0KVGhpcyBrZXJuZWwncyBnb2FsIGlzIHRvIGVzdGFibGlzaCBhbiBhZHZhbmNlZCByZWdyZXNzaW9uIG1vZGVsIHRvIHByZWRpY3QgaG91c2luZyBwcmljZSBpbiBNZWxib3VybmUsIEF1c3RyYWxpYS4gVGhlIGRhdGEgaXMgYSBzbmFwc2hvdCBpbiBTZXB0ZW1iZXIgMjAxNyBvZiBbTWVsYm91cm5lIGhvdXNpbmcgY2xlYXJhbmNlIGRhdGFdKCJodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzL2FudGhvbnlwaW5vL21lbGJvdXJuZS1ob3VzaW5nLW1hcmtldCIpIGNyZWF0ZWQgYnkgVG9ueSBQaW5vLiBBIHdlbGwtdHVuZWQgbW9kZWwgY2FuIGhlbHAgZGlzY292ZXIgYSByZWFzb25hYmx5IHByaWNlZCBwcm9wZXJ0eS4NCg0KIyBEYXRhIEV4cGxvcmF0aW9uDQoNCiMjIERhdGEgU3RydWN0dXJlDQoNCmBgYHtyIExvYWQgZGF0YSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCm1lbGJfaG91c2UgPC0gcmVhZF9jc3YoIm1lbGJfZGF0YS5jc3YiLCBuYSA9ICIiKQ0KbWVsYl9ob3VzZSREYXRlIDwtIGFzLkRhdGUobWVsYl9ob3VzZSREYXRlLCBmb3JtYXQgPSAiJWQvJW0vJVkiKSAjIGZvcm1hdCBkYXRlIGNvbHVtbg0KZ2xpbXBzZShtZWxiX2hvdXNlKQ0KcHJpbnQocGFzdGUoIlRoZSBzdGFydCBkYXRlIGlzIiwgbWluKG1lbGJfaG91c2UkRGF0ZSkpKQ0KcHJpbnQocGFzdGUoIlRoZSBlbmQgZGF0ZSBpcyIsIG1heChtZWxiX2hvdXNlJERhdGUpKSkNCg0KYGBgDQpUaGUgZGF0YXNldCBjb250YWlucyAxMzU4MCBwcm9wZXJ0aWVzIHdpdGggMjEgYXR0cmlidXRlcy4gSXQgY292ZXJzIHRoZSBwZXJpb2QgZnJvbSAyOC8wMS8yMDE2IHRvIDIzLzA5LzIwMTcuDQoNCiMjIFRhcmdldCBWYXJpYWJsZQ0KDQpBcyB3ZSB3YW50IHRvIHByZWRpY3QgdGhlIGhvdXNpbmcgcHJpY2UsICpQcmljZSogd2lsbCBiZSB0aGUgdGFyZ2V0IHZhcmlhYmxlLg0KDQpgYGB7ciBUYXJnZXQgdmFyaWFibGUgc3VtbWFyeX0NCnN1bW1hcnkobWVsYl9ob3VzZSRQcmljZSkNCmBgYA0KDQpgYGB7ciBUYXJnZXQgdmFyaWFibGUgcGxvdH0NCmdncGxvdChtZWxiX2hvdXNlLCBhZXMoUHJpY2UpKSArDQogIGdlb21faGlzdG9ncmFtKGZpbGwgPSAiZGVlcHNreWJsdWUzIiwgYmlud2lkdGggPSAxMDAwMDApDQpgYGANCg0KVGhlIHNhbGUgcHJpY2VzIGFyZSByaWdodCBza2V3ZWQsIHdpdGggc29tZSBvdXRsaWVycyBpbiB0aGUgaGlnaGx5IHByaWNlZCByYW5nZS4gDQoNCiMgRmVhdHVyZSBTZWxlY3Rpb24NCg0KIyMgSGFuZGxlIE1pc3NpbmcgRGF0YQ0KDQpGaXJzdCB3ZSBwbG90IGFsbCB0aGUgdmFyaWFibGVzIHRoYXQgaGF2ZSBtaXNzaW5nIHZhbHVlcy4NCg0KYGBge3IgTWlzc2luZyB2YWx1ZXN9DQpwbG90X21pc3NpbmcobWVsYl9ob3VzZSwgbWlzc2luZ19vbmx5ID0gVFJVRSkNCmBgYA0KDQpUaGVyZSBhcmUgNCB2YXJpYWJsZXMgd2l0aCBtaXNzaW5nIHZhbHVlcy4gVGhlICpCdWlsZGluZ0FyZWEqIGhhcyBhbG1vc3QgaGFsZiBvZiB0aGUgdmFsdWVzIGFyZSBOQXMsIHRoZXJlZm9yZSBpdCB3aWxsIGJlIHJlbW92ZWQgaW4gdGhlIGFuYWx5c2lzLiBGb3IgdGhlIHZhcmlhYmxlICpZZWFyQnVpbHQqLCB0aGVyZSBhcmUgMzkuNTglIE5Bcy4gQmVjYXVzZSB0aGlzIHZhcmlhYmxlIG1heSBpbXBseSB0aGUgaG91c2UncyBjb25kaXRpb24sIHdlIHdpbGwgcmVwbGFjZSB0aGUgTkFzIHdpdGggKk5vdCBBdmFpbGFibGUqIGFuZCB0cnkgYmlubmluZyB0aGUgb3RoZXJzIGludG8gZ3JvdXBzLiBMZXRzIHRha2UgYSBsb29rIGF0IHRoZSBxdWFudGlsZSBvZiB0aG9zZSB2YWx1ZXM6DQoNCmBgYHtyIFllYXJCdWlsdCBxdWFudGlsZX0NCnF1YW50aWxlKG1lbGJfaG91c2UkWWVhckJ1aWx0LCBuYS5ybSA9IFRSVUUpDQpgYGANClRoZXJlIHNlZW1zIHRvIGJlIHJlYWxseSBvbGQgcHJvcGVydGllcyBkYXRpbmcgYmFjayB0byB0aGUgeWVhciAxMTk2LiBMb29raW5nIGF0IHRoZSBxdWFudGlsZSB3ZSBwcm9wb3NlIDQgYXBwcm94aW1hdGVseSBzaW1pbGFyIGdyb3VwcyBvZiBob3VzZSBiYXNlZCBvbiBhZ2U6ICAqT2xkZXIgdGhhbiA4MCB5ZWFycyosICo1MC04MCB5ZWFycyosICoyMC01MCB5ZWFycyosICpMZXNzIHRoYW4gMjAgeWVhcnMqLg0KDQpgYGB7ciBMdW1waW5nIGRhdGEgaW50byBBZ2V9DQptZWxiX2hvdXNlX3ByZXAgPC0gbWVsYl9ob3VzZSAlPiUgDQogIG11dGF0ZShBZ2UgPSBpZl9lbHNlKGlzLm5hKFllYXJCdWlsdCksICJOb3QgQXZhaWxhYmxlIg0KICAgICAgICAgICAsIGlmX2Vsc2UoWWVhckJ1aWx0IDwgMTkzNywgIk9sZGVyIHRoYW4gODAgeWVhcnMiIA0KICAgICAgICAgICAgICAgICAgICAgICAsIGlmX2Vsc2UoWWVhckJ1aWx0IDwgMTk2NywgIjUwLTgwIHllYXJzIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgaWZfZWxzZShZZWFyQnVpbHQgPCAxOTk3LCAiMjAtNTAgeWVhcnMiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsICJMZXNzIHRoYW4gMjAgeWVhcnMiKSkpKQ0KICAgICAgICAgKQ0KZ2dwbG90KG1lbGJfaG91c2VfcHJlcCwgYWVzKEFnZSkpICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICJkZWVwc2t5Ymx1ZTMiKQ0KYGBgDQpGb3IgdGhlICpDYXIqIHZhcmlhYmxlLCB0aGVyZSBhcmUgb25seSAwLjQ2JSBOQXMgd2hlcmUgdGhlIG51bWJlciBvZiBjYXIgc2xvdHMgYXJlIG5vdCBpbmZvcm1lZC4gV2Ugd2lsbCBjb25zaWRlciB0aGVtIGFzIG5vIHBhcmtpbmcgc2xvdHMuDQoNCmBgYHtyIENhcn0NCm1lbGJfaG91c2VfcHJlcCRDYXJbaXMubmEobWVsYl9ob3VzZV9wcmVwJENhcildIDwtIDANCmdncGxvdChtZWxiX2hvdXNlX3ByZXAsIGFlcyhDYXIpKSArDQogIGdlb21faGlzdG9ncmFtKGZpbGwgPSAiZGVlcHNreWJsdWUzIiwgYmlud2lkdGggPSAwLjUpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCAxMCwgMSkpDQpgYGANCkZvciAqQ291bmNpbEFyZWEqLCB0aGVyZSBhcmUgb3RoZXIgdmFyaWFibGVzIHRoYXQgcmVmZXIgdG8gbG9jYXRpb24sIHN1Y2ggYXMgKlN1YnVyYiogYW5kICpQb3N0Y29kZSouIExldCB1cyBjb25zaWRlciB0aGVpciBjb3JyZWxhdGlvbiB1c2luZyBjaGktc3F1YXJlZCB0ZXN0IGFuZCBDcmFtZXIncyBWIHZhbHVlOg0KDQpgYGB7ciBDb3JyZWxhdGlvbiB0ZXN0IFN1YnVyYiwgQ291bmNpbEFyZWEsIFBvc3Rjb2RlLCB3YXJuaW5nPUZBTFNFfQ0KbWVsYl9ob3VzZSRTdWJ1cmIgPC0gZmFjdG9yKG1lbGJfaG91c2UkU3VidXJiKQ0KbWVsYl9ob3VzZSRQb3N0Y29kZSA8LSBmYWN0b3IobWVsYl9ob3VzZSRQb3N0Y29kZSkNCmRmIDwtIG1lbGJfaG91c2UgJT4lIHNlbGVjdChTdWJ1cmIsIFBvc3Rjb2RlLCBDb3VuY2lsQXJlYSkNCmRmJENvdW5jaWxBcmVhIDwtIGRmJENvdW5jaWxBcmVhICU+JSByZXBsYWNlX25hKCJOb3QgQXZhaWxhYmxlIikNCmRmJENvdW5jaWxBcmVhIDwtIGZhY3RvcihkZiRDb3VuY2lsQXJlYSkNCg0KIyBmdW5jdGlvbiB0byBnZXQgY2hpIHNxdWFyZSBwIHZhbHVlIGFuZCBDcmFtZXJzIFYNCmYgPC0gZnVuY3Rpb24oeCx5KSB7DQogIHRibCA8LSBkZiAlPiUgc2VsZWN0KHgseSkgJT4lIHRhYmxlKCkNCiAgY2hpc3FfcHZhbCA8LSByb3VuZChjaGlzcS50ZXN0KHRibCkkcC52YWx1ZSwgNCkNCiAgY3JhbVYgPC0gcm91bmQoY3JhbWVyc1YodGJsKSwgNCkgDQogIGRhdGEuZnJhbWUoeCwgeSwgY2hpc3FfcHZhbCwgY3JhbVYpIH0NCg0KIyBjcmVhdGUgdW5pcXVlIGNvbWJpbmF0aW9ucyBvZiBjb2x1bW4gbmFtZXMNCiMgc29ydGluZyB3aWxsIGhlbHAgZ2V0dGluZyBhIGJldHRlciBwbG90ICh1cHBlciB0cmlhbmd1bGFyKQ0KZGZfY29tYiA8LSBkYXRhLmZyYW1lKHQoY29tYm4oc29ydChuYW1lcyhkZikpLCAyKSksIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQ0KDQojIGFwcGx5IGZ1bmN0aW9uIHRvIGVhY2ggdmFyaWFibGUgY29tYmluYXRpb24NCmRmX3JlcyA8LSBtYXAyX2RmKGRmX2NvbWIkWDEsIGRmX2NvbWIkWDIsIGYpDQpgYGANCg0KYGBge3IgUGxvdCBjb3JyZWxhdGlvbiByZXN1bHRzfQ0KZGZfcmVzICU+JQ0KICBnZ3Bsb3QoYWVzKHgsIHksIGZpbGwgPSBjaGlzcV9wdmFsKSkrDQogIGdlb21fdGlsZSgpKw0KICBnZW9tX3RleHQoYWVzKHgseSxsYWJlbD1jcmFtVikpKw0KICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0icmVkIiwgaGlnaD0ieWVsbG93IikrDQogIHRoZW1lX2NsYXNzaWMoKQ0KYGBgDQoNCg0KVGhlcmUgYXJlIHN0cm9uZyBjb3JyZWxhdGlvbnMgYmV0d2VlbiBlYWNoIG9mIHRoZSAzIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBiYXNlZCBvbiB0aGUgaGlnaCBDcmFtZXIncyBWIGFuZCB0aGUgcC12YWx1ZSBvZiBjaGktc3F1YXJlZCB0ZXN0IGNsb3NlIHRvIDAuIFRoZXJlZm9yZSwgd2Ugd2lsbCBvbmx5IHVzZSAqU3VidXJiKiBpbiB0aGUgcHJlZGljdGlvbiBtb2RlbCBhbmQgZHJvcCB0aGUgb3RoZXIgMiB2YXJpYWJsZXMgYXMgdGhlcmUgd2lsbCBiZSBsaXR0bGUgaW5mb3JtYXRpb24gbG9zcy4gRm9yIHRoZSBzYW1lIHJlYXNvbiwgKkFkZHJlc3MqLCAqTGF0dGl0dWRlKiBhbmQgKkxvbmd0aXR1ZGUqIGNhbiBhbHNvIGJlIGRyb3BwZWQgYXMgdGhleSBvbmx5IGNvbnRhaW4gdW5pcXVlIGluZm9ybWF0aW9uIGFib3V0IGxvY2F0aW9uLg0KDQojIyBUcmFuc2Zvcm1hdGlvbiBvZiBEYXRlIHZhcmlhYmxlDQoNClRoZSBkYXRlIHRoYXQgdGhlIGhvdXNlIGlzIHNvbGQgY291bGQgYmUgb2Ygc29tZSB2YWx1ZXMsIGV2ZW4gdGhvdWdoIGl0IGlzIG5vdCByZWFsbHkgaW5mb3JtYXRpdmUgd2l0aCBqdXN0IHRoZSBleGFjdCBkYXRlLiBXZSB3aWxsIHNlcGFyYXRlIHRoZSB2YXJpYWJsZSAqRGF0ZSogaW50byAqTW9udGhZZWFyKiBpbmRpY2F0aW5nIHRoZSBtb250aCBhbmQgdGhlIHllYXIgaXQgaXMgaW4uDQoNCmBgYHtyIENoYW5nZSBkYXRlIHRvIG1vbnRoIGFuZCB5ZWFyfQ0KbWVsYl9ob3VzZV9wcmVwIDwtIG1lbGJfaG91c2VfcHJlcCAlPiUgDQogIG11dGF0ZShNb250aFllYXIgPSBmb3JtYXQoYXMuRGF0ZShEYXRlKSwgIiVZLSViIikpDQpgYGANCg0KYGBge3IgQ29udmVydCBhbGwgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGludG8gZmFjdG9yfQ0KbWVsYl9ob3VzZV9wcmVwJFN1YnVyYiA8LSBmYWN0b3IobWVsYl9ob3VzZV9wcmVwJFN1YnVyYikNCm1lbGJfaG91c2VfcHJlcCRUeXBlIDwtIGZhY3RvcihtZWxiX2hvdXNlX3ByZXAkVHlwZSkNCm1lbGJfaG91c2VfcHJlcCRNZXRob2QgPC0gZmFjdG9yKG1lbGJfaG91c2VfcHJlcCRNZXRob2QpDQptZWxiX2hvdXNlX3ByZXAkU2VsbGVyRyA8LSBmYWN0b3IobWVsYl9ob3VzZV9wcmVwJFNlbGxlckcpDQptZWxiX2hvdXNlX3ByZXAkUmVnaW9ubmFtZSA8LSBmYWN0b3IobWVsYl9ob3VzZV9wcmVwJFJlZ2lvbm5hbWUpDQptZWxiX2hvdXNlX3ByZXAkQWdlIDwtIGZhY3RvcihtZWxiX2hvdXNlX3ByZXAkQWdlKQ0KIyBPcmRlciBtb250aCBuYW1lDQptb250aGxldmVsIDwtIHBhc3RlMChyZXAoMjAxNjoyMDE3LCBlYWNoID0gMTIpLCAiLSIsIG1vbnRoLmFiYikNCm1lbGJfaG91c2VfcHJlcCRNb250aFllYXIgPC0gZmFjdG9yKG1lbGJfaG91c2VfcHJlcCRNb250aFllYXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgbGV2ZWxzID0gbW9udGhsZXZlbCkNCmBgYA0KDQojIyBDb3JyZWxhdGlvbiBvZiBjb250aW51b3VzIHZhcmlhYmxlcw0KDQpOZXh0IHdlIHdpbGwgY2hlY2sgdGhlIGNvcnJlbGF0aW9uIHBsb3QuIFRoZXJlIGFyZSA3IGNvbnRpbnVvdXMgcHJlZGljdG9yIHZhcmlhYmxlcyByZW1haW5lZC4gTGV0IHVzIGNoZWNrIGlmIHRoZXkgYXJlIGNvcnJlbGF0ZWQgd2l0aCAqUHJpY2UqIHNvbGQuDQoNCmBgYHtyIENvcnJlbGF0aW9uIG9mIG51bWVyaWMgdmFyaWFibGVzfQ0KIyBSZWR1Y2UgdGhlIHZhcmlhYmxlcyBpbiB0aGUgZGF0YXNldA0KbWVsYl9ob3VzZV9wcmVwIDwtIG1lbGJfaG91c2VfcHJlcCAlPiUgDQogIHNlbGVjdChQcmljZSwgU3VidXJiLCBSb29tcywgVHlwZSwgTWV0aG9kLCBTZWxsZXJHLCBEaXN0YW5jZSwgQmVkcm9vbTINCiAgICAgICAgICwgQmF0aHJvb20sIENhciwgTGFuZHNpemUsIFJlZ2lvbm5hbWUsIFByb3BlcnR5Y291bnQsIEFnZSwgTW9udGhZZWFyKQ0KIyBJbmRleCB2ZWN0b3IgbnVtZXJpYyB2YXJpYWJsZXMNCm51bWVyaWNWYXJzIDwtIHdoaWNoKHNhcHBseShtZWxiX2hvdXNlX3ByZXAsIGlzLm51bWVyaWMpKQ0KbnVtZXJpY1Zhck5hbWVzIDwtIG5hbWVzKG51bWVyaWNWYXJzKSAjc2F2aW5nIG5hbWVzIHZlY3RvciBmb3IgdXNlIGxhdGVyIG9uDQojIENvcnJlbGF0aW9ucyBvZiBhbGwgbnVtZXJpYyB2YXJpYWJsZXMNCmFsbF9udW1WYXIgPC0gbWVsYl9ob3VzZV9wcmVwWywgbnVtZXJpY1ZhcnNdDQpjb3JfbnVtVmFyIDwtIGNvcihhbGxfbnVtVmFyLCB1c2UgPSAicGFpcndpc2UuY29tcGxldGUub2JzIikNCmNvcnJwbG90Lm1peGVkKGNvcl9udW1WYXIsIHRsLmNvbD0iYmxhY2siLCB0bC5wb3MgPSAibHQiKQ0KYGBgDQpUaGUgbnVtYmVyIG9mIHJvb21zLCBiZWRyb29tcyBhbmQgYmF0aHJvb21zIHNlZW1zIHRvIGJlIGNvcnJlbGF0ZWQgd2l0aCB0aGUgcHJpY2Ugb2YgdGhlIGhvdXNlLiBSb29tcyBhbmQgQmVkcm9vbXMgYXJlIGFsc28gaGlnaGx5IGNvcnJlbGF0ZWQsIHNvIHdlIGNhbiBkcm9wIEJlZHJvb21zIGluIHRoZSBtb2RlbGxpbmcgcHJvY2Vzcy4gV2Ugd2lsbCBhbHNvIG9taXQgKlByb3BlcnR5Q291bnQqIGJlY2F1c2UgaXQgaGFzIGFuIG9uZSBvbiBvbmUgcmVwcmVzZW50YXRpb24gb2YgZWFjaCAqU3VidXJiKi4NCg0KYGBge3IgRHJvcCBiZWRyb29tfQ0KbWVsYl9ob3VzZV9wcmVwIDwtIG1lbGJfaG91c2VfcHJlcCAlPiUNCiAgc2VsZWN0KC1CZWRyb29tMiwgLVByb3BlcnR5Y291bnQpDQpgYGANCg0KDQojIERhdGEgUHJlLXByb2Nlc3NpbmcNCg0KQWZ0ZXIgdHJhbnNmb3JtaW5nIGFuZCByZWR1Y2luZyB0aGUgbnVtYmVyIG9mIHByZWRpY3RvcnM6DQoNCmBgYHtyIE51bWJlciBvZiBwcmVkaWN0b3JzfQ0KbmV3bnVtZXJpY1ZhcnMgPC0gbWVsYl9ob3VzZV9wcmVwICU+JSANCiAgc2VsZWN0KHdoZXJlKGlzLm51bWVyaWMpKQ0Kbm9ubnVtZXJpY1ZhcnMgPC0gbWVsYl9ob3VzZV9wcmVwICU+JSANCiAgc2VsZWN0KCF3aGVyZShpcy5udW1lcmljKSkNCmNhdCgnVGhlcmUgYXJlJywgbGVuZ3RoKG5ld251bWVyaWNWYXJzKSAtIDEsICdjb250aW51b3VzIHByZWRpY3RvciB2YXJpYWJsZXMnDQogICAgLCAnYW5kJywgbGVuZ3RoKG5vbm51bWVyaWNWYXJzKSwgJ2NhdGVnb3JpY2FsIHByZWRpY3RvciB2YXJpYWJsZXMuJykNCmBgYA0KIyMgQ29udGludW91cyB2YXJpYWJsZXMNCg0KYGBge3IgSGlzdG9ncmFtIG9mIG51bWVyaWMgdmFyaWFibGVzfQ0KcGxvdF9oaXN0b2dyYW0obmV3bnVtZXJpY1ZhcnMsIG5jb2wgPSAzTCkNCmBgYA0KVGhlIGNvbnRpbm91cyB2YXJpYWJsZXMgbmVlZCB0byBiZSBub3JtYWxpemVkIHRvIGFsbGV2aWF0ZSBza2V3bmVzcy4gVGhlIGhpc3RvZ3JhbSBzaG93cyBzb21lIGFibm9ybWFsIHBhdHRlcm4gaW4gKkxhbmRzaXplKi4gQSBiZXR0ZXIgbG9vayBhdCBpdHMgYm94cGxvdCBzaG93cyBhbiBleHRyZW1lIG91dGxpZXIuIFdlIHNoYWxsIHJlbW92ZSB0aGF0IGluc3RhbmNlIGZvciBiZXR0ZXIgbW9kZWwgcHJlZGljdGlvbi4NCg0KYGBge3IgTGFuZHNpemV9DQpnZ3Bsb3QobWVsYl9ob3VzZV9wcmVwLCBhZXMoTGFuZHNpemUpKSArDQogIGdlb21fYm94cGxvdCgpKw0KICBjb29yZF9mbGlwKCkNCmBgYA0KYGBge3IgUmVtb3ZlIG91dGxpZXJ9DQptZWxiX2hvdXNlX3ByZXAgPC0gbWVsYl9ob3VzZV9wcmVwWy13aGljaChtZWxiX2hvdXNlX3ByZXAkTGFuZHNpemUgPT0gbWF4KG1lbGJfaG91c2VfcHJlcCRMYW5kc2l6ZSkpLCBdDQpgYGANCg0KIyMgQ2F0ZWdvcmljYWwgdmFyaWFibGVzDQoNCk5vdyBsb29raW5nIGF0IHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMsICpTdWJ1cmIqIGhhcyAzMTQgY2F0ZWdvcmllcyBhbmQgKlNlbGxlckcqIGhhcyAyNjggY2F0ZWdvcmllcywgd2hpY2ggd2lsbCBuZWVkIGJpbm5pbmcuDQoNCmBgYHtyIEJhciBwbG90IG9mIGNhdGVnb3JpY2FsIHZhcmlhYmxlc30NCiMgUGxvdCB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzDQpwbG90X2Jhcihub25udW1lcmljVmFycywgbmNvbCA9IDNMKQ0KYGBgDQpUaGUgY29udGluZ2VuY3kgdGFibGUgZm9yICpNb250aFllYXIqIHNob3dzIHRoYXQgdGhlcmUgYXJlIHZlcnkgZmV3IG9ic2VydmF0aW9ucyBpbiAyMDE2LUphbiBhbmQgMjAxNi1GZWIsIGFuZCB0aGVyZSB3ZXJlIGV2ZW4gbm8gaG91c2Ugc2FsZSBpbiAyMDE2LU1hciBhbmQgMjAxNy1KYW4gKHRoZXJlIHdlcmUgbm8gcHJvYmxlbSBpbiB0aGUgY29udGluZ2VuY3kgdGFibGUgZm9yIG90aGVyIGNhdGVnb3JpY2FsIHZhcmlhYmxlcykuDQoNCmBgYHtyIE1vbnRoWWVhciB0YWJsZX0NCnRhYmxlKG1lbGJfaG91c2VfcHJlcCRNb250aFllYXIpDQpgYGANClRvIHByZXZlbnQgb3ZlcmZpdHRpbmcsIHdlIHdpbGwgYmluIDIwMTYtSmFuIGFuZCAyMDE2LUZlYiB0b2dldGhlci4NCg0KYGBge3IgQmluIE1vbnRoWWVhcn0NCm1lbGJfaG91c2VfcHJlcCA8LSBtZWxiX2hvdXNlX3ByZXAgJT4lIA0KICBtdXRhdGUoTW9udGhZZWFyTmV3ID0gaWZlbHNlKE1vbnRoWWVhciAlaW4lIGMoIjIwMTYtSmFuIiwgIjIwMTYtRmViIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjIwMTYtSmFuRmViIiwgYXMuY2hhcmFjdGVyKE1vbnRoWWVhcikpKSAlPiUgDQogIHNlbGVjdCgtTW9udGhZZWFyKQ0KYGBgDQoNCiMjIFNhbXBsaW5nIGFuZCBkYXRhIHBhcnRpdGlvbmluZw0KDQpXZSBzcGxpdCB0aGUgZGF0YSB0byBiZWdpbiB0aGUgbW9kZWxsaW5nIHByb2Nlc3MgYnkgcGVyZm9ybWluZyBzdHJhdGlmaWVkIHNhbXBsaW5nIHRvIGtlZXAgdGhlIHNpbWlsYXIgdGFyZ2V0IGRpc3RyaWJ1dGlvbi4gVGhlIHRyYWluaW5nIHNldCB3aWxsIGtlZXAgNzAlIG9mIHRoZSBvYnNlcnZhdGlvbnMgYW5kIHRoZSB0ZXN0IHNldCBrZWVwcyAzMCUuDQoNCmBgYHtyIFNwbGl0IHRoZSBkYXRhfQ0KIyBTdHJhdGlmaWVkIHNhbXBsaW5nIHdpdGggdGhlIHJzYW1wbGUgcGFja2FnZQ0Kc2V0LnNlZWQoMTIzNSkNCnNwbGl0X3N0cmF0ICA8LSBpbml0aWFsX3NwbGl0KG1lbGJfaG91c2VfcHJlcCwgcHJvcCA9IDAuNw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBzdHJhdGEgPSAiUHJpY2UiKQ0KbWVsYl90cmFpbiAgPC0gdHJhaW5pbmcoc3BsaXRfc3RyYXQpDQptZWxiX3Rlc3QgICA8LSB0ZXN0aW5nKHNwbGl0X3N0cmF0KQ0KYGBgDQoNCiMjIEZlYXR1cmUgZW5naW5lZXJpbmcNCg0KRXhhbWluaW5nIHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMsIHRoZXJlIGFyZSBubyBuZWFyLXplcm8gdmFyaWFuY2UgdmFyaWFibGVzLg0KYGBge3IgTmVhcnplcm8gdmFyaWFuY2UgY2hlY2t9DQojIENoZWNrIGZvciBuZWFyLXplcm8gdmFyaWFuY2VzDQpuZWFyWmVyb1ZhcihtZWxiX3RyYWluLCBzYXZlTWV0cmljcyA9IFRSVUUpICU+JSANCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oKSAlPiUgDQogIGZpbHRlcihuenYpDQpgYGANCg0KSW4gdGhlIG5leHQgc3RlcCB3ZSBleGVjdXRlIG5vcm1hbGl6YXRpb24gdG8gbWluaW1pemUgdGhlIHNrZXduZXNzIG9mIG51bWVyaWNhbCB2YXJpYWJsZXMuIFdlIHVzZSBsb2cgdHJhbnNmb3JtIGZvciB0aGUgdGFyZ2V0IHZhcmlhYmxlICpQcmljZSogYW5kIFllby1Kb2huc29uIHRyYW5zZm9ybWF0aW9uIGZvciB0aGUgcHJlZGljdG9ycyBzaW5jZSB0aGUgdmFsdWVzIGFyZSBub3Qgc3RyaWN0bHkgcG9zaXRpdmUuIFdlIGFsc28gc3RhbmRhcmRpemUgdGhlIG51bWVyaWNhbCBwcmVkaWN0b3JzIHNvIHRoYXQgdGhleSBoYXZlIHRoZSBzYW1lIG1hZ25pdHVkZSBpbiB0aGUgbW9kZWxzLiBUaGVuLCBhcyBzYWlkIGFib3ZlLCB3ZSB3aWxsIHJlZHVjZSB0aGUgbnVtYmVyIG9mIGNhdGVnb3JpZXMgaW4gKlN1YnVyYiogYW5kICpTZWxsZXJHKi4gQWxsIGxldmVscyB0aGF0IGFyZSBvYnNlcnZlZCBpbiBsZXNzIHRoYW4gMC41JSBvZiB0aGUgdHJhaW5pbmcgc2FtcGxlIHdpbGwgYmUgY29sbGFwc2VkIGludG8gYW4gKk90aGVyKiBjYXRlZ29yeS4gV2UgaGF2ZSB0byBjYXJlZnVsbHkgY2hvb3NlIGEgc21hbGwgcGVyY2VudGFnZSBiZWNhdXNlIG9mIHBvc3NpYmxlIGluZm9ybWF0aW9uIGxvc3MuIExhc3RseSwgd2UgdXNlIGR1bW15IGVuY29kaW5nIHRvIHRyYW5zZm9ybSB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGludG8gbnVtZXJpYyByZXByZXNlbnRhdGlvbnMuIFRoZSBmZWF0dXJlIGVuZ2luZWVyaW5nIHN0ZXBzIGluIHN1bW1hcnkgYXJlOg0KMS4gTm9ybWFsaXplIG51bWVyaWMgZmVhdHVyZXMuDQoyLiBTdGFuZGFyZGl6ZSBudW1lcmljIHByZWRpY3RvciBmZWF0dXJlcy4NCjMuIEJpbm5pbmcgKlN1YnVyYiogYW5kICpTZWxsZXJHKi4NCjQuIER1bW15IGVuY29kZSBjYXRlZ29yaWNhbCBmZWF0dXJlcy4NCg0KYGBge3IgUHJlcHJvY2Vzc2luZyByZWNpcGV9DQojIENyZWF0ZSByZWNpcGUgZm9yIHByZS1wcm9jZXNzaW5nDQpibHVlcHJpbnRfYWxsIDwtIHJlY2lwZShQcmljZSB+IC4sIGRhdGEgPSBtZWxiX3RyYWluKSAlPiUNCiAgc3RlcF9sb2coYWxsX291dGNvbWVzKCkpICU+JSANCiAgc3RlcF9ZZW9Kb2huc29uKGFsbF9udW1lcmljKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lDQogIHN0ZXBfY2VudGVyKGFsbF9udW1lcmljKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lIA0KICBzdGVwX3NjYWxlKGFsbF9udW1lcmljKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lDQogIHN0ZXBfb3RoZXIoU3VidXJiLCB0aHJlc2hvbGQgPSAwLjAwNSwgb3RoZXIgPSAiT3RoZXIiKSAlPiUgDQogIHN0ZXBfb3RoZXIoU2VsbGVyRywgdGhyZXNob2xkID0gMC4wMDUsIG90aGVyID0gIk90aGVyIikgJT4lIA0KICBzdGVwX2R1bW15KGFsbF9ub21pbmFsKCkpDQpwcmVwYXJlIDwtIHByZXAoYmx1ZXByaW50X2FsbCwgdHJhaW5pbmcgPSBtZWxiX3RyYWluKQ0KYmFrZWRfdHJhaW4gPC0gYmFrZShwcmVwYXJlLCBuZXdfZGF0YSA9IG1lbGJfdHJhaW4pICMgZGYgYWZ0ZXIgcHJlcHJvY2Vzcw0KI3Bsb3RfYmFyKGJha2VkX3RyYWluKSAjIGNoZWNrIHRoZSBiYXIgcGxvdA0KYGBgDQoNCiMgTW9kZWxsaW5nDQoNCldlIGFyZSBnb2luZyB0byB0ZXN0IDMgdHlwZXMgb2YgcmVncmVzc2lvbiBtb2RlbHMgb24gdGhlIGRhdGE6IGEgc2ltcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsLCBhIHJlZ3VsYXJpemVkIG1vZGVsIGFuZCBhIGxpZ2h0IGdyYWRpZW50IGJvb3N0aW5nIG1vZGVsLiAxMC1mb2xkIGNyb3NzLXZhbGlkYXRpb24gc2FtcGxpbmcgaXMgdXNlZCBmb3IgZXZhbHVhdGlvbi4NCg0KYGBge3IgQ3Jvc3MgdmFsaWRhdGlvbn0NCiMgRGVmaW5lIGEgY3Jvc3MgdmFsaWRhdGlvbiBtZXRob2QNCnRyX2NvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICwgbnVtYmVyID0gMTANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICwgc2F2ZVByZWRpY3Rpb25zID0gVFJVRSkNCmBgYA0KDQojIyBMaW5lYXIgbW9kZWwgYW5kIFJlZ3VsYXJpemVkIG1vZGVsDQoNClVzaW5nIHRoZSAqY2FyZXQqIHBhY2thZ2UsIHRoZSBsaW5lYXIgbW9kZWwgYW5kIHRoZSByZWd1bGFyaXplZCBtb2RlbCBjYW4gYmUgcHJlLXByb2Nlc3NlZCBhbmQgdHJhaW5lZCBpbiB0aGUgc2FtZSBmYXNoaW9uLg0KDQpgYGB7ciBMaW5lYXIgcmVncmVzc2lvbn0NCnNldC5zZWVkKDEyMzUpICAjIGZvciByZXByb2R1Y2liaWxpdHkNCmN2X2xtIDwtIHRyYWluKA0KICBibHVlcHJpbnRfYWxsDQogICwgZGF0YSA9IG1lbGJfdHJhaW4NCiAgLCBtZXRob2QgPSAibG0iDQogICwgdHJDb250cm9sID0gdHJfY29udHJvbA0KKQ0KYGBgDQoNClRoZSByZWd1bGFyaXplZCBtb2RlbCwgb3IgZWxhc3RpYyBuZXQsIGhhcyAyIGh5cGVycGFyYW1ldGVycyBhbHBoYSBhbmQgbGFtYmRhLiBXZSBzZXQgKnR1bmVsZW5ndGggPSAxMCogdG8gZG8gYSBncmlkIHNlYXJjaCBvZiB0aGUgMTAgZGVmYXVsdCB2YWx1ZXMgb2YgZWFjaCBwYXJhbWV0ZXIuDQoNCmBgYHtyIEVsYXN0aWMgbmV0fQ0Kc2V0LnNlZWQoMTIzNSkNCmN2X2VuIDwtIHRyYWluKA0KICBibHVlcHJpbnRfYWxsDQogICwgZGF0YSA9IG1lbGJfdHJhaW4NCiAgLCBtZXRob2QgPSAiZ2xtbmV0Ig0KICAsIHRyQ29udHJvbCA9IHRyX2NvbnRyb2wNCiAgLCB0dW5lTGVuZ3RoID0gMTANCikNCmBgYA0KDQpCYXNlZCBvbiB0aGUgcGxvdCBvZiB0aGUgcGVyZm9ybWFuY2UgbWVhc3VyZXMsIHRoZXJlIGFyZSBubyBzaWduaWZpY2FudCBkaWZmZXJlbmNlcyBiZXR3ZWVuIHRoZSBsaW5lYXIgbW9kZWwgYW5kIHRoZSByZWd1bGFyaXplZCBtb2RlbC4gSW4gdGhpcyBjYXNlLCB0aGUgbGluZWFyIG1vZGVsIG1pZ2h0IGJlIGEgYmV0dGVyIGNob2ljZSBiZWNhdXNlIG9mIGl0cyBpbnRlcnByZXRhYmlsaXR5Lg0KDQpgYGB7ciBQZXJmb3JtYW5jZSBtZWFzdXJlc30NCnJlc2FtcHMgPC0gcmVzYW1wbGVzKGxpc3QoDQogIExNID0gY3ZfbG0NCiAgLCBHTE1uZXQgPSBjdl9lbg0KKSkNCnN1bW1hcnkocmVzYW1wcykNCg0KdHJlbGxpcy5wYXIuc2V0KGNhcmV0VGhlbWUoKSkNCmJ3cGxvdChyZXNhbXBzKQ0KYGBgDQpUaGUgUk1TRSBpcyBhZnRlciBsb2cgdHJhbnNmb3JtYXRpb24gb2YgdGhlIHRhcmdldCB2YXJpYWJsZS4gUmV2ZXJzaW5nIGl0IHRvIG5vcm1hbCBzY2FsZSBjYW4gaGVscCB3aXRoIHByYWN0aWNhbCByZWFzb25pbmcuDQoNCmBgYHtyfQ0Kcm1zZXQgPC0gZnVuY3Rpb24obW9kZWwpew0KICB5ID0gbG9nKG1lbGJfdHJhaW4kUHJpY2UpDQogIHkucHJlZCA9IHByZWRpY3QobW9kZWwsIG1lbGJfdHJhaW4pDQogIHkgPC0gZXhwKHkpDQogIHkucHJlZCA8LSBleHAoeS5wcmVkKQ0KICByZXR1cm4oc3FydChtZWFuKCh5IC0geS5wcmVkKV4yKSkpDQp9DQoNCnBhc3RlKCJUaGUgUk1TRSBvZiB0aGUgbGluZWFyIG1vZGVsIGlzOiIsIHJtc2V0KGN2X2xtKSkNCmBgYA0KDQojIyBMaWdodCBncmFkaWVudCBib29zdGluZyBtYWNoaW5lDQoNCkdyYWRpZW50IEJvb3N0aW5nIE1hY2hpbmUgaXMgYSBtb2Rlcm4gbWFjaGluZSBsZWFybmluZyB0ZWNobmlxdWUgdGhhdCBtYWtlcyB1c2Ugb2YgYW4gZW5zZW1ibGUgb2Ygd2VhayBsZWFybmVycywgdHlwaWNhbGx5IGRlY2lzaW9uIHRyZWVzLiBMaWdodCBHQk0gKGJ5IE1pY3Jvc29mdCkgaXMgYSBoaWdoLXBlcmZvcm1hbmNlIGZyYW1ld29yayB0aGF0IG9mZmVycyBzaWduaWZpY2FudGx5IGZhc3QgdHJhaW5pbmcgc3BlZWQgYW5kIGFjY3VyYWN5IHRoYW5rcyB0byBsZWFmLXdpc2UgZ3Jvd3RoIGFuZCBvdGhlciBmZWF0dXJlcy4gVG8gZml0IHRoZSBtb2RlbCwgZmlyc3Qgd2UgbmVlZCB0byBjb252ZXJ0IHRoZSB0cmFpbmluZyBkYXRhIGludG8gYSBtYXRyaXggdGhhdCBpcyBzdXBwb3J0ZWQgYnkgKmxpZ2h0Z2JtKiBwYWNrYWdlLg0KDQpgYGB7ciBMaWdodEdCTSBkYXRhfQ0KDQojIENvbnZlcnQgaW50byBsZ2IgZGF0YXNldA0KbmV3X21lbGIgPC0gbGdiLmNvbnZlcnRfd2l0aF9ydWxlcyhtZWxiX3RyYWluICU+JSBzZWxlY3QoLVByaWNlKSkNCnRyYWluX2xhYmVsIDwtIG1lbGJfdHJhaW5bLCAiUHJpY2UiXQ0KdHJhaW5fbWF0cml4IDwtIGxnYi5EYXRhc2V0KGRhdGEgPSBhcy5tYXRyaXgobmV3X21lbGIkZGF0YSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIGxhYmVsID0gYXMubWF0cml4KHRyYWluX2xhYmVsKSkNCg0KYGBgDQoNClRoZXJlIGFyZSBhIHZhcmlldHkgb2YgaHlwZXJwYXJhbWV0ZXJzIGluIGxpZ2h0Z2JtLiBXZSB3aWxsIHBlcmZvcm0gdHVuaW5nIGZvciA0IGltcG9ydGFudCBoeXBlcnBhcmFtZXRlcnM6DQoxLiBudW1fbGVhdmVzOiB0aGUgbWF4aW11bSBudW1iZXIgb2YgbGVhdmVzIHRoYXQgZWFjaCBsZWFybmVycyBoYXMuDQoyLiBtYXhfZGVwdGg6IHRoZSBtYXhpbXVtIGRlcHRoIG9mIGVhY2ggdHJhaW5lZCB0cmVlLg0KMy4gYmFnZ2luZ19mcmFjdGlvbjogdGhlIHBlcmNlbnRhZ2Ugb2Ygcm93cyB1c2VkIHBlciBpdGVyYXRpb24uDQo0LiBmZWF0dXJlX2ZyYWN0aW9uOiB0aGUgcGVyY2VudGFnZSBvZiByYW5kb20gc3Vic2V0IG9mIGZlYXR1cmVzIHBlciBpdGVyYXRpb24uDQoNCkh5cGVycGFyYW1ldGVyIHR1bmluZyB3aWxsIGJlIGNhcnJpZWQgb3V0IGJ5IEJheWVzaWFuIE9wdGltaXphdGlvbiwgd2hpY2ggdXNlcyB0aGUgaW5mb3JtYXRpb24gZnJvbSBwcmlvciBtb2RlbCBldmFsdWF0aW9ucyB0byBndWlkZSBmdXR1cmUgcGFyYW1ldGVyIHNlYXJjaGVzLiBJbiB0aGlzIG1ldGhvZCwgdGhlIHByaW9yIHJlc3BvbnNlIGRpc3RyaWJ1dGlvbiBpcyBpdGVyYXRpdmVseSB1cGRhdGVkIGJhc2VkIG9uIG91ciBiZXN0IGd1ZXNzIG9mIHdoZXJlIHRoZSBiZXN0IHBhcmFtZXRlcnMgYXJlLiBUaGUgcGFja2FnZSB1c2VkIGlzICpQYXJCYXllc2lhbk9wdGltaXphdGlvbiouDQoNCmBgYHtyIExnYiB0dW5pbmcgZGVmaW5pdGlvbn0NCiMgRGVmaW5lIHNjb3JpbmcgZnVuY3Rpb24gZm9yIEJheWVzT3B0DQpzY29yaW5nRnVuY3Rpb24gPC0gZnVuY3Rpb24obnVtX2xlYXZlcywgbWF4X2RlcHRoDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBiYWdnaW5nX2ZyYWN0aW9uLCBmZWF0dXJlX2ZyYWN0aW9uKSB7DQogIA0KICBQYXJzIDwtIGxpc3QoDQogICAgb2JqZWN0aXZlID0gInJlZ3Jlc3Npb24iDQogICAgLCBtZXRyaWMgPSAicm1zZSINCiAgICAsIG51bV9pdGVyYXRpb25zID0gMTAwMA0KICAgICwgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gNTANCiAgICAsIGxlYXJuaW5nX3JhdGUgPSAwLjAxDQogICAgLCBudW1fbGVhdmVzID0gbnVtX2xlYXZlcw0KICAgICwgbWF4X2RlcHRoID0gbWF4X2RlcHRoDQogICAgLCBiYWdnaW5nX2ZyYWN0aW9uID0gYmFnZ2luZ19mcmFjdGlvbg0KICAgICwgYmFnZ2luZ19mcmVxID0gMQ0KICAgICwgZmVhdHVyZV9mcmFjdGlvbiA9IGZlYXR1cmVfZnJhY3Rpb24NCiAgKQ0KICANCiAgbGdiY3YgPC0gbGdiLmN2KA0KICAgIHBhcmFtcyA9IFBhcnMNCiAgICAsIGRhdGEgPSB0cmFpbl9tYXRyaXgNCiAgICAsIG5mb2xkID0gNUwNCiAgICAsIHN0cmF0aWZpZWQgPSBGQUxTRQ0KICApDQogIA0KICByZXR1cm4obGlzdChTY29yZSA9IC0xKmxnYmN2JGJlc3Rfc2NvcmUNCiAgICAgICAgICAgICAgLCBucm91bmRzID0gbGdiY3YkYmVzdF9pdGVyDQogICkNCiAgKQ0KfQ0KDQojIERlZmluZSB0aGUgc2VhcmNoIGJvdW5kYXJpZXMNCmJvdW5kcyA8LSBsaXN0KA0KICBudW1fbGVhdmVzID0gYyg1TCwgMzBMKQ0KICAsIG1heF9kZXB0aCA9IGMoMkwsIDVMKQ0KICAsIGJhZ2dpbmdfZnJhY3Rpb24gPSBjKDAuNCwgMSkNCiAgLCBmZWF0dXJlX2ZyYWN0aW9uID0gYygwLjQsIDEpDQopDQoNCmBgYA0KDQpXZSBkZWZpbmUgYSBzY29yaW5nIGZ1bmN0aW9uIHRvIG9wdGltaXplLiBUaGVuLCB3ZSBzZXQgdGhlIHNlYXJjaCBib3VuZGFyaWVzIGZvciBlYWNoIGh5cGVycGFyYW1ldGVyLiBCYXllc09wdCB0aGVuIHRyeSBmaW5kIHBhcmFtZXRlcnMgdGhhdCBtYXhpbWl6ZSB0aGUgc2NvcmluZyBmdW5jdGlvbiBiYXNlZCBvbiBzb21lIGluaXRpYWwgc2NvcmluZy4NCg0KYGBge3IgQmF5ZXNPcHQgdHVuaW5nLCByZXN1bHRzPSdoaWRlJ30NCnN0YXJ0LnRpbWUgPC0gU3lzLnRpbWUoKQ0Kc2V0LnNlZWQoMTIzNSkNCg0Kb3B0T2JqIDwtIGJheWVzT3B0KA0KICBGVU4gPSBzY29yaW5nRnVuY3Rpb24NCiAgLCBib3VuZHMgPSBib3VuZHMNCiAgLCBpbml0UG9pbnRzID0gNQ0KICAsIGl0ZXJzLm4gPSAyMA0KICAsIGl0ZXJzLmsgPSAxDQopDQplbmQudGltZSA8LSBTeXMudGltZSgpDQp0aW1lLnRha2VuIDwtIGVuZC50aW1lIC0gc3RhcnQudGltZQ0KdGltZS50YWtlbg0KYGBgDQoNClRoZSByZXN1bHRzIG9mIDIwIGl0ZXJhdGlvbnMgYXJlIGFzIGJlbG93Lg0KYGBge3IgQmF5ZXNPcHQgc3VtbWFyeX0NCm9wdE9iaiRzY29yZVN1bW1hcnkNCmBgYA0KDQpUaGUgYmVzdCB0dW5lZCBoeXBlcnBhcmFtZXRlcnMgYXJlIG51bV9sZWF2ZXMgPSAyMSwgbWF4X2RlcHRoID0gNSwgYmFnZ2luZ19mcmFjdGlvbiA9IDAuNzQxIGFuZCBmZWF0dXJlX2ZyYWN0aW9uID0gMC42NTguDQoNCmBgYHtyIEJheWVzT3B0IGdldCBiZXN0IHNjb3JlfQ0KZ2V0QmVzdFBhcnMob3B0T2JqKQ0KYGBgDQpUaGUgUk1TRSBvZiB0aGUgTGlnaHQgR0JNIG1vZGVsIGlzIGJldHRlciB0aGFuIHRoZSBMaW5lYXIgbW9kZWwuIEZvciBwcmVkaWN0aW9uIHB1cnBvc2Ugd2UgcHJvY2VlZCB3aXRoIHRoZSBMaWdodCBHQk0gbW9kZWwgd2l0aCB0aGUgYmVzdCBoeXBlcnBhcmFtZXRlcnMsIGFsdGhvdWdoIHdlIGNhbiBjaG9vc2UgdGhlIGxpbmVhciBtb2RlbCBpZiB0aGUgZ29hbCBpcyBpbnRlcnByZXRhYmlsaXR5Lg0KDQpgYGB7ciBMZ2Igcm1zZX0NCi0xKm1heChvcHRPYmokc2NvcmVTdW1tYXJ5JFNjb3JlKQ0KYGBgDQoNCiMjIEZpbmFsIG1vZGVsIHBlcmZvcm1hbmNlDQoNCldlIHVzZSB0aGUgY2hvc2VuIGh5cGVycGFyYW1ldGVycyB0byB0cmFpbiBhIExpZ2h0IEdCTSBtb2RlbCB3aXRoIGhpZ2hlciBpdGVyYXRpb25zLg0KDQpgYGB7ciBMZ2IgY2hvc2VuIG1vZGVsLCByZXN1bHRzPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KIyBIeXBlcnBhcmFtZXRlcg0KcGFyYW1zIDwtIGxpc3QoDQogIG9iamVjdGl2ZSA9ICJyZWdyZXNzaW9uIg0KICAsIG1ldHJpYyA9ICJSTVNFIg0KICAsIG51bV9pdGVyYXRpb25zID0gNTAwMA0KICAsIG51bV90aHJlYWRzID0gMg0KICAsIGxlYXJuaW5nX3JhdGUgPSAwLjAxDQogICwgbnVtX2xlYXZlcyA9IDIxDQogICwgbWF4X2RlcHRoID0gNQ0KICAsIHNlZWQgPSAxMjM1DQogICwgYmFnZ2luZ19mcmFjdGlvbiA9IDAuNzQxDQogICwgYmFnZ2luZ19mcmVxID0gMQ0KICAsIGZlYXR1cmVfZnJhY3Rpb24gPSAwLjY1OA0KKQ0KDQojIFRyYWluIGxnYiBtb2RlbA0KbGdiX2ZpbiA8LSBsZ2IudHJhaW4ocGFyYW1zID0gcGFyYW1zLCBkYXRhID0gdHJhaW5fbWF0cml4KQ0KYGBgDQpBcHBseWluZyB0aGUgZmluYWwgbW9kZWwgdG8gdGhlIHVuc2VlbiB0ZXN0IHNldCB5aWVsZHMgdGhlIGZvbGxvd2luZyByZXN1bHRzOg0KDQpgYGB7ciBQcmVkaWN0aW9ufQ0KIyBDb252ZXJ0IHRlc3Qgc2V0IGludG8gbGdiIGRhdGFzZXQNCm5ld19tZWxiX3Rlc3QgPC0gbGdiLmNvbnZlcnRfd2l0aF9ydWxlcyhtZWxiX3Rlc3QgJT4lIHNlbGVjdCgtUHJpY2UpKQ0KdGVzdF9sYWJlbCA8LSBtZWxiX3Rlc3QkUHJpY2UNCiMgUHJlZGljdCB0ZXN0IGRhdGENCnByZWRzIDwtIHByZWRpY3QobGdiX2ZpbiwgZGF0YSA9IGFzLm1hdHJpeChuZXdfbWVsYl90ZXN0JGRhdGEpKQ0KDQojIEFjY3VyYWN5IGNoZWNrDQptc2UgPC0gbWVhbigodGVzdF9sYWJlbCAtIHByZWRzKV4yKQ0KbWFlIDwtIGNhcmV0OjpNQUUodGVzdF9sYWJlbCwgcHJlZHMpDQpybXNlIDwtIGNhcmV0OjpSTVNFKHRlc3RfbGFiZWwsIHByZWRzKQ0KcnNxIDwtIGZ1bmN0aW9uICh4LCB5KSBjb3IoeCwgeSkgXiAyDQpyX3NxdWFyZWQgPC0gcnNxKHRlc3RfbGFiZWwsIHByZWRzKQ0KDQpjYXQoIk1TRTogIiwgbXNlLCAiXG5NQUU6ICIsIG1hZSwgIlxuUk1TRTogIiwgcm1zZSwgIlxuUi1zcXVhcmVkOiAiLCByX3NxdWFyZWQpDQpgYGANClRoZSBwbG90IGJldHdlZW4gYWN0dWFsIGFuZCBwcmVkaWN0ZWQgdmFsdWVzIGlzIGFzIGZvbGxvdzoNCg0KYGBge3IgUGxvdCBwcmVkaWN0aW9uIHZzIHRlc3QgZGF0YX0NCmRmX3Zpc3VhbCA8LSBkYXRhLmZyYW1lKHRlc3RfbGFiZWwsIHByZWRzKQ0KZGZfdmlzdWFsJG9ic2VydmF0aW9ucyA8LSAxOm5yb3coZGZfdmlzdWFsKQ0KZ2dwbG90KGRhdGEgPSBkZl92aXN1YWwpICsgDQogIGdlb21fbGluZShhZXMoeCA9IG9ic2VydmF0aW9ucywgeSA9IHRlc3RfbGFiZWwsIGNvbG9yID0gJ3Rlc3QnKSkgKyANCiAgZ2VvbV9saW5lKGFlcyh4ID0gb2JzZXJ2YXRpb25zLCB5ID0gcHJlZHMsIGNvbG9yID0gJ3ByZWRzJykpICsNCiAgZ2d0aXRsZSgiTWVsYm91cm5lIGhvdXNpbmcgdGVzdCBkYXRhIHByZWRpY3Rpb24iKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArDQogIHlsYWIoJ1ByaWNlJykNCmBgYA0KDQpCeSBwbG90dGluZyB0aGUgZmVhdHVyZSBpbXBvcnRhbmNlIGNoYXJ0IG9mIHRoZSBtb2RlbCwgd2UgY2FuIHNlZSB0aGF0ICpEaXN0YW5jZSogaGFzIHRoZSBiaWdnZXN0IGVmZmVjdCBvbiBwcmVkaWN0aW5nICpQcmljZSouIE90aGVyIGltcG9ydGFudCBmZWF0dXJlcyBpbiB0aGUgdG9wIDUgYXJlICpSZWdpb25uYW1lKiwgKlJvb21zKiwgKkxhbmRzaXplKiBhbmQgKkJhdGhyb29tKi4gSXQgc2VlbXMgbGlrZSBwcmljZSBpcyBhZmZlY3RlZCB0aGUgbW9zdCBieSB0aGUgbG9jYXRpb24gb2YgdGhlIGhvdXNlIChjbG9zZW5lc3MgdG8gdGhlIGNlbnRyYWwgYnVzaW5lc3MgZGlzdHJpY3QpIGFuZCB0aGUgc2l6ZSBvZiB0aGUgaG91c2UgKHJvb21zLCBsYW5kc2l6ZSwgYmF0aHJvb20pLg0KDQpgYGB7ciBGZWF0dXJlIGltcG9ydGFuY2V9DQp0cmVlX2ltcCA8LSBsZ2IuaW1wb3J0YW5jZShsZ2JfZmluLCBwZXJjZW50YWdlID0gVFJVRSkNCmxnYi5wbG90LmltcG9ydGFuY2UodHJlZV9pbXAsIHRvcF9uID0gMTBMLCBtZWFzdXJlID0gIkdhaW4iKQ0KYGBgDQojIENvbmNsdXNpb24NCg0KVGhlIGtlcm5lbCBoYXMgc2hvd24gc3RlcHMgb2YgYXBwbHlpbmcgYWR2YW5jZWQgbWFjaGluZSBsZWFybmluZyB0ZWNobmlxdWVzIHRvIGEgcmVncmVzc2lvbiBwcm9ibGVtLCBwYXJ0aWN1bGFybHkgcHJlZGljdGluZyBNZWxib3VybmUgaG91c2UgcHJpY2UuIEFmdGVyIGdldHRpbmcgdGhlIGRhdGFzZXQsIGV4cGxvcmF0aW9uIGFuZCBmZWF0dXJlIGVuZ2luZWVyaW5nIHNob3VsZCBiZSBkb25lIHRob3JvdWdobHkgdG8gaGVscCB3aXRoIG1vZGVsIGJ1aWxkaW5nLiBUaGUgTGlnaHQgR3JhZGllbnQgQm9vc3RpbmcgbW9kZWwgKExpZ2h0IEdCTSkgcGVyZm9ybXMgdGhlIGJlc3QgYXQgcHJlZGljdGlvbiBhbmQgc2hvd3Mgc29tZSBub3RhYmxlIGZhY3RvcnMgdGhhdCBjYW4gYWZmZWN0IHByaWNpbmcgYSBob3VzZS4gVGhpcyBmcmFtZXdvcmsgY2FuIGJlIGFwcGxpZWQgdG8gb3RoZXIgZGF0YSBwcm9ibGVtcywgYWx0aG91Z2ggbW9yZSBmZWF0dXJlIGF2YWlsYWJpbGl0eSBvciBicm9hZGVyIGh5cGVycGFyYW1ldGVyIHR1bmluZyBjYW4gaGVscCBpbmNyZWFzZSBwZXJmb3JtYW5jZS4NCg==