Science/Health Science/Data Science Module

Topic 6B: Big Data I (Clustering)


Welcome to the sixth computer lab for the Science, Health Science and Data Science modules.

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!

1 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 metric1.

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 clustering2 is to:

  1. Analyse the data structure
  2. Relate the different elements of the data to each other, and then
  3. 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 analysis3.

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.

2 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 packages4. 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.

2.1 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

2.2

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?

2.3 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.

2.3.1

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]

2.3.2

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:

  1. Computing the sample mean and sample standard deviation for each variable
  2. 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.

2.3.3

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

3 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 means5.

3.1

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)

3.2

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.

3.3 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.

3.4

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?

3.5

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?

3.6

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:

  1. The data set,
  2. The clustering method (i.e. kmeans), and
  3. The assessment method (one of “wss” , “silhouette” , or “gap_stat”)

3.6.1

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

3.6.2

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.

3.6.3

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.

3.7

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.16, and then, using this code as a base, visualise the sets of clusters found in 3.5.

fviz_cluster(kmeans_fit3, data = penguins_scaled)

3.8

Based on your k-means clustering analyses, which value of \(k\) would you recommend using, and why?

4 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.

4.1

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.

4.2

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.

4.3

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.


5 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’).

5.1

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.

5.2

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)

5.3

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?

5.4

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.

6 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 cluster7. 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).

6.1

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.

6.2

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

6.3

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/.
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 Mathematics and Statistics 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.


  1. Mirkin (1996), p.25↩︎

  2. see e.g. Mirkin (1996), p.25↩︎

  3. see e.g. Gan, Ma, and Wu (2007)↩︎

  4. the factoextra package was created by Kassambara and Mundt (2020) and the ggfortify package was created by Horikoshi et al. (2021). The cluster package is part of the base R suite of packages (R Core Team 2021).↩︎

  5. See e.g. section 4.10.3 of Thulin (2021)↩︎

  6. Note that these might look a little different to those visualised in 3.3, since the axes variables are different↩︎

  7. See e.g. Thulin (2021)↩︎

LS0tDQp0aXRsZTogIlNUTTEwMDE6IENvbXB1dGVyIExhYiA2QiINCm91dHB1dDoNCiAgYm9va2Rvd246Omh0bWxfZG9jdW1lbnQyOiANCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgdGhlbWU6IHJlYWRhYmxlDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQpiaWJsaW9ncmFwaHk6IFNUTTEwMDFfRFNfQ0xfcmVmZXJlbmNlcy5iaWIgDQpsaW5rLWNpdGF0aW9uczogeWVzDQotLS0NCg0KPHN0eWxlPg0KI1RPQyB7DQogIGJhY2tncm91bmQ6IHVybCgiaHR0cHM6Ly93d3cubGF0cm9iZS5lZHUuYXUvX21lZGlhL2xhLXRyb2JlLWFwaS92NS9pbWcvbG9nby5zdmciKTsNCiAgYmFja2dyb3VuZC1zaXplOiBjb250YWluOw0KICBwYWRkaW5nLXRvcDogODBweCAhaW1wb3J0YW50Ow0KICBiYWNrZ3JvdW5kLXJlcGVhdDogbm8tcmVwZWF0Ow0KfQ0KPC9zdHlsZT4NCg0KIyMjIFNjaWVuY2UvSGVhbHRoIFNjaWVuY2UvRGF0YSBTY2llbmNlIE1vZHVsZSB7LX0NCg0KIyMjIFRvcGljIDZCOiBCaWcgRGF0YSBJIChDbHVzdGVyaW5nKSB7LX0NCg0KPGJyPg0KDQpXZWxjb21lIHRvIHRoZSBzaXh0aCBjb21wdXRlciBsYWIgZm9yIHRoZSBTY2llbmNlLCBIZWFsdGggU2NpZW5jZSBhbmQgRGF0YSBTY2llbmNlIG1vZHVsZXMuDQoNCkluIHRoaXMgY29tcHV0ZXIgbGFiIHdlIHdpbGwgY2Fycnkgb3V0IGEgdmFyaWV0eSBvZiBjbHVzdGVyaW5nIGFuYWx5c2VzIHVzaW5nIHBlbmd1aW4gZGF0YSBmcm9tIHRoZSBgcGFsbWVycGVuZ3VpbnNgIFIgcGFja2FnZSBbQHBlbmd1aW5zXS4NCg0KQnkgdGhlIGVuZCBvZiB0aGlzIGxhYiwgeW91IHNob3VsZCBmZWVsIGNvbWZvcnRhYmxlIGFwcGx5aW5nIHZhcmlvdXMgY2x1c3RlcmluZyB0ZWNobmlxdWVzIGluIFIuIExldCdzIGdldCBzdGFydGVkIQ0KDQojIENsdXN0ZXIgQW5hbHlzaXMNCg0KQSBjbHVzdGVyIGlzIGEgc3Vic2V0IG9mIGEgZGF0YSBzZXQgdGhhdCBjb25zaXN0cyBvZiBvYnNlcnZhdGlvbnMgd2hpY2gsIHVzaW5nIGEgY2hvc2VuIG1ldHJpYywgYXJlICoqc2ltaWxhciB0byBlYWNoIG90aGVyKiosIGFuZCB3aGljaCBhcmUgYWxzbyAqKmRpc3NpbWlsYXIgdG8gb3RoZXIgb2JzZXJ2YXRpb25zKiosIHZpYSB0aGUgc2FtZSBtZXRyaWNeW0BNYXRoQ2x1c3RlciwgcC4yNV0uDQoNCkNsdXN0ZXIgQW5hbHlzaXMgaXMgdGhlIG1ldGhvZCBvZiBncm91cGluZyBkYXRhIGludG8gY2x1c3RlcnMsIHVzaW5nIGEgY2x1c3RlcmluZyB0ZWNobmlxdWUuIFRoZXJlIGlzIGEgbGFyZ2UgdmFyaWV0eSBvZiBjbHVzdGVyaW5nIHRlY2huaXF1ZXMsIGFuZCBlYWNoIG9mIHRoZXNlIHRlY2huaXF1ZXMgdXNlcyBhIGRpZmZlcmVudCBtZXRyaWMgZm9yIGRldGVybWluaW5nIHRoZSBzaW1pbGFyaXR5IGJldHdlZW4gb2JzZXJ2YXRpb25zLiBEb24ndCB3b3JyeSwgd2Ugd29uJ3QgZ28gaW50byBhbGwgdGhlIGNvbXBsaWNhdGVkIG1hdGhlbWF0aWNzIGludm9sdmVkIGluIHRoZXNlIHRlY2huaXF1ZXMgLSBvdXIgZm9jdXMgaXMgb24gbGVhcm5pbmcgaG93IHRvIGNvbmR1Y3QgY2x1c3RlciBhbmFseXNlcyBpbiBSLg0KDQojIyMgUHVycG9zZSB7LX0NCg0KQ2x1c3RlcmluZyBpcyBvZnRlbiB1c2VkIGFzIHBhcnQgb2YgYW4gZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcyBwcm9jZWR1cmUsIHRvIHVuY292ZXIgaGlkZGVuIHBhdHRlcm5zIGluIGEgbmV3IGRhdGEgc2V0Lg0KVGhlIG1haW4gcHVycG9zZSBvZiBjbHVzdGVyaW5nXltzZWUgZS5nLiBATWF0aENsdXN0ZXIsIHAuMjVdIGlzIHRvOg0KDQoxLiBBbmFseXNlIHRoZSBkYXRhIHN0cnVjdHVyZQ0KMi4gUmVsYXRlIHRoZSBkaWZmZXJlbnQgZWxlbWVudHMgb2YgdGhlIGRhdGEgdG8gZWFjaCBvdGhlciwgYW5kIHRoZW4NCjMuIEFpZCBpbiBjbGFzc2lmeWluZyB0aGUgZGF0YSBpbnRvIGNlcnRhaW4gY2xhc3Nlcw0KDQpJdCBpcyB3b3J0aCBub3RpbmcgdGhhdCBpbiBnZW5lcmFsLCB3ZSBtYXkgbm90IGJlIGF3YXJlIG9mIHdoYXQgdGhlc2UgY2xhc3NlcyBhcmUsIHByaW9yIHRvIHRoZSBjbHVzdGVyaW5nIGFuYWx5c2lzXltzZWUgZS5nLiBARGF0YUNsdXN0ZXJdLg0KDQojIyMgQ2x1c3RlcmluZyBUZWNobmlxdWVzIHstfQ0KDQpJbiB0aGlzIGNvbXB1dGVyIGxhYiwgd2Ugd2lsbCBhcHBseSB0aGUgZm9sbG93aW5nIHBvcHVsYXIgY2x1c3RlcmluZyB0ZWNobmlxdWVzOg0KDQoqIGstbWVhbnMgQ2x1c3RlcmluZw0KKiBQQU0gQ2x1c3RlcmluZw0KKiBGdXp6eSBDbHVzdGVyaW5nDQoqIEhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nDQoNCldlIHdpbGwgcHJvdmlkZSBicmllZiBleHBsYW5hdGlvbnMgb2YgZWFjaCBvZiB0aGVzZSB0ZWNobmlxdWVzIGFzIHdlIHdvcmsgdGhyb3VnaCB0aGlzIGNvbXB1dGVyIGxhYi4NCg0KIyBQcmVwYXJhdGlvbnMgeyNwcmVwfQ0KDQpJbiBSLCB3ZSBjYW4gdXNlIHRoZSBpbmJ1aWx0IGBjbHVzdGVyYCBwYWNrYWdlIHRvIGNhcnJ5IG91dCBhIHJhbmdlIG9mIGNsdXN0ZXIgYW5hbHlzZXMuIEhvd2V2ZXIsIHdlIHdpbGwgYWxzbyByZXF1aXJlIGEgZmV3IGFkZGl0aW9uYWwgcGFja2FnZXNeW3RoZSBgZmFjdG9leHRyYWAgcGFja2FnZSB3YXMgY3JlYXRlZCBieSBAZmFjdG8gYW5kIHRoZSBgZ2dmb3J0aWZ5YCBwYWNrYWdlIHdhcyBjcmVhdGVkIGJ5IEBnZy4gVGhlIGBjbHVzdGVyYCBwYWNrYWdlIGlzIHBhcnQgb2YgdGhlIGJhc2UgUiBzdWl0ZSBvZiBwYWNrYWdlcyBbQFJdLl0uIFBsZWFzZSBydW4gdGhlIFIgY29kZSBiZWxvdyB0byBpbnN0YWxsIGFuZCBsb2FkIHRoZXNlIHBhY2thZ2VzLg0KDQpgYGB7ciBldmFsID0gVCwgaW5jbHVkZSA9IEZ9DQpsaWJyYXJ5KGNsdXN0ZXIpDQpsaWJyYXJ5KGZhY3RvZXh0cmEpDQpsaWJyYXJ5KGdnZm9ydGlmeSkNCmxpYnJhcnkocGFsbWVycGVuZ3VpbnMpDQpgYGANCg0KYGBge3IgZXZhbCA9IEYsIGVjaG8gPSBUfQ0KaW5zdGFsbC5wYWNrYWdlcyhjKCJmYWN0b2V4dHJhIiwgImdnZm9ydGlmeSIsICJwYWxtZXJwZW5ndWlucyIpKQ0KbGlicmFyeShjbHVzdGVyKQ0KbGlicmFyeShmYWN0b2V4dHJhKQ0KbGlicmFyeShnZ2ZvcnRpZnkpDQpsaWJyYXJ5KHBhbG1lcnBlbmd1aW5zKQ0KYGBgDQoNCipOb3RlOiBJZiB5b3UgYXJlIGluIHRoZSBEYXRhIFNjaWVuY2UgbW9kdWxlLCB5b3Ugc2hvdWxkIGFscmVhZHkgaGF2ZSB0aGUgYHBhbG1lcnBlbmd1aW5zYCBwYWNrYWdlIGluc3RhbGxlZC4qDQoNCiMjIFBlbmd1aW4gRGF0YQ0KDQpUaGUgYHBlbmd1aW5zYCBkYXRhIHNldCBpbiB0aGUgYHBhbG1lcnBlbmd1aW5zYCBSIHBhY2thZ2UgW0BwZW5ndWluc10gY29udGFpbnMgZGF0YSBvbiAzIHNwZWNpZXMgb2YgcGVuZ3VpbiAoQWRlbGllLCBDaGluc3RyYXAgYW5kIEdlbnRvbykgbGl2aW5nIG9uIGlzbGFuZHMgaW4gdGhlIFBhbG1lciBhcmNoaXBlbGFnbywgb2ZmIHRoZSBjb2FzdCBvZiBBbnRhcmN0aWNhLiBXZSB3aWxsIHVzZSB0aGlzIGRhdGEgZm9yIG91ciBjbHVzdGVyaW5nIGFuYWx5c2VzLg0KDQpUaGlzIHBlbmd1aW4gZGF0YSBjb250YWlucyBpbmZvcm1hdGlvbiBvbiB2YXJpYWJsZXMgc3VjaCBhczoNCg0KKiBmbGlwcGVyIGxlbmd0aA0KKiBib2R5IG1hc3MNCiogYmlsbCBsZW5ndGgNCiogc2V4DQoqIHNwZWNpZXMNCiogaXNsYW5kIG9uIHdoaWNoIHRoZSBwZW5ndWluIGxpdmVzDQoNCiMjIHsjbWF0cml4cGxvdHN9DQoNClJ1biB0aGUgUiBjb2RlIGJlbG93IHRvIHRha2UgYSBsb29rIGF0IHRoZSBgcGVuZ3VpbnNgIGRhdGEgc2V0LCB0byBmYW1pbGlhcmlzZSB5b3Vyc2VsZiB3aXRoIHRoZSBkaWZmZXJlbnQgdmFyaWFibGVzLg0KDQpgYGB7ciBldmFsID0gRiwgZWNobyA9IFR9DQpoZWFkKHBlbmd1aW5zKQ0KcGxvdChwZW5ndWlucykNCmBgYA0KDQpOb3RlIGhlcmUgdGhhdCBgcGxvdChwZW5ndWlucylgIHdpbGwgcHJvZHVjZSBhIG1hdHJpeCBvZiBzY2F0dGVyIHBsb3RzLg0KDQpFYWNoIHBsb3Qgc2hvd3MgdHdvIG9mIHRoZSB2YXJpYWJsZXMgaW4gdGhlIGBwZW5ndWluc2AgZGF0YSBzZXQgcGxvdHRlZCBhZ2FpbnN0IGVhY2ggb3RoZXIuDQpUaGUgdmFyaWFibGUgbmFtZXMgYXJlIHNob3duIG9uIHRoZSBkaWFnb25hbCBib3hlcyAtIGp1c3QgY3Jvc3MgcmVmZXJlbmNlIGEgcGxvdCB0byB0aGUgdmFyaWFibGUgbmFtZXMgaW4gdGhhdCByb3cgYW5kIGNvbHVtbiB0byBkZXRlcm1pbmUgdGhlIHZhcmlhYmxlcyBwbG90dGVkIG9uIHRoZSB5LWF4aXMgYW5kIHgtYXhpcyByZXNwZWN0aXZlbHkuDQoNCipIaW50OiBGb3IgZXhhbXBsZSwgdGhlIHBsb3Qgc2hvd24gaW4gdGhlIHNpeHRoIHJvdywgZmlmdGggY29sdW1uIHNob3VsZCBiZSBgYm9keV9tYXNzX2dgIHBsb3R0ZWQgb24gdGhlIHktYXhpcyBhZ2FpbnN0IGBmbGlwcGVyX2xlbmd0aF9tbWAgb24gdGhlIHgtYXhpcy4qDQoNCkxvb2tpbmcgYXQgdGhlIHNjYXR0ZXIgcGxvdHMgZm9yIHRoZSBjb250aW51b3VzIHJhbmRvbSB2YXJpYWJsZXMsIGRvZXMgaXQgYXBwZWFyIHRoYXQgdGhlcmUgYXJlIGFueSBuYXR1cmFsIGNsdXN0ZXJzIHRoYXQgd2lsbCBiZSBlYXN5IHRvIGlkZW50aWZ5IHVzaW5nIGEgY2x1c3RlcmluZyB0ZWNobmlxdWU/DQoNCiMjIFByZXBhcmluZyBkYXRhIGZvciBDbHVzdGVyIEFuYWx5c2lzDQoNCkZvciB0aGUgcHVycG9zZXMgb2YgdGhpcyBjb21wdXRlciBsYWIsIHdlIHdpbGwgYXNzdW1lIHRoYXQgd2UgZG9uJ3QgYWN0dWFsbHkgaGF2ZSBkYXRhIG9uIHRoZSBzcGVjaWVzIG9mIGVhY2ggcGVuZ3VpbiBpbiB0aGUgYHBlbmd1aW5zYCBkYXRhIHNldC4NCkluc3RlYWQsIHdlIHdpbGwgY29uZHVjdCBjbHVzdGVyIGFuYWx5c2VzIHVzaW5nIHRoZSBvdGhlciBpbmZvcm1hdGlvbiBjb250YWluZWQgaW4gdGhpcyBkYXRhIHNldCwgdG8gdHJ5IHRvIGdyb3VwIHRoZSBwZW5ndWlucyBpbnRvIGNsdXN0ZXJzLg0KDQpJZiB0aGUgY2x1c3RlciB0ZWNobmlxdWUgd29ya3Mgd2VsbCwgd2UgY291bGQgZW5kIHVwIHdpdGggY2x1c3RlcnMgZm9yIGVhY2ggcGVuZ3VpbiBzcGVjaWVzIQ0KDQpIb3dldmVyLCBiZWZvcmUgd2UgYmVnaW4gb3VyIGNsdXN0ZXJpbmcgYW5hbHlzZXMsIHdlIHdpbGwgbmVlZCB0byBlbnN1cmUgdGhhdCBvdXIgZGF0YSBpcyBpbiB0aGUgYXBwcm9wcmlhdGUgZm9ybWF0Lg0KSXQgaXMgaW1wb3J0YW50IHRvIG5vdGUgdGhhdCBjbHVzdGVyaW5nIGFsZ29yaXRobXMgb25seSB3b3JrIG9uIGNvbnRpbnVvdXMgdmFyaWFibGVzLiBBcyB3ZSBoYXZlIHNlZW4sIHRoZSBgcGVuZ3VpbnNgIGRhdGEgc2V0IGNvbnRhaW5zIGJvdGggY29udGludW91cyBhbmQgY2F0ZWdvcmljYWwgKGRpc2NyZXRlKSB2YXJpYWJsZXMuIA0KDQpUaGVyZWZvcmUsIGZvciB0aGUgdGltZSBiZWluZywgd2Ugd2lsbCBvbmx5IHVzZSBhIHN1YnNldCBvZiB0aGUgdmFyaWFibGVzIGluIHRoZSBgcGVuZ3VpbnNgIGRhdGEgc2V0LCBuYW1lbHkgYGJpbGxfbGVuZ3RoX21tYCwgYGJpbGxfZGVwdGhfbW1gLCBgZmxpcHBlcl9sZW5ndGhfbW1gIGFuZCBgYm9keV9tYXNzX2dgLg0KDQojIyMNCg0KUnVuIHRoZSBSIGNvZGUgYmVsb3cgdG8gcmVtb3ZlIG1pc3NpbmcgdmFsdWVzIGZyb20gb3VyIGRhdGEsIGFuZCBjcmVhdGUgYSBzdWJzZXQgb2YgdGhlIGBwZW5ndWluc2AgZGF0YSB0aGF0IG9ubHkgY29udGFpbnMgdGhlIGNvbnRpbnVvdXMgdmFyaWFibGVzOg0KDQpgYGB7ciBldmFsID0gVCwgZWNobyA9IFR9DQpwZW5ndWlucyA8LSBuYS5vbWl0KHBlbmd1aW5zKQ0KcGVuZ3VpbnNfc3Vic2V0IDwtIHBlbmd1aW5zWywgMzo2XQ0KYGBgDQoNCiMjIw0KDQpCZWZvcmUgY29uZHVjdGluZyBhIGNsdXN0ZXIgYW5hbHlzaXMsIGl0IGlzIGFsc28gZ2VuZXJhbGx5IGEgZ29vZCBpZGVhIHRvIG5vcm1hbGlzZSBvdXIgZGF0YSAodGhpcyBwcm9jZXNzIGlzIGxpa2UgdGhlIHN0YW5kYXJkaXNhdGlvbiBwcm9jZXNzIGNvdmVyZWQgaW4gW3NlY3Rpb24gNC4yIG9mIFRvcGljIDNdKGh0dHBzOi8vYm9va2Rvd24ub3JnL2Ffc2hha2VyL1NUTTEwMDFfVG9waWNfMy80LTItc3RhbmRhcmRpc2F0aW9uLmh0bWwpKS4gVGhpcyB3aWxsIHJlbW92ZSB0aGUgZWZmZWN0IG9mIGRpZmZlcmVudCB2YXJpYWJsZXMgYmVpbmcgbWVhc3VyZWQgb24gZGlmZmVyZW50IHNjYWxlcyAoZS5nLiBmbGlwcGVyIGxlbmd0aCBpbiBtbSBhbmQgYm9keSBtYXNzIGluIGdyYW1zKSwgYW5kIGNhbiBwcmV2ZW50IGFueSBzaW5nbGUgdmFyaWFibGUgb3ZlcnBvd2VyaW5nIG90aGVycyB3aGVuIGJlaW5nIGFzc2Vzc2VkIGJ5IHRoZSBjbHVzdGVyIGFsZ29yaXRobS4NCg0KTm9ybWFsaXNpbmcgdGhlIGRhdGEgY29uc2lzdHMgb2Y6IA0KDQoxLiBDb21wdXRpbmcgdGhlIHNhbXBsZSBtZWFuIGFuZCBzYW1wbGUgc3RhbmRhcmQgZGV2aWF0aW9uIGZvciBlYWNoIHZhcmlhYmxlDQoyLiBTdWJ0cmFjdGluZyB0aGUgcmVsZXZhbnQgc2FtcGxlIG1lYW4gZnJvbSBlYWNoIG9ic2VydmF0aW9uLCBhbmQgZGl2aWRpbmcgYnkgdGhlIHJlbGV2YW50IHNhbXBsZSBzdGFuZGFyZCBkZXZpYXRpb24NCg0KRm9yIGV4YW1wbGUsIHRoZSBzYW1wbGUgbWVhbiBmb3IgYGJpbGxfZGVwdGhfbW1gIGlzIHJvdWdobHkgMTcuMTUxLCBhbmQgdGhlIHNhbXBsZSBzdGFuZGFyZCBkZXZpYXRpb24gaXMgcm91Z2hseSAxLjk3NS4gVGhlIGZpcnN0IHBlbmd1aW4gaW4gdGhlIGRhdGEgc2V0IGhhcyBhIHJlY29yZGVkIGBiaWxsX2RlcHRoX21tYCB2YWx1ZSBvZiAxOC43LiBUaGUgbm9ybWFsaXNlZCB2ZXJzaW9uIG9mIHRoaXMgdmFsdWUgd291bGQgdGhlcmVmb3JlIGJlICQkXGRpc3BsYXlzdHlsZSBcZnJhY3sxOC43IC0gMTcuMTUxfXsxLjk3NX0gXGFwcHJveCAwLjc4LiQkIA0KDQpGb3J0dW5hdGVseSwgd2UgY2FuIGNvbmR1Y3QgdGhpcyBub3JtYWxpc2F0aW9uIHByb2Nlc3Mgd2l0aCBvbmUgbGluZSBvZiBjb2RlIGluIFIsIGZvciBhbGwgb3VyIHZhcmlhYmxlcy4NCg0KIyMjDQoNClVzZSB0aGUgYHNjYWxlYCBmdW5jdGlvbiB0byBub3JtYWxpc2Ugb3VyIGBwZW5ndWluc19zdWJzZXRgIGRhdGEsIGFuZCBhc3NpZ24gdGhlIG5vcm1hbGlzZWQgZGF0YSB0byB0aGUgb2JqZWN0IGBwZW5ndWluc19zY2FsZWRgLiBUaGVuLCB0YWtlIGEgbG9vayBhdCB5b3VyIG5vcm1hbGlzZWQgZGF0YSB1c2luZyB0aGUgYGhlYWRgIGZ1bmN0aW9uLg0KDQoqSGludDogWW91ciBvdXRwdXQgc2hvdWxkIGxvb2sgbGlrZSB0aGUgb3V0cHV0IGluIHRoZSBgQ29kZWAgY2h1bmsgYmVsb3c6Kg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIiwgZXZhbCA9IFQsIGluY2x1ZGUgPSBUfQ0KcGVuZ3VpbnNfc2NhbGVkIDwtIHNjYWxlKHBlbmd1aW5zX3N1YnNldCkNCmhlYWQocGVuZ3VpbnNfc2NhbGVkKQ0KYGBgDQoNCiMgay1tZWFucyBDbHVzdGVyaW5nDQoNCk5vdyB0aGF0IG91ciBkYXRhIGlzIHN1aXRhYmx5IHByZXBhcmVkLCB3ZSBjYW4gYmVnaW4gb3VyIGNsdXN0ZXIgYW5hbHlzZXMuDQoNClRoZSBmaXJzdCBjbHVzdGVyaW5nIHRlY2huaXF1ZSB3ZSB3aWxsIGNvbnNpZGVyIGlzICprLW1lYW5zIGNsdXN0ZXJpbmcqLg0KDQpJbiBrLW1lYW5zIGNsdXN0ZXJpbmcsIHdlIHNwZWNpZnkgYW4gYXJiaXRyYXJ5IG51bWJlciBvZiBjbHVzdGVycyAkayQsIGFuZCBlYWNoIGRhdGEgcG9pbnQgaXMgYXNzaWduZWQgdG8gb25lIG9mIHRoZXNlICRrJCBjbHVzdGVycywgYmFzZWQgb24gdGhlIGRpc3RhbmNlIGJldHdlZW4gdGhlIHBvaW50IGFuZCB0aGUgbWVhbiBvZiBhbGwgcG9pbnRzLiANCg0KT25jZSB0aGVzZSBpbml0aWFsIGNsdXN0ZXJzIGFyZSBjcmVhdGVkLCB0aGUgbWVhbiBvZiBlYWNoIGNsdXN0ZXIgaXMgY2FsY3VsYXRlZCwgYW5kIGFuIGl0ZXJhdGl2ZSBwcm9jZXNzIGJlZ2luczogDQoNCiogUG9pbnRzIGFyZSBtb3ZlZCBiZXR3ZWVuIGNsdXN0ZXJzLCBvbmUgcG9pbnQgYXQgYSB0aW1lLCBkZXBlbmRpbmcgb24gaG93IGNsb3NlIHRoZXkgYXJlIHRvIGVhY2ggY2x1c3RlciBtZWFuLg0KKiBUaGlzIHByb2Nlc3MgY29udGludWVzLCB1bnRpbCBubyBwb2ludCBjYW4gYmUgbW92ZWQgYmV0d2VlbiBjbHVzdGVycyB3aXRob3V0IGluY3JlYXNpbmcgdGhlIGF2ZXJhZ2UgZGlzdGFuY2UgYmV0d2VlbiB0aGUgcG9pbnRzIGFuZCB0aGUgY2x1c3RlciBtZWFuc15bU2VlIGUuZy4gc2VjdGlvbiA0LjEwLjMgb2YgQE1vZFN0YXRdLg0KDQojIyB7I2ttZWFuczN9DQoNCldlIGNhbiB1c2UgdGhlIGBrbWVhbnNgIGZ1bmN0aW9uIGZyb20gdGhlIGluYnVpbHQgYGNsdXN0ZXJgIFIgcGFja2FnZSB0byBjb25kdWN0IGEgay1tZWFucyBjbHVzdGVyaW5nIGFuYWx5c2lzLg0KUnVuIHRoZSBmb2xsb3dpbmcgUiBjb2RlIHRvIGNvbmR1Y3QgdGhpcyBhbmFseXNpcyBvbiBvdXIgYHBlbmd1aW5zX3NjYWxlZGAgZGF0YSwgd2l0aCBgaz0zYCBzcGVjaWZpZWQuDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFR9DQprbWVhbnNfZml0MyA8LSBrbWVhbnMocGVuZ3VpbnNfc2NhbGVkLCAzKQ0KYGBgDQoNCiMjDQoNCkxldCdzIGRpc2N1c3MgdGhlIHJlc3VsdHMuIElmIHdlIHJ1biB0aGUgbGluZSBga21lYW5zX2ZpdDNgLCBhIGxpc3Qgb2Ygb3V0cHV0IGFwcGVhcnMgaW4gUi4NCg0KV2UgYXJlIHByaW1hcmlseSBpbnRlcmVzdGVkIGluIHRoZSBjbHVzdGVyIG1lbWJlcnNoaXAgYXNzaWduZWQgYnkgdGhlIGFsZ29yaXRobS4NCldlIGNhbiBleHRyYWN0IHRoaXMgY2x1c3RlciBtZW1iZXJzaGlwIHVzaW5nIGBrbWVhbnNfZml0MyRjbHVzdGVyYC4NCg0KSXQgaXMgYWxzbyBpbXBvcnRhbnQgdG8gY2hlY2sgdGhlIGRldGFpbHMgaW4gdGhlIGBXaXRoaW4gY2x1c3RlciBzdW0gb2Ygc3F1YXJlcyBieSBjbHVzdGVyYCBzZWN0aW9uLg0KSGVyZSwgYGJldHdlZW5fU1NgIGRlbm90ZXMgdGhlIHN1bSBvZiBzcXVhcmVzIGJldHdlZW4gY2x1c3RlcnMgKGkuZS4sIGhvdyB3ZWxsIHRoZSBjbHVzdGVycyBhcmUgc2VwYXJhdGVkIGZyb20gZWFjaCBvdGhlciksDQp3aGlsZSBgdG90YWxfc3NgIGRlbm90ZXMgdGhlIHRvdGFsIHZhcmlhYmlsaXR5IGluIHRoZSBkYXRhLg0KVGhlIGNhbGN1bGF0aW9uIGBiZXR3ZWVuX3NzIC90b3RhbF9zc2AgdGVsbHMgdXMgaG93IG11Y2ggb2YgdGhlIHZhcmlhYmlsaXR5IGluIHRoZSBkYXRhIGlzIGFjY291bnRlZCBmb3IgYnkgdGhlIGNsdXN0ZXJzIGZvdW5kIGJ5IHRoZSBhbGdvcml0aG0uDQoNClRoaXMgdmFsdWUgY2FuIHJhbmdlIGZyb20gMCUgdG8gMTAwJSwgd2l0aCBsYXJnZXIgdmFsdWVzIGluZGljYXRpbmcgYSBiZXR0ZXIgcmVzdWx0LiBIZXJlLCA3Mi4xJSBpcyBkZWNlbnQsIGFsdGhvdWdoIHdlIG1pZ2h0IGhhdmUgZXhwZWN0ZWQgYSBzbGlnaHRseSBoaWdoZXIgcmVzdWx0Lg0KDQojIyBWaXN1YWxpc2luZyBvdXIgcmVzdWx0cyB7I2ttZWFuc3Zpc3VhbGlzZX0NCg0KV2UgY2FuIHZpc3VhbGlzZSBvdXIgcmVzdWx0cyBpbiBzZXZlcmFsIHdheXMuDQoNCkZpcnN0bHksIHJ1biB0aGUgZm9sbG93aW5nIGNvZGUgdG8gcHJvZHVjZSBhIG1hdHJpeCBvZiBzY2F0dGVyIHBsb3RzIG9mIHRoZSBjb250aW51b3VzIHZhcmlhYmxlcywgY29sb3VyZWQgYnkgY2x1c3Rlci4gDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFR9DQp3aW5kb3dzKCkgIyBvciBxdWFydHooKSBpZiB5b3UgYXJlIG9uIGEgTWFjDQpwbG90KHBlbmd1aW5zWywgYygxLDM6NildLCBjb2wgPSBrbWVhbnNfZml0MyRjbHVzdGVyKQ0KYGBgDQoNCkRvbid0IHdvcnJ5IGlmIHRoZSBwbG90cyBwcm9kdWNlZCBsb29rIGEgYml0IGNvbmZ1c2luZy4gVG8gbWFrZSB0aGUgcGxvdHMgZWFzaWVyIHRvIHJlYWQsIGNvbnNpZGVyIG1heGltaXNpbmcgdGhlIGdyYXBoaWNzIHdpbmRvdy4gDQoNClJlY2FsbCBmcm9tIFxAcmVmKG1hdHJpeHBsb3RzKSB0aGF0IGVhY2ggc2NhdHRlciBwbG90IGhlcmUgc2hvd3MgdHdvIG9mIHRoZSB2YXJpYWJsZXMgaW4gdGhlIGBwZW5ndWluc2AgZGF0YSBzZXQgcGxvdHRlZCBhZ2FpbnN0IGVhY2ggb3RoZXIuIE5vdyBob3dldmVyLCB0aGV5IGFyZSBjb2xvdXJlZCBieSBjbHVzdGVyIChhcyBkZXRlcm1pbmVkIGJ5IHRoZSBrLW1lYW5zIG1ldGhvZCkuDQoNCklmLCBieSBpbnNwZWN0aW5nIHRoZXNlIHBsb3RzLCB5b3UgY2FuIGNsZWFybHkgZGlzdGluZ3Vpc2ggYmV0d2VlbiBjbHVzdGVycywgdGhlbiB0aGUgY2x1c3RlcmluZyBtZXRob2QgaGFzIHBlcmZvcm1lZCB3ZWxsLg0KDQoqSGludDogSWYgeW91IGFyZSBoYXZpbmcgdHJvdWJsZSBpbnRlcnByZXRpbmcgdGhlIGdyYXBoLCB0aGlzIG5vdGUgbWF5IGhlbHAuIEZvciBleGFtcGxlLCB0aGUgcGxvdCBzaG93biBpbiB0aGUgZmlmdGggcm93LCBmb3VydGggY29sdW1uIHNob3VsZCBiZSBgYm9keV9tYXNzX2dgIHBsb3R0ZWQgb24gdGhlIHktYXhpcyBhZ2FpbnN0IGBmbGlwcGVyX2xlbmd0aF9tbWAgb24gdGhlIHgtYXhpcy4qDQoNCiMjDQoNCldlIGNvdWxkIGFsc28gdXNlIHRoZSBSIGNvZGUgYmVsb3cgdG8gcGxvdCB0aGUgYHNwZWNpZXNgIHZhcmlhYmxlIGFzIGEgbnVtZXJpYyB2YWx1ZSAoQWRlbGllID0gMSwgZXRjKSwgd2l0aCB0aGUgcG9pbnRzIGNvbG91cmVkIGJ5IGNsdXN0ZXI6DQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFR9DQpwbG90KGFzLm51bWVyaWMocGVuZ3VpbnMkc3BlY2llcyksIGNvbCA9IGttZWFuc19maXQzJGNsdXN0ZXIpDQpgYGANCg0KQmFzZWQgb24gdGhpcyBwbG90LCBhbmQgdGhlIHBsb3QgcHJvZHVjZWQgaW4gXEByZWYoa21lYW5zdmlzdWFsaXNlKSwgZG8geW91IHRoaW5rIHRoZSBrLW1lYW5zIGNsdXN0ZXJpbmcgaGFzIHBlcmZvcm1lZCB3ZWxsPw0KDQojIyB7I2ttZWFuc21vcmV9DQoNCkF0IHRoaXMgcG9pbnQsIGl0IG1pZ2h0IHNlZW0gdGhhdCBvdXIgam9iIGlzIGRvbmUuIFdlIGhhdmUgcmVzdWx0cyBmb3IgJGs9MyQsIGFuZCB0aGVyZSBhcmUgdGhyZWUgc3BlY2llcyBvZiBwZW5ndWlucy4NCg0KUmVtZW1iZXIgdGhvdWdoLCBub3JtYWxseSB3ZSB3b3VsZCBub3QgYWN0dWFsbHkga25vdyB3aGF0IHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgc2hvdWxkIGJlLiBUaGVyZWZvcmUsIHdlIHdvdWxkIHVzdWFsbHkgdHJ5IGEgcmFuZ2Ugb2YgZGlmZmVyZW50IGsgdmFsdWVzLg0KDQpDb25kdWN0IGstbWVhbnMgY2x1c3RlcmluZyBvbiBvdXIgYHBlbmd1aW5zX3NjYWxlZGAgZGF0YSwgZm9yICRrJCB2YWx1ZXMgb2YgMiwgNCBhbmQgNS4gDQoNCk5vdGUgZG93biB0aGUgYGJldHdlZW5fc3MgL3RvdGFsX3NzYCByZXN1bHRzLiBXaGF0IGRvIHlvdSBvYnNlcnZlPw0KDQojIyB7I25iY2x1c3R9DQoNCllvdSBtaWdodCBoYXZlIG5vdGljZWQgdGhhdCB0aGUgYGJldHdlZW5fc3MgL3RvdGFsX3NzYCB2YWx1ZSBpbmNyZWFzZXMgYXMgd2UgaW5jcmVhc2UgdGhlIG51bWJlciBvZiBjbHVzdGVycy4gVGhpcyBjYW4gaGFwcGVuIGV2ZW4gaWYgYWRkaW5nIGFub3RoZXIgY2x1c3RlciBpc24ndCBhY3R1YWxseSB0aGF0IGhlbHBmdWwuIFRoZXJlZm9yZSwgd2Ugc2hvdWxkIGFsc28gY29uc2lkZXIgc29tZSBvdGhlciBhc3Nlc3NtZW50IG1ldGhvZHMuDQoNCklmIHdlIGRpZCBub3Qga25vdyB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzLCB3ZSBjb3VsZCB1c2Ugc2V2ZXJhbCBtZXRob2RzIHRvIGRldGVybWluZSB0aGUgYXBwcm9wcmlhdGUgdmFsdWUgb2YgJGskLg0KDQpUaGUgYGZhY3RvZXh0cmFgIFIgcGFja2FnZSBjb250YWlucyBhIGhlbHBmdWwgZnVuY3Rpb24gYGZ2aXpfbmJjbHVzdGAsIHdoaWNoIHdlIGNhbiB1c2UgdG8gcHJvZHVjZSBzZXZlcmFsIGluZm9ybWF0aXZlIHBsb3RzLg0KDQpUYWtlIGEgbG9vayBhdCB0aGUgY29tbWVudHMgaW4gdGhlIGBDb2RlYCBjaHVuayBiZWxvdyAodGhlIGAuLi5gIHNpZ25pZnkgdGhhdCB0aGVzZSBhcmUgY29tbWVudHMsIGFuZCBzaG91bGQgYmUgcmVwbGFjZWQgYnkgYWN0dWFsIGFyZ3VtZW50cyk6DQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFR9DQpleGFtcGxlIDwtIGZ2aXpfbmJjbHVzdCguLi5zcGVjaWZ5IGRhdGEgc2V0IGhlcmUuLi4gLCANCiAgICAgICAgICAgICAgICAgICAgICAgIC4uLnNwZWNpZnkgY2x1c3RlcmluZyBtZXRob2QgaGVyZS4uLiAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gIi4uLnNwZWNpZnkgYXNzZXNzbWVudCBtZXRob2QgaGVyZS4uLiIpDQpgYGANCg0KQXMgeW91IGNhbiBzZWUsIHRoZSBgZnZpel9uYmNsdXN0YCBmdW5jdGlvbiB0YWtlcyB0aHJlZSBtYWluIGFyZ3VtZW50czogDQoNCjEuIFRoZSBkYXRhIHNldCwgDQoyLiBUaGUgY2x1c3RlcmluZyBtZXRob2QgKGkuZS4ga21lYW5zKSwgYW5kIA0KMy4gVGhlIGFzc2Vzc21lbnQgbWV0aG9kIChvbmUgb2YgIndzcyIgLCAic2lsaG91ZXR0ZSIgLCBvciAiZ2FwX3N0YXQiKQ0KDQojIyMNCg0KVGhlIGB3c3NgIGFzc2Vzc21lbnQgbWV0aG9kIHByb2R1Y2VzIGEgc2NyZWUgcGxvdCwgd2l0aCB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIHBsb3R0ZWQgb24gdGhlIGhvcml6b250YWwgYXhpcywgYW5kIHRoZSBUb3RhbCBXaXRoaW4gU3VtIG9mIFNxdWFyZXMgKHdzcykgdmFyaWFuY2UgcGxvdHRlZCBvbiB0aGUgdmVydGljYWwgYXhpcy4gQSBsb3dlciB2YXJpYW5jZSBpcyBwcmVmZXJhYmxlLCBidXQgYWZ0ZXIgYSBjZXJ0YWluIHBvaW50LCBhZGRpbmcgbW9yZSBjbHVzdGVycyB3aWxsIG5vdCBsb3dlciB0aGlzIHZhcmlhYmlsaXR5IHNpZ25pZmljYW50bHkuIFdoZW4gYXNzZXNzaW5nIHRoaXMgcGxvdCwgd2Ugc2VsZWN0LCBhcyBvdXIgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMsIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgZm9yIHdoaWNoIGFkZGluZyBhbiBleHRyYSBjbHVzdGVyIGRvZXNuJ3QgZ3JlYXRseSByZWR1Y2UgdGhpcyB2YXJpYWJpbGl0eS4NCg0KVXNpbmcgdGhlIGBmdml6X25iY2x1c3RgIGZ1bmN0aW9uLCB3aXRoIHRoZSBga21lYW5zYCBjbHVzdGVyaW5nIG1ldGhvZCBhbmQgdGhlIGAid3NzImAgYXNzZXNzbWVudCBtZXRob2Qgc3BlY2lmaWVkLCBkZXRlcm1pbmUgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzIGZvciBvdXIgYHBlbmd1aW5zX3NjYWxlZGAgZGF0YS4NCg0KKkhpbnQ6IElmIHlvdSBhcmUgbm90IHN1cmUgaG93IHRvIHByb2NlZWQsIGNoZWNrIHRoZSBgQ29kZWAgY2h1bmsgYmVsb3c6Kg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIiwgZXZhbCA9IEYsIGVjaG8gPSBUfQ0Ka21lYW5zX3dzcyA8LSBmdml6X25iY2x1c3QocGVuZ3VpbnNfc2NhbGVkLCBrbWVhbnMsIG1ldGhvZCA9ICJ3c3MiKQ0Kd2luZG93cygpDQprbWVhbnNfd3NzDQpgYGANCg0KIyMjDQoNClRoZSBgc2lsaG91ZXR0ZWAgYXNzZXNzbWVudCBtZXRob2QgbWVhc3VyZXMgdGhlIHNpbWlsYXJpdHkgb2YgYSBwb2ludCB0byBvdGhlciBwb2ludHMgaW4gaXRzIGNsdXN0ZXIuIFRoZSBoaWdoZXIgdGhpcyAnYXZlcmFnZSBzaWxob3VldHRlIHdpZHRoJyAsIHRoZSBiZXR0ZXIuDQoNClVzaW5nIHRoZSBgZnZpel9uYmNsdXN0YCBmdW5jdGlvbiwgd2l0aCB0aGUgYGttZWFuc2AgY2x1c3RlcmluZyBtZXRob2QgYW5kIHRoZSBgInNpbGhvdWV0dGUiYCBhc3Nlc3NtZW50IG1ldGhvZCBzcGVjaWZpZWQsIGRldGVybWluZSB0aGUgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMgZm9yIG91ciBgcGVuZ3VpbnNfc2NhbGVkYCBkYXRhLg0KDQojIyMNCg0KVGhlIGZpbmFsIGstbWVhbnMgY2x1c3RlcmluZyBhc3Nlc3NtZW50IG1ldGhvZCB3ZSB3aWxsIGNvbnNpZGVyIGlzIHRoZSBgZ2FwX3N0YXRgIGFzc2Vzc21lbnQgbWV0aG9kLiBUaGlzIGNvbXB1dGVzIGEgc3RhdGlzdGljIGtub3duIGFzIHRoZSAnZ2FwIHN0YXRpc3RpYycuIFdlIHdvbid0IGdvIGludG8gdGhlIG1hdGhlbWF0aWNhbCBkZXRhaWxzIG9mIHRoaXMgc3RhdGlzdGljOyBzdWZmaWNlIHRvIHNheSB0aGF0IGhpZ2hlciB2YWx1ZXMgYXJlIGNvbnNpZGVyZWQgcHJlZmVyYWJsZS4NCg0KVXNpbmcgdGhlIGBmdml6X25iY2x1c3RgIGZ1bmN0aW9uLCB3aXRoIHRoZSBga21lYW5zYCBjbHVzdGVyaW5nIG1ldGhvZCBhbmQgdGhlIGAiZ2FwX3N0YXQiYCBhc3Nlc3NtZW50IG1ldGhvZCBzcGVjaWZpZWQsIGRldGVybWluZSB0aGUgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMgZm9yIG91ciBgcGVuZ3VpbnNfc2NhbGVkYCBkYXRhLg0KDQojIw0KDQpXZSBjYW4gYWxzbyB1c2UgdGhlIGBmdml6X2NsdXN0ZXJgIGZ1bmN0aW9uIGZyb20gdGhlIGBmYWN0b2V4dHJhYCBSIHBhY2thZ2UgdG8gdmlzdWFsaXNlIHRoZSBjbHVzdGVycy4NCg0KUnVuIHRoZSBjb2RlIGJlbG93IHRvIHZpc3VhbGlzZSB0aGUgdGhyZWUgY2x1c3RlcnMgZm91bmQgaW4gXEByZWYoa21lYW5zMyleW05vdGUgdGhhdCB0aGVzZSBtaWdodCBsb29rIGEgbGl0dGxlIGRpZmZlcmVudCB0byB0aG9zZSB2aXN1YWxpc2VkIGluIFxAcmVmKGttZWFuc3Zpc3VhbGlzZSksIHNpbmNlIHRoZSBheGVzIHZhcmlhYmxlcyBhcmUgZGlmZmVyZW50XSwgYW5kIHRoZW4sIHVzaW5nIHRoaXMgY29kZSBhcyBhIGJhc2UsIHZpc3VhbGlzZSB0aGUgc2V0cyBvZiBjbHVzdGVycyBmb3VuZCBpbiBcQHJlZihrbWVhbnNtb3JlKS4NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBGLCBlY2hvID0gVH0NCmZ2aXpfY2x1c3RlcihrbWVhbnNfZml0MywgZGF0YSA9IHBlbmd1aW5zX3NjYWxlZCkNCmBgYA0KDQojIyANCg0KQmFzZWQgb24geW91ciBrLW1lYW5zIGNsdXN0ZXJpbmcgYW5hbHlzZXMsIHdoaWNoIHZhbHVlIG9mICRrJCB3b3VsZCB5b3UgcmVjb21tZW5kIHVzaW5nLCBhbmQgd2h5Pw0KDQojIFBBTSBDbHVzdGVyaW5nDQoNCmstbWVhbnMgY2x1c3RlcmluZyBpcyBhIGZvcm0gb2YgY2VudHJvaWQtYmFzZWQgY2x1c3RlcmluZywgd2hlcmVieSBjbHVzdGVyIG1lYW5zIChpLmUuIGNlbnRyb2lkcykgYXJlIHVzZWQgYXMgYSBtZXRyaWMgaW4gdGhlIGNsdXN0ZXJpbmcgYWxnb3JpdGhtLg0KQW4gYWx0ZXJuYXRpdmUgdG8gay1tZWFucyBjbHVzdGVyaW5nIGlzICpQQU0qIChQYXJ0aW9uIGFyb3VuZCBNZWRvaWRzKSAqY2x1c3RlcmluZyouIFRoaXMgb3BlcmF0ZXMgaW4gYSBzaW1pbGFyIG1hbm5lciB0byBrLW1lYW5zIGNsdXN0ZXJpbmcsIGJ1dCB1c2VzIG1lZG9pZHMsIChtZWRpYW4tbGlrZSBwb2ludHMpLCByYXRoZXIgdGhhbiBjZW50cm9pZHMsIHdoZW4gZGVjaWRpbmcgY2x1c3RlciBtZW1iZXJzaGlwLiBUaGlzIG1ha2VzIHRoZSBQQU0gY2x1c3RlcmluZyB0ZWNobmlxdWUgbW9yZSByb2J1c3QgdG8gb3V0bGllcnMuDQoNCiMjIHsjcGFtfQ0KDQpXZSBjYW4gZWFzaWx5IGNvbmR1Y3QgUEFNIGNsdXN0ZXJpbmcgdXNpbmcgdGhlIGBwYW1gIGZ1bmN0aW9uIGZyb20gdGhlIGluYnVpbHQgYGNsdXN0ZXJgIFIgcGFja2FnZS4gVGhlIHByb2Nlc3MgaXMgc2ltaWxhciB0byB0aGUgay1tZWFucyBjbHVzdGVyaW5nIHByb2Nlc3MgY29uZHVjdGVkIGluIFxAcmVmKGttZWFuczMpLg0KDQpVc2UgdGhlIGBwYW1gIGZ1bmN0aW9uIHRvIGNvbmR1Y3QgUEFNIGNsdXN0ZXJpbmcgZm9yICRrJCB2YWx1ZXMgb2YgMiwgMywgNCBhbmQgNS4gDQoNCiMjIHsjcGFtYmVzdH0NCg0KTmV4dCwgdXNlIHRoZSBgZnZpel9uYmNsdXN0YCBmdW5jdGlvbiB0byBkZXRlcm1pbmUgdGhlIGJlc3QgbnVtYmVyIG9mIFBBTSBjbHVzdGVycyB0byB1c2UgKHVzZSBhbGwgdGhyZWUgbWV0aG9kcykuDQoNCipIaW50OiBVc2UgYHBhbWAgaW5zdGVhZCBvZiBga21lYW5zYCBmb3IgdGhlIGNsdXN0ZXJpbmcgbWV0aG9kIHNwZWNpZmljYXRpb24uKg0KDQojIw0KDQpGaW5hbGx5LCB1c2UgdGhlIGBmdml6X2NsdXN0ZXJgIGZ1bmN0aW9uIGFuZCB0aGUgY29kZSBiZWxvdyB0byB2aXN1YWxpc2UgdGhlIGNsdXN0ZXJzIGZvciB0aGUgUEFNIGNsdXN0ZXJpbmcgcmVzdWx0KHMpIHlvdSBjaG9zZSBhYm92ZSBpbiBcQHJlZihwYW1iZXN0KS4gV2hhdCBkbyB5b3UgY29uY2x1ZGU/DQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gRiwgZWNobyA9IFR9DQojIFRoaXMgY29kZSBhc3N1bWVzIHlvdSBhcmUgYXNzZXNzaW5nIGEgcmVzdWx0IHN0b3JlZCBpbiB0aGUgb2JqZWN0IHBhbV9maXQzDQp3aW5kb3dzKCkNCnBsb3QoYXMubnVtZXJpYyhwZW5ndWlucyRzcGVjaWVzKSwgY29sID0gcGFtX2ZpdDMkY2x1c3RlcmluZykNCmBgYA0KDQo8YnI+DQoNCiMjIyMgQXQgdGhpcyBwb2ludCwgd2UndmUgY292ZXJlZCB0aGUgbWFpbiBtYXRlcmlhbCBmb3IgdGhpcyBjb21wdXRlciBsYWIuIElmIHlvdSB3b3VsZCBsaWtlLCB5b3UgY2FuIGV4dGVuZCB5b3VyIGtub3dsZWRnZSBvbiBjbHVzdGVyaW5nIGJ5IGdvaW5nIHRocm91Z2ggdGhlIEV4dGVuc2lvbiBzZWN0aW9ucyBiZWxvdy4gT3RoZXJ3aXNlLCBpZiB5b3UgaGF2ZSB0aW1lIGxlZnQsIHlvdSBtaWdodCBsaWtlIHRvIHdvcmsgb24geW91ciBhc3Nlc3NtZW50cy4gIyMjIyB7LX0NCg0KPGJyPg0KDQojIEV4dGVuc2lvbiAxIC0gSGllcmFyY2hpY2FsIENsdXN0ZXJpbmcNCg0KVW5saWtlIHRoZSBwcmV2aW91cyBjbHVzdGVyaW5nIHRlY2huaXF1ZXMgZGlzY3Vzc2VkLCBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyBiZWdpbnMgYnkgYXNzaWduaW5nIGVhY2ggcG9pbnQgdG8gaXRzIG93biBjbHVzdGVyLiBGb3IgZXhhbXBsZSwgc2luY2Ugd2UgaGF2ZSAzMzMgcGVuZ3VpbnMgaW4gb3VyIGZpbHRlcmVkIGRhdGEgc2V0LCB3ZSB3b3VsZCBzdGFydCB3aXRoIDMzMyBjbHVzdGVycy4gVGhlbiwgdGhlIHR3byBtb3N0IHNpbWlsYXIgY2x1c3RlcnMgYXJlIG1lcmdlZCAoY29udGludWluZyBvdXIgZWFtcGxlLCB0aGlzIHdvdWxkIHJlc3VsdCBpbiB1cyBub3cgaGF2aW5nIDMzMiBjbHVzdGVycykuIFdlIHJlcGVhdCB0aGlzIHByb2Nlc3MsIHVudGlsIHdlIGhhdmUganVzdCBvbmUgbGFyZ2UgY2x1c3RlciAod2hpY2ggY29udGFpbnMgYWxsIHRoZSBwb2ludHMpLg0KDQpBcyBhIHJlc3VsdCwgd2Ugbm93IGhhdmUgYSAqaGllcmFyY2h5KiBvZiBjbHVzdGVycywgd2hpY2ggbG9va3MgYSBiaXQgbGlrZSBhbiB1cHNpZGUtZG93biB0cmVlICh3aXRoIG9ic2VydmF0aW9ucyBmcm9tIHRoZSBzYW1lIGNsdXN0ZXIgb24gdGhlIHNhbWUgJ2JyYW5jaCcpLg0KDQojIyB7I2hpZXJhcmNoaWNhbH0NCg0KV2UgY2FuIGNvbmR1Y3QgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgdXNpbmcgdGhlIGBhZ25lc2AgZnVuY3Rpb24gZnJvbSB0aGUgYGNsdXN0ZXJgIFIgcGFja2FnZS4gU2luY2Ugd2UgYXJlIG5vdCBzcGVjaWZ5aW5nIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMsIHdlIG9ubHkgbmVlZCB0byBwcm92aWRlIHRoZSBvbmUgYXJndW1lbnQsIGkuZS4gdGhlIGRhdGEgc2V0IHRvIGFuYWx5c2UuDQoNClVzZSB0aGUgYGFnbmVzYCBmdW5jdGlvbiB0byBjb25kdWN0IGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG9uIG91ciBzY2FsZWQgcGVuZ3VpbiBkYXRhLg0KDQojIw0KDQpXZSBjYW4gdmlzdWFsaXNlIHRoZSByZXN1bHRzIG9mIHRoZSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyB1c2luZyBhICpkZW5kcm9ncmFtKi4NCg0KQ29tcGxldGUgdGhlIGNvZGUgYmVsb3cgdG8gY3JlYXRlIGEgZGVuZHJvZ3JhbSBmb3IgeW91ciByZXN1bHRzIChqdXN0IHJlcGxhY2UgdGhlIGAuLi5gIHdpdGggeW91ciByZXN1bHRzKQ0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IEYsIGVjaG8gPSBUfQ0KcGxvdCguLi4sIHdoaWNoID0gMikNCmBgYA0KDQojIw0KDQpUaGVyZSBhcmUgc2V2ZXJhbCBvcHRpb25zIHdlIGNhbiB1c2Ugd2hlbiBjb25kdWN0aW5nIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nLiBUaGVzZSByZWxhdGUgdG8gdGhlIHdheSBpbiB3aGljaCBkaXN0YW5jZSBpcyBtZWFzdXJlZCBiZXR3ZWVuIHR3byBwb2ludHMsIHRvIGRldGVybWluZSB0aGVpciBsZXZlbCBvZiBzaW1pbGFyaXR5LiANCg0KVGhlIGRlZmF1bHQgbWV0aG9kIGluIHRoZSBgYWduZXNgIGZ1bmN0aW9uIGlzIGBhdmVyYWdlYC4gVHJ5IHJlLXJ1bm5pbmcgeW91ciBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyBhbGdvcml0aG0gdXNpbmcgc29tZSBvdGhlciBvcHRpb25zLCBieSBhZGRpbmcgdGhlIGFyZ3VtZW50IGBtZXRob2QgPSAiLi4uImAgdG8geW91ciBjb2RlIGluIFxAcmVmKGhpZXJhcmNoaWNhbCksIGFuZCByZXBsYWNpbmcgdGhlIGAiLi4uImAgd2l0aCBgInNpbmdsZSJgLCBgImNvbXBsZXRlImAgYW5kIHRoZW4gYCJ3YXJkImAuIA0KDQpXaGVuIHlvdSB2aXN1YWxpc2UgdGhlIHJlc3VsdHMsIGRvIHRoZSBkZW5kcm9ncmFtcyBsb29rIHZlcnkgZGlmZmVyZW50Pw0KDQojIw0KDQpTbyBmYXIsIHRoZSBkZW5kcm9ncmFtcyB3ZSBoYXZlIHByb2R1Y2VkIGxvb2tlZCByYXRoZXIgdW5hcHBlYWxpbmcgLSB3ZSBjYW4gZG8gYmV0dGVyLg0KDQpJdCBpcyB3b3J0aCBub3RpbmcgdGhhdCB0aGUgc2VsZWN0aW9uIG9mIGFuIGFwcHJvcHJpYXRlIGhvcml6b250YWwgY3V0LW9mZiBwb2ludCBvbiB0aGUgZGVuZHJvZ3JhbSAodGh1cyBzZWxlY3RpbmcgdGhlIG51bWJlciBvZiBjbHVzdGVycyB0byB1c2UpIGlzIGFyYml0cmFyeS4gQmFzZWQgb24geW91ciBmaW5kaW5ncyBmcm9tIHRoZSBwcmV2aW91cyBhbmFseXNlcyBob3dldmVyLCB5b3Ugc2hvdWxkIG5vdyBoYXZlIGRlY2lkZWQgb24gYW4gJ29wdGltYWwnIG51bWJlciBvZiAkayQgY2x1c3RlcnMgdG8gc2VsZWN0LiANCg0KV2UgY2FuIHZpc3VhbGlzZSB0aGUgZGVuZHJvZ3JhbSB3aXRoIHRoZSB0cmVlIGN1dCBpbnRvIHRoZXNlICRrJCBjbHVzdGVycyB1c2luZyB0aGUgYGhjdXRgIGFuZCBgZnZpel9kZW5kYCBmdW5jdGlvbnMgZnJvbSB0aGUgYGZhY3RvZXh0cmFgIFIgcGFja2FnZS4NCg0KVGFrZSBhIGxvb2sgYXQgdGhlIGNvZGUgYmVsb3csIGFuZCBmaWxsIGluIHRoZSBgLi4uYCBtaXNzaW5nIHBhcnRzIHdpdGggdGhlIHJlbGV2YW50IGRldGFpbHMuIE9uY2UgeW91IGFyZSBoYXBweSB3aXRoIHRoZSBjb2RlLCBydW4gaXQuIFRoZSByZXN1bHRhbnQgZGVuZHJvZ3JhbSBzaG91bGQgbG9vayBtdWNoIG5pY2VyLg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IEYsIGVjaG8gPSBUfQ0KcGVuZ3Vpbl9kZW5kcm8gPC0gaGN1dCguLi4sIA0KICAgICAgICAgICAgICAgIGsgPSAuLi4sIA0KICAgICAgICAgICAgICAgIGhjX2Z1bmMgPSAiYWduZXMiLA0KICAgICAgICAgICAgICAgIGhjX21ldGhvZCA9ICIuLi4iLA0KICAgICAgICAgICAgICAgIGhjX21ldHJpYyA9ICJldWNsaWRlYW4iKSANCg0KZnZpel9kZW5kKHBlbmd1aW5fZGVuZHJvKQ0KYGBgDQoNCipOb3RlOiBZb3Ugd2lsbCBuZWVkIHRvIHNwZWNpZnkgdGhlIG1lYXN1cmVtZW50IG1ldGhvZCwgZS5nLiBgIndhcmQiYCBpbiB0aGUgYGhjX21ldGhvZCA9YCBhcmd1bWVudC4qDQoNCiMgRXh0ZW5zaW9uIDIgLSBGdXp6eSBDbHVzdGVyaW5nDQoNCkZvciB0aGUgcHJldmlvdXMgY2x1c3RlcmluZyBtZXRob2RzLCBlYWNoIGRhdGEgcG9pbnQgYmVsb25nZWQgdG8gb25seSBvbmUgY2x1c3Rlci4gVGhpcyBpcyBzb21ldGltZXMgcmVmZXJyZWQgdG8gYXMgKmhhcmQgY2x1c3RlcmluZyouIA0KDQpJbiBjb250cmFzdCwgaW4gZnV6enkgY2x1c3RlcmluZyBlYWNoIHBvaW50IGlzIGFsbG93ZWQgdG8gYmVsb25nIHRvIG11bHRpcGxlIGNsdXN0ZXJzLiBUaGUgbW9yZSBzaW1pbGFyIHRoZSBwb2ludCBpcyB0byBvdGhlciBwb2ludHMgaW4gYSBjbHVzdGVyLCB0aGUgaGlnaGVyIHRoZSBwb2ludCdzIHBlcmNlbnRhZ2Ugb2YgbWVtYmVyc2hpcCB0byB0aGF0IGNsdXN0ZXJeW1NlZSBlLmcuIEBNb2RTdGF0XS4NClRoaXMgY291bGQgYmUgaGVscGZ1bCB3aGVuIGEgcG9pbnQgbmF0dXJhbGx5IHNob3VsZCBiZWxvbmcgdG8gbXVsdGlwbGUgY2x1c3RlcnMgKGZvciBpbnN0YW5jZSwgaWYgcGVuZ3VpbnMgd2VyZSBjbHVzdGVyZWQgaW50byA2IGNsdXN0ZXJzLCBmb3IgbWFsZXMgYW5kIGZlbWFsZXMgb2YgZWFjaCBzcGVjaWVzLCB0aGVuIGVhY2ggcGVuZ3VpbiB3b3VsZCBiZWxvbmcgdG8gdHdvIGNsdXN0ZXJzKS4NCg0KIyMNCg0KV2UgY2FuIGVhc2lseSBjb25kdWN0IGZ1enp5IGNsdXN0ZXJpbmcgdXNpbmcgdGhlIGBmYW5ueWAgZnVuY3Rpb24gZnJvbSB0aGUgaW5idWlsdCBgY2x1c3RlcmAgUiBwYWNrYWdlLiBUaGUgcHJvY2VzcyBpcyBzaW1pbGFyIHRvIHRoZSBrLW1lYW5zIGNsdXN0ZXJpbmcgcHJvY2VzcyAoY29uZHVjdGVkIGluIFxAcmVmKGttZWFuczMpKSBhbmQgdGhlIFBBTSBjbHVzdGVyaW5nIHByb2Nlc3MgKGNvbmR1Y3RlZCBpbiBcQHJlZihwYW0pKS4NCg0KVXNlIHRoZSBgZmFubnlgIGZ1bmN0aW9uIHRvIGNvbmR1Y3QgZnV6enkgY2x1c3RlcmluZyBmb3IgJGskIHZhbHVlcyBvZiAyLCAzLCA0IGFuZCA1LiANCg0KKk5vdGU6IFRoZSBvdXRwdXQgZm9yIHRoZXNlIGFuYWx5c2VzIHdpbGwgaW5jbHVkZSBhIGBtZW1iZXJzaGlwIGNvZWZmaWNpZW50c2AgbGlzdCBvZiB0aGUgbWVtYmVyc2hpcCBwZXJjZW50YWdlcyBmb3IgZWFjaCBwb2ludC4gV2hpbGUgaXQgaXMgbm90IGVhc3kgdG8gdmlzdWFsaXNlIHRoZXNlLCB0aGUgb3V0cHV0IGFsc28gaW5jbHVkZXMgdGhlIGNsb3Nlc3QgJ2hhcmQnIGNsdXN0ZXIgZm9yIGVhY2ggcG9pbnQsIHdoaWNoIGNhbiBiZSBjYWxsZWQgdmlhIGAkY2x1c3RlcmAsIGFzIHBlciB0aGUgcHJldmlvdXMgY2x1c3RlcmluZyB0ZWNobmlxdWVzLioNCg0KIyMgeyNmdXp6eWJlc3R9DQoNCk5leHQsIHVzZSB0aGUgYGZ2aXpfbmJjbHVzdGAgZnVuY3Rpb24gdG8gZGV0ZXJtaW5lIHRoZSBiZXN0IG51bWJlciBvZiBmdXp6eSBjbHVzdGVycyB0byB1c2UuIEp1c3QgdXNlIHRoZSBgd3NzYCBhbmQgYHNpbGhvdWV0dGVgIG1ldGhvZHMgKHRoZSBgZ2FwX3N0YXRgIG1ldGhvZCBjYW4gdGFrZSBhIGxvbmcgdGltZSB0byBjb21wdXRlIGZvciBmdXp6eSBjbHVzdGVyaW5nKS4NCg0KKk5vdGU6IERvbid0IHdvcnJ5IGlmIGFueSB3YXJuaW5nIG1lc3NhZ2VzIGxpa2UgdGhlIG9uZSBzaG93biBpbiB0aGUgY29kZSBjaHVuayBiZWxvdyBhcHBlYXIgLSB5b3Ugc2hvdWxkIHN0aWxsIGJlIGFibGUgdG8gcHJvZHVjZSB0aGUgcGxvdHM6Kg0KDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUiLCBldmFsID0gRiwgZWNobyA9IFR9DQojIyBXYXJuaW5nIGluIEZVTmNsdXN0ZXIoeCwgaSwgLi4uKTogRkFOTlkgYWxnb3JpdGhtIGhhcyBub3QgY29udmVyZ2VkIGluICdtYXhpdCcgPQ0KIyMgNTAwIGl0ZXJhdGlvbnMNCmBgYA0KDQojIw0KDQpVc2UgdGhlIGBmdml6X2NsdXN0ZXJgIGZ1bmN0aW9uIGFuZCB0aGUgY29kZSBiZWxvdyB0byB2aXN1YWxpc2UgdGhlIGNsdXN0ZXJzIGZvciB0aGUgZnV6enkgY2x1c3RlcmluZyByZXN1bHQgeW91IGNob3NlIGFib3ZlIGluIFxAcmVmKGZ1enp5YmVzdCkuIFdoYXQgZG8geW91IGNvbmNsdWRlPw0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IEYsIGVjaG8gPSBUfQ0KIyBUaGlzIGNvZGUgYXNzdW1lcyB5b3UgYXJlIGFzc2Vzc2luZyBhIHJlc3VsdCBzdG9yZWQgaW4gdGhlIG9iamVjdCBmdXp6eV9maXQzDQp3aW5kb3dzKCkNCnBsb3QoYXMubnVtZXJpYyhwZW5ndWlucyRzcGVjaWVzKSwgY29sID0gZnV6enlfZml0MyRjbHVzdGVyKQ0KYGBgDQoNCjxicj4NCg0KIyMjIyBHcmVhdCB3b3JrLCB0aGF0J3MgZXZlcnl0aGluZyBmb3IgdG9kYXkhICMjIyMgey19DQoNClRoYXQgY29uY2x1ZGVzIG91ciB3b3JrIG9uIGNsdXN0ZXJpbmcgdGVjaG5pcXVlcy4gRGlkIHlvdSBoYXZlIGEgcHJlZmVyZW5jZSBmb3Igb25lIG9mIHRoZSBmb3VyIHRlY2huaXF1ZXMgaW50cm9kdWNlZCBpbiB0aGlzIGNvbXB1dGVyIGxhYj8NCg0KPGJyPg0KDQojIFJlZmVyZW5jZXMgey0gI1JlZn0NCjxkaXYgaWQ9InJlZnMiPjwvZGl2Pg0KDQo8YnI+DQoNCjxmb250IGNvbG9yID0gImdyZXkiPg0KVGhlc2Ugbm90ZXMgaGF2ZSBiZWVuIHByZXBhcmVkIGJ5IFJ1cGVydCBLdXZla2UuIFBsZWFzZSBub3RlIHRoYXQgc29tZSBvZiB0aGUgY29udGVudCBpbiB0aGVzZSBub3RlcyBoYXMgYmVlbiBkZXZlbG9wZWQgZnJvbSBjb250ZW50IGluIEBNb2RTdGF0LiBUaGUgY29weXJpZ2h0IGZvciB0aGUgbWF0ZXJpYWwgaW4gdGhlc2Ugbm90ZXMgcmVzaWRlcyB3aXRoIHRoZSBhdXRob3JzIG5hbWVkIGFib3ZlLCB3aXRoIHRoZSBEZXBhcnRtZW50IG9mIE1hdGhlbWF0aWNzIGFuZCBTdGF0aXN0aWNzIGFuZCB3aXRoIExhIFRyb2JlIFVuaXZlcnNpdHkuIENvcHlyaWdodCBpbiB0aGlzIHdvcmsgaXMgdmVzdGVkIGluIExhIFRyb2JlIFVuaXZlcnNpdHkgaW5jbHVkaW5nIGFsbCBMYSBUcm9iZSBVbml2ZXJzaXR5IGJyYW5kaW5nIGFuZCBuYW1pbmcuIFVubGVzcyBvdGhlcndpc2Ugc3RhdGVkLCBtYXRlcmlhbCB3aXRoaW4gdGhpcyB3b3JrIGlzIGxpY2Vuc2VkIHVuZGVyIGEgQ3JlYXRpdmUgQ29tbW9ucyBBdHRyaWJ1dGlvbi1Ob24gQ29tbWVyY2lhbC1Ob24gRGVyaXZhdGl2ZXMgTGljZW5zZSANCjxhIGhyZWYgPSAiaHR0cHM6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLW5kLzQuMC9DQyIgdGFyZ2V0PSJfYmxhbmsiPiBCWS1OQy1ORC4gPC9hPg0KPC9mb250Pg==