Introduction

In this report, we are going to train different machine learning methods and test their performance on our test data. Our data can be downloaded from the link:. Our task is regression and the method we used in this report are: MLR, Random Forest, Decision Tree, k-NN, SVM regression and a stacking ensemble method from the library H20.

Research Question

Our research question is:

“What is the best machine learning method or algorithm that best predicts the salary index based on different factors?”

Data & data structure

Here’s a look into the excel file…

Fig.1: Data Excel File
Fig.1: Data Excel File

The excel file consists of multiple tables stacked on top of each

other each representing the features of an economic activity. The features columns are:

  • Employment Index: Unadjusted, Calender adjusted, Seasonal and

    calender adjusted

  • Hours worked Index: Unadjusted, Calender adjusted, Seasonal and

    calender adjusted

  • Gross wages-salaries Index: Unadjusted, Calender adjusted,Seasonal and

    calender adjusted

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

  • Training and testing…
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

  • Training…
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
  • Testing…
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

  • Training…
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)
  • Testing…
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

  • Training…
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)
  • Testing…
set.seed(123)
knn_predictions <- predict(knn_model, newdata = test_x)
knn_rmse = normalized_rmse(knn_predictions, test_y)
print(knn_rmse)

SVM Regression

  • Now we will be trying the regression version of the support vector machines we will be implementing grid search for hyper-parameter tuning…

  • Training with hyper-parameter tuning…

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)
  • Testing…
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
Model Normalized RMSE
Stacking (Blender) \(0.025 \pm 0.005\)

Conclusion

As shown in the results summary, the answer to our research question is…

“The best out of all the tested machine learning algorithm is the stacking method called Blender more commonly”

which makes sense as it aligns with the initial assumption of the ensemble method which imply that combining more than one learner produces better predictions.

LS0tDQp0aXRsZTogIlByZWRpY3RpbmcgU2FsYXJpZXMgSW5kZXggV2l0aCBEaWZmZXJlbnQgTWFjaGluZSBMZWFybmluZyBNZXRob2RzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyMjIEludHJvZHVjdGlvbg0KDQpJbiB0aGlzIHJlcG9ydCwgd2UgYXJlIGdvaW5nIHRvIHRyYWluIGRpZmZlcmVudCBtYWNoaW5lIGxlYXJuaW5nIG1ldGhvZHMgYW5kIHRlc3QgdGhlaXIgcGVyZm9ybWFuY2Ugb24gb3VyIHRlc3QgZGF0YS4gT3VyIGRhdGEgY2FuIGJlIGRvd25sb2FkZWQgZnJvbSB0aGUgbGluazpcdGV4dGNvbG9ye2JsdWV9ezxodHRwczovL2RhdGEudHVpay5nb3YudHIvQnVsdGVuL0Rvd25sb2FkSXN0YXRpc3Rpa3NlbFRhYmxvP3A9VDVnL1dPaVkxQkg0SmJtd3czanRDbzFxMDMxSS9Nbnp6dTdIVklqejc1T3p2Mk56bWhyTlR6TnphTEN3NzRQdT59LiBPdXIgdGFzayBpcyByZWdyZXNzaW9uIGFuZCB0aGUgbWV0aG9kIHdlIHVzZWQgaW4gdGhpcyByZXBvcnQgYXJlOiBNTFIsIFJhbmRvbSBGb3Jlc3QsIERlY2lzaW9uIFRyZWUsIGstTk4sIFNWTSByZWdyZXNzaW9uIGFuZCBhIHN0YWNraW5nIGVuc2VtYmxlIG1ldGhvZCBmcm9tIHRoZSBsaWJyYXJ5IEgyMC4NCg0KKipSZXNlYXJjaCBRdWVzdGlvbioqDQoNCk91ciByZXNlYXJjaCBxdWVzdGlvbiBpczoNCg0KKioqIldoYXQgaXMgdGhlIGJlc3QgbWFjaGluZSBsZWFybmluZyBtZXRob2Qgb3IgYWxnb3JpdGhtIHRoYXQgYmVzdCBwcmVkaWN0cyB0aGUgc2FsYXJ5IGluZGV4IGJhc2VkIG9uIGRpZmZlcmVudCBmYWN0b3JzPyIqKioNCg0KKipEYXRhICYgZGF0YSBzdHJ1Y3R1cmUqKg0KDQpIZXJlJ3MgYSBsb29rIGludG8gdGhlIGV4Y2VsIGZpbGUuLi4NCg0KKzotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSsNCnwgIVtGaWcuMTogRGF0YSBFeGNlbCBGaWxlXShwaWN0dXJlcy9TY3JlZW5zaG90JTIwMjAyNC0wNS0wOSUyMDE0MDYxMi5wbmcpe3dpZHRoPSIzLjczaW4ifSB8IFRoZSBleGNlbCBmaWxlIGNvbnNpc3RzIG9mIG11bHRpcGxlIHRhYmxlcyBzdGFja2VkIG9uIHRvcCBvZiBlYWNoICAgICAgICAgICAgICAgICAgICAgICB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgb3RoZXIgZWFjaCByZXByZXNlbnRpbmcgdGhlIGZlYXR1cmVzIG9mIGFuIGVjb25vbWljIGFjdGl2aXR5LiBUaGUgZmVhdHVyZXMgY29sdW1ucyBhcmU6IHwNCnwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAtICAgRW1wbG95bWVudCBJbmRleDogVW5hZGp1c3RlZCwgQ2FsZW5kZXIgYWRqdXN0ZWQsIFNlYXNvbmFsIGFuZCAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICBjYWxlbmRlciBhZGp1c3RlZCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgLSAgIEhvdXJzIHdvcmtlZCBJbmRleDogVW5hZGp1c3RlZCwgQ2FsZW5kZXIgYWRqdXN0ZWQsIFNlYXNvbmFsIGFuZCAgICAgICAgICAgICAgICAgICAgIHwNCnwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAgICAgY2FsZW5kZXIgYWRqdXN0ZWQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IC0gICBHcm9zcyB3YWdlcy1zYWxhcmllcyBJbmRleDogVW5hZGp1c3RlZCwgQ2FsZW5kZXIgYWRqdXN0ZWQsU2Vhc29uYWwgYW5kICAgICAgICAgICAgICB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgICAgIGNhbGVuZGVyIGFkanVzdGVkICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCistLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rDQoNCiMjIENvZGU6DQoNCmBgYHs9dGV4fQ0KXGxldFxvbGRTaGFkZWRcU2hhZGVkDQpcbGV0XGVuZG9sZFNoYWRlZFxlbmRTaGFkZWQNClxyZW5ld2Vudmlyb25tZW50e1NoYWRlZH17XHNtYWxsXG9sZFNoYWRlZH17XGVuZG9sZFNoYWRlZH0NCmBgYA0KYGBge3IgZWNobz1GQUxTRSwgIHNpemUgPSAic21hbGwifQ0KI1x0aW55DQprbml0cjo6b3B0c19jaHVuayRzZXQobWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIGVjaG8gPSBUUlVFLCByZXN1bHRzID0gImhvbGQiLCBlY2hvID0gVFJVRSwgZmlnLmFsaWduPSJjZW50ZXIiLCBzaXplID0gJ3NtYWxsJykNCg0KYGBgDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsICBzaXplID0gInNtYWxsIn0NCmxpYnJhcnkocmVhZHhsKQ0KZGF0YTEgPC0gcmVhZF9leGNlbCgiZGF0YS9zYWxhcnkgaW5kZXggZGF0YS54bHMiLCAgcmFuZ2U9ICJBMTI3Olo3MjYiLCBjb2xfbmFtZXMgPSBGQUxTRSkNCmRhdGEyIDwtIHJlYWRfZXhjZWwoImRhdGEvc2FsYXJ5IGluZGV4IGRhdGEueGxzIiwgIHJhbmdlPSAiQTc4NzpaMTI2NiIsIGNvbF9uYW1lcyA9IEZBTFNFKQ0KZnVsbF9kYXRhIDwtIHJiaW5kKGRhdGExLCBkYXRhMikNCmRpbShmdWxsX2RhdGEpDQpgYGANCg0KYGBge3Igd2FybmluZz1GQUxTRSwgIHNpemUgPSAic21hbGwifQ0KcHJpbnQoZnVsbF9kYXRhWzE6MywgMToxM10pDQpgYGANCg0KIyMjIERhdGEgQ2xlYW5pbmcNCg0KKipSZW5hbWluZyBhbmQgZHJvcHBpbmcgdW53YW50ZWQgY29sdW1ucyoqDQoNCldlIGFyZSBvbmx5IHVzaW5nIHRoZSB1bmFkanVzdGVkIHZlcnNpb24gb2YgdGhlIGNvbHVtbnMuLi4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgIHNpemUgPSAic21hbGwifQ0KbGlicmFyeShkcGx5cikNCnNlbGVjdF9hbmRfcmVuYW1lX3YyIDwtIGZ1bmN0aW9uKGRmKSB7ICMgY2xlYW5pbmcgcGlwZWxpbmUNCiAgc2VsZWN0ZWRfZGYgPC0gZGZbLCBjKDEsIDIsIDMsIDQsIDEyLCAyMCldICMgU2VsZWN0IGNvbHVtbnMgMSwgMiwgMywgNCwgMTIsIGFuZCAyMA0KICAjIFJlbmFtZSB0aGUgY29sdW1ucyB3aXRoIGRlc2lyZWQgbmFtZXMNCiAgbmFtZXMoc2VsZWN0ZWRfZGYpIDwtIGMoIkVjb25vbWljIGFjdGl2aXR5IiwgIlllYXIiLCAiUXVhcnRlciIsICJFbXBsb3ltZW50IGluZGV4IiwgIkhvdXJzIHdvcmtlZCBpbmRleCIsICJzYWxhcmllcyBpbmRleCIpDQogICMgUmVvcmRlcmluZw0KICBzZWxlY3RlZF9kZiA9IHNlbGVjdGVkX2RmW2MoIlllYXIiLCAiUXVhcnRlciIsICJFbXBsb3ltZW50IGluZGV4IiwgIkhvdXJzIHdvcmtlZCBpbmRleCIsICJzYWxhcmllcyBpbmRleCIsICJFY29ub21pYyBhY3Rpdml0eSIpXQ0KICByZXR1cm4oc2VsZWN0ZWRfZGYpIH0NCmNsZWFuX2Z1bGxfZGF0YSA9IHNlbGVjdF9hbmRfcmVuYW1lX3YyKGZ1bGxfZGF0YSkNCmhlYWQoY2xlYW5fZnVsbF9kYXRhLCAzKQ0KYGBgDQoNCioqRml4aW5nIHdyb25nIGVudHJpZXMqKg0KDQpTb21lIGNvbW1lbnQgd2hlcmUgbWFkZSBieSB0dWlrIGFuZCB0aGF0IGNhdXNlZCBzb21lIGVudHJpZXMgaW4gdGhlIFllYXIgY29sdW1uIHRvIGhhdmUgbGV0dGVycyBzbyB3ZSBhcmUgZml4aW5nIHRoZW0gc28gdGhhdCB0aGUgY29sdW1uIGNhbiBiZSBvZiB0eXBlIGludC4uLg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCAgc2l6ZSA9ICJzbWFsbCJ9DQpjbGVhbl9mdWxsX2RhdGFbNTMsXQ0KYGBgDQoNCmBgYHtyIHdhcm5pbmc9RkFMU0V9DQpjbGVhbl9mdWxsX2RhdGEkWWVhciA8LSBnc3ViKCJcXChyXFwpIiwgIiIsIGNsZWFuX2Z1bGxfZGF0YSRZZWFyKSAjIHVzaW5nIHJlZ3VsYXIgZXhwcmVzc2lvbnMgdG8gZml4IHdyb25nIGVudHJpZXMNCmNsZWFuX2Z1bGxfZGF0YSRZZWFyIDwtIGFzLm51bWVyaWMoY2xlYW5fZnVsbF9kYXRhJFllYXIpDQpjbGVhbl9mdWxsX2RhdGFbNTMsXQ0KYGBgDQoNCioqRmlsbGluZyBOQSdzKioNCg0Kc2VlaW5nIHdoYXQgY29sdW1uIGhhdmUgTkEgdmFsdWVzLi4NCg0KYGBge3Igd2FybmluZz1GQUxTRSwgIHNpemUgPSAic21hbGwifQ0KY29sU3Vtcyhpcy5uYShjbGVhbl9mdWxsX2RhdGEpKQ0KYGBgDQoNCipZZWFyIGFuZCBRdWFydGVyIGNvbHVtbnM6IHRoZSB5ZWFyIGNvbHVtbiBoYXMgZW1wdHkgZW50cmllcyB0aGF0IGluZGljYXRlIHRoZSB2YWx1ZSBvZiB0aGUgbGFzdCBub24gZW1wdHkgYW5kIGJlY2F1c2Ugd2Ugc3RhY2tlZCBhbGwgdGhlIHRhYmxlcy4gRm9yIHRob3NlIGVtcHR5IGVudHJpZXMgd2Ugd2lsbCBiZSB1c2luZyBhIGZ1bmN0aW9uIHRoYXQgcmVwbGFjZXMgdGhlIE5BcyB3aXRoIHRoZSBsYXN0IG5vbi1OQSB2YWx1ZS4gV2UgYXJlIG1ha2luZyBhIGZ1bmN0aW9uIHBpcGVsaW5lIHRoYXQgcmVwbGFjZXMgcXVhcnRlciB3aXRoIGEgbnVtYmVyIGFuZCBtZXJnZXMgYm90aCBjb2x1bW5zIGludG8gb25lIHNvIGl0IGJlY29tZXMgb3VyIG9ubHkgdGltZSBzZXJpZXMgY29sdW1uLioNCg0KYGBge3Igd2FybmluZz1GQUxTRSwgIHNpemUgPSAic21hbGwifQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoem9vKQ0Kb25lX2NvbHVtbl9mdW5jdGlvbiA8LSBmdW5jdGlvbihkZikgew0KICBxdWFydGVyX21hcHBpbmcgPC0gYygiSSIgPSAxLCAiSUkiID0gMiwgIklJSSIgPSAzLCAiSVYiID0gNCkNCiAgZGYgPC0gZGYgfD4NCiAgICBtdXRhdGUoDQogICAgICBZZWFyID0gbmEubG9jZihkZiRZZWFyLCBuYS5ybSA9IEZBTFNFKSwNCiAgICAgIFllYXJfUXVhcnRlciA9IGFzLm51bWVyaWMoYXMubnVtZXJpYyhZZWFyKSArIChxdWFydGVyX21hcHBpbmdbUXVhcnRlcl0gLSAxKSAvIDQNCiAgICApKSB8Pg0KICAgIHNlbGVjdCgtWWVhciwgLVF1YXJ0ZXIpDQogIHJldHVybihkZikgfQ0KY2xlYW5fZnVsbF9kYXRhID0gb25lX2NvbHVtbl9mdW5jdGlvbihjbGVhbl9mdWxsX2RhdGEpDQpoZWFkKGNsZWFuX2Z1bGxfZGF0YSwzKQ0KYGBgDQoNCipFY29ub21pYyBBY3Rpdml0eSBDb2x1bW46IGhhcyBlbXB0eSBlbnRyaWVzIGZvciB0aGUgc2FtZSByZWFzb24gYWJvdmUgYnV0IGluIHRoaXMgY2FzZSBlYWNoIHRhYmxlIGZyb20gdGhlIHRhYmxlcyBzdGFja2VkIGhhcyBhIGRpZmZlcmVudCB2YWx1ZSBmb3IgdGhhdCB3ZSBhcmUgbWFraW5nIGEgd2luZG93IGFuZCBpdGVyYXRpbmcgdGhyb3VnaCBlYWNoIHdpbmRvdyB0byBmaWxsIHRoZSBOQSdzIHdpdGggdGhlIGZpcnN0IG5vbi1OQSB2YWx1ZSBpbiB0aGF0IHdpbmRvdyAoZWFjaCB0YWJsZSBmcm9tIHRoZSBzdGFja2VkIHRhYmxlcyBoYXMgNjAgcm93cyB0aHVzIHRoZSAvNjApLiBBZnRlciB0aGF0IHdlIG1hZGUgdGhlIGNvbHVtbiBvZiB0eXBlIGZhY3Rvci4uLioNCg0KYGBge3Igd2FybmluZz1GQUxTRSwgIHNpemUgPSAic21hbGwifQ0KbGlicmFyeShkcGx5cikNCmZpbGxfcGF0dGVybiA8LSBmdW5jdGlvbihkZikgew0KICBuX3Jvd3MgPC0gbnJvdyhkZikNCiAgbnVtX3dpbmRvd3MgPC0gY2VpbGluZyhuX3Jvd3MgLyA2MCkNCiAgZm9yIChpIGluIDE6bnVtX3dpbmRvd3MpIHsgIyBJdGVyYXRlIG92ZXIgZWFjaCB3aW5kb3cNCiAgICBzdGFydF9pbmRleCA8LSAoaSAtIDEpICogNjAgKyAxICMgRGV0ZXJtaW5lIHRoZSBzdGFydCBhbmQgZW5kIGluZGljZXMgb2YgdGhlIGN1cnJlbnQgd2luZG93DQogICAgZW5kX2luZGV4IDwtIG1pbihpICogNjAsIG5fcm93cykNCiAgICBkZltzdGFydF9pbmRleDplbmRfaW5kZXgsICJFY29ub21pYyBhY3Rpdml0eSJdIDwtIGRmW3N0YXJ0X2luZGV4KzEsICJFY29ub21pYyBhY3Rpdml0eSJdICMgRmlsbCB0aGUgY3VycmVudCB3aW5kb3cgd2l0aCB0aGUgc2Vjb25kIHJvdyB2YWx1ZQ0KICB9DQogIHJldHVybihkZil9DQpjbGVhbl9mdWxsX2RhdGEgPSBmaWxsX3BhdHRlcm4oY2xlYW5fZnVsbF9kYXRhKQ0KaGVhZChjbGVhbl9mdWxsX2RhdGEsMykNCmBgYA0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCAgc2l6ZSA9ICJzbWFsbCJ9DQpjbGVhbl9mdWxsX2RhdGEkYEVjb25vbWljIGFjdGl2aXR5YCA9IGZhY3RvcihjbGVhbl9mdWxsX2RhdGEkYEVjb25vbWljIGFjdGl2aXR5YCkNCmBgYA0KDQoqZml4aW5nIG5hbWVzOiBiZWNhdXNlIHNvbWUgbmFtZXMgaGF2ZSBzb21lIHNwZWNpYWwgVHVya2lzaCBjaGFyYWN0ZXJzIGFuZCBjaGFuZ2luZyB0aGVtIHdvdWxkIG1lYW4gbm90IHJ1bm5pbmcgaW50byB1bmV4cGVjdGVkIGVycm9ycyB3aGVuIHRyYWluaW5nKiBcdGlueQ0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCAgc2l6ZSA9ICJzbWFsbCJ9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShmb3JjYXRzKQ0KY2xlYW5fZnVsbF9kYXRhIDwtIGNsZWFuX2Z1bGxfZGF0YSB8PiAjIGNoYW5naW5nIG5hbWUgd2l0aCBmb3JjYXRzDQogIG11dGF0ZShgRWNvbm9taWMgYWN0aXZpdHlgID0gZmN0X3JlY29kZShgRWNvbm9taWMgYWN0aXZpdHlgLCAiSW50ZXJtZWRpYXRlIGdvb2RzIiA9ICJJRy1JbnRlcm1lZGlhdGUgZ29vZHMiLA0KICAgICAgICAgICJEdXJhYmxlIGNvbnN1bWVyIGdvb2RzIiA9ICJEQ0ctRHVyYWJsZSBjb25zdW1lciBnb29kcyIsIk5vbi1kdXJhYmxlIGNvbnN1bWVyIGdvb2RzIiA9ICJORENHLU5vbi1kdXJhYmxlIGNvbnN1bWVyIGdvb2RzIiwNCiAgICAgICAgICAiRW5lcmd5IiA9ICJOUkctRW5lcmd5IiwiQ2FwaXRhbCBnb29kcyIgPSAiQ0ctQ2FwaXRhbCBnb29kcyIsDQogICAgICAgICAgIk1pbmluZyBhbmQgcXVhcnJ5aW5nIiA9ICJNYWRlbmNpbGlrIHZlIHRhxZ8gb2Nha8OnxLFsxLHEn8SxIiwiTWFudWZhY3R1cmluZyIgPSAixLBtYWxhdCIsDQogICAgICAgICAgIkVsZWN0cmljaXR5LCBnYXMsIHN0ZWFtIGFuZCBhaXIgY29uZGl0aW9uaW5nIHN1cHBseSIgPSAiRWxla3RyaWssIGdheiwgYnVoYXIgdmUgaWtsaW1sZW5kaXJtZSIsDQogICAgICAgICAgIldhdGVyIHN1cHBseSwgc2V3ZXJhZ2UsIHdhc3RlIG1hbmFnZW1lbnQgYW5kIHJlbWVkaWF0aW9uIGFjdGl2aXRpZXMiID0gIlN1IHRlbWluaTsga2FuYWxpemFzeW9uLCBhdMSxayB5w7ZuZXRpbWkiLA0KICAgICAgICAgICJDb25zdHJ1Y3Rpb24iID0gIsSwbsWfYWF0IiwiV2hvbGVzYWxlIGFuZCByZXRhaWwgdHJhZGUiID0gIlRvcHRhbiB2ZSBwZXJha2VuZGUgdGljYXJldDsiLA0KICAgICAgICAgICJUcmFuc3BvcnRhdGlvbiBhbmQgc3RvcmFnZSIgPSAiVWxhxZ90xLFybWEgdmUgZGVwb2xhbWEiLCJBY2NvbW1vZGF0aW9uIGFuZCBmb29kIHNlcnZpY2UgYWN0aXZpdGllcyIgPSAiS29uYWtsYW1hIHZlIHlpeWVjZWsgaGl6bWV0aSIsDQogICAgICAgICAgIkluZm9ybWF0aW9uIGFuZCBjb21tdW5pY2F0aW9uIiA9ICJCaWxnaSB2ZSBpbGV0acWfaW0iLCJGaW5hbmNpYWwgYW5kIGluc3VyYW5jZSBhY3Rpdml0aWVzIiA9ICJGaW5hbnMgdmUgc2lnb3J0YSBmYWFsaXlldGxlcmkiLA0KICAgICAgICAgICJSZWFsIGVzdGF0ZSBhY3Rpdml0aWVzIiA9ICJHYXlyaW1lbmt1bCBmYWFsaXlldGxlcmkiLA0KICAgICAgICAgICJQcm9mZXNzaW9uYWwsIHNjaWVudGlmaWMgYW5kIHRlY2huaWNhbCBhY3Rpdml0aWVzIiA9ICJNZXNsZWtpLCBiaWxpbXNlbCB2ZSB0ZWtuaWsgZmFhbGl5ZXRsZXIiLA0KICAgICAgICAgICJBZG1pbmlzdHJhdGl2ZSBhbmQgc3VwcG9ydCBzZXJ2aWNlIGFjdGl2aXRpZXMiID0gIsSwZGFyaSB2ZSBkZXN0ZWsgaGl6bWV0IGZhYWxpeWV0bGVyaSIpKQ0KaGVhZChsZXZlbHMoY2xlYW5fZnVsbF9kYXRhJGBFY29ub21pYyBhY3Rpdml0eWApLDMpDQpgYGANCg0KKipGaW5hbCBzdHJ1Y3R1cmUqKg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCAgc2l6ZSA9ICJzbWFsbCJ9DQpzdHIoY2xlYW5fZnVsbF9kYXRhKQ0KZGltKGNsZWFuX2Z1bGxfZGF0YSkNCmBgYA0KDQojIyMgVmlzdWFsaXphdGlvbnMNCg0KLSAgIGNvcnJlbGF0aW9uIGhlYXRtYXAgYmV0d2VlbiBhbGwgdmFyaWFibGVzDQoNCmBgYHtyIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTYsICBzaXplID0gInNtYWxsIn0NCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkocmVzaGFwZTIpDQpkYXRhIDwtIGNvcihjbGVhbl9mdWxsX2RhdGFbc2FwcGx5KGNsZWFuX2Z1bGxfZGF0YSwgaXMubnVtZXJpYyldKSAjIENhbGN1bGF0aW5nIGNvcnJlbGF0aW9uIG1hdHJpeA0KZGF0YTEgPC0gbWVsdChkYXRhKSAjIFJlc2hhcGluZyBkYXRhDQpwIDwtIGdncGxvdChkYXRhMSwgYWVzKFZhcjEsIFZhcjIsIGZpbGwgPSB2YWx1ZSkpICsgDQogIGdlb21fdGlsZSgpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKHZhbHVlLCA4KSksIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDMpICsNCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAid2hpdGUiLCBoaWdoID0gInB1cnBsZSIpICsgIyBDb2xvciBncmFkaWVudCBmb3IgaGVhdG1hcA0KICB0aGVtZV9taW5pbWFsKCkgKyAgDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksICMgUm90YXRpbmcgeC1heGlzIGxhYmVscyBmb3IgYmV0dGVyIHJlYWRhYmlsaXR5DQogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwgIyBSZW1vdmluZyBheGVzIHRpdGxlcw0KICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCkpDQpwcmludChwKQ0KYGBgDQoNClRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSB0YXJnZXQgYW5kIHRoZSBmZWF0dXJlcyBpcyBub3QgYmFkIGJ1dCB0aGUgYmlnZ2VzdCBtYXliZSBwcm9ibGVtIGhlYXIgaXMgdGhlIGZlYXR1cmUgY28tbGluZWFyaXR5LCBhbHRob3VnaCB0aGF0IGlzIGEgYmFkIHRoaW5nIHdlIGFyZSBub3QgZ29pbmcgdG8gZHJvcCBhbnkgZmVhdHVyZSBiZWNhdXNlIHdlIGRvbid0IGhhdmUgYSBsb3Qgb2YgZmVhdHVyZXMgdG8gYmVnaW4gd2l0aC4NCg0KLSAgIFZpc3VhbGl6aW5nIHRyZW5kIG92ZXIgdGltZQ0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD02LCAgc2l6ZSA9ICJzbWFsbCJ9DQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KIyBSZXNoYXBpbmcgZGF0YSBpbnRvIGxvbmcgZm9ybWF0LCBleGNsdWRpbmcgRWNvbm9taWNfYWN0aXZpdHkgYW5kIFllYXJfUXVhcnRlciBjb2x1bW5zDQpkZl9sb25nIDwtIGdhdGhlcihjbGVhbl9mdWxsX2RhdGEsIGtleSA9ICJWYXJpYWJsZSIsIHZhbHVlID0gIlZhbHVlIiwgLVllYXJfUXVhcnRlciwgLWBFY29ub21pYyBhY3Rpdml0eWApDQojIFBsb3RpbmcgbGluZSBwbG90cyBmb3IgZWFjaCB2YXJpYWJsZSwgdXNpbmcgRWNvbm9taWNfYWN0aXZpdHkgYXMgaHVlDQpnZ3Bsb3QoZGZfbG9uZywgYWVzKHggPSBZZWFyX1F1YXJ0ZXIsIHkgPSBWYWx1ZSwgY29sb3IgPSBgRWNvbm9taWMgYWN0aXZpdHlgLCBncm91cCA9IGBFY29ub21pYyBhY3Rpdml0eWApKSArDQogIGdlb21fbGluZSgpICsNCiAgZmFjZXRfd3JhcCh+IFZhcmlhYmxlLCBzY2FsZXMgPSAiZnJlZV95IiwgbnJvdyA9IDEpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKw0KICBndWlkZXMoY29sb3IgPSBGQUxTRSkgICMgUmVtb3ZpbmcgdGhlIGxlZ2VuZA0KYGBgDQoNCldlIGNhbiBzZWUgdGhlIGNvLWxpbmVhcml0eSBiZXR3ZWVuIHRoZSBwcmV2aW91cyB0d28gZmVhdHVyZXMgYnV0IG5vdGljZSBob3cgdGhlIHRyZW5kcyBhcmUgYSBiaXQgZGlmZmVyZW50IHdoaWNoIGlzIGluZm9ybWF0aW9uIHdlIG9idGFpbiBieSBrZWVwaW5nIHRoZSBmZWF0dXJlIGFuZCB0cmFpbmluZyBvbiB0aGUgd2hvbGUgZGF0YSBzZXQuDQoNCiMjIyBEYXRhIFByZS1wcm9jZXNzaW5nDQoNCioqRmVhdHVyZSBTZWxlY3Rpb24qKg0KDQotICAgd2UgYXJlIGdvbm5hIHVzZSBhbGwgdGhlIGZlYXR1cmVzIGRlc3BpdGUgdHdvIGJlaW5nIGhpZ2hseSBjb3JyZWxhdGVkLiBXZSB3aWxsIGxhdGVyIHVzZSBvdGhlciBtZXRob2RzIHRvIGVuc3VyZSBnb29kIGFjY3VyYWN5Li4uDQoNCmBgYHtyIHdhcm5pbmc9RkFMU0UsICBzaXplID0gInNtYWxsIn0NCmRhdGEgPC0gY2xlYW5fZnVsbF9kYXRhDQpgYGANCg0KKipFbmNvZGluZyoqDQoNCmVuY29kaW5nIGlzIHJlY29tbWVuZGVkIHNvIHRoYXQgdGhlIGFsZ29yaXRobSBkb2Vzbid0IGhhdmUgdG8gZGVhbCB3aXRoIGNoYXJhY3RlcnMsIHdlIGFyZSB1c2luZyBvbmUtaG90IGVuY29kaW5nIGJlY2F1c2UgdGhlIGNhdGVnb3JpY2FsIGNvbHVtbiBpcyBub3Qgb3JkaW5hbCBidXQgbm9taW5hbC4NCg0KYGBge3Igd2FybmluZz1GQUxTRSwgIHNpemUgPSAic21hbGwifQ0KbGlicmFyeShmYXN0RHVtbWllcykNCmRhdGEgPC0gZHVtbXlfY29scyhkYXRhLCBzZWxlY3RfY29sdW1ucyA9ICJFY29ub21pYyBhY3Rpdml0eSIpDQpkYXRhIDwtIHN1YnNldChkYXRhLCBzZWxlY3QgPSAtYyhgRWNvbm9taWMgYWN0aXZpdHlgKSkNCnRhaWwoY29sbmFtZXMoZGF0YSksMykNCmBgYA0KDQoqKlJlbmFtaW5nICJzYWxhcmllcyBpbmRleCIgdG8geSBmb3IgZWFzaWVyIGFuZCBzaG9ydGVyIGNvZGUqKg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCAgc2l6ZSA9ICJzbWFsbCJ9DQpuYW1lcyhkYXRhKVtuYW1lcyhkYXRhKSA9PSAic2FsYXJpZXMgaW5kZXgiXSA8LSAieSINCmhlYWQoZGF0YSR5LDMpDQpgYGANCg0KKipUcmFpbiBUZXN0IFNwbGl0KioNCg0KV2Ugd2lsbCBiZSBkb2luZyBhbiA4MCUgdHJhaW5pbmcsIDEwJSB2YWxpZGF0aW9uLCAxMCUgdGVzdGluZyByYXRpbyBmb3IgbW9zdCBtb2RlbHMgdGh1cyB0aGUgY29kZSBiZWxvdy4uLg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCAgc2l6ZSA9ICJzbWFsbCJ9DQpsaWJyYXJ5KGNhcmV0KQ0Kc2V0LnNlZWQoNDUpICMgU2V0dGluZyB0aGUgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5DQp0cmFpbl9pbmRpY2VzIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0YSR5LCBwID0gMC45LCBsaXN0ID0gRkFMU0UpICMgU3BsaXR0aW5nIHRoZSBkYXRhc2V0IGludG8gOTAlIHRyYWluaW5nIGFuZCAxMCUgdGVzdA0KdHJhaW5fZGF0YSA8LSBkYXRhW3RyYWluX2luZGljZXMsIF0NCnRlc3RfZGF0YSA8LSBkYXRhWy10cmFpbl9pbmRpY2VzLCBdDQpjYXQoIlRyYWluaW5nIGRhdGEgZGltZW5zaW9uczoiLCBkaW0odHJhaW5fZGF0YSksICJcbiIpDQpjYXQoIlRlc3QgZGF0YSBkaW1lbnNpb25zOiIsIGRpbSh0ZXN0X2RhdGEpLCAiXG4iKQ0KYGBgDQoNCioqU2NhbGluZyoqDQoNCmJlY2F1c2Ugc2NhbGluZyBpcyByZWNvbW1lbmRlZCBmb3IgbW9zdCBtb2RlbHMgd2UgYXJlIGFwcGx5aW5nIGl0IGJ1dCBmb3IgYW55IG90aGVyIGNhc2Ugd2Ugd2lsbCBmaXJzdCBzYXZlIHRoZSB1bnNjYWxlZCB2ZXJzaW9uLi4uDQoNCmBgYHtyIHdhcm5pbmc9RkFMU0UsICBzaXplID0gInNtYWxsIn0NCnVuc2NhbGVkX3RyYWluX2RhdGEgPSB0cmFpbl9kYXRhICMgU2F2aW5nIHVuc2NhbGVkIHZlcnNpb24NCnVuc2NhbGVkX3Rlc3RfZGF0YSA9IHRlc3RfZGF0YQ0KbGlicmFyeShjYXJldCkNCnNldC5zZWVkKDQ1KQ0KY29sdW1uc190b19ub3JtYWxpemUgPC0gYygiRW1wbG95bWVudCBpbmRleCIsICJIb3VycyB3b3JrZWQgaW5kZXgiLCAieSIsICJZZWFyX1F1YXJ0ZXIiKQ0KcHJlcHJvY2VzcyA8LSBwcmVQcm9jZXNzKHRyYWluX2RhdGFbLCBjb2x1bW5zX3RvX25vcm1hbGl6ZV0sIG1ldGhvZCA9IGMoImNlbnRlciIsICJzY2FsZSIpKSMgUHJlLXByb2Nlc3NpbmcgcGlwZWxpbmUgdG8gKm5vcm1hbGl6ZSogdGhlIHNlbGVjdGVkIGNvbHVtbnMNCm5vcm1fdHJhaW5fZGF0YSA8LSBwcmVkaWN0KHByZXByb2Nlc3MsIG5ld2RhdGEgPSB0cmFpbl9kYXRhKSMgQXBwbHlpbmcgdGhlIHBpcGVsaW5lIHRvIHRoZSB0cmFpbmluZyBkYXRhDQpub3JtX3Rlc3RfZGF0YSA8LSBwcmVkaWN0KHByZXByb2Nlc3MsIG5ld2RhdGEgPSB0ZXN0X2RhdGEpIyBBcHBseWluZyB0aGUgc2FtZSBwaXBlbGluZSB0byB0aGUgdGVzdCBkYXRhDQpwcmludChub3JtX3RyYWluX2RhdGFbMTozLCAxOjRdKQ0KYGBgDQoNCioqU3BlY2lmeWluZyBYICYgWSoqDQoNCkFzc2lnbmluZyBYIGFuZCBZIGZvciB1cGNvbWluZyB0ZXN0aW5nLi4uDQoNCmBgYHtyIHNpemUgPSAic21hbGwifQ0KbGlicmFyeShkcGx5cikNCiMgVW5zY2FsZWQgdHJhaW4gJiB0ZXN0LCB5ICYgeA0KdW5zY2FsZWRfdHJhaW5feCA9IHNlbGVjdCh1bnNjYWxlZF90cmFpbl9kYXRhLCAteSkNCnVuc2NhbGVkX3RyYWluX3kgPSBhcy5udW1lcmljKHVuc2NhbGVkX3RyYWluX2RhdGFbWyJ5Il1dKQ0KdW5zY2FsZWRfdGVzdF94ID0gc2VsZWN0KHVuc2NhbGVkX3Rlc3RfZGF0YSwgLXkpDQp1bnNjYWxlZF90ZXN0X3kgPSBhcy5udW1lcmljKHVuc2NhbGVkX3Rlc3RfZGF0YVtbInkiXV0pDQojIFNjYWxlZCB0cmFpbiAmIHRlc3QsIHkgJiB4DQp0cmFpbl94ID0gc2VsZWN0KG5vcm1fdHJhaW5fZGF0YSwgLXkpDQp0cmFpbl95ID0gYXMubnVtZXJpYyhub3JtX3RyYWluX2RhdGFbWyJ5Il1dKQ0KdGVzdF94ID0gc2VsZWN0KG5vcm1fdGVzdF9kYXRhLCAteSkNCnRlc3RfeSA9IGFzLm51bWVyaWMobm9ybV90ZXN0X2RhdGFbWyJ5Il1dKQ0KYGBgDQoNCiMjIyBNb2RlbHMNCg0KKipUZXN0aW5nIHNjb3JlIGZ1bmN0aW9uKioNCg0KV2UgYXJlIGdvaW5nIHRvIHVzZSB0aGUgbm9ybWFsaXplZCB2ZXJzaW9uIG9mIFJNU0UgYXMgdGhlIG5vcm1hbCBSTVNFIGRvZXMgbm90IHRha2UgaW50byBjb25zaWRlcmF0aW9uIHRoZSBlcnJvciBhY2NvcmRpbmcgdG8gdGhlIHJhbmdlIG9mIG91ciB0YXJnZXQgdmFyaWFibGUgYW5kIHdlIHdvdWxkIGhhdmUgdG8gYW5hbHl6ZSB0aGUgcmFuZ2UgdG8gZGV0ZXJtaW5lIGlmIHRoZSBzY29yZSBpcyBnb29kIG9yIG5vdC4gTm9ybWFsaXplZCBSTVNFIHRha2VzIGludG8gY29uc2lkZXJhdGlvbiBvdXIgWSByYW5nZSBhbmQgb3V0cHV0cyBhIGRlY2ltYWwgYmV0d2VlbiAwLTEgYW5kIHRoZSBjbG9zZXIgaXQgaXMgdG8gMCB0aGUgYmV0dGVyIHRoZSBtb2RlbCBpcy4gSGVyZSdzIGhvdyB3ZSBpbXBsZW1lbnRlZCBpdC4uLg0KDQpgYGB7ciBzaXplID0gInNtYWxsIn0NCm5vcm1hbGl6ZWRfcm1zZSA9IGZ1bmN0aW9uKHByZWQsIHJlYWwpeyAjTm9ybWFsaXplZCBSb290IE1lYW4gU3F1YXJlZCBFcnJvcg0KICBlcnJvciA9IHJlYWwgLSBwcmVkDQogIHNxcnQobWVhbihlcnJvcl4yKSkvKG1heChyZWFsKSAtIG1pbihyZWFsKSkgfQ0KYGBgDQoNCiMjIyMgTUxSOiBNdWx0aXBsZSBMaW5lYXIgUmVncmVzc2lvbg0KDQotICAgKipUcmFpbmluZyBhbmQgdGVzdGluZy4uLioqDQoNCmBgYHtyIHNpemUgPSAic21hbGwifQ0Kc2V0LnNlZWQoMTIzKQ0KbWxyX21vZGVsIDwtIGxtKHkgfiAuLCBkYXRhID0gbm9ybV90cmFpbl9kYXRhKSAjIFRyYWluaW5nLi4uDQptbHJfcHJlZGljdGlvbnMgPC0gcHJlZGljdChtbHJfbW9kZWwsIG5ld2RhdGEgPSB0ZXN0X3gpICAjIFRlc3RpbmcuLi4NCm1scl9ybXNlIDwtIG5vcm1hbGl6ZWRfcm1zZShtbHJfcHJlZGljdGlvbnMsIHRlc3RfeSkNCnByaW50KG1scl9ybXNlKQ0KYGBgDQoNCiMjIyMgUmFuZG9tIEZvcmVzdA0KDQotICAgKipUcmFpbmluZy4uLioqDQoNCmBgYHtyIHNpemUgPSAic21hbGwifQ0KbGlicmFyeShjYXJldCkNCnNldC5zZWVkKDEyMykNCnRyYWluX2NvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDUpICMgUGVyZm9ybWluZyBjcm9zcyB2YWxpZGF0aW9uDQpyZl9tb2RlbCA8LSB0cmFpbiggIyBUcmFpbmluZyB0aGUgUmFuZG9tIEZvcmVzdCBtb2RlbA0KICB5IH4gLiwgDQogIGRhdGEgPSBub3JtX3RyYWluX2RhdGEsIA0KICBtZXRob2QgPSAicmYiLA0KICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLA0KICBudHJlZSA9IDUwMCkgICMgTnVtYmVyIG9mIHRyZWVzIGluIHRoZSBmb3Jlc3QNCnByaW50KHJmX21vZGVsKSAjIFByaW50aW5nIG1ldHJpY3Mgc2NvcmVzDQpgYGANCg0KLSAgICoqVGVzdGluZy4uLioqDQoNCmBgYHtyIHNpemUgPSAic21hbGwifQ0Kc2V0LnNlZWQoMTIzKQ0KcmZfcHJlZGljdGlvbnMgPC0gcHJlZGljdChyZl9tb2RlbCwgbmV3ZGF0YSA9IG5vcm1fdGVzdF9kYXRhKQ0KcmZfcm1zZSA9IG5vcm1hbGl6ZWRfcm1zZShyZl9wcmVkaWN0aW9ucywgdGVzdF95KQ0KcHJpbnQocmZfcm1zZSkNCmBgYA0KDQojIyMjIERlY2lzaW9uIFRyZWUNCg0KLSAgICoqVHJhaW5pbmcuLi4qKg0KDQpgYGB7ciBzaXplID0gInNtYWxsIn0NCmxpYnJhcnkocnBhcnQpDQpzZXQuc2VlZCgxMjMpDQp0cmVlX21vZGVsIDwtIHJwYXJ0KA0KICBmb3JtdWxhID0geSB+IC4sDQogIGRhdGEgPSBub3JtX3RyYWluX2RhdGEsIA0KICBtZXRob2QgPSAiYW5vdmEiLCAjICJhbm92YSIgdG8gc3BlY2lmeSByZWdyZXNzaW9uDQogIGNvbnRyb2wgPSBycGFydC5jb250cm9sKGNwID0gMC4wMSkpICAjIENvbnRyb2wgcGFyYW1ldGVycyAoY29tcGxleGl0eSBwYXJhbWV0ZXIpDQpgYGANCg0KLSAgICoqVGVzdGluZy4uLioqDQoNCmBgYHtyIHNpemUgPSAic21hbGwifQ0Kc2V0LnNlZWQoMTIzKQ0KZHRfcHJlZGljdGlvbnMgPC0gcHJlZGljdCh0cmVlX21vZGVsLCBuZXdkYXRhID0gbm9ybV90ZXN0X2RhdGEpDQpkdF9ybXNlID0gbm9ybWFsaXplZF9ybXNlKGR0X3ByZWRpY3Rpb25zLCB0ZXN0X3kpIA0KcHJpbnQoZHRfcm1zZSkNCmBgYA0KDQojIyMjIEtOTjogSy1OZWFyZXN0IE5laWdoYm9yDQoNCi0gICAqKlRyYWluaW5nLi4uKioNCg0KYGBge3Igc2l6ZSA9ICJzbWFsbCJ9DQpsaWJyYXJ5KGNhcmV0KQ0Kc2V0LnNlZWQoMTIzKQ0Ka25uX21vZGVsIDwtIHRyYWluKCAjIFRyYWluaW5nIHRoZSBLTk4gcmVncmVzc2lvbiBtb2RlbA0KICB4ID0gdHJhaW5feCwgICAgICANCiAgeSA9IHRyYWluX3ksICAgICAgDQogIG1ldGhvZCA9ICJrbm4iLCAgICANCiAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDUpLCAjIENyb3NzLXZhbGlkYXRpb24gc2V0dGluZ3MNCiAgdHVuZUdyaWQgPSBleHBhbmQuZ3JpZChrID0gYygxLCAzLCA1KSkpICAjIEh5cGVycGFyYW1ldGVyIGdyaWQgKGspDQpgYGANCg0KLSAgICoqVGVzdGluZy4uLioqDQoNCmBgYHtyIHNpemUgPSAic21hbGwifQ0Kc2V0LnNlZWQoMTIzKQ0Ka25uX3ByZWRpY3Rpb25zIDwtIHByZWRpY3Qoa25uX21vZGVsLCBuZXdkYXRhID0gdGVzdF94KQ0Ka25uX3Jtc2UgPSBub3JtYWxpemVkX3Jtc2Uoa25uX3ByZWRpY3Rpb25zLCB0ZXN0X3kpDQpwcmludChrbm5fcm1zZSkNCmBgYA0KDQojIyMjIFNWTSBSZWdyZXNzaW9uDQoNCi0gICBOb3cgd2Ugd2lsbCBiZSB0cnlpbmcgdGhlIHJlZ3Jlc3Npb24gdmVyc2lvbiBvZiB0aGUgc3VwcG9ydCB2ZWN0b3IgbWFjaGluZXMgd2Ugd2lsbCBiZSBpbXBsZW1lbnRpbmcgZ3JpZCBzZWFyY2ggZm9yIGh5cGVyLXBhcmFtZXRlciB0dW5pbmcuLi4NCg0KLSAgICoqVHJhaW5pbmcgd2l0aCBoeXBlci1wYXJhbWV0ZXIgdHVuaW5nLi4uKioNCg0KYGBge3Igd2FybmluZz1GQUxTRSwgIHNpemUgPSAic21hbGwifQ0KbGlicmFyeShlMTA3MSkNCnNldC5zZWVkKDQ1KQ0KI1R1bmluZyB0aGUgU1ZNIG1vZGVsDQp0dW5lZF9zdm09dHVuZShzdm0sIHl+LiwgZGF0YT1ub3JtX3RyYWluX2RhdGEsIHJhbmdlcz1saXN0KGVwc2lsb249c2VxKDAsMSwwLjEpLCBjb3N0PWMoMC4wMSwgMC4xLCAxLCAxMCwgMTAwKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2VybmFsID0gYygibGluZWFyIiwgInBvbHlub21pYWwiLCAicmFkaWFsIGJhc2lzIiwgInNpZ21vaWQiKSkpDQpwcmludCh0dW5lZF9zdm0pICMgcGVyZm9ybWFuY2UgbWVhc3VyZSA9IE1TRQ0KYGBgDQoNCi0gICAqKlRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIGFjY3VyYWN5KioNCg0KYGBge3Igc2l6ZSA9ICJzbWFsbCJ9DQpiZXN0X3R1bmVkX3N2bSA9IHR1bmVkX3N2bSRiZXN0Lm1vZGVsDQpzdm1fdHJhaW5fZXJyID0gbm9ybWFsaXplZF9ybXNlKHByZWRpY3QoYmVzdF90dW5lZF9zdm0sIHRyYWluX3gpLCB0cmFpbl95KQ0KcHJpbnQoc3ZtX3RyYWluX2VycikNCmBgYA0KDQotICAgKipUZXN0aW5nLi4uKioNCg0KYGBge3Igc2l6ZSA9ICJzbWFsbCJ9DQpzdm1fcHJlZGljdGlvbnMgPSBwcmVkaWN0KGJlc3RfdHVuZWRfc3ZtLCB0ZXN0X3gpDQpzdm1fcm1zZSA9IG5vcm1hbGl6ZWRfcm1zZShzdm1fcHJlZGljdGlvbnMsIHRlc3RfeSkNCnByaW50KHN2bV9ybXNlKQ0KYGBgDQoNCiMjIyMgQUxMIElOIE9ORTogQmxlbmRlcg0KDQotICAgSW4gdGhpcyBzZWN0aW9uIHdlIGFyZSBnb2luZyB0byBiZSBpbXBsZW1lbnRpbmcgdGhlIGVuc2VtYmxlICoqc3RhY2tpbmcqKiBtZXRob2QgaW52b2x2aW5nIHRocmVlIGJhc2UgbGVhcm5lcnMgYW5kIGEgbWV0YSBsZWFybmVyIHdoaWNoIGxlYXJucyBmcm9tIHRoZSBwcmVkaWN0aW9ucyBvZiB0aGUgYmFzZSBsZWFybmVycy4uLg0KDQotICAgKkxFQVJORVItMTogR3JhZGllbnQgQm9vc3RpbmcgTWFjaGluZSoNCg0KYGBge3Igd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFLCByZXN1bHRzPSdoaWRlJywgIHNpemUgPSAic21hbGwifQ0KbGlicmFyeShoMm8pDQpoMm8uaW5pdCgpICMgSW5pdGlhdGluZyBoMjAgZW52aXJvbm1lbnQNCnNldC5zZWVkKDQyKQ0KbmZvbGRzIDwtIDEwICMgTnVtYmVyIG9mIENWIGZvbGRzICh0byBnZW5lcmF0ZSBsZXZlbC1vbmUgZGF0YSBmb3Igc3RhY2tpbmcpDQpnYm0gPC0gaDJvLmdibSh4ID0gbmFtZXModHJhaW5feCksICMgR3JhZGllbnQgQm9vc3RpbmcgTWFjaGluZQ0KICAgICAgICAgICAgICAgeSA9ICJ5IiwNCiAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lPWFzLmgybyhub3JtX3RyYWluX2RhdGEpLA0KICAgICAgICAgICAgICAgbmZvbGRzID0gbmZvbGRzLA0KICAgICAgICAgICAgICAga2VlcF9jcm9zc192YWxpZGF0aW9uX3ByZWRpY3Rpb25zID0gVFJVRSwNCiAgICAgICAgICAgICAgIHNlZWQgPSA0MikNCmBgYA0KDQotICAgKkxFQVJORVItMjogR2VuZXJhbGl6ZWQgTGluZWFyIE1vZGVsKg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCByZXN1bHRzPSdoaWRlJywgIHNpemUgPSAic21hbGwifQ0Kc2V0LnNlZWQoNDIpDQpnbG0gPC0gaDJvLmdsbSh4ID0gbmFtZXModHJhaW5feCksICMgR2VuZXJhbGl6ZWQgTGluZWFyIE1vZGVsDQogICAgICAgICAgICAgICB5ID0gInkiLA0KICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWU9YXMuaDJvKG5vcm1fdHJhaW5fZGF0YSksDQogICAgICAgICAgICAgICBuZm9sZHMgPSBuZm9sZHMsDQogICAgICAgICAgICAgICBrZWVwX2Nyb3NzX3ZhbGlkYXRpb25fcHJlZGljdGlvbnMgPSBUUlVFLA0KICAgICAgICAgICAgICAgc2VlZCA9IDQyKQ0KYGBgDQoNCi0gICAqTEVBUk5FUi0zOiBGdWxseSBjb25uZWN0ZWQgTmV1cmFsIE5ldHdvcmsqDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHJlc3VsdHM9J2hpZGUnLCBzaXplID0gInNtYWxsIn0NCnNldC5zZWVkKDQyKQ0KZGw8LSBoMm8uZGVlcGxlYXJuaW5nKHggPSBuYW1lcyh0cmFpbl94KSwgIyBGdWxseSBDb25uZWN0ZWQgTmV1cmFsIE5ldHdvcmsNCiAgICAgICAgICAgICAgICAgICAgICB5ID0gInkiLA0KICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lPWFzLmgybyhub3JtX3RyYWluX2RhdGEpLA0KICAgICAgICAgICAgICAgICAgICAgIG5mb2xkcyA9IG5mb2xkcywNCiAgICAgICAgICAgICAgICAgICAgICBrZWVwX2Nyb3NzX3ZhbGlkYXRpb25fcHJlZGljdGlvbnMgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSA0MikNCmBgYA0KDQotICAgKipNRVRBIExFQVJORVI6IFJhbmRvbSBGb3Jlc3QqKg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCByZXN1bHRzPSdoaWRlJywgIHNpemUgPSAic21hbGwifQ0Kc2V0LnNlZWQoNDIpDQojIFRyYWluIGEgc3RhY2tlZCBSYW5kb20gZm9yZXN0IGVuc2VtYmxlIHVzaW5nIHRoZSBwcmV2aW91c2x5IHRyYWluZWQgbW9kZWxzDQplbnNlbWJsZSA8LSBoMm8uc3RhY2tlZEVuc2VtYmxlKHggPSBuYW1lcyh0cmFpbl94KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9ICJ5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWU9YXMuaDJvKG5vcm1fdHJhaW5fZGF0YSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGFsZWFybmVyX2FsZ29yaXRobT0iZHJmIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0YWxlYXJuZXJfbmZvbGRzID0gMzAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhc2VfbW9kZWxzID0gbGlzdChnYm0sIGdsbSwgZGwpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRhbGVhcm5lcl9wYXJhbXMgPSBsaXN0KG50cmVlcyA9IDEwMCwga2VlcF9jcm9zc192YWxpZGF0aW9uX3ByZWRpY3Rpb25zID0gVFJVRSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSA0MikNCmBgYA0KDQotICAgKiptZXRhIGxlYXJuZXIgdHJhaW5pbmcgbWV0cmljcy4uLioqDQoNCmBgYHtyIHNpemUgPSAic21hbGwifQ0KZW5zZW1ibGVAbW9kZWwkdHJhaW5pbmdfbWV0cmljcw0KYGBgDQoNCi0gICAqKm1ldGEgbGVhcm5lciB2YWxpZGF0aW9uIHJtc2UgY3VydmUuLi4qKg0KDQpgYGB7ciBmaWcuaGVpZ2h0PTIsIGZpZy53aWR0aD00LCAgc2l6ZSA9ICJzbWFsbCJ9DQpybXNlX2RmID0gYXMuZGF0YS5mcmFtZSh0KGFzLmRhdGEuZnJhbWUoZW5zZW1ibGVAbW9kZWwkY3Jvc3NfdmFsaWRhdGlvbl9tZXRyaWNzX3N1bW1hcnkpWzYsMzozMl0pKQ0KZ2dwbG90KHJtc2VfZGYsIGFlcyh4ID0gaW5kZXgocm1zZV9kZiksIHkgPSBybXNlKSkgKw0KICBnZW9tX2xpbmUoKSArDQogIGdlb21fcG9pbnQoKSArDQogIGxhYnMoeCA9ICJDViIsIHkgPSAiUk1TRSIsIHRpdGxlID0gIlJNU0UgVmFsdWVzIGFjcm9zcyBDcm9zcy1WYWxpZGF0aW9uIEZvbGRzIikNCmBgYA0KDQotICAgKipUZXN0aW5nIGJhc2UgbGVhcm5lcnMgcGVyZm9ybWFuY2UuLi4qKg0KDQpgYGB7ciBzaXplID0gInNtYWxsIn0NCnNldC5zZWVkKDQ1KQ0KaDIwX3Rlc3QgPSBhcy5oMm8obm9ybV90ZXN0X2RhdGEpICMgZGF0YSB0byBIMk8gZGF0YQ0KZ2JtX3BlcmYgPC0gaDJvLnBlcmZvcm1hbmNlKGdibSwgbmV3ZGF0YSA9IGgyMF90ZXN0KQ0KZ2xtX3BlcmYgPC0gaDJvLnBlcmZvcm1hbmNlKGdsbSwgbmV3ZGF0YSA9IGgyMF90ZXN0KQ0KZGxfcGVyZiA8LSBoMm8ucGVyZm9ybWFuY2UoZGwsIG5ld2RhdGEgPSBoMjBfdGVzdCkNCnByaW50KGdibV9wZXJmKQ0KcHJpbnQoZ2xtX3BlcmYpDQpwcmludChkbF9wZXJmKQ0KYGBgDQoNCi0gICAqKm1ldGEgbGVhcm5lciBwZXJmb3JtYW5jZSBvbiB0ZXN0IGRhdGEuLi4qKg0KDQpgYGB7ciBzaXplID0gInNtYWxsIn0NCnNldC5zZWVkKDQ1KQ0KbWV0YV9wZXJmIDwtIGgyby5wZXJmb3JtYW5jZShlbnNlbWJsZSwgbmV3ZGF0YSA9IGgyMF90ZXN0KQ0KcHJpbnQobWV0YV9wZXJmKQ0KYGBgDQoNCiMjIyBUZXN0IFJlc3VsdHMgU3VtbWFyeQ0KDQotICAgTm93IHRvIHN1bSB1cCBhbGwgdGhlIHJlc3VsdHMgb2Ygb3VyIHByb2plY3Qgd2UgYXJlIGdvaW5nIHRvIG1ha2UgYSB0YWJsZSB3aXRoIGFsbCB0aGUgbW9kZWxzJyBSTVNFIHNjb3JlcyBvbiB0aGUgdGVzdCBkYXRhIGFuZCBjb21wYXJlIHRoZW0uLi4NCg0KYGBge3Igc2l6ZSA9ICJzbWFsbCJ9DQplbnNfcm1zZSA9IG1ldGFfcGVyZkBtZXRyaWNzJFJNU0UvKG1heCh0ZXN0X3kpIC0gbWluKHRlc3RfeSkpDQpybXNlX2RmID0gZGF0YS5mcmFtZSgiTW9kZWwiID0gYygiTXVsdGlwbGUgTGluZWFyIFJlZ3Jlc3Npb24iLCAiUmFuZG9tIEZvcmVzdCIsICJEZWNpc2lvbiBUcmVlIiwgIkstTmVhcmVzdCBOZWlnaGJvcnMiLCAiU3VwcG9ydCBWZWN0b3IgTWFjaGluZSBSZWdyZXNzaW9uIiwgIlN0YWNraW5nIChCbGVuZGVyKSIpLA0KICAgICAgICAgICAgICAgICJOb3JtYWxpemVkIFJNU0UiID0gYyhtbHJfcm1zZSwgcmZfcm1zZSwgZHRfcm1zZSwga25uX3Jtc2UsIHN2bV9ybXNlLCBlbnNfcm1zZSksIGNoZWNrLm5hbWVzPUZBTFNFKQ0Kcm1zZV9kZiA8LSBybXNlX2RmW29yZGVyKHJtc2VfZGYkYE5vcm1hbGl6ZWQgUk1TRWAsIGRlY3JlYXNpbmcgPSBUUlVFKSwgXQ0KcHJpbnQocm1zZV9kZikNCmBgYA0KDQp8ICpNb2RlbCogICAgICAgICAgICB8ICpOb3JtYWxpemVkIFJNU0UqIHwNCnwtLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tfA0KfCBTdGFja2luZyAoQmxlbmRlcikgfCAkMC4wMjUgXHBtIDAuMDA1JCB8DQoNCjogQmVzdCBQZXJmb3JtaW5nIE1vZGVsDQoNCiMjIENvbmNsdXNpb24NCg0KQXMgc2hvd24gaW4gdGhlIHJlc3VsdHMgc3VtbWFyeSwgdGhlIGFuc3dlciB0byBvdXIgcmVzZWFyY2ggcXVlc3Rpb24gaXMuLi4NCg0KKioqIlRoZSBiZXN0IG91dCBvZiBhbGwgdGhlIHRlc3RlZCBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobSBpcyB0aGUgc3RhY2tpbmcgbWV0aG9kIGNhbGxlZCBCbGVuZGVyIG1vcmUgY29tbW9ubHkiKioqDQoNCndoaWNoIG1ha2VzIHNlbnNlIGFzIGl0IGFsaWducyB3aXRoIHRoZSBpbml0aWFsIGFzc3VtcHRpb24gb2YgdGhlIGVuc2VtYmxlIG1ldGhvZCB3aGljaCBpbXBseSB0aGF0IGNvbWJpbmluZyBtb3JlIHRoYW4gb25lIGxlYXJuZXIgcHJvZHVjZXMgYmV0dGVyIHByZWRpY3Rpb25zLg0K