Code:
library(readxl)
data1 <- read_excel("data/salary index data.xls", range= "A127:Z726", col_names = FALSE)
data2 <- read_excel("data/salary index data.xls", range= "A787:Z1266", col_names = FALSE)
full_data <- rbind(data1, data2)
dim(full_data)
print(full_data[1:3, 1:13])
Data Cleaning
Renaming and dropping unwanted columns
We are only using the unadjusted version of the columns…
library(dplyr)
select_and_rename_v2 <- function(df) { # cleaning pipeline
selected_df <- df[, c(1, 2, 3, 4, 12, 20)] # Select columns 1, 2, 3, 4, 12, and 20
# Rename the columns with desired names
names(selected_df) <- c("Economic activity", "Year", "Quarter", "Employment index", "Hours worked index", "salaries index")
# Reordering
selected_df = selected_df[c("Year", "Quarter", "Employment index", "Hours worked index", "salaries index", "Economic activity")]
return(selected_df) }
clean_full_data = select_and_rename_v2(full_data)
head(clean_full_data, 3)
Fixing wrong entries
Some comment where made by tuik and that caused some entries in the
Year column to have letters so we are fixing them so that the column can
be of type int…
clean_full_data[53,]
clean_full_data$Year <- gsub("\\(r\\)", "", clean_full_data$Year) # using regular expressions to fix wrong entries
clean_full_data$Year <- as.numeric(clean_full_data$Year)
clean_full_data[53,]
Filling NA’s
seeing what column have NA values..
colSums(is.na(clean_full_data))
Year and Quarter columns: the year column has empty entries that
indicate the value of the last non empty and because we stacked all the
tables. For those empty entries we will be using a function that
replaces the NAs with the last non-NA value. We are making a function
pipeline that replaces quarter with a number and merges both columns
into one so it becomes our only time series column.
library(dplyr)
library(zoo)
one_column_function <- function(df) {
quarter_mapping <- c("I" = 1, "II" = 2, "III" = 3, "IV" = 4)
df <- df |>
mutate(
Year = na.locf(df$Year, na.rm = FALSE),
Year_Quarter = as.numeric(as.numeric(Year) + (quarter_mapping[Quarter] - 1) / 4
)) |>
select(-Year, -Quarter)
return(df) }
clean_full_data = one_column_function(clean_full_data)
head(clean_full_data,3)
Economic Activity Column: has empty entries for the same reason
above but in this case each table from the tables stacked has a
different value for that we are making a window and iterating through
each window to fill the NA’s with the first non-NA value in that window
(each table from the stacked tables has 60 rows thus the /60). After
that we made the column of type factor…
library(dplyr)
fill_pattern <- function(df) {
n_rows <- nrow(df)
num_windows <- ceiling(n_rows / 60)
for (i in 1:num_windows) { # Iterate over each window
start_index <- (i - 1) * 60 + 1 # Determine the start and end indices of the current window
end_index <- min(i * 60, n_rows)
df[start_index:end_index, "Economic activity"] <- df[start_index+1, "Economic activity"] # Fill the current window with the second row value
}
return(df)}
clean_full_data = fill_pattern(clean_full_data)
head(clean_full_data,3)
clean_full_data$`Economic activity` = factor(clean_full_data$`Economic activity`)
fixing names: because some names have some special Turkish
characters and changing them would mean not running into unexpected
errors when training
library(dplyr)
library(forcats)
clean_full_data <- clean_full_data |> # changing name with forcats
mutate(`Economic activity` = fct_recode(`Economic activity`, "Intermediate goods" = "IG-Intermediate goods",
"Durable consumer goods" = "DCG-Durable consumer goods","Non-durable consumer goods" = "NDCG-Non-durable consumer goods",
"Energy" = "NRG-Energy","Capital goods" = "CG-Capital goods",
"Mining and quarrying" = "Madencilik ve taş ocakçılığı","Manufacturing" = "İmalat",
"Electricity, gas, steam and air conditioning supply" = "Elektrik, gaz, buhar ve iklimlendirme",
"Water supply, sewerage, waste management and remediation activities" = "Su temini; kanalizasyon, atık yönetimi",
"Construction" = "İnşaat","Wholesale and retail trade" = "Toptan ve perakende ticaret;",
"Transportation and storage" = "Ulaştırma ve depolama","Accommodation and food service activities" = "Konaklama ve yiyecek hizmeti",
"Information and communication" = "Bilgi ve iletişim","Financial and insurance activities" = "Finans ve sigorta faaliyetleri",
"Real estate activities" = "Gayrimenkul faaliyetleri",
"Professional, scientific and technical activities" = "Mesleki, bilimsel ve teknik faaliyetler",
"Administrative and support service activities" = "İdari ve destek hizmet faaliyetleri"))
head(levels(clean_full_data$`Economic activity`),3)
Final structure
str(clean_full_data)
dim(clean_full_data)
Visualizations
- correlation heatmap between all variables
library(ggplot2)
library(reshape2)
data <- cor(clean_full_data[sapply(clean_full_data, is.numeric)]) # Calculating correlation matrix
data1 <- melt(data) # Reshaping data
p <- ggplot(data1, aes(Var1, Var2, fill = value)) +
geom_tile() +
geom_text(aes(label = round(value, 8)), color = "black", size = 3) +
scale_fill_gradient(low = "white", high = "purple") + # Color gradient for heatmap
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1), # Rotating x-axis labels for better readability
axis.title.x = element_blank(), # Removing axes titles
axis.title.y = element_blank())
print(p)
The correlation between the target and the features is not bad but
the biggest maybe problem hear is the feature co-linearity, although
that is a bad thing we are not going to drop any feature because we
don’t have a lot of features to begin with.
- Visualizing trend over time
library(tidyr)
library(ggplot2)
# Reshaping data into long format, excluding Economic_activity and Year_Quarter columns
df_long <- gather(clean_full_data, key = "Variable", value = "Value", -Year_Quarter, -`Economic activity`)
# Ploting line plots for each variable, using Economic_activity as hue
ggplot(df_long, aes(x = Year_Quarter, y = Value, color = `Economic activity`, group = `Economic activity`)) +
geom_line() +
facet_wrap(~ Variable, scales = "free_y", nrow = 1) +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
guides(color = FALSE) # Removing the legend
We can see the co-linearity between the previous two features but
notice how the trends are a bit different which is information we obtain
by keeping the feature and training on the whole data set.
Data Pre-processing
Feature Selection
- we are gonna use all the features despite two being highly
correlated. We will later use other methods to ensure good
accuracy…
data <- clean_full_data
Encoding
encoding is recommended so that the algorithm doesn’t have to deal
with characters, we are using one-hot encoding because the categorical
column is not ordinal but nominal.
library(fastDummies)
data <- dummy_cols(data, select_columns = "Economic activity")
data <- subset(data, select = -c(`Economic activity`))
tail(colnames(data),3)
Renaming “salaries index” to y for easier and shorter
code
names(data)[names(data) == "salaries index"] <- "y"
head(data$y,3)
Train Test Split
We will be doing an 80% training, 10% validation, 10% testing ratio
for most models thus the code below…
library(caret)
set.seed(45) # Setting the seed for reproducibility
train_indices <- createDataPartition(data$y, p = 0.9, list = FALSE) # Splitting the dataset into 90% training and 10% test
train_data <- data[train_indices, ]
test_data <- data[-train_indices, ]
cat("Training data dimensions:", dim(train_data), "\n")
cat("Test data dimensions:", dim(test_data), "\n")
Scaling
because scaling is recommended for most models we are applying it but
for any other case we will first save the unscaled version…
unscaled_train_data = train_data # Saving unscaled version
unscaled_test_data = test_data
library(caret)
set.seed(45)
columns_to_normalize <- c("Employment index", "Hours worked index", "y", "Year_Quarter")
preprocess <- preProcess(train_data[, columns_to_normalize], method = c("center", "scale"))# Pre-processing pipeline to *normalize* the selected columns
norm_train_data <- predict(preprocess, newdata = train_data)# Applying the pipeline to the training data
norm_test_data <- predict(preprocess, newdata = test_data)# Applying the same pipeline to the test data
print(norm_train_data[1:3, 1:4])
Specifying X & Y
Assigning X and Y for upcoming testing…
library(dplyr)
# Unscaled train & test, y & x
unscaled_train_x = select(unscaled_train_data, -y)
unscaled_train_y = as.numeric(unscaled_train_data[["y"]])
unscaled_test_x = select(unscaled_test_data, -y)
unscaled_test_y = as.numeric(unscaled_test_data[["y"]])
# Scaled train & test, y & x
train_x = select(norm_train_data, -y)
train_y = as.numeric(norm_train_data[["y"]])
test_x = select(norm_test_data, -y)
test_y = as.numeric(norm_test_data[["y"]])
Models
Testing score function
We are going to use the normalized version of RMSE as the normal RMSE
does not take into consideration the error according to the range of our
target variable and we would have to analyze the range to determine if
the score is good or not. Normalized RMSE takes into consideration our Y
range and outputs a decimal between 0-1 and the closer it is to 0 the
better the model is. Here’s how we implemented it…
normalized_rmse = function(pred, real){ #Normalized Root Mean Squared Error
error = real - pred
sqrt(mean(error^2))/(max(real) - min(real)) }
MLR: Multiple Linear Regression
set.seed(123)
mlr_model <- lm(y ~ ., data = norm_train_data) # Training...
mlr_predictions <- predict(mlr_model, newdata = test_x) # Testing...
mlr_rmse <- normalized_rmse(mlr_predictions, test_y)
print(mlr_rmse)
Random Forest
library(caret)
set.seed(123)
train_control <- trainControl(method = "cv", number = 5) # Performing cross validation
rf_model <- train( # Training the Random Forest model
y ~ .,
data = norm_train_data,
method = "rf",
trControl = train_control,
ntree = 500) # Number of trees in the forest
print(rf_model) # Printing metrics scores
set.seed(123)
rf_predictions <- predict(rf_model, newdata = norm_test_data)
rf_rmse = normalized_rmse(rf_predictions, test_y)
print(rf_rmse)
Decision Tree
library(rpart)
set.seed(123)
tree_model <- rpart(
formula = y ~ .,
data = norm_train_data,
method = "anova", # "anova" to specify regression
control = rpart.control(cp = 0.01)) # Control parameters (complexity parameter)
set.seed(123)
dt_predictions <- predict(tree_model, newdata = norm_test_data)
dt_rmse = normalized_rmse(dt_predictions, test_y)
print(dt_rmse)
KNN: K-Nearest Neighbor
library(caret)
set.seed(123)
knn_model <- train( # Training the KNN regression model
x = train_x,
y = train_y,
method = "knn",
trControl = trainControl(method = "cv", number = 5), # Cross-validation settings
tuneGrid = expand.grid(k = c(1, 3, 5))) # Hyperparameter grid (k)
set.seed(123)
knn_predictions <- predict(knn_model, newdata = test_x)
knn_rmse = normalized_rmse(knn_predictions, test_y)
print(knn_rmse)
SVM Regression
library(e1071)
set.seed(45)
#Tuning the SVM model
tuned_svm=tune(svm, y~., data=norm_train_data, ranges=list(epsilon=seq(0,1,0.1), cost=c(0.01, 0.1, 1, 10, 100),
kernal = c("linear", "polynomial", "radial basis", "sigmoid")))
print(tuned_svm) # performance measure = MSE
- Training and validation accuracy
best_tuned_svm = tuned_svm$best.model
svm_train_err = normalized_rmse(predict(best_tuned_svm, train_x), train_y)
print(svm_train_err)
svm_predictions = predict(best_tuned_svm, test_x)
svm_rmse = normalized_rmse(svm_predictions, test_y)
print(svm_rmse)
ALL IN ONE: Blender
In this section we are going to be implementing the ensemble
stacking method involving three base learners and a
meta learner which learns from the predictions of the base
learners…
LEARNER-1: Gradient Boosting Machine
- LEARNER-2: Generalized Linear Model
- LEARNER-3: Fully connected Neural Network
- META LEARNER: Random Forest
- meta learner training metrics…
ensemble@model$training_metrics
- meta learner validation rmse curve…
rmse_df = as.data.frame(t(as.data.frame(ensemble@model$cross_validation_metrics_summary)[6,3:32]))
ggplot(rmse_df, aes(x = index(rmse_df), y = rmse)) +
geom_line() +
geom_point() +
labs(x = "CV", y = "RMSE", title = "RMSE Values across Cross-Validation Folds")
- Testing base learners performance…
set.seed(45)
h20_test = as.h2o(norm_test_data) # data to H2O data
gbm_perf <- h2o.performance(gbm, newdata = h20_test)
glm_perf <- h2o.performance(glm, newdata = h20_test)
dl_perf <- h2o.performance(dl, newdata = h20_test)
print(gbm_perf)
print(glm_perf)
print(dl_perf)
- meta learner performance on test data…
set.seed(45)
meta_perf <- h2o.performance(ensemble, newdata = h20_test)
print(meta_perf)
Test Results Summary
- Now to sum up all the results of our project we are going to make a
table with all the models’ RMSE scores on the test data and compare
them…
ens_rmse = meta_perf@metrics$RMSE/(max(test_y) - min(test_y))
rmse_df = data.frame("Model" = c("Multiple Linear Regression", "Random Forest", "Decision Tree", "K-Nearest Neighbors", "Support Vector Machine Regression", "Stacking (Blender)"),
"Normalized RMSE" = c(mlr_rmse, rf_rmse, dt_rmse, knn_rmse, svm_rmse, ens_rmse), check.names=FALSE)
rmse_df <- rmse_df[order(rmse_df$`Normalized RMSE`, decreasing = TRUE), ]
print(rmse_df)
Best Performing Model
| Stacking (Blender) |
\(0.025 \pm 0.005\) |
LS0tDQp0aXRsZTogIlByZWRpY3RpbmcgU2FsYXJpZXMgSW5kZXggV2l0aCBEaWZmZXJlbnQgTWFjaGluZSBMZWFybmluZyBNZXRob2RzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyMjIEludHJvZHVjdGlvbg0KDQpJbiB0aGlzIHJlcG9ydCwgd2UgYXJlIGdvaW5nIHRvIHRyYWluIGRpZmZlcmVudCBtYWNoaW5lIGxlYXJuaW5nIG1ldGhvZHMgYW5kIHRlc3QgdGhlaXIgcGVyZm9ybWFuY2Ugb24gb3VyIHRlc3QgZGF0YS4gT3VyIGRhdGEgY2FuIGJlIGRvd25sb2FkZWQgZnJvbSB0aGUgbGluazpcdGV4dGNvbG9ye2JsdWV9ezxodHRwczovL2RhdGEudHVpay5nb3YudHIvQnVsdGVuL0Rvd25sb2FkSXN0YXRpc3Rpa3NlbFRhYmxvP3A9VDVnL1dPaVkxQkg0SmJtd3czanRDbzFxMDMxSS9Nbnp6dTdIVklqejc1T3p2Mk56bWhyTlR6TnphTEN3NzRQdT59LiBPdXIgdGFzayBpcyByZWdyZXNzaW9uIGFuZCB0aGUgbWV0aG9kIHdlIHVzZWQgaW4gdGhpcyByZXBvcnQgYXJlOiBNTFIsIFJhbmRvbSBGb3Jlc3QsIERlY2lzaW9uIFRyZWUsIGstTk4sIFNWTSByZWdyZXNzaW9uIGFuZCBhIHN0YWNraW5nIGVuc2VtYmxlIG1ldGhvZCBmcm9tIHRoZSBsaWJyYXJ5IEgyMC4NCg0KKipSZXNlYXJjaCBRdWVzdGlvbioqDQoNCk91ciByZXNlYXJjaCBxdWVzdGlvbiBpczoNCg0KKioqIldoYXQgaXMgdGhlIGJlc3QgbWFjaGluZSBsZWFybmluZyBtZXRob2Qgb3IgYWxnb3JpdGhtIHRoYXQgYmVzdCBwcmVkaWN0cyB0aGUgc2FsYXJ5IGluZGV4IGJhc2VkIG9uIGRpZmZlcmVudCBmYWN0b3JzPyIqKioNCg0KKipEYXRhICYgZGF0YSBzdHJ1Y3R1cmUqKg0KDQpIZXJlJ3MgYSBsb29rIGludG8gdGhlIGV4Y2VsIGZpbGUuLi4NCg0KKzotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSsNCnwgIVtGaWcuMTogRGF0YSBFeGNlbCBGaWxlXShwaWN0dXJlcy9TY3JlZW5zaG90JTIwMjAyNC0wNS0wOSUyMDE0MDYxMi5wbmcpe3dpZHRoPSIzLjczaW4ifSB8IFRoZSBleGNlbCBmaWxlIGNvbnNpc3RzIG9mIG11bHRpcGxlIHRhYmxlcyBzdGFja2VkIG9uIHRvcCBvZiBlYWNoICAgICAgICAgICAgICAgICAgICAgICB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgb3RoZXIgZWFjaCByZXByZXNlbnRpbmcgdGhlIGZlYXR1cmVzIG9mIGFuIGVjb25vbWljIGFjdGl2aXR5LiBUaGUgZmVhdHVyZXMgY29sdW1ucyBhcmU6IHwNCnwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAtICAgRW1wbG95bWVudCBJbmRleDogVW5hZGp1c3RlZCwgQ2FsZW5kZXIgYWRqdXN0ZWQsIFNlYXNvbmFsIGFuZCAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICBjYWxlbmRlciBhZGp1c3RlZCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgLSAgIEhvdXJzIHdvcmtlZCBJbmRleDogVW5hZGp1c3RlZCwgQ2FsZW5kZXIgYWRqdXN0ZWQsIFNlYXNvbmFsIGFuZCAgICAgICAgICAgICAgICAgICAgIHwNCnwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAgICAgY2FsZW5kZXIgYWRqdXN0ZWQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IC0gICBHcm9zcyB3YWdlcy1zYWxhcmllcyBJbmRleDogVW5hZGp1c3RlZCwgQ2FsZW5kZXIgYWRqdXN0ZWQsU2Vhc29uYWwgYW5kICAgICAgICAgICAgICB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgICAgIGNhbGVuZGVyIGFkanVzdGVkICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCistLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rDQoNCiMjIENvZGU6DQoNCmBgYHs9dGV4fQ0KXGxldFxvbGRTaGFkZWRcU2hhZGVkDQpcbGV0XGVuZG9sZFNoYWRlZFxlbmRTaGFkZWQNClxyZW5ld2Vudmlyb25tZW50e1NoYWRlZH17XHNtYWxsXG9sZFNoYWRlZH17XGVuZG9sZFNoYWRlZH0NCmBgYA0KYGBge3IgZWNobz1GQUxTRSwgIHNpemUgPSAic21hbGwifQ0KI1x0aW55DQprbml0cjo6b3B0c19jaHVuayRzZXQobWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIGVjaG8gPSBUUlVFLCByZXN1bHRzID0gImhvbGQiLCBlY2hvID0gVFJVRSwgZmlnLmFsaWduPSJjZW50ZXIiLCBzaXplID0gJ3NtYWxsJykNCg0KYGBgDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsICBzaXplID0gInNtYWxsIn0NCmxpYnJhcnkocmVhZHhsKQ0KZGF0YTEgPC0gcmVhZF9leGNlbCgiZGF0YS9zYWxhcnkgaW5kZXggZGF0YS54bHMiLCAgcmFuZ2U9ICJBMTI3Olo3MjYiLCBjb2xfbmFtZXMgPSBGQUxTRSkNCmRhdGEyIDwtIHJlYWRfZXhjZWwoImRhdGEvc2FsYXJ5IGluZGV4IGRhdGEueGxzIiwgIHJhbmdlPSAiQTc4NzpaMTI2NiIsIGNvbF9uYW1lcyA9IEZBTFNFKQ0KZnVsbF9kYXRhIDwtIHJiaW5kKGRhdGExLCBkYXRhMikNCmRpbShmdWxsX2RhdGEpDQpgYGANCg0KYGBge3Igd2FybmluZz1GQUxTRSwgIHNpemUgPSAic21hbGwifQ0KcHJpbnQoZnVsbF9kYXRhWzE6MywgMToxM10pDQpgYGANCg0KIyMjIERhdGEgQ2xlYW5pbmcNCg0KKipSZW5hbWluZyBhbmQgZHJvcHBpbmcgdW53YW50ZWQgY29sdW1ucyoqDQoNCldlIGFyZSBvbmx5IHVzaW5nIHRoZSB1bmFkanVzdGVkIHZlcnNpb24gb2YgdGhlIGNvbHVtbnMuLi4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgIHNpemUgPSAic21hbGwifQ0KbGlicmFyeShkcGx5cikNCnNlbGVjdF9hbmRfcmVuYW1lX3YyIDwtIGZ1bmN0aW9uKGRmKSB7ICMgY2xlYW5pbmcgcGlwZWxpbmUNCiAgc2VsZWN0ZWRfZGYgPC0gZGZbLCBjKDEsIDIsIDMsIDQsIDEyLCAyMCldICMgU2VsZWN0IGNvbHVtbnMgMSwgMiwgMywgNCwgMTIsIGFuZCAyMA0KICAjIFJlbmFtZSB0aGUgY29sdW1ucyB3aXRoIGRlc2lyZWQgbmFtZXMNCiAgbmFtZXMoc2VsZWN0ZWRfZGYpIDwtIGMoIkVjb25vbWljIGFjdGl2aXR5IiwgIlllYXIiLCAiUXVhcnRlciIsICJFbXBsb3ltZW50IGluZGV4IiwgIkhvdXJzIHdvcmtlZCBpbmRleCIsICJzYWxhcmllcyBpbmRleCIpDQogICMgUmVvcmRlcmluZw0KICBzZWxlY3RlZF9kZiA9IHNlbGVjdGVkX2RmW2MoIlllYXIiLCAiUXVhcnRlciIsICJFbXBsb3ltZW50IGluZGV4IiwgIkhvdXJzIHdvcmtlZCBpbmRleCIsICJzYWxhcmllcyBpbmRleCIsICJFY29ub21pYyBhY3Rpdml0eSIpXQ0KICByZXR1cm4oc2VsZWN0ZWRfZGYpIH0NCmNsZWFuX2Z1bGxfZGF0YSA9IHNlbGVjdF9hbmRfcmVuYW1lX3YyKGZ1bGxfZGF0YSkNCmhlYWQoY2xlYW5fZnVsbF9kYXRhLCAzKQ0KYGBgDQoNCioqRml4aW5nIHdyb25nIGVudHJpZXMqKg0KDQpTb21lIGNvbW1lbnQgd2hlcmUgbWFkZSBieSB0dWlrIGFuZCB0aGF0IGNhdXNlZCBzb21lIGVudHJpZXMgaW4gdGhlIFllYXIgY29sdW1uIHRvIGhhdmUgbGV0dGVycyBzbyB3ZSBhcmUgZml4aW5nIHRoZW0gc28gdGhhdCB0aGUgY29sdW1uIGNhbiBiZSBvZiB0eXBlIGludC4uLg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCAgc2l6ZSA9ICJzbWFsbCJ9DQpjbGVhbl9mdWxsX2RhdGFbNTMsXQ0KYGBgDQoNCmBgYHtyIHdhcm5pbmc9RkFMU0V9DQpjbGVhbl9mdWxsX2RhdGEkWWVhciA8LSBnc3ViKCJcXChyXFwpIiwgIiIsIGNsZWFuX2Z1bGxfZGF0YSRZZWFyKSAjIHVzaW5nIHJlZ3VsYXIgZXhwcmVzc2lvbnMgdG8gZml4IHdyb25nIGVudHJpZXMNCmNsZWFuX2Z1bGxfZGF0YSRZZWFyIDwtIGFzLm51bWVyaWMoY2xlYW5fZnVsbF9kYXRhJFllYXIpDQpjbGVhbl9mdWxsX2RhdGFbNTMsXQ0KYGBgDQoNCioqRmlsbGluZyBOQSdzKioNCg0Kc2VlaW5nIHdoYXQgY29sdW1uIGhhdmUgTkEgdmFsdWVzLi4NCg0KYGBge3Igd2FybmluZz1GQUxTRSwgIHNpemUgPSAic21hbGwifQ0KY29sU3Vtcyhpcy5uYShjbGVhbl9mdWxsX2RhdGEpKQ0KYGBgDQoNCipZZWFyIGFuZCBRdWFydGVyIGNvbHVtbnM6IHRoZSB5ZWFyIGNvbHVtbiBoYXMgZW1wdHkgZW50cmllcyB0aGF0IGluZGljYXRlIHRoZSB2YWx1ZSBvZiB0aGUgbGFzdCBub24gZW1wdHkgYW5kIGJlY2F1c2Ugd2Ugc3RhY2tlZCBhbGwgdGhlIHRhYmxlcy4gRm9yIHRob3NlIGVtcHR5IGVudHJpZXMgd2Ugd2lsbCBiZSB1c2luZyBhIGZ1bmN0aW9uIHRoYXQgcmVwbGFjZXMgdGhlIE5BcyB3aXRoIHRoZSBsYXN0IG5vbi1OQSB2YWx1ZS4gV2UgYXJlIG1ha2luZyBhIGZ1bmN0aW9uIHBpcGVsaW5lIHRoYXQgcmVwbGFjZXMgcXVhcnRlciB3aXRoIGEgbnVtYmVyIGFuZCBtZXJnZXMgYm90aCBjb2x1bW5zIGludG8gb25lIHNvIGl0IGJlY29tZXMgb3VyIG9ubHkgdGltZSBzZXJpZXMgY29sdW1uLioNCg0KYGBge3Igd2FybmluZz1GQUxTRSwgIHNpemUgPSAic21hbGwifQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoem9vKQ0Kb25lX2NvbHVtbl9mdW5jdGlvbiA8LSBmdW5jdGlvbihkZikgew0KICBxdWFydGVyX21hcHBpbmcgPC0gYygiSSIgPSAxLCAiSUkiID0gMiwgIklJSSIgPSAzLCAiSVYiID0gNCkNCiAgZGYgPC0gZGYgfD4NCiAgICBtdXRhdGUoDQogICAgICBZZWFyID0gbmEubG9jZihkZiRZZWFyLCBuYS5ybSA9IEZBTFNFKSwNCiAgICAgIFllYXJfUXVhcnRlciA9IGFzLm51bWVyaWMoYXMubnVtZXJpYyhZZWFyKSArIChxdWFydGVyX21hcHBpbmdbUXVhcnRlcl0gLSAxKSAvIDQNCiAgICApKSB8Pg0KICAgIHNlbGVjdCgtWWVhciwgLVF1YXJ0ZXIpDQogIHJldHVybihkZikgfQ0KY2xlYW5fZnVsbF9kYXRhID0gb25lX2NvbHVtbl9mdW5jdGlvbihjbGVhbl9mdWxsX2RhdGEpDQpoZWFkKGNsZWFuX2Z1bGxfZGF0YSwzKQ0KYGBgDQoNCipFY29ub21pYyBBY3Rpdml0eSBDb2x1bW46IGhhcyBlbXB0eSBlbnRyaWVzIGZvciB0aGUgc2FtZSByZWFzb24gYWJvdmUgYnV0IGluIHRoaXMgY2FzZSBlYWNoIHRhYmxlIGZyb20gdGhlIHRhYmxlcyBzdGFja2VkIGhhcyBhIGRpZmZlcmVudCB2YWx1ZSBmb3IgdGhhdCB3ZSBhcmUgbWFraW5nIGEgd2luZG93IGFuZCBpdGVyYXRpbmcgdGhyb3VnaCBlYWNoIHdpbmRvdyB0byBmaWxsIHRoZSBOQSdzIHdpdGggdGhlIGZpcnN0IG5vbi1OQSB2YWx1ZSBpbiB0aGF0IHdpbmRvdyAoZWFjaCB0YWJsZSBmcm9tIHRoZSBzdGFja2VkIHRhYmxlcyBoYXMgNjAgcm93cyB0aHVzIHRoZSAvNjApLiBBZnRlciB0aGF0IHdlIG1hZGUgdGhlIGNvbHVtbiBvZiB0eXBlIGZhY3Rvci4uLioNCg0KYGBge3Igd2FybmluZz1GQUxTRSwgIHNpemUgPSAic21hbGwifQ0KbGlicmFyeShkcGx5cikNCmZpbGxfcGF0dGVybiA8LSBmdW5jdGlvbihkZikgew0KICBuX3Jvd3MgPC0gbnJvdyhkZikNCiAgbnVtX3dpbmRvd3MgPC0gY2VpbGluZyhuX3Jvd3MgLyA2MCkNCiAgZm9yIChpIGluIDE6bnVtX3dpbmRvd3MpIHsgIyBJdGVyYXRlIG92ZXIgZWFjaCB3aW5kb3cNCiAgICBzdGFydF9pbmRleCA8LSAoaSAtIDEpICogNjAgKyAxICMgRGV0ZXJtaW5lIHRoZSBzdGFydCBhbmQgZW5kIGluZGljZXMgb2YgdGhlIGN1cnJlbnQgd2luZG93DQogICAgZW5kX2luZGV4IDwtIG1pbihpICogNjAsIG5fcm93cykNCiAgICBkZltzdGFydF9pbmRleDplbmRfaW5kZXgsICJFY29ub21pYyBhY3Rpdml0eSJdIDwtIGRmW3N0YXJ0X2luZGV4KzEsICJFY29ub21pYyBhY3Rpdml0eSJdICMgRmlsbCB0aGUgY3VycmVudCB3aW5kb3cgd2l0aCB0aGUgc2Vjb25kIHJvdyB2YWx1ZQ0KICB9DQogIHJldHVybihkZil9DQpjbGVhbl9mdWxsX2RhdGEgPSBmaWxsX3BhdHRlcm4oY2xlYW5fZnVsbF9kYXRhKQ0KaGVhZChjbGVhbl9mdWxsX2RhdGEsMykNCmBgYA0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCAgc2l6ZSA9ICJzbWFsbCJ9DQpjbGVhbl9mdWxsX2RhdGEkYEVjb25vbWljIGFjdGl2aXR5YCA9IGZhY3RvcihjbGVhbl9mdWxsX2RhdGEkYEVjb25vbWljIGFjdGl2aXR5YCkNCmBgYA0KDQoqZml4aW5nIG5hbWVzOiBiZWNhdXNlIHNvbWUgbmFtZXMgaGF2ZSBzb21lIHNwZWNpYWwgVHVya2lzaCBjaGFyYWN0ZXJzIGFuZCBjaGFuZ2luZyB0aGVtIHdvdWxkIG1lYW4gbm90IHJ1bm5pbmcgaW50byB1bmV4cGVjdGVkIGVycm9ycyB3aGVuIHRyYWluaW5nKiBcdGlueQ0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCAgc2l6ZSA9ICJzbWFsbCJ9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShmb3JjYXRzKQ0KY2xlYW5fZnVsbF9kYXRhIDwtIGNsZWFuX2Z1bGxfZGF0YSB8PiAjIGNoYW5naW5nIG5hbWUgd2l0aCBmb3JjYXRzDQogIG11dGF0ZShgRWNvbm9taWMgYWN0aXZpdHlgID0gZmN0X3JlY29kZShgRWNvbm9taWMgYWN0aXZpdHlgLCAiSW50ZXJtZWRpYXRlIGdvb2RzIiA9ICJJRy1JbnRlcm1lZGlhdGUgZ29vZHMiLA0KICAgICAgICAgICJEdXJhYmxlIGNvbnN1bWVyIGdvb2RzIiA9ICJEQ0ctRHVyYWJsZSBjb25zdW1lciBnb29kcyIsIk5vbi1kdXJhYmxlIGNvbnN1bWVyIGdvb2RzIiA9ICJORENHLU5vbi1kdXJhYmxlIGNvbnN1bWVyIGdvb2RzIiwNCiAgICAgICAgICAiRW5lcmd5IiA9ICJOUkctRW5lcmd5IiwiQ2FwaXRhbCBnb29kcyIgPSAiQ0ctQ2FwaXRhbCBnb29kcyIsDQogICAgICAgICAgIk1pbmluZyBhbmQgcXVhcnJ5aW5nIiA9ICJNYWRlbmNpbGlrIHZlIHRhxZ8gb2Nha8OnxLFsxLHEn8SxIiwiTWFudWZhY3R1cmluZyIgPSAixLBtYWxhdCIsDQogICAgICAgICAgIkVsZWN0cmljaXR5LCBnYXMsIHN0ZWFtIGFuZCBhaXIgY29uZGl0aW9uaW5nIHN1cHBseSIgPSAiRWxla3RyaWssIGdheiwgYnVoYXIgdmUgaWtsaW1sZW5kaXJtZSIsDQogICAgICAgICAgIldhdGVyIHN1cHBseSwgc2V3ZXJhZ2UsIHdhc3RlIG1hbmFnZW1lbnQgYW5kIHJlbWVkaWF0aW9uIGFjdGl2aXRpZXMiID0gIlN1IHRlbWluaTsga2FuYWxpemFzeW9uLCBhdMSxayB5w7ZuZXRpbWkiLA0KICAgICAgICAgICJDb25zdHJ1Y3Rpb24iID0gIsSwbsWfYWF0IiwiV2hvbGVzYWxlIGFuZCByZXRhaWwgdHJhZGUiID0gIlRvcHRhbiB2ZSBwZXJha2VuZGUgdGljYXJldDsiLA0KICAgICAgICAgICJUcmFuc3BvcnRhdGlvbiBhbmQgc3RvcmFnZSIgPSAiVWxhxZ90xLFybWEgdmUgZGVwb2xhbWEiLCJBY2NvbW1vZGF0aW9uIGFuZCBmb29kIHNlcnZpY2UgYWN0aXZpdGllcyIgPSAiS29uYWtsYW1hIHZlIHlpeWVjZWsgaGl6bWV0aSIsDQogICAgICAgICAgIkluZm9ybWF0aW9uIGFuZCBjb21tdW5pY2F0aW9uIiA9ICJCaWxnaSB2ZSBpbGV0acWfaW0iLCJGaW5hbmNpYWwgYW5kIGluc3VyYW5jZSBhY3Rpdml0aWVzIiA9ICJGaW5hbnMgdmUgc2lnb3J0YSBmYWFsaXlldGxlcmkiLA0KICAgICAgICAgICJSZWFsIGVzdGF0ZSBhY3Rpdml0aWVzIiA9ICJHYXlyaW1lbmt1bCBmYWFsaXlldGxlcmkiLA0KICAgICAgICAgICJQcm9mZXNzaW9uYWwsIHNjaWVudGlmaWMgYW5kIHRlY2huaWNhbCBhY3Rpdml0aWVzIiA9ICJNZXNsZWtpLCBiaWxpbXNlbCB2ZSB0ZWtuaWsgZmFhbGl5ZXRsZXIiLA0KICAgICAgICAgICJBZG1pbmlzdHJhdGl2ZSBhbmQgc3VwcG9ydCBzZXJ2aWNlIGFjdGl2aXRpZXMiID0gIsSwZGFyaSB2ZSBkZXN0ZWsgaGl6bWV0IGZhYWxpeWV0bGVyaSIpKQ0KaGVhZChsZXZlbHMoY2xlYW5fZnVsbF9kYXRhJGBFY29ub21pYyBhY3Rpdml0eWApLDMpDQpgYGANCg0KKipGaW5hbCBzdHJ1Y3R1cmUqKg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCAgc2l6ZSA9ICJzbWFsbCJ9DQpzdHIoY2xlYW5fZnVsbF9kYXRhKQ0KZGltKGNsZWFuX2Z1bGxfZGF0YSkNCmBgYA0KDQojIyMgVmlzdWFsaXphdGlvbnMNCg0KLSAgIGNvcnJlbGF0aW9uIGhlYXRtYXAgYmV0d2VlbiBhbGwgdmFyaWFibGVzDQoNCmBgYHtyIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTYsICBzaXplID0gInNtYWxsIn0NCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkocmVzaGFwZTIpDQpkYXRhIDwtIGNvcihjbGVhbl9mdWxsX2RhdGFbc2FwcGx5KGNsZWFuX2Z1bGxfZGF0YSwgaXMubnVtZXJpYyldKSAjIENhbGN1bGF0aW5nIGNvcnJlbGF0aW9uIG1hdHJpeA0KZGF0YTEgPC0gbWVsdChkYXRhKSAjIFJlc2hhcGluZyBkYXRhDQpwIDwtIGdncGxvdChkYXRhMSwgYWVzKFZhcjEsIFZhcjIsIGZpbGwgPSB2YWx1ZSkpICsgDQogIGdlb21fdGlsZSgpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKHZhbHVlLCA4KSksIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDMpICsNCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAid2hpdGUiLCBoaWdoID0gInB1cnBsZSIpICsgIyBDb2xvciBncmFkaWVudCBmb3IgaGVhdG1hcA0KICB0aGVtZV9taW5pbWFsKCkgKyAgDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksICMgUm90YXRpbmcgeC1heGlzIGxhYmVscyBmb3IgYmV0dGVyIHJlYWRhYmlsaXR5DQogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwgIyBSZW1vdmluZyBheGVzIHRpdGxlcw0KICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCkpDQpwcmludChwKQ0KYGBgDQoNClRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSB0YXJnZXQgYW5kIHRoZSBmZWF0dXJlcyBpcyBub3QgYmFkIGJ1dCB0aGUgYmlnZ2VzdCBtYXliZSBwcm9ibGVtIGhlYXIgaXMgdGhlIGZlYXR1cmUgY28tbGluZWFyaXR5LCBhbHRob3VnaCB0aGF0IGlzIGEgYmFkIHRoaW5nIHdlIGFyZSBub3QgZ29pbmcgdG8gZHJvcCBhbnkgZmVhdHVyZSBiZWNhdXNlIHdlIGRvbid0IGhhdmUgYSBsb3Qgb2YgZmVhdHVyZXMgdG8gYmVnaW4gd2l0aC4NCg0KLSAgIFZpc3VhbGl6aW5nIHRyZW5kIG92ZXIgdGltZQ0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD02LCAgc2l6ZSA9ICJzbWFsbCJ9DQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KIyBSZXNoYXBpbmcgZGF0YSBpbnRvIGxvbmcgZm9ybWF0LCBleGNsdWRpbmcgRWNvbm9taWNfYWN0aXZpdHkgYW5kIFllYXJfUXVhcnRlciBjb2x1bW5zDQpkZl9sb25nIDwtIGdhdGhlcihjbGVhbl9mdWxsX2RhdGEsIGtleSA9ICJWYXJpYWJsZSIsIHZhbHVlID0gIlZhbHVlIiwgLVllYXJfUXVhcnRlciwgLWBFY29ub21pYyBhY3Rpdml0eWApDQojIFBsb3RpbmcgbGluZSBwbG90cyBmb3IgZWFjaCB2YXJpYWJsZSwgdXNpbmcgRWNvbm9taWNfYWN0aXZpdHkgYXMgaHVlDQpnZ3Bsb3QoZGZfbG9uZywgYWVzKHggPSBZZWFyX1F1YXJ0ZXIsIHkgPSBWYWx1ZSwgY29sb3IgPSBgRWNvbm9taWMgYWN0aXZpdHlgLCBncm91cCA9IGBFY29ub21pYyBhY3Rpdml0eWApKSArDQogIGdlb21fbGluZSgpICsNCiAgZmFjZXRfd3JhcCh+IFZhcmlhYmxlLCBzY2FsZXMgPSAiZnJlZV95IiwgbnJvdyA9IDEpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKw0KICBndWlkZXMoY29sb3IgPSBGQUxTRSkgICMgUmVtb3ZpbmcgdGhlIGxlZ2VuZA0KYGBgDQoNCldlIGNhbiBzZWUgdGhlIGNvLWxpbmVhcml0eSBiZXR3ZWVuIHRoZSBwcmV2aW91cyB0d28gZmVhdHVyZXMgYnV0IG5vdGljZSBob3cgdGhlIHRyZW5kcyBhcmUgYSBiaXQgZGlmZmVyZW50IHdoaWNoIGlzIGluZm9ybWF0aW9uIHdlIG9idGFpbiBieSBrZWVwaW5nIHRoZSBmZWF0dXJlIGFuZCB0cmFpbmluZyBvbiB0aGUgd2hvbGUgZGF0YSBzZXQuDQoNCiMjIyBEYXRhIFByZS1wcm9jZXNzaW5nDQoNCioqRmVhdHVyZSBTZWxlY3Rpb24qKg0KDQotICAgd2UgYXJlIGdvbm5hIHVzZSBhbGwgdGhlIGZlYXR1cmVzIGRlc3BpdGUgdHdvIGJlaW5nIGhpZ2hseSBjb3JyZWxhdGVkLiBXZSB3aWxsIGxhdGVyIHVzZSBvdGhlciBtZXRob2RzIHRvIGVuc3VyZSBnb29kIGFjY3VyYWN5Li4uDQoNCmBgYHtyIHdhcm5pbmc9RkFMU0UsICBzaXplID0gInNtYWxsIn0NCmRhdGEgPC0gY2xlYW5fZnVsbF9kYXRhDQpgYGANCg0KKipFbmNvZGluZyoqDQoNCmVuY29kaW5nIGlzIHJlY29tbWVuZGVkIHNvIHRoYXQgdGhlIGFsZ29yaXRobSBkb2Vzbid0IGhhdmUgdG8gZGVhbCB3aXRoIGNoYXJhY3RlcnMsIHdlIGFyZSB1c2luZyBvbmUtaG90IGVuY29kaW5nIGJlY2F1c2UgdGhlIGNhdGVnb3JpY2FsIGNvbHVtbiBpcyBub3Qgb3JkaW5hbCBidXQgbm9taW5hbC4NCg0KYGBge3Igd2FybmluZz1GQUxTRSwgIHNpemUgPSAic21hbGwifQ0KbGlicmFyeShmYXN0RHVtbWllcykNCmRhdGEgPC0gZHVtbXlfY29scyhkYXRhLCBzZWxlY3RfY29sdW1ucyA9ICJFY29ub21pYyBhY3Rpdml0eSIpDQpkYXRhIDwtIHN1YnNldChkYXRhLCBzZWxlY3QgPSAtYyhgRWNvbm9taWMgYWN0aXZpdHlgKSkNCnRhaWwoY29sbmFtZXMoZGF0YSksMykNCmBgYA0KDQoqKlJlbmFtaW5nICJzYWxhcmllcyBpbmRleCIgdG8geSBmb3IgZWFzaWVyIGFuZCBzaG9ydGVyIGNvZGUqKg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCAgc2l6ZSA9ICJzbWFsbCJ9DQpuYW1lcyhkYXRhKVtuYW1lcyhkYXRhKSA9PSAic2FsYXJpZXMgaW5kZXgiXSA8LSAieSINCmhlYWQoZGF0YSR5LDMpDQpgYGANCg0KKipUcmFpbiBUZXN0IFNwbGl0KioNCg0KV2Ugd2lsbCBiZSBkb2luZyBhbiA4MCUgdHJhaW5pbmcsIDEwJSB2YWxpZGF0aW9uLCAxMCUgdGVzdGluZyByYXRpbyBmb3IgbW9zdCBtb2RlbHMgdGh1cyB0aGUgY29kZSBiZWxvdy4uLg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCAgc2l6ZSA9ICJzbWFsbCJ9DQpsaWJyYXJ5KGNhcmV0KQ0Kc2V0LnNlZWQoNDUpICMgU2V0dGluZyB0aGUgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5DQp0cmFpbl9pbmRpY2VzIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0YSR5LCBwID0gMC45LCBsaXN0ID0gRkFMU0UpICMgU3BsaXR0aW5nIHRoZSBkYXRhc2V0IGludG8gOTAlIHRyYWluaW5nIGFuZCAxMCUgdGVzdA0KdHJhaW5fZGF0YSA8LSBkYXRhW3RyYWluX2luZGljZXMsIF0NCnRlc3RfZGF0YSA8LSBkYXRhWy10cmFpbl9pbmRpY2VzLCBdDQpjYXQoIlRyYWluaW5nIGRhdGEgZGltZW5zaW9uczoiLCBkaW0odHJhaW5fZGF0YSksICJcbiIpDQpjYXQoIlRlc3QgZGF0YSBkaW1lbnNpb25zOiIsIGRpbSh0ZXN0X2RhdGEpLCAiXG4iKQ0KYGBgDQoNCioqU2NhbGluZyoqDQoNCmJlY2F1c2Ugc2NhbGluZyBpcyByZWNvbW1lbmRlZCBmb3IgbW9zdCBtb2RlbHMgd2UgYXJlIGFwcGx5aW5nIGl0IGJ1dCBmb3IgYW55IG90aGVyIGNhc2Ugd2Ugd2lsbCBmaXJzdCBzYXZlIHRoZSB1bnNjYWxlZCB2ZXJzaW9uLi4uDQoNCmBgYHtyIHdhcm5pbmc9RkFMU0UsICBzaXplID0gInNtYWxsIn0NCnVuc2NhbGVkX3RyYWluX2RhdGEgPSB0cmFpbl9kYXRhICMgU2F2aW5nIHVuc2NhbGVkIHZlcnNpb24NCnVuc2NhbGVkX3Rlc3RfZGF0YSA9IHRlc3RfZGF0YQ0KbGlicmFyeShjYXJldCkNCnNldC5zZWVkKDQ1KQ0KY29sdW1uc190b19ub3JtYWxpemUgPC0gYygiRW1wbG95bWVudCBpbmRleCIsICJIb3VycyB3b3JrZWQgaW5kZXgiLCAieSIsICJZZWFyX1F1YXJ0ZXIiKQ0KcHJlcHJvY2VzcyA8LSBwcmVQcm9jZXNzKHRyYWluX2RhdGFbLCBjb2x1bW5zX3RvX25vcm1hbGl6ZV0sIG1ldGhvZCA9IGMoImNlbnRlciIsICJzY2FsZSIpKSMgUHJlLXByb2Nlc3NpbmcgcGlwZWxpbmUgdG8gKm5vcm1hbGl6ZSogdGhlIHNlbGVjdGVkIGNvbHVtbnMNCm5vcm1fdHJhaW5fZGF0YSA8LSBwcmVkaWN0KHByZXByb2Nlc3MsIG5ld2RhdGEgPSB0cmFpbl9kYXRhKSMgQXBwbHlpbmcgdGhlIHBpcGVsaW5lIHRvIHRoZSB0cmFpbmluZyBkYXRhDQpub3JtX3Rlc3RfZGF0YSA8LSBwcmVkaWN0KHByZXByb2Nlc3MsIG5ld2RhdGEgPSB0ZXN0X2RhdGEpIyBBcHBseWluZyB0aGUgc2FtZSBwaXBlbGluZSB0byB0aGUgdGVzdCBkYXRhDQpwcmludChub3JtX3RyYWluX2RhdGFbMTozLCAxOjRdKQ0KYGBgDQoNCioqU3BlY2lmeWluZyBYICYgWSoqDQoNCkFzc2lnbmluZyBYIGFuZCBZIGZvciB1cGNvbWluZyB0ZXN0aW5nLi4uDQoNCmBgYHtyIHNpemUgPSAic21hbGwifQ0KbGlicmFyeShkcGx5cikNCiMgVW5zY2FsZWQgdHJhaW4gJiB0ZXN0LCB5ICYgeA0KdW5zY2FsZWRfdHJhaW5feCA9IHNlbGVjdCh1bnNjYWxlZF90cmFpbl9kYXRhLCAteSkNCnVuc2NhbGVkX3RyYWluX3kgPSBhcy5udW1lcmljKHVuc2NhbGVkX3RyYWluX2RhdGFbWyJ5Il1dKQ0KdW5zY2FsZWRfdGVzdF94ID0gc2VsZWN0KHVuc2NhbGVkX3Rlc3RfZGF0YSwgLXkpDQp1bnNjYWxlZF90ZXN0X3kgPSBhcy5udW1lcmljKHVuc2NhbGVkX3Rlc3RfZGF0YVtbInkiXV0pDQojIFNjYWxlZCB0cmFpbiAmIHRlc3QsIHkgJiB4DQp0cmFpbl94ID0gc2VsZWN0KG5vcm1fdHJhaW5fZGF0YSwgLXkpDQp0cmFpbl95ID0gYXMubnVtZXJpYyhub3JtX3RyYWluX2RhdGFbWyJ5Il1dKQ0KdGVzdF94ID0gc2VsZWN0KG5vcm1fdGVzdF9kYXRhLCAteSkNCnRlc3RfeSA9IGFzLm51bWVyaWMobm9ybV90ZXN0X2RhdGFbWyJ5Il1dKQ0KYGBgDQoNCiMjIyBNb2RlbHMNCg0KKipUZXN0aW5nIHNjb3JlIGZ1bmN0aW9uKioNCg0KV2UgYXJlIGdvaW5nIHRvIHVzZSB0aGUgbm9ybWFsaXplZCB2ZXJzaW9uIG9mIFJNU0UgYXMgdGhlIG5vcm1hbCBSTVNFIGRvZXMgbm90IHRha2UgaW50byBjb25zaWRlcmF0aW9uIHRoZSBlcnJvciBhY2NvcmRpbmcgdG8gdGhlIHJhbmdlIG9mIG91ciB0YXJnZXQgdmFyaWFibGUgYW5kIHdlIHdvdWxkIGhhdmUgdG8gYW5hbHl6ZSB0aGUgcmFuZ2UgdG8gZGV0ZXJtaW5lIGlmIHRoZSBzY29yZSBpcyBnb29kIG9yIG5vdC4gTm9ybWFsaXplZCBSTVNFIHRha2VzIGludG8gY29uc2lkZXJhdGlvbiBvdXIgWSByYW5nZSBhbmQgb3V0cHV0cyBhIGRlY2ltYWwgYmV0d2VlbiAwLTEgYW5kIHRoZSBjbG9zZXIgaXQgaXMgdG8gMCB0aGUgYmV0dGVyIHRoZSBtb2RlbCBpcy4gSGVyZSdzIGhvdyB3ZSBpbXBsZW1lbnRlZCBpdC4uLg0KDQpgYGB7ciBzaXplID0gInNtYWxsIn0NCm5vcm1hbGl6ZWRfcm1zZSA9IGZ1bmN0aW9uKHByZWQsIHJlYWwpeyAjTm9ybWFsaXplZCBSb290IE1lYW4gU3F1YXJlZCBFcnJvcg0KICBlcnJvciA9IHJlYWwgLSBwcmVkDQogIHNxcnQobWVhbihlcnJvcl4yKSkvKG1heChyZWFsKSAtIG1pbihyZWFsKSkgfQ0KYGBgDQoNCiMjIyMgTUxSOiBNdWx0aXBsZSBMaW5lYXIgUmVncmVzc2lvbg0KDQotICAgKipUcmFpbmluZyBhbmQgdGVzdGluZy4uLioqDQoNCmBgYHtyIHNpemUgPSAic21hbGwifQ0Kc2V0LnNlZWQoMTIzKQ0KbWxyX21vZGVsIDwtIGxtKHkgfiAuLCBkYXRhID0gbm9ybV90cmFpbl9kYXRhKSAjIFRyYWluaW5nLi4uDQptbHJfcHJlZGljdGlvbnMgPC0gcHJlZGljdChtbHJfbW9kZWwsIG5ld2RhdGEgPSB0ZXN0X3gpICAjIFRlc3RpbmcuLi4NCm1scl9ybXNlIDwtIG5vcm1hbGl6ZWRfcm1zZShtbHJfcHJlZGljdGlvbnMsIHRlc3RfeSkNCnByaW50KG1scl9ybXNlKQ0KYGBgDQoNCiMjIyMgUmFuZG9tIEZvcmVzdA0KDQotICAgKipUcmFpbmluZy4uLioqDQoNCmBgYHtyIHNpemUgPSAic21hbGwifQ0KbGlicmFyeShjYXJldCkNCnNldC5zZWVkKDEyMykNCnRyYWluX2NvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDUpICMgUGVyZm9ybWluZyBjcm9zcyB2YWxpZGF0aW9uDQpyZl9tb2RlbCA8LSB0cmFpbiggIyBUcmFpbmluZyB0aGUgUmFuZG9tIEZvcmVzdCBtb2RlbA0KICB5IH4gLiwgDQogIGRhdGEgPSBub3JtX3RyYWluX2RhdGEsIA0KICBtZXRob2QgPSAicmYiLA0KICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLA0KICBudHJlZSA9IDUwMCkgICMgTnVtYmVyIG9mIHRyZWVzIGluIHRoZSBmb3Jlc3QNCnByaW50KHJmX21vZGVsKSAjIFByaW50aW5nIG1ldHJpY3Mgc2NvcmVzDQpgYGANCg0KLSAgICoqVGVzdGluZy4uLioqDQoNCmBgYHtyIHNpemUgPSAic21hbGwifQ0Kc2V0LnNlZWQoMTIzKQ0KcmZfcHJlZGljdGlvbnMgPC0gcHJlZGljdChyZl9tb2RlbCwgbmV3ZGF0YSA9IG5vcm1fdGVzdF9kYXRhKQ0KcmZfcm1zZSA9IG5vcm1hbGl6ZWRfcm1zZShyZl9wcmVkaWN0aW9ucywgdGVzdF95KQ0KcHJpbnQocmZfcm1zZSkNCmBgYA0KDQojIyMjIERlY2lzaW9uIFRyZWUNCg0KLSAgICoqVHJhaW5pbmcuLi4qKg0KDQpgYGB7ciBzaXplID0gInNtYWxsIn0NCmxpYnJhcnkocnBhcnQpDQpzZXQuc2VlZCgxMjMpDQp0cmVlX21vZGVsIDwtIHJwYXJ0KA0KICBmb3JtdWxhID0geSB+IC4sDQogIGRhdGEgPSBub3JtX3RyYWluX2RhdGEsIA0KICBtZXRob2QgPSAiYW5vdmEiLCAjICJhbm92YSIgdG8gc3BlY2lmeSByZWdyZXNzaW9uDQogIGNvbnRyb2wgPSBycGFydC5jb250cm9sKGNwID0gMC4wMSkpICAjIENvbnRyb2wgcGFyYW1ldGVycyAoY29tcGxleGl0eSBwYXJhbWV0ZXIpDQpgYGANCg0KLSAgICoqVGVzdGluZy4uLioqDQoNCmBgYHtyIHNpemUgPSAic21hbGwifQ0Kc2V0LnNlZWQoMTIzKQ0KZHRfcHJlZGljdGlvbnMgPC0gcHJlZGljdCh0cmVlX21vZGVsLCBuZXdkYXRhID0gbm9ybV90ZXN0X2RhdGEpDQpkdF9ybXNlID0gbm9ybWFsaXplZF9ybXNlKGR0X3ByZWRpY3Rpb25zLCB0ZXN0X3kpIA0KcHJpbnQoZHRfcm1zZSkNCmBgYA0KDQojIyMjIEtOTjogSy1OZWFyZXN0IE5laWdoYm9yDQoNCi0gICAqKlRyYWluaW5nLi4uKioNCg0KYGBge3Igc2l6ZSA9ICJzbWFsbCJ9DQpsaWJyYXJ5KGNhcmV0KQ0Kc2V0LnNlZWQoMTIzKQ0Ka25uX21vZGVsIDwtIHRyYWluKCAjIFRyYWluaW5nIHRoZSBLTk4gcmVncmVzc2lvbiBtb2RlbA0KICB4ID0gdHJhaW5feCwgICAgICANCiAgeSA9IHRyYWluX3ksICAgICAgDQogIG1ldGhvZCA9ICJrbm4iLCAgICANCiAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDUpLCAjIENyb3NzLXZhbGlkYXRpb24gc2V0dGluZ3MNCiAgdHVuZUdyaWQgPSBleHBhbmQuZ3JpZChrID0gYygxLCAzLCA1KSkpICAjIEh5cGVycGFyYW1ldGVyIGdyaWQgKGspDQpgYGANCg0KLSAgICoqVGVzdGluZy4uLioqDQoNCmBgYHtyIHNpemUgPSAic21hbGwifQ0Kc2V0LnNlZWQoMTIzKQ0Ka25uX3ByZWRpY3Rpb25zIDwtIHByZWRpY3Qoa25uX21vZGVsLCBuZXdkYXRhID0gdGVzdF94KQ0Ka25uX3Jtc2UgPSBub3JtYWxpemVkX3Jtc2Uoa25uX3ByZWRpY3Rpb25zLCB0ZXN0X3kpDQpwcmludChrbm5fcm1zZSkNCmBgYA0KDQojIyMjIFNWTSBSZWdyZXNzaW9uDQoNCi0gICBOb3cgd2Ugd2lsbCBiZSB0cnlpbmcgdGhlIHJlZ3Jlc3Npb24gdmVyc2lvbiBvZiB0aGUgc3VwcG9ydCB2ZWN0b3IgbWFjaGluZXMgd2Ugd2lsbCBiZSBpbXBsZW1lbnRpbmcgZ3JpZCBzZWFyY2ggZm9yIGh5cGVyLXBhcmFtZXRlciB0dW5pbmcuLi4NCg0KLSAgICoqVHJhaW5pbmcgd2l0aCBoeXBlci1wYXJhbWV0ZXIgdHVuaW5nLi4uKioNCg0KYGBge3Igd2FybmluZz1GQUxTRSwgIHNpemUgPSAic21hbGwifQ0KbGlicmFyeShlMTA3MSkNCnNldC5zZWVkKDQ1KQ0KI1R1bmluZyB0aGUgU1ZNIG1vZGVsDQp0dW5lZF9zdm09dHVuZShzdm0sIHl+LiwgZGF0YT1ub3JtX3RyYWluX2RhdGEsIHJhbmdlcz1saXN0KGVwc2lsb249c2VxKDAsMSwwLjEpLCBjb3N0PWMoMC4wMSwgMC4xLCAxLCAxMCwgMTAwKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2VybmFsID0gYygibGluZWFyIiwgInBvbHlub21pYWwiLCAicmFkaWFsIGJhc2lzIiwgInNpZ21vaWQiKSkpDQpwcmludCh0dW5lZF9zdm0pICMgcGVyZm9ybWFuY2UgbWVhc3VyZSA9IE1TRQ0KYGBgDQoNCi0gICAqKlRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIGFjY3VyYWN5KioNCg0KYGBge3Igc2l6ZSA9ICJzbWFsbCJ9DQpiZXN0X3R1bmVkX3N2bSA9IHR1bmVkX3N2bSRiZXN0Lm1vZGVsDQpzdm1fdHJhaW5fZXJyID0gbm9ybWFsaXplZF9ybXNlKHByZWRpY3QoYmVzdF90dW5lZF9zdm0sIHRyYWluX3gpLCB0cmFpbl95KQ0KcHJpbnQoc3ZtX3RyYWluX2VycikNCmBgYA0KDQotICAgKipUZXN0aW5nLi4uKioNCg0KYGBge3Igc2l6ZSA9ICJzbWFsbCJ9DQpzdm1fcHJlZGljdGlvbnMgPSBwcmVkaWN0KGJlc3RfdHVuZWRfc3ZtLCB0ZXN0X3gpDQpzdm1fcm1zZSA9IG5vcm1hbGl6ZWRfcm1zZShzdm1fcHJlZGljdGlvbnMsIHRlc3RfeSkNCnByaW50KHN2bV9ybXNlKQ0KYGBgDQoNCiMjIyMgQUxMIElOIE9ORTogQmxlbmRlcg0KDQotICAgSW4gdGhpcyBzZWN0aW9uIHdlIGFyZSBnb2luZyB0byBiZSBpbXBsZW1lbnRpbmcgdGhlIGVuc2VtYmxlICoqc3RhY2tpbmcqKiBtZXRob2QgaW52b2x2aW5nIHRocmVlIGJhc2UgbGVhcm5lcnMgYW5kIGEgbWV0YSBsZWFybmVyIHdoaWNoIGxlYXJucyBmcm9tIHRoZSBwcmVkaWN0aW9ucyBvZiB0aGUgYmFzZSBsZWFybmVycy4uLg0KDQotICAgKkxFQVJORVItMTogR3JhZGllbnQgQm9vc3RpbmcgTWFjaGluZSoNCg0KYGBge3Igd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFLCByZXN1bHRzPSdoaWRlJywgIHNpemUgPSAic21hbGwifQ0KbGlicmFyeShoMm8pDQpoMm8uaW5pdCgpICMgSW5pdGlhdGluZyBoMjAgZW52aXJvbm1lbnQNCnNldC5zZWVkKDQyKQ0KbmZvbGRzIDwtIDEwICMgTnVtYmVyIG9mIENWIGZvbGRzICh0byBnZW5lcmF0ZSBsZXZlbC1vbmUgZGF0YSBmb3Igc3RhY2tpbmcpDQpnYm0gPC0gaDJvLmdibSh4ID0gbmFtZXModHJhaW5feCksICMgR3JhZGllbnQgQm9vc3RpbmcgTWFjaGluZQ0KICAgICAgICAgICAgICAgeSA9ICJ5IiwNCiAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lPWFzLmgybyhub3JtX3RyYWluX2RhdGEpLA0KICAgICAgICAgICAgICAgbmZvbGRzID0gbmZvbGRzLA0KICAgICAgICAgICAgICAga2VlcF9jcm9zc192YWxpZGF0aW9uX3ByZWRpY3Rpb25zID0gVFJVRSwNCiAgICAgICAgICAgICAgIHNlZWQgPSA0MikNCmBgYA0KDQotICAgKkxFQVJORVItMjogR2VuZXJhbGl6ZWQgTGluZWFyIE1vZGVsKg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCByZXN1bHRzPSdoaWRlJywgIHNpemUgPSAic21hbGwifQ0Kc2V0LnNlZWQoNDIpDQpnbG0gPC0gaDJvLmdsbSh4ID0gbmFtZXModHJhaW5feCksICMgR2VuZXJhbGl6ZWQgTGluZWFyIE1vZGVsDQogICAgICAgICAgICAgICB5ID0gInkiLA0KICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWU9YXMuaDJvKG5vcm1fdHJhaW5fZGF0YSksDQogICAgICAgICAgICAgICBuZm9sZHMgPSBuZm9sZHMsDQogICAgICAgICAgICAgICBrZWVwX2Nyb3NzX3ZhbGlkYXRpb25fcHJlZGljdGlvbnMgPSBUUlVFLA0KICAgICAgICAgICAgICAgc2VlZCA9IDQyKQ0KYGBgDQoNCi0gICAqTEVBUk5FUi0zOiBGdWxseSBjb25uZWN0ZWQgTmV1cmFsIE5ldHdvcmsqDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHJlc3VsdHM9J2hpZGUnLCBzaXplID0gInNtYWxsIn0NCnNldC5zZWVkKDQyKQ0KZGw8LSBoMm8uZGVlcGxlYXJuaW5nKHggPSBuYW1lcyh0cmFpbl94KSwgIyBGdWxseSBDb25uZWN0ZWQgTmV1cmFsIE5ldHdvcmsNCiAgICAgICAgICAgICAgICAgICAgICB5ID0gInkiLA0KICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lPWFzLmgybyhub3JtX3RyYWluX2RhdGEpLA0KICAgICAgICAgICAgICAgICAgICAgIG5mb2xkcyA9IG5mb2xkcywNCiAgICAgICAgICAgICAgICAgICAgICBrZWVwX2Nyb3NzX3ZhbGlkYXRpb25fcHJlZGljdGlvbnMgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSA0MikNCmBgYA0KDQotICAgKipNRVRBIExFQVJORVI6IFJhbmRvbSBGb3Jlc3QqKg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCByZXN1bHRzPSdoaWRlJywgIHNpemUgPSAic21hbGwifQ0Kc2V0LnNlZWQoNDIpDQojIFRyYWluIGEgc3RhY2tlZCBSYW5kb20gZm9yZXN0IGVuc2VtYmxlIHVzaW5nIHRoZSBwcmV2aW91c2x5IHRyYWluZWQgbW9kZWxzDQplbnNlbWJsZSA8LSBoMm8uc3RhY2tlZEVuc2VtYmxlKHggPSBuYW1lcyh0cmFpbl94KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9ICJ5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWU9YXMuaDJvKG5vcm1fdHJhaW5fZGF0YSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGFsZWFybmVyX2FsZ29yaXRobT0iZHJmIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0YWxlYXJuZXJfbmZvbGRzID0gMzAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhc2VfbW9kZWxzID0gbGlzdChnYm0sIGdsbSwgZGwpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRhbGVhcm5lcl9wYXJhbXMgPSBsaXN0KG50cmVlcyA9IDEwMCwga2VlcF9jcm9zc192YWxpZGF0aW9uX3ByZWRpY3Rpb25zID0gVFJVRSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSA0MikNCmBgYA0KDQotICAgKiptZXRhIGxlYXJuZXIgdHJhaW5pbmcgbWV0cmljcy4uLioqDQoNCmBgYHtyIHNpemUgPSAic21hbGwifQ0KZW5zZW1ibGVAbW9kZWwkdHJhaW5pbmdfbWV0cmljcw0KYGBgDQoNCi0gICAqKm1ldGEgbGVhcm5lciB2YWxpZGF0aW9uIHJtc2UgY3VydmUuLi4qKg0KDQpgYGB7ciBmaWcuaGVpZ2h0PTIsIGZpZy53aWR0aD00LCAgc2l6ZSA9ICJzbWFsbCJ9DQpybXNlX2RmID0gYXMuZGF0YS5mcmFtZSh0KGFzLmRhdGEuZnJhbWUoZW5zZW1ibGVAbW9kZWwkY3Jvc3NfdmFsaWRhdGlvbl9tZXRyaWNzX3N1bW1hcnkpWzYsMzozMl0pKQ0KZ2dwbG90KHJtc2VfZGYsIGFlcyh4ID0gaW5kZXgocm1zZV9kZiksIHkgPSBybXNlKSkgKw0KICBnZW9tX2xpbmUoKSArDQogIGdlb21fcG9pbnQoKSArDQogIGxhYnMoeCA9ICJDViIsIHkgPSAiUk1TRSIsIHRpdGxlID0gIlJNU0UgVmFsdWVzIGFjcm9zcyBDcm9zcy1WYWxpZGF0aW9uIEZvbGRzIikNCmBgYA0KDQotICAgKipUZXN0aW5nIGJhc2UgbGVhcm5lcnMgcGVyZm9ybWFuY2UuLi4qKg0KDQpgYGB7ciBzaXplID0gInNtYWxsIn0NCnNldC5zZWVkKDQ1KQ0KaDIwX3Rlc3QgPSBhcy5oMm8obm9ybV90ZXN0X2RhdGEpICMgZGF0YSB0byBIMk8gZGF0YQ0KZ2JtX3BlcmYgPC0gaDJvLnBlcmZvcm1hbmNlKGdibSwgbmV3ZGF0YSA9IGgyMF90ZXN0KQ0KZ2xtX3BlcmYgPC0gaDJvLnBlcmZvcm1hbmNlKGdsbSwgbmV3ZGF0YSA9IGgyMF90ZXN0KQ0KZGxfcGVyZiA8LSBoMm8ucGVyZm9ybWFuY2UoZGwsIG5ld2RhdGEgPSBoMjBfdGVzdCkNCnByaW50KGdibV9wZXJmKQ0KcHJpbnQoZ2xtX3BlcmYpDQpwcmludChkbF9wZXJmKQ0KYGBgDQoNCi0gICAqKm1ldGEgbGVhcm5lciBwZXJmb3JtYW5jZSBvbiB0ZXN0IGRhdGEuLi4qKg0KDQpgYGB7ciBzaXplID0gInNtYWxsIn0NCnNldC5zZWVkKDQ1KQ0KbWV0YV9wZXJmIDwtIGgyby5wZXJmb3JtYW5jZShlbnNlbWJsZSwgbmV3ZGF0YSA9IGgyMF90ZXN0KQ0KcHJpbnQobWV0YV9wZXJmKQ0KYGBgDQoNCiMjIyBUZXN0IFJlc3VsdHMgU3VtbWFyeQ0KDQotICAgTm93IHRvIHN1bSB1cCBhbGwgdGhlIHJlc3VsdHMgb2Ygb3VyIHByb2plY3Qgd2UgYXJlIGdvaW5nIHRvIG1ha2UgYSB0YWJsZSB3aXRoIGFsbCB0aGUgbW9kZWxzJyBSTVNFIHNjb3JlcyBvbiB0aGUgdGVzdCBkYXRhIGFuZCBjb21wYXJlIHRoZW0uLi4NCg0KYGBge3Igc2l6ZSA9ICJzbWFsbCJ9DQplbnNfcm1zZSA9IG1ldGFfcGVyZkBtZXRyaWNzJFJNU0UvKG1heCh0ZXN0X3kpIC0gbWluKHRlc3RfeSkpDQpybXNlX2RmID0gZGF0YS5mcmFtZSgiTW9kZWwiID0gYygiTXVsdGlwbGUgTGluZWFyIFJlZ3Jlc3Npb24iLCAiUmFuZG9tIEZvcmVzdCIsICJEZWNpc2lvbiBUcmVlIiwgIkstTmVhcmVzdCBOZWlnaGJvcnMiLCAiU3VwcG9ydCBWZWN0b3IgTWFjaGluZSBSZWdyZXNzaW9uIiwgIlN0YWNraW5nIChCbGVuZGVyKSIpLA0KICAgICAgICAgICAgICAgICJOb3JtYWxpemVkIFJNU0UiID0gYyhtbHJfcm1zZSwgcmZfcm1zZSwgZHRfcm1zZSwga25uX3Jtc2UsIHN2bV9ybXNlLCBlbnNfcm1zZSksIGNoZWNrLm5hbWVzPUZBTFNFKQ0Kcm1zZV9kZiA8LSBybXNlX2RmW29yZGVyKHJtc2VfZGYkYE5vcm1hbGl6ZWQgUk1TRWAsIGRlY3JlYXNpbmcgPSBUUlVFKSwgXQ0KcHJpbnQocm1zZV9kZikNCmBgYA0KDQp8ICpNb2RlbCogICAgICAgICAgICB8ICpOb3JtYWxpemVkIFJNU0UqIHwNCnwtLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tfA0KfCBTdGFja2luZyAoQmxlbmRlcikgfCAkMC4wMjUgXHBtIDAuMDA1JCB8DQoNCjogQmVzdCBQZXJmb3JtaW5nIE1vZGVsDQoNCiMjIENvbmNsdXNpb24NCg0KQXMgc2hvd24gaW4gdGhlIHJlc3VsdHMgc3VtbWFyeSwgdGhlIGFuc3dlciB0byBvdXIgcmVzZWFyY2ggcXVlc3Rpb24gaXMuLi4NCg0KKioqIlRoZSBiZXN0IG91dCBvZiBhbGwgdGhlIHRlc3RlZCBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobSBpcyB0aGUgc3RhY2tpbmcgbWV0aG9kIGNhbGxlZCBCbGVuZGVyIG1vcmUgY29tbW9ubHkiKioqDQoNCndoaWNoIG1ha2VzIHNlbnNlIGFzIGl0IGFsaWducyB3aXRoIHRoZSBpbml0aWFsIGFzc3VtcHRpb24gb2YgdGhlIGVuc2VtYmxlIG1ldGhvZCB3aGljaCBpbXBseSB0aGF0IGNvbWJpbmluZyBtb3JlIHRoYW4gb25lIGxlYXJuZXIgcHJvZHVjZXMgYmV0dGVyIHByZWRpY3Rpb25zLg0K