library(tidyverse)
library(openintro)

Instructions

  • Complete the coded in the code chunks.
  • Type the answers as text below each question.
  • Knit your file.
  • Upload resulting HTML file to the Canvas

Dataset

The data is related with direct marketing campaigns (phone calls) of a Portuguese banking institution. The classification goal is to predict if the client will subscribe a term deposit (variable y).

Input variables:

# bank client data:

1 - age (numeric)
2 - job: type of job (categorical: “admin.”,“unknown”,“unemployed”,“management”,“housemaid”,“entrepreneur”,“student”, “blue-collar”,“self-employed”,“retired”,“technician”,“services”) 3 - marital: marital status (categorical: “married”,“divorced”,“single”; note: “divorced” means divorced or widowed)
4 - education (categorical: 1=“primary”,2=“secondary”,2=“tertiary”,9=“unknown”,)
5 - default: has credit in default? (binary: “yes”,“no”)
6 - balance: average yearly balance, in euros (numeric)
7 - housing: has housing loan? (binary: “yes”,“no”)
8 - loan: has personal loan? (binary: “yes”,“no”)

# related with the last contact of the current campaign:

9 - contact: contact communication type (categorical: “unknown”,“telephone”,“cellular”) 10 - day: last contact day of the month (numeric)
11 - month: last contact month of year (categorical: “jan”, “feb”, “mar”, …, “nov”, “dec”)
12 - duration: last contact duration, in seconds (numeric)

# other attributes:

13 - campaign: number of contacts performed during this campaign and for this client (numeric, includes last contact)
14 - pdays: number of days that passed by after the client was last contacted from a previous campaign (numeric, -1 means client was not previously contacted)
15 - previous: number of contacts performed before this campaign and for this client (numeric)
16 - poutcome: outcome of the previous marketing campaign (categorical: “unknown”,“other”,“failure”,“success”)

Output variable (desired target):
17 - y - has the client subscribed a term deposit? (binary: “yes”,“no”)

Load and Inspect Data

# Load required libraries
library(neuralnet)
## 
## Attaching package: 'neuralnet'
## The following object is masked from 'package:dplyr':
## 
##     compute
library(nnet) # for dummy variable coding
library(gmodels) # for confusion matrix

# Load the dataset
bank.df <- read.csv("bank.csv", na.strings = "", sep = ";")

# Display the first few rows and structure
head(bank.df)
##   age         job marital education default balance housing loan  contact day
## 1  30  unemployed married   primary      no    1787      no   no cellular  19
## 2  33    services married secondary      no    4789     yes  yes cellular  11
## 3  35  management  single  tertiary      no    1350     yes   no cellular  16
## 4  30  management married  tertiary      no    1476     yes  yes  unknown   3
## 5  59 blue-collar married secondary      no       0     yes   no  unknown   5
## 6  35  management  single  tertiary      no     747      no   no cellular  23
##   month duration campaign pdays previous poutcome  y
## 1   oct       79        1    -1        0  unknown no
## 2   may      220        1   339        4  failure no
## 3   apr      185        1   330        1  failure no
## 4   jun      199        4    -1        0  unknown no
## 5   may      226        1    -1        0  unknown no
## 6   feb      141        2   176        3  failure no
str(bank.df)
## 'data.frame':    4521 obs. of  17 variables:
##  $ age      : int  30 33 35 30 59 35 36 39 41 43 ...
##  $ job      : chr  "unemployed" "services" "management" "management" ...
##  $ marital  : chr  "married" "married" "single" "married" ...
##  $ education: chr  "primary" "secondary" "tertiary" "tertiary" ...
##  $ default  : chr  "no" "no" "no" "no" ...
##  $ balance  : int  1787 4789 1350 1476 0 747 307 147 221 -88 ...
##  $ housing  : chr  "no" "yes" "yes" "yes" ...
##  $ loan     : chr  "no" "yes" "no" "yes" ...
##  $ contact  : chr  "cellular" "cellular" "cellular" "unknown" ...
##  $ day      : int  19 11 16 3 5 23 14 6 14 17 ...
##  $ month    : chr  "oct" "may" "apr" "jun" ...
##  $ duration : int  79 220 185 199 226 141 341 151 57 313 ...
##  $ campaign : int  1 1 1 4 1 2 1 2 2 1 ...
##  $ pdays    : int  -1 339 330 -1 -1 176 330 -1 -1 147 ...
##  $ previous : int  0 4 1 0 0 3 2 0 0 2 ...
##  $ poutcome : chr  "unknown" "failure" "failure" "unknown" ...
##  $ y        : chr  "no" "no" "no" "no" ...

**Complete the code below by filling spaces (“___”).**

Question 1: Scale Numeric Variables

Instructions: - Normalize age, balance, campaign, and previous to the range [0, 1]. - Use the formula: (x - min(x)) / (max(x) - min(x)). - Report the first 6 scaled records.

# Define the normalization function
normalize <- function(x) {
  return((x - min(x)) / (max(x) - min(x)))
}

# Normalize numeric variables to [0,1]
bank.df$age <- normalize(bank.df$age)
bank.df$balance <- normalize(bank.df$balance)
bank.df$campaign <- normalize(bank.df$campaign)
bank.df$previous <- normalize(bank.df$previous)

# Display first 6 records
head(bank.df, n = 6)
##         age         job marital education default    balance housing loan
## 1 0.1617647  unemployed married   primary      no 0.06845546      no   no
## 2 0.2058824    services married secondary      no 0.10875022     yes  yes
## 3 0.2352941  management  single  tertiary      no 0.06258976     yes   no
## 4 0.1617647  management married  tertiary      no 0.06428102     yes  yes
## 5 0.5882353 blue-collar married secondary      no 0.04446920     yes   no
## 6 0.2352941  management  single  tertiary      no 0.05449591      no   no
##    contact day month duration   campaign pdays previous poutcome  y
## 1 cellular  19   oct       79 0.00000000    -1     0.00  unknown no
## 2 cellular  11   may      220 0.00000000   339     0.16  failure no
## 3 cellular  16   apr      185 0.00000000   330     0.04  failure no
## 4  unknown   3   jun      199 0.06122449    -1     0.00  unknown no
## 5  unknown   5   may      226 0.00000000    -1     0.00  unknown no
## 6 cellular  23   feb      141 0.02040816   176     0.12  failure no

Question 2: Create Dummy Variables

Instructions: - Create dummy variables for education and y using class.ind from the nnet package. - Combine them with the original dataframe to create bank.df1. - Rename the new columns appropriately.

# Create dummy variables
bank.df1 <- cbind(bank.df, class.ind(bank.df$education), class.ind(bank.df$y))

# Rename columns
names(bank.df1)[18:23] <- c(
  paste("edu_", c(1, 2, 3, 9), sep = ""),
  paste("term_d_", c("yes", "no"), sep = "")
)

# Display column names
names(bank.df1)
##  [1] "age"        "job"        "marital"    "education"  "default"   
##  [6] "balance"    "housing"    "loan"       "contact"    "day"       
## [11] "month"      "duration"   "campaign"   "pdays"      "previous"  
## [16] "poutcome"   "y"          "edu_1"      "edu_2"      "edu_3"     
## [21] "edu_9"      "term_d_yes" "term_d_no"

Question 3: Select Attributes

Instructions: - Select the following attributes: age, balance, campaign, previous, edu_1, edu_2, edu_3, edu_9, term_d_yes, term_d_no. - Create a new dataframe bank.df2 with only these attributes.

# Select specific attributes
vars <- c("age", "balance", "campaign", "previous", "edu_1", "edu_2", "edu_3", "edu_9", "term_d_yes", "term_d_no")
bank.df2 <- bank.df1[, vars]

# Display column names
names(bank.df2)
##  [1] "age"        "balance"    "campaign"   "previous"   "edu_1"     
##  [6] "edu_2"      "edu_3"      "edu_9"      "term_d_yes" "term_d_no"

Question 4: Partition Data

Instructions: - Partition the data into 60% training and 40% test sets. - Use a random seed to ensure reproducibility.

# Partition the data
set.seed(2)
train.index <- sample(rownames(bank.df2), dim(bank.df2)[1] * 0.6)
test.index <- setdiff(rownames(bank.df2), train.index)
train.df <- bank.df2[train.index, ]
test.df <- bank.df2[test.index, ]

# Display dimensions of training and test sets
dim(train.df)
## [1] 2712   10
dim(test.df)
## [1] 1809   10

Question 5: Fit Neural Network

Instructions: - Fit a neural network with hidden layers (3, 2). - Plot the resulting network.

# Fit neural network
nn <- neuralnet(term_d_yes + term_d_no ~ ., data = train.df, hidden = c(3, 2))

# Plot the neural network
plot(nn, rep = "best")

Question 6: Evaluate Model Performance

Instructions: - Predict classes for training and test datasets. - Use CrossTable from the gmodels package to create a confusion matrix. - Calculate the accuracy for both training and test predictions using mean.

# (a) Predictions on training set
training.prediction <- compute(nn, train.df[, -c(9:10)])
training.class <- apply(training.prediction$net.result, 1, which.max) - 1

# Confusion matrix and accuracy for training set
CrossTable(factor(training.class), factor(train.df$term_d_no), 
           prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE)
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  2712 
## 
##  
##                        | factor(train.df$term_d_no) 
## factor(training.class) |         0 |         1 | Row Total | 
## -----------------------|-----------|-----------|-----------|
##                      0 |      2369 |       304 |      2673 | 
##                        |     0.874 |     0.112 |           | 
## -----------------------|-----------|-----------|-----------|
##                      1 |        10 |        29 |        39 | 
##                        |     0.004 |     0.011 |           | 
## -----------------------|-----------|-----------|-----------|
##           Column Total |      2379 |       333 |      2712 | 
## -----------------------|-----------|-----------|-----------|
## 
## 
training.accuracy <- mean(training.class == train.df$term_d_no)
training.accuracy
## [1] 0.8842183
# (b) Predictions on test set
test.prediction <- compute(nn, test.df[, -c(9:10)])
test.class <- apply(test.prediction$net.result, 1, which.max) - 1

# Confusion matrix and accuracy for test set
CrossTable(factor(test.class), factor(test.df$term_d_no),
           prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE)
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  1809 
## 
##  
##                    | factor(test.df$term_d_no) 
## factor(test.class) |         0 |         1 | Row Total | 
## -------------------|-----------|-----------|-----------|
##                  0 |      1594 |       178 |      1772 | 
##                    |     0.881 |     0.098 |           | 
## -------------------|-----------|-----------|-----------|
##                  1 |        27 |        10 |        37 | 
##                    |     0.015 |     0.006 |           | 
## -------------------|-----------|-----------|-----------|
##       Column Total |      1621 |       188 |      1809 | 
## -------------------|-----------|-----------|-----------|
## 
## 
test.accuracy <- mean(test.class == test.df$term_d_no)
test.accuracy
## [1] 0.8866777

Answer the following questions:

  1. How does the ANN model perform in predicting positive (“yes”) and negative (“no”) outcomes?

Answer:

The ANN model is very good at predicting “no” (negative) outcomes, but it struggles to predict “yes” (positive) outcomes. It has a high number of true negatives and a low number of true positives, indicating it is biased toward predicting “no”.

The recall for “yes” (TP rate) is quite high in the training set (~74.4%), which means the model is fairly good at identifying most of the positive cases when they are present. However, the precision for “yes” is low (~8.7%), which means when the model predicts “yes”, it is wrong a significant proportion of the time (304 false positives).

In the training set:True Negatives (TN): 2369, True Positives (TP): 29 , False Positives (FP): 304, False Negatives (FN): 10

The recall for “yes” (TP rate) is quite high in the training set (~74.4%), which means the model is fairly good at identifying most of the positive cases when they are present. However, the precision for “yes” is low (~8.7%), which means when the model predicts “yes”, it is wrong a significant proportion of the time (304 false positives).

In the test set: True Negatives (TN): 1594, True Positives (TP): 10, False Positives (FP): 178, False Negatives (FN): 27

In summary, the model seems biased towards predicting “no” due to the class imbalance, but when it does predict “yes”, it is often incorrect. This indicates that the model’s sensitivity to the positive class (“yes”) is low, and it struggles to correctly identify the minority class.

  1. What are your thoughts on the performance of the ANN model based on the accuracy estimates for the training and test datasets?

Answer:

The model has high accuracy (~88%) in both training and test sets, but this is mainly due to correctly predicting the dominant “no” class. The low recall and precision for “yes” indicate that the model performs poorly for the minority class. The high accuracy is misleading due to the class imbalance.

Accuracy doesn’t reflect model’s true performance:

While 88.4% accuracy in the training set and 88.7% in the test set might seem good at first glance, it is important to note that these accuracy values are driven largely by the model’s success in predicting the dominant class (“no”). Since the “no” class is more frequent, the model is likely predicting “no” most of the time, which leads to a high accuracy even though it fails to properly identify the minority class (“yes”). Class Imbalance:

The model is heavily biased toward predicting “no”, as demonstrated by the very high number of true negatives (TN) and the low number of true positives (TP) for the “yes” class. This results in low precision and low recall for the “yes” class, which means that although the model correctly identifies “no” outcomes most of the time, it misses many “yes” cases and often misclassifies them as “no”.

Potential Overfitting:

Given the relatively small difference in accuracy between the training (88.4%) and test (88.7%) datasets, there is a chance the model is not overfitting significantly. However, it is still important to consider other performance metrics, such as precision, recall, and F1-score, which are more informative when dealing with imbalanced datasets.

Question 7: Logistic Regression Model

Instructions: - Fit a logistic regression model to classify the outcome term_d_no. - Use the same predictors and the same training and test datasets, except edu_9 and term_d_yes - Display the coefficients using coef. - Add a summary of the model.

# need to drop columns 8 and 9 (edu_9 and term_d_yes)
train.df.log <- train.df[, -c(8,9)]
test.df.log <- test.df[, -c(8,9)]

# Fit logistic regression model
logistic.model <- glm(term_d_no ~ ., data = train.df.log, family = binomial())

# Display coefficients
coef(logistic.model)
##  (Intercept)          age      balance     campaign     previous        edu_1 
## -2.433914133  1.211885563  1.023242015 -3.716195024  2.985451634 -0.367928506 
##        edu_2        edu_3 
##  0.003841825  0.261760907
# Add a summary of the model
summary(logistic.model)
## 
## Call:
## glm(formula = term_d_no ~ ., family = binomial(), data = train.df.log)
## 
## Coefficients:
##              Estimate Std. Error z value Pr(>|z|)    
## (Intercept) -2.433914   0.340480  -7.148 8.77e-13 ***
## age          1.211886   0.376046   3.223  0.00127 ** 
## balance      1.023242   1.363276   0.751  0.45291    
## campaign    -3.716195   1.346734  -2.759  0.00579 ** 
## previous     2.985452   0.714903   4.176 2.97e-05 ***
## edu_1       -0.367929   0.336362  -1.094  0.27402    
## edu_2        0.003842   0.302848   0.013  0.98988    
## edu_3        0.261761   0.307831   0.850  0.39514    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 2020.1  on 2711  degrees of freedom
## Residual deviance: 1973.2  on 2704  degrees of freedom
## AIC: 1989.2
## 
## Number of Fisher Scoring iterations: 5

Question 8: Predict and Evaluate Logistic Model

Instructions: - Predict the outcomes for the test set using the logistic regression model. - Use the pROC library to plot the ROC curve. - Display the AUC value. - Create a confusion matrix for the predictions with a threshold of 0.50, assuming “yes” for predicted probabilities greater than 0.50.

# Load pROC library
library(pROC)
## Type 'citation("pROC")' for a citation.
## 
## Attaching package: 'pROC'
## The following object is masked from 'package:gmodels':
## 
##     ci
## The following objects are masked from 'package:stats':
## 
##     cov, smooth, var
# Predict probabilities for test set
test.prob <- predict(logistic.model, test.df.log, type = "response")

# Plot ROC curve
roc_curve <- roc(test.df.log$term_d_no,test.prob )
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
plot(roc_curve, main = "Satya's ROC Curve for Logistic Regression")

# Display AUC value
auc(roc_curve)
## Area under the curve: 0.6534
# Create confusion matrix with threshold 0.50
test.pred.class <- ifelse(test.prob > 0.50, 1, 0)
CrossTable(factor(test.pred.class), factor(test.df.log$term_d_no), 
           prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE, 
           dnn = c("Predicted", "Actual"))
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  1809 
## 
##  
##              | Actual 
##    Predicted |         0 |         1 | Row Total | 
## -------------|-----------|-----------|-----------|
##            0 |      1617 |       188 |      1805 | 
##              |     0.894 |     0.104 |           | 
## -------------|-----------|-----------|-----------|
##            1 |         4 |         0 |         4 | 
##              |     0.002 |     0.000 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      1621 |       188 |      1809 | 
## -------------|-----------|-----------|-----------|
## 
## 
test.accuracy.log <- mean(test.pred.class == test.df.log$term_d_no)
test.accuracy.log
## [1] 0.893864

Answer the following questions:

  1. How does the logistic model perform in predicting positive the (“no”) outcomes?

Answer: The logistic model performs well in predicting “no” (negative) outcomes. It correctly predicts 1617 out of 1621”no” cases, giving a high accuracy for the “no” class (around 89%). However, it fails to predict any positive (“yes”) outcomes in the test set, as indicated by the 0 predicted positives.

  1. Which predictors are significant? Why?

Answer: The significant predictors in the model are:age (p-value = 0.00127): Significant because the p-value is below the 0.05 threshold.campaign (p-value = 0.00579): Significant due to its p-value being below 0.05.previous (p-value = 2.97e-05): Highly significant with a very low p-value.

The other predictors like balance, edu_1, edu_2, and edu_3 are not significant (p-values > 0.05), suggesting that they do not contribute meaningfully to predicting the outcome.

  1. How would you interpret the coefficient of edu_3=tertiary?

Answer:

The coefficient for edu_3 (tertiary) is 0.261761, but it is not statistically significant (p-value = 0.39514). This means that having a tertiary education does not significantly affect the likelihood of subscribing to a term deposit compared to the reference category (likely “primary” education). Since it’s not significant, it doesn’t provide useful information for predicting the target variable.

  1. How would you explain properties of ROC and AUC estimates you have obtained?

Answer:

The ROC curve is a graphical representation of the trade-off between the true positive rate (sensitivity) and false positive rate (1 - specificity). The AUC (Area Under the Curve) quantifies the overall ability of the model to distinguish between the two classes.

AUC ranges from 0 to 1, where AUC = 1 represents perfect classification and AUC = 0.5represents a random classifier.

In this case, the AUC of 0.893864 suggests that the model does a good job at distinguishing between the “yes” and “no” outcomes, though not perfectly.

  1. Do you see any problems using the accuracy estimate for the logistic model based on the probabilty threshold of 0.50?

Answer:

Yes, there is a significant problem with using accuracy based on a 0.50 threshold for the logistic model. The model has failed to predict any positive (“yes”) cases in the test set, so the accuracy estimate (~89%) is misleading.

The high accuracy is primarily due to the model correctly predicting “no” (the majority class), but it fails to identify the minority class (“yes”).

This issue is a result of class imbalance, where the majority class dominates the predictions, leading to inflated accuracy despite poor performance on the minority class.

LS0tCnRpdGxlOiAiQkFOTCAzMjAwOiBTdXBlcnZpc2VzIE1hY2hpbmUgTGVhcm5pbmciCnN1YnRpdGxlOiAiRmluYWwgRXhhbTogU3VnZ2VzdGVkIFNvbHV0aW9uIgphdXRob3I6ICJTYXR5YSBOYXJheWFuYSBQYW5kYSIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6IG9wZW5pbnRybzo6bGFiX3JlcG9ydAplZGl0b3Jfb3B0aW9uczogCiAgbWFya2Rvd246IAogICAgd3JhcDogNzIKLS0tCgpgYGB7ciwgc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBldmFsID0gVFJVRQopCmBgYAoKYGBge3IgbG9hZC1wYWNrYWdlcywgbWVzc2FnZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkob3BlbmludHJvKQpgYGAKCiMjIyBJbnN0cnVjdGlvbnMKCi0gICBDb21wbGV0ZSB0aGUgY29kZWQgaW4gdGhlIGNvZGUgY2h1bmtzLgotICAgVHlwZSB0aGUgYW5zd2VycyBhcyB0ZXh0IGJlbG93IGVhY2ggcXVlc3Rpb24uCi0gICBLbml0IHlvdXIgZmlsZS4KLSAgIFVwbG9hZCByZXN1bHRpbmcgSFRNTCBmaWxlIHRvIHRoZSBDYW52YXMKCiMjIyBEYXRhc2V0CgpUaGUgZGF0YSBpcyByZWxhdGVkIHdpdGggZGlyZWN0IG1hcmtldGluZyBjYW1wYWlnbnMgKHBob25lIGNhbGxzKSBvZiBhClBvcnR1Z3Vlc2UgYmFua2luZyBpbnN0aXR1dGlvbi4gVGhlIGNsYXNzaWZpY2F0aW9uIGdvYWwgaXMgdG8gcHJlZGljdCBpZgp0aGUgY2xpZW50IHdpbGwgc3Vic2NyaWJlIGEgdGVybSBkZXBvc2l0ICh2YXJpYWJsZSBgeWApLgoKKipJbnB1dCB2YXJpYWJsZXM6KioKCioqYCNgIGJhbmsgY2xpZW50IGRhdGE6KioKCjEgLSAqKmFnZSoqIChudW1lcmljKVwKMiAtICoqam9iKio6IHR5cGUgb2Ygam9iIChjYXRlZ29yaWNhbDoKImFkbWluLiIsInVua25vd24iLCJ1bmVtcGxveWVkIiwibWFuYWdlbWVudCIsImhvdXNlbWFpZCIsImVudHJlcHJlbmV1ciIsInN0dWRlbnQiLAoiYmx1ZS1jb2xsYXIiLCJzZWxmLWVtcGxveWVkIiwicmV0aXJlZCIsInRlY2huaWNpYW4iLCJzZXJ2aWNlcyIpIDMgLQoqKm1hcml0YWwqKjogbWFyaXRhbCBzdGF0dXMgKGNhdGVnb3JpY2FsOiAibWFycmllZCIsImRpdm9yY2VkIiwic2luZ2xlIjsKbm90ZTogImRpdm9yY2VkIiBtZWFucyBkaXZvcmNlZCBvciB3aWRvd2VkKVwKNCAtICoqZWR1Y2F0aW9uKiogKGNhdGVnb3JpY2FsOgoxPSJwcmltYXJ5IiwyPSJzZWNvbmRhcnkiLDI9InRlcnRpYXJ5Iiw5PSJ1bmtub3duIiwpXAo1IC0gKipkZWZhdWx0Kio6IGhhcyBjcmVkaXQgaW4gZGVmYXVsdD8gKGJpbmFyeTogInllcyIsIm5vIilcCjYgLSAqKmJhbGFuY2UqKjogYXZlcmFnZSB5ZWFybHkgYmFsYW5jZSwgaW4gZXVyb3MgKG51bWVyaWMpXAo3IC0gKipob3VzaW5nKio6IGhhcyBob3VzaW5nIGxvYW4/IChiaW5hcnk6ICJ5ZXMiLCJubyIpXAo4IC0gKipsb2FuKio6IGhhcyBwZXJzb25hbCBsb2FuPyAoYmluYXJ5OiAieWVzIiwibm8iKQoKKipgI2AgcmVsYXRlZCB3aXRoIHRoZSBsYXN0IGNvbnRhY3Qgb2YgdGhlIGN1cnJlbnQgY2FtcGFpZ246KioKCjkgLSAqKmNvbnRhY3QqKjogY29udGFjdCBjb21tdW5pY2F0aW9uIHR5cGUgKGNhdGVnb3JpY2FsOgoidW5rbm93biIsInRlbGVwaG9uZSIsImNlbGx1bGFyIikgMTAgLSAqKmRheSoqOiBsYXN0IGNvbnRhY3QgZGF5IG9mIHRoZQptb250aCAobnVtZXJpYylcCjExIC0gbW9udGg6IGxhc3QgY29udGFjdCBtb250aCBvZiB5ZWFyIChjYXRlZ29yaWNhbDogImphbiIsICJmZWIiLAoibWFyIiwgLi4uLCAibm92IiwgImRlYyIpXAoxMiAtICoqZHVyYXRpb24qKjogbGFzdCBjb250YWN0IGR1cmF0aW9uLCBpbiBzZWNvbmRzIChudW1lcmljKQoKKipgI2Agb3RoZXIgYXR0cmlidXRlczoqKgoKMTMgLSAqKmNhbXBhaWduKio6IG51bWJlciBvZiBjb250YWN0cyBwZXJmb3JtZWQgZHVyaW5nIHRoaXMgY2FtcGFpZ24gYW5kCmZvciB0aGlzIGNsaWVudCAobnVtZXJpYywgaW5jbHVkZXMgbGFzdCBjb250YWN0KVwKMTQgLSAqKnBkYXlzKio6IG51bWJlciBvZiBkYXlzIHRoYXQgcGFzc2VkIGJ5IGFmdGVyIHRoZSBjbGllbnQgd2FzIGxhc3QKY29udGFjdGVkIGZyb20gYSBwcmV2aW91cyBjYW1wYWlnbiAobnVtZXJpYywgLTEgbWVhbnMgY2xpZW50IHdhcyBub3QKcHJldmlvdXNseSBjb250YWN0ZWQpXAoxNSAtICoqcHJldmlvdXMqKjogbnVtYmVyIG9mIGNvbnRhY3RzIHBlcmZvcm1lZCBiZWZvcmUgdGhpcyBjYW1wYWlnbiBhbmQKZm9yIHRoaXMgY2xpZW50IChudW1lcmljKVwKMTYgLSAqKnBvdXRjb21lKio6IG91dGNvbWUgb2YgdGhlIHByZXZpb3VzIG1hcmtldGluZyBjYW1wYWlnbgooY2F0ZWdvcmljYWw6ICJ1bmtub3duIiwib3RoZXIiLCJmYWlsdXJlIiwic3VjY2VzcyIpCgoqKk91dHB1dCB2YXJpYWJsZSAoZGVzaXJlZCB0YXJnZXQpOioqXAoxNyAtICoqeSoqIC0gaGFzIHRoZSBjbGllbnQgc3Vic2NyaWJlZCBhIHRlcm0gZGVwb3NpdD8gKGJpbmFyeToKInllcyIsIm5vIikKCiMjIyBMb2FkIGFuZCBJbnNwZWN0IERhdGEKCmBgYHtyIGxvYWQtZGF0YX0KIyBMb2FkIHJlcXVpcmVkIGxpYnJhcmllcwpsaWJyYXJ5KG5ldXJhbG5ldCkKbGlicmFyeShubmV0KSAjIGZvciBkdW1teSB2YXJpYWJsZSBjb2RpbmcKbGlicmFyeShnbW9kZWxzKSAjIGZvciBjb25mdXNpb24gbWF0cml4CgojIExvYWQgdGhlIGRhdGFzZXQKYmFuay5kZiA8LSByZWFkLmNzdigiYmFuay5jc3YiLCBuYS5zdHJpbmdzID0gIiIsIHNlcCA9ICI7IikKCiMgRGlzcGxheSB0aGUgZmlyc3QgZmV3IHJvd3MgYW5kIHN0cnVjdHVyZQpoZWFkKGJhbmsuZGYpCnN0cihiYW5rLmRmKQpgYGAKClwqXCpDb21wbGV0ZSB0aGUgY29kZSBiZWxvdyBieSBmaWxsaW5nIHNwYWNlcyAoIlxfXF9cXyIpLlwqXCoKCiMjIyBRdWVzdGlvbiAxOiBTY2FsZSBOdW1lcmljIFZhcmlhYmxlcwoKKipJbnN0cnVjdGlvbnM6KiogLSBOb3JtYWxpemUgYGFnZWAsIGBiYWxhbmNlYCwgYGNhbXBhaWduYCwgYW5kCmBwcmV2aW91c2AgdG8gdGhlIHJhbmdlIFswLCAxXS4gLSBVc2UgdGhlIGZvcm11bGE6CmAoeCAtIG1pbih4KSkgLyAobWF4KHgpIC0gbWluKHgpKWAuIC0gUmVwb3J0IHRoZSBmaXJzdCA2IHNjYWxlZCByZWNvcmRzLgoKYGBge3Igc2NhbGUtdmFyaWFibGVzfQojIERlZmluZSB0aGUgbm9ybWFsaXphdGlvbiBmdW5jdGlvbgpub3JtYWxpemUgPC0gZnVuY3Rpb24oeCkgewogIHJldHVybigoeCAtIG1pbih4KSkgLyAobWF4KHgpIC0gbWluKHgpKSkKfQoKIyBOb3JtYWxpemUgbnVtZXJpYyB2YXJpYWJsZXMgdG8gWzAsMV0KYmFuay5kZiRhZ2UgPC0gbm9ybWFsaXplKGJhbmsuZGYkYWdlKQpiYW5rLmRmJGJhbGFuY2UgPC0gbm9ybWFsaXplKGJhbmsuZGYkYmFsYW5jZSkKYmFuay5kZiRjYW1wYWlnbiA8LSBub3JtYWxpemUoYmFuay5kZiRjYW1wYWlnbikKYmFuay5kZiRwcmV2aW91cyA8LSBub3JtYWxpemUoYmFuay5kZiRwcmV2aW91cykKCiMgRGlzcGxheSBmaXJzdCA2IHJlY29yZHMKaGVhZChiYW5rLmRmLCBuID0gNikKYGBgCgojIyMgUXVlc3Rpb24gMjogQ3JlYXRlIER1bW15IFZhcmlhYmxlcwoKKipJbnN0cnVjdGlvbnM6KiogLSBDcmVhdGUgZHVtbXkgdmFyaWFibGVzIGZvciBgZWR1Y2F0aW9uYCBhbmQgYHlgIHVzaW5nCmBjbGFzcy5pbmRgIGZyb20gdGhlIGBubmV0YCBwYWNrYWdlLiAtIENvbWJpbmUgdGhlbSB3aXRoIHRoZSBvcmlnaW5hbApkYXRhZnJhbWUgdG8gY3JlYXRlIGBiYW5rLmRmMWAuIC0gUmVuYW1lIHRoZSBuZXcgY29sdW1ucyBhcHByb3ByaWF0ZWx5LgoKYGBge3IgY3JlYXRlLWR1bW15LXZhcmlhYmxlc30KIyBDcmVhdGUgZHVtbXkgdmFyaWFibGVzCmJhbmsuZGYxIDwtIGNiaW5kKGJhbmsuZGYsIGNsYXNzLmluZChiYW5rLmRmJGVkdWNhdGlvbiksIGNsYXNzLmluZChiYW5rLmRmJHkpKQoKIyBSZW5hbWUgY29sdW1ucwpuYW1lcyhiYW5rLmRmMSlbMTg6MjNdIDwtIGMoCiAgcGFzdGUoImVkdV8iLCBjKDEsIDIsIDMsIDkpLCBzZXAgPSAiIiksCiAgcGFzdGUoInRlcm1fZF8iLCBjKCJ5ZXMiLCAibm8iKSwgc2VwID0gIiIpCikKCiMgRGlzcGxheSBjb2x1bW4gbmFtZXMKbmFtZXMoYmFuay5kZjEpCgoKYGBgCgojIyMgUXVlc3Rpb24gMzogU2VsZWN0IEF0dHJpYnV0ZXMKCioqSW5zdHJ1Y3Rpb25zOioqIC0gU2VsZWN0IHRoZSBmb2xsb3dpbmcgYXR0cmlidXRlczogYGFnZWAsIGBiYWxhbmNlYCwKYGNhbXBhaWduYCwgYHByZXZpb3VzYCwgYGVkdV8xYCwgYGVkdV8yYCwgYGVkdV8zYCwgYGVkdV85YCwKYHRlcm1fZF95ZXNgLCBgdGVybV9kX25vYC4gLSBDcmVhdGUgYSBuZXcgZGF0YWZyYW1lIGBiYW5rLmRmMmAgd2l0aCBvbmx5CnRoZXNlIGF0dHJpYnV0ZXMuCgpgYGB7ciBzZWxlY3QtYXR0cmlidXRlc30KIyBTZWxlY3Qgc3BlY2lmaWMgYXR0cmlidXRlcwp2YXJzIDwtIGMoImFnZSIsICJiYWxhbmNlIiwgImNhbXBhaWduIiwgInByZXZpb3VzIiwgImVkdV8xIiwgImVkdV8yIiwgImVkdV8zIiwgImVkdV85IiwgInRlcm1fZF95ZXMiLCAidGVybV9kX25vIikKYmFuay5kZjIgPC0gYmFuay5kZjFbLCB2YXJzXQoKIyBEaXNwbGF5IGNvbHVtbiBuYW1lcwpuYW1lcyhiYW5rLmRmMikKYGBgCgojIyMgUXVlc3Rpb24gNDogUGFydGl0aW9uIERhdGEKCioqSW5zdHJ1Y3Rpb25zOioqIC0gUGFydGl0aW9uIHRoZSBkYXRhIGludG8gNjAlIHRyYWluaW5nIGFuZCA0MCUgdGVzdApzZXRzLiAtIFVzZSBhIHJhbmRvbSBzZWVkIHRvIGVuc3VyZSByZXByb2R1Y2liaWxpdHkuCgpgYGB7ciBwYXJ0aXRpb24tZGF0YX0KIyBQYXJ0aXRpb24gdGhlIGRhdGEKc2V0LnNlZWQoMikKdHJhaW4uaW5kZXggPC0gc2FtcGxlKHJvd25hbWVzKGJhbmsuZGYyKSwgZGltKGJhbmsuZGYyKVsxXSAqIDAuNikKdGVzdC5pbmRleCA8LSBzZXRkaWZmKHJvd25hbWVzKGJhbmsuZGYyKSwgdHJhaW4uaW5kZXgpCnRyYWluLmRmIDwtIGJhbmsuZGYyW3RyYWluLmluZGV4LCBdCnRlc3QuZGYgPC0gYmFuay5kZjJbdGVzdC5pbmRleCwgXQoKIyBEaXNwbGF5IGRpbWVuc2lvbnMgb2YgdHJhaW5pbmcgYW5kIHRlc3Qgc2V0cwpkaW0odHJhaW4uZGYpCmRpbSh0ZXN0LmRmKQoKYGBgCgojIyMgUXVlc3Rpb24gNTogRml0IE5ldXJhbCBOZXR3b3JrCgoqKkluc3RydWN0aW9uczoqKiAtIEZpdCBhIG5ldXJhbCBuZXR3b3JrIHdpdGggaGlkZGVuIGxheWVycyAoMywgMikuIC0KUGxvdCB0aGUgcmVzdWx0aW5nIG5ldHdvcmsuCgpgYGB7ciBmaXQtbmV1cmFsLW5ldHdvcmt9CiMgRml0IG5ldXJhbCBuZXR3b3JrCm5uIDwtIG5ldXJhbG5ldCh0ZXJtX2RfeWVzICsgdGVybV9kX25vIH4gLiwgZGF0YSA9IHRyYWluLmRmLCBoaWRkZW4gPSBjKDMsIDIpKQoKIyBQbG90IHRoZSBuZXVyYWwgbmV0d29yawpwbG90KG5uLCByZXAgPSAiYmVzdCIpCgoKYGBgCgojIyMgUXVlc3Rpb24gNjogRXZhbHVhdGUgTW9kZWwgUGVyZm9ybWFuY2UKCioqSW5zdHJ1Y3Rpb25zOioqIC0gUHJlZGljdCBjbGFzc2VzIGZvciB0cmFpbmluZyBhbmQgdGVzdCBkYXRhc2V0cy4gLQpVc2UgYENyb3NzVGFibGVgIGZyb20gdGhlIGBnbW9kZWxzYCBwYWNrYWdlIHRvIGNyZWF0ZSBhIGNvbmZ1c2lvbgptYXRyaXguIC0gQ2FsY3VsYXRlIHRoZSBhY2N1cmFjeSBmb3IgYm90aCB0cmFpbmluZyBhbmQgdGVzdCBwcmVkaWN0aW9ucwp1c2luZyBgbWVhbmAuCgpgYGB7ciBldmFsdWF0ZS1wZXJmb3JtYW5jZX0KIyAoYSkgUHJlZGljdGlvbnMgb24gdHJhaW5pbmcgc2V0CnRyYWluaW5nLnByZWRpY3Rpb24gPC0gY29tcHV0ZShubiwgdHJhaW4uZGZbLCAtYyg5OjEwKV0pCnRyYWluaW5nLmNsYXNzIDwtIGFwcGx5KHRyYWluaW5nLnByZWRpY3Rpb24kbmV0LnJlc3VsdCwgMSwgd2hpY2gubWF4KSAtIDEKCiMgQ29uZnVzaW9uIG1hdHJpeCBhbmQgYWNjdXJhY3kgZm9yIHRyYWluaW5nIHNldApDcm9zc1RhYmxlKGZhY3Rvcih0cmFpbmluZy5jbGFzcyksIGZhY3Rvcih0cmFpbi5kZiR0ZXJtX2Rfbm8pLCAKICAgICAgICAgICBwcm9wLmNoaXNxID0gRkFMU0UsIHByb3AuYyA9IEZBTFNFLCBwcm9wLnIgPSBGQUxTRSkKdHJhaW5pbmcuYWNjdXJhY3kgPC0gbWVhbih0cmFpbmluZy5jbGFzcyA9PSB0cmFpbi5kZiR0ZXJtX2Rfbm8pCnRyYWluaW5nLmFjY3VyYWN5CgojIChiKSBQcmVkaWN0aW9ucyBvbiB0ZXN0IHNldAp0ZXN0LnByZWRpY3Rpb24gPC0gY29tcHV0ZShubiwgdGVzdC5kZlssIC1jKDk6MTApXSkKdGVzdC5jbGFzcyA8LSBhcHBseSh0ZXN0LnByZWRpY3Rpb24kbmV0LnJlc3VsdCwgMSwgd2hpY2gubWF4KSAtIDEKCiMgQ29uZnVzaW9uIG1hdHJpeCBhbmQgYWNjdXJhY3kgZm9yIHRlc3Qgc2V0CkNyb3NzVGFibGUoZmFjdG9yKHRlc3QuY2xhc3MpLCBmYWN0b3IodGVzdC5kZiR0ZXJtX2Rfbm8pLAogICAgICAgICAgIHByb3AuY2hpc3EgPSBGQUxTRSwgcHJvcC5jID0gRkFMU0UsIHByb3AuciA9IEZBTFNFKQp0ZXN0LmFjY3VyYWN5IDwtIG1lYW4odGVzdC5jbGFzcyA9PSB0ZXN0LmRmJHRlcm1fZF9ubykKdGVzdC5hY2N1cmFjeQpgYGAKCioqQW5zd2VyIHRoZSBmb2xsb3dpbmcgcXVlc3Rpb25zOioqCgoxLiAgSG93IGRvZXMgdGhlIEFOTiBtb2RlbCBwZXJmb3JtIGluIHByZWRpY3RpbmcgcG9zaXRpdmUgKCJ5ZXMiKSBhbmQgbmVnYXRpdmUgKCJubyIpIG91dGNvbWVzPwoKKioqQW5zd2VyOioqKgoKVGhlIEFOTiBtb2RlbCBpcyB2ZXJ5IGdvb2QgYXQgcHJlZGljdGluZyAibm8iIChuZWdhdGl2ZSkgb3V0Y29tZXMsIGJ1dCBpdCBzdHJ1Z2dsZXMgdG8gcHJlZGljdCAieWVzIiAocG9zaXRpdmUpIG91dGNvbWVzLiBJdCBoYXMgYSBoaWdoIG51bWJlciBvZiB0cnVlIG5lZ2F0aXZlcyBhbmQgYSBsb3cgbnVtYmVyIG9mIHRydWUgcG9zaXRpdmVzLCBpbmRpY2F0aW5nIGl0IGlzIGJpYXNlZCB0b3dhcmQgcHJlZGljdGluZyAibm8iLgoKVGhlIHJlY2FsbCBmb3IgInllcyIgKFRQIHJhdGUpIGlzIHF1aXRlIGhpZ2ggaW4gdGhlIHRyYWluaW5nIHNldCAofjc0LjQlKSwgd2hpY2ggbWVhbnMgdGhlIG1vZGVsIGlzIGZhaXJseSBnb29kIGF0IGlkZW50aWZ5aW5nIG1vc3Qgb2YgdGhlIHBvc2l0aXZlIGNhc2VzIHdoZW4gdGhleSBhcmUgcHJlc2VudC4gSG93ZXZlciwgdGhlIHByZWNpc2lvbiBmb3IgInllcyIgaXMgbG93ICh+OC43JSksIHdoaWNoIG1lYW5zIHdoZW4gdGhlIG1vZGVsIHByZWRpY3RzICJ5ZXMiLCBpdCBpcyB3cm9uZyBhIHNpZ25pZmljYW50IHByb3BvcnRpb24gb2YgdGhlIHRpbWUgKDMwNCBmYWxzZSBwb3NpdGl2ZXMpLgoKSW4gdGhlIHRyYWluaW5nIHNldDpUcnVlIE5lZ2F0aXZlcyAoVE4pOiAyMzY5LCBUcnVlIFBvc2l0aXZlcyAoVFApOiAyOSAsIEZhbHNlIFBvc2l0aXZlcyAoRlApOiAzMDQsIEZhbHNlIE5lZ2F0aXZlcyAoRk4pOiAxMAoKVGhlIHJlY2FsbCBmb3IgInllcyIgKFRQIHJhdGUpIGlzIHF1aXRlIGhpZ2ggaW4gdGhlIHRyYWluaW5nIHNldCAofjc0LjQlKSwgd2hpY2ggbWVhbnMgdGhlIG1vZGVsIGlzIGZhaXJseSBnb29kIGF0IGlkZW50aWZ5aW5nIG1vc3Qgb2YgdGhlIHBvc2l0aXZlIGNhc2VzIHdoZW4gdGhleSBhcmUgcHJlc2VudC4gSG93ZXZlciwgdGhlIHByZWNpc2lvbiBmb3IgInllcyIgaXMgbG93ICh+OC43JSksIHdoaWNoIG1lYW5zIHdoZW4gdGhlIG1vZGVsIHByZWRpY3RzICJ5ZXMiLCBpdCBpcyB3cm9uZyBhIHNpZ25pZmljYW50IHByb3BvcnRpb24gb2YgdGhlIHRpbWUgKDMwNCBmYWxzZSBwb3NpdGl2ZXMpLgoKSW4gdGhlIHRlc3Qgc2V0OiBUcnVlIE5lZ2F0aXZlcyAoVE4pOiAxNTk0LCBUcnVlIFBvc2l0aXZlcyAoVFApOiAxMCwgRmFsc2UgUG9zaXRpdmVzIChGUCk6IDE3OCwgRmFsc2UgTmVnYXRpdmVzIChGTik6IDI3CgpJbiBzdW1tYXJ5LCB0aGUgbW9kZWwgc2VlbXMgYmlhc2VkIHRvd2FyZHMgcHJlZGljdGluZyAibm8iIGR1ZSB0byB0aGUgY2xhc3MgaW1iYWxhbmNlLCBidXQgd2hlbiBpdCBkb2VzIHByZWRpY3QgInllcyIsIGl0IGlzIG9mdGVuIGluY29ycmVjdC4gVGhpcyBpbmRpY2F0ZXMgdGhhdCB0aGUgbW9kZWwncyBzZW5zaXRpdml0eSB0byB0aGUgcG9zaXRpdmUgY2xhc3MgKCJ5ZXMiKSBpcyBsb3csIGFuZCBpdCBzdHJ1Z2dsZXMgdG8gY29ycmVjdGx5IGlkZW50aWZ5IHRoZSBtaW5vcml0eSBjbGFzcy4KCjIuICBXaGF0IGFyZSB5b3VyIHRob3VnaHRzIG9uIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgQU5OIG1vZGVsIGJhc2VkIG9uIHRoZSBhY2N1cmFjeSBlc3RpbWF0ZXMgZm9yIHRoZSB0cmFpbmluZyBhbmQgdGVzdCBkYXRhc2V0cz8KCioqKkFuc3dlcjoqKiogCgpUaGUgbW9kZWwgaGFzIGhpZ2ggYWNjdXJhY3kgKH44OCUpIGluIGJvdGggdHJhaW5pbmcgYW5kIHRlc3Qgc2V0cywgYnV0IHRoaXMgaXMgbWFpbmx5IGR1ZSB0byBjb3JyZWN0bHkgcHJlZGljdGluZyB0aGUgZG9taW5hbnQgIm5vIiBjbGFzcy4gVGhlIGxvdyByZWNhbGwgYW5kIHByZWNpc2lvbiBmb3IgInllcyIgaW5kaWNhdGUgdGhhdCB0aGUgbW9kZWwgcGVyZm9ybXMgcG9vcmx5IGZvciB0aGUgbWlub3JpdHkgY2xhc3MuIFRoZSBoaWdoIGFjY3VyYWN5IGlzIG1pc2xlYWRpbmcgZHVlIHRvIHRoZSBjbGFzcyBpbWJhbGFuY2UuCgoKQWNjdXJhY3kgZG9lc24ndCByZWZsZWN0IG1vZGVsJ3MgdHJ1ZSBwZXJmb3JtYW5jZToKCldoaWxlIDg4LjQlIGFjY3VyYWN5IGluIHRoZSB0cmFpbmluZyBzZXQgYW5kIDg4LjclIGluIHRoZSB0ZXN0IHNldCBtaWdodCBzZWVtIGdvb2QgYXQgZmlyc3QgZ2xhbmNlLCBpdCBpcyBpbXBvcnRhbnQgdG8gbm90ZSB0aGF0IHRoZXNlIGFjY3VyYWN5IHZhbHVlcyBhcmUgZHJpdmVuIGxhcmdlbHkgYnkgdGhlIG1vZGVsJ3Mgc3VjY2VzcyBpbiBwcmVkaWN0aW5nIHRoZSBkb21pbmFudCBjbGFzcyAoIm5vIikuClNpbmNlIHRoZSAibm8iIGNsYXNzIGlzIG1vcmUgZnJlcXVlbnQsIHRoZSBtb2RlbCBpcyBsaWtlbHkgcHJlZGljdGluZyAibm8iIG1vc3Qgb2YgdGhlIHRpbWUsIHdoaWNoIGxlYWRzIHRvIGEgaGlnaCBhY2N1cmFjeSBldmVuIHRob3VnaCBpdCBmYWlscyB0byBwcm9wZXJseSBpZGVudGlmeSB0aGUgbWlub3JpdHkgY2xhc3MgKCJ5ZXMiKS4KQ2xhc3MgSW1iYWxhbmNlOgoKVGhlIG1vZGVsIGlzIGhlYXZpbHkgYmlhc2VkIHRvd2FyZCBwcmVkaWN0aW5nICJubyIsIGFzIGRlbW9uc3RyYXRlZCBieSB0aGUgdmVyeSBoaWdoIG51bWJlciBvZiB0cnVlIG5lZ2F0aXZlcyAoVE4pIGFuZCB0aGUgbG93IG51bWJlciBvZiB0cnVlIHBvc2l0aXZlcyAoVFApIGZvciB0aGUgInllcyIgY2xhc3MuIFRoaXMgcmVzdWx0cyBpbiBsb3cgcHJlY2lzaW9uIGFuZCBsb3cgcmVjYWxsIGZvciB0aGUgInllcyIgY2xhc3MsIHdoaWNoIG1lYW5zIHRoYXQgYWx0aG91Z2ggdGhlIG1vZGVsIGNvcnJlY3RseSBpZGVudGlmaWVzICJubyIgb3V0Y29tZXMgbW9zdCBvZiB0aGUgdGltZSwgaXQgbWlzc2VzIG1hbnkgInllcyIgY2FzZXMgYW5kIG9mdGVuIG1pc2NsYXNzaWZpZXMgdGhlbSBhcyAibm8iLgoKUG90ZW50aWFsIE92ZXJmaXR0aW5nOgoKR2l2ZW4gdGhlIHJlbGF0aXZlbHkgc21hbGwgZGlmZmVyZW5jZSBpbiBhY2N1cmFjeSBiZXR3ZWVuIHRoZSB0cmFpbmluZyAoODguNCUpIGFuZCB0ZXN0ICg4OC43JSkgZGF0YXNldHMsIHRoZXJlIGlzIGEgY2hhbmNlIHRoZSBtb2RlbCBpcyBub3Qgb3ZlcmZpdHRpbmcgc2lnbmlmaWNhbnRseS4gSG93ZXZlciwgaXQgaXMgc3RpbGwgaW1wb3J0YW50IHRvIGNvbnNpZGVyIG90aGVyIHBlcmZvcm1hbmNlIG1ldHJpY3MsIHN1Y2ggYXMgcHJlY2lzaW9uLCByZWNhbGwsIGFuZCBGMS1zY29yZSwgd2hpY2ggYXJlIG1vcmUgaW5mb3JtYXRpdmUgd2hlbiBkZWFsaW5nIHdpdGggaW1iYWxhbmNlZCBkYXRhc2V0cy4KCgoKIyMjIFF1ZXN0aW9uIDc6IExvZ2lzdGljIFJlZ3Jlc3Npb24gTW9kZWwKCioqSW5zdHJ1Y3Rpb25zOioqIC0gRml0IGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB0byBjbGFzc2lmeSB0aGUKb3V0Y29tZSBgdGVybV9kX25vYC4gLSBVc2UgdGhlIHNhbWUgcHJlZGljdG9ycyBhbmQgdGhlIHNhbWUgdHJhaW5pbmcgYW5kCnRlc3QgZGF0YXNldHMsIGV4Y2VwdCBgZWR1XzlgIGFuZCBgdGVybV9kX3llc2AgLSBEaXNwbGF5IHRoZQpjb2VmZmljaWVudHMgdXNpbmcgYGNvZWZgLiAtIEFkZCBhIHN1bW1hcnkgb2YgdGhlIG1vZGVsLgoKYGBge3IgbG9naXN0aWMtcmVncmVzc2lvbn0KIyBuZWVkIHRvIGRyb3AgY29sdW1ucyA4IGFuZCA5IChlZHVfOSBhbmQgdGVybV9kX3llcykKdHJhaW4uZGYubG9nIDwtIHRyYWluLmRmWywgLWMoOCw5KV0KdGVzdC5kZi5sb2cgPC0gdGVzdC5kZlssIC1jKDgsOSldCgojIEZpdCBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsCmxvZ2lzdGljLm1vZGVsIDwtIGdsbSh0ZXJtX2Rfbm8gfiAuLCBkYXRhID0gdHJhaW4uZGYubG9nLCBmYW1pbHkgPSBiaW5vbWlhbCgpKQoKIyBEaXNwbGF5IGNvZWZmaWNpZW50cwpjb2VmKGxvZ2lzdGljLm1vZGVsKQoKIyBBZGQgYSBzdW1tYXJ5IG9mIHRoZSBtb2RlbApzdW1tYXJ5KGxvZ2lzdGljLm1vZGVsKQpgYGAKCiMjIyBRdWVzdGlvbiA4OiBQcmVkaWN0IGFuZCBFdmFsdWF0ZSBMb2dpc3RpYyBNb2RlbAoKKipJbnN0cnVjdGlvbnM6KiogLSBQcmVkaWN0IHRoZSBvdXRjb21lcyBmb3IgdGhlIHRlc3Qgc2V0IHVzaW5nIHRoZQpsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsLiAtIFVzZSB0aGUgYHBST0NgIGxpYnJhcnkgdG8gcGxvdCB0aGUgUk9DCmN1cnZlLiAtIERpc3BsYXkgdGhlIEFVQyB2YWx1ZS4gLSBDcmVhdGUgYSBjb25mdXNpb24gbWF0cml4IGZvciB0aGUKcHJlZGljdGlvbnMgd2l0aCBhIHRocmVzaG9sZCBvZiAwLjUwLCBhc3N1bWluZyAieWVzIiBmb3IgcHJlZGljdGVkCnByb2JhYmlsaXRpZXMgZ3JlYXRlciB0aGFuIDAuNTAuCgpgYGB7ciBsb2dpc3RpYy1yb2MtYXVjfQojIExvYWQgcFJPQyBsaWJyYXJ5CmxpYnJhcnkocFJPQykKCiMgUHJlZGljdCBwcm9iYWJpbGl0aWVzIGZvciB0ZXN0IHNldAp0ZXN0LnByb2IgPC0gcHJlZGljdChsb2dpc3RpYy5tb2RlbCwgdGVzdC5kZi5sb2csIHR5cGUgPSAicmVzcG9uc2UiKQoKIyBQbG90IFJPQyBjdXJ2ZQpyb2NfY3VydmUgPC0gcm9jKHRlc3QuZGYubG9nJHRlcm1fZF9ubyx0ZXN0LnByb2IgKQpwbG90KHJvY19jdXJ2ZSwgbWFpbiA9ICJTYXR5YSdzIFJPQyBDdXJ2ZSBmb3IgTG9naXN0aWMgUmVncmVzc2lvbiIpCgojIERpc3BsYXkgQVVDIHZhbHVlCmF1Yyhyb2NfY3VydmUpCgojIENyZWF0ZSBjb25mdXNpb24gbWF0cml4IHdpdGggdGhyZXNob2xkIDAuNTAKdGVzdC5wcmVkLmNsYXNzIDwtIGlmZWxzZSh0ZXN0LnByb2IgPiAwLjUwLCAxLCAwKQpDcm9zc1RhYmxlKGZhY3Rvcih0ZXN0LnByZWQuY2xhc3MpLCBmYWN0b3IodGVzdC5kZi5sb2ckdGVybV9kX25vKSwgCiAgICAgICAgICAgcHJvcC5jaGlzcSA9IEZBTFNFLCBwcm9wLmMgPSBGQUxTRSwgcHJvcC5yID0gRkFMU0UsIAogICAgICAgICAgIGRubiA9IGMoIlByZWRpY3RlZCIsICJBY3R1YWwiKSkKCnRlc3QuYWNjdXJhY3kubG9nIDwtIG1lYW4odGVzdC5wcmVkLmNsYXNzID09IHRlc3QuZGYubG9nJHRlcm1fZF9ubykKdGVzdC5hY2N1cmFjeS5sb2cKCmBgYAoKKipBbnN3ZXIgdGhlIGZvbGxvd2luZyBxdWVzdGlvbnM6KioKCjEuICBIb3cgZG9lcyB0aGUgbG9naXN0aWMgbW9kZWwgcGVyZm9ybSBpbiBwcmVkaWN0aW5nIHBvc2l0aXZlIHRoZQogICAgKCJubyIpIG91dGNvbWVzPwoKKioqQW5zd2VyOioqKiAKVGhlIGxvZ2lzdGljIG1vZGVsIHBlcmZvcm1zIHdlbGwgaW4gcHJlZGljdGluZyAibm8iCihuZWdhdGl2ZSkgb3V0Y29tZXMuIEl0IGNvcnJlY3RseSBwcmVkaWN0cyAxNjE3IG91dCBvZiAxNjIxIm5vIiBjYXNlcywKZ2l2aW5nIGEgaGlnaCBhY2N1cmFjeSBmb3IgdGhlICJubyIgY2xhc3MgKGFyb3VuZCA4OSUpLiBIb3dldmVyLCBpdApmYWlscyB0byBwcmVkaWN0IGFueSBwb3NpdGl2ZSAoInllcyIpIG91dGNvbWVzIGluIHRoZSB0ZXN0IHNldCwgYXMKaW5kaWNhdGVkIGJ5IHRoZSAwIHByZWRpY3RlZCBwb3NpdGl2ZXMuCgoyLiAgV2hpY2ggcHJlZGljdG9ycyBhcmUgc2lnbmlmaWNhbnQ/IFdoeT8KCioqKkFuc3dlcjoqKiogClRoZSBzaWduaWZpY2FudCBwcmVkaWN0b3JzIGluIHRoZSBtb2RlbCBhcmU6YWdlIChwLXZhbHVlID0KMC4wMDEyNyk6IFNpZ25pZmljYW50IGJlY2F1c2UgdGhlIHAtdmFsdWUgaXMgYmVsb3cgdGhlIDAuMDUKdGhyZXNob2xkLmNhbXBhaWduIChwLXZhbHVlID0gMC4wMDU3OSk6IFNpZ25pZmljYW50IGR1ZSB0byBpdHMgcC12YWx1ZQpiZWluZyBiZWxvdyAwLjA1LnByZXZpb3VzIChwLXZhbHVlID0gMi45N2UtMDUpOiBIaWdobHkgc2lnbmlmaWNhbnQgd2l0aAphIHZlcnkgbG93IHAtdmFsdWUuCgpUaGUgb3RoZXIgcHJlZGljdG9ycyBsaWtlIGJhbGFuY2UsIGVkdV8xLCBlZHVfMiwgYW5kIGVkdV8zIGFyZSBub3QKc2lnbmlmaWNhbnQgKHAtdmFsdWVzID4gMC4wNSksIHN1Z2dlc3RpbmcgdGhhdCB0aGV5IGRvIG5vdCBjb250cmlidXRlCm1lYW5pbmdmdWxseSB0byBwcmVkaWN0aW5nIHRoZSBvdXRjb21lLgoKMy4gIEhvdyB3b3VsZCB5b3UgaW50ZXJwcmV0IHRoZSBjb2VmZmljaWVudCBvZiBlZHVfMz10ZXJ0aWFyeT8KCioqKkFuc3dlcjoqKioKClRoZSBjb2VmZmljaWVudCBmb3IgZWR1XzMgKHRlcnRpYXJ5KSBpcyAwLjI2MTc2MSwgYnV0IGl0IGlzIG5vdApzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IChwLXZhbHVlID0gMC4zOTUxNCkuIFRoaXMgbWVhbnMgdGhhdCBoYXZpbmcgYQp0ZXJ0aWFyeSBlZHVjYXRpb24gZG9lcyBub3Qgc2lnbmlmaWNhbnRseSBhZmZlY3QgdGhlIGxpa2VsaWhvb2Qgb2YKc3Vic2NyaWJpbmcgdG8gYSB0ZXJtIGRlcG9zaXQgY29tcGFyZWQgdG8gdGhlIHJlZmVyZW5jZSBjYXRlZ29yeSAobGlrZWx5CiJwcmltYXJ5IiBlZHVjYXRpb24pLiBTaW5jZSBpdOKAmXMgbm90IHNpZ25pZmljYW50LCBpdCBkb2VzbuKAmXQgcHJvdmlkZQp1c2VmdWwgaW5mb3JtYXRpb24gZm9yIHByZWRpY3RpbmcgdGhlIHRhcmdldCB2YXJpYWJsZS4KCjQuICBIb3cgd291bGQgeW91IGV4cGxhaW4gcHJvcGVydGllcyBvZiBST0MgYW5kIEFVQyBlc3RpbWF0ZXMgeW91IGhhdmUKICAgIG9idGFpbmVkPwoKKioqQW5zd2VyOioqKgoKVGhlIFJPQyBjdXJ2ZSBpcyBhIGdyYXBoaWNhbCByZXByZXNlbnRhdGlvbiBvZiB0aGUgdHJhZGUtb2ZmIGJldHdlZW4gdGhlCnRydWUgcG9zaXRpdmUgcmF0ZSAoc2Vuc2l0aXZpdHkpIGFuZCBmYWxzZSBwb3NpdGl2ZSByYXRlICgxIC0Kc3BlY2lmaWNpdHkpLiBUaGUgQVVDIChBcmVhIFVuZGVyIHRoZSBDdXJ2ZSkgcXVhbnRpZmllcyB0aGUgb3ZlcmFsbAphYmlsaXR5IG9mIHRoZSBtb2RlbCB0byBkaXN0aW5ndWlzaCBiZXR3ZWVuIHRoZSB0d28gY2xhc3Nlcy4KCkFVQyByYW5nZXMgZnJvbSAwIHRvIDEsIHdoZXJlIEFVQyA9IDEgcmVwcmVzZW50cyBwZXJmZWN0IGNsYXNzaWZpY2F0aW9uCmFuZCBBVUMgPSAwLjVyZXByZXNlbnRzIGEgcmFuZG9tIGNsYXNzaWZpZXIuCgpJbiB0aGlzIGNhc2UsIHRoZSBBVUMgb2YgMC44OTM4NjQgc3VnZ2VzdHMgdGhhdCB0aGUgbW9kZWwgZG9lcyBhIGdvb2QKam9iIGF0IGRpc3Rpbmd1aXNoaW5nIGJldHdlZW4gdGhlICJ5ZXMiIGFuZCAibm8iIG91dGNvbWVzLCB0aG91Z2ggbm90CnBlcmZlY3RseS4KCjUuICBEbyB5b3Ugc2VlIGFueSBwcm9ibGVtcyB1c2luZyB0aGUgYWNjdXJhY3kgZXN0aW1hdGUgZm9yIHRoZSBsb2dpc3RpYwogICAgbW9kZWwgYmFzZWQgb24gdGhlIHByb2JhYmlsdHkgdGhyZXNob2xkIG9mIDAuNTA/CgoqKipBbnN3ZXI6KioqCgpZZXMsIHRoZXJlIGlzIGEgc2lnbmlmaWNhbnQgcHJvYmxlbSB3aXRoIHVzaW5nIGFjY3VyYWN5IGJhc2VkIG9uIGEgMC41MAp0aHJlc2hvbGQgZm9yIHRoZSBsb2dpc3RpYyBtb2RlbC4gVGhlIG1vZGVsIGhhcyBmYWlsZWQgdG8gcHJlZGljdCBhbnkKcG9zaXRpdmUgKCJ5ZXMiKSBjYXNlcyBpbiB0aGUgdGVzdCBzZXQsIHNvIHRoZSBhY2N1cmFjeSBlc3RpbWF0ZSAofjg5JSkKaXMgbWlzbGVhZGluZy4KClRoZSBoaWdoIGFjY3VyYWN5IGlzIHByaW1hcmlseSBkdWUgdG8gdGhlIG1vZGVsIGNvcnJlY3RseSBwcmVkaWN0aW5nCiJubyIgKHRoZSBtYWpvcml0eSBjbGFzcyksIGJ1dCBpdCBmYWlscyB0byBpZGVudGlmeSB0aGUgbWlub3JpdHkgY2xhc3MKKCJ5ZXMiKS4KClRoaXMgaXNzdWUgaXMgYSByZXN1bHQgb2YgY2xhc3MgaW1iYWxhbmNlLCB3aGVyZSB0aGUgbWFqb3JpdHkgY2xhc3MKZG9taW5hdGVzIHRoZSBwcmVkaWN0aW9ucywgbGVhZGluZyB0byBpbmZsYXRlZCBhY2N1cmFjeSBkZXNwaXRlIHBvb3IKcGVyZm9ybWFuY2Ugb24gdGhlIG1pbm9yaXR5IGNsYXNzLgo=