Objectives
Upon completion of this lesson, you will be able to:
- define the kNN algorithm
- know when to use kNN
- tune the value of the hyper-parameter k
- appreciate the need for feature scaling
- recognize why outliers can be problematic for kNN
Introduction
The k-Nearest Neighbors (kNN) algorithm is a simple yet powerful supervised learning method used for classification and regression tasks. It is a non-parametric and instance-based learning algorithm, meaning that it does not assume a specific functional form for the data and instead memorizes the training data to make predictions.
The fundamental idea behind kNN is that similar data points tend to be found in close proximity to one another. The algorithm classifies or predicts the target value of a new data point by considering the target values of its nearest neighbors in the training data.
k-Nearest Neighbors (k-NN or kNN) is a type of instance-based learning algorithm used in both classification (predicting a categorical variable) and regression (predicting a numeric variable) tasks. It is called ‘lazy learning’ as it doesn’t build a model but instead memorizes the training dataset.
Examples of applications of kNN include classification, recommendation systems, image recognition, and text categorization, among others.
This lesson will only consider kNN for classification tasks.
Overview of the Algorithm
The basic steps involved in the k-NN algorithm are:
Choose the number of k and a distance metric: The number of neighbors (k) and the type of distance are chosen as per the problem context. For example, you could choose k=3 and use Euclidean distance as the metric.
Find the k nearest neighbors of the sample that you want to classify: For each data point that you want to make a prediction for, you identify the k points in the training dataset that are “closest” according to the chosen distance metric. While Euclidean distance is most common, other distance calculations methods can be used depending on the data.
Assign the label: For a classification problem, the new data point is assigned the class which has the majority among its k nearest neighbors. For a regression problem, the new data point could be assigned the mean value of its k nearest neighbors.
kNN is a simple algorithm that is easy to understand and implement. It works well with smaller datasets that have fewer dimensions (features), and where the data points are clearly separable. However, it may not be suitable for large, high-dimensional datasets, as the computation of distances can become very expensive. It is also sensitive to irrelevant or correlated features, which can impact the calculation of “closest” neighbors. The model does not perform an explicit training phase; it simply stores the dataset and makes predictions dynamically based on the stored instances.
An important aspect of kNN is the choice of k. A small value of k can make the algorithm sensitive to noise, while a large value of k makes the boundaries between classes less distinct. Typically, the optimal value of k is selected using techniques such as cross-validation.
To illustrate how kNN works, consider the following dataset of points plotted on a two-dimensional plane:
\[
\begin{array}{|c|c|c|}
\hline
x_1 & x_2 & Class \\
\hline
1 & 2 & A \\
2 & 3 & A \\
3 & 1 & B \\
5 & 4 & B \\
4 & 3 & A \\
\hline
\end{array}
\]
Suppose we need to classify a new data point at (3,2). If \(k = 3\), we find the three closest points using the Euclidean distance formula:
\[
d = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2}
\]
After computing distances, we identify the closest three points and assign the most frequent class.
In the narrated presentation below, Khoury Boston’s Dr. Schedlbauer provides an introduction to the kNN machine learning algorithm for predicting categorical target variables.
Feature Scaling
Feature scaling, also known as normalization or standardization, is the process of transforming numerical features in a dataset so that they have a consistent scale. This is critical in many machine learning algorithms, including k-Nearest Neighbors (kNN), to prevent features with larger numerical ranges from dominating those with smaller ranges.
Feature scaling is necessary in machine learning models that rely on distance-based calculations, such as k-Nearest Neighbors (kNN), Support Vector Machines (SVM), Principal Component Analysis (PCA), and Gradient Descent-based algorithms (e.g., Regression, Neural Networks).
Without scaling, features with larger ranges (e.g., “Annual Salary” in thousands vs. “Age” in years) can disproportionately influence distance calculations, leading to biased results. Scaling generally improves classification accuracy in distance-based algorithms.
There are two primary methods of feature scaling:
- Min-Max Normalization (Rescaling to [0,1] or [-1,1])
- Z-Score Standardization (Mean = 0, Standard Deviation = 1)
Min-Max Normalization
Min-max normalization (or rescaling) scales the features to a fixed range, typically [0,1] or [-1,1]. This ensures that all values fall within the same range.
The formula for min-max normalization is:
\[
X_{\text{scaled}} = \frac{X - X_{\text{min}}}{X_{\text{max}} - X_{\text{min}}}
\]
In R, we can normalize a dataset by building a simple function:
# Define a min-max normalization function
min_max_normalize <- function(x) {
return((x - min(x)) / (max(x) - min(x)))
}
# apply scaling to numeric features of the 'iris' dataset
data(iris)
iris_normalized <- as.data.frame(lapply(iris[, 1:4], min_max_normalize))
# View scaled data
head(iris_normalized)
## Sepal.Length Sepal.Width Petal.Length Petal.Width
## 1 0.22222222 0.6250000 0.06779661 0.04166667
## 2 0.16666667 0.4166667 0.06779661 0.04166667
## 3 0.11111111 0.5000000 0.05084746 0.04166667
## 4 0.08333333 0.4583333 0.08474576 0.04166667
## 5 0.19444444 0.6666667 0.06779661 0.04166667
## 6 0.30555556 0.7916667 0.11864407 0.12500000
Z-Score Standardization
Z-score standardization (or standard scaling) transforms the feature distribution to have a mean of 0 and standard deviation of 1.
The formula for standardization is:
\[
X_{\text{standardized}} = \frac{X - \mu}{\sigma}
\]
where:
- \(\mu\) is the mean of the feature
- \(\sigma\) is the standard deviation
In R, we can use the Base R scale()
function for standardization or write our own function:
# Standardize the dataset using scale()
iris_standardized <- as.data.frame(scale(iris[, 1:4]))
# View standardized data
head(iris_standardized)
## Sepal.Length Sepal.Width Petal.Length Petal.Width
## 1 -0.8976739 1.01560199 -1.335752 -1.311052
## 2 -1.1392005 -0.13153881 -1.335752 -1.311052
## 3 -1.3807271 0.32731751 -1.392399 -1.311052
## 4 -1.5014904 0.09788935 -1.279104 -1.311052
## 5 -1.0184372 1.24503015 -1.335752 -1.311052
## 6 -0.5353840 1.93331463 -1.165809 -1.048667
Standardization is preferred when data follows a normal distribution and distance-based algorithms (e.g., kNN or regression) are used. However, since there is a presumption of normality, we must ensure that the features are reasonably normally distributed. Use a visual method such as a Q-Q Plot or a statistical test such as the Shapiro-Wilk Test to verify the assumption.
Since kNN relies on distance, scaling is necessary. All features must be scaled using the same approach.
Selecting Scaling Method
- Use Min-Max Normalization when data does not follow a normal distribution or when feature values must be within a fixed range.
- Use Z-Score Standardization when data follows a normal distribution and algorithms rely on distance metrics.
Classifying with kNN in R
To demonstrate kNN in R, we will use the well-known built-in iris dataset. The dataset consists of measurements of sepal length, sepal width, petal length, and petal width, along with the species of the iris flower as the target variable. The target variable is one of several pre-defined values, so it is categorical. Predicting a categorical variables is referred to as a classification task as we want to “classify” which type of iris flower a plant most likely is based on its characteristics of sepal length, sepal width, petal length, and petal width.
We will perform the following steps:
- Load the dataset and explore it. The data set is most commonly in a CSV file, but it must be tabular and have each case on a row. One of the columns must be the target variable. The data generally comes from observation and the assignment of classes is often done manually.
- Shape the data. kNN makes specific assumptions about the data. The data should not have outliers, no missing values, and the scales of all features must be normalized. In addition, all of the features must be numeric, so any categorical features must be encoded.
- Split the data into training and testing sets. This is an essential step in machine learning. We choose a random subset of the data to build the model and then use the held-back data to determine how well the model works by comparing the prediction made by the model against the actual value of the target variable. This allows us to calculate metrics such as accuracy where we measure how often the algorithm made the correct prediction. By doing this we can evaluate the effectiveness of the classification model and also use it for comparison to other models.
- Train a kNN model using the
class::knn()
function. There are numerous implementations of kNN found in a variety of packages. Other packages that include an implementation of kNN are caret and FNN.
- Make predictions on the test data. This is the validation step where the algorithm is used to make predictions for each of the held-back cases and comparing them to the actual observed value which allows calculating metrics such as accuracy.
- Evaluate the model by calculating accuracy for classification. Evaluating classification models, including k-Nearest Neighbors (kNN), involves using various performance metrics to assess how well the model distinguishes between classes. These metrics provide insights into the accuracy, robustness, and reliability of the model’s predictions. Accuracy is the proportion of correctly predicted values for the target variable in the held-back test data. It is generally reported as a percentage. Other evaluation metrics include precision, recall, Kappa Statistic, and the F1-Score.
1: Load Data
There are a number of packages that are needed and that must first be installed and then loaded:
library(class)
library(caret)
2: Inspect Structure
First, we load the iris dataset and inspect its structure. The iris dataset is “built-in” and thus does not have to be loaded from an external file.
# Load the iris dataset
data(iris)
Let’s check the structure of the dataset:
# Check the structure of the dataset
str(iris)
## 'data.frame': 150 obs. of 5 variables:
## $ Sepal.Length: num 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
## $ Sepal.Width : num 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
## $ Petal.Length: num 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
## $ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
## $ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
And let’s look at the first few rows:
# View first few rows
head(iris, 4)
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1 5.1 3.5 1.4 0.2 setosa
## 2 4.9 3.0 1.4 0.2 setosa
## 3 4.7 3.2 1.3 0.2 setosa
## 4 4.6 3.1 1.5 0.2 setosa
So, we can see that iris has 150 observations (rows or cases), with four numeric features and one categorical variable (Species), which is our target variable.
3: Shape Data
A: Identify Outliers
The first step is to identify whether any of the numeric features have outliers or extreme values. The most common rule of thumb is to consider any value that is more than 3 standard deviations from the mean an outlier. Outliers are then either removed or treated as missing values. We need to be careful to ignore missing values in the calculation of the mean and standard deviation as those have not yet been dealt with.
## copy data so we do not pollute the original data
df <- iris
## calculate mean and sd for each numeric column
numeric.cols <- sapply(df, is.numeric)
for (c in 1:length(numeric.cols)) {
if (numeric.cols[c] == TRUE) {
## find outliers in numeric column
m <- mean(df[,c], na.rm = T)
s <- sd(df[,c], na.rm = T)
outliers <- which(abs((m - df[,c]) / s) > 3.0)
if (length(outliers) > 0) {
## found outliers; replace with NA and impute later
cat("Found outliers in column '", names(df)[c], "': \n")
cat(" --> ", df[outliers,c], "\n\n")
}
}
}
## Found outliers in column ' Sepal.Width ':
## --> 4.4
There is one outlier, but it is not too far from the mean, so we will simply ignore it.
Lesson 3.203 – Detecting and Managing Outliers provides a more detailed treatment of this subject.
B: Manage Missing Values
Missing values must be addressed either by eliminating any observation (row) that has a missing value in any of its features or by imputing the missing value with an estimate. For numeric features, a common strategy is to impute the missing value with the mean or median, while for categorical features, the mode is often used.
The first step is to check each feature for missing values. While we can certainly check each feature manually, iterating over all columns is more practical. For missing numeric values we can check for NA with the function is.na()
, while for character (text) features we need to check whether it is ““.
num.Rows <- nrow(df)
num.Cols <- ncol(df)
found <- F
for (c in 1:num.Cols){
missing.Values <- which(is.na(df[,c]) | df[,c] == "")
num.Missing.Values <- length(missing.Values)
if (num.Missing.Values > 0) {
print(paste0("Column '", names(df)[c], "' has ", num.Missing.Values, " missing values"))
found <- T
}
}
if (!found) {
print("no missing values detected")
}
## [1] "no missing values detected"
There are no missing values in this dataset. If you want to know more about how to deal with missing values, study Lesson 3.204 – Managing Missing Values in Data.
C: Encode Categorical Features
So far, we have primarily dealt with the numeric features. Let’s turn our attention to the categorical features. Binary categorical features (where there are only two possible values) should be turned into a 0/1 encoding. Multi-level categorical features are often encoded with one-hot dummy codes, their frequency, or one of several other methods. At the end of the day, we must convert categorical features to numeric features. Lesson 3.207 - Encoding Categorical Features provides a more detailed treatment of this subject.
There are no categorical features in this dataset, so we can move on to the next step.
D: Scale Numeric Features
Numeric features must be scaled for kNN to perform well. We will use z-score standardization as our method for scaling and use the scale()
function from Base R to do so:
# standardize the numeric features using scale()
iris.scaled <- as.data.frame(scale(iris[, 1:4]))
# add the species into the scaled dataframe
iris.scaled$Species <- iris$Species
# view standardized data
head(iris.scaled,4)
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1 -0.8976739 1.01560199 -1.335752 -1.311052 setosa
## 2 -1.1392005 -0.13153881 -1.335752 -1.311052 setosa
## 3 -1.3807271 0.32731751 -1.392399 -1.311052 setosa
## 4 -1.5014904 0.09788935 -1.279104 -1.311052 setosa
Going forward, we will use the scaled dataset in iris.scaled
rather than the original unscaled dataset.
4: Split Data
To evaluate our model, we split the data into a training set (80%) and a test set (20%). The selection of the proportion to hold back for testing is generally 20%, but for smaller dataset we may choose 10% or 15%. When the target classes are not all occurring in the same proportion, then we may end up with class imbalance, i.e., in our case, a iris species may appears in the testing data but not in the training data as there are too few occurrences.
When we have class imbalance we need to select the training and test data differently, using methods such as stratified sampling or SMOTE. Lesson 3.224 - Managing Class Imbalance provides an introduction to these methods.
Now, kNN involves two elements of “randomness”: the selection of the subset sample for training and dealing with ties during prediction. So, if we use random numbers then every time we run the code we may get different results. To avoid this, we need to set a “seed” for the random number generator so it generates the same random numbers every time. And, naturally, we choose 42 as the seed as that is the answer to the universe.
# Set seed for reproducibility
set.seed(42)
So, now we can split the data by selecting a random subset for training (to make predictions) and the held-back data to evaluate the accuracy and performance of the model. While there are many ways to do this, we are using the createDataPartition()
function from the caret package. This function is commonly used for splitting data into training and test sets (without replacement) while maintaining the distribution of the target variable (stratified sampling). It is particularly useful for classification tasks to ensure that both sets contain representative proportions of each class.
# Split the dataset into training and testing sets
trainIndex <- createDataPartition(iris.scaled$Species, p = 0.8, list = FALSE)
trainData <- iris.scaled[trainIndex, ]
testData <- iris.scaled[-trainIndex, ]
Just a quick check of the dimensions to be sure it all went well.
# Check the dimensions
dim(trainData)
## [1] 120 5
## [1] 30 5
At this point, the training set contains approximately 120 observations, while the test set contains 30.
5: Train Model
Since kNN does not require explicit training like regression, we simply prepare the features and use the knn()
function from the class package. The function requires a vector of the known target variables (this is the key to “supervised machine learning”) to be separate from the predictor features.
# Extract features (predictors) and target variable
trainX <- trainData[, -5] # extract the Species column
trainY <- trainData$Species # predictor features
testX <- testData[, -5] # extract the Species column
testY <- testData$Species # predictor features
# Choose k (e.g., k = 5)
k_value <- 5
# Perform _kNN_ classification on the test data using the training data
predictions <- class::knn(train = trainX, test = testX, cl = trainY, k = k_value)
The predictions for the observations in the testing data using the training are stored in the variable predictions
.
The choice of k, i.e., how many of the “closest” neighbors are considered when determining the predicting class, is arbitrarily set at 5 in this example. Of course, we need to find the “optimal” k to get the “best” predictions by trying various k on the testing data and on different training samples. There are methods for this, such as k-fold cross validation or bagging, but those are beyond the scope of this primer. In practice, it has been found through empirical studies that a k equal to the square root of the number of observations in the training data is generally best.
6: Evaluate Model
To measure how well our model performs, we compute the accuracy by comparing the predicted and actual values. Accuracy measure the percentage of correct predictions, i.e., in our case the number of times that the algorithm correctly predicted the iris species for the test data.
# Calculate accuracy
accuracy <- sum(predictions == testY) / length(testY)
This results in a model Accuracy of 86.67%. Naturally, we must have an accuracy of more than 50% otherwise random guessing is just as effective.
7: Make Prediction
To make a prediction for a new unseen instance, we create a new data frame with a sample observation.
Let’s assume we have been provided with the data for an iris flower with the following measurements and we want to determine to which species it likely belongs:
- Sepal Length: 5.8
- Sepal Width: 3.0
- Petal Length: 4.2
- Petal Width: 1.2
We construct this case as a data frame and pass it to the “trained” kNN model using a k of 5.
# create dataframe containing new observations to be classified
new.case <- data.frame(Sepal.Length = 5.8, Sepal.Width = 3.0,
Petal.Length = 4.2, Petal.Width = 1.2)
# predict species for the new case using kNN with the training data
predicted.species <- knn(train = trainX, test = new.case, cl = trainY, k = 5)
Note that the prediction result is a variable of type “factor” which can be converted to a character string as follows:
species <- as.character(predicted.species)
8: Report Result
kNN predicts (based on finding the 5 closest neighbors and seeing which species they belong to) that the new case is most likely of the ‘virginica’ species. Based on our measured accuracy, we can say that this prediction is correct with a probability of 0.87.
Summary
The k-Nearest Neighbors (kNN) algorithm is a versatile and intuitive method for both classification and regression tasks. Unlike other machine learning models, kNN does not explicitly learn a function; instead, it memorizes the training data and makes predictions dynamically based on similarity measures. Choosing the right value of k is important, as small values lead to high variance, while large values increase bias.
By implementing kNN in R, we illustrated how to split data into training and test sets, perform classification and regression, and evaluate model performance.
References
No references.
LS0tCnRpdGxlOiAia05OIGZvciBDbGFzc2lmaWNhdGlvbjogQSBQcmltZXIiCnBhcmFtczoKICBjYXRlZ29yeTogMwogIHN0YWNrczogMAogIG51bWJlcjogNDA5CiAgdGltZTogOTAKICBsZXZlbDogYmVnaW5uZXIKICB0YWdzOiBfa05OXyxtYWNoaW5lIGxlYXJuaW5nLGNsYXNzaWZpY2F0aW9uLGNhcmV0LGNsYXNzCiAgZGVzY3JpcHRpb246ICJFeHBsYWlucyB0aGUgX2tOTl8gKGsgTmVhcmVzdCBOZWlnaGJvcikgbWFjaGluZSBsZWFybmluZwogICAgICAgICAgICAgICAgYWxnb3JpdGhtIGZvciBwcmVkaWN0aW5nIGEgY2F0ZWdvcmljYWwgdGFyZ2V0IHZhcmlhYmxlCiAgICAgICAgICAgICAgICAoY2xhc3NpZmljYXRpb24pLiBEZW1vbnN0cmF0ZXMgaG93IHRvIHRyYWluIGFuZCBldmFsdWF0ZQogICAgICAgICAgICAgICAgYSBfa05OXyBtb2RlbCBpbiBSLiIKZGF0ZTogIjxzbWFsbD5gciBTeXMuRGF0ZSgpYDwvc21hbGw+IgphdXRob3I6ICI8c21hbGw+TWFydGluIFNjaGVkbGJhdWVyPC9zbWFsbD4iCmVtYWlsOiAibS5zY2hlZGxiYXVlckBuZXUuZWR1IgphZmZpbGl0YXRpb246ICJOb3J0aGVhc3Rlcm4gVW5pdmVyc2l0eSIKb3V0cHV0OiAKICBib29rZG93bjo6aHRtbF9kb2N1bWVudDI6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29sbGFwc2VkOiBmYWxzZQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgdGhlbWU6IGx1bWVuCiAgICBoaWdobGlnaHQ6IHRhbmdvCi0tLQoKLS0tCnRpdGxlOiAiPHNtYWxsPmByIHBhcmFtcyRjYXRlZ29yeWAuYHIgcGFyYW1zJG51bWJlcmA8L3NtYWxsPjxici8+PHNwYW4gc3R5bGU9J2NvbG9yOiAjMkU0MDUzOyBmb250LXNpemU6IDAuOWVtJz5gciBybWFya2Rvd246Om1ldGFkYXRhJHRpdGxlYDwvc3Bhbj4iCi0tLQoKYGBge3IgY29kZT14ZnVuOjpyZWFkX3V0ZjgocGFzdGUwKGhlcmU6OmhlcmUoKSwnL1IvX2luc2VydDJEQi5SJykpLCBpbmNsdWRlID0gRkFMU0V9CmBgYAoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyBPYmplY3RpdmVzCgpVcG9uIGNvbXBsZXRpb24gb2YgdGhpcyBsZXNzb24sIHlvdSB3aWxsIGJlIGFibGUgdG86CgotICAgZGVmaW5lIHRoZSAqa05OKiBhbGdvcml0aG0KLSAgIGtub3cgd2hlbiB0byB1c2UgKmtOTioKLSAgIHR1bmUgdGhlIHZhbHVlIG9mIHRoZSBoeXBlci1wYXJhbWV0ZXIgKmsqCi0gICBhcHByZWNpYXRlIHRoZSBuZWVkIGZvciBmZWF0dXJlIHNjYWxpbmcKLSAgIHJlY29nbml6ZSB3aHkgb3V0bGllcnMgY2FuIGJlIHByb2JsZW1hdGljIGZvciAqa05OKgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyBJbnRyb2R1Y3Rpb24KClRoZSBrLU5lYXJlc3QgTmVpZ2hib3JzICgqa05OKikgYWxnb3JpdGhtIGlzIGEgc2ltcGxlIHlldCBwb3dlcmZ1bCBzdXBlcnZpc2VkIGxlYXJuaW5nIG1ldGhvZCB1c2VkIGZvciBjbGFzc2lmaWNhdGlvbiBhbmQgcmVncmVzc2lvbiB0YXNrcy4gSXQgaXMgYSAqKm5vbi1wYXJhbWV0cmljKiogYW5kICoqaW5zdGFuY2UtYmFzZWQqKiBsZWFybmluZyBhbGdvcml0aG0sIG1lYW5pbmcgdGhhdCBpdCBkb2VzIG5vdCBhc3N1bWUgYSBzcGVjaWZpYyBmdW5jdGlvbmFsIGZvcm0gZm9yIHRoZSBkYXRhIGFuZCBpbnN0ZWFkIG1lbW9yaXplcyB0aGUgdHJhaW5pbmcgZGF0YSB0byBtYWtlIHByZWRpY3Rpb25zLgoKVGhlIGZ1bmRhbWVudGFsIGlkZWEgYmVoaW5kICprTk4qIGlzIHRoYXQgc2ltaWxhciBkYXRhIHBvaW50cyB0ZW5kIHRvIGJlIGZvdW5kIGluIGNsb3NlIHByb3hpbWl0eSB0byBvbmUgYW5vdGhlci4gVGhlIGFsZ29yaXRobSBjbGFzc2lmaWVzIG9yIHByZWRpY3RzIHRoZSB0YXJnZXQgdmFsdWUgb2YgYSBuZXcgZGF0YSBwb2ludCBieSBjb25zaWRlcmluZyB0aGUgdGFyZ2V0IHZhbHVlcyBvZiBpdHMgbmVhcmVzdCBuZWlnaGJvcnMgaW4gdGhlIHRyYWluaW5nIGRhdGEuCgprLU5lYXJlc3QgTmVpZ2hib3JzICgqay1OTiogb3IgKmtOTiopIGlzIGEgdHlwZSBvZiBpbnN0YW5jZS1iYXNlZCBsZWFybmluZyBhbGdvcml0aG0gdXNlZCBpbiBib3RoIGNsYXNzaWZpY2F0aW9uIChwcmVkaWN0aW5nIGEgY2F0ZWdvcmljYWwgdmFyaWFibGUpIGFuZCByZWdyZXNzaW9uIChwcmVkaWN0aW5nIGEgbnVtZXJpYyB2YXJpYWJsZSkgdGFza3MuIEl0IGlzIGNhbGxlZCAnbGF6eSBsZWFybmluZycgYXMgaXQgZG9lc24ndCBidWlsZCBhIG1vZGVsIGJ1dCBpbnN0ZWFkIG1lbW9yaXplcyB0aGUgdHJhaW5pbmcgZGF0YXNldC4KCkV4YW1wbGVzIG9mIGFwcGxpY2F0aW9ucyBvZiAqa05OKiBpbmNsdWRlIGNsYXNzaWZpY2F0aW9uLCByZWNvbW1lbmRhdGlvbiBzeXN0ZW1zLCBpbWFnZSByZWNvZ25pdGlvbiwgYW5kIHRleHQgY2F0ZWdvcml6YXRpb24sIGFtb25nIG90aGVycy4KClRoaXMgbGVzc29uIHdpbGwgb25seSBjb25zaWRlciAqa05OKiBmb3IgY2xhc3NpZmljYXRpb24gdGFza3MuCgojIyBPdmVydmlldyBvZiB0aGUgQWxnb3JpdGhtCgpUaGUgYmFzaWMgc3RlcHMgaW52b2x2ZWQgaW4gdGhlIGstTk4gYWxnb3JpdGhtIGFyZToKCjEuICAqKkNob29zZSB0aGUgbnVtYmVyIG9mIGsgYW5kIGEgZGlzdGFuY2UgbWV0cmljKio6IFRoZSBudW1iZXIgb2YgbmVpZ2hib3JzICgqayopIGFuZCB0aGUgdHlwZSBvZiBkaXN0YW5jZSBhcmUgY2hvc2VuIGFzIHBlciB0aGUgcHJvYmxlbSBjb250ZXh0LiBGb3IgZXhhbXBsZSwgeW91IGNvdWxkIGNob29zZSAqaz0zKiBhbmQgdXNlIEV1Y2xpZGVhbiBkaXN0YW5jZSBhcyB0aGUgbWV0cmljLgoKMi4gICoqRmluZCB0aGUgKmsqIG5lYXJlc3QgbmVpZ2hib3JzIG9mIHRoZSBzYW1wbGUgdGhhdCB5b3Ugd2FudCB0byBjbGFzc2lmeSoqOiBGb3IgZWFjaCBkYXRhIHBvaW50IHRoYXQgeW91IHdhbnQgdG8gbWFrZSBhIHByZWRpY3Rpb24gZm9yLCB5b3UgaWRlbnRpZnkgdGhlICprKiBwb2ludHMgaW4gdGhlIHRyYWluaW5nIGRhdGFzZXQgdGhhdCBhcmUgImNsb3Nlc3QiIGFjY29yZGluZyB0byB0aGUgY2hvc2VuIGRpc3RhbmNlIG1ldHJpYy4gV2hpbGUgRXVjbGlkZWFuIGRpc3RhbmNlIGlzIG1vc3QgY29tbW9uLCBvdGhlciBkaXN0YW5jZSBjYWxjdWxhdGlvbnMgbWV0aG9kcyBjYW4gYmUgdXNlZCBkZXBlbmRpbmcgb24gdGhlIGRhdGEuCgozLiAgKipBc3NpZ24gdGhlIGxhYmVsKio6IEZvciBhIGNsYXNzaWZpY2F0aW9uIHByb2JsZW0sIHRoZSBuZXcgZGF0YSBwb2ludCBpcyBhc3NpZ25lZCB0aGUgY2xhc3Mgd2hpY2ggaGFzIHRoZSBtYWpvcml0eSBhbW9uZyBpdHMgKmsqIG5lYXJlc3QgbmVpZ2hib3JzLiBGb3IgYSByZWdyZXNzaW9uIHByb2JsZW0sIHRoZSBuZXcgZGF0YSBwb2ludCBjb3VsZCBiZSBhc3NpZ25lZCB0aGUgbWVhbiB2YWx1ZSBvZiBpdHMgKmsqIG5lYXJlc3QgbmVpZ2hib3JzLgoKKmtOTiogaXMgYSBzaW1wbGUgYWxnb3JpdGhtIHRoYXQgaXMgZWFzeSB0byB1bmRlcnN0YW5kIGFuZCBpbXBsZW1lbnQuIEl0IHdvcmtzIHdlbGwgd2l0aCBzbWFsbGVyIGRhdGFzZXRzIHRoYXQgaGF2ZSBmZXdlciBkaW1lbnNpb25zIChmZWF0dXJlcyksIGFuZCB3aGVyZSB0aGUgZGF0YSBwb2ludHMgYXJlIGNsZWFybHkgc2VwYXJhYmxlLiBIb3dldmVyLCBpdCBtYXkgbm90IGJlIHN1aXRhYmxlIGZvciBsYXJnZSwgaGlnaC1kaW1lbnNpb25hbCBkYXRhc2V0cywgYXMgdGhlIGNvbXB1dGF0aW9uIG9mIGRpc3RhbmNlcyBjYW4gYmVjb21lIHZlcnkgZXhwZW5zaXZlLiBJdCBpcyBhbHNvIHNlbnNpdGl2ZSB0byBpcnJlbGV2YW50IG9yIGNvcnJlbGF0ZWQgZmVhdHVyZXMsIHdoaWNoIGNhbiBpbXBhY3QgdGhlIGNhbGN1bGF0aW9uIG9mICJjbG9zZXN0IiBuZWlnaGJvcnMuIFRoZSBtb2RlbCBkb2VzIG5vdCBwZXJmb3JtIGFuIGV4cGxpY2l0IHRyYWluaW5nIHBoYXNlOyBpdCBzaW1wbHkgc3RvcmVzIHRoZSBkYXRhc2V0IGFuZCBtYWtlcyBwcmVkaWN0aW9ucyBkeW5hbWljYWxseSBiYXNlZCBvbiB0aGUgc3RvcmVkIGluc3RhbmNlcy4KCkFuIGltcG9ydGFudCBhc3BlY3Qgb2YgKmtOTiogaXMgdGhlIGNob2ljZSBvZiAqayouIEEgc21hbGwgdmFsdWUgb2YgKmsqIGNhbiBtYWtlIHRoZSBhbGdvcml0aG0gc2Vuc2l0aXZlIHRvIG5vaXNlLCB3aGlsZSBhIGxhcmdlIHZhbHVlIG9mICprKiBtYWtlcyB0aGUgYm91bmRhcmllcyBiZXR3ZWVuIGNsYXNzZXMgbGVzcyBkaXN0aW5jdC4gVHlwaWNhbGx5LCB0aGUgb3B0aW1hbCB2YWx1ZSBvZiAqayogaXMgc2VsZWN0ZWQgdXNpbmcgdGVjaG5pcXVlcyBzdWNoIGFzIGNyb3NzLXZhbGlkYXRpb24uCgpUbyBpbGx1c3RyYXRlIGhvdyAqa05OKiB3b3JrcywgY29uc2lkZXIgdGhlIGZvbGxvd2luZyBkYXRhc2V0IG9mIHBvaW50cyBwbG90dGVkIG9uIGEgdHdvLWRpbWVuc2lvbmFsIHBsYW5lOgoKJCQKXGJlZ2lue2FycmF5fXt8Y3xjfGN8fQpcaGxpbmUKeF8xICYgeF8yICYgQ2xhc3MgXFwKXGhsaW5lCjEgJiAyICYgQSBcXAoyICYgMyAmIEEgXFwKMyAmIDEgJiBCIFxcCjUgJiA0ICYgQiBcXAo0ICYgMyAmIEEgXFwKXGhsaW5lClxlbmR7YXJyYXl9CiQkCgpTdXBwb3NlIHdlIG5lZWQgdG8gY2xhc3NpZnkgYSBuZXcgZGF0YSBwb2ludCBhdCAoMywyKS4gSWYgJGsgPSAzJCwgd2UgZmluZCB0aGUgdGhyZWUgY2xvc2VzdCBwb2ludHMgdXNpbmcgdGhlIEV1Y2xpZGVhbiBkaXN0YW5jZSBmb3JtdWxhOgoKJCQKZCA9IFxzcXJ0eyh4XzEgLSB4XzIpXjIgKyAoeV8xIC0geV8yKV4yfQokJAoKQWZ0ZXIgY29tcHV0aW5nIGRpc3RhbmNlcywgd2UgaWRlbnRpZnkgdGhlIGNsb3Nlc3QgdGhyZWUgcG9pbnRzIGFuZCBhc3NpZ24gdGhlIG1vc3QgZnJlcXVlbnQgY2xhc3MuCgpJbiB0aGUgbmFycmF0ZWQgcHJlc2VudGF0aW9uIGJlbG93LCBLaG91cnkgQm9zdG9uJ3MgRHIuIFNjaGVkbGJhdWVyIHByb3ZpZGVzIGFuIGludHJvZHVjdGlvbiB0byB0aGUgKmtOTiogbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG0gZm9yIHByZWRpY3RpbmcgY2F0ZWdvcmljYWwgdGFyZ2V0IHZhcmlhYmxlcy4KCjxpZnJhbWUgc3JjPSJodHRwczovL3BsYXllci52aW1lby5jb20vdmlkZW8vODMwMzk3MzYzP2g9NGI0MWZhMDAxYSZhbXA7dGl0bGU9MCZhbXA7YnlsaW5lPTAmYW1wO3BvcnRyYWl0PTAmYW1wO3NwZWVkPTAmYW1wO2JhZGdlPTAmYW1wO2F1dG9wYXVzZT0wJmFtcDtwbGF5ZXJfaWQ9MCZhbXA7YXBwX2lkPTU4NDc5IiB3aWR0aD0iNjQwIiBoZWlnaHQ9IjM2MCIgZnJhbWVib3JkZXI9IjAiIGFsbG93PSJhdXRvcGxheTsgZnVsbHNjcmVlbjsgcGljdHVyZS1pbi1waWN0dXJlIiBhbGxvd2Z1bGxzY3JlZW4gdGl0bGU9IlVuZGVyc3RhbmRpbmcgX2tOTl8iIGRhdGEtZXh0ZXJuYWw9IjEiPgoKPC9pZnJhbWU+CgojIyBSZXF1aXJlZCBEYXRhIFNoYXBpbmcKClByaW9yIHRvIGFwcGx5aW5nICprTk4qLCB0aGUgZGF0YSBtdXN0IGJlIHNoYXBlZDoKCjEuICByZW1vdmUgb3V0bGllcnMgYW5kIGV4dHJlbWUgdmFsdWVzCjIuICBpbXB1dGUgb3IgcmVtb3ZlIG1pc3NpbmcgZmVhdHVyZSB2YWx1ZXMKMy4gIG5vcm1hbGl6ZSBudW1lcmljIGZlYXR1cmVzIHRvIGEgY29tbW9uIHNjYWxlCjQuICBlbmNvZGUgY2F0ZWdvcmljYWwgZmVhdHVyZXMKCkZvciBtb3JlIGluZm9ybWF0aW9uIG9uIGhvdyBzaGFwZSB0aGUgZGF0YSBmb3IgdXNlIHdpdGggKmtOTiosIGNvbnN1bHQgdGhlc2UgbGVzc29uczoKCjEuICBbMy4yMDMgLS0gRGV0ZWN0aW5nIGFuZCBNYW5hZ2luZyBPdXRsaWVyc10oaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy8wMy5tbC9sLTMtMjAzLW91dGxpZXJzL2wtMy0yMDMuaHRtbCkKMi4gIFszLjIwNCAtLSBNYW5hZ2luZyBNaXNzaW5nIFZhbHVlcyBpbiBEYXRhXShodHRwOi8vYXJ0aWZpY2l1bS51cy9sZXNzb25zLzAzLm1sL2wtMy0yMDQtbWlzc2luZy12YWx1ZXMvbC0zLTIwNC5odG1sKQozLiAgWzMuMjA2IC0tIE5vcm1hbGl6aW5nIE51bWVyaWMgRmVhdHVyZXMgZm9yIE1hY2hpbmUgTGVhcm5pbmcgQWxnb3JpdGhtc10oaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy8wMy5tbC9sLTMtMjA2LWZlYXR1cmUtbm9ybWFsaXphdGlvbi9sLTMtMjA2Lmh0bWwpCjQuICBbMy4yMDcgLS0gRW5jb2RpbmcgQ2F0ZWdvcmljYWwgRmVhdHVyZXNdKGh0dHA6Ly9hcnRpZmljaXVtLnVzL2xlc3NvbnMvMDMubWwvbC0zLTIwNy1jYXRlZ29yaWNhbC1lbmNvZGluZy9sLTMtMjA3Lmh0bWwpCgpTdGVwICgzKSByZXF1aXJpbmcgYWxsIGZlYXR1cmVzIHRvIGJlIG5vcm1hbGl6ZWQgdG8gYSBjb21tb24gc2NhbGUgaXMgZXNzZW50aWFsIGZvciAqa05OKiBhbmQgY2Fubm90IGJlIG9taXR0ZWQuCgojIyBGZWF0dXJlIFNjYWxpbmcKCkZlYXR1cmUgc2NhbGluZywgYWxzbyBrbm93biBhcyAqbm9ybWFsaXphdGlvbiogb3IgKnN0YW5kYXJkaXphdGlvbiosIGlzIHRoZSBwcm9jZXNzIG9mIHRyYW5zZm9ybWluZyBudW1lcmljYWwgZmVhdHVyZXMgaW4gYSBkYXRhc2V0IHNvIHRoYXQgdGhleSBoYXZlIGEgY29uc2lzdGVudCBzY2FsZS4gVGhpcyBpcyBjcml0aWNhbCBpbiBtYW55IG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtcywgaW5jbHVkaW5nICoqay1OZWFyZXN0IE5laWdoYm9ycyAoa05OKSoqLCB0byBwcmV2ZW50IGZlYXR1cmVzIHdpdGggbGFyZ2VyIG51bWVyaWNhbCByYW5nZXMgZnJvbSBkb21pbmF0aW5nIHRob3NlIHdpdGggc21hbGxlciByYW5nZXMuCgpGZWF0dXJlIHNjYWxpbmcgaXMgbmVjZXNzYXJ5IGluIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIHRoYXQgcmVseSBvbiBkaXN0YW5jZS1iYXNlZCBjYWxjdWxhdGlvbnMsIHN1Y2ggYXMgay1OZWFyZXN0IE5laWdoYm9ycyAoa05OKSwgU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMgKFNWTSksIFByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMgKFBDQSksIGFuZCBHcmFkaWVudCBEZXNjZW50LWJhc2VkIGFsZ29yaXRobXMgKCplLmcuKiwgUmVncmVzc2lvbiwgTmV1cmFsIE5ldHdvcmtzKS4KCldpdGhvdXQgc2NhbGluZywgZmVhdHVyZXMgd2l0aCBsYXJnZXIgcmFuZ2VzICgqZS5nLiosICJBbm51YWwgU2FsYXJ5IiBpbiB0aG91c2FuZHMgdnMuICJBZ2UiIGluIHllYXJzKSBjYW4gZGlzcHJvcG9ydGlvbmF0ZWx5IGluZmx1ZW5jZSBkaXN0YW5jZSBjYWxjdWxhdGlvbnMsIGxlYWRpbmcgdG8gYmlhc2VkIHJlc3VsdHMuIFNjYWxpbmcgZ2VuZXJhbGx5IGltcHJvdmVzICpjbGFzc2lmaWNhdGlvbiBhY2N1cmFjeSogaW4gZGlzdGFuY2UtYmFzZWQgYWxnb3JpdGhtcy4KClRoZXJlIGFyZSB0d28gcHJpbWFyeSBtZXRob2RzIG9mIGZlYXR1cmUgc2NhbGluZzoKCjEuICBNaW4tTWF4IE5vcm1hbGl6YXRpb24gKFJlc2NhbGluZyB0byBbMCwxXSBvciBbLTEsMV0pCjIuICBaLVNjb3JlIFN0YW5kYXJkaXphdGlvbiAoTWVhbiA9IDAsIFN0YW5kYXJkIERldmlhdGlvbiA9IDEpCgojIyMgTWluLU1heCBOb3JtYWxpemF0aW9uCgpNaW4tbWF4IG5vcm1hbGl6YXRpb24gKG9yIHJlc2NhbGluZykgc2NhbGVzIHRoZSBmZWF0dXJlcyB0byBhIGZpeGVkIHJhbmdlLCB0eXBpY2FsbHkgWzAsMV0gb3IgWy0xLDFdLiBUaGlzIGVuc3VyZXMgdGhhdCBhbGwgdmFsdWVzIGZhbGwgd2l0aGluIHRoZSBzYW1lIHJhbmdlLgoKVGhlIGZvcm11bGEgZm9yIG1pbi1tYXggbm9ybWFsaXphdGlvbiBpczoKCiQkClhfe1x0ZXh0e3NjYWxlZH19ID0gXGZyYWN7WCAtIFhfe1x0ZXh0e21pbn19fXtYX3tcdGV4dHttYXh9fSAtIFhfe1x0ZXh0e21pbn19fQokJAoKSW4gUiwgd2UgY2FuIG5vcm1hbGl6ZSBhIGRhdGFzZXQgYnkgYnVpbGRpbmcgYSBzaW1wbGUgZnVuY3Rpb246CgpgYGB7cn0KIyBEZWZpbmUgYSBtaW4tbWF4IG5vcm1hbGl6YXRpb24gZnVuY3Rpb24KbWluX21heF9ub3JtYWxpemUgPC0gZnVuY3Rpb24oeCkgewogIHJldHVybigoeCAtIG1pbih4KSkgLyAobWF4KHgpIC0gbWluKHgpKSkKfQoKIyBhcHBseSBzY2FsaW5nIHRvIG51bWVyaWMgZmVhdHVyZXMgb2YgdGhlICdpcmlzJyBkYXRhc2V0CmRhdGEoaXJpcykKaXJpc19ub3JtYWxpemVkIDwtIGFzLmRhdGEuZnJhbWUobGFwcGx5KGlyaXNbLCAxOjRdLCBtaW5fbWF4X25vcm1hbGl6ZSkpCgojIFZpZXcgc2NhbGVkIGRhdGEKaGVhZChpcmlzX25vcm1hbGl6ZWQpCmBgYAoKIyMjIFotU2NvcmUgU3RhbmRhcmRpemF0aW9uCgpaLXNjb3JlIHN0YW5kYXJkaXphdGlvbiAob3Igc3RhbmRhcmQgc2NhbGluZykgdHJhbnNmb3JtcyB0aGUgZmVhdHVyZSBkaXN0cmlidXRpb24gdG8gaGF2ZSBhICptZWFuIG9mIDAqIGFuZCAqc3RhbmRhcmQgZGV2aWF0aW9uIG9mIDEqLgoKVGhlIGZvcm11bGEgZm9yIHN0YW5kYXJkaXphdGlvbiBpczoKCiQkClhfe1x0ZXh0e3N0YW5kYXJkaXplZH19ID0gXGZyYWN7WCAtIFxtdX17XHNpZ21hfQokJAoKd2hlcmU6CgotICAgJFxtdSQgaXMgdGhlIG1lYW4gb2YgdGhlIGZlYXR1cmUKLSAgICRcc2lnbWEkIGlzIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24KCkluIFIsIHdlIGNhbiB1c2UgdGhlIEJhc2UgUiBgc2NhbGUoKWAgZnVuY3Rpb24gZm9yIHN0YW5kYXJkaXphdGlvbiBvciB3cml0ZSBvdXIgb3duIGZ1bmN0aW9uOgoKYGBge3J9CiMgU3RhbmRhcmRpemUgdGhlIGRhdGFzZXQgdXNpbmcgc2NhbGUoKQppcmlzX3N0YW5kYXJkaXplZCA8LSBhcy5kYXRhLmZyYW1lKHNjYWxlKGlyaXNbLCAxOjRdKSkKCiMgVmlldyBzdGFuZGFyZGl6ZWQgZGF0YQpoZWFkKGlyaXNfc3RhbmRhcmRpemVkKQpgYGAKCipTdGFuZGFyZGl6YXRpb24qIGlzIHByZWZlcnJlZCB3aGVuIGRhdGEgZm9sbG93cyBhICpub3JtYWwgZGlzdHJpYnV0aW9uKiBhbmQgZGlzdGFuY2UtYmFzZWQgYWxnb3JpdGhtcyAoKmUuZy4qLCAqa05OKiBvciByZWdyZXNzaW9uKSBhcmUgdXNlZC4gSG93ZXZlciwgc2luY2UgdGhlcmUgaXMgYSBwcmVzdW1wdGlvbiBvZiBub3JtYWxpdHksIHdlIG11c3QgZW5zdXJlIHRoYXQgdGhlIGZlYXR1cmVzIGFyZSByZWFzb25hYmx5IG5vcm1hbGx5IGRpc3RyaWJ1dGVkLiBVc2UgYSB2aXN1YWwgbWV0aG9kIHN1Y2ggYXMgYSBRLVEgUGxvdCBvciBhIHN0YXRpc3RpY2FsIHRlc3Qgc3VjaCBhcyB0aGUgU2hhcGlyby1XaWxrIFRlc3QgdG8gdmVyaWZ5IHRoZSBhc3N1bXB0aW9uLgoKPiBTaW5jZSAqa05OKiByZWxpZXMgb24gZGlzdGFuY2UsICoqc2NhbGluZyBpcyBuZWNlc3NhcnkqKi4gQWxsIGZlYXR1cmVzIG11c3QgYmUgc2NhbGVkIHVzaW5nIHRoZSBzYW1lIGFwcHJvYWNoLgoKIyMjIFNlbGVjdGluZyBTY2FsaW5nIE1ldGhvZAoKLSAgIFVzZSAqKk1pbi1NYXggTm9ybWFsaXphdGlvbioqIHdoZW4gZGF0YSAqKmRvZXMgbm90IGZvbGxvdyBhIG5vcm1hbCBkaXN0cmlidXRpb24qKiBvciB3aGVuIGZlYXR1cmUgdmFsdWVzIG11c3QgYmUgd2l0aGluIGEgZml4ZWQgcmFuZ2UuCi0gICBVc2UgKipaLVNjb3JlIFN0YW5kYXJkaXphdGlvbioqIHdoZW4gZGF0YSAqKmZvbGxvd3MgYSBub3JtYWwgZGlzdHJpYnV0aW9uKiogYW5kIGFsZ29yaXRobXMgcmVseSBvbiBkaXN0YW5jZSBtZXRyaWNzLgoKIyMgQ2xhc3NpZnlpbmcgd2l0aCAqa05OKiBpbiBSCgpUbyBkZW1vbnN0cmF0ZSAqa05OKiBpbiBSLCB3ZSB3aWxsIHVzZSB0aGUgd2VsbC1rbm93biBidWlsdC1pbiAqaXJpcyogZGF0YXNldC4gVGhlIGRhdGFzZXQgY29uc2lzdHMgb2YgbWVhc3VyZW1lbnRzIG9mICoqc2VwYWwgbGVuZ3RoLCBzZXBhbCB3aWR0aCwgcGV0YWwgbGVuZ3RoLCBhbmQgcGV0YWwgd2lkdGgqKiwgYWxvbmcgd2l0aCB0aGUgc3BlY2llcyBvZiB0aGUgaXJpcyBmbG93ZXIgYXMgdGhlIHRhcmdldCB2YXJpYWJsZS4gVGhlIHRhcmdldCB2YXJpYWJsZSBpcyBvbmUgb2Ygc2V2ZXJhbCBwcmUtZGVmaW5lZCB2YWx1ZXMsIHNvIGl0IGlzIGNhdGVnb3JpY2FsLiBQcmVkaWN0aW5nIGEgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGlzIHJlZmVycmVkIHRvIGFzIGEgKmNsYXNzaWZpY2F0aW9uIHRhc2sqIGFzIHdlIHdhbnQgdG8gImNsYXNzaWZ5IiB3aGljaCB0eXBlIG9mIGlyaXMgZmxvd2VyIGEgcGxhbnQgbW9zdCBsaWtlbHkgaXMgYmFzZWQgb24gaXRzIGNoYXJhY3RlcmlzdGljcyBvZiAqKnNlcGFsIGxlbmd0aCwgc2VwYWwgd2lkdGgsIHBldGFsIGxlbmd0aCwgYW5kIHBldGFsIHdpZHRoKiouCgpXZSB3aWxsIHBlcmZvcm0gdGhlIGZvbGxvd2luZyBzdGVwczoKCjEuICBMb2FkIHRoZSBkYXRhc2V0IGFuZCBleHBsb3JlIGl0LiBUaGUgZGF0YSBzZXQgaXMgbW9zdCBjb21tb25seSBpbiBhIENTViBmaWxlLCBidXQgaXQgbXVzdCBiZSB0YWJ1bGFyIGFuZCBoYXZlIGVhY2ggY2FzZSBvbiBhIHJvdy4gT25lIG9mIHRoZSBjb2x1bW5zIG11c3QgYmUgdGhlIHRhcmdldCB2YXJpYWJsZS4gVGhlIGRhdGEgZ2VuZXJhbGx5IGNvbWVzIGZyb20gb2JzZXJ2YXRpb24gYW5kIHRoZSBhc3NpZ25tZW50IG9mIGNsYXNzZXMgaXMgb2Z0ZW4gZG9uZSBtYW51YWxseS4KMi4gIFNoYXBlIHRoZSBkYXRhLiBrTk4gbWFrZXMgc3BlY2lmaWMgYXNzdW1wdGlvbnMgYWJvdXQgdGhlIGRhdGEuIFRoZSBkYXRhIHNob3VsZCBub3QgaGF2ZSBvdXRsaWVycywgbm8gbWlzc2luZyB2YWx1ZXMsIGFuZCB0aGUgc2NhbGVzIG9mIGFsbCBmZWF0dXJlcyBtdXN0IGJlIG5vcm1hbGl6ZWQuIEluIGFkZGl0aW9uLCBhbGwgb2YgdGhlIGZlYXR1cmVzIG11c3QgYmUgbnVtZXJpYywgc28gYW55IGNhdGVnb3JpY2FsIGZlYXR1cmVzIG11c3QgYmUgZW5jb2RlZC4KMy4gIFNwbGl0IHRoZSBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0cy4gVGhpcyBpcyBhbiBlc3NlbnRpYWwgc3RlcCBpbiBtYWNoaW5lIGxlYXJuaW5nLiBXZSBjaG9vc2UgYSByYW5kb20gc3Vic2V0IG9mIHRoZSBkYXRhIHRvIGJ1aWxkIHRoZSBtb2RlbCBhbmQgdGhlbiB1c2UgdGhlIGhlbGQtYmFjayBkYXRhIHRvIGRldGVybWluZSBob3cgd2VsbCB0aGUgbW9kZWwgd29ya3MgYnkgY29tcGFyaW5nIHRoZSBwcmVkaWN0aW9uIG1hZGUgYnkgdGhlIG1vZGVsIGFnYWluc3QgdGhlIGFjdHVhbCB2YWx1ZSBvZiB0aGUgdGFyZ2V0IHZhcmlhYmxlLiBUaGlzIGFsbG93cyB1cyB0byBjYWxjdWxhdGUgbWV0cmljcyBzdWNoIGFzIGFjY3VyYWN5IHdoZXJlIHdlIG1lYXN1cmUgaG93IG9mdGVuIHRoZSBhbGdvcml0aG0gbWFkZSB0aGUgY29ycmVjdCBwcmVkaWN0aW9uLiBCeSBkb2luZyB0aGlzIHdlIGNhbiBldmFsdWF0ZSB0aGUgZWZmZWN0aXZlbmVzcyBvZiB0aGUgY2xhc3NpZmljYXRpb24gbW9kZWwgYW5kIGFsc28gdXNlIGl0IGZvciBjb21wYXJpc29uIHRvIG90aGVyIG1vZGVscy4KNC4gIFRyYWluIGEgKmtOTiogbW9kZWwgdXNpbmcgdGhlIGBjbGFzczo6a25uKClgIGZ1bmN0aW9uLiBUaGVyZSBhcmUgbnVtZXJvdXMgaW1wbGVtZW50YXRpb25zIG9mICprTk4qIGZvdW5kIGluIGEgdmFyaWV0eSBvZiBwYWNrYWdlcy4gT3RoZXIgcGFja2FnZXMgdGhhdCBpbmNsdWRlIGFuIGltcGxlbWVudGF0aW9uIG9mICprTk4qIGFyZSAqKmNhcmV0KiogYW5kICoqRk5OKiouCjUuICBNYWtlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IGRhdGEuIFRoaXMgaXMgdGhlIHZhbGlkYXRpb24gc3RlcCB3aGVyZSB0aGUgYWxnb3JpdGhtIGlzIHVzZWQgdG8gbWFrZSBwcmVkaWN0aW9ucyBmb3IgZWFjaCBvZiB0aGUgaGVsZC1iYWNrIGNhc2VzIGFuZCBjb21wYXJpbmcgdGhlbSB0byB0aGUgYWN0dWFsIG9ic2VydmVkIHZhbHVlIHdoaWNoIGFsbG93cyBjYWxjdWxhdGluZyBtZXRyaWNzIHN1Y2ggYXMgYWNjdXJhY3kuCjYuICBFdmFsdWF0ZSB0aGUgbW9kZWwgYnkgY2FsY3VsYXRpbmcgYWNjdXJhY3kgZm9yIGNsYXNzaWZpY2F0aW9uLiBFdmFsdWF0aW5nIGNsYXNzaWZpY2F0aW9uIG1vZGVscywgaW5jbHVkaW5nIGstTmVhcmVzdCBOZWlnaGJvcnMgKCprTk4qKSwgaW52b2x2ZXMgdXNpbmcgdmFyaW91cyBwZXJmb3JtYW5jZSBtZXRyaWNzIHRvIGFzc2VzcyBob3cgd2VsbCB0aGUgbW9kZWwgZGlzdGluZ3Vpc2hlcyBiZXR3ZWVuIGNsYXNzZXMuIFRoZXNlIG1ldHJpY3MgcHJvdmlkZSBpbnNpZ2h0cyBpbnRvIHRoZSBhY2N1cmFjeSwgcm9idXN0bmVzcywgYW5kIHJlbGlhYmlsaXR5IG9mIHRoZSBtb2RlbCdzIHByZWRpY3Rpb25zLiBBY2N1cmFjeSBpcyB0aGUgcHJvcG9ydGlvbiBvZiBjb3JyZWN0bHkgcHJlZGljdGVkIHZhbHVlcyBmb3IgdGhlIHRhcmdldCB2YXJpYWJsZSBpbiB0aGUgaGVsZC1iYWNrIHRlc3QgZGF0YS4gSXQgaXMgZ2VuZXJhbGx5IHJlcG9ydGVkIGFzIGEgcGVyY2VudGFnZS4gT3RoZXIgZXZhbHVhdGlvbiBtZXRyaWNzIGluY2x1ZGUgcHJlY2lzaW9uLCByZWNhbGwsIEthcHBhIFN0YXRpc3RpYywgYW5kIHRoZSAqRjEqLVNjb3JlLgoKIyMjIDE6IExvYWQgRGF0YQoKVGhlcmUgYXJlIGEgbnVtYmVyIG9mIHBhY2thZ2VzIHRoYXQgYXJlIG5lZWRlZCBhbmQgdGhhdCBtdXN0IGZpcnN0IGJlIGluc3RhbGxlZCBhbmQgdGhlbiBsb2FkZWQ6CgpgYGB7ciBsb2FkUGFja2dzLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KbGlicmFyeShjbGFzcykKbGlicmFyeShjYXJldCkKYGBgCgojIyMgMjogSW5zcGVjdCBTdHJ1Y3R1cmUKCkZpcnN0LCB3ZSBsb2FkIHRoZSAqaXJpcyogZGF0YXNldCBhbmQgaW5zcGVjdCBpdHMgc3RydWN0dXJlLiBUaGUgKmlyaXMqIGRhdGFzZXQgaXMgImJ1aWx0LWluIiBhbmQgdGh1cyBkb2VzIG5vdCBoYXZlIHRvIGJlIGxvYWRlZCBmcm9tIGFuIGV4dGVybmFsIGZpbGUuCgpgYGB7cn0KIyBMb2FkIHRoZSBpcmlzIGRhdGFzZXQKZGF0YShpcmlzKQpgYGAKCkxldCdzIGNoZWNrIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGRhdGFzZXQ6CgpgYGB7cn0KIyBDaGVjayB0aGUgc3RydWN0dXJlIG9mIHRoZSBkYXRhc2V0CnN0cihpcmlzKQpgYGAKCkFuZCBsZXQncyBsb29rIGF0IHRoZSBmaXJzdCBmZXcgcm93czoKCmBgYHtyfQojIFZpZXcgZmlyc3QgZmV3IHJvd3MKaGVhZChpcmlzLCA0KQpgYGAKClNvLCB3ZSBjYW4gc2VlIHRoYXQgKmlyaXMqIGhhcyAxNTAgb2JzZXJ2YXRpb25zIChyb3dzIG9yIGNhc2VzKSwgd2l0aCBmb3VyIG51bWVyaWMgZmVhdHVyZXMgYW5kIG9uZSBjYXRlZ29yaWNhbCB2YXJpYWJsZSAoKlNwZWNpZXMqKSwgd2hpY2ggaXMgb3VyIHRhcmdldCB2YXJpYWJsZS4KCiMjIyAzOiBTaGFwZSBEYXRhCgojIyMjIEE6IElkZW50aWZ5IE91dGxpZXJzCgpUaGUgZmlyc3Qgc3RlcCBpcyB0byBpZGVudGlmeSB3aGV0aGVyIGFueSBvZiB0aGUgbnVtZXJpYyBmZWF0dXJlcyBoYXZlIG91dGxpZXJzIG9yIGV4dHJlbWUgdmFsdWVzLiBUaGUgbW9zdCBjb21tb24gcnVsZSBvZiB0aHVtYiBpcyB0byBjb25zaWRlciBhbnkgdmFsdWUgdGhhdCBpcyBtb3JlIHRoYW4gMyBzdGFuZGFyZCBkZXZpYXRpb25zIGZyb20gdGhlIG1lYW4gYW4gb3V0bGllci4gT3V0bGllcnMgYXJlIHRoZW4gZWl0aGVyIHJlbW92ZWQgb3IgdHJlYXRlZCBhcyBtaXNzaW5nIHZhbHVlcy4gV2UgbmVlZCB0byBiZSBjYXJlZnVsIHRvIGlnbm9yZSBtaXNzaW5nIHZhbHVlcyBpbiB0aGUgY2FsY3VsYXRpb24gb2YgdGhlIG1lYW4gYW5kIHN0YW5kYXJkIGRldmlhdGlvbiBhcyB0aG9zZSBoYXZlIG5vdCB5ZXQgYmVlbiBkZWFsdCB3aXRoLgoKYGBge3J9CiMjIGNvcHkgZGF0YSBzbyB3ZSBkbyBub3QgcG9sbHV0ZSB0aGUgb3JpZ2luYWwgZGF0YQpkZiA8LSBpcmlzCgojIyBjYWxjdWxhdGUgbWVhbiBhbmQgc2QgZm9yIGVhY2ggbnVtZXJpYyBjb2x1bW4KbnVtZXJpYy5jb2xzIDwtIHNhcHBseShkZiwgaXMubnVtZXJpYykKCmZvciAoYyBpbiAxOmxlbmd0aChudW1lcmljLmNvbHMpKSB7CiAgaWYgKG51bWVyaWMuY29sc1tjXSA9PSBUUlVFKSB7CiAgICAjIyBmaW5kIG91dGxpZXJzIGluIG51bWVyaWMgY29sdW1uCiAgICBtIDwtIG1lYW4oZGZbLGNdLCBuYS5ybSA9IFQpCiAgICBzIDwtIHNkKGRmWyxjXSwgbmEucm0gPSBUKQogICAgCiAgICBvdXRsaWVycyA8LSB3aGljaChhYnMoKG0gLSBkZlssY10pIC8gcykgPiAzLjApCiAgICAKICAgIGlmIChsZW5ndGgob3V0bGllcnMpID4gMCkgewogICAgICAjIyBmb3VuZCBvdXRsaWVyczsgcmVwbGFjZSB3aXRoIE5BIGFuZCBpbXB1dGUgbGF0ZXIKICAgICAgY2F0KCJGb3VuZCBvdXRsaWVycyBpbiBjb2x1bW4gJyIsIG5hbWVzKGRmKVtjXSwgIic6IFxuIikKICAgICAgY2F0KCIgICAtLT4gIiwgZGZbb3V0bGllcnMsY10sICJcblxuIikKICAgIH0KICB9Cn0KYGBgCgpUaGVyZSBpcyBvbmUgb3V0bGllciwgYnV0IGl0IGlzIG5vdCB0b28gZmFyIGZyb20gdGhlIG1lYW4sIHNvIHdlIHdpbGwgc2ltcGx5IGlnbm9yZSBpdC4KCkxlc3NvbiBbMy4yMDMgLS0gRGV0ZWN0aW5nIGFuZCBNYW5hZ2luZyBPdXRsaWVyc10oaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy8wMy5tbC9sLTMtMjAzLW91dGxpZXJzL2wtMy0yMDMuaHRtbCkgcHJvdmlkZXMgYSBtb3JlIGRldGFpbGVkIHRyZWF0bWVudCBvZiB0aGlzIHN1YmplY3QuCgojIyMjIEI6IE1hbmFnZSBNaXNzaW5nIFZhbHVlcwoKTWlzc2luZyB2YWx1ZXMgbXVzdCBiZSBhZGRyZXNzZWQgZWl0aGVyIGJ5IGVsaW1pbmF0aW5nIGFueSBvYnNlcnZhdGlvbiAocm93KSB0aGF0IGhhcyBhIG1pc3NpbmcgdmFsdWUgaW4gYW55IG9mIGl0cyBmZWF0dXJlcyBvciBieSBpbXB1dGluZyB0aGUgbWlzc2luZyB2YWx1ZSB3aXRoIGFuIGVzdGltYXRlLiBGb3IgbnVtZXJpYyBmZWF0dXJlcywgYSBjb21tb24gc3RyYXRlZ3kgaXMgdG8gaW1wdXRlIHRoZSBtaXNzaW5nIHZhbHVlIHdpdGggdGhlIG1lYW4gb3IgbWVkaWFuLCB3aGlsZSBmb3IgY2F0ZWdvcmljYWwgZmVhdHVyZXMsIHRoZSBtb2RlIGlzIG9mdGVuIHVzZWQuCgpUaGUgZmlyc3Qgc3RlcCBpcyB0byBjaGVjayBlYWNoIGZlYXR1cmUgZm9yIG1pc3NpbmcgdmFsdWVzLiBXaGlsZSB3ZSBjYW4gY2VydGFpbmx5IGNoZWNrIGVhY2ggZmVhdHVyZSBtYW51YWxseSwgaXRlcmF0aW5nIG92ZXIgYWxsIGNvbHVtbnMgaXMgbW9yZSBwcmFjdGljYWwuIEZvciBtaXNzaW5nIG51bWVyaWMgdmFsdWVzIHdlIGNhbiBjaGVjayBmb3IgKk5BKiB3aXRoIHRoZSBmdW5jdGlvbiBgaXMubmEoKWAsIHdoaWxlIGZvciBjaGFyYWN0ZXIgKHRleHQpIGZlYXR1cmVzIHdlIG5lZWQgdG8gY2hlY2sgd2hldGhlciBpdCBpcyAiIi4KCmBgYHtyIGNoZWNrTWlzc2luZ1ZhbHVlc30KbnVtLlJvd3MgPC0gbnJvdyhkZikKbnVtLkNvbHMgPC0gbmNvbChkZikKCmZvdW5kIDwtIEYKCmZvciAoYyBpbiAxOm51bS5Db2xzKXsKICBtaXNzaW5nLlZhbHVlcyA8LSB3aGljaChpcy5uYShkZlssY10pIHwgZGZbLGNdID09ICIiKQogIG51bS5NaXNzaW5nLlZhbHVlcyA8LSBsZW5ndGgobWlzc2luZy5WYWx1ZXMpCiAgaWYgKG51bS5NaXNzaW5nLlZhbHVlcyA+IDApIHsKICAgIHByaW50KHBhc3RlMCgiQ29sdW1uICciLCBuYW1lcyhkZilbY10sICInIGhhcyAiLCBudW0uTWlzc2luZy5WYWx1ZXMsICIgbWlzc2luZyB2YWx1ZXMiKSkKICAgIGZvdW5kIDwtIFQKICB9Cn0KCmlmICghZm91bmQpIHsKICBwcmludCgibm8gbWlzc2luZyB2YWx1ZXMgZGV0ZWN0ZWQiKQp9CmBgYAoKVGhlcmUgYXJlIG5vIG1pc3NpbmcgdmFsdWVzIGluIHRoaXMgZGF0YXNldC4gSWYgeW91IHdhbnQgdG8ga25vdyBtb3JlIGFib3V0IGhvdyB0byBkZWFsIHdpdGggbWlzc2luZyB2YWx1ZXMsIHN0dWR5IFtMZXNzb24gMy4yMDQgLS0gTWFuYWdpbmcgTWlzc2luZyBWYWx1ZXMgaW4gRGF0YV0oaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy8wMy5tbC9sLTMtMjA0LW1pc3NpbmctdmFsdWVzL2wtMy0yMDQuaHRtbCkuCgojIyMjIEM6IEVuY29kZSBDYXRlZ29yaWNhbCBGZWF0dXJlcwoKU28gZmFyLCB3ZSBoYXZlIHByaW1hcmlseSBkZWFsdCB3aXRoIHRoZSBudW1lcmljIGZlYXR1cmVzLiBMZXQncyB0dXJuIG91ciBhdHRlbnRpb24gdG8gdGhlIGNhdGVnb3JpY2FsIGZlYXR1cmVzLiBCaW5hcnkgY2F0ZWdvcmljYWwgZmVhdHVyZXMgKHdoZXJlIHRoZXJlIGFyZSBvbmx5IHR3byBwb3NzaWJsZSB2YWx1ZXMpIHNob3VsZCBiZSB0dXJuZWQgaW50byBhIDAvMSBlbmNvZGluZy4gTXVsdGktbGV2ZWwgY2F0ZWdvcmljYWwgZmVhdHVyZXMgYXJlIG9mdGVuIGVuY29kZWQgd2l0aCBvbmUtaG90IGR1bW15IGNvZGVzLCB0aGVpciBmcmVxdWVuY3ksIG9yIG9uZSBvZiBzZXZlcmFsIG90aGVyIG1ldGhvZHMuIEF0IHRoZSBlbmQgb2YgdGhlIGRheSwgd2UgbXVzdCBjb252ZXJ0IGNhdGVnb3JpY2FsIGZlYXR1cmVzIHRvIG51bWVyaWMgZmVhdHVyZXMuIExlc3NvbiBbMy4yMDcgLSBFbmNvZGluZyBDYXRlZ29yaWNhbCBGZWF0dXJlc10oaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy8wMy5tbC9sLTMtMjA3LWNhdGVnb3JpY2FsLWVuY29kaW5nL2wtMy0yMDcuaHRtbCkgcHJvdmlkZXMgYSBtb3JlIGRldGFpbGVkIHRyZWF0bWVudCBvZiB0aGlzIHN1YmplY3QuCgpUaGVyZSBhcmUgbm8gY2F0ZWdvcmljYWwgZmVhdHVyZXMgaW4gdGhpcyBkYXRhc2V0LCBzbyB3ZSBjYW4gbW92ZSBvbiB0byB0aGUgbmV4dCBzdGVwLgoKIyMjIyBEOiBTY2FsZSBOdW1lcmljIEZlYXR1cmVzCgpOdW1lcmljIGZlYXR1cmVzIG11c3QgYmUgc2NhbGVkIGZvciAqa05OKiB0byBwZXJmb3JtIHdlbGwuIFdlIHdpbGwgdXNlICp6LXNjb3JlIHN0YW5kYXJkaXphdGlvbiogYXMgb3VyIG1ldGhvZCBmb3Igc2NhbGluZyBhbmQgdXNlIHRoZSBgc2NhbGUoKWAgZnVuY3Rpb24gZnJvbSBCYXNlIFIgdG8gZG8gc286CgpgYGB7cn0KIyBzdGFuZGFyZGl6ZSB0aGUgbnVtZXJpYyBmZWF0dXJlcyB1c2luZyBzY2FsZSgpCmlyaXMuc2NhbGVkIDwtIGFzLmRhdGEuZnJhbWUoc2NhbGUoaXJpc1ssIDE6NF0pKQoKIyBhZGQgdGhlIHNwZWNpZXMgaW50byB0aGUgc2NhbGVkIGRhdGFmcmFtZQppcmlzLnNjYWxlZCRTcGVjaWVzIDwtIGlyaXMkU3BlY2llcwoKIyB2aWV3IHN0YW5kYXJkaXplZCBkYXRhCmhlYWQoaXJpcy5zY2FsZWQsNCkKYGBgCgpHb2luZyBmb3J3YXJkLCB3ZSB3aWxsIHVzZSB0aGUgc2NhbGVkIGRhdGFzZXQgaW4gYGlyaXMuc2NhbGVkYCByYXRoZXIgdGhhbiB0aGUgb3JpZ2luYWwgdW5zY2FsZWQgZGF0YXNldC4KCiMjIyA0OiBTcGxpdCBEYXRhCgpUbyBldmFsdWF0ZSBvdXIgbW9kZWwsIHdlIHNwbGl0IHRoZSBkYXRhIGludG8gYSAqdHJhaW5pbmcgc2V0ICg4MCUpKiBhbmQgYSAqdGVzdCBzZXQgKDIwJSkqLiBUaGUgc2VsZWN0aW9uIG9mIHRoZSBwcm9wb3J0aW9uIHRvIGhvbGQgYmFjayBmb3IgdGVzdGluZyBpcyBnZW5lcmFsbHkgMjAlLCBidXQgZm9yIHNtYWxsZXIgZGF0YXNldCB3ZSBtYXkgY2hvb3NlIDEwJSBvciAxNSUuIFdoZW4gdGhlIHRhcmdldCBjbGFzc2VzIGFyZSBub3QgYWxsIG9jY3VycmluZyBpbiB0aGUgc2FtZSBwcm9wb3J0aW9uLCB0aGVuIHdlIG1heSBlbmQgdXAgd2l0aCBjbGFzcyBpbWJhbGFuY2UsICppLmUuKiwgaW4gb3VyIGNhc2UsIGEgaXJpcyBzcGVjaWVzIG1heSBhcHBlYXJzIGluIHRoZSB0ZXN0aW5nIGRhdGEgYnV0IG5vdCBpbiB0aGUgdHJhaW5pbmcgZGF0YSBhcyB0aGVyZSBhcmUgdG9vIGZldyBvY2N1cnJlbmNlcy4KCldoZW4gd2UgaGF2ZSBjbGFzcyBpbWJhbGFuY2Ugd2UgbmVlZCB0byBzZWxlY3QgdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgZGlmZmVyZW50bHksIHVzaW5nIG1ldGhvZHMgc3VjaCBhcyBzdHJhdGlmaWVkIHNhbXBsaW5nIG9yIFNNT1RFLiBMZXNzb24gWzMuMjI0IC0gTWFuYWdpbmcgQ2xhc3MgSW1iYWxhbmNlXShodHRwOi8vYXJ0aWZpY2l1bS51cy9sZXNzb25zLzAzLm1sL2wtMy0yMjQtbWFuYWdpbmctY2xhc3MtaW1iYWxhbmNlL2wtMy0yMjQuaHRtbCkgcHJvdmlkZXMgYW4gaW50cm9kdWN0aW9uIHRvIHRoZXNlIG1ldGhvZHMuCgpOb3csICprTk4qIGludm9sdmVzIHR3byBlbGVtZW50cyBvZiAicmFuZG9tbmVzcyI6IHRoZSBzZWxlY3Rpb24gb2YgdGhlIHN1YnNldCBzYW1wbGUgZm9yIHRyYWluaW5nIGFuZCBkZWFsaW5nIHdpdGggdGllcyBkdXJpbmcgcHJlZGljdGlvbi4gU28sIGlmIHdlIHVzZSByYW5kb20gbnVtYmVycyB0aGVuIGV2ZXJ5IHRpbWUgd2UgcnVuIHRoZSBjb2RlIHdlIG1heSBnZXQgZGlmZmVyZW50IHJlc3VsdHMuIFRvIGF2b2lkIHRoaXMsIHdlIG5lZWQgdG8gc2V0IGEgInNlZWQiIGZvciB0aGUgcmFuZG9tIG51bWJlciBnZW5lcmF0b3Igc28gaXQgZ2VuZXJhdGVzIHRoZSBzYW1lIHJhbmRvbSBudW1iZXJzIGV2ZXJ5IHRpbWUuIEFuZCwgbmF0dXJhbGx5LCB3ZSBjaG9vc2UgNDIgYXMgdGhlIHNlZWQgYXMgdGhhdCBpcyB0aGUgYW5zd2VyIHRvIHRoZSB1bml2ZXJzZVteMV0uCgpbXjFdOiBUaGUgbnVtYmVyIDQyIGhvbGRzIGEgc3BlY2lhbCBwbGFjZSBpbiBjb21wdXRlciBzY2llbmNlIGFuZCBnZWVrIGN1bHR1cmUgcHJpbWFyaWx5IGR1ZSB0byBpdHMgcmVmZXJlbmNlIGluIERvdWdsYXMgQWRhbXMnIG5vdmVsICpUaGUgSGl0Y2hoaWtlcuKAmXMgR3VpZGUgdG8gdGhlIEdhbGF4eSouIEluIHRoZSBib29rLCBhIGdyb3VwIG9mIGh5cGVyLWludGVsbGlnZW50IGJlaW5ncyBidWlsZHMgYSBzdXBlcmNvbXB1dGVyIG5hbWVkICpEZWVwIFRob3VnaHQqIHRvIGZpbmQgdGhlICJBbnN3ZXIgdG8gdGhlIFVsdGltYXRlIFF1ZXN0aW9uIG9mIExpZmUsIHRoZSBVbml2ZXJzZSwgYW5kIEV2ZXJ5dGhpbmcuIiBBZnRlciBzZXZlbiBhbmQgYSBoYWxmIG1pbGxpb24geWVhcnMgb2YgY29tcHV0YXRpb24sIHRoZSBtYWNoaW5lIGZpbmFsbHkgcmV2ZWFscyB0aGUgYW5zd2VyOiAiNDIuIiBUaGUgcHJvYmxlbSwgaG93ZXZlciwgaXMgdGhhdCBub2JvZHkga25vd3MgYW55bW9yZSB3aGF0IHRoZSBhY3R1YWwgcXVlc3Rpb24gd2FzLgoKYGBge3J9CiMgU2V0IHNlZWQgZm9yIHJlcHJvZHVjaWJpbGl0eQpzZXQuc2VlZCg0MikKYGBgCgpTbywgbm93IHdlIGNhbiBzcGxpdCB0aGUgZGF0YSBieSBzZWxlY3RpbmcgYSByYW5kb20gc3Vic2V0IGZvciB0cmFpbmluZyAodG8gbWFrZSBwcmVkaWN0aW9ucykgYW5kIHRoZSBoZWxkLWJhY2sgZGF0YSB0byBldmFsdWF0ZSB0aGUgYWNjdXJhY3kgYW5kIHBlcmZvcm1hbmNlIG9mIHRoZSBtb2RlbC4gV2hpbGUgdGhlcmUgYXJlIG1hbnkgd2F5cyB0byBkbyB0aGlzLCB3ZSBhcmUgdXNpbmcgdGhlIGBjcmVhdGVEYXRhUGFydGl0aW9uKClgIGZ1bmN0aW9uIGZyb20gdGhlICoqY2FyZXQqKiBwYWNrYWdlLiBUaGlzIGZ1bmN0aW9uIGlzIGNvbW1vbmx5IHVzZWQgZm9yIHNwbGl0dGluZyBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHRlc3Qgc2V0cyAod2l0aG91dCByZXBsYWNlbWVudCkgd2hpbGUgbWFpbnRhaW5pbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgdGFyZ2V0IHZhcmlhYmxlIChzdHJhdGlmaWVkIHNhbXBsaW5nKS4gSXQgaXMgcGFydGljdWxhcmx5IHVzZWZ1bCBmb3IgY2xhc3NpZmljYXRpb24gdGFza3MgdG8gZW5zdXJlIHRoYXQgYm90aCBzZXRzIGNvbnRhaW4gcmVwcmVzZW50YXRpdmUgcHJvcG9ydGlvbnMgb2YgZWFjaCBjbGFzcy4KCmBgYHtyfQojIFNwbGl0IHRoZSBkYXRhc2V0IGludG8gdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0cwp0cmFpbkluZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oaXJpcy5zY2FsZWQkU3BlY2llcywgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQp0cmFpbkRhdGEgPC0gaXJpcy5zY2FsZWRbdHJhaW5JbmRleCwgXQp0ZXN0RGF0YSA8LSBpcmlzLnNjYWxlZFstdHJhaW5JbmRleCwgXQpgYGAKCkp1c3QgYSBxdWljayBjaGVjayBvZiB0aGUgZGltZW5zaW9ucyB0byBiZSBzdXJlIGl0IGFsbCB3ZW50IHdlbGwuCgpgYGB7cn0KIyBDaGVjayB0aGUgZGltZW5zaW9ucwpkaW0odHJhaW5EYXRhKQpkaW0odGVzdERhdGEpCmBgYAoKQXQgdGhpcyBwb2ludCwgdGhlIHRyYWluaW5nIHNldCBjb250YWlucyBhcHByb3hpbWF0ZWx5IDEyMCBvYnNlcnZhdGlvbnMsIHdoaWxlIHRoZSB0ZXN0IHNldCBjb250YWlucyAzMC4KCiMjIyA1OiBUcmFpbiBNb2RlbAoKU2luY2UgKmtOTiogZG9lcyBub3QgcmVxdWlyZSBleHBsaWNpdCB0cmFpbmluZyBsaWtlIHJlZ3Jlc3Npb24sIHdlIHNpbXBseSBwcmVwYXJlIHRoZSBmZWF0dXJlcyBhbmQgdXNlIHRoZSBga25uKClgIGZ1bmN0aW9uIGZyb20gdGhlICoqY2xhc3MqKiBwYWNrYWdlLiBUaGUgZnVuY3Rpb24gcmVxdWlyZXMgYSB2ZWN0b3Igb2YgdGhlIGtub3duIHRhcmdldCB2YXJpYWJsZXMgKHRoaXMgaXMgdGhlIGtleSB0byAic3VwZXJ2aXNlZCBtYWNoaW5lIGxlYXJuaW5nIikgdG8gYmUgc2VwYXJhdGUgZnJvbSB0aGUgcHJlZGljdG9yIGZlYXR1cmVzLgoKYGBge3J9CiMgRXh0cmFjdCBmZWF0dXJlcyAocHJlZGljdG9ycykgYW5kIHRhcmdldCB2YXJpYWJsZQp0cmFpblggPC0gdHJhaW5EYXRhWywgLTVdICAgICAgICMgZXh0cmFjdCB0aGUgU3BlY2llcyBjb2x1bW4KdHJhaW5ZIDwtIHRyYWluRGF0YSRTcGVjaWVzICAgICAjIHByZWRpY3RvciBmZWF0dXJlcwoKdGVzdFggPC0gdGVzdERhdGFbLCAtNV0gICAgICAgICAjIGV4dHJhY3QgdGhlIFNwZWNpZXMgY29sdW1uCnRlc3RZIDwtIHRlc3REYXRhJFNwZWNpZXMgICAgICAgIyBwcmVkaWN0b3IgZmVhdHVyZXMKCiMgQ2hvb3NlIGsgKGUuZy4sIGsgPSA1KQprX3ZhbHVlIDwtIDUKCiMgUGVyZm9ybSBfa05OXyBjbGFzc2lmaWNhdGlvbiBvbiB0aGUgdGVzdCBkYXRhIHVzaW5nIHRoZSB0cmFpbmluZyBkYXRhCnByZWRpY3Rpb25zIDwtIGNsYXNzOjprbm4odHJhaW4gPSB0cmFpblgsIHRlc3QgPSB0ZXN0WCwgY2wgPSB0cmFpblksIGsgPSBrX3ZhbHVlKQpgYGAKClRoZSBwcmVkaWN0aW9ucyBmb3IgdGhlIG9ic2VydmF0aW9ucyBpbiB0aGUgdGVzdGluZyBkYXRhIHVzaW5nIHRoZSB0cmFpbmluZyBhcmUgc3RvcmVkIGluIHRoZSB2YXJpYWJsZSBgcHJlZGljdGlvbnNgLgoKVGhlIGNob2ljZSBvZiAqayosICppLmUuKiwgaG93IG1hbnkgb2YgdGhlICJjbG9zZXN0IiBuZWlnaGJvcnMgYXJlIGNvbnNpZGVyZWQgd2hlbiBkZXRlcm1pbmluZyB0aGUgcHJlZGljdGluZyBjbGFzcywgaXMgYXJiaXRyYXJpbHkgc2V0IGF0IDUgaW4gdGhpcyBleGFtcGxlLiBPZiBjb3Vyc2UsIHdlIG5lZWQgdG8gZmluZCB0aGUgIm9wdGltYWwiICprKiB0byBnZXQgdGhlICJiZXN0IiBwcmVkaWN0aW9ucyBieSB0cnlpbmcgdmFyaW91cyAqayogb24gdGhlIHRlc3RpbmcgZGF0YSBhbmQgb24gZGlmZmVyZW50IHRyYWluaW5nIHNhbXBsZXMuIFRoZXJlIGFyZSBtZXRob2RzIGZvciB0aGlzLCBzdWNoIGFzIGstZm9sZCBjcm9zcyB2YWxpZGF0aW9uIG9yIGJhZ2dpbmcsIGJ1dCB0aG9zZSBhcmUgYmV5b25kIHRoZSBzY29wZSBvZiB0aGlzIHByaW1lci4gSW4gcHJhY3RpY2UsIGl0IGhhcyBiZWVuIGZvdW5kIHRocm91Z2ggZW1waXJpY2FsIHN0dWRpZXMgdGhhdCBhICprKiBlcXVhbCB0byB0aGUgc3F1YXJlIHJvb3Qgb2YgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgaW4gdGhlIHRyYWluaW5nIGRhdGEgaXMgZ2VuZXJhbGx5IGJlc3QuCgojIyMgNjogRXZhbHVhdGUgTW9kZWwKClRvIG1lYXN1cmUgaG93IHdlbGwgb3VyIG1vZGVsIHBlcmZvcm1zLCB3ZSBjb21wdXRlIHRoZSBhY2N1cmFjeSBieSBjb21wYXJpbmcgdGhlIHByZWRpY3RlZCBhbmQgYWN0dWFsIHZhbHVlcy4gQWNjdXJhY3kgbWVhc3VyZSB0aGUgcGVyY2VudGFnZSBvZiBjb3JyZWN0IHByZWRpY3Rpb25zLCAqaS5lLiosIGluIG91ciBjYXNlIHRoZSBudW1iZXIgb2YgdGltZXMgdGhhdCB0aGUgYWxnb3JpdGhtIGNvcnJlY3RseSBwcmVkaWN0ZWQgdGhlIGlyaXMgc3BlY2llcyBmb3IgdGhlIHRlc3QgZGF0YS4KCmBgYHtyfQojIENhbGN1bGF0ZSBhY2N1cmFjeQphY2N1cmFjeSA8LSBzdW0ocHJlZGljdGlvbnMgPT0gdGVzdFkpIC8gbGVuZ3RoKHRlc3RZKQpgYGAKClRoaXMgcmVzdWx0cyBpbiBhIG1vZGVsIEFjY3VyYWN5IG9mIGByIHJvdW5kKGFjY3VyYWN5ICogMTAwLCAyKWAlLiBOYXR1cmFsbHksIHdlIG11c3QgaGF2ZSBhbiBhY2N1cmFjeSBvZiBtb3JlIHRoYW4gNTAlIG90aGVyd2lzZSByYW5kb20gZ3Vlc3NpbmcgaXMganVzdCBhcyBlZmZlY3RpdmUuCgojIyMgNzogTWFrZSBQcmVkaWN0aW9uCgpUbyBtYWtlIGEgcHJlZGljdGlvbiBmb3IgYSBuZXcgdW5zZWVuIGluc3RhbmNlLCB3ZSBjcmVhdGUgYSBuZXcgZGF0YSBmcmFtZSB3aXRoIGEgc2FtcGxlIG9ic2VydmF0aW9uLgoKTGV0J3MgYXNzdW1lIHdlIGhhdmUgYmVlbiBwcm92aWRlZCB3aXRoIHRoZSBkYXRhIGZvciBhbiBpcmlzIGZsb3dlciB3aXRoIHRoZSBmb2xsb3dpbmcgbWVhc3VyZW1lbnRzIGFuZCB3ZSB3YW50IHRvIGRldGVybWluZSB0byB3aGljaCBzcGVjaWVzIGl0IGxpa2VseSBiZWxvbmdzOgoKLSAgIFNlcGFsIExlbmd0aDogNS44Ci0gICBTZXBhbCBXaWR0aDogMy4wCi0gICBQZXRhbCBMZW5ndGg6IDQuMgotICAgUGV0YWwgV2lkdGg6IDEuMgoKV2UgY29uc3RydWN0IHRoaXMgY2FzZSBhcyBhIGRhdGEgZnJhbWUgYW5kIHBhc3MgaXQgdG8gdGhlICJ0cmFpbmVkIiAqa05OKiBtb2RlbCB1c2luZyBhICprKiBvZiA1LgoKYGBge3J9CiMgY3JlYXRlIGRhdGFmcmFtZSBjb250YWluaW5nIG5ldyBvYnNlcnZhdGlvbnMgdG8gYmUgY2xhc3NpZmllZApuZXcuY2FzZSA8LSBkYXRhLmZyYW1lKFNlcGFsLkxlbmd0aCA9IDUuOCwgU2VwYWwuV2lkdGggPSAzLjAsIAogICAgICAgICAgICAgICAgICAgICAgIFBldGFsLkxlbmd0aCA9IDQuMiwgUGV0YWwuV2lkdGggPSAxLjIpCgojIHByZWRpY3Qgc3BlY2llcyBmb3IgdGhlIG5ldyBjYXNlIHVzaW5nIGtOTiB3aXRoIHRoZSB0cmFpbmluZyBkYXRhCnByZWRpY3RlZC5zcGVjaWVzIDwtIGtubih0cmFpbiA9IHRyYWluWCwgdGVzdCA9IG5ldy5jYXNlLCBjbCA9IHRyYWluWSwgayA9IDUpCmBgYAoKTm90ZSB0aGF0IHRoZSBwcmVkaWN0aW9uIHJlc3VsdCBpcyBhIHZhcmlhYmxlIG9mIHR5cGUgImZhY3RvciIgd2hpY2ggY2FuIGJlIGNvbnZlcnRlZCB0byBhIGNoYXJhY3RlciBzdHJpbmcgYXMgZm9sbG93czoKCmBgYHtyfQpzcGVjaWVzIDwtIGFzLmNoYXJhY3RlcihwcmVkaWN0ZWQuc3BlY2llcykKYGBgCgojIyMgODogUmVwb3J0IFJlc3VsdAoKKmtOTiogcHJlZGljdHMgKGJhc2VkIG9uIGZpbmRpbmcgdGhlIDUgY2xvc2VzdCBuZWlnaGJvcnMgYW5kIHNlZWluZyB3aGljaCBzcGVjaWVzIHRoZXkgYmVsb25nIHRvKSB0aGF0IHRoZSBuZXcgY2FzZSBpcyBtb3N0IGxpa2VseSBvZiB0aGUgJ2ByIHNwZWNpZXNgJyBzcGVjaWVzLiBCYXNlZCBvbiBvdXIgbWVhc3VyZWQgYWNjdXJhY3ksIHdlIGNhbiBzYXkgdGhhdCB0aGlzIHByZWRpY3Rpb24gaXMgY29ycmVjdCB3aXRoIGEgcHJvYmFiaWxpdHkgb2YgYHIgcm91bmQoYWNjdXJhY3ksIDIpYC4KCiMjIFN1bW1hcnkKClRoZSBrLU5lYXJlc3QgTmVpZ2hib3JzICgqa05OKikgYWxnb3JpdGhtIGlzIGEgdmVyc2F0aWxlIGFuZCBpbnR1aXRpdmUgbWV0aG9kIGZvciBib3RoIGNsYXNzaWZpY2F0aW9uIGFuZCByZWdyZXNzaW9uIHRhc2tzLiBVbmxpa2Ugb3RoZXIgbWFjaGluZSBsZWFybmluZyBtb2RlbHMsICprTk4qIGRvZXMgbm90IGV4cGxpY2l0bHkgbGVhcm4gYSBmdW5jdGlvbjsgaW5zdGVhZCwgaXQgbWVtb3JpemVzIHRoZSB0cmFpbmluZyBkYXRhIGFuZCBtYWtlcyBwcmVkaWN0aW9ucyBkeW5hbWljYWxseSBiYXNlZCBvbiBzaW1pbGFyaXR5IG1lYXN1cmVzLiBDaG9vc2luZyB0aGUgcmlnaHQgdmFsdWUgb2YgKiprKiogaXMgaW1wb3J0YW50LCBhcyBzbWFsbCB2YWx1ZXMgbGVhZCB0byBoaWdoIHZhcmlhbmNlLCB3aGlsZSBsYXJnZSB2YWx1ZXMgaW5jcmVhc2UgYmlhcy4KCkJ5IGltcGxlbWVudGluZyAqa05OKiBpbiBSLCB3ZSBpbGx1c3RyYXRlZCBob3cgdG8gc3BsaXQgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IHNldHMsIHBlcmZvcm0gY2xhc3NpZmljYXRpb24gYW5kIHJlZ3Jlc3Npb24sIGFuZCBldmFsdWF0ZSBtb2RlbCBwZXJmb3JtYW5jZS4KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMgRmlsZXMgJiBSZXNvdXJjZXMKCmBgYHtyIHppcEZpbGVzLCBlY2hvPUZBTFNFfQp6aXBOYW1lID0gc3ByaW50ZigiTGVzc29uRmlsZXMtJXMtJXMuemlwIiwgCiAgICAgICAgICAgICAgICAgcGFyYW1zJGNhdGVnb3J5LAogICAgICAgICAgICAgICAgIHBhcmFtcyRudW1iZXIpCgp0ZXh0QUxpbmsgPSBwYXN0ZTAoIkFsbCBGaWxlcyBmb3IgTGVzc29uICIsIAogICAgICAgICAgICAgICBwYXJhbXMkY2F0ZWdvcnksIi4iLHBhcmFtcyRudW1iZXIpCgojIGRvd25sb2FkRmlsZXNMaW5rKCkgaXMgaW5jbHVkZWQgZnJvbSBfaW5zZXJ0MkRCLlIKa25pdHI6OnJhd19odG1sKGRvd25sb2FkRmlsZXNMaW5rKCIuIiwgemlwTmFtZSwgdGV4dEFMaW5rKSkKYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIFNlZSBBbHNvCgotICAgWzMuMTAxIC0tIE92ZXJ2aWV3IG9mIHRoZSBNYWNoaW5lIExlYXJuaW5nIFBpcGVsaW5lXShodHRwOi8vYXJ0aWZpY2l1bS51cy9sZXNzb25zLzAzLm1sL2wtMy0xMDEtaW50cm8tbWwvbC0zLTEwMS5odG1sKQotICAgWzMuMjA4IC0tIERpc3RhbmNlIE1lYXN1cmVzXShodHRwOi8vYXJ0aWZpY2l1bS51cy9sZXNzb25zLzAzLm1sL2wtMy0yMDgtZGlzdGFuY2UtbWVhc3VyZXMvbC0zLTIwOC5odG1sKQotICAgWzMuMjAzIC0tIERldGVjdGluZyBhbmQgTWFuYWdpbmcgT3V0bGllcnNdKGh0dHA6Ly9hcnRpZmljaXVtLnVzL2xlc3NvbnMvMDMubWwvbC0zLTIwMy1vdXRsaWVycy9sLTMtMjAzLmh0bWwpCi0gICBbMy4yMjQgLS0gTWFuYWdpbmcgQ2xhc3MgSW1iYWxhbmNlXShodHRwOi8vYXJ0aWZpY2l1bS51cy9sZXNzb25zLzAzLm1sL2wtMy0yMjQtbWFuYWdpbmctY2xhc3MtaW1iYWxhbmNlL2wtMy0yMjQuaHRtbCkKLSAgIFszLjIwMyAtLSBEZXRlY3RpbmcgYW5kIE1hbmFnaW5nIE91dGxpZXJzXShodHRwOi8vYXJ0aWZpY2l1bS51cy9sZXNzb25zLzAzLm1sL2wtMy0yMDMtb3V0bGllcnMvbC0zLTIwMy5odG1sKQotICAgWzMuMjA3IC0gRW5jb2RpbmcgQ2F0ZWdvcmljYWwgRmVhdHVyZXNdKGh0dHA6Ly9hcnRpZmljaXVtLnVzL2xlc3NvbnMvMDMubWwvbC0zLTIwNy1jYXRlZ29yaWNhbC1lbmNvZGluZy9sLTMtMjA3Lmh0bWwpCi0gICBbMy40MTEgLS0gU2ltcGxlIEltcGxlbWVudGF0aW9uIG9mICprTk4qIGluIFJdKGh0dHA6Ly9hcnRpZmljaXVtLnVzL2xlc3NvbnMvMDMubWwvbC0zLTQxMS1fa05OXy1pbXBsZW1lbnRhdGlvbi1yL2wtMy00MTEuaHRtbCkKCiMjIFJlZmVyZW5jZXMKCk5vIHJlZmVyZW5jZXMuCgojIyBFcnJhdGEKCk5vbmUgY29sbGVjdGVkIHlldC4gW0xldCB1cyBrbm93XShodHRwczovL2Zvcm0uam90Zm9ybS5jb20vMjEyMTg3MDcyNzg0MTU3KXt0YXJnZXQ9Il9ibGFuayJ9Lgo=