getwd()
churndata <- read.csv("Churn Data For Tree.csv", stringsAsFactors = TRUE)
View(churndata)
summary(churndata)
str(churndata)
RNGversion("3.5.2"); set.seed(123)
train_sample <- sample( 10000, 9000)
str(train_sample)

#We begin this model by loading the "Churn Data For Tree.csv" dataset into a data frame named churndata. The dataset contains information related to customer churn. After some data exploration, the data set is then split into training data and testing data to use for the model. 

churn_train <- churndata[train_sample, ]
churn_test <- churndata[-train_sample, ]

prop.table(table(churn_train$Exited))
prop.table(table(churn_test$Exited))

#When looking at these tables, we can see that in the training dataset, there were 20.2% of customer churn and there was 21.5% in the testing data.

table(churndata$Exited)
install.packages('libcoin', dependencies = T)
install.packages('C50', dependencies = T)
install.packages('inum')
library(inum)
library(libcoin)
library(C50)

churn_model <- C5.0(churn_train[-11], churn_train$Exited)

#After the appropriate packages were installed in order to start creating the model, the c5.0 algorithm was chosen in creating the decision tree. The model is created using the training data before using on the testing data. After this was done, a summary of the model was evaluated. After reviewing the results of the model, we can see that there is a 12.6% error rate overall. 

churn_model

summary(churn_model)

churn_predict <- predict(churn_model, churn_test)
library(gmodels)
CrossTable(churn_test$Exited, churn_predict, prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE, dnn = c('actual churn', 'predicted churn'))

#After running the original model on the testing data next, we can see that our model accurately predicted 85.4%. This is a good percentage for this model but there could still be some room for improvement and so pruning the tree was done next to see if there could be improved results. 

pruned_churn_model <- C5.0(churn_train[-11], churn_train$Exited, control = C5.0Control(winnow = TRUE))
pruned_churn_pred <- predict(pruned_churn_model, newdata = churn_test)
CrossTable(churn_test$Exited, pruned_churn_pred, prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE, dnn = c('actual churn', 'predicted churn'))

#Pruning the tree did not give us improvement in our results taking us from a 85.4% accuracy to a 85% accuracy overall. While there was only a slight decrease in the accuracy results, pruning the tree did not seem to be effective in this case. 

churn_boost10 <- C5.0(churn_train[-11], churn_train$Exited, trials = 15)
churn_boost10

summary(churn_boost10)

churn_boost_pred10 <- predict(churn_boost10, churn_test)
CrossTable(churn_test$Exited, churn_boost_pred10, prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE, dnn = c('actual churn', 'predicted churn'))

#A boost was then applied to the training model and after review of the results, it was applied to the testing data to see if we could achieve any increase improvements from our original model and there was a slight improvement up to 85.7% for the boosted model. From here, I was able to confirm my results with a confusion matrix showing these results while also taking a look at the accuracy level, precision level, and F1-score for this model. These results were then charted.While the accuracy level is not bad, the F1-score is only okay and could use some improvement with the precision score of 74% also needing some improvement as well. 

library(caret)
library(ggplot2)

confusion_matrix <- table(churn_test$Exited, churn_boost_pred10)
print(confusion_matrix)

true_positives <- confusion_matrix[2, 2]
true_negatives <- confusion_matrix[1, 1]
false_positives <- confusion_matrix[1, 2]
false_negatives <- confusion_matrix[2, 1]

accuracy <- (true_positives + true_negatives) / sum(confusion_matrix)
precision <- true_positives / (true_positives + false_positives)
recall <- true_positives / (true_positives + false_negatives)
f1_score <- 2 * (precision * recall) / (precision + recall)

cat("Accuracy:", accuracy, "\n")
cat("Precision:", precision, "\n")
cat("Recall:", recall, "\n")
cat("F1-Score:", f1_score, "\n")

metrics <- c("Accuracy", "Precision", "Recall", "F1-Score")
values <- c(0.857, 0.74, 0.5162791, 0.6082192)
performance_data <- data.frame(Metric = metrics, Value = values)

ggplot(performance_data, aes(x = Metric, y = Value, group = 1)) +
  geom_line(color = "blue") +
  geom_point(color = "blue", size = 3) +
  labs(title = "Model Performance Metrics",
       x = "Metric",
       y = "Value") +
  theme_minimal()


install.packages("pROC")
library(pROC)


roc_obj <- roc(churn_test$Exited, as.numeric(churn_boost_pred10))

plot(roc_obj, main = "ROC Curve",
     xlab = "False Positive Rate (FPR)", ylab = "True Positive Rate (TPR)",
     col = "blue", lwd = 2)

abline(a = 0, b = 1, col = "red", lwd = 1, lty = 2)
auc_value <- auc(roc_obj)
text(0.5, 0.3, paste("AUC =", round(auc_value, 2)), adj = c(0.5, 0.5), col = "blue")




getwd()
churndata_regress <- read.csv("Churn Data for Project.csv", stringsAsFactors = TRUE)
View(churndata_regress)
summary(churndata_regress)
summary(churndata_regress$Exited)

#When first starting to create the regression model for analyzing customer churn for the bank, I went over a summary of the data itself along with viewing the amount of customers that had churned from the bank. The amount of those that had churned was a total of 20.37%. I also created a table for both those that had exited, or churned from the bank and I wanted to take a look at how many people were located in each country to see if there was more of a chance of customer churn in specific countries due to the the mount of people that banked from each country. 

str(churndata_regress)
table(churndata_regress$Exited)
table(churndata_regress$Geography)

library("psych")


pairs.panels(churndata_regress[c("Age", "CreditScore", "Exited", "NumOfProducts", "Balance")])
plot(churndata_regress$CreditScore ~ churndata_regress$Exited)

#When viewing the panel, we can see that there is some correlation between those who have chosen to churn or exit and the number of products and balance within their account. We can tell this from the oval shape. We can also see that there is some slight correlation between those exiting and churning and the age of the customers. 

library(dplyr)

categorical <- c("Gender", "Geography")

total_churn_rate <- mean(churndata_regress$Exited)
for (c in categorical) {
  df_group <- churndata_regress %>%
    group_by(!!sym(c)) %>%
    summarise(
      mean_churn = mean(Exited),
      count = n()
    ) %>%
    mutate(
      diff = mean_churn - total_churn_rate,
      risk = mean_churn / total_churn_rate
    )
  
  print(paste("Analysis for", c))
  print(df_group)
  print("\n\n")
}

#The risk associated with those who may potentially churn when looking at gender and location, we can see that there is a higher risk for females to churn and that those living in Germany are more likely to churn compared to those living in Spain or France. 

library(rlang)
library(ggplot2)

set.seed(1234)
trainIndex <- createDataPartition(churndata_regress$Exited, p = 0.8, list = FALSE, times = 1)
training_data <- churndata_regress[ trainIndex,]
testing_data  <- churndata_regress[-trainIndex,]

#Here is where the data was split into training data and testing data before being able to create the model. 

churn_logmodel = glm(Exited ~ ., data = training_data, family = "binomial")
summary(churn_logmodel)

#When choosing the type of regression model, I initially went with a multiple linear regression model and was having a difficult time achieving the results I was aiming to achieve. Because of this, I chose to go with a logistical regression model working with the training data first. After reviewing the results of the model, I chose to eliminate Estimated Salary, Tenure, And Has Credit Card from the model due to it not having a significant impact on whether or not a customer will churn or not. 

churn_logmodel2 = glm(Exited ~ CreditScore + Age + Balance + Gender + NumOfProducts + IsActiveMember + Geography, data = training_data, family = "binomial") 
summary(churn_logmodel2)

#After reviewing the results of the first model, I created the second model and ran the summary on that and decided to keep this model as each of the independent variables that were kepy for this model held significance in customer churn for this bank. 

prediction <- predict(churn_logmodel2,testing_data,type="response")
churn_stop <- ifelse(prediction>=0.50, 1,0)
results_matrix <- confusionMatrix(as.factor(testing_data$Exited),as.factor(churn_stop),positive ='1')
results_matrix

#Running this model against the testing data gave me an accuracy level of 81.95%. While this could have been better, this is not necessarily a poor model. The sensitivity is at 61% and the specificity is at 83%. The area under the curve is at 77.8% which is decent result. 

library(pROC)
predicted_probs <- predict(churn_logmodel2, testing_data, type = "response")
roc_obj <- roc(testing_data$Exited, predicted_probs)
print(roc_obj$auc)
roc_plot <- ggroc(roc_obj)
print(roc_plot)



getwd()
churndata <- read.csv("Churn Data for Project.csv", stringsAsFactors = TRUE)
View(churndata)
summary(churndata)
summary(churndata$Exited)
table(churndata$Geography)
table(churndata$Gender)
table(churndata$Tenure)
str(churndata)
dim(churndata)
head(churndata)
tail(churndata)
names(churndata)
class(churndata)
par(mar = c(0.1, 0.1, 0.1, 0.1))
pairs(churndata)

#When creating the neural network, I first chose to load the data and then examine the data further making sure there were no missing values in preparation to normalize the data before creating my neural network. 

dummy_matrix <- model.matrix( ~ Gender -1, data = churndata)
churndata <- cbind(churndata, dummy_matrix)
dummy_matrix_1 <- model.matrix( ~ Geography -1, data = churndata)
churndata <- cbind(churndata, dummy_matrix_1)
dummy_matrix_2 <- model.matrix ( ~Exited -1, data = churndata)
churndata <- cbind(churndata, dummy_matrix_2)

#For the Gender, Geography, and Exited columns in the dataset, dummy variables needed to be created in order to normalize the data set.

columns_to_normalize <- c(1:5, 7:9, 11:16)
min_max_norm <- function(x) {
  (x-min(x)) / (max(x) - min(x))
}
churndata_norm <- as.data.frame(lapply(churndata[, columns_to_normalize], min_max_norm))
head(churndata_norm)
summary(churndata_norm$Exited)

#After normalizing the data for the model, I examined the data to make sure that everything had been normalized and was ready and prepared to be separated into testing and training datasets. As you can see, the training data was set to use 9000 of the 10000 and the testing was to use the remaining 1000 values. 

churndata_train <- churndata_norm[1:9000, ]
churndata_test <- churndata_norm[9001:10000, ]

install.packages("neuralnet")
library(neuralnet)

set.seed(1234)
churndata_model <- neuralnet(Exited ~ CreditScore + Age + Balance + EstimatedSalary + Tenure + NumOfProducts + HasCrCard + IsActiveMember + GenderFemale + GenderMale + GeographyFrance + GeographyGermany + GeographySpain, data = churndata_train)
plot(churndata_model)

#This first neural network model was set to include all of the data and did not include any hidden networks. The results from this first model was very poor with an accuracy rate of only 38.4%. This was really surprising to me with how poorly it performed and so I decided to add some hidden layers to my next model. 

model_results <- compute(churndata_model, churndata_test[1:14])
predicted_strength <- model_results$net.result
cor(predicted_strength, churndata_test$Exited)

set.seed(1234)
churndata_model2 <- neuralnet(Exited ~ CreditScore + Age + Balance + EstimatedSalary + Tenure + NumOfProducts + HasCrCard + IsActiveMember + GenderFemale + GenderMale + GeographyFrance + GeographyGermany + GeographySpain, data = churndata_train, hidden = 5)
plot(churndata_model2)

model_results2 <- compute(churndata_model2, churndata_test[1:14])
predicted_strength <- model_results2$net.result
cor(predicted_strength, churndata_test$Exited)

#After realizing that the first model performed so poorly, I decided to add hidden layers into my second neural network in hopes of making great improvements in my results. While it did boost my results up to 55.6%, it still was a far cry from being a model that could be used in predicting customer churn. The last model I created, I decided to use an activation function known as a rectifier. For this particular model, I chose to go with a softplus activation function. 

softplus <- function(x) { log(1 + exp(x)) }

set.seed(12345)
churndata_model3 <- neuralnet(Exited ~ CreditScore + Age + Balance + EstimatedSalary + Tenure + NumOfProducts + HasCrCard + IsActiveMember + GenderFemale + GenderMale + GeographyFrance + GeographyGermany + GeographySpain, data = churndata_train, hidden = c( 5, 1) , act.fct = softplus)

test_input <- as.matrix(churndata_test[, 1:14])
model_results3 <- compute(churndata_model3, test_input)
predicted_strength <- model_results3$net.result
cor(predicted_strength, churndata_test$Exited)

#After creating the softplus activation function for this third and final model, hidden layers and neurons were also added in order to see in more improvement could have been made. Unfortunately, every time I tried to run this code, it would run for about 30-40 minutes before my system would crash and I was unable to see if this would actually improve the models performance. Because of this, I would have to go with the results achieved from my second regression model. 




#After exploring all three models and their results, I believe that the decision tree or the logistic regression model would be best to use in predicting customer churn. While the decision tree can be helpful due to its high accuracy, the regression model was a great model in helping us to understand reasoning as to why a person may churn from the bank such as age. Those more likely to churn were the younger and older customers and so that may be a good indication of something that could be addressed marketing wise in order to maintain those customers business. 

Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I.

When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Ctrl+Shift+K to preview the HTML file).

The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.

LS0tDQp0aXRsZTogIkJhbmsgQ3VzdG9tZXIgQ2h1cm4gVXNpbmcgUiINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCg0KDQpgYGB7cn0NCmdldHdkKCkNCmNodXJuZGF0YSA8LSByZWFkLmNzdigiQ2h1cm4gRGF0YSBGb3IgVHJlZS5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gVFJVRSkNClZpZXcoY2h1cm5kYXRhKQ0Kc3VtbWFyeShjaHVybmRhdGEpDQpzdHIoY2h1cm5kYXRhKQ0KUk5HdmVyc2lvbigiMy41LjIiKTsgc2V0LnNlZWQoMTIzKQ0KdHJhaW5fc2FtcGxlIDwtIHNhbXBsZSggMTAwMDAsIDkwMDApDQpzdHIodHJhaW5fc2FtcGxlKQ0KDQojV2UgYmVnaW4gdGhpcyBtb2RlbCBieSBsb2FkaW5nIHRoZSAiQ2h1cm4gRGF0YSBGb3IgVHJlZS5jc3YiIGRhdGFzZXQgaW50byBhIGRhdGEgZnJhbWUgbmFtZWQgY2h1cm5kYXRhLiBUaGUgZGF0YXNldCBjb250YWlucyBpbmZvcm1hdGlvbiByZWxhdGVkIHRvIGN1c3RvbWVyIGNodXJuLiBBZnRlciBzb21lIGRhdGEgZXhwbG9yYXRpb24sIHRoZSBkYXRhIHNldCBpcyB0aGVuIHNwbGl0IGludG8gdHJhaW5pbmcgZGF0YSBhbmQgdGVzdGluZyBkYXRhIHRvIHVzZSBmb3IgdGhlIG1vZGVsLiANCg0KY2h1cm5fdHJhaW4gPC0gY2h1cm5kYXRhW3RyYWluX3NhbXBsZSwgXQ0KY2h1cm5fdGVzdCA8LSBjaHVybmRhdGFbLXRyYWluX3NhbXBsZSwgXQ0KDQpwcm9wLnRhYmxlKHRhYmxlKGNodXJuX3RyYWluJEV4aXRlZCkpDQpwcm9wLnRhYmxlKHRhYmxlKGNodXJuX3Rlc3QkRXhpdGVkKSkNCg0KI1doZW4gbG9va2luZyBhdCB0aGVzZSB0YWJsZXMsIHdlIGNhbiBzZWUgdGhhdCBpbiB0aGUgdHJhaW5pbmcgZGF0YXNldCwgdGhlcmUgd2VyZSAyMC4yJSBvZiBjdXN0b21lciBjaHVybiBhbmQgdGhlcmUgd2FzIDIxLjUlIGluIHRoZSB0ZXN0aW5nIGRhdGEuDQoNCnRhYmxlKGNodXJuZGF0YSRFeGl0ZWQpDQppbnN0YWxsLnBhY2thZ2VzKCdsaWJjb2luJywgZGVwZW5kZW5jaWVzID0gVCkNCmluc3RhbGwucGFja2FnZXMoJ0M1MCcsIGRlcGVuZGVuY2llcyA9IFQpDQppbnN0YWxsLnBhY2thZ2VzKCdpbnVtJykNCmxpYnJhcnkoaW51bSkNCmxpYnJhcnkobGliY29pbikNCmxpYnJhcnkoQzUwKQ0KDQpjaHVybl9tb2RlbCA8LSBDNS4wKGNodXJuX3RyYWluWy0xMV0sIGNodXJuX3RyYWluJEV4aXRlZCkNCg0KI0FmdGVyIHRoZSBhcHByb3ByaWF0ZSBwYWNrYWdlcyB3ZXJlIGluc3RhbGxlZCBpbiBvcmRlciB0byBzdGFydCBjcmVhdGluZyB0aGUgbW9kZWwsIHRoZSBjNS4wIGFsZ29yaXRobSB3YXMgY2hvc2VuIGluIGNyZWF0aW5nIHRoZSBkZWNpc2lvbiB0cmVlLiBUaGUgbW9kZWwgaXMgY3JlYXRlZCB1c2luZyB0aGUgdHJhaW5pbmcgZGF0YSBiZWZvcmUgdXNpbmcgb24gdGhlIHRlc3RpbmcgZGF0YS4gQWZ0ZXIgdGhpcyB3YXMgZG9uZSwgYSBzdW1tYXJ5IG9mIHRoZSBtb2RlbCB3YXMgZXZhbHVhdGVkLiBBZnRlciByZXZpZXdpbmcgdGhlIHJlc3VsdHMgb2YgdGhlIG1vZGVsLCB3ZSBjYW4gc2VlIHRoYXQgdGhlcmUgaXMgYSAxMi42JSBlcnJvciByYXRlIG92ZXJhbGwuIA0KDQpjaHVybl9tb2RlbA0KDQpzdW1tYXJ5KGNodXJuX21vZGVsKQ0KDQpjaHVybl9wcmVkaWN0IDwtIHByZWRpY3QoY2h1cm5fbW9kZWwsIGNodXJuX3Rlc3QpDQpsaWJyYXJ5KGdtb2RlbHMpDQpDcm9zc1RhYmxlKGNodXJuX3Rlc3QkRXhpdGVkLCBjaHVybl9wcmVkaWN0LCBwcm9wLmNoaXNxID0gRkFMU0UsIHByb3AuYyA9IEZBTFNFLCBwcm9wLnIgPSBGQUxTRSwgZG5uID0gYygnYWN0dWFsIGNodXJuJywgJ3ByZWRpY3RlZCBjaHVybicpKQ0KDQojQWZ0ZXIgcnVubmluZyB0aGUgb3JpZ2luYWwgbW9kZWwgb24gdGhlIHRlc3RpbmcgZGF0YSBuZXh0LCB3ZSBjYW4gc2VlIHRoYXQgb3VyIG1vZGVsIGFjY3VyYXRlbHkgcHJlZGljdGVkIDg1LjQlLiBUaGlzIGlzIGEgZ29vZCBwZXJjZW50YWdlIGZvciB0aGlzIG1vZGVsIGJ1dCB0aGVyZSBjb3VsZCBzdGlsbCBiZSBzb21lIHJvb20gZm9yIGltcHJvdmVtZW50IGFuZCBzbyBwcnVuaW5nIHRoZSB0cmVlIHdhcyBkb25lIG5leHQgdG8gc2VlIGlmIHRoZXJlIGNvdWxkIGJlIGltcHJvdmVkIHJlc3VsdHMuIA0KDQpwcnVuZWRfY2h1cm5fbW9kZWwgPC0gQzUuMChjaHVybl90cmFpblstMTFdLCBjaHVybl90cmFpbiRFeGl0ZWQsIGNvbnRyb2wgPSBDNS4wQ29udHJvbCh3aW5ub3cgPSBUUlVFKSkNCnBydW5lZF9jaHVybl9wcmVkIDwtIHByZWRpY3QocHJ1bmVkX2NodXJuX21vZGVsLCBuZXdkYXRhID0gY2h1cm5fdGVzdCkNCkNyb3NzVGFibGUoY2h1cm5fdGVzdCRFeGl0ZWQsIHBydW5lZF9jaHVybl9wcmVkLCBwcm9wLmNoaXNxID0gRkFMU0UsIHByb3AuYyA9IEZBTFNFLCBwcm9wLnIgPSBGQUxTRSwgZG5uID0gYygnYWN0dWFsIGNodXJuJywgJ3ByZWRpY3RlZCBjaHVybicpKQ0KDQojUHJ1bmluZyB0aGUgdHJlZSBkaWQgbm90IGdpdmUgdXMgaW1wcm92ZW1lbnQgaW4gb3VyIHJlc3VsdHMgdGFraW5nIHVzIGZyb20gYSA4NS40JSBhY2N1cmFjeSB0byBhIDg1JSBhY2N1cmFjeSBvdmVyYWxsLiBXaGlsZSB0aGVyZSB3YXMgb25seSBhIHNsaWdodCBkZWNyZWFzZSBpbiB0aGUgYWNjdXJhY3kgcmVzdWx0cywgcHJ1bmluZyB0aGUgdHJlZSBkaWQgbm90IHNlZW0gdG8gYmUgZWZmZWN0aXZlIGluIHRoaXMgY2FzZS4gDQoNCmNodXJuX2Jvb3N0MTAgPC0gQzUuMChjaHVybl90cmFpblstMTFdLCBjaHVybl90cmFpbiRFeGl0ZWQsIHRyaWFscyA9IDE1KQ0KY2h1cm5fYm9vc3QxMA0KDQpzdW1tYXJ5KGNodXJuX2Jvb3N0MTApDQoNCmNodXJuX2Jvb3N0X3ByZWQxMCA8LSBwcmVkaWN0KGNodXJuX2Jvb3N0MTAsIGNodXJuX3Rlc3QpDQpDcm9zc1RhYmxlKGNodXJuX3Rlc3QkRXhpdGVkLCBjaHVybl9ib29zdF9wcmVkMTAsIHByb3AuY2hpc3EgPSBGQUxTRSwgcHJvcC5jID0gRkFMU0UsIHByb3AuciA9IEZBTFNFLCBkbm4gPSBjKCdhY3R1YWwgY2h1cm4nLCAncHJlZGljdGVkIGNodXJuJykpDQoNCiNBIGJvb3N0IHdhcyB0aGVuIGFwcGxpZWQgdG8gdGhlIHRyYWluaW5nIG1vZGVsIGFuZCBhZnRlciByZXZpZXcgb2YgdGhlIHJlc3VsdHMsIGl0IHdhcyBhcHBsaWVkIHRvIHRoZSB0ZXN0aW5nIGRhdGEgdG8gc2VlIGlmIHdlIGNvdWxkIGFjaGlldmUgYW55IGluY3JlYXNlIGltcHJvdmVtZW50cyBmcm9tIG91ciBvcmlnaW5hbCBtb2RlbCBhbmQgdGhlcmUgd2FzIGEgc2xpZ2h0IGltcHJvdmVtZW50IHVwIHRvIDg1LjclIGZvciB0aGUgYm9vc3RlZCBtb2RlbC4gRnJvbSBoZXJlLCBJIHdhcyBhYmxlIHRvIGNvbmZpcm0gbXkgcmVzdWx0cyB3aXRoIGEgY29uZnVzaW9uIG1hdHJpeCBzaG93aW5nIHRoZXNlIHJlc3VsdHMgd2hpbGUgYWxzbyB0YWtpbmcgYSBsb29rIGF0IHRoZSBhY2N1cmFjeSBsZXZlbCwgcHJlY2lzaW9uIGxldmVsLCBhbmQgRjEtc2NvcmUgZm9yIHRoaXMgbW9kZWwuIFRoZXNlIHJlc3VsdHMgd2VyZSB0aGVuIGNoYXJ0ZWQuV2hpbGUgdGhlIGFjY3VyYWN5IGxldmVsIGlzIG5vdCBiYWQsIHRoZSBGMS1zY29yZSBpcyBvbmx5IG9rYXkgYW5kIGNvdWxkIHVzZSBzb21lIGltcHJvdmVtZW50IHdpdGggdGhlIHByZWNpc2lvbiBzY29yZSBvZiA3NCUgYWxzbyBuZWVkaW5nIHNvbWUgaW1wcm92ZW1lbnQgYXMgd2VsbC4gDQoNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KGdncGxvdDIpDQoNCmNvbmZ1c2lvbl9tYXRyaXggPC0gdGFibGUoY2h1cm5fdGVzdCRFeGl0ZWQsIGNodXJuX2Jvb3N0X3ByZWQxMCkNCnByaW50KGNvbmZ1c2lvbl9tYXRyaXgpDQoNCnRydWVfcG9zaXRpdmVzIDwtIGNvbmZ1c2lvbl9tYXRyaXhbMiwgMl0NCnRydWVfbmVnYXRpdmVzIDwtIGNvbmZ1c2lvbl9tYXRyaXhbMSwgMV0NCmZhbHNlX3Bvc2l0aXZlcyA8LSBjb25mdXNpb25fbWF0cml4WzEsIDJdDQpmYWxzZV9uZWdhdGl2ZXMgPC0gY29uZnVzaW9uX21hdHJpeFsyLCAxXQ0KDQphY2N1cmFjeSA8LSAodHJ1ZV9wb3NpdGl2ZXMgKyB0cnVlX25lZ2F0aXZlcykgLyBzdW0oY29uZnVzaW9uX21hdHJpeCkNCnByZWNpc2lvbiA8LSB0cnVlX3Bvc2l0aXZlcyAvICh0cnVlX3Bvc2l0aXZlcyArIGZhbHNlX3Bvc2l0aXZlcykNCnJlY2FsbCA8LSB0cnVlX3Bvc2l0aXZlcyAvICh0cnVlX3Bvc2l0aXZlcyArIGZhbHNlX25lZ2F0aXZlcykNCmYxX3Njb3JlIDwtIDIgKiAocHJlY2lzaW9uICogcmVjYWxsKSAvIChwcmVjaXNpb24gKyByZWNhbGwpDQoNCmNhdCgiQWNjdXJhY3k6IiwgYWNjdXJhY3ksICJcbiIpDQpjYXQoIlByZWNpc2lvbjoiLCBwcmVjaXNpb24sICJcbiIpDQpjYXQoIlJlY2FsbDoiLCByZWNhbGwsICJcbiIpDQpjYXQoIkYxLVNjb3JlOiIsIGYxX3Njb3JlLCAiXG4iKQ0KDQptZXRyaWNzIDwtIGMoIkFjY3VyYWN5IiwgIlByZWNpc2lvbiIsICJSZWNhbGwiLCAiRjEtU2NvcmUiKQ0KdmFsdWVzIDwtIGMoMC44NTcsIDAuNzQsIDAuNTE2Mjc5MSwgMC42MDgyMTkyKQ0KcGVyZm9ybWFuY2VfZGF0YSA8LSBkYXRhLmZyYW1lKE1ldHJpYyA9IG1ldHJpY3MsIFZhbHVlID0gdmFsdWVzKQ0KDQpnZ3Bsb3QocGVyZm9ybWFuY2VfZGF0YSwgYWVzKHggPSBNZXRyaWMsIHkgPSBWYWx1ZSwgZ3JvdXAgPSAxKSkgKw0KICBnZW9tX2xpbmUoY29sb3IgPSAiYmx1ZSIpICsNCiAgZ2VvbV9wb2ludChjb2xvciA9ICJibHVlIiwgc2l6ZSA9IDMpICsNCiAgbGFicyh0aXRsZSA9ICJNb2RlbCBQZXJmb3JtYW5jZSBNZXRyaWNzIiwNCiAgICAgICB4ID0gIk1ldHJpYyIsDQogICAgICAgeSA9ICJWYWx1ZSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCg0KaW5zdGFsbC5wYWNrYWdlcygicFJPQyIpDQpsaWJyYXJ5KHBST0MpDQoNCg0Kcm9jX29iaiA8LSByb2MoY2h1cm5fdGVzdCRFeGl0ZWQsIGFzLm51bWVyaWMoY2h1cm5fYm9vc3RfcHJlZDEwKSkNCg0KcGxvdChyb2Nfb2JqLCBtYWluID0gIlJPQyBDdXJ2ZSIsDQogICAgIHhsYWIgPSAiRmFsc2UgUG9zaXRpdmUgUmF0ZSAoRlBSKSIsIHlsYWIgPSAiVHJ1ZSBQb3NpdGl2ZSBSYXRlIChUUFIpIiwNCiAgICAgY29sID0gImJsdWUiLCBsd2QgPSAyKQ0KDQphYmxpbmUoYSA9IDAsIGIgPSAxLCBjb2wgPSAicmVkIiwgbHdkID0gMSwgbHR5ID0gMikNCmF1Y192YWx1ZSA8LSBhdWMocm9jX29iaikNCnRleHQoMC41LCAwLjMsIHBhc3RlKCJBVUMgPSIsIHJvdW5kKGF1Y192YWx1ZSwgMikpLCBhZGogPSBjKDAuNSwgMC41KSwgY29sID0gImJsdWUiKQ0KDQoNCg0KDQpnZXR3ZCgpDQpjaHVybmRhdGFfcmVncmVzcyA8LSByZWFkLmNzdigiQ2h1cm4gRGF0YSBmb3IgUHJvamVjdC5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gVFJVRSkNClZpZXcoY2h1cm5kYXRhX3JlZ3Jlc3MpDQpzdW1tYXJ5KGNodXJuZGF0YV9yZWdyZXNzKQ0Kc3VtbWFyeShjaHVybmRhdGFfcmVncmVzcyRFeGl0ZWQpDQoNCiNXaGVuIGZpcnN0IHN0YXJ0aW5nIHRvIGNyZWF0ZSB0aGUgcmVncmVzc2lvbiBtb2RlbCBmb3IgYW5hbHl6aW5nIGN1c3RvbWVyIGNodXJuIGZvciB0aGUgYmFuaywgSSB3ZW50IG92ZXIgYSBzdW1tYXJ5IG9mIHRoZSBkYXRhIGl0c2VsZiBhbG9uZyB3aXRoIHZpZXdpbmcgdGhlIGFtb3VudCBvZiBjdXN0b21lcnMgdGhhdCBoYWQgY2h1cm5lZCBmcm9tIHRoZSBiYW5rLiBUaGUgYW1vdW50IG9mIHRob3NlIHRoYXQgaGFkIGNodXJuZWQgd2FzIGEgdG90YWwgb2YgMjAuMzclLiBJIGFsc28gY3JlYXRlZCBhIHRhYmxlIGZvciBib3RoIHRob3NlIHRoYXQgaGFkIGV4aXRlZCwgb3IgY2h1cm5lZCBmcm9tIHRoZSBiYW5rIGFuZCBJIHdhbnRlZCB0byB0YWtlIGEgbG9vayBhdCBob3cgbWFueSBwZW9wbGUgd2VyZSBsb2NhdGVkIGluIGVhY2ggY291bnRyeSB0byBzZWUgaWYgdGhlcmUgd2FzIG1vcmUgb2YgYSBjaGFuY2Ugb2YgY3VzdG9tZXIgY2h1cm4gaW4gc3BlY2lmaWMgY291bnRyaWVzIGR1ZSB0byB0aGUgdGhlIG1vdW50IG9mIHBlb3BsZSB0aGF0IGJhbmtlZCBmcm9tIGVhY2ggY291bnRyeS4gDQoNCnN0cihjaHVybmRhdGFfcmVncmVzcykNCnRhYmxlKGNodXJuZGF0YV9yZWdyZXNzJEV4aXRlZCkNCnRhYmxlKGNodXJuZGF0YV9yZWdyZXNzJEdlb2dyYXBoeSkNCg0KbGlicmFyeSgicHN5Y2giKQ0KDQoNCnBhaXJzLnBhbmVscyhjaHVybmRhdGFfcmVncmVzc1tjKCJBZ2UiLCAiQ3JlZGl0U2NvcmUiLCAiRXhpdGVkIiwgIk51bU9mUHJvZHVjdHMiLCAiQmFsYW5jZSIpXSkNCnBsb3QoY2h1cm5kYXRhX3JlZ3Jlc3MkQ3JlZGl0U2NvcmUgfiBjaHVybmRhdGFfcmVncmVzcyRFeGl0ZWQpDQoNCiNXaGVuIHZpZXdpbmcgdGhlIHBhbmVsLCB3ZSBjYW4gc2VlIHRoYXQgdGhlcmUgaXMgc29tZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRob3NlIHdobyBoYXZlIGNob3NlbiB0byBjaHVybiBvciBleGl0IGFuZCB0aGUgbnVtYmVyIG9mIHByb2R1Y3RzIGFuZCBiYWxhbmNlIHdpdGhpbiB0aGVpciBhY2NvdW50LiBXZSBjYW4gdGVsbCB0aGlzIGZyb20gdGhlIG92YWwgc2hhcGUuIFdlIGNhbiBhbHNvIHNlZSB0aGF0IHRoZXJlIGlzIHNvbWUgc2xpZ2h0IGNvcnJlbGF0aW9uIGJldHdlZW4gdGhvc2UgZXhpdGluZyBhbmQgY2h1cm5pbmcgYW5kIHRoZSBhZ2Ugb2YgdGhlIGN1c3RvbWVycy4gDQoNCmxpYnJhcnkoZHBseXIpDQoNCmNhdGVnb3JpY2FsIDwtIGMoIkdlbmRlciIsICJHZW9ncmFwaHkiKQ0KDQp0b3RhbF9jaHVybl9yYXRlIDwtIG1lYW4oY2h1cm5kYXRhX3JlZ3Jlc3MkRXhpdGVkKQ0KZm9yIChjIGluIGNhdGVnb3JpY2FsKSB7DQogIGRmX2dyb3VwIDwtIGNodXJuZGF0YV9yZWdyZXNzICU+JQ0KICAgIGdyb3VwX2J5KCEhc3ltKGMpKSAlPiUNCiAgICBzdW1tYXJpc2UoDQogICAgICBtZWFuX2NodXJuID0gbWVhbihFeGl0ZWQpLA0KICAgICAgY291bnQgPSBuKCkNCiAgICApICU+JQ0KICAgIG11dGF0ZSgNCiAgICAgIGRpZmYgPSBtZWFuX2NodXJuIC0gdG90YWxfY2h1cm5fcmF0ZSwNCiAgICAgIHJpc2sgPSBtZWFuX2NodXJuIC8gdG90YWxfY2h1cm5fcmF0ZQ0KICAgICkNCiAgDQogIHByaW50KHBhc3RlKCJBbmFseXNpcyBmb3IiLCBjKSkNCiAgcHJpbnQoZGZfZ3JvdXApDQogIHByaW50KCJcblxuIikNCn0NCg0KI1RoZSByaXNrIGFzc29jaWF0ZWQgd2l0aCB0aG9zZSB3aG8gbWF5IHBvdGVudGlhbGx5IGNodXJuIHdoZW4gbG9va2luZyBhdCBnZW5kZXIgYW5kIGxvY2F0aW9uLCB3ZSBjYW4gc2VlIHRoYXQgdGhlcmUgaXMgYSBoaWdoZXIgcmlzayBmb3IgZmVtYWxlcyB0byBjaHVybiBhbmQgdGhhdCB0aG9zZSBsaXZpbmcgaW4gR2VybWFueSBhcmUgbW9yZSBsaWtlbHkgdG8gY2h1cm4gY29tcGFyZWQgdG8gdGhvc2UgbGl2aW5nIGluIFNwYWluIG9yIEZyYW5jZS4gDQoNCmxpYnJhcnkocmxhbmcpDQpsaWJyYXJ5KGdncGxvdDIpDQoNCnNldC5zZWVkKDEyMzQpDQp0cmFpbkluZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oY2h1cm5kYXRhX3JlZ3Jlc3MkRXhpdGVkLCBwID0gMC44LCBsaXN0ID0gRkFMU0UsIHRpbWVzID0gMSkNCnRyYWluaW5nX2RhdGEgPC0gY2h1cm5kYXRhX3JlZ3Jlc3NbIHRyYWluSW5kZXgsXQ0KdGVzdGluZ19kYXRhICA8LSBjaHVybmRhdGFfcmVncmVzc1stdHJhaW5JbmRleCxdDQoNCiNIZXJlIGlzIHdoZXJlIHRoZSBkYXRhIHdhcyBzcGxpdCBpbnRvIHRyYWluaW5nIGRhdGEgYW5kIHRlc3RpbmcgZGF0YSBiZWZvcmUgYmVpbmcgYWJsZSB0byBjcmVhdGUgdGhlIG1vZGVsLiANCg0KY2h1cm5fbG9nbW9kZWwgPSBnbG0oRXhpdGVkIH4gLiwgZGF0YSA9IHRyYWluaW5nX2RhdGEsIGZhbWlseSA9ICJiaW5vbWlhbCIpDQpzdW1tYXJ5KGNodXJuX2xvZ21vZGVsKQ0KDQojV2hlbiBjaG9vc2luZyB0aGUgdHlwZSBvZiByZWdyZXNzaW9uIG1vZGVsLCBJIGluaXRpYWxseSB3ZW50IHdpdGggYSBtdWx0aXBsZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCBhbmQgd2FzIGhhdmluZyBhIGRpZmZpY3VsdCB0aW1lIGFjaGlldmluZyB0aGUgcmVzdWx0cyBJIHdhcyBhaW1pbmcgdG8gYWNoaWV2ZS4gQmVjYXVzZSBvZiB0aGlzLCBJIGNob3NlIHRvIGdvIHdpdGggYSBsb2dpc3RpY2FsIHJlZ3Jlc3Npb24gbW9kZWwgd29ya2luZyB3aXRoIHRoZSB0cmFpbmluZyBkYXRhIGZpcnN0LiBBZnRlciByZXZpZXdpbmcgdGhlIHJlc3VsdHMgb2YgdGhlIG1vZGVsLCBJIGNob3NlIHRvIGVsaW1pbmF0ZSBFc3RpbWF0ZWQgU2FsYXJ5LCBUZW51cmUsIEFuZCBIYXMgQ3JlZGl0IENhcmQgZnJvbSB0aGUgbW9kZWwgZHVlIHRvIGl0IG5vdCBoYXZpbmcgYSBzaWduaWZpY2FudCBpbXBhY3Qgb24gd2hldGhlciBvciBub3QgYSBjdXN0b21lciB3aWxsIGNodXJuIG9yIG5vdC4gDQoNCmNodXJuX2xvZ21vZGVsMiA9IGdsbShFeGl0ZWQgfiBDcmVkaXRTY29yZSArIEFnZSArIEJhbGFuY2UgKyBHZW5kZXIgKyBOdW1PZlByb2R1Y3RzICsgSXNBY3RpdmVNZW1iZXIgKyBHZW9ncmFwaHksIGRhdGEgPSB0cmFpbmluZ19kYXRhLCBmYW1pbHkgPSAiYmlub21pYWwiKSANCnN1bW1hcnkoY2h1cm5fbG9nbW9kZWwyKQ0KDQojQWZ0ZXIgcmV2aWV3aW5nIHRoZSByZXN1bHRzIG9mIHRoZSBmaXJzdCBtb2RlbCwgSSBjcmVhdGVkIHRoZSBzZWNvbmQgbW9kZWwgYW5kIHJhbiB0aGUgc3VtbWFyeSBvbiB0aGF0IGFuZCBkZWNpZGVkIHRvIGtlZXAgdGhpcyBtb2RlbCBhcyBlYWNoIG9mIHRoZSBpbmRlcGVuZGVudCB2YXJpYWJsZXMgdGhhdCB3ZXJlIGtlcHkgZm9yIHRoaXMgbW9kZWwgaGVsZCBzaWduaWZpY2FuY2UgaW4gY3VzdG9tZXIgY2h1cm4gZm9yIHRoaXMgYmFuay4gDQoNCnByZWRpY3Rpb24gPC0gcHJlZGljdChjaHVybl9sb2dtb2RlbDIsdGVzdGluZ19kYXRhLHR5cGU9InJlc3BvbnNlIikNCmNodXJuX3N0b3AgPC0gaWZlbHNlKHByZWRpY3Rpb24+PTAuNTAsIDEsMCkNCnJlc3VsdHNfbWF0cml4IDwtIGNvbmZ1c2lvbk1hdHJpeChhcy5mYWN0b3IodGVzdGluZ19kYXRhJEV4aXRlZCksYXMuZmFjdG9yKGNodXJuX3N0b3ApLHBvc2l0aXZlID0nMScpDQpyZXN1bHRzX21hdHJpeA0KDQojUnVubmluZyB0aGlzIG1vZGVsIGFnYWluc3QgdGhlIHRlc3RpbmcgZGF0YSBnYXZlIG1lIGFuIGFjY3VyYWN5IGxldmVsIG9mIDgxLjk1JS4gV2hpbGUgdGhpcyBjb3VsZCBoYXZlIGJlZW4gYmV0dGVyLCB0aGlzIGlzIG5vdCBuZWNlc3NhcmlseSBhIHBvb3IgbW9kZWwuIFRoZSBzZW5zaXRpdml0eSBpcyBhdCA2MSUgYW5kIHRoZSBzcGVjaWZpY2l0eSBpcyBhdCA4MyUuIFRoZSBhcmVhIHVuZGVyIHRoZSBjdXJ2ZSBpcyBhdCA3Ny44JSB3aGljaCBpcyBkZWNlbnQgcmVzdWx0LiANCg0KbGlicmFyeShwUk9DKQ0KcHJlZGljdGVkX3Byb2JzIDwtIHByZWRpY3QoY2h1cm5fbG9nbW9kZWwyLCB0ZXN0aW5nX2RhdGEsIHR5cGUgPSAicmVzcG9uc2UiKQ0Kcm9jX29iaiA8LSByb2ModGVzdGluZ19kYXRhJEV4aXRlZCwgcHJlZGljdGVkX3Byb2JzKQ0KcHJpbnQocm9jX29iaiRhdWMpDQpyb2NfcGxvdCA8LSBnZ3JvYyhyb2Nfb2JqKQ0KcHJpbnQocm9jX3Bsb3QpDQoNCg0KDQpnZXR3ZCgpDQpjaHVybmRhdGEgPC0gcmVhZC5jc3YoIkNodXJuIERhdGEgZm9yIFByb2plY3QuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IFRSVUUpDQpWaWV3KGNodXJuZGF0YSkNCnN1bW1hcnkoY2h1cm5kYXRhKQ0Kc3VtbWFyeShjaHVybmRhdGEkRXhpdGVkKQ0KdGFibGUoY2h1cm5kYXRhJEdlb2dyYXBoeSkNCnRhYmxlKGNodXJuZGF0YSRHZW5kZXIpDQp0YWJsZShjaHVybmRhdGEkVGVudXJlKQ0Kc3RyKGNodXJuZGF0YSkNCmRpbShjaHVybmRhdGEpDQpoZWFkKGNodXJuZGF0YSkNCnRhaWwoY2h1cm5kYXRhKQ0KbmFtZXMoY2h1cm5kYXRhKQ0KY2xhc3MoY2h1cm5kYXRhKQ0KcGFyKG1hciA9IGMoMC4xLCAwLjEsIDAuMSwgMC4xKSkNCnBhaXJzKGNodXJuZGF0YSkNCg0KI1doZW4gY3JlYXRpbmcgdGhlIG5ldXJhbCBuZXR3b3JrLCBJIGZpcnN0IGNob3NlIHRvIGxvYWQgdGhlIGRhdGEgYW5kIHRoZW4gZXhhbWluZSB0aGUgZGF0YSBmdXJ0aGVyIG1ha2luZyBzdXJlIHRoZXJlIHdlcmUgbm8gbWlzc2luZyB2YWx1ZXMgaW4gcHJlcGFyYXRpb24gdG8gbm9ybWFsaXplIHRoZSBkYXRhIGJlZm9yZSBjcmVhdGluZyBteSBuZXVyYWwgbmV0d29yay4gDQoNCmR1bW15X21hdHJpeCA8LSBtb2RlbC5tYXRyaXgoIH4gR2VuZGVyIC0xLCBkYXRhID0gY2h1cm5kYXRhKQ0KY2h1cm5kYXRhIDwtIGNiaW5kKGNodXJuZGF0YSwgZHVtbXlfbWF0cml4KQ0KZHVtbXlfbWF0cml4XzEgPC0gbW9kZWwubWF0cml4KCB+IEdlb2dyYXBoeSAtMSwgZGF0YSA9IGNodXJuZGF0YSkNCmNodXJuZGF0YSA8LSBjYmluZChjaHVybmRhdGEsIGR1bW15X21hdHJpeF8xKQ0KZHVtbXlfbWF0cml4XzIgPC0gbW9kZWwubWF0cml4ICggfkV4aXRlZCAtMSwgZGF0YSA9IGNodXJuZGF0YSkNCmNodXJuZGF0YSA8LSBjYmluZChjaHVybmRhdGEsIGR1bW15X21hdHJpeF8yKQ0KDQojRm9yIHRoZSBHZW5kZXIsIEdlb2dyYXBoeSwgYW5kIEV4aXRlZCBjb2x1bW5zIGluIHRoZSBkYXRhc2V0LCBkdW1teSB2YXJpYWJsZXMgbmVlZGVkIHRvIGJlIGNyZWF0ZWQgaW4gb3JkZXIgdG8gbm9ybWFsaXplIHRoZSBkYXRhIHNldC4NCg0KY29sdW1uc190b19ub3JtYWxpemUgPC0gYygxOjUsIDc6OSwgMTE6MTYpDQptaW5fbWF4X25vcm0gPC0gZnVuY3Rpb24oeCkgew0KICAoeC1taW4oeCkpIC8gKG1heCh4KSAtIG1pbih4KSkNCn0NCmNodXJuZGF0YV9ub3JtIDwtIGFzLmRhdGEuZnJhbWUobGFwcGx5KGNodXJuZGF0YVssIGNvbHVtbnNfdG9fbm9ybWFsaXplXSwgbWluX21heF9ub3JtKSkNCmhlYWQoY2h1cm5kYXRhX25vcm0pDQpzdW1tYXJ5KGNodXJuZGF0YV9ub3JtJEV4aXRlZCkNCg0KI0FmdGVyIG5vcm1hbGl6aW5nIHRoZSBkYXRhIGZvciB0aGUgbW9kZWwsIEkgZXhhbWluZWQgdGhlIGRhdGEgdG8gbWFrZSBzdXJlIHRoYXQgZXZlcnl0aGluZyBoYWQgYmVlbiBub3JtYWxpemVkIGFuZCB3YXMgcmVhZHkgYW5kIHByZXBhcmVkIHRvIGJlIHNlcGFyYXRlZCBpbnRvIHRlc3RpbmcgYW5kIHRyYWluaW5nIGRhdGFzZXRzLiBBcyB5b3UgY2FuIHNlZSwgdGhlIHRyYWluaW5nIGRhdGEgd2FzIHNldCB0byB1c2UgOTAwMCBvZiB0aGUgMTAwMDAgYW5kIHRoZSB0ZXN0aW5nIHdhcyB0byB1c2UgdGhlIHJlbWFpbmluZyAxMDAwIHZhbHVlcy4gDQoNCmNodXJuZGF0YV90cmFpbiA8LSBjaHVybmRhdGFfbm9ybVsxOjkwMDAsIF0NCmNodXJuZGF0YV90ZXN0IDwtIGNodXJuZGF0YV9ub3JtWzkwMDE6MTAwMDAsIF0NCg0KaW5zdGFsbC5wYWNrYWdlcygibmV1cmFsbmV0IikNCmxpYnJhcnkobmV1cmFsbmV0KQ0KDQpzZXQuc2VlZCgxMjM0KQ0KY2h1cm5kYXRhX21vZGVsIDwtIG5ldXJhbG5ldChFeGl0ZWQgfiBDcmVkaXRTY29yZSArIEFnZSArIEJhbGFuY2UgKyBFc3RpbWF0ZWRTYWxhcnkgKyBUZW51cmUgKyBOdW1PZlByb2R1Y3RzICsgSGFzQ3JDYXJkICsgSXNBY3RpdmVNZW1iZXIgKyBHZW5kZXJGZW1hbGUgKyBHZW5kZXJNYWxlICsgR2VvZ3JhcGh5RnJhbmNlICsgR2VvZ3JhcGh5R2VybWFueSArIEdlb2dyYXBoeVNwYWluLCBkYXRhID0gY2h1cm5kYXRhX3RyYWluKQ0KcGxvdChjaHVybmRhdGFfbW9kZWwpDQoNCiNUaGlzIGZpcnN0IG5ldXJhbCBuZXR3b3JrIG1vZGVsIHdhcyBzZXQgdG8gaW5jbHVkZSBhbGwgb2YgdGhlIGRhdGEgYW5kIGRpZCBub3QgaW5jbHVkZSBhbnkgaGlkZGVuIG5ldHdvcmtzLiBUaGUgcmVzdWx0cyBmcm9tIHRoaXMgZmlyc3QgbW9kZWwgd2FzIHZlcnkgcG9vciB3aXRoIGFuIGFjY3VyYWN5IHJhdGUgb2Ygb25seSAzOC40JS4gVGhpcyB3YXMgcmVhbGx5IHN1cnByaXNpbmcgdG8gbWUgd2l0aCBob3cgcG9vcmx5IGl0IHBlcmZvcm1lZCBhbmQgc28gSSBkZWNpZGVkIHRvIGFkZCBzb21lIGhpZGRlbiBsYXllcnMgdG8gbXkgbmV4dCBtb2RlbC4gDQoNCm1vZGVsX3Jlc3VsdHMgPC0gY29tcHV0ZShjaHVybmRhdGFfbW9kZWwsIGNodXJuZGF0YV90ZXN0WzE6MTRdKQ0KcHJlZGljdGVkX3N0cmVuZ3RoIDwtIG1vZGVsX3Jlc3VsdHMkbmV0LnJlc3VsdA0KY29yKHByZWRpY3RlZF9zdHJlbmd0aCwgY2h1cm5kYXRhX3Rlc3QkRXhpdGVkKQ0KDQpzZXQuc2VlZCgxMjM0KQ0KY2h1cm5kYXRhX21vZGVsMiA8LSBuZXVyYWxuZXQoRXhpdGVkIH4gQ3JlZGl0U2NvcmUgKyBBZ2UgKyBCYWxhbmNlICsgRXN0aW1hdGVkU2FsYXJ5ICsgVGVudXJlICsgTnVtT2ZQcm9kdWN0cyArIEhhc0NyQ2FyZCArIElzQWN0aXZlTWVtYmVyICsgR2VuZGVyRmVtYWxlICsgR2VuZGVyTWFsZSArIEdlb2dyYXBoeUZyYW5jZSArIEdlb2dyYXBoeUdlcm1hbnkgKyBHZW9ncmFwaHlTcGFpbiwgZGF0YSA9IGNodXJuZGF0YV90cmFpbiwgaGlkZGVuID0gNSkNCnBsb3QoY2h1cm5kYXRhX21vZGVsMikNCg0KbW9kZWxfcmVzdWx0czIgPC0gY29tcHV0ZShjaHVybmRhdGFfbW9kZWwyLCBjaHVybmRhdGFfdGVzdFsxOjE0XSkNCnByZWRpY3RlZF9zdHJlbmd0aCA8LSBtb2RlbF9yZXN1bHRzMiRuZXQucmVzdWx0DQpjb3IocHJlZGljdGVkX3N0cmVuZ3RoLCBjaHVybmRhdGFfdGVzdCRFeGl0ZWQpDQoNCiNBZnRlciByZWFsaXppbmcgdGhhdCB0aGUgZmlyc3QgbW9kZWwgcGVyZm9ybWVkIHNvIHBvb3JseSwgSSBkZWNpZGVkIHRvIGFkZCBoaWRkZW4gbGF5ZXJzIGludG8gbXkgc2Vjb25kIG5ldXJhbCBuZXR3b3JrIGluIGhvcGVzIG9mIG1ha2luZyBncmVhdCBpbXByb3ZlbWVudHMgaW4gbXkgcmVzdWx0cy4gV2hpbGUgaXQgZGlkIGJvb3N0IG15IHJlc3VsdHMgdXAgdG8gNTUuNiUsIGl0IHN0aWxsIHdhcyBhIGZhciBjcnkgZnJvbSBiZWluZyBhIG1vZGVsIHRoYXQgY291bGQgYmUgdXNlZCBpbiBwcmVkaWN0aW5nIGN1c3RvbWVyIGNodXJuLiBUaGUgbGFzdCBtb2RlbCBJIGNyZWF0ZWQsIEkgZGVjaWRlZCB0byB1c2UgYW4gYWN0aXZhdGlvbiBmdW5jdGlvbiBrbm93biBhcyBhIHJlY3RpZmllci4gRm9yIHRoaXMgcGFydGljdWxhciBtb2RlbCwgSSBjaG9zZSB0byBnbyB3aXRoIGEgc29mdHBsdXMgYWN0aXZhdGlvbiBmdW5jdGlvbi4gDQoNCnNvZnRwbHVzIDwtIGZ1bmN0aW9uKHgpIHsgbG9nKDEgKyBleHAoeCkpIH0NCg0Kc2V0LnNlZWQoMTIzNDUpDQpjaHVybmRhdGFfbW9kZWwzIDwtIG5ldXJhbG5ldChFeGl0ZWQgfiBDcmVkaXRTY29yZSArIEFnZSArIEJhbGFuY2UgKyBFc3RpbWF0ZWRTYWxhcnkgKyBUZW51cmUgKyBOdW1PZlByb2R1Y3RzICsgSGFzQ3JDYXJkICsgSXNBY3RpdmVNZW1iZXIgKyBHZW5kZXJGZW1hbGUgKyBHZW5kZXJNYWxlICsgR2VvZ3JhcGh5RnJhbmNlICsgR2VvZ3JhcGh5R2VybWFueSArIEdlb2dyYXBoeVNwYWluLCBkYXRhID0gY2h1cm5kYXRhX3RyYWluLCBoaWRkZW4gPSBjKCA1LCAxKSAsIGFjdC5mY3QgPSBzb2Z0cGx1cykNCg0KdGVzdF9pbnB1dCA8LSBhcy5tYXRyaXgoY2h1cm5kYXRhX3Rlc3RbLCAxOjE0XSkNCm1vZGVsX3Jlc3VsdHMzIDwtIGNvbXB1dGUoY2h1cm5kYXRhX21vZGVsMywgdGVzdF9pbnB1dCkNCnByZWRpY3RlZF9zdHJlbmd0aCA8LSBtb2RlbF9yZXN1bHRzMyRuZXQucmVzdWx0DQpjb3IocHJlZGljdGVkX3N0cmVuZ3RoLCBjaHVybmRhdGFfdGVzdCRFeGl0ZWQpDQoNCiNBZnRlciBjcmVhdGluZyB0aGUgc29mdHBsdXMgYWN0aXZhdGlvbiBmdW5jdGlvbiBmb3IgdGhpcyB0aGlyZCBhbmQgZmluYWwgbW9kZWwsIGhpZGRlbiBsYXllcnMgYW5kIG5ldXJvbnMgd2VyZSBhbHNvIGFkZGVkIGluIG9yZGVyIHRvIHNlZSBpbiBtb3JlIGltcHJvdmVtZW50IGNvdWxkIGhhdmUgYmVlbiBtYWRlLiBVbmZvcnR1bmF0ZWx5LCBldmVyeSB0aW1lIEkgdHJpZWQgdG8gcnVuIHRoaXMgY29kZSwgaXQgd291bGQgcnVuIGZvciBhYm91dCAzMC00MCBtaW51dGVzIGJlZm9yZSBteSBzeXN0ZW0gd291bGQgY3Jhc2ggYW5kIEkgd2FzIHVuYWJsZSB0byBzZWUgaWYgdGhpcyB3b3VsZCBhY3R1YWxseSBpbXByb3ZlIHRoZSBtb2RlbHMgcGVyZm9ybWFuY2UuIEJlY2F1c2Ugb2YgdGhpcywgSSB3b3VsZCBoYXZlIHRvIGdvIHdpdGggdGhlIHJlc3VsdHMgYWNoaWV2ZWQgZnJvbSBteSBzZWNvbmQgcmVncmVzc2lvbiBtb2RlbC4gDQoNCg0KDQoNCiNBZnRlciBleHBsb3JpbmcgYWxsIHRocmVlIG1vZGVscyBhbmQgdGhlaXIgcmVzdWx0cywgSSBiZWxpZXZlIHRoYXQgdGhlIGRlY2lzaW9uIHRyZWUgb3IgdGhlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgd291bGQgYmUgYmVzdCB0byB1c2UgaW4gcHJlZGljdGluZyBjdXN0b21lciBjaHVybi4gV2hpbGUgdGhlIGRlY2lzaW9uIHRyZWUgY2FuIGJlIGhlbHBmdWwgZHVlIHRvIGl0cyBoaWdoIGFjY3VyYWN5LCB0aGUgcmVncmVzc2lvbiBtb2RlbCB3YXMgYSBncmVhdCBtb2RlbCBpbiBoZWxwaW5nIHVzIHRvIHVuZGVyc3RhbmQgcmVhc29uaW5nIGFzIHRvIHdoeSBhIHBlcnNvbiBtYXkgY2h1cm4gZnJvbSB0aGUgYmFuayBzdWNoIGFzIGFnZS4gVGhvc2UgbW9yZSBsaWtlbHkgdG8gY2h1cm4gd2VyZSB0aGUgeW91bmdlciBhbmQgb2xkZXIgY3VzdG9tZXJzIGFuZCBzbyB0aGF0IG1heSBiZSBhIGdvb2QgaW5kaWNhdGlvbiBvZiBzb21ldGhpbmcgdGhhdCBjb3VsZCBiZSBhZGRyZXNzZWQgbWFya2V0aW5nIHdpc2UgaW4gb3JkZXIgdG8gbWFpbnRhaW4gdGhvc2UgY3VzdG9tZXJzIGJ1c2luZXNzLiANCg0KYGBgDQoNCg0KQWRkIGEgbmV3IGNodW5rIGJ5IGNsaWNraW5nIHRoZSAqSW5zZXJ0IENodW5rKiBidXR0b24gb24gdGhlIHRvb2xiYXIgb3IgYnkgcHJlc3NpbmcgKkN0cmwrQWx0K0kqLg0KDQpXaGVuIHlvdSBzYXZlIHRoZSBub3RlYm9vaywgYW4gSFRNTCBmaWxlIGNvbnRhaW5pbmcgdGhlIGNvZGUgYW5kIG91dHB1dCB3aWxsIGJlIHNhdmVkIGFsb25nc2lkZSBpdCAoY2xpY2sgdGhlICpQcmV2aWV3KiBidXR0b24gb3IgcHJlc3MgKkN0cmwrU2hpZnQrSyogdG8gcHJldmlldyB0aGUgSFRNTCBmaWxlKS4NCg0KVGhlIHByZXZpZXcgc2hvd3MgeW91IGEgcmVuZGVyZWQgSFRNTCBjb3B5IG9mIHRoZSBjb250ZW50cyBvZiB0aGUgZWRpdG9yLiBDb25zZXF1ZW50bHksIHVubGlrZSAqS25pdCosICpQcmV2aWV3KiBkb2VzIG5vdCBydW4gYW55IFIgY29kZSBjaHVua3MuIEluc3RlYWQsIHRoZSBvdXRwdXQgb2YgdGhlIGNodW5rIHdoZW4gaXQgd2FzIGxhc3QgcnVuIGluIHRoZSBlZGl0b3IgaXMgZGlzcGxheWVkLg0K