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.
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.
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.
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.
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
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
.
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.
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)
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)
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
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.
LS0tDQp0aXRsZTogJ0NsdXN0ZXJpbmcgd29yZHMgYW5kIGNsYXNzaWZ5aW5nIGRvY3VtZW50cyB3aXRoIFInDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IA0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KLS0tDQoNCiMgU3RhcnRpbmcgZnJvbSB0aGUgIkRvY3VtZW50VGVybSBtYXRyaXgiIGJ1aWx0IGluIHByZXZpb3VzIHR1dG9yaWFsDQoNCk9uY2UgdGhlIGRvY3VtZW50LXRlcm0gbWF0cml4IHdpdGggYW4gc3BlY2lmaWMgc3BhcnNlbmVzcyB2YWx1ZSBpcyBlbGVjdGVkIGFuZCBkZXBlbmRpbmcgb24gdGhlIGludGVyZXN0cyBvZiB0aGUgTkxQIGV4cGVydCwgZGlmZmVyZW50IHR5cGVzIG9mIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIGNhbiBiZSBsZWFybmVkLiBUaGlzIHR1dG9yaWFsIGNvdmVycyB0aGUgY2x1c3RlcmluZyBvZiB3b3JkcyB3aXRoIHNpbWlsYXIgcGF0dGVybnMgb2Ygb2NjdXJyZW5jZXMgYWNyb3NzIGRvY3VtZW50cyBhbmQgdGhlIGNsYXNzaWZpY2F0aW9uIG9mIGRvY3VtZW50cy4gV2UgY29udGludWUgd29ya2luZyB3aXRoIHRoZSBkb2N1bWVudC10ZXJtIG1hdHJpY2VzIGJ1aWx0IGluIHRoZSBwcmV2aW91cyB0dXRvcmlhbCwg4oCcQSBzaG9ydCBpbnRyb2R1Y3Rpb24gdG8gdGhlIGB0bWAgKHRleHQgbWluaW5nKSBwYWNrYWdlIGluIGBSYDogdGV4dCBwcm9jZXNzaW5n4oCdLg0KDQpZb3UgaGF2ZSBhbHJlYWR5IHJlYWxpemVkIHRoYXQgYFJgIHdvcmtzIGJ5IGNyZWF0aW5nIOKAnG9iamVjdHPigJ0gYW5kIGNvbnN1bHRpbmcgdGhlaXIgYXR0cmlidXRlcy4gT2JqZWN0cyBhcmUgb2YgZGlmZmVyZW50IHR5cGVzOiBhbmQgdGhpcyBjb25zdHJhaW5zIHRoZSB0eXBlIG9mIG9wZXJhdGlvbnMgdGhhdCBjYW4gYmUgYXBwbGllZCB0byBlYWNoIG9iamVjdC4gRm9yIGV4YW1wbGUsIHRleHQgcHJlcHJvY2Vzc2luZyBvcGVyYXRpb25zIGNhbiBvbmx5IGJlIGFwcGxpZWQgdG8gYW4gb2JqZWN0IG9mIGBWQ29ycHVzYCB0eXBlLiBPbiB0aGUgb3RoZXIgaGFuZCwgbWFjaGluZSBsZWFybmluZyBvcGVyYXRpb25zIChlLmcuIGxlYXJuaW5nIGNsYXNzaWZpZXJzKSBjYW4gbm90IGJlIGFwcGxpZWQgdG8gYSBgVkNvcnB1c2AgdHlwZSBvYmplY3Q6IGl0IGhhcyB0byBiZSB0cmFuc2Zvcm1lZCAoaS5lLiDigJxjYXN0aW5n4oCdKSB0byBhIGRhdGEtbWF0cml4IG9yIGRhdGEtZnJhbWUgdHlwZS4NCg0KIyBDbHVzdGVyaW5nIG9mIHdvcmRzIHdpdGggc2ltaWxhciBwYXR0ZXJucyBvZiBvY2N1cnJlbmNlcyBhY3Jvc3MgZG9jdW1lbnRzDQoNCldlIHRyeSB0byBmaW5kIGNsdXN0ZXJzIG9mIHdvcmRzIHdpdGggaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcsIGEgcG9wdWxhciBjbHVzdGVyaW5nIHRlY2huaXF1ZXMgd2hpY2ggYnVpbGRzIGEgZGVuZG9ncmFtIHRvIGl0ZXJhdGl2ZWx5IGdyb3VwIHBhaXJzIG9mIHNpbWlsYXIgb2JqZWN0cy4gVG8gZG8gc28sIGEgbWF0cml4IHdoaWNoIGhhcyByZW1vdmVkIHNwYXJzZSBpcyBuZWVkZWQ6IHRoZSBzdGFydGluZyBwb2ludCBpcyB0aGUgMC44IHNwYXJzZW5lc3MtdmFsdWUgbWF0cml4LiBBZnRlciB0aGUgYXBwbGljYXRpb24gb2YgdGhlIG1hdHJpeC1jYXN0aW5nIG9wZXJhdG9yLCBudW1iZXIgb2Ygb2NjdXJyZW5jZXMgYXJlIHNjYWxlZDogZmlyc3QgY29sdW1uICh0ZXJtKSBtZWFuIGlzIHN1YnN0cmFjdGVkLCBhbmQgdGhlbiB0aGlzIGlzIGRpdmlkZWQgYnkgaXRzIHN0YW5kYXJkIGRldmlhdGlvbi4gSXQgaXMgbmVlZGVkIHRvIGNhbGN1bGF0ZSB0aGUgZGlzdGFuY2UgYmV0d2VlbiBwYWlycyBvZiBvYmplY3RzICh0ZXJtcyk6IHRoZXNlIGFyZSBzYXZlZCBpbiBgZGlzdE1hdHJpeGAuIFRoZSBgZGlzdGAgb3BlcmF0b3IgcGVyZm9ybXMgdGhpcyBjYWxjdWxhdGlvbiBiZXR3ZWVuIHBhaXJzIG9mIHJvd3Mgb2YgdGhlIHByb3ZpZGVkIG1hdHJpeC4gQXMgdGVybXMgYXBwZWFyIGluIHRoZSBjb2x1bW5zIG9mIHRoZSBkb2N1bWVudC10ZXJtIG1hdHJpeCAoYHNjaS5yZWwuZHRtLjgwYCksIGl0IGlzIG5lZWRlZCB0byBiZSB0cmFuc3Bvc2VkIGJ5IG1lYW5zIG9mIHRoZSBgdGAgb3BlcmF0b3IuIFRoZSBjbHVzdGVyaW5nLWRlbmRvZ3JhbSBpcyBidWlsdCB3aXRoIHRoZSBgaGNsdXN0YCBvcGVyYXRvci4gSXQgbmVlZHMgYXMgaW5wdXQgdGhlIGNhbGN1bGF0ZWQgZGlzdGFuY2UgbWF0cml4IGJldHdlZW4gcGFpcnMgb2YgdGVybXMgYW5kIGEgY3JpdGVyaWEgdG8gZGVjaWRlIHdoaWNoIHBhaXIgb2YgY2x1c3RlcnMgdG8gYmUgY29uc2VjdXRpdmVseSBqb2luZWQgaW4gdGhlIGJvdHRvbS11cCBkZW5kb2dyYW0uIEluIHRoaXMgY2FzZSwgdGhlIOKAnGNvbXBsZXRl4oCdIGNyaXRlcmlhIHRha2VzIGludG8gYWNjb3VudCB0aGUgbWF4aW11bSBkaXN0YW5jZSBiZXR3ZWVuIGFueSBwYWlyIG9mIG9iamVjdHMgKHRlcm1zKSBvZiBib3RoIGNsdXN0ZXJzIHRvIGJlIG1lcmdlZC4gSGVpZ3RoIGluIHRoZSBkZW5kb2dyYW0gZGVub3RlcyB0aGUgKmRpc3RhbmNlKiBiZXR3ZWVuIGEgbWVyZ2VkIHBhaXIgb2YgY2x1c3RlcnMuDQoNCmBgYHtyfQ0KZGlzdE1hdHJpeCA8LSBkaXN0KHQoc2NhbGUoYXMubWF0cml4KHNjaS5yZWwuZHRtLjgwKSkpKQ0KdGVybUNsdXN0ZXJpbmcgPC0gaGNsdXN0KGRpc3RNYXRyaXgsIG1ldGhvZCA9ICJjb21wbGV0ZSIpDQpwbG90KHRlcm1DbHVzdGVyaW5nKQ0KYGBgDQoNCkluc3RlYWQgb2YgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgYW5kIGZvbGxvd2luZyBhIHNpbWlsYXIgc2V0IG9mIGZ1bmN0aW9ucywgdGhlIGBmcGNgIHBhY2thZ2UgYWxsb3dzIHRvIGNvbnN0cnVjdCBhIGBrLW1lYW5zYCBjbHVzdGVyaW5nLg0KDQpBbm90aGVyIHR5cGUgb2YgcG9wdWxhciBOTFAgbWFjaGluZS1sZWFybmluZyBhbmFseXNpcyBpcyB0byBjb25zdHJ1Y3QgY2x1c3RlcnMgb2Ygc2ltaWxhciBkb2N1bWVudHMgYmFzZWQgb24gdGhlIGZyZXF1ZW5jaWVzIG9mIHdvcmQgb2NjdXJyZW5jZXMuDQoNCiMgQ2xhc3NpZmljYXRpb24gb2YgZG9jdW1lbnRzDQoNCk91ciBvYmplY3RpdmUgaXMgdG8gbGVhcm4gYSBjbGFzc2lmaWVyLW1vZGVsIHdoaWNoLCBiYXNlZCBvbiB0ZXJtcyBvY2N1cnJlbmNlcywgcHJlZGljdHMgdGhlIHR5cGUtdG9waWMgKOKAnHNjaWVuY2UtZWxlY3Ryb25pY3PigJ0gb3Ig4oCccmVsaWdpb27igJ0pIG9mIGZ1dHVyZSBkb2N1bWVudHMgKHBvc3QtbmV3LCBpbiB0aGUgY2FzZSBvZiBuZXdzZ3JvdXBzKS4gV2UgaGF2ZSBhIHR3by1jbGFzcyBwcm9ibGVtLg0KDQojIyBDb25jYXRlbmF0ZSB0aGUgYW5ub3RhdGlvbiBjb2x1bW46IHR5cGUgb2YgdHJhaW5pbmcgZG9jdW1lbnRzDQoNClRoZSAwLjkgc3BhcnNlbmVzcyB2YWx1ZSBkb2N1bWVudC10ZXJtIG1hdHJpeCBpcyBvdXIgc3RhcnRpbmcgcG9pbnQuIFdlIGZpcnN0IG5lZWQgdG8gYXBwZW5kIHRoZSBjbGFzcyAoZG9jdW1lbnQgdHlwZSkgdmVjdG9yIGFzIHRoZSBsYXN0IGNvbHVtbiBvZiB0aGUgbWF0cml4OiB0aGUgZmlyc3QgNTkxIGRvY3VtZW50cyBjb3ZlciB0aGUg4oCcc2NpZW5jZS1lbGVjdHJvbmljc+KAnSBuZXdncm91cC4NCg0KYGBge3J9DQpkaW0oc2NpLnJlbC5kdG0uOTApDQp0eXBlIDwtIGMocmVwKCJzY2llbmNlIiwgNTkxKSwgcmVwKCJyZWxpZ2lvbiIsIDM3NykpICMgY3JlYXRlIHRoZSB0eXBlIHZlY3Rvcg0Kc2NpLnJlbC5kdG0uOTAgPC0gY2JpbmQoc2NpLnJlbC5kdG0uOTAsIHR5cGUpICMgYXBwZW5kDQpkaW0oc2NpLnJlbC5kdG0uOTApICMgY29uc3VsdCB0aGUgdXBkYXRlZCBudW1iZXIgb2YgY29sdW1ucw0KYGBgDQoNClRoaXMgbmV3IG1hdHJpeCBpcyB0aGUgc3RhcnRpbmcgcG9pbnQgZm9yIGFueSBzb2Z0d2FyZSBzcGVjaWFsaXplZCBvbiBzdXBlcnZpc2VkIGNsYXNzaWZpY2F0aW9uLiBIb3dldmVyLCBpdCBpcyBuZWVkZWQgdG8gY29uY2F0ZW5hdGUg4oCcbWF0cml44oCdIGFuZCDigJxkYXRhLmZyYW1l4oCdIGNhc3Rpbmcgb3BlcmF0aW9ucy4gVGhlIG5hbWUgb2YgdGhlIGxhc3QgY29sdW1uIGlzIHVwZGF0ZWQuDQoNCmBgYHtyfQ0Kc2NpLnJlbC5kdG0uOTAuTUwubWF0cml4IDwtIGFzLmRhdGEuZnJhbWUoYXMubWF0cml4KHNjaS5yZWwuZHRtLjkwKSkNCmNvbG5hbWVzKHNjaS5yZWwuZHRtLjkwLk1MLm1hdHJpeClbMTE5XSA8LSAidHlwZSINCmBgYA0KDQpUaGUgZGlmZmVyZW50IGNvbHVtbnMgb2YgYW4gb2JqZWN0IGNhbiBiZSBhY2Nlc3NlZCBieSB0eXBpbmcgdGhlIHRhYiBhZnRlciB0aGUgb2JqZWN0IG5hbWUgYW5kIHRoZSAkIHN5bWJvbC4NCg0KIyMgQ2xhc3NpZmljYXRpb24gYnkgUi1wYWNrYWdlIGBjYXJldGAsIGNvdmVyaW5nIGEgY2xhc3NpYyBkYXRhLW1pbmluZyBhbmFseXNpcyBwaXBlbGluZQ0KDQpUaGUgYGNhcmV0YCBbMiwgMV0gcGFja2FnZSBpcyB0aGUgcmVmZXJlbmNlIHRvb2wgZm9yIGJ1aWxkaW5nIHN1cGVydmlzZWQgY2xhc3NpZmljYXRpb24gYW5kIHJlZ3Jlc3Npb24gbW9kZWxzIGluIFIuIFRoZSBmb2xsb3dpbmcgc2hvd3MgdGhlIGN1cnJlbnQgdG9wIG1hY2hpbmUgbGVhcm5pbmcgcGFja2FnZXMgaW4gYFJgOiBodHRwczovL3d3dy5rZG51Z2dldHMuY29tLzIwMTcvMDIvdG9wLXItcGFja2FnZXMtbWFjaGluZS1sZWFybmluZy5odG1sLiBgY2FyZXRgIHBhY2thZ2UgY292ZXJzIGFsbCB0aGUgc3RlcHMgb2YgYSBjbGFzc2ljIHBpcGVsaW5lOiBkYXRhIHByZXByb2Nlc3NpbmcsIG1vZGVsIGJ1aWxkaW5nLCBhY2N1cmFjeSBlc3RpbWF0aW9uLCBwcmVkaWN0aW9uIG9mIHRoZSB0eXBlIG9mIG5ldyBzYW1wbGVzLCBhbmQgc3RhdGlzdGljYWwgY29tcGFyaXNpb24gYmV0d2VlbiB0aGUgcGVyZm9ybWFuY2Ugb2YgZGlmZmVyZW50IG1vZGVscy4gQW5vdGhlciBzaW1pbGFyIHBhY2thZ2UgaXMgYG1scjNgLiBJZiB5b3UgYXJlIGludGVyZXN0ZWQsIHlvdSBjYW4gZmluZCBhbiBpbnRlcmVzdGluZyB0dXRvcmlhbDogaHR0cHM6Ly9tbHIzLm1sci1vcmcuY29tLy4NCg0KVmVyeSB1c2VmdWw6IHRoZSBjaGVhdHNoZWV0IG9mIGNhcmV0OiBodHRwczovL2dpdGh1Yi5jb20vQ0FCQUgvbGVhcm5pbmdScmVzb3VyY2VzL2Jsb2IvbWFpbi9jaGVhdHNoZWV0cy9jYXJldC5wZGYuIEl0cyBwcmluY2lwYWwgZnVuY3Rpb25zIGlsbHVzdHJhdGVkIGluIGEgc2luZ2xlIHBhZ2UuDQoNCiMjIENyZWF0ZSBhICJUcmFpbi1UZXN0IiBwYXJ0aXRpb24gZm9yIGNsYXNzaWZpZXIgdmFsaWRhdGlvbg0KDQpCZWZvcmUgbGVhcm5pbmcgYSBjbGFzc2lmaWNhdGlvbiBtb2RlbCBpdCBpcyBuZWVkZWQgdG8gZGVmaW5lIHRoZSBzdWJzZXRzIG9mIHNhbXBsZXMgKGRvY3VtZW50cykgdG8gdHJhaW4gYW5kIHRlc3QgdGhlIGl0LiBUaGUgYGNyZWF0ZURhdGFQYXJ0aXRpb25gIHByb2R1Y2VzIGEgdHJhaW4tdGVzdCBwYXJ0aXRpb24gb2Ygb3VyIGNvcnB1cyBvZiA5NjggZG9jdW1lbnRzLiBUaGlzIHdpbGwgYmUgbWFpbnRhaW5lZCBkdXJpbmcgdGhlIHdob2xlIHBpcGVsaW5lIG9mIGFuYWx5c2lzLiBUZXN0IHNhbXBsZXMgd29u4oCZdCBiZSB1c2VkIGZvciBhbnkgbW9kZWxpbmcgZGVjaXNpb246b25seSB0byBwcmVkaWN0IHRoZWlyIGNsYXNzIGFuZCBjcmVhdGUgYSBjb25mdXNpb24gbWF0cml4LiBDb25zdWx0IHRoZSBwYXJhbWV0ZXJzIG9mIGBjcmVhdGVEYXRhUGFydGl0aW9uYCwgYXMgd2VsbCBhcyBvdGhlciB0d28gZnVuY3Rpb25zIHdpdGggc2ltaWxhciBwdXJwb3NlcywgYGNyZWF0ZUZvbGRzYCBhbmQgYGNyZWF0ZVJlc2FtcGxlYC4gQSBsaXN0IG9mIHJhbmRvbWx5IHNhbXBsZWQgbnVtYmVycyAob2JqZWN0IGBpblRyYWluYCksIGFzIGluZGV4IG51bWJlcnMsIGlzIHVzZWQgdG8gcGFydGl0aW9uIHRoZSB3aG9sZSBjb3JwdXMgaW4gdHdvIGBSYCBvYmplY3RzLg0KDQpgYGB7cn0NCmxpYnJhcnkoY2FyZXQpDQpzZXQuc2VlZCgxMDcpICMgYSByYW5kb20gc2VlZCB0byBlbmFibGUgcmVwcm9kdWNpYmlsaXR5DQppblRyYWluIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeSA9IHNjaS5yZWwuZHRtLjkwLk1MLm1hdHJpeCR0eXBlLCBwID0gLjc1LCBsaXN0ID0gRkFMU0UpDQpzdHIoaW5UcmFpbikNCnRyYWluaW5nIDwtIHNjaS5yZWwuZHRtLjkwLk1MLm1hdHJpeFtpblRyYWluLCBdDQp0ZXN0aW5nIDwtIHNjaS5yZWwuZHRtLjkwLk1MLm1hdHJpeFstaW5UcmFpbiwgXQ0KbnJvdyh0cmFpbmluZykNCmBgYA0KDQojIyBTZWxlY3Rpb24gb2Ygc3VwZXJ2aXNlZCBjbGFzc2lmaWNhdGlvbiBhbGdvcml0aG1zIA0KDQpXZSBub3cgY2FuIHN0YXJ0IHRyYWluaW5nIGFuZCB0ZXN0aW5nIGRpZmZlcmVudCBzdXBlcnZpc2VkIGNsYXNzaWZpY2F0aW9uIG1vZGVscy4gdHJhaW4gZnVuY3Rpb24gaW1wbGVtZW50cyB0aGUgYnVpbGRpbmcgcHJvY2Vzcy4gQ2hlY2sgaXRzIHBhcmFtZXRlcnM6IGFtb25nIHRoZW0sIHdlIGhpZ2hsaWdodCB0aGUgZm9sbG93aW5nOg0KDQoqIGBwcmVQcm9jZXNzYCBwYXJhbWV0ZXIgZGVmaW5lcyB0aGUgcHJlcHJvY2Vzc2luZyBzdGVwcyB0byBiZSBhcHBsaWVkLiBUaGV5IGFyZSBwb3B1bGFyIHdpdGggY2xhc3NpYyBudW1lcmljIHZhcmlhYmxlcywgc3VjaCBhcyBpbXB1dGF0aW9uIG9mIG1pc3NpbmcgdmFsdWVzLCBjZW50ZXJpbmcgYW5kIHNjYWxpbmcsIGV0Yy4gQXMgaXQgd2FzIHNob3duIGluIHRoZSBwcmV2aW91cyB0dXRvcmlhbCwgTkxQIGRhdGFzZXRzIGhhdmUgdGhlaXIgb3duIHByZXByb2Nlc3NpbmcgdG9vbHMuIFRoZXkgYXJlIG5vdCBnb2luZyB0byBiZSBhcHBsaWVkIGluIG91ciBkYXRhc2V0Lg0KDQoqIGB0ckNvbnRyb2xgIHBhcmFtZXRlciBkZWZpbmVzIHRoZSBtZXRob2QgdG8gZXN0aW1hdGUgdGhlIGVycm9yIG9mIHRoZSBjbGFzc2lmaWVyLiBJdCBpcyBkZWZpbmVkIGJ5IG1lYW5zIG9mIHRoZSBhcHBsaWNhdGlvbiBvZiB0aGUgYHRyYWluQ29udHJvbGAgZnVuY3Rpb24uIFRoaXMgYWxsb3dzIHRoZSB1c2Ugb2YgZGlmZmVyZW50IHBlcmZvcm1hbmNlIGVzdGltYXRpb24gcHJvY2VkdXJlcyBzdWNoIGFzIGstZm9sZCBjcm9zcy12YWxpZGF0aW9uLCBib290c3RyYXBwaW5nLCBldGMuIFdlIGFwcGx5IGEgMTAtZm9sZCBjcm9zcy12YWxpZGF0aW9uLCByZXBlYXRlZCAzIHRpbWVzLg0KDQoqIGBtZXRob2RgIHBhcmFtZXRlciBmaXhlcyB0aGUgdHlwZSBvZiBjbGFzc2lmaWNhdGlvbiBhbGdvcml0aG0gdG8gYmUgbGVhcm5lZC4gVGhlIHdpZGUgbGlzdCBvZiBhbGdvcml0aG1zIChhbmQgaXRzIHBhcmFtZXRlcnMpIGNvdmVyZWQgYnkgYGNhcmV0YCBjYW4gYmUgZm91bmQgaW4gaHR0cHM6Ly90b3BlcG8uZ2l0aHViLmlvL2NhcmV0L3RyYWluLW1vZGVscy1ieS10YWcuaHRtbC4gVGFraW5nIGludG8gYWNjb3VudCB0aGUgbGFyZ2UgZGltZW5zaW9uYWxpdHkgb2YgY2xhc3NpYyBOTFAgZGF0YXNldHMsIHRoZSB1c2Ugb2YgY2xhc3NpZmllcnMgY2FwYWJsZSB0byBkZWFsIHdpdGggdGhpcyBjaGFyYWN0ZXJpc3RpYyBpcyBoaWdobHkgcmVjb21tZW5kZWQuIEluIHRoaXMgdHV0b3JpYWwsIExpbmVhciBzdXBwb3J0IHZlY3RvciBtYWNoaW5lIChTVk0pIGFuZCBrLW5lYXJlc3QgbmVpZ2hib3VyIChLLU5OKSBtb2RlbHMgYXJlIGxlYXJuZWQgYW5kIHZhbGlkYXRlZC4NCg0KKiBgbWV0cmljYCBwYXJhbWV0ZXIgZml4ZXMgdGhlIHNjb3JlIHRvIGFzc2Vzcy12YWxpZGF0ZXMgdGhlIGdvb2RuZXNzIG9mIGVhY2ggbW9kZWwuIEFwYXJ0IGZyb20gdGhlIFJPQyBtZXRyaWMgdXNlZCBpbiB0aGlzIHR1dG9yaWFsLCBhIGxhcmdlIHNldCBvZiBtZXRyaWNzIGlzIG9mZmVyZWQ6IEFjY3VyYWN5IChwZXJjZW50YWdlIG9mIGNvcnJlY3QgY2xhc3NpZmljYXRpb24pLCBrYXBwYSwgU2VucyAoU2Vuc2l0aXZpdHkpLCBTcGVjaWZpY2l0eSAoU3BlYyksIFJNU0UgaW4gdGhlIGNhc2Ugb2YgcmVncmVzc2lvbiBwcm9ibGVtcy4uLiBJIGhhdmUgbm90IGZvdW5kIGEgbGlzdCB3aXRoIGFsbCB0aGUgbWV0cmljcyBvZmZlcmVkIGJ5IGBjYXJldGAsIGJ1dCBjb25zdWx0aW5nIHRoZSBoZWxwIG9mIHRoZSBwYWNrYWdlIHdpbGwgZ2l2ZSB5b3UgYSBicm9hZCBpZGVhLg0KDQpUYWtlIGludG8gYWNjb3VudCB0aGUgZm9sbG93aW5nIGZhY3RzIGFib3V0IHRoZSBtb2RlbC10cmFpbmluZyBzdGVwOg0KDQoqIHRoZSBleHByZXNzaW9uIGB0eXBlIH5gIGlzIHF1aXRlIHBvcHVsYXIgaW4gUiB0byBkZW5vdGUgdGhlIHNwZWNpZmljIHZhcmlhYmxlIHRvIGJlIHByZWRpY3RlZCwgZm9sbG93ZWQgYnkgdGhlIHNldCBvZiBwcmVkaWN0b3JzLiBBIHBvaW50IGluZGljYXRlcyB0aGF0IHRoZSByZXN0IG9mIHZhcmlhYmxlcyBhcmUgdXNlZCBhcyBwcmVkaWN0b3JzLg0KDQoqIHRvZ2V0aGVyIHdpdGggdGhlIGVzdGltYXRpb24gb2YgdGhlIHBlcmNlbnRhZ2UgcmVjb2duaXRpb24gKGFjY3VyYWN5KSwgdGhlIHZhbHVlIG9mIHRoZSAqS2FwcGEgc3RhdGlzdGljKiBpcyBzaG93bi4gSXQgaXMgYSBwb3B1bGFyIHNjb3JlIGluIE5MUCBzdHVkaWVzLiBJdHMgZGVmaW5pdGlvbiBpcyBub3QgdHJpdmlhbCBpbiB0aGUgY29udGV4dCBvZiBzdXBlcnZpc2VkIGNsYXNzaWZpY2F0aW9uLiBSb3VnaGx5LCB0aGlzIHNjb3JlIGNvbXBhcmVzIHRoZSDigJxvYnNlcnZlZOKAnSBhY2N1cmFjeSBvZiB0aGUgbGVhcm5lZCBjbGFzc2lmaWVyIHdpdGggcmVzcGVjdCB0byBhIHJhbmRvbSBjbGFzc2lmaWVyOiBtZWFzdXJpbmcgdGhlIHNjb3JlIGRpZmZlcmVuY2UgYmV0d2VlbiBvdXIgY2xhc3NpZmllciBhbmQgYSByYW5kb20gY2xhc3NpZmllci4gQSBsYXJnZXIgZGVmaW5pdGlvbiBvZiB0aGlzIG1ldHJpYyBjYW4gYmUgZm91bmQgaW4gaHR0cDovL3N0YXRzLnN0YWNrZXhjaGFuZ2UuY29tL3F1ZXN0aW9ucy84MjE2Mi9rYXBwYS1zdGF0aXN0aWMtaW4tcGxhaW4tZW5nbGlzaC4NCg0KKiB3aGlsZSB0aGUgbGluZWFyIFNWTSBjbGFzc2lmaWVyIGRvZXMgbm90IGhhdmUgcGFyYW1ldGVycywgSy1OTiBoYXMgdGhlIOKAnG51bWJlciBvZiBuZWlnaGJvdXJz4oCdIChLKSBrZXkgcGFyYW1ldGVyLiBCeSBkZWZhdWx0LCBjaGFuZ2luZyB0aGUgdmFsdWUgb2YgdGhlIHBhcmFtZXRlciwgYGNhcmV0YCBldmFsdWF0ZXMgMyBtb2RlbHMgKGB0dW5lTGVuZ3RoYCBlcXVhbCB0byAzKS4gVGhlIGB0dW5lTGVuZ3RoYCBvcHRpb24gb2YgdGhlIGB0cmFpbmAgZnVuY3Rpb24gZml4ZXMgdGhlIG51bWJlciBvZiB2YWx1ZXMgb2YgZWFjaCBwYXJhbWV0ZXIgdG8gYmUgY2hlY2tlZC4gRm9yIGV4YW1wbGUsIGlmIHRoZSBjbGFzc2lmaWVyIGhhcyAyIHBhcmFtZXRlcnMgYW5kIHRoZSBgdHVuZUxlbmd0aGAgcGFyYW1ldGVyIGlzIG5vdCBjaGFuZ2VkLCAzIHggMyA9IDkgbW9kZWxzIGFyZSBldmFsdWF0ZWQuIFRoZSBgdHVuZUdyaWRgIG9wdGlvbiBvZmZlcnMgdGhlIHBvc3NpYmlsaXR5IHRvIHNlbGVjdCBhbW9uZyBhIHNldCBvZiB2YWx1ZXMgdG8gYmUgdHVuZWQtdGVzdGVkLg0KDQoqIGBjYXJldGAgc3VwcG9ydHMgbW9yZSB0aGFuIDE1MCBzdXBlcnZpc2VkIGNsYXNzaWZpY2F0aW9uIGFuZCByZWdyZXNzaW9uIGFsZ29yaXRobXMuIEEgc21hbGwgcG9ydGlvbiBvZiB0aGVtIGFyZSBsZWFybmVkIGJ5IG1lYW5zIG9mIHRoZSBzb2Z0d2FyZSBvZiB0aGUgcGFja2FnZSBpdHNlbGYuIFRoZSBtYWpvcml0eSBvZiB0aGUgYWxnb3JpdGhtcyBhcmUgbGVhcm5lZCBieSBvdGhlciBgUmAgcGFja2FnZXMgd2hpY2ggYXJlIGNvbnZlbmllbnRseSBhY2Nlc3NlZCBieSBgY2FyZXRgLg0KDQojIyBJbnRlcm5hbCBwZXJmb3JtYW5jZSBlc3RpbWF0aW9uIGluIHRoZSB0cmFpbmluZyBwYXJ0aXRpb24NCg0KYGBge3J9DQojIGZpeGluZyB0aGUgcGVyZm9ybWFuY2UgZXN0aW1hdGlvbiBwcm9jZWR1cmUNCmN0cmwgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgcmVwZWF0cyA9IDMpDQpzdm1Nb2RlbDN4MTBjdiA8LSB0cmFpbih0eXBlIH4gLiwgZGF0YSA9IHRyYWluaW5nLCBtZXRob2QgPSAic3ZtTGluZWFyIiwgdHJDb250cm9sID0gY3RybCkNCnN2bU1vZGVsM3gxMGN2DQprbm5Nb2RlbDN4MTBjdiA8LSB0cmFpbih0eXBlIH4gLiwgZGF0YSA9IHRyYWluaW5nLCBtZXRob2QgPSAia25uIiwgdHJDb250cm9sID0gY3RybCkNCmtubk1vZGVsM3gxMGN2DQpgYGANCiMjIFR1bmluZyBvZiB0aGUgcGFyYW1ldGVycyBvZiB0aGUgc2VsZWN0ZWQgY2xhc3NpZmllcnMNCg0KVGhlIHRyYWluaW5nIHByb2Nlc3MgY2FuIHN0aWxsIGJlIGVucmljaGVkIHdpdGggZXh0cmEgcGFyYW1ldGVycyBpbiB0aGUgYHRyYWluQ29udHJvbGAgZnVuY3Rpb246IGBzdW1tYXJ5RnVuY3Rpb25gIGNvbnRyb2xzIHRoZSB0eXBlIG9mIGV2YWx1YXRpb24gbWV0cmljcy4gSW4gYmluYXJ5IGNsYXNzaWZpY2F0aW9uIHByb2JsZW1zIChlLmcuIOKAnHNjaWVuY2XigJ0gdmVyc3VzIOKAnHJlbGlnaW9u4oCdKSB0aGUgYHR3b0NsYXNzU3VtbWFyeWAgb3B0aW9uIGRpc3BsYXlzIFthcmVhIHVuZGVyIHRoZSBST0MgY3VydmVdKGh0dHA6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvUmVjZWl2ZXJfb3BlcmF0aW5nX2NoYXJhY3RlcmlzdGljKSwgc2Vuc2l0aXR5LXJlY2FsbCBhbmQgc3BlY2lmaWNpdHkgbWV0cmljcy4gVG8gZG8gc28sIGl0IGlzIGFsc28gbmVlZGVkIHRvIGFjdGl2YXRlIHRoZSBgY2xhc3NQcm9ic2Agb3B0aW9uIHdoaWNoIHNhdmVzIHRoZSBwcm9iYWJpbGl0eSB0aGF0IHRoZSBjbGFzc2lmaWVyIGFzc2lnbnMgdG8gZWFjaCBzYW1wbGUgYmVsb25naW5nIHRvIGVhY2ggY2xhc3MtdmFsdWUuDQoNCmBgYHtyfQ0KbGlicmFyeShwUk9DKQ0KY3RybCA8LSB0cmFpbkNvbnRyb2woDQogICAgbWV0aG9kID0gInJlcGVhdGVkY3YiLCByZXBlYXRzID0gMywgY2xhc3NQcm9icyA9IFRSVUUsDQogICAgc3VtbWFyeUZ1bmN0aW9uID0gdHdvQ2xhc3NTdW1tYXJ5DQopDQprbm5Nb2RlbDN4MTBjdlJPQyA8LSB0cmFpbih0eXBlIH4gLiwNCiAgICBkYXRhID0gdHJhaW5pbmcsIG1ldGhvZCA9ICJrbm4iLCB0ckNvbnRyb2wgPSBjdHJsLA0KICAgIG1ldHJpYyA9ICJST0MiLCB0dW5lTGVuZ3RoID0gMTANCikNCmtubk1vZGVsM3gxMGN2Uk9DDQpwbG90KGtubk1vZGVsM3gxMGN2Uk9DKQ0KYGBgDQojIyBQcmVkaWN0IHRoZSBjbGFzcy10eXBlIG9mIGZ1dHVyZSB1bnNlZW4tdW5sYWJlbGVkIHRleHRzDQoNCkluIG9yZGVyIHRvIHByZWRpY3QgdGhlIGNsYXNzIHZhbHVlIG9mIHVuc2VlbiBkb2N1bWVudHMgb2YgdGhlIHRlc3QgcGFydGl0aW9uIGNhcmV0IHVzZXMgdGhlIGNsYXNzaWZpZXIgd2hpY2ggc2hvd3MgdGhlIGJlc3QgYWNjdXJhY3kgZXN0aW1hdGlvbiBvZiB0aGVpciBwYXJhbWV0ZXJzLiBGdW5jdGlvbiBwcmVkaWN0IGltcGxlbWVudHMgdGhpcyBmdW5jdGlvbmFsaXR5LiBDb25zdWx0IGl0cyBwYXJhbWV0ZXJzLiBUaGUgYHR5cGVgIHBhcmFtZXRlciwgYnkgbWVhbnMgb2YgaXRzIGBwcm9ic2AgdmFsdWUsIG91dHB1dHMgdGhlIHByb2JhYmlsaXR5IG9mIHRlc3QgZWFjaCBzYW1wbGUgYmVsb25naW5nIHRvIGVhY2ggY2xhc3MgKOKAnGEtcG9zdGVyaW9yaeKAnSBwcm9iYWJpbGl0eSkuIE9uIHRoZSBvdGhlciBoYW5kLCB0aGUgYHJhd2AgdmFsdWUgb3V0cHV0cyB0aGUgY2xhc3MgdmFsdWUgd2l0aCB0aGUgbGFyZ2VzdCBwcm9iYWJpbGl0eS4gQnkgbWVhbnMgb2YgdGhlIGByYXdgIG9wdGlvbiB0aGUgY29uZnVzaW9uIG1hdHJpeCBjYW4gYmUgY2FsY3VsYXRlZDogdGhpcyBjcm9zc2VzLCBmb3IgZWFjaCB0ZXN0IHNhbXBsZSwgcHJlZGljdGVkIHdpdGggcmVhbCBjbGFzcyB2YWx1ZXMuDQoNCmBgYHtyfQ0Kc3ZtTW9kZWxDbGFzc2VzIDwtIHByZWRpY3Qoc3ZtTW9kZWwzeDEwY3YsIG5ld2RhdGEgPSB0ZXN0aW5nLCB0eXBlID0gInJhdyIpDQpjb25mdXNpb25NYXRyaXgoZGF0YSA9IHN2bU1vZGVsQ2xhc3NlcywgdGVzdGluZyR0eXBlKQ0KYGBgDQoNCiMjIFN0YXRpc3RpY2FsIGNvbXBhcmlzb24gYmV0d2VlbiB0d28gY2xhc3NpZmllcnMgYnkgbWVhbnMgb2YgdC10ZXN0DQoNCkNhbiBhIHN0YXRpc3RpY2FsIGNvbXBhcmlzb24gYmUgcGVyZm9ybWVkIGJldHdlZW4gdGhlIDN4MTBjdiB2YWxpZGF0aW9uIHJlc3VsdHMgb2YgSy1OTiBhbmQgU1ZNPyBOb3RlIHRoYW4gaW4gb3VyIGNhc2UsIGR1ZSB0byB0aGUgMyByZXBldGl0aW9ucyBvZiB0aGUgMTAtZm9sZCBjcm9zcy12YWxpZGF0aW9uIHByb2Nlc3MsIHRoZXJlIGFyZSAzMCByZXNhbXBsaW5nIHJlc3VsdHMgZm9yIGVhY2ggY2xhc3NpZmllci4gRmlyc3QsIHJlc3VsdHMgb2YgYm90aCBjbGFzc2lmaWVycyBhcmUgY3Jvc3NlZCB1c2luZyB0aGUgYHJlc2FtcGxlc2AgZnVuY3Rpb24uIEFzIHRoZSBgc2V0LnNlZWRgIGRpZCBub3QgY2hhbmdlLCB0aGUgc2FtZSBwYWlyZWQgY3Jvc3MtdmFsaWRhdGlvbiBzdWJzZXRzIG9mIHNhbXBsZXMgd2VyZSB1c2VkIGZvciBib3RoIGNsYXNzaWZpZXJzLiBUaGlzIGZvcmNlcyB0byB1c2UgYSBwYWlyZWQgdC10ZXN0IHRvIGNhbGN1bGF0ZSB0aGUgc2lnbmlmaWNhbmNlIG9mIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGJvdGggY2xhc3NpZmllcnMuIEEgc2ltcGxlIHBsb3QgaXMgZHJhd24sIHNob3dpbmcgdGhlIGFjY3VyYWN5IGRpZmZlcmVuY2VzIGJldHdlZW4gYm90aCBtb2RlbHMgZm9yIGVhY2ggb2YgdGhlIDMwIGNyb3NzLXZhbGlkYXRpb24gZm9sZHM6IG5vdGUgdGhhdCBzdm0gaGFzIGEgYmV0dGVyIGFjY3VyYWN5IHRoYW4ga25uIGZvciBhbGwgdGhlIGNyb3NzLXZhbGlkYXRpb24gZm9sZCByZXN1bHRzIChGaWd1cmUgMikuDQoNClVzaW5nIHRoZSBgZGlmZmAgZnVuY3Rpb24gb3ZlciB0aGUgYHJlc2FtcHNgIG9iamVjdCB3aGljaCBzYXZlcyB0aGUg4oCcY3Jvc3NpbmfigJ0gb2YgYm90aCBjbGFzc2lmaWVycywgd2UgY2FuIHNob3cgYSByaWNoIG91dHB1dCBvZiB0aGUgcGVyZm9ybWVkIGNvbXBhcmlzb246IGVhY2ggbnVtYmVyIG1hdHRlcnMuIFRoZSBvdXRwdXQgc2hvd3MsIGZvciBlYWNoIG1ldHJpYyAoYXJlYSB1bmRlciB0aGUgUk9DIGN1cnZlLCBzZW5zaXRpdml0eSwgc3BlY2lmaWNpdHkpLCB0aGUgZGlmZmVyZW5jZSBvZiB0aGUgbWVhbiAocG9zaXRpdmUgb3IgbmVnYXRpdmUsIGZvbGxvd2luZyB0aGUgb3JkZXIgaW4gdGhlIGByZXNhbXBsZXNgIGZ1bmN0aW9uKSBiZXR3ZWVuIGJvdGggY2xhc3NpZmllcnMuIFRoZSBwLXZhbHVlIG9mIHRoZSBhc3NvY2lhdGVkIHQtdGVzdCBpcyBhbHNvIHNob3duLg0KDQpUaGUgaW50ZXJwcmV0YXRpb24gb2YgdGhlIHAtdmFsdWUgaGFzIHRoZSBrZXkuIEl0IGlzIHJlbGF0ZWQgd2l0aCB0aGUgcmlzayBvZiBlcnJvbmVvdXNseSBkaXNjYXJkaW5nIHRoZSBudWxsaHlwb3RoZXNpcyBvZiBzaW1pbGFyaXR5IGJldHdlZW4gY29tcGFyZWQgY2xhc3NpZmllcnMsIHdoZW4gdGhlcmUgaXMgbm8gcmVhbCBkaWZmZXJlbmNlLiBSb3VnaGx5IHNwZWFraW5nLCBpdCBjYW4gYWxzbyBiZSBpbnRlcnByZXRlZCBhcyB0aGUgZGVncmVlIG9mIHNpbWlsYXJpdHkgYmV0d2VlbiBib3RoIGNsYXNzaWZpZXJzLiBBIHAtdmFsdWUgc21hbGxlciB0aGFuIDAuMDUgKG9yIDAuMSwgZGVwZW5kaW5nIG9uIHlvdXIgaW50ZXJwcmV0YXRpb24gYW5kIHRocmVzaG9sZCkgYWxlcnRzIGFib3V0IHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZXMgYmV0d2VlbiBib3RoIGNsYXNzaWZpZXJzLCBodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9TdGF0aXN0aWNhbF9zaWduaWZpY2FuY2UuIFRoYXQgaXMsIHdoZW4gdGhlIHJpc2sgb2YgZXJyb25ldXNseSBkaXNjYXJkaW5nIHRoZSBoeXBvdGhlc2lzIG9mIHNpbWlsYXJpdHkgYmV0d2VlbiBib3RoIGNsYXNzaWZpZXJzIGlzIGxvdywgd2UgYXNzdW1lIHRoYXQgdGhlcmUgaXMgYSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IGRpZmZlcmVuY2UgYmV0d2VlbiBjbGFzc2lmaWVycy4NCg0KYGBge3J9DQpyZXNhbXBzIDwtIHJlc2FtcGxlcyhsaXN0KGtubiA9IGtubk1vZGVsM3gxMGN2LCBzdm0gPSBzdm1Nb2RlbDN4MTBjdikpDQpzdW1tYXJ5KHJlc2FtcHMpDQp4eXBsb3QocmVzYW1wcywgd2hhdCA9ICJCbGFuZEFsdG1hbiIpDQpkaWZmcyA8LSBkaWZmKHJlc2FtcHMpDQpzdW1tYXJ5KGRpZmZzKQ0KYGBgDQoNCiMgRXZhbHVhdGlvbiBleGVyY2lzZQ0KDQpUb2dldGhlciB3aXRoIHRoZSBleHBsYW5hdGlvbnMgb2YgdGhlIHByZXZpb3VzIGB0bWAgdHV0b3JpYWwsIGNyZWF0ZSBhIGNvcnB1cyBhbmQgYSBkb2N1bWVudC10ZXJtIG1hdHJpeCB3aGljaCBjaGFyYWN0ZXJpc2VzIGEgKipzdXBlcnZpc2VkIGNsYXNzaWZpY2F0aW9uKiogcHJvYmxlbSBvZiB5b3VyIGludGVyZXN0OiAyIGRpZmZlcmVudCB0eXBlcyBvZiBkb2N1bWVudHMsIGRpZmZlcmVudCB0eXBlcyBvZiBzZW50aW1lbnRzLCBkaWZmZXJlbnQgdHlwZXMgb2YgdGV4dCBkaWZmaWN1bHRpZXMuLi4gdGhhdCBpcywgdGhlIGNvcnB1cyBoYXMgdG8gYmUgYW5ub3RhdGVkLCBhdCBsZWFzdCB3aXRoIHR3byBkaWZmZXJlbnQgbGFiZWxzLCBpbiBvcmRlciB0byBwcm9jZWVkIHdpdGggc3VwZXJ2aXNlZCBjbGFzc2lmaWNhdGlvbiB0YXNrcy4NCg0KQXMgdGhlIG91dHB1dCBvZiB0aGUgZXhlcmNpc2UsIHdlIGhvcGUgYSBkb2N1bWVudCBzaW1pbGFyIHRvIHRoaXMgdHV0b3JpYWw6IHRoYXQgaXMsIGEgKipub3RlYm9vayoqIHdoaWNoIG1peGVzIGNvZGUgYW5kIHRoZSBleHBsYW5hdGlvbnMgb2YgdGhpcyBjb2RlIGFuZCB5b3VyIGRlY2lzaW9ucy4gQSByaWNoIGRvY3VtZW50LiBUaGUgZG9jdW1lbnQgaGFzIHRvIGFsdGVybmF0ZSBleHBsYW5hdGlvbnMgYWJvdXQgeW91ciBkZWNpc2lvbiBhbmQgdGhlIGBSYCBjb2RlIHRoYXQgaW1wbGVtZW50cyBpdDogY29sbG9xdWlhbGx5LCBkbyDigJxkbyB5b3VyIG93biB0dXRvcmlhbC1ub3RlYm9va+KAnSwgd2l0aCB5b3VyIGNob3NlbiBhcHBsaWNhdGlvbi1jb3JwdXMuIFNwZWNpZmljYWxseSwgdGhlIGRvY3VtZW50IHRoYXQgeW91IGhhdmUgcmVhZCBoYXMgYmVlbiBlZGl0ZWQgd2l0aCB0aGUgYGtuaXRyYCBwYWNrYWdlIGFuZCB0aGUgYFJTdHVkaW9gIHNvZnR3YXJlOiBgUm53YCBmb3JtYXQuIFlvdSBjYW4gb2YgY291cnNlIGVkaXQgd2l0aCB0aGlzIHRlY2hub2xvZ3kgb3IgYW5vdGhlciBvZiB5b3VyIGNvbnZlbmllbmNlOiBSLW1hcmtkb3duLCBqdXB5dGVyIG5vdGVib29rcywgZXRjLg0KDQpZb3VyIG5vdGVib29rLXR1dG9yaWFsIGhhcyB0byBjb3ZlciB0aGUgZm9sbG93aW5nIGl0ZW1zOg0KDQoqIGEgc2hvcnQgZGVzY3JpcHRpb24gb2YgeW91ciBkYXRhc2V0IGFuZCBOTFAgcHJvYmxlbS4gVGhlIG5hdHVyZSBvZiB0aGUgcHJvYmxlbSBhbmQgdGhlIG9iamVjdGl2ZSBvZiB0aGUgbW9kZWxpemF0aW9uLCB0aGUgY2xhc3MgdmFyaWFibGVzIHRvIGJlIHByZWRpY3RlZCBhbmQgaXRzIHBvc3NpYmxlIHZhbHVlcywgaG93IGNhbiB0aGUgbW9kZWxzIGJlIGV2YWx1YXRlZCwgdGhlIHByZWRpY3RpdmUgZmVhdHVyZXMgYW5kIGFub3RoZXIgY29uY2VwdCB3aGljaCBoZWxwcyBpbiB0aGUgZGVzY3JpcHRpb24gb2YgdGhlIGRvbWFpbi4NCg0KKiBvdmVyIHRoZSB3aG9sZSBjb3JwdXMsIGFwcGx5IG9uZSBvZiB0aGUgKipvdXRsaWVyIGRldGVjdGlvbioqIHRlY2huaXF1ZXMgZXhwb3NlZCBpbiBjbGFzcyBhbmQgY29uc2lkZXIgZGVsZXRpbmcgZXh0cmVtZS1vdXRsaWVyIGRvY3VtZW50cy4NCg0KKiBzaG93IGEgKip3b3JkY2xvdWQqKiBiYXNlZCBvbiB5b3VyIGNvcnB1cyBiYWctb2Ytd29yZHMuDQoNCiogdGhlIHdheSB0byAqKnBhcnRpdGlvbiB0aGUgY29ycHVzIGluIHRyYWluLXRlc3QqKiBkb2VzIG5vdCBoYXZlIHRvIGJlIHRoZSBzYW1lIG9mIHRoZSB0dXRvcmlhbC4gVGhlcmUgYXJlIG90aGVyIHBvc3NpYmlsaXRpZXMgc3VjaCBhcyBgY3JlYXRlRm9sZHMoKWAsIGBjcmVhdGVSZXNhbXBsZSgpYCwgZXRjLg0KDQoqIGNob29zZSB0d28gb3RoZXIgKipjbGFzc2lmaWNhdGlvbiBhbGdvcml0aG1zKiogbm90IHVzZWQgaW4gdGhpcyB0dXRvcmlhbC4gRm9yIHlvdXIgc2VsZWN0aW9uLCAqKmRlc2NyaWJlKiogYnJpZWZseSB0aGVpciBiZWhhdmlvdXIgYW5kIGl0cyBwYXJhbWV0ZXJzLiBUYWtlIGludG8gYWNjb3VudCB0aGUgZWZmZWN0IG9mIHRoZSBleHBvc2VkIGB0dW5lTGVuZ3RoYCBvcHRpb246IHVzZSB0aGUgYHR1bmVMZW5ndGhgIGFuZC1vciBgdHVuZUdyaWRgIG9wdGlvbnMgdG8gKip0dW5lIHRoZSBwYXJhbWV0ZXJzIG9mIGNob3NlbiBjbGFzc2lmaWNhdGlvbiBhbGdvcml0aG1zKiouDQoNCiogcmVtZW1iZXIgdGhhdCB0aGUgYHRyYWluQ29udHJvbCgpYCBmdW5jdGlvbiBhbGxvd3MgdG8gc2VsZWN0IHRoZSBvcHRpb25zIHRvIHZhbGlkYXRlIHRoZSBjbGFzc2lmaWVyLiBDb25zdWx0IGl0cyBvcHRpb25zIGFuZCBkbyAqKnZhcmlhdGlvbnMqKiB3aXRoIHJlc3BlY3QgdG8gbXkgc2VsZWN0aW9uLg0KDQoqIGlmIGNsYXNzLWxhYmVsIGRpc3RyaWJ1dGlvbnMgYXJlICoqdW5iYWxhbmNlZCoqIGluIHlvdXIgY29ycHVzLCB0ZXN0IGEg4oCYcmVzYW1wbGluZ+KAmSBtZXRob2Qgd2hpY2ggd2lsbCB0cnkgdG8gaW1wcm92ZSB0aGUgcmVjb3ZlcnkgcmF0ZSBpbiB0aGUgbWlub3JpdHkgY2xhc3MuIFdoaWNoIGlzIHRoZSBjbGFzcy1sYWJlbCBkaXN0cmlidXRpb24gb2YgeW91ciBjb3JwdXM/IFNob3cgaXQuIFN0dWR5IHRoZSBvcHRpb25zIG9mIHRoZSBgc2FtcGxpbmdgIG9wdGlvbiBpbiBgdHJhaW5Db250cm9sYDogaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL2NhcmV0L3ZlcnNpb25zLzYuMC04NC90b3BpY3MvdHJhaW5Db250cm9sLiBgY2FyZXRgIGhhcyBhIGJyaWVmIGFuZCBpbnR1aXRpdmUgdHV0b3JpYWwgYWJvdXQgdGhlIHRvcGljOiBjaGVjayB0aGUgZmlyc3Qgc2VudGVuY2VzIG9mIHRoaXMgbGluayBodHRwczovL3RvcGVwby5naXRodWIuaW8vY2FyZXQvc3Vic2FtcGxpbmctZm9yLWNsYXNzLWltYmFsYW5jZXMuaHRtbC4NCg0KKiB3aGlsZSBpbiBteSB0dXRvcmlhbCB0aGUgKipjaG9zZW4gbWV0cmljKiogdG8gdmFsaWRhdGUgdGhlIG1vZGVscyBoYXMgYmVlbiBST0MsIHlvdSBjYW4gb2YgY291cnNlIHNlbGVjdCBhbm90aGVyIG1ldHJpYyBvZiB5b3VyIGNvbnZlbmllbmNlOiBhY2N1cmFjeSwgc2Vuc2l0aXZpdHksIEYxLCBrYXBwYSwgZXRjLiBJZiB5b3UgZG8gbm90IGhhdmUgcHJpb3IgaW5mb3JtYXRpb24sIHRoZSBkZWZhdWx0IGFuZCBjb21tb24tc2Vuc2Ugc2NvcmUgbWV0cmljIHRvIGNob29zZSBpbiB5b3VyIHN1cGVydmlzZWQgY2xhc3NpZmljYXRpb24gYXBwbGljYXRpb24gaXMgYWNjdXJhY3kuDQoNCiogdGVzdCB0aGUgKipmZWF0dXJlIHNlbGVjdGlvbioqIG9wdGlvbnMgb2ZmZXJlZCBieSBgY2FyZXRgLiBBdCBsZWFzdCwgYXBwbHkgb25lIG9mIHRoZW0gb3ZlciB5b3VyIGNvcnB1cy4gQ2FuIHRoZSBmZWF0dXJlIHN1YnNldCB0byBsZWFybiB0aGUgZmluYWwgY2xhc3NpZmllciBiZSBvcHRpbWl6ZWQ/IGBjYXJldGAgb2ZmZXJzIGdlbmV0aWMgYWxnb3JpdGhtcyBieSB3cmFwcGVyLCBzaW1tdWxhdGVkIGFubmVhbGluZyBieSB3cmFwcGVyLCByZWN1cnNpdmUgYmFja3dhcmQgZWxpbWluYXRpb24gYnkgd3JhcHBlci4gT3IgdW5pdmFyaWF0ZSBmaWx0ZXJzIHRoYXQgcmFuayBpbmRlcGVuZGVudGx5IHRoZSB2YXJpYWJsZXMgd2l0aCByZXNwZWN0IHRvIHRoZWlyIHJlbGV2YW5jZS1jb3JyZWxhdGlvbiB3aXRoIHJlc3BlY3QgdG8gdGhlIGFubm90YXRpb24tY29sdW1uOw0KDQoqIGxldOKAmXMgbm93IHByYWN0aXNlIHdpdGggKipmZWF0dXJlIGV4dHJhY3Rpb24qKiwgd2hlcmUgYSBzZXQgb2YgZmVhdHVyZXMgaXMg4oCYY29uc3RydWN0ZWTigJkgZnJvbSBvcmlnaW5hbCBvbmVzOiBjb21tb25seSwgbGluZWFyIGNvbWJpbmF0aW9ucyBvZiBvcmlnaW5hbCBvbmVzLiBJbiB0aGlzIGFyZWEsIGl0IGlzIGxpa2VseSB0aGF0IHlvdSBrbm93IGFsZ29yaXRobXMgc3VjaCBhcyBwcmluY2lwYWwgY29tcG9uZW50IGFuYWx5c2lzIC0gUENBLCBzaW5ndWxhciB2YWx1ZSBkZWNvbXBvc2l0aW9uLCBldGMuIEl0IGlzIGVhc3kgdG8gbGVhcm4gYSBQQ0EgaW4gYFJgIHdpdGggdGhlIGBwcmNvbXBgIGZ1bmN0aW9uIGFuZCB2aXN1YWxpemUgaW4gYSAyLUQgZ3JhcGggdHdvIGZpcnN0IGNvbXBvbmVudHMgKGkuZS4gdGhvc2UgdGhhdCBzYXZlIGxhcmdlciB2YXJpYWJpbGl0eSBvZiBvcmlnaW5hbCBkYXRhKTogdHJ5aW5nIHRvIGZpbmQgYW4gaW50dWl0aXZlIHNlcGFyYXRpb24gb2YgcHJvYmxlbWNsYXNzZXMuIFRyeSBpdCBhbmQgY29tbWVudCB0aGUgcmVzdWx0cy4NCg0KKiByZW1lbWJlciB0aGF0IHRoZSBgcHJlZGljdCgpYCBmdW5jdGlvbnMgYWxsb3dzIHRvIHNlbGVjdCB0aGUgb3B0aW9ucyB0byAqKnByZWRpY3QgdGhlIGNsYXNzLWxhYmVsIG9mIHRoZSBzYW1wbGVzIG9mIHRoZSB0ZXN0IHBhcnRpdGlvbioqLiBDb25zdWx0IGl0cyBvcHRpb25zIGFuZCBkbyB2YXJpYXRpb25zIHdpdGggcmVzcGVjdCB0byBteSBzZWxlY3Rpb24uIERvIGNsYXNzLWxhYmVsIHByZWRpY3Rpb25zIG92ZXIgbmV3LXVuc2Vlbi11bm5hbm90YXRlZCBkb2N1bWVudHMuDQoNCiogdG8gZG8gdGhlIGZpbmFsICoqc3RhdGlzdGljYWwgY29tcGFyaXNvbioqIGJldHdlZW4geW91ciBwYWlyIG9mIHNlbGVjdGVkIGNsYXNzaWZpZXJzLCBpdCBpcyBuZWVkZWQgdG8gdW5kZXJzdGFuZCB0aGUgdXNlIGFuZCBvdXRwdXQgb2YgYHJlc2FtcGxlcygpYCwgYHN1bW1hcnkoKWAsIGBkaWZmKClgIGZ1bmN0aW9ucy4gVGhlIG91dHB1dCBpcyBsb25nIGJ1dCByaWNoIGluIGluZm9ybWF0aW9uIHRvIGV4dHJhY3QgY29uY2x1c2lvbnMgb2YgdGhlIGNvbXBhcmlzb24uIEludGVycHJldCB0aGUgY2FsY3VsYXRlZCAqKnAtdmFsdWUqKiB0byBhbmFseXplIHRoZSBzaWduaWZpY2FuY2Ugb2YgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gY29tcGFyZWQgY2xhc3NpZmllcnMuIEluIGl0cyBjdXJyZW50IGltcGxlbWVudGF0aW9uLCBgY2FyZXRgIGFwcGxpZXMgYSB0LXRlc3QgdG8gb2J0YWluIHRoZSBzaWduaWZpY2FuY2Ugb2YgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gdGhlIHBhaXIgb2YgY29tcGFyZWQgY2xhc3NpZmllcnMuDQoNCiMgUmVmZXJlbmNlcw0KDQpbMV0gTWF4IEt1aG4uIENvbnRyaWJ1dGlvbnMgZnJvbSBKZWQgV2luZywgU3RldmUgV2VzdG9uLCBBbmRyZSBXaWxsaWFtcywgQ2hyaXMgS2VlZmVyLCBBbGxhbiBFbmdlbGhhcmR0LCBUb255IENvb3BlciwgWmFjaGFyeSBNYXllciwgYW5kIHRoZSBSIENvcmUgVGVhbS4gY2FyZXQ6IENsYXNzaWZpY2F0aW9uIGFuZCBSZWdyZXNzaW9uIFRyYWluaW5nLCAyMDE0LiBSIHBhY2thZ2UgdmVyc2lvbiA2LjAtMzUuDQoNClsyXSBNLiBLdWhuIGFuZCBLLiBKb2huc29uLiBBcHBsaWVkIFByZWRpY3RpdmUgTW9kZWxpbmcuIFNwcmluZ2VyLCAyMDEzLg0K