Cluster Analysis
A cluster is a subset of a data set that consists of observations which, using a chosen metric, are similar to each other, and which are also dissimilar to other observations, via the same metric.
Cluster Analysis is the method of grouping data into clusters, using a clustering technique. There is a large variety of clustering techniques, and each of these techniques uses a different metric for determining the similarity between observations. Don’t worry, we won’t go into all the complicated mathematics involved in these techniques - our focus is on learning how to conduct cluster analyses in R.
Purpose
Clustering is often used as part of an exploratory data analysis procedure, to uncover hidden patterns in a new data set.
The main purpose of clustering is to:
- Analyse the data structure
- Relate the different elements of the data to each other, and then
- Aid in classifying the data into certain classes
It is worth noting that in general, we may not be aware of what these classes are, prior to the clustering analysis.
Clustering Techniques
In this computer lab, we will apply the following popular clustering techniques:
- k-means Clustering
- PAM Clustering
- Fuzzy Clustering
- Hierarchical Clustering
We will provide brief explanations of each of these techniques as we work through this computer lab.
Preparations
In R, we can use the inbuilt cluster
package to carry out a range of cluster analyses. However, we will also require a few additional packages. Please run the R code below to install and load these packages.
install.packages(c("factoextra", "ggfortify", "palmerpenguins"))
library(cluster)
library(factoextra)
library(ggfortify)
library(palmerpenguins)
Note: If you are in the Data Science module, you should already have the palmerpenguins
package installed.
Penguin Data
The penguins
data set in the palmerpenguins
R package (Horst, Hill, and Gorman 2020) contains data on 3 species of penguin (Adelie, Chinstrap and Gentoo) living on islands in the Palmer archipelago, off the coast of Antarctica. We will use this data for our clustering analyses.
This penguin data contains information on variables such as:
- flipper length
- body mass
- bill length
- sex
- species
- island on which the penguin lives
Run the R code below to take a look at the penguins
data set, to familiarise yourself with the different variables.
head(penguins)
plot(penguins)
Note here that plot(penguins)
will produce a matrix of scatter plots.
Each plot shows two of the variables in the penguins
data set plotted against each other.
The variable names are shown on the diagonal boxes - just cross reference a plot to the variable names in that row and column to determine the variables plotted on the y-axis and x-axis respectively.
Hint: For example, the plot shown in the sixth row, fifth column should be body_mass_g
plotted on the y-axis against flipper_length_mm
on the x-axis.
Looking at the scatter plots for the continuous random variables, does it appear that there are any natural clusters that will be easy to identify using a clustering technique?
Preparing data for Cluster Analysis
For the purposes of this computer lab, we will assume that we don’t actually have data on the species of each penguin in the penguins
data set.
Instead, we will conduct cluster analyses using the other information contained in this data set, to try to group the penguins into clusters.
If the cluster technique works well, we could end up with clusters for each penguin species!
However, before we begin our clustering analyses, we will need to ensure that our data is in the appropriate format.
It is important to note that clustering algorithms only work on continuous variables. As we have seen, the penguins
data set contains both continuous and categorical (discrete) variables.
Therefore, for the time being, we will only use a subset of the variables in the penguins
data set, namely bill_length_mm
, bill_depth_mm
, flipper_length_mm
and body_mass_g
.
Run the R code below to remove missing values from our data, and create a subset of the penguins
data that only contains the continuous variables:
penguins <- na.omit(penguins)
penguins_subset <- penguins[, 3:6]
Before conducting a cluster analysis, it is also generally a good idea to normalise our data (this process is like the standardisation process covered in section 4.2 of Topic 3). This will remove the effect of different variables being measured on different scales (e.g. flipper length in mm and body mass in grams), and can prevent any single variable overpowering others when being assessed by the cluster algorithm.
Normalising the data consists of:
- Computing the sample mean and sample standard deviation for each variable
- Subtracting the relevant sample mean from each observation, and dividing by the relevant sample standard deviation
For example, the sample mean for bill_depth_mm
is roughly 17.151, and the sample standard deviation is roughly 1.975. The first penguin in the data set has a recorded bill_depth_mm
value of 18.7. The normalised version of this value would therefore be \[\displaystyle \frac{18.7 - 17.151}{1.975} \approx 0.78.\]
Fortunately, we can conduct this normalisation process with one line of code in R, for all our variables.
Use the scale
function to normalise our penguins_subset
data, and assign the normalised data to the object penguins_scaled
. Then, take a look at your normalised data using the head
function.
Hint: Your output should look like the output in the Code
chunk below:
penguins_scaled <- scale(penguins_subset)
head(penguins_scaled)
## bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
## [1,] -0.8946955 0.7795590 -1.4246077 -0.5676206
## [2,] -0.8215515 0.1194043 -1.0678666 -0.5055254
## [3,] -0.6752636 0.4240910 -0.4257325 -1.1885721
## [4,] -1.3335592 1.0842457 -0.5684290 -0.9401915
## [5,] -0.8581235 1.7444004 -0.7824736 -0.6918109
## [6,] -0.9312674 0.3225288 -1.4246077 -0.7228585
k-means Clustering
Now that our data is suitably prepared, we can begin our cluster analyses.
The first clustering technique we will consider is k-means clustering.
In k-means clustering, we specify an arbitrary number of clusters \(k\), and each data point is assigned to one of these \(k\) clusters, based on the distance between the point and the mean of all points.
Once these initial clusters are created, the mean of each cluster is calculated, and an iterative process begins:
- Points are moved between clusters, one point at a time, depending on how close they are to each cluster mean.
- This process continues, until no point can be moved between clusters without increasing the average distance between the points and the cluster means.
We can use the kmeans
function from the inbuilt cluster
R package to conduct a k-means clustering analysis.
Run the following R code to conduct this analysis on our penguins_scaled
data, with k=3
specified.
kmeans_fit3 <- kmeans(penguins_scaled, 3)
Let’s discuss the results. If we run the line kmeans_fit3
, a list of output appears in R.
We are primarily interested in the cluster membership assigned by the algorithm.
We can extract this cluster membership using kmeans_fit3$cluster
.
It is also important to check the details in the Within cluster sum of squares by cluster
section.
Here, between_SS
denotes the sum of squares between clusters (i.e., how well the clusters are separated from each other),
while total_ss
denotes the total variability in the data.
The calculation between_ss /total_ss
tells us how much of the variability in the data is accounted for by the clusters found by the algorithm.
This value can range from 0% to 100%, with larger values indicating a better result. Here, 72.1% is decent, although we might have expected a slightly higher result.
Visualising our results
We can visualise our results in several ways.
Firstly, run the following code to produce a matrix of scatter plots of the continuous variables, coloured by cluster.
windows() # or quartz() if you are on a Mac
plot(penguins[, c(1,3:6)], col = kmeans_fit3$cluster)
Don’t worry if the plots produced look a bit confusing. To make the plots easier to read, consider maximising the graphics window.
Recall from 2.2 that each scatter plot here shows two of the variables in the penguins
data set plotted against each other. Now however, they are coloured by cluster (as determined by the k-means method).
If, by inspecting these plots, you can clearly distinguish between clusters, then the clustering method has performed well.
Hint: If you are having trouble interpreting the graph, this note may help. For example, the plot shown in the fifth row, fourth column should be body_mass_g
plotted on the y-axis against flipper_length_mm
on the x-axis.
We could also use the R code below to plot the species
variable as a numeric value (Adelie = 1, etc), with the points coloured by cluster:
plot(as.numeric(penguins$species), col = kmeans_fit3$cluster)
Based on this plot, and the plot produced in 3.3, do you think the k-means clustering has performed well?
At this point, it might seem that our job is done. We have results for \(k=3\), and there are three species of penguins.
Remember though, normally we would not actually know what the number of clusters should be. Therefore, we would usually try a range of different k values.
Conduct k-means clustering on our penguins_scaled
data, for \(k\) values of 2, 4 and 5.
Note down the between_ss /total_ss
results. What do you observe?
You might have noticed that the between_ss /total_ss
value increases as we increase the number of clusters. This can happen even if adding another cluster isn’t actually that helpful. Therefore, we should also consider some other assessment methods.
If we did not know the number of clusters, we could use several methods to determine the appropriate value of \(k\).
The factoextra
R package contains a helpful function fviz_nbclust
, which we can use to produce several informative plots.
Take a look at the comments in the Code
chunk below (the ...
signify that these are comments, and should be replaced by actual arguments):
example <- fviz_nbclust(...specify data set here... ,
...specify clustering method here... ,
method = "...specify assessment method here...")
As you can see, the fviz_nbclust
function takes three main arguments:
- The data set,
- The clustering method (i.e. kmeans), and
- The assessment method (one of “wss” , “silhouette” , or “gap_stat”)
The wss
assessment method produces a scree plot, with the number of clusters plotted on the horizontal axis, and the Total Within Sum of Squares (wss) variance plotted on the vertical axis. A lower variance is preferable, but after a certain point, adding more clusters will not lower this variability significantly. When assessing this plot, we select, as our optimal number of clusters, the number of clusters for which adding an extra cluster doesn’t greatly reduce this variability.
Using the fviz_nbclust
function, with the kmeans
clustering method and the "wss"
assessment method specified, determine the optimal number of clusters for our penguins_scaled
data.
Hint: If you are not sure how to proceed, check the Code
chunk below:
kmeans_wss <- fviz_nbclust(penguins_scaled, kmeans, method = "wss")
windows()
kmeans_wss
The silhouette
assessment method measures the similarity of a point to other points in its cluster. The higher this ‘average silhouette width’ , the better.
Using the fviz_nbclust
function, with the kmeans
clustering method and the "silhouette"
assessment method specified, determine the optimal number of clusters for our penguins_scaled
data.
The final k-means clustering assessment method we will consider is the gap_stat
assessment method. This computes a statistic known as the ‘gap statistic.’ We won’t go into the mathematical details of this statistic; suffice to say that higher values are considered preferable.
Using the fviz_nbclust
function, with the kmeans
clustering method and the "gap_stat"
assessment method specified, determine the optimal number of clusters for our penguins_scaled
data.
We can also use the fviz_cluster
function from the factoextra
R package to visualise the clusters.
Run the code below to visualise the three clusters found in 3.1, and then, using this code as a base, visualise the sets of clusters found in 3.5.
fviz_cluster(kmeans_fit3, data = penguins_scaled)
Based on your k-means clustering analyses, which value of \(k\) would you recommend using, and why?
PAM Clustering
k-means clustering is a form of centroid-based clustering, whereby cluster means (i.e. centroids) are used as a metric in the clustering algorithm.
An alternative to k-means clustering is PAM (Partion around Medoids) clustering. This operates in a similar manner to k-means clustering, but uses medoids, (median-like points), rather than centroids, when deciding cluster membership. This makes the PAM clustering technique more robust to outliers.
We can easily conduct PAM clustering using the pam
function from the inbuilt cluster
R package. The process is similar to the k-means clustering process conducted in 3.1.
Use the pam
function to conduct PAM clustering for \(k\) values of 2, 3, 4 and 5.
Next, use the fviz_nbclust
function to determine the best number of PAM clusters to use (use all three methods).
Hint: Use pam
instead of kmeans
for the clustering method specification.
Finally, use the fviz_cluster
function and the code below to visualise the clusters for the PAM clustering result(s) you chose above in 4.2. What do you conclude?
# This code assumes you are assessing a result stored in the object pam_fit3
windows()
plot(as.numeric(penguins$species), col = pam_fit3$clustering)
At this point, we’ve covered the main material for this computer lab. If you would like, you can extend your knowledge on clustering by going through the Extension sections below. Otherwise, if you have time left, you might like to work on your assessments.
Extension 1 - Hierarchical Clustering
Unlike the previous clustering techniques discussed, hierarchical clustering begins by assigning each point to its own cluster. For example, since we have 333 penguins in our filtered data set, we would start with 333 clusters. Then, the two most similar clusters are merged (continuing our eample, this would result in us now having 332 clusters). We repeat this process, until we have just one large cluster (which contains all the points).
As a result, we now have a hierarchy of clusters, which looks a bit like an upside-down tree (with observations from the same cluster on the same ‘branch’).
We can conduct hierarchical clustering using the agnes
function from the cluster
R package. Since we are not specifying the number of clusters, we only need to provide the one argument, i.e. the data set to analyse.
Use the agnes
function to conduct hierarchical clustering on our scaled penguin data.
We can visualise the results of the hierarchical clustering using a dendrogram.
Complete the code below to create a dendrogram for your results (just replace the ...
with your results)
plot(..., which = 2)
There are several options we can use when conducting hierarchical clustering. These relate to the way in which distance is measured between two points, to determine their level of similarity.
The default method in the agnes
function is average
. Try re-running your hierarchical clustering algorithm using some other options, by adding the argument method = "..."
to your code in 5.1, and replacing the "..."
with "single"
, "complete"
and then "ward"
.
When you visualise the results, do the dendrograms look very different?
So far, the dendrograms we have produced looked rather unappealing - we can do better.
It is worth noting that the selection of an appropriate horizontal cut-off point on the dendrogram (thus selecting the number of clusters to use) is arbitrary. Based on your findings from the previous analyses however, you should now have decided on an ‘optimal’ number of \(k\) clusters to select.
We can visualise the dendrogram with the tree cut into these \(k\) clusters using the hcut
and fviz_dend
functions from the factoextra
R package.
Take a look at the code below, and fill in the ...
missing parts with the relevant details. Once you are happy with the code, run it. The resultant dendrogram should look much nicer.
penguin_dendro <- hcut(...,
k = ...,
hc_func = "agnes",
hc_method = "...",
hc_metric = "euclidean")
fviz_dend(penguin_dendro)
Note: You will need to specify the measurement method, e.g. "ward"
in the hc_method =
argument.
Extension 2 - Fuzzy Clustering
For the previous clustering methods, each data point belonged to only one cluster. This is sometimes referred to as hard clustering.
In contrast, in fuzzy clustering each point is allowed to belong to multiple clusters. The more similar the point is to other points in a cluster, the higher the point’s percentage of membership to that cluster.
This could be helpful when a point naturally should belong to multiple clusters (for instance, if penguins were clustered into 6 clusters, for males and females of each species, then each penguin would belong to two clusters).
We can easily conduct fuzzy clustering using the fanny
function from the inbuilt cluster
R package. The process is similar to the k-means clustering process (conducted in 3.1) and the PAM clustering process (conducted in 4.1).
Use the fanny
function to conduct fuzzy clustering for \(k\) values of 2, 3, 4 and 5.
Note: The output for these analyses will include a membership coefficients
list of the membership percentages for each point. While it is not easy to visualise these, the output also includes the closest ‘hard’ cluster for each point, which can be called via $cluster
, as per the previous clustering techniques.
Next, use the fviz_nbclust
function to determine the best number of fuzzy clusters to use. Just use the wss
and silhouette
methods (the gap_stat
method can take a long time to compute for fuzzy clustering).
Note: Don’t worry if any warning messages like the one shown in the code chunk below appear - you should still be able to produce the plots:
## Warning in FUNcluster(x, i, ...): FANNY algorithm has not converged in 'maxit' =
## 500 iterations
Use the fviz_cluster
function and the code below to visualise the clusters for the fuzzy clustering result you chose above in 6.2. What do you conclude?
# This code assumes you are assessing a result stored in the object fuzzy_fit3
windows()
plot(as.numeric(penguins$species), col = fuzzy_fit3$cluster)
Great work, that’s everything for today!
That concludes our work on clustering techniques. Did you have a preference for one of the four techniques introduced in this computer lab?
LS0tDQp0aXRsZTogIlNUTTEwMDE6IENvbXB1dGVyIExhYiA2QiINCm91dHB1dDoNCiAgYm9va2Rvd246Omh0bWxfZG9jdW1lbnQyOiANCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgdGhlbWU6IHJlYWRhYmxlDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQpiaWJsaW9ncmFwaHk6IFNUTTEwMDFfRFNfQ0xfcmVmZXJlbmNlcy5iaWIgDQpsaW5rLWNpdGF0aW9uczogeWVzDQotLS0NCg0KPHN0eWxlPg0KI1RPQyB7DQogIGJhY2tncm91bmQ6IHVybCgiaHR0cHM6Ly93d3cubGF0cm9iZS5lZHUuYXUvX21lZGlhL2xhLXRyb2JlLWFwaS92NS9pbWcvbG9nby5zdmciKTsNCiAgYmFja2dyb3VuZC1zaXplOiBjb250YWluOw0KICBwYWRkaW5nLXRvcDogODBweCAhaW1wb3J0YW50Ow0KICBiYWNrZ3JvdW5kLXJlcGVhdDogbm8tcmVwZWF0Ow0KfQ0KPC9zdHlsZT4NCg0KIyMjIFNjaWVuY2UvSGVhbHRoIFNjaWVuY2UvRGF0YSBTY2llbmNlIE1vZHVsZSB7LX0NCg0KIyMjIFRvcGljIDZCOiBCaWcgRGF0YSBJIChDbHVzdGVyaW5nKSB7LX0NCg0KPGJyPg0KDQpXZWxjb21lIHRvIHRoZSBzaXh0aCBjb21wdXRlciBsYWIgZm9yIHRoZSBTY2llbmNlLCBIZWFsdGggU2NpZW5jZSBhbmQgRGF0YSBTY2llbmNlIG1vZHVsZXMuDQoNCkluIHRoaXMgY29tcHV0ZXIgbGFiIHdlIHdpbGwgY2Fycnkgb3V0IGEgdmFyaWV0eSBvZiBjbHVzdGVyaW5nIGFuYWx5c2VzIHVzaW5nIHBlbmd1aW4gZGF0YSBmcm9tIHRoZSBgcGFsbWVycGVuZ3VpbnNgIFIgcGFja2FnZSBbQHBlbmd1aW5zXS4NCg0KQnkgdGhlIGVuZCBvZiB0aGlzIGxhYiwgeW91IHNob3VsZCBmZWVsIGNvbWZvcnRhYmxlIGFwcGx5aW5nIHZhcmlvdXMgY2x1c3RlcmluZyB0ZWNobmlxdWVzIGluIFIuIExldCdzIGdldCBzdGFydGVkIQ0KDQojIENsdXN0ZXIgQW5hbHlzaXMNCg0KQSBjbHVzdGVyIGlzIGEgc3Vic2V0IG9mIGEgZGF0YSBzZXQgdGhhdCBjb25zaXN0cyBvZiBvYnNlcnZhdGlvbnMgd2hpY2gsIHVzaW5nIGEgY2hvc2VuIG1ldHJpYywgYXJlICoqc2ltaWxhciB0byBlYWNoIG90aGVyKiosIGFuZCB3aGljaCBhcmUgYWxzbyAqKmRpc3NpbWlsYXIgdG8gb3RoZXIgb2JzZXJ2YXRpb25zKiosIHZpYSB0aGUgc2FtZSBtZXRyaWNeW0BNYXRoQ2x1c3RlciwgcC4yNV0uDQoNCkNsdXN0ZXIgQW5hbHlzaXMgaXMgdGhlIG1ldGhvZCBvZiBncm91cGluZyBkYXRhIGludG8gY2x1c3RlcnMsIHVzaW5nIGEgY2x1c3RlcmluZyB0ZWNobmlxdWUuIFRoZXJlIGlzIGEgbGFyZ2UgdmFyaWV0eSBvZiBjbHVzdGVyaW5nIHRlY2huaXF1ZXMsIGFuZCBlYWNoIG9mIHRoZXNlIHRlY2huaXF1ZXMgdXNlcyBhIGRpZmZlcmVudCBtZXRyaWMgZm9yIGRldGVybWluaW5nIHRoZSBzaW1pbGFyaXR5IGJldHdlZW4gb2JzZXJ2YXRpb25zLiBEb24ndCB3b3JyeSwgd2Ugd29uJ3QgZ28gaW50byBhbGwgdGhlIGNvbXBsaWNhdGVkIG1hdGhlbWF0aWNzIGludm9sdmVkIGluIHRoZXNlIHRlY2huaXF1ZXMgLSBvdXIgZm9jdXMgaXMgb24gbGVhcm5pbmcgaG93IHRvIGNvbmR1Y3QgY2x1c3RlciBhbmFseXNlcyBpbiBSLg0KDQojIyMgUHVycG9zZSB7LX0NCg0KQ2x1c3RlcmluZyBpcyBvZnRlbiB1c2VkIGFzIHBhcnQgb2YgYW4gZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcyBwcm9jZWR1cmUsIHRvIHVuY292ZXIgaGlkZGVuIHBhdHRlcm5zIGluIGEgbmV3IGRhdGEgc2V0Lg0KVGhlIG1haW4gcHVycG9zZSBvZiBjbHVzdGVyaW5nXltzZWUgZS5nLiBATWF0aENsdXN0ZXIsIHAuMjVdIGlzIHRvOg0KDQoxLiBBbmFseXNlIHRoZSBkYXRhIHN0cnVjdHVyZQ0KMi4gUmVsYXRlIHRoZSBkaWZmZXJlbnQgZWxlbWVudHMgb2YgdGhlIGRhdGEgdG8gZWFjaCBvdGhlciwgYW5kIHRoZW4NCjMuIEFpZCBpbiBjbGFzc2lmeWluZyB0aGUgZGF0YSBpbnRvIGNlcnRhaW4gY2xhc3Nlcw0KDQpJdCBpcyB3b3J0aCBub3RpbmcgdGhhdCBpbiBnZW5lcmFsLCB3ZSBtYXkgbm90IGJlIGF3YXJlIG9mIHdoYXQgdGhlc2UgY2xhc3NlcyBhcmUsIHByaW9yIHRvIHRoZSBjbHVzdGVyaW5nIGFuYWx5c2lzXltzZWUgZS5nLiBARGF0YUNsdXN0ZXJdLg0KDQojIyMgQ2x1c3RlcmluZyBUZWNobmlxdWVzIHstfQ0KDQpJbiB0aGlzIGNvbXB1dGVyIGxhYiwgd2Ugd2lsbCBhcHBseSB0aGUgZm9sbG93aW5nIHBvcHVsYXIgY2x1c3RlcmluZyB0ZWNobmlxdWVzOg0KDQoqIGstbWVhbnMgQ2x1c3RlcmluZw0KKiBQQU0gQ2x1c3RlcmluZw0KKiBGdXp6eSBDbHVzdGVyaW5nDQoqIEhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nDQoNCldlIHdpbGwgcHJvdmlkZSBicmllZiBleHBsYW5hdGlvbnMgb2YgZWFjaCBvZiB0aGVzZSB0ZWNobmlxdWVzIGFzIHdlIHdvcmsgdGhyb3VnaCB0aGlzIGNvbXB1dGVyIGxhYi4NCg0KIyBQcmVwYXJhdGlvbnMgeyNwcmVwfQ0KDQpJbiBSLCB3ZSBjYW4gdXNlIHRoZSBpbmJ1aWx0IGBjbHVzdGVyYCBwYWNrYWdlIHRvIGNhcnJ5IG91dCBhIHJhbmdlIG9mIGNsdXN0ZXIgYW5hbHlzZXMuIEhvd2V2ZXIsIHdlIHdpbGwgYWxzbyByZXF1aXJlIGEgZmV3IGFkZGl0aW9uYWwgcGFja2FnZXNeW3RoZSBgZmFjdG9leHRyYWAgcGFja2FnZSB3YXMgY3JlYXRlZCBieSBAZmFjdG8gYW5kIHRoZSBgZ2dmb3J0aWZ5YCBwYWNrYWdlIHdhcyBjcmVhdGVkIGJ5IEBnZy4gVGhlIGBjbHVzdGVyYCBwYWNrYWdlIGlzIHBhcnQgb2YgdGhlIGJhc2UgUiBzdWl0ZSBvZiBwYWNrYWdlcyBbQFJdLl0uIFBsZWFzZSBydW4gdGhlIFIgY29kZSBiZWxvdyB0byBpbnN0YWxsIGFuZCBsb2FkIHRoZXNlIHBhY2thZ2VzLg0KDQpgYGB7ciBldmFsID0gVCwgaW5jbHVkZSA9IEZ9DQpsaWJyYXJ5KGNsdXN0ZXIpDQpsaWJyYXJ5KGZhY3RvZXh0cmEpDQpsaWJyYXJ5KGdnZm9ydGlmeSkNCmxpYnJhcnkocGFsbWVycGVuZ3VpbnMpDQpgYGANCg0KYGBge3IgZXZhbCA9IEYsIGVjaG8gPSBUfQ0KaW5zdGFsbC5wYWNrYWdlcyhjKCJmYWN0b2V4dHJhIiwgImdnZm9ydGlmeSIsICJwYWxtZXJwZW5ndWlucyIpKQ0KbGlicmFyeShjbHVzdGVyKQ0KbGlicmFyeShmYWN0b2V4dHJhKQ0KbGlicmFyeShnZ2ZvcnRpZnkpDQpsaWJyYXJ5KHBhbG1lcnBlbmd1aW5zKQ0KYGBgDQoNCipOb3RlOiBJZiB5b3UgYXJlIGluIHRoZSBEYXRhIFNjaWVuY2UgbW9kdWxlLCB5b3Ugc2hvdWxkIGFscmVhZHkgaGF2ZSB0aGUgYHBhbG1lcnBlbmd1aW5zYCBwYWNrYWdlIGluc3RhbGxlZC4qDQoNCiMjIFBlbmd1aW4gRGF0YQ0KDQpUaGUgYHBlbmd1aW5zYCBkYXRhIHNldCBpbiB0aGUgYHBhbG1lcnBlbmd1aW5zYCBSIHBhY2thZ2UgW0BwZW5ndWluc10gY29udGFpbnMgZGF0YSBvbiAzIHNwZWNpZXMgb2YgcGVuZ3VpbiAoQWRlbGllLCBDaGluc3RyYXAgYW5kIEdlbnRvbykgbGl2aW5nIG9uIGlzbGFuZHMgaW4gdGhlIFBhbG1lciBhcmNoaXBlbGFnbywgb2ZmIHRoZSBjb2FzdCBvZiBBbnRhcmN0aWNhLiBXZSB3aWxsIHVzZSB0aGlzIGRhdGEgZm9yIG91ciBjbHVzdGVyaW5nIGFuYWx5c2VzLg0KDQpUaGlzIHBlbmd1aW4gZGF0YSBjb250YWlucyBpbmZvcm1hdGlvbiBvbiB2YXJpYWJsZXMgc3VjaCBhczoNCg0KKiBmbGlwcGVyIGxlbmd0aA0KKiBib2R5IG1hc3MNCiogYmlsbCBsZW5ndGgNCiogc2V4DQoqIHNwZWNpZXMNCiogaXNsYW5kIG9uIHdoaWNoIHRoZSBwZW5ndWluIGxpdmVzDQoNCiMjIHsjbWF0cml4cGxvdHN9DQoNClJ1biB0aGUgUiBjb2RlIGJlbG93IHRvIHRha2UgYSBsb29rIGF0IHRoZSBgcGVuZ3VpbnNgIGRhdGEgc2V0LCB0byBmYW1pbGlhcmlzZSB5b3Vyc2VsZiB3aXRoIHRoZSBkaWZmZXJlbnQgdmFyaWFibGVzLg0KDQpgYGB7ciBldmFsID0gRiwgZWNobyA9IFR9DQpoZWFkKHBlbmd1aW5zKQ0KcGxvdChwZW5ndWlucykNCmBgYA0KDQpOb3RlIGhlcmUgdGhhdCBgcGxvdChwZW5ndWlucylgIHdpbGwgcHJvZHVjZSBhIG1hdHJpeCBvZiBzY2F0dGVyIHBsb3RzLg0KDQpFYWNoIHBsb3Qgc2hvd3MgdHdvIG9mIHRoZSB2YXJpYWJsZXMgaW4gdGhlIGBwZW5ndWluc2AgZGF0YSBzZXQgcGxvdHRlZCBhZ2FpbnN0IGVhY2ggb3RoZXIuDQpUaGUgdmFyaWFibGUgbmFtZXMgYXJlIHNob3duIG9uIHRoZSBkaWFnb25hbCBib3hlcyAtIGp1c3QgY3Jvc3MgcmVmZXJlbmNlIGEgcGxvdCB0byB0aGUgdmFyaWFibGUgbmFtZXMgaW4gdGhhdCByb3cgYW5kIGNvbHVtbiB0byBkZXRlcm1pbmUgdGhlIHZhcmlhYmxlcyBwbG90dGVkIG9uIHRoZSB5LWF4aXMgYW5kIHgtYXhpcyByZXNwZWN0aXZlbHkuDQoNCipIaW50OiBGb3IgZXhhbXBsZSwgdGhlIHBsb3Qgc2hvd24gaW4gdGhlIHNpeHRoIHJvdywgZmlmdGggY29sdW1uIHNob3VsZCBiZSBgYm9keV9tYXNzX2dgIHBsb3R0ZWQgb24gdGhlIHktYXhpcyBhZ2FpbnN0IGBmbGlwcGVyX2xlbmd0aF9tbWAgb24gdGhlIHgtYXhpcy4qDQoNCkxvb2tpbmcgYXQgdGhlIHNjYXR0ZXIgcGxvdHMgZm9yIHRoZSBjb250aW51b3VzIHJhbmRvbSB2YXJpYWJsZXMsIGRvZXMgaXQgYXBwZWFyIHRoYXQgdGhlcmUgYXJlIGFueSBuYXR1cmFsIGNsdXN0ZXJzIHRoYXQgd2lsbCBiZSBlYXN5IHRvIGlkZW50aWZ5IHVzaW5nIGEgY2x1c3RlcmluZyB0ZWNobmlxdWU/DQoNCiMjIFByZXBhcmluZyBkYXRhIGZvciBDbHVzdGVyIEFuYWx5c2lzDQoNCkZvciB0aGUgcHVycG9zZXMgb2YgdGhpcyBjb21wdXRlciBsYWIsIHdlIHdpbGwgYXNzdW1lIHRoYXQgd2UgZG9uJ3QgYWN0dWFsbHkgaGF2ZSBkYXRhIG9uIHRoZSBzcGVjaWVzIG9mIGVhY2ggcGVuZ3VpbiBpbiB0aGUgYHBlbmd1aW5zYCBkYXRhIHNldC4NCkluc3RlYWQsIHdlIHdpbGwgY29uZHVjdCBjbHVzdGVyIGFuYWx5c2VzIHVzaW5nIHRoZSBvdGhlciBpbmZvcm1hdGlvbiBjb250YWluZWQgaW4gdGhpcyBkYXRhIHNldCwgdG8gdHJ5IHRvIGdyb3VwIHRoZSBwZW5ndWlucyBpbnRvIGNsdXN0ZXJzLg0KDQpJZiB0aGUgY2x1c3RlciB0ZWNobmlxdWUgd29ya3Mgd2VsbCwgd2UgY291bGQgZW5kIHVwIHdpdGggY2x1c3RlcnMgZm9yIGVhY2ggcGVuZ3VpbiBzcGVjaWVzIQ0KDQpIb3dldmVyLCBiZWZvcmUgd2UgYmVnaW4gb3VyIGNsdXN0ZXJpbmcgYW5hbHlzZXMsIHdlIHdpbGwgbmVlZCB0byBlbnN1cmUgdGhhdCBvdXIgZGF0YSBpcyBpbiB0aGUgYXBwcm9wcmlhdGUgZm9ybWF0Lg0KSXQgaXMgaW1wb3J0YW50IHRvIG5vdGUgdGhhdCBjbHVzdGVyaW5nIGFsZ29yaXRobXMgb25seSB3b3JrIG9uIGNvbnRpbnVvdXMgdmFyaWFibGVzLiBBcyB3ZSBoYXZlIHNlZW4sIHRoZSBgcGVuZ3VpbnNgIGRhdGEgc2V0IGNvbnRhaW5zIGJvdGggY29udGludW91cyBhbmQgY2F0ZWdvcmljYWwgKGRpc2NyZXRlKSB2YXJpYWJsZXMuIA0KDQpUaGVyZWZvcmUsIGZvciB0aGUgdGltZSBiZWluZywgd2Ugd2lsbCBvbmx5IHVzZSBhIHN1YnNldCBvZiB0aGUgdmFyaWFibGVzIGluIHRoZSBgcGVuZ3VpbnNgIGRhdGEgc2V0LCBuYW1lbHkgYGJpbGxfbGVuZ3RoX21tYCwgYGJpbGxfZGVwdGhfbW1gLCBgZmxpcHBlcl9sZW5ndGhfbW1gIGFuZCBgYm9keV9tYXNzX2dgLg0KDQojIyMNCg0KUnVuIHRoZSBSIGNvZGUgYmVsb3cgdG8gcmVtb3ZlIG1pc3NpbmcgdmFsdWVzIGZyb20gb3VyIGRhdGEsIGFuZCBjcmVhdGUgYSBzdWJzZXQgb2YgdGhlIGBwZW5ndWluc2AgZGF0YSB0aGF0IG9ubHkgY29udGFpbnMgdGhlIGNvbnRpbnVvdXMgdmFyaWFibGVzOg0KDQpgYGB7ciBldmFsID0gVCwgZWNobyA9IFR9DQpwZW5ndWlucyA8LSBuYS5vbWl0KHBlbmd1aW5zKQ0KcGVuZ3VpbnNfc3Vic2V0IDwtIHBlbmd1aW5zWywgMzo2XQ0KYGBgDQoNCiMjIw0KDQpCZWZvcmUgY29uZHVjdGluZyBhIGNsdXN0ZXIgYW5hbHlzaXMsIGl0IGlzIGFsc28gZ2VuZXJhbGx5IGEgZ29vZCBpZGVhIHRvIG5vcm1hbGlzZSBvdXIgZGF0YSAodGhpcyBwcm9jZXNzIGlzIGxpa2UgdGhlIHN0YW5kYXJkaXNhdGlvbiBwcm9jZXNzIGNvdmVyZWQgaW4gW3NlY3Rpb24gNC4yIG9mIFRvcGljIDNdKGh0dHBzOi8vYm9va2Rvd24ub3JnL2Ffc2hha2VyL1NUTTEwMDFfVG9waWNfMy80LTItc3RhbmRhcmRpc2F0aW9uLmh0bWwpKS4gVGhpcyB3aWxsIHJlbW92ZSB0aGUgZWZmZWN0IG9mIGRpZmZlcmVudCB2YXJpYWJsZXMgYmVpbmcgbWVhc3VyZWQgb24gZGlmZmVyZW50IHNjYWxlcyAoZS5nLiBmbGlwcGVyIGxlbmd0aCBpbiBtbSBhbmQgYm9keSBtYXNzIGluIGdyYW1zKSwgYW5kIGNhbiBwcmV2ZW50IGFueSBzaW5nbGUgdmFyaWFibGUgb3ZlcnBvd2VyaW5nIG90aGVycyB3aGVuIGJlaW5nIGFzc2Vzc2VkIGJ5IHRoZSBjbHVzdGVyIGFsZ29yaXRobS4NCg0KTm9ybWFsaXNpbmcgdGhlIGRhdGEgY29uc2lzdHMgb2Y6IA0KDQoxLiBDb21wdXRpbmcgdGhlIHNhbXBsZSBtZWFuIGFuZCBzYW1wbGUgc3RhbmRhcmQgZGV2aWF0aW9uIGZvciBlYWNoIHZhcmlhYmxlDQoyLiBTdWJ0cmFjdGluZyB0aGUgcmVsZXZhbnQgc2FtcGxlIG1lYW4gZnJvbSBlYWNoIG9ic2VydmF0aW9uLCBhbmQgZGl2aWRpbmcgYnkgdGhlIHJlbGV2YW50IHNhbXBsZSBzdGFuZGFyZCBkZXZpYXRpb24NCg0KRm9yIGV4YW1wbGUsIHRoZSBzYW1wbGUgbWVhbiBmb3IgYGJpbGxfZGVwdGhfbW1gIGlzIHJvdWdobHkgMTcuMTUxLCBhbmQgdGhlIHNhbXBsZSBzdGFuZGFyZCBkZXZpYXRpb24gaXMgcm91Z2hseSAxLjk3NS4gVGhlIGZpcnN0IHBlbmd1aW4gaW4gdGhlIGRhdGEgc2V0IGhhcyBhIHJlY29yZGVkIGBiaWxsX2RlcHRoX21tYCB2YWx1ZSBvZiAxOC43LiBUaGUgbm9ybWFsaXNlZCB2ZXJzaW9uIG9mIHRoaXMgdmFsdWUgd291bGQgdGhlcmVmb3JlIGJlICQkXGRpc3BsYXlzdHlsZSBcZnJhY3sxOC43IC0gMTcuMTUxfXsxLjk3NX0gXGFwcHJveCAwLjc4LiQkIA0KDQpGb3J0dW5hdGVseSwgd2UgY2FuIGNvbmR1Y3QgdGhpcyBub3JtYWxpc2F0aW9uIHByb2Nlc3Mgd2l0aCBvbmUgbGluZSBvZiBjb2RlIGluIFIsIGZvciBhbGwgb3VyIHZhcmlhYmxlcy4NCg0KIyMjDQoNClVzZSB0aGUgYHNjYWxlYCBmdW5jdGlvbiB0byBub3JtYWxpc2Ugb3VyIGBwZW5ndWluc19zdWJzZXRgIGRhdGEsIGFuZCBhc3NpZ24gdGhlIG5vcm1hbGlzZWQgZGF0YSB0byB0aGUgb2JqZWN0IGBwZW5ndWluc19zY2FsZWRgLiBUaGVuLCB0YWtlIGEgbG9vayBhdCB5b3VyIG5vcm1hbGlzZWQgZGF0YSB1c2luZyB0aGUgYGhlYWRgIGZ1bmN0aW9uLg0KDQoqSGludDogWW91ciBvdXRwdXQgc2hvdWxkIGxvb2sgbGlrZSB0aGUgb3V0cHV0IGluIHRoZSBgQ29kZWAgY2h1bmsgYmVsb3c6Kg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIiwgZXZhbCA9IFQsIGluY2x1ZGUgPSBUfQ0KcGVuZ3VpbnNfc2NhbGVkIDwtIHNjYWxlKHBlbmd1aW5zX3N1YnNldCkNCmhlYWQocGVuZ3VpbnNfc2NhbGVkKQ0KYGBgDQoNCiMgay1tZWFucyBDbHVzdGVyaW5nDQoNCk5vdyB0aGF0IG91ciBkYXRhIGlzIHN1aXRhYmx5IHByZXBhcmVkLCB3ZSBjYW4gYmVnaW4gb3VyIGNsdXN0ZXIgYW5hbHlzZXMuDQoNClRoZSBmaXJzdCBjbHVzdGVyaW5nIHRlY2huaXF1ZSB3ZSB3aWxsIGNvbnNpZGVyIGlzICprLW1lYW5zIGNsdXN0ZXJpbmcqLg0KDQpJbiBrLW1lYW5zIGNsdXN0ZXJpbmcsIHdlIHNwZWNpZnkgYW4gYXJiaXRyYXJ5IG51bWJlciBvZiBjbHVzdGVycyAkayQsIGFuZCBlYWNoIGRhdGEgcG9pbnQgaXMgYXNzaWduZWQgdG8gb25lIG9mIHRoZXNlICRrJCBjbHVzdGVycywgYmFzZWQgb24gdGhlIGRpc3RhbmNlIGJldHdlZW4gdGhlIHBvaW50IGFuZCB0aGUgbWVhbiBvZiBhbGwgcG9pbnRzLiANCg0KT25jZSB0aGVzZSBpbml0aWFsIGNsdXN0ZXJzIGFyZSBjcmVhdGVkLCB0aGUgbWVhbiBvZiBlYWNoIGNsdXN0ZXIgaXMgY2FsY3VsYXRlZCwgYW5kIGFuIGl0ZXJhdGl2ZSBwcm9jZXNzIGJlZ2luczogDQoNCiogUG9pbnRzIGFyZSBtb3ZlZCBiZXR3ZWVuIGNsdXN0ZXJzLCBvbmUgcG9pbnQgYXQgYSB0aW1lLCBkZXBlbmRpbmcgb24gaG93IGNsb3NlIHRoZXkgYXJlIHRvIGVhY2ggY2x1c3RlciBtZWFuLg0KKiBUaGlzIHByb2Nlc3MgY29udGludWVzLCB1bnRpbCBubyBwb2ludCBjYW4gYmUgbW92ZWQgYmV0d2VlbiBjbHVzdGVycyB3aXRob3V0IGluY3JlYXNpbmcgdGhlIGF2ZXJhZ2UgZGlzdGFuY2UgYmV0d2VlbiB0aGUgcG9pbnRzIGFuZCB0aGUgY2x1c3RlciBtZWFuc15bU2VlIGUuZy4gc2VjdGlvbiA0LjEwLjMgb2YgQE1vZFN0YXRdLg0KDQojIyB7I2ttZWFuczN9DQoNCldlIGNhbiB1c2UgdGhlIGBrbWVhbnNgIGZ1bmN0aW9uIGZyb20gdGhlIGluYnVpbHQgYGNsdXN0ZXJgIFIgcGFja2FnZSB0byBjb25kdWN0IGEgay1tZWFucyBjbHVzdGVyaW5nIGFuYWx5c2lzLg0KUnVuIHRoZSBmb2xsb3dpbmcgUiBjb2RlIHRvIGNvbmR1Y3QgdGhpcyBhbmFseXNpcyBvbiBvdXIgYHBlbmd1aW5zX3NjYWxlZGAgZGF0YSwgd2l0aCBgaz0zYCBzcGVjaWZpZWQuDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFR9DQprbWVhbnNfZml0MyA8LSBrbWVhbnMocGVuZ3VpbnNfc2NhbGVkLCAzKQ0KYGBgDQoNCiMjDQoNCkxldCdzIGRpc2N1c3MgdGhlIHJlc3VsdHMuIElmIHdlIHJ1biB0aGUgbGluZSBga21lYW5zX2ZpdDNgLCBhIGxpc3Qgb2Ygb3V0cHV0IGFwcGVhcnMgaW4gUi4NCg0KV2UgYXJlIHByaW1hcmlseSBpbnRlcmVzdGVkIGluIHRoZSBjbHVzdGVyIG1lbWJlcnNoaXAgYXNzaWduZWQgYnkgdGhlIGFsZ29yaXRobS4NCldlIGNhbiBleHRyYWN0IHRoaXMgY2x1c3RlciBtZW1iZXJzaGlwIHVzaW5nIGBrbWVhbnNfZml0MyRjbHVzdGVyYC4NCg0KSXQgaXMgYWxzbyBpbXBvcnRhbnQgdG8gY2hlY2sgdGhlIGRldGFpbHMgaW4gdGhlIGBXaXRoaW4gY2x1c3RlciBzdW0gb2Ygc3F1YXJlcyBieSBjbHVzdGVyYCBzZWN0aW9uLg0KSGVyZSwgYGJldHdlZW5fU1NgIGRlbm90ZXMgdGhlIHN1bSBvZiBzcXVhcmVzIGJldHdlZW4gY2x1c3RlcnMgKGkuZS4sIGhvdyB3ZWxsIHRoZSBjbHVzdGVycyBhcmUgc2VwYXJhdGVkIGZyb20gZWFjaCBvdGhlciksDQp3aGlsZSBgdG90YWxfc3NgIGRlbm90ZXMgdGhlIHRvdGFsIHZhcmlhYmlsaXR5IGluIHRoZSBkYXRhLg0KVGhlIGNhbGN1bGF0aW9uIGBiZXR3ZWVuX3NzIC90b3RhbF9zc2AgdGVsbHMgdXMgaG93IG11Y2ggb2YgdGhlIHZhcmlhYmlsaXR5IGluIHRoZSBkYXRhIGlzIGFjY291bnRlZCBmb3IgYnkgdGhlIGNsdXN0ZXJzIGZvdW5kIGJ5IHRoZSBhbGdvcml0aG0uDQoNClRoaXMgdmFsdWUgY2FuIHJhbmdlIGZyb20gMCUgdG8gMTAwJSwgd2l0aCBsYXJnZXIgdmFsdWVzIGluZGljYXRpbmcgYSBiZXR0ZXIgcmVzdWx0LiBIZXJlLCA3Mi4xJSBpcyBkZWNlbnQsIGFsdGhvdWdoIHdlIG1pZ2h0IGhhdmUgZXhwZWN0ZWQgYSBzbGlnaHRseSBoaWdoZXIgcmVzdWx0Lg0KDQojIyBWaXN1YWxpc2luZyBvdXIgcmVzdWx0cyB7I2ttZWFuc3Zpc3VhbGlzZX0NCg0KV2UgY2FuIHZpc3VhbGlzZSBvdXIgcmVzdWx0cyBpbiBzZXZlcmFsIHdheXMuDQoNCkZpcnN0bHksIHJ1biB0aGUgZm9sbG93aW5nIGNvZGUgdG8gcHJvZHVjZSBhIG1hdHJpeCBvZiBzY2F0dGVyIHBsb3RzIG9mIHRoZSBjb250aW51b3VzIHZhcmlhYmxlcywgY29sb3VyZWQgYnkgY2x1c3Rlci4gDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFR9DQp3aW5kb3dzKCkgIyBvciBxdWFydHooKSBpZiB5b3UgYXJlIG9uIGEgTWFjDQpwbG90KHBlbmd1aW5zWywgYygxLDM6NildLCBjb2wgPSBrbWVhbnNfZml0MyRjbHVzdGVyKQ0KYGBgDQoNCkRvbid0IHdvcnJ5IGlmIHRoZSBwbG90cyBwcm9kdWNlZCBsb29rIGEgYml0IGNvbmZ1c2luZy4gVG8gbWFrZSB0aGUgcGxvdHMgZWFzaWVyIHRvIHJlYWQsIGNvbnNpZGVyIG1heGltaXNpbmcgdGhlIGdyYXBoaWNzIHdpbmRvdy4gDQoNClJlY2FsbCBmcm9tIFxAcmVmKG1hdHJpeHBsb3RzKSB0aGF0IGVhY2ggc2NhdHRlciBwbG90IGhlcmUgc2hvd3MgdHdvIG9mIHRoZSB2YXJpYWJsZXMgaW4gdGhlIGBwZW5ndWluc2AgZGF0YSBzZXQgcGxvdHRlZCBhZ2FpbnN0IGVhY2ggb3RoZXIuIE5vdyBob3dldmVyLCB0aGV5IGFyZSBjb2xvdXJlZCBieSBjbHVzdGVyIChhcyBkZXRlcm1pbmVkIGJ5IHRoZSBrLW1lYW5zIG1ldGhvZCkuDQoNCklmLCBieSBpbnNwZWN0aW5nIHRoZXNlIHBsb3RzLCB5b3UgY2FuIGNsZWFybHkgZGlzdGluZ3Vpc2ggYmV0d2VlbiBjbHVzdGVycywgdGhlbiB0aGUgY2x1c3RlcmluZyBtZXRob2QgaGFzIHBlcmZvcm1lZCB3ZWxsLg0KDQoqSGludDogSWYgeW91IGFyZSBoYXZpbmcgdHJvdWJsZSBpbnRlcnByZXRpbmcgdGhlIGdyYXBoLCB0aGlzIG5vdGUgbWF5IGhlbHAuIEZvciBleGFtcGxlLCB0aGUgcGxvdCBzaG93biBpbiB0aGUgZmlmdGggcm93LCBmb3VydGggY29sdW1uIHNob3VsZCBiZSBgYm9keV9tYXNzX2dgIHBsb3R0ZWQgb24gdGhlIHktYXhpcyBhZ2FpbnN0IGBmbGlwcGVyX2xlbmd0aF9tbWAgb24gdGhlIHgtYXhpcy4qDQoNCiMjDQoNCldlIGNvdWxkIGFsc28gdXNlIHRoZSBSIGNvZGUgYmVsb3cgdG8gcGxvdCB0aGUgYHNwZWNpZXNgIHZhcmlhYmxlIGFzIGEgbnVtZXJpYyB2YWx1ZSAoQWRlbGllID0gMSwgZXRjKSwgd2l0aCB0aGUgcG9pbnRzIGNvbG91cmVkIGJ5IGNsdXN0ZXI6DQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFR9DQpwbG90KGFzLm51bWVyaWMocGVuZ3VpbnMkc3BlY2llcyksIGNvbCA9IGttZWFuc19maXQzJGNsdXN0ZXIpDQpgYGANCg0KQmFzZWQgb24gdGhpcyBwbG90LCBhbmQgdGhlIHBsb3QgcHJvZHVjZWQgaW4gXEByZWYoa21lYW5zdmlzdWFsaXNlKSwgZG8geW91IHRoaW5rIHRoZSBrLW1lYW5zIGNsdXN0ZXJpbmcgaGFzIHBlcmZvcm1lZCB3ZWxsPw0KDQojIyB7I2ttZWFuc21vcmV9DQoNCkF0IHRoaXMgcG9pbnQsIGl0IG1pZ2h0IHNlZW0gdGhhdCBvdXIgam9iIGlzIGRvbmUuIFdlIGhhdmUgcmVzdWx0cyBmb3IgJGs9MyQsIGFuZCB0aGVyZSBhcmUgdGhyZWUgc3BlY2llcyBvZiBwZW5ndWlucy4NCg0KUmVtZW1iZXIgdGhvdWdoLCBub3JtYWxseSB3ZSB3b3VsZCBub3QgYWN0dWFsbHkga25vdyB3aGF0IHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgc2hvdWxkIGJlLiBUaGVyZWZvcmUsIHdlIHdvdWxkIHVzdWFsbHkgdHJ5IGEgcmFuZ2Ugb2YgZGlmZmVyZW50IGsgdmFsdWVzLg0KDQpDb25kdWN0IGstbWVhbnMgY2x1c3RlcmluZyBvbiBvdXIgYHBlbmd1aW5zX3NjYWxlZGAgZGF0YSwgZm9yICRrJCB2YWx1ZXMgb2YgMiwgNCBhbmQgNS4gDQoNCk5vdGUgZG93biB0aGUgYGJldHdlZW5fc3MgL3RvdGFsX3NzYCByZXN1bHRzLiBXaGF0IGRvIHlvdSBvYnNlcnZlPw0KDQojIyB7I25iY2x1c3R9DQoNCllvdSBtaWdodCBoYXZlIG5vdGljZWQgdGhhdCB0aGUgYGJldHdlZW5fc3MgL3RvdGFsX3NzYCB2YWx1ZSBpbmNyZWFzZXMgYXMgd2UgaW5jcmVhc2UgdGhlIG51bWJlciBvZiBjbHVzdGVycy4gVGhpcyBjYW4gaGFwcGVuIGV2ZW4gaWYgYWRkaW5nIGFub3RoZXIgY2x1c3RlciBpc24ndCBhY3R1YWxseSB0aGF0IGhlbHBmdWwuIFRoZXJlZm9yZSwgd2Ugc2hvdWxkIGFsc28gY29uc2lkZXIgc29tZSBvdGhlciBhc3Nlc3NtZW50IG1ldGhvZHMuDQoNCklmIHdlIGRpZCBub3Qga25vdyB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzLCB3ZSBjb3VsZCB1c2Ugc2V2ZXJhbCBtZXRob2RzIHRvIGRldGVybWluZSB0aGUgYXBwcm9wcmlhdGUgdmFsdWUgb2YgJGskLg0KDQpUaGUgYGZhY3RvZXh0cmFgIFIgcGFja2FnZSBjb250YWlucyBhIGhlbHBmdWwgZnVuY3Rpb24gYGZ2aXpfbmJjbHVzdGAsIHdoaWNoIHdlIGNhbiB1c2UgdG8gcHJvZHVjZSBzZXZlcmFsIGluZm9ybWF0aXZlIHBsb3RzLg0KDQpUYWtlIGEgbG9vayBhdCB0aGUgY29tbWVudHMgaW4gdGhlIGBDb2RlYCBjaHVuayBiZWxvdyAodGhlIGAuLi5gIHNpZ25pZnkgdGhhdCB0aGVzZSBhcmUgY29tbWVudHMsIGFuZCBzaG91bGQgYmUgcmVwbGFjZWQgYnkgYWN0dWFsIGFyZ3VtZW50cyk6DQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFR9DQpleGFtcGxlIDwtIGZ2aXpfbmJjbHVzdCguLi5zcGVjaWZ5IGRhdGEgc2V0IGhlcmUuLi4gLCANCiAgICAgICAgICAgICAgICAgICAgICAgIC4uLnNwZWNpZnkgY2x1c3RlcmluZyBtZXRob2QgaGVyZS4uLiAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gIi4uLnNwZWNpZnkgYXNzZXNzbWVudCBtZXRob2QgaGVyZS4uLiIpDQpgYGANCg0KQXMgeW91IGNhbiBzZWUsIHRoZSBgZnZpel9uYmNsdXN0YCBmdW5jdGlvbiB0YWtlcyB0aHJlZSBtYWluIGFyZ3VtZW50czogDQoNCjEuIFRoZSBkYXRhIHNldCwgDQoyLiBUaGUgY2x1c3RlcmluZyBtZXRob2QgKGkuZS4ga21lYW5zKSwgYW5kIA0KMy4gVGhlIGFzc2Vzc21lbnQgbWV0aG9kIChvbmUgb2YgIndzcyIgLCAic2lsaG91ZXR0ZSIgLCBvciAiZ2FwX3N0YXQiKQ0KDQojIyMNCg0KVGhlIGB3c3NgIGFzc2Vzc21lbnQgbWV0aG9kIHByb2R1Y2VzIGEgc2NyZWUgcGxvdCwgd2l0aCB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIHBsb3R0ZWQgb24gdGhlIGhvcml6b250YWwgYXhpcywgYW5kIHRoZSBUb3RhbCBXaXRoaW4gU3VtIG9mIFNxdWFyZXMgKHdzcykgdmFyaWFuY2UgcGxvdHRlZCBvbiB0aGUgdmVydGljYWwgYXhpcy4gQSBsb3dlciB2YXJpYW5jZSBpcyBwcmVmZXJhYmxlLCBidXQgYWZ0ZXIgYSBjZXJ0YWluIHBvaW50LCBhZGRpbmcgbW9yZSBjbHVzdGVycyB3aWxsIG5vdCBsb3dlciB0aGlzIHZhcmlhYmlsaXR5IHNpZ25pZmljYW50bHkuIFdoZW4gYXNzZXNzaW5nIHRoaXMgcGxvdCwgd2Ugc2VsZWN0LCBhcyBvdXIgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMsIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgZm9yIHdoaWNoIGFkZGluZyBhbiBleHRyYSBjbHVzdGVyIGRvZXNuJ3QgZ3JlYXRseSByZWR1Y2UgdGhpcyB2YXJpYWJpbGl0eS4NCg0KVXNpbmcgdGhlIGBmdml6X25iY2x1c3RgIGZ1bmN0aW9uLCB3aXRoIHRoZSBga21lYW5zYCBjbHVzdGVyaW5nIG1ldGhvZCBhbmQgdGhlIGAid3NzImAgYXNzZXNzbWVudCBtZXRob2Qgc3BlY2lmaWVkLCBkZXRlcm1pbmUgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzIGZvciBvdXIgYHBlbmd1aW5zX3NjYWxlZGAgZGF0YS4NCg0KKkhpbnQ6IElmIHlvdSBhcmUgbm90IHN1cmUgaG93IHRvIHByb2NlZWQsIGNoZWNrIHRoZSBgQ29kZWAgY2h1bmsgYmVsb3c6Kg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIiwgZXZhbCA9IEYsIGVjaG8gPSBUfQ0Ka21lYW5zX3dzcyA8LSBmdml6X25iY2x1c3QocGVuZ3VpbnNfc2NhbGVkLCBrbWVhbnMsIG1ldGhvZCA9ICJ3c3MiKQ0Kd2luZG93cygpDQprbWVhbnNfd3NzDQpgYGANCg0KIyMjDQoNClRoZSBgc2lsaG91ZXR0ZWAgYXNzZXNzbWVudCBtZXRob2QgbWVhc3VyZXMgdGhlIHNpbWlsYXJpdHkgb2YgYSBwb2ludCB0byBvdGhlciBwb2ludHMgaW4gaXRzIGNsdXN0ZXIuIFRoZSBoaWdoZXIgdGhpcyAnYXZlcmFnZSBzaWxob3VldHRlIHdpZHRoJyAsIHRoZSBiZXR0ZXIuDQoNClVzaW5nIHRoZSBgZnZpel9uYmNsdXN0YCBmdW5jdGlvbiwgd2l0aCB0aGUgYGttZWFuc2AgY2x1c3RlcmluZyBtZXRob2QgYW5kIHRoZSBgInNpbGhvdWV0dGUiYCBhc3Nlc3NtZW50IG1ldGhvZCBzcGVjaWZpZWQsIGRldGVybWluZSB0aGUgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMgZm9yIG91ciBgcGVuZ3VpbnNfc2NhbGVkYCBkYXRhLg0KDQojIyMNCg0KVGhlIGZpbmFsIGstbWVhbnMgY2x1c3RlcmluZyBhc3Nlc3NtZW50IG1ldGhvZCB3ZSB3aWxsIGNvbnNpZGVyIGlzIHRoZSBgZ2FwX3N0YXRgIGFzc2Vzc21lbnQgbWV0aG9kLiBUaGlzIGNvbXB1dGVzIGEgc3RhdGlzdGljIGtub3duIGFzIHRoZSAnZ2FwIHN0YXRpc3RpYycuIFdlIHdvbid0IGdvIGludG8gdGhlIG1hdGhlbWF0aWNhbCBkZXRhaWxzIG9mIHRoaXMgc3RhdGlzdGljOyBzdWZmaWNlIHRvIHNheSB0aGF0IGhpZ2hlciB2YWx1ZXMgYXJlIGNvbnNpZGVyZWQgcHJlZmVyYWJsZS4NCg0KVXNpbmcgdGhlIGBmdml6X25iY2x1c3RgIGZ1bmN0aW9uLCB3aXRoIHRoZSBga21lYW5zYCBjbHVzdGVyaW5nIG1ldGhvZCBhbmQgdGhlIGAiZ2FwX3N0YXQiYCBhc3Nlc3NtZW50IG1ldGhvZCBzcGVjaWZpZWQsIGRldGVybWluZSB0aGUgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMgZm9yIG91ciBgcGVuZ3VpbnNfc2NhbGVkYCBkYXRhLg0KDQojIw0KDQpXZSBjYW4gYWxzbyB1c2UgdGhlIGBmdml6X2NsdXN0ZXJgIGZ1bmN0aW9uIGZyb20gdGhlIGBmYWN0b2V4dHJhYCBSIHBhY2thZ2UgdG8gdmlzdWFsaXNlIHRoZSBjbHVzdGVycy4NCg0KUnVuIHRoZSBjb2RlIGJlbG93IHRvIHZpc3VhbGlzZSB0aGUgdGhyZWUgY2x1c3RlcnMgZm91bmQgaW4gXEByZWYoa21lYW5zMyleW05vdGUgdGhhdCB0aGVzZSBtaWdodCBsb29rIGEgbGl0dGxlIGRpZmZlcmVudCB0byB0aG9zZSB2aXN1YWxpc2VkIGluIFxAcmVmKGttZWFuc3Zpc3VhbGlzZSksIHNpbmNlIHRoZSBheGVzIHZhcmlhYmxlcyBhcmUgZGlmZmVyZW50XSwgYW5kIHRoZW4sIHVzaW5nIHRoaXMgY29kZSBhcyBhIGJhc2UsIHZpc3VhbGlzZSB0aGUgc2V0cyBvZiBjbHVzdGVycyBmb3VuZCBpbiBcQHJlZihrbWVhbnNtb3JlKS4NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBGLCBlY2hvID0gVH0NCmZ2aXpfY2x1c3RlcihrbWVhbnNfZml0MywgZGF0YSA9IHBlbmd1aW5zX3NjYWxlZCkNCmBgYA0KDQojIyANCg0KQmFzZWQgb24geW91ciBrLW1lYW5zIGNsdXN0ZXJpbmcgYW5hbHlzZXMsIHdoaWNoIHZhbHVlIG9mICRrJCB3b3VsZCB5b3UgcmVjb21tZW5kIHVzaW5nLCBhbmQgd2h5Pw0KDQojIFBBTSBDbHVzdGVyaW5nDQoNCmstbWVhbnMgY2x1c3RlcmluZyBpcyBhIGZvcm0gb2YgY2VudHJvaWQtYmFzZWQgY2x1c3RlcmluZywgd2hlcmVieSBjbHVzdGVyIG1lYW5zIChpLmUuIGNlbnRyb2lkcykgYXJlIHVzZWQgYXMgYSBtZXRyaWMgaW4gdGhlIGNsdXN0ZXJpbmcgYWxnb3JpdGhtLg0KQW4gYWx0ZXJuYXRpdmUgdG8gay1tZWFucyBjbHVzdGVyaW5nIGlzICpQQU0qIChQYXJ0aW9uIGFyb3VuZCBNZWRvaWRzKSAqY2x1c3RlcmluZyouIFRoaXMgb3BlcmF0ZXMgaW4gYSBzaW1pbGFyIG1hbm5lciB0byBrLW1lYW5zIGNsdXN0ZXJpbmcsIGJ1dCB1c2VzIG1lZG9pZHMsIChtZWRpYW4tbGlrZSBwb2ludHMpLCByYXRoZXIgdGhhbiBjZW50cm9pZHMsIHdoZW4gZGVjaWRpbmcgY2x1c3RlciBtZW1iZXJzaGlwLiBUaGlzIG1ha2VzIHRoZSBQQU0gY2x1c3RlcmluZyB0ZWNobmlxdWUgbW9yZSByb2J1c3QgdG8gb3V0bGllcnMuDQoNCiMjIHsjcGFtfQ0KDQpXZSBjYW4gZWFzaWx5IGNvbmR1Y3QgUEFNIGNsdXN0ZXJpbmcgdXNpbmcgdGhlIGBwYW1gIGZ1bmN0aW9uIGZyb20gdGhlIGluYnVpbHQgYGNsdXN0ZXJgIFIgcGFja2FnZS4gVGhlIHByb2Nlc3MgaXMgc2ltaWxhciB0byB0aGUgay1tZWFucyBjbHVzdGVyaW5nIHByb2Nlc3MgY29uZHVjdGVkIGluIFxAcmVmKGttZWFuczMpLg0KDQpVc2UgdGhlIGBwYW1gIGZ1bmN0aW9uIHRvIGNvbmR1Y3QgUEFNIGNsdXN0ZXJpbmcgZm9yICRrJCB2YWx1ZXMgb2YgMiwgMywgNCBhbmQgNS4gDQoNCiMjIHsjcGFtYmVzdH0NCg0KTmV4dCwgdXNlIHRoZSBgZnZpel9uYmNsdXN0YCBmdW5jdGlvbiB0byBkZXRlcm1pbmUgdGhlIGJlc3QgbnVtYmVyIG9mIFBBTSBjbHVzdGVycyB0byB1c2UgKHVzZSBhbGwgdGhyZWUgbWV0aG9kcykuDQoNCipIaW50OiBVc2UgYHBhbWAgaW5zdGVhZCBvZiBga21lYW5zYCBmb3IgdGhlIGNsdXN0ZXJpbmcgbWV0aG9kIHNwZWNpZmljYXRpb24uKg0KDQojIw0KDQpGaW5hbGx5LCB1c2UgdGhlIGBmdml6X2NsdXN0ZXJgIGZ1bmN0aW9uIGFuZCB0aGUgY29kZSBiZWxvdyB0byB2aXN1YWxpc2UgdGhlIGNsdXN0ZXJzIGZvciB0aGUgUEFNIGNsdXN0ZXJpbmcgcmVzdWx0KHMpIHlvdSBjaG9zZSBhYm92ZSBpbiBcQHJlZihwYW1iZXN0KS4gV2hhdCBkbyB5b3UgY29uY2x1ZGU/DQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFR9DQojIFRoaXMgY29kZSBhc3N1bWVzIHlvdSBhcmUgYXNzZXNzaW5nIGEgcmVzdWx0IHN0b3JlZCBpbiB0aGUgb2JqZWN0IHBhbV9maXQzDQp3aW5kb3dzKCkNCnBsb3QoYXMubnVtZXJpYyhwZW5ndWlucyRzcGVjaWVzKSwgY29sID0gcGFtX2ZpdDMkY2x1c3RlcmluZykNCmBgYA0KDQo8YnI+DQoNCiMjIyMgQXQgdGhpcyBwb2ludCwgd2UndmUgY292ZXJlZCB0aGUgbWFpbiBtYXRlcmlhbCBmb3IgdGhpcyBjb21wdXRlciBsYWIuIElmIHlvdSB3b3VsZCBsaWtlLCB5b3UgY2FuIGV4dGVuZCB5b3VyIGtub3dsZWRnZSBvbiBjbHVzdGVyaW5nIGJ5IGdvaW5nIHRocm91Z2ggdGhlIEV4dGVuc2lvbiBzZWN0aW9ucyBiZWxvdy4gT3RoZXJ3aXNlLCBpZiB5b3UgaGF2ZSB0aW1lIGxlZnQsIHlvdSBtaWdodCBsaWtlIHRvIHdvcmsgb24geW91ciBhc3Nlc3NtZW50cy4gIyMjIyB7LX0NCg0KPGJyPg0KDQojIEV4dGVuc2lvbiAxIC0gSGllcmFyY2hpY2FsIENsdXN0ZXJpbmcNCg0KVW5saWtlIHRoZSBwcmV2aW91cyBjbHVzdGVyaW5nIHRlY2huaXF1ZXMgZGlzY3Vzc2VkLCBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyBiZWdpbnMgYnkgYXNzaWduaW5nIGVhY2ggcG9pbnQgdG8gaXRzIG93biBjbHVzdGVyLiBGb3IgZXhhbXBsZSwgc2luY2Ugd2UgaGF2ZSAzMzMgcGVuZ3VpbnMgaW4gb3VyIGZpbHRlcmVkIGRhdGEgc2V0LCB3ZSB3b3VsZCBzdGFydCB3aXRoIDMzMyBjbHVzdGVycy4gVGhlbiwgdGhlIHR3byBtb3N0IHNpbWlsYXIgY2x1c3RlcnMgYXJlIG1lcmdlZCAoY29udGludWluZyBvdXIgZWFtcGxlLCB0aGlzIHdvdWxkIHJlc3VsdCBpbiB1cyBub3cgaGF2aW5nIDMzMiBjbHVzdGVycykuIFdlIHJlcGVhdCB0aGlzIHByb2Nlc3MsIHVudGlsIHdlIGhhdmUganVzdCBvbmUgbGFyZ2UgY2x1c3RlciAod2hpY2ggY29udGFpbnMgYWxsIHRoZSBwb2ludHMpLg0KDQpBcyBhIHJlc3VsdCwgd2Ugbm93IGhhdmUgYSAqaGllcmFyY2h5KiBvZiBjbHVzdGVycywgd2hpY2ggbG9va3MgYSBiaXQgbGlrZSBhbiB1cHNpZGUtZG93biB0cmVlICh3aXRoIG9ic2VydmF0aW9ucyBmcm9tIHRoZSBzYW1lIGNsdXN0ZXIgb24gdGhlIHNhbWUgJ2JyYW5jaCcpLg0KDQojIyB7I2hpZXJhcmNoaWNhbH0NCg0KV2UgY2FuIGNvbmR1Y3QgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgdXNpbmcgdGhlIGBhZ25lc2AgZnVuY3Rpb24gZnJvbSB0aGUgYGNsdXN0ZXJgIFIgcGFja2FnZS4gU2luY2Ugd2UgYXJlIG5vdCBzcGVjaWZ5aW5nIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMsIHdlIG9ubHkgbmVlZCB0byBwcm92aWRlIHRoZSBvbmUgYXJndW1lbnQsIGkuZS4gdGhlIGRhdGEgc2V0IHRvIGFuYWx5c2UuDQoNClVzZSB0aGUgYGFnbmVzYCBmdW5jdGlvbiB0byBjb25kdWN0IGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG9uIG91ciBzY2FsZWQgcGVuZ3VpbiBkYXRhLg0KDQojIw0KDQpXZSBjYW4gdmlzdWFsaXNlIHRoZSByZXN1bHRzIG9mIHRoZSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyB1c2luZyBhICpkZW5kcm9ncmFtKi4NCg0KQ29tcGxldGUgdGhlIGNvZGUgYmVsb3cgdG8gY3JlYXRlIGEgZGVuZHJvZ3JhbSBmb3IgeW91ciByZXN1bHRzIChqdXN0IHJlcGxhY2UgdGhlIGAuLi5gIHdpdGggeW91ciByZXN1bHRzKQ0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IEYsIGVjaG8gPSBUfQ0KcGxvdCguLi4sIHdoaWNoID0gMikNCmBgYA0KDQojIw0KDQpUaGVyZSBhcmUgc2V2ZXJhbCBvcHRpb25zIHdlIGNhbiB1c2Ugd2hlbiBjb25kdWN0aW5nIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nLiBUaGVzZSByZWxhdGUgdG8gdGhlIHdheSBpbiB3aGljaCBkaXN0YW5jZSBpcyBtZWFzdXJlZCBiZXR3ZWVuIHR3byBwb2ludHMsIHRvIGRldGVybWluZSB0aGVpciBsZXZlbCBvZiBzaW1pbGFyaXR5LiANCg0KVGhlIGRlZmF1bHQgbWV0aG9kIGluIHRoZSBgYWduZXNgIGZ1bmN0aW9uIGlzIGBhdmVyYWdlYC4gVHJ5IHJlLXJ1bm5pbmcgeW91ciBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyBhbGdvcml0aG0gdXNpbmcgc29tZSBvdGhlciBvcHRpb25zLCBieSBhZGRpbmcgdGhlIGFyZ3VtZW50IGBtZXRob2QgPSAiLi4uImAgdG8geW91ciBjb2RlIGluIFxAcmVmKGhpZXJhcmNoaWNhbCksIGFuZCByZXBsYWNpbmcgdGhlIGAiLi4uImAgd2l0aCBgInNpbmdsZSJgLCBgImNvbXBsZXRlImAgYW5kIHRoZW4gYCJ3YXJkImAuIA0KDQpXaGVuIHlvdSB2aXN1YWxpc2UgdGhlIHJlc3VsdHMsIGRvIHRoZSBkZW5kcm9ncmFtcyBsb29rIHZlcnkgZGlmZmVyZW50Pw0KDQojIw0KDQpTbyBmYXIsIHRoZSBkZW5kcm9ncmFtcyB3ZSBoYXZlIHByb2R1Y2VkIGxvb2tlZCByYXRoZXIgdW5hcHBlYWxpbmcgLSB3ZSBjYW4gZG8gYmV0dGVyLg0KDQpJdCBpcyB3b3J0aCBub3RpbmcgdGhhdCB0aGUgc2VsZWN0aW9uIG9mIGFuIGFwcHJvcHJpYXRlIGhvcml6b250YWwgY3V0LW9mZiBwb2ludCBvbiB0aGUgZGVuZHJvZ3JhbSAodGh1cyBzZWxlY3RpbmcgdGhlIG51bWJlciBvZiBjbHVzdGVycyB0byB1c2UpIGlzIGFyYml0cmFyeS4gQmFzZWQgb24geW91ciBmaW5kaW5ncyBmcm9tIHRoZSBwcmV2aW91cyBhbmFseXNlcyBob3dldmVyLCB5b3Ugc2hvdWxkIG5vdyBoYXZlIGRlY2lkZWQgb24gYW4gJ29wdGltYWwnIG51bWJlciBvZiAkayQgY2x1c3RlcnMgdG8gc2VsZWN0LiANCg0KV2UgY2FuIHZpc3VhbGlzZSB0aGUgZGVuZHJvZ3JhbSB3aXRoIHRoZSB0cmVlIGN1dCBpbnRvIHRoZXNlICRrJCBjbHVzdGVycyB1c2luZyB0aGUgYGhjdXRgIGFuZCBgZnZpel9kZW5kYCBmdW5jdGlvbnMgZnJvbSB0aGUgYGZhY3RvZXh0cmFgIFIgcGFja2FnZS4NCg0KVGFrZSBhIGxvb2sgYXQgdGhlIGNvZGUgYmVsb3csIGFuZCBmaWxsIGluIHRoZSBgLi4uYCBtaXNzaW5nIHBhcnRzIHdpdGggdGhlIHJlbGV2YW50IGRldGFpbHMuIE9uY2UgeW91IGFyZSBoYXBweSB3aXRoIHRoZSBjb2RlLCBydW4gaXQuIFRoZSByZXN1bHRhbnQgZGVuZHJvZ3JhbSBzaG91bGQgbG9vayBtdWNoIG5pY2VyLg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IEYsIGVjaG8gPSBUfQ0KcGVuZ3Vpbl9kZW5kcm8gPC0gaGN1dCguLi4sIA0KICAgICAgICAgICAgICAgIGsgPSAuLi4sIA0KICAgICAgICAgICAgICAgIGhjX2Z1bmMgPSAiYWduZXMiLA0KICAgICAgICAgICAgICAgIGhjX21ldGhvZCA9ICIuLi4iLA0KICAgICAgICAgICAgICAgIGhjX21ldHJpYyA9ICJldWNsaWRlYW4iKSANCg0KZnZpel9kZW5kKHBlbmd1aW5fZGVuZHJvKQ0KYGBgDQoNCipOb3RlOiBZb3Ugd2lsbCBuZWVkIHRvIHNwZWNpZnkgdGhlIG1lYXN1cmVtZW50IG1ldGhvZCwgZS5nLiBgIndhcmQiYCBpbiB0aGUgYGhjX21ldGhvZCA9YCBhcmd1bWVudC4qDQoNCiMgRXh0ZW5zaW9uIDIgLSBGdXp6eSBDbHVzdGVyaW5nDQoNCkZvciB0aGUgcHJldmlvdXMgY2x1c3RlcmluZyBtZXRob2RzLCBlYWNoIGRhdGEgcG9pbnQgYmVsb25nZWQgdG8gb25seSBvbmUgY2x1c3Rlci4gVGhpcyBpcyBzb21ldGltZXMgcmVmZXJyZWQgdG8gYXMgKmhhcmQgY2x1c3RlcmluZyouIA0KDQpJbiBjb250cmFzdCwgaW4gZnV6enkgY2x1c3RlcmluZyBlYWNoIHBvaW50IGlzIGFsbG93ZWQgdG8gYmVsb25nIHRvIG11bHRpcGxlIGNsdXN0ZXJzLiBUaGUgbW9yZSBzaW1pbGFyIHRoZSBwb2ludCBpcyB0byBvdGhlciBwb2ludHMgaW4gYSBjbHVzdGVyLCB0aGUgaGlnaGVyIHRoZSBwb2ludCdzIHBlcmNlbnRhZ2Ugb2YgbWVtYmVyc2hpcCB0byB0aGF0IGNsdXN0ZXJeW1NlZSBlLmcuIEBNb2RTdGF0XS4NClRoaXMgY291bGQgYmUgaGVscGZ1bCB3aGVuIGEgcG9pbnQgbmF0dXJhbGx5IHNob3VsZCBiZWxvbmcgdG8gbXVsdGlwbGUgY2x1c3RlcnMgKGZvciBpbnN0YW5jZSwgaWYgcGVuZ3VpbnMgd2VyZSBjbHVzdGVyZWQgaW50byA2IGNsdXN0ZXJzLCBmb3IgbWFsZXMgYW5kIGZlbWFsZXMgb2YgZWFjaCBzcGVjaWVzLCB0aGVuIGVhY2ggcGVuZ3VpbiB3b3VsZCBiZWxvbmcgdG8gdHdvIGNsdXN0ZXJzKS4NCg0KIyMNCg0KV2UgY2FuIGVhc2lseSBjb25kdWN0IGZ1enp5IGNsdXN0ZXJpbmcgdXNpbmcgdGhlIGBmYW5ueWAgZnVuY3Rpb24gZnJvbSB0aGUgaW5idWlsdCBgY2x1c3RlcmAgUiBwYWNrYWdlLiBUaGUgcHJvY2VzcyBpcyBzaW1pbGFyIHRvIHRoZSBrLW1lYW5zIGNsdXN0ZXJpbmcgcHJvY2VzcyAoY29uZHVjdGVkIGluIFxAcmVmKGttZWFuczMpKSBhbmQgdGhlIFBBTSBjbHVzdGVyaW5nIHByb2Nlc3MgKGNvbmR1Y3RlZCBpbiBcQHJlZihwYW0pKS4NCg0KVXNlIHRoZSBgZmFubnlgIGZ1bmN0aW9uIHRvIGNvbmR1Y3QgZnV6enkgY2x1c3RlcmluZyBmb3IgJGskIHZhbHVlcyBvZiAyLCAzLCA0IGFuZCA1LiANCg0KKk5vdGU6IFRoZSBvdXRwdXQgZm9yIHRoZXNlIGFuYWx5c2VzIHdpbGwgaW5jbHVkZSBhIGBtZW1iZXJzaGlwIGNvZWZmaWNpZW50c2AgbGlzdCBvZiB0aGUgbWVtYmVyc2hpcCBwZXJjZW50YWdlcyBmb3IgZWFjaCBwb2ludC4gV2hpbGUgaXQgaXMgbm90IGVhc3kgdG8gdmlzdWFsaXNlIHRoZXNlLCB0aGUgb3V0cHV0IGFsc28gaW5jbHVkZXMgdGhlIGNsb3Nlc3QgJ2hhcmQnIGNsdXN0ZXIgZm9yIGVhY2ggcG9pbnQsIHdoaWNoIGNhbiBiZSBjYWxsZWQgdmlhIGAkY2x1c3RlcmAsIGFzIHBlciB0aGUgcHJldmlvdXMgY2x1c3RlcmluZyB0ZWNobmlxdWVzLioNCg0KIyMgeyNmdXp6eWJlc3R9DQoNCk5leHQsIHVzZSB0aGUgYGZ2aXpfbmJjbHVzdGAgZnVuY3Rpb24gdG8gZGV0ZXJtaW5lIHRoZSBiZXN0IG51bWJlciBvZiBmdXp6eSBjbHVzdGVycyB0byB1c2UuIEp1c3QgdXNlIHRoZSBgd3NzYCBhbmQgYHNpbGhvdWV0dGVgIG1ldGhvZHMgKHRoZSBgZ2FwX3N0YXRgIG1ldGhvZCBjYW4gdGFrZSBhIGxvbmcgdGltZSB0byBjb21wdXRlIGZvciBmdXp6eSBjbHVzdGVyaW5nKS4NCg0KKk5vdGU6IERvbid0IHdvcnJ5IGlmIGFueSB3YXJuaW5nIG1lc3NhZ2VzIGxpa2UgdGhlIG9uZSBzaG93biBpbiB0aGUgY29kZSBjaHVuayBiZWxvdyBhcHBlYXIgLSB5b3Ugc2hvdWxkIHN0aWxsIGJlIGFibGUgdG8gcHJvZHVjZSB0aGUgcGxvdHM6Kg0KDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUiLCBldmFsID0gRiwgZWNobyA9IFR9DQojIyBXYXJuaW5nIGluIEZVTmNsdXN0ZXIoeCwgaSwgLi4uKTogRkFOTlkgYWxnb3JpdGhtIGhhcyBub3QgY29udmVyZ2VkIGluICdtYXhpdCcgPQ0KIyMgNTAwIGl0ZXJhdGlvbnMNCmBgYA0KDQojIw0KDQpVc2UgdGhlIGBmdml6X2NsdXN0ZXJgIGZ1bmN0aW9uIGFuZCB0aGUgY29kZSBiZWxvdyB0byB2aXN1YWxpc2UgdGhlIGNsdXN0ZXJzIGZvciB0aGUgZnV6enkgY2x1c3RlcmluZyByZXN1bHQgeW91IGNob3NlIGFib3ZlIGluIFxAcmVmKGZ1enp5YmVzdCkuIFdoYXQgZG8geW91IGNvbmNsdWRlPw0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IEYsIGVjaG8gPSBUfQ0KIyBUaGlzIGNvZGUgYXNzdW1lcyB5b3UgYXJlIGFzc2Vzc2luZyBhIHJlc3VsdCBzdG9yZWQgaW4gdGhlIG9iamVjdCBmdXp6eV9maXQzDQp3aW5kb3dzKCkNCnBsb3QoYXMubnVtZXJpYyhwZW5ndWlucyRzcGVjaWVzKSwgY29sID0gZnV6enlfZml0MyRjbHVzdGVyKQ0KYGBgDQoNCjxicj4NCg0KIyMjIyBHcmVhdCB3b3JrLCB0aGF0J3MgZXZlcnl0aGluZyBmb3IgdG9kYXkhICMjIyMgey19DQoNClRoYXQgY29uY2x1ZGVzIG91ciB3b3JrIG9uIGNsdXN0ZXJpbmcgdGVjaG5pcXVlcy4gRGlkIHlvdSBoYXZlIGEgcHJlZmVyZW5jZSBmb3Igb25lIG9mIHRoZSBmb3VyIHRlY2huaXF1ZXMgaW50cm9kdWNlZCBpbiB0aGlzIGNvbXB1dGVyIGxhYj8NCg0KPGJyPg0KDQojIFJlZmVyZW5jZXMgey0gI1JlZn0NCjxkaXYgaWQ9InJlZnMiPjwvZGl2Pg0KDQo8YnI+DQoNCjxmb250IGNvbG9yID0gImdyZXkiPg0KVGhlc2Ugbm90ZXMgaGF2ZSBiZWVuIHByZXBhcmVkIGJ5IFJ1cGVydCBLdXZla2UuIFBsZWFzZSBub3RlIHRoYXQgc29tZSBvZiB0aGUgY29udGVudCBpbiB0aGVzZSBub3RlcyBoYXMgYmVlbiBkZXZlbG9wZWQgZnJvbSBjb250ZW50IGluIEBNb2RTdGF0LiBUaGUgY29weXJpZ2h0IGZvciB0aGUgbWF0ZXJpYWwgaW4gdGhlc2Ugbm90ZXMgcmVzaWRlcyB3aXRoIHRoZSBhdXRob3JzIG5hbWVkIGFib3ZlLCB3aXRoIHRoZSBEZXBhcnRtZW50IG9mIE1hdGhlbWF0aWNzIGFuZCBTdGF0aXN0aWNzIGFuZCB3aXRoIExhIFRyb2JlIFVuaXZlcnNpdHkuIENvcHlyaWdodCBpbiB0aGlzIHdvcmsgaXMgdmVzdGVkIGluIExhIFRyb2JlIFVuaXZlcnNpdHkgaW5jbHVkaW5nIGFsbCBMYSBUcm9iZSBVbml2ZXJzaXR5IGJyYW5kaW5nIGFuZCBuYW1pbmcuIFVubGVzcyBvdGhlcndpc2Ugc3RhdGVkLCBtYXRlcmlhbCB3aXRoaW4gdGhpcyB3b3JrIGlzIGxpY2Vuc2VkIHVuZGVyIGEgQ3JlYXRpdmUgQ29tbW9ucyBBdHRyaWJ1dGlvbi1Ob24gQ29tbWVyY2lhbC1Ob24gRGVyaXZhdGl2ZXMgTGljZW5zZSANCjxhIGhyZWYgPSAiaHR0cHM6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLW5kLzQuMC9DQyIgdGFyZ2V0PSJfYmxhbmsiPiBCWS1OQy1ORC4gPC9hPg0KPC9mb250Pg==