knitr::opts_chunk$set(echo = FALSE)

PROJECT TASKS

  1. To apply models to Large Matrix file to complete the analysis of overall sentiment toward both iPhone and Samsung Galaxy.
  2. To identify one optimized model to predict the overall sentiment toward iPhones and one optimized model to predict overall sentiment toward Samsung Galaxy handsets using R for the analysis.

Summary of Findings

  1. Narrative of the data supported by the results.
  2. How confident the results are.
  3. The reported performance metrics from R
  4. Personal sense of how well the attributes measured will actually capture pages that have relevant sentiment.
  5. Caveats of this analysis process might not be capturing the sentiment accurately and suggestions for how to do better in the next round of analysis.
  6. What implications the narrative has for the client’s goals.
  7. High-level explanation of the data analytics performed.
  8. Lessons Learned Report: a report of no more than five pages in Word that includes: For both iPhone and Galaxy:
  9. The classifier selected and the features (attributes) used to train the classifier.
  10. Rationale for selecting the classifier. 10.Any features eliminated from the data matrics and rationale for doing so. 11.Comparative performance of the classifiers tried. 12.What worked well. What didn’t work. What was difficult.What was not difficult. 13.How the process to execute similar projects should be changed for the future.
#install.packages("Boruta")
#Find how many cores are on my machine
detectCores() 
# Create Cluster with desired number of cores. 
cl <- makeCluster(2)

# Register Cluster
registerDoParallel(cl)

#Confirm how many cores are now "assigned" to R and RStudio
getDoParWorkers() # Result 2 
#Stop Cluster 
#stopCluster(cl)

  1. EXPLOARTORY ANALYSIS

The workflow of the project focuses on one small matrix at a time. In the plan of attack we will use iPhone matrix first. Once iPhone modeling and prediction is complete, Galaxy matrix will be imported and perform the same steps.

iPhone_Matrix <- read.csv("iphone_smallmatrix_labeled_8d.csv", header = TRUE)

#glimpse(iPhone_Matrix)

plot_ly(iPhone_Matrix, x= ~iPhone_Matrix$iphonesentiment, type='histogram')
iPhone_Matrix <- read.csv("iphone_smallmatrix_labeled_8d.csv", header = TRUE)

#glimpse(iPhone_Matrix)

plot_ly(iPhone_Matrix, x= ~iPhone_Matrix$iphonesentiment, type='histogram')

3 FEATURE SELECTION

We will create a new data set for each feature selection method. Then model with each of these new data sets to determine which method, if any, provides the best model accuracy for this project.

3.1 Examine Correlation

While correlation doesn’t always imply causation, it’s a good practice to begin the analysis by finding the correlation between all the variables. Using cor() function to create a correlation matrix to visualize and ascertain the correlation between all of the features.

#Use the cor() function to build the correlation matrix
CORRiphone_Matrix <- cor(iPhone_Matrix)
CORRiphone_Matrix
options(max.print=10000000)
#Use the cor() function to build the correlation matrix
CORRiphone_Matrix <- cor(iPhone_Matrix)
#Running correlation matrix as p-value
rCORRiphone_Matrix <- rcorr(as.matrix(CORRiphone_Matrix))
corrplot(CORRiphone_Matrix)

#Generating correlation matrix with other colors
palette = colorRampPalette(c("green", "white", "red")) (20)
heatmap(x = CORRiphone_Matrix, col = palette, symm = TRUE)
#Create new df & remove highly correlated features
iphoneMatrix_Cor <- iPhone_Matrix %>% select(-ios, -htcphone, 
                                             -samsungdispos, -iphonedispos, -nokiadisunc,
                                             -sonyperneg, -iosperpos, -iphonecamunc,
                                             -nokialumina, -googleandroid, -samsungcamneg,
                                             -sonycamunc, -nokiadispos, -nokiacampos,
                                             -samsungcamunc, -nokiacamunc, -sonydispos,
                                             -iphonedisneg, -nokiadisneg, -htcdisneg,
                                             -samsungdisunc, -nokiadisunc, -samsungperpos,
                                             -nokiaperpos, -htcperpos, -samsungperneg,
                                             -samsungperunc, -nokiaperunc, -googleperpos,
                                             -googleperneg)
#Running correlation matrix as p-value
rCORRiphone_Matrix <- rcorr(as.matrix(CORRiphone_Matrix))

3.2 Examine Feature Variances

The distribution of values within a feature is related to how much information that feature holds in the data set. Features with no variance can be said to hold little to no information. Features that have very little, or “near zero variance”, may or may not have useful information. To explore feature variance we can use nearZeroVar() from the caret package.

nearZeroVar() with saveMetrics = TRUE returns an object containing a table including: frequency ratio, percentage unique, zero variance and near zero variance

#Check for nearZero Variances iphone small matrix
nzv_iphoneMetrics <- nearZeroVar(iphoneMatrix_Cor, saveMetrics = TRUE)
nzv_iphoneMetrics

dim(iphoneMatrix_Cor)

#Filtering out nearZeroVar Predictors
nzv_iphoneMetrics <- nearZeroVar(iphoneMatrix_Cor)

#nearZeroVar() with saveMetrics = FALSE returns a vector 
#(create indexes of nearZero Variances Predictors)
nzv <- nearZeroVar(iphoneMatrix_Cor, saveMetrics = FALSE) 
nzv

#Create new df without nearZeroVar predictors
filtered_nzv <- iphoneMatrix_Cor[, -nzv]
dim(filtered_nzv)

FEATURE ELIMINATION APPROACHES

3.3 Approach 1: Recursive Feature Elimination (RFE)

Furthermore, an automated feature selection function, Caret’s rfe() function when used with random forest will try every combination of feature subsets and return a final list of recommended features.

#take 300 samples of the original data set before applying RFE
set.seed(123)

iphoneSample_noRFE <- iPhone_Matrix[sample(1:nrow(iPhone_Matrix), 300, replace=FALSE),]

#Set up rfeControl with randomforest with cross validation and no updates
ctrl <- rfeControl(functions = rfFuncs,
                   method = "repeatedcv",
                   repeats = 1,
                   verbose = FALSE)

#apply rfe to all variables except the target (col 59 "iphonesentiment")
system.time(rfeResults <- rfe(iphoneSample_noRFE[,1:58],
                  iphoneSample_noRFE$iphonesentiment,
                  sizes=c(1:58),
                  rfeControl=ctrl))
#Generating correlation matrix with other colors
palette = colorRampPalette(c("green", "white", "red")) (20)
heatmap(x = CORRiphone_Matrix, col = palette, symm = TRUE)

#Create new df & remove highly correlated features
iphoneMatrix_Cor <- iPhone_Matrix %>% select(-ios, -htcphone, 
                                             -samsungdispos, -iphonedispos, -nokiadisunc,
                                             -sonyperneg, -iosperpos, -iphonecamunc,
                                             -nokialumina, -googleandroid, -samsungcamneg,
                                             -sonycamunc, -nokiadispos, -nokiacampos,
                                             -samsungcamunc, -nokiacamunc, -sonydispos,
                                             -iphonedisneg, -nokiadisneg, -htcdisneg,
                                             -samsungdisunc, -nokiadisunc, -samsungperpos,
                                             -nokiaperpos, -htcperpos, -samsungperneg,
                                             -samsungperunc, -nokiaperunc, -googleperpos,
                                             -googleperneg)

The resulting table and plot display each subset and its accuracy and kappa. An asterisk denotes the the number of features that is judged the most optimal from RFE.

3.4 Approach 2: Using Boruta Algorithm for Feature Selection

Boruta is based on RF. It creates shadow attributes of the original attributes with all the values in each shadow attribute shuffled across to create randomness with a good representation of the data. Running 58 attributes on a model creation proves to be very time consuming and takes a lot of system resources. Some of the features were found to be unimportant to the model. Hence, applying this algorithm in our feature selection eliminate all unimportant attributes and allow the model to provide recommended attributes for modeling.

#Using original iphone_Matrix data set
summary(iPhone_Matrix)
#create new df copy of original data set
iphoneMatrix2 <- iPhone_Matrix

#Change dependent variable (iphonesentiment) from integer to factor variable for classifcation model
iphoneMatrix2$iphonesentiment <- as.factor(iPhone_Matrix$iphonesentiment)
#Check str, and how many classes in target
str(iphoneMatrix2)

#Check number of classes
nlevels(iphoneMatrix2$iphonesentiment)

We have 6 classes in the iphonesentiment. According to iphone Sentiment labeling, these classes represent the following:

0: very negative 1: negative 2: somewhat negative 3: somewhat positive 4: positive 5: very positive

3.5 Engineering The Dependant Variable

We do not really need 6 levels to understand positive and negative sentiment of iphone. Perhaps combining some of these levels will help increase accuracy and kappa. Using dplyr package recode() function can help us with this. Let’s remapped the values as follows:

1: negative 2: somewhat negative 3: somewhat positive 4: positive

#create a new dataset that will be used for recoding sentiments
iphoneM_RC <- iphoneMatrix2

#recode sentiments by combining factor levels 0 & 1 and 4 & 5
iphoneM_RC$iphonesentiment <- recode(iphoneM_RC$iphonesentiment, 
                                   '0' = 1, '1' = 1, '2' = 2, 
                                   '3' = 3, '4' = 4, '5' = 4) 
#inspect levels recode results
#summary(iphoneM_RC)
str(iphoneM_RC)
#convert dependent variable, iphonesentiment to a factor
iphoneM_RC$iphonesentiment <- as.factor(iphoneM_RC$iphonesentiment)
nlevels(iphoneM_RC$iphonesentiment)

To perform feature selection using Boruta, let’s create a manageable sample size of the data set.

iphone_Bdf <- iphoneMatrix2[sample(1:nrow(iphoneMatrix2), 1000, replace=FALSE),]

set.seed(111)

#Use Boruta() on the target and include all independent variable. Set doTrace to view progress
boruta <- Boruta(iphonesentiment~ ., data = iphone_Bdf, doTrace = 2)

#View Boruta output
print(boruta)

The algorithm after running through 99 iterations, found 8 Tentative attributes, 20 Confirmed Importance attributes and 30 Unconfirmed Importance. To properly classify, we’ll need to verify if the 8 tentative attributes are Confirmed Importance or Unconfirmed by running a fix on Boruta.

#Plot results
plot(rfeResults, type=c("g", "o"))

From the boxplot above, all Confirmed attributes are colored “green” with their respective level of importance. Unconfirmed attributes are colored in “red”. The 8 Tentative attributes (htccamneg, htcperunc, samsungdisunc, etc.) are colored in “yellow” and the shadow attributes are blue.

The importance of the shadow attributes can be used to measure the importance of each attribute on accuracy of the model. The three blue shadow attributes shown on the boxplot correspond to MIN, AVE, MAX of the shadow attributes’ importance level.

plotImpHistory(boruta)

Above plot shows Importance values. Within the blue line are Unconfirmed attributes. Confirmed attributes and their level of importance are represented in green lines. The tentative attributes (no decision from model) falls on the yellow line region.

3.4.1 Fixing Tentative Attributes

#apply TentativeRoughFix() function to fix the undecided (tentative) attributes
borFix_df <- TentativeRoughFix(boruta)

print(borFix_df)
glimpse(borFix_df)

The following results obtained after fixing the tentative attributes:

– Boruta performed 99 iterations in 8.508441 mins. – Tentatives roughfixed over the last 99 iterations. – 20 attributes confirmed important: googleandroid, htccamneg, htccampos, htcdisneg, htcdispos and more; – 38 attributes confirmed unimportant: googleperneg, googleperpos, googleperunc, htccamunc, htcdisunc and more;

Clearly, TentativeRoughFix function helps us to identify all the Unconfirmed and Confirmed Importance of the attributes and categorize them according to each attribute’s importance.

#obtain attribute stats
attStats(boruta)

The above stats show detail statistical information of each attribute Importance: Mean, Median, Min, Max, and Norminal History. Previously from the boxplot above, we noticed that attributes like iphone, Samsunggalaxy, ios, iphonedispos, iphonedisunc with normHits of 1.000000 are 100% more important than their shadows. While some attributes with 0.0% have no Importance and were found to be less important than their shadows, thus rejected by the algorithm.

#use getNonRejectedFormula() to select Confirmed and Tentative attributes
getNonRejectedFormula(boruta)
#use getConfirmedFormula() to Select only Confirmed Importance
bor_Results <- getConfirmedFormula(boruta)
bor_Results
#Create df of only Confirmed Importance or recommended attributes
iphoneMatrics20 <- iphoneM_RC %>% select(iphone, samsunggalaxy, sonyxperia, htcphone,
                                            ios, googleandroid, iphonecampos, htccampos,
                                            iphonecamneg, iphonecamunc, htccamunc,
                                            iphonedispos, htcdispos, iphonedisneg,
                                            iphonedisunc, iphoneperpos, htcperpos,
                                            iphoneperneg, htcperneg, iphoneperunc,
                                            iphonesentiment)

str(iphoneMatrics20)

#plot to view distribution of iphone sentiments
plot_ly(iphoneMatrics20, x= ~iphoneMatrics20$iphonesentiment, 
                              type='histogram') %>%
                              layout(title = "Histogram of iphonesentiment",
                              xaxis = list(title = "Classes"),
                              yaxis = list(title = "Frequency"))
  
  1. BUILD MODELS

With feature selection and preprocessing of the small matrix file completed, it is time to build models. The initial models will use all 58 attributes from the data set to gain “out of the box” accuracy and kappa. Then, we’ll predict again with features selected from data sets. The goal is to find the best combination of data set and algorithm as measured by resulting performance metrics.

4.1 Create Data Partition Using Caret (all 58 independent attributes)

set.seed(222)

#create data partition
inTraining58 <- createDataPartition(iphoneM_RC$iphonesentiment,
                                        p = .7, list = FALSE)

#Set Training and Testion Data
training <- iphoneM_RC[inTraining58,]
testing <- iphoneM_RC[-inTraining58,]

#train model
#3 fold cross validation
fitControl <- trainControl(method = "repeatedcv", number = 3, repeats = 2)

4.2 Random Forest Model (using all variables)

#train Random Forest classification model
system.time(iphone58_RF <- train(iphonesentiment~., data = training, 
                            method = "rf", 
                            trControl=fitControl, 
                            tuneLength = 3))
#create new df copy of original data set
iphoneMatrix2 <- iPhone_Matrix

#Change dependent variable (iphonesentiment) from integer to factor variable for classifcation model
iphoneMatrix2$iphonesentiment <- as.factor(iPhone_Matrix$iphonesentiment)
plot(iphone58_RF)
Pred_rf58 <- predict(iphone58_RF, testing)

#iphone58_RF Create Confusion Matrix
CM_rf58 <- confusionMatrix(Pred_rf58, testing$iphonesentiment)
CM_rf58

Random Forest

9083 samples 58 predictor 4 classes: ‘1’, ‘2’, ‘3’, ‘4’

No pre-processing Resampling: Cross-Validated (3 fold, repeated 2 times) Summary of sample sizes: 6055, 6056, 6055, 6056, 6054, 6056, … Resampling results across tuning parameters:

mtry Accuracy Kappa
2 0.7717732 0.3555679 30 0.8496645 0.6284048 58 0.8446550 0.6186618

Accuracy was used to select the optimal model using the largest value. The final value used for the model was mtry = 30.

The RF model using all 58 attributes obtain 84% Accuracy rate and 62% Kappa. Class 4 (positive iphone sentiment) has the higest sensitivity. This means the model performed the following predictions:

Accurately predicted 1 (negative iphone sentiment) - 378 times 2 (somewhat negative iphone sentiment) - 19 times 3 (somewhat positive iphone sentiment) -229 times 4 (positive iphone sentiment) - 2667 times

4.3 Building RF Model Using Feature Selection

#create a new dataset that will be used for recoding sentiments
iphoneM_RC <- iphoneMatrix2

#recode sentiments by combining factor levels 0 & 1 and 4 & 5
iphoneM_RC$iphonesentiment <- recode(iphoneM_RC$iphonesentiment, 
                                   '0' = 1, '1' = 1, '2' = 2, 
                                   '3' = 3, '4' = 4, '5' = 4) 
#train Random Forest classification model with a tuneLenght = 3
system.time(iphone20_RF <- train(iphonesentiment~., data = training2,
                            method = "rf",
                            trControl=fitControl,
                            tuneLength = 3))
iphone20_RF

#plot rf model
plot(iphone20_RF)

Random Forest 9083 samples 20 predictor 4 classes: ‘1’, ‘2’, ‘3’, ‘4’

No pre-processing Resampling: Cross-Validated (3 fold, repeated 2 times) Summary of sample sizes: 6055, 6056, 6055, 6056, 6054, 6056, … Resampling results across tuning parameters: mtry Accuracy Kappa
2 0.8030398 0.4719019 11 0.8486738 0.6268412 20 0.8438294 0.6176853

Accuracy was used to select the optimal model using the largest value. The final value used for the model was mtry = 11.

Pred_rf20 <- predict(iphone20_RF, testing2)

Pred_rf20

#Create Confusion Matrix
confusionMatrix(Pred_rf20, testing2$iphonesentiment)

Confusion Matrix and Statistics Reference Prediction 1 2 3 4 1 383 3 5 10 2 0 19 0 6 3 2 0 229 9 4 320 114 122 2668

Overall Statistics Accuracy : 0.8481
95% CI : (0.8364, 0.8592) No Information Rate : 0.6923
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.6218

The above results show we can significantly reduce the number of attributes by about 50% of the original data set without significantly losing model prediction accuracy. However, we’ve gain advantage in reducing running time and less system resources used. It’s worth to note that the model prediction accuracy has little or no effect with less attributes.

  1. Build C5.0 Model (using all 58 attributes)
set.seed(222)

#Run C5.0 Decision tree model 
system.time(fit_C50model <- train(iphonesentiment~., data = training, 
                            method='C5.0',
                            preProcess = c('zv'),
                            trControl = fitControl))
#convert dependent variable, iphonesentiment to a factor
iphoneM_RC$iphonesentiment <- as.factor(iphoneM_RC$iphonesentiment)
nlevels(iphoneM_RC$iphonesentiment)
[1] 4
#plot C50 model
plot(fit_C50model)

#Checking RF Performance Metrics
fit_C50model$finalModel

5.1 C5.0 Prediction & Confusion Matrix

#prediction 
C50Pred_58 <- predict(fit_C50model, testing)

#confusion matrix
cmC50_Pred58 <- confusionMatrix(C50Pred_58, testing$iphonesentiment)

cmC50_Pred58

6.0 SVM Model (from the e1071 package) Using All Attributes iphone Small Matrix

#vertically placing the chart with axis label font size of 0.65
plot(boruta, las = 2, cex.axis = 0.65)

rr #looking at Importance History plotImpHistory(boruta)

6.1 SVM-Kernel: Radial

#Run SVM model (Kernel = radial)
system.time(fitSVM <- svm(iphonesentiment~., data = training))

rr print(borFix_df)

Boruta performed 99 iterations in 7.79663 mins.
Tentatives roughfixed over the last 99 iterations.
 25 attributes confirmed important: googleandroid, htccamneg, htccampos, htccamunc,
htcdispos and 20 more;
 33 attributes confirmed unimportant: googleperneg, googleperpos, googleperunc,
htcdisneg, htcdisunc and 28 more;

After building model with the radial kernel, we obtain 3802 support vectors for the 4 classes of iphonesentiment. Class-1 (1218), Class-2 (1491), Class-3 (775), Class-4 (318)

6.1.1 Prediction & Confusion Matrix for SVM (Radial Function)

#prediction
rad_Pred <- predict(fitSVM, testing)

#Store the predicted data in a table for analysis
SMV_table1 <- table(Predicted = rad_Pred, Actual = testing$iphonesentiment)
SMV_table1

#confusion matrix
cmSVM1 <- confusionMatrix(rad_Pred, testing$iphonesentiment)
cmSVM1

To calculate Model Accuracy: (Sum of Accurately Predicted Data Points / Total Number of Data Points)

Misclassification Rate: (Sum of Misclassified Data Points / Total Number of Data Points) Misclassification Error: 0.2102828

Overall Statistics Accuracy : 0.7897
95% CI : (0.7766, 0.8024) No Information Rate : 0.6923
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.4565

Mcnemar’s Test P-Value : < 2.2e-16

rr glimpse(borFix_df)

rr #obtain attribute stats attStats(boruta)

6.2 Tunning SVM Model With Linear Function

#Run SVM model (Kernel = linear)
system.time(fitSVM <- svm(iphonesentiment~., data = training, kernel = "linear"))

summary(fitSVM)
#linear kernel prediction
lin_Pred <- predict(fitSVM, testing)

#Store the predicted data in a table for analysis
SMV_table2 <- table(Predicted = lin_Pred, Actual = testing$iphonesentiment)
SMV_table2

#Calculate Accuracy
sum(diag(SMV_table2))/sum(SMV_table2)

#Calculate Misclassification Error 
1 - sum(diag(SMV_table2))/sum(SMV_table2)

#confusion matrix
cmSVM2 <- confusionMatrix(lin_Pred, testing$iphonesentiment)
cmSVM2

Accuracy: Accuracy : 0.7748 Misclassification Error: 0.2251928

Above shows linear kernel slightly decreses the Model accuracy rate to 77.48% and the Misclassification rate reduces to about 22.51%

6.3 Tunning SVM Model With Polynomial Function

#Run SVM model (Kernel = polynomial)
system.time(fitSVM <- svm(iphonesentiment~., data = training, kernel = "polynomial"))

summary(fitSVM)
#polynomial Tuning prediction
pol_Pred <- predict(fitSVM, testing)

#Store the predicted data in a table for analysis
SVM_table3 <- table(Predicted = pol_Pred, Actual = testing$iphonesentiment)
SVM_table3

#Calculate Accuracy
sum(diag(SVM_table3))/sum(SVM_table3)

#Calculate Misclassification Error 
1 - sum(diag(SVM_table3))/sum(SVM_table3)

cmSVM3 <- confusionMatrix(pol_Pred, testing$iphonesentiment)
cmSVM3

Accuracy : 0.7496
95% CI : (0.7357, 0.7632) No Information Rate : 0.6923
P-Value [Acc > NIR] : 1.676e-15

Kappa : 0.2777
Misclassification Error: 0.2503856

The polynomial kernel function reduces the Model accuracy rate to 74.96% and the Misclassification Error rate increase to about 25%. Clearly, the Model performs worst with this function.

6.4 Tunning SVM Model With Sigmoid Function

#Run SVM model (Kernel = sigmoid)
fitSVM <- svm(iphonesentiment~., data = training, kernel = "sigmoid")

summary(fitSVM)

#sigmoid tuning prediction
sig_Pred <- predict(fitSVM, testing)

#Store the predicted data in a table
SVM_table4 <- table(Predicted = sig_Pred, Actual = testing$iphonesentiment)
SVM_table4

#Calculate Accuracy
sum(diag(SVM_table4))/sum(SVM_table4)

#Calculate Misclassification Error 
1 - sum(diag(SVM_table4))/sum(SVM_table4)

cmSVM4 <- confusionMatrix(sig_Pred, testing$iphonesentiment)
cmSVM4

Confusion Matrix and Statistics Reference Prediction 1 2 3 4 1 288 6 78 65 2 1 0 0 0 3 10 16 41 4 4 406 114 237 2624

Overall Statistics Accuracy : 0.7591
95% CI : (0.7454, 0.7725) No Information Rate : 0.6923
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.3598

Misclassification Error: 0.240874

From the results, polynomial and sigmoid kernel functions performed worst on this data set and reduce the model accuracy rate to 75.91% and Misclassification Error rate to about 25%. Clearly, svm poorly performs with these functions on this data set.

6.4 Tune SVM Model

It’s worth exploring model tunning options to improve the model. Using tune(), ranges and a parameter ‘epsilon’ with sequence that goes from 0-1 with increment of 0.1 (i.e, epsilon values increase as: (0,0.1:1.0)). let’s go up to 0.5 to save time due to a large data set.

Another parameter, “cost” (default value of 1) is used to capture constraint violations. If the cost is too high, the model may expect to store too many support vectors due to high penalty for non-separable data points. This may lead to overfitting. Coversely, if the cost value is too small, the model may be underfitting with lesser accuracy rate. Specifying a large range for cost helps capture optimum cost value. Multiplying 4 cost values by 6 epsilon values will give the model 24 different combinations.

set.seed(222)

#Tunning SVM. cost ranges from 2^2, 2^3.......to 2^5
#epsilon ranges from 0, 0.1, 0.2.........0.5
system.time(SVMtune <- tune(svm, iphonesentiment~., data = training,
                    ranges = list(epsilon = seq(0,1,0.5), cost = 2^(2:5))))

summary(SVMtune)

#Plot SVMtune model Performance Evaluation of SVM
plot(SVMtune)

The plot of both cost & epsilon parameters used in tuning the model is shown above. Darker blue area represent higher accuracy/lower misclassification error region and vice vesa.

Parameter tuning of svm:

  • sampling method: 10-fold cross validation
  • best parameters: epsilon 0 and cost 32
  • best performance: error 0.268632
  • This means that the model used 10-fold CV and at cost value = 0 and epsilon value = 32, and achieved lowest misclassification error of 0.268632
#selecting best svm model
SVM_bestM <- SVMtune$best.model

summary(SVM_bestM)

SVMtune_Pred <- predict(SVM_bestM, testing)

#Store the predicted data in a table
SVM_table5 <- table(Predicted = SVMtune_Pred, Actual = testing$iphonesentiment)
SVM_table5

sum(diag(SVM_table5))/sum(SVM_table5)

#Calculate Misclassification Error 
1 - sum(diag(SVM_table5))/sum(SVM_table5)

#Confusion Matrix
CM_svm <- confusionMatrix(SVMtune_Pred, testing$iphonesentiment)
CM_svm

Tunning the model helps to obtain higher performance but still lower than performances from other models.

8.0 Gradrient Boosting Model on All Attributes

set.seed(222)

system.time(gbmFit <- train(iphonesentiment ~., data = training,
                 method = "gbm",
                 trControl = fitControl,
                 verbose = FALSE))

gbmFit

plot(gbmFit)
gbmPred <- predict(gbmFit, testing)

#Store prediction in a table
gbmtable <- table(Predicted = gbmPred, Actual = testing$iphonesentiment)
gbmtable

CM_gbm <- confusionMatrix(gbmPred, testing$iphonesentiment)
CM_gbm

#verify Accuracy
sum(diag(gbmtable))/sum(gbmtable)

#Calculate Misclassification Error 
1 - sum(diag(gbmtable))/sum(gbmtable)

9.0 Compare Models Evaluating Performance Metrics

#use getNonRejectedFormula() to select Confirmed and Tentative attributes
getNonRejectedFormula(boruta)
iphonesentiment ~ iphone + samsunggalaxy + sonyxperia + htcphone + 
    ios + googleandroid + iphonecampos + samsungcampos + htccampos + 
    iphonecamneg + samsungcamneg + htccamneg + iphonecamunc + 
    htccamunc + iphonedispos + htcdispos + iphonedisneg + htcdisneg + 
    iphonedisunc + samsungdisunc + htcdisunc + iphoneperpos + 
    samsungperpos + htcperpos + iphoneperneg + htcperneg + iphoneperunc + 
    htcperunc + iosperpos
<environment: 0x00000235db928920>
model_list <- list(c5.0 = fit_C50model,
                   rf = iphone58_RF,
                   gbm = gbmFit)


resamp <- resamples(model_list)
resamp

#Plot models summary 
bwplot(resamp)
  1. Model and Prediction Using Selected Attributes

The most optimized model selected is gbm based on its performance metrics. GBM is applied to predict the iphone sentiment using selected features and measure its Accuracy and Kappa.

set.seed(222)

#train gbm model on selected 20 variables
system.time(iphone20_gbm <- train(iphonesentiment ~., data = training2,
                 method = "gbm",
                 trControl = fitControl,
                 verbose = FALSE))


iphone20_gbm

#plot gbm model
plot(iphone20_gbm)
#prediction
gbmPRED_20 <- predict(iphone20_gbm, testing2)

#confusion matrix
CM_gbm <- confusionMatrix(gbmPRED_20, testing2$iphonesentiment)
CM_gbm
#use getConfirmedFormula() to Select only Confirmed Importance
bor_Results <- getConfirmedFormula(boruta)
bor_Results
iphonesentiment ~ iphone + samsunggalaxy + sonyxperia + htcphone + 
    ios + googleandroid + iphonecampos + htccampos + iphonecamneg + 
    iphonecamunc + htccamunc + iphonedispos + htcdispos + iphonedisneg + 
    htcdisneg + iphonedisunc + htcdisunc + iphoneperpos + samsungperpos + 
    htcperpos + iphoneperneg + iphoneperunc
<environment: 0x00000235db74fb20>
#Create df of only Confirmed Importance or recommended attributes
iphoneMatrics20 <- iphoneM_RC %>% select(iphone, samsunggalaxy, sonyxperia, htcphone,
                                            ios, googleandroid, iphonecampos, htccampos,
                                            iphonecamneg, iphonecamunc, htccamunc,
                                            iphonedispos, htcdispos, iphonedisneg,
                                            iphonedisunc, iphoneperpos, htcperpos,
                                            iphoneperneg, htcperneg, iphoneperunc,
                                            iphonesentiment)

str(iphoneMatrics20)
'data.frame':   12973 obs. of  21 variables:
 $ iphone         : int  1 1 1 1 1 41 1 1 1 1 ...
 $ samsunggalaxy  : int  0 0 0 0 0 0 0 0 0 0 ...
 $ sonyxperia     : int  0 0 0 0 0 0 0 0 0 0 ...
 $ htcphone       : int  0 0 0 0 0 0 0 0 0 0 ...
 $ ios            : int  0 0 0 0 0 6 0 0 0 0 ...
 $ googleandroid  : int  0 0 0 0 0 0 0 0 0 0 ...
 $ iphonecampos   : int  0 0 0 0 0 1 1 0 0 0 ...
 $ htccampos      : int  0 0 0 0 0 0 0 0 0 0 ...
 $ iphonecamneg   : int  0 0 0 0 0 3 1 0 0 0 ...
 $ iphonecamunc   : int  0 0 0 0 0 7 1 0 0 0 ...
 $ htccamunc      : int  0 0 0 0 0 0 0 0 0 0 ...
 $ iphonedispos   : int  0 0 0 0 0 1 13 0 0 0 ...
 $ htcdispos      : int  0 0 0 0 0 0 0 0 0 0 ...
 $ iphonedisneg   : int  0 0 0 0 0 3 10 0 0 0 ...
 $ iphonedisunc   : int  0 0 0 0 0 4 9 0 0 0 ...
 $ iphoneperpos   : int  0 1 0 1 1 0 5 3 0 0 ...
 $ htcperpos      : int  0 0 0 0 0 0 0 0 0 0 ...
 $ iphoneperneg   : int  0 0 0 0 0 0 4 1 0 0 ...
 $ htcperneg      : int  0 0 0 0 0 0 0 0 0 0 ...
 $ iphoneperunc   : int  0 0 0 1 0 0 5 0 0 0 ...
 $ iphonesentiment: Factor w/ 4 levels "1","2","3","4": 1 1 1 1 1 4 4 1 1 1 ...
#plot to view distribution of iphone sentiments
plot_ly(iphoneMatrics20, x= ~iphoneMatrics20$iphonesentiment, 
                              type='histogram') %>%
                              layout(title = "Histogram of iphonesentiment",
                              xaxis = list(title = "Classes"),
                              yaxis = list(title = "Frequency"))

NA

11.0 Prediction and Confusion Matrics on iphoneLarge Matrix

iPhone_LargeMatrix <- read.csv("iphoneLargeMatrix.csv", header = TRUE)  

#glimpse(iPhone_LargeMatrix)
set.seed(222)

#create data partition
inTraining58 <- createDataPartition(iphoneM_RC$iphonesentiment,
                                        p = .7, list = FALSE)

#Set Training and Testion Data
training <- iphoneM_RC[inTraining58,]
testing <- iphoneM_RC[-inTraining58,]

#train model
#3 fold cross validation
fitControl <- trainControl(method = "repeatedcv", number = 3, repeats = 2)

iphone_LargeMatrics20 <- iPhone_LargeMatrix %>% select(iphone, samsunggalaxy, sonyxperia,
                                            htcphone,ios, googleandroid, iphonecampos,
                                            htccampos,
                                            iphonecamneg, iphonecamunc, htccamunc,
                                            iphonedispos, htcdispos, iphonedisneg,
                                            iphonedisunc, iphoneperpos, htcperpos,
                                            iphoneperneg, htcperneg, iphoneperunc,
                                            iphonesentiment)

glimpse(iphone_LargeMatrics20)

#create a factor target variable
iphone_LargeMatrics20$iphonesentiment <- as.factor(iphone_LargeMatrics20$iphonesentiment)
str(iphone_LargeMatrics20)
  1. Final iphone Sentiments Prediction
#Gradient Boosted Model
gbmPRED_LM20 <- predict(iphone20_gbm, iphone_LargeMatrics20)
#Plot prediction results
plot(gbmPRED_LM20)

#Add predictions to the iphone Large matrix data set 
Final_iphoneSentiment <- iPhone_LargeMatrix 

Final_iphoneSentiment$iphonesentiment <- gbmPRED_LM20

#glimpse(gbmPRED_LM20)
plot_ly(Final_iphoneSentiment, x= ~Final_iphoneSentiment$iphonesentiment,
                              type='histogram') %>%
                              layout(title = "Histogram of iphonesentiment",
                              xaxis = list(title = "Classes"),
                              yaxis = list(title = "Frequency"))
#Create a csv file and write it to local drive 
write.csv(Final_iphoneSentiment, file="iphoneSentiments.csv", row.names = TRUE)

13 PREDICTION FOR SAMSUNG SENTIMENTS

13.1 Exploratory Analysis

The second part of the project workflow is to focus on galaxy small matrix to perform similar analysis and predictions.

galaxy_Matrix <- read.csv("galaxy_smallmatrix_labeled_9d.csv", header = TRUE)

glimpse(galaxy_Matrix)

plot_ly(galaxy_Matrix, x= ~galaxy_Matrix$galaxysentiment, type='histogram')

rr plot(iphone58_RF)

13.2 Feature Selection

#Use the cor() function to build the correlation matrix
CORRgalaxy_Matrix <- cor(galaxy_Matrix)
set.seed(222)

#create data partition using selected data set
inTraining20 <- createDataPartition(iphoneMatrics20$iphonesentiment, 
                                    p = .7, list = FALSE)

#Set Training and Testing Data
training2 <- iphoneMatrics20[inTraining20,]
testing2 <- iphoneMatrics20[-inTraining20,]

#train model 3 fold cross validation
fitControl <- trainControl(method = "repeatedcv", number = 3, repeats = 2)
corrplot(CORRgalaxy_Matrix)

rCORRgalaxy_Matrix <- rcorr(as.matrix(CORRgalaxy_Matrix))
print(rCORRgalaxy_Matrix)

13.3 FEATURE ELIMINATION APPROACHES

Approach 1: Recursive Feature Elimination (RFE)

Caret’s RFE function is a form of automated feature selection. The function with random forest will try every combination of feature subsets and return a final list of recommended features. Now, let’s use RFE function to remove unwanted attributes from galaxy small matrix data set. Since, RFE does not use the target so it must be removed from the data set before implementation and then added back in before modeling"

#take 1000 samples the original data set before applying RFE
set.seed(123)

galaxySample_noRFE <- galaxy_Matrix[sample(1:nrow(galaxy_Matrix), 1000, replace=FALSE),]

#set up rfeControl with random forest, repeated cross validation and no updates
ctrl <- rfeControl(functions = rfFuncs,
                   method = "repeatedcv",
                   repeats = 1,
                   verbose = FALSE)
iphone20_RF
Random Forest 

9083 samples
  20 predictor
   4 classes: '1', '2', '3', '4' 

No pre-processing
Resampling: Cross-Validated (3 fold, repeated 2 times) 
Summary of sample sizes: 6055, 6056, 6055, 6056, 6054, 6056, ... 
Resampling results across tuning parameters:

  mtry  Accuracy   Kappa    
   2    0.8030398  0.4719019
  11    0.8486738  0.6268412
  20    0.8438294  0.6176853

Accuracy was used to select the optimal model using the largest value.
The final value used for the model was mtry = 11.
#plot rf model
plot(iphone20_RF)

# Plot results
plot(rfeResults, type=c("g", "o"))

The resulting table and plot display each subset and its accuracy and kappa. An asterisk denotes the the number of features that is judged the most optimal from RFE.

It’s worth to note here that, unlike the results from Boruta model used previously in iphone feature selection, RFE model only found 2 attributes as unimportant to building rf model. We’ll use the recommended attributes in building the models for galaxy sentiment predictions. After identifying unwanted attributes, we’ll need to create a new data set and add back the dependent variable.

#create new data set with rfe recommended features
galaxyRFE <- galaxy_Matrix[,predictors(rfeResults)]
#glimpse(galaxyRFE)

#add the dependent variable to galaxyRFE
galaxyRFE$galaxysentiment <- galaxy_Matrix$galaxysentiment

#review outcome
str(galaxyRFE)

rr fit_C50model

#plot C50 model
plot(fit_C50model)


#Checking RF Performance Metrics
fit_C50model$finalModel

Call:
(function (x, y, trials = 1, rules = FALSE, weights = NULL, control = C5.0Control(), costs
 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0

Classification Tree
Number of samples: 9083 
Number of predictors: 58 

Tree size: 47 

Non-standard options: attempt to group attributes
galaxyRFE$galaxysentiment <- as.factor(galaxyRFE$galaxysentiment)
#str(galaxyRFE)

13.4 Engineering Dependent Variable

We do not really need 6 levels to understand positive and negative sentiment of Samsung galaxy. Perhaps combining some of these levels will help increase accuracy and kappa. Let’s remapped the values as follows:

1: negative 2: somewhat negative 3: somewhat positive 4: positive

#create a new dataset that will be used for recoding sentiment
galaxyM_RC <- galaxyRFE
#recode sentiment to combine factor levels 0 & 1 and 4 & 5
galaxyM_RC$galaxysentiment <- recode(galaxyM_RC$galaxysentiment, 
                                   '0' = 1, '1' = 1, '2' = 2, 
                                   '3' = 3, '4' = 4, '5' = 4) 
#make dependent a factor again
galaxyM_RC$galaxysentiment <- as.factor(galaxyM_RC$galaxysentiment)
#str(galaxyM_RC)
#check and verify classes
nlevels(galaxyM_RC$galaxysentiment)
#plot distribution of data set
plot_ly(galaxyM_RC, x= ~galaxyM_RC$galaxysentiment, 
                              type='histogram') %>%
                              layout(title = "Histogram of galaxysentiment",
                              xaxis = list(title = "Classes"),
                              yaxis = list(title = "Frequency"))
  

14 Build 4 Models To Predict Galaxy Sentiments

Our experience from iphone sentinment modeling and prediction showed that 3 algorithms (rf, C5.0, gbm) proved to do well. We’ll concentrate on these 3 algorithms and select the most optimized model to predict final Samsung galaxy sentiments.

set.seed(222)

#create data partition using selected galaxy matrix data set
inTrain_G56 <- createDataPartition(galaxyM_RC$galaxysentiment,
                                        p = .7, list = FALSE)

#Set Training and Testing Data
G56_train <- galaxyM_RC[inTrain_G56,]
G56_test <- galaxyM_RC[-inTrain_G56,]

#train model with 3 fold cross validation
fitControl <- trainControl(method = "repeatedcv", number = 3, repeats = 2)

14.1 Random Forest Model

#train Random Forest classification model with a tuneLenght = 3. 
system.time(G56_rf <- train(galaxysentiment~., data = G56_train, 
                            method = "rf", 
                            trControl=fitControl, 
                            tuneLength = 3))
G56_rf
plot(G56_rf)
#rf prediction
G56rf_PRED <- predict(G56_rf, G56_test)
#Create RF prediction Confusion Matrix
cmG56rf_PRED <- confusionMatrix(G56rf_PRED, G56_test$galaxysentiment)
cmG56rf_PRED

14.2. C5.0 Model

set.seed(222)

#Run C5.0 Decision tree model 
system.time(G56_C50 <- train(galaxysentiment~., data = G56_train, 
                            method='C5.0',
                            preProcess = c('zv'),
                            trControl = fitControl))
G56_C50

#plot C5.0 model
plot(G56_C50)
#prediction with C5.0
G56_C50_PRED <- predict(G56_C50, G56_test)

cmC50_Pred58 <- confusionMatrix(G56_C50_PRED, G56_test$galaxysentiment)

cmC50_Pred58

14.3 Gradrient Boost Model

set.seed(222)

system.time(G56_gbm <- train(galaxysentiment ~., data = G56_train,
                 method = "gbm",
                 trControl = fitControl,
                 verbose = FALSE))

G56_gbm
#plot gbm model
plot(G56_gbm)
#galaxy prediction and confusion matrix - 
G56_gbmPred <- predict(G56_gbm, G56_test)

#Confusion Matrix
cm_G56gbm <- confusionMatrix(G56_gbmPred, G56_test$galaxysentiment)
cm_G56gbm
#Checking Prediction (tbl only). Store the predicted data in a table for analysis
G56_gbmtable <- table(Predicted = G56_gbmPred, Actual = G56_test$galaxysentiment)
G56_gbmtable

#Calculate Accuracy
sum(diag(G56_gbmtable))/sum(G56_gbmtable)

#Calculate Misclassification Error 
1 - sum(diag(G56_gbmtable))/sum(G56_gbmtable)

14.4 Variable Importance

Caret’s varImp() function is another method of feature selection that returns a ranked list of features from a decision tree model. The ranked list can be used to select features importance. Let’s use VarImp() to ascertain how the model prioritized each feature in the training.

varImp(G56_C50)
#Add prediction to galaxy small matrix
P_small_galaxySentiment <- G56_test

P_small_galaxySentiment$galaxysentiment <- G56_gbmPred

#glimpse(P_small_galaxySentiment)
summary(G56_gbmPred)
#Plot predicted galaxy sentiment
plot_ly(P_small_galaxySentiment, x= ~P_small_galaxySentiment$galaxysentiment,
                              type='histogram') %>%
                              layout(title = "Histogram of Galaxysentiment",
                              xaxis = list(title = "Classes"),
                              yaxis = list(title = "Frequency"))

15.0 Models Comparison

Caret::resamples function is used to compare the models and pick the one with the highest AUC and lowest AUC standard deviation.

Gmodel_list <- list(c5.0 = G56_C50,
                      rf = G56_rf,
                     gbm = G56_gbm)


resamp <- resamples(Gmodel_list)
#summarise distribution
summary(resamp)

#Plot models summary 
bwplot(resamp)

#dot plots of results
dotplot(resamp)
LS0tDQp0aXRsZTogImlwaG9uZSBhbmQgU2Ftc3VuZyBHYWxheHkgQ29uc3VtZXIgU2VudGltZW50cyBBbmFseXNpcyINCkRhdGU6ICJTZXB0ZW1iZXIgMjAxOSINCkF1dGhvcjogIkl0b3JvIEV0dWtzIg0Kb3V0cHV0OiAgaHRtbF9ub3RlYm9vaw0KLS0tDQoNCmBgYHtyLCBlY2hvID0gVFJVRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gRkFMU0UpDQpgYGANCg0KIyBQUk9KRUNUIFRBU0tTDQoNCjEuIFRvIGFwcGx5IG1vZGVscyB0byBMYXJnZSBNYXRyaXggZmlsZSB0byBjb21wbGV0ZSB0aGUgYW5hbHlzaXMgb2Ygb3ZlcmFsbCBzZW50aW1lbnQgdG93YXJkIGJvdGggICAgICAgIGlQaG9uZSBhbmQgU2Ftc3VuZyBHYWxheHkuIA0KMi4gVG8gaWRlbnRpZnkgb25lIG9wdGltaXplZCBtb2RlbCB0byBwcmVkaWN0IHRoZSBvdmVyYWxsIHNlbnRpbWVudCB0b3dhcmQgaVBob25lcyBhbmQgb25lICAgICAgICAgICAgICAgb3B0aW1pemVkIG1vZGVsIHRvIHByZWRpY3Qgb3ZlcmFsbCBzZW50aW1lbnQgdG93YXJkIFNhbXN1bmcgR2FsYXh5IGhhbmRzZXRzIHVzaW5nIFIgZm9yIHRoZSAgICAgICAgICAgYW5hbHlzaXMuDQoNCiMjIFN1bW1hcnkgb2YgRmluZGluZ3MNCg0KMS4gTmFycmF0aXZlIG9mIHRoZSBkYXRhIHN1cHBvcnRlZCBieSB0aGUgcmVzdWx0cy4NCjIuIEhvdyBjb25maWRlbnQgdGhlIHJlc3VsdHMgYXJlLg0KMy4gVGhlIHJlcG9ydGVkIHBlcmZvcm1hbmNlIG1ldHJpY3MgZnJvbSBSDQo0LiBQZXJzb25hbCBzZW5zZSBvZiBob3cgd2VsbCB0aGUgYXR0cmlidXRlcyBtZWFzdXJlZCB3aWxsIGFjdHVhbGx5IGNhcHR1cmUgcGFnZXMgdGhhdCBoYXZlICAgICAgICAgICAgICByZWxldmFudCBzZW50aW1lbnQuDQo1LiBDYXZlYXRzIG9mIHRoaXMgYW5hbHlzaXMgcHJvY2VzcyBtaWdodCBub3QgYmUgY2FwdHVyaW5nIHRoZSBzZW50aW1lbnQgYWNjdXJhdGVseSBhbmQgc3VnZ2VzdGlvbnMgICAgICBmb3IgaG93IHRvIGRvIGJldHRlciBpbiB0aGUgbmV4dCByb3VuZCBvZiBhbmFseXNpcy4NCjYuIFdoYXQgaW1wbGljYXRpb25zIHRoZSBuYXJyYXRpdmUgaGFzIGZvciB0aGUgY2xpZW504oCZcyBnb2Fscy4NCjcuIEhpZ2gtbGV2ZWwgZXhwbGFuYXRpb24gb2YgdGhlIGRhdGEgYW5hbHl0aWNzIHBlcmZvcm1lZC4NCjguIExlc3NvbnMgTGVhcm5lZCBSZXBvcnQ6IGEgcmVwb3J0IG9mIG5vIG1vcmUgdGhhbiBmaXZlIHBhZ2VzIGluIFdvcmQgdGhhdCBpbmNsdWRlczoNCiAgIEZvciBib3RoIGlQaG9uZSBhbmQgR2FsYXh5Og0KOC4gVGhlIGNsYXNzaWZpZXIgc2VsZWN0ZWQgYW5kIHRoZSBmZWF0dXJlcyAoYXR0cmlidXRlcykgdXNlZCB0byB0cmFpbiB0aGUgY2xhc3NpZmllci4NCjkuIFJhdGlvbmFsZSBmb3Igc2VsZWN0aW5nIHRoZSBjbGFzc2lmaWVyLg0KMTAuQW55IGZlYXR1cmVzIGVsaW1pbmF0ZWQgZnJvbSB0aGUgZGF0YSBtYXRyaWNzIGFuZCByYXRpb25hbGUgZm9yIGRvaW5nIHNvLg0KMTEuQ29tcGFyYXRpdmUgcGVyZm9ybWFuY2Ugb2YgdGhlIGNsYXNzaWZpZXJzIHRyaWVkLg0KMTIuV2hhdCB3b3JrZWQgd2VsbC4gV2hhdCBkaWRu4oCZdCB3b3JrLiBXaGF0IHdhcyBkaWZmaWN1bHQuV2hhdCB3YXMgbm90IGRpZmZpY3VsdC4NCjEzLkhvdyB0aGUgcHJvY2VzcyB0byBleGVjdXRlIHNpbWlsYXIgcHJvamVjdHMgc2hvdWxkIGJlIGNoYW5nZWQgZm9yIHRoZSBmdXR1cmUuDQoNCmBgYHtyfQ0KI2luc3RhbGwucGFja2FnZXMoIkJvcnV0YSIpDQpgYGANCg0KYGBge3IgcmVzdWx0cz0naGlkZSd9DQojTG9hZGluZyBMaWJyYXJpZXMNCmxpYnJhcnkoZG9QYXJhbGxlbCkgI1NldHRpbmcgVXAgUGFyYWxsZWwgUHJvY2Vzc2luZw0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkobWxiZW5jaCkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkoSG1pc2MpDQpsaWJyYXJ5KEM1MCkNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KbGlicmFyeShCb3J1dGEpDQpsaWJyYXJ5KGtrbm4pDQojbGlicmFyeSh4Z2Jvb3N0KQ0KbGlicmFyeShlMTA3MSkNCmxpYnJhcnkocGxvdGx5KQ0KYGBgDQoNCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30NCiNGaW5kIGhvdyBtYW55IGNvcmVzIGFyZSBvbiBteSBtYWNoaW5lDQpkZXRlY3RDb3JlcygpIA0KDQojIENyZWF0ZSBDbHVzdGVyIHdpdGggZGVzaXJlZCBudW1iZXIgb2YgY29yZXMuIA0KY2wgPC0gbWFrZUNsdXN0ZXIoMikNCg0KIyBSZWdpc3RlciBDbHVzdGVyDQpyZWdpc3RlckRvUGFyYWxsZWwoY2wpDQoNCiNDb25maXJtIGhvdyBtYW55IGNvcmVzIGFyZSBub3cgImFzc2lnbmVkIiB0byBSIGFuZCBSU3R1ZGlvDQpnZXREb1BhcldvcmtlcnMoKSAjIFJlc3VsdCAyIA0KYGBgDQpgYGB7cn0NCiNTdG9wIENsdXN0ZXIgDQojc3RvcENsdXN0ZXIoY2wpDQpgYGANCioqKg0KMi4gRVhQTE9BUlRPUlkgQU5BTFlTSVMNCg0KVGhlIHdvcmtmbG93IG9mIHRoZSBwcm9qZWN0IGZvY3VzZXMgb24gb25lIHNtYWxsIG1hdHJpeCBhdCBhIHRpbWUuIEluIHRoZSBwbGFuIG9mIGF0dGFjayB3ZSB3aWxsIHVzZSBpUGhvbmUgbWF0cml4IGZpcnN0LiBPbmNlIGlQaG9uZSBtb2RlbGluZyBhbmQgcHJlZGljdGlvbiBpcyBjb21wbGV0ZSwgR2FsYXh5IG1hdHJpeCB3aWxsIGJlIGltcG9ydGVkIGFuZCBwZXJmb3JtIHRoZSBzYW1lIHN0ZXBzLiANCg0KYGBge3IsIEltcG9ydC1pcGhvbmVTTX0NCmlQaG9uZV9NYXRyaXggPC0gcmVhZC5jc3YoImlwaG9uZV9zbWFsbG1hdHJpeF9sYWJlbGVkXzhkLmNzdiIsIGhlYWRlciA9IFRSVUUpDQoNCiNnbGltcHNlKGlQaG9uZV9NYXRyaXgpDQoNCnBsb3RfbHkoaVBob25lX01hdHJpeCwgeD0gfmlQaG9uZV9NYXRyaXgkaXBob25lc2VudGltZW50LCB0eXBlPSdoaXN0b2dyYW0nKQ0KYGBgDQoNCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30NCmRpbShpUGhvbmVfTWF0cml4KQ0KDQojQ2hlY2sgZm9yIG1pc3NpbmcgdmFsdWVzDQojaXMubmEoaXBob25lX01hdHJpeCkNCiNzdW0oaXMubmEoaVBob25lX01hdHJpeCkpDQpgYGANCg0KMyBGRUFUVVJFIFNFTEVDVElPTg0KDQpXZSB3aWxsIGNyZWF0ZSBhIG5ldyBkYXRhIHNldCBmb3IgZWFjaCBmZWF0dXJlIHNlbGVjdGlvbiBtZXRob2QuIFRoZW4gbW9kZWwgd2l0aCBlYWNoIG9mIHRoZXNlIG5ldyBkYXRhIHNldHMgdG8gZGV0ZXJtaW5lIHdoaWNoIG1ldGhvZCwgaWYgYW55LCBwcm92aWRlcyB0aGUgYmVzdCBtb2RlbCBhY2N1cmFjeSBmb3IgdGhpcyBwcm9qZWN0Lg0KDQozLjEgRXhhbWluZSBDb3JyZWxhdGlvbg0KDQpXaGlsZSBjb3JyZWxhdGlvbiBkb2Vzbid0IGFsd2F5cyBpbXBseSBjYXVzYXRpb24sIGl0J3MgYSBnb29kIHByYWN0aWNlIHRvIGJlZ2luIHRoZSBhbmFseXNpcyBieSBmaW5kaW5nIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIGFsbCB0aGUgdmFyaWFibGVzLiBVc2luZyBjb3IoKSBmdW5jdGlvbiB0byBjcmVhdGUgYSBjb3JyZWxhdGlvbiBtYXRyaXggdG8gdmlzdWFsaXplIGFuZCBhc2NlcnRhaW4gdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gYWxsIG9mIHRoZSBmZWF0dXJlcy4NCg0KYGBge3IsIEJ1aWxkLUNvcnJhbHRpb24tTWF0cml4fQ0KI1VzZSB0aGUgY29yKCkgZnVuY3Rpb24gdG8gYnVpbGQgdGhlIGNvcnJlbGF0aW9uIG1hdHJpeA0KQ09SUmlwaG9uZV9NYXRyaXggPC0gY29yKGlQaG9uZV9NYXRyaXgpDQpgYGANCmBgYHtyfQ0KQ09SUmlwaG9uZV9NYXRyaXgNCm9wdGlvbnMobWF4LnByaW50PTEwMDAwMDAwKQ0KYGBgDQpgYGB7cn0NCmNvcnJwbG90KENPUlJpcGhvbmVfTWF0cml4KQ0KYGBgDQoNCmBgYHtyfQ0KI1J1bm5pbmcgY29ycmVsYXRpb24gbWF0cml4IGFzIHAtdmFsdWUNCnJDT1JSaXBob25lX01hdHJpeCA8LSByY29ycihhcy5tYXRyaXgoQ09SUmlwaG9uZV9NYXRyaXgpKQ0KYGBgDQpgYGB7cn0NCnByaW50KHJDT1JSaXBob25lX01hdHJpeCkNCmBgYA0KDQpgYGB7ciwgR2VuZXJhdGUtQ29yci1tYXRyaXgtY29sb3JzfQ0KI0dlbmVyYXRpbmcgY29ycmVsYXRpb24gbWF0cml4IHdpdGggb3RoZXIgY29sb3JzDQpwYWxldHRlID0gY29sb3JSYW1wUGFsZXR0ZShjKCJncmVlbiIsICJ3aGl0ZSIsICJyZWQiKSkgKDIwKQ0KaGVhdG1hcCh4ID0gQ09SUmlwaG9uZV9NYXRyaXgsIGNvbCA9IHBhbGV0dGUsIHN5bW0gPSBUUlVFKQ0KYGBgDQoNCmBgYHtyLCBSZW1vdmUtSGlnaGx5LUNvcnJlbGF0ZWQtdmFyaWFibGVzfQ0KI0NyZWF0ZSBuZXcgZGYgJiByZW1vdmUgaGlnaGx5IGNvcnJlbGF0ZWQgZmVhdHVyZXMNCmlwaG9uZU1hdHJpeF9Db3IgPC0gaVBob25lX01hdHJpeCAlPiUgc2VsZWN0KC1pb3MsIC1odGNwaG9uZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtc2Ftc3VuZ2Rpc3BvcywgLWlwaG9uZWRpc3BvcywgLW5va2lhZGlzdW5jLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLXNvbnlwZXJuZWcsIC1pb3NwZXJwb3MsIC1pcGhvbmVjYW11bmMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtbm9raWFsdW1pbmEsIC1nb29nbGVhbmRyb2lkLCAtc2Ftc3VuZ2NhbW5lZywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC1zb255Y2FtdW5jLCAtbm9raWFkaXNwb3MsIC1ub2tpYWNhbXBvcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC1zYW1zdW5nY2FtdW5jLCAtbm9raWFjYW11bmMsIC1zb255ZGlzcG9zLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLWlwaG9uZWRpc25lZywgLW5va2lhZGlzbmVnLCAtaHRjZGlzbmVnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLXNhbXN1bmdkaXN1bmMsIC1ub2tpYWRpc3VuYywgLXNhbXN1bmdwZXJwb3MsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtbm9raWFwZXJwb3MsIC1odGNwZXJwb3MsIC1zYW1zdW5ncGVybmVnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLXNhbXN1bmdwZXJ1bmMsIC1ub2tpYXBlcnVuYywgLWdvb2dsZXBlcnBvcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC1nb29nbGVwZXJuZWcpDQpgYGANCg0KYGBge3J9DQpnbGltcHNlKGlwaG9uZU1hdHJpeF9Db3IpDQpgYGANCg0KMy4yIEV4YW1pbmUgRmVhdHVyZSBWYXJpYW5jZXMNCg0KVGhlIGRpc3RyaWJ1dGlvbiBvZiB2YWx1ZXMgd2l0aGluIGEgZmVhdHVyZSBpcyByZWxhdGVkIHRvIGhvdyBtdWNoIGluZm9ybWF0aW9uIHRoYXQgZmVhdHVyZSBob2xkcyBpbiB0aGUgZGF0YSBzZXQuIEZlYXR1cmVzIHdpdGggbm8gdmFyaWFuY2UgY2FuIGJlIHNhaWQgdG8gaG9sZCBsaXR0bGUgdG8gbm8gaW5mb3JtYXRpb24uIEZlYXR1cmVzIHRoYXQgaGF2ZSB2ZXJ5IGxpdHRsZSwgb3IgIm5lYXIgemVybyB2YXJpYW5jZSIsIG1heSBvciBtYXkgbm90IGhhdmUgdXNlZnVsIGluZm9ybWF0aW9uLiBUbyBleHBsb3JlIGZlYXR1cmUgdmFyaWFuY2Ugd2UgY2FuIHVzZSBuZWFyWmVyb1ZhcigpIGZyb20gdGhlIGNhcmV0IHBhY2thZ2UuICANCg0KbmVhclplcm9WYXIoKSB3aXRoIHNhdmVNZXRyaWNzID0gVFJVRSByZXR1cm5zIGFuIG9iamVjdCBjb250YWluaW5nIGEgdGFibGUgaW5jbHVkaW5nOiBmcmVxdWVuY3kgcmF0aW8sIHBlcmNlbnRhZ2UgdW5pcXVlLCB6ZXJvIHZhcmlhbmNlIGFuZCBuZWFyIHplcm8gdmFyaWFuY2UNCg0KYGBge3J9DQojQ2hlY2sgZm9yIG5lYXJaZXJvIFZhcmlhbmNlcyBpcGhvbmUgc21hbGwgbWF0cml4DQpuenZfaXBob25lTWV0cmljcyA8LSBuZWFyWmVyb1ZhcihpcGhvbmVNYXRyaXhfQ29yLCBzYXZlTWV0cmljcyA9IFRSVUUpDQpuenZfaXBob25lTWV0cmljcw0KDQpkaW0oaXBob25lTWF0cml4X0NvcikNCg0KI0ZpbHRlcmluZyBvdXQgbmVhclplcm9WYXIgUHJlZGljdG9ycw0Kbnp2X2lwaG9uZU1ldHJpY3MgPC0gbmVhclplcm9WYXIoaXBob25lTWF0cml4X0NvcikNCg0KI25lYXJaZXJvVmFyKCkgd2l0aCBzYXZlTWV0cmljcyA9IEZBTFNFIHJldHVybnMgYSB2ZWN0b3IgDQojKGNyZWF0ZSBpbmRleGVzIG9mIG5lYXJaZXJvIFZhcmlhbmNlcyBQcmVkaWN0b3JzKQ0Kbnp2IDwtIG5lYXJaZXJvVmFyKGlwaG9uZU1hdHJpeF9Db3IsIHNhdmVNZXRyaWNzID0gRkFMU0UpIA0Kbnp2DQoNCiNDcmVhdGUgbmV3IGRmIHdpdGhvdXQgbmVhclplcm9WYXIgcHJlZGljdG9ycw0KZmlsdGVyZWRfbnp2IDwtIGlwaG9uZU1hdHJpeF9Db3JbLCAtbnp2XQ0KZGltKGZpbHRlcmVkX256dikNCmBgYA0KDQpGRUFUVVJFIEVMSU1JTkFUSU9OIEFQUFJPQUNIRVMNCg0KMy4zIEFwcHJvYWNoIDE6IFJlY3Vyc2l2ZSBGZWF0dXJlIEVsaW1pbmF0aW9uIChSRkUpDQoNCkZ1cnRoZXJtb3JlLCBhbiBhdXRvbWF0ZWQgZmVhdHVyZSBzZWxlY3Rpb24gZnVuY3Rpb24sIENhcmV04oCZcyByZmUoKSBmdW5jdGlvbiB3aGVuIHVzZWQgd2l0aCByYW5kb20gZm9yZXN0IHdpbGwgdHJ5IGV2ZXJ5IGNvbWJpbmF0aW9uIG9mIGZlYXR1cmUgc3Vic2V0cyBhbmQgcmV0dXJuIGEgZmluYWwgbGlzdCBvZiByZWNvbW1lbmRlZCBmZWF0dXJlcy4gDQoNCmBgYHtyLCBGZWF0dXJlLVNlbGVjdGlvbi1yZmV9DQojdGFrZSAzMDAgc2FtcGxlcyBvZiB0aGUgb3JpZ2luYWwgZGF0YSBzZXQgYmVmb3JlIGFwcGx5aW5nIFJGRQ0Kc2V0LnNlZWQoMTIzKQ0KDQppcGhvbmVTYW1wbGVfbm9SRkUgPC0gaVBob25lX01hdHJpeFtzYW1wbGUoMTpucm93KGlQaG9uZV9NYXRyaXgpLCAzMDAsIHJlcGxhY2U9RkFMU0UpLF0NCg0KI1NldCB1cCByZmVDb250cm9sIHdpdGggcmFuZG9tZm9yZXN0IHdpdGggY3Jvc3MgdmFsaWRhdGlvbiBhbmQgbm8gdXBkYXRlcw0KY3RybCA8LSByZmVDb250cm9sKGZ1bmN0aW9ucyA9IHJmRnVuY3MsDQogICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJlcGVhdGVkY3YiLA0KICAgICAgICAgICAgICAgICAgIHJlcGVhdHMgPSAxLA0KICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBGQUxTRSkNCg0KI2FwcGx5IHJmZSB0byBhbGwgdmFyaWFibGVzIGV4Y2VwdCB0aGUgdGFyZ2V0IChjb2wgNTkgImlwaG9uZXNlbnRpbWVudCIpDQpzeXN0ZW0udGltZShyZmVSZXN1bHRzIDwtIHJmZShpcGhvbmVTYW1wbGVfbm9SRkVbLDE6NThdLA0KICAgICAgICAgICAgICAgICAgaXBob25lU2FtcGxlX25vUkZFJGlwaG9uZXNlbnRpbWVudCwNCiAgICAgICAgICAgICAgICAgIHNpemVzPWMoMTo1OCksDQogICAgICAgICAgICAgICAgICByZmVDb250cm9sPWN0cmwpKQ0KYGBgDQpgYGB7cn0NCiNHZXQgcmVzdWx0cw0KcmZlUmVzdWx0cw0KYGBgDQpgYGB7cn0NCiNQbG90IHJlc3VsdHMNCnBsb3QocmZlUmVzdWx0cywgdHlwZT1jKCJnIiwgIm8iKSkNCmBgYA0KDQpUaGUgcmVzdWx0aW5nIHRhYmxlIGFuZCBwbG90IGRpc3BsYXkgZWFjaCBzdWJzZXQgYW5kIGl0cyBhY2N1cmFjeSBhbmQga2FwcGEuIEFuIGFzdGVyaXNrIGRlbm90ZXMgdGhlIHRoZSBudW1iZXIgb2YgZmVhdHVyZXMgdGhhdCBpcyBqdWRnZWQgdGhlIG1vc3Qgb3B0aW1hbCBmcm9tIFJGRS4NCg0KMy40IEFwcHJvYWNoIDI6IFVzaW5nIEJvcnV0YSBBbGdvcml0aG0gZm9yIEZlYXR1cmUgU2VsZWN0aW9uDQoNCkJvcnV0YSBpcyBiYXNlZCBvbiBSRi4gSXQgY3JlYXRlcyBzaGFkb3cgYXR0cmlidXRlcyBvZiB0aGUgb3JpZ2luYWwgYXR0cmlidXRlcyB3aXRoIGFsbCB0aGUgdmFsdWVzIGluIGVhY2ggc2hhZG93IGF0dHJpYnV0ZSBzaHVmZmxlZCBhY3Jvc3MgdG8gY3JlYXRlIHJhbmRvbW5lc3Mgd2l0aCBhIGdvb2QgcmVwcmVzZW50YXRpb24gb2YgdGhlIGRhdGEuIFJ1bm5pbmcgNTggYXR0cmlidXRlcyBvbiBhIG1vZGVsIGNyZWF0aW9uIHByb3ZlcyB0byBiZSB2ZXJ5IHRpbWUgY29uc3VtaW5nIGFuZCB0YWtlcyBhIGxvdCBvZiBzeXN0ZW0gcmVzb3VyY2VzLiBTb21lIG9mIHRoZSBmZWF0dXJlcyB3ZXJlIGZvdW5kIHRvIGJlIHVuaW1wb3J0YW50IHRvIHRoZSBtb2RlbC4gSGVuY2UsIGFwcGx5aW5nIHRoaXMgYWxnb3JpdGhtIGluIG91ciBmZWF0dXJlIHNlbGVjdGlvbiBlbGltaW5hdGUgYWxsIHVuaW1wb3J0YW50IGF0dHJpYnV0ZXMgYW5kIGFsbG93IHRoZSBtb2RlbCB0byBwcm92aWRlIHJlY29tbWVuZGVkIGF0dHJpYnV0ZXMgZm9yIG1vZGVsaW5nLg0KDQpgYGB7cn0NCiNVc2luZyBvcmlnaW5hbCBpcGhvbmVfTWF0cml4IGRhdGEgc2V0DQpzdW1tYXJ5KGlQaG9uZV9NYXRyaXgpDQpgYGANCmBgYHtyfQ0KI2NyZWF0ZSBuZXcgZGYgY29weSBvZiBvcmlnaW5hbCBkYXRhIHNldA0KaXBob25lTWF0cml4MiA8LSBpUGhvbmVfTWF0cml4DQoNCiNDaGFuZ2UgZGVwZW5kZW50IHZhcmlhYmxlIChpcGhvbmVzZW50aW1lbnQpIGZyb20gaW50ZWdlciB0byBmYWN0b3IgdmFyaWFibGUgZm9yIGNsYXNzaWZjYXRpb24gbW9kZWwNCmlwaG9uZU1hdHJpeDIkaXBob25lc2VudGltZW50IDwtIGFzLmZhY3RvcihpUGhvbmVfTWF0cml4JGlwaG9uZXNlbnRpbWVudCkNCmBgYA0KYGBge3J9DQojQ2hlY2sgc3RyLCBhbmQgaG93IG1hbnkgY2xhc3NlcyBpbiB0YXJnZXQNCnN0cihpcGhvbmVNYXRyaXgyKQ0KDQojQ2hlY2sgbnVtYmVyIG9mIGNsYXNzZXMNCm5sZXZlbHMoaXBob25lTWF0cml4MiRpcGhvbmVzZW50aW1lbnQpDQpgYGANCg0KV2UgaGF2ZSA2IGNsYXNzZXMgaW4gdGhlIGlwaG9uZXNlbnRpbWVudC4gQWNjb3JkaW5nIHRvIGlwaG9uZSBTZW50aW1lbnQgbGFiZWxpbmcsIHRoZXNlIGNsYXNzZXMgcmVwcmVzZW50IHRoZSBmb2xsb3dpbmc6DQoNCjA6IHZlcnkgbmVnYXRpdmUNCjE6IG5lZ2F0aXZlDQoyOiBzb21ld2hhdCBuZWdhdGl2ZQ0KMzogc29tZXdoYXQgcG9zaXRpdmUNCjQ6IHBvc2l0aXZlDQo1OiB2ZXJ5IHBvc2l0aXZlDQoNCg0KMy41IEVuZ2luZWVyaW5nIFRoZSBEZXBlbmRhbnQgVmFyaWFibGUNCg0KV2UgZG8gbm90IHJlYWxseSBuZWVkIDYgbGV2ZWxzIHRvIHVuZGVyc3RhbmQgcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIHNlbnRpbWVudCBvZiBpcGhvbmUuIFBlcmhhcHMgY29tYmluaW5nIHNvbWUgb2YgdGhlc2UgbGV2ZWxzIHdpbGwgaGVscCBpbmNyZWFzZSBhY2N1cmFjeSBhbmQga2FwcGEuIFVzaW5nIGRwbHlyIHBhY2thZ2UgcmVjb2RlKCkgZnVuY3Rpb24gY2FuIGhlbHAgdXMgd2l0aCB0aGlzLiBMZXQncyByZW1hcHBlZCB0aGUgdmFsdWVzIGFzIGZvbGxvd3M6DQoNCjE6IG5lZ2F0aXZlDQoyOiBzb21ld2hhdCBuZWdhdGl2ZQ0KMzogc29tZXdoYXQgcG9zaXRpdmUNCjQ6IHBvc2l0aXZlDQoNCmBgYHtyLCBSZWNvZGluZy1pcGhvbmUtbWF0cml4fQ0KI2NyZWF0ZSBhIG5ldyBkYXRhc2V0IHRoYXQgd2lsbCBiZSB1c2VkIGZvciByZWNvZGluZyBzZW50aW1lbnRzDQppcGhvbmVNX1JDIDwtIGlwaG9uZU1hdHJpeDINCg0KI3JlY29kZSBzZW50aW1lbnRzIGJ5IGNvbWJpbmluZyBmYWN0b3IgbGV2ZWxzIDAgJiAxIGFuZCA0ICYgNQ0KaXBob25lTV9SQyRpcGhvbmVzZW50aW1lbnQgPC0gcmVjb2RlKGlwaG9uZU1fUkMkaXBob25lc2VudGltZW50LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzAnID0gMSwgJzEnID0gMSwgJzInID0gMiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICczJyA9IDMsICc0JyA9IDQsICc1JyA9IDQpIA0KYGBgDQpgYGB7cn0NCiNpbnNwZWN0IGxldmVscyByZWNvZGUgcmVzdWx0cw0KI3N1bW1hcnkoaXBob25lTV9SQykNCnN0cihpcGhvbmVNX1JDKQ0KYGBgDQpgYGB7ciwgQ29udmVydC10by1mYWN0b3J9DQojY29udmVydCBkZXBlbmRlbnQgdmFyaWFibGUsIGlwaG9uZXNlbnRpbWVudCB0byBhIGZhY3Rvcg0KaXBob25lTV9SQyRpcGhvbmVzZW50aW1lbnQgPC0gYXMuZmFjdG9yKGlwaG9uZU1fUkMkaXBob25lc2VudGltZW50KQ0KbmxldmVscyhpcGhvbmVNX1JDJGlwaG9uZXNlbnRpbWVudCkNCmBgYA0KDQpUbyBwZXJmb3JtIGZlYXR1cmUgc2VsZWN0aW9uIHVzaW5nIEJvcnV0YSwgbGV0J3MgY3JlYXRlIGEgbWFuYWdlYWJsZSBzYW1wbGUgc2l6ZSBvZiB0aGUgZGF0YSBzZXQuDQoNCmBgYHtyLCBidWlsZC1Cb3J1dGF9DQppcGhvbmVfQmRmIDwtIGlwaG9uZU1hdHJpeDJbc2FtcGxlKDE6bnJvdyhpcGhvbmVNYXRyaXgyKSwgMTAwMCwgcmVwbGFjZT1GQUxTRSksXQ0KDQpzZXQuc2VlZCgxMTEpDQoNCiNVc2UgQm9ydXRhKCkgb24gdGhlIHRhcmdldCBhbmQgaW5jbHVkZSBhbGwgaW5kZXBlbmRlbnQgdmFyaWFibGUuIFNldCBkb1RyYWNlIHRvIHZpZXcgcHJvZ3Jlc3MNCmJvcnV0YSA8LSBCb3J1dGEoaXBob25lc2VudGltZW50fiAuLCBkYXRhID0gaXBob25lX0JkZiwgZG9UcmFjZSA9IDIpDQoNCiNWaWV3IEJvcnV0YSBvdXRwdXQNCnByaW50KGJvcnV0YSkNCmBgYA0KDQpUaGUgYWxnb3JpdGhtIGFmdGVyIHJ1bm5pbmcgdGhyb3VnaCA5OSBpdGVyYXRpb25zLCBmb3VuZCA4IFRlbnRhdGl2ZSBhdHRyaWJ1dGVzLCAyMCBDb25maXJtZWQgSW1wb3J0YW5jZSBhdHRyaWJ1dGVzIGFuZCAzMCBVbmNvbmZpcm1lZCBJbXBvcnRhbmNlLiBUbyBwcm9wZXJseSBjbGFzc2lmeSwgd2UnbGwgbmVlZCB0byB2ZXJpZnkgaWYgdGhlIDggdGVudGF0aXZlIGF0dHJpYnV0ZXMgYXJlIENvbmZpcm1lZCBJbXBvcnRhbmNlIG9yIFVuY29uZmlybWVkIGJ5IHJ1bm5pbmcgYSBmaXggb24gQm9ydXRhLg0KDQpgYGB7cn0NCiN2ZXJ0aWNhbGx5IHBsYWNpbmcgdGhlIGNoYXJ0IHdpdGggYXhpcyBsYWJlbCBmb250IHNpemUgb2YgMC42NQ0KcGxvdChib3J1dGEsIGxhcyA9IDIsIGNleC5heGlzID0gMC42NSkNCmBgYA0KDQpGcm9tIHRoZSBib3hwbG90IGFib3ZlLCBhbGwgQ29uZmlybWVkIGF0dHJpYnV0ZXMgYXJlIGNvbG9yZWQgImdyZWVuIiB3aXRoIHRoZWlyIHJlc3BlY3RpdmUgbGV2ZWwgb2YgaW1wb3J0YW5jZS4gVW5jb25maXJtZWQgYXR0cmlidXRlcyBhcmUgY29sb3JlZCBpbiAicmVkIi4gVGhlIDggVGVudGF0aXZlIGF0dHJpYnV0ZXMgKGh0Y2NhbW5lZywgaHRjcGVydW5jLCBzYW1zdW5nZGlzdW5jLCBldGMuKSBhcmUgY29sb3JlZCBpbiAieWVsbG93IiBhbmQgdGhlIHNoYWRvdyBhdHRyaWJ1dGVzIGFyZSBibHVlLiANCg0KVGhlIGltcG9ydGFuY2Ugb2YgdGhlIHNoYWRvdyBhdHRyaWJ1dGVzIGNhbiBiZSB1c2VkIHRvIG1lYXN1cmUgdGhlIGltcG9ydGFuY2Ugb2YgZWFjaCBhdHRyaWJ1dGUgb24gYWNjdXJhY3kgb2YgdGhlIG1vZGVsLiBUaGUgdGhyZWUgYmx1ZSBzaGFkb3cgYXR0cmlidXRlcyBzaG93biBvbiB0aGUgYm94cGxvdCBjb3JyZXNwb25kIHRvIE1JTiwgQVZFLCBNQVggb2YgdGhlIHNoYWRvdyBhdHRyaWJ1dGVzJyBpbXBvcnRhbmNlIGxldmVsLiAgDQoNCmBgYHtyLCBCb3J1dGEtSW1wb3J0YW5jZS1IaXN0b3J5fQ0KcGxvdEltcEhpc3RvcnkoYm9ydXRhKQ0KYGBgDQoNCkFib3ZlIHBsb3Qgc2hvd3MgSW1wb3J0YW5jZSB2YWx1ZXMuIFdpdGhpbiB0aGUgYmx1ZSBsaW5lIGFyZSBVbmNvbmZpcm1lZCBhdHRyaWJ1dGVzLiBDb25maXJtZWQgYXR0cmlidXRlcyBhbmQgdGhlaXIgbGV2ZWwgb2YgaW1wb3J0YW5jZSBhcmUgcmVwcmVzZW50ZWQgaW4gZ3JlZW4gbGluZXMuIFRoZSB0ZW50YXRpdmUgYXR0cmlidXRlcyAobm8gZGVjaXNpb24gZnJvbSBtb2RlbCkgZmFsbHMgb24gdGhlIHllbGxvdyBsaW5lIHJlZ2lvbi4NCg0KMy40LjEgRml4aW5nIFRlbnRhdGl2ZSBBdHRyaWJ1dGVzDQoNCmBgYHtyLCBGaXhpbmctdW5kZWNpZGVkLWF0dHJpYnV0ZXN9DQojYXBwbHkgVGVudGF0aXZlUm91Z2hGaXgoKSBmdW5jdGlvbiB0byBmaXggdGhlIHVuZGVjaWRlZCAodGVudGF0aXZlKSBhdHRyaWJ1dGVzDQpib3JGaXhfZGYgPC0gVGVudGF0aXZlUm91Z2hGaXgoYm9ydXRhKQ0KDQpwcmludChib3JGaXhfZGYpDQpgYGANCg0KYGBge3J9DQpnbGltcHNlKGJvckZpeF9kZikNCmBgYA0KDQpUaGUgZm9sbG93aW5nIHJlc3VsdHMgb2J0YWluZWQgYWZ0ZXIgZml4aW5nIHRoZSB0ZW50YXRpdmUgYXR0cmlidXRlczoNCg0KLS0gQm9ydXRhIHBlcmZvcm1lZCA5OSBpdGVyYXRpb25zIGluIDguNTA4NDQxIG1pbnMuDQotLSBUZW50YXRpdmVzIHJvdWdoZml4ZWQgb3ZlciB0aGUgbGFzdCA5OSBpdGVyYXRpb25zLg0KLS0gMjAgYXR0cmlidXRlcyBjb25maXJtZWQgaW1wb3J0YW50OiBnb29nbGVhbmRyb2lkLCBodGNjYW1uZWcsIGh0Y2NhbXBvcywgaHRjZGlzbmVnLCAgICAgICAgICAgICAgICAgICAgaHRjZGlzcG9zIGFuZCBtb3JlOw0KLS0gMzggYXR0cmlidXRlcyBjb25maXJtZWQgdW5pbXBvcnRhbnQ6IGdvb2dsZXBlcm5lZywgZ29vZ2xlcGVycG9zLCBnb29nbGVwZXJ1bmMsICAgICAgICAgICAgICAgICAgICAgICAgaHRjY2FtdW5jLCBodGNkaXN1bmMgYW5kIG1vcmU7DQoNCkNsZWFybHksIFRlbnRhdGl2ZVJvdWdoRml4IGZ1bmN0aW9uIGhlbHBzIHVzIHRvIGlkZW50aWZ5IGFsbCB0aGUgVW5jb25maXJtZWQgYW5kIENvbmZpcm1lZCBJbXBvcnRhbmNlIG9mIHRoZSBhdHRyaWJ1dGVzIGFuZCBjYXRlZ29yaXplIHRoZW0gYWNjb3JkaW5nIHRvIGVhY2ggYXR0cmlidXRlJ3MgaW1wb3J0YW5jZS4gDQoNCmBgYHtyLCBCb3J1dGEtU3RhdHN9DQojb2J0YWluIGF0dHJpYnV0ZSBzdGF0cw0KYXR0U3RhdHMoYm9ydXRhKQ0KYGBgDQoNClRoZSBhYm92ZSBzdGF0cyBzaG93IGRldGFpbCBzdGF0aXN0aWNhbCBpbmZvcm1hdGlvbiBvZiBlYWNoIGF0dHJpYnV0ZSBJbXBvcnRhbmNlOiBNZWFuLCBNZWRpYW4sIE1pbiwgTWF4LCBhbmQgTm9ybWluYWwgSGlzdG9yeS4gUHJldmlvdXNseSBmcm9tIHRoZSBib3hwbG90IGFib3ZlLCB3ZSBub3RpY2VkIHRoYXQgYXR0cmlidXRlcyBsaWtlIGlwaG9uZSwgU2Ftc3VuZ2dhbGF4eSwgaW9zLCBpcGhvbmVkaXNwb3MsIGlwaG9uZWRpc3VuYyB3aXRoIG5vcm1IaXRzIG9mIDEuMDAwMDAwIGFyZSAxMDAlIG1vcmUgaW1wb3J0YW50IHRoYW4gdGhlaXIgc2hhZG93cy4gV2hpbGUgc29tZSBhdHRyaWJ1dGVzIHdpdGggMC4wJSBoYXZlIG5vIEltcG9ydGFuY2UgYW5kIHdlcmUgZm91bmQgdG8gYmUgbGVzcyBpbXBvcnRhbnQgdGhhbiB0aGVpciBzaGFkb3dzLCB0aHVzIHJlamVjdGVkIGJ5IHRoZSBhbGdvcml0aG0uDQoNCmBgYHtyLCBDb25maXJtZWQtYW5kLVRlbnRhdGl2ZS1hdHRyaWJ1dGVzfQ0KI3VzZSBnZXROb25SZWplY3RlZEZvcm11bGEoKSB0byBzZWxlY3QgQ29uZmlybWVkIGFuZCBUZW50YXRpdmUgYXR0cmlidXRlcw0KZ2V0Tm9uUmVqZWN0ZWRGb3JtdWxhKGJvcnV0YSkNCmBgYA0KYGBge3IsIENvbmZpcm1lZC1JbXBvcnRhbmNlfQ0KI3VzZSBnZXRDb25maXJtZWRGb3JtdWxhKCkgdG8gU2VsZWN0IG9ubHkgQ29uZmlybWVkIEltcG9ydGFuY2UNCmJvcl9SZXN1bHRzIDwtIGdldENvbmZpcm1lZEZvcm11bGEoYm9ydXRhKQ0KYm9yX1Jlc3VsdHMNCmBgYA0KDQpgYGB7ciwgUmVjb21tZW5kZWQtQXR0cmlidXRlc30NCiNDcmVhdGUgZGYgb2Ygb25seSBDb25maXJtZWQgSW1wb3J0YW5jZSBvciByZWNvbW1lbmRlZCBhdHRyaWJ1dGVzDQppcGhvbmVNYXRyaWNzMjAgPC0gaXBob25lTV9SQyAlPiUgc2VsZWN0KGlwaG9uZSwgc2Ftc3VuZ2dhbGF4eSwgc29ueXhwZXJpYSwgaHRjcGhvbmUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlvcywgZ29vZ2xlYW5kcm9pZCwgaXBob25lY2FtcG9zLCBodGNjYW1wb3MsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlwaG9uZWNhbW5lZywgaXBob25lY2FtdW5jLCBodGNjYW11bmMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlwaG9uZWRpc3BvcywgaHRjZGlzcG9zLCBpcGhvbmVkaXNuZWcsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlwaG9uZWRpc3VuYywgaXBob25lcGVycG9zLCBodGNwZXJwb3MsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlwaG9uZXBlcm5lZywgaHRjcGVybmVnLCBpcGhvbmVwZXJ1bmMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlwaG9uZXNlbnRpbWVudCkNCg0Kc3RyKGlwaG9uZU1hdHJpY3MyMCkNCg0KI3Bsb3QgdG8gdmlldyBkaXN0cmlidXRpb24gb2YgaXBob25lIHNlbnRpbWVudHMNCnBsb3RfbHkoaXBob25lTWF0cmljczIwLCB4PSB+aXBob25lTWF0cmljczIwJGlwaG9uZXNlbnRpbWVudCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlPSdoaXN0b2dyYW0nKSAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxheW91dCh0aXRsZSA9ICJIaXN0b2dyYW0gb2YgaXBob25lc2VudGltZW50IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJDbGFzc2VzIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiRnJlcXVlbmN5IikpDQogIA0KYGBgDQoNCjQuIEJVSUxEIE1PREVMUw0KDQpXaXRoIGZlYXR1cmUgc2VsZWN0aW9uIGFuZCBwcmVwcm9jZXNzaW5nIG9mIHRoZSBzbWFsbCBtYXRyaXggZmlsZSBjb21wbGV0ZWQsIGl0IGlzIHRpbWUgdG8gYnVpbGQgbW9kZWxzLiBUaGUgaW5pdGlhbCBtb2RlbHMgd2lsbCB1c2UgYWxsIDU4IGF0dHJpYnV0ZXMgZnJvbSB0aGUgZGF0YSBzZXQgdG8gZ2FpbiAib3V0IG9mIHRoZSBib3giIGFjY3VyYWN5IGFuZCBrYXBwYS4gVGhlbiwgd2UnbGwgcHJlZGljdCBhZ2FpbiB3aXRoIGZlYXR1cmVzIHNlbGVjdGVkIGZyb20gZGF0YSBzZXRzLiBUaGUgZ29hbCBpcyB0byBmaW5kIHRoZSBiZXN0IGNvbWJpbmF0aW9uIG9mIGRhdGEgc2V0IGFuZCBhbGdvcml0aG0gYXMgbWVhc3VyZWQgYnkgcmVzdWx0aW5nIHBlcmZvcm1hbmNlIG1ldHJpY3MuDQoNCjQuMSBDcmVhdGUgRGF0YSBQYXJ0aXRpb24gVXNpbmcgQ2FyZXQgKGFsbCA1OCBpbmRlcGVuZGVudCBhdHRyaWJ1dGVzKQ0KDQpgYGB7ciwgRGF0YS1QYXJ0aXRpb24tTW9kZWwtQ3Jvc3MtVmFsaWRhdGlvbn0NCnNldC5zZWVkKDIyMikNCg0KI2NyZWF0ZSBkYXRhIHBhcnRpdGlvbg0KaW5UcmFpbmluZzU4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oaXBob25lTV9SQyRpcGhvbmVzZW50aW1lbnQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcCA9IC43LCBsaXN0ID0gRkFMU0UpDQoNCiNTZXQgVHJhaW5pbmcgYW5kIFRlc3Rpb24gRGF0YQ0KdHJhaW5pbmcgPC0gaXBob25lTV9SQ1tpblRyYWluaW5nNTgsXQ0KdGVzdGluZyA8LSBpcGhvbmVNX1JDWy1pblRyYWluaW5nNTgsXQ0KDQojdHJhaW4gbW9kZWwNCiMzIGZvbGQgY3Jvc3MgdmFsaWRhdGlvbg0KZml0Q29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gInJlcGVhdGVkY3YiLCBudW1iZXIgPSAzLCByZXBlYXRzID0gMikNCmBgYA0KDQo0LjIgUmFuZG9tIEZvcmVzdCBNb2RlbCAodXNpbmcgYWxsIHZhcmlhYmxlcykNCg0KYGBge3IgQnVpbGQtUkYtbW9kZWx9DQojdHJhaW4gUmFuZG9tIEZvcmVzdCBjbGFzc2lmaWNhdGlvbiBtb2RlbA0Kc3lzdGVtLnRpbWUoaXBob25lNThfUkYgPC0gdHJhaW4oaXBob25lc2VudGltZW50fi4sIGRhdGEgPSB0cmFpbmluZywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJmIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sPWZpdENvbnRyb2wsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR1bmVMZW5ndGggPSAzKSkNCmBgYA0KYGBge3J9DQppcGhvbmU1OF9SRg0KYGBgDQpgYGB7cn0NCnBsb3QoaXBob25lNThfUkYpDQpgYGANCg0KYGBge3IgUkY1OC1QcmVkaWN0aW9uLUNvbmZ1c2lvbi1NYXRyaXh9DQpQcmVkX3JmNTggPC0gcHJlZGljdChpcGhvbmU1OF9SRiwgdGVzdGluZykNCg0KI2lwaG9uZTU4X1JGIENyZWF0ZSBDb25mdXNpb24gTWF0cml4DQpDTV9yZjU4IDwtIGNvbmZ1c2lvbk1hdHJpeChQcmVkX3JmNTgsIHRlc3RpbmckaXBob25lc2VudGltZW50KQ0KQ01fcmY1OA0KYGBgDQoNClJhbmRvbSBGb3Jlc3QgDQoNCjkwODMgc2FtcGxlcw0KICA1OCBwcmVkaWN0b3INCiAgIDQgY2xhc3NlczogJzEnLCAnMicsICczJywgJzQnIA0KDQpObyBwcmUtcHJvY2Vzc2luZw0KUmVzYW1wbGluZzogQ3Jvc3MtVmFsaWRhdGVkICgzIGZvbGQsIHJlcGVhdGVkIDIgdGltZXMpIA0KU3VtbWFyeSBvZiBzYW1wbGUgc2l6ZXM6IDYwNTUsIDYwNTYsIDYwNTUsIDYwNTYsIDYwNTQsIDYwNTYsIC4uLiANClJlc2FtcGxpbmcgcmVzdWx0cyBhY3Jvc3MgdHVuaW5nIHBhcmFtZXRlcnM6DQoNCiAgbXRyeSAgQWNjdXJhY3kgICBLYXBwYSAgICANCiAgIDIgICAgMC43NzE3NzMyICAwLjM1NTU2NzkNCiAgMzAgICAgMC44NDk2NjQ1ICAwLjYyODQwNDgNCiAgNTggICAgMC44NDQ2NTUwICAwLjYxODY2MTgNCg0KQWNjdXJhY3kgd2FzIHVzZWQgdG8gc2VsZWN0IHRoZSBvcHRpbWFsIG1vZGVsIHVzaW5nIHRoZSBsYXJnZXN0IHZhbHVlLg0KVGhlIGZpbmFsIHZhbHVlIHVzZWQgZm9yIHRoZSBtb2RlbCB3YXMgbXRyeSA9IDMwLg0KDQpUaGUgUkYgbW9kZWwgdXNpbmcgYWxsIDU4IGF0dHJpYnV0ZXMgb2J0YWluIDg0JSBBY2N1cmFjeSByYXRlIGFuZCA2MiUgS2FwcGEuIENsYXNzIDQgKHBvc2l0aXZlIGlwaG9uZSBzZW50aW1lbnQpIGhhcyB0aGUgaGlnZXN0IHNlbnNpdGl2aXR5LiBUaGlzIG1lYW5zIHRoZSBtb2RlbCBwZXJmb3JtZWQgdGhlIGZvbGxvd2luZyBwcmVkaWN0aW9uczoNCg0KQWNjdXJhdGVseSBwcmVkaWN0ZWQgDQoxIChuZWdhdGl2ZSBpcGhvbmUgc2VudGltZW50KSAtICAgICAgICAgMzc4IHRpbWVzDQoyIChzb21ld2hhdCBuZWdhdGl2ZSBpcGhvbmUgc2VudGltZW50KSAtIDE5IHRpbWVzDQozIChzb21ld2hhdCBwb3NpdGl2ZSBpcGhvbmUgc2VudGltZW50KSAtMjI5IHRpbWVzDQo0IChwb3NpdGl2ZSBpcGhvbmUgc2VudGltZW50KSAtICAgICAgICAyNjY3IHRpbWVzDQoNCg0KNC4zIEJ1aWxkaW5nIFJGIE1vZGVsIFVzaW5nIEZlYXR1cmUgU2VsZWN0aW9uDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMjIyKQ0KDQojY3JlYXRlIGRhdGEgcGFydGl0aW9uIHVzaW5nIHNlbGVjdGVkIGRhdGEgc2V0DQppblRyYWluaW5nMjAgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihpcGhvbmVNYXRyaWNzMjAkaXBob25lc2VudGltZW50LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHAgPSAuNywgbGlzdCA9IEZBTFNFKQ0KDQojU2V0IFRyYWluaW5nIGFuZCBUZXN0aW5nIERhdGENCnRyYWluaW5nMiA8LSBpcGhvbmVNYXRyaWNzMjBbaW5UcmFpbmluZzIwLF0NCnRlc3RpbmcyIDwtIGlwaG9uZU1hdHJpY3MyMFstaW5UcmFpbmluZzIwLF0NCg0KI3RyYWluIG1vZGVsIDMgZm9sZCBjcm9zcyB2YWxpZGF0aW9uDQpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsIG51bWJlciA9IDMsIHJlcGVhdHMgPSAyKQ0KYGBgDQoNCmBgYHtyLCBSYW5kb20tRm9yZXN0LU1vZGVsLVNlbGVjdGVkLVZhcmlhYmxlc30NCiN0cmFpbiBSYW5kb20gRm9yZXN0IGNsYXNzaWZpY2F0aW9uIG1vZGVsIHdpdGggYSB0dW5lTGVuZ2h0ID0gMw0Kc3lzdGVtLnRpbWUoaXBob25lMjBfUkYgPC0gdHJhaW4oaXBob25lc2VudGltZW50fi4sIGRhdGEgPSB0cmFpbmluZzIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJmIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2w9Zml0Q29udHJvbCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gMykpDQpgYGANCmBgYHtyfQ0KaXBob25lMjBfUkYNCg0KI3Bsb3QgcmYgbW9kZWwNCnBsb3QoaXBob25lMjBfUkYpDQpgYGANCg0KUmFuZG9tIEZvcmVzdCANCjkwODMgc2FtcGxlcw0KICAyMCBwcmVkaWN0b3INCiAgIDQgY2xhc3NlczogJzEnLCAnMicsICczJywgJzQnIA0KDQpObyBwcmUtcHJvY2Vzc2luZw0KUmVzYW1wbGluZzogQ3Jvc3MtVmFsaWRhdGVkICgzIGZvbGQsIHJlcGVhdGVkIDIgdGltZXMpIA0KU3VtbWFyeSBvZiBzYW1wbGUgc2l6ZXM6IDYwNTUsIDYwNTYsIDYwNTUsIDYwNTYsIDYwNTQsIDYwNTYsIC4uLiANClJlc2FtcGxpbmcgcmVzdWx0cyBhY3Jvc3MgdHVuaW5nIHBhcmFtZXRlcnM6DQogIG10cnkgIEFjY3VyYWN5ICAgS2FwcGEgICAgDQogICAyICAgIDAuODAzMDM5OCAgMC40NzE5MDE5DQogIDExICAgIDAuODQ4NjczOCAgMC42MjY4NDEyDQogIDIwICAgIDAuODQzODI5NCAgMC42MTc2ODUzDQoNCkFjY3VyYWN5IHdhcyB1c2VkIHRvIHNlbGVjdCB0aGUgb3B0aW1hbCBtb2RlbCB1c2luZyB0aGUgbGFyZ2VzdCB2YWx1ZS4NClRoZSBmaW5hbCB2YWx1ZSB1c2VkIGZvciB0aGUgbW9kZWwgd2FzIG10cnkgPSAxMS4NCg0KYGBge3IsIHJmMjAtUHJlZGljdGlvbn0NClByZWRfcmYyMCA8LSBwcmVkaWN0KGlwaG9uZTIwX1JGLCB0ZXN0aW5nMikNCg0KUHJlZF9yZjIwDQoNCiNDcmVhdGUgQ29uZnVzaW9uIE1hdHJpeA0KY29uZnVzaW9uTWF0cml4KFByZWRfcmYyMCwgdGVzdGluZzIkaXBob25lc2VudGltZW50KQ0KYGBgDQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KDQpDb25mdXNpb24gTWF0cml4IGFuZCBTdGF0aXN0aWNzDQogICAgICAgIFJlZmVyZW5jZQ0KUHJlZGljdGlvbiAgICAxICAgIDIgICAgMyAgICA0DQogICAgICAgICAxICAzODMgICAgMyAgICA1ICAgMTANCiAgICAgICAgIDIgICAgMCAgIDE5ICAgIDAgICAgNg0KICAgICAgICAgMyAgICAyICAgIDAgIDIyOSAgICA5DQogICAgICAgICA0ICAzMjAgIDExNCAgMTIyIDI2NjgNCg0KT3ZlcmFsbCBTdGF0aXN0aWNzDQogICAgICAgICAgICAgIEFjY3VyYWN5IDogMC44NDgxICAgICAgICAgIA0KICAgICAgICAgICAgICAgICA5NSUgQ0kgOiAoMC44MzY0LCAwLjg1OTIpDQogICAgTm8gSW5mb3JtYXRpb24gUmF0ZSA6IDAuNjkyMyAgICAgICAgICANCiAgICBQLVZhbHVlIFtBY2MgPiBOSVJdIDogPCAyLjJlLTE2ICAgICAgIA0KICAgICAgICAgICAgICAgICAgS2FwcGEgOiAwLjYyMTggIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQpUaGUgYWJvdmUgcmVzdWx0cyBzaG93IHdlIGNhbiBzaWduaWZpY2FudGx5IHJlZHVjZSB0aGUgbnVtYmVyIG9mIGF0dHJpYnV0ZXMgYnkgYWJvdXQgNTAlIG9mIHRoZSBvcmlnaW5hbCBkYXRhIHNldCB3aXRob3V0IHNpZ25pZmljYW50bHkgbG9zaW5nIG1vZGVsIHByZWRpY3Rpb24gYWNjdXJhY3kuIEhvd2V2ZXIsIHdlJ3ZlIGdhaW4gYWR2YW50YWdlIGluIHJlZHVjaW5nIHJ1bm5pbmcgdGltZSBhbmQgbGVzcyBzeXN0ZW0gcmVzb3VyY2VzIHVzZWQuIEl0J3Mgd29ydGggdG8gbm90ZSB0aGF0IHRoZSBtb2RlbCBwcmVkaWN0aW9uIGFjY3VyYWN5IGhhcyBsaXR0bGUgb3Igbm8gZWZmZWN0IHdpdGggbGVzcyBhdHRyaWJ1dGVzLg0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCg0KNS4gQnVpbGQgQzUuMCBNb2RlbCAodXNpbmcgYWxsIDU4IGF0dHJpYnV0ZXMpDQoNCmBgYHtyLCBCdWlsZC1DNTAtTW9kZWwtaXBob25lU019DQpzZXQuc2VlZCgyMjIpDQoNCiNSdW4gQzUuMCBEZWNpc2lvbiB0cmVlIG1vZGVsIA0Kc3lzdGVtLnRpbWUoZml0X0M1MG1vZGVsIDwtIHRyYWluKGlwaG9uZXNlbnRpbWVudH4uLCBkYXRhID0gdHJhaW5pbmcsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZD0nQzUuMCcsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoJ3p2JyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkpDQpgYGANCmBgYHtyfQ0KZml0X0M1MG1vZGVsDQpgYGANCmBgYHtyfQ0KI3Bsb3QgQzUwIG1vZGVsDQpwbG90KGZpdF9DNTBtb2RlbCkNCg0KI0NoZWNraW5nIFJGIFBlcmZvcm1hbmNlIE1ldHJpY3MNCmZpdF9DNTBtb2RlbCRmaW5hbE1vZGVsDQpgYGANCg0KNS4xIEM1LjAgUHJlZGljdGlvbiAmIENvbmZ1c2lvbiBNYXRyaXgNCg0KYGBge3IsIHByZWRpY3Rpb24tYzUwfQ0KI3ByZWRpY3Rpb24gDQpDNTBQcmVkXzU4IDwtIHByZWRpY3QoZml0X0M1MG1vZGVsLCB0ZXN0aW5nKQ0KDQojY29uZnVzaW9uIG1hdHJpeA0KY21DNTBfUHJlZDU4IDwtIGNvbmZ1c2lvbk1hdHJpeChDNTBQcmVkXzU4LCB0ZXN0aW5nJGlwaG9uZXNlbnRpbWVudCkNCg0KY21DNTBfUHJlZDU4DQpgYGANCg0KNi4wIFNWTSBNb2RlbCAoZnJvbSB0aGUgZTEwNzEgcGFja2FnZSkgVXNpbmcgQWxsIEF0dHJpYnV0ZXMgaXBob25lIFNtYWxsIE1hdHJpeA0KDQpgYGB7cn0NCmxpYnJhcnkoa2VybmxhYikgI3VzZSBmb3IgZmVhdHVyZSBzZWxlY3Rpb24gaW4gc3ZtDQpsaWJyYXJ5KGdncGxvdDIpICN0byBjcmVhdGUgYm94cGxvdCB0byB2aWV3IGRhdGEgcG9pbnQgd2l0aCBzdm0NCmxpYnJhcnkocmVzaGFwZTIpI3dpbGwgYXNzaXN0IGluIGNyZWF0aW5nIGJveHBsb3RzDQpsaWJyYXJ5KHBST0MpDQojbGlicmFyeShra25uKQ0KYGBgDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMjIyKQ0KDQojdmlldyB0cmFpbmluZyBhbmQgdGVzdGluZyBkYXRhIHNldHMgb2YgYWxsIDU4IHZhcmlhYmxlcyBwcmV2aW91c2x5IGNyZWF0ZWQgYmVmb3JlIFNWTSBNb2RlbGluZw0Kc3RyKHRyYWluaW5nKQ0KDQpzdHIodGVzdGluZykNCmBgYA0KDQo2LjEgU1ZNLUtlcm5lbDogUmFkaWFsDQpgYGB7ciwgQnVpbGQtU1ZNLXJhZGlhbH0NCiNSdW4gU1ZNIG1vZGVsIChLZXJuZWwgPSByYWRpYWwpDQpzeXN0ZW0udGltZShmaXRTVk0gPC0gc3ZtKGlwaG9uZXNlbnRpbWVudH4uLCBkYXRhID0gdHJhaW5pbmcpKQ0KYGBgDQpgYGB7cn0NCnN1bW1hcnkoZml0U1ZNKQ0KYGBgDQoNCkFmdGVyIGJ1aWxkaW5nIG1vZGVsIHdpdGggdGhlIHJhZGlhbCBrZXJuZWwsIHdlIG9idGFpbiAzODAyIHN1cHBvcnQgdmVjdG9ycyBmb3IgdGhlIDQgY2xhc3NlcyBvZiBpcGhvbmVzZW50aW1lbnQuIENsYXNzLTEgKDEyMTgpLCBDbGFzcy0yICgxNDkxKSwgQ2xhc3MtMyAoNzc1KSwgQ2xhc3MtNCAoMzE4KQ0KDQo2LjEuMSBQcmVkaWN0aW9uICYgQ29uZnVzaW9uIE1hdHJpeCAgZm9yIFNWTSAoUmFkaWFsIEZ1bmN0aW9uKQ0KYGBge3IsIFNWTS1yZWRpYWwtcHJlZGljdGlvbn0NCiNwcmVkaWN0aW9uDQpyYWRfUHJlZCA8LSBwcmVkaWN0KGZpdFNWTSwgdGVzdGluZykNCg0KI1N0b3JlIHRoZSBwcmVkaWN0ZWQgZGF0YSBpbiBhIHRhYmxlIGZvciBhbmFseXNpcw0KU01WX3RhYmxlMSA8LSB0YWJsZShQcmVkaWN0ZWQgPSByYWRfUHJlZCwgQWN0dWFsID0gdGVzdGluZyRpcGhvbmVzZW50aW1lbnQpDQpTTVZfdGFibGUxDQoNCiNjb25mdXNpb24gbWF0cml4DQpjbVNWTTEgPC0gY29uZnVzaW9uTWF0cml4KHJhZF9QcmVkLCB0ZXN0aW5nJGlwaG9uZXNlbnRpbWVudCkNCmNtU1ZNMQ0KYGBgDQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQoNClRvIGNhbGN1bGF0ZSBNb2RlbCBBY2N1cmFjeTogDQooU3VtIG9mIEFjY3VyYXRlbHkgUHJlZGljdGVkIERhdGEgUG9pbnRzIC8gVG90YWwgTnVtYmVyIG9mIERhdGEgUG9pbnRzKQ0KDQpNaXNjbGFzc2lmaWNhdGlvbiBSYXRlOiAoU3VtIG9mIE1pc2NsYXNzaWZpZWQgRGF0YSBQb2ludHMgLyBUb3RhbCBOdW1iZXIgb2YgRGF0YSBQb2ludHMpIA0KTWlzY2xhc3NpZmljYXRpb24gRXJyb3I6IDAuMjEwMjgyOA0KDQpPdmVyYWxsIFN0YXRpc3RpY3MNCiAgICAgICAgICAgICAgIEFjY3VyYWN5IDogMC43ODk3ICAgICAgICAgIA0KICAgICAgICAgICAgICAgICA5NSUgQ0kgOiAoMC43NzY2LCAwLjgwMjQpDQogICAgTm8gSW5mb3JtYXRpb24gUmF0ZSA6IDAuNjkyMyAgICAgICAgICANCiAgICBQLVZhbHVlIFtBY2MgPiBOSVJdIDogPCAyLjJlLTE2ICAgICAgIA0KICAgICAgICAgICAgICAgICAgS2FwcGEgOiAwLjQ1NjUgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiBNY25lbWFyJ3MgVGVzdCBQLVZhbHVlIDogPCAyLjJlLTE2ICAgDQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KDQpgYGB7cn0NCiNDYWxjdWxhdGUgQWNjdXJhY3kNCnN1bShkaWFnKFNNVl90YWJsZTEpKS9zdW0oU01WX3RhYmxlMSkNCmBgYA0KYGBge3J9DQojQ2FsY3VsYXRlIE1pc2NsYXNzaWZpY2F0aW9uIEVycm9yIA0KMSAtIHN1bShkaWFnKFNNVl90YWJsZTEpKS9zdW0oU01WX3RhYmxlMSkNCmBgYA0KDQo2LjIgVHVubmluZyBTVk0gTW9kZWwgV2l0aCBMaW5lYXIgRnVuY3Rpb24NCg0KYGBge3IsIFNWTS1saW5lYXIta2VybmVsfQ0KI1J1biBTVk0gbW9kZWwgKEtlcm5lbCA9IGxpbmVhcikNCnN5c3RlbS50aW1lKGZpdFNWTSA8LSBzdm0oaXBob25lc2VudGltZW50fi4sIGRhdGEgPSB0cmFpbmluZywga2VybmVsID0gImxpbmVhciIpKQ0KDQpzdW1tYXJ5KGZpdFNWTSkNCmBgYA0KDQpgYGB7ciwgU1ZNLWxpbmVhci1QcmVkaWN0aW9ufQ0KI2xpbmVhciBrZXJuZWwgcHJlZGljdGlvbg0KbGluX1ByZWQgPC0gcHJlZGljdChmaXRTVk0sIHRlc3RpbmcpDQoNCiNTdG9yZSB0aGUgcHJlZGljdGVkIGRhdGEgaW4gYSB0YWJsZSBmb3IgYW5hbHlzaXMNClNNVl90YWJsZTIgPC0gdGFibGUoUHJlZGljdGVkID0gbGluX1ByZWQsIEFjdHVhbCA9IHRlc3RpbmckaXBob25lc2VudGltZW50KQ0KU01WX3RhYmxlMg0KDQojQ2FsY3VsYXRlIEFjY3VyYWN5DQpzdW0oZGlhZyhTTVZfdGFibGUyKSkvc3VtKFNNVl90YWJsZTIpDQoNCiNDYWxjdWxhdGUgTWlzY2xhc3NpZmljYXRpb24gRXJyb3IgDQoxIC0gc3VtKGRpYWcoU01WX3RhYmxlMikpL3N1bShTTVZfdGFibGUyKQ0KDQojY29uZnVzaW9uIG1hdHJpeA0KY21TVk0yIDwtIGNvbmZ1c2lvbk1hdHJpeChsaW5fUHJlZCwgdGVzdGluZyRpcGhvbmVzZW50aW1lbnQpDQpjbVNWTTINCmBgYA0KDQpBY2N1cmFjeTogQWNjdXJhY3kgOiAwLjc3NDgNCk1pc2NsYXNzaWZpY2F0aW9uIEVycm9yOiAwLjIyNTE5MjgNCg0KQWJvdmUgc2hvd3MgbGluZWFyIGtlcm5lbCBzbGlnaHRseSBkZWNyZXNlcyB0aGUgTW9kZWwgYWNjdXJhY3kgcmF0ZSB0byA3Ny40OCUgYW5kIHRoZSBNaXNjbGFzc2lmaWNhdGlvbiByYXRlIHJlZHVjZXMgdG8gYWJvdXQgMjIuNTElDQoNCjYuMyBUdW5uaW5nIFNWTSBNb2RlbCBXaXRoIFBvbHlub21pYWwgRnVuY3Rpb24NCmBgYHtyLCBTVk0tcG9seW5vbWlhbC1rZXJuZWx9DQojUnVuIFNWTSBtb2RlbCAoS2VybmVsID0gcG9seW5vbWlhbCkNCnN5c3RlbS50aW1lKGZpdFNWTSA8LSBzdm0oaXBob25lc2VudGltZW50fi4sIGRhdGEgPSB0cmFpbmluZywga2VybmVsID0gInBvbHlub21pYWwiKSkNCg0Kc3VtbWFyeShmaXRTVk0pDQpgYGANCmBgYHtyLCBQcmVkaWN0aW9uLVNWTS1wb2x5bm9taWFsfQ0KI3BvbHlub21pYWwgVHVuaW5nIHByZWRpY3Rpb24NCnBvbF9QcmVkIDwtIHByZWRpY3QoZml0U1ZNLCB0ZXN0aW5nKQ0KDQojU3RvcmUgdGhlIHByZWRpY3RlZCBkYXRhIGluIGEgdGFibGUgZm9yIGFuYWx5c2lzDQpTVk1fdGFibGUzIDwtIHRhYmxlKFByZWRpY3RlZCA9IHBvbF9QcmVkLCBBY3R1YWwgPSB0ZXN0aW5nJGlwaG9uZXNlbnRpbWVudCkNClNWTV90YWJsZTMNCg0KI0NhbGN1bGF0ZSBBY2N1cmFjeQ0Kc3VtKGRpYWcoU1ZNX3RhYmxlMykpL3N1bShTVk1fdGFibGUzKQ0KDQojQ2FsY3VsYXRlIE1pc2NsYXNzaWZpY2F0aW9uIEVycm9yIA0KMSAtIHN1bShkaWFnKFNWTV90YWJsZTMpKS9zdW0oU1ZNX3RhYmxlMykNCg0KY21TVk0zIDwtIGNvbmZ1c2lvbk1hdHJpeChwb2xfUHJlZCwgdGVzdGluZyRpcGhvbmVzZW50aW1lbnQpDQpjbVNWTTMNCmBgYA0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KDQpBY2N1cmFjeSA6IDAuNzQ5NiAgICAgICAgICANCiAgICAgICAgICAgICAgICAgOTUlIENJIDogKDAuNzM1NywgMC43NjMyKQ0KICAgIE5vIEluZm9ybWF0aW9uIFJhdGUgOiAwLjY5MjMgICAgICAgICAgDQogICAgUC1WYWx1ZSBbQWNjID4gTklSXSA6IDEuNjc2ZS0xNSAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICBLYXBwYSA6IDAuMjc3NyAgICAgICAgICANCk1pc2NsYXNzaWZpY2F0aW9uIEVycm9yOiAwLjI1MDM4NTYNCg0KVGhlIHBvbHlub21pYWwga2VybmVsIGZ1bmN0aW9uIHJlZHVjZXMgdGhlIE1vZGVsIGFjY3VyYWN5IHJhdGUgdG8gNzQuOTYlIGFuZCB0aGUgTWlzY2xhc3NpZmljYXRpb24gRXJyb3IgcmF0ZSBpbmNyZWFzZSB0byBhYm91dCAyNSUuIENsZWFybHksIHRoZSBNb2RlbCBwZXJmb3JtcyB3b3JzdCB3aXRoIHRoaXMgZnVuY3Rpb24uDQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQoNCg0KNi40IFR1bm5pbmcgU1ZNIE1vZGVsIFdpdGggU2lnbW9pZCBGdW5jdGlvbg0KYGBge3IsIFNWTS1TaWdtb2lkfQ0KI1J1biBTVk0gbW9kZWwgKEtlcm5lbCA9IHNpZ21vaWQpDQpmaXRTVk0gPC0gc3ZtKGlwaG9uZXNlbnRpbWVudH4uLCBkYXRhID0gdHJhaW5pbmcsIGtlcm5lbCA9ICJzaWdtb2lkIikNCg0Kc3VtbWFyeShmaXRTVk0pDQoNCiNzaWdtb2lkIHR1bmluZyBwcmVkaWN0aW9uDQpzaWdfUHJlZCA8LSBwcmVkaWN0KGZpdFNWTSwgdGVzdGluZykNCg0KI1N0b3JlIHRoZSBwcmVkaWN0ZWQgZGF0YSBpbiBhIHRhYmxlDQpTVk1fdGFibGU0IDwtIHRhYmxlKFByZWRpY3RlZCA9IHNpZ19QcmVkLCBBY3R1YWwgPSB0ZXN0aW5nJGlwaG9uZXNlbnRpbWVudCkNClNWTV90YWJsZTQNCg0KI0NhbGN1bGF0ZSBBY2N1cmFjeQ0Kc3VtKGRpYWcoU1ZNX3RhYmxlNCkpL3N1bShTVk1fdGFibGU0KQ0KDQojQ2FsY3VsYXRlIE1pc2NsYXNzaWZpY2F0aW9uIEVycm9yIA0KMSAtIHN1bShkaWFnKFNWTV90YWJsZTQpKS9zdW0oU1ZNX3RhYmxlNCkNCg0KY21TVk00IDwtIGNvbmZ1c2lvbk1hdHJpeChzaWdfUHJlZCwgdGVzdGluZyRpcGhvbmVzZW50aW1lbnQpDQpjbVNWTTQNCmBgYA0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KDQpDb25mdXNpb24gTWF0cml4IGFuZCBTdGF0aXN0aWNzDQogICAgICAgUmVmZXJlbmNlDQpQcmVkaWN0aW9uICAgIDEgICAgMiAgICAzICAgIDQNCiAgICAgICAgIDEgIDI4OCAgICA2ICAgNzggICA2NQ0KICAgICAgICAgMiAgICAxICAgIDAgICAgMCAgICAwDQogICAgICAgICAzICAgMTAgICAxNiAgIDQxICAgIDQNCiAgICAgICAgIDQgIDQwNiAgMTE0ICAyMzcgMjYyNA0KDQpPdmVyYWxsIFN0YXRpc3RpY3MNCiAgICAgICAgICAgICAgIEFjY3VyYWN5IDogMC43NTkxICAgICAgICAgIA0KICAgICAgICAgICAgICAgICA5NSUgQ0kgOiAoMC43NDU0LCAwLjc3MjUpDQogICAgTm8gSW5mb3JtYXRpb24gUmF0ZSA6IDAuNjkyMyAgICAgICAgICANCiAgICBQLVZhbHVlIFtBY2MgPiBOSVJdIDogPCAyLjJlLTE2ICAgICAgIA0KICAgICAgICAgICAgICAgICAgS2FwcGEgOiAwLjM1OTggICAgICAgICAgDQoNCk1pc2NsYXNzaWZpY2F0aW9uIEVycm9yOiAwLjI0MDg3NA0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KDQpGcm9tIHRoZSByZXN1bHRzLCBwb2x5bm9taWFsIGFuZCBzaWdtb2lkIGtlcm5lbCBmdW5jdGlvbnMgcGVyZm9ybWVkIHdvcnN0IG9uIHRoaXMgZGF0YSBzZXQgYW5kIHJlZHVjZSB0aGUgbW9kZWwgYWNjdXJhY3kgcmF0ZSB0byA3NS45MSUgYW5kIE1pc2NsYXNzaWZpY2F0aW9uIEVycm9yIHJhdGUgdG8gYWJvdXQgMjUlLiBDbGVhcmx5LCBzdm0gcG9vcmx5IHBlcmZvcm1zIHdpdGggdGhlc2UgZnVuY3Rpb25zIG9uIHRoaXMgZGF0YSBzZXQuDQoNCjYuNCBUdW5lIFNWTSBNb2RlbA0KDQpJdCdzIHdvcnRoIGV4cGxvcmluZyBtb2RlbCB0dW5uaW5nIG9wdGlvbnMgdG8gaW1wcm92ZSB0aGUgbW9kZWwuIFVzaW5nIHR1bmUoKSwgcmFuZ2VzIGFuZCBhIHBhcmFtZXRlciAnZXBzaWxvbicgd2l0aCBzZXF1ZW5jZSB0aGF0IGdvZXMgZnJvbSAwLTEgd2l0aCBpbmNyZW1lbnQgb2YgMC4xIChpLmUsIGVwc2lsb24gdmFsdWVzIGluY3JlYXNlIGFzOiAoMCwwLjE6MS4wKSkuIGxldCdzIGdvIHVwIHRvIDAuNSB0byBzYXZlIHRpbWUgZHVlIHRvIGEgbGFyZ2UgZGF0YSBzZXQuDQoNCkFub3RoZXIgcGFyYW1ldGVyLCAiY29zdCIgKGRlZmF1bHQgdmFsdWUgb2YgMSkgaXMgdXNlZCB0byBjYXB0dXJlIGNvbnN0cmFpbnQgdmlvbGF0aW9ucy4gSWYgdGhlIGNvc3QgaXMgdG9vIGhpZ2gsIHRoZSBtb2RlbCBtYXkgZXhwZWN0IHRvIHN0b3JlIHRvbyBtYW55IHN1cHBvcnQgdmVjdG9ycyBkdWUgdG8gaGlnaCBwZW5hbHR5IGZvciBub24tc2VwYXJhYmxlIGRhdGEgcG9pbnRzLiBUaGlzIG1heSBsZWFkIHRvIG92ZXJmaXR0aW5nLiBDb3ZlcnNlbHksIGlmIHRoZSBjb3N0IHZhbHVlIGlzIHRvbyBzbWFsbCwgdGhlIG1vZGVsIG1heSBiZSB1bmRlcmZpdHRpbmcgd2l0aCBsZXNzZXIgYWNjdXJhY3kgcmF0ZS4gU3BlY2lmeWluZyBhIGxhcmdlIHJhbmdlIGZvciBjb3N0IGhlbHBzIGNhcHR1cmUgb3B0aW11bSBjb3N0IHZhbHVlLiBNdWx0aXBseWluZyA0IGNvc3QgdmFsdWVzIGJ5IDYgZXBzaWxvbiB2YWx1ZXMgd2lsbCBnaXZlIHRoZSBtb2RlbCAyNCBkaWZmZXJlbnQgY29tYmluYXRpb25zLg0KDQpgYGB7ciwgVHVuaW5nLVNWTS1Nb2RlbH0NCnNldC5zZWVkKDIyMikNCg0KI1R1bm5pbmcgU1ZNLiBjb3N0IHJhbmdlcyBmcm9tIDJeMiwgMl4zLi4uLi4uLnRvIDJeNQ0KI2Vwc2lsb24gcmFuZ2VzIGZyb20gMCwgMC4xLCAwLjIuLi4uLi4uLi4wLjUNCnN5c3RlbS50aW1lKFNWTXR1bmUgPC0gdHVuZShzdm0sIGlwaG9uZXNlbnRpbWVudH4uLCBkYXRhID0gdHJhaW5pbmcsDQogICAgICAgICAgICAgICAgICAgIHJhbmdlcyA9IGxpc3QoZXBzaWxvbiA9IHNlcSgwLDEsMC41KSwgY29zdCA9IDJeKDI6NSkpKSkNCg0Kc3VtbWFyeShTVk10dW5lKQ0KDQojUGxvdCBTVk10dW5lIG1vZGVsIFBlcmZvcm1hbmNlIEV2YWx1YXRpb24gb2YgU1ZNDQpwbG90KFNWTXR1bmUpDQpgYGANCg0KVGhlIHBsb3Qgb2YgYm90aCBjb3N0ICYgZXBzaWxvbiBwYXJhbWV0ZXJzIHVzZWQgaW4gdHVuaW5nIHRoZSBtb2RlbCBpcyBzaG93biBhYm92ZS4NCkRhcmtlciBibHVlIGFyZWEgcmVwcmVzZW50IGhpZ2hlciBhY2N1cmFjeS9sb3dlciBtaXNjbGFzc2lmaWNhdGlvbiBlcnJvciByZWdpb24gYW5kIHZpY2UgdmVzYS4NCg0KUGFyYW1ldGVyIHR1bmluZyBvZiBzdm06DQoNCi0gc2FtcGxpbmcgbWV0aG9kOiAxMC1mb2xkIGNyb3NzIHZhbGlkYXRpb24gDQotIGJlc3QgcGFyYW1ldGVyczogZXBzaWxvbiAwIGFuZCBjb3N0IDMyDQotIGJlc3QgcGVyZm9ybWFuY2U6IGVycm9yIDAuMjY4NjMyIA0KLSBUaGlzIG1lYW5zIHRoYXQgdGhlIG1vZGVsIHVzZWQgMTAtZm9sZCBDViBhbmQgYXQgY29zdCB2YWx1ZSA9IDAgYW5kIGVwc2lsb24gdmFsdWUgPSAzMiwNCiAgYW5kIGFjaGlldmVkIGxvd2VzdCBtaXNjbGFzc2lmaWNhdGlvbiBlcnJvciBvZiAwLjI2ODYzMg0KDQpgYGB7ciwgU2VsZWN0aW5nLWJlc3QtU1ZNLW1vZGVsfQ0KI3NlbGVjdGluZyBiZXN0IHN2bSBtb2RlbA0KU1ZNX2Jlc3RNIDwtIFNWTXR1bmUkYmVzdC5tb2RlbA0KDQpzdW1tYXJ5KFNWTV9iZXN0TSkNCmBgYA0KDQpgYGB7ciwgUHJlZGljdGlvbi10dW5lZC1TVk19DQoNClNWTXR1bmVfUHJlZCA8LSBwcmVkaWN0KFNWTV9iZXN0TSwgdGVzdGluZykNCg0KI1N0b3JlIHRoZSBwcmVkaWN0ZWQgZGF0YSBpbiBhIHRhYmxlDQpTVk1fdGFibGU1IDwtIHRhYmxlKFByZWRpY3RlZCA9IFNWTXR1bmVfUHJlZCwgQWN0dWFsID0gdGVzdGluZyRpcGhvbmVzZW50aW1lbnQpDQpTVk1fdGFibGU1DQoNCnN1bShkaWFnKFNWTV90YWJsZTUpKS9zdW0oU1ZNX3RhYmxlNSkNCg0KI0NhbGN1bGF0ZSBNaXNjbGFzc2lmaWNhdGlvbiBFcnJvciANCjEgLSBzdW0oZGlhZyhTVk1fdGFibGU1KSkvc3VtKFNWTV90YWJsZTUpDQoNCiNDb25mdXNpb24gTWF0cml4DQpDTV9zdm0gPC0gY29uZnVzaW9uTWF0cml4KFNWTXR1bmVfUHJlZCwgdGVzdGluZyRpcGhvbmVzZW50aW1lbnQpDQpDTV9zdm0NCmBgYA0KVHVubmluZyB0aGUgbW9kZWwgaGVscHMgdG8gb2J0YWluIGhpZ2hlciBwZXJmb3JtYW5jZSBidXQgc3RpbGwgbG93ZXIgdGhhbiBwZXJmb3JtYW5jZXMgZnJvbSBvdGhlciBtb2RlbHMuDQoNCjguMCBHcmFkcmllbnQgQm9vc3RpbmcgTW9kZWwgb24gQWxsIEF0dHJpYnV0ZXMNCg0KYGBge3IsIEJ1aWxkLWdibX0NCnNldC5zZWVkKDIyMikNCg0Kc3lzdGVtLnRpbWUoZ2JtRml0IDwtIHRyYWluKGlwaG9uZXNlbnRpbWVudCB+LiwgZGF0YSA9IHRyYWluaW5nLA0KICAgICAgICAgICAgICAgICBtZXRob2QgPSAiZ2JtIiwNCiAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCwNCiAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFKSkNCg0KZ2JtRml0DQoNCnBsb3QoZ2JtRml0KQ0KYGBgDQpgYGB7ciwgZ2JtLVByZWRpY3Rpb24tIGFsbC1hdHRyaWJ1dGVzfQ0KZ2JtUHJlZCA8LSBwcmVkaWN0KGdibUZpdCwgdGVzdGluZykNCg0KI1N0b3JlIHByZWRpY3Rpb24gaW4gYSB0YWJsZQ0KZ2JtdGFibGUgPC0gdGFibGUoUHJlZGljdGVkID0gZ2JtUHJlZCwgQWN0dWFsID0gdGVzdGluZyRpcGhvbmVzZW50aW1lbnQpDQpnYm10YWJsZQ0KYGBgDQpgYGB7ciwgZ2JtLUNvbmZ1c2lvbi1NYXRyaXh9DQoNCkNNX2dibSA8LSBjb25mdXNpb25NYXRyaXgoZ2JtUHJlZCwgdGVzdGluZyRpcGhvbmVzZW50aW1lbnQpDQpDTV9nYm0NCg0KI3ZlcmlmeSBBY2N1cmFjeQ0Kc3VtKGRpYWcoZ2JtdGFibGUpKS9zdW0oZ2JtdGFibGUpDQoNCiNDYWxjdWxhdGUgTWlzY2xhc3NpZmljYXRpb24gRXJyb3IgDQoxIC0gc3VtKGRpYWcoZ2JtdGFibGUpKS9zdW0oZ2JtdGFibGUpDQpgYGANCg0KDQo5LjAgQ29tcGFyZSBNb2RlbHMgRXZhbHVhdGluZyBQZXJmb3JtYW5jZSBNZXRyaWNzDQoNCmBgYHtyfQ0KcmVxdWlyZShyZXNoYXBlMikNCmBgYA0KDQpgYGB7ciwgQ29tcGFyZS1Nb2RlbHMtaXBob25lLVNtYWxsLU1hdHJpeH0NCm1vZGVsX2xpc3QgPC0gbGlzdChjNS4wID0gZml0X0M1MG1vZGVsLA0KICAgICAgICAgICAgICAgICAgIHJmID0gaXBob25lNThfUkYsDQogICAgICAgICAgICAgICAgICAgZ2JtID0gZ2JtRml0KQ0KDQoNCnJlc2FtcCA8LSByZXNhbXBsZXMobW9kZWxfbGlzdCkNCnJlc2FtcA0KDQojUGxvdCBtb2RlbHMgc3VtbWFyeSANCmJ3cGxvdChyZXNhbXApDQpgYGANCg0KMTAuIE1vZGVsIGFuZCBQcmVkaWN0aW9uIFVzaW5nIFNlbGVjdGVkIEF0dHJpYnV0ZXMNCg0KVGhlIG1vc3Qgb3B0aW1pemVkIG1vZGVsIHNlbGVjdGVkIGlzIGdibSBiYXNlZCBvbiBpdHMgcGVyZm9ybWFuY2UgbWV0cmljcy4gR0JNIGlzIGFwcGxpZWQgdG8gcHJlZGljdCB0aGUgaXBob25lIHNlbnRpbWVudCB1c2luZyBzZWxlY3RlZCBmZWF0dXJlcyBhbmQgbWVhc3VyZSBpdHMgQWNjdXJhY3kgYW5kIEthcHBhLg0KDQpgYGB7ciwgTW9kZWwtR0JNLWlwaG9uZTIwLW1hdHJpeH0NCnNldC5zZWVkKDIyMikNCg0KI3RyYWluIGdibSBtb2RlbCBvbiBzZWxlY3RlZCAyMCB2YXJpYWJsZXMNCnN5c3RlbS50aW1lKGlwaG9uZTIwX2dibSA8LSB0cmFpbihpcGhvbmVzZW50aW1lbnQgfi4sIGRhdGEgPSB0cmFpbmluZzIsDQogICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJnYm0iLA0KICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sLA0KICAgICAgICAgICAgICAgICB2ZXJib3NlID0gRkFMU0UpKQ0KDQoNCmlwaG9uZTIwX2dibQ0KDQojcGxvdCBnYm0gbW9kZWwNCnBsb3QoaXBob25lMjBfZ2JtKQ0KYGBgDQoNCmBgYHtyLCBHQk0tUHJlZGljdGlvbi1hbmQtQ29uZnVzaW9ufQ0KI3ByZWRpY3Rpb24NCmdibVBSRURfMjAgPC0gcHJlZGljdChpcGhvbmUyMF9nYm0sIHRlc3RpbmcyKQ0KDQojY29uZnVzaW9uIG1hdHJpeA0KQ01fZ2JtIDwtIGNvbmZ1c2lvbk1hdHJpeChnYm1QUkVEXzIwLCB0ZXN0aW5nMiRpcGhvbmVzZW50aW1lbnQpDQpDTV9nYm0NCmBgYA0KYGBge3J9DQojU3RvcmUgcHJlZGljdGlvbiBpbiBhIHRhYmxlDQpnYm0yMHRhYmxlIDwtIHRhYmxlKFByZWRpY3RlZCA9IGdibVBSRURfMjAsIEFjdHVhbCA9IHRlc3RpbmcyJGlwaG9uZXNlbnRpbWVudCkNCmdibTIwdGFibGUNCg0KI2NhbGN1bGF0ZSBNaXNzY2xhc3NpZmljYXRpb24gRXJyb3INCjEgLSBzdW0oZGlhZyhnYm0yMHRhYmxlKSkvc3VtKGdibTIwdGFibGUpDQpgYGANCg0KYGBge3J9DQojcG9zdFJlc2FtcGxlDQpwb3N0UmVzYW1wbGUocHJlZCA9IGdibVBSRURfMjAsIG9icyA9IGFzLmZhY3Rvcih0ZXN0aW5nMiRpcGhvbmVzZW50aW1lbnQpKQ0KYGBgDQoNCjExLjAgUHJlZGljdGlvbiBhbmQgQ29uZnVzaW9uIE1hdHJpY3Mgb24gaXBob25lTGFyZ2UgTWF0cml4DQoNCmBgYHtyLCBJbXBvcnQtaXBob25lLUxhcmdlLU1hdHJpeH0NCmlQaG9uZV9MYXJnZU1hdHJpeCA8LSByZWFkLmNzdigiaXBob25lTGFyZ2VNYXRyaXguY3N2IiwgaGVhZGVyID0gVFJVRSkgIA0KDQojZ2xpbXBzZShpUGhvbmVfTGFyZ2VNYXRyaXgpDQpgYGANCg0KYGBge3J9DQpzdHIoaVBob25lX0xhcmdlTWF0cml4KQ0KYGBgDQoNCmBgYHtyLCByZWNvbW1lbmRlZC1hdHRyaWJ1dGVzLWxhcmdlLW1hdHJpeH0NCg0KaXBob25lX0xhcmdlTWF0cmljczIwIDwtIGlQaG9uZV9MYXJnZU1hdHJpeCAlPiUgc2VsZWN0KGlwaG9uZSwgc2Ftc3VuZ2dhbGF4eSwgc29ueXhwZXJpYSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaHRjcGhvbmUsaW9zLCBnb29nbGVhbmRyb2lkLCBpcGhvbmVjYW1wb3MsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGh0Y2NhbXBvcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXBob25lY2FtbmVnLCBpcGhvbmVjYW11bmMsIGh0Y2NhbXVuYywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXBob25lZGlzcG9zLCBodGNkaXNwb3MsIGlwaG9uZWRpc25lZywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXBob25lZGlzdW5jLCBpcGhvbmVwZXJwb3MsIGh0Y3BlcnBvcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXBob25lcGVybmVnLCBodGNwZXJuZWcsIGlwaG9uZXBlcnVuYywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXBob25lc2VudGltZW50KQ0KDQpnbGltcHNlKGlwaG9uZV9MYXJnZU1hdHJpY3MyMCkNCg0KI2NyZWF0ZSBhIGZhY3RvciB0YXJnZXQgdmFyaWFibGUNCmlwaG9uZV9MYXJnZU1hdHJpY3MyMCRpcGhvbmVzZW50aW1lbnQgPC0gYXMuZmFjdG9yKGlwaG9uZV9MYXJnZU1hdHJpY3MyMCRpcGhvbmVzZW50aW1lbnQpDQpzdHIoaXBob25lX0xhcmdlTWF0cmljczIwKQ0KYGBgDQoNCjEyLiBGaW5hbCBpcGhvbmUgU2VudGltZW50cyBQcmVkaWN0aW9uDQoNCmBgYHtyLCBGaW5hbC1QcmVkaWN0aW9uLWlwaG9uZVNlbnRpbWVudH0NCiNHcmFkaWVudCBCb29zdGVkIE1vZGVsDQpnYm1QUkVEX0xNMjAgPC0gcHJlZGljdChpcGhvbmUyMF9nYm0sIGlwaG9uZV9MYXJnZU1hdHJpY3MyMCkNCmBgYA0KDQpgYGB7cn0NCiNQbG90IHByZWRpY3Rpb24gcmVzdWx0cw0KcGxvdChnYm1QUkVEX0xNMjApDQoNCiNBZGQgcHJlZGljdGlvbnMgdG8gdGhlIGlwaG9uZSBMYXJnZSBtYXRyaXggZGF0YSBzZXQgDQpGaW5hbF9pcGhvbmVTZW50aW1lbnQgPC0gaVBob25lX0xhcmdlTWF0cml4IA0KDQpGaW5hbF9pcGhvbmVTZW50aW1lbnQkaXBob25lc2VudGltZW50IDwtIGdibVBSRURfTE0yMA0KDQojZ2xpbXBzZShnYm1QUkVEX0xNMjApDQpgYGANCmBgYHtyLCBQbG90LWZpbmFsLWlwaG9uZXNlbnRpbWVudH0NCnBsb3RfbHkoRmluYWxfaXBob25lU2VudGltZW50LCB4PSB+RmluYWxfaXBob25lU2VudGltZW50JGlwaG9uZXNlbnRpbWVudCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGU9J2hpc3RvZ3JhbScpICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGF5b3V0KHRpdGxlID0gIkhpc3RvZ3JhbSBvZiBpcGhvbmVzZW50aW1lbnQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIkNsYXNzZXMiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJGcmVxdWVuY3kiKSkNCg0KYGBgDQoNCmBgYHtyLCBMb2FkLUZpbmFsLWlwaG9uZVNlbnRpbWVudC1maWxlfQ0KI0NyZWF0ZSBhIGNzdiBmaWxlIGFuZCB3cml0ZSBpdCB0byBsb2NhbCBkcml2ZSANCndyaXRlLmNzdihGaW5hbF9pcGhvbmVTZW50aW1lbnQsIGZpbGU9ImlwaG9uZVNlbnRpbWVudHMuY3N2Iiwgcm93Lm5hbWVzID0gVFJVRSkNCmBgYA0KDQoNCjEzIFBSRURJQ1RJT04gRk9SIFNBTVNVTkcgU0VOVElNRU5UUw0KDQoxMy4xIEV4cGxvcmF0b3J5IEFuYWx5c2lzDQoNClRoZSBzZWNvbmQgcGFydCBvZiB0aGUgcHJvamVjdCB3b3JrZmxvdyBpcyB0byBmb2N1cyBvbiBnYWxheHkgc21hbGwgbWF0cml4IHRvIHBlcmZvcm0gc2ltaWxhciBhbmFseXNpcyBhbmQgcHJlZGljdGlvbnMuIA0KYGBge3IsIEltcG9ydC1nYWxheHktU21hbGwtTWF0cml4LUZpbGV9DQpnYWxheHlfTWF0cml4IDwtIHJlYWQuY3N2KCJnYWxheHlfc21hbGxtYXRyaXhfbGFiZWxlZF85ZC5jc3YiLCBoZWFkZXIgPSBUUlVFKQ0KDQpnbGltcHNlKGdhbGF4eV9NYXRyaXgpDQoNCnBsb3RfbHkoZ2FsYXh5X01hdHJpeCwgeD0gfmdhbGF4eV9NYXRyaXgkZ2FsYXh5c2VudGltZW50LCB0eXBlPSdoaXN0b2dyYW0nKQ0KYGBgDQpgYGB7cn0NCnN0cihnYWxheHlfTWF0cml4KQ0KYGBgDQoNCjEzLjIgRmVhdHVyZSBTZWxlY3Rpb24NCg0KYGBge3IsIEV4YW1pbmUtQ29ycmVsYXRpb24tZ2FsYXh5TWF0cml4fQ0KI1VzZSB0aGUgY29yKCkgZnVuY3Rpb24gdG8gYnVpbGQgdGhlIGNvcnJlbGF0aW9uIG1hdHJpeA0KQ09SUmdhbGF4eV9NYXRyaXggPC0gY29yKGdhbGF4eV9NYXRyaXgpDQpgYGANCmBgYHtyfQ0KQ09SUmdhbGF4eV9NYXRyaXgNCm9wdGlvbnMobWF4LnByaW50PTEwMDAwMDAwKQ0KYGBgDQoNCmBgYHtyIFBsb3R0aW5nIEhlYXRtYXB9DQpjb3JycGxvdChDT1JSZ2FsYXh5X01hdHJpeCkNCmBgYA0KDQpgYGB7ciBSdW5uaW5nIENvcnJlbGF0aW9uIE1hdHJpeCBhcyBwLXZhbHVlfQ0KckNPUlJnYWxheHlfTWF0cml4IDwtIHJjb3JyKGFzLm1hdHJpeChDT1JSZ2FsYXh5X01hdHJpeCkpDQpgYGANCmBgYHtyfQ0KcHJpbnQockNPUlJnYWxheHlfTWF0cml4KQ0KYGBgDQoNCjEzLjMgRkVBVFVSRSBFTElNSU5BVElPTiBBUFBST0FDSEVTDQoNCkFwcHJvYWNoIDE6IFJlY3Vyc2l2ZSBGZWF0dXJlIEVsaW1pbmF0aW9uIChSRkUpDQoNCkNhcmV0J3MgUkZFIGZ1bmN0aW9uIGlzIGEgZm9ybSBvZiBhdXRvbWF0ZWQgZmVhdHVyZSBzZWxlY3Rpb24uIFRoZSBmdW5jdGlvbiB3aXRoIHJhbmRvbSBmb3Jlc3Qgd2lsbCB0cnkgZXZlcnkgY29tYmluYXRpb24gb2YgZmVhdHVyZSBzdWJzZXRzIGFuZCByZXR1cm4gYSBmaW5hbCBsaXN0IG9mIHJlY29tbWVuZGVkIGZlYXR1cmVzLiBOb3csIGxldCdzIHVzZSBSRkUgZnVuY3Rpb24gdG8gcmVtb3ZlIHVud2FudGVkIGF0dHJpYnV0ZXMgZnJvbSBnYWxheHkgc21hbGwgbWF0cml4IGRhdGEgc2V0LiBTaW5jZSwgUkZFIGRvZXMgbm90IHVzZSB0aGUgdGFyZ2V0IHNvIGl0IG11c3QgYmUgcmVtb3ZlZCBmcm9tIHRoZSBkYXRhIHNldCBiZWZvcmUgaW1wbGVtZW50YXRpb24gYW5kIHRoZW4gYWRkZWQgYmFjayBpbiBiZWZvcmUgbW9kZWxpbmciDQoNCmBgYHtyLCByZmVDb250cm9sLWdhbGF4eXNtYWxsbWF0cml4fQ0KI3Rha2UgMTAwMCBzYW1wbGVzIHRoZSBvcmlnaW5hbCBkYXRhIHNldCBiZWZvcmUgYXBwbHlpbmcgUkZFDQpzZXQuc2VlZCgxMjMpDQoNCmdhbGF4eVNhbXBsZV9ub1JGRSA8LSBnYWxheHlfTWF0cml4W3NhbXBsZSgxOm5yb3coZ2FsYXh5X01hdHJpeCksIDEwMDAsIHJlcGxhY2U9RkFMU0UpLF0NCg0KI3NldCB1cCByZmVDb250cm9sIHdpdGggcmFuZG9tIGZvcmVzdCwgcmVwZWF0ZWQgY3Jvc3MgdmFsaWRhdGlvbiBhbmQgbm8gdXBkYXRlcw0KY3RybCA8LSByZmVDb250cm9sKGZ1bmN0aW9ucyA9IHJmRnVuY3MsDQogICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJlcGVhdGVkY3YiLA0KICAgICAgICAgICAgICAgICAgIHJlcGVhdHMgPSAxLA0KICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBGQUxTRSkNCmBgYA0KYGBge3IsIG1lc3NhZ2U9VFJVRSwgd2FybmluZz1UUlVFLCBwYWdlZC5wcmludD1UUlVFfQ0KI05vdyBhcHBseSByZmUgZnVuY3Rpb24gdG8gYWxsIHZhcmlhYmxlcyBleGNlcHQgdGFyZ2V0DQpzeXN0ZW0udGltZShyZmVSZXN1bHRzIDwtIHJmZShnYWxheHlTYW1wbGVfbm9SRkVbLDE6NThdLA0KICAgICAgICAgICAgICAgICAgZ2FsYXh5U2FtcGxlX25vUkZFJGdhbGF4eXNlbnRpbWVudCwNCiAgICAgICAgICAgICAgICAgIHNpemVzPWMoMTo1OCksDQogICAgICAgICAgICAgICAgICByZmVDb250cm9sPWN0cmwpKQ0KDQojR2V0IHJlc3VsdHMNCnJmZVJlc3VsdHMNCmBgYA0KYGBge3J9DQojIFBsb3QgcmVzdWx0cw0KcGxvdChyZmVSZXN1bHRzLCB0eXBlPWMoImciLCAibyIpKQ0KYGBgDQoNClRoZSByZXN1bHRpbmcgdGFibGUgYW5kIHBsb3QgZGlzcGxheSBlYWNoIHN1YnNldCBhbmQgaXRzIGFjY3VyYWN5IGFuZCBrYXBwYS4gQW4gYXN0ZXJpc2sgZGVub3RlcyB0aGUgdGhlIG51bWJlciBvZiBmZWF0dXJlcyB0aGF0IGlzIGp1ZGdlZCB0aGUgbW9zdCBvcHRpbWFsIGZyb20gUkZFLiANCg0KSXQncyB3b3J0aCB0byBub3RlIGhlcmUgdGhhdCwgdW5saWtlIHRoZSByZXN1bHRzIGZyb20gQm9ydXRhIG1vZGVsIHVzZWQgcHJldmlvdXNseSBpbiBpcGhvbmUgZmVhdHVyZSBzZWxlY3Rpb24sIFJGRSBtb2RlbCBvbmx5IGZvdW5kIDIgYXR0cmlidXRlcyBhcyB1bmltcG9ydGFudCB0byBidWlsZGluZyByZiBtb2RlbC4gV2UnbGwgdXNlIHRoZSByZWNvbW1lbmRlZCBhdHRyaWJ1dGVzIGluIGJ1aWxkaW5nIHRoZSBtb2RlbHMgZm9yIGdhbGF4eSBzZW50aW1lbnQgcHJlZGljdGlvbnMuDQpBZnRlciBpZGVudGlmeWluZyB1bndhbnRlZCBhdHRyaWJ1dGVzLCB3ZSdsbCBuZWVkIHRvIGNyZWF0ZSBhIG5ldyBkYXRhIHNldCBhbmQgYWRkIGJhY2sgdGhlIGRlcGVuZGVudCB2YXJpYWJsZS4gIA0KDQpgYGB7ciwgU3RvcmUtUmVjb21tZW5kZWQtQXR0cmlidXRlc30NCiNjcmVhdGUgbmV3IGRhdGEgc2V0IHdpdGggcmZlIHJlY29tbWVuZGVkIGZlYXR1cmVzDQpnYWxheHlSRkUgPC0gZ2FsYXh5X01hdHJpeFsscHJlZGljdG9ycyhyZmVSZXN1bHRzKV0NCmBgYA0KYGBge3J9DQojZ2xpbXBzZShnYWxheHlSRkUpDQoNCiNhZGQgdGhlIGRlcGVuZGVudCB2YXJpYWJsZSB0byBnYWxheHlSRkUNCmdhbGF4eVJGRSRnYWxheHlzZW50aW1lbnQgPC0gZ2FsYXh5X01hdHJpeCRnYWxheHlzZW50aW1lbnQNCg0KI3JldmlldyBvdXRjb21lDQpzdHIoZ2FsYXh5UkZFKQ0KYGBgDQoNCmBgYHtyfQ0KI2NvbnZlcnQgdGFyZ2V0IHZhcmlhYmxlIHRvIGZhY3Rvcg0KZ2FsYXh5X01hdHJpeCRnYWxheHlzZW50aW1lbnQgPC0gYXMuZmFjdG9yKGdhbGF4eV9NYXRyaXgkZ2FsYXh5c2VudGltZW50KQ0KYGBgDQpgYGB7cn0NCnN0cihnYWxheHlfTWF0cml4KQ0KYGBgDQpgYGB7cn0NCmdhbGF4eVJGRSRnYWxheHlzZW50aW1lbnQgPC0gYXMuZmFjdG9yKGdhbGF4eVJGRSRnYWxheHlzZW50aW1lbnQpDQojc3RyKGdhbGF4eVJGRSkNCmBgYA0KDQoNCjEzLjQgRW5naW5lZXJpbmcgRGVwZW5kZW50IFZhcmlhYmxlDQoNCldlIGRvIG5vdCByZWFsbHkgbmVlZCA2IGxldmVscyB0byB1bmRlcnN0YW5kIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSBzZW50aW1lbnQgb2YgU2Ftc3VuZyBnYWxheHkuIFBlcmhhcHMgY29tYmluaW5nIHNvbWUgb2YgdGhlc2UgbGV2ZWxzIHdpbGwgaGVscCBpbmNyZWFzZSBhY2N1cmFjeSBhbmQga2FwcGEuIExldCdzIHJlbWFwcGVkIHRoZSB2YWx1ZXMgYXMgZm9sbG93czoNCg0KMTogbmVnYXRpdmUNCjI6IHNvbWV3aGF0IG5lZ2F0aXZlDQozOiBzb21ld2hhdCBwb3NpdGl2ZQ0KNDogcG9zaXRpdmUNCg0KYGBge3J9DQojY3JlYXRlIGEgbmV3IGRhdGFzZXQgdGhhdCB3aWxsIGJlIHVzZWQgZm9yIHJlY29kaW5nIHNlbnRpbWVudA0KZ2FsYXh5TV9SQyA8LSBnYWxheHlSRkUNCmBgYA0KYGBge3J9DQojcmVjb2RlIHNlbnRpbWVudCB0byBjb21iaW5lIGZhY3RvciBsZXZlbHMgMCAmIDEgYW5kIDQgJiA1DQpnYWxheHlNX1JDJGdhbGF4eXNlbnRpbWVudCA8LSByZWNvZGUoZ2FsYXh5TV9SQyRnYWxheHlzZW50aW1lbnQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnMCcgPSAxLCAnMScgPSAxLCAnMicgPSAyLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzMnID0gMywgJzQnID0gNCwgJzUnID0gNCkgDQpgYGANCmBgYHtyfQ0KI21ha2UgZGVwZW5kZW50IGEgZmFjdG9yIGFnYWluDQpnYWxheHlNX1JDJGdhbGF4eXNlbnRpbWVudCA8LSBhcy5mYWN0b3IoZ2FsYXh5TV9SQyRnYWxheHlzZW50aW1lbnQpDQojc3RyKGdhbGF4eU1fUkMpDQpgYGANCmBgYHtyfQ0KI2NoZWNrIGFuZCB2ZXJpZnkgY2xhc3Nlcw0KbmxldmVscyhnYWxheHlNX1JDJGdhbGF4eXNlbnRpbWVudCkNCmBgYA0KYGBge3J9DQojcGxvdCBkaXN0cmlidXRpb24gb2YgZGF0YSBzZXQNCnBsb3RfbHkoZ2FsYXh5TV9SQywgeD0gfmdhbGF4eU1fUkMkZ2FsYXh5c2VudGltZW50LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGU9J2hpc3RvZ3JhbScpICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGF5b3V0KHRpdGxlID0gIkhpc3RvZ3JhbSBvZiBnYWxheHlzZW50aW1lbnQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIkNsYXNzZXMiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJGcmVxdWVuY3kiKSkNCiAgDQpgYGANCg0KMTQgQnVpbGQgNCBNb2RlbHMgVG8gUHJlZGljdCBHYWxheHkgU2VudGltZW50cw0KDQpPdXIgZXhwZXJpZW5jZSBmcm9tIGlwaG9uZSBzZW50aW5tZW50IG1vZGVsaW5nIGFuZCBwcmVkaWN0aW9uIHNob3dlZCB0aGF0IDMgYWxnb3JpdGhtcyAocmYsIEM1LjAsIGdibSkgcHJvdmVkIHRvIGRvIHdlbGwuIFdlJ2xsIGNvbmNlbnRyYXRlIG9uIHRoZXNlIDMgYWxnb3JpdGhtcyBhbmQgc2VsZWN0IHRoZSBtb3N0IG9wdGltaXplZCBtb2RlbCB0byBwcmVkaWN0IGZpbmFsIFNhbXN1bmcgZ2FsYXh5IHNlbnRpbWVudHMuDQoNCmBgYHtyLCBEYXRhLVBhcnRpdGlvbn0NCnNldC5zZWVkKDIyMikNCg0KI2NyZWF0ZSBkYXRhIHBhcnRpdGlvbiB1c2luZyBzZWxlY3RlZCBnYWxheHkgbWF0cml4IGRhdGEgc2V0DQppblRyYWluX0c1NiA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGdhbGF4eU1fUkMkZ2FsYXh5c2VudGltZW50LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHAgPSAuNywgbGlzdCA9IEZBTFNFKQ0KDQojU2V0IFRyYWluaW5nIGFuZCBUZXN0aW5nIERhdGENCkc1Nl90cmFpbiA8LSBnYWxheHlNX1JDW2luVHJhaW5fRzU2LF0NCkc1Nl90ZXN0IDwtIGdhbGF4eU1fUkNbLWluVHJhaW5fRzU2LF0NCg0KI3RyYWluIG1vZGVsIHdpdGggMyBmb2xkIGNyb3NzIHZhbGlkYXRpb24NCmZpdENvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgbnVtYmVyID0gMywgcmVwZWF0cyA9IDIpDQpgYGANCg0KMTQuMSBSYW5kb20gRm9yZXN0IE1vZGVsDQoNCmBgYHtyLCBCdWlsZC1SRiBNb2RlbC1nYWxheHlzZW50aW1lbnR9DQojdHJhaW4gUmFuZG9tIEZvcmVzdCBjbGFzc2lmaWNhdGlvbiBtb2RlbCB3aXRoIGEgdHVuZUxlbmdodCA9IDMuIA0Kc3lzdGVtLnRpbWUoRzU2X3JmIDwtIHRyYWluKGdhbGF4eXNlbnRpbWVudH4uLCBkYXRhID0gRzU2X3RyYWluLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAicmYiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2w9Zml0Q29udHJvbCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUxlbmd0aCA9IDMpKQ0KYGBgDQpgYGB7cn0NCkc1Nl9yZg0KYGBgDQpgYGB7cn0NCnBsb3QoRzU2X3JmKQ0KYGBgDQoNCmBgYHtyLCBSRi1QcmVkaWN0aW9uLUNvbmZ1c2lvbi1NYXRyaXh9DQojcmYgcHJlZGljdGlvbg0KRzU2cmZfUFJFRCA8LSBwcmVkaWN0KEc1Nl9yZiwgRzU2X3Rlc3QpDQpgYGANCmBgYHtyfQ0KI0NyZWF0ZSBSRiBwcmVkaWN0aW9uIENvbmZ1c2lvbiBNYXRyaXgNCmNtRzU2cmZfUFJFRCA8LSBjb25mdXNpb25NYXRyaXgoRzU2cmZfUFJFRCwgRzU2X3Rlc3QkZ2FsYXh5c2VudGltZW50KQ0KY21HNTZyZl9QUkVEDQpgYGANCg0KMTQuMi4gQzUuMCBNb2RlbA0KDQpgYGB7ciwgQnVpbGQtQzUwTW9kZWx9DQpzZXQuc2VlZCgyMjIpDQoNCiNSdW4gQzUuMCBEZWNpc2lvbiB0cmVlIG1vZGVsIA0Kc3lzdGVtLnRpbWUoRzU2X0M1MCA8LSB0cmFpbihnYWxheHlzZW50aW1lbnR+LiwgZGF0YSA9IEc1Nl90cmFpbiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kPSdDNS4wJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmVQcm9jZXNzID0gYygnenYnKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKSkNCmBgYA0KYGBge3J9DQpHNTZfQzUwDQoNCiNwbG90IEM1LjAgbW9kZWwNCnBsb3QoRzU2X0M1MCkNCmBgYA0KYGBge3IsIEM1MC1QcmVkaWN0aW9uLWdhbGF4eX0NCiNwcmVkaWN0aW9uIHdpdGggQzUuMA0KRzU2X0M1MF9QUkVEIDwtIHByZWRpY3QoRzU2X0M1MCwgRzU2X3Rlc3QpDQoNCmNtQzUwX1ByZWQ1OCA8LSBjb25mdXNpb25NYXRyaXgoRzU2X0M1MF9QUkVELCBHNTZfdGVzdCRnYWxheHlzZW50aW1lbnQpDQoNCmNtQzUwX1ByZWQ1OA0KYGBgDQoNCjE0LjMgR3JhZHJpZW50IEJvb3N0IE1vZGVsDQoNCmBgYHtyLCBCdWlsZC1nYm0tR2FsYXh5c2VudGltZW50fQ0Kc2V0LnNlZWQoMjIyKQ0KDQpzeXN0ZW0udGltZShHNTZfZ2JtIDwtIHRyYWluKGdhbGF4eXNlbnRpbWVudCB+LiwgZGF0YSA9IEc1Nl90cmFpbiwNCiAgICAgICAgICAgICAgICAgbWV0aG9kID0gImdibSIsDQogICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wsDQogICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBGQUxTRSkpDQoNCkc1Nl9nYm0NCmBgYA0KYGBge3J9DQojcGxvdCBnYm0gbW9kZWwNCnBsb3QoRzU2X2dibSkNCmBgYA0KDQpgYGB7ciwgZ2JtLVByZWRpY3Rpb24tZ2FsYXh5fQ0KI2dhbGF4eSBwcmVkaWN0aW9uIGFuZCBjb25mdXNpb24gbWF0cml4IC0gDQpHNTZfZ2JtUHJlZCA8LSBwcmVkaWN0KEc1Nl9nYm0sIEc1Nl90ZXN0KQ0KDQojQ29uZnVzaW9uIE1hdHJpeA0KY21fRzU2Z2JtIDwtIGNvbmZ1c2lvbk1hdHJpeChHNTZfZ2JtUHJlZCwgRzU2X3Rlc3QkZ2FsYXh5c2VudGltZW50KQ0KY21fRzU2Z2JtDQpgYGANCiAgICAgICANCmBgYHtyLCBDYWxjdWxhdGluZy1BY2N1cmFjeS1hbmQtS2FwcGF9DQojQ2hlY2tpbmcgUHJlZGljdGlvbiAodGJsIG9ubHkpLiBTdG9yZSB0aGUgcHJlZGljdGVkIGRhdGEgaW4gYSB0YWJsZSBmb3IgYW5hbHlzaXMNCkc1Nl9nYm10YWJsZSA8LSB0YWJsZShQcmVkaWN0ZWQgPSBHNTZfZ2JtUHJlZCwgQWN0dWFsID0gRzU2X3Rlc3QkZ2FsYXh5c2VudGltZW50KQ0KRzU2X2dibXRhYmxlDQoNCiNDYWxjdWxhdGUgQWNjdXJhY3kNCnN1bShkaWFnKEc1Nl9nYm10YWJsZSkpL3N1bShHNTZfZ2JtdGFibGUpDQoNCiNDYWxjdWxhdGUgTWlzY2xhc3NpZmljYXRpb24gRXJyb3IgDQoxIC0gc3VtKGRpYWcoRzU2X2dibXRhYmxlKSkvc3VtKEc1Nl9nYm10YWJsZSkNCmBgYA0KDQoxNC40IFZhcmlhYmxlIEltcG9ydGFuY2UNCg0KQ2FyZXTigJlzIHZhckltcCgpIGZ1bmN0aW9uIGlzIGFub3RoZXIgbWV0aG9kIG9mIGZlYXR1cmUgc2VsZWN0aW9uIHRoYXQgcmV0dXJucyBhIHJhbmtlZCBsaXN0IG9mIGZlYXR1cmVzIGZyb20gYSBkZWNpc2lvbiB0cmVlIG1vZGVsLiBUaGUgcmFua2VkIGxpc3QgY2FuIGJlIHVzZWQgdG8gc2VsZWN0IGZlYXR1cmVzIGltcG9ydGFuY2UuIExldCdzIHVzZSBWYXJJbXAoKSB0byBhc2NlcnRhaW4gaG93IHRoZSBtb2RlbCBwcmlvcml0aXplZCBlYWNoIGZlYXR1cmUgaW4gdGhlIHRyYWluaW5nLg0KDQpgYGB7ciwgR0JNLVZhcmlhYmxlLUltcG9ydGFuY2V9DQp2YXJJbXAoRzU2X0M1MCkNCmBgYA0KDQpgYGB7ciwgQWRkLXByZWRpY3Rpb24tdG8tZ2FsYXh5LXNtYWxsLW1hdHJpeH0NCiNBZGQgcHJlZGljdGlvbiB0byBnYWxheHkgc21hbGwgbWF0cml4DQpQX3NtYWxsX2dhbGF4eVNlbnRpbWVudCA8LSBHNTZfdGVzdA0KDQpQX3NtYWxsX2dhbGF4eVNlbnRpbWVudCRnYWxheHlzZW50aW1lbnQgPC0gRzU2X2dibVByZWQNCg0KI2dsaW1wc2UoUF9zbWFsbF9nYWxheHlTZW50aW1lbnQpDQpgYGANCg0KYGBge3IsIGNoZWNrLXRoZS1zZW50aW1lbnQtY291bnRzLWZvci1lYWNoLWxldmVsfQ0Kc3VtbWFyeShHNTZfZ2JtUHJlZCkNCg0KYGBgDQpgYGB7cn0NCiNQbG90IHByZWRpY3RlZCBnYWxheHkgc2VudGltZW50DQpwbG90X2x5KFBfc21hbGxfZ2FsYXh5U2VudGltZW50LCB4PSB+UF9zbWFsbF9nYWxheHlTZW50aW1lbnQkZ2FsYXh5c2VudGltZW50LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZT0naGlzdG9ncmFtJykgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXlvdXQodGl0bGUgPSAiSGlzdG9ncmFtIG9mIEdhbGF4eXNlbnRpbWVudCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAiQ2xhc3NlcyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIkZyZXF1ZW5jeSIpKQ0KYGBgDQoNCjE1LjAgTW9kZWxzIENvbXBhcmlzb24NCg0KQ2FyZXQ6OnJlc2FtcGxlcyBmdW5jdGlvbiBpcyB1c2VkIHRvIGNvbXBhcmUgdGhlIG1vZGVscyBhbmQgcGljayB0aGUgb25lIHdpdGggdGhlIGhpZ2hlc3QgQVVDIGFuZCBsb3dlc3QgQVVDIHN0YW5kYXJkIGRldmlhdGlvbi4NCg0KYGBge3IsIENvbXBhcmUtTW9kZWxzLWdhbGF4eXNlbnRpbWVudH0NCkdtb2RlbF9saXN0IDwtIGxpc3QoYzUuMCA9IEc1Nl9DNTAsDQogICAgICAgICAgICAgICAgICAgICAgcmYgPSBHNTZfcmYsDQogICAgICAgICAgICAgICAgICAgICBnYm0gPSBHNTZfZ2JtKQ0KDQoNCnJlc2FtcCA8LSByZXNhbXBsZXMoR21vZGVsX2xpc3QpDQojc3VtbWFyaXNlIGRpc3RyaWJ1dGlvbg0Kc3VtbWFyeShyZXNhbXApDQpgYGANCmBgYHtyfQ0KDQojUGxvdCBtb2RlbHMgc3VtbWFyeSANCmJ3cGxvdChyZXNhbXApDQoNCiNkb3QgcGxvdHMgb2YgcmVzdWx0cw0KZG90cGxvdChyZXNhbXApDQpgYGANCg0KYGBge3IsIExvYWQtZmluYWwtZ2FsYXh5c2VudGltZW50LVByZWRpY3Rpb24sIGVjaG89RkFMU0V9DQojQ3JlYXRlIGEgY3N2IGZpbGUgYW5kIHdyaXRlIGl0IHRvIHlvdXIgaGFyZCBkcml2ZS4gDQp3cml0ZS5jc3YoUF9zbWFsbF9nYWxheHlTZW50aW1lbnQsIGZpbGU9ImdhbGF4eXNlbnRpbWVudC5jc3YiLCByb3cubmFtZXMgPSBUUlVFKQ0KYGBgDQoNCg==