Motivation

Among the various machine learning techniques, decision trees have gained widespread use in the banking sector due to their high accuracy and the ability to translate complex statistical models into plain language and make the prediction of the model explainable.

In many countries, lending practices are closely monitored by government agencies to ensure fairness. This scrutiny requires bank executives to clearly articulate the reasons behind loan approvals or rejections, which is where decision trees excel. They offer a clear, interpretable rationale for each decision, which is crucial not only for regulatory compliance but also for providing customers with understandable explanations for their credit ratings.

Automated credit scoring models are likely employed in scenarios such as credit card mailings and instant online approval processes. In this lesson, we will develop a simple credit approval model in R using C5.0 decision trees. Additionally, we will explore how to fine-tune the model to minimize errors that improve the model and direct the bias of the model to reduce financial loss.

This lesson is an expansion of the tutorial in Chapter 5 of (Lantz, 2019).

Model Explainabiity

One of the reasons why decision trees remain a commonly used supervised machine learning algorithm, is it’s superior explainability and ease with which the decision process can be reproduced. Explainability in the context of predictive modeling and supervised machine learning refers to the extent to which the internal mechanics of a machine learning model and its decision-making processes can be understood by humans. It requires making the model’s operations transparent and its predictions interpretable, allowing stakeholders to comprehend how input features are transformed into output predictions.

Explainability is essential and desirable for several reasons:

  1. Trust and Adoption: When the workings of a model are transparent, users and stakeholders are more likely to trust and adopt the technology. Trust is particularly important in fields like healthcare, medicine, finance, banking, and criminal justice, where decisions have significant consequences and often deeply affect people’s lives.

  2. Regulatory Compliance: Many industries are subject to regulations that require clear justifications for decisions, especially those affecting individuals’ lives and finances. For instance, in banking, the Basel III framework and the General Data Protection Regulation (GDPR) necessitate explainability in credit scoring models to ensure fairness and accountability, as well as expose any potential covert bias.

  3. Debugging and Improvement: Explainable models make it easier to identify and rectify errors or biases in the model. Understanding why a model makes certain predictions can highlight areas for improvement and refinement, leading to better performance and more accurate results.

  4. Ethical Responsibility: Machine learning models can inadvertently perpetuate or exacerbate biases present in training data. Explainability helps in identifying such biases, enabling developers to take corrective actions to ensure ethical and fair outcomes.

  5. User Understanding and Engagement: When users understand how a model works, they are more likely to engage with it appropriately. For example, in medical diagnostics, explainability can help doctors understand the rationale behind a model’s prediction, allowing them to make more informed decisions about patient care.

  6. Decision Accountability: In many applications, it is essential to hold decision-makers accountable. Explainable models provide the necessary transparency to ensure that decisions can be scrutinized and justified, thereby maintaining accountability and integrity in automated decision-making systems.

In short, explainability bridges the gap between complex machine learning models and human understanding. It ensures that models are not only accurate but also transparent, fair, as free from bias as possible, and trustworthy, thereby fostering wider acceptance – and responsible use – of machine learning and applied artificial intelligence.

Decision Trees for Classification

Decision tree algorithms are useful for both classification and regression tasks, although we will focus on classification tasks. These algorithms work by recursively partitioning the data into subsets based on feature values, creating a tree-like model of decisions. Each internal node of the tree represents a conditional test on an attribute, each branch represents the outcome of the test, and each leaf node represents a class label or a regression value. The goal is to create a model that predicts the value of a target variable by learning simple decision rules inferred from the data features. Decision trees are preferable when features are a mix of categorical and numeric values. Unlike regression methods, decision trees do not rely on any particular distribution of values.

Overview of Decision Tree Algorithms

A decision tree starts with a root node that encompasses the entire dataset. The algorithm then selects the feature that best divides the data into distinct classes or values, based on a criterion like Information Gain or Gini Index. This process is repeated recursively for each child node until one of the stopping conditions is met, such as reaching a maximum depth, having a minimum number of samples per node, or achieving homogeneous nodes. The construction of decision trees on data sets with many observations and a high number of dimensions can be time consuming and take a long time.

Information Gain measures the reduction in entropy or uncertainty about the class labels after splitting the data based on an attribute, while the Gini Index measures the impurity or diversity of the split. It is calculated as the probability of misclassifying a randomly chosen element if it were labeled according to the distribution of labels in the subset. Some decision tree algorithm will evaluate the statistical significance of the association between categorical variables or attempt to reduce variance. The latter approach is often used in regression trees which works to minimize the variance of the target variable within each node.

To prevent overfitting, decision trees often use pruning techniques to remove branches that have little importance. Pruning can be done in a pre-pruning manner (stopping the tree growth early) or post-pruning (removing branches from a fully grown tree).

Common Decision Tree Algorithms

Several decision tree algorithms have been developed over the years, each with its unique approach to building and refining trees. Some of the most notable ones include:

  1. ID3 (Iterative Dichotomiser 3): Developed by Ross Quinlan, ID3 uses Information Gain as its splitting criterion. It builds a tree top-down, starting from a set of training instances and the features that describe them.

  2. C4.5 and C5.0: Also developed by Ross Quinlan, C4.5 is an extension of ID3. It uses Gain Ratio, a modification of Information Gain that reduces its bias towards features with many distinct values. C5.0 is a commercial version that is more efficient and provides better performance than C4.5.

  3. CART (Classification and Regression Trees): Introduced by Breiman et al., CART can be used for both classification and regression tasks. It uses the Gini Index for classification trees and reduction in variance for regression trees. CART produces binary trees, where each node has exactly two children.

  4. CHAID (Chi-squared Automatic Interaction Detector): This algorithm is used primarily for classification and relies on the Chi-Square test to determine the best splits. It is particularly effective for categorical data and handles multi-way splits.

  5. MARS (Multivariate Adaptive Regression Splines): Although not strictly a decision tree, MARS builds models by dividing the data into distinct regions using hinge functions. It is highly effective for regression tasks and can handle interactions between variables.

  6. Random Forest: An ensemble method that constructs multiple decision trees during training and outputs the mode of the classes for classification or the mean prediction for regression of the individual trees. It uses techniques like bagging and feature randomness to improve model accuracy and control overfitting.

  7. Gradient Boosted Trees: Another ensemble technique that builds trees sequentially. Each tree is constructed to correct the errors of the previous ones, focusing on the residual errors. Prominent algorithms in this category include XGBoost, LightGBM, and CatBoost.

  8. Oblique Decision Trees: These trees use linear combinations of features rather than a single feature for splitting nodes. This can lead to more compact and potentially more accurate trees but are computationally more intensive to build.

Decision tree algorithms offer a robust and interpretable approach to both classification and regression tasks. Their versatility and ease of understanding make them popular in various fields, from finance to healthcare. Each variant of decision tree algorithms brings unique strengths, and understanding these can help in selecting the most appropriate model for a given problem. This lesson will focus on the use of the C5.0 (aka C50) algorithm.

The C5.0 Algorithm

The C5.0 (aka C50) algorithm, developed by Ross Quinlan, is an advanced and commercially available version of the C4.5 algorithm. It is widely used for creating classification models due to its efficiency, accuracy, and interpretability/explainability. The C5.0 algorithm builds a decision tree by recursively splitting the data into subsets based on the attribute that provides the highest information gain ratio. This process continues until the algorithm meets a stopping criterion, such as reaching a specified depth or having too few samples to split further.

How C5.0 Works

  1. Data Preparation: Before building the tree, the C5.0 algorithm requires the data to be prepared, which involves handling missing values, converting categorical data to numeric where necessary, and optionally normalizing numeric data. In practice, C5.0 handles missing values quite well and doesn’t often necessitate the removal of outliers, imputing missing values, encoding categorical features, and normalize numeric features.

  2. Tree Construction: The algorithm starts with the entire dataset and evaluates each attribute to find the one that provides the highest information gain ratio. This attribute becomes the root node of the tree. Splitting the tree is based on the information gain ratio. The information gain ratio is a measure of how well an attribute splits the data into distinct classes. It is calculated as the ratio of information gain (reduction in entropy) to the intrinsic information (a measure of the potential information generated by splitting the dataset based on this attribute). After selecting the root node, the dataset is divided into subsets based on the possible values of the chosen attribute. The algorithm then repeats the process for each subset, selecting the best attribute for further splitting. This recursive process continues, creating branches and leaf nodes, until a stopping criterion is met.

  3. Pruning: To avoid overfitting, the algorithm may use pre-pruning techniques such as limiting the depth of the tree, requiring a minimum number of samples per leaf, or setting a threshold for the information gain ratio. Once the tree is fully grown, post-pruning can be applied to remove branches that contribute little to the overall predictive power of the model. This involves evaluating the performance of subtrees and collapsing those that do not significantly improve accuracy.

  4. Rule Extraction: The C5.0 algorithm can convert the decision tree into a set of IF-THEN rules, which are often easier to interpret and understand. Each path from the root to a leaf node corresponds to a rule.

  5. Boosting: One of the notable features of C5.0 is its ability to use boosting, an ensemble technique that combines multiple weak classifiers to form a strong classifier. In boosting, the algorithm iteratively builds multiple decision trees, each focusing on the errors made by the previous ones. The final model is a weighted vote of these individual trees.

  6. Handling Missing Values and Noise: The C5.0 algorithm has robust mechanisms for handling missing values and noisy data. It can work with incomplete datasets by using surrogate splits, which allow it to proceed with the next best attribute when the primary attribute is missing.

Advantages of C5.0

C5.0 is significantly faster than its predecessor, C4.5, both in terms of memory usage and processing time. It can handle large datasets more efficiently, making it suitable for real-world applications with substantial amounts of data. The boosting feature of C5.0 helps in improving the model’s accuracy by combining the strengths of multiple decision trees. This ensemble method reduces bias and variance, leading to better generalization on unseen data. Furthermore, decision trees produced by C5.0 are easy to understand and interpret. The conversion of trees into a set of rules further enhances their interpretability, making it easier for stakeholders to grasp the decision-making process.

C5.0 offers various parameters for fine-tuning the model, such as adjusting the confidence level for pruning, setting minimum sample sizes, and enabling boosting. This flexibility allows users to customize the model according to their specific needs.

Lastly, the algorithm’s ability to handle noisy data and missing values ensures that the model remains robust and reliable, even when the quality of the data is not perfect. The C5.0 algorithm stands out due to its efficiency, accuracy, and ease of interpretation. Its ability to handle large datasets, robust performance with noisy and incomplete data, and the option to use boosting make it a go-to algorithm for classification tasks. These advantages often make C5.0 preferable over other decision tree algorithms, particularly in applications where accuracy, speed, and interpretability are paramount.

Computational Intractability and Decision Trees

Building a perfect decision tree is an NP-complete problem and thus no polynomial algorithm exists, which means that the time it takes to build a perfect decision tree increases exponentially with the size of the training data. This complexity arises from the combinatorial nature of the decision tree construction process. The various algorithms, including C50, use heuristics to create a tree that works well in practice, is computationally tractable, but is suboptimal.

A decision tree is built by recursively splitting a dataset based on the values of its attributes to create branches until the dataset is perfectly classified or a stopping criterion is met. The goal is to find the optimal set of splits that results in the most accurate tree with the least complexity (in terms of depth and number of nodes). For any given dataset with \(n\) attributes, there is an exponential number of ways to partition the data and choose splits. Evaluating all possible trees to find the best one involves an exhaustive search through all these combinations, which grows exponentially with the size of the dataset and the number of attributes. The task of finding the optimal decision tree can be framed as a combinatorial optimization problem. Each possible split at each node represents a combinatorial choice, and the objective is to find the combination of choices that minimizes some cost function (e.g., misclassification rate, tree depth, etc.).

The problem of constructing an optimal decision tree can be reduced to a known NP-complete problem, such as the Boolean satisfiability problem (SAT). If one could solve the decision tree optimization problem efficiently, it would imply an efficient solution to all NP-complete problems, which is not believed to be possible.

Because constructing a perfect decision tree is NP-complete, it implies that there is no known polynomial-time algorithm to solve this problem for all instances. Therefore, heuristic methods and approximations, such as those employed by C50, are typically used in practice to construct decision trees. These methods aim to find a good (though not necessarily optimal) tree in a reasonable amount of time that produces acceptable results in practice. Among the common heuristic methods are the use of a greedy approach which makes the best split decision at each step based on a certain criterion (e.g., information gain or Gini index). While this does not guarantee an optimal tree, it often results in a good and computationally feasible solution. To avoid overfitting and improve generalization, decision tree algorithms incorporate pruning methods, both pre-pruning (limiting the growth of the tree during construction) and post-pruning (removing branches from a fully grown tree). In addition, techniques such as bagging and boosting (e.g., Random Forests or Gradient Boosted Trees) build multiple decision trees on different sets of training data and combine their predictions to improve accuracy and robustness. In combination, these methods mitigate the suboptimality of individual trees by leveraging the strengths of multiple models and practical heuristics.

Worked Example: Credit Decision Making

The objective of our predictive model is to pinpoint factors associated with an increased risk of loan default. This should eventually assist lenders in making un-biased credit decisions or to automate the credit approval process. Achieving this requires access to extensive data on previous bank loans (a word of caution: the data may contain inherent bias which would then propagate to the model), coupled with detailed information about the applicants.

This worked example uses the dataset contributed to the UCI Machine Learning Repository by Hans Hofmann of the University of Hamburg (Hofmann, 1994). This dataset includes comprehensive information on loans sourced from a credit agency in Germany, so the applicability of any model might be limited to applicants from Germany – we need to be cautious in generalizing models as they are based on specific training data.

URL for data set: https://archive.ics.uci.edu/dataset/144/statlog+german+credit+data

The credit dataset is comprised of 1,000 loan decisions made by loan officers, along with a set of numeric and nominal features that describe the characteristics of both the loans and the applicants. A (binary) class variable specifies whether each loan resulted in default. Our goal is to analyze this data to uncover patterns that might be able to predict loan defaults and thus assist a loan officer is making a credit decision, i.e., an applicant who is likely going to default might not be a good credit candidate or, alternatively, would require adjusted lending parameters such as a substantially higher interest rate and a shorter repayment period.

Workflow

Set up your own project in R Studio and create a new R Notebook in which to do your work and follow along. Experiment with the code, try additional techniques. The key is to type the code yourself and not simply copy and paste. You learn by doing!

Data Loading

We will load the data set directly from its URL using the utils package, although downloading to a project folder might be best when inspecting the data set. The code below also sets column headers as the data set does not have those. A data dictionary is provided at the above URL.

library(utils)

# URL of the dataset
url <- "https://archive.ics.uci.edu/ml/machine-learning-databases/statlog/german/german.data"

# define the column names for the dataset (as the dataset does not contain headers)
col_names <- c("Status.of.existing.checking.account", "Duration.in.month", "Credit.history",
               "Purpose", "Credit.amount", "Savings.account.bonds", "Present.employment.since",
               "Installment.rate.in.percentage.of.disposable.income", "Personal.status.and.sex",
               "Other.debtors.guarantors", "Present.residence.since", "Property", "Age.in.years",
               "Other.installment.plans", "Housing", "Number.of.existing.credits.at.this.bank",
               "Job", "Number.of.people.being.liable.to.provide.maintenance.for", "Telephone",
               "Foreign.worker", "Class")

# fetch and read the dataset
credit.df <- read.csv(url, 
                      header = FALSE, sep = " ", 
                      col.names = col_names)

# Display the first few rows of the dataset to ensure it was loaded
head(credit.df[,c(2,5,8,11,21)], 4)
##   Duration.in.month Credit.amount
## 1                 6          1169
## 2                48          5951
## 3                12          2096
## 4                42          7882
##   Installment.rate.in.percentage.of.disposable.income Present.residence.since
## 1                                                   4                       4
## 2                                                   2                       2
## 3                                                   2                       3
## 4                                                   2                       4
##   Class
## 1     1
## 2     2
## 3     1
## 4     1

Data Inspection

d <- ncol(credit.df)
n <- nrow(credit.df)

The data set contains 1000 rows (observations/cases) and 21 dimensions (features). The loan amounts in are DM (as this data set was collected prior to Germany adopting the Euro). The “target variable” is the column Class. A “1” indicates that the borrower did not default while a “2” indicates that they did default.

t <- table(credit.df$Class)
print(t)
## 
##   1   2 
## 700 300

In your own code, inspect additional features. Which ones are numeric? Which ones are categorical? Which features might be likely predictors?

Data Preparation

The first step is to revise the encoding of the “Class” target variable to “yes” (default) and “no” (no default) rather than the values 2 and 1. This is purely cosmetic and for readability and has no impact on the model construction.

credit.df$Class <- ifelse(credit.df$Class == 1, "no", "yes")

There are 700 classified as “no” out of 1000 credit transactions, making the overall default rate 30%.

Sampling Training Data

We will train the model on a random sample of the cases in the data set, leaving the remainder for model validation. A random sample involves selecting a subset of records randomly from a larger dataset. In R, this can be accomplished using the sample() function or the runif() function to generate a sequence of random numbers following a uniform distribution. There are additional functions that produce random numbers that follow other distributions such as the Normal (Gaussian) Distribution.

It is a common practice to set a seed value beforehand using the set.seed() function. Setting a seed ensures that the randomization process follows a reproducible sequence. While this might seem counter-intuitive for generating random numbers, it serves an important purpose. By specifying a seed value, the analysis can be repeated in the future with identical results, ensuring consistency and reproducibility in the findings.

The code below generates a random sample (without replacement, i.e., any observation appears once in either the training data or the validation data, but never in both and never more than once in either). We are not specifying a probability distribution, so every observation has the same chance of appearing in the training data. We are selecting 90% of the data for training and the rest for validation.

Note that your sample might be different if you are using a different version of R that was used when generating this lesson; this will also affect the results for the remainder of the lesson. However, the analysis and the model are just as valid.

set.seed(617)

n <- nrow(credit.df)
train.data <- sample(n, n * 0.9, replace = FALSE)

train.df <- credit.df[train.data,]
val.df <- credit.df[-train.data,]

If the selection was done at random, we would expect the same proportion of defaults to be appear in the training and the validation data sets – and they should be about 30% as that was the proportion of defaults in the original data set.

Training Data Set: Proportion of defaults

prop.table(table(train.df$Class))
## 
##        no       yes 
## 0.7022222 0.2977778

Validation Data Set: Proportion of defaults

prop.table(table(val.df$Class))
## 
##   no  yes 
## 0.68 0.32

Both the training and test datasets exhibit similar proportions of loan defaults, allowing us to proceed with building our decision tree. If the proportions had differed significantly, we might have needed to resample the dataset or employ a more sophisticated sampling approach to ensure balanced representation, such as a stratified sample or using k-fold cross validation rather than a dedicated validation data set.

A common problem of random sampling is class imbalance. What if some level of the target variable only appears a few times in the data set and the random sample is such that all instances occur in only the validation data – then the model will never be trained on those. This will often cause an error when making a prediction. In those situations, startified sampling or sampling with replacement (oversampling) may be better.

Random Number Generation

Let’s take a short detour on random numbers in computers, as random selection is common in many machine learning algorithms. The question is: how do computers generate random numbers?

Computers generate random numbers using algorithms called pseudo-random number generators (PRNGs). While these numbers are not truly random, they are sufficient for most practical purposes because they exhibit properties of randomness and cannot be predicted unless one knows the algorithm and the starting conditions of the algorithm.

You may (safely) skip the remainder of this section if you are not interested in how “random number generation” works.

A PRNG starts with an initial value called a seed. The seed can be any number, but it’s often derived from a variable source such as the current time or perhaps some external sensor that detects some natural phenomenon that is unpredictable. Once set, the seed allows the PRNG to produce a reproducible sequence of numbers that appears to be random. Naturally, the PRNG uses a deterministic algorithm to generate a sequence of numbers, so the numbers are really random but exhibit the properties of random numbers. Common algorithms include the Linear Congruential Generator (LCG), the Mersenne Twister, and the Xorshift algorithm.

Linear Congruential Generator (LCG):

An LCG produces numbers based on the formula:

\[ X_{n+1} = (aX_n + c) \mod m \]

where:

  • \(X\) is the sequence of pseudorandom values,
  • \(a\), \(c\), and \(m\) are constants,
  • \(X_0\) is the initial seed.

Mersenne Twister:

The Mersenne Twister is widely used because of its long period and high-quality randomness. It generates numbers with a period of \(2^{19937} - 1\), ensuring that the sequence doesn’t repeat for a very long time.

Xorshift:

Xorshift algorithms use bitwise operations to generate random numbers. These are known for being fast and having good statistical properties.

For applications requiring true randomness, such as cryptography, pseudorandom numbers may not be sufficient. Instead, True Random Number Generators (TRNGs) are used. TRNGs generate numbers based on physical processes and natural phenomena that are inherently random, such as: measuring the thermal noise in electronic circuits, monitoring the decay of radioactive materials, or observing the behavior of photons passing through a semi-transparent mirror. TRNGs are generally slower, require an external sensor, and are thus more expensive than PRNGs but provide true randomness.

Model Training

We will employ the C5.0 algorithm from the C50 package to train our decision tree model. If you haven’t already installed the package, you can do so by running install.packages("C50") or using the interface of R Studio. After installation, load the package into your R session with library(C50). The code below checks of the package is installed, and, if not, will install the package.

package_name <- "C50"

if (!require(package_name, 
             character.only = T, 
             warn.conflicts = F,
             quietly = T)) {
    install.packages(package_name, dependencies = TRUE)
}

library(C50)

The model is built using the function C5.0(). This function has five key arguments:

  • x: a data frame of the training features
  • y: a factor vector with two or more levels of the corresponding target classes
  • trials: an optional argument indicating the number of boosting iterations that are applied (default is 1)
  • costs: a matrix of costs associated with possible errors; the matrix must have C columns and rows where C is the number of levels in the factor vector for y
  • rules: a logical argument indicating whether the tree should be decomposed into rules

We are first converting the target variable from “character” to a factor as that is what the C5.0() function expects. Of course, it may have been better to have done the conversion before splitting.

train.df$Class <- as.factor(train.df$Class)
val.df$Class <- as.factor(val.df$Class)

Next, we are training a decision tree model using the C5.0 algorithm on all features,except for column 21 which is the target variable. Be sure not to include the column of the target variable in the training features as that would certainly cause overfitting.

credit.model <- C5.0(train.df[-21], train.df$Class, trials = 1, rules = F)

The “decision tree” model is now available for prediction. We can view the model’s characteristics by printing it out:

print(credit.model)
## 
## Call:
## C5.0.default(x = train.df[-21], y = train.df$Class, trials = 1, rules = F)
## 
## Classification Tree
## Number of samples: 900 
## Number of predictors: 20 
## 
## Tree size: 46 
## 
## Non-standard options: attempt to group attributes

The actual “tree” can be visualized - in a rudimentary fashion – using the summary() function. Only a partial output of the model is shown.

summary(credit.model)
C5.0 [Release 2.07 GPL Edition]     Sun Jun 23 13:00:32 2024
-------------------------------

Class specified by attribute `outcome'

Read 900 cases (21 attributes) from undefined.data

Decision tree:

Status.of.existing.checking.account in {A14,A13}: no (416/56)
Status.of.existing.checking.account in {A12,A11}:
:...Credit.history in {A30,A31}:
    :...Housing = A151: yes (15)
    :   Housing in {A152,A153}:
    :   :...Other.debtors.guarantors in {A102,A103}: no (3)
    :       Other.debtors.guarantors = A101:
    :       :...Housing = A153: yes (11/1)
    :           Housing = A152:
    
    ...

In terms of decision tree construction with C5.0, the numbers in parentheses in the summary above indicate the number of examples that meet the criteria for that decision and the number incorrectly classified by the decision. For instance, on the first line, 416/56 means that out of the 416 examples reaching that decision point, 56 were incorrectly classified as “not likely to default.” This indicates that 56 applicants actually defaulted despite the model’s prediction to the contrary.

Occasionally, a decision tree produces decision sequences that may seem (or are) illogical. For example, it might suggest that an applicant with a very good credit history is likely to default, while those whose checking balance is unknown are not likely to default. These contradictory rules can occur and may either reflect real patterns in the data or be statistical anomalies. It is essential to investigate such unusual branches to determine whether the tree’s logic is valid and is reasonable for the business domain; naturally, this requires business subject matter expertise.

Model Evaluation

While the summary() function outputs a “confusion matrix”, we can generate one directly as well by using the decision tree model to predict the target variable for all of the cases in the validation data set where we know the actual outcome – the confusion matrix then compares the predicted output to the actual observed output.

In the code below, we use the function predict() to make predictions using the features of the validation data set (minus the target column “Class” which is column 21).

credit.preds.val <- predict(credit.model, val.df[-21])

We can now create a confusion matrix using the function confusionMatrix() from the caret package, comparing the predicted labels against the actual observed labels for the validation data set.

package_name <- "caret"

if (!require(package_name, 
             character.only = T, 
             warn.conflicts = F,
             quietly = T)) {
    install.packages(package_name, dependencies = TRUE)
}

library(caret)
conf.matrix.c50 <- confusionMatrix(credit.preds.val, val.df$Class)
print(conf.matrix.c50)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction no yes
##        no  59  16
##        yes  9  16
##                                           
##                Accuracy : 0.75            
##                  95% CI : (0.6534, 0.8312)
##     No Information Rate : 0.68            
##     P-Value [Acc > NIR] : 0.07961         
##                                           
##                   Kappa : 0.3902          
##                                           
##  Mcnemar's Test P-Value : 0.23014         
##                                           
##             Sensitivity : 0.8676          
##             Specificity : 0.5000          
##          Pos Pred Value : 0.7867          
##          Neg Pred Value : 0.6400          
##              Prevalence : 0.6800          
##          Detection Rate : 0.5900          
##    Detection Prevalence : 0.7500          
##       Balanced Accuracy : 0.6838          
##                                           
##        'Positive' Class : no              
## 

Alternatively, we could use the function `CrossTable() from the gmodels package. As always, we need to check first if the package is installed, and, if not, install it before loading the package.

package_name <- "gmodels"

if (!require(package_name, 
             character.only = T, 
             warn.conflicts = F,
             quietly = T)) {
    install.packages(package_name, dependencies = TRUE)
}

library(gmodels)
CrossTable(val.df$Class, credit.preds.val,
             prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE,
             dnn = c('actual default', 'predicted default'))
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  100 
## 
##  
##                | predicted default 
## actual default |        no |       yes | Row Total | 
## ---------------|-----------|-----------|-----------|
##             no |        59 |         9 |        68 | 
##                |     0.590 |     0.090 |           | 
## ---------------|-----------|-----------|-----------|
##            yes |        16 |        16 |        32 | 
##                |     0.160 |     0.160 |           | 
## ---------------|-----------|-----------|-----------|
##   Column Total |        75 |        25 |       100 | 
## ---------------|-----------|-----------|-----------|
## 
## 

From the above we can infer that out of the 100 loan applications in the validation data set, our C5.0 decision tree model correctly predicted that 57 did not default and 16 did default, resulting in an accuracy of 73% (57 + 16 / 100) and an error rate of 27% where the model incorrectly predicted the actual observed outcome. While this is a bit worse than expected based on the 30% observed default rate in the training data, it is not unexpected, since a model’s actual performance against unseen data is almost always worse than expected.

It is noteworthy that the model predicted only 16 out of the actual 28 defaults correctly, which amounts to a correct prediction of loan defaults of 57% – only a bit better than a random guess. That is not very encouraging and it is worth investigating to see if a better model can be built that is more “biased” towards correct predictions of defaults as that is of more business interest.

Model Improvement

Once an initial model is trained and evaluated, we can tune model parameters or try out engineered features to see if the model’s accuracy, F1-Score, precision, or recall can be improved to better suit the business use cases. One clear issue is that we have a relatively small training data set and the random sampling into training and validation can have a significant effect on the decision tree that is generated.

A common strategy for C5.0is boosting. Boosting is an ensemble learning technique that aims to improve the accuracy of a predictive model by combining the strengths of multiple weak learners. In the context of decision trees, boosting involves creating a series of decision trees, where each subsequent tree attempts to correct the errors made by the previous ones. The goal is to convert a collection of weak learners (trees that are only slightly better than random guessing) into a strong learner (a highly accurate model).

The process of building a boosted model begins by initializing the model with a single weak learner, typically a small decision tree. The initial model predicts the target variable based on the training data. Boosting proceeds iteratively, adding one tree at a time. Each new tree is trained to predict the residual errors (the differences between the actual values and the predictions made by the current model) from the previous iteration. During each iteration, boosting adjusts the weights of the training examples. Examples that were incorrectly predicted by the previous trees are given higher weights, making them more important in the next iteration. This ensures that the new tree focuses more on the hard-to-predict cases. The final boosted model is a weighted sum of all the individual trees. Each tree’s contribution to the final prediction is weighted based on its accuracy. This combination helps to smooth out individual errors and improve the overall model performance.

Key Variants of Boosting

  1. AdaBoost (Adaptive Boosting): AdaBoost is one of the earliest and most well-known boosting algorithms. It works by adjusting the weights of incorrectly classified instances so that subsequent classifiers focus more on difficult cases. AdaBoost combines the predictions of the weak learners using a weighted majority vote.

  2. Gradient Boosting: Gradient Boosting builds trees sequentially, where each tree is trained to reduce the residual errors of the previous tree using a gradient descent-like procedure. It generalizes AdaBoost by optimizing any differentiable loss function.

  3. XGBoost (Extreme Gradient Boosting): XGBoost is an efficient and scalable implementation of gradient boosting that includes several optimizations, such as regularization techniques, to prevent overfitting. It is highly effective in practice and has become a popular choice in many machine learning competitions.

  4. LightGBM and CatBoost: LightGBM (Light Gradient Boosting Machine) and CatBoost (Categorical Boosting) are other advanced implementations of gradient boosting. LightGBM is designed for efficiency and scalability, while CatBoost is optimized for categorical data and handles high-cardinality features well.

Advantages of Boosting

  1. Improved Accuracy: Boosting significantly improves the accuracy of the model by focusing on difficult-to-predict instances and reducing bias and variance.

  2. Robustness: By combining multiple weak learners, boosting creates a robust model that is less prone to overfitting compared to a single complex model.

  3. Flexibility: Boosting can be applied to various types of weak learners, although decision trees are commonly used due to their simplicity and interpretability.

  4. Handling Imbalanced Data: Boosting can be particularly effective in handling imbalanced datasets, as it focuses on hard-to-predict cases which often belong to the minority class.

The C5.0 algorithm in the C50 package supports boosting. Below is an example that uses boosting with C5.0 to train a decision tree model – the key parameter is trials set of a value greater than 1 (the default):

credit.boosted.model <- C5.0(x = train.df[-21], 
                             y = train.df$Class, 
                             data = train_data, 
                             trials = 10)

Let’s see if the boosted model performs better:

credit.preds.boosted.val <- predict(credit.boosted.model, val.df[-21])
conf.matrix.boosted <- confusionMatrix(credit.preds.boosted.val, val.df$Class)
print(conf.matrix.boosted)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction no yes
##        no  60  15
##        yes  8  17
##                                           
##                Accuracy : 0.77            
##                  95% CI : (0.6751, 0.8483)
##     No Information Rate : 0.68            
##     P-Value [Acc > NIR] : 0.03154         
##                                           
##                   Kappa : 0.439           
##                                           
##  Mcnemar's Test P-Value : 0.21090         
##                                           
##             Sensitivity : 0.8824          
##             Specificity : 0.5312          
##          Pos Pred Value : 0.8000          
##          Neg Pred Value : 0.6800          
##              Prevalence : 0.6800          
##          Detection Rate : 0.6000          
##    Detection Prevalence : 0.7500          
##       Balanced Accuracy : 0.7068          
##                                           
##        'Positive' Class : no              
## 

And, once again, let’s build a prediction table:

CrossTable(val.df$Class, credit.preds.boosted.val,
             prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE,
             dnn = c('actual default', 'predicted default'))
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  100 
## 
##  
##                | predicted default 
## actual default |        no |       yes | Row Total | 
## ---------------|-----------|-----------|-----------|
##             no |        60 |         8 |        68 | 
##                |     0.600 |     0.080 |           | 
## ---------------|-----------|-----------|-----------|
##            yes |        15 |        17 |        32 | 
##                |     0.150 |     0.170 |           | 
## ---------------|-----------|-----------|-----------|
##   Column Total |        75 |        25 |       100 | 
## ---------------|-----------|-----------|-----------|
## 
## 

The boosted model made correct predictions at the rate of 78% which is a slight improvement. However, the rate of correctly predicting a default increased to 68% (19/28) – a more significant improvement.

Given the apparent ease of adding boosting, one might wonder why it isn’t applied by default to every decision tree. There are two important reasons for not doing so by default. For one, if building a single decision tree requires substantial computational resources (it is after all a computationally complex algorithm), constructing multiple trees for boosting can become computationally impractical – it would just take too long to train a boosted model and during the training time, new training may have already been generated if data velocity is high. In addition, in cases where the training data is very noisy, boosting may not yield any improvement and could even exacerbate the noise. Nevertheless, when higher accuracy is important, it is worth experimenting with boosting to see if it provides the desired enhancement in performance, perhaps experimenting on a smaller random sample of the data.

Nevertheless, boosting is a useful (and commonly applied) technique that can significantly improve the performance of decision trees by iteratively focusing on hard-to-predict instances and combining multiple weak learners. This generally results in a robust and accurate model, making boosting a popular choice in various machine learning tasks.

Directing Bias

Some incorrect classification are more impactful than others. For instance, giving a loan to someone who is likely going to default is more consequential than not giving a loan to someone who is not likely to default. In other words, false negatives are better than false positive. Data scientists need to introduce bias so that some errors are avoided.

The C5.0 algorithm offers the opportunity to assign different penalties (costs) to various types of classification errors, thereby discouraging the model from making more costly mistakes – in other words, “biasing” the model. This is achieved through the use of a cost matrix, which details the relative cost of each type of error. By specifying these costs, we can guide the algorithm to prioritize minimizing the most expensive errors, improving the model’s performance in contexts where some mistakes are more critical than others.

A cost matrix for a binary classifier is a 2x2 matrix. The code below demonstrates how to construct a cost matrix.

matrix.dims <- list(c("no", "yes"), c("no", "yes"))
names(matrix.dims) <- c("predicted", "actual")

Next, we need to define the cost (i.e., the penalty) for each type of error. Since R populates a matrix by filling columns from top to bottom, we must provide the values in this order given that we have “predicted” in column one and “actual” on column two:

  1. Predicted no, actual no
  2. Predicted yes, actual no
  3. Predicted no, actual yes
  4. Predicted yes, actual yes

The costs (or penalties) are relative. A cost of 0 means that there is no cost. Let’s fill the matrix with some cost values; the cost matrix must mimic the dimension matrix defined prior:

cost.matrix <- matrix(c(0, 1, 4, 0), 
                      nrow = 2,
                      dimnames = matrix.dims)

Here is what the cost matrix contains:

print(cost.matrix)
##          actual
## predicted no yes
##       no   0   4
##       yes  1   0

For instance, the cost of a default when none was predicted is four times the cost of predicting a default when there was none, so a false negative has four times the impact of a false positive. Naturally, the correct predictions carry a cost of 0 as we do not want to discourage those.

Now that we have a cost matrix, we can train a new C5.0 decision tree model with boosting that also takes those costs into account when building the tree.

credit.boosted.costs.model <- C5.0(x = train.df[-21], 
                                   y = train.df$Class, 
                                   data = train_data, 
                                   trials = 10,
                                   costs = cost.matrix)

Let’s see if the boosted model with costs performs better:

credit.preds.boosted.costs.val <- predict(credit.boosted.costs.model, 
                                          val.df[-21])

And, once again, let’s build a prediction table:

CrossTable(val.df$Class, credit.preds.boosted.costs.val,
             prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE,
             dnn = c('actual default', 'predicted default'))
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  100 
## 
##  
##                | predicted default 
## actual default |        no |       yes | Row Total | 
## ---------------|-----------|-----------|-----------|
##             no |        60 |         8 |        68 | 
##                |     0.600 |     0.080 |           | 
## ---------------|-----------|-----------|-----------|
##            yes |        21 |        11 |        32 | 
##                |     0.210 |     0.110 |           | 
## ---------------|-----------|-----------|-----------|
##   Column Total |        81 |        19 |       100 | 
## ---------------|-----------|-----------|-----------|
## 
## 

The accuracy (correct predictions, i.e., true positives and true negatives) is now 79% which is about the same as before, although a bit better. However, false positive are now at 6% while false negatives are at 15%. While the accuracy might be lower for a boosted model versus one that is boosted with costs, the types of errors are more amenable to business interests.

Summary

In this lesson, we discussed the construction process and common enhancements of decision trees using the C5.0 algorithm. Decision trees result powerful models for classification tasks that are interpretable. Their performance can be further improved through boosting, which involves combining multiple trees to reduce errors and increase accuracy. However, boosting may not always be practical due to computational demands and sensitivity to noisy data.

We also explored the concept of a cost matrix in the C5.0 algorithm, which allows assigning different penalties to various types of classification errors. This helps prioritize the minimization of more costly mistakes, thereby tailoring the model’s performance to specific business needs.


Files & Resources

All Files for Lesson 3.435

References

Hofmann, Hans. (1994). Statlog (German Credit Data). UCI Machine Learning Repository. https://doi.org/10.24432/C5NC77.

Lantz, Brent. (2019). Machine learning with R: Expert Techniques for Predictive Modeling. Packt Publishing Ltd.

Errata

None collected yet. Let us know.

LS0tCnRpdGxlOiAiRGVjaXNpb24gVHJlZXM6IEEgV29ya2VkIEV4YW1wbGUgaW4gUiIKcGFyYW1zOgogIGNhdGVnb3J5OiAzCiAgc3RhY2tzOiAwCiAgbnVtYmVyOiA0MzUKICB0aW1lOiA5MAogIGxldmVsOiBiZWdpbm5lcgogIHRhZ3M6IG5haXZlIGJheWVzLGJheWVzLG1hY2hpbmUgbGVhcm5pbmcsY2xhc3NpZmljYXRpb24sc3BhbSBkZXRlY3Rpb24KICBkZXNjcmlwdGlvbjogIldvcmtlZCBleGFtcGxlIGZvciBwcmVkaWN0aW5nIGNyZWRpdCBkZWZhdWx0IHVzaW5nCiAgICAgICAgICAgICAgICB0aGUgQzUuMCBkZWNpc2lvbiB0cmVlIGFsZ29yaXRobSBpbiBSLiIKZGF0ZTogIjxzbWFsbD5gciBTeXMuRGF0ZSgpYDwvc21hbGw+IgphdXRob3I6ICI8c21hbGw+TWFydGluIFNjaGVkbGJhdWVyPC9zbWFsbD4iCmVtYWlsOiAibS5zY2hlZGxiYXVlckBuZXUuZWR1IgphZmZpbGl0YXRpb246ICJOb3J0aGVhc3Rlcm4gVW5pdmVyc2l0eSIKb3V0cHV0OiAKICBib29rZG93bjo6aHRtbF9kb2N1bWVudDI6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29sbGFwc2VkOiBmYWxzZQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgdGhlbWU6IHNwYWNlbGFiCiAgICBoaWdobGlnaHQ6IHRhbmdvCi0tLQoKLS0tCnRpdGxlOiAiPHNtYWxsPmByIHBhcmFtcyRjYXRlZ29yeWAuYHIgcGFyYW1zJG51bWJlcmA8L3NtYWxsPjxici8+PHNwYW4gc3R5bGU9J2NvbG9yOiAjMkU0MDUzOyBmb250LXNpemU6IDAuOWVtJz5gciBybWFya2Rvd246Om1ldGFkYXRhJHRpdGxlYDwvc3Bhbj4iCi0tLQoKYGBge3IgY29kZT14ZnVuOjpyZWFkX3V0ZjgocGFzdGUwKGhlcmU6OmhlcmUoKSwnL1IvX2luc2VydDJEQi5SJykpLCBpbmNsdWRlID0gRkFMU0V9CmBgYAoKIyMgTW90aXZhdGlvbgoKQW1vbmcgdGhlIHZhcmlvdXMgbWFjaGluZSBsZWFybmluZyB0ZWNobmlxdWVzLCBkZWNpc2lvbiB0cmVlcyBoYXZlIGdhaW5lZCB3aWRlc3ByZWFkIHVzZSBpbiB0aGUgYmFua2luZyBzZWN0b3IgZHVlIHRvIHRoZWlyIGhpZ2ggYWNjdXJhY3kgYW5kIHRoZSBhYmlsaXR5IHRvIHRyYW5zbGF0ZSBjb21wbGV4IHN0YXRpc3RpY2FsIG1vZGVscyBpbnRvIHBsYWluIGxhbmd1YWdlIGFuZCBtYWtlIHRoZSBwcmVkaWN0aW9uIG9mIHRoZSBtb2RlbCBleHBsYWluYWJsZS4KCkluIG1hbnkgY291bnRyaWVzLCBsZW5kaW5nIHByYWN0aWNlcyBhcmUgY2xvc2VseSBtb25pdG9yZWQgYnkgZ292ZXJubWVudCBhZ2VuY2llcyB0byBlbnN1cmUgZmFpcm5lc3MuIFRoaXMgc2NydXRpbnkgcmVxdWlyZXMgYmFuayBleGVjdXRpdmVzIHRvIGNsZWFybHkgYXJ0aWN1bGF0ZSB0aGUgcmVhc29ucyBiZWhpbmQgbG9hbiBhcHByb3ZhbHMgb3IgcmVqZWN0aW9ucywgd2hpY2ggaXMgd2hlcmUgZGVjaXNpb24gdHJlZXMgZXhjZWwuIFRoZXkgb2ZmZXIgYSBjbGVhciwgaW50ZXJwcmV0YWJsZSByYXRpb25hbGUgZm9yIGVhY2ggZGVjaXNpb24sIHdoaWNoIGlzIGNydWNpYWwgbm90IG9ubHkgZm9yIHJlZ3VsYXRvcnkgY29tcGxpYW5jZSBidXQgYWxzbyBmb3IgcHJvdmlkaW5nIGN1c3RvbWVycyB3aXRoIHVuZGVyc3RhbmRhYmxlIGV4cGxhbmF0aW9ucyBmb3IgdGhlaXIgY3JlZGl0IHJhdGluZ3MuCgpBdXRvbWF0ZWQgY3JlZGl0IHNjb3JpbmcgbW9kZWxzIGFyZSBsaWtlbHkgZW1wbG95ZWQgaW4gc2NlbmFyaW9zIHN1Y2ggYXMgY3JlZGl0IGNhcmQgbWFpbGluZ3MgYW5kIGluc3RhbnQgb25saW5lIGFwcHJvdmFsIHByb2Nlc3Nlcy4gSW4gdGhpcyBsZXNzb24sIHdlIHdpbGwgZGV2ZWxvcCBhIHNpbXBsZSBjcmVkaXQgYXBwcm92YWwgbW9kZWwgaW4gUiB1c2luZyBDNS4wIGRlY2lzaW9uIHRyZWVzLiBBZGRpdGlvbmFsbHksIHdlIHdpbGwgZXhwbG9yZSBob3cgdG8gZmluZS10dW5lIHRoZSBtb2RlbCB0byBtaW5pbWl6ZSBlcnJvcnMgdGhhdCBpbXByb3ZlIHRoZSBtb2RlbCBhbmQgZGlyZWN0IHRoZSBiaWFzIG9mIHRoZSBtb2RlbCB0byByZWR1Y2UgZmluYW5jaWFsIGxvc3MuCgo+IFRoaXMgbGVzc29uIGlzIGFuIGV4cGFuc2lvbiBvZiB0aGUgdHV0b3JpYWwgaW4gQ2hhcHRlciA1IG9mIChMYW50eiwgMjAxOSkuCgojIyBNb2RlbCBFeHBsYWluYWJpaXR5CgpPbmUgb2YgdGhlIHJlYXNvbnMgd2h5IGRlY2lzaW9uIHRyZWVzIHJlbWFpbiBhIGNvbW1vbmx5IHVzZWQgc3VwZXJ2aXNlZCBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobSwgaXMgaXQncyBzdXBlcmlvciBleHBsYWluYWJpbGl0eSBhbmQgZWFzZSB3aXRoIHdoaWNoIHRoZSBkZWNpc2lvbiBwcm9jZXNzIGNhbiBiZSByZXByb2R1Y2VkLiBFeHBsYWluYWJpbGl0eSBpbiB0aGUgY29udGV4dCBvZiBwcmVkaWN0aXZlIG1vZGVsaW5nIGFuZCBzdXBlcnZpc2VkIG1hY2hpbmUgbGVhcm5pbmcgcmVmZXJzIHRvIHRoZSBleHRlbnQgdG8gd2hpY2ggdGhlIGludGVybmFsIG1lY2hhbmljcyBvZiBhIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWwgYW5kIGl0cyBkZWNpc2lvbi1tYWtpbmcgcHJvY2Vzc2VzIGNhbiBiZSB1bmRlcnN0b29kIGJ5IGh1bWFucy4gSXQgcmVxdWlyZXMgbWFraW5nIHRoZSBtb2RlbOKAmXMgb3BlcmF0aW9ucyB0cmFuc3BhcmVudCBhbmQgaXRzIHByZWRpY3Rpb25zIGludGVycHJldGFibGUsIGFsbG93aW5nIHN0YWtlaG9sZGVycyB0byBjb21wcmVoZW5kIGhvdyBpbnB1dCBmZWF0dXJlcyBhcmUgdHJhbnNmb3JtZWQgaW50byBvdXRwdXQgcHJlZGljdGlvbnMuCgpFeHBsYWluYWJpbGl0eSBpcyBlc3NlbnRpYWwgYW5kIGRlc2lyYWJsZSBmb3Igc2V2ZXJhbCByZWFzb25zOgoKMS4gICoqVHJ1c3QgYW5kIEFkb3B0aW9uKio6IFdoZW4gdGhlIHdvcmtpbmdzIG9mIGEgbW9kZWwgYXJlIHRyYW5zcGFyZW50LCB1c2VycyBhbmQgc3Rha2Vob2xkZXJzIGFyZSBtb3JlIGxpa2VseSB0byB0cnVzdCBhbmQgYWRvcHQgdGhlIHRlY2hub2xvZ3kuIFRydXN0IGlzIHBhcnRpY3VsYXJseSBpbXBvcnRhbnQgaW4gZmllbGRzIGxpa2UgaGVhbHRoY2FyZSwgbWVkaWNpbmUsIGZpbmFuY2UsIGJhbmtpbmcsIGFuZCBjcmltaW5hbCBqdXN0aWNlLCB3aGVyZSBkZWNpc2lvbnMgaGF2ZSBzaWduaWZpY2FudCBjb25zZXF1ZW5jZXMgYW5kIG9mdGVuIGRlZXBseSBhZmZlY3QgcGVvcGxlJ3MgbGl2ZXMuCgoyLiAgKipSZWd1bGF0b3J5IENvbXBsaWFuY2UqKjogTWFueSBpbmR1c3RyaWVzIGFyZSBzdWJqZWN0IHRvIHJlZ3VsYXRpb25zIHRoYXQgcmVxdWlyZSBjbGVhciBqdXN0aWZpY2F0aW9ucyBmb3IgZGVjaXNpb25zLCBlc3BlY2lhbGx5IHRob3NlIGFmZmVjdGluZyBpbmRpdmlkdWFsc+KAmSBsaXZlcyBhbmQgZmluYW5jZXMuIEZvciBpbnN0YW5jZSwgaW4gYmFua2luZywgdGhlIEJhc2VsIElJSSBmcmFtZXdvcmsgYW5kIHRoZSBHZW5lcmFsIERhdGEgUHJvdGVjdGlvbiBSZWd1bGF0aW9uIChHRFBSKSBuZWNlc3NpdGF0ZSBleHBsYWluYWJpbGl0eSBpbiBjcmVkaXQgc2NvcmluZyBtb2RlbHMgdG8gZW5zdXJlIGZhaXJuZXNzIGFuZCBhY2NvdW50YWJpbGl0eSwgYXMgd2VsbCBhcyBleHBvc2UgYW55IHBvdGVudGlhbCBjb3ZlcnQgYmlhcy4KCjMuICAqKkRlYnVnZ2luZyBhbmQgSW1wcm92ZW1lbnQqKjogRXhwbGFpbmFibGUgbW9kZWxzIG1ha2UgaXQgZWFzaWVyIHRvIGlkZW50aWZ5IGFuZCByZWN0aWZ5IGVycm9ycyBvciBiaWFzZXMgaW4gdGhlIG1vZGVsLiBVbmRlcnN0YW5kaW5nIHdoeSBhIG1vZGVsIG1ha2VzIGNlcnRhaW4gcHJlZGljdGlvbnMgY2FuIGhpZ2hsaWdodCBhcmVhcyBmb3IgaW1wcm92ZW1lbnQgYW5kIHJlZmluZW1lbnQsIGxlYWRpbmcgdG8gYmV0dGVyIHBlcmZvcm1hbmNlIGFuZCBtb3JlIGFjY3VyYXRlIHJlc3VsdHMuCgo0LiAgKipFdGhpY2FsIFJlc3BvbnNpYmlsaXR5Kio6IE1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIGNhbiBpbmFkdmVydGVudGx5IHBlcnBldHVhdGUgb3IgZXhhY2VyYmF0ZSBiaWFzZXMgcHJlc2VudCBpbiB0cmFpbmluZyBkYXRhLiBFeHBsYWluYWJpbGl0eSBoZWxwcyBpbiBpZGVudGlmeWluZyBzdWNoIGJpYXNlcywgZW5hYmxpbmcgZGV2ZWxvcGVycyB0byB0YWtlIGNvcnJlY3RpdmUgYWN0aW9ucyB0byBlbnN1cmUgZXRoaWNhbCBhbmQgZmFpciBvdXRjb21lcy4KCjUuICAqKlVzZXIgVW5kZXJzdGFuZGluZyBhbmQgRW5nYWdlbWVudCoqOiBXaGVuIHVzZXJzIHVuZGVyc3RhbmQgaG93IGEgbW9kZWwgd29ya3MsIHRoZXkgYXJlIG1vcmUgbGlrZWx5IHRvIGVuZ2FnZSB3aXRoIGl0IGFwcHJvcHJpYXRlbHkuIEZvciBleGFtcGxlLCBpbiBtZWRpY2FsIGRpYWdub3N0aWNzLCBleHBsYWluYWJpbGl0eSBjYW4gaGVscCBkb2N0b3JzIHVuZGVyc3RhbmQgdGhlIHJhdGlvbmFsZSBiZWhpbmQgYSBtb2RlbOKAmXMgcHJlZGljdGlvbiwgYWxsb3dpbmcgdGhlbSB0byBtYWtlIG1vcmUgaW5mb3JtZWQgZGVjaXNpb25zIGFib3V0IHBhdGllbnQgY2FyZS4KCjYuICAqKkRlY2lzaW9uIEFjY291bnRhYmlsaXR5Kio6IEluIG1hbnkgYXBwbGljYXRpb25zLCBpdCBpcyBlc3NlbnRpYWwgdG8gaG9sZCBkZWNpc2lvbi1tYWtlcnMgYWNjb3VudGFibGUuIEV4cGxhaW5hYmxlIG1vZGVscyBwcm92aWRlIHRoZSBuZWNlc3NhcnkgdHJhbnNwYXJlbmN5IHRvIGVuc3VyZSB0aGF0IGRlY2lzaW9ucyBjYW4gYmUgc2NydXRpbml6ZWQgYW5kIGp1c3RpZmllZCwgdGhlcmVieSBtYWludGFpbmluZyBhY2NvdW50YWJpbGl0eSBhbmQgaW50ZWdyaXR5IGluIGF1dG9tYXRlZCBkZWNpc2lvbi1tYWtpbmcgc3lzdGVtcy4KCkluIHNob3J0LCBleHBsYWluYWJpbGl0eSBicmlkZ2VzIHRoZSBnYXAgYmV0d2VlbiBjb21wbGV4IG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIGFuZCBodW1hbiB1bmRlcnN0YW5kaW5nLiBJdCBlbnN1cmVzIHRoYXQgbW9kZWxzIGFyZSBub3Qgb25seSBhY2N1cmF0ZSBidXQgYWxzbyB0cmFuc3BhcmVudCwgZmFpciwgYXMgZnJlZSBmcm9tIGJpYXMgYXMgcG9zc2libGUsIGFuZCB0cnVzdHdvcnRoeSwgdGhlcmVieSBmb3N0ZXJpbmcgd2lkZXIgYWNjZXB0YW5jZSAtLSBhbmQgcmVzcG9uc2libGUgdXNlIC0tIG9mIG1hY2hpbmUgbGVhcm5pbmcgYW5kIGFwcGxpZWQgYXJ0aWZpY2lhbCBpbnRlbGxpZ2VuY2UuCgojIyBEZWNpc2lvbiBUcmVlcyBmb3IgQ2xhc3NpZmljYXRpb24KCkRlY2lzaW9uIHRyZWUgYWxnb3JpdGhtcyBhcmUgdXNlZnVsIGZvciBib3RoIGNsYXNzaWZpY2F0aW9uIGFuZCByZWdyZXNzaW9uIHRhc2tzLCBhbHRob3VnaCB3ZSB3aWxsIGZvY3VzIG9uIGNsYXNzaWZpY2F0aW9uIHRhc2tzLiBUaGVzZSBhbGdvcml0aG1zIHdvcmsgYnkgcmVjdXJzaXZlbHkgcGFydGl0aW9uaW5nIHRoZSBkYXRhIGludG8gc3Vic2V0cyBiYXNlZCBvbiBmZWF0dXJlIHZhbHVlcywgY3JlYXRpbmcgYSB0cmVlLWxpa2UgbW9kZWwgb2YgZGVjaXNpb25zLiBFYWNoIGludGVybmFsIG5vZGUgb2YgdGhlIHRyZWUgcmVwcmVzZW50cyBhIGNvbmRpdGlvbmFsIHRlc3Qgb24gYW4gYXR0cmlidXRlLCBlYWNoIGJyYW5jaCByZXByZXNlbnRzIHRoZSBvdXRjb21lIG9mIHRoZSB0ZXN0LCBhbmQgZWFjaCBsZWFmIG5vZGUgcmVwcmVzZW50cyBhIGNsYXNzIGxhYmVsIG9yIGEgcmVncmVzc2lvbiB2YWx1ZS4gVGhlIGdvYWwgaXMgdG8gY3JlYXRlIGEgbW9kZWwgdGhhdCBwcmVkaWN0cyB0aGUgdmFsdWUgb2YgYSB0YXJnZXQgdmFyaWFibGUgYnkgbGVhcm5pbmcgc2ltcGxlIGRlY2lzaW9uIHJ1bGVzIGluZmVycmVkIGZyb20gdGhlIGRhdGEgZmVhdHVyZXMuIERlY2lzaW9uIHRyZWVzIGFyZSBwcmVmZXJhYmxlIHdoZW4gZmVhdHVyZXMgYXJlIGEgbWl4IG9mIGNhdGVnb3JpY2FsIGFuZCBudW1lcmljIHZhbHVlcy4gVW5saWtlIHJlZ3Jlc3Npb24gbWV0aG9kcywgZGVjaXNpb24gdHJlZXMgZG8gbm90IHJlbHkgb24gYW55IHBhcnRpY3VsYXIgZGlzdHJpYnV0aW9uIG9mIHZhbHVlcy4KCiMjIyBPdmVydmlldyBvZiBEZWNpc2lvbiBUcmVlIEFsZ29yaXRobXMKCkEgZGVjaXNpb24gdHJlZSBzdGFydHMgd2l0aCBhIHJvb3Qgbm9kZSB0aGF0IGVuY29tcGFzc2VzIHRoZSBlbnRpcmUgZGF0YXNldC4gVGhlIGFsZ29yaXRobSB0aGVuIHNlbGVjdHMgdGhlIGZlYXR1cmUgdGhhdCBiZXN0IGRpdmlkZXMgdGhlIGRhdGEgaW50byBkaXN0aW5jdCBjbGFzc2VzIG9yIHZhbHVlcywgYmFzZWQgb24gYSBjcml0ZXJpb24gbGlrZSAqSW5mb3JtYXRpb24gR2Fpbiogb3IgKkdpbmkgSW5kZXgqLiBUaGlzIHByb2Nlc3MgaXMgcmVwZWF0ZWQgcmVjdXJzaXZlbHkgZm9yIGVhY2ggY2hpbGQgbm9kZSB1bnRpbCBvbmUgb2YgdGhlIHN0b3BwaW5nIGNvbmRpdGlvbnMgaXMgbWV0LCBzdWNoIGFzIHJlYWNoaW5nIGEgbWF4aW11bSBkZXB0aCwgaGF2aW5nIGEgbWluaW11bSBudW1iZXIgb2Ygc2FtcGxlcyBwZXIgbm9kZSwgb3IgYWNoaWV2aW5nIGhvbW9nZW5lb3VzIG5vZGVzLiBUaGUgY29uc3RydWN0aW9uIG9mIGRlY2lzaW9uIHRyZWVzIG9uIGRhdGEgc2V0cyB3aXRoIG1hbnkgb2JzZXJ2YXRpb25zIGFuZCBhIGhpZ2ggbnVtYmVyIG9mIGRpbWVuc2lvbnMgY2FuIGJlIHRpbWUgY29uc3VtaW5nIGFuZCB0YWtlIGEgbG9uZyB0aW1lLgoKSW5mb3JtYXRpb24gR2FpbiBtZWFzdXJlcyB0aGUgcmVkdWN0aW9uIGluIGVudHJvcHkgb3IgdW5jZXJ0YWludHkgYWJvdXQgdGhlIGNsYXNzIGxhYmVscyBhZnRlciBzcGxpdHRpbmcgdGhlIGRhdGEgYmFzZWQgb24gYW4gYXR0cmlidXRlLCB3aGlsZSB0aGUgR2luaSBJbmRleCBtZWFzdXJlcyB0aGUgaW1wdXJpdHkgb3IgZGl2ZXJzaXR5IG9mIHRoZSBzcGxpdC4gSXQgaXMgY2FsY3VsYXRlZCBhcyB0aGUgcHJvYmFiaWxpdHkgb2YgbWlzY2xhc3NpZnlpbmcgYSByYW5kb21seSBjaG9zZW4gZWxlbWVudCBpZiBpdCB3ZXJlIGxhYmVsZWQgYWNjb3JkaW5nIHRvIHRoZSBkaXN0cmlidXRpb24gb2YgbGFiZWxzIGluIHRoZSBzdWJzZXQuIFNvbWUgZGVjaXNpb24gdHJlZSBhbGdvcml0aG0gd2lsbCBldmFsdWF0ZSB0aGUgc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlIG9mIHRoZSBhc3NvY2lhdGlvbiBiZXR3ZWVuIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBvciBhdHRlbXB0IHRvIHJlZHVjZSB2YXJpYW5jZS4gVGhlIGxhdHRlciBhcHByb2FjaCBpcyBvZnRlbiB1c2VkIGluIHJlZ3Jlc3Npb24gdHJlZXMgd2hpY2ggd29ya3MgdG8gbWluaW1pemUgdGhlIHZhcmlhbmNlIG9mIHRoZSB0YXJnZXQgdmFyaWFibGUgd2l0aGluIGVhY2ggbm9kZS4KClRvIHByZXZlbnQgb3ZlcmZpdHRpbmcsIGRlY2lzaW9uIHRyZWVzIG9mdGVuIHVzZSBwcnVuaW5nIHRlY2huaXF1ZXMgdG8gcmVtb3ZlIGJyYW5jaGVzIHRoYXQgaGF2ZSBsaXR0bGUgaW1wb3J0YW5jZS4gUHJ1bmluZyBjYW4gYmUgZG9uZSBpbiBhIHByZS1wcnVuaW5nIG1hbm5lciAoc3RvcHBpbmcgdGhlIHRyZWUgZ3Jvd3RoIGVhcmx5KSBvciBwb3N0LXBydW5pbmcgKHJlbW92aW5nIGJyYW5jaGVzIGZyb20gYSBmdWxseSBncm93biB0cmVlKS4KCiMjIyBDb21tb24gRGVjaXNpb24gVHJlZSBBbGdvcml0aG1zCgpTZXZlcmFsIGRlY2lzaW9uIHRyZWUgYWxnb3JpdGhtcyBoYXZlIGJlZW4gZGV2ZWxvcGVkIG92ZXIgdGhlIHllYXJzLCBlYWNoIHdpdGggaXRzIHVuaXF1ZSBhcHByb2FjaCB0byBidWlsZGluZyBhbmQgcmVmaW5pbmcgdHJlZXMuIFNvbWUgb2YgdGhlIG1vc3Qgbm90YWJsZSBvbmVzIGluY2x1ZGU6CgoxLiAgKipJRDMgKEl0ZXJhdGl2ZSBEaWNob3RvbWlzZXIgMykqKjogRGV2ZWxvcGVkIGJ5IFJvc3MgUXVpbmxhbiwgSUQzIHVzZXMgSW5mb3JtYXRpb24gR2FpbiBhcyBpdHMgc3BsaXR0aW5nIGNyaXRlcmlvbi4gSXQgYnVpbGRzIGEgdHJlZSB0b3AtZG93biwgc3RhcnRpbmcgZnJvbSBhIHNldCBvZiB0cmFpbmluZyBpbnN0YW5jZXMgYW5kIHRoZSBmZWF0dXJlcyB0aGF0IGRlc2NyaWJlIHRoZW0uCgoyLiAgKipDNC41IGFuZCBDNS4wKio6IEFsc28gZGV2ZWxvcGVkIGJ5IFJvc3MgUXVpbmxhbiwgQzQuNSBpcyBhbiBleHRlbnNpb24gb2YgSUQzLiBJdCB1c2VzICpHYWluIFJhdGlvKiwgYSBtb2RpZmljYXRpb24gb2YgKkluZm9ybWF0aW9uIEdhaW4qIHRoYXQgcmVkdWNlcyBpdHMgYmlhcyB0b3dhcmRzIGZlYXR1cmVzIHdpdGggbWFueSBkaXN0aW5jdCB2YWx1ZXMuIEM1LjAgaXMgYSBjb21tZXJjaWFsIHZlcnNpb24gdGhhdCBpcyBtb3JlIGVmZmljaWVudCBhbmQgcHJvdmlkZXMgYmV0dGVyIHBlcmZvcm1hbmNlIHRoYW4gQzQuNS4KCjMuICAqKkNBUlQgKENsYXNzaWZpY2F0aW9uIGFuZCBSZWdyZXNzaW9uIFRyZWVzKSoqOiBJbnRyb2R1Y2VkIGJ5IEJyZWltYW4gZXQgYWwuLCBDQVJUIGNhbiBiZSB1c2VkIGZvciBib3RoIGNsYXNzaWZpY2F0aW9uIGFuZCByZWdyZXNzaW9uIHRhc2tzLiBJdCB1c2VzIHRoZSAqR2luaSBJbmRleCogZm9yIGNsYXNzaWZpY2F0aW9uIHRyZWVzIGFuZCByZWR1Y3Rpb24gaW4gdmFyaWFuY2UgZm9yIHJlZ3Jlc3Npb24gdHJlZXMuIENBUlQgcHJvZHVjZXMgYmluYXJ5IHRyZWVzLCB3aGVyZSBlYWNoIG5vZGUgaGFzIGV4YWN0bHkgdHdvIGNoaWxkcmVuLgoKNC4gICoqQ0hBSUQgKENoaS1zcXVhcmVkIEF1dG9tYXRpYyBJbnRlcmFjdGlvbiBEZXRlY3RvcikqKjogVGhpcyBhbGdvcml0aG0gaXMgdXNlZCBwcmltYXJpbHkgZm9yIGNsYXNzaWZpY2F0aW9uIGFuZCByZWxpZXMgb24gdGhlIENoaS1TcXVhcmUgdGVzdCB0byBkZXRlcm1pbmUgdGhlIGJlc3Qgc3BsaXRzLiBJdCBpcyBwYXJ0aWN1bGFybHkgZWZmZWN0aXZlIGZvciBjYXRlZ29yaWNhbCBkYXRhIGFuZCBoYW5kbGVzIG11bHRpLXdheSBzcGxpdHMuCgo1LiAgKipNQVJTIChNdWx0aXZhcmlhdGUgQWRhcHRpdmUgUmVncmVzc2lvbiBTcGxpbmVzKSoqOiBBbHRob3VnaCBub3Qgc3RyaWN0bHkgYSBkZWNpc2lvbiB0cmVlLCBNQVJTIGJ1aWxkcyBtb2RlbHMgYnkgZGl2aWRpbmcgdGhlIGRhdGEgaW50byBkaXN0aW5jdCByZWdpb25zIHVzaW5nIGhpbmdlIGZ1bmN0aW9ucy4gSXQgaXMgaGlnaGx5IGVmZmVjdGl2ZSBmb3IgcmVncmVzc2lvbiB0YXNrcyBhbmQgY2FuIGhhbmRsZSBpbnRlcmFjdGlvbnMgYmV0d2VlbiB2YXJpYWJsZXMuCgo2LiAgKipSYW5kb20gRm9yZXN0Kio6IEFuIGVuc2VtYmxlIG1ldGhvZCB0aGF0IGNvbnN0cnVjdHMgbXVsdGlwbGUgZGVjaXNpb24gdHJlZXMgZHVyaW5nIHRyYWluaW5nIGFuZCBvdXRwdXRzIHRoZSBtb2RlIG9mIHRoZSBjbGFzc2VzIGZvciBjbGFzc2lmaWNhdGlvbiBvciB0aGUgbWVhbiBwcmVkaWN0aW9uIGZvciByZWdyZXNzaW9uIG9mIHRoZSBpbmRpdmlkdWFsIHRyZWVzLiBJdCB1c2VzIHRlY2huaXF1ZXMgbGlrZSBiYWdnaW5nIGFuZCBmZWF0dXJlIHJhbmRvbW5lc3MgdG8gaW1wcm92ZSBtb2RlbCBhY2N1cmFjeSBhbmQgY29udHJvbCBvdmVyZml0dGluZy4KCjcuICAqKkdyYWRpZW50IEJvb3N0ZWQgVHJlZXMqKjogQW5vdGhlciBlbnNlbWJsZSB0ZWNobmlxdWUgdGhhdCBidWlsZHMgdHJlZXMgc2VxdWVudGlhbGx5LiBFYWNoIHRyZWUgaXMgY29uc3RydWN0ZWQgdG8gY29ycmVjdCB0aGUgZXJyb3JzIG9mIHRoZSBwcmV2aW91cyBvbmVzLCBmb2N1c2luZyBvbiB0aGUgcmVzaWR1YWwgZXJyb3JzLiBQcm9taW5lbnQgYWxnb3JpdGhtcyBpbiB0aGlzIGNhdGVnb3J5IGluY2x1ZGUgWEdCb29zdCwgTGlnaHRHQk0sIGFuZCBDYXRCb29zdC4KCjguICAqKk9ibGlxdWUgRGVjaXNpb24gVHJlZXMqKjogVGhlc2UgdHJlZXMgdXNlIGxpbmVhciBjb21iaW5hdGlvbnMgb2YgZmVhdHVyZXMgcmF0aGVyIHRoYW4gYSBzaW5nbGUgZmVhdHVyZSBmb3Igc3BsaXR0aW5nIG5vZGVzLiBUaGlzIGNhbiBsZWFkIHRvIG1vcmUgY29tcGFjdCBhbmQgcG90ZW50aWFsbHkgbW9yZSBhY2N1cmF0ZSB0cmVlcyBidXQgYXJlIGNvbXB1dGF0aW9uYWxseSBtb3JlIGludGVuc2l2ZSB0byBidWlsZC4KCkRlY2lzaW9uIHRyZWUgYWxnb3JpdGhtcyBvZmZlciBhIHJvYnVzdCBhbmQgaW50ZXJwcmV0YWJsZSBhcHByb2FjaCB0byBib3RoIGNsYXNzaWZpY2F0aW9uIGFuZCByZWdyZXNzaW9uIHRhc2tzLiBUaGVpciB2ZXJzYXRpbGl0eSBhbmQgZWFzZSBvZiB1bmRlcnN0YW5kaW5nIG1ha2UgdGhlbSBwb3B1bGFyIGluIHZhcmlvdXMgZmllbGRzLCBmcm9tIGZpbmFuY2UgdG8gaGVhbHRoY2FyZS4gRWFjaCB2YXJpYW50IG9mIGRlY2lzaW9uIHRyZWUgYWxnb3JpdGhtcyBicmluZ3MgdW5pcXVlIHN0cmVuZ3RocywgYW5kIHVuZGVyc3RhbmRpbmcgdGhlc2UgY2FuIGhlbHAgaW4gc2VsZWN0aW5nIHRoZSBtb3N0IGFwcHJvcHJpYXRlIG1vZGVsIGZvciBhIGdpdmVuIHByb2JsZW0uIFRoaXMgbGVzc29uIHdpbGwgZm9jdXMgb24gdGhlIHVzZSBvZiB0aGUgQzUuMCAoKmFrYSogQzUwKSBhbGdvcml0aG0uCgojIyMgVGhlIEM1LjAgQWxnb3JpdGhtCgpUaGUgQzUuMCAoKmFrYSogQzUwKSBhbGdvcml0aG0sIGRldmVsb3BlZCBieSBSb3NzIFF1aW5sYW4sIGlzIGFuIGFkdmFuY2VkIGFuZCBjb21tZXJjaWFsbHkgYXZhaWxhYmxlIHZlcnNpb24gb2YgdGhlIEM0LjUgYWxnb3JpdGhtLiBJdCBpcyB3aWRlbHkgdXNlZCBmb3IgY3JlYXRpbmcgY2xhc3NpZmljYXRpb24gbW9kZWxzIGR1ZSB0byBpdHMgZWZmaWNpZW5jeSwgYWNjdXJhY3ksIGFuZCBpbnRlcnByZXRhYmlsaXR5L2V4cGxhaW5hYmlsaXR5LiBUaGUgQzUuMCBhbGdvcml0aG0gYnVpbGRzIGEgZGVjaXNpb24gdHJlZSBieSByZWN1cnNpdmVseSBzcGxpdHRpbmcgdGhlIGRhdGEgaW50byBzdWJzZXRzIGJhc2VkIG9uIHRoZSBhdHRyaWJ1dGUgdGhhdCBwcm92aWRlcyB0aGUgaGlnaGVzdCBpbmZvcm1hdGlvbiBnYWluIHJhdGlvLiBUaGlzIHByb2Nlc3MgY29udGludWVzIHVudGlsIHRoZSBhbGdvcml0aG0gbWVldHMgYSBzdG9wcGluZyBjcml0ZXJpb24sIHN1Y2ggYXMgcmVhY2hpbmcgYSBzcGVjaWZpZWQgZGVwdGggb3IgaGF2aW5nIHRvbyBmZXcgc2FtcGxlcyB0byBzcGxpdCBmdXJ0aGVyLgoKIyMjIEhvdyBDNS4wIFdvcmtzCgoxLiAgKipEYXRhIFByZXBhcmF0aW9uKio6IEJlZm9yZSBidWlsZGluZyB0aGUgdHJlZSwgdGhlIEM1LjAgYWxnb3JpdGhtIHJlcXVpcmVzIHRoZSBkYXRhIHRvIGJlIHByZXBhcmVkLCB3aGljaCBpbnZvbHZlcyBoYW5kbGluZyBtaXNzaW5nIHZhbHVlcywgY29udmVydGluZyBjYXRlZ29yaWNhbCBkYXRhIHRvIG51bWVyaWMgd2hlcmUgbmVjZXNzYXJ5LCBhbmQgb3B0aW9uYWxseSBub3JtYWxpemluZyBudW1lcmljIGRhdGEuIEluIHByYWN0aWNlLCBDNS4wIGhhbmRsZXMgbWlzc2luZyB2YWx1ZXMgcXVpdGUgd2VsbCBhbmQgZG9lc24ndCBvZnRlbiBuZWNlc3NpdGF0ZSB0aGUgcmVtb3ZhbCBvZiBvdXRsaWVycywgaW1wdXRpbmcgbWlzc2luZyB2YWx1ZXMsIGVuY29kaW5nIGNhdGVnb3JpY2FsIGZlYXR1cmVzLCBhbmQgbm9ybWFsaXplIG51bWVyaWMgZmVhdHVyZXMuCgoyLiAgKipUcmVlIENvbnN0cnVjdGlvbioqOiBUaGUgYWxnb3JpdGhtIHN0YXJ0cyB3aXRoIHRoZSBlbnRpcmUgZGF0YXNldCBhbmQgZXZhbHVhdGVzIGVhY2ggYXR0cmlidXRlIHRvIGZpbmQgdGhlIG9uZSB0aGF0IHByb3ZpZGVzIHRoZSBoaWdoZXN0IGluZm9ybWF0aW9uIGdhaW4gcmF0aW8uIFRoaXMgYXR0cmlidXRlIGJlY29tZXMgdGhlIHJvb3Qgbm9kZSBvZiB0aGUgdHJlZS4gU3BsaXR0aW5nIHRoZSB0cmVlIGlzIGJhc2VkIG9uIHRoZSBpbmZvcm1hdGlvbiBnYWluIHJhdGlvLiBUaGUgaW5mb3JtYXRpb24gZ2FpbiByYXRpbyBpcyBhIG1lYXN1cmUgb2YgaG93IHdlbGwgYW4gYXR0cmlidXRlIHNwbGl0cyB0aGUgZGF0YSBpbnRvIGRpc3RpbmN0IGNsYXNzZXMuIEl0IGlzIGNhbGN1bGF0ZWQgYXMgdGhlIHJhdGlvIG9mIGluZm9ybWF0aW9uIGdhaW4gKHJlZHVjdGlvbiBpbiBlbnRyb3B5KSB0byB0aGUgaW50cmluc2ljIGluZm9ybWF0aW9uIChhIG1lYXN1cmUgb2YgdGhlIHBvdGVudGlhbCBpbmZvcm1hdGlvbiBnZW5lcmF0ZWQgYnkgc3BsaXR0aW5nIHRoZSBkYXRhc2V0IGJhc2VkIG9uIHRoaXMgYXR0cmlidXRlKS4gQWZ0ZXIgc2VsZWN0aW5nIHRoZSByb290IG5vZGUsIHRoZSBkYXRhc2V0IGlzIGRpdmlkZWQgaW50byBzdWJzZXRzIGJhc2VkIG9uIHRoZSBwb3NzaWJsZSB2YWx1ZXMgb2YgdGhlIGNob3NlbiBhdHRyaWJ1dGUuIFRoZSBhbGdvcml0aG0gdGhlbiByZXBlYXRzIHRoZSBwcm9jZXNzIGZvciBlYWNoIHN1YnNldCwgc2VsZWN0aW5nIHRoZSBiZXN0IGF0dHJpYnV0ZSBmb3IgZnVydGhlciBzcGxpdHRpbmcuIFRoaXMgcmVjdXJzaXZlIHByb2Nlc3MgY29udGludWVzLCBjcmVhdGluZyBicmFuY2hlcyBhbmQgbGVhZiBub2RlcywgdW50aWwgYSBzdG9wcGluZyBjcml0ZXJpb24gaXMgbWV0LgoKMy4gICoqUHJ1bmluZyoqOiBUbyBhdm9pZCBvdmVyZml0dGluZywgdGhlIGFsZ29yaXRobSBtYXkgdXNlIHByZS1wcnVuaW5nIHRlY2huaXF1ZXMgc3VjaCBhcyBsaW1pdGluZyB0aGUgZGVwdGggb2YgdGhlIHRyZWUsIHJlcXVpcmluZyBhIG1pbmltdW0gbnVtYmVyIG9mIHNhbXBsZXMgcGVyIGxlYWYsIG9yIHNldHRpbmcgYSB0aHJlc2hvbGQgZm9yIHRoZSBpbmZvcm1hdGlvbiBnYWluIHJhdGlvLiBPbmNlIHRoZSB0cmVlIGlzIGZ1bGx5IGdyb3duLCBwb3N0LXBydW5pbmcgY2FuIGJlIGFwcGxpZWQgdG8gcmVtb3ZlIGJyYW5jaGVzIHRoYXQgY29udHJpYnV0ZSBsaXR0bGUgdG8gdGhlIG92ZXJhbGwgcHJlZGljdGl2ZSBwb3dlciBvZiB0aGUgbW9kZWwuIFRoaXMgaW52b2x2ZXMgZXZhbHVhdGluZyB0aGUgcGVyZm9ybWFuY2Ugb2Ygc3VidHJlZXMgYW5kIGNvbGxhcHNpbmcgdGhvc2UgdGhhdCBkbyBub3Qgc2lnbmlmaWNhbnRseSBpbXByb3ZlIGFjY3VyYWN5LgoKNC4gICoqUnVsZSBFeHRyYWN0aW9uKio6IFRoZSBDNS4wIGFsZ29yaXRobSBjYW4gY29udmVydCB0aGUgZGVjaXNpb24gdHJlZSBpbnRvIGEgc2V0IG9mIElGLVRIRU4gcnVsZXMsIHdoaWNoIGFyZSBvZnRlbiBlYXNpZXIgdG8gaW50ZXJwcmV0IGFuZCB1bmRlcnN0YW5kLiBFYWNoIHBhdGggZnJvbSB0aGUgcm9vdCB0byBhIGxlYWYgbm9kZSBjb3JyZXNwb25kcyB0byBhIHJ1bGUuCgo1LiAgKipCb29zdGluZyoqOiBPbmUgb2YgdGhlIG5vdGFibGUgZmVhdHVyZXMgb2YgQzUuMCBpcyBpdHMgYWJpbGl0eSB0byB1c2UgYm9vc3RpbmcsIGFuIGVuc2VtYmxlIHRlY2huaXF1ZSB0aGF0IGNvbWJpbmVzIG11bHRpcGxlIHdlYWsgY2xhc3NpZmllcnMgdG8gZm9ybSBhIHN0cm9uZyBjbGFzc2lmaWVyLiBJbiBib29zdGluZywgdGhlIGFsZ29yaXRobSBpdGVyYXRpdmVseSBidWlsZHMgbXVsdGlwbGUgZGVjaXNpb24gdHJlZXMsIGVhY2ggZm9jdXNpbmcgb24gdGhlIGVycm9ycyBtYWRlIGJ5IHRoZSBwcmV2aW91cyBvbmVzLiBUaGUgZmluYWwgbW9kZWwgaXMgYSB3ZWlnaHRlZCB2b3RlIG9mIHRoZXNlIGluZGl2aWR1YWwgdHJlZXMuCgo2LiAgKipIYW5kbGluZyBNaXNzaW5nIFZhbHVlcyBhbmQgTm9pc2UqKjogVGhlIEM1LjAgYWxnb3JpdGhtIGhhcyByb2J1c3QgbWVjaGFuaXNtcyBmb3IgaGFuZGxpbmcgbWlzc2luZyB2YWx1ZXMgYW5kIG5vaXN5IGRhdGEuIEl0IGNhbiB3b3JrIHdpdGggaW5jb21wbGV0ZSBkYXRhc2V0cyBieSB1c2luZyBzdXJyb2dhdGUgc3BsaXRzLCB3aGljaCBhbGxvdyBpdCB0byBwcm9jZWVkIHdpdGggdGhlIG5leHQgYmVzdCBhdHRyaWJ1dGUgd2hlbiB0aGUgcHJpbWFyeSBhdHRyaWJ1dGUgaXMgbWlzc2luZy4KCiMjIyBBZHZhbnRhZ2VzIG9mIEM1LjAKCkM1LjAgaXMgc2lnbmlmaWNhbnRseSBmYXN0ZXIgdGhhbiBpdHMgcHJlZGVjZXNzb3IsIEM0LjUsIGJvdGggaW4gdGVybXMgb2YgbWVtb3J5IHVzYWdlIGFuZCBwcm9jZXNzaW5nIHRpbWUuIEl0IGNhbiBoYW5kbGUgbGFyZ2UgZGF0YXNldHMgbW9yZSBlZmZpY2llbnRseSwgbWFraW5nIGl0IHN1aXRhYmxlIGZvciByZWFsLXdvcmxkIGFwcGxpY2F0aW9ucyB3aXRoIHN1YnN0YW50aWFsIGFtb3VudHMgb2YgZGF0YS4gVGhlIGJvb3N0aW5nIGZlYXR1cmUgb2YgQzUuMCBoZWxwcyBpbiBpbXByb3ZpbmcgdGhlIG1vZGVsJ3MgYWNjdXJhY3kgYnkgY29tYmluaW5nIHRoZSBzdHJlbmd0aHMgb2YgbXVsdGlwbGUgZGVjaXNpb24gdHJlZXMuIFRoaXMgZW5zZW1ibGUgbWV0aG9kIHJlZHVjZXMgYmlhcyBhbmQgdmFyaWFuY2UsIGxlYWRpbmcgdG8gYmV0dGVyIGdlbmVyYWxpemF0aW9uIG9uIHVuc2VlbiBkYXRhLiBGdXJ0aGVybW9yZSwgZGVjaXNpb24gdHJlZXMgcHJvZHVjZWQgYnkgQzUuMCBhcmUgZWFzeSB0byB1bmRlcnN0YW5kIGFuZCBpbnRlcnByZXQuIFRoZSBjb252ZXJzaW9uIG9mIHRyZWVzIGludG8gYSBzZXQgb2YgcnVsZXMgZnVydGhlciBlbmhhbmNlcyB0aGVpciBpbnRlcnByZXRhYmlsaXR5LCBtYWtpbmcgaXQgZWFzaWVyIGZvciBzdGFrZWhvbGRlcnMgdG8gZ3Jhc3AgdGhlIGRlY2lzaW9uLW1ha2luZyBwcm9jZXNzLgoKQzUuMCBvZmZlcnMgdmFyaW91cyBwYXJhbWV0ZXJzIGZvciBmaW5lLXR1bmluZyB0aGUgbW9kZWwsIHN1Y2ggYXMgYWRqdXN0aW5nIHRoZSBjb25maWRlbmNlIGxldmVsIGZvciBwcnVuaW5nLCBzZXR0aW5nIG1pbmltdW0gc2FtcGxlIHNpemVzLCBhbmQgZW5hYmxpbmcgYm9vc3RpbmcuIFRoaXMgZmxleGliaWxpdHkgYWxsb3dzIHVzZXJzIHRvIGN1c3RvbWl6ZSB0aGUgbW9kZWwgYWNjb3JkaW5nIHRvIHRoZWlyIHNwZWNpZmljIG5lZWRzLgoKTGFzdGx5LCB0aGUgYWxnb3JpdGhtJ3MgYWJpbGl0eSB0byBoYW5kbGUgbm9pc3kgZGF0YSBhbmQgbWlzc2luZyB2YWx1ZXMgZW5zdXJlcyB0aGF0IHRoZSBtb2RlbCByZW1haW5zIHJvYnVzdCBhbmQgcmVsaWFibGUsIGV2ZW4gd2hlbiB0aGUgcXVhbGl0eSBvZiB0aGUgZGF0YSBpcyBub3QgcGVyZmVjdC4gVGhlIEM1LjAgYWxnb3JpdGhtIHN0YW5kcyBvdXQgZHVlIHRvIGl0cyBlZmZpY2llbmN5LCBhY2N1cmFjeSwgYW5kIGVhc2Ugb2YgaW50ZXJwcmV0YXRpb24uIEl0cyBhYmlsaXR5IHRvIGhhbmRsZSBsYXJnZSBkYXRhc2V0cywgcm9idXN0IHBlcmZvcm1hbmNlIHdpdGggbm9pc3kgYW5kIGluY29tcGxldGUgZGF0YSwgYW5kIHRoZSBvcHRpb24gdG8gdXNlIGJvb3N0aW5nIG1ha2UgaXQgYSBnby10byBhbGdvcml0aG0gZm9yIGNsYXNzaWZpY2F0aW9uIHRhc2tzLiBUaGVzZSBhZHZhbnRhZ2VzIG9mdGVuIG1ha2UgQzUuMCBwcmVmZXJhYmxlIG92ZXIgb3RoZXIgZGVjaXNpb24gdHJlZSBhbGdvcml0aG1zLCBwYXJ0aWN1bGFybHkgaW4gYXBwbGljYXRpb25zIHdoZXJlIGFjY3VyYWN5LCBzcGVlZCwgYW5kIGludGVycHJldGFiaWxpdHkgYXJlIHBhcmFtb3VudC4KCiMjIyBDb21wdXRhdGlvbmFsIEludHJhY3RhYmlsaXR5IGFuZCBEZWNpc2lvbiBUcmVlcwoKQnVpbGRpbmcgYSBwZXJmZWN0IGRlY2lzaW9uIHRyZWUgaXMgYW4gTlAtY29tcGxldGUgcHJvYmxlbSBhbmQgdGh1cyBubyBwb2x5bm9taWFsIGFsZ29yaXRobSBleGlzdHMsIHdoaWNoIG1lYW5zIHRoYXQgdGhlIHRpbWUgaXQgdGFrZXMgdG8gYnVpbGQgYSBwZXJmZWN0IGRlY2lzaW9uIHRyZWUgaW5jcmVhc2VzIGV4cG9uZW50aWFsbHkgd2l0aCB0aGUgc2l6ZSBvZiB0aGUgdHJhaW5pbmcgZGF0YS4gVGhpcyBjb21wbGV4aXR5IGFyaXNlcyBmcm9tIHRoZSBjb21iaW5hdG9yaWFsIG5hdHVyZSBvZiB0aGUgZGVjaXNpb24gdHJlZSBjb25zdHJ1Y3Rpb24gcHJvY2Vzcy4gVGhlIHZhcmlvdXMgYWxnb3JpdGhtcywgaW5jbHVkaW5nIEM1MCwgdXNlIGhldXJpc3RpY3MgdG8gY3JlYXRlIGEgdHJlZSB0aGF0IHdvcmtzIHdlbGwgaW4gcHJhY3RpY2UsIGlzIGNvbXB1dGF0aW9uYWxseSB0cmFjdGFibGUsIGJ1dCBpcyBzdWJvcHRpbWFsLgoKQSBkZWNpc2lvbiB0cmVlIGlzIGJ1aWx0IGJ5IHJlY3Vyc2l2ZWx5IHNwbGl0dGluZyBhIGRhdGFzZXQgYmFzZWQgb24gdGhlIHZhbHVlcyBvZiBpdHMgYXR0cmlidXRlcyB0byBjcmVhdGUgYnJhbmNoZXMgdW50aWwgdGhlIGRhdGFzZXQgaXMgcGVyZmVjdGx5IGNsYXNzaWZpZWQgb3IgYSBzdG9wcGluZyBjcml0ZXJpb24gaXMgbWV0LiBUaGUgZ29hbCBpcyB0byBmaW5kIHRoZSBvcHRpbWFsIHNldCBvZiBzcGxpdHMgdGhhdCByZXN1bHRzIGluIHRoZSBtb3N0IGFjY3VyYXRlIHRyZWUgd2l0aCB0aGUgbGVhc3QgY29tcGxleGl0eSAoaW4gdGVybXMgb2YgZGVwdGggYW5kIG51bWJlciBvZiBub2RlcykuIEZvciBhbnkgZ2l2ZW4gZGF0YXNldCB3aXRoICRuJCBhdHRyaWJ1dGVzLCB0aGVyZSBpcyBhbiBleHBvbmVudGlhbCBudW1iZXIgb2Ygd2F5cyB0byBwYXJ0aXRpb24gdGhlIGRhdGEgYW5kIGNob29zZSBzcGxpdHMuIEV2YWx1YXRpbmcgYWxsIHBvc3NpYmxlIHRyZWVzIHRvIGZpbmQgdGhlIGJlc3Qgb25lIGludm9sdmVzIGFuIGV4aGF1c3RpdmUgc2VhcmNoIHRocm91Z2ggYWxsIHRoZXNlIGNvbWJpbmF0aW9ucywgd2hpY2ggZ3Jvd3MgZXhwb25lbnRpYWxseSB3aXRoIHRoZSBzaXplIG9mIHRoZSBkYXRhc2V0IGFuZCB0aGUgbnVtYmVyIG9mIGF0dHJpYnV0ZXMuIFRoZSB0YXNrIG9mIGZpbmRpbmcgdGhlIG9wdGltYWwgZGVjaXNpb24gdHJlZSBjYW4gYmUgZnJhbWVkIGFzIGEgY29tYmluYXRvcmlhbCBvcHRpbWl6YXRpb24gcHJvYmxlbS4gRWFjaCBwb3NzaWJsZSBzcGxpdCBhdCBlYWNoIG5vZGUgcmVwcmVzZW50cyBhIGNvbWJpbmF0b3JpYWwgY2hvaWNlLCBhbmQgdGhlIG9iamVjdGl2ZSBpcyB0byBmaW5kIHRoZSBjb21iaW5hdGlvbiBvZiBjaG9pY2VzIHRoYXQgbWluaW1pemVzIHNvbWUgY29zdCBmdW5jdGlvbiAoKmUuZy4qLCBtaXNjbGFzc2lmaWNhdGlvbiByYXRlLCB0cmVlIGRlcHRoLCBldGMuKS4KClRoZSBwcm9ibGVtIG9mIGNvbnN0cnVjdGluZyBhbiBvcHRpbWFsIGRlY2lzaW9uIHRyZWUgY2FuIGJlIHJlZHVjZWQgdG8gYSBrbm93biBOUC1jb21wbGV0ZSBwcm9ibGVtLCBzdWNoIGFzIHRoZSBCb29sZWFuIHNhdGlzZmlhYmlsaXR5IHByb2JsZW0gKFNBVCkuIElmIG9uZSBjb3VsZCBzb2x2ZSB0aGUgZGVjaXNpb24gdHJlZSBvcHRpbWl6YXRpb24gcHJvYmxlbSBlZmZpY2llbnRseSwgaXQgd291bGQgaW1wbHkgYW4gZWZmaWNpZW50IHNvbHV0aW9uIHRvIGFsbCBOUC1jb21wbGV0ZSBwcm9ibGVtcywgd2hpY2ggaXMgbm90IGJlbGlldmVkIHRvIGJlIHBvc3NpYmxlLgoKQmVjYXVzZSBjb25zdHJ1Y3RpbmcgYSBwZXJmZWN0IGRlY2lzaW9uIHRyZWUgaXMgTlAtY29tcGxldGUsIGl0IGltcGxpZXMgdGhhdCB0aGVyZSBpcyBubyBrbm93biBwb2x5bm9taWFsLXRpbWUgYWxnb3JpdGhtIHRvIHNvbHZlIHRoaXMgcHJvYmxlbSBmb3IgYWxsIGluc3RhbmNlcy4gVGhlcmVmb3JlLCBoZXVyaXN0aWMgbWV0aG9kcyBhbmQgYXBwcm94aW1hdGlvbnMsIHN1Y2ggYXMgdGhvc2UgZW1wbG95ZWQgYnkgQzUwLCBhcmUgdHlwaWNhbGx5IHVzZWQgaW4gcHJhY3RpY2UgdG8gY29uc3RydWN0IGRlY2lzaW9uIHRyZWVzLiBUaGVzZSBtZXRob2RzIGFpbSB0byBmaW5kIGEgZ29vZCAodGhvdWdoIG5vdCBuZWNlc3NhcmlseSBvcHRpbWFsKSB0cmVlIGluIGEgcmVhc29uYWJsZSBhbW91bnQgb2YgdGltZSB0aGF0IHByb2R1Y2VzIGFjY2VwdGFibGUgcmVzdWx0cyBpbiBwcmFjdGljZS4gQW1vbmcgdGhlIGNvbW1vbiBoZXVyaXN0aWMgbWV0aG9kcyBhcmUgdGhlIHVzZSBvZiBhIGdyZWVkeSBhcHByb2FjaCB3aGljaCBtYWtlcyB0aGUgYmVzdCBzcGxpdCBkZWNpc2lvbiBhdCBlYWNoIHN0ZXAgYmFzZWQgb24gYSBjZXJ0YWluIGNyaXRlcmlvbiAoKmUuZy4qLCBpbmZvcm1hdGlvbiBnYWluIG9yIEdpbmkgaW5kZXgpLiBXaGlsZSB0aGlzIGRvZXMgbm90IGd1YXJhbnRlZSBhbiBvcHRpbWFsIHRyZWUsIGl0IG9mdGVuIHJlc3VsdHMgaW4gYSBnb29kIGFuZCBjb21wdXRhdGlvbmFsbHkgZmVhc2libGUgc29sdXRpb24uIFRvIGF2b2lkIG92ZXJmaXR0aW5nIGFuZCBpbXByb3ZlIGdlbmVyYWxpemF0aW9uLCBkZWNpc2lvbiB0cmVlIGFsZ29yaXRobXMgaW5jb3Jwb3JhdGUgcHJ1bmluZyBtZXRob2RzLCBib3RoIHByZS1wcnVuaW5nIChsaW1pdGluZyB0aGUgZ3Jvd3RoIG9mIHRoZSB0cmVlIGR1cmluZyBjb25zdHJ1Y3Rpb24pIGFuZCBwb3N0LXBydW5pbmcgKHJlbW92aW5nIGJyYW5jaGVzIGZyb20gYSBmdWxseSBncm93biB0cmVlKS4gSW4gYWRkaXRpb24sIHRlY2huaXF1ZXMgc3VjaCBhcyBiYWdnaW5nIGFuZCBib29zdGluZyAoKmUuZy4qLCBSYW5kb20gRm9yZXN0cyBvciBHcmFkaWVudCBCb29zdGVkIFRyZWVzKSBidWlsZCBtdWx0aXBsZSBkZWNpc2lvbiB0cmVlcyBvbiBkaWZmZXJlbnQgc2V0cyBvZiB0cmFpbmluZyBkYXRhIGFuZCBjb21iaW5lIHRoZWlyIHByZWRpY3Rpb25zIHRvIGltcHJvdmUgYWNjdXJhY3kgYW5kIHJvYnVzdG5lc3MuIEluIGNvbWJpbmF0aW9uLCB0aGVzZSBtZXRob2RzIG1pdGlnYXRlIHRoZSBzdWJvcHRpbWFsaXR5IG9mIGluZGl2aWR1YWwgdHJlZXMgYnkgbGV2ZXJhZ2luZyB0aGUgc3RyZW5ndGhzIG9mIG11bHRpcGxlIG1vZGVscyBhbmQgcHJhY3RpY2FsIGhldXJpc3RpY3MuCgojIyBXb3JrZWQgRXhhbXBsZTogQ3JlZGl0IERlY2lzaW9uIE1ha2luZwoKVGhlIG9iamVjdGl2ZSBvZiBvdXIgcHJlZGljdGl2ZSBtb2RlbCBpcyB0byBwaW5wb2ludCBmYWN0b3JzIGFzc29jaWF0ZWQgd2l0aCBhbiBpbmNyZWFzZWQgcmlzayBvZiBsb2FuIGRlZmF1bHQuIFRoaXMgc2hvdWxkIGV2ZW50dWFsbHkgYXNzaXN0IGxlbmRlcnMgaW4gbWFraW5nIHVuLWJpYXNlZCBjcmVkaXQgZGVjaXNpb25zIG9yIHRvIGF1dG9tYXRlIHRoZSBjcmVkaXQgYXBwcm92YWwgcHJvY2Vzcy4gQWNoaWV2aW5nIHRoaXMgcmVxdWlyZXMgYWNjZXNzIHRvIGV4dGVuc2l2ZSBkYXRhIG9uIHByZXZpb3VzIGJhbmsgbG9hbnMgKGEgd29yZCBvZiBjYXV0aW9uOiB0aGUgZGF0YSBtYXkgY29udGFpbiBpbmhlcmVudCBiaWFzIHdoaWNoIHdvdWxkIHRoZW4gcHJvcGFnYXRlIHRvIHRoZSBtb2RlbCksIGNvdXBsZWQgd2l0aCBkZXRhaWxlZCBpbmZvcm1hdGlvbiBhYm91dCB0aGUgYXBwbGljYW50cy4KClRoaXMgd29ya2VkIGV4YW1wbGUgdXNlcyB0aGUgZGF0YXNldCBjb250cmlidXRlZCB0byB0aGUgW1VDSSBNYWNoaW5lIExlYXJuaW5nIFJlcG9zaXRvcnldKGh0dHA6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sKSBieSBIYW5zIEhvZm1hbm4gb2YgdGhlIFVuaXZlcnNpdHkgb2YgSGFtYnVyZyAoSG9mbWFubiwgMTk5NCkuIFRoaXMgZGF0YXNldCBpbmNsdWRlcyBjb21wcmVoZW5zaXZlIGluZm9ybWF0aW9uIG9uIGxvYW5zIHNvdXJjZWQgZnJvbSBhIGNyZWRpdCBhZ2VuY3kgaW4gR2VybWFueSwgc28gdGhlIGFwcGxpY2FiaWxpdHkgb2YgYW55IG1vZGVsIG1pZ2h0IGJlIGxpbWl0ZWQgdG8gYXBwbGljYW50cyBmcm9tIEdlcm1hbnkgLS0gd2UgbmVlZCB0byBiZSBjYXV0aW91cyBpbiBnZW5lcmFsaXppbmcgbW9kZWxzIGFzIHRoZXkgYXJlIGJhc2VkIG9uIHNwZWNpZmljIHRyYWluaW5nIGRhdGEuCgoqKlVSTCBmb3IgZGF0YSBzZXQqKjogPGh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9kYXRhc2V0LzE0NC9zdGF0bG9nK2dlcm1hbitjcmVkaXQrZGF0YT4KClRoZSBjcmVkaXQgZGF0YXNldCBpcyBjb21wcmlzZWQgb2YgMSwwMDAgbG9hbiBkZWNpc2lvbnMgbWFkZSBieSBsb2FuIG9mZmljZXJzLCBhbG9uZyB3aXRoIGEgc2V0IG9mIG51bWVyaWMgYW5kIG5vbWluYWwgZmVhdHVyZXMgdGhhdCBkZXNjcmliZSB0aGUgY2hhcmFjdGVyaXN0aWNzIG9mIGJvdGggdGhlIGxvYW5zIGFuZCB0aGUgYXBwbGljYW50cy4gQSAoYmluYXJ5KSBjbGFzcyB2YXJpYWJsZSBzcGVjaWZpZXMgd2hldGhlciBlYWNoIGxvYW4gcmVzdWx0ZWQgaW4gZGVmYXVsdC4gT3VyIGdvYWwgaXMgdG8gYW5hbHl6ZSB0aGlzIGRhdGEgdG8gdW5jb3ZlciBwYXR0ZXJucyB0aGF0IG1pZ2h0IGJlIGFibGUgdG8gcHJlZGljdCBsb2FuIGRlZmF1bHRzIGFuZCB0aHVzIGFzc2lzdCBhIGxvYW4gb2ZmaWNlciBpcyBtYWtpbmcgYSBjcmVkaXQgZGVjaXNpb24sICppLmUuKiwgYW4gYXBwbGljYW50IHdobyBpcyBsaWtlbHkgZ29pbmcgdG8gZGVmYXVsdCBtaWdodCBub3QgYmUgYSBnb29kIGNyZWRpdCBjYW5kaWRhdGUgb3IsIGFsdGVybmF0aXZlbHksIHdvdWxkIHJlcXVpcmUgYWRqdXN0ZWQgbGVuZGluZyBwYXJhbWV0ZXJzIHN1Y2ggYXMgYSBzdWJzdGFudGlhbGx5IGhpZ2hlciBpbnRlcmVzdCByYXRlIGFuZCBhIHNob3J0ZXIgcmVwYXltZW50IHBlcmlvZC4KCiMjIFdvcmtmbG93CgpTZXQgdXAgeW91ciBvd24gcHJvamVjdCBpbiBSIFN0dWRpbyBhbmQgY3JlYXRlIGEgbmV3IFIgTm90ZWJvb2sgaW4gd2hpY2ggdG8gZG8geW91ciB3b3JrIGFuZCBmb2xsb3cgYWxvbmcuIEV4cGVyaW1lbnQgd2l0aCB0aGUgY29kZSwgdHJ5IGFkZGl0aW9uYWwgdGVjaG5pcXVlcy4gVGhlIGtleSBpcyB0byB0eXBlIHRoZSBjb2RlIHlvdXJzZWxmIGFuZCBub3Qgc2ltcGx5IGNvcHkgYW5kIHBhc3RlLiBZb3UgbGVhcm4gYnkgZG9pbmchCgojIyMgRGF0YSBMb2FkaW5nCgpXZSB3aWxsIGxvYWQgdGhlIGRhdGEgc2V0IGRpcmVjdGx5IGZyb20gaXRzIFVSTCB1c2luZyB0aGUgKip1dGlscyoqIHBhY2thZ2UsIGFsdGhvdWdoIGRvd25sb2FkaW5nIHRvIGEgcHJvamVjdCBmb2xkZXIgbWlnaHQgYmUgYmVzdCB3aGVuIGluc3BlY3RpbmcgdGhlIGRhdGEgc2V0LiBUaGUgY29kZSBiZWxvdyBhbHNvIHNldHMgY29sdW1uIGhlYWRlcnMgYXMgdGhlIGRhdGEgc2V0IGRvZXMgbm90IGhhdmUgdGhvc2UuIEEgZGF0YSBkaWN0aW9uYXJ5IGlzIHByb3ZpZGVkIGF0IHRoZSBhYm92ZSBVUkwuCgpgYGB7cn0KbGlicmFyeSh1dGlscykKCiMgVVJMIG9mIHRoZSBkYXRhc2V0CnVybCA8LSAiaHR0cHM6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sL21hY2hpbmUtbGVhcm5pbmctZGF0YWJhc2VzL3N0YXRsb2cvZ2VybWFuL2dlcm1hbi5kYXRhIgoKIyBkZWZpbmUgdGhlIGNvbHVtbiBuYW1lcyBmb3IgdGhlIGRhdGFzZXQgKGFzIHRoZSBkYXRhc2V0IGRvZXMgbm90IGNvbnRhaW4gaGVhZGVycykKY29sX25hbWVzIDwtIGMoIlN0YXR1cy5vZi5leGlzdGluZy5jaGVja2luZy5hY2NvdW50IiwgIkR1cmF0aW9uLmluLm1vbnRoIiwgIkNyZWRpdC5oaXN0b3J5IiwKICAgICAgICAgICAgICAgIlB1cnBvc2UiLCAiQ3JlZGl0LmFtb3VudCIsICJTYXZpbmdzLmFjY291bnQuYm9uZHMiLCAiUHJlc2VudC5lbXBsb3ltZW50LnNpbmNlIiwKICAgICAgICAgICAgICAgIkluc3RhbGxtZW50LnJhdGUuaW4ucGVyY2VudGFnZS5vZi5kaXNwb3NhYmxlLmluY29tZSIsICJQZXJzb25hbC5zdGF0dXMuYW5kLnNleCIsCiAgICAgICAgICAgICAgICJPdGhlci5kZWJ0b3JzLmd1YXJhbnRvcnMiLCAiUHJlc2VudC5yZXNpZGVuY2Uuc2luY2UiLCAiUHJvcGVydHkiLCAiQWdlLmluLnllYXJzIiwKICAgICAgICAgICAgICAgIk90aGVyLmluc3RhbGxtZW50LnBsYW5zIiwgIkhvdXNpbmciLCAiTnVtYmVyLm9mLmV4aXN0aW5nLmNyZWRpdHMuYXQudGhpcy5iYW5rIiwKICAgICAgICAgICAgICAgIkpvYiIsICJOdW1iZXIub2YucGVvcGxlLmJlaW5nLmxpYWJsZS50by5wcm92aWRlLm1haW50ZW5hbmNlLmZvciIsICJUZWxlcGhvbmUiLAogICAgICAgICAgICAgICAiRm9yZWlnbi53b3JrZXIiLCAiQ2xhc3MiKQoKIyBmZXRjaCBhbmQgcmVhZCB0aGUgZGF0YXNldApjcmVkaXQuZGYgPC0gcmVhZC5jc3YodXJsLCAKICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IEZBTFNFLCBzZXAgPSAiICIsIAogICAgICAgICAgICAgICAgICAgICAgY29sLm5hbWVzID0gY29sX25hbWVzKQoKIyBEaXNwbGF5IHRoZSBmaXJzdCBmZXcgcm93cyBvZiB0aGUgZGF0YXNldCB0byBlbnN1cmUgaXQgd2FzIGxvYWRlZApoZWFkKGNyZWRpdC5kZlssYygyLDUsOCwxMSwyMSldLCA0KQoKYGBgCgpgYGB7ciBlY2hvPUZ9CmRmLm9yaWcgPC0gY3JlZGl0LmRmCmBgYAoKIyMjIERhdGEgSW5zcGVjdGlvbgoKYGBge3IgZWNobyA9IFR9CmQgPC0gbmNvbChjcmVkaXQuZGYpCm4gPC0gbnJvdyhjcmVkaXQuZGYpCmBgYAoKVGhlIGRhdGEgc2V0IGNvbnRhaW5zIGByIG5gIHJvd3MgKG9ic2VydmF0aW9ucy9jYXNlcykgYW5kIGByIGRgIGRpbWVuc2lvbnMgKGZlYXR1cmVzKS4gVGhlIGxvYW4gYW1vdW50cyBpbiBhcmUgKkRNKiAoYXMgdGhpcyBkYXRhIHNldCB3YXMgY29sbGVjdGVkIHByaW9yIHRvIEdlcm1hbnkgYWRvcHRpbmcgdGhlIEV1cm8pLiBUaGUgInRhcmdldCB2YXJpYWJsZSIgaXMgdGhlIGNvbHVtbiAqQ2xhc3MqLiBBICIxIiBpbmRpY2F0ZXMgdGhhdCB0aGUgYm9ycm93ZXIgZGlkIG5vdCBkZWZhdWx0IHdoaWxlIGEgIjIiIGluZGljYXRlcyB0aGF0IHRoZXkgZGlkIGRlZmF1bHQuCgpgYGB7cn0KdCA8LSB0YWJsZShjcmVkaXQuZGYkQ2xhc3MpCnByaW50KHQpCmBgYAoKSW4geW91ciBvd24gY29kZSwgaW5zcGVjdCBhZGRpdGlvbmFsIGZlYXR1cmVzLiBXaGljaCBvbmVzIGFyZSBudW1lcmljPyBXaGljaCBvbmVzIGFyZSBjYXRlZ29yaWNhbD8gV2hpY2ggZmVhdHVyZXMgbWlnaHQgYmUgbGlrZWx5IHByZWRpY3RvcnM/CgojIyMgRGF0YSBQcmVwYXJhdGlvbgoKVGhlIGZpcnN0IHN0ZXAgaXMgdG8gcmV2aXNlIHRoZSBlbmNvZGluZyBvZiB0aGUgIkNsYXNzIiB0YXJnZXQgdmFyaWFibGUgdG8gInllcyIgKGRlZmF1bHQpIGFuZCAibm8iIChubyBkZWZhdWx0KSByYXRoZXIgdGhhbiB0aGUgdmFsdWVzIDIgYW5kIDEuIFRoaXMgaXMgcHVyZWx5IGNvc21ldGljIGFuZCBmb3IgcmVhZGFiaWxpdHkgYW5kIGhhcyBubyBpbXBhY3Qgb24gdGhlIG1vZGVsIGNvbnN0cnVjdGlvbi4KCmBgYHtyfQpjcmVkaXQuZGYkQ2xhc3MgPC0gaWZlbHNlKGNyZWRpdC5kZiRDbGFzcyA9PSAxLCAibm8iLCAieWVzIikKYGBgCgpUaGVyZSBhcmUgYHIgYXMubnVtZXJpYyh0WzFdKWAgY2xhc3NpZmllZCBhcyAibm8iIG91dCBvZiBgciBuYCBjcmVkaXQgdHJhbnNhY3Rpb25zLCBtYWtpbmcgdGhlIG92ZXJhbGwgZGVmYXVsdCByYXRlIGByIHJvdW5kKGFzLm51bWVyaWModFsyXSkvKGFzLm51bWVyaWModFsxXSkrYXMubnVtZXJpYyh0WzJdKSkqMTAwLDEpYCUuCgojIyMgU2FtcGxpbmcgVHJhaW5pbmcgRGF0YQoKV2Ugd2lsbCB0cmFpbiB0aGUgbW9kZWwgb24gYSByYW5kb20gc2FtcGxlIG9mIHRoZSBjYXNlcyBpbiB0aGUgZGF0YSBzZXQsIGxlYXZpbmcgdGhlIHJlbWFpbmRlciBmb3IgbW9kZWwgdmFsaWRhdGlvbi4gQSByYW5kb20gc2FtcGxlIGludm9sdmVzIHNlbGVjdGluZyBhIHN1YnNldCBvZiByZWNvcmRzIHJhbmRvbWx5IGZyb20gYSBsYXJnZXIgZGF0YXNldC4gSW4gUiwgdGhpcyBjYW4gYmUgYWNjb21wbGlzaGVkIHVzaW5nIHRoZSBgc2FtcGxlKClgIGZ1bmN0aW9uIG9yIHRoZSBgcnVuaWYoKWAgZnVuY3Rpb24gdG8gZ2VuZXJhdGUgYSBzZXF1ZW5jZSBvZiByYW5kb20gbnVtYmVycyBmb2xsb3dpbmcgYSB1bmlmb3JtIGRpc3RyaWJ1dGlvbi4gVGhlcmUgYXJlIGFkZGl0aW9uYWwgZnVuY3Rpb25zIHRoYXQgcHJvZHVjZSByYW5kb20gbnVtYmVycyB0aGF0IGZvbGxvdyBvdGhlciBkaXN0cmlidXRpb25zIHN1Y2ggYXMgdGhlIE5vcm1hbCAoR2F1c3NpYW4pIERpc3RyaWJ1dGlvbi4KCkl0IGlzIGEgY29tbW9uIHByYWN0aWNlIHRvIHNldCBhIHNlZWQgdmFsdWUgYmVmb3JlaGFuZCB1c2luZyB0aGUgYHNldC5zZWVkKClgIGZ1bmN0aW9uLiBTZXR0aW5nIGEgc2VlZCBlbnN1cmVzIHRoYXQgdGhlIHJhbmRvbWl6YXRpb24gcHJvY2VzcyBmb2xsb3dzIGEgcmVwcm9kdWNpYmxlIHNlcXVlbmNlLiBXaGlsZSB0aGlzIG1pZ2h0IHNlZW0gY291bnRlci1pbnR1aXRpdmUgZm9yIGdlbmVyYXRpbmcgcmFuZG9tIG51bWJlcnMsIGl0IHNlcnZlcyBhbiBpbXBvcnRhbnQgcHVycG9zZS4gQnkgc3BlY2lmeWluZyBhIHNlZWQgdmFsdWUsIHRoZSBhbmFseXNpcyBjYW4gYmUgcmVwZWF0ZWQgaW4gdGhlIGZ1dHVyZSB3aXRoIGlkZW50aWNhbCByZXN1bHRzLCBlbnN1cmluZyBjb25zaXN0ZW5jeSBhbmQgcmVwcm9kdWNpYmlsaXR5IGluIHRoZSBmaW5kaW5ncy4KClRoZSBjb2RlIGJlbG93IGdlbmVyYXRlcyBhIHJhbmRvbSBzYW1wbGUgKHdpdGhvdXQgcmVwbGFjZW1lbnQsICppLmUuKiwgYW55IG9ic2VydmF0aW9uIGFwcGVhcnMgb25jZSBpbiBlaXRoZXIgdGhlIHRyYWluaW5nIGRhdGEgb3IgdGhlIHZhbGlkYXRpb24gZGF0YSwgYnV0IG5ldmVyIGluIGJvdGggYW5kIG5ldmVyIG1vcmUgdGhhbiBvbmNlIGluIGVpdGhlcikuIFdlIGFyZSBub3Qgc3BlY2lmeWluZyBhIHByb2JhYmlsaXR5IGRpc3RyaWJ1dGlvbiwgc28gZXZlcnkgb2JzZXJ2YXRpb24gaGFzIHRoZSBzYW1lIGNoYW5jZSBvZiBhcHBlYXJpbmcgaW4gdGhlIHRyYWluaW5nIGRhdGEuIFdlIGFyZSBzZWxlY3RpbmcgOTAlIG9mIHRoZSBkYXRhIGZvciB0cmFpbmluZyBhbmQgdGhlIHJlc3QgZm9yIHZhbGlkYXRpb24uCgpOb3RlIHRoYXQgeW91ciBzYW1wbGUgbWlnaHQgYmUgZGlmZmVyZW50IGlmIHlvdSBhcmUgdXNpbmcgYSBkaWZmZXJlbnQgdmVyc2lvbiBvZiBSIHRoYXQgd2FzIHVzZWQgd2hlbiBnZW5lcmF0aW5nIHRoaXMgbGVzc29uOyB0aGlzIHdpbGwgYWxzbyBhZmZlY3QgdGhlIHJlc3VsdHMgZm9yIHRoZSByZW1haW5kZXIgb2YgdGhlIGxlc3Nvbi4gSG93ZXZlciwgdGhlIGFuYWx5c2lzIGFuZCB0aGUgbW9kZWwgYXJlIGp1c3QgYXMgdmFsaWQuCgpgYGB7cn0Kc2V0LnNlZWQoNjE3KQoKbiA8LSBucm93KGNyZWRpdC5kZikKdHJhaW4uZGF0YSA8LSBzYW1wbGUobiwgbiAqIDAuOSwgcmVwbGFjZSA9IEZBTFNFKQoKdHJhaW4uZGYgPC0gY3JlZGl0LmRmW3RyYWluLmRhdGEsXQp2YWwuZGYgPC0gY3JlZGl0LmRmWy10cmFpbi5kYXRhLF0KYGBgCgpJZiB0aGUgc2VsZWN0aW9uIHdhcyBkb25lIGF0IHJhbmRvbSwgd2Ugd291bGQgZXhwZWN0IHRoZSBzYW1lIHByb3BvcnRpb24gb2YgZGVmYXVsdHMgdG8gYmUgYXBwZWFyIGluIHRoZSB0cmFpbmluZyBhbmQgdGhlIHZhbGlkYXRpb24gZGF0YSBzZXRzIC0tIGFuZCB0aGV5IHNob3VsZCBiZSBhYm91dCAzMCUgYXMgdGhhdCB3YXMgdGhlIHByb3BvcnRpb24gb2YgZGVmYXVsdHMgaW4gdGhlIG9yaWdpbmFsIGRhdGEgc2V0LgoKKipUcmFpbmluZyBEYXRhIFNldDogUHJvcG9ydGlvbiBvZiBkZWZhdWx0cyoqCgpgYGB7cn0KcHJvcC50YWJsZSh0YWJsZSh0cmFpbi5kZiRDbGFzcykpCmBgYAoKKipWYWxpZGF0aW9uIERhdGEgU2V0OiBQcm9wb3J0aW9uIG9mIGRlZmF1bHRzKioKCmBgYHtyfQpwcm9wLnRhYmxlKHRhYmxlKHZhbC5kZiRDbGFzcykpCmBgYAoKQm90aCB0aGUgdHJhaW5pbmcgYW5kIHRlc3QgZGF0YXNldHMgZXhoaWJpdCBzaW1pbGFyIHByb3BvcnRpb25zIG9mIGxvYW4gZGVmYXVsdHMsIGFsbG93aW5nIHVzIHRvIHByb2NlZWQgd2l0aCBidWlsZGluZyBvdXIgZGVjaXNpb24gdHJlZS4gSWYgdGhlIHByb3BvcnRpb25zIGhhZCBkaWZmZXJlZCBzaWduaWZpY2FudGx5LCB3ZSBtaWdodCBoYXZlIG5lZWRlZCB0byByZXNhbXBsZSB0aGUgZGF0YXNldCBvciBlbXBsb3kgYSBtb3JlIHNvcGhpc3RpY2F0ZWQgc2FtcGxpbmcgYXBwcm9hY2ggdG8gZW5zdXJlIGJhbGFuY2VkIHJlcHJlc2VudGF0aW9uLCBzdWNoIGFzIGEgc3RyYXRpZmllZCBzYW1wbGUgb3IgdXNpbmcgKmsqLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiByYXRoZXIgdGhhbiBhIGRlZGljYXRlZCB2YWxpZGF0aW9uIGRhdGEgc2V0LgoKQSBjb21tb24gcHJvYmxlbSBvZiByYW5kb20gc2FtcGxpbmcgaXMgY2xhc3MgaW1iYWxhbmNlLiBXaGF0IGlmIHNvbWUgbGV2ZWwgb2YgdGhlIHRhcmdldCB2YXJpYWJsZSBvbmx5IGFwcGVhcnMgYSBmZXcgdGltZXMgaW4gdGhlIGRhdGEgc2V0IGFuZCB0aGUgcmFuZG9tIHNhbXBsZSBpcyBzdWNoIHRoYXQgYWxsIGluc3RhbmNlcyBvY2N1ciBpbiBvbmx5IHRoZSB2YWxpZGF0aW9uIGRhdGEgLS0gdGhlbiB0aGUgbW9kZWwgd2lsbCBuZXZlciBiZSB0cmFpbmVkIG9uIHRob3NlLiBUaGlzIHdpbGwgb2Z0ZW4gY2F1c2UgYW4gZXJyb3Igd2hlbiBtYWtpbmcgYSBwcmVkaWN0aW9uLiBJbiB0aG9zZSBzaXR1YXRpb25zLCBzdGFydGlmaWVkIHNhbXBsaW5nIG9yIHNhbXBsaW5nIHdpdGggcmVwbGFjZW1lbnQgKG92ZXJzYW1wbGluZykgbWF5IGJlIGJldHRlci4KCiMjIyMgUmFuZG9tIE51bWJlciBHZW5lcmF0aW9uCgpMZXQncyB0YWtlIGEgc2hvcnQgZGV0b3VyIG9uIHJhbmRvbSBudW1iZXJzIGluIGNvbXB1dGVycywgYXMgcmFuZG9tIHNlbGVjdGlvbiBpcyBjb21tb24gaW4gbWFueSBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobXMuIFRoZSBxdWVzdGlvbiBpczogaG93IGRvIGNvbXB1dGVycyBnZW5lcmF0ZSByYW5kb20gbnVtYmVycz8KCkNvbXB1dGVycyBnZW5lcmF0ZSByYW5kb20gbnVtYmVycyB1c2luZyBhbGdvcml0aG1zIGNhbGxlZCBwc2V1ZG8tcmFuZG9tIG51bWJlciBnZW5lcmF0b3JzIChQUk5HcykuIFdoaWxlIHRoZXNlIG51bWJlcnMgYXJlIG5vdCB0cnVseSByYW5kb20sIHRoZXkgYXJlIHN1ZmZpY2llbnQgZm9yIG1vc3QgcHJhY3RpY2FsIHB1cnBvc2VzIGJlY2F1c2UgdGhleSBleGhpYml0IHByb3BlcnRpZXMgb2YgcmFuZG9tbmVzcyBhbmQgY2Fubm90IGJlIHByZWRpY3RlZCB1bmxlc3Mgb25lIGtub3dzIHRoZSBhbGdvcml0aG0gYW5kIHRoZSBzdGFydGluZyBjb25kaXRpb25zIG9mIHRoZSBhbGdvcml0aG0uCgoqKipZb3UgbWF5IChzYWZlbHkpIHNraXAgdGhlIHJlbWFpbmRlciBvZiB0aGlzIHNlY3Rpb24gaWYgeW91IGFyZSBub3QgaW50ZXJlc3RlZCBpbiBob3cgInJhbmRvbSBudW1iZXIgZ2VuZXJhdGlvbiIgd29ya3MuKioqCgpBIFBSTkcgc3RhcnRzIHdpdGggYW4gaW5pdGlhbCB2YWx1ZSBjYWxsZWQgYSAqc2VlZC4qIFRoZSBzZWVkIGNhbiBiZSBhbnkgbnVtYmVyLCBidXQgaXQncyBvZnRlbiBkZXJpdmVkIGZyb20gYSB2YXJpYWJsZSBzb3VyY2Ugc3VjaCBhcyB0aGUgY3VycmVudCB0aW1lIG9yIHBlcmhhcHMgc29tZSBleHRlcm5hbCBzZW5zb3IgdGhhdCBkZXRlY3RzIHNvbWUgbmF0dXJhbCBwaGVub21lbm9uIHRoYXQgaXMgdW5wcmVkaWN0YWJsZS4gT25jZSBzZXQsIHRoZSBzZWVkIGFsbG93cyB0aGUgUFJORyB0byBwcm9kdWNlIGEgcmVwcm9kdWNpYmxlIHNlcXVlbmNlIG9mIG51bWJlcnMgdGhhdCBhcHBlYXJzIHRvIGJlIHJhbmRvbS4gTmF0dXJhbGx5LCB0aGUgUFJORyB1c2VzIGEgZGV0ZXJtaW5pc3RpYyBhbGdvcml0aG0gdG8gZ2VuZXJhdGUgYSBzZXF1ZW5jZSBvZiBudW1iZXJzLCBzbyB0aGUgbnVtYmVycyBhcmUgcmVhbGx5IHJhbmRvbSBidXQgZXhoaWJpdCB0aGUgcHJvcGVydGllcyBvZiByYW5kb20gbnVtYmVycy4gQ29tbW9uIGFsZ29yaXRobXMgaW5jbHVkZSB0aGUgTGluZWFyIENvbmdydWVudGlhbCBHZW5lcmF0b3IgKExDRyksIHRoZSBNZXJzZW5uZSBUd2lzdGVyLCBhbmQgdGhlIFhvcnNoaWZ0IGFsZ29yaXRobS4KCioqTGluZWFyIENvbmdydWVudGlhbCBHZW5lcmF0b3IgKExDRykqKjoKCkFuIExDRyBwcm9kdWNlcyBudW1iZXJzIGJhc2VkIG9uIHRoZSBmb3JtdWxhOgoKJCQKICAgWF97bisxfSA9IChhWF9uICsgYykgXG1vZCBtCiQkCgp3aGVyZToKCi0gICAkWCQgaXMgdGhlIHNlcXVlbmNlIG9mIHBzZXVkb3JhbmRvbSB2YWx1ZXMsCi0gICAkYSQsICRjJCwgYW5kICRtJCBhcmUgY29uc3RhbnRzLAotICAgJFhfMCQgaXMgdGhlIGluaXRpYWwgc2VlZC4KCioqTWVyc2VubmUgVHdpc3RlcioqOgoKVGhlIE1lcnNlbm5lIFR3aXN0ZXIgaXMgd2lkZWx5IHVzZWQgYmVjYXVzZSBvZiBpdHMgbG9uZyBwZXJpb2QgYW5kIGhpZ2gtcXVhbGl0eSByYW5kb21uZXNzLiBJdCBnZW5lcmF0ZXMgbnVtYmVycyB3aXRoIGEgcGVyaW9kIG9mICQyXnsxOTkzN30gLSAxJCwgZW5zdXJpbmcgdGhhdCB0aGUgc2VxdWVuY2UgZG9lc24ndCByZXBlYXQgZm9yIGEgdmVyeSBsb25nIHRpbWUuCgoqKlhvcnNoaWZ0Kio6CgpYb3JzaGlmdCBhbGdvcml0aG1zIHVzZSBiaXR3aXNlIG9wZXJhdGlvbnMgdG8gZ2VuZXJhdGUgcmFuZG9tIG51bWJlcnMuIFRoZXNlIGFyZSBrbm93biBmb3IgYmVpbmcgZmFzdCBhbmQgaGF2aW5nIGdvb2Qgc3RhdGlzdGljYWwgcHJvcGVydGllcy4KCkZvciBhcHBsaWNhdGlvbnMgcmVxdWlyaW5nIHRydWUgcmFuZG9tbmVzcywgc3VjaCBhcyBjcnlwdG9ncmFwaHksIHBzZXVkb3JhbmRvbSBudW1iZXJzIG1heSBub3QgYmUgc3VmZmljaWVudC4gSW5zdGVhZCwgVHJ1ZSBSYW5kb20gTnVtYmVyIEdlbmVyYXRvcnMgKFRSTkdzKSBhcmUgdXNlZC4gVFJOR3MgZ2VuZXJhdGUgbnVtYmVycyBiYXNlZCBvbiBwaHlzaWNhbCBwcm9jZXNzZXMgYW5kIG5hdHVyYWwgcGhlbm9tZW5hIHRoYXQgYXJlIGluaGVyZW50bHkgcmFuZG9tLCBzdWNoIGFzOiBtZWFzdXJpbmcgdGhlIHRoZXJtYWwgbm9pc2UgaW4gZWxlY3Ryb25pYyBjaXJjdWl0cywgbW9uaXRvcmluZyB0aGUgZGVjYXkgb2YgcmFkaW9hY3RpdmUgbWF0ZXJpYWxzLCBvciBvYnNlcnZpbmcgdGhlIGJlaGF2aW9yIG9mIHBob3RvbnMgcGFzc2luZyB0aHJvdWdoIGEgc2VtaS10cmFuc3BhcmVudCBtaXJyb3IuIFRSTkdzIGFyZSBnZW5lcmFsbHkgc2xvd2VyLCByZXF1aXJlIGFuIGV4dGVybmFsIHNlbnNvciwgYW5kIGFyZSB0aHVzIG1vcmUgZXhwZW5zaXZlIHRoYW4gUFJOR3MgYnV0IHByb3ZpZGUgdHJ1ZSByYW5kb21uZXNzLgoKIyMjIE1vZGVsIFRyYWluaW5nCgpXZSB3aWxsIGVtcGxveSB0aGUgQzUuMCBhbGdvcml0aG0gZnJvbSB0aGUgKipDNTAqKiBwYWNrYWdlIHRvIHRyYWluIG91ciBkZWNpc2lvbiB0cmVlIG1vZGVsLiBJZiB5b3UgaGF2ZW4ndCBhbHJlYWR5IGluc3RhbGxlZCB0aGUgcGFja2FnZSwgeW91IGNhbiBkbyBzbyBieSBydW5uaW5nIGBpbnN0YWxsLnBhY2thZ2VzKCJDNTAiKWAgb3IgdXNpbmcgdGhlIGludGVyZmFjZSBvZiBSIFN0dWRpby4gQWZ0ZXIgaW5zdGFsbGF0aW9uLCBsb2FkIHRoZSBwYWNrYWdlIGludG8geW91ciBSIHNlc3Npb24gd2l0aCBgbGlicmFyeShDNTApYC4gVGhlIGNvZGUgYmVsb3cgY2hlY2tzIG9mIHRoZSBwYWNrYWdlIGlzIGluc3RhbGxlZCwgYW5kLCBpZiBub3QsIHdpbGwgaW5zdGFsbCB0aGUgcGFja2FnZS4KCmBgYHtyIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQpwYWNrYWdlX25hbWUgPC0gIkM1MCIKCmlmICghcmVxdWlyZShwYWNrYWdlX25hbWUsIAogICAgICAgICAgICAgY2hhcmFjdGVyLm9ubHkgPSBULCAKICAgICAgICAgICAgIHdhcm4uY29uZmxpY3RzID0gRiwKICAgICAgICAgICAgIHF1aWV0bHkgPSBUKSkgewogICAgaW5zdGFsbC5wYWNrYWdlcyhwYWNrYWdlX25hbWUsIGRlcGVuZGVuY2llcyA9IFRSVUUpCn0KCmxpYnJhcnkoQzUwKQpgYGAKClRoZSBtb2RlbCBpcyBidWlsdCB1c2luZyB0aGUgZnVuY3Rpb24gYEM1LjAoKWAuIFRoaXMgZnVuY3Rpb24gaGFzIGZpdmUga2V5IGFyZ3VtZW50czoKCi0gICAqeCo6IGEgZGF0YSBmcmFtZSBvZiB0aGUgdHJhaW5pbmcgZmVhdHVyZXMKLSAgICp5KjogYSBmYWN0b3IgdmVjdG9yIHdpdGggdHdvIG9yIG1vcmUgbGV2ZWxzIG9mIHRoZSBjb3JyZXNwb25kaW5nIHRhcmdldCBjbGFzc2VzCi0gICAqdHJpYWxzKjogYW4gb3B0aW9uYWwgYXJndW1lbnQgaW5kaWNhdGluZyB0aGUgbnVtYmVyIG9mIGJvb3N0aW5nIGl0ZXJhdGlvbnMgdGhhdCBhcmUgYXBwbGllZCAoZGVmYXVsdCBpcyAxKQotICAgKmNvc3RzKjogYSBtYXRyaXggb2YgY29zdHMgYXNzb2NpYXRlZCB3aXRoIHBvc3NpYmxlIGVycm9yczsgdGhlIG1hdHJpeCBtdXN0IGhhdmUgKkMqIGNvbHVtbnMgYW5kIHJvd3Mgd2hlcmUgKkMqIGlzIHRoZSBudW1iZXIgb2YgbGV2ZWxzIGluIHRoZSBmYWN0b3IgdmVjdG9yIGZvciAqeSoKLSAgICpydWxlcyo6IGEgbG9naWNhbCBhcmd1bWVudCBpbmRpY2F0aW5nIHdoZXRoZXIgdGhlIHRyZWUgc2hvdWxkIGJlIGRlY29tcG9zZWQgaW50byBydWxlcwoKV2UgYXJlIGZpcnN0IGNvbnZlcnRpbmcgdGhlIHRhcmdldCB2YXJpYWJsZSBmcm9tICJjaGFyYWN0ZXIiIHRvIGEgZmFjdG9yIGFzIHRoYXQgaXMgd2hhdCB0aGUgYEM1LjAoKWAgZnVuY3Rpb24gZXhwZWN0cy4gT2YgY291cnNlLCBpdCBtYXkgaGF2ZSBiZWVuIGJldHRlciB0byBoYXZlIGRvbmUgdGhlIGNvbnZlcnNpb24gYmVmb3JlIHNwbGl0dGluZy4KCmBgYHtyfQp0cmFpbi5kZiRDbGFzcyA8LSBhcy5mYWN0b3IodHJhaW4uZGYkQ2xhc3MpCnZhbC5kZiRDbGFzcyA8LSBhcy5mYWN0b3IodmFsLmRmJENsYXNzKQpgYGAKCk5leHQsIHdlIGFyZSB0cmFpbmluZyBhIGRlY2lzaW9uIHRyZWUgbW9kZWwgdXNpbmcgdGhlIEM1LjAgYWxnb3JpdGhtIG9uIGFsbCBmZWF0dXJlcyxleGNlcHQgZm9yIGNvbHVtbiAyMSB3aGljaCBpcyB0aGUgdGFyZ2V0IHZhcmlhYmxlLiBCZSBzdXJlIG5vdCB0byBpbmNsdWRlIHRoZSBjb2x1bW4gb2YgdGhlIHRhcmdldCB2YXJpYWJsZSBpbiB0aGUgdHJhaW5pbmcgZmVhdHVyZXMgYXMgdGhhdCB3b3VsZCBjZXJ0YWlubHkgY2F1c2Ugb3ZlcmZpdHRpbmcuCgpgYGB7cn0KY3JlZGl0Lm1vZGVsIDwtIEM1LjAodHJhaW4uZGZbLTIxXSwgdHJhaW4uZGYkQ2xhc3MsIHRyaWFscyA9IDEsIHJ1bGVzID0gRikKYGBgCgpUaGUgImRlY2lzaW9uIHRyZWUiIG1vZGVsIGlzIG5vdyBhdmFpbGFibGUgZm9yIHByZWRpY3Rpb24uIFdlIGNhbiB2aWV3IHRoZSBtb2RlbCdzIGNoYXJhY3RlcmlzdGljcyBieSBwcmludGluZyBpdCBvdXQ6CgpgYGB7cn0KcHJpbnQoY3JlZGl0Lm1vZGVsKQpgYGAKClRoZSBhY3R1YWwgInRyZWUiIGNhbiBiZSB2aXN1YWxpemVkIC0gaW4gYSBydWRpbWVudGFyeSBmYXNoaW9uIC0tIHVzaW5nIHRoZSBgc3VtbWFyeSgpYCBmdW5jdGlvbi4gT25seSBhIHBhcnRpYWwgb3V0cHV0IG9mIHRoZSBtb2RlbCBpcyBzaG93bi4KCmBgYHtyIGV2YWw9Rn0Kc3VtbWFyeShjcmVkaXQubW9kZWwpCmBgYAoKYGBgICAgICAgICAgCkM1LjAgW1JlbGVhc2UgMi4wNyBHUEwgRWRpdGlvbl0gICAgIFN1biBKdW4gMjMgMTM6MDA6MzIgMjAyNAotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgpDbGFzcyBzcGVjaWZpZWQgYnkgYXR0cmlidXRlIGBvdXRjb21lJwoKUmVhZCA5MDAgY2FzZXMgKDIxIGF0dHJpYnV0ZXMpIGZyb20gdW5kZWZpbmVkLmRhdGEKCkRlY2lzaW9uIHRyZWU6CgpTdGF0dXMub2YuZXhpc3RpbmcuY2hlY2tpbmcuYWNjb3VudCBpbiB7QTE0LEExM306IG5vICg0MTYvNTYpClN0YXR1cy5vZi5leGlzdGluZy5jaGVja2luZy5hY2NvdW50IGluIHtBMTIsQTExfToKOi4uLkNyZWRpdC5oaXN0b3J5IGluIHtBMzAsQTMxfToKICAgIDouLi5Ib3VzaW5nID0gQTE1MTogeWVzICgxNSkKICAgIDogICBIb3VzaW5nIGluIHtBMTUyLEExNTN9OgogICAgOiAgIDouLi5PdGhlci5kZWJ0b3JzLmd1YXJhbnRvcnMgaW4ge0ExMDIsQTEwM306IG5vICgzKQogICAgOiAgICAgICBPdGhlci5kZWJ0b3JzLmd1YXJhbnRvcnMgPSBBMTAxOgogICAgOiAgICAgICA6Li4uSG91c2luZyA9IEExNTM6IHllcyAoMTEvMSkKICAgIDogICAgICAgICAgIEhvdXNpbmcgPSBBMTUyOgogICAgCiAgICAuLi4KYGBgCgpJbiB0ZXJtcyBvZiBkZWNpc2lvbiB0cmVlIGNvbnN0cnVjdGlvbiB3aXRoIEM1LjAsIHRoZSBudW1iZXJzIGluIHBhcmVudGhlc2VzIGluIHRoZSBzdW1tYXJ5IGFib3ZlIGluZGljYXRlIHRoZSBudW1iZXIgb2YgZXhhbXBsZXMgdGhhdCBtZWV0IHRoZSBjcml0ZXJpYSBmb3IgdGhhdCBkZWNpc2lvbiBhbmQgdGhlIG51bWJlciBpbmNvcnJlY3RseSBjbGFzc2lmaWVkIGJ5IHRoZSBkZWNpc2lvbi4gRm9yIGluc3RhbmNlLCBvbiB0aGUgZmlyc3QgbGluZSwgNDE2LzU2IG1lYW5zIHRoYXQgb3V0IG9mIHRoZSA0MTYgZXhhbXBsZXMgcmVhY2hpbmcgdGhhdCBkZWNpc2lvbiBwb2ludCwgNTYgd2VyZSBpbmNvcnJlY3RseSBjbGFzc2lmaWVkIGFzICJub3QgbGlrZWx5IHRvIGRlZmF1bHQuIiBUaGlzIGluZGljYXRlcyB0aGF0IDU2IGFwcGxpY2FudHMgYWN0dWFsbHkgZGVmYXVsdGVkIGRlc3BpdGUgdGhlIG1vZGVsJ3MgcHJlZGljdGlvbiB0byB0aGUgY29udHJhcnkuCgpPY2Nhc2lvbmFsbHksIGEgZGVjaXNpb24gdHJlZSBwcm9kdWNlcyBkZWNpc2lvbiBzZXF1ZW5jZXMgdGhhdCBtYXkgc2VlbSAob3IgYXJlKSBpbGxvZ2ljYWwuIEZvciBleGFtcGxlLCBpdCBtaWdodCBzdWdnZXN0IHRoYXQgYW4gYXBwbGljYW50IHdpdGggYSB2ZXJ5IGdvb2QgY3JlZGl0IGhpc3RvcnkgaXMgbGlrZWx5IHRvIGRlZmF1bHQsIHdoaWxlIHRob3NlIHdob3NlIGNoZWNraW5nIGJhbGFuY2UgaXMgdW5rbm93biBhcmUgbm90IGxpa2VseSB0byBkZWZhdWx0LiBUaGVzZSBjb250cmFkaWN0b3J5IHJ1bGVzIGNhbiBvY2N1ciBhbmQgbWF5IGVpdGhlciByZWZsZWN0IHJlYWwgcGF0dGVybnMgaW4gdGhlIGRhdGEgb3IgYmUgc3RhdGlzdGljYWwgYW5vbWFsaWVzLiBJdCBpcyBlc3NlbnRpYWwgdG8gaW52ZXN0aWdhdGUgc3VjaCB1bnVzdWFsIGJyYW5jaGVzIHRvIGRldGVybWluZSB3aGV0aGVyIHRoZSB0cmVlJ3MgbG9naWMgaXMgdmFsaWQgYW5kIGlzIHJlYXNvbmFibGUgZm9yIHRoZSBidXNpbmVzcyBkb21haW47IG5hdHVyYWxseSwgdGhpcyByZXF1aXJlcyBidXNpbmVzcyBzdWJqZWN0IG1hdHRlciBleHBlcnRpc2UuCgojIyMgTW9kZWwgRXZhbHVhdGlvbgoKV2hpbGUgdGhlIGBzdW1tYXJ5KClgIGZ1bmN0aW9uIG91dHB1dHMgYSAiY29uZnVzaW9uIG1hdHJpeCIsIHdlIGNhbiBnZW5lcmF0ZSBvbmUgZGlyZWN0bHkgYXMgd2VsbCBieSB1c2luZyB0aGUgZGVjaXNpb24gdHJlZSBtb2RlbCB0byBwcmVkaWN0IHRoZSB0YXJnZXQgdmFyaWFibGUgZm9yIGFsbCBvZiB0aGUgY2FzZXMgaW4gdGhlIHZhbGlkYXRpb24gZGF0YSBzZXQgd2hlcmUgd2Uga25vdyB0aGUgYWN0dWFsIG91dGNvbWUgLS0gdGhlIGNvbmZ1c2lvbiBtYXRyaXggdGhlbiBjb21wYXJlcyB0aGUgcHJlZGljdGVkIG91dHB1dCB0byB0aGUgYWN0dWFsIG9ic2VydmVkIG91dHB1dC4KCkluIHRoZSBjb2RlIGJlbG93LCB3ZSB1c2UgdGhlIGZ1bmN0aW9uIGBwcmVkaWN0KClgIHRvIG1ha2UgcHJlZGljdGlvbnMgdXNpbmcgdGhlIGZlYXR1cmVzIG9mIHRoZSB2YWxpZGF0aW9uIGRhdGEgc2V0IChtaW51cyB0aGUgdGFyZ2V0IGNvbHVtbiAiQ2xhc3MiIHdoaWNoIGlzIGNvbHVtbiAyMSkuCgpgYGB7cn0KY3JlZGl0LnByZWRzLnZhbCA8LSBwcmVkaWN0KGNyZWRpdC5tb2RlbCwgdmFsLmRmWy0yMV0pCmBgYAoKV2UgY2FuIG5vdyBjcmVhdGUgYSBjb25mdXNpb24gbWF0cml4IHVzaW5nIHRoZSBmdW5jdGlvbiBgY29uZnVzaW9uTWF0cml4KClgIGZyb20gdGhlICoqY2FyZXQqKiBwYWNrYWdlLCBjb21wYXJpbmcgdGhlIHByZWRpY3RlZCBsYWJlbHMgYWdhaW5zdCB0aGUgYWN0dWFsIG9ic2VydmVkIGxhYmVscyBmb3IgdGhlIHZhbGlkYXRpb24gZGF0YSBzZXQuCgpgYGB7ciB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KcGFja2FnZV9uYW1lIDwtICJjYXJldCIKCmlmICghcmVxdWlyZShwYWNrYWdlX25hbWUsIAogICAgICAgICAgICAgY2hhcmFjdGVyLm9ubHkgPSBULCAKICAgICAgICAgICAgIHdhcm4uY29uZmxpY3RzID0gRiwKICAgICAgICAgICAgIHF1aWV0bHkgPSBUKSkgewogICAgaW5zdGFsbC5wYWNrYWdlcyhwYWNrYWdlX25hbWUsIGRlcGVuZGVuY2llcyA9IFRSVUUpCn0KCmxpYnJhcnkoY2FyZXQpCmBgYAoKYGBge3J9CmNvbmYubWF0cml4LmM1MCA8LSBjb25mdXNpb25NYXRyaXgoY3JlZGl0LnByZWRzLnZhbCwgdmFsLmRmJENsYXNzKQpwcmludChjb25mLm1hdHJpeC5jNTApCmBgYAoKQWx0ZXJuYXRpdmVseSwgd2UgY291bGQgdXNlIHRoZSBmdW5jdGlvbiBcYENyb3NzVGFibGUoKSBmcm9tIHRoZSAqKmdtb2RlbHMqKiBwYWNrYWdlLiBBcyBhbHdheXMsIHdlIG5lZWQgdG8gY2hlY2sgZmlyc3QgaWYgdGhlIHBhY2thZ2UgaXMgaW5zdGFsbGVkLCBhbmQsIGlmIG5vdCwgaW5zdGFsbCBpdCBiZWZvcmUgbG9hZGluZyB0aGUgcGFja2FnZS4KCmBgYHtyIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQpwYWNrYWdlX25hbWUgPC0gImdtb2RlbHMiCgppZiAoIXJlcXVpcmUocGFja2FnZV9uYW1lLCAKICAgICAgICAgICAgIGNoYXJhY3Rlci5vbmx5ID0gVCwgCiAgICAgICAgICAgICB3YXJuLmNvbmZsaWN0cyA9IEYsCiAgICAgICAgICAgICBxdWlldGx5ID0gVCkpIHsKICAgIGluc3RhbGwucGFja2FnZXMocGFja2FnZV9uYW1lLCBkZXBlbmRlbmNpZXMgPSBUUlVFKQp9CgpsaWJyYXJ5KGdtb2RlbHMpCmBgYAoKYGBge3J9CkNyb3NzVGFibGUodmFsLmRmJENsYXNzLCBjcmVkaXQucHJlZHMudmFsLAogICAgICAgICAgICAgcHJvcC5jaGlzcSA9IEZBTFNFLCBwcm9wLmMgPSBGQUxTRSwgcHJvcC5yID0gRkFMU0UsCiAgICAgICAgICAgICBkbm4gPSBjKCdhY3R1YWwgZGVmYXVsdCcsICdwcmVkaWN0ZWQgZGVmYXVsdCcpKQpgYGAKCkZyb20gdGhlIGFib3ZlIHdlIGNhbiBpbmZlciB0aGF0IG91dCBvZiB0aGUgMTAwIGxvYW4gYXBwbGljYXRpb25zIGluIHRoZSB2YWxpZGF0aW9uIGRhdGEgc2V0LCBvdXIgQzUuMCBkZWNpc2lvbiB0cmVlIG1vZGVsIGNvcnJlY3RseSBwcmVkaWN0ZWQgdGhhdCA1NyBkaWQgbm90IGRlZmF1bHQgYW5kIDE2IGRpZCBkZWZhdWx0LCByZXN1bHRpbmcgaW4gYW4gYWNjdXJhY3kgb2YgNzMlICg1NyArIDE2IC8gMTAwKSBhbmQgYW4gZXJyb3IgcmF0ZSBvZiAyNyUgd2hlcmUgdGhlIG1vZGVsIGluY29ycmVjdGx5IHByZWRpY3RlZCB0aGUgYWN0dWFsIG9ic2VydmVkIG91dGNvbWUuIFdoaWxlIHRoaXMgaXMgYSBiaXQgd29yc2UgdGhhbiBleHBlY3RlZCBiYXNlZCBvbiB0aGUgMzAlIG9ic2VydmVkIGRlZmF1bHQgcmF0ZSBpbiB0aGUgdHJhaW5pbmcgZGF0YSwgaXQgaXMgbm90IHVuZXhwZWN0ZWQsIHNpbmNlIGEgbW9kZWwncyBhY3R1YWwgcGVyZm9ybWFuY2UgYWdhaW5zdCB1bnNlZW4gZGF0YSBpcyBhbG1vc3QgYWx3YXlzIHdvcnNlIHRoYW4gZXhwZWN0ZWQuCgpJdCBpcyBub3Rld29ydGh5IHRoYXQgdGhlIG1vZGVsIHByZWRpY3RlZCBvbmx5IDE2IG91dCBvZiB0aGUgYWN0dWFsIDI4IGRlZmF1bHRzIGNvcnJlY3RseSwgd2hpY2ggYW1vdW50cyB0byBhIGNvcnJlY3QgcHJlZGljdGlvbiBvZiBsb2FuIGRlZmF1bHRzIG9mIDU3JSAtLSBvbmx5IGEgYml0IGJldHRlciB0aGFuIGEgcmFuZG9tIGd1ZXNzLiBUaGF0IGlzIG5vdCB2ZXJ5IGVuY291cmFnaW5nIGFuZCBpdCBpcyB3b3J0aCBpbnZlc3RpZ2F0aW5nIHRvIHNlZSBpZiBhIGJldHRlciBtb2RlbCBjYW4gYmUgYnVpbHQgdGhhdCBpcyBtb3JlICJiaWFzZWQiIHRvd2FyZHMgY29ycmVjdCBwcmVkaWN0aW9ucyBvZiBkZWZhdWx0cyBhcyB0aGF0IGlzIG9mIG1vcmUgYnVzaW5lc3MgaW50ZXJlc3QuCgojIyMgTW9kZWwgSW1wcm92ZW1lbnQKCk9uY2UgYW4gaW5pdGlhbCBtb2RlbCBpcyB0cmFpbmVkIGFuZCBldmFsdWF0ZWQsIHdlIGNhbiB0dW5lIG1vZGVsIHBhcmFtZXRlcnMgb3IgdHJ5IG91dCBlbmdpbmVlcmVkIGZlYXR1cmVzIHRvIHNlZSBpZiB0aGUgbW9kZWwncyBhY2N1cmFjeSwgRjEtU2NvcmUsIHByZWNpc2lvbiwgb3IgcmVjYWxsIGNhbiBiZSBpbXByb3ZlZCB0byBiZXR0ZXIgc3VpdCB0aGUgYnVzaW5lc3MgdXNlIGNhc2VzLiBPbmUgY2xlYXIgaXNzdWUgaXMgdGhhdCB3ZSBoYXZlIGEgcmVsYXRpdmVseSBzbWFsbCB0cmFpbmluZyBkYXRhIHNldCBhbmQgdGhlIHJhbmRvbSBzYW1wbGluZyBpbnRvIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIGNhbiBoYXZlIGEgc2lnbmlmaWNhbnQgZWZmZWN0IG9uIHRoZSBkZWNpc2lvbiB0cmVlIHRoYXQgaXMgZ2VuZXJhdGVkLgoKQSBjb21tb24gc3RyYXRlZ3kgZm9yIEM1LjBpcyAqYm9vc3RpbmcqLiBCb29zdGluZyBpcyBhbiBlbnNlbWJsZSBsZWFybmluZyB0ZWNobmlxdWUgdGhhdCBhaW1zIHRvIGltcHJvdmUgdGhlIGFjY3VyYWN5IG9mIGEgcHJlZGljdGl2ZSBtb2RlbCBieSBjb21iaW5pbmcgdGhlIHN0cmVuZ3RocyBvZiBtdWx0aXBsZSB3ZWFrIGxlYXJuZXJzLiBJbiB0aGUgY29udGV4dCBvZiBkZWNpc2lvbiB0cmVlcywgYm9vc3RpbmcgaW52b2x2ZXMgY3JlYXRpbmcgYSBzZXJpZXMgb2YgZGVjaXNpb24gdHJlZXMsIHdoZXJlIGVhY2ggc3Vic2VxdWVudCB0cmVlIGF0dGVtcHRzIHRvIGNvcnJlY3QgdGhlIGVycm9ycyBtYWRlIGJ5IHRoZSBwcmV2aW91cyBvbmVzLiBUaGUgZ29hbCBpcyB0byBjb252ZXJ0IGEgY29sbGVjdGlvbiBvZiB3ZWFrIGxlYXJuZXJzICh0cmVlcyB0aGF0IGFyZSBvbmx5IHNsaWdodGx5IGJldHRlciB0aGFuIHJhbmRvbSBndWVzc2luZykgaW50byBhIHN0cm9uZyBsZWFybmVyIChhIGhpZ2hseSBhY2N1cmF0ZSBtb2RlbCkuCgpUaGUgcHJvY2VzcyBvZiBidWlsZGluZyBhIGJvb3N0ZWQgbW9kZWwgYmVnaW5zIGJ5IGluaXRpYWxpemluZyB0aGUgbW9kZWwgd2l0aCBhIHNpbmdsZSB3ZWFrIGxlYXJuZXIsIHR5cGljYWxseSBhIHNtYWxsIGRlY2lzaW9uIHRyZWUuIFRoZSBpbml0aWFsIG1vZGVsIHByZWRpY3RzIHRoZSB0YXJnZXQgdmFyaWFibGUgYmFzZWQgb24gdGhlIHRyYWluaW5nIGRhdGEuIEJvb3N0aW5nIHByb2NlZWRzIGl0ZXJhdGl2ZWx5LCBhZGRpbmcgb25lIHRyZWUgYXQgYSB0aW1lLiBFYWNoIG5ldyB0cmVlIGlzIHRyYWluZWQgdG8gcHJlZGljdCB0aGUgcmVzaWR1YWwgZXJyb3JzICh0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgYWN0dWFsIHZhbHVlcyBhbmQgdGhlIHByZWRpY3Rpb25zIG1hZGUgYnkgdGhlIGN1cnJlbnQgbW9kZWwpIGZyb20gdGhlIHByZXZpb3VzIGl0ZXJhdGlvbi4gRHVyaW5nIGVhY2ggaXRlcmF0aW9uLCBib29zdGluZyBhZGp1c3RzIHRoZSB3ZWlnaHRzIG9mIHRoZSB0cmFpbmluZyBleGFtcGxlcy4gRXhhbXBsZXMgdGhhdCB3ZXJlIGluY29ycmVjdGx5IHByZWRpY3RlZCBieSB0aGUgcHJldmlvdXMgdHJlZXMgYXJlIGdpdmVuIGhpZ2hlciB3ZWlnaHRzLCBtYWtpbmcgdGhlbSBtb3JlIGltcG9ydGFudCBpbiB0aGUgbmV4dCBpdGVyYXRpb24uIFRoaXMgZW5zdXJlcyB0aGF0IHRoZSBuZXcgdHJlZSBmb2N1c2VzIG1vcmUgb24gdGhlIGhhcmQtdG8tcHJlZGljdCBjYXNlcy4gVGhlIGZpbmFsIGJvb3N0ZWQgbW9kZWwgaXMgYSB3ZWlnaHRlZCBzdW0gb2YgYWxsIHRoZSBpbmRpdmlkdWFsIHRyZWVzLiBFYWNoIHRyZWUncyBjb250cmlidXRpb24gdG8gdGhlIGZpbmFsIHByZWRpY3Rpb24gaXMgd2VpZ2h0ZWQgYmFzZWQgb24gaXRzIGFjY3VyYWN5LiBUaGlzIGNvbWJpbmF0aW9uIGhlbHBzIHRvIHNtb290aCBvdXQgaW5kaXZpZHVhbCBlcnJvcnMgYW5kIGltcHJvdmUgdGhlIG92ZXJhbGwgbW9kZWwgcGVyZm9ybWFuY2UuCgojIyMjIEtleSBWYXJpYW50cyBvZiBCb29zdGluZwoKMS4gICoqQWRhQm9vc3QgKEFkYXB0aXZlIEJvb3N0aW5nKSoqOiBBZGFCb29zdCBpcyBvbmUgb2YgdGhlIGVhcmxpZXN0IGFuZCBtb3N0IHdlbGwta25vd24gYm9vc3RpbmcgYWxnb3JpdGhtcy4gSXQgd29ya3MgYnkgYWRqdXN0aW5nIHRoZSB3ZWlnaHRzIG9mIGluY29ycmVjdGx5IGNsYXNzaWZpZWQgaW5zdGFuY2VzIHNvIHRoYXQgc3Vic2VxdWVudCBjbGFzc2lmaWVycyBmb2N1cyBtb3JlIG9uIGRpZmZpY3VsdCBjYXNlcy4gQWRhQm9vc3QgY29tYmluZXMgdGhlIHByZWRpY3Rpb25zIG9mIHRoZSB3ZWFrIGxlYXJuZXJzIHVzaW5nIGEgd2VpZ2h0ZWQgbWFqb3JpdHkgdm90ZS4KCjIuICAqKkdyYWRpZW50IEJvb3N0aW5nKio6IEdyYWRpZW50IEJvb3N0aW5nIGJ1aWxkcyB0cmVlcyBzZXF1ZW50aWFsbHksIHdoZXJlIGVhY2ggdHJlZSBpcyB0cmFpbmVkIHRvIHJlZHVjZSB0aGUgcmVzaWR1YWwgZXJyb3JzIG9mIHRoZSBwcmV2aW91cyB0cmVlIHVzaW5nIGEgZ3JhZGllbnQgZGVzY2VudC1saWtlIHByb2NlZHVyZS4gSXQgZ2VuZXJhbGl6ZXMgQWRhQm9vc3QgYnkgb3B0aW1pemluZyBhbnkgZGlmZmVyZW50aWFibGUgbG9zcyBmdW5jdGlvbi4KCjMuICAqKlhHQm9vc3QgKEV4dHJlbWUgR3JhZGllbnQgQm9vc3RpbmcpKio6IFhHQm9vc3QgaXMgYW4gZWZmaWNpZW50IGFuZCBzY2FsYWJsZSBpbXBsZW1lbnRhdGlvbiBvZiBncmFkaWVudCBib29zdGluZyB0aGF0IGluY2x1ZGVzIHNldmVyYWwgb3B0aW1pemF0aW9ucywgc3VjaCBhcyByZWd1bGFyaXphdGlvbiB0ZWNobmlxdWVzLCB0byBwcmV2ZW50IG92ZXJmaXR0aW5nLiBJdCBpcyBoaWdobHkgZWZmZWN0aXZlIGluIHByYWN0aWNlIGFuZCBoYXMgYmVjb21lIGEgcG9wdWxhciBjaG9pY2UgaW4gbWFueSBtYWNoaW5lIGxlYXJuaW5nIGNvbXBldGl0aW9ucy4KCjQuICAqKkxpZ2h0R0JNIGFuZCBDYXRCb29zdCoqOiBMaWdodEdCTSAoTGlnaHQgR3JhZGllbnQgQm9vc3RpbmcgTWFjaGluZSkgYW5kIENhdEJvb3N0IChDYXRlZ29yaWNhbCBCb29zdGluZykgYXJlIG90aGVyIGFkdmFuY2VkIGltcGxlbWVudGF0aW9ucyBvZiBncmFkaWVudCBib29zdGluZy4gTGlnaHRHQk0gaXMgZGVzaWduZWQgZm9yIGVmZmljaWVuY3kgYW5kIHNjYWxhYmlsaXR5LCB3aGlsZSBDYXRCb29zdCBpcyBvcHRpbWl6ZWQgZm9yIGNhdGVnb3JpY2FsIGRhdGEgYW5kIGhhbmRsZXMgaGlnaC1jYXJkaW5hbGl0eSBmZWF0dXJlcyB3ZWxsLgoKIyMjIyBBZHZhbnRhZ2VzIG9mIEJvb3N0aW5nCgoxLiAgKipJbXByb3ZlZCBBY2N1cmFjeSoqOiBCb29zdGluZyBzaWduaWZpY2FudGx5IGltcHJvdmVzIHRoZSBhY2N1cmFjeSBvZiB0aGUgbW9kZWwgYnkgZm9jdXNpbmcgb24gZGlmZmljdWx0LXRvLXByZWRpY3QgaW5zdGFuY2VzIGFuZCByZWR1Y2luZyBiaWFzIGFuZCB2YXJpYW5jZS4KCjIuICAqKlJvYnVzdG5lc3MqKjogQnkgY29tYmluaW5nIG11bHRpcGxlIHdlYWsgbGVhcm5lcnMsIGJvb3N0aW5nIGNyZWF0ZXMgYSByb2J1c3QgbW9kZWwgdGhhdCBpcyBsZXNzIHByb25lIHRvIG92ZXJmaXR0aW5nIGNvbXBhcmVkIHRvIGEgc2luZ2xlIGNvbXBsZXggbW9kZWwuCgozLiAgKipGbGV4aWJpbGl0eSoqOiBCb29zdGluZyBjYW4gYmUgYXBwbGllZCB0byB2YXJpb3VzIHR5cGVzIG9mIHdlYWsgbGVhcm5lcnMsIGFsdGhvdWdoIGRlY2lzaW9uIHRyZWVzIGFyZSBjb21tb25seSB1c2VkIGR1ZSB0byB0aGVpciBzaW1wbGljaXR5IGFuZCBpbnRlcnByZXRhYmlsaXR5LgoKNC4gICoqSGFuZGxpbmcgSW1iYWxhbmNlZCBEYXRhKio6IEJvb3N0aW5nIGNhbiBiZSBwYXJ0aWN1bGFybHkgZWZmZWN0aXZlIGluIGhhbmRsaW5nIGltYmFsYW5jZWQgZGF0YXNldHMsIGFzIGl0IGZvY3VzZXMgb24gaGFyZC10by1wcmVkaWN0IGNhc2VzIHdoaWNoIG9mdGVuIGJlbG9uZyB0byB0aGUgbWlub3JpdHkgY2xhc3MuCgpUaGUgQzUuMCBhbGdvcml0aG0gaW4gdGhlICoqQzUwKiogcGFja2FnZSBzdXBwb3J0cyBib29zdGluZy4gQmVsb3cgaXMgYW4gZXhhbXBsZSB0aGF0IHVzZXMgYm9vc3Rpbmcgd2l0aCBDNS4wIHRvIHRyYWluIGEgZGVjaXNpb24gdHJlZSBtb2RlbCAtLSB0aGUga2V5IHBhcmFtZXRlciBpcyAqdHJpYWxzKiBzZXQgb2YgYSB2YWx1ZSBncmVhdGVyIHRoYW4gMSAodGhlIGRlZmF1bHQpOgoKYGBge3IgYm9vc3RlZE1vZGVsfQpjcmVkaXQuYm9vc3RlZC5tb2RlbCA8LSBDNS4wKHggPSB0cmFpbi5kZlstMjFdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gdHJhaW4uZGYkQ2xhc3MsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9kYXRhLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmlhbHMgPSAxMCkKYGBgCgpMZXQncyBzZWUgaWYgdGhlIGJvb3N0ZWQgbW9kZWwgcGVyZm9ybXMgYmV0dGVyOgoKYGBge3J9CmNyZWRpdC5wcmVkcy5ib29zdGVkLnZhbCA8LSBwcmVkaWN0KGNyZWRpdC5ib29zdGVkLm1vZGVsLCB2YWwuZGZbLTIxXSkKYGBgCgpgYGB7cn0KY29uZi5tYXRyaXguYm9vc3RlZCA8LSBjb25mdXNpb25NYXRyaXgoY3JlZGl0LnByZWRzLmJvb3N0ZWQudmFsLCB2YWwuZGYkQ2xhc3MpCnByaW50KGNvbmYubWF0cml4LmJvb3N0ZWQpCmBgYAoKQW5kLCBvbmNlIGFnYWluLCBsZXQncyBidWlsZCBhIHByZWRpY3Rpb24gdGFibGU6CgpgYGB7cn0KQ3Jvc3NUYWJsZSh2YWwuZGYkQ2xhc3MsIGNyZWRpdC5wcmVkcy5ib29zdGVkLnZhbCwKICAgICAgICAgICAgIHByb3AuY2hpc3EgPSBGQUxTRSwgcHJvcC5jID0gRkFMU0UsIHByb3AuciA9IEZBTFNFLAogICAgICAgICAgICAgZG5uID0gYygnYWN0dWFsIGRlZmF1bHQnLCAncHJlZGljdGVkIGRlZmF1bHQnKSkKYGBgCgpUaGUgYm9vc3RlZCBtb2RlbCBtYWRlIGNvcnJlY3QgcHJlZGljdGlvbnMgYXQgdGhlIHJhdGUgb2YgNzglIHdoaWNoIGlzIGEgc2xpZ2h0IGltcHJvdmVtZW50LiBIb3dldmVyLCB0aGUgcmF0ZSBvZiBjb3JyZWN0bHkgcHJlZGljdGluZyBhIGRlZmF1bHQgaW5jcmVhc2VkIHRvIDY4JSAoMTkvMjgpIC0tIGEgbW9yZSBzaWduaWZpY2FudCBpbXByb3ZlbWVudC4KCkdpdmVuIHRoZSBhcHBhcmVudCBlYXNlIG9mIGFkZGluZyBib29zdGluZywgb25lIG1pZ2h0IHdvbmRlciB3aHkgaXQgaXNuJ3QgYXBwbGllZCBieSBkZWZhdWx0IHRvIGV2ZXJ5IGRlY2lzaW9uIHRyZWUuIFRoZXJlIGFyZSB0d28gaW1wb3J0YW50IHJlYXNvbnMgZm9yIG5vdCBkb2luZyBzbyBieSBkZWZhdWx0LiBGb3Igb25lLCBpZiBidWlsZGluZyBhIHNpbmdsZSBkZWNpc2lvbiB0cmVlIHJlcXVpcmVzIHN1YnN0YW50aWFsIGNvbXB1dGF0aW9uYWwgcmVzb3VyY2VzIChpdCBpcyBhZnRlciBhbGwgYSBjb21wdXRhdGlvbmFsbHkgY29tcGxleCBhbGdvcml0aG0pLCBjb25zdHJ1Y3RpbmcgbXVsdGlwbGUgdHJlZXMgZm9yIGJvb3N0aW5nIGNhbiBiZWNvbWUgY29tcHV0YXRpb25hbGx5IGltcHJhY3RpY2FsIC0tIGl0IHdvdWxkIGp1c3QgdGFrZSB0b28gbG9uZyB0byB0cmFpbiBhIGJvb3N0ZWQgbW9kZWwgYW5kIGR1cmluZyB0aGUgdHJhaW5pbmcgdGltZSwgbmV3IHRyYWluaW5nIG1heSBoYXZlIGFscmVhZHkgYmVlbiBnZW5lcmF0ZWQgaWYgZGF0YSB2ZWxvY2l0eSBpcyBoaWdoLiBJbiBhZGRpdGlvbiwgaW4gY2FzZXMgd2hlcmUgdGhlIHRyYWluaW5nIGRhdGEgaXMgdmVyeSBub2lzeSwgYm9vc3RpbmcgbWF5IG5vdCB5aWVsZCBhbnkgaW1wcm92ZW1lbnQgYW5kIGNvdWxkIGV2ZW4gZXhhY2VyYmF0ZSB0aGUgbm9pc2UuIE5ldmVydGhlbGVzcywgd2hlbiBoaWdoZXIgYWNjdXJhY3kgaXMgaW1wb3J0YW50LCBpdCBpcyB3b3J0aCBleHBlcmltZW50aW5nIHdpdGggYm9vc3RpbmcgdG8gc2VlIGlmIGl0IHByb3ZpZGVzIHRoZSBkZXNpcmVkIGVuaGFuY2VtZW50IGluIHBlcmZvcm1hbmNlLCBwZXJoYXBzIGV4cGVyaW1lbnRpbmcgb24gYSBzbWFsbGVyIHJhbmRvbSBzYW1wbGUgb2YgdGhlIGRhdGEuCgpOZXZlcnRoZWxlc3MsIGJvb3N0aW5nIGlzIGEgdXNlZnVsIChhbmQgY29tbW9ubHkgYXBwbGllZCkgdGVjaG5pcXVlIHRoYXQgY2FuIHNpZ25pZmljYW50bHkgaW1wcm92ZSB0aGUgcGVyZm9ybWFuY2Ugb2YgZGVjaXNpb24gdHJlZXMgYnkgaXRlcmF0aXZlbHkgZm9jdXNpbmcgb24gaGFyZC10by1wcmVkaWN0IGluc3RhbmNlcyBhbmQgY29tYmluaW5nIG11bHRpcGxlIHdlYWsgbGVhcm5lcnMuIFRoaXMgZ2VuZXJhbGx5IHJlc3VsdHMgaW4gYSByb2J1c3QgYW5kIGFjY3VyYXRlIG1vZGVsLCBtYWtpbmcgYm9vc3RpbmcgYSBwb3B1bGFyIGNob2ljZSBpbiB2YXJpb3VzIG1hY2hpbmUgbGVhcm5pbmcgdGFza3MuCgojIyMgRGlyZWN0aW5nIEJpYXMKClNvbWUgaW5jb3JyZWN0IGNsYXNzaWZpY2F0aW9uIGFyZSBtb3JlIGltcGFjdGZ1bCB0aGFuIG90aGVycy4gRm9yIGluc3RhbmNlLCBnaXZpbmcgYSBsb2FuIHRvIHNvbWVvbmUgd2hvIGlzIGxpa2VseSBnb2luZyB0byBkZWZhdWx0IGlzIG1vcmUgY29uc2VxdWVudGlhbCB0aGFuIG5vdCBnaXZpbmcgYSBsb2FuIHRvIHNvbWVvbmUgd2hvIGlzIG5vdCBsaWtlbHkgdG8gZGVmYXVsdC4gSW4gb3RoZXIgd29yZHMsIGZhbHNlIG5lZ2F0aXZlcyBhcmUgYmV0dGVyIHRoYW4gZmFsc2UgcG9zaXRpdmUuIERhdGEgc2NpZW50aXN0cyBuZWVkIHRvIGludHJvZHVjZSBiaWFzIHNvIHRoYXQgc29tZSBlcnJvcnMgYXJlIGF2b2lkZWQuCgpUaGUgQzUuMCBhbGdvcml0aG0gb2ZmZXJzIHRoZSBvcHBvcnR1bml0eSB0byBhc3NpZ24gZGlmZmVyZW50IHBlbmFsdGllcyAoY29zdHMpIHRvIHZhcmlvdXMgdHlwZXMgb2YgY2xhc3NpZmljYXRpb24gZXJyb3JzLCB0aGVyZWJ5IGRpc2NvdXJhZ2luZyB0aGUgbW9kZWwgZnJvbSBtYWtpbmcgbW9yZSBjb3N0bHkgbWlzdGFrZXMgLS0gaW4gb3RoZXIgd29yZHMsICJiaWFzaW5nIiB0aGUgbW9kZWwuIFRoaXMgaXMgYWNoaWV2ZWQgdGhyb3VnaCB0aGUgdXNlIG9mIGEgY29zdCBtYXRyaXgsIHdoaWNoIGRldGFpbHMgdGhlIHJlbGF0aXZlIGNvc3Qgb2YgZWFjaCB0eXBlIG9mIGVycm9yLiBCeSBzcGVjaWZ5aW5nIHRoZXNlIGNvc3RzLCB3ZSBjYW4gZ3VpZGUgdGhlIGFsZ29yaXRobSB0byBwcmlvcml0aXplIG1pbmltaXppbmcgdGhlIG1vc3QgZXhwZW5zaXZlIGVycm9ycywgaW1wcm92aW5nIHRoZSBtb2RlbCdzIHBlcmZvcm1hbmNlIGluIGNvbnRleHRzIHdoZXJlIHNvbWUgbWlzdGFrZXMgYXJlIG1vcmUgY3JpdGljYWwgdGhhbiBvdGhlcnMuCgpBIGNvc3QgbWF0cml4IGZvciBhIGJpbmFyeSBjbGFzc2lmaWVyIGlzIGEgMngyIG1hdHJpeC4gVGhlIGNvZGUgYmVsb3cgZGVtb25zdHJhdGVzIGhvdyB0byBjb25zdHJ1Y3QgYSBjb3N0IG1hdHJpeC4KCmBgYHtyfQptYXRyaXguZGltcyA8LSBsaXN0KGMoIm5vIiwgInllcyIpLCBjKCJubyIsICJ5ZXMiKSkKbmFtZXMobWF0cml4LmRpbXMpIDwtIGMoInByZWRpY3RlZCIsICJhY3R1YWwiKQpgYGAKCk5leHQsIHdlIG5lZWQgdG8gZGVmaW5lIHRoZSBjb3N0ICgqaS5lLiosIHRoZSBwZW5hbHR5KSBmb3IgZWFjaCB0eXBlIG9mIGVycm9yLiBTaW5jZSBSIHBvcHVsYXRlcyBhIG1hdHJpeCBieSBmaWxsaW5nIGNvbHVtbnMgZnJvbSB0b3AgdG8gYm90dG9tLCB3ZSBtdXN0IHByb3ZpZGUgdGhlIHZhbHVlcyBpbiB0aGlzIG9yZGVyIGdpdmVuIHRoYXQgd2UgaGF2ZSAicHJlZGljdGVkIiBpbiBjb2x1bW4gb25lIGFuZCAiYWN0dWFsIiBvbiBjb2x1bW4gdHdvOgoKMS4gIFByZWRpY3RlZCBubywgYWN0dWFsIG5vCjIuICBQcmVkaWN0ZWQgeWVzLCBhY3R1YWwgbm8KMy4gIFByZWRpY3RlZCBubywgYWN0dWFsIHllcwo0LiAgUHJlZGljdGVkIHllcywgYWN0dWFsIHllcwoKVGhlIGNvc3RzIChvciBwZW5hbHRpZXMpIGFyZSByZWxhdGl2ZS4gQSBjb3N0IG9mIDAgbWVhbnMgdGhhdCB0aGVyZSBpcyBubyBjb3N0LiBMZXQncyBmaWxsIHRoZSBtYXRyaXggd2l0aCBzb21lIGNvc3QgdmFsdWVzOyB0aGUgY29zdCBtYXRyaXggbXVzdCBtaW1pYyB0aGUgZGltZW5zaW9uIG1hdHJpeCBkZWZpbmVkIHByaW9yOgoKYGBge3J9CmNvc3QubWF0cml4IDwtIG1hdHJpeChjKDAsIDEsIDQsIDApLCAKICAgICAgICAgICAgICAgICAgICAgIG5yb3cgPSAyLAogICAgICAgICAgICAgICAgICAgICAgZGltbmFtZXMgPSBtYXRyaXguZGltcykKYGBgCgpIZXJlIGlzIHdoYXQgdGhlIGNvc3QgbWF0cml4IGNvbnRhaW5zOgoKYGBge3J9CnByaW50KGNvc3QubWF0cml4KQpgYGAKCkZvciBpbnN0YW5jZSwgdGhlIGNvc3Qgb2YgYSBkZWZhdWx0IHdoZW4gbm9uZSB3YXMgcHJlZGljdGVkIGlzIGZvdXIgdGltZXMgdGhlIGNvc3Qgb2YgcHJlZGljdGluZyBhIGRlZmF1bHQgd2hlbiB0aGVyZSB3YXMgbm9uZSwgc28gYSBmYWxzZSBuZWdhdGl2ZSBoYXMgZm91ciB0aW1lcyB0aGUgaW1wYWN0IG9mIGEgZmFsc2UgcG9zaXRpdmUuIE5hdHVyYWxseSwgdGhlIGNvcnJlY3QgcHJlZGljdGlvbnMgY2FycnkgYSBjb3N0IG9mIDAgYXMgd2UgZG8gbm90IHdhbnQgdG8gZGlzY291cmFnZSB0aG9zZS4KCk5vdyB0aGF0IHdlIGhhdmUgYSBjb3N0IG1hdHJpeCwgd2UgY2FuIHRyYWluIGEgbmV3IEM1LjAgZGVjaXNpb24gdHJlZSBtb2RlbCB3aXRoIGJvb3N0aW5nIHRoYXQgYWxzbyB0YWtlcyB0aG9zZSBjb3N0cyBpbnRvIGFjY291bnQgd2hlbiBidWlsZGluZyB0aGUgdHJlZS4KCmBgYHtyIGJvb3N0ZWRNb2RlbFdpdGhDb3N0fQpjcmVkaXQuYm9vc3RlZC5jb3N0cy5tb2RlbCA8LSBDNS4wKHggPSB0cmFpbi5kZlstMjFdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gdHJhaW4uZGYkQ2xhc3MsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9kYXRhLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmlhbHMgPSAxMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb3N0cyA9IGNvc3QubWF0cml4KQpgYGAKCkxldCdzIHNlZSBpZiB0aGUgYm9vc3RlZCBtb2RlbCB3aXRoIGNvc3RzIHBlcmZvcm1zIGJldHRlcjoKCmBgYHtyfQpjcmVkaXQucHJlZHMuYm9vc3RlZC5jb3N0cy52YWwgPC0gcHJlZGljdChjcmVkaXQuYm9vc3RlZC5jb3N0cy5tb2RlbCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbC5kZlstMjFdKQpgYGAKCkFuZCwgb25jZSBhZ2FpbiwgbGV0J3MgYnVpbGQgYSBwcmVkaWN0aW9uIHRhYmxlOgoKYGBge3J9CkNyb3NzVGFibGUodmFsLmRmJENsYXNzLCBjcmVkaXQucHJlZHMuYm9vc3RlZC5jb3N0cy52YWwsCiAgICAgICAgICAgICBwcm9wLmNoaXNxID0gRkFMU0UsIHByb3AuYyA9IEZBTFNFLCBwcm9wLnIgPSBGQUxTRSwKICAgICAgICAgICAgIGRubiA9IGMoJ2FjdHVhbCBkZWZhdWx0JywgJ3ByZWRpY3RlZCBkZWZhdWx0JykpCmBgYAoKVGhlIGFjY3VyYWN5IChjb3JyZWN0IHByZWRpY3Rpb25zLCAqaS5lLiosIHRydWUgcG9zaXRpdmVzIGFuZCB0cnVlIG5lZ2F0aXZlcykgaXMgbm93IDc5JSB3aGljaCBpcyBhYm91dCB0aGUgc2FtZSBhcyBiZWZvcmUsIGFsdGhvdWdoIGEgYml0IGJldHRlci4gSG93ZXZlciwgZmFsc2UgcG9zaXRpdmUgYXJlIG5vdyBhdCA2JSB3aGlsZSBmYWxzZSBuZWdhdGl2ZXMgYXJlIGF0IDE1JS4gV2hpbGUgdGhlIGFjY3VyYWN5IG1pZ2h0IGJlIGxvd2VyIGZvciBhIGJvb3N0ZWQgbW9kZWwgdmVyc3VzIG9uZSB0aGF0IGlzIGJvb3N0ZWQgd2l0aCBjb3N0cywgdGhlIHR5cGVzIG9mIGVycm9ycyBhcmUgbW9yZSBhbWVuYWJsZSB0byBidXNpbmVzcyBpbnRlcmVzdHMuCgojIyBTdW1tYXJ5CgpJbiB0aGlzIGxlc3Nvbiwgd2UgZGlzY3Vzc2VkIHRoZSBjb25zdHJ1Y3Rpb24gcHJvY2VzcyBhbmQgY29tbW9uIGVuaGFuY2VtZW50cyBvZiBkZWNpc2lvbiB0cmVlcyB1c2luZyB0aGUgQzUuMCBhbGdvcml0aG0uIERlY2lzaW9uIHRyZWVzIHJlc3VsdCBwb3dlcmZ1bCBtb2RlbHMgZm9yIGNsYXNzaWZpY2F0aW9uIHRhc2tzIHRoYXQgYXJlIGludGVycHJldGFibGUuIFRoZWlyIHBlcmZvcm1hbmNlIGNhbiBiZSBmdXJ0aGVyIGltcHJvdmVkIHRocm91Z2ggYm9vc3RpbmcsIHdoaWNoIGludm9sdmVzIGNvbWJpbmluZyBtdWx0aXBsZSB0cmVlcyB0byByZWR1Y2UgZXJyb3JzIGFuZCBpbmNyZWFzZSBhY2N1cmFjeS4gSG93ZXZlciwgYm9vc3RpbmcgbWF5IG5vdCBhbHdheXMgYmUgcHJhY3RpY2FsIGR1ZSB0byBjb21wdXRhdGlvbmFsIGRlbWFuZHMgYW5kIHNlbnNpdGl2aXR5IHRvIG5vaXN5IGRhdGEuCgpXZSBhbHNvIGV4cGxvcmVkIHRoZSBjb25jZXB0IG9mIGEgY29zdCBtYXRyaXggaW4gdGhlIEM1LjAgYWxnb3JpdGhtLCB3aGljaCBhbGxvd3MgYXNzaWduaW5nIGRpZmZlcmVudCBwZW5hbHRpZXMgdG8gdmFyaW91cyB0eXBlcyBvZiBjbGFzc2lmaWNhdGlvbiBlcnJvcnMuIFRoaXMgaGVscHMgcHJpb3JpdGl6ZSB0aGUgbWluaW1pemF0aW9uIG9mIG1vcmUgY29zdGx5IG1pc3Rha2VzLCB0aGVyZWJ5IHRhaWxvcmluZyB0aGUgbW9kZWwncyBwZXJmb3JtYW5jZSB0byBzcGVjaWZpYyBidXNpbmVzcyBuZWVkcy4KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMgRmlsZXMgJiBSZXNvdXJjZXMKCmBgYHtyIHppcEZpbGVzLCBlY2hvPUZBTFNFfQp6aXBOYW1lID0gc3ByaW50ZigiTGVzc29uRmlsZXMtJXMtJXMuemlwIiwgCiAgICAgICAgICAgICAgICAgcGFyYW1zJGNhdGVnb3J5LAogICAgICAgICAgICAgICAgIHBhcmFtcyRudW1iZXIpCgp0ZXh0QUxpbmsgPSBwYXN0ZTAoIkFsbCBGaWxlcyBmb3IgTGVzc29uICIsIAogICAgICAgICAgICAgICBwYXJhbXMkY2F0ZWdvcnksIi4iLHBhcmFtcyRudW1iZXIpCgojIGRvd25sb2FkRmlsZXNMaW5rKCkgaXMgaW5jbHVkZWQgZnJvbSBfaW5zZXJ0MkRCLlIKa25pdHI6OnJhd19odG1sKGRvd25sb2FkRmlsZXNMaW5rKCIuIiwgemlwTmFtZSwgdGV4dEFMaW5rKSkKYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIFJlZmVyZW5jZXMKCkhvZm1hbm4sIEhhbnMuICgxOTk0KS4gU3RhdGxvZyAoR2VybWFuIENyZWRpdCBEYXRhKS4gVUNJIE1hY2hpbmUgTGVhcm5pbmcgUmVwb3NpdG9yeS4gPGh0dHBzOi8vZG9pLm9yZy8xMC4yNDQzMi9DNU5DNzc+LgoKTGFudHosIEJyZW50LiAoMjAxOSkuICpNYWNoaW5lIGxlYXJuaW5nIHdpdGggUjogRXhwZXJ0IFRlY2huaXF1ZXMgZm9yIFByZWRpY3RpdmUgTW9kZWxpbmcqLiBQYWNrdCBQdWJsaXNoaW5nIEx0ZC4KCiMjIEVycmF0YQoKTm9uZSBjb2xsZWN0ZWQgeWV0LiBbTGV0IHVzIGtub3ddKGh0dHBzOi8vZm9ybS5qb3Rmb3JtLmNvbS8yMTIxODcwNzI3ODQxNTcpe3RhcmdldD0iX2JsYW5rIn0uCg==