1 Starting from the “DocumentTerm matrix” built in previous tutorial

Once the document-term matrix with an specific sparseness value is elected and depending on the interests of the NLP expert, different types of machine learning models can be learned. This tutorial covers the clustering of words with similar patterns of occurrences across documents and the classification of documents. We continue working with the document-term matrices built in the previous tutorial, “A short introduction to the tm (text mining) package in R: text processing”.

You have already realized that R works by creating “objects” and consulting their attributes. Objects are of different types: and this constrains the type of operations that can be applied to each object. For example, text preprocessing operations can only be applied to an object of VCorpus type. On the other hand, machine learning operations (e.g. learning classifiers) can not be applied to a VCorpus type object: it has to be transformed (i.e. “casting”) to a data-matrix or data-frame type.

2 Clustering of words with similar patterns of occurrences across documents

We try to find clusters of words with hierarchical clustering, a popular clustering techniques which builds a dendogram to iteratively group pairs of similar objects. To do so, a matrix which has removed sparse is needed: the starting point is the 0.8 sparseness-value matrix. After the application of the matrix-casting operator, number of occurrences are scaled: first column (term) mean is substracted, and then this is divided by its standard deviation. It is needed to calculate the distance between pairs of objects (terms): these are saved in distMatrix. The dist operator performs this calculation between pairs of rows of the provided matrix. As terms appear in the columns of the document-term matrix (sci.rel.dtm.80), it is needed to be transposed by means of the t operator. The clustering-dendogram is built with the hclust operator. It needs as input the calculated distance matrix between pairs of terms and a criteria to decide which pair of clusters to be consecutively joined in the bottom-up dendogram. In this case, the “complete” criteria takes into account the maximum distance between any pair of objects (terms) of both clusters to be merged. Heigth in the dendogram denotes the distance between a merged pair of clusters.

distMatrix <- dist(t(scale(as.matrix(sci.rel.dtm.80))))
termClustering <- hclust(distMatrix, method = "complete")
plot(termClustering)

Instead of hierarchical clustering and following a similar set of functions, the fpc package allows to construct a k-means clustering.

Another type of popular NLP machine-learning analysis is to construct clusters of similar documents based on the frequencies of word occurrences.

3 Classification of documents

Our objective is to learn a classifier-model which, based on terms occurrences, predicts the type-topic (“science-electronics” or “religion”) of future documents (post-new, in the case of newsgroups). We have a two-class problem.

3.1 Concatenate the annotation column: type of training documents

The 0.9 sparseness value document-term matrix is our starting point. We first need to append the class (document type) vector as the last column of the matrix: the first 591 documents cover the “science-electronics” newgroup.

dim(sci.rel.dtm.90)
[1] 968 119
type <- c(rep("science", 591), rep("religion", 377)) # create the type vector
sci.rel.dtm.90 <- cbind(sci.rel.dtm.90, type) # append
dim(sci.rel.dtm.90) # consult the updated number of columns
[1] 968 120

This new matrix is the starting point for any software specialized on supervised classification. However, it is needed to concatenate “matrix” and “data.frame” casting operations. The name of the last column is updated.

sci.rel.dtm.90.ML.matrix <- as.data.frame(as.matrix(sci.rel.dtm.90))
colnames(sci.rel.dtm.90.ML.matrix)[119] <- "type"

The different columns of an object can be accessed by typing the tab after the object name and the $ symbol.

3.2 Classification by R-package caret, covering a classic data-mining analysis pipeline

The caret [2, 1] package is the reference tool for building supervised classification and regression models in R. The following shows the current top machine learning packages in R: https://www.kdnuggets.com/2017/02/top-r-packages-machine-learning.html. caret package covers all the steps of a classic pipeline: data preprocessing, model building, accuracy estimation, prediction of the type of new samples, and statistical comparision between the performance of different models. Another similar package is mlr3. If you are interested, you can find an interesting tutorial: https://mlr3.mlr-org.com/.

Very useful: the cheatsheet of caret: https://github.com/CABAH/learningRresources/blob/main/cheatsheets/caret.pdf. Its principal functions illustrated in a single page.

3.3 Create a “Train-Test” partition for classifier validation

Before learning a classification model it is needed to define the subsets of samples (documents) to train and test the it. The createDataPartition produces a train-test partition of our corpus of 968 documents. This will be maintained during the whole pipeline of analysis. Test samples won’t be used for any modeling decision:only to predict their class and create a confusion matrix. Consult the parameters of createDataPartition, as well as other two functions with similar purposes, createFolds and createResample. A list of randomly sampled numbers (object inTrain), as index numbers, is used to partition the whole corpus in two R objects.

library(caret)
set.seed(107) # a random seed to enable reproducibility
inTrain <- createDataPartition(y = sci.rel.dtm.90.ML.matrix$type, p = .75, list = FALSE)
str(inTrain)
 int [1:727, 1] 2 3 6 7 8 9 10 12 13 14 ...
 - attr(*, "dimnames")=List of 2
  ..$ : NULL
  ..$ : chr "Resample1"
training <- sci.rel.dtm.90.ML.matrix[inTrain, ]
testing <- sci.rel.dtm.90.ML.matrix[-inTrain, ]
nrow(training)
[1] 727

3.4 Selection of supervised classification algorithms

We now can start training and testing different supervised classification models. train function implements the building process. Check its parameters: among them, we highlight the following:

  • preProcess parameter defines the preprocessing steps to be applied. They are popular with classic numeric variables, such as imputation of missing values, centering and scaling, etc. As it was shown in the previous tutorial, NLP datasets have their own preprocessing tools. They are not going to be applied in our dataset.

  • trControl parameter defines the method to estimate the error of the classifier. It is defined by means of the application of the trainControl function. This allows the use of different performance estimation procedures such as k-fold cross-validation, bootstrapping, etc. We apply a 10-fold cross-validation, repeated 3 times.

  • method parameter fixes the type of classification algorithm to be learned. The wide list of algorithms (and its parameters) covered by caret can be found in https://topepo.github.io/caret/train-models-by-tag.html. Taking into account the large dimensionality of classic NLP datasets, the use of classifiers capable to deal with this characteristic is highly recommended. In this tutorial, Linear support vector machine (SVM) and k-nearest neighbour (K-NN) models are learned and validated.

  • metric parameter fixes the score to assess-validates the goodness of each model. Apart from the ROC metric used in this tutorial, a large set of metrics is offered: Accuracy (percentage of correct classification), kappa, Sens (Sensitivity), Specificity (Spec), RMSE in the case of regression problems… I have not found a list with all the metrics offered by caret, but consulting the help of the package will give you a broad idea.

Take into account the following facts about the model-training step:

  • the expression type ~ is quite popular in R to denote the specific variable to be predicted, followed by the set of predictors. A point indicates that the rest of variables are used as predictors.

  • together with the estimation of the percentage recognition (accuracy), the value of the Kappa statistic is shown. It is a popular score in NLP studies. Its definition is not trivial in the context of supervised classification. Roughly, this score compares the “observed” accuracy of the learned classifier with respect to a random classifier: measuring the score difference between our classifier and a random classifier. A larger definition of this metric can be found in http://stats.stackexchange.com/questions/82162/kappa-statistic-in-plain-english.

  • while the linear SVM classifier does not have parameters, K-NN has the “number of neighbours” (K) key parameter. By default, changing the value of the parameter, caret evaluates 3 models (tuneLength equal to 3). The tuneLength option of the train function fixes the number of values of each parameter to be checked. For example, if the classifier has 2 parameters and the tuneLength parameter is not changed, 3 x 3 = 9 models are evaluated. The tuneGrid option offers the possibility to select among a set of values to be tuned-tested.

  • caret supports more than 150 supervised classification and regression algorithms. A small portion of them are learned by means of the software of the package itself. The majority of the algorithms are learned by other R packages which are conveniently accessed by caret.

3.5 Internal performance estimation in the training partition

# fixing the performance estimation procedure
ctrl <- trainControl(method = "repeatedcv", repeats = 3)
svmModel3x10cv <- train(type ~ ., data = training, method = "svmLinear", trControl = ctrl)
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.
svmModel3x10cv
Support Vector Machines with Linear Kernel 

727 samples
119 predictors
  2 classes: 'religion', 'science' 

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times) 
Summary of sample sizes: 654, 654, 654, 654, 654, 655, ... 
Resampling results:

  Accuracy  Kappa
  1         1    

Tuning parameter 'C' was held constant at a value of 1
knnModel3x10cv <- train(type ~ ., data = training, method = "knn", trControl = ctrl)
knnModel3x10cv
k-Nearest Neighbors 

727 samples
119 predictors
  2 classes: 'religion', 'science' 

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times) 
Summary of sample sizes: 654, 654, 655, 653, 654, 655, ... 
Resampling results across tuning parameters:

  k  Accuracy   Kappa    
  5  0.8483610  0.6562741
  7  0.8377955  0.6294669
  9  0.8318090  0.6144636

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

3.6 Tuning of the parameters of the selected classifiers

The training process can still be enriched with extra parameters in the trainControl function: summaryFunction controls the type of evaluation metrics. In binary classification problems (e.g. “science” versus “religion”) the twoClassSummary option displays area under the ROC curve, sensitity-recall and specificity metrics. To do so, it is also needed to activate the classProbs option which saves the probability that the classifier assigns to each sample belonging to each class-value.

library(pROC)
ctrl <- trainControl(
    method = "repeatedcv", repeats = 3, classProbs = TRUE,
    summaryFunction = twoClassSummary
)
knnModel3x10cvROC <- train(type ~ .,
    data = training, method = "knn", trControl = ctrl,
    metric = "ROC", tuneLength = 10
)
knnModel3x10cvROC
k-Nearest Neighbors 

727 samples
119 predictors
  2 classes: 'religion', 'science' 

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times) 
Summary of sample sizes: 654, 654, 655, 654, 655, 654, ... 
Resampling results across tuning parameters:

  k   ROC        Sens       Spec     
   5  0.9820156  0.6114943  0.9977609
   7  0.9894958  0.5811987  1.0000000
   9  0.9946740  0.5573071  1.0000000
  11  0.9956430  0.5103448  1.0000000
  13  0.9961840  0.4666256  1.0000000
  15  0.9957186  0.4097291  1.0000000
  17  0.9954323  0.3722496  1.0000000
  19  0.9958159  0.3383415  1.0000000
  21  0.9961822  0.3087438  1.0000000
  23  0.9968183  0.2830049  1.0000000

ROC was used to select the optimal model using the largest value.
The final value used for the model was k = 23.
plot(knnModel3x10cvROC)

3.7 Predict the class-type of future unseen-unlabeled texts

In order to predict the class value of unseen documents of the test partition caret uses the classifier which shows the best accuracy estimation of their parameters. Function predict implements this functionality. Consult its parameters. The type parameter, by means of its probs value, outputs the probability of test each sample belonging to each class (“a-posteriori” probability). On the other hand, the raw value outputs the class value with the largest probability. By means of the raw option the confusion matrix can be calculated: this crosses, for each test sample, predicted with real class values.

svmModelClasses <- predict(svmModel3x10cv, newdata = testing, type = "raw")
confusionMatrix(data = svmModelClasses, testing$type)

3.8 Statistical comparison between two classifiers by means of t-test

Can a statistical comparison be performed between the 3x10cv validation results of K-NN and SVM? Note than in our case, due to the 3 repetitions of the 10-fold cross-validation process, there are 30 resampling results for each classifier. First, results of both classifiers are crossed using the resamples function. As the set.seed did not change, the same paired cross-validation subsets of samples were used for both classifiers. This forces to use a paired t-test to calculate the significance of the differences between both classifiers. A simple plot is drawn, showing the accuracy differences between both models for each of the 30 cross-validation folds: note that svm has a better accuracy than knn for all the cross-validation fold results (Figure 2).

Using the diff function over the resamps object which saves the “crossing” of both classifiers, we can show a rich output of the performed comparison: each number matters. The output shows, for each metric (area under the ROC curve, sensitivity, specificity), the difference of the mean (positive or negative, following the order in the resamples function) between both classifiers. The p-value of the associated t-test is also shown.

The interpretation of the p-value has the key. It is related with the risk of erroneously discarding the nullhypothesis of similarity between compared classifiers, when there is no real difference. Roughly speaking, it can also be interpreted as the degree of similarity between both classifiers. A p-value smaller than 0.05 (or 0.1, depending on your interpretation and threshold) alerts about statistically significant differences between both classifiers, https://en.wikipedia.org/wiki/Statistical_significance. That is, when the risk of erroneusly discarding the hypothesis of similarity between both classifiers is low, we assume that there is a statistically significant difference between classifiers.

resamps <- resamples(list(knn = knnModel3x10cv, svm = svmModel3x10cv))
summary(resamps)

Call:
summary.resamples(object = resamps)

Models: knn, svm 
Number of resamples: 30 

Accuracy 
         Min.   1st Qu.    Median     Mean 3rd Qu.      Max. NA's
knn 0.7567568 0.8265766 0.8472222 0.848361   0.875 0.9166667    0
svm 1.0000000 1.0000000 1.0000000 1.000000   1.000 1.0000000    0

Kappa 
         Min.   1st Qu.    Median      Mean   3rd Qu. Max. NA's
knn 0.4263566 0.6085039 0.6562334 0.6562741 0.7219975 0.82    0
svm 1.0000000 1.0000000 1.0000000 1.0000000 1.0000000 1.00    0
xyplot(resamps, what = "BlandAltman")

diffs <- diff(resamps)
summary(diffs)

Call:
summary.diff.resamples(object = diffs)

p-value adjustment: bonferroni 
Upper diagonal: estimates of the difference
Lower diagonal: p-value for H0: difference = 0

Accuracy 
    knn       svm    
knn           -0.1516
svm < 2.2e-16        

Kappa 
    knn       svm    
knn           -0.3437
svm < 2.2e-16        

4 Evaluation exercise

Together with the explanations of the previous tm tutorial, create a corpus and a document-term matrix which characterises a supervised classification problem of your interest: 2 different types of documents, different types of sentiments, different types of text difficulties… that is, the corpus has to be annotated, at least with two different labels, in order to proceed with supervised classification tasks.

As the output of the exercise, we hope a document similar to this tutorial: that is, a notebook which mixes code and the explanations of this code and your decisions. A rich document. The document has to alternate explanations about your decision and the R code that implements it: colloquially, do “do your own tutorial-notebook”, with your chosen application-corpus. Specifically, the document that you have read has been edited with the knitr package and the RStudio software: Rnw format. You can of course edit with this technology or another of your convenience: R-markdown, jupyter notebooks, etc.

Your notebook-tutorial has to cover the following items:

  • a short description of your dataset and NLP problem. The nature of the problem and the objective of the modelization, the class variables to be predicted and its possible values, how can the models be evaluated, the predictive features and another concept which helps in the description of the domain.

  • over the whole corpus, apply one of the outlier detection techniques exposed in class and consider deleting extreme-outlier documents.

  • show a wordcloud based on your corpus bag-of-words.

  • the way to partition the corpus in train-test does not have to be the same of the tutorial. There are other possibilities such as createFolds(), createResample(), etc.

  • choose two other classification algorithms not used in this tutorial. For your selection, describe briefly their behaviour and its parameters. Take into account the effect of the exposed tuneLength option: use the tuneLength and-or tuneGrid options to tune the parameters of chosen classification algorithms.

  • remember that the trainControl() function allows to select the options to validate the classifier. Consult its options and do variations with respect to my selection.

  • if class-label distributions are unbalanced in your corpus, test a ‘resampling’ method which will try to improve the recovery rate in the minority class. Which is the class-label distribution of your corpus? Show it. Study the options of the sampling option in trainControl: https://www.rdocumentation.org/packages/caret/versions/6.0-84/topics/trainControl. caret has a brief and intuitive tutorial about the topic: check the first sentences of this link https://topepo.github.io/caret/subsampling-for-class-imbalances.html.

  • while in my tutorial the chosen metric to validate the models has been ROC, you can of course select another metric of your convenience: accuracy, sensitivity, F1, kappa, etc. If you do not have prior information, the default and common-sense score metric to choose in your supervised classification application is accuracy.

  • test the feature selection options offered by caret. At least, apply one of them over your corpus. Can the feature subset to learn the final classifier be optimized? caret offers genetic algorithms by wrapper, simmulated annealing by wrapper, recursive backward elimination by wrapper. Or univariate filters that rank independently the variables with respect to their relevance-correlation with respect to the annotation-column;

  • let’s now practise with feature extraction, where a set of features is ‘constructed’ from original ones: commonly, linear combinations of original ones. In this area, it is likely that you know algorithms such as principal component analysis - PCA, singular value decomposition, etc. It is easy to learn a PCA in R with the prcomp function and visualize in a 2-D graph two first components (i.e. those that save larger variability of original data): trying to find an intuitive separation of problemclasses. Try it and comment the results.

  • remember that the predict() functions allows to select the options to predict the class-label of the samples of the test partition. Consult its options and do variations with respect to my selection. Do class-label predictions over new-unseen-unnanotated documents.

  • to do the final statistical comparison between your pair of selected classifiers, it is needed to understand the use and output of resamples(), summary(), diff() functions. The output is long but rich in information to extract conclusions of the comparison. Interpret the calculated p-value to analyze the significance of the differences between compared classifiers. In its current implementation, caret applies a t-test to obtain the significance of the differences between the pair of compared classifiers.

5 References

[1] Max Kuhn. Contributions from Jed Wing, Steve Weston, Andre Williams, Chris Keefer, Allan Engelhardt, Tony Cooper, Zachary Mayer, and the R Core Team. caret: Classification and Regression Training, 2014. R package version 6.0-35.

[2] M. Kuhn and K. Johnson. Applied Predictive Modeling. Springer, 2013.

LS0tDQp0aXRsZTogJ0NsdXN0ZXJpbmcgd29yZHMgYW5kIGNsYXNzaWZ5aW5nIGRvY3VtZW50cyB3aXRoIFInDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IA0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KLS0tDQoNCiMgU3RhcnRpbmcgZnJvbSB0aGUgIkRvY3VtZW50VGVybSBtYXRyaXgiIGJ1aWx0IGluIHByZXZpb3VzIHR1dG9yaWFsDQoNCk9uY2UgdGhlIGRvY3VtZW50LXRlcm0gbWF0cml4IHdpdGggYW4gc3BlY2lmaWMgc3BhcnNlbmVzcyB2YWx1ZSBpcyBlbGVjdGVkIGFuZCBkZXBlbmRpbmcgb24gdGhlIGludGVyZXN0cyBvZiB0aGUgTkxQIGV4cGVydCwgZGlmZmVyZW50IHR5cGVzIG9mIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIGNhbiBiZSBsZWFybmVkLiBUaGlzIHR1dG9yaWFsIGNvdmVycyB0aGUgY2x1c3RlcmluZyBvZiB3b3JkcyB3aXRoIHNpbWlsYXIgcGF0dGVybnMgb2Ygb2NjdXJyZW5jZXMgYWNyb3NzIGRvY3VtZW50cyBhbmQgdGhlIGNsYXNzaWZpY2F0aW9uIG9mIGRvY3VtZW50cy4gV2UgY29udGludWUgd29ya2luZyB3aXRoIHRoZSBkb2N1bWVudC10ZXJtIG1hdHJpY2VzIGJ1aWx0IGluIHRoZSBwcmV2aW91cyB0dXRvcmlhbCwg4oCcQSBzaG9ydCBpbnRyb2R1Y3Rpb24gdG8gdGhlIGB0bWAgKHRleHQgbWluaW5nKSBwYWNrYWdlIGluIGBSYDogdGV4dCBwcm9jZXNzaW5n4oCdLg0KDQpZb3UgaGF2ZSBhbHJlYWR5IHJlYWxpemVkIHRoYXQgYFJgIHdvcmtzIGJ5IGNyZWF0aW5nIOKAnG9iamVjdHPigJ0gYW5kIGNvbnN1bHRpbmcgdGhlaXIgYXR0cmlidXRlcy4gT2JqZWN0cyBhcmUgb2YgZGlmZmVyZW50IHR5cGVzOiBhbmQgdGhpcyBjb25zdHJhaW5zIHRoZSB0eXBlIG9mIG9wZXJhdGlvbnMgdGhhdCBjYW4gYmUgYXBwbGllZCB0byBlYWNoIG9iamVjdC4gRm9yIGV4YW1wbGUsIHRleHQgcHJlcHJvY2Vzc2luZyBvcGVyYXRpb25zIGNhbiBvbmx5IGJlIGFwcGxpZWQgdG8gYW4gb2JqZWN0IG9mIGBWQ29ycHVzYCB0eXBlLiBPbiB0aGUgb3RoZXIgaGFuZCwgbWFjaGluZSBsZWFybmluZyBvcGVyYXRpb25zIChlLmcuIGxlYXJuaW5nIGNsYXNzaWZpZXJzKSBjYW4gbm90IGJlIGFwcGxpZWQgdG8gYSBgVkNvcnB1c2AgdHlwZSBvYmplY3Q6IGl0IGhhcyB0byBiZSB0cmFuc2Zvcm1lZCAoaS5lLiDigJxjYXN0aW5n4oCdKSB0byBhIGRhdGEtbWF0cml4IG9yIGRhdGEtZnJhbWUgdHlwZS4NCg0KIyBDbHVzdGVyaW5nIG9mIHdvcmRzIHdpdGggc2ltaWxhciBwYXR0ZXJucyBvZiBvY2N1cnJlbmNlcyBhY3Jvc3MgZG9jdW1lbnRzDQoNCldlIHRyeSB0byBmaW5kIGNsdXN0ZXJzIG9mIHdvcmRzIHdpdGggaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcsIGEgcG9wdWxhciBjbHVzdGVyaW5nIHRlY2huaXF1ZXMgd2hpY2ggYnVpbGRzIGEgZGVuZG9ncmFtIHRvIGl0ZXJhdGl2ZWx5IGdyb3VwIHBhaXJzIG9mIHNpbWlsYXIgb2JqZWN0cy4gVG8gZG8gc28sIGEgbWF0cml4IHdoaWNoIGhhcyByZW1vdmVkIHNwYXJzZSBpcyBuZWVkZWQ6IHRoZSBzdGFydGluZyBwb2ludCBpcyB0aGUgMC44IHNwYXJzZW5lc3MtdmFsdWUgbWF0cml4LiBBZnRlciB0aGUgYXBwbGljYXRpb24gb2YgdGhlIG1hdHJpeC1jYXN0aW5nIG9wZXJhdG9yLCBudW1iZXIgb2Ygb2NjdXJyZW5jZXMgYXJlIHNjYWxlZDogZmlyc3QgY29sdW1uICh0ZXJtKSBtZWFuIGlzIHN1YnN0cmFjdGVkLCBhbmQgdGhlbiB0aGlzIGlzIGRpdmlkZWQgYnkgaXRzIHN0YW5kYXJkIGRldmlhdGlvbi4gSXQgaXMgbmVlZGVkIHRvIGNhbGN1bGF0ZSB0aGUgZGlzdGFuY2UgYmV0d2VlbiBwYWlycyBvZiBvYmplY3RzICh0ZXJtcyk6IHRoZXNlIGFyZSBzYXZlZCBpbiBgZGlzdE1hdHJpeGAuIFRoZSBgZGlzdGAgb3BlcmF0b3IgcGVyZm9ybXMgdGhpcyBjYWxjdWxhdGlvbiBiZXR3ZWVuIHBhaXJzIG9mIHJvd3Mgb2YgdGhlIHByb3ZpZGVkIG1hdHJpeC4gQXMgdGVybXMgYXBwZWFyIGluIHRoZSBjb2x1bW5zIG9mIHRoZSBkb2N1bWVudC10ZXJtIG1hdHJpeCAoYHNjaS5yZWwuZHRtLjgwYCksIGl0IGlzIG5lZWRlZCB0byBiZSB0cmFuc3Bvc2VkIGJ5IG1lYW5zIG9mIHRoZSBgdGAgb3BlcmF0b3IuIFRoZSBjbHVzdGVyaW5nLWRlbmRvZ3JhbSBpcyBidWlsdCB3aXRoIHRoZSBgaGNsdXN0YCBvcGVyYXRvci4gSXQgbmVlZHMgYXMgaW5wdXQgdGhlIGNhbGN1bGF0ZWQgZGlzdGFuY2UgbWF0cml4IGJldHdlZW4gcGFpcnMgb2YgdGVybXMgYW5kIGEgY3JpdGVyaWEgdG8gZGVjaWRlIHdoaWNoIHBhaXIgb2YgY2x1c3RlcnMgdG8gYmUgY29uc2VjdXRpdmVseSBqb2luZWQgaW4gdGhlIGJvdHRvbS11cCBkZW5kb2dyYW0uIEluIHRoaXMgY2FzZSwgdGhlIOKAnGNvbXBsZXRl4oCdIGNyaXRlcmlhIHRha2VzIGludG8gYWNjb3VudCB0aGUgbWF4aW11bSBkaXN0YW5jZSBiZXR3ZWVuIGFueSBwYWlyIG9mIG9iamVjdHMgKHRlcm1zKSBvZiBib3RoIGNsdXN0ZXJzIHRvIGJlIG1lcmdlZC4gSGVpZ3RoIGluIHRoZSBkZW5kb2dyYW0gZGVub3RlcyB0aGUgKmRpc3RhbmNlKiBiZXR3ZWVuIGEgbWVyZ2VkIHBhaXIgb2YgY2x1c3RlcnMuDQoNCmBgYHtyfQ0KZGlzdE1hdHJpeCA8LSBkaXN0KHQoc2NhbGUoYXMubWF0cml4KHNjaS5yZWwuZHRtLjgwKSkpKQ0KdGVybUNsdXN0ZXJpbmcgPC0gaGNsdXN0KGRpc3RNYXRyaXgsIG1ldGhvZCA9ICJjb21wbGV0ZSIpDQpwbG90KHRlcm1DbHVzdGVyaW5nKQ0KYGBgDQoNCkluc3RlYWQgb2YgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgYW5kIGZvbGxvd2luZyBhIHNpbWlsYXIgc2V0IG9mIGZ1bmN0aW9ucywgdGhlIGBmcGNgIHBhY2thZ2UgYWxsb3dzIHRvIGNvbnN0cnVjdCBhIGBrLW1lYW5zYCBjbHVzdGVyaW5nLg0KDQpBbm90aGVyIHR5cGUgb2YgcG9wdWxhciBOTFAgbWFjaGluZS1sZWFybmluZyBhbmFseXNpcyBpcyB0byBjb25zdHJ1Y3QgY2x1c3RlcnMgb2Ygc2ltaWxhciBkb2N1bWVudHMgYmFzZWQgb24gdGhlIGZyZXF1ZW5jaWVzIG9mIHdvcmQgb2NjdXJyZW5jZXMuDQoNCiMgQ2xhc3NpZmljYXRpb24gb2YgZG9jdW1lbnRzDQoNCk91ciBvYmplY3RpdmUgaXMgdG8gbGVhcm4gYSBjbGFzc2lmaWVyLW1vZGVsIHdoaWNoLCBiYXNlZCBvbiB0ZXJtcyBvY2N1cnJlbmNlcywgcHJlZGljdHMgdGhlIHR5cGUtdG9waWMgKOKAnHNjaWVuY2UtZWxlY3Ryb25pY3PigJ0gb3Ig4oCccmVsaWdpb27igJ0pIG9mIGZ1dHVyZSBkb2N1bWVudHMgKHBvc3QtbmV3LCBpbiB0aGUgY2FzZSBvZiBuZXdzZ3JvdXBzKS4gV2UgaGF2ZSBhIHR3by1jbGFzcyBwcm9ibGVtLg0KDQojIyBDb25jYXRlbmF0ZSB0aGUgYW5ub3RhdGlvbiBjb2x1bW46IHR5cGUgb2YgdHJhaW5pbmcgZG9jdW1lbnRzDQoNClRoZSAwLjkgc3BhcnNlbmVzcyB2YWx1ZSBkb2N1bWVudC10ZXJtIG1hdHJpeCBpcyBvdXIgc3RhcnRpbmcgcG9pbnQuIFdlIGZpcnN0IG5lZWQgdG8gYXBwZW5kIHRoZSBjbGFzcyAoZG9jdW1lbnQgdHlwZSkgdmVjdG9yIGFzIHRoZSBsYXN0IGNvbHVtbiBvZiB0aGUgbWF0cml4OiB0aGUgZmlyc3QgNTkxIGRvY3VtZW50cyBjb3ZlciB0aGUg4oCcc2NpZW5jZS1lbGVjdHJvbmljc+KAnSBuZXdncm91cC4NCg0KYGBge3J9DQpkaW0oc2NpLnJlbC5kdG0uOTApDQp0eXBlIDwtIGMocmVwKCJzY2llbmNlIiwgNTkxKSwgcmVwKCJyZWxpZ2lvbiIsIDM3NykpICMgY3JlYXRlIHRoZSB0eXBlIHZlY3Rvcg0Kc2NpLnJlbC5kdG0uOTAgPC0gY2JpbmQoc2NpLnJlbC5kdG0uOTAsIHR5cGUpICMgYXBwZW5kDQpkaW0oc2NpLnJlbC5kdG0uOTApICMgY29uc3VsdCB0aGUgdXBkYXRlZCBudW1iZXIgb2YgY29sdW1ucw0KYGBgDQoNClRoaXMgbmV3IG1hdHJpeCBpcyB0aGUgc3RhcnRpbmcgcG9pbnQgZm9yIGFueSBzb2Z0d2FyZSBzcGVjaWFsaXplZCBvbiBzdXBlcnZpc2VkIGNsYXNzaWZpY2F0aW9uLiBIb3dldmVyLCBpdCBpcyBuZWVkZWQgdG8gY29uY2F0ZW5hdGUg4oCcbWF0cml44oCdIGFuZCDigJxkYXRhLmZyYW1l4oCdIGNhc3Rpbmcgb3BlcmF0aW9ucy4gVGhlIG5hbWUgb2YgdGhlIGxhc3QgY29sdW1uIGlzIHVwZGF0ZWQuDQoNCmBgYHtyfQ0Kc2NpLnJlbC5kdG0uOTAuTUwubWF0cml4IDwtIGFzLmRhdGEuZnJhbWUoYXMubWF0cml4KHNjaS5yZWwuZHRtLjkwKSkNCmNvbG5hbWVzKHNjaS5yZWwuZHRtLjkwLk1MLm1hdHJpeClbMTE5XSA8LSAidHlwZSINCmBgYA0KDQpUaGUgZGlmZmVyZW50IGNvbHVtbnMgb2YgYW4gb2JqZWN0IGNhbiBiZSBhY2Nlc3NlZCBieSB0eXBpbmcgdGhlIHRhYiBhZnRlciB0aGUgb2JqZWN0IG5hbWUgYW5kIHRoZSAkIHN5bWJvbC4NCg0KIyMgQ2xhc3NpZmljYXRpb24gYnkgUi1wYWNrYWdlIGBjYXJldGAsIGNvdmVyaW5nIGEgY2xhc3NpYyBkYXRhLW1pbmluZyBhbmFseXNpcyBwaXBlbGluZQ0KDQpUaGUgYGNhcmV0YCBbMiwgMV0gcGFja2FnZSBpcyB0aGUgcmVmZXJlbmNlIHRvb2wgZm9yIGJ1aWxkaW5nIHN1cGVydmlzZWQgY2xhc3NpZmljYXRpb24gYW5kIHJlZ3Jlc3Npb24gbW9kZWxzIGluIFIuIFRoZSBmb2xsb3dpbmcgc2hvd3MgdGhlIGN1cnJlbnQgdG9wIG1hY2hpbmUgbGVhcm5pbmcgcGFja2FnZXMgaW4gYFJgOiBodHRwczovL3d3dy5rZG51Z2dldHMuY29tLzIwMTcvMDIvdG9wLXItcGFja2FnZXMtbWFjaGluZS1sZWFybmluZy5odG1sLiBgY2FyZXRgIHBhY2thZ2UgY292ZXJzIGFsbCB0aGUgc3RlcHMgb2YgYSBjbGFzc2ljIHBpcGVsaW5lOiBkYXRhIHByZXByb2Nlc3NpbmcsIG1vZGVsIGJ1aWxkaW5nLCBhY2N1cmFjeSBlc3RpbWF0aW9uLCBwcmVkaWN0aW9uIG9mIHRoZSB0eXBlIG9mIG5ldyBzYW1wbGVzLCBhbmQgc3RhdGlzdGljYWwgY29tcGFyaXNpb24gYmV0d2VlbiB0aGUgcGVyZm9ybWFuY2Ugb2YgZGlmZmVyZW50IG1vZGVscy4gQW5vdGhlciBzaW1pbGFyIHBhY2thZ2UgaXMgYG1scjNgLiBJZiB5b3UgYXJlIGludGVyZXN0ZWQsIHlvdSBjYW4gZmluZCBhbiBpbnRlcmVzdGluZyB0dXRvcmlhbDogaHR0cHM6Ly9tbHIzLm1sci1vcmcuY29tLy4NCg0KVmVyeSB1c2VmdWw6IHRoZSBjaGVhdHNoZWV0IG9mIGNhcmV0OiBodHRwczovL2dpdGh1Yi5jb20vQ0FCQUgvbGVhcm5pbmdScmVzb3VyY2VzL2Jsb2IvbWFpbi9jaGVhdHNoZWV0cy9jYXJldC5wZGYuIEl0cyBwcmluY2lwYWwgZnVuY3Rpb25zIGlsbHVzdHJhdGVkIGluIGEgc2luZ2xlIHBhZ2UuDQoNCiMjIENyZWF0ZSBhICJUcmFpbi1UZXN0IiBwYXJ0aXRpb24gZm9yIGNsYXNzaWZpZXIgdmFsaWRhdGlvbg0KDQpCZWZvcmUgbGVhcm5pbmcgYSBjbGFzc2lmaWNhdGlvbiBtb2RlbCBpdCBpcyBuZWVkZWQgdG8gZGVmaW5lIHRoZSBzdWJzZXRzIG9mIHNhbXBsZXMgKGRvY3VtZW50cykgdG8gdHJhaW4gYW5kIHRlc3QgdGhlIGl0LiBUaGUgYGNyZWF0ZURhdGFQYXJ0aXRpb25gIHByb2R1Y2VzIGEgdHJhaW4tdGVzdCBwYXJ0aXRpb24gb2Ygb3VyIGNvcnB1cyBvZiA5NjggZG9jdW1lbnRzLiBUaGlzIHdpbGwgYmUgbWFpbnRhaW5lZCBkdXJpbmcgdGhlIHdob2xlIHBpcGVsaW5lIG9mIGFuYWx5c2lzLiBUZXN0IHNhbXBsZXMgd29u4oCZdCBiZSB1c2VkIGZvciBhbnkgbW9kZWxpbmcgZGVjaXNpb246b25seSB0byBwcmVkaWN0IHRoZWlyIGNsYXNzIGFuZCBjcmVhdGUgYSBjb25mdXNpb24gbWF0cml4LiBDb25zdWx0IHRoZSBwYXJhbWV0ZXJzIG9mIGBjcmVhdGVEYXRhUGFydGl0aW9uYCwgYXMgd2VsbCBhcyBvdGhlciB0d28gZnVuY3Rpb25zIHdpdGggc2ltaWxhciBwdXJwb3NlcywgYGNyZWF0ZUZvbGRzYCBhbmQgYGNyZWF0ZVJlc2FtcGxlYC4gQSBsaXN0IG9mIHJhbmRvbWx5IHNhbXBsZWQgbnVtYmVycyAob2JqZWN0IGBpblRyYWluYCksIGFzIGluZGV4IG51bWJlcnMsIGlzIHVzZWQgdG8gcGFydGl0aW9uIHRoZSB3aG9sZSBjb3JwdXMgaW4gdHdvIGBSYCBvYmplY3RzLg0KDQpgYGB7cn0NCmxpYnJhcnkoY2FyZXQpDQpzZXQuc2VlZCgxMDcpICMgYSByYW5kb20gc2VlZCB0byBlbmFibGUgcmVwcm9kdWNpYmlsaXR5DQppblRyYWluIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeSA9IHNjaS5yZWwuZHRtLjkwLk1MLm1hdHJpeCR0eXBlLCBwID0gLjc1LCBsaXN0ID0gRkFMU0UpDQpzdHIoaW5UcmFpbikNCnRyYWluaW5nIDwtIHNjaS5yZWwuZHRtLjkwLk1MLm1hdHJpeFtpblRyYWluLCBdDQp0ZXN0aW5nIDwtIHNjaS5yZWwuZHRtLjkwLk1MLm1hdHJpeFstaW5UcmFpbiwgXQ0KbnJvdyh0cmFpbmluZykNCmBgYA0KDQojIyBTZWxlY3Rpb24gb2Ygc3VwZXJ2aXNlZCBjbGFzc2lmaWNhdGlvbiBhbGdvcml0aG1zIA0KDQpXZSBub3cgY2FuIHN0YXJ0IHRyYWluaW5nIGFuZCB0ZXN0aW5nIGRpZmZlcmVudCBzdXBlcnZpc2VkIGNsYXNzaWZpY2F0aW9uIG1vZGVscy4gdHJhaW4gZnVuY3Rpb24gaW1wbGVtZW50cyB0aGUgYnVpbGRpbmcgcHJvY2Vzcy4gQ2hlY2sgaXRzIHBhcmFtZXRlcnM6IGFtb25nIHRoZW0sIHdlIGhpZ2hsaWdodCB0aGUgZm9sbG93aW5nOg0KDQoqIGBwcmVQcm9jZXNzYCBwYXJhbWV0ZXIgZGVmaW5lcyB0aGUgcHJlcHJvY2Vzc2luZyBzdGVwcyB0byBiZSBhcHBsaWVkLiBUaGV5IGFyZSBwb3B1bGFyIHdpdGggY2xhc3NpYyBudW1lcmljIHZhcmlhYmxlcywgc3VjaCBhcyBpbXB1dGF0aW9uIG9mIG1pc3NpbmcgdmFsdWVzLCBjZW50ZXJpbmcgYW5kIHNjYWxpbmcsIGV0Yy4gQXMgaXQgd2FzIHNob3duIGluIHRoZSBwcmV2aW91cyB0dXRvcmlhbCwgTkxQIGRhdGFzZXRzIGhhdmUgdGhlaXIgb3duIHByZXByb2Nlc3NpbmcgdG9vbHMuIFRoZXkgYXJlIG5vdCBnb2luZyB0byBiZSBhcHBsaWVkIGluIG91ciBkYXRhc2V0Lg0KDQoqIGB0ckNvbnRyb2xgIHBhcmFtZXRlciBkZWZpbmVzIHRoZSBtZXRob2QgdG8gZXN0aW1hdGUgdGhlIGVycm9yIG9mIHRoZSBjbGFzc2lmaWVyLiBJdCBpcyBkZWZpbmVkIGJ5IG1lYW5zIG9mIHRoZSBhcHBsaWNhdGlvbiBvZiB0aGUgYHRyYWluQ29udHJvbGAgZnVuY3Rpb24uIFRoaXMgYWxsb3dzIHRoZSB1c2Ugb2YgZGlmZmVyZW50IHBlcmZvcm1hbmNlIGVzdGltYXRpb24gcHJvY2VkdXJlcyBzdWNoIGFzIGstZm9sZCBjcm9zcy12YWxpZGF0aW9uLCBib290c3RyYXBwaW5nLCBldGMuIFdlIGFwcGx5IGEgMTAtZm9sZCBjcm9zcy12YWxpZGF0aW9uLCByZXBlYXRlZCAzIHRpbWVzLg0KDQoqIGBtZXRob2RgIHBhcmFtZXRlciBmaXhlcyB0aGUgdHlwZSBvZiBjbGFzc2lmaWNhdGlvbiBhbGdvcml0aG0gdG8gYmUgbGVhcm5lZC4gVGhlIHdpZGUgbGlzdCBvZiBhbGdvcml0aG1zIChhbmQgaXRzIHBhcmFtZXRlcnMpIGNvdmVyZWQgYnkgYGNhcmV0YCBjYW4gYmUgZm91bmQgaW4gaHR0cHM6Ly90b3BlcG8uZ2l0aHViLmlvL2NhcmV0L3RyYWluLW1vZGVscy1ieS10YWcuaHRtbC4gVGFraW5nIGludG8gYWNjb3VudCB0aGUgbGFyZ2UgZGltZW5zaW9uYWxpdHkgb2YgY2xhc3NpYyBOTFAgZGF0YXNldHMsIHRoZSB1c2Ugb2YgY2xhc3NpZmllcnMgY2FwYWJsZSB0byBkZWFsIHdpdGggdGhpcyBjaGFyYWN0ZXJpc3RpYyBpcyBoaWdobHkgcmVjb21tZW5kZWQuIEluIHRoaXMgdHV0b3JpYWwsIExpbmVhciBzdXBwb3J0IHZlY3RvciBtYWNoaW5lIChTVk0pIGFuZCBrLW5lYXJlc3QgbmVpZ2hib3VyIChLLU5OKSBtb2RlbHMgYXJlIGxlYXJuZWQgYW5kIHZhbGlkYXRlZC4NCg0KKiBgbWV0cmljYCBwYXJhbWV0ZXIgZml4ZXMgdGhlIHNjb3JlIHRvIGFzc2Vzcy12YWxpZGF0ZXMgdGhlIGdvb2RuZXNzIG9mIGVhY2ggbW9kZWwuIEFwYXJ0IGZyb20gdGhlIFJPQyBtZXRyaWMgdXNlZCBpbiB0aGlzIHR1dG9yaWFsLCBhIGxhcmdlIHNldCBvZiBtZXRyaWNzIGlzIG9mZmVyZWQ6IEFjY3VyYWN5IChwZXJjZW50YWdlIG9mIGNvcnJlY3QgY2xhc3NpZmljYXRpb24pLCBrYXBwYSwgU2VucyAoU2Vuc2l0aXZpdHkpLCBTcGVjaWZpY2l0eSAoU3BlYyksIFJNU0UgaW4gdGhlIGNhc2Ugb2YgcmVncmVzc2lvbiBwcm9ibGVtcy4uLiBJIGhhdmUgbm90IGZvdW5kIGEgbGlzdCB3aXRoIGFsbCB0aGUgbWV0cmljcyBvZmZlcmVkIGJ5IGBjYXJldGAsIGJ1dCBjb25zdWx0aW5nIHRoZSBoZWxwIG9mIHRoZSBwYWNrYWdlIHdpbGwgZ2l2ZSB5b3UgYSBicm9hZCBpZGVhLg0KDQpUYWtlIGludG8gYWNjb3VudCB0aGUgZm9sbG93aW5nIGZhY3RzIGFib3V0IHRoZSBtb2RlbC10cmFpbmluZyBzdGVwOg0KDQoqIHRoZSBleHByZXNzaW9uIGB0eXBlIH5gIGlzIHF1aXRlIHBvcHVsYXIgaW4gUiB0byBkZW5vdGUgdGhlIHNwZWNpZmljIHZhcmlhYmxlIHRvIGJlIHByZWRpY3RlZCwgZm9sbG93ZWQgYnkgdGhlIHNldCBvZiBwcmVkaWN0b3JzLiBBIHBvaW50IGluZGljYXRlcyB0aGF0IHRoZSByZXN0IG9mIHZhcmlhYmxlcyBhcmUgdXNlZCBhcyBwcmVkaWN0b3JzLg0KDQoqIHRvZ2V0aGVyIHdpdGggdGhlIGVzdGltYXRpb24gb2YgdGhlIHBlcmNlbnRhZ2UgcmVjb2duaXRpb24gKGFjY3VyYWN5KSwgdGhlIHZhbHVlIG9mIHRoZSAqS2FwcGEgc3RhdGlzdGljKiBpcyBzaG93bi4gSXQgaXMgYSBwb3B1bGFyIHNjb3JlIGluIE5MUCBzdHVkaWVzLiBJdHMgZGVmaW5pdGlvbiBpcyBub3QgdHJpdmlhbCBpbiB0aGUgY29udGV4dCBvZiBzdXBlcnZpc2VkIGNsYXNzaWZpY2F0aW9uLiBSb3VnaGx5LCB0aGlzIHNjb3JlIGNvbXBhcmVzIHRoZSDigJxvYnNlcnZlZOKAnSBhY2N1cmFjeSBvZiB0aGUgbGVhcm5lZCBjbGFzc2lmaWVyIHdpdGggcmVzcGVjdCB0byBhIHJhbmRvbSBjbGFzc2lmaWVyOiBtZWFzdXJpbmcgdGhlIHNjb3JlIGRpZmZlcmVuY2UgYmV0d2VlbiBvdXIgY2xhc3NpZmllciBhbmQgYSByYW5kb20gY2xhc3NpZmllci4gQSBsYXJnZXIgZGVmaW5pdGlvbiBvZiB0aGlzIG1ldHJpYyBjYW4gYmUgZm91bmQgaW4gaHR0cDovL3N0YXRzLnN0YWNrZXhjaGFuZ2UuY29tL3F1ZXN0aW9ucy84MjE2Mi9rYXBwYS1zdGF0aXN0aWMtaW4tcGxhaW4tZW5nbGlzaC4NCg0KKiB3aGlsZSB0aGUgbGluZWFyIFNWTSBjbGFzc2lmaWVyIGRvZXMgbm90IGhhdmUgcGFyYW1ldGVycywgSy1OTiBoYXMgdGhlIOKAnG51bWJlciBvZiBuZWlnaGJvdXJz4oCdIChLKSBrZXkgcGFyYW1ldGVyLiBCeSBkZWZhdWx0LCBjaGFuZ2luZyB0aGUgdmFsdWUgb2YgdGhlIHBhcmFtZXRlciwgYGNhcmV0YCBldmFsdWF0ZXMgMyBtb2RlbHMgKGB0dW5lTGVuZ3RoYCBlcXVhbCB0byAzKS4gVGhlIGB0dW5lTGVuZ3RoYCBvcHRpb24gb2YgdGhlIGB0cmFpbmAgZnVuY3Rpb24gZml4ZXMgdGhlIG51bWJlciBvZiB2YWx1ZXMgb2YgZWFjaCBwYXJhbWV0ZXIgdG8gYmUgY2hlY2tlZC4gRm9yIGV4YW1wbGUsIGlmIHRoZSBjbGFzc2lmaWVyIGhhcyAyIHBhcmFtZXRlcnMgYW5kIHRoZSBgdHVuZUxlbmd0aGAgcGFyYW1ldGVyIGlzIG5vdCBjaGFuZ2VkLCAzIHggMyA9IDkgbW9kZWxzIGFyZSBldmFsdWF0ZWQuIFRoZSBgdHVuZUdyaWRgIG9wdGlvbiBvZmZlcnMgdGhlIHBvc3NpYmlsaXR5IHRvIHNlbGVjdCBhbW9uZyBhIHNldCBvZiB2YWx1ZXMgdG8gYmUgdHVuZWQtdGVzdGVkLg0KDQoqIGBjYXJldGAgc3VwcG9ydHMgbW9yZSB0aGFuIDE1MCBzdXBlcnZpc2VkIGNsYXNzaWZpY2F0aW9uIGFuZCByZWdyZXNzaW9uIGFsZ29yaXRobXMuIEEgc21hbGwgcG9ydGlvbiBvZiB0aGVtIGFyZSBsZWFybmVkIGJ5IG1lYW5zIG9mIHRoZSBzb2Z0d2FyZSBvZiB0aGUgcGFja2FnZSBpdHNlbGYuIFRoZSBtYWpvcml0eSBvZiB0aGUgYWxnb3JpdGhtcyBhcmUgbGVhcm5lZCBieSBvdGhlciBgUmAgcGFja2FnZXMgd2hpY2ggYXJlIGNvbnZlbmllbnRseSBhY2Nlc3NlZCBieSBgY2FyZXRgLg0KDQojIyBJbnRlcm5hbCBwZXJmb3JtYW5jZSBlc3RpbWF0aW9uIGluIHRoZSB0cmFpbmluZyBwYXJ0aXRpb24NCg0KYGBge3J9DQojIGZpeGluZyB0aGUgcGVyZm9ybWFuY2UgZXN0aW1hdGlvbiBwcm9jZWR1cmUNCmN0cmwgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgcmVwZWF0cyA9IDMpDQpzdm1Nb2RlbDN4MTBjdiA8LSB0cmFpbih0eXBlIH4gLiwgZGF0YSA9IHRyYWluaW5nLCBtZXRob2QgPSAic3ZtTGluZWFyIiwgdHJDb250cm9sID0gY3RybCkNCnN2bU1vZGVsM3gxMGN2DQprbm5Nb2RlbDN4MTBjdiA8LSB0cmFpbih0eXBlIH4gLiwgZGF0YSA9IHRyYWluaW5nLCBtZXRob2QgPSAia25uIiwgdHJDb250cm9sID0gY3RybCkNCmtubk1vZGVsM3gxMGN2DQpgYGANCiMjIFR1bmluZyBvZiB0aGUgcGFyYW1ldGVycyBvZiB0aGUgc2VsZWN0ZWQgY2xhc3NpZmllcnMNCg0KVGhlIHRyYWluaW5nIHByb2Nlc3MgY2FuIHN0aWxsIGJlIGVucmljaGVkIHdpdGggZXh0cmEgcGFyYW1ldGVycyBpbiB0aGUgYHRyYWluQ29udHJvbGAgZnVuY3Rpb246IGBzdW1tYXJ5RnVuY3Rpb25gIGNvbnRyb2xzIHRoZSB0eXBlIG9mIGV2YWx1YXRpb24gbWV0cmljcy4gSW4gYmluYXJ5IGNsYXNzaWZpY2F0aW9uIHByb2JsZW1zIChlLmcuIOKAnHNjaWVuY2XigJ0gdmVyc3VzIOKAnHJlbGlnaW9u4oCdKSB0aGUgYHR3b0NsYXNzU3VtbWFyeWAgb3B0aW9uIGRpc3BsYXlzIFthcmVhIHVuZGVyIHRoZSBST0MgY3VydmVdKGh0dHA6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvUmVjZWl2ZXJfb3BlcmF0aW5nX2NoYXJhY3RlcmlzdGljKSwgc2Vuc2l0aXR5LXJlY2FsbCBhbmQgc3BlY2lmaWNpdHkgbWV0cmljcy4gVG8gZG8gc28sIGl0IGlzIGFsc28gbmVlZGVkIHRvIGFjdGl2YXRlIHRoZSBgY2xhc3NQcm9ic2Agb3B0aW9uIHdoaWNoIHNhdmVzIHRoZSBwcm9iYWJpbGl0eSB0aGF0IHRoZSBjbGFzc2lmaWVyIGFzc2lnbnMgdG8gZWFjaCBzYW1wbGUgYmVsb25naW5nIHRvIGVhY2ggY2xhc3MtdmFsdWUuDQoNCmBgYHtyfQ0KbGlicmFyeShwUk9DKQ0KY3RybCA8LSB0cmFpbkNvbnRyb2woDQogICAgbWV0aG9kID0gInJlcGVhdGVkY3YiLCByZXBlYXRzID0gMywgY2xhc3NQcm9icyA9IFRSVUUsDQogICAgc3VtbWFyeUZ1bmN0aW9uID0gdHdvQ2xhc3NTdW1tYXJ5DQopDQprbm5Nb2RlbDN4MTBjdlJPQyA8LSB0cmFpbih0eXBlIH4gLiwNCiAgICBkYXRhID0gdHJhaW5pbmcsIG1ldGhvZCA9ICJrbm4iLCB0ckNvbnRyb2wgPSBjdHJsLA0KICAgIG1ldHJpYyA9ICJST0MiLCB0dW5lTGVuZ3RoID0gMTANCikNCmtubk1vZGVsM3gxMGN2Uk9DDQpwbG90KGtubk1vZGVsM3gxMGN2Uk9DKQ0KYGBgDQojIyBQcmVkaWN0IHRoZSBjbGFzcy10eXBlIG9mIGZ1dHVyZSB1bnNlZW4tdW5sYWJlbGVkIHRleHRzDQoNCkluIG9yZGVyIHRvIHByZWRpY3QgdGhlIGNsYXNzIHZhbHVlIG9mIHVuc2VlbiBkb2N1bWVudHMgb2YgdGhlIHRlc3QgcGFydGl0aW9uIGNhcmV0IHVzZXMgdGhlIGNsYXNzaWZpZXIgd2hpY2ggc2hvd3MgdGhlIGJlc3QgYWNjdXJhY3kgZXN0aW1hdGlvbiBvZiB0aGVpciBwYXJhbWV0ZXJzLiBGdW5jdGlvbiBwcmVkaWN0IGltcGxlbWVudHMgdGhpcyBmdW5jdGlvbmFsaXR5LiBDb25zdWx0IGl0cyBwYXJhbWV0ZXJzLiBUaGUgYHR5cGVgIHBhcmFtZXRlciwgYnkgbWVhbnMgb2YgaXRzIGBwcm9ic2AgdmFsdWUsIG91dHB1dHMgdGhlIHByb2JhYmlsaXR5IG9mIHRlc3QgZWFjaCBzYW1wbGUgYmVsb25naW5nIHRvIGVhY2ggY2xhc3MgKOKAnGEtcG9zdGVyaW9yaeKAnSBwcm9iYWJpbGl0eSkuIE9uIHRoZSBvdGhlciBoYW5kLCB0aGUgYHJhd2AgdmFsdWUgb3V0cHV0cyB0aGUgY2xhc3MgdmFsdWUgd2l0aCB0aGUgbGFyZ2VzdCBwcm9iYWJpbGl0eS4gQnkgbWVhbnMgb2YgdGhlIGByYXdgIG9wdGlvbiB0aGUgY29uZnVzaW9uIG1hdHJpeCBjYW4gYmUgY2FsY3VsYXRlZDogdGhpcyBjcm9zc2VzLCBmb3IgZWFjaCB0ZXN0IHNhbXBsZSwgcHJlZGljdGVkIHdpdGggcmVhbCBjbGFzcyB2YWx1ZXMuDQoNCmBgYHtyfQ0Kc3ZtTW9kZWxDbGFzc2VzIDwtIHByZWRpY3Qoc3ZtTW9kZWwzeDEwY3YsIG5ld2RhdGEgPSB0ZXN0aW5nLCB0eXBlID0gInJhdyIpDQpjb25mdXNpb25NYXRyaXgoZGF0YSA9IHN2bU1vZGVsQ2xhc3NlcywgdGVzdGluZyR0eXBlKQ0KYGBgDQoNCiMjIFN0YXRpc3RpY2FsIGNvbXBhcmlzb24gYmV0d2VlbiB0d28gY2xhc3NpZmllcnMgYnkgbWVhbnMgb2YgdC10ZXN0DQoNCkNhbiBhIHN0YXRpc3RpY2FsIGNvbXBhcmlzb24gYmUgcGVyZm9ybWVkIGJldHdlZW4gdGhlIDN4MTBjdiB2YWxpZGF0aW9uIHJlc3VsdHMgb2YgSy1OTiBhbmQgU1ZNPyBOb3RlIHRoYW4gaW4gb3VyIGNhc2UsIGR1ZSB0byB0aGUgMyByZXBldGl0aW9ucyBvZiB0aGUgMTAtZm9sZCBjcm9zcy12YWxpZGF0aW9uIHByb2Nlc3MsIHRoZXJlIGFyZSAzMCByZXNhbXBsaW5nIHJlc3VsdHMgZm9yIGVhY2ggY2xhc3NpZmllci4gRmlyc3QsIHJlc3VsdHMgb2YgYm90aCBjbGFzc2lmaWVycyBhcmUgY3Jvc3NlZCB1c2luZyB0aGUgYHJlc2FtcGxlc2AgZnVuY3Rpb24uIEFzIHRoZSBgc2V0LnNlZWRgIGRpZCBub3QgY2hhbmdlLCB0aGUgc2FtZSBwYWlyZWQgY3Jvc3MtdmFsaWRhdGlvbiBzdWJzZXRzIG9mIHNhbXBsZXMgd2VyZSB1c2VkIGZvciBib3RoIGNsYXNzaWZpZXJzLiBUaGlzIGZvcmNlcyB0byB1c2UgYSBwYWlyZWQgdC10ZXN0IHRvIGNhbGN1bGF0ZSB0aGUgc2lnbmlmaWNhbmNlIG9mIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGJvdGggY2xhc3NpZmllcnMuIEEgc2ltcGxlIHBsb3QgaXMgZHJhd24sIHNob3dpbmcgdGhlIGFjY3VyYWN5IGRpZmZlcmVuY2VzIGJldHdlZW4gYm90aCBtb2RlbHMgZm9yIGVhY2ggb2YgdGhlIDMwIGNyb3NzLXZhbGlkYXRpb24gZm9sZHM6IG5vdGUgdGhhdCBzdm0gaGFzIGEgYmV0dGVyIGFjY3VyYWN5IHRoYW4ga25uIGZvciBhbGwgdGhlIGNyb3NzLXZhbGlkYXRpb24gZm9sZCByZXN1bHRzIChGaWd1cmUgMikuDQoNClVzaW5nIHRoZSBgZGlmZmAgZnVuY3Rpb24gb3ZlciB0aGUgYHJlc2FtcHNgIG9iamVjdCB3aGljaCBzYXZlcyB0aGUg4oCcY3Jvc3NpbmfigJ0gb2YgYm90aCBjbGFzc2lmaWVycywgd2UgY2FuIHNob3cgYSByaWNoIG91dHB1dCBvZiB0aGUgcGVyZm9ybWVkIGNvbXBhcmlzb246IGVhY2ggbnVtYmVyIG1hdHRlcnMuIFRoZSBvdXRwdXQgc2hvd3MsIGZvciBlYWNoIG1ldHJpYyAoYXJlYSB1bmRlciB0aGUgUk9DIGN1cnZlLCBzZW5zaXRpdml0eSwgc3BlY2lmaWNpdHkpLCB0aGUgZGlmZmVyZW5jZSBvZiB0aGUgbWVhbiAocG9zaXRpdmUgb3IgbmVnYXRpdmUsIGZvbGxvd2luZyB0aGUgb3JkZXIgaW4gdGhlIGByZXNhbXBsZXNgIGZ1bmN0aW9uKSBiZXR3ZWVuIGJvdGggY2xhc3NpZmllcnMuIFRoZSBwLXZhbHVlIG9mIHRoZSBhc3NvY2lhdGVkIHQtdGVzdCBpcyBhbHNvIHNob3duLg0KDQpUaGUgaW50ZXJwcmV0YXRpb24gb2YgdGhlIHAtdmFsdWUgaGFzIHRoZSBrZXkuIEl0IGlzIHJlbGF0ZWQgd2l0aCB0aGUgcmlzayBvZiBlcnJvbmVvdXNseSBkaXNjYXJkaW5nIHRoZSBudWxsaHlwb3RoZXNpcyBvZiBzaW1pbGFyaXR5IGJldHdlZW4gY29tcGFyZWQgY2xhc3NpZmllcnMsIHdoZW4gdGhlcmUgaXMgbm8gcmVhbCBkaWZmZXJlbmNlLiBSb3VnaGx5IHNwZWFraW5nLCBpdCBjYW4gYWxzbyBiZSBpbnRlcnByZXRlZCBhcyB0aGUgZGVncmVlIG9mIHNpbWlsYXJpdHkgYmV0d2VlbiBib3RoIGNsYXNzaWZpZXJzLiBBIHAtdmFsdWUgc21hbGxlciB0aGFuIDAuMDUgKG9yIDAuMSwgZGVwZW5kaW5nIG9uIHlvdXIgaW50ZXJwcmV0YXRpb24gYW5kIHRocmVzaG9sZCkgYWxlcnRzIGFib3V0IHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZXMgYmV0d2VlbiBib3RoIGNsYXNzaWZpZXJzLCBodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9TdGF0aXN0aWNhbF9zaWduaWZpY2FuY2UuIFRoYXQgaXMsIHdoZW4gdGhlIHJpc2sgb2YgZXJyb25ldXNseSBkaXNjYXJkaW5nIHRoZSBoeXBvdGhlc2lzIG9mIHNpbWlsYXJpdHkgYmV0d2VlbiBib3RoIGNsYXNzaWZpZXJzIGlzIGxvdywgd2UgYXNzdW1lIHRoYXQgdGhlcmUgaXMgYSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IGRpZmZlcmVuY2UgYmV0d2VlbiBjbGFzc2lmaWVycy4NCg0KYGBge3J9DQpyZXNhbXBzIDwtIHJlc2FtcGxlcyhsaXN0KGtubiA9IGtubk1vZGVsM3gxMGN2LCBzdm0gPSBzdm1Nb2RlbDN4MTBjdikpDQpzdW1tYXJ5KHJlc2FtcHMpDQp4eXBsb3QocmVzYW1wcywgd2hhdCA9ICJCbGFuZEFsdG1hbiIpDQpkaWZmcyA8LSBkaWZmKHJlc2FtcHMpDQpzdW1tYXJ5KGRpZmZzKQ0KYGBgDQoNCiMgRXZhbHVhdGlvbiBleGVyY2lzZQ0KDQpUb2dldGhlciB3aXRoIHRoZSBleHBsYW5hdGlvbnMgb2YgdGhlIHByZXZpb3VzIGB0bWAgdHV0b3JpYWwsIGNyZWF0ZSBhIGNvcnB1cyBhbmQgYSBkb2N1bWVudC10ZXJtIG1hdHJpeCB3aGljaCBjaGFyYWN0ZXJpc2VzIGEgKipzdXBlcnZpc2VkIGNsYXNzaWZpY2F0aW9uKiogcHJvYmxlbSBvZiB5b3VyIGludGVyZXN0OiAyIGRpZmZlcmVudCB0eXBlcyBvZiBkb2N1bWVudHMsIGRpZmZlcmVudCB0eXBlcyBvZiBzZW50aW1lbnRzLCBkaWZmZXJlbnQgdHlwZXMgb2YgdGV4dCBkaWZmaWN1bHRpZXMuLi4gdGhhdCBpcywgdGhlIGNvcnB1cyBoYXMgdG8gYmUgYW5ub3RhdGVkLCBhdCBsZWFzdCB3aXRoIHR3byBkaWZmZXJlbnQgbGFiZWxzLCBpbiBvcmRlciB0byBwcm9jZWVkIHdpdGggc3VwZXJ2aXNlZCBjbGFzc2lmaWNhdGlvbiB0YXNrcy4NCg0KQXMgdGhlIG91dHB1dCBvZiB0aGUgZXhlcmNpc2UsIHdlIGhvcGUgYSBkb2N1bWVudCBzaW1pbGFyIHRvIHRoaXMgdHV0b3JpYWw6IHRoYXQgaXMsIGEgKipub3RlYm9vayoqIHdoaWNoIG1peGVzIGNvZGUgYW5kIHRoZSBleHBsYW5hdGlvbnMgb2YgdGhpcyBjb2RlIGFuZCB5b3VyIGRlY2lzaW9ucy4gQSByaWNoIGRvY3VtZW50LiBUaGUgZG9jdW1lbnQgaGFzIHRvIGFsdGVybmF0ZSBleHBsYW5hdGlvbnMgYWJvdXQgeW91ciBkZWNpc2lvbiBhbmQgdGhlIGBSYCBjb2RlIHRoYXQgaW1wbGVtZW50cyBpdDogY29sbG9xdWlhbGx5LCBkbyDigJxkbyB5b3VyIG93biB0dXRvcmlhbC1ub3RlYm9va+KAnSwgd2l0aCB5b3VyIGNob3NlbiBhcHBsaWNhdGlvbi1jb3JwdXMuIFNwZWNpZmljYWxseSwgdGhlIGRvY3VtZW50IHRoYXQgeW91IGhhdmUgcmVhZCBoYXMgYmVlbiBlZGl0ZWQgd2l0aCB0aGUgYGtuaXRyYCBwYWNrYWdlIGFuZCB0aGUgYFJTdHVkaW9gIHNvZnR3YXJlOiBgUm53YCBmb3JtYXQuIFlvdSBjYW4gb2YgY291cnNlIGVkaXQgd2l0aCB0aGlzIHRlY2hub2xvZ3kgb3IgYW5vdGhlciBvZiB5b3VyIGNvbnZlbmllbmNlOiBSLW1hcmtkb3duLCBqdXB5dGVyIG5vdGVib29rcywgZXRjLg0KDQpZb3VyIG5vdGVib29rLXR1dG9yaWFsIGhhcyB0byBjb3ZlciB0aGUgZm9sbG93aW5nIGl0ZW1zOg0KDQoqIGEgc2hvcnQgZGVzY3JpcHRpb24gb2YgeW91ciBkYXRhc2V0IGFuZCBOTFAgcHJvYmxlbS4gVGhlIG5hdHVyZSBvZiB0aGUgcHJvYmxlbSBhbmQgdGhlIG9iamVjdGl2ZSBvZiB0aGUgbW9kZWxpemF0aW9uLCB0aGUgY2xhc3MgdmFyaWFibGVzIHRvIGJlIHByZWRpY3RlZCBhbmQgaXRzIHBvc3NpYmxlIHZhbHVlcywgaG93IGNhbiB0aGUgbW9kZWxzIGJlIGV2YWx1YXRlZCwgdGhlIHByZWRpY3RpdmUgZmVhdHVyZXMgYW5kIGFub3RoZXIgY29uY2VwdCB3aGljaCBoZWxwcyBpbiB0aGUgZGVzY3JpcHRpb24gb2YgdGhlIGRvbWFpbi4NCg0KKiBvdmVyIHRoZSB3aG9sZSBjb3JwdXMsIGFwcGx5IG9uZSBvZiB0aGUgKipvdXRsaWVyIGRldGVjdGlvbioqIHRlY2huaXF1ZXMgZXhwb3NlZCBpbiBjbGFzcyBhbmQgY29uc2lkZXIgZGVsZXRpbmcgZXh0cmVtZS1vdXRsaWVyIGRvY3VtZW50cy4NCg0KKiBzaG93IGEgKip3b3JkY2xvdWQqKiBiYXNlZCBvbiB5b3VyIGNvcnB1cyBiYWctb2Ytd29yZHMuDQoNCiogdGhlIHdheSB0byAqKnBhcnRpdGlvbiB0aGUgY29ycHVzIGluIHRyYWluLXRlc3QqKiBkb2VzIG5vdCBoYXZlIHRvIGJlIHRoZSBzYW1lIG9mIHRoZSB0dXRvcmlhbC4gVGhlcmUgYXJlIG90aGVyIHBvc3NpYmlsaXRpZXMgc3VjaCBhcyBgY3JlYXRlRm9sZHMoKWAsIGBjcmVhdGVSZXNhbXBsZSgpYCwgZXRjLg0KDQoqIGNob29zZSB0d28gb3RoZXIgKipjbGFzc2lmaWNhdGlvbiBhbGdvcml0aG1zKiogbm90IHVzZWQgaW4gdGhpcyB0dXRvcmlhbC4gRm9yIHlvdXIgc2VsZWN0aW9uLCAqKmRlc2NyaWJlKiogYnJpZWZseSB0aGVpciBiZWhhdmlvdXIgYW5kIGl0cyBwYXJhbWV0ZXJzLiBUYWtlIGludG8gYWNjb3VudCB0aGUgZWZmZWN0IG9mIHRoZSBleHBvc2VkIGB0dW5lTGVuZ3RoYCBvcHRpb246IHVzZSB0aGUgYHR1bmVMZW5ndGhgIGFuZC1vciBgdHVuZUdyaWRgIG9wdGlvbnMgdG8gKip0dW5lIHRoZSBwYXJhbWV0ZXJzIG9mIGNob3NlbiBjbGFzc2lmaWNhdGlvbiBhbGdvcml0aG1zKiouDQoNCiogcmVtZW1iZXIgdGhhdCB0aGUgYHRyYWluQ29udHJvbCgpYCBmdW5jdGlvbiBhbGxvd3MgdG8gc2VsZWN0IHRoZSBvcHRpb25zIHRvIHZhbGlkYXRlIHRoZSBjbGFzc2lmaWVyLiBDb25zdWx0IGl0cyBvcHRpb25zIGFuZCBkbyAqKnZhcmlhdGlvbnMqKiB3aXRoIHJlc3BlY3QgdG8gbXkgc2VsZWN0aW9uLg0KDQoqIGlmIGNsYXNzLWxhYmVsIGRpc3RyaWJ1dGlvbnMgYXJlICoqdW5iYWxhbmNlZCoqIGluIHlvdXIgY29ycHVzLCB0ZXN0IGEg4oCYcmVzYW1wbGluZ+KAmSBtZXRob2Qgd2hpY2ggd2lsbCB0cnkgdG8gaW1wcm92ZSB0aGUgcmVjb3ZlcnkgcmF0ZSBpbiB0aGUgbWlub3JpdHkgY2xhc3MuIFdoaWNoIGlzIHRoZSBjbGFzcy1sYWJlbCBkaXN0cmlidXRpb24gb2YgeW91ciBjb3JwdXM/IFNob3cgaXQuIFN0dWR5IHRoZSBvcHRpb25zIG9mIHRoZSBgc2FtcGxpbmdgIG9wdGlvbiBpbiBgdHJhaW5Db250cm9sYDogaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL2NhcmV0L3ZlcnNpb25zLzYuMC04NC90b3BpY3MvdHJhaW5Db250cm9sLiBgY2FyZXRgIGhhcyBhIGJyaWVmIGFuZCBpbnR1aXRpdmUgdHV0b3JpYWwgYWJvdXQgdGhlIHRvcGljOiBjaGVjayB0aGUgZmlyc3Qgc2VudGVuY2VzIG9mIHRoaXMgbGluayBodHRwczovL3RvcGVwby5naXRodWIuaW8vY2FyZXQvc3Vic2FtcGxpbmctZm9yLWNsYXNzLWltYmFsYW5jZXMuaHRtbC4NCg0KKiB3aGlsZSBpbiBteSB0dXRvcmlhbCB0aGUgKipjaG9zZW4gbWV0cmljKiogdG8gdmFsaWRhdGUgdGhlIG1vZGVscyBoYXMgYmVlbiBST0MsIHlvdSBjYW4gb2YgY291cnNlIHNlbGVjdCBhbm90aGVyIG1ldHJpYyBvZiB5b3VyIGNvbnZlbmllbmNlOiBhY2N1cmFjeSwgc2Vuc2l0aXZpdHksIEYxLCBrYXBwYSwgZXRjLiBJZiB5b3UgZG8gbm90IGhhdmUgcHJpb3IgaW5mb3JtYXRpb24sIHRoZSBkZWZhdWx0IGFuZCBjb21tb24tc2Vuc2Ugc2NvcmUgbWV0cmljIHRvIGNob29zZSBpbiB5b3VyIHN1cGVydmlzZWQgY2xhc3NpZmljYXRpb24gYXBwbGljYXRpb24gaXMgYWNjdXJhY3kuDQoNCiogdGVzdCB0aGUgKipmZWF0dXJlIHNlbGVjdGlvbioqIG9wdGlvbnMgb2ZmZXJlZCBieSBgY2FyZXRgLiBBdCBsZWFzdCwgYXBwbHkgb25lIG9mIHRoZW0gb3ZlciB5b3VyIGNvcnB1cy4gQ2FuIHRoZSBmZWF0dXJlIHN1YnNldCB0byBsZWFybiB0aGUgZmluYWwgY2xhc3NpZmllciBiZSBvcHRpbWl6ZWQ/IGBjYXJldGAgb2ZmZXJzIGdlbmV0aWMgYWxnb3JpdGhtcyBieSB3cmFwcGVyLCBzaW1tdWxhdGVkIGFubmVhbGluZyBieSB3cmFwcGVyLCByZWN1cnNpdmUgYmFja3dhcmQgZWxpbWluYXRpb24gYnkgd3JhcHBlci4gT3IgdW5pdmFyaWF0ZSBmaWx0ZXJzIHRoYXQgcmFuayBpbmRlcGVuZGVudGx5IHRoZSB2YXJpYWJsZXMgd2l0aCByZXNwZWN0IHRvIHRoZWlyIHJlbGV2YW5jZS1jb3JyZWxhdGlvbiB3aXRoIHJlc3BlY3QgdG8gdGhlIGFubm90YXRpb24tY29sdW1uOw0KDQoqIGxldOKAmXMgbm93IHByYWN0aXNlIHdpdGggKipmZWF0dXJlIGV4dHJhY3Rpb24qKiwgd2hlcmUgYSBzZXQgb2YgZmVhdHVyZXMgaXMg4oCYY29uc3RydWN0ZWTigJkgZnJvbSBvcmlnaW5hbCBvbmVzOiBjb21tb25seSwgbGluZWFyIGNvbWJpbmF0aW9ucyBvZiBvcmlnaW5hbCBvbmVzLiBJbiB0aGlzIGFyZWEsIGl0IGlzIGxpa2VseSB0aGF0IHlvdSBrbm93IGFsZ29yaXRobXMgc3VjaCBhcyBwcmluY2lwYWwgY29tcG9uZW50IGFuYWx5c2lzIC0gUENBLCBzaW5ndWxhciB2YWx1ZSBkZWNvbXBvc2l0aW9uLCBldGMuIEl0IGlzIGVhc3kgdG8gbGVhcm4gYSBQQ0EgaW4gYFJgIHdpdGggdGhlIGBwcmNvbXBgIGZ1bmN0aW9uIGFuZCB2aXN1YWxpemUgaW4gYSAyLUQgZ3JhcGggdHdvIGZpcnN0IGNvbXBvbmVudHMgKGkuZS4gdGhvc2UgdGhhdCBzYXZlIGxhcmdlciB2YXJpYWJpbGl0eSBvZiBvcmlnaW5hbCBkYXRhKTogdHJ5aW5nIHRvIGZpbmQgYW4gaW50dWl0aXZlIHNlcGFyYXRpb24gb2YgcHJvYmxlbWNsYXNzZXMuIFRyeSBpdCBhbmQgY29tbWVudCB0aGUgcmVzdWx0cy4NCg0KKiByZW1lbWJlciB0aGF0IHRoZSBgcHJlZGljdCgpYCBmdW5jdGlvbnMgYWxsb3dzIHRvIHNlbGVjdCB0aGUgb3B0aW9ucyB0byAqKnByZWRpY3QgdGhlIGNsYXNzLWxhYmVsIG9mIHRoZSBzYW1wbGVzIG9mIHRoZSB0ZXN0IHBhcnRpdGlvbioqLiBDb25zdWx0IGl0cyBvcHRpb25zIGFuZCBkbyB2YXJpYXRpb25zIHdpdGggcmVzcGVjdCB0byBteSBzZWxlY3Rpb24uIERvIGNsYXNzLWxhYmVsIHByZWRpY3Rpb25zIG92ZXIgbmV3LXVuc2Vlbi11bm5hbm90YXRlZCBkb2N1bWVudHMuDQoNCiogdG8gZG8gdGhlIGZpbmFsICoqc3RhdGlzdGljYWwgY29tcGFyaXNvbioqIGJldHdlZW4geW91ciBwYWlyIG9mIHNlbGVjdGVkIGNsYXNzaWZpZXJzLCBpdCBpcyBuZWVkZWQgdG8gdW5kZXJzdGFuZCB0aGUgdXNlIGFuZCBvdXRwdXQgb2YgYHJlc2FtcGxlcygpYCwgYHN1bW1hcnkoKWAsIGBkaWZmKClgIGZ1bmN0aW9ucy4gVGhlIG91dHB1dCBpcyBsb25nIGJ1dCByaWNoIGluIGluZm9ybWF0aW9uIHRvIGV4dHJhY3QgY29uY2x1c2lvbnMgb2YgdGhlIGNvbXBhcmlzb24uIEludGVycHJldCB0aGUgY2FsY3VsYXRlZCAqKnAtdmFsdWUqKiB0byBhbmFseXplIHRoZSBzaWduaWZpY2FuY2Ugb2YgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gY29tcGFyZWQgY2xhc3NpZmllcnMuIEluIGl0cyBjdXJyZW50IGltcGxlbWVudGF0aW9uLCBgY2FyZXRgIGFwcGxpZXMgYSB0LXRlc3QgdG8gb2J0YWluIHRoZSBzaWduaWZpY2FuY2Ugb2YgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gdGhlIHBhaXIgb2YgY29tcGFyZWQgY2xhc3NpZmllcnMuDQoNCiMgUmVmZXJlbmNlcw0KDQpbMV0gTWF4IEt1aG4uIENvbnRyaWJ1dGlvbnMgZnJvbSBKZWQgV2luZywgU3RldmUgV2VzdG9uLCBBbmRyZSBXaWxsaWFtcywgQ2hyaXMgS2VlZmVyLCBBbGxhbiBFbmdlbGhhcmR0LCBUb255IENvb3BlciwgWmFjaGFyeSBNYXllciwgYW5kIHRoZSBSIENvcmUgVGVhbS4gY2FyZXQ6IENsYXNzaWZpY2F0aW9uIGFuZCBSZWdyZXNzaW9uIFRyYWluaW5nLCAyMDE0LiBSIHBhY2thZ2UgdmVyc2lvbiA2LjAtMzUuDQoNClsyXSBNLiBLdWhuIGFuZCBLLiBKb2huc29uLiBBcHBsaWVkIFByZWRpY3RpdmUgTW9kZWxpbmcuIFNwcmluZ2VyLCAyMDEzLg0K