Data Science Stream
Topic 7B: Big Data I (Clustering)
Welcome to the seventh computer lab for the Data Science stream of STM1001.
In this computer lab we will carry out a variety of clustering analyses using penguin data from the palmerpenguins
R package (Horst, Hill, and Gorman 2020).
By the end of this lab, you should feel comfortable applying various clustering techniques in R. Let’s get started!
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 also dissimilar to other observations.
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 purposes of clustering are to:
- Analyse the data structure
- Relate the different elements of the data to each other, and then, most importantly
- 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 (optional)
- Hierarchical Clustering (optional)
- Fuzzy Clustering (optional)
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.
# Specify required packages
cluster_packages <- c("cluster", "factoextra", "ggfortify", "palmerpenguins")
# Install missing packages
install.packages(setdiff(cluster_packages, rownames(installed.packages())))
# Load all packages
lapply(cluster_packages, library, character.only = TRUE)
Note: If you are in the Data Science stream, 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 recent 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 numerous variables. The table below provides an example of some recorded values for these different variables (the column names), for 3 different penguins:
species
|
island
|
bill_length_mm
|
bill_depth_mm
|
flipper_length_mm
|
body_mass_g
|
sex
|
year
|
Adelie
|
Torgersen
|
39.1
|
18.7
|
181
|
3750
|
male
|
2007
|
Gentoo
|
Biscoe
|
49.2
|
15.2
|
221
|
6300
|
male
|
2007
|
Chinstrap
|
Dream
|
45.5
|
17.0
|
196
|
3500
|
female
|
2008
|
Run the R code below to produce a matrix of scatter plots for the different variables in the penguins
data set:
plot(penguins)
Note here that each scatter plot shows two variables from 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.
If the cluster technique works well, we may 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 two steps:
- First, we compute the sample mean and sample standard deviation for each variable.
- Second, we subtract the relevant sample mean from each observation, and then divide by the relevant sample standard deviation.
For example, across the whole data set 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, as outlined below.
Run the R code below to assign the normalised penguin data to the object penguins_scaled
. Before moving on, inspect your normalised data using the head
function.
penguins_scaled <- scale(penguins_subset)
\(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, which is a form of centroid-based clustering. A simplified description of \(k\)-means clustering is presented below.
We begin by arbitrarily choosing a number of clusters \(k\) to use.
Each data point from our data set is assigned to one of these \(k\) initial clusters, based on the distance between the point and the mean of all points.
The centroid, i.e. the mean of each cluster, is then calculated, and an iterative process begins:
- Points are moved between clusters, one point at a time, depending on how close they are to each centroid.
- This process continues, until no point can be moved between clusters without increasing the average distance between the points and the centroids. At this point the clustering stops.
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 3 clusters 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),
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 and 4.
Note down the between_ss /total_ss
results. What do you observe?
Diagnostics
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”)
For the purposes of this lab, we will focus on just the silhouette (see Rousseeuw 1987) and gap methods.
The silhouette method
The silhouette assessment method measures the similarity of a point to other points in its cluster.
- The silhouette value for each observation can be between 0 and 1.
- Values close to 0 suggest clusters are very similar, while values close to 1 indicate clusters are very different. Thus, the higher the average silhouette width value (across all points in a cluster), 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 gap statistic method
The second \(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.
Cluster plots
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.4.
fviz_cluster(kmeans_fit3, data = penguins_scaled)
In conclusion, based on all your \(k\)-means clustering analyses, which value of \(k\) would you recommend using, and why?
At this point, we’ve covered the main material for this computer lab - well done! 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: PAM Clustering
An alternative to \(k\)-means clustering is PAM (Partition around Medoids) clustering.
PAM clustering 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 and 4.
Next, use the fviz_nbclust
function to determine the best number of PAM clusters to use, via the silhouette method.
Hint: Remember to 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)
Extension 2: Hierarchical Clustering
Unlike the previous clustering techniques discussed, hierarchical clustering begins by assigning each point to its own cluster.
This means that, for our penguins_scaled
data set, we would start with 333 clusters - 1 for each penguin. Then, the two most similar clusters are merged (continuing our example, 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 penguins_scaled
data set.
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 linkage method options we can choose from 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"
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 3: 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 that 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 it would be reasonable for each penguin to 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 and 4.
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, via the silhouette method.
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?
References
Gan, G., C. Ma, and J. Wu. 2007.
Data Clustering: Theory, Algorithms, and Applications. ASA-SIAM Series on Statistics and Applied Probability ; 20. Philadelphia, Pa.: Society for Industrial; Applied Mathematics (SIAM, 3600 Market Street, Floor 6, Philadelphia, PA 19104). https://doi.org/
https://doi.org/10.1137/1.9780898718348.
Horikoshi, M., Y. Tang, A. Dickey, M. Grenie, R. Thompson, L. Selzer, D. Strbenac, K. Voronin, and D. Pulatov. 2021.
ggfortify: Data Visualization Tools for Statistical Analysis Results.
https://github.com/sinhrks/ggfortify.
Horst, Allison Marie, Alison Presmanes Hill, and Kristen B Gorman. 2020.
Palmerpenguins: Palmer Archipelago (Antarctica) Penguin Data.
https://doi.org/10.5281/zenodo.3960218.
Kassambara, A., and F. Mundt. 2020.
factoextra: Extract and Visualize the Results of Multivariate Data Analyses.
http://www.sthda.com/english/rpkgs/factoextra.
Mirkin, B. 1996. Mathematical Classification and Clustering. 1st ed. 1996.. Nonconvex Optimization and Its Applications, 11.
R Core Team. 2021.
R: A Language and Environment for Statistical Computing. Vienna, Austria: R Foundation for Statistical Computing.
https://www.R-project.org/.
Rousseeuw, P. J. 1987. “Silhouette: A Graphical Aid to the Interpretation and Validation of Cluster Analysis.” Computational and Applied Mathematics 20: 53–65.
Thulin, M. 2021. Modern Statistics with R: From Wrangling and Exploring Data to Inference and Predictive Modelling.
These notes have been prepared by Rupert Kuveke. Please note that some of the content in these notes has been developed from content in Thulin (2021). The copyright for the material in these notes resides with the authors named above, with the Department of Mathematical and Physical Sciences and with La Trobe University. Copyright in this work is vested in La Trobe University including all La Trobe University branding and naming. Unless otherwise stated, material within this work is licensed under a Creative Commons Attribution-Non Commercial-Non Derivatives License
BY-NC-ND.
LS0tDQp0aXRsZTogIlNUTTEwMDE6IENvbXB1dGVyIExhYiA3QiINCm91dHB1dDoNCiAgYm9va2Rvd246Omh0bWxfZG9jdW1lbnQyOiANCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgdGhlbWU6IHJlYWRhYmxlDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQpiaWJsaW9ncmFwaHk6IFNUTTEwMDFfRFNfQ0xfcmVmZXJlbmNlcy5iaWIgDQpsaW5rLWNpdGF0aW9uczogeWVzDQotLS0NCg0KPHN0eWxlPg0KI1RPQyB7DQogIGJhY2tncm91bmQ6IHVybCgiaHR0cHM6Ly93d3cubGF0cm9iZS5lZHUuYXUvX21lZGlhL2xhLXRyb2JlLWFwaS92NS9pbWcvbG9nby5zdmciKTsNCiAgYmFja2dyb3VuZC1zaXplOiBjb250YWluOw0KICBwYWRkaW5nLXRvcDogODBweCAhaW1wb3J0YW50Ow0KICBiYWNrZ3JvdW5kLXJlcGVhdDogbm8tcmVwZWF0Ow0KfQ0KPC9zdHlsZT4NCg0KIyMjIERhdGEgU2NpZW5jZSBTdHJlYW0gey19DQoNCiMjIyBUb3BpYyA3QjogQmlnIERhdGEgSSAoQ2x1c3RlcmluZykgey19DQoNCjxicj4NCg0KV2VsY29tZSB0byB0aGUgc2V2ZW50aCBjb21wdXRlciBsYWIgZm9yIHRoZSBEYXRhIFNjaWVuY2Ugc3RyZWFtIG9mIFNUTTEwMDEuDQoNCkluIHRoaXMgY29tcHV0ZXIgbGFiIHdlIHdpbGwgY2Fycnkgb3V0IGEgdmFyaWV0eSBvZiBjbHVzdGVyaW5nIGFuYWx5c2VzIHVzaW5nIHBlbmd1aW4gZGF0YSBmcm9tIHRoZSBgcGFsbWVycGVuZ3VpbnNgIFIgcGFja2FnZSBbQHBlbmd1aW5zXS4NCg0KQnkgdGhlIGVuZCBvZiB0aGlzIGxhYiwgeW91IHNob3VsZCBmZWVsIGNvbWZvcnRhYmxlIGFwcGx5aW5nIHZhcmlvdXMgY2x1c3RlcmluZyB0ZWNobmlxdWVzIGluIFIuIExldCdzIGdldCBzdGFydGVkIQ0KDQojIENsdXN0ZXIgQW5hbHlzaXMNCg0KQSAqKmNsdXN0ZXIqKiBpcyBhIHN1YnNldCBvZiBhIGRhdGEgc2V0IHRoYXQgY29uc2lzdHMgb2Ygb2JzZXJ2YXRpb25zIHdoaWNoLCB1c2luZyBhIGNob3NlbiBtZXRyaWMsIGFyZSAqKnNpbWlsYXIgdG8gZWFjaCBvdGhlciwgYW5kIGFsc28gZGlzc2ltaWxhciB0byBvdGhlciBvYnNlcnZhdGlvbnMqKl5bQE1hdGhDbHVzdGVyLCBwLjI1XS4NCg0KQ2x1c3RlciBBbmFseXNpcyBpcyB0aGUgbWV0aG9kIG9mIGdyb3VwaW5nIGRhdGEgaW50byBjbHVzdGVycywgdXNpbmcgYSBjbHVzdGVyaW5nIHRlY2huaXF1ZS4gVGhlcmUgaXMgYSBsYXJnZSB2YXJpZXR5IG9mIGNsdXN0ZXJpbmcgdGVjaG5pcXVlcywgYW5kIGVhY2ggb2YgdGhlc2UgdGVjaG5pcXVlcyB1c2VzIGEgZGlmZmVyZW50IG1ldHJpYyBmb3IgZGV0ZXJtaW5pbmcgdGhlIHNpbWlsYXJpdHkgYmV0d2VlbiBvYnNlcnZhdGlvbnMuIERvbid0IHdvcnJ5LCB3ZSB3b24ndCBnbyBpbnRvIGFsbCB0aGUgY29tcGxpY2F0ZWQgbWF0aGVtYXRpY3MgaW52b2x2ZWQgaW4gdGhlc2UgdGVjaG5pcXVlcyAtIG91ciBmb2N1cyBpcyBvbiBsZWFybmluZyBob3cgdG8gY29uZHVjdCBjbHVzdGVyIGFuYWx5c2VzIGluIFIuDQoNCiMjIyBQdXJwb3NlIHstfQ0KDQpDbHVzdGVyaW5nIGlzIG9mdGVuIHVzZWQgYXMgcGFydCBvZiBhbiBleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzIHByb2NlZHVyZSwgdG8gdW5jb3ZlciBoaWRkZW4gcGF0dGVybnMgaW4gYSBuZXcgZGF0YSBzZXQuDQpUaGUgbWFpbiBwdXJwb3NlcyBvZiBjbHVzdGVyaW5nXltzZWUgZS5nLiBATWF0aENsdXN0ZXIsIHAuMjVdIGFyZSB0bzoNCg0KMS4gQW5hbHlzZSB0aGUgZGF0YSBzdHJ1Y3R1cmUNCjIuIFJlbGF0ZSB0aGUgZGlmZmVyZW50IGVsZW1lbnRzIG9mIHRoZSBkYXRhIHRvIGVhY2ggb3RoZXIsIGFuZCB0aGVuLCBtb3N0IGltcG9ydGFudGx5DQozLiBBaWQgaW4gY2xhc3NpZnlpbmcgdGhlIGRhdGEgaW50byBjZXJ0YWluIGNsYXNzZXMNCg0KSXQgaXMgd29ydGggbm90aW5nIHRoYXQgaW4gZ2VuZXJhbCwgd2UgbWF5IG5vdCBiZSBhd2FyZSBvZiB3aGF0IHRoZXNlIGNsYXNzZXMgYXJlLCBwcmlvciB0byB0aGUgY2x1c3RlcmluZyBhbmFseXNpc15bc2VlIGUuZy4gQERhdGFDbHVzdGVyXS4NCg0KIyMjIENsdXN0ZXJpbmcgVGVjaG5pcXVlcyB7LX0NCg0KSW4gdGhpcyBjb21wdXRlciBsYWIsIHdlIHdpbGwgYXBwbHkgdGhlIGZvbGxvd2luZyBwb3B1bGFyIGNsdXN0ZXJpbmcgdGVjaG5pcXVlczoNCg0KKiAkayQtbWVhbnMgQ2x1c3RlcmluZw0KKiBQQU0gQ2x1c3RlcmluZyAob3B0aW9uYWwpDQoqIEhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nIChvcHRpb25hbCkNCiogRnV6enkgQ2x1c3RlcmluZyAob3B0aW9uYWwpDQoNCldlIHdpbGwgcHJvdmlkZSBicmllZiBleHBsYW5hdGlvbnMgb2YgZWFjaCBvZiB0aGVzZSB0ZWNobmlxdWVzIGFzIHdlIHdvcmsgdGhyb3VnaCB0aGlzIGNvbXB1dGVyIGxhYi4NCg0KIyBQcmVwYXJhdGlvbnMgeyNwcmVwfQ0KDQpJbiBSLCB3ZSBjYW4gdXNlIHRoZSBpbmJ1aWx0IGBjbHVzdGVyYCBwYWNrYWdlIHRvIGNhcnJ5IG91dCBhIHJhbmdlIG9mIGNsdXN0ZXIgYW5hbHlzZXMuIEhvd2V2ZXIsIHdlIHdpbGwgYWxzbyByZXF1aXJlIGEgZmV3IGFkZGl0aW9uYWwgcGFja2FnZXNeW3RoZSBgZmFjdG9leHRyYWAgcGFja2FnZSB3YXMgY3JlYXRlZCBieSBAZmFjdG8gYW5kIHRoZSBgZ2dmb3J0aWZ5YCBwYWNrYWdlIHdhcyBjcmVhdGVkIGJ5IEBnZy4gVGhlIGBjbHVzdGVyYCBwYWNrYWdlIGlzIHBhcnQgb2YgdGhlIGJhc2UgUiBzdWl0ZSBvZiBwYWNrYWdlcyBbQFJdLl0uIFBsZWFzZSBydW4gdGhlIFIgY29kZSBiZWxvdyB0byBpbnN0YWxsIGFuZCBsb2FkIHRoZXNlIHBhY2thZ2VzLg0KDQpgYGB7ciBldmFsID0gVCwgaW5jbHVkZSA9IEZ9DQpsaWJyYXJ5KGNsdXN0ZXIpDQpsaWJyYXJ5KGZhY3RvZXh0cmEpDQpsaWJyYXJ5KGdnZm9ydGlmeSkNCmxpYnJhcnkocGFsbWVycGVuZ3VpbnMpDQppbnN0YWxsLnBhY2thZ2VzKHNldGRpZmYoImthYmxlRXh0cmEiLCByb3duYW1lcyhpbnN0YWxsZWQucGFja2FnZXMoKSkpKQ0KbGlicmFyeShrYWJsZUV4dHJhKQ0KYGBgDQoNCmBgYHtyIGV2YWwgPSBGLCBlY2hvID0gVH0NCiMgU3BlY2lmeSByZXF1aXJlZCBwYWNrYWdlcw0KY2x1c3Rlcl9wYWNrYWdlcyA8LSBjKCJjbHVzdGVyIiwgImZhY3RvZXh0cmEiLCAiZ2dmb3J0aWZ5IiwgInBhbG1lcnBlbmd1aW5zIikNCiMgSW5zdGFsbCBtaXNzaW5nIHBhY2thZ2VzDQppbnN0YWxsLnBhY2thZ2VzKHNldGRpZmYoY2x1c3Rlcl9wYWNrYWdlcywgcm93bmFtZXMoaW5zdGFsbGVkLnBhY2thZ2VzKCkpKSkNCiMgTG9hZCBhbGwgcGFja2FnZXMNCmxhcHBseShjbHVzdGVyX3BhY2thZ2VzLCBsaWJyYXJ5LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpDQpgYGANCg0KKk5vdGU6IElmIHlvdSBhcmUgaW4gdGhlIERhdGEgU2NpZW5jZSBzdHJlYW0sIHlvdSBzaG91bGQgYWxyZWFkeSBoYXZlIHRoZSBgcGFsbWVycGVuZ3VpbnNgIHBhY2thZ2UgaW5zdGFsbGVkLioNCg0KIyMgUGVuZ3VpbiBEYXRhDQoNClRoZSBgcGVuZ3VpbnNgIGRhdGEgc2V0IGluIHRoZSBgcGFsbWVycGVuZ3VpbnNgIFIgcGFja2FnZSBbQHBlbmd1aW5zXSBjb250YWlucyByZWNlbnQgZGF0YSBvbiAzIHNwZWNpZXMgb2YgcGVuZ3VpbiAoQWRlbGllLCBDaGluc3RyYXAgYW5kIEdlbnRvbykgbGl2aW5nIG9uIGlzbGFuZHMgaW4gdGhlIFBhbG1lciBhcmNoaXBlbGFnbywgb2ZmIHRoZSBjb2FzdCBvZiBBbnRhcmN0aWNhLiBXZSB3aWxsIHVzZSB0aGlzIGRhdGEgZm9yIG91ciBjbHVzdGVyaW5nIGFuYWx5c2VzLg0KDQpUaGlzIHBlbmd1aW4gZGF0YSBjb250YWlucyBpbmZvcm1hdGlvbiBvbiBudW1lcm91cyB2YXJpYWJsZXMuIFRoZSB0YWJsZSBiZWxvdyBwcm92aWRlcyBhbiBleGFtcGxlIG9mIHNvbWUgcmVjb3JkZWQgdmFsdWVzIGZvciB0aGVzZSBkaWZmZXJlbnQgdmFyaWFibGVzICh0aGUgY29sdW1uIG5hbWVzKSwgZm9yIDMgZGlmZmVyZW50IHBlbmd1aW5zOg0KDQpgYGB7ciBldmFsID0gVCwgZWNobyA9IEZ9DQpwZW5ndWluc1tjKDEsMTcwLDMyMCksXSAlPiUNCiAga2JsKCkgJT4lDQogIGthYmxlX3BhcGVyKCJob3ZlciIsIGZ1bGxfd2lkdGggPSBGKQ0KYGBgDQoNCiMjIHsjbWF0cml4cGxvdHN9DQoNClJ1biB0aGUgUiBjb2RlIGJlbG93IHRvIHByb2R1Y2UgYSBtYXRyaXggb2Ygc2NhdHRlciBwbG90cyBmb3IgdGhlIGRpZmZlcmVudCB2YXJpYWJsZXMgaW4gdGhlIGBwZW5ndWluc2AgZGF0YSBzZXQ6DQoNCmBgYHtyIGV2YWwgPSBGLCBlY2hvID0gVH0NCnBsb3QocGVuZ3VpbnMpDQpgYGANCg0KTm90ZSBoZXJlIHRoYXQgZWFjaCBzY2F0dGVyIHBsb3Qgc2hvd3MgdHdvIHZhcmlhYmxlcyBmcm9tIHRoZSBgcGVuZ3VpbnNgIGRhdGEgc2V0LCBwbG90dGVkIGFnYWluc3QgZWFjaCBvdGhlci4NCg0KVGhlIHZhcmlhYmxlIG5hbWVzIGFyZSBzaG93biBvbiB0aGUgZGlhZ29uYWwgYm94ZXMgLSBqdXN0IGNyb3NzIHJlZmVyZW5jZSBhIHBsb3QgdG8gdGhlIHZhcmlhYmxlIG5hbWVzIGluIHRoYXQgcm93IGFuZCBjb2x1bW4gdG8gZGV0ZXJtaW5lIHRoZSB2YXJpYWJsZXMgcGxvdHRlZCBvbiB0aGUgJHkkLWF4aXMgYW5kICR4JC1heGlzIHJlc3BlY3RpdmVseS4NCg0KKkhpbnQ6IEZvciBleGFtcGxlLCB0aGUgcGxvdCBzaG93biBpbiB0aGUgc2l4dGggcm93LCBmaWZ0aCBjb2x1bW4gc2hvdWxkIGJlIGBib2R5X21hc3NfZ2AgcGxvdHRlZCBvbiB0aGUgeS1heGlzIGFnYWluc3QgYGZsaXBwZXJfbGVuZ3RoX21tYCBvbiB0aGUgeC1heGlzLioNCg0KIyMNCg0KTG9va2luZyBhdCB0aGUgc2NhdHRlciBwbG90cyBmb3IgdGhlIGNvbnRpbnVvdXMgcmFuZG9tIHZhcmlhYmxlcywgZG9lcyBpdCBhcHBlYXIgdGhhdCB0aGVyZSBhcmUgYW55IG5hdHVyYWwgY2x1c3RlcnMgdGhhdCB3aWxsIGJlIGVhc3kgdG8gaWRlbnRpZnkgdXNpbmcgYSBjbHVzdGVyaW5nIHRlY2huaXF1ZT8NCg0KIyMgUHJlcGFyaW5nIGRhdGEgZm9yIENsdXN0ZXIgQW5hbHlzaXMNCg0KRm9yIHRoZSBwdXJwb3NlcyBvZiB0aGlzIGNvbXB1dGVyIGxhYiwgd2Ugd2lsbCBhc3N1bWUgdGhhdCB3ZSBkb24ndCBhY3R1YWxseSBoYXZlIGRhdGEgb24gdGhlIGBzcGVjaWVzYCBvZiBlYWNoIHBlbmd1aW4gaW4gdGhlIGBwZW5ndWluc2AgZGF0YSBzZXQuDQpJbnN0ZWFkLCB3ZSB3aWxsIGNvbmR1Y3QgY2x1c3RlciBhbmFseXNlcyB1c2luZyB0aGUgb3RoZXIgaW5mb3JtYXRpb24gY29udGFpbmVkIGluIHRoaXMgZGF0YSBzZXQuDQoNCklmIHRoZSBjbHVzdGVyIHRlY2huaXF1ZSB3b3JrcyB3ZWxsLCB3ZSBtYXkgZW5kIHVwIHdpdGggY2x1c3RlcnMgZm9yIGVhY2ggcGVuZ3VpbiBzcGVjaWVzIQ0KDQpIb3dldmVyLCBiZWZvcmUgd2UgYmVnaW4gb3VyIGNsdXN0ZXJpbmcgYW5hbHlzZXMsIHdlIHdpbGwgbmVlZCB0byBlbnN1cmUgdGhhdCBvdXIgZGF0YSBpcyBpbiB0aGUgYXBwcm9wcmlhdGUgZm9ybWF0Lg0KSXQgaXMgaW1wb3J0YW50IHRvIG5vdGUgdGhhdCBjbHVzdGVyaW5nIGFsZ29yaXRobXMgb25seSB3b3JrIG9uIGNvbnRpbnVvdXMgdmFyaWFibGVzLiBBcyB3ZSBoYXZlIHNlZW4sIHRoZSBgcGVuZ3VpbnNgIGRhdGEgc2V0IGNvbnRhaW5zIGJvdGggY29udGludW91cyBhbmQgY2F0ZWdvcmljYWwgKGRpc2NyZXRlKSB2YXJpYWJsZXMuIA0KDQpUaGVyZWZvcmUsIGZvciB0aGUgdGltZSBiZWluZywgd2Ugd2lsbCBvbmx5IHVzZSBhIHN1YnNldCBvZiB0aGUgdmFyaWFibGVzIGluIHRoZSBgcGVuZ3VpbnNgIGRhdGEgc2V0LCBuYW1lbHkgYGJpbGxfbGVuZ3RoX21tYCwgYGJpbGxfZGVwdGhfbW1gLCBgZmxpcHBlcl9sZW5ndGhfbW1gIGFuZCBgYm9keV9tYXNzX2dgLg0KDQojIyMNCg0KUnVuIHRoZSBSIGNvZGUgYmVsb3cgdG8gcmVtb3ZlIG1pc3NpbmcgdmFsdWVzIGZyb20gb3VyIGRhdGEsIGFuZCBjcmVhdGUgYSBzdWJzZXQgb2YgdGhlIGBwZW5ndWluc2AgZGF0YSB0aGF0IG9ubHkgY29udGFpbnMgdGhlIGNvbnRpbnVvdXMgdmFyaWFibGVzOg0KDQpgYGB7ciBldmFsID0gVCwgZWNobyA9IFR9DQpwZW5ndWlucyA8LSBuYS5vbWl0KHBlbmd1aW5zKQ0KcGVuZ3VpbnNfc3Vic2V0IDwtIHBlbmd1aW5zWywgMzo2XQ0KYGBgDQoNCiMjIw0KDQpCZWZvcmUgY29uZHVjdGluZyBhIGNsdXN0ZXIgYW5hbHlzaXMsIGl0IGlzIGFsc28gZ2VuZXJhbGx5IGEgZ29vZCBpZGVhIHRvIG5vcm1hbGlzZSBvdXIgZGF0YSAodGhpcyBwcm9jZXNzIGlzIGxpa2UgdGhlIHN0YW5kYXJkaXNhdGlvbiBwcm9jZXNzIGNvdmVyZWQgaW4gW3NlY3Rpb24gNC4yIG9mIFRvcGljIDNdKGh0dHBzOi8vYm9va2Rvd24ub3JnL2Ffc2hha2VyL1NUTTEwMDFfVG9waWNfMy80LTItc3RhbmRhcmRpc2F0aW9uLmh0bWwpKS4gVGhpcyB3aWxsIHJlbW92ZSB0aGUgZWZmZWN0IG9mIGRpZmZlcmVudCB2YXJpYWJsZXMgYmVpbmcgbWVhc3VyZWQgb24gZGlmZmVyZW50IHNjYWxlcyAoZS5nLiBmbGlwcGVyIGxlbmd0aCBpbiBtbSBhbmQgYm9keSBtYXNzIGluIGdyYW1zKSwgYW5kIGNhbiBwcmV2ZW50IGFueSBzaW5nbGUgdmFyaWFibGUgb3ZlcnBvd2VyaW5nIG90aGVycyB3aGVuIGJlaW5nIGFzc2Vzc2VkIGJ5IHRoZSBjbHVzdGVyIGFsZ29yaXRobS4NCg0KTm9ybWFsaXNpbmcgdGhlIGRhdGEgY29uc2lzdHMgb2YgdHdvIHN0ZXBzOiANCg0KMS4gRmlyc3QsIHdlIGNvbXB1dGUgdGhlIHNhbXBsZSBtZWFuIGFuZCBzYW1wbGUgc3RhbmRhcmQgZGV2aWF0aW9uIGZvciBlYWNoIHZhcmlhYmxlLg0KMi4gU2Vjb25kLCB3ZSBzdWJ0cmFjdCB0aGUgcmVsZXZhbnQgc2FtcGxlIG1lYW4gZnJvbSBlYWNoIG9ic2VydmF0aW9uLCBhbmQgdGhlbiBkaXZpZGUgYnkgdGhlIHJlbGV2YW50IHNhbXBsZSBzdGFuZGFyZCBkZXZpYXRpb24uDQoNCkZvciBleGFtcGxlLCBhY3Jvc3MgdGhlIHdob2xlIGRhdGEgc2V0IHRoZSBzYW1wbGUgbWVhbiBmb3IgYGJpbGxfZGVwdGhfbW1gIGlzIHJvdWdobHkgMTcuMTUxLCBhbmQgdGhlIHNhbXBsZSBzdGFuZGFyZCBkZXZpYXRpb24gaXMgcm91Z2hseSAxLjk3NS4gVGhlIGZpcnN0IHBlbmd1aW4gaW4gdGhlIGRhdGEgc2V0IGhhcyBhIHJlY29yZGVkIGBiaWxsX2RlcHRoX21tYCB2YWx1ZSBvZiAxOC43LiBUaGUgbm9ybWFsaXNlZCB2ZXJzaW9uIG9mIHRoaXMgdmFsdWUgd291bGQgdGhlcmVmb3JlIGJlICQkXGRpc3BsYXlzdHlsZSBcZnJhY3sxOC43IC0gMTcuMTUxfXsxLjk3NX0gXGFwcHJveCAwLjc4LiQkIA0KDQpGb3J0dW5hdGVseSwgd2UgY2FuIGNvbmR1Y3QgdGhpcyBub3JtYWxpc2F0aW9uIHByb2Nlc3Mgd2l0aCBvbmUgbGluZSBvZiBjb2RlIGluIFIsIGZvciBhbGwgb3VyIHZhcmlhYmxlcywgYXMgb3V0bGluZWQgYmVsb3cuDQoNCiMjIw0KDQpSdW4gdGhlIFIgY29kZSBiZWxvdyB0byBhc3NpZ24gdGhlIG5vcm1hbGlzZWQgcGVuZ3VpbiBkYXRhIHRvIHRoZSBvYmplY3QgYHBlbmd1aW5zX3NjYWxlZGAuIEJlZm9yZSBtb3Zpbmcgb24sIGluc3BlY3QgeW91ciBub3JtYWxpc2VkIGRhdGEgdXNpbmcgdGhlIGBoZWFkYCBmdW5jdGlvbi4NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBpbmNsdWRlID0gVH0NCnBlbmd1aW5zX3NjYWxlZCA8LSBzY2FsZShwZW5ndWluc19zdWJzZXQpDQpgYGANCg0KIyAkayQtbWVhbnMgQ2x1c3RlcmluZw0KDQpOb3cgdGhhdCBvdXIgZGF0YSBpcyBzdWl0YWJseSBwcmVwYXJlZCwgd2UgY2FuIGJlZ2luIG91ciBjbHVzdGVyIGFuYWx5c2VzLg0KDQpUaGUgZmlyc3QgY2x1c3RlcmluZyB0ZWNobmlxdWUgd2Ugd2lsbCBjb25zaWRlciBpcyAqJGskLW1lYW5zIGNsdXN0ZXJpbmcqLCB3aGljaCBpcyBhIGZvcm0gb2YgY2VudHJvaWQtYmFzZWQgY2x1c3RlcmluZy4gQSBzaW1wbGlmaWVkIGRlc2NyaXB0aW9uIG9mICRrJC1tZWFucyBjbHVzdGVyaW5nIGlzIHByZXNlbnRlZCBiZWxvdy4NCg0KKiBXZSBiZWdpbiBieSBhcmJpdHJhcmlseSBjaG9vc2luZyBhIG51bWJlciBvZiBjbHVzdGVycyAkayQgdG8gdXNlLg0KDQoqIEVhY2ggZGF0YSBwb2ludCBmcm9tIG91ciBkYXRhIHNldCBpcyBhc3NpZ25lZCB0byBvbmUgb2YgdGhlc2UgJGskIGluaXRpYWwgY2x1c3RlcnMsIGJhc2VkIG9uIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHRoZSBwb2ludCBhbmQgdGhlIG1lYW4gb2YgYWxsIHBvaW50cy4gDQoNCiogVGhlIGNlbnRyb2lkLCBpLmUuIHRoZSBtZWFuIG9mIGVhY2ggY2x1c3RlciwgaXMgdGhlbiBjYWxjdWxhdGVkLCBhbmQgYW4gaXRlcmF0aXZlIHByb2Nlc3MgYmVnaW5zOiANCg0KICAqIFBvaW50cyBhcmUgbW92ZWQgYmV0d2VlbiBjbHVzdGVycywgb25lIHBvaW50IGF0IGEgdGltZSwgZGVwZW5kaW5nIG9uIGhvdyBjbG9zZSB0aGV5IGFyZSB0byBlYWNoIGNlbnRyb2lkLg0KICAqIFRoaXMgcHJvY2VzcyBjb250aW51ZXMsIHVudGlsIG5vIHBvaW50IGNhbiBiZSBtb3ZlZCBiZXR3ZWVuIGNsdXN0ZXJzIHdpdGhvdXQgaW5jcmVhc2luZyB0aGUgYXZlcmFnZSBkaXN0YW5jZSBiZXR3ZWVuIHRoZSBwb2ludHMgYW5kIHRoZSBjZW50cm9pZHNeW1NlZSBlLmcuIHNlY3Rpb24gNC4xMC4zIG9mIEBNb2RTdGF0XS4gQXQgdGhpcyBwb2ludCB0aGUgY2x1c3RlcmluZyBzdG9wcy4NCg0KIyMgeyNrbWVhbnMzfQ0KDQpXZSBjYW4gdXNlIHRoZSBga21lYW5zYCBmdW5jdGlvbiBmcm9tIHRoZSBpbmJ1aWx0IGBjbHVzdGVyYCBSIHBhY2thZ2UgdG8gY29uZHVjdCBhICRrJC1tZWFucyBjbHVzdGVyaW5nIGFuYWx5c2lzLg0KUnVuIHRoZSBmb2xsb3dpbmcgUiBjb2RlIHRvIGNvbmR1Y3QgdGhpcyBhbmFseXNpcyBvbiBvdXIgYHBlbmd1aW5zX3NjYWxlZGAgZGF0YSwgd2l0aCAzIGNsdXN0ZXJzIHNwZWNpZmllZC4NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVH0NCmttZWFuc19maXQzIDwtIGttZWFucyhwZW5ndWluc19zY2FsZWQsIDMpDQpgYGANCg0KIyMNCg0KTGV0J3MgZGlzY3VzcyB0aGUgcmVzdWx0cy4gSWYgd2UgcnVuIHRoZSBsaW5lIGBrbWVhbnNfZml0M2AsIGEgbGlzdCBvZiBvdXRwdXQgYXBwZWFycyBpbiBSLg0KDQpXZSBhcmUgcHJpbWFyaWx5IGludGVyZXN0ZWQgaW4gdGhlIGNsdXN0ZXIgbWVtYmVyc2hpcCBhc3NpZ25lZCBieSB0aGUgYWxnb3JpdGhtLg0KV2UgY2FuIGV4dHJhY3QgdGhpcyBjbHVzdGVyIG1lbWJlcnNoaXAgdXNpbmcgYGttZWFuc19maXQzJGNsdXN0ZXJgLg0KDQpJdCBpcyBhbHNvIGltcG9ydGFudCB0byBjaGVjayB0aGUgZGV0YWlscyBpbiB0aGUgYFdpdGhpbiBjbHVzdGVyIHN1bSBvZiBzcXVhcmVzIGJ5IGNsdXN0ZXJgIHNlY3Rpb24uDQpIZXJlOiANCg0KKiBgYmV0d2Vlbl9TU2AgZGVub3RlcyB0aGUgc3VtIG9mIHNxdWFyZXMgYmV0d2VlbiBjbHVzdGVycyAoaS5lLiwgaG93IHdlbGwgdGhlIGNsdXN0ZXJzIGFyZSBzZXBhcmF0ZWQgZnJvbSBlYWNoIG90aGVyKSwNCiogYHRvdGFsX3NzYCBkZW5vdGVzIHRoZSB0b3RhbCB2YXJpYWJpbGl0eSBpbiB0aGUgZGF0YS4NCiogVGhlIGNhbGN1bGF0aW9uIGBiZXR3ZWVuX3NzIC90b3RhbF9zc2AgdGVsbHMgdXMgaG93IG11Y2ggb2YgdGhlIHZhcmlhYmlsaXR5IGluIHRoZSBkYXRhIGlzIGFjY291bnRlZCBmb3IgYnkgdGhlIGNsdXN0ZXJzIGZvdW5kIGJ5IHRoZSBhbGdvcml0aG0uIFRoaXMgdmFsdWUgY2FuIHJhbmdlIGZyb20gMCUgdG8gMTAwJSwgd2l0aCBsYXJnZXIgdmFsdWVzIGluZGljYXRpbmcgYSBiZXR0ZXIgcmVzdWx0LiBIZXJlLCA3Mi4xJSBpcyBkZWNlbnQsIGFsdGhvdWdoIHdlIG1pZ2h0IGhhdmUgZXhwZWN0ZWQgYSBzbGlnaHRseSBoaWdoZXIgcmVzdWx0Lg0KDQojIyBWaXN1YWxpc2luZyBvdXIgcmVzdWx0cyB7I2ttZWFuc3Zpc3VhbGlzZX0NCg0KV2UgY2FuIHZpc3VhbGlzZSBvdXIgcmVzdWx0cyBpbiBzZXZlcmFsIHdheXMuDQoNCkZpcnN0bHksIHJ1biB0aGUgZm9sbG93aW5nIGNvZGUgdG8gcHJvZHVjZSBhIG1hdHJpeCBvZiBzY2F0dGVyIHBsb3RzIG9mIHRoZSBjb250aW51b3VzIHZhcmlhYmxlcywgY29sb3VyZWQgYnkgY2x1c3Rlci4gDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFR9DQp3aW5kb3dzKCkgIyBvciBxdWFydHooKSBpZiB5b3UgYXJlIG9uIGEgTWFjDQpwbG90KHBlbmd1aW5zWywgYygxLDM6NildLCBjb2wgPSBrbWVhbnNfZml0MyRjbHVzdGVyKQ0KYGBgDQoNCkRvbid0IHdvcnJ5IGlmIHRoZSBwbG90cyBwcm9kdWNlZCBsb29rIGEgYml0IGNvbmZ1c2luZy4gVG8gbWFrZSB0aGUgcGxvdHMgZWFzaWVyIHRvIHJlYWQsIGNvbnNpZGVyIG1heGltaXNpbmcgdGhlIGdyYXBoaWNzIHdpbmRvdy4gDQoNClJlY2FsbCBmcm9tIFxAcmVmKG1hdHJpeHBsb3RzKSB0aGF0IGVhY2ggc2NhdHRlciBwbG90IGhlcmUgc2hvd3MgdHdvIG9mIHRoZSB2YXJpYWJsZXMgaW4gdGhlIGBwZW5ndWluc2AgZGF0YSBzZXQgcGxvdHRlZCBhZ2FpbnN0IGVhY2ggb3RoZXIuIE5vdyBob3dldmVyLCB0aGV5IGFyZSBjb2xvdXJlZCBieSBjbHVzdGVyIChhcyBkZXRlcm1pbmVkIGJ5IHRoZSAkayQtbWVhbnMgbWV0aG9kKS4NCg0KSWYsIGJ5IGluc3BlY3RpbmcgdGhlc2UgcGxvdHMsIHlvdSBjYW4gY2xlYXJseSBkaXN0aW5ndWlzaCBiZXR3ZWVuIGNsdXN0ZXJzLCB0aGVuIHRoZSBjbHVzdGVyaW5nIG1ldGhvZCBoYXMgcGVyZm9ybWVkIHdlbGwuDQoNCipIaW50OiBJZiB5b3UgYXJlIGhhdmluZyB0cm91YmxlIGludGVycHJldGluZyB0aGUgZ3JhcGgsIHRoaXMgbm90ZSBtYXkgaGVscC4gRm9yIGV4YW1wbGUsIHRoZSBwbG90IHNob3duIGluIHRoZSBmaWZ0aCByb3csIGZvdXJ0aCBjb2x1bW4gc2hvdWxkIGJlIGBib2R5X21hc3NfZ2AgcGxvdHRlZCBvbiB0aGUgeS1heGlzIGFnYWluc3QgYGZsaXBwZXJfbGVuZ3RoX21tYCBvbiB0aGUgeC1heGlzLioNCg0KIyMjDQoNCldlIGNvdWxkIGFsc28gdXNlIHRoZSBSIGNvZGUgYmVsb3cgdG8gcGxvdCB0aGUgYHNwZWNpZXNgIHZhcmlhYmxlIGFzIGEgbnVtZXJpYyB2YWx1ZSAoQWRlbGllID0gMSwgZXRjKSwgd2l0aCB0aGUgcG9pbnRzIGNvbG91cmVkIGJ5IGNsdXN0ZXI6DQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFR9DQpwbG90KGFzLm51bWVyaWMocGVuZ3VpbnMkc3BlY2llcyksIGNvbCA9IGttZWFuc19maXQzJGNsdXN0ZXIpDQpgYGANCg0KQmFzZWQgb24gdGhpcyBwbG90LCBhbmQgdGhlIHBsb3QgcHJvZHVjZWQgaW4gXEByZWYoa21lYW5zdmlzdWFsaXNlKSwgZG8geW91IHRoaW5rIHRoZSAkayQtbWVhbnMgY2x1c3RlcmluZyBoYXMgcGVyZm9ybWVkIHdlbGw/DQoNCiMjIHsja21lYW5zbW9yZX0NCg0KQXQgdGhpcyBwb2ludCwgaXQgbWlnaHQgc2VlbSB0aGF0IG91ciBqb2IgaXMgZG9uZS4gV2UgaGF2ZSByZXN1bHRzIGZvciAkaz0zJCwgYW5kIHRoZXJlIGFyZSB0aHJlZSBzcGVjaWVzIG9mIHBlbmd1aW5zLg0KDQpSZW1lbWJlciB0aG91Z2gsIG5vcm1hbGx5IHdlIHdvdWxkIG5vdCBhY3R1YWxseSBrbm93IHdoYXQgdGhlIG51bWJlciBvZiBjbHVzdGVycyBzaG91bGQgYmUuIFRoZXJlZm9yZSwgd2Ugd291bGQgdXN1YWxseSB0cnkgYSByYW5nZSBvZiBkaWZmZXJlbnQgJGskIHZhbHVlcy4NCg0KQ29uZHVjdCAkayQtbWVhbnMgY2x1c3RlcmluZyBvbiBvdXIgYHBlbmd1aW5zX3NjYWxlZGAgZGF0YSwgZm9yICRrJCB2YWx1ZXMgb2YgMiBhbmQgNC4NCg0KTm90ZSBkb3duIHRoZSBgYmV0d2Vlbl9zcyAvdG90YWxfc3NgIHJlc3VsdHMuIFdoYXQgZG8geW91IG9ic2VydmU/DQoNCiMjIERpYWdub3N0aWNzIHsjbmJjbHVzdH0NCg0KWW91IG1pZ2h0IGhhdmUgbm90aWNlZCB0aGF0IHRoZSBgYmV0d2Vlbl9zcyAvdG90YWxfc3NgIHZhbHVlIGluY3JlYXNlcyBhcyB3ZSBpbmNyZWFzZSB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzLiBUaGlzIGNhbiBoYXBwZW4gZXZlbiBpZiBhZGRpbmcgYW5vdGhlciBjbHVzdGVyIGlzbid0IGFjdHVhbGx5IHRoYXQgaGVscGZ1bC4gVGhlcmVmb3JlLCB3ZSBzaG91bGQgYWxzbyBjb25zaWRlciBzb21lIG90aGVyIGFzc2Vzc21lbnQgbWV0aG9kcy4NCg0KSWYgd2UgZGlkIG5vdCBrbm93IHRoZSBudW1iZXIgb2YgY2x1c3RlcnMsIHdlIGNvdWxkIHVzZSBzZXZlcmFsIG1ldGhvZHMgdG8gZGV0ZXJtaW5lIHRoZSBhcHByb3ByaWF0ZSB2YWx1ZSBvZiAkayQuDQoNClRoZSBgZmFjdG9leHRyYWAgUiBwYWNrYWdlIGNvbnRhaW5zIGEgaGVscGZ1bCBmdW5jdGlvbiBgZnZpel9uYmNsdXN0YCwgd2hpY2ggd2UgY2FuIHVzZSB0byBwcm9kdWNlIHNldmVyYWwgaW5mb3JtYXRpdmUgcGxvdHMuDQoNClRha2UgYSBsb29rIGF0IHRoZSBjb21tZW50cyBpbiB0aGUgYENvZGVgIGNodW5rIGJlbG93ICh0aGUgYC4uLmAgc2lnbmlmeSB0aGF0IHRoZXNlIGFyZSBjb21tZW50cywgYW5kIHNob3VsZCBiZSByZXBsYWNlZCBieSBhY3R1YWwgYXJndW1lbnRzKToNCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBGLCBlY2hvID0gVH0NCmV4YW1wbGUgPC0gZnZpel9uYmNsdXN0KC4uLnNwZWNpZnkgZGF0YSBzZXQgaGVyZS4uLiAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgLi4uc3BlY2lmeSBjbHVzdGVyaW5nIG1ldGhvZCBoZXJlLi4uICwgDQogICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiLi4uc3BlY2lmeSBhc3Nlc3NtZW50IG1ldGhvZCBoZXJlLi4uIikNCmBgYA0KDQpBcyB5b3UgY2FuIHNlZSwgdGhlIGBmdml6X25iY2x1c3RgIGZ1bmN0aW9uIHRha2VzIHRocmVlIG1haW4gYXJndW1lbnRzOiANCg0KMS4gVGhlIGRhdGEgc2V0LCANCjIuIFRoZSBjbHVzdGVyaW5nIG1ldGhvZCAoaS5lLiBrbWVhbnMpLCBhbmQgDQozLiBUaGUgYXNzZXNzbWVudCBtZXRob2QgKG9uZSBvZiAid3NzIiAsICJzaWxob3VldHRlIiAsIG9yICJnYXBfc3RhdCIpDQoNCkZvciB0aGUgcHVycG9zZXMgb2YgdGhpcyBsYWIsIHdlIHdpbGwgZm9jdXMgb24ganVzdCB0aGUgKipzaWxob3VldHRlKiogW3NlZSBAUm91c3NlZXV3XSBhbmQgKipnYXAqKiBtZXRob2RzLg0KDQojIyMgVGhlIHNpbGhvdWV0dGUgbWV0aG9kDQoNCg0KVGhlIHNpbGhvdWV0dGUgYXNzZXNzbWVudCBtZXRob2QgbWVhc3VyZXMgdGhlIHNpbWlsYXJpdHkgb2YgYSBwb2ludCB0byBvdGhlciBwb2ludHMgaW4gaXRzIGNsdXN0ZXIuIA0KDQoqIFRoZSBzaWxob3VldHRlIHZhbHVlIGZvciBlYWNoIG9ic2VydmF0aW9uIGNhbiBiZSBiZXR3ZWVuIDAgYW5kIDEuIA0KKiBWYWx1ZXMgY2xvc2UgdG8gMCBzdWdnZXN0IGNsdXN0ZXJzIGFyZSB2ZXJ5IHNpbWlsYXIsIHdoaWxlIHZhbHVlcyBjbG9zZSB0byAxIGluZGljYXRlIGNsdXN0ZXJzIGFyZSB2ZXJ5IGRpZmZlcmVudC4gVGh1cywgdGhlIGhpZ2hlciB0aGUgYXZlcmFnZSBzaWxob3VldHRlIHdpZHRoIHZhbHVlIChhY3Jvc3MgYWxsIHBvaW50cyBpbiBhIGNsdXN0ZXIpLCB0aGUgYmV0dGVyLg0KDQpVc2luZyB0aGUgYGZ2aXpfbmJjbHVzdGAgZnVuY3Rpb24sIHdpdGggdGhlIGBrbWVhbnNgIGNsdXN0ZXJpbmcgbWV0aG9kIGFuZCB0aGUgYCJzaWxob3VldHRlImAgYXNzZXNzbWVudCBtZXRob2Qgc3BlY2lmaWVkLCBkZXRlcm1pbmUgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzIGZvciBvdXIgYHBlbmd1aW5zX3NjYWxlZGAgZGF0YS4NCg0KIyMjIFRoZSBnYXAgc3RhdGlzdGljIG1ldGhvZA0KDQpUaGUgc2Vjb25kICRrJC1tZWFucyBjbHVzdGVyaW5nIGFzc2Vzc21lbnQgbWV0aG9kIHdlIHdpbGwgY29uc2lkZXIgaXMgdGhlIGBnYXBfc3RhdGAgYXNzZXNzbWVudCBtZXRob2QuIFRoaXMgY29tcHV0ZXMgYSBzdGF0aXN0aWMga25vd24gYXMgdGhlICdnYXAgc3RhdGlzdGljJy4gV2Ugd29uJ3QgZ28gaW50byB0aGUgbWF0aGVtYXRpY2FsIGRldGFpbHMgb2YgdGhpcyBzdGF0aXN0aWM7IHN1ZmZpY2UgdG8gc2F5IHRoYXQgaGlnaGVyIHZhbHVlcyBhcmUgY29uc2lkZXJlZCBwcmVmZXJhYmxlLg0KDQpVc2luZyB0aGUgYGZ2aXpfbmJjbHVzdGAgZnVuY3Rpb24sIHdpdGggdGhlIGBrbWVhbnNgIGNsdXN0ZXJpbmcgbWV0aG9kIGFuZCB0aGUgYCJnYXBfc3RhdCJgIGFzc2Vzc21lbnQgbWV0aG9kIHNwZWNpZmllZCwgZGV0ZXJtaW5lIHRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycyBmb3Igb3VyIGBwZW5ndWluc19zY2FsZWRgIGRhdGEuDQoNCiMjIyBDbHVzdGVyIHBsb3RzDQoNCldlIGNhbiBhbHNvIHVzZSB0aGUgYGZ2aXpfY2x1c3RlcmAgZnVuY3Rpb24gZnJvbSB0aGUgYGZhY3RvZXh0cmFgIFIgcGFja2FnZSB0byB2aXN1YWxpc2UgdGhlIGNsdXN0ZXJzLg0KDQpSdW4gdGhlIGNvZGUgYmVsb3cgdG8gdmlzdWFsaXNlIHRoZSB0aHJlZSBjbHVzdGVycyBmb3VuZCBpbiBcQHJlZihrbWVhbnMzKV5bTm90ZSB0aGF0IHRoZXNlIG1pZ2h0IGxvb2sgYSBsaXR0bGUgZGlmZmVyZW50IHRvIHRob3NlIHZpc3VhbGlzZWQgaW4gXEByZWYoa21lYW5zdmlzdWFsaXNlKSwgc2luY2UgdGhlIGF4ZXMgdmFyaWFibGVzIGFyZSBkaWZmZXJlbnRdLCBhbmQgdGhlbiwgdXNpbmcgdGhpcyBjb2RlIGFzIGEgYmFzZSwgdmlzdWFsaXNlIHRoZSBzZXRzIG9mIGNsdXN0ZXJzIGZvdW5kIGluIFxAcmVmKGttZWFuc21vcmUpLg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IEYsIGVjaG8gPSBUfQ0KZnZpel9jbHVzdGVyKGttZWFuc19maXQzLCBkYXRhID0gcGVuZ3VpbnNfc2NhbGVkKQ0KYGBgDQoNCiMjIA0KDQpJbiBjb25jbHVzaW9uLCBiYXNlZCBvbiBhbGwgeW91ciAkayQtbWVhbnMgY2x1c3RlcmluZyBhbmFseXNlcywgd2hpY2ggdmFsdWUgb2YgJGskIHdvdWxkIHlvdSByZWNvbW1lbmQgdXNpbmcsIGFuZCB3aHk/DQoNCjxicj4NCg0KIyMjIyBBdCB0aGlzIHBvaW50LCB3ZSd2ZSBjb3ZlcmVkIHRoZSBtYWluIG1hdGVyaWFsIGZvciB0aGlzIGNvbXB1dGVyIGxhYiAtIHdlbGwgZG9uZSEgSWYgeW91IHdvdWxkIGxpa2UsIHlvdSBjYW4gZXh0ZW5kIHlvdXIga25vd2xlZGdlIG9uIGNsdXN0ZXJpbmcgYnkgZ29pbmcgdGhyb3VnaCB0aGUgRXh0ZW5zaW9uIHNlY3Rpb25zIGJlbG93LiBPdGhlcndpc2UsIGlmIHlvdSBoYXZlIHRpbWUgbGVmdCwgeW91IG1pZ2h0IGxpa2UgdG8gd29yayBvbiB5b3VyIGFzc2Vzc21lbnRzLiAjIyMjIHstfQ0KDQo8YnI+DQoNCiMgRXh0ZW5zaW9uIDE6IFBBTSBDbHVzdGVyaW5nDQoNCkFuIGFsdGVybmF0aXZlIHRvICRrJC1tZWFucyBjbHVzdGVyaW5nIGlzICpQQU0qIChQYXJ0aXRpb24gYXJvdW5kIE1lZG9pZHMpICpjbHVzdGVyaW5nKi4gDQoNClBBTSBjbHVzdGVyaW5nIG9wZXJhdGVzIGluIGEgc2ltaWxhciBtYW5uZXIgdG8gJGskLW1lYW5zIGNsdXN0ZXJpbmcsIGJ1dCB1c2VzIG1lZG9pZHMgKG1lZGlhbi1saWtlIHBvaW50cykgcmF0aGVyIHRoYW4gY2VudHJvaWRzLCB3aGVuIGRlY2lkaW5nIGNsdXN0ZXIgbWVtYmVyc2hpcC4gVGhpcyBtYWtlcyB0aGUgUEFNIGNsdXN0ZXJpbmcgdGVjaG5pcXVlIG1vcmUgcm9idXN0IHRvIG91dGxpZXJzLg0KDQojIyB7I3BhbX0NCg0KV2UgY2FuIGVhc2lseSBjb25kdWN0IFBBTSBjbHVzdGVyaW5nIHVzaW5nIHRoZSBgcGFtYCBmdW5jdGlvbiBmcm9tIHRoZSBpbmJ1aWx0IGBjbHVzdGVyYCBSIHBhY2thZ2UuIFRoZSBwcm9jZXNzIGlzIHNpbWlsYXIgdG8gdGhlICRrJC1tZWFucyBjbHVzdGVyaW5nIHByb2Nlc3MgY29uZHVjdGVkIGluIFxAcmVmKGttZWFuczMpLg0KDQpVc2UgdGhlIGBwYW1gIGZ1bmN0aW9uIHRvIGNvbmR1Y3QgUEFNIGNsdXN0ZXJpbmcgZm9yICRrJCB2YWx1ZXMgb2YgMiwgMyBhbmQgNC4NCg0KIyMgeyNwYW1iZXN0fQ0KDQpOZXh0LCB1c2UgdGhlIGBmdml6X25iY2x1c3RgIGZ1bmN0aW9uIHRvIGRldGVybWluZSB0aGUgYmVzdCBudW1iZXIgb2YgUEFNIGNsdXN0ZXJzIHRvIHVzZSwgdmlhIHRoZSBzaWxob3VldHRlIG1ldGhvZC4NCg0KKkhpbnQ6IFJlbWVtYmVyIHRvIHVzZSBgcGFtYCBpbnN0ZWFkIG9mIGBrbWVhbnNgIGZvciB0aGUgY2x1c3RlcmluZyBtZXRob2Qgc3BlY2lmaWNhdGlvbi4qDQoNCiMjDQoNCkZpbmFsbHksIHVzZSB0aGUgYGZ2aXpfY2x1c3RlcmAgZnVuY3Rpb24gYW5kIHRoZSBjb2RlIGJlbG93IHRvIHZpc3VhbGlzZSB0aGUgY2x1c3RlcnMgZm9yIHRoZSBQQU0gY2x1c3RlcmluZyByZXN1bHQocykgeW91IGNob3NlIGFib3ZlIGluIFxAcmVmKHBhbWJlc3QpLiBXaGF0IGRvIHlvdSBjb25jbHVkZT8NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBGLCBlY2hvID0gVH0NCiMgVGhpcyBjb2RlIGFzc3VtZXMgeW91IGFyZSBhc3Nlc3NpbmcgYSByZXN1bHQgc3RvcmVkIGluIHRoZSBvYmplY3QgcGFtX2ZpdDMNCndpbmRvd3MoKQ0KcGxvdChhcy5udW1lcmljKHBlbmd1aW5zJHNwZWNpZXMpLCBjb2wgPSBwYW1fZml0MyRjbHVzdGVyaW5nKQ0KYGBgDQoNCg0KDQojIEV4dGVuc2lvbiAyOiBIaWVyYXJjaGljYWwgQ2x1c3RlcmluZw0KDQpVbmxpa2UgdGhlIHByZXZpb3VzIGNsdXN0ZXJpbmcgdGVjaG5pcXVlcyBkaXNjdXNzZWQsIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGJlZ2lucyBieSBhc3NpZ25pbmcgZWFjaCBwb2ludCB0byBpdHMgb3duIGNsdXN0ZXIuIA0KDQpUaGlzIG1lYW5zIHRoYXQsIGZvciBvdXIgYHBlbmd1aW5zX3NjYWxlZGAgZGF0YSBzZXQsICB3ZSB3b3VsZCBzdGFydCB3aXRoIDMzMyBjbHVzdGVycyAtIDEgZm9yIGVhY2ggcGVuZ3Vpbi4gVGhlbiwgdGhlIHR3byBtb3N0IHNpbWlsYXIgY2x1c3RlcnMgYXJlIG1lcmdlZCAoY29udGludWluZyBvdXIgZXhhbXBsZSwgdGhpcyB3b3VsZCByZXN1bHQgaW4gdXMgbm93IGhhdmluZyAzMzIgY2x1c3RlcnMpLiBXZSByZXBlYXQgdGhpcyBwcm9jZXNzLCB1bnRpbCB3ZSBoYXZlIGp1c3Qgb25lIGxhcmdlIGNsdXN0ZXIgKHdoaWNoIGNvbnRhaW5zIGFsbCB0aGUgcG9pbnRzKS4NCg0KQXMgYSByZXN1bHQsIHdlIG5vdyBoYXZlIGEgKmhpZXJhcmNoeSogb2YgY2x1c3RlcnMsIHdoaWNoIGxvb2tzIGEgYml0IGxpa2UgYW4gdXBzaWRlLWRvd24gdHJlZSAod2l0aCBvYnNlcnZhdGlvbnMgZnJvbSB0aGUgc2FtZSBjbHVzdGVyIG9uIHRoZSBzYW1lICdicmFuY2gnKS4NCg0KIyMgeyNoaWVyYXJjaGljYWx9DQoNCldlIGNhbiBjb25kdWN0IGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIHVzaW5nIHRoZSBgYWduZXNgIGZ1bmN0aW9uIGZyb20gdGhlIGBjbHVzdGVyYCBSIHBhY2thZ2UuIFNpbmNlIHdlIGFyZSBub3Qgc3BlY2lmeWluZyB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzLCB3ZSBvbmx5IG5lZWQgdG8gcHJvdmlkZSB0aGUgb25lIGFyZ3VtZW50LCBpLmUuIHRoZSBkYXRhIHNldCB0byBhbmFseXNlLg0KDQpVc2UgdGhlIGBhZ25lc2AgZnVuY3Rpb24gdG8gY29uZHVjdCBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyBvbiBvdXIgYHBlbmd1aW5zX3NjYWxlZGAgZGF0YSBzZXQuDQoNCiMjDQoNCldlIGNhbiB2aXN1YWxpc2UgdGhlIHJlc3VsdHMgb2YgdGhlIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIHVzaW5nIGEgKmRlbmRyb2dyYW0qLg0KDQpDb21wbGV0ZSB0aGUgY29kZSBiZWxvdyB0byBjcmVhdGUgYSBkZW5kcm9ncmFtIGZvciB5b3VyIHJlc3VsdHMgKGp1c3QgcmVwbGFjZSB0aGUgYC4uLmAgd2l0aCB5b3VyIHJlc3VsdHMpDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFR9DQpwbG90KC4uLiwgd2hpY2ggPSAyKQ0KYGBgDQoNCiMjDQoNClRoZXJlIGFyZSBzZXZlcmFsIGxpbmthZ2UgbWV0aG9kIG9wdGlvbnMgd2UgY2FuIGNob29zZSBmcm9tIHdoZW4gY29uZHVjdGluZyBoaWVyYXJjaGljYWwgY2x1c3RlcmluZy4gVGhlc2UgcmVsYXRlIHRvIHRoZSB3YXkgaW4gd2hpY2ggZGlzdGFuY2UgaXMgbWVhc3VyZWQgYmV0d2VlbiB0d28gcG9pbnRzLCB0byBkZXRlcm1pbmUgdGhlaXIgbGV2ZWwgb2Ygc2ltaWxhcml0eS4gDQoNClRoZSBkZWZhdWx0IG1ldGhvZCBpbiB0aGUgYGFnbmVzYCBmdW5jdGlvbiBpcyBgYXZlcmFnZWAuIFRyeSByZS1ydW5uaW5nIHlvdXIgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgYWxnb3JpdGhtIHVzaW5nIHNvbWUgb3RoZXIgb3B0aW9ucywgYnkgYWRkaW5nIHRoZSBhcmd1bWVudCBgbWV0aG9kID0gIi4uLiJgIHRvIHlvdXIgY29kZSBpbiBcQHJlZihoaWVyYXJjaGljYWwpLCBhbmQgcmVwbGFjaW5nIHRoZSBgIi4uLiJgIHdpdGggYCJzaW5nbGUiYCBhbmQgdGhlbiBgIndhcmQiYC4gDQoNCldoZW4geW91IHZpc3VhbGlzZSB0aGUgcmVzdWx0cywgZG8gdGhlIGRlbmRyb2dyYW1zIGxvb2sgdmVyeSBkaWZmZXJlbnQ/DQoNCiMjDQoNClNvIGZhciwgdGhlIGRlbmRyb2dyYW1zIHdlIGhhdmUgcHJvZHVjZWQgbG9va2VkIHJhdGhlciB1bmFwcGVhbGluZyAtIHdlIGNhbiBkbyBiZXR0ZXIuDQoNCkl0IGlzIHdvcnRoIG5vdGluZyB0aGF0IHRoZSBzZWxlY3Rpb24gb2YgYW4gYXBwcm9wcmlhdGUgaG9yaXpvbnRhbCBjdXQtb2ZmIHBvaW50IG9uIHRoZSBkZW5kcm9ncmFtICh0aHVzIHNlbGVjdGluZyB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIHRvIHVzZSkgaXMgYXJiaXRyYXJ5LiBCYXNlZCBvbiB5b3VyIGZpbmRpbmdzIGZyb20gdGhlIHByZXZpb3VzIGFuYWx5c2VzIGhvd2V2ZXIsIHlvdSBzaG91bGQgbm93IGhhdmUgZGVjaWRlZCBvbiBhbiAnb3B0aW1hbCcgbnVtYmVyIG9mICRrJCBjbHVzdGVycyB0byBzZWxlY3QuIA0KDQpXZSBjYW4gdmlzdWFsaXNlIHRoZSBkZW5kcm9ncmFtIHdpdGggdGhlIHRyZWUgY3V0IGludG8gdGhlc2UgJGskIGNsdXN0ZXJzIHVzaW5nIHRoZSBgaGN1dGAgYW5kIGBmdml6X2RlbmRgIGZ1bmN0aW9ucyBmcm9tIHRoZSBgZmFjdG9leHRyYWAgUiBwYWNrYWdlLg0KDQpUYWtlIGEgbG9vayBhdCB0aGUgY29kZSBiZWxvdywgYW5kIGZpbGwgaW4gdGhlIGAuLi5gIG1pc3NpbmcgcGFydHMgd2l0aCB0aGUgcmVsZXZhbnQgZGV0YWlscy4gT25jZSB5b3UgYXJlIGhhcHB5IHdpdGggdGhlIGNvZGUsIHJ1biBpdC4gVGhlIHJlc3VsdGFudCBkZW5kcm9ncmFtIHNob3VsZCBsb29rIG11Y2ggbmljZXIuDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFR9DQpwZW5ndWluX2RlbmRybyA8LSBoY3V0KC4uLiwgDQogICAgICAgICAgICAgICAgICAgICAgIGsgPSAuLi4sIA0KICAgICAgICAgICAgICAgICAgICAgICBoY19mdW5jID0gImFnbmVzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgaGNfbWV0aG9kID0gIi4uLiIsDQogICAgICAgICAgICAgICAgICAgICAgIGhjX21ldHJpYyA9ICJldWNsaWRlYW4iKSANCg0KZnZpel9kZW5kKHBlbmd1aW5fZGVuZHJvKQ0KYGBgDQoNCipOb3RlOiBZb3Ugd2lsbCBuZWVkIHRvIHNwZWNpZnkgdGhlIG1lYXN1cmVtZW50IG1ldGhvZCwgZS5nLiBgIndhcmQiYCBpbiB0aGUgYGhjX21ldGhvZCA9YCBhcmd1bWVudC4qDQoNCiMgRXh0ZW5zaW9uIDM6IEZ1enp5IENsdXN0ZXJpbmcNCg0KRm9yIHRoZSBwcmV2aW91cyBjbHVzdGVyaW5nIG1ldGhvZHMsIGVhY2ggZGF0YSBwb2ludCBiZWxvbmdlZCB0byBvbmx5IG9uZSBjbHVzdGVyLiBUaGlzIGlzIHNvbWV0aW1lcyByZWZlcnJlZCB0byBhcyAqaGFyZCBjbHVzdGVyaW5nKi4gDQoNCkluIGNvbnRyYXN0LCBpbiAqZnV6enkgY2x1c3RlcmluZyogZWFjaCBwb2ludCBpcyBhbGxvd2VkIHRvIGJlbG9uZyB0byBtdWx0aXBsZSBjbHVzdGVycy4gVGhlIG1vcmUgc2ltaWxhciB0aGUgcG9pbnQgaXMgdG8gb3RoZXIgcG9pbnRzIGluIGEgY2x1c3RlciwgdGhlIGhpZ2hlciB0aGF0IHBvaW50J3MgcGVyY2VudGFnZSBvZiBtZW1iZXJzaGlwIHRvIHRoYXQgY2x1c3Rlcl5bU2VlIGUuZy4gQE1vZFN0YXRdLg0KVGhpcyBjb3VsZCBiZSBoZWxwZnVsIHdoZW4gYSBwb2ludCBuYXR1cmFsbHkgc2hvdWxkIGJlbG9uZyB0byBtdWx0aXBsZSBjbHVzdGVycyAoZm9yIGluc3RhbmNlLCBpZiBwZW5ndWlucyB3ZXJlIGNsdXN0ZXJlZCBpbnRvIDYgY2x1c3RlcnMsIGZvciBtYWxlcyBhbmQgZmVtYWxlcyBvZiBlYWNoIHNwZWNpZXMsIHRoZW4gaXQgd291bGQgYmUgcmVhc29uYWJsZSBmb3IgZWFjaCBwZW5ndWluIHRvIGJlbG9uZyB0byB0d28gY2x1c3RlcnMpLg0KDQojIw0KDQpXZSBjYW4gZWFzaWx5IGNvbmR1Y3QgZnV6enkgY2x1c3RlcmluZyB1c2luZyB0aGUgYGZhbm55YCBmdW5jdGlvbiBmcm9tIHRoZSBpbmJ1aWx0IGBjbHVzdGVyYCBSIHBhY2thZ2UuIFRoZSBwcm9jZXNzIGlzIHNpbWlsYXIgdG8gdGhlICRrJC1tZWFucyBjbHVzdGVyaW5nIHByb2Nlc3MgKGNvbmR1Y3RlZCBpbiBcQHJlZihrbWVhbnMzKSkgYW5kIHRoZSBQQU0gY2x1c3RlcmluZyBwcm9jZXNzIChjb25kdWN0ZWQgaW4gXEByZWYocGFtKSkuDQoNClVzZSB0aGUgYGZhbm55YCBmdW5jdGlvbiB0byBjb25kdWN0IGZ1enp5IGNsdXN0ZXJpbmcgZm9yICRrJCB2YWx1ZXMgb2YgMiwgMyBhbmQgNC4gDQoNCipOb3RlOiBUaGUgb3V0cHV0IGZvciB0aGVzZSBhbmFseXNlcyB3aWxsIGluY2x1ZGUgYSBgbWVtYmVyc2hpcCBjb2VmZmljaWVudHNgIGxpc3Qgb2YgdGhlIG1lbWJlcnNoaXAgcGVyY2VudGFnZXMgZm9yIGVhY2ggcG9pbnQuIFdoaWxlIGl0IGlzIG5vdCBlYXN5IHRvIHZpc3VhbGlzZSB0aGVzZSwgdGhlIG91dHB1dCBhbHNvIGluY2x1ZGVzIHRoZSBjbG9zZXN0ICdoYXJkJyBjbHVzdGVyIGZvciBlYWNoIHBvaW50LCB3aGljaCBjYW4gYmUgY2FsbGVkIHZpYSBgJGNsdXN0ZXJgLCBhcyBwZXIgdGhlIHByZXZpb3VzIGNsdXN0ZXJpbmcgdGVjaG5pcXVlcy4qDQoNCiMjIHsjZnV6enliZXN0fQ0KDQpOZXh0LCB1c2UgdGhlIGBmdml6X25iY2x1c3RgIGZ1bmN0aW9uIHRvIGRldGVybWluZSB0aGUgYmVzdCBudW1iZXIgb2YgZnV6enkgY2x1c3RlcnMgdG8gdXNlLCB2aWEgdGhlIHNpbGhvdWV0dGUgbWV0aG9kLg0KDQoqTm90ZTogRG9uJ3Qgd29ycnkgaWYgYW55IHdhcm5pbmcgbWVzc2FnZXMgbGlrZSB0aGUgb25lIHNob3duIGluIHRoZSBjb2RlIGNodW5rIGJlbG93IGFwcGVhciAtIHlvdSBzaG91bGQgc3RpbGwgYmUgYWJsZSB0byBwcm9kdWNlIHRoZSBwbG90czoqDQoNCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSIsIGV2YWwgPSBGLCBlY2hvID0gVH0NCiMjIFdhcm5pbmcgaW4gRlVOY2x1c3Rlcih4LCBpLCAuLi4pOiBGQU5OWSBhbGdvcml0aG0gaGFzIG5vdCBjb252ZXJnZWQgaW4gJ21heGl0JyA9DQojIyA1MDAgaXRlcmF0aW9ucw0KYGBgDQoNCiMjDQoNClVzZSB0aGUgYGZ2aXpfY2x1c3RlcmAgZnVuY3Rpb24gYW5kIHRoZSBjb2RlIGJlbG93IHRvIHZpc3VhbGlzZSB0aGUgY2x1c3RlcnMgZm9yIHRoZSBmdXp6eSBjbHVzdGVyaW5nIHJlc3VsdCB5b3UgY2hvc2UgYWJvdmUgaW4gXEByZWYoZnV6enliZXN0KS4gV2hhdCBkbyB5b3UgY29uY2x1ZGU/DQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFR9DQojIFRoaXMgY29kZSBhc3N1bWVzIHlvdSBhcmUgYXNzZXNzaW5nIGEgcmVzdWx0IHN0b3JlZCBpbiB0aGUgb2JqZWN0IGZ1enp5X2ZpdDMNCndpbmRvd3MoKQ0KcGxvdChhcy5udW1lcmljKHBlbmd1aW5zJHNwZWNpZXMpLCBjb2wgPSBmdXp6eV9maXQzJGNsdXN0ZXIpDQpgYGANCg0KPGJyPg0KDQojIyMjIEdyZWF0IHdvcmssIHRoYXQncyBldmVyeXRoaW5nIGZvciB0b2RheSEgIyMjIyB7LX0NCg0KVGhhdCBjb25jbHVkZXMgb3VyIHdvcmsgb24gY2x1c3RlcmluZyB0ZWNobmlxdWVzLiBEaWQgeW91IGhhdmUgYSBwcmVmZXJlbmNlIGZvciBvbmUgb2YgdGhlIGZvdXIgdGVjaG5pcXVlcyBpbnRyb2R1Y2VkIGluIHRoaXMgY29tcHV0ZXIgbGFiPw0KDQo8YnI+DQoNCiMgUmVmZXJlbmNlcyB7LSAjUmVmfQ0KPGRpdiBpZD0icmVmcyI+PC9kaXY+DQoNCjxicj4NCg0KPGZvbnQgY29sb3IgPSAiZ3JleSI+DQpUaGVzZSBub3RlcyBoYXZlIGJlZW4gcHJlcGFyZWQgYnkgUnVwZXJ0IEt1dmVrZS4gUGxlYXNlIG5vdGUgdGhhdCBzb21lIG9mIHRoZSBjb250ZW50IGluIHRoZXNlIG5vdGVzIGhhcyBiZWVuIGRldmVsb3BlZCBmcm9tIGNvbnRlbnQgaW4gQE1vZFN0YXQuIFRoZSBjb3B5cmlnaHQgZm9yIHRoZSBtYXRlcmlhbCBpbiB0aGVzZSBub3RlcyByZXNpZGVzIHdpdGggdGhlIGF1dGhvcnMgbmFtZWQgYWJvdmUsIHdpdGggdGhlIERlcGFydG1lbnQgb2YgTWF0aGVtYXRpY2FsIGFuZCBQaHlzaWNhbCBTY2llbmNlcyBhbmQgd2l0aCBMYSBUcm9iZSBVbml2ZXJzaXR5LiBDb3B5cmlnaHQgaW4gdGhpcyB3b3JrIGlzIHZlc3RlZCBpbiBMYSBUcm9iZSBVbml2ZXJzaXR5IGluY2x1ZGluZyBhbGwgTGEgVHJvYmUgVW5pdmVyc2l0eSBicmFuZGluZyBhbmQgbmFtaW5nLiBVbmxlc3Mgb3RoZXJ3aXNlIHN0YXRlZCwgbWF0ZXJpYWwgd2l0aGluIHRoaXMgd29yayBpcyBsaWNlbnNlZCB1bmRlciBhIENyZWF0aXZlIENvbW1vbnMgQXR0cmlidXRpb24tTm9uIENvbW1lcmNpYWwtTm9uIERlcml2YXRpdmVzIExpY2Vuc2UgDQo8YSBocmVmID0gImh0dHBzOi8vY3JlYXRpdmVjb21tb25zLm9yZy9saWNlbnNlcy9ieS1uYy1uZC80LjAvQ0MiIHRhcmdldD0iX2JsYW5rIj4gQlktTkMtTkQuIDwvYT4NCjwvZm9udD4=