INTRODUCTION
- The notebook includes a short exploratory analysis and comparison of few selected models for house price prediction task.
- It uses Beijing housing prices from 2011-2017 period scrapped from Lianjia.com and shared on kaggle datastes: https://www.kaggle.com/ruiqurm/lianjia
- Some of the variables included in the original dataset has been removed after innitial exploration to make sure the modeling corresponds as much as possible to a real situation.
LOAD DATA
- getting data and removing columns with unique ids
- innitial spliting for test and development set
- small sample dataframe for interactive development
# beijing_housing -> bh
bh <- read_csv("data/new.csv", locale = readr::locale(encoding = "latin1")) %>%
select(-url, -id, -Cid) # removing ids
# save test and make sample df
set.seed(23)
bh <- bh[sample(nrow(bh)),] # random shuffle rows
df_splits <- bh %>%
mutate(split_flag = sample(1:2, size = dim(bh)[1], prob=c(0.1, 0.9), replace=TRUE)) %>%
mutate(split_flag = ifelse(split_flag == 1, "test", "dev")) %>%
group_by(split_flag) %>%
group_split()
bh_dev <- df_splits[[1]]
bh_test <- df_splits[[2]]
write_csv(bh_test, "bh_test.csv")
bh <- bh_dev
bh_sample <- bh %>% sample_n(0.02 * dim(bh)[1])
[1] "Lng" "Lat" "tradeTime"
[4] "DOM" "followers" "totalPrice"
[7] "price" "square" "livingRoom"
[10] "drawingRoom" "kitchen" "bathRoom"
[13] "floor" "buildingType" "constructionTime"
[16] "renovationCondition" "buildingStructure" "ladderRatio"
[19] "elevator" "fiveYearsProperty" "subway"
[22] "district" "communityAverage" "split_flag"
[1] "Development dataset size: 286904" "Development dataset size: 24"
SHORT EDA
| Lng |
116.4636 |
116.2589 |
116.3398 |
116.2866 |
116.3045 |
116.4557 |
| Lat |
39.88396 |
39.93433 |
40.08534 |
39.84665 |
39.89543 |
39.96799 |
| tradeTime |
2016-02-21 |
2017-07-23 |
2015-03-21 |
2013-09-13 |
2015-11-05 |
2013-01-30 |
| DOM |
NA |
127 |
NA |
NA |
1 |
NA |
| followers |
10 |
88 |
0 |
12 |
4 |
0 |
| totalPrice |
300 |
1022 |
100 |
190 |
203 |
230 |
| price |
33367 |
78707 |
22417 |
30400 |
37524 |
38251 |
| square |
89.91 |
129.85 |
44.61 |
62.50 |
54.10 |
60.13 |
| livingRoom |
3 |
3 |
1 |
2 |
1 |
2 |
| drawingRoom |
1 |
1 |
0 |
1 |
0 |
1 |
| kitchen |
1 |
1 |
1 |
1 |
1 |
1 |
| bathRoom |
1 |
2 |
1 |
1 |
1 |
1 |
| floor |
¶¥ 6 |
µÍ 13 |
ÖÐ 7 |
ÖÐ 6 |
µÍ 24 |
µÍ 6 |
| buildingType |
4 |
3 |
1 |
4 |
1 |
4 |
| constructionTime |
1984 |
2004 |
2012 |
1994 |
1997 |
δ֪ |
| renovationCondition |
4 |
4 |
1 |
1 |
3 |
1 |
| buildingStructure |
4 |
6 |
6 |
2 |
6 |
2 |
| ladderRatio |
0.500 |
0.500 |
0.062 |
0.333 |
0.250 |
0.333 |
| elevator |
0 |
1 |
1 |
0 |
1 |
0 |
| fiveYearsProperty |
1 |
1 |
0 |
1 |
1 |
1 |
| subway |
1 |
0 |
0 |
0 |
0 |
1 |
| district |
7 |
8 |
6 |
2 |
8 |
7 |
| communityAverage |
58694 |
76569 |
31381 |
44440 |
65996 |
65909 |
| split_flag |
dev |
dev |
dev |
dev |
dev |
dev |
Check types
The `show_plot = TRUE` is deprecated and will be removed in a future version. The `show_plot()` function should be used instead. For more info, check out the help file ?show_plot()

- It seems many of the integers are actually categorical variables. I will deal with that in the preprocessing section and make a decision based on what makes more sense for the modeling.
Check NAs
- DOM (Days on market) looks really bad with almost half of the records with NAs. Since data was scrapped from the online platform this can potentially have different meanings, for example we can miss data for properties that were just added (NA = 0). This would be important for filling missing data so let’s try to verify it by checking its distribution.

Looking at the distribution it seems quite possible that the NAs values in DOM are actually “day 0” on the market. Also Beijing is very fast-paced with 50% of the offers staying under 6 days on the portal!
Beside buildingType and communityAverage it looks quite complete.
Price distribution (target variable)

Correlations
- Looking into correlation of price with available continious variables:

- Ok, there are few highly correlated variables… but should we really use them? In the real situation they wouldn’t be available. We will have to remove things like totalPrice as well as days on the market, number of followers and even communityAverage as it could have been calculated using our targets. It looks the only continious variable we have left is the apartment’s size (
square).
Time trends

- In general there is a visible upward trend but because there is no consecutive growth across all dates (2017 > 2016 but 2018 < 2017) it might be usefull to kepp year as categorical variable.
Geo trends
map <- openmap(upperLeft = c(max(bh$Lat),min(bh$Lng)),
lowerRight = c(min(bh$Lat),max(bh$Lng)),
zoom = NULL,
type = c("osm"))
map.latlon <- openproj(map, projection = "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
autoplot(map.latlon) +
geom_point(data = bh_sample, aes(Lng, Lat, colour = price), alpha = 0.6) +
labs(title = "Beijing - house price by location", x = " ", y = " ", colour = "Price") +
theme(plot.margin = unit(c(0,0,0,0),"cm"))

Districts prices:
p1 <- autoplot(map.latlon) +
geom_point(data = bh_sample, aes(Lng, Lat, colour = as.factor(district)), alpha = 0.6) +
labs(title = "Price distribution by district", x = " ", y = " ", colour = "District") +
theme(plot.margin = unit(c(0,0,0,0),"cm"), legend.position = "none")
p2 <- ggplot(bh, aes(as.factor(district), price, fill = as.factor(district))) +
geom_boxplot(outlier.shape = NA) +
scale_x_discrete(position = "top") +
labs(title = " ", x = "District", y = "Price", colour = " ") +
coord_flip(ylim=c(0, 100000)) +
theme(legend.position = "none", plot.margin = unit(c(0,0,0,0),"cm"))
p1 + p2 + plot_layout(ncol = 2)

- Districts 1 and 10 located in the center with visibly higher prices. It does not make sense to keep districts as continious variables as the numbers corespond very losely to locations.
Baseline model
Before moving to the preprocessing part lets try to fit simple linear model with raw data as a baseline:
base_fit <- lm(price ~ ., data = select(bh, -totalPrice, -communityAverage, -followers, -DOM, -split_flag) %>% na.omit)
cat(paste0("R-squared: ", round(summary(base_fit)$adj.r.squared, 2),
"\nRMSE: ", round(sqrt(mean(base_fit$residuals^2)), 2),
"\nMAE: ", round(mean(abs(base_fit$residuals)), 2)))
R-squared: 0.46
RMSE: 15909.25
MAE: 11758.45
PREPROCESSING
Preprocessing steps:
- Removing all non property features - the ones we hadn’t have available in the real world situation (totalPrice, communityAverage, days on market, followers)
- Correcting columns types
- Remove extreame outliers
- Removing prices below 10k as obviosuly erroneous data
- Removing some extreme outliers (ladderRatio)
- Extract floor number from floor text field
- Extract date related features for tradeTime
- One-hot encode categorical variables
- Split training and validation
- Imputing missing values with knn
- Reduce correlation and coolinearity
- Remove variables with near zero variance (+ levels with very small frequency)
- Scale & center
Date features
date_features <- function(dates, as_factors = FALSE){
"Takes a vector of dates and returns a dataframe including year, quarter_of_year, month_of_year, week_of_year, week_of_month, day_of_month, weekday and weekend"
monthweeks <- function(x) {
ceiling(as.numeric(format(x, "%d"))/7)
}
dates <- as.data.frame(dates)
colnames(dates) <- c("date")
dates_features <- dates %>% mutate(year = lubridate::year(date)) %>%
mutate(quarter_of_year = lubridate::quarter(date)) %>%
mutate(month_of_year = lubridate::month(date)) %>%
mutate(month_name = lubridate::month(date, label = TRUE)) %>%
mutate(week_of_year = lubridate::week(date)) %>%
mutate(week_of_month = monthweeks(date)) %>% mutate(day_of_month = lubridate::day(date)) %>%
mutate(day_of_year = lubridate::yday(date)) %>% mutate(weekday = lubridate::wday(date)) %>%
mutate(weekday_name = lubridate::wday(date, label = TRUE)) %>%
mutate(weekend = ifelse(weekday %in% c(1, 7), 1, 0)) %>%
mutate(ymd_format = lubridate::ymd(paste(year, month_of_year, day_of_month, sep = "-")))
if (as_factors) {
dates_features[colnames(dates_features)[-1]] <- lapply(dates_features[colnames(dates_features)[-1]],
factor)
}
return(dates_features)
}
tradetime_features <- date_features(bh$tradeTime, as_factors = FALSE) %>% select(-date, -ymd_format, -weekday, -month_of_year, -day_of_year)
1 2 3 4 5 6
year "2016" "2017" "2015" "2013" "2015" "2013"
quarter_of_year "1" "3" "1" "3" "4" "1"
month_name "lut" "lip" "mar" "wrz" "lis" "sty"
week_of_year " 8" "30" "12" "37" "45" " 5"
week_of_month "3" "4" "3" "2" "1" "5"
day_of_month "21" "23" "21" "13" " 5" "30"
weekday_name "nie" "nie" "sob" "pią" "czw" "śro"
weekend "1" "1" "1" "0" "0" "0"
Cleaning
bh_processed <- bh %>%
rename(floor_character = `floor`) %>%
cbind(tradetime_features) %>%
mutate(floor_number = str_extract(floor_character, "(\\d+)(?: \\d+)*$")) %>%
filter(as.numeric(year) > 2011) %>%
map_at(c("livingRoom", "drawingRoom", "kitchen", "bathRoom", "buildingType" , "buildingStructure", "renovationCondition", "fiveYearsProperty", "elevator", "subway", "district", "quarter_of_year", "weekend", "weekday_name", "month_name", "year"), as.factor) %>%
map_at(c("Lng", "Lat", "DOM", "followers", "price", "square", "constructionTime", "ladderRatio", "floor_number", "constructionTime", "week_of_year", "day_of_month"), as.numeric) %>%
as_tibble() %>%
select(-floor_character, -tradeTime, -totalPrice, -communityAverage, -DOM, -followers, -split_flag) %>%
filter(buildingType %in% c(1, 2, 3, 4)) %>%
filter(ladderRatio < quantile(bh$ladderRatio, 0.99)) %>%
filter(price > 10000) %>%
filter(constructionTime > 1800) %>%
mutate(naConstruction = as.factor(ifelse(is.na(constructionTime), "na_ct", "ok_ct"))) %>%
mutate(constructionTime = replace_na(constructionTime, 0)) %>%
na.omit()
stopifnot(map(bh_processed, class)[1] %in% c("factor", "ordered", "numeric"))
stopifnot(sum(is.na(bh_processed)) == 0)
Final preprocessing (imputation, colinearity, scaling, nzv)
# caret preprocessing would automatically scale dummies if we keep them as numeric and skip other operations if we keep them as factors -> two preproc steps
tic("caret preproc")
preproc_numeric <- preProcess(x_train, method = c("center", "scale", "YeoJohnson", "knnImpute"))
x_train <- predict(preproc_numeric, x_train)
x_train <- map_df(x_train, as.numeric)
preproc_all <- preProcess(x_train, method = c("nzv", "corr", "conditionalX"))
x_train <- predict(preproc_all, x_train)
x_val <- predict(preproc_numeric, x_val)
x_val <- map_df(x_val, as.numeric)
x_val <- predict(preproc_all, x_val)
write_rds(x_train, "data/x_train.rds")
write_rds(x_val, "data/x_val.rds")
toc()
caret preproc: 21.237 sec elapsed
Created from 205238 samples and 83 variables
Pre-processing:
- conditionalX (42)
- ignored (0)
- removed (41)
Created from 205238 samples and 83 variables
Pre-processing:
- centered (9)
- ignored (74)
- 5 nearest neighbor imputation (9)
- scaled (9)
- Yeo-Johnson transformation (6)
Lambda estimates for Yeo-Johnson transformation:
-0.19, 0.77, 0.72, 0.66, 0.8, -0.26
This is how our final training set looks like:
| Lng |
0.4035157 |
-1.4338343 |
-0.7080302 |
-1.1852662 |
-1.0249428 |
-0.4532327 |
| Lat |
-0.7164604 |
-0.1664836 |
1.4823553 |
-1.1237949 |
-0.5912225 |
-0.3342391 |
| square |
0.4824763 |
1.3850917 |
-1.4036126 |
-0.4685898 |
-0.8624907 |
1.4433778 |
| constructionTime |
-1.7182057 |
0.5572131 |
1.4673806 |
-0.5804963 |
-0.2391835 |
0.1021293 |
| ladderRatio |
0.9986597 |
0.9986597 |
-2.3033900 |
-0.2298309 |
-0.8535341 |
-0.8535341 |
| week_of_year |
-1.2676085 |
0.2791417 |
-0.9414940 |
0.6938684 |
1.1425776 |
0.9770737 |
| day_of_month |
0.5413353 |
0.7515817 |
0.5413353 |
-0.3444333 |
-1.3476779 |
0.2184442 |
| floor_number |
-0.9926215 |
0.3295334 |
-0.7191630 |
-0.9926215 |
1.2673657 |
0.3295334 |
| livingRoom_3 |
2.0000000 |
2.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
2.0000000 |
| livingRoom_1 |
1.0000000 |
1.0000000 |
2.0000000 |
1.0000000 |
2.0000000 |
1.0000000 |
| livingRoom_2 |
1.0000000 |
1.0000000 |
1.0000000 |
2.0000000 |
1.0000000 |
1.0000000 |
| drawingRoom_1 |
2.0000000 |
2.0000000 |
1.0000000 |
2.0000000 |
1.0000000 |
1.0000000 |
| drawingRoom_0 |
1.0000000 |
1.0000000 |
2.0000000 |
1.0000000 |
2.0000000 |
1.0000000 |
| drawingRoom_2 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
2.0000000 |
| bathRoom_2 |
1.0000000 |
2.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
2.0000000 |
| buildingType_4 |
2.0000000 |
1.0000000 |
1.0000000 |
2.0000000 |
1.0000000 |
1.0000000 |
| buildingType_3 |
1.0000000 |
2.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
| buildingType_1 |
1.0000000 |
1.0000000 |
2.0000000 |
1.0000000 |
2.0000000 |
2.0000000 |
| renovationCondition_4 |
2.0000000 |
2.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
| renovationCondition_1 |
1.0000000 |
1.0000000 |
2.0000000 |
2.0000000 |
1.0000000 |
1.0000000 |
| renovationCondition_3 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
2.0000000 |
2.0000000 |
| buildingStructure_2 |
1.0000000 |
1.0000000 |
1.0000000 |
2.0000000 |
1.0000000 |
1.0000000 |
| elevator_1 |
1.0000000 |
2.0000000 |
2.0000000 |
1.0000000 |
2.0000000 |
2.0000000 |
| fiveYearsProperty_1 |
2.0000000 |
2.0000000 |
1.0000000 |
2.0000000 |
2.0000000 |
1.0000000 |
| subway_1 |
2.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
2.0000000 |
| district_7 |
2.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
| district_8 |
1.0000000 |
2.0000000 |
1.0000000 |
1.0000000 |
2.0000000 |
1.0000000 |
| district_6 |
1.0000000 |
1.0000000 |
2.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
| district_2 |
1.0000000 |
1.0000000 |
1.0000000 |
2.0000000 |
1.0000000 |
1.0000000 |
| district_10 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
2.0000000 |
| district_1 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
| year_2016 |
2.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
| year_2017 |
1.0000000 |
2.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
| year_2015 |
1.0000000 |
1.0000000 |
2.0000000 |
1.0000000 |
2.0000000 |
2.0000000 |
| year_2013 |
1.0000000 |
1.0000000 |
1.0000000 |
2.0000000 |
1.0000000 |
1.0000000 |
| year_2012 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
| year_2014 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
| quarter_of_year_1 |
2.0000000 |
1.0000000 |
2.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
| quarter_of_year_3 |
1.0000000 |
2.0000000 |
1.0000000 |
2.0000000 |
1.0000000 |
1.0000000 |
| quarter_of_year_4 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
2.0000000 |
2.0000000 |
| quarter_of_year_2 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
1.0000000 |
| weekend_1 |
2.0000000 |
2.0000000 |
2.0000000 |
1.0000000 |
1.0000000 |
2.0000000 |
[1] 205238 42
MODELING
used (Mb) gc trigger (Mb) max used (Mb)
Ncells 3138924 167.7 4703850 251.3 4703850 251.3
Vcells 153017995 1167.5 316973979 2418.4 316915827 2417.9
Train
I train 4 different models including basic grid search of hyperparameters and repeated 5-fold crossvalidation. I’ve tried to select varying approaches and allowing for somehow interpretable results:
- Lasso - linnear regression with L1 regularization (
lasso)
- Multivariate Adaptive Regression Splines (MARS - link). (Also called
earth)
- Bayesian regularized neural network, 2layers (
brnn)
- Random forest (
random_forest)
tic("Training models")
set.seed(23)
training_data <- cbind(y_train, x_train)
methods <- c("lasso", "earth", "brnn", "ranger")
fitControl <- trainControl(method = "repeatedcv",
number = 5,
repeats = 2,
search = "grid",
allowParallel = TRUE,
verboseIter = TRUE)
#cl <- makePSOCKcluster(2)
#registerDoParallel(cl)
models_metrics <- list()
for (m in methods) {
model <- caret::train(price ~ .,
data = training_data,
method = m,
trControl = fitControl,
importance = 'impurity',
na.action = na.pass)
saveRDS(model, paste0("models/", m, ".rds"))
models_metrics[[m]] <- model$resample
gc()
}
#stopCluster(cl)
toc()
VALIDATION
Ranger result
Call:
ranger::ranger(dependent.variable.name = ".outcome", data = x, mtry = min(param$mtry, ncol(x)), min.node.size = param$min.node.size, splitrule = as.character(param$splitrule), write.forest = TRUE, probability = classProbs, ...)
Type: Regression
Number of trees: 500
Sample size: 205238
Number of independent variables: 42
Mtry: 42
Target node size: 5
Variable importance mode: impurity
Splitrule: variance
OOB prediction error (MSE): 24989313
R squared (OOB): 0.9464689
- The best performing model (~0.95 R-squared) is a random forest with 500 trees, 22 randomly selected predictors, minimal node size of 5 and variance based spliting rule. Altough doing extensive grid search over 3 hyperparameters took much more time than other models the result is significantly better.
Let’s visualize performance of the best model:
p1 <- ggplot() +
geom_density(aes(y), data = preds_df, colour = "darkgreen", fill = "darkgreen", alpha = 0.5) +
geom_density(aes(y_hat), data = preds_df, colour = "darkblue", fill = "darkblue", alpha = 0.5) +
labs(x = "price", title="Best model - validation", subtitle="Actuals & predictions distributions") +
scale_x_comma()
p2 <- ggplot(preds_df, aes(y, y_hat)) +
geom_point(alpha = 0.5) +
geom_smooth() +
scale_x_comma() +
scale_y_comma() +
labs(x = "Price", y="Predicted price", subtitle="Actuals vs predictions")
p3 <- ggplot(preds_df, aes(abs_error)) +
geom_histogram(bins = 100) +
scale_x_comma(limits = c(0, 40000)) +
labs(x = "Absolute error", subtitle = "Absolute error distribution")
p4 <- ggplot(preds_df, aes(y_hat, sd_residuals)) +
geom_point(alpha = 0.5) +
geom_hline(yintercept = 0, colour="red", linetype = "dashed") +
labs(x = "Predicted price", y = "Standarized residuals", subtitle = "Residuals")
p1 + p2 + p3 + p4 + plot_layout(nrow = 2, ncol = 2)

Median price in the validation set: 39330.5
Mean absolute error: 3199.81
Features Importance:

- Predictions were generated mainly by looking at the location (Lat/Lng or district) and time the apartment was posted for sale. Such characteristics as size of the apartment, construction time or renovation condition didn’t make it into top 10.
Individual examples
At the end let’s look at few individual examples of apartments and try to explain which variables were the most important for the model to make price estimation:
Attaching package: ‘lime’
The following object is masked from ‘package:dplyr’:
explain

THE END
LS0tCnRpdGxlOiAiLy8vIgpzdWJ0aXRsZTogPGgzPkJlaWppbmcgaG91c2luZyBtYXJrZXQgLSBleHBsb3JhdG9yeSBhbmFseXNpcyBhbmQgcHJpY2UgbW9kZWxpbmc8L2gzPgphdXRob3I6ICJtaWMuc3RhbkBvdXRsb29rLmNvbSIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICAjY29kZV9mb2xkaW5nOiBoaWRlCiAgICBmaWdfd2lkdGg6IDEwCiAgICBmaWdfaGVpZ2h0OiA2CiAgICBzbWFydDogbm8KICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBtYXRoamF4OiBudWxsCiAgICBoaWdobGlnaHQ6IHB5Z21lbnRzCiAgICB0aGVtZTogbHVtZW4KLS0tCgo8c3R5bGU+CiN0b2NpZnktaGVhZGVyMCB7CiAgICBkaXNwbGF5OiBub25lOwp9CgojdG9jaWZ5LWhlYWRlcjEgewogICAgZGlzcGxheTogbm9uZTsKfQo8L3N0eWxlPgoKLS0tLS0tCgojIyMgSU5UUk9EVUNUSU9OCgotIFRoZSBub3RlYm9vayBpbmNsdWRlcyBhIHNob3J0IGV4cGxvcmF0b3J5IGFuYWx5c2lzIGFuZCBjb21wYXJpc29uIG9mIGZldyBzZWxlY3RlZCBtb2RlbHMgZm9yIGhvdXNlIHByaWNlIHByZWRpY3Rpb24gdGFzay4KLSBJdCB1c2VzIEJlaWppbmcgaG91c2luZyBwcmljZXMgZnJvbSAyMDExLTIwMTcgcGVyaW9kIHNjcmFwcGVkIGZyb20gTGlhbmppYS5jb20gYW5kIHNoYXJlZCBvbiBrYWdnbGUgZGF0YXN0ZXM6IFtodHRwczovL3d3dy5rYWdnbGUuY29tL3J1aXF1cm0vbGlhbmppYV0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9ydWlxdXJtL2xpYW5qaWEpCi0gU29tZSBvZiB0aGUgdmFyaWFibGVzIGluY2x1ZGVkIGluIHRoZSBvcmlnaW5hbCBkYXRhc2V0IGhhcyBiZWVuIHJlbW92ZWQgYWZ0ZXIgaW5uaXRpYWwgZXhwbG9yYXRpb24gdG8gbWFrZSBzdXJlIHRoZSBtb2RlbGluZyBjb3JyZXNwb25kcyBhcyBtdWNoIGFzIHBvc3NpYmxlIHRvIGEgcmVhbCBzaXR1YXRpb24uCgotLS0tLS0KCiMjIyBTRVRVUAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkodGljdG9jKTsgbGlicmFyeShkb1BhcmFsbGVsKQpsaWJyYXJ5KHN0cmluZ3IpOyBsaWJyYXJ5KGx1YnJpZGF0ZSk7IGxpYnJhcnkoaW5zcGVjdGRmKTsgbGlicmFyeShQZXJmb3JtYW5jZUFuYWx5dGljcyk7IGxpYnJhcnkoZmFzdER1bW1pZXMpCmxpYnJhcnkoY2FyZXQpOyBsaWJyYXJ5KG1sYmVuY2gpOyBsaWJyYXJ5KHhnYm9vc3QpOyBsaWJyYXJ5KGVhcnRoKTsgbGlicmFyeShyYW5nZXIpCmxpYnJhcnkoT3BlblN0cmVldE1hcCkKbGlicmFyeShwYXRjaHdvcmspOyBsaWJyYXJ5KGhyYnJ0aGVtZXMpOyB0aGVtZV9zZXQodGhlbWVfaXBzdW0oKSkKYGBgCgojIyMgTE9BRCBEQVRBCgotIGdldHRpbmcgZGF0YSBhbmQgcmVtb3ZpbmcgY29sdW1ucyB3aXRoIHVuaXF1ZSBpZHMKLSBpbm5pdGlhbCBzcGxpdGluZyBmb3IgdGVzdCBhbmQgZGV2ZWxvcG1lbnQgc2V0Ci0gc21hbGwgc2FtcGxlIGRhdGFmcmFtZSBmb3IgaW50ZXJhY3RpdmUgZGV2ZWxvcG1lbnQKCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBiZWlqaW5nX2hvdXNpbmcgLT4gYmgKYmggPC0gcmVhZF9jc3YoImRhdGEvbmV3LmNzdiIsIGxvY2FsZSA9IHJlYWRyOjpsb2NhbGUoZW5jb2RpbmcgPSAibGF0aW4xIikpICU+JQogIHNlbGVjdCgtdXJsLCAtaWQsIC1DaWQpICMgcmVtb3ZpbmcgaWRzCgojIHNhdmUgdGVzdCBhbmQgbWFrZSBzYW1wbGUgZGYKc2V0LnNlZWQoMjMpCmJoIDwtIGJoW3NhbXBsZShucm93KGJoKSksXSAjIHJhbmRvbSBzaHVmZmxlIHJvd3MKZGZfc3BsaXRzIDwtIGJoICU+JQogIG11dGF0ZShzcGxpdF9mbGFnID0gc2FtcGxlKDE6Miwgc2l6ZSA9IGRpbShiaClbMV0sIHByb2I9YygwLjEsIDAuOSksIHJlcGxhY2U9VFJVRSkpICU+JQogIG11dGF0ZShzcGxpdF9mbGFnID0gaWZlbHNlKHNwbGl0X2ZsYWcgPT0gMSwgInRlc3QiLCAiZGV2IikpICU+JQogIGdyb3VwX2J5KHNwbGl0X2ZsYWcpICU+JQogIGdyb3VwX3NwbGl0KCkKCmJoX2RldiA8LSBkZl9zcGxpdHNbWzFdXQpiaF90ZXN0IDwtIGRmX3NwbGl0c1tbMl1dCndyaXRlX2NzdihiaF90ZXN0LCAiYmhfdGVzdC5jc3YiKQpiaCA8LSBiaF9kZXYKYmhfc2FtcGxlIDwtIGJoICU+JSBzYW1wbGVfbigwLjAyICogZGltKGJoKVsxXSkKYGBgCgpgYGB7cn0KY29sbmFtZXMoYmgpCnByaW50KHBhc3RlKCJEZXZlbG9wbWVudCBkYXRhc2V0IHNpemU6ICIsIGRpbShiaCkpKQpgYGAKCgojIyMgU0hPUlQgRURBCgpgYGB7ciB3YXJuaW5nPUZBTFNFfQprbml0cjo6a2FibGUodChoZWFkKGJoKSkpCmBgYAoKPGJyPiAgCgojIyMjICoqQ2hlY2sgdHlwZXMqKgoKYGBge3J9Cmluc3BlY3RfdHlwZXMoYmgsIHNob3dfcGxvdCA9IFRSVUUpCmBgYAoKLSBJdCBzZWVtcyBtYW55IG9mIHRoZSBpbnRlZ2VycyBhcmUgYWN0dWFsbHkgY2F0ZWdvcmljYWwgdmFyaWFibGVzLiBJIHdpbGwgZGVhbCB3aXRoIHRoYXQgaW4gdGhlIHByZXByb2Nlc3Npbmcgc2VjdGlvbiBhbmQgbWFrZSBhIGRlY2lzaW9uIGJhc2VkIG9uIHdoYXQgbWFrZXMgbW9yZSBzZW5zZSBmb3IgdGhlIG1vZGVsaW5nLgoKPGJyPgoKIyMjIyAqKkNoZWNrIE5BcyoqCgpgYGB7cn0KaW5zcGVjdF9uYShiaCkKYGBgCgotIERPTSAoRGF5cyBvbiBtYXJrZXQpIGxvb2tzIHJlYWxseSBiYWQgd2l0aCBhbG1vc3QgaGFsZiBvZiB0aGUgcmVjb3JkcyB3aXRoIE5Bcy4gU2luY2UgZGF0YSB3YXMgc2NyYXBwZWQgZnJvbSB0aGUgb25saW5lIHBsYXRmb3JtIHRoaXMgY2FuIHBvdGVudGlhbGx5IGhhdmUgZGlmZmVyZW50IG1lYW5pbmdzLCBmb3IgZXhhbXBsZSB3ZSBjYW4gbWlzcyBkYXRhIGZvciBwcm9wZXJ0aWVzIHRoYXQgd2VyZSBqdXN0IGFkZGVkIChOQSA9IDApLiBUaGlzIHdvdWxkIGJlIGltcG9ydGFudCBmb3IgZmlsbGluZyBtaXNzaW5nIGRhdGEgc28gbGV0J3MgdHJ5IHRvIHZlcmlmeSBpdCBieSBjaGVja2luZyBpdHMgZGlzdHJpYnV0aW9uLgoKYGBge3J9CmdncGxvdChiaCwgYWVzKERPTSkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMTAwKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gbWVkaWFuKGJoJERPTSwgbmEucm09VFJVRSksIGNvbG91ciA9ICJyZWQiKQpgYGAKLSBMb29raW5nIGF0IHRoZSBkaXN0cmlidXRpb24gaXQgc2VlbXMgcXVpdGUgcG9zc2libGUgdGhhdCB0aGUgTkFzIHZhbHVlcyBpbiBET00gYXJlIGFjdHVhbGx5ICJkYXkgMCIgb24gdGhlIG1hcmtldC4gQWxzbyBCZWlqaW5nIGlzIHZlcnkgZmFzdC1wYWNlZCB3aXRoIDUwJSBvZiB0aGUgb2ZmZXJzIHN0YXlpbmcgdW5kZXIgNiBkYXlzIG9uIHRoZSBwb3J0YWwhCgotIEJlc2lkZSBidWlsZGluZ1R5cGUgYW5kIGNvbW11bml0eUF2ZXJhZ2UgaXQgbG9va3MgcXVpdGUgY29tcGxldGUuIAoKPGJyPgoKIyMjIyAqKlByaWNlIGRpc3RyaWJ1dGlvbiAodGFyZ2V0IHZhcmlhYmxlKSoqCgoKYGBge3J9CmdncGxvdChiaCwgYWVzKHByaWNlKSkgKyBnZW9tX2hpc3RvZ3JhbShiaW5zPTEwMCkKYGBgCgoKIyMjIyAqKkNvcnJlbGF0aW9ucyoqCgotIExvb2tpbmcgaW50byBjb3JyZWxhdGlvbiBvZiBwcmljZSB3aXRoIGF2YWlsYWJsZSBjb250aW5pb3VzIHZhcmlhYmxlczoKCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTYsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9ClBlcmZvcm1hbmNlQW5hbHl0aWNzOjpjaGFydC5Db3JyZWxhdGlvbihzZWxlY3QoYmhfc2FtcGxlLCBET00sIGZvbGxvd2VycywgcHJpY2UsIHNxdWFyZSwgbGFkZGVyUmF0aW8sIHRvdGFsUHJpY2UsIGNvbW11bml0eUF2ZXJhZ2UpKQpgYGAKCi0gT2ssIHRoZXJlIGFyZSBmZXcgaGlnaGx5IGNvcnJlbGF0ZWQgdmFyaWFibGVzLi4uIGJ1dCBzaG91bGQgd2UgcmVhbGx5IHVzZSB0aGVtPyAqKkluIHRoZSByZWFsIHNpdHVhdGlvbiB0aGV5IHdvdWxkbid0IGJlIGF2YWlsYWJsZSoqLiBXZSB3aWxsIGhhdmUgdG8gcmVtb3ZlIHRoaW5ncyBsaWtlIHRvdGFsUHJpY2UgYXMgd2VsbCBhcyBkYXlzIG9uIHRoZSBtYXJrZXQsIG51bWJlciBvZiBmb2xsb3dlcnMgYW5kIGV2ZW4gY29tbXVuaXR5QXZlcmFnZSBhcyBpdCBjb3VsZCBoYXZlIGJlZW4gY2FsY3VsYXRlZCB1c2luZyBvdXIgdGFyZ2V0cy4gSXQgbG9va3MgdGhlIG9ubHkgY29udGluaW91cyB2YXJpYWJsZSB3ZSBoYXZlIGxlZnQgaXMgdGhlIGFwYXJ0bWVudCdzIHNpemUgKGBzcXVhcmVgKS4KCjxicj4KCiMjIyMjICoqVGltZSB0cmVuZHMqKgoKCmBgYHtyIHdhcm5pbmc9RkFMU0V9CndlZWtseV9hdmcgPC0gYmggJT4lCiAgZ3JvdXBfYnkod2VlayA9IGx1YnJpZGF0ZTo6Zmxvb3JfZGF0ZShhcy5EYXRlKHRyYWRlVGltZSksIHVuaXQgPSAid2VlayIpKSAlPiUKICBzdW1tYXJpemUoYXZnX3ByaWNlID0gbWVhbihwcmljZSkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBmaWx0ZXIoeWVhcih3ZWVrKSA+IDIwMTEpCgpnZ3Bsb3Qod2Vla2x5X2F2ZywgYWVzKHdlZWssIGF2Z19wcmljZSkpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9zbW9vdGgoKSArCiAgbGFicyh5ID0gIkF2ZXJhZ2Ugd2Vla2x5IHByaWNlIiwgeCA9ICJXZWVrIikKYGBgCgotIEluIGdlbmVyYWwgdGhlcmUgaXMgYSB2aXNpYmxlIHVwd2FyZCB0cmVuZCBidXQgYmVjYXVzZSB0aGVyZSBpcyBubyBjb25zZWN1dGl2ZSBncm93dGggYWNyb3NzIGFsbCBkYXRlcyAoMjAxNyA+IDIwMTYgYnV0IDIwMTggPCAyMDE3KSBpdCBtaWdodCBiZSB1c2VmdWxsIHRvIGtlcHAgeWVhciBhcyBjYXRlZ29yaWNhbCB2YXJpYWJsZS4KCjxicj4KCiMjIyMjICoqR2VvIHRyZW5kcyoqCgoKYGBge3Igd2FybmluZz1GQUxTRX0KbWFwIDwtIG9wZW5tYXAodXBwZXJMZWZ0ID0gYyhtYXgoYmgkTGF0KSxtaW4oYmgkTG5nKSksCiAgICAgICAgICAgICAgIGxvd2VyUmlnaHQgPSBjKG1pbihiaCRMYXQpLG1heChiaCRMbmcpKSwKICAgICAgICAgICAgICAgem9vbSA9IE5VTEwsCiAgICAgICAgICAgICAgIHR5cGUgPSBjKCJvc20iKSkKbWFwLmxhdGxvbiA8LSBvcGVucHJvaihtYXAsIHByb2plY3Rpb24gPSAiK3Byb2o9bG9uZ2xhdCArZWxscHM9V0dTODQgK2RhdHVtPVdHUzg0ICtub19kZWZzIikKCmF1dG9wbG90KG1hcC5sYXRsb24pICsKICBnZW9tX3BvaW50KGRhdGEgPSBiaF9zYW1wbGUsIGFlcyhMbmcsIExhdCwgY29sb3VyID0gcHJpY2UpLCBhbHBoYSA9IDAuNikgKyAKICBsYWJzKHRpdGxlID0gIkJlaWppbmcgLSBob3VzZSBwcmljZSBieSBsb2NhdGlvbiIsIHggPSAiICIsIHkgPSAiICIsIGNvbG91ciA9ICJQcmljZSIpICsgCiAgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMCwwLDAsMCksImNtIikpCmBgYAoKPGJyPiAKKipEaXN0cmljdHMgcHJpY2VzOioqCgpgYGB7cn0KcDEgPC0gYXV0b3Bsb3QobWFwLmxhdGxvbikgKwogIGdlb21fcG9pbnQoZGF0YSA9IGJoX3NhbXBsZSwgYWVzKExuZywgTGF0LCBjb2xvdXIgPSBhcy5mYWN0b3IoZGlzdHJpY3QpKSwgYWxwaGEgPSAwLjYpICsgCiAgbGFicyh0aXRsZSA9ICJQcmljZSBkaXN0cmlidXRpb24gYnkgZGlzdHJpY3QiLCB4ID0gIiAiLCB5ID0gIiAiLCBjb2xvdXIgPSAiRGlzdHJpY3QiKSArIAogIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDAsMCwwLDApLCJjbSIpLCBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgpwMiA8LSBnZ3Bsb3QoYmgsIGFlcyhhcy5mYWN0b3IoZGlzdHJpY3QpLCBwcmljZSwgZmlsbCA9IGFzLmZhY3RvcihkaXN0cmljdCkpKSArIAogIGdlb21fYm94cGxvdChvdXRsaWVyLnNoYXBlID0gTkEpICsgCiAgc2NhbGVfeF9kaXNjcmV0ZShwb3NpdGlvbiA9ICJ0b3AiKSArCiAgbGFicyh0aXRsZSA9ICIgIiwgeCA9ICJEaXN0cmljdCIsIHkgPSAiUHJpY2UiLCBjb2xvdXIgPSAiICIpICsgCiAgY29vcmRfZmxpcCh5bGltPWMoMCwgMTAwMDAwKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwgcGxvdC5tYXJnaW4gPSB1bml0KGMoMCwwLDAsMCksImNtIikpCgpwMSArIHAyICsgcGxvdF9sYXlvdXQobmNvbCA9IDIpCmBgYAoKLSBEaXN0cmljdHMgMSBhbmQgMTAgbG9jYXRlZCBpbiB0aGUgY2VudGVyIHdpdGggdmlzaWJseSBoaWdoZXIgcHJpY2VzLiBJdCBkb2VzIG5vdCBtYWtlIHNlbnNlIHRvIGtlZXAgZGlzdHJpY3RzIGFzIGNvbnRpbmlvdXMgdmFyaWFibGVzIGFzIHRoZSBudW1iZXJzIGNvcmVzcG9uZCB2ZXJ5IGxvc2VseSB0byBsb2NhdGlvbnMuCgo8YnI+CgojIyMjICoqQmFzZWxpbmUgbW9kZWwqKgoKQmVmb3JlIG1vdmluZyB0byB0aGUgcHJlcHJvY2Vzc2luZyBwYXJ0IGxldHMgdHJ5IHRvIGZpdCBzaW1wbGUgbGluZWFyIG1vZGVsIHdpdGggcmF3IGRhdGEgYXMgYSBiYXNlbGluZToKYGBge3J9CmJhc2VfZml0IDwtIGxtKHByaWNlIH4gLiwgZGF0YSA9IHNlbGVjdChiaCwgLXRvdGFsUHJpY2UsIC1jb21tdW5pdHlBdmVyYWdlLCAtZm9sbG93ZXJzLCAtRE9NLCAtc3BsaXRfZmxhZykgJT4lIG5hLm9taXQpCmNhdChwYXN0ZTAoIlItc3F1YXJlZDogIiwgcm91bmQoc3VtbWFyeShiYXNlX2ZpdCkkYWRqLnIuc3F1YXJlZCwgMiksCiAgICAgICAgICAgIlxuUk1TRTogIiwgcm91bmQoc3FydChtZWFuKGJhc2VfZml0JHJlc2lkdWFsc14yKSksIDIpLAogICAgICAgICAgICJcbk1BRTogIiwgcm91bmQobWVhbihhYnMoYmFzZV9maXQkcmVzaWR1YWxzKSksIDIpKSkKYGBgCgoKLS0tLS0tCgojIyMgUFJFUFJPQ0VTU0lORwoKKipQcmVwcm9jZXNzaW5nIHN0ZXBzOioqICAKCi0gUmVtb3ZpbmcgYWxsIG5vbiBwcm9wZXJ0eSBmZWF0dXJlcyAtIHRoZSBvbmVzIHdlIGhhZG4ndCBoYXZlIGF2YWlsYWJsZSBpbiB0aGUgcmVhbCB3b3JsZCBzaXR1YXRpb24gKHRvdGFsUHJpY2UsIGNvbW11bml0eUF2ZXJhZ2UsIGRheXMgb24gbWFya2V0LCBmb2xsb3dlcnMpCi0gQ29ycmVjdGluZyBjb2x1bW5zIHR5cGVzCi0gUmVtb3ZlIGV4dHJlYW1lIG91dGxpZXJzCi0gUmVtb3ZpbmcgcHJpY2VzIGJlbG93IDEwayBhcyBvYnZpb3N1bHkgZXJyb25lb3VzIGRhdGEKLSBSZW1vdmluZyBzb21lIGV4dHJlbWUgb3V0bGllcnMgKGxhZGRlclJhdGlvKQotIEV4dHJhY3QgZmxvb3IgbnVtYmVyIGZyb20gZmxvb3IgdGV4dCBmaWVsZAotIEV4dHJhY3QgZGF0ZSByZWxhdGVkIGZlYXR1cmVzIGZvciB0cmFkZVRpbWUKLSBPbmUtaG90IGVuY29kZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMKLSBTcGxpdCB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbgotIEltcHV0aW5nIG1pc3NpbmcgdmFsdWVzIHdpdGgga25uCi0gUmVkdWNlIGNvcnJlbGF0aW9uIGFuZCBjb29saW5lYXJpdHkKLSBSZW1vdmUgdmFyaWFibGVzIHdpdGggbmVhciB6ZXJvIHZhcmlhbmNlICgrIGxldmVscyB3aXRoIHZlcnkgc21hbGwgZnJlcXVlbmN5KQotIFNjYWxlICYgY2VudGVyCgo8YnI+CgojIyMjICoqRGF0ZSBmZWF0dXJlcyoqCgpgYGB7cn0KZGF0ZV9mZWF0dXJlcyA8LSBmdW5jdGlvbihkYXRlcywgYXNfZmFjdG9ycyA9IEZBTFNFKXsKICAiVGFrZXMgYSB2ZWN0b3Igb2YgZGF0ZXMgYW5kIHJldHVybnMgYSBkYXRhZnJhbWUgaW5jbHVkaW5nIHllYXIsIHF1YXJ0ZXJfb2ZfeWVhciwgbW9udGhfb2ZfeWVhciwgd2Vla19vZl95ZWFyLCB3ZWVrX29mX21vbnRoLCBkYXlfb2ZfbW9udGgsIHdlZWtkYXkgYW5kIHdlZWtlbmQiCiAgCiAgbW9udGh3ZWVrcyA8LSBmdW5jdGlvbih4KSB7CiAgICBjZWlsaW5nKGFzLm51bWVyaWMoZm9ybWF0KHgsICIlZCIpKS83KQogIH0KICBkYXRlcyA8LSBhcy5kYXRhLmZyYW1lKGRhdGVzKQogIGNvbG5hbWVzKGRhdGVzKSA8LSBjKCJkYXRlIikKICBkYXRlc19mZWF0dXJlcyA8LSBkYXRlcyAlPiUgbXV0YXRlKHllYXIgPSBsdWJyaWRhdGU6OnllYXIoZGF0ZSkpICU+JSAKICAgIG11dGF0ZShxdWFydGVyX29mX3llYXIgPSBsdWJyaWRhdGU6OnF1YXJ0ZXIoZGF0ZSkpICU+JSAKICAgIG11dGF0ZShtb250aF9vZl95ZWFyID0gbHVicmlkYXRlOjptb250aChkYXRlKSkgJT4lCiAgICBtdXRhdGUobW9udGhfbmFtZSA9IGx1YnJpZGF0ZTo6bW9udGgoZGF0ZSwgbGFiZWwgPSBUUlVFKSkgJT4lCiAgICBtdXRhdGUod2Vla19vZl95ZWFyID0gbHVicmlkYXRlOjp3ZWVrKGRhdGUpKSAlPiUgCiAgICBtdXRhdGUod2Vla19vZl9tb250aCA9IG1vbnRod2Vla3MoZGF0ZSkpICU+JSBtdXRhdGUoZGF5X29mX21vbnRoID0gbHVicmlkYXRlOjpkYXkoZGF0ZSkpICU+JSAKICAgIG11dGF0ZShkYXlfb2ZfeWVhciA9IGx1YnJpZGF0ZTo6eWRheShkYXRlKSkgJT4lIG11dGF0ZSh3ZWVrZGF5ID0gbHVicmlkYXRlOjp3ZGF5KGRhdGUpKSAlPiUgCiAgICBtdXRhdGUod2Vla2RheV9uYW1lID0gbHVicmlkYXRlOjp3ZGF5KGRhdGUsIGxhYmVsID0gVFJVRSkpICU+JSAKICAgIG11dGF0ZSh3ZWVrZW5kID0gaWZlbHNlKHdlZWtkYXkgJWluJSBjKDEsIDcpLCAxLCAwKSkgJT4lIAogICAgbXV0YXRlKHltZF9mb3JtYXQgPSBsdWJyaWRhdGU6OnltZChwYXN0ZSh5ZWFyLCBtb250aF9vZl95ZWFyLCBkYXlfb2ZfbW9udGgsIHNlcCA9ICItIikpKQogIGlmIChhc19mYWN0b3JzKSB7CiAgICBkYXRlc19mZWF0dXJlc1tjb2xuYW1lcyhkYXRlc19mZWF0dXJlcylbLTFdXSA8LSBsYXBwbHkoZGF0ZXNfZmVhdHVyZXNbY29sbmFtZXMoZGF0ZXNfZmVhdHVyZXMpWy0xXV0sIAogICAgICBmYWN0b3IpCiAgfQogIHJldHVybihkYXRlc19mZWF0dXJlcykKfQoKdHJhZGV0aW1lX2ZlYXR1cmVzIDwtIGRhdGVfZmVhdHVyZXMoYmgkdHJhZGVUaW1lLCBhc19mYWN0b3JzID0gRkFMU0UpICU+JSBzZWxlY3QoLWRhdGUsIC15bWRfZm9ybWF0LCAtd2Vla2RheSwgLW1vbnRoX29mX3llYXIsIC1kYXlfb2ZfeWVhcikKYGBgCgpgYGB7ciB3YXJuaW5nPUZBTFNFfQp0KGhlYWQodHJhZGV0aW1lX2ZlYXR1cmVzKSkKYGBgCgo8YnI+CgojIyMjICoqQ2xlYW5pbmcqKgoKYGBge3Igd2FybmluZz1GQUxTRX0KYmhfcHJvY2Vzc2VkIDwtIGJoICU+JQogIHJlbmFtZShmbG9vcl9jaGFyYWN0ZXIgPSBgZmxvb3JgKSAlPiUKICBjYmluZCh0cmFkZXRpbWVfZmVhdHVyZXMpICU+JQogIG11dGF0ZShmbG9vcl9udW1iZXIgPSBzdHJfZXh0cmFjdChmbG9vcl9jaGFyYWN0ZXIsICIoXFxkKykoPzogXFxkKykqJCIpKSAlPiUKICBmaWx0ZXIoYXMubnVtZXJpYyh5ZWFyKSA+IDIwMTEpICU+JQogIG1hcF9hdChjKCJsaXZpbmdSb29tIiwgImRyYXdpbmdSb29tIiwgImtpdGNoZW4iLCAiYmF0aFJvb20iLCAiYnVpbGRpbmdUeXBlIiAsICJidWlsZGluZ1N0cnVjdHVyZSIsICJyZW5vdmF0aW9uQ29uZGl0aW9uIiwgImZpdmVZZWFyc1Byb3BlcnR5IiwgImVsZXZhdG9yIiwgInN1YndheSIsICJkaXN0cmljdCIsICJxdWFydGVyX29mX3llYXIiLCAid2Vla2VuZCIsICJ3ZWVrZGF5X25hbWUiLCAibW9udGhfbmFtZSIsICJ5ZWFyIiksIGFzLmZhY3RvcikgJT4lCiAgbWFwX2F0KGMoIkxuZyIsICJMYXQiLCAiRE9NIiwgImZvbGxvd2VycyIsICJwcmljZSIsICJzcXVhcmUiLCAiY29uc3RydWN0aW9uVGltZSIsICJsYWRkZXJSYXRpbyIsICJmbG9vcl9udW1iZXIiLCAiY29uc3RydWN0aW9uVGltZSIsICJ3ZWVrX29mX3llYXIiLCAiZGF5X29mX21vbnRoIiksIGFzLm51bWVyaWMpICU+JQogIGFzX3RpYmJsZSgpICU+JQogIHNlbGVjdCgtZmxvb3JfY2hhcmFjdGVyLCAtdHJhZGVUaW1lLCAtdG90YWxQcmljZSwgLWNvbW11bml0eUF2ZXJhZ2UsIC1ET00sIC1mb2xsb3dlcnMsIC1zcGxpdF9mbGFnKSAlPiUKICBmaWx0ZXIoYnVpbGRpbmdUeXBlICVpbiUgYygxLCAyLCAzLCA0KSkgJT4lCiAgZmlsdGVyKGxhZGRlclJhdGlvIDwgcXVhbnRpbGUoYmgkbGFkZGVyUmF0aW8sIDAuOTkpKSAlPiUKICBmaWx0ZXIocHJpY2UgPiAxMDAwMCkgJT4lCiAgZmlsdGVyKGNvbnN0cnVjdGlvblRpbWUgPiAxODAwKSAlPiUKICBtdXRhdGUobmFDb25zdHJ1Y3Rpb24gPSBhcy5mYWN0b3IoaWZlbHNlKGlzLm5hKGNvbnN0cnVjdGlvblRpbWUpLCAibmFfY3QiLCAib2tfY3QiKSkpICU+JQogIG11dGF0ZShjb25zdHJ1Y3Rpb25UaW1lID0gcmVwbGFjZV9uYShjb25zdHJ1Y3Rpb25UaW1lLCAwKSkgJT4lCiAgbmEub21pdCgpCgpzdG9waWZub3QobWFwKGJoX3Byb2Nlc3NlZCwgY2xhc3MpWzFdICVpbiUgYygiZmFjdG9yIiwgIm9yZGVyZWQiLCAibnVtZXJpYyIpKQpzdG9waWZub3Qoc3VtKGlzLm5hKGJoX3Byb2Nlc3NlZCkpID09IDApCmBgYAoKPGJyPgoKIyMjIyAqKkR1bW1pZXMqKgoKYGBge3J9CiMgZmFzdGVyIHRoZW4gY2FyZXQgZGVmYXVsdHMKYmhfZmFjdG9ycyA8LSBzZWxlY3RfaWYoYmhfcHJvY2Vzc2VkLCBpcy5mYWN0b3IpCmR1bW1pZXMgPC0gc2VsZWN0X2lmKGZhc3REdW1taWVzOjpkdW1teV9jb2xzKGJoX2ZhY3RvcnMpLCBpcy5pbnRlZ2VyKQpiaF9wcm9jZXNzZWQgPC0gc2VsZWN0X2lmKGJoX3Byb2Nlc3NlZCwgaXMubnVtZXJpYykgJT4lIGNiaW5kKGR1bW1pZXMpCmJoX3Byb2Nlc3NlZCA8LSBjYmluZChiaF9wcm9jZXNzZWRbLDE6MTBdLCBtYXBfZGYoYmhfcHJvY2Vzc2VkWzExOmRpbShiaF9wcm9jZXNzZWQpWzJdXSwgYXMuZmFjdG9yKSkKYGBgCgo8YnI+CgojIyMjICoqVHJhaW5pbmcgc3BsaXQqKgoKYGBge3J9CnNldC5zZWVkKDIzKQp0dl9zcGxpdCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGJoX3Byb2Nlc3NlZCRwcmljZSwgcCA9IDAuOCwgbGlzdD1GQUxTRSkKYmhfdHJhaW4gPC0gYmhfcHJvY2Vzc2VkW3R2X3NwbGl0LF0KYmhfdmFsIDwtIGJoX3Byb2Nlc3NlZFstdHZfc3BsaXQsXQoKeF90cmFpbiA8LSBiaF90cmFpbiAlPiUgc2VsZWN0KC1wcmljZSkKeV90cmFpbiA8LSBiaF90cmFpbiAlPiUgc2VsZWN0KHByaWNlKQp4X3ZhbCA8LSBiaF92YWwgJT4lIHNlbGVjdCgtcHJpY2UpCnlfdmFsIDwtIGJoX3ZhbCAlPiUgc2VsZWN0KHByaWNlKQpgYGAKCjxicj4KCiMjIyMgKipGaW5hbCBwcmVwcm9jZXNzaW5nIChpbXB1dGF0aW9uLCBjb2xpbmVhcml0eSwgc2NhbGluZywgbnp2KSoqCgpgYGB7ciB3YXJuaW5nPUZBTFNFfQoKIyBjYXJldCBwcmVwcm9jZXNzaW5nIHdvdWxkIGF1dG9tYXRpY2FsbHkgc2NhbGUgZHVtbWllcyBpZiB3ZSBrZWVwIHRoZW0gYXMgbnVtZXJpYyBhbmQgc2tpcCBvdGhlciBvcGVyYXRpb25zIGlmIHdlIGtlZXAgdGhlbSBhcyBmYWN0b3JzIC0+IHR3byBwcmVwcm9jIHN0ZXBzCgp0aWMoImNhcmV0IHByZXByb2MiKQpwcmVwcm9jX251bWVyaWMgPC0gcHJlUHJvY2Vzcyh4X3RyYWluLCBtZXRob2QgPSBjKCJjZW50ZXIiLCAic2NhbGUiLCAiWWVvSm9obnNvbiIsICJrbm5JbXB1dGUiKSkKeF90cmFpbiA8LSBwcmVkaWN0KHByZXByb2NfbnVtZXJpYywgeF90cmFpbikKeF90cmFpbiA8LSBtYXBfZGYoeF90cmFpbiwgYXMubnVtZXJpYykKcHJlcHJvY19hbGwgPC0gcHJlUHJvY2Vzcyh4X3RyYWluLCBtZXRob2QgPSBjKCJuenYiLCAiY29yciIsICJjb25kaXRpb25hbFgiKSkKeF90cmFpbiA8LSBwcmVkaWN0KHByZXByb2NfYWxsLCB4X3RyYWluKQoKeF92YWwgPC0gcHJlZGljdChwcmVwcm9jX251bWVyaWMsIHhfdmFsKQp4X3ZhbCA8LSBtYXBfZGYoeF92YWwsIGFzLm51bWVyaWMpCnhfdmFsIDwtIHByZWRpY3QocHJlcHJvY19hbGwsIHhfdmFsKQoKd3JpdGVfcmRzKHhfdHJhaW4sICJkYXRhL3hfdHJhaW4ucmRzIikKd3JpdGVfcmRzKHhfdmFsLCAiZGF0YS94X3ZhbC5yZHMiKQoKdG9jKCkKCmBgYAoKYGBge3J9CnByaW50KHByZXByb2NfYWxsKQpwcmludChwcmVwcm9jX251bWVyaWMpCmBgYAoKPGJyPgoKKipUaGlzIGlzIGhvdyBvdXIgZmluYWwgdHJhaW5pbmcgc2V0IGxvb2tzIGxpa2U6KioKCmBgYHtyIHdhcm5pbmc9RkFMU0V9CmtuaXRyOjprYWJsZSh0KGhlYWQoeF90cmFpbikpKQpgYGAKCmBgYHtyfQpkaW0oeF90cmFpbikKYGBgCgotLS0tLS0KCiMjIyBNT0RFTElORwoKYGBge3J9CmdjKCkKYGBgCgojIyMjIFRyYWluCgpJIHRyYWluIDQgZGlmZmVyZW50IG1vZGVscyBpbmNsdWRpbmcgYmFzaWMgZ3JpZCBzZWFyY2ggb2YgaHlwZXJwYXJhbWV0ZXJzIGFuZCByZXBlYXRlZCA1LWZvbGQgY3Jvc3N2YWxpZGF0aW9uLiBJJ3ZlIHRyaWVkIHRvIHNlbGVjdCB2YXJ5aW5nIGFwcHJvYWNoZXMgYW5kIGFsbG93aW5nIGZvciBzb21laG93IGludGVycHJldGFibGUgcmVzdWx0czoKCi0gTGFzc28gLSBsaW5uZWFyIHJlZ3Jlc3Npb24gd2l0aCBMMSByZWd1bGFyaXphdGlvbiAoYGxhc3NvYCkKLSBNdWx0aXZhcmlhdGUgQWRhcHRpdmUgUmVncmVzc2lvbiBTcGxpbmVzIChNQVJTIC0gW2xpbmtdKGh0dHA6Ly91Yy1yLmdpdGh1Yi5pby9tYXJzKSkuIChBbHNvIGNhbGxlZCBgZWFydGhgKQotIEJheWVzaWFuIHJlZ3VsYXJpemVkIG5ldXJhbCBuZXR3b3JrLCAybGF5ZXJzIChgYnJubmApCi0gUmFuZG9tIGZvcmVzdCAoYHJhbmRvbV9mb3Jlc3RgKQoKCmBgYHtyIHdhcm5pbmc9RkFMU0V9CnRpYygiVHJhaW5pbmcgbW9kZWxzIikKc2V0LnNlZWQoMjMpCnRyYWluaW5nX2RhdGEgPC0gY2JpbmQoeV90cmFpbiwgeF90cmFpbikKbWV0aG9kcyA8LSBjKCJsYXNzbyIsICJlYXJ0aCIsICJicm5uIiwgInJhbmdlciIpCmZpdENvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtYmVyID0gNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwZWF0cyA9IDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlYXJjaCA9ICJncmlkIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxsb3dQYXJhbGxlbCA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2VJdGVyID0gVFJVRSkKCiNjbCA8LSBtYWtlUFNPQ0tjbHVzdGVyKDIpCiNyZWdpc3RlckRvUGFyYWxsZWwoY2wpCm1vZGVsc19tZXRyaWNzIDwtIGxpc3QoKQpmb3IgKG0gaW4gbWV0aG9kcykgewoKICBtb2RlbCA8LSBjYXJldDo6dHJhaW4ocHJpY2UgfiAuLAogICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5pbmdfZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gbSwKICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCwKICAgICAgICAgICAgICAgICAgICAgICAgaW1wb3J0YW5jZSA9ICdpbXB1cml0eScsCiAgICAgICAgICAgICAgICAgICAgICAgIG5hLmFjdGlvbiA9IG5hLnBhc3MpCgogIHNhdmVSRFMobW9kZWwsIHBhc3RlMCgibW9kZWxzLyIsIG0sICIucmRzIikpCiAgbW9kZWxzX21ldHJpY3NbW21dXSA8LSBtb2RlbCRyZXNhbXBsZQogIGdjKCkKfQoKI3N0b3BDbHVzdGVyKGNsKQp0b2MoKQpgYGAKCjxicj4KCiMjIyMgKipDb21wYXJlIG1vZGVscyBwZXJmb3JtYW5jZSAqKgoKYGBge3J9Cm1vZGVscyA8LSBsaXN0LmZpbGVzKCJtb2RlbHMvIikKbW9kZWxzX21ldHJpY3MgPC0gbGlzdCgpCmZvciAobW9kZWwgaW4gbW9kZWxzKSB7CiAgZml0IDwtIHJlYWRfcmRzKHBhc3RlMCgibW9kZWxzLyIsIG1vZGVsKSkKICBtb2RlbHNfbWV0cmljc1tbZml0JG1ldGhvZF1dIDwtIGZpdCRyZXNhbXBsZQp9CmBgYAoKYGBge3J9CmRmX21ldHJpY3MgPC0gZG8uY2FsbCh3aGF0ID0gcmJpbmQsIGFyZ3MgPSBtb2RlbHNfbWV0cmljcykgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCkgJT4lCiAgc2VwYXJhdGUocm93bmFtZSwgaW50byA9IGMoIm1vZGVsX25hbWUiLCAiaXRlcmF0aW9uIikpICU+JQogIGZpbHRlcihtb2RlbF9uYW1lICVpbiUgYygibGFzc28iLCAiZWFydGgiLCAiYnJubiIsICJyYW5nZXIiKSkgJT4lCiAgbXV0YXRlKG1vZGVsX25hbWUgPSBpZmVsc2UobW9kZWxfbmFtZSA9PSAicmFuZ2VyIiwgInJhbmRvbV9mb3Jlc3QiLCBtb2RlbF9uYW1lKSkKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD0xMH0KcDEgPC0gZ2dwbG90KGRmX21ldHJpY3MsIGFlcyhmb3JjYXRzOjpmY3RfcmVvcmRlcihtb2RlbF9uYW1lLCBNQUUsIC5kZXNjID0gVFJVRSksIE1BRSkpICsKICBnZW9tX2JveHBsb3QoKSArIAogIGdlb21fcG9pbnQoKSArCiAgbGFicyh0aXRsZSA9ICJSZXNhbXBsZWQgbWV0cmljcyIsIHN1YnRpdGxlID0gIk1lYW4gYWJzb2x1dGUgZXJyb3IiLCB4ID0gIiAiLCB5ID0gIk1BRSIpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCBwbG90Lm1hcmdpbiA9IHVuaXQoYygwLDAsMCwwKSwiY20iKSkKCnAyIDwtIGdncGxvdChkZl9tZXRyaWNzLCBhZXMoZm9yY2F0czo6ZmN0X3Jlb3JkZXIobW9kZWxfbmFtZSwgUnNxdWFyZWQsIC5kZXNjID0gVFJVRSksIFJzcXVhcmVkKSkgKwogIGdlb21fYm94cGxvdCgpICsgCiAgZ2VvbV9wb2ludCgpICsKICBsYWJzKHN1YnRpdGxlID0gIlJzcXVhcmVkIiwgeCA9ICIgIiwgeSA9ICJSc3F1YXJlZCIpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCBwbG90Lm1hcmdpbiA9IHVuaXQoYygwLDAsMCwxKSwiY20iKSkKCnAxICsgcDIgKyBwYXRjaHdvcms6OnBsb3RfbGF5b3V0KG5jb2wgPSAyKQpgYGAKClJhbmRvbSBmb3Jlc3Qgb3V0cGVyZm9ybXMgb3RoZXIgc2VsZWN0ZWQgbWV0aG9kcy4gTGV0J3QgdmFsaWRhdGUgaXRzIHBlcmZvcm1hbmNlIGluIG1vcmUgZGV0YWlsIGFuZCBsb29rIGludG8gbW9zdCBpbXBvcnRhbnQgZmVhdHVyZXMgaXQgcGlja2VkIHVwLgoKLS0tLS0tCgojIyMgVkFMSURBVElPTgoKYGBge3J9CnJmIDwtIHJlYWRfcmRzKCJtb2RlbHMvcmFuZ2VyLnJkcyIpCnJmJGZpbmFsTW9kZWwKYGBgCgotIFRoZSBiZXN0IHBlcmZvcm1pbmcgbW9kZWwgKH4wLjk1IFItc3F1YXJlZCkgaXMgYSByYW5kb20gZm9yZXN0IHdpdGggNTAwIHRyZWVzLCAyMiByYW5kb21seSBzZWxlY3RlZCBwcmVkaWN0b3JzLCBtaW5pbWFsIG5vZGUgc2l6ZSBvZiA1IGFuZCB2YXJpYW5jZSBiYXNlZCBzcGxpdGluZyBydWxlLiBBbHRvdWdoIGRvaW5nIGV4dGVuc2l2ZSBncmlkIHNlYXJjaCBvdmVyIDMgaHlwZXJwYXJhbWV0ZXJzIHRvb2sgbXVjaCBtb3JlIHRpbWUgdGhhbiBvdGhlciBtb2RlbHMgdGhlIHJlc3VsdCBpcyBzaWduaWZpY2FudGx5IGJldHRlci4KCkxldCdzIHZpc3VhbGl6ZSBwZXJmb3JtYW5jZSBvZiB0aGUgYmVzdCBtb2RlbDoKCmBgYHtyfQptb2RlbCA8LSByZgp5X2hhdCA8LSBwcmVkaWN0KG1vZGVsLCBuZXdkYXRhID0geF92YWwpCgpwcmVkc19kZiA8LSBhcy5kYXRhLmZyYW1lKHlfaGF0KSAlPiUKICBtdXRhdGUoeSA9IHlfdmFsJHByaWNlKSAlPiUKICBtdXRhdGUocmVzaWR1YWxzID0geV9oYXQgLSB5LAogICAgICAgICBzZF9yZXNpZHVhbHMgPSBzY2FsZShyZXNpZHVhbHMpLAogICAgICAgICBhYnNfZXJyb3IgPSBhYnMocmVzaWR1YWxzKSkKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9MTAsIHdhcm5pbmc9RkFMU0V9CnAxIDwtIGdncGxvdCgpICsgCiAgZ2VvbV9kZW5zaXR5KGFlcyh5KSwgZGF0YSA9IHByZWRzX2RmLCBjb2xvdXIgPSAiZGFya2dyZWVuIiwgZmlsbCA9ICJkYXJrZ3JlZW4iLCBhbHBoYSA9IDAuNSkgKyAKICBnZW9tX2RlbnNpdHkoYWVzKHlfaGF0KSwgZGF0YSA9IHByZWRzX2RmLCBjb2xvdXIgPSAiZGFya2JsdWUiLCBmaWxsID0gImRhcmtibHVlIiwgYWxwaGEgPSAwLjUpICsgCiAgbGFicyh4ID0gInByaWNlIiwgdGl0bGU9IkJlc3QgbW9kZWwgLSB2YWxpZGF0aW9uIiwgc3VidGl0bGU9IkFjdHVhbHMgJiBwcmVkaWN0aW9ucyBkaXN0cmlidXRpb25zIikgKwogIHNjYWxlX3hfY29tbWEoKQoKcDIgPC0gZ2dwbG90KHByZWRzX2RmLCBhZXMoeSwgeV9oYXQpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21fc21vb3RoKCkgKyAKICBzY2FsZV94X2NvbW1hKCkgKwogIHNjYWxlX3lfY29tbWEoKSArCiAgbGFicyh4ID0gIlByaWNlIiwgeT0iUHJlZGljdGVkIHByaWNlIiwgc3VidGl0bGU9IkFjdHVhbHMgdnMgcHJlZGljdGlvbnMiKQoKcDMgPC0gZ2dwbG90KHByZWRzX2RmLCBhZXMoYWJzX2Vycm9yKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAxMDApICsKICBzY2FsZV94X2NvbW1hKGxpbWl0cyA9IGMoMCwgNDAwMDApKSArCiAgbGFicyh4ID0gIkFic29sdXRlIGVycm9yIiwgc3VidGl0bGUgPSAiQWJzb2x1dGUgZXJyb3IgZGlzdHJpYnV0aW9uIikKCnA0IDwtIGdncGxvdChwcmVkc19kZiwgYWVzKHlfaGF0LCBzZF9yZXNpZHVhbHMpKSArIAogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsgCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3VyPSJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArIAogIGxhYnMoeCA9ICJQcmVkaWN0ZWQgcHJpY2UiLCB5ID0gIlN0YW5kYXJpemVkIHJlc2lkdWFscyIsIHN1YnRpdGxlID0gIlJlc2lkdWFscyIpCgpwMSArIHAyICsgcDMgKyBwNCArIHBsb3RfbGF5b3V0KG5yb3cgPSAyLCBuY29sID0gMikKYGBgCgoKYGBge3J9CmNhdChwYXN0ZTAoIk1lZGlhbiBwcmljZSBpbiB0aGUgdmFsaWRhdGlvbiBzZXQ6ICIsIG1lZGlhbih5X3ZhbCRwcmljZSksCiAgICAgICAgICAiXG5NZWFuIGFic29sdXRlIGVycm9yOiAiLCByb3VuZChtZWFuKG1vZGVsJHJlc2FtcGxlJE1BRSksIDIpKSkKYGBgCjxicj4KCgojIyMjICoqRmVhdHVyZXMgSW1wb3J0YW5jZToqKgoKCmBgYHtyIGZpZy5oZWlnaHQ9NywgZmlnLndpZHRoPTd9CmZlYXR1cmVzX2ltcG9ydGFuY2UgPC0gdmFySW1wKG1vZGVsKSRpbXBvcnRhbmNlICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigpICU+JQogIHNlbGVjdChmZWF0dXJlID0gcm93bmFtZSwgZ2luaV9pbXBvcnRhbmNlID0gT3ZlcmFsbCkgJT4lCiAgYXJyYW5nZShkZXNjKGdpbmlfaW1wb3J0YW5jZSkpICU+JQogIGhlYWQoMTUpCgpnZ3Bsb3QoZmVhdHVyZXNfaW1wb3J0YW5jZSwgYWVzKGZvcmNhdHM6OmZjdF9yZW9yZGVyKGZlYXR1cmUsIGdpbmlfaW1wb3J0YW5jZSksIGdpbmlfaW1wb3J0YW5jZSkpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMikgKyAKICBjb29yZF9mbGlwKCkgKwogIGxhYnMoeCA9ICIgIiwgeSA9ICJHaW5pIGltcG9ydGFuY2UgKE1lYW4gRGVjcmVhc2UgaW4gSW1wdXJpdHkpIiwgc3VidGl0bGUgPSAiVG9wIGZlYXR1cmVzIHVzZWQgYnkgcmFuZG9tIGZvcmVzdCBmb3Igc3BsaXRzIikKYGBgCgotIFByZWRpY3Rpb25zIHdlcmUgZ2VuZXJhdGVkIG1haW5seSBieSBsb29raW5nIGF0IHRoZSBsb2NhdGlvbiAoTGF0L0xuZyBvciBkaXN0cmljdCkgYW5kIHRpbWUgdGhlIGFwYXJ0bWVudCB3YXMgcG9zdGVkIGZvciBzYWxlLiBTdWNoIGNoYXJhY3RlcmlzdGljcyBhcyBzaXplIG9mIHRoZSBhcGFydG1lbnQsIGNvbnN0cnVjdGlvbiB0aW1lIG9yIHJlbm92YXRpb24gY29uZGl0aW9uIGRpZG4ndCBtYWtlIGl0IGludG8gdG9wIDEwLiAKCjxicj4KCioqSW5kaXZpZHVhbCBleGFtcGxlcyoqCgpBdCB0aGUgZW5kIGxldCdzIGxvb2sgYXQgZmV3IGluZGl2aWR1YWwgZXhhbXBsZXMgb2YgYXBhcnRtZW50cyBhbmQgdHJ5IHRvIGV4cGxhaW4gd2hpY2ggdmFyaWFibGVzIHdlcmUgdGhlIG1vc3QgaW1wb3J0YW50IGZvciB0aGUgbW9kZWwgdG8gbWFrZSBwcmljZSBlc3RpbWF0aW9uOgoKYGBge3Igd2FybmluZz1GQUxTRX0KbGlicmFyeShsaW1lKQpzZXQuc2VlZCgyMykKc2VsZWN0ZWRfYXBhcnRtZW50cyA8LSBzYW1wbGVfbih4X3ZhbCwgNikKZXhwbGFpbmVyIDwtIGxpbWUoc2VsZWN0ZWRfYXBhcnRtZW50cywgbW9kZWwgPSBtb2RlbCkKZXhwbGFuYXRpb24gPC0gZXhwbGFpbihzZWxlY3RlZF9hcGFydG1lbnRzLCBleHBsYWluZXIsIG5fZmVhdHVyZXMgPSA3KQpgYGAKCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMCwgd2FybmluZz1GQUxTRX0KcGxvdF9mZWF0dXJlcyhleHBsYW5hdGlvbikKYGBgCgotLS0tLS0KCjxjZW50ZXI+VEhFIEVORDwvY2VudGVyPgoK