1 Introduction

This note overviews the basic unsupervised machine learning algorithms (also known as knowledge discovery) in which models are not supervised using a training data set. Instead, models themselves find the hidden patterns and insights from the given data.

The goal of unsupervised learning is to find the underlying structure of the data set and group that data according to similarities. Common algorithms used in unsupervised learning include clustering, anomaly detection, neural networks, and approaches for learning latent variable models.

The following types of unsupervised machine learning algorithms are commonly used in practice.

  • K-means Clustering
  • Hierarchical Clustering
  • Anomaly Detection
  • Principal Component Analysis

There are many methods to calculate this distance information; the choice of distance measures is a critical step in clustering. It defines how the similarity of two data points (x, y) is calculated and it will influence the shape of the clusters. The choice of distance measures is a critical step in clustering. It defines how the similarity of two data points (x, y) is calculated and it will influence the shape of the clusters.

Figure 1.  Demonstration of the clustering process.

Figure 1. Demonstration of the clustering process.

Here are a few sites you check for these “distances”.

In the next few sections, we will describe each of these algorithms.

2 K-means Clustering

K-means algorithm is an iterative algorithm that partitions the data set into K pre-defined, distinct, and non-overlapping subgroups (clusters) where each data point belongs to only one group. Data points within each subgroup are similar while data points across the subgroups are “different** according to a selected dissimilarity measure used in the algorithm.

In other words, k-means clustering consists of defining clusters so that the total intra-cluster variation (known as a total within-cluster variation) is minimized. There are several k-means algorithms available. The standard algorithm is to define the total within-cluster variation as the sum of squared (SS) distances (Euclidean distances) between data points and the corresponding centroid. To be more specific, let \(x_i\) be the data point in cluster k, denoted by \(C_k\) and \(\mu_k\) is the center of cluster \(C_k\) (i.e. the mean value of the points when Euclidean distance is used). The within-cluster SS is defined by

\[ W(C_k) = \sum_{x_i\in C_k} (x_i - \mu_i)^2 \]

Each observation (\(x_i\)) is assigned to a given cluster such that the sum of squares (SS) distance of the observation to their assigned cluster centers \(\mu_k\) is a minimum.

We define the total within-cluster variation as follows.

\[ \mbox{TW} = \sum_{k=1}^k W(C_l) = \sum_{i=1}^k\sum_{x_k\in C_k}(x_i - \mu_k)^2 \]

The total within-cluster sum of square measures the compactness (i.e. goodness) of the clustering and we want it to be as small as possible.

Two fundamental questions to answer are: (1) how many initial clusters should be selected; (2) how to choose the initial “centers”.

Figure 2. illustration of cluster analysis.

Figure 2. illustration of cluster analysis.

K-means clustering algorithm works in the following three steps.

2.1 Optimal Number of Clusters Determination

Several algorithms can be used to find the optimal number of clusters. Elbow and Silhouette algorithms are commonly used and are implemented in R.

Elbow Method

The Elbow method gives us an idea of what a good k number of clusters would be based on the sum of squared distance (SSE) between data points and their assigned clusters’ centroids. We pick k at the spot where SSE starts to flatten out and form an elbow. We’ll use the geyser dataset and evaluate SSE for different values of k and see where the curve might form an elbow and flatten out.

Elbow is one of the most famous methods for selecting the right value of k. We also perform the hyper-parameter tuning to choose the best value of k. The Elbow method is an empirical method to find out the best value of k.

Figure 3. (a) A visual curve with an explicit elbow point. (b) A visual curve being fairly smooth with an ambiguous elbow point.

Figure 3. (a) A visual curve with an explicit elbow point. (b) A visual curve being fairly smooth with an ambiguous elbow point.

Silhouette Method

The Silhouette Method uses a similarity measure (Silhouette coefficient) that is defined in the following

\[ S_i = \frac{b_i-a_i}{\max{ \{a_i, b_i\}}}, \]

where \(S_i\) is the silhouette coefficient of the data point \(i\); \(a_i\) is the average distance between \(i\) and all the other data points in the cluster to which \(i\) belongs, and \(b_i\) is the average distance from \(i\) to all clusters to which \(i\) does not belong.

We can plot the Silhouette coefficient against the pre-determined clusters \(k\). The plot of the silhouette is between \(-1\) to \(1\).

A high average silhouette width indicates good clustering. The average silhouette method computes the average silhouette of observations for different values of k. The optimal number of clusters k is the one that maximizes the average silhouette over a range of possible values for k.

Observe the plot and choose the k value that is closer to 1 as the optimal number of clusters.

2.2 Steps for K-mean Algorithm

There different versions of k-mean algorithms. The most commonly used are three: Lloyd, McQueen and Hartigan-Wong. Lloyd algorithms is easy to understand. We will use it to illustrate the step of K-mean clustering. R implemeted all three algorithms in the R function kmean().

2.2.1 Initialize Centroids.

Initialize centroids by first shuffling the data set and then randomly selecting K data points for the centroids without replacement.

2.2.2 Update Centroids

Updating centroids is an iterative process:

  1. Compute the sum of the squared distance between data points and all (initial) centroids. Assign each data point to the closest cluster (centroid).

  2. Compute the (updating) centroids for the clusters by taking the average of all data points that belong to each cluster.

Keep iterating until there is no change to the centroids. i.e., the assignment of data points to clusters isn’t changing.

The following figure illustrates how to find the updated centroids immediately after the initial centroids.

Figure 4. Updating centroids in the process of finding the final centroids

Figure 4. Updating centroids in the process of finding the final centroids

2.3 Some Remarks on K-means

  1. K-means clustering assumes numerical features since the Euclidean distance is used to define similarity measures.

  2. K-Means clustering performs well only for a convex set of clusters and not for non-convex sets.

  3. Recent development allows categorical feature variables with non-Euclidean distance.

  4. The k-means algorithm does not guarantee finding the optimal solution. k-means is a fairly simple sequence of tasks and its clustering quality depends a lot on two factors: the number of k clusters and initial centroids.

2.4 Case Study

For illustrative purposes, we only use two numerical variables in a simple data set that is publically available in Github https://raw.githubusercontent.com/satishgunjal/datasets/master/Mall_Customers.csv.

df = read.csv("https://raw.githubusercontent.com/satishgunjal/datasets/master/Mall_Customers.csv")
## rename the two variables and then subset the data
names(df)[names(df)=="Annual.Income..k.."] = "AnnualIncome"
names(df)[names(df)=="Spending.Score..1.100."] = "SpendingScore"
clust.data = df[, c("AnnualIncome", "SpendingScore")]
scaled.data = as.data.frame(scale(clust.data)[,1:2])
distance = get_dist(scaled.data)
fviz_dist(distance, gradient = list(low = "yellow", mid = "orange", high = "darkred"), show_labels = FALSE)
Figure 5: Heatmap representation of potential clusters

Figure 5: Heatmap representation of potential clusters

The above heatmap indicates that different clusters exist in this data (based on the two numerical variables).

  • The syntax of kmeans() is given in the following code chunk.
k2 <- kmeans(x = scaled.data, 
             centers = 2, 
             iter.max = 10,
             nstart = 25,
             algorithm = "Lloyd", #"Hartigan-Wong",
             trace = FALSE)
  • Determination of optimal class.

We use the elbow method to find the optimal number of clusters.

wss = NULL
K = 15
for (i in 1:K){
  wss[i] = kmeans(scaled.data, i, 1 )$tot.withinss
 }
## elbow plot
plot(1:K, wss, type ="b",
          col= "blue",
          xlab="Number of Clusters",
          ylab = "WSS",
          main = "Elbow Plot for Selecting Optimal Number of Clusters")
Figure 6: Elbow plot for optimal number of clusters.

Figure 6: Elbow plot for optimal number of clusters.

From the above elbow plot, it seems that the optimal number of clusters is 5. So select k - 5 hereafter.

  • Cluster the data with 5 centroids

We will cluster the data into 5 groups and then add the cluster ID to the data. Since only two continuous feature variables were used to cluster the data. After we added the cluster ID to the data, we used color coding to make a scatter plot and view the clusters.

k5 <- kmeans(x = scaled.data, 
             centers = 5, 
             iter.max = 10,
             nstart = 25,
             algorithm = "Lloyd", # "Hartigan-Wong",
             trace = FALSE)
scaled.data$group = k5$cluster
### Plot the clusters
# Scatter plot
plot(scaled.data$AnnualIncome, scaled.data$SpendingScore,
     pch = 19,
     col = factor(scaled.data$group),
     xlab ="Spending Score",
     ylab = "Annual Income",
     main = "Clustering Performance Visual Check")

# Legend
legend("topright",
       legend = levels(factor(scaled.data$group)),
       pch = 19,
       col = factor(levels(factor(scaled.data$group))))
Figure 7. Final cluster results: visual inspection

Figure 7. Final cluster results: visual inspection

3 Hierarchical Clustering

In this section, we introduce one of the most popular clustering methods: ** Hierarchical Clustering **. Hierarchical clustering is an alternative approach to k-means clustering for identifying groups in the dataset. It does not require us to pre-specify the number of clusters to be generated as is required by the k-means approach. Furthermore, hierarchical clustering has an added advantage over K-means clustering in that it results in an attractive tree-based representation of the observations, called a dendrogram.

3.1 Types of Hierarchical Clustering

There are two types of hierarchical clustering: agglomerative and divisive.

  • Agglomerative*: An agglomerative approach begins with each observation in a distinct (singleton) cluster, and successively merges clusters until a stopping criterion is satisfied.

  • Divisive: A divisive method begins with all patterns in a single cluster and performs splitting until a stopping criterion is met.

Figure 8. Illustration of types of hierarchical clustering.

Figure 8. Illustration of types of hierarchical clustering.

As an example, we look at how agglomerative clustering works using five data points in the following figure.

Figure 9. Illustration of steps of agglomerative hierarchical clustering.

Figure 9. Illustration of steps of agglomerative hierarchical clustering.


3.2 Case Study I – Clustering with Two Features

We still use the same data set that we used in the previous case study of K-means clustering but will include age variable in the data frame for following hierarchical clustering.

df = read.csv("https://pengdsci.github.io/STA551/w11/Mall_Customers.csv")
## Rename the two variables and then subset the data
names(df)[names(df)=="Annual.Income..k.."] = "AnnualIncome"
names(df)[names(df)=="Spending.Score..1.100."] = "SpendingScore"
hierarch.data = df[, c("Age", "AnnualIncome", "SpendingScore")]


3.2.1 Pre-processing Operations for Clustering

There are a couple of things you should take care of before starting.

Scaling is imperative that we normalize the scale of feature values in order to start with the clustering process. This is because each observation’s feature values are represented as coordinates in n-dimensional space (n is the number of features) and then the distances between these coordinates are calculated. If these coordinates are not normalized, then it may lead to false results. R has functions scale() and normalize().

Missing Value imputation is also important to deal with missing/null/inf values in your data set beforehand. There are many ways to deal with such values, one is to either remove them or impute them with mean, median, mode, or use some advanced regression techniques. R has many packages and functions to deal with missing value imputations like impute().

3.2.2 Hierarchical Clustering with R

There are different functions available in R for computing hierarchical clustering. The commonly used functions are:

  • hclust() [in stats package] and agnes() [in cluster package] for agglomerative hierarchical clustering (HC).

  • diana() [in cluster package] for divisive HC.

scales.hierarch = as.data.frame(hierarch.data)
distance <- dist(scales.hierarch, method = "euclidean")
# Hierarchical clustering using Complete Linkage
hc1 <- hclust(distance, method = "complete" )
# Plot the obtained dendrogram
plot(hc1, cex = 0.6, labels = FALSE, hang = -1, xlab = "", main = "Dendrogram: hierarchical clustering")
rect.hclust(hc1, k = 5, border = 2:9)
Figure 10. Dendrogram: hierarchical clustering

Figure 10. Dendrogram: hierarchical clustering

# Figured this out by coloring the labels white to the background
avg_dend_obj <- as.dendrogram(hc1)
labels_colors(avg_dend_obj) <- "white"
plot(avg_dend_obj, cex = 0.6, 
     labels = FALSE, 
     hang = -1, 
     xlab = "", 
     ylab= "Height",
     main = "Dendrogram: hierarchical clustering: No X-Labels")
rect.hclust(hc1, k = 5, border = 2:9)
Figure 11. Dendrogram: hierarchical clustering: No X-Labels

Figure 11. Dendrogram: hierarchical clustering: No X-Labels

3.2.3 Determination Optimal Number of Clusters

The determination of the optimal number of clusters is an important and challenging problem. In hierarchical clustering, different similarity measures impact the number of optimal clusters. We will not discuss this topic in detail. To know more about this topic, you are referred to the following article with examples in R https://www.jstatsoft.org/article/view/2194/798.

We can use the same elbow and silhouette methods to plot.

fviz_nbclust(scales.hierarch, FUN = hcut, method = "wss")
Figure 12. Elbow plot: Optimal number of clusters

Figure 12. Elbow plot: Optimal number of clusters

fviz_nbclust(scales.hierarch, FUN = hcut, method = "silhouette")
Figure 13. Silhouette plot: Optimal number of clusters

Figure 13. Silhouette plot: Optimal number of clusters

3.2.4 Extracting Cluster ID

The above elbow plot indicates that choosing 4 clusters is appropriate. Next, we perform a 4-cluster analysis and extract the cluster ID to add them to the data frame. This cluster ID could be used as a new feature variable in subsequent modeling.

hc4 <- hclust(distance, method = "complete" )
group = cutree(hc4, k = 6)
scales.hierarch$group = group
## 
plot(scales.hierarch$AnnualIncome, scales.hierarch$SpendingScore,
     pch = 19,
     col = factor(scales.hierarch$group),
     xlab ="Spending Score",
     ylab = "Annual Income",
     main = "Hierarchical Clustering Performance Visual Check")

## Legend
legend("topright",
       legend = levels(factor(scales.hierarch$group)),
       pch = 19,
       col = factor(levels(factor(scales.hierarch$group))))
Figure 14. Visual check the resulting clusters obtained from agglomerative hierarchical clustering.

Figure 14. Visual check the resulting clusters obtained from agglomerative hierarchical clustering.


3.3 Case Study II: Multi-class Clustering

The Iris Dataset contains four features (length and width of sepals and petals) of 50 samples of three species of Iris (Iris setosa, Iris virginica, and Iris versicolor). These measures were used to create a linear discriminant model to classify the species. The dataset is often used in data mining, classification, and clustering examples and to test algorithms.

Figure 15. Iris data set: variables illustration - pedal and sepal

Figure 15. Iris data set: variables illustration - pedal and sepal

This 100-year-old data set has been included in the R base package. The first few records of the data set are displayed in the following table.

pander(head(iris))
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
5.1 3.5 1.4 0.2 setosa
4.9 3 1.4 0.2 setosa
4.7 3.2 1.3 0.2 setosa
4.6 3.1 1.5 0.2 setosa
5 3.6 1.4 0.2 setosa
5.4 3.9 1.7 0.4 setosa

We use k-means method to perform cluster analysis on the iris data with the four numerical feature variables.

myClusteredIris = iris
# We start with 3 clusters since we know there are 3 species in the data.
# In practice, we need relatively try different numbers of clusters and then
# use the elbow plot to determine the best number of clusters.
km.iris <- kmeans( x = myClusteredIris[, -5] , centers = 3)  
clust.ID <- km.iris$cluster        # extracting cluster IDs

table(clust.ID)                    # frequency of clusters
## clust.ID
##  1  2  3 
## 50 62 38

Since this clustering task involves 4 numerical feature variables, we cannot create a 2D plot to show the clustering performance with all four original feature variables. However, we can so-called PCA (to be discussed in the next section) to create two new feature variables and then plot the new features to show the performance of the clustering algorithm.

clusplot(iris[, -5],
 clust.ID,
 lines = 0,
 shade = TRUE,
 color = TRUE,
 labels = 1,
 plotchar = FALSE,
 span = TRUE,
 main = paste("Clusters of Iris Flowers")
)

3.4 Memory Usage of Clustering

One of the potential issues in clustering analysis is the use of memory. If the data size is too large,


4 Dimensionality Reduction Algorithms

Like clustering methods, dimension reduction seeks and explores the inherent structure in the data, but in this case in an unsupervised manner or to summarize or describe data using less information.

This can be useful to visualize high-dimensional data or to simplify data which can then be used in a supervised learning method. Many of these methods can be adapted for use in classification and regression. The following are the frequently used algorithms.

  • Principal Component Analysis (PCA)
  • Linear Discriminant Analysis (LDA)
  • Quadratic Discriminant Analysis (QDA)
  • Independent Component Analysis (ICA)

In this section, we introduce the most commonly used PCA.

4.1 The Logic of PCA

We use a two-variable animation as an example to illustrate the idea of principal component analysis (PCA).

Figure 16. Illustration PCA.

Figure 16. Illustration PCA.

The above animated graph shows that two or more numerical feature variables are highly correlated, the PCA can be used to aggregate the information in the correlated feature variables by transforming them into a set of uncorrelated new feature variables such that the majority of the total information is captured by the first few new feature variables.

Figure 17. Graphical interpretation of PCA with two correlated variables

Figure 17. Graphical interpretation of PCA with two correlated variables

4.2 Case Study - Iris Data

We have used the well-known Iris Data set in clustering algorithms. The data set has 4 correlated numerical variables(sepal width and length, petal width and length) and a categorical variable. The four variables measure the size of the flowers. We use PCA to see whether reducing the number of feature variables for related modeling.

4.2.1 Fitting PCA model to Iris data

We want to PCA method to reduce the dimensions from 4 (numerical variables) to a smaller number. The R function prcomp() to the factor loadings associated with the four numerical variables.

log.iris = log(iris[,-5])   # drop the categorical variable in the original 
                            # data set and transform all numerical to the
                            # log-scale
ir.pca <- prcomp(log.iris, center = TRUE, scale = TRUE)
# summary(ir.pca)[6]   # use the command to explore the possible information
                       # available in the output of the summary.

In the above R function, three arguments are explained in the following.

log.iris = log of the four variables
cater = TRUE, this means the variables are centered, i.e.,  you move the origin of the original coordinate system to the center of the data cloud.
scale = TRUE, divide the difference between the value of each variable and its mean by the standard deviation of the corresponding variable. 

Next, we find the factor loading of the above fitted PCA. We can write an explicit system of linear transformation by using the loadings.

kable(round(ir.pca$rotation, 2), caption="Factor loadings of the PCA")
Factor loadings of the PCA
PC1 PC2 PC3 PC4
Sepal.Length 0.50 -0.45 0.71 0.19
Sepal.Width -0.30 -0.89 -0.33 -0.09
Petal.Length 0.58 -0.03 -0.22 -0.79
Petal.Width 0.57 -0.04 -0.58 0.58

The explicit expression of the predictive system of PC is given by

\[ \begin{aligned} PC_1 & = 0.50 Sepal.Length -0.30 Sepal.Width + 0.58 Petal.Length + 0.57 Petal.Width \\ PC_2 & = -0.45 Sepal.Length - 0.89 Sepal.Width -0.03 Petal.Length - 0.04 Petal.Width \\ PC_3 & = 0.71 Sepal.Length -0.33 Sepal.Width - 0.22 Petal.Length - 0.58 Petal.Width \\ PC_4 & = 0.19 Sepal.Length -0.09 Sepal.Width -0.79 Petal.Length + 0.58 Petal.Width \\ \end{aligned} \]

The magnitude of factor loadings indicates the amount of information that original variables contribute to the corresponding principal components. For example, the absolute value of loadings associated with petal width and length and sepal length in \(PC_1\) is greater than or equal to 0.5. We can simply call \(PC_1\) the size of iris flowers. Similarly, sepal length and width are major contributors to \(PC_2\), we can name \(PC_2\) as sepal size.

4.2.2 Optimal number of PCs to be retained

The object of PCA is to reduce the dimension without losing a significant amount of information. In PCA, we look at how much total variation is captured by each principal component. Most of the libraries that are capable of performing PCA automatically rank the PCA based on the variation captured by each principal component.

The following summary table gives the importance of the principal components.

kable(summary(ir.pca)$importance, caption="The importance of each principal component")
The importance of each principal component
PC1 PC2 PC3 PC4
Standard deviation 1.712458 0.9523797 0.3647029 0.165684
Proportion of Variance 0.733130 0.2267600 0.0332500 0.006860
Cumulative Proportion 0.733130 0.9598900 0.9931400 1.000000

From the above table, we can see that the first PC explains about \(73.33\%\) of the variation. But we first two principal components explain about \(96\%\) of the total variation. In the data analysis, you only need to use the first two PCs that lose about \(4\%\) of the information.

We can also make a scree plot as a visual tool to show the number of principal components to retain for future analysis.

screeplot(ir.pca, 
          type = "lines",
          main = "Scree Plot of PCA Iris Flower Sizes")
Figure 18. Scree plot of PCA on Iris Data

Figure 18. Scree plot of PCA on Iris Data

Note that the vertical axis in the above scree plot uses the variances of PCs. The standard deviation was used in the above summary table.

4.2.3 Extracting PC Scores

The predictive principle scores are values of the newly transformed variables. We can choose the first few principal components to use as response variables to do relevant modeling.

The command ir.pcs$x extracts the PC scores from the PCA procedure. These scores are the values of the new transformed variables. They can be used as response or predictor variables in statistical models. The following table shows the

kable(ir.pca$x[1:15,], caption = "The first 15 PC scores transformed from the original variable. In the analysis, you want to either the first PC or the first two PCs.")
The first 15 PC scores transformed from the original variable. In the analysis, you want to either the first PC or the first two PCs.
PC1 PC2 PC3 PC4
-2.406639 -0.3969554 0.1939647 0.0047795
-2.223539 0.6901804 0.3500015 0.0488684
-2.581105 0.4275418 0.0188976 0.0499095
-2.450869 0.6860074 -0.0687460 -0.1496465
-2.536853 -0.5082516 0.0293226 -0.0400482
-1.841495 -1.2899381 -0.2527683 0.1638906
-2.479490 0.1011323 -0.4974043 0.1227590
-2.348593 -0.1569003 0.1360186 -0.0954982
-2.535948 1.2477681 -0.1118812 -0.0754686
-2.625580 0.5074073 0.6594737 -0.4732591
-2.252707 -0.9305324 0.3266434 -0.0450709
-2.431184 -0.0290417 -0.0929138 -0.2368403
-2.697278 0.7816300 0.6575035 -0.3883884
-3.325521 1.1499290 0.1948436 -0.2162823
-2.380613 -1.6326568 0.5878310 0.2993832

As the final step, we rename the two PCs and then add the two new variables to the original data set for future analysis. Since \(PC_1\) captures variation of both sepal and pedal, we rename \(PC_1\) as iris.size. Similarly, we rename \(PC_2\) as sepal.size.

my.final.iris.data = iris
my.final.iris.data$iris.size = ir.pca$x[, 1]
my.final.iris.data$sepal.size = ir.pca$x[, 2]
## write the final data set to a local folder
write.csv(my.final.iris.data, file = "C:\\Users\\75CPENG\\OneDrive - West Chester University of PA\\Desktop\\cpeng\\WCU-Teaching\\2023Summer\\STA551\\w08\\Final-Iris-Data.csv")

The following screenshot shows the final data file was saved in a local folder and the two renamed principal components were added to the final data set.

Figure 19. Screenshot of the final iris data set with new variables defined based on the principal components

Figure 19. Screenshot of the final iris data set with new variables defined based on the principal components

LS0tDQp0aXRsZTogIkFuIE92ZXJ2aWV3IG9mIENvbW1vbmx5IFVzZWQgVW5zdXBlcnZpc2VkIE1MIEFsZ29yaXRobXMiDQphdXRob3I6ICJDaGVuZyBQZW5nIg0KZGF0ZTogIlNUQSA1NTEgRm91bmRhdGlvbnMgb2YgRGF0YSBTY2llbmNlICINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIHRvY19kZXB0aDogNA0KICAgIGZpZ193aWR0aDogNg0KICAgIGZpZ19oZWlnaHQ6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0b2NfY29sbGFwc2VkOiB5ZXMNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBzbW9vdGhfc2Nyb2xsOiB5ZXMNCiAgICB0aGVtZTogbHVtZW4NCiAgICBkZl9wcmludDoga2FibGUNCiAgcGRmX2RvY3VtZW50OiANCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNA0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgZmlnX3dpZHRoOiA1DQogICAgZmlnX2hlaWdodDogNA0KICB3b3JkX2RvY3VtZW50OiANCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNA0KICAgIGZpZ19jYXB0aW9uOiB5ZXMNCiAgICBrZWVwX21kOiB5ZXMNCi0tLQ0KDQpgYGB7PWh0bWx9DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KDQpkaXYjVE9DIGxpIHsNCiAgICBsaXN0LXN0eWxlOm5vbmU7DQogICAgYmFja2dyb3VuZC1pbWFnZTpub25lOw0KICAgIGJhY2tncm91bmQtcmVwZWF0Om5vbmU7DQogICAgYmFja2dyb3VuZC1wb3NpdGlvbjowOw0KfQ0KDQpoMS50aXRsZSB7DQogIGZvbnQtc2l6ZTogMjRweDsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogIGNvbG9yOiBEYXJrUmVkOw0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQpoNC5hdXRob3IgeyAvKiBIZWFkZXIgNCAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLw0KICAgIGZvbnQtc2l6ZTogMThweDsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCiAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogIGNvbG9yOiBEYXJrUmVkOw0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQpoNC5kYXRlIHsgLyogSGVhZGVyIDQgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8NCiAgZm9udC1zaXplOiAxOHB4Ow0KICBmb250LXdlaWdodDogYm9sZDsNCiAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDEgeyAvKiBIZWFkZXIgMyAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLw0KICAgIGZvbnQtc2l6ZTogMjJweDsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogZGFya3JlZDsNCiAgICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQpoMiB7IC8qIEhlYWRlciAzIC0gYW5kIHRoZSBhdXRob3IgYW5kIGRhdGEgaGVhZGVycyB1c2UgdGhpcyB0b28gICovDQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIGZvbnQtd2VpZ2h0OiBib2xkOw0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmgzIHsgLyogSGVhZGVyIDMgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8NCiAgICBmb250LXNpemU6IDE1cHg7DQogICAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KaDQgeyAvKiBIZWFkZXIgNCAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLw0KICAgIGZvbnQtc2l6ZTogMThweDsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogZGFya3JlZDsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KPC9zdHlsZT4NCmBgYA0KDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KIyBjb2RlIGNodW5rIHNwZWNpZmllcyB3aGV0aGVyIHRoZSBSIGNvZGUsIHdhcm5pbmdzLCBhbmQgb3V0cHV0IA0KIyB3aWxsIGJlIGluY2x1ZGVkIGluIHRoZSBvdXRwdXQgZmlsZXMuDQoNCmlmICghcmVxdWlyZSgiZ2dwbG90MiIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikNCiAgIGxpYnJhcnkoZ2dwbG90MikNCn0NCmlmICghcmVxdWlyZSgiZmFjdG9leHRyYSIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJmYWN0b2V4dHJhIikNCiAgIGxpYnJhcnkoZmFjdG9leHRyYSkNCn0NCmlmICghcmVxdWlyZSgiY2x1c3RlciIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJjbHVzdGVyIikNCiAgIGxpYnJhcnkoY2x1c3RlcikNCn0NCmlmICghcmVxdWlyZSgiZGVuZGV4dGVuZCIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJkZW5kZXh0ZW5kIikNCiAgIGxpYnJhcnkoZGVuZGV4dGVuZCkNCn0NCmlmICghcmVxdWlyZSgiZ2dkZW5kcm8iKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiZ2dkZW5kcm8iKQ0KICAgbGlicmFyeShnZ2RlbmRybykNCn0NCmlmICghcmVxdWlyZSgiYnJvb20iKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiYnJvb20iKQ0KICAgbGlicmFyeShicm9vbSkNCn0NCmlmICghcmVxdWlyZSgia25pdHIiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygia25pdHIiKQ0KICAgbGlicmFyeShrbml0cikNCn0NCmlmICghcmVxdWlyZSgicGFuZGVyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInBhbmRlciIpDQogICBsaWJyYXJ5KHBhbmRlcikNCn0NCiMga25pdHI6Om9wdHNfa25pdCRzZXQocm9vdC5kaXIgPSAiQzovVXNlcnMvNzVDUEVORy9PbmVEcml2ZSAtIFdlc3QgQ2hlc3RlciBVbml2ZXJzaXR5IG9mIFBBL0RvY3VtZW50cyIpDQojIGtuaXRyOjpvcHRzX2tuaXQkc2V0KHJvb3QuZGlyID0gIkM6XFxTVEE0OTBcXHcwNSIpDQoNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLCAgIA0KICAgICAgICAgICAgICAgICAgICAgIHJlc3VsdCA9IFRSVUUsICAgDQogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIGZpZy5hbGlnbj0nY2VudGVyJywgDQogICAgICAgICAgICAgICAgICAgICAgZmlnLnBvcyA9ICdodCcpDQpgYGANCg0KDQpcDQoNCg0KIyBJbnRyb2R1Y3Rpb24NCg0KVGhpcyBub3RlIG92ZXJ2aWV3cyB0aGUgYmFzaWMgdW5zdXBlcnZpc2VkIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtcyAoYWxzbyBrbm93biBhcyBrbm93bGVkZ2UgZGlzY292ZXJ5KSBpbiB3aGljaCBtb2RlbHMgYXJlIG5vdCBzdXBlcnZpc2VkIHVzaW5nIGEgdHJhaW5pbmcgZGF0YSBzZXQuIEluc3RlYWQsIG1vZGVscyB0aGVtc2VsdmVzIGZpbmQgdGhlICoqaGlkZGVuIHBhdHRlcm5zKiogYW5kIGluc2lnaHRzIGZyb20gdGhlIGdpdmVuIGRhdGEuIA0KDQpUaGUgZ29hbCBvZiB1bnN1cGVydmlzZWQgbGVhcm5pbmcgaXMgdG8gZmluZCB0aGUgdW5kZXJseWluZyBzdHJ1Y3R1cmUgb2YgdGhlIGRhdGEgc2V0IGFuZCBncm91cCB0aGF0IGRhdGEgYWNjb3JkaW5nIHRvIHNpbWlsYXJpdGllcy4gQ29tbW9uIGFsZ29yaXRobXMgdXNlZCBpbiB1bnN1cGVydmlzZWQgbGVhcm5pbmcgaW5jbHVkZSBjbHVzdGVyaW5nLCBhbm9tYWx5IGRldGVjdGlvbiwgbmV1cmFsIG5ldHdvcmtzLCBhbmQgYXBwcm9hY2hlcyBmb3IgbGVhcm5pbmcgbGF0ZW50IHZhcmlhYmxlIG1vZGVscy4NCg0KVGhlIGZvbGxvd2luZyB0eXBlcyBvZiB1bnN1cGVydmlzZWQgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zIGFyZSBjb21tb25seSB1c2VkIGluIHByYWN0aWNlLiANCg0KKiBLLW1lYW5zIENsdXN0ZXJpbmcNCiogSGllcmFyY2hpY2FsIENsdXN0ZXJpbmcNCiogQW5vbWFseSBEZXRlY3Rpb24NCiogUHJpbmNpcGFsIENvbXBvbmVudCBBbmFseXNpcw0KDQpUaGVyZSBhcmUgbWFueSBtZXRob2RzIHRvIGNhbGN1bGF0ZSB0aGlzIGRpc3RhbmNlIGluZm9ybWF0aW9uOyB0aGUgY2hvaWNlIG9mIGRpc3RhbmNlIG1lYXN1cmVzIGlzIGEgY3JpdGljYWwgc3RlcCBpbiBjbHVzdGVyaW5nLiBJdCBkZWZpbmVzIGhvdyB0aGUgc2ltaWxhcml0eSBvZiB0d28gZGF0YSBwb2ludHMgKHgsIHkpIGlzIGNhbGN1bGF0ZWQgYW5kIGl0IHdpbGwgaW5mbHVlbmNlIHRoZSBzaGFwZSBvZiB0aGUgY2x1c3RlcnMuIFRoZSBjaG9pY2Ugb2YgZGlzdGFuY2UgbWVhc3VyZXMgaXMgYSBjcml0aWNhbCBzdGVwIGluIGNsdXN0ZXJpbmcuIEl0IGRlZmluZXMgaG93IHRoZSBzaW1pbGFyaXR5IG9mIHR3byBkYXRhIHBvaW50cyAoeCwgeSkgaXMgY2FsY3VsYXRlZCBhbmQgaXQgd2lsbCBpbmZsdWVuY2UgdGhlIHNoYXBlIG9mIHRoZSBjbHVzdGVycy4gDQoNCg0KYGBge3IgZWNobz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjgwJSIsIGZpZy5jYXA9IkZpZ3VyZSAxLiAgRGVtb25zdHJhdGlvbiBvZiB0aGUgY2x1c3RlcmluZyBwcm9jZXNzLiJ9DQppZiAoa25pdHI6Ojppc19sYXRleF9vdXRwdXQoKSkgew0KICBrbml0cjo6YXNpc19vdXRwdXQoJ1xcdXJse2h0dHBzOi8vZ2l0aHViLmNvbS9wZW5nZHNjaS9TVEE1NTEvYmxvYi9tYWluL3cwOC9pbWcvdzA4LWtNZWFucy1naWYuZ2lmfScpDQp9IGVsc2Ugew0KICBrbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1nL3cwOC1rTWVhbnMtZ2lmLmdpZiIpDQp9DQpgYGANCg0KDQogSGVyZSBhcmUgYSBmZXcgc2l0ZXMgeW91IGNoZWNrIGZvciB0aGVzZSAiZGlzdGFuY2VzIi4NCg0KKiA8aHR0cHM6Ly9lbGtpLXByb2plY3QuZ2l0aHViLmlvL2FsZ29yaXRobXMvZGlzdGFuY2VzPg0KKiA8aHR0cHM6Ly9pZWVleHBsb3JlLmllZWUub3JnL3N0YW1wL3N0YW1wLmpzcD9hcm51bWJlcj02ODUzMzM4Pg0KKiA8aHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL1NpbWlsYXJpdHlNZWFzdXJlcy9TaW1pbGFyaXR5TWVhc3VyZXMucGRmPg0KDQoNCkluIHRoZSBuZXh0IGZldyBzZWN0aW9ucywgd2Ugd2lsbCBkZXNjcmliZSBlYWNoIG9mIHRoZXNlIGFsZ29yaXRobXMuIA0KDQojIEstbWVhbnMgQ2x1c3RlcmluZw0KDQpLLW1lYW5zIGFsZ29yaXRobSBpcyBhbiBpdGVyYXRpdmUgYWxnb3JpdGhtIHRoYXQgcGFydGl0aW9ucyB0aGUgZGF0YSBzZXQgaW50byBLIDxmb250IGNvbG9yID0gInJlZCI+KnByZS1kZWZpbmVkKjwvZm9udD4sIDxmb250IGNvbG9yID0gImJsdWUiPiBkaXN0aW5jdDwvZm9udD4sIGFuZCA8Zm9udCBjb2xvciA9ICJkZWVwcGluayI+IG5vbi1vdmVybGFwcGluZzwvZm9udD4gc3ViZ3JvdXBzIChjbHVzdGVycykgd2hlcmUgZWFjaCBkYXRhIHBvaW50IGJlbG9uZ3MgdG8gb25seSBvbmUgZ3JvdXAuIERhdGEgcG9pbnRzIHdpdGhpbiBlYWNoIHN1Ymdyb3VwIGFyZSAqKnNpbWlsYXIqKiB3aGlsZSBkYXRhIHBvaW50cyBhY3Jvc3MgdGhlIHN1Ymdyb3VwcyBhcmUgImRpZmZlcmVudCoqIGFjY29yZGluZyB0byBhIHNlbGVjdGVkIGRpc3NpbWlsYXJpdHkgbWVhc3VyZSB1c2VkIGluIHRoZSBhbGdvcml0aG0uDQoNCkluIG90aGVyIHdvcmRzLCBrLW1lYW5zIGNsdXN0ZXJpbmcgY29uc2lzdHMgb2YgZGVmaW5pbmcgY2x1c3RlcnMgc28gdGhhdCB0aGUgdG90YWwgaW50cmEtY2x1c3RlciB2YXJpYXRpb24gKGtub3duIGFzIGEgdG90YWwgd2l0aGluLWNsdXN0ZXIgdmFyaWF0aW9uKSBpcyBtaW5pbWl6ZWQuIFRoZXJlIGFyZSBzZXZlcmFsIGstbWVhbnMgYWxnb3JpdGhtcyBhdmFpbGFibGUuIFRoZSBzdGFuZGFyZCBhbGdvcml0aG0gaXMgdG8gZGVmaW5lIHRoZSB0b3RhbCB3aXRoaW4tY2x1c3RlciB2YXJpYXRpb24gYXMgdGhlIHN1bSBvZiBzcXVhcmVkIChTUykgZGlzdGFuY2VzIChFdWNsaWRlYW4gZGlzdGFuY2VzKSBiZXR3ZWVuIGRhdGEgcG9pbnRzIGFuZCB0aGUgY29ycmVzcG9uZGluZyBjZW50cm9pZC4gVG8gYmUgbW9yZSBzcGVjaWZpYywgbGV0ICR4X2kkIGJlIHRoZSBkYXRhIHBvaW50IGluIGNsdXN0ZXIgaywgZGVub3RlZCBieSAkQ19rJCBhbmQgJFxtdV9rJCBpcyB0aGUgY2VudGVyIG9mIGNsdXN0ZXIgJENfayQgKGkuZS4gdGhlIG1lYW4gdmFsdWUgb2YgdGhlIHBvaW50cyB3aGVuIEV1Y2xpZGVhbiBkaXN0YW5jZSBpcyB1c2VkKS4gVGhlIHdpdGhpbi1jbHVzdGVyIFNTIGlzIGRlZmluZWQgYnkNCg0KJCQNClcoQ19rKSA9IFxzdW1fe3hfaVxpbiBDX2t9ICh4X2kgLSBcbXVfaSleMg0KJCQNCg0KPiBFYWNoIG9ic2VydmF0aW9uICgkeF9pJCkgaXMgYXNzaWduZWQgdG8gYSBnaXZlbiBjbHVzdGVyIHN1Y2ggdGhhdCB0aGUgc3VtIG9mIHNxdWFyZXMgKFNTKSBkaXN0YW5jZSBvZiB0aGUgb2JzZXJ2YXRpb24gdG8gdGhlaXIgYXNzaWduZWQgY2x1c3RlciBjZW50ZXJzICRcbXVfayQgaXMgYSBtaW5pbXVtLg0KDQpXZSBkZWZpbmUgdGhlIHRvdGFsIHdpdGhpbi1jbHVzdGVyIHZhcmlhdGlvbiBhcyBmb2xsb3dzLg0KDQokJA0KXG1ib3h7VFd9ID0gXHN1bV97az0xfV5rIFcoQ19sKSA9IFxzdW1fe2k9MX1ea1xzdW1fe3hfa1xpbiBDX2t9KHhfaSAtIFxtdV9rKV4yDQokJA0KDQo+IFRoZSB0b3RhbCB3aXRoaW4tY2x1c3RlciBzdW0gb2Ygc3F1YXJlIG1lYXN1cmVzIHRoZSBjb21wYWN0bmVzcyAoaS5lLiBnb29kbmVzcykgb2YgdGhlIGNsdXN0ZXJpbmcgYW5kIHdlIHdhbnQgaXQgdG8gYmUgYXMgc21hbGwgYXMgcG9zc2libGUuDQoNClR3byBmdW5kYW1lbnRhbCBxdWVzdGlvbnMgdG8gYW5zd2VyIGFyZTogKDEpIGhvdyBtYW55IGluaXRpYWwgY2x1c3RlcnMgc2hvdWxkIGJlIHNlbGVjdGVkOyAoMikgaG93IHRvIGNob29zZSB0aGUgaW5pdGlhbCAiY2VudGVycyIuIA0KDQpgYGB7ciBlY2hvPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcicsIG91dC53aWR0aD0iODAlIiwgZmlnLmNhcD0iRmlndXJlIDIuIGlsbHVzdHJhdGlvbiBvZiBjbHVzdGVyIGFuYWx5c2lzLiJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcvdzA4LWstTWVhbnMuanBnIikNCmBgYA0KDQoNCkstbWVhbnMgY2x1c3RlcmluZyBhbGdvcml0aG0gd29ya3MgaW4gdGhlIGZvbGxvd2luZyB0aHJlZSBzdGVwcy4gDQoNCg0KIyMgT3B0aW1hbCBOdW1iZXIgb2YgQ2x1c3RlcnMgRGV0ZXJtaW5hdGlvbg0KDQpTZXZlcmFsIGFsZ29yaXRobXMgY2FuIGJlIHVzZWQgdG8gZmluZCB0aGUgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMuIEVsYm93IGFuZCBTaWxob3VldHRlIGFsZ29yaXRobXMgYXJlIGNvbW1vbmx5IHVzZWQgYW5kIGFyZSBpbXBsZW1lbnRlZCBpbiBSLg0KDQoNCioqRWxib3cgTWV0aG9kKioNCg0KVGhlIEVsYm93IG1ldGhvZCBnaXZlcyB1cyBhbiBpZGVhIG9mIHdoYXQgYSBnb29kIGsgbnVtYmVyIG9mIGNsdXN0ZXJzIHdvdWxkIGJlIGJhc2VkIG9uIHRoZSBzdW0gb2Ygc3F1YXJlZCBkaXN0YW5jZSAoU1NFKSBiZXR3ZWVuIGRhdGEgcG9pbnRzIGFuZCB0aGVpciBhc3NpZ25lZCBjbHVzdGVyc+KAmSBjZW50cm9pZHMuIFdlIHBpY2sgayBhdCB0aGUgc3BvdCB3aGVyZSBTU0Ugc3RhcnRzIHRvIGZsYXR0ZW4gb3V0IGFuZCBmb3JtIGFuIGVsYm93LiBXZeKAmWxsIHVzZSB0aGUgZ2V5c2VyIGRhdGFzZXQgYW5kIGV2YWx1YXRlIFNTRSBmb3IgZGlmZmVyZW50IHZhbHVlcyBvZiBrIGFuZCBzZWUgd2hlcmUgdGhlIGN1cnZlIG1pZ2h0IGZvcm0gYW4gZWxib3cgYW5kIGZsYXR0ZW4gb3V0Lg0KDQpFbGJvdyBpcyBvbmUgb2YgdGhlIG1vc3QgZmFtb3VzIG1ldGhvZHMgZm9yIHNlbGVjdGluZyB0aGUgcmlnaHQgdmFsdWUgb2Ygay4gV2UgYWxzbyBwZXJmb3JtIHRoZSBoeXBlci1wYXJhbWV0ZXIgdHVuaW5nIHRvIGNob29zZSB0aGUgYmVzdCB2YWx1ZSBvZiBrLiBUaGUgRWxib3cgbWV0aG9kIGlzIGFuIGVtcGlyaWNhbCBtZXRob2QgdG8gZmluZCBvdXQgdGhlIGJlc3QgdmFsdWUgb2Ygay4gDQoNCmBgYHtyIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSI4MCUiLCBmaWcuY2FwPSJGaWd1cmUgMy4gKGEpIEEgdmlzdWFsIGN1cnZlIHdpdGggYW4gZXhwbGljaXQgZWxib3cgcG9pbnQuIChiKSBBIHZpc3VhbCBjdXJ2ZSBiZWluZyBmYWlybHkgc21vb3RoIHdpdGggYW4gYW1iaWd1b3VzIGVsYm93IHBvaW50LiJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcvdzA4LWVsYm93LmpwZyIpDQpgYGANCg0KDQoqKlNpbGhvdWV0dGUgTWV0aG9kKioNCg0KVGhlIFNpbGhvdWV0dGUgTWV0aG9kIHVzZXMgYSBzaW1pbGFyaXR5IG1lYXN1cmUgKFNpbGhvdWV0dGUgY29lZmZpY2llbnQpIHRoYXQgaXMgZGVmaW5lZCBpbiB0aGUgZm9sbG93aW5nDQoNCiQkDQpTX2kgPSBcZnJhY3tiX2ktYV9pfXtcbWF4eyBce2FfaSwgYl9pXH19fSwNCiQkDQoNCndoZXJlICRTX2kkIGlzIHRoZSBzaWxob3VldHRlIGNvZWZmaWNpZW50IG9mIHRoZSBkYXRhIHBvaW50ICRpJDsgJGFfaSQgaXMgdGhlIGF2ZXJhZ2UgZGlzdGFuY2UgYmV0d2VlbiAkaSQgYW5kIGFsbCB0aGUgb3RoZXIgZGF0YSBwb2ludHMgaW4gdGhlIGNsdXN0ZXIgdG8gd2hpY2ggJGkkIGJlbG9uZ3MsIGFuZCAkYl9pJCBpcyB0aGUgYXZlcmFnZSBkaXN0YW5jZSBmcm9tICRpJCB0byBhbGwgY2x1c3RlcnMgdG8gd2hpY2ggJGkkIGRvZXMgbm90IGJlbG9uZy4NCg0KDQpXZSBjYW4gcGxvdCB0aGUgU2lsaG91ZXR0ZSBjb2VmZmljaWVudCBhZ2FpbnN0IHRoZSBwcmUtZGV0ZXJtaW5lZCBjbHVzdGVycyAkayQuIFRoZSBwbG90IG9mIHRoZSBzaWxob3VldHRlIGlzIGJldHdlZW4gJC0xJCB0byAkMSQuIA0KDQpBIGhpZ2ggYXZlcmFnZSBzaWxob3VldHRlIHdpZHRoIGluZGljYXRlcyBnb29kIGNsdXN0ZXJpbmcuIFRoZSBhdmVyYWdlIHNpbGhvdWV0dGUgbWV0aG9kIGNvbXB1dGVzIHRoZSBhdmVyYWdlIHNpbGhvdWV0dGUgb2Ygb2JzZXJ2YXRpb25zIGZvciBkaWZmZXJlbnQgdmFsdWVzIG9mIGsuIFRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycyBrIGlzIHRoZSBvbmUgdGhhdCBtYXhpbWl6ZXMgdGhlIGF2ZXJhZ2Ugc2lsaG91ZXR0ZSBvdmVyIGEgcmFuZ2Ugb2YgcG9zc2libGUgdmFsdWVzIGZvciBrLg0KDQpPYnNlcnZlIHRoZSBwbG90IGFuZCBjaG9vc2UgdGhlIGsgdmFsdWUgdGhhdCBpcyBjbG9zZXIgdG8gMSBhcyB0aGUgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMuDQoNCiMjIFN0ZXBzIGZvciBLLW1lYW4gQWxnb3JpdGhtDQoNClRoZXJlIGRpZmZlcmVudCB2ZXJzaW9ucyBvZiBrLW1lYW4gYWxnb3JpdGhtcy4gVGhlIG1vc3QgY29tbW9ubHkgdXNlZCBhcmUgdGhyZWU6IExsb3lkLCBNY1F1ZWVuIGFuZCBIYXJ0aWdhbi1Xb25nLiBMbG95ZCBhbGdvcml0aG1zIGlzIGVhc3kgdG8gdW5kZXJzdGFuZC4gV2Ugd2lsbCB1c2UgaXQgdG8gaWxsdXN0cmF0ZSB0aGUgc3RlcCBvZiBLLW1lYW4gY2x1c3RlcmluZy4gUiBpbXBsZW1ldGVkIGFsbCB0aHJlZSBhbGdvcml0aG1zIGluIHRoZSBSIGZ1bmN0aW9uIGBrbWVhbigpYC4NCg0KIyMjIEluaXRpYWxpemUgQ2VudHJvaWRzLg0KDQpJbml0aWFsaXplIGNlbnRyb2lkcyBieSBmaXJzdCBzaHVmZmxpbmcgdGhlIGRhdGEgc2V0IGFuZCB0aGVuIHJhbmRvbWx5IHNlbGVjdGluZyBLIGRhdGEgcG9pbnRzIGZvciB0aGUgY2VudHJvaWRzIHdpdGhvdXQgcmVwbGFjZW1lbnQuDQoNCiMjIyBVcGRhdGUgQ2VudHJvaWRzDQoNClVwZGF0aW5nIGNlbnRyb2lkcyBpcyBhbiBpdGVyYXRpdmUgcHJvY2VzczoNCg0KMS4gQ29tcHV0ZSB0aGUgc3VtIG9mIHRoZSBzcXVhcmVkIGRpc3RhbmNlIGJldHdlZW4gZGF0YSBwb2ludHMgYW5kIGFsbCAoaW5pdGlhbCkgY2VudHJvaWRzLiBBc3NpZ24gZWFjaCBkYXRhIHBvaW50IHRvIHRoZSBjbG9zZXN0IGNsdXN0ZXIgKGNlbnRyb2lkKS4NCg0KMi4gQ29tcHV0ZSB0aGUgKHVwZGF0aW5nKSBjZW50cm9pZHMgZm9yIHRoZSBjbHVzdGVycyBieSB0YWtpbmcgdGhlIGF2ZXJhZ2Ugb2YgYWxsIGRhdGEgcG9pbnRzIHRoYXQgYmVsb25nIHRvIGVhY2ggY2x1c3Rlci4NCg0KS2VlcCBpdGVyYXRpbmcgdW50aWwgdGhlcmUgaXMgbm8gY2hhbmdlIHRvIHRoZSBjZW50cm9pZHMuIGkuZS4sICB0aGUgYXNzaWdubWVudCBvZiBkYXRhIHBvaW50cyB0byBjbHVzdGVycyBpc27igJl0IGNoYW5naW5nLg0KDQoNClRoZSBmb2xsb3dpbmcgZmlndXJlIGlsbHVzdHJhdGVzIGhvdyB0byBmaW5kIHRoZSB1cGRhdGVkIGNlbnRyb2lkcyBpbW1lZGlhdGVseSBhZnRlciB0aGUgaW5pdGlhbCBjZW50cm9pZHMuDQoNCmBgYHtyIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSI4MCUiLCBmaWcuY2FwPSJGaWd1cmUgNC4gVXBkYXRpbmcgY2VudHJvaWRzIGluIHRoZSBwcm9jZXNzIG9mIGZpbmRpbmcgdGhlIGZpbmFsIGNlbnRyb2lkcyJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcvdzA4LUstbWVhbnMtaXRlcmF0aW9uLmpwZyIpDQpgYGANCg0KDQojIyBTb21lIFJlbWFya3Mgb24gSy1tZWFucw0KDQoxLiBLLW1lYW5zIGNsdXN0ZXJpbmcgYXNzdW1lcyBudW1lcmljYWwgZmVhdHVyZXMgc2luY2UgdGhlIEV1Y2xpZGVhbiBkaXN0YW5jZSBpcyB1c2VkIHRvIGRlZmluZSBzaW1pbGFyaXR5IG1lYXN1cmVzLg0KDQoyLiBLLU1lYW5zIGNsdXN0ZXJpbmcgcGVyZm9ybXMgd2VsbCBvbmx5IGZvciBhIGNvbnZleCBzZXQgb2YgY2x1c3RlcnMgYW5kIG5vdCBmb3Igbm9uLWNvbnZleCBzZXRzLg0KDQozLiBSZWNlbnQgZGV2ZWxvcG1lbnQgYWxsb3dzIGNhdGVnb3JpY2FsIGZlYXR1cmUgdmFyaWFibGVzIHdpdGggbm9uLUV1Y2xpZGVhbiBkaXN0YW5jZS4NCg0KNC4gVGhlIGstbWVhbnMgYWxnb3JpdGhtIGRvZXMgbm90IGd1YXJhbnRlZSBmaW5kaW5nIHRoZSBvcHRpbWFsIHNvbHV0aW9uLiBrLW1lYW5zIGlzIGEgZmFpcmx5IHNpbXBsZSBzZXF1ZW5jZSBvZiB0YXNrcyBhbmQgaXRzIGNsdXN0ZXJpbmcgcXVhbGl0eSBkZXBlbmRzIGEgbG90IG9uIHR3byBmYWN0b3JzOiB0aGUgbnVtYmVyIG9mIGsgY2x1c3RlcnMgYW5kIGluaXRpYWwgY2VudHJvaWRzLg0KDQoNCiMjIENhc2UgU3R1ZHkNCg0KRm9yIGlsbHVzdHJhdGl2ZSBwdXJwb3Nlcywgd2Ugb25seSB1c2UgdHdvIG51bWVyaWNhbCB2YXJpYWJsZXMgaW4gYSBzaW1wbGUgZGF0YSBzZXQgdGhhdCBpcyBwdWJsaWNhbGx5IGF2YWlsYWJsZSBpbiBHaXRodWIgPGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zYXRpc2hndW5qYWwvZGF0YXNldHMvbWFzdGVyL01hbGxfQ3VzdG9tZXJzLmNzdj4uDQoNCmBgYHtyfQ0KZGYgPSByZWFkLmNzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3NhdGlzaGd1bmphbC9kYXRhc2V0cy9tYXN0ZXIvTWFsbF9DdXN0b21lcnMuY3N2IikNCiMjIHJlbmFtZSB0aGUgdHdvIHZhcmlhYmxlcyBhbmQgdGhlbiBzdWJzZXQgdGhlIGRhdGENCm5hbWVzKGRmKVtuYW1lcyhkZik9PSJBbm51YWwuSW5jb21lLi5rLi4iXSA9ICJBbm51YWxJbmNvbWUiDQpuYW1lcyhkZilbbmFtZXMoZGYpPT0iU3BlbmRpbmcuU2NvcmUuLjEuMTAwLiJdID0gIlNwZW5kaW5nU2NvcmUiDQpjbHVzdC5kYXRhID0gZGZbLCBjKCJBbm51YWxJbmNvbWUiLCAiU3BlbmRpbmdTY29yZSIpXQ0Kc2NhbGVkLmRhdGEgPSBhcy5kYXRhLmZyYW1lKHNjYWxlKGNsdXN0LmRhdGEpWywxOjJdKQ0KYGBgDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NSwgIGZpZy5jYXA9IkZpZ3VyZSA1OiBIZWF0bWFwIHJlcHJlc2VudGF0aW9uIG9mIHBvdGVudGlhbCBjbHVzdGVycyJ9DQpkaXN0YW5jZSA9IGdldF9kaXN0KHNjYWxlZC5kYXRhKQ0KZnZpel9kaXN0KGRpc3RhbmNlLCBncmFkaWVudCA9IGxpc3QobG93ID0gInllbGxvdyIsIG1pZCA9ICJvcmFuZ2UiLCBoaWdoID0gImRhcmtyZWQiKSwgc2hvd19sYWJlbHMgPSBGQUxTRSkNCmBgYA0KDQpUaGUgYWJvdmUgaGVhdG1hcCBpbmRpY2F0ZXMgdGhhdCBkaWZmZXJlbnQgY2x1c3RlcnMgZXhpc3QgaW4gdGhpcyBkYXRhIChiYXNlZCBvbiB0aGUgdHdvIG51bWVyaWNhbCB2YXJpYWJsZXMpLg0KDQoNCiogVGhlIHN5bnRheCBvZiAqKmttZWFucygpKiogaXMgZ2l2ZW4gaW4gdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rLg0KDQpgYGB7cn0NCmsyIDwtIGttZWFucyh4ID0gc2NhbGVkLmRhdGEsIA0KICAgICAgICAgICAgIGNlbnRlcnMgPSAyLCANCiAgICAgICAgICAgICBpdGVyLm1heCA9IDEwLA0KICAgICAgICAgICAgIG5zdGFydCA9IDI1LA0KICAgICAgICAgICAgIGFsZ29yaXRobSA9ICJMbG95ZCIsICMiSGFydGlnYW4tV29uZyIsDQogICAgICAgICAgICAgdHJhY2UgPSBGQUxTRSkNCmBgYA0KDQoqIERldGVybWluYXRpb24gb2Ygb3B0aW1hbCBjbGFzcy4NCg0KV2UgdXNlIHRoZSBlbGJvdyBtZXRob2QgdG8gZmluZCB0aGUgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMuIA0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTUsIGZpZy5jYXA9IkZpZ3VyZSA2OiBFbGJvdyBwbG90IGZvciBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycy4ifQ0Kd3NzID0gTlVMTA0KSyA9IDE1DQpmb3IgKGkgaW4gMTpLKXsNCiAgd3NzW2ldID0ga21lYW5zKHNjYWxlZC5kYXRhLCBpLCAxICkkdG90LndpdGhpbnNzDQogfQ0KIyMgZWxib3cgcGxvdA0KcGxvdCgxOkssIHdzcywgdHlwZSA9ImIiLA0KICAgICAgICAgIGNvbD0gImJsdWUiLA0KICAgICAgICAgIHhsYWI9Ik51bWJlciBvZiBDbHVzdGVycyIsDQogICAgICAgICAgeWxhYiA9ICJXU1MiLA0KICAgICAgICAgIG1haW4gPSAiRWxib3cgUGxvdCBmb3IgU2VsZWN0aW5nIE9wdGltYWwgTnVtYmVyIG9mIENsdXN0ZXJzIikNCmBgYA0KDQpGcm9tIHRoZSBhYm92ZSBlbGJvdyBwbG90LCBpdCBzZWVtcyB0aGF0IHRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycyBpcyA1LiBTbyBzZWxlY3QgayAtIDUgaGVyZWFmdGVyLg0KDQoqIENsdXN0ZXIgdGhlIGRhdGEgd2l0aCA1IGNlbnRyb2lkcw0KDQpXZSB3aWxsIGNsdXN0ZXIgdGhlIGRhdGEgaW50byA1IGdyb3VwcyBhbmQgdGhlbiBhZGQgdGhlIGNsdXN0ZXIgSUQgdG8gdGhlIGRhdGEuIFNpbmNlIG9ubHkgdHdvIGNvbnRpbnVvdXMgZmVhdHVyZSB2YXJpYWJsZXMgd2VyZSB1c2VkIHRvIGNsdXN0ZXIgdGhlIGRhdGEuIEFmdGVyIHdlIGFkZGVkIHRoZSBjbHVzdGVyIElEIHRvIHRoZSBkYXRhLCB3ZSB1c2VkIGNvbG9yIGNvZGluZyB0byBtYWtlIGEgc2NhdHRlciBwbG90IGFuZCB2aWV3IHRoZSBjbHVzdGVycy4NCg0KYGBge3IsIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NSwgIGZpZy5jYXA9ICJGaWd1cmUgNy4gRmluYWwgY2x1c3RlciByZXN1bHRzOiB2aXN1YWwgaW5zcGVjdGlvbiJ9DQprNSA8LSBrbWVhbnMoeCA9IHNjYWxlZC5kYXRhLCANCiAgICAgICAgICAgICBjZW50ZXJzID0gNSwgDQogICAgICAgICAgICAgaXRlci5tYXggPSAxMCwNCiAgICAgICAgICAgICBuc3RhcnQgPSAyNSwNCiAgICAgICAgICAgICBhbGdvcml0aG0gPSAiTGxveWQiLCAjICJIYXJ0aWdhbi1Xb25nIiwNCiAgICAgICAgICAgICB0cmFjZSA9IEZBTFNFKQ0Kc2NhbGVkLmRhdGEkZ3JvdXAgPSBrNSRjbHVzdGVyDQojIyMgUGxvdCB0aGUgY2x1c3RlcnMNCiMgU2NhdHRlciBwbG90DQpwbG90KHNjYWxlZC5kYXRhJEFubnVhbEluY29tZSwgc2NhbGVkLmRhdGEkU3BlbmRpbmdTY29yZSwNCiAgICAgcGNoID0gMTksDQogICAgIGNvbCA9IGZhY3RvcihzY2FsZWQuZGF0YSRncm91cCksDQogICAgIHhsYWIgPSJTcGVuZGluZyBTY29yZSIsDQogICAgIHlsYWIgPSAiQW5udWFsIEluY29tZSIsDQogICAgIG1haW4gPSAiQ2x1c3RlcmluZyBQZXJmb3JtYW5jZSBWaXN1YWwgQ2hlY2siKQ0KDQojIExlZ2VuZA0KbGVnZW5kKCJ0b3ByaWdodCIsDQogICAgICAgbGVnZW5kID0gbGV2ZWxzKGZhY3RvcihzY2FsZWQuZGF0YSRncm91cCkpLA0KICAgICAgIHBjaCA9IDE5LA0KICAgICAgIGNvbCA9IGZhY3RvcihsZXZlbHMoZmFjdG9yKHNjYWxlZC5kYXRhJGdyb3VwKSkpKQ0KYGBgDQoNCiMgSGllcmFyY2hpY2FsIENsdXN0ZXJpbmcNCg0KSW4gdGhpcyBzZWN0aW9uLCB3ZSBpbnRyb2R1Y2Ugb25lIG9mIHRoZSBtb3N0IHBvcHVsYXIgY2x1c3RlcmluZyBtZXRob2RzOiAqKiBIaWVyYXJjaGljYWwgQ2x1c3RlcmluZyAqKi4gIEhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGlzIGFuIGFsdGVybmF0aXZlIGFwcHJvYWNoIHRvIGstbWVhbnMgY2x1c3RlcmluZyBmb3IgaWRlbnRpZnlpbmcgZ3JvdXBzIGluIHRoZSBkYXRhc2V0LiBJdCBkb2VzIG5vdCByZXF1aXJlIHVzIHRvIHByZS1zcGVjaWZ5IHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgdG8gYmUgZ2VuZXJhdGVkIGFzIGlzIHJlcXVpcmVkIGJ5IHRoZSBrLW1lYW5zIGFwcHJvYWNoLiBGdXJ0aGVybW9yZSwgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgaGFzIGFuIGFkZGVkIGFkdmFudGFnZSBvdmVyIEstbWVhbnMgY2x1c3RlcmluZyBpbiB0aGF0IGl0IHJlc3VsdHMgaW4gYW4gYXR0cmFjdGl2ZSB0cmVlLWJhc2VkIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBvYnNlcnZhdGlvbnMsIGNhbGxlZCBhIGRlbmRyb2dyYW0uDQoNCiMjIFR5cGVzIG9mIEhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nDQoNClRoZXJlIGFyZSB0d28gdHlwZXMgb2YgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmc6IGFnZ2xvbWVyYXRpdmUgYW5kIGRpdmlzaXZlLiANCg0KKiAqKkFnZ2xvbWVyYXRpdmUqKio6IEFuIGFnZ2xvbWVyYXRpdmUgYXBwcm9hY2ggYmVnaW5zIHdpdGggZWFjaCBvYnNlcnZhdGlvbiBpbiBhIGRpc3RpbmN0IChzaW5nbGV0b24pIGNsdXN0ZXIsIGFuZCBzdWNjZXNzaXZlbHkgbWVyZ2VzIGNsdXN0ZXJzIHVudGlsIGEgc3RvcHBpbmcgY3JpdGVyaW9uIGlzIHNhdGlzZmllZC4NCg0KKiAqKkRpdmlzaXZlKio6IEEgZGl2aXNpdmUgbWV0aG9kIGJlZ2lucyB3aXRoIGFsbCBwYXR0ZXJucyBpbiBhIHNpbmdsZSBjbHVzdGVyIGFuZCBwZXJmb3JtcyBzcGxpdHRpbmcgdW50aWwgYSBzdG9wcGluZyBjcml0ZXJpb24gaXMgbWV0Lg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcicsIG91dC53aWR0aD0iODAlIiwgZmlnLmNhcD0iRmlndXJlIDguIElsbHVzdHJhdGlvbiBvZiB0eXBlcyBvZiBoaWVyYXJjaGljYWwgY2x1c3RlcmluZy4ifQ0KaW5jbHVkZV9ncmFwaGljcygiaW1nL3cwOC10eXBlcy1wZi1oaXJhcmNoaWNhbC1jbHVzdGVyaW5nLmpwZyIpDQpgYGANCg0KQXMgYW4gZXhhbXBsZSwgd2UgbG9vayBhdCBob3cgYWdnbG9tZXJhdGl2ZSBjbHVzdGVyaW5nIHdvcmtzIHVzaW5nIGZpdmUgZGF0YSBwb2ludHMgaW4gdGhlIGZvbGxvd2luZyBmaWd1cmUuDQoNCmBgYHtyIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgIG91dC53aWR0aD0iODAlIiwgZmlnLmNhcD0iRmlndXJlIDkuIElsbHVzdHJhdGlvbiBvZiBzdGVwcyBvZiBhZ2dsb21lcmF0aXZlIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nLiJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcvdzA4LUFnbGxvbWVyYXRpdmUtQ2x1c3RlcmluZy5qcGciKQ0KYGBgDQoNClwNCg0KIyMgQ2FzZSBTdHVkeSBJIOKAkyBDbHVzdGVyaW5nIHdpdGggVHdvIEZlYXR1cmVzDQoNCldlIHN0aWxsIHVzZSB0aGUgc2FtZSBkYXRhIHNldCB0aGF0IHdlIHVzZWQgaW4gdGhlIHByZXZpb3VzIGNhc2Ugc3R1ZHkgb2YgSy1tZWFucyBjbHVzdGVyaW5nIGJ1dCB3aWxsIGluY2x1ZGUgKiphZ2UqKiB2YXJpYWJsZSBpbiB0aGUgZGF0YSBmcmFtZSBmb3IgZm9sbG93aW5nIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nLg0KDQpgYGB7cn0NCmRmID0gcmVhZC5jc3YoImh0dHBzOi8vcGVuZ2RzY2kuZ2l0aHViLmlvL1NUQTU1MS93MTEvTWFsbF9DdXN0b21lcnMuY3N2IikNCiMjIFJlbmFtZSB0aGUgdHdvIHZhcmlhYmxlcyBhbmQgdGhlbiBzdWJzZXQgdGhlIGRhdGENCm5hbWVzKGRmKVtuYW1lcyhkZik9PSJBbm51YWwuSW5jb21lLi5rLi4iXSA9ICJBbm51YWxJbmNvbWUiDQpuYW1lcyhkZilbbmFtZXMoZGYpPT0iU3BlbmRpbmcuU2NvcmUuLjEuMTAwLiJdID0gIlNwZW5kaW5nU2NvcmUiDQpoaWVyYXJjaC5kYXRhID0gZGZbLCBjKCJBZ2UiLCAiQW5udWFsSW5jb21lIiwgIlNwZW5kaW5nU2NvcmUiKV0NCmBgYA0KDQoNClwNCg0KIyMjIFByZS1wcm9jZXNzaW5nIE9wZXJhdGlvbnMgZm9yIENsdXN0ZXJpbmcNCg0KVGhlcmUgYXJlIGEgY291cGxlIG9mIHRoaW5ncyB5b3Ugc2hvdWxkIHRha2UgY2FyZSBvZiBiZWZvcmUgc3RhcnRpbmcuDQoNCioqU2NhbGluZyoqIGlzIGltcGVyYXRpdmUgdGhhdCB3ZSBub3JtYWxpemUgdGhlIHNjYWxlIG9mIGZlYXR1cmUgdmFsdWVzIGluIG9yZGVyIHRvIHN0YXJ0IHdpdGggdGhlIGNsdXN0ZXJpbmcgcHJvY2Vzcy4gVGhpcyBpcyBiZWNhdXNlIGVhY2ggb2JzZXJ2YXRpb24ncyBmZWF0dXJlIHZhbHVlcyBhcmUgcmVwcmVzZW50ZWQgYXMgY29vcmRpbmF0ZXMgaW4gbi1kaW1lbnNpb25hbCBzcGFjZSAobiBpcyB0aGUgbnVtYmVyIG9mIGZlYXR1cmVzKSBhbmQgdGhlbiB0aGUgZGlzdGFuY2VzIGJldHdlZW4gdGhlc2UgY29vcmRpbmF0ZXMgYXJlIGNhbGN1bGF0ZWQuIElmIHRoZXNlIGNvb3JkaW5hdGVzIGFyZSBub3Qgbm9ybWFsaXplZCwgdGhlbiBpdCBtYXkgbGVhZCB0byBmYWxzZSByZXN1bHRzLiBSIGhhcyBmdW5jdGlvbnMgKipzY2FsZSgpKiogYW5kICoqbm9ybWFsaXplKCkqKi4NCg0KDQoqKk1pc3NpbmcgVmFsdWUgaW1wdXRhdGlvbioqIGlzIGFsc28gaW1wb3J0YW50IHRvIGRlYWwgd2l0aCBtaXNzaW5nL251bGwvaW5mIHZhbHVlcyBpbiB5b3VyIGRhdGEgc2V0IGJlZm9yZWhhbmQuIFRoZXJlIGFyZSBtYW55IHdheXMgdG8gZGVhbCB3aXRoIHN1Y2ggdmFsdWVzLCBvbmUgaXMgdG8gZWl0aGVyIHJlbW92ZSB0aGVtIG9yIGltcHV0ZSB0aGVtIHdpdGggbWVhbiwgbWVkaWFuLCBtb2RlLCBvciB1c2Ugc29tZSBhZHZhbmNlZCByZWdyZXNzaW9uIHRlY2huaXF1ZXMuIFIgaGFzIG1hbnkgcGFja2FnZXMgYW5kIGZ1bmN0aW9ucyB0byBkZWFsIHdpdGggbWlzc2luZyB2YWx1ZSBpbXB1dGF0aW9ucyBsaWtlICoqaW1wdXRlKCkqKi4NCg0KIyMjIEhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nIHdpdGggUg0KDQpUaGVyZSBhcmUgZGlmZmVyZW50IGZ1bmN0aW9ucyBhdmFpbGFibGUgaW4gUiBmb3IgY29tcHV0aW5nIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nLiBUaGUgY29tbW9ubHkgdXNlZCBmdW5jdGlvbnMgYXJlOg0KDQoqICoqaGNsdXN0KCkqKiBbaW4gc3RhdHMgcGFja2FnZV0gYW5kICoqYWduZXMoKSoqICBbaW4gY2x1c3RlciBwYWNrYWdlXSBmb3IgYWdnbG9tZXJhdGl2ZSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyAoSEMpLg0KDQoqICoqZGlhbmEoKSoqIFtpbiBjbHVzdGVyIHBhY2thZ2VdIGZvciBkaXZpc2l2ZSBIQy4NCg0KDQoNCmBgYHtyLCBmaWcuYWxpZ24gPSAnY2VudGVyJywgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NSwgICBmaWcuY2FwPSAiRmlndXJlIDEwLiBEZW5kcm9ncmFtOiBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyJ9DQpzY2FsZXMuaGllcmFyY2ggPSBhcy5kYXRhLmZyYW1lKGhpZXJhcmNoLmRhdGEpDQpkaXN0YW5jZSA8LSBkaXN0KHNjYWxlcy5oaWVyYXJjaCwgbWV0aG9kID0gImV1Y2xpZGVhbiIpDQojIEhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIHVzaW5nIENvbXBsZXRlIExpbmthZ2UNCmhjMSA8LSBoY2x1c3QoZGlzdGFuY2UsIG1ldGhvZCA9ICJjb21wbGV0ZSIgKQ0KIyBQbG90IHRoZSBvYnRhaW5lZCBkZW5kcm9ncmFtDQpwbG90KGhjMSwgY2V4ID0gMC42LCBsYWJlbHMgPSBGQUxTRSwgaGFuZyA9IC0xLCB4bGFiID0gIiIsIG1haW4gPSAiRGVuZHJvZ3JhbTogaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmciKQ0KcmVjdC5oY2x1c3QoaGMxLCBrID0gNSwgYm9yZGVyID0gMjo5KQ0KYGBgDQoNCmBgYHtyLCBmaWcuYWxpZ249ImNlbnRlciIsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTUsICBmaWcuY2FwPSJGaWd1cmUgMTEuIERlbmRyb2dyYW06IGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nOiBObyBYLUxhYmVscyJ9DQojIEZpZ3VyZWQgdGhpcyBvdXQgYnkgY29sb3JpbmcgdGhlIGxhYmVscyB3aGl0ZSB0byB0aGUgYmFja2dyb3VuZA0KYXZnX2RlbmRfb2JqIDwtIGFzLmRlbmRyb2dyYW0oaGMxKQ0KbGFiZWxzX2NvbG9ycyhhdmdfZGVuZF9vYmopIDwtICJ3aGl0ZSINCnBsb3QoYXZnX2RlbmRfb2JqLCBjZXggPSAwLjYsIA0KICAgICBsYWJlbHMgPSBGQUxTRSwgDQogICAgIGhhbmcgPSAtMSwgDQogICAgIHhsYWIgPSAiIiwgDQogICAgIHlsYWI9ICJIZWlnaHQiLA0KICAgICBtYWluID0gIkRlbmRyb2dyYW06IGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nOiBObyBYLUxhYmVscyIpDQpyZWN0LmhjbHVzdChoYzEsIGsgPSA1LCBib3JkZXIgPSAyOjkpDQpgYGANCg0KIyMjIERldGVybWluYXRpb24gT3B0aW1hbCBOdW1iZXIgb2YgQ2x1c3RlcnMNCg0KVGhlIGRldGVybWluYXRpb24gb2YgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzIGlzIGFuIGltcG9ydGFudCBhbmQgY2hhbGxlbmdpbmcgcHJvYmxlbS4gSW4gaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcsIGRpZmZlcmVudCBzaW1pbGFyaXR5IG1lYXN1cmVzIGltcGFjdCB0aGUgbnVtYmVyIG9mIG9wdGltYWwgY2x1c3RlcnMuIFdlIHdpbGwgbm90IGRpc2N1c3MgdGhpcyB0b3BpYyBpbiBkZXRhaWwuIFRvIGtub3cgbW9yZSBhYm91dCB0aGlzIHRvcGljLCB5b3UgYXJlIHJlZmVycmVkIHRvIHRoZSBmb2xsb3dpbmcgYXJ0aWNsZSB3aXRoIGV4YW1wbGVzIGluIFIgPGh0dHBzOi8vd3d3LmpzdGF0c29mdC5vcmcvYXJ0aWNsZS92aWV3LzIxOTQvNzk4Pi4NCg0KV2UgY2FuIHVzZSB0aGUgc2FtZSBlbGJvdyBhbmQgc2lsaG91ZXR0ZSBtZXRob2RzIHRvIHBsb3QuIA0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01LCAgZmlnLmNhcD0gIkZpZ3VyZSAxMi4gRWxib3cgcGxvdDogT3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMifQ0KZnZpel9uYmNsdXN0KHNjYWxlcy5oaWVyYXJjaCwgRlVOID0gaGN1dCwgbWV0aG9kID0gIndzcyIpDQpgYGANCg0KYGBge3IsZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01LCBmaWcuY2FwPSAiRmlndXJlIDEzLiBTaWxob3VldHRlIHBsb3Q6IE9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzIn0NCmZ2aXpfbmJjbHVzdChzY2FsZXMuaGllcmFyY2gsIEZVTiA9IGhjdXQsIG1ldGhvZCA9ICJzaWxob3VldHRlIikNCmBgYA0KDQojIyMgRXh0cmFjdGluZyBDbHVzdGVyIElEDQoNClRoZSBhYm92ZSBlbGJvdyBwbG90IGluZGljYXRlcyB0aGF0IGNob29zaW5nIDQgY2x1c3RlcnMgaXMgYXBwcm9wcmlhdGUuIE5leHQsIHdlIHBlcmZvcm0gYSA0LWNsdXN0ZXIgYW5hbHlzaXMgYW5kIGV4dHJhY3QgdGhlIGNsdXN0ZXIgSUQgdG8gYWRkIHRoZW0gdG8gdGhlIGRhdGEgZnJhbWUuICoqVGhpcyBjbHVzdGVyIElEIGNvdWxkIGJlIHVzZWQgYXMgYSBuZXcgZmVhdHVyZSB2YXJpYWJsZSBpbiBzdWJzZXF1ZW50IG1vZGVsaW5nKiouDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcicsZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NSwgIGZpZy5jYXA9ICJGaWd1cmUgMTQuIFZpc3VhbCBjaGVjayB0aGUgcmVzdWx0aW5nIGNsdXN0ZXJzIG9idGFpbmVkIGZyb20gYWdnbG9tZXJhdGl2ZSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZy4ifQ0KaGM0IDwtIGhjbHVzdChkaXN0YW5jZSwgbWV0aG9kID0gImNvbXBsZXRlIiApDQpncm91cCA9IGN1dHJlZShoYzQsIGsgPSA2KQ0Kc2NhbGVzLmhpZXJhcmNoJGdyb3VwID0gZ3JvdXANCiMjIA0KcGxvdChzY2FsZXMuaGllcmFyY2gkQW5udWFsSW5jb21lLCBzY2FsZXMuaGllcmFyY2gkU3BlbmRpbmdTY29yZSwNCiAgICAgcGNoID0gMTksDQogICAgIGNvbCA9IGZhY3RvcihzY2FsZXMuaGllcmFyY2gkZ3JvdXApLA0KICAgICB4bGFiID0iU3BlbmRpbmcgU2NvcmUiLA0KICAgICB5bGFiID0gIkFubnVhbCBJbmNvbWUiLA0KICAgICBtYWluID0gIkhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nIFBlcmZvcm1hbmNlIFZpc3VhbCBDaGVjayIpDQoNCiMjIExlZ2VuZA0KbGVnZW5kKCJ0b3ByaWdodCIsDQogICAgICAgbGVnZW5kID0gbGV2ZWxzKGZhY3RvcihzY2FsZXMuaGllcmFyY2gkZ3JvdXApKSwNCiAgICAgICBwY2ggPSAxOSwNCiAgICAgICBjb2wgPSBmYWN0b3IobGV2ZWxzKGZhY3RvcihzY2FsZXMuaGllcmFyY2gkZ3JvdXApKSkpDQpgYGANCg0KXA0KDQojIyBDYXNlIFN0dWR5IElJOiBNdWx0aS1jbGFzcyBDbHVzdGVyaW5nDQoNClRoZSBJcmlzIERhdGFzZXQgY29udGFpbnMgZm91ciBmZWF0dXJlcyAobGVuZ3RoIGFuZCB3aWR0aCBvZiBzZXBhbHMgYW5kIHBldGFscykgb2YgNTAgc2FtcGxlcyBvZiB0aHJlZSBzcGVjaWVzIG9mIElyaXMgKElyaXMgc2V0b3NhLCBJcmlzIHZpcmdpbmljYSwgYW5kIElyaXMgdmVyc2ljb2xvcikuIFRoZXNlIG1lYXN1cmVzIHdlcmUgdXNlZCB0byBjcmVhdGUgYSBsaW5lYXIgZGlzY3JpbWluYW50IG1vZGVsIHRvIGNsYXNzaWZ5IHRoZSBzcGVjaWVzLiBUaGUgZGF0YXNldCBpcyBvZnRlbiB1c2VkIGluIGRhdGEgbWluaW5nLCBjbGFzc2lmaWNhdGlvbiwgYW5kIGNsdXN0ZXJpbmcgZXhhbXBsZXMgYW5kIHRvIHRlc3QgYWxnb3JpdGhtcy4NCg0KYGBge3IgZWNobz1GQUxTRSwgZmlnLmNhcD0iRmlndXJlIDE1LiBJcmlzIGRhdGEgc2V0OiB2YXJpYWJsZXMgaWxsdXN0cmF0aW9uIC0gcGVkYWwgYW5kIHNlcGFsIiwgb3V0LndpZHRoPSI2MCUiLCAgZmlnLmFsaWduID0gImNlbnRlciJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcvdzA4LWlyaXMuanBnIikNCmBgYA0KDQpUaGlzIDEwMC15ZWFyLW9sZCBkYXRhIHNldCBoYXMgYmVlbiBpbmNsdWRlZCBpbiB0aGUgUiBiYXNlIHBhY2thZ2UuIFRoZSBmaXJzdCBmZXcgcmVjb3JkcyBvZiB0aGUgZGF0YSBzZXQgYXJlIGRpc3BsYXllZCBpbiB0aGUgZm9sbG93aW5nIHRhYmxlLg0KDQpgYGB7cn0NCnBhbmRlcihoZWFkKGlyaXMpKQ0KYGBgDQoNCldlIHVzZSAqKmstbWVhbnMqKiBtZXRob2QgdG8gcGVyZm9ybSBjbHVzdGVyIGFuYWx5c2lzIG9uIHRoZSAqKmlyaXMgZGF0YSoqIHdpdGggdGhlIGZvdXIgbnVtZXJpY2FsIGZlYXR1cmUgdmFyaWFibGVzLg0KDQpgYGB7cn0NCm15Q2x1c3RlcmVkSXJpcyA9IGlyaXMNCiMgV2Ugc3RhcnQgd2l0aCAzIGNsdXN0ZXJzIHNpbmNlIHdlIGtub3cgdGhlcmUgYXJlIDMgc3BlY2llcyBpbiB0aGUgZGF0YS4NCiMgSW4gcHJhY3RpY2UsIHdlIG5lZWQgcmVsYXRpdmVseSB0cnkgZGlmZmVyZW50IG51bWJlcnMgb2YgY2x1c3RlcnMgYW5kIHRoZW4NCiMgdXNlIHRoZSBlbGJvdyBwbG90IHRvIGRldGVybWluZSB0aGUgYmVzdCBudW1iZXIgb2YgY2x1c3RlcnMuDQprbS5pcmlzIDwtIGttZWFucyggeCA9IG15Q2x1c3RlcmVkSXJpc1ssIC01XSAsIGNlbnRlcnMgPSAzKSAgDQpjbHVzdC5JRCA8LSBrbS5pcmlzJGNsdXN0ZXIgICAgICAgICMgZXh0cmFjdGluZyBjbHVzdGVyIElEcw0KDQp0YWJsZShjbHVzdC5JRCkgICAgICAgICAgICAgICAgICAgICMgZnJlcXVlbmN5IG9mIGNsdXN0ZXJzDQpgYGANCg0KU2luY2UgdGhpcyBjbHVzdGVyaW5nIHRhc2sgaW52b2x2ZXMgNCBudW1lcmljYWwgZmVhdHVyZSB2YXJpYWJsZXMsIHdlIGNhbm5vdCBjcmVhdGUgYSAyRCBwbG90IHRvIHNob3cgdGhlIGNsdXN0ZXJpbmcgcGVyZm9ybWFuY2Ugd2l0aCBhbGwgZm91ciBvcmlnaW5hbCBmZWF0dXJlIHZhcmlhYmxlcy4gSG93ZXZlciwgd2UgY2FuIHNvLWNhbGxlZCBQQ0EgKHRvIGJlIGRpc2N1c3NlZCBpbiB0aGUgbmV4dCBzZWN0aW9uKSB0byBjcmVhdGUgdHdvIG5ldyBmZWF0dXJlIHZhcmlhYmxlcyBhbmQgdGhlbiBwbG90IHRoZSBuZXcgZmVhdHVyZXMgdG8gc2hvdyB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIGNsdXN0ZXJpbmcgYWxnb3JpdGhtLiANCg0KDQoNCmBgYHtyfQ0KY2x1c3Bsb3QoaXJpc1ssIC01XSwNCiBjbHVzdC5JRCwNCiBsaW5lcyA9IDAsDQogc2hhZGUgPSBUUlVFLA0KIGNvbG9yID0gVFJVRSwNCiBsYWJlbHMgPSAxLA0KIHBsb3RjaGFyID0gRkFMU0UsDQogc3BhbiA9IFRSVUUsDQogbWFpbiA9IHBhc3RlKCJDbHVzdGVycyBvZiBJcmlzIEZsb3dlcnMiKQ0KKQ0KYGBgDQoNCiMjIE1lbW9yeSBVc2FnZSBvZiBDbHVzdGVyaW5nDQoNCk9uZSBvZiB0aGUgcG90ZW50aWFsIGlzc3VlcyBpbiBjbHVzdGVyaW5nIGFuYWx5c2lzIGlzIHRoZSB1c2Ugb2YgbWVtb3J5LiBJZiB0aGUgZGF0YSBzaXplIGlzIHRvbyBsYXJnZSwgDQoNClwNCg0KIyBEaW1lbnNpb25hbGl0eSBSZWR1Y3Rpb24gQWxnb3JpdGhtcw0KDQpMaWtlIGNsdXN0ZXJpbmcgbWV0aG9kcywgZGltZW5zaW9uIHJlZHVjdGlvbiBzZWVrcyBhbmQgZXhwbG9yZXMgdGhlIGluaGVyZW50IHN0cnVjdHVyZSBpbiB0aGUgZGF0YSwgYnV0IGluIHRoaXMgY2FzZSBpbiBhbiB1bnN1cGVydmlzZWQgbWFubmVyIG9yIHRvIHN1bW1hcml6ZSBvciBkZXNjcmliZSBkYXRhIHVzaW5nIGxlc3MgaW5mb3JtYXRpb24uDQogDQpUaGlzIGNhbiBiZSB1c2VmdWwgdG8gdmlzdWFsaXplIGhpZ2gtZGltZW5zaW9uYWwgZGF0YSBvciB0byBzaW1wbGlmeSBkYXRhIHdoaWNoIGNhbiB0aGVuIGJlIHVzZWQgaW4gYSBzdXBlcnZpc2VkIGxlYXJuaW5nIG1ldGhvZC4gTWFueSBvZiB0aGVzZSBtZXRob2RzIGNhbiBiZSBhZGFwdGVkIGZvciB1c2UgaW4gY2xhc3NpZmljYXRpb24gYW5kIHJlZ3Jlc3Npb24uIFRoZSBmb2xsb3dpbmcgYXJlIHRoZSBmcmVxdWVudGx5IHVzZWQgYWxnb3JpdGhtcy4NCg0KKglQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzIChQQ0EpDQoqCUxpbmVhciBEaXNjcmltaW5hbnQgQW5hbHlzaXMgKExEQSkNCioJUXVhZHJhdGljIERpc2NyaW1pbmFudCBBbmFseXNpcyAoUURBKQ0KKiBJbmRlcGVuZGVudCBDb21wb25lbnQgQW5hbHlzaXMgKElDQSkNCg0KSW4gdGhpcyBzZWN0aW9uLCB3ZSBpbnRyb2R1Y2UgdGhlIG1vc3QgY29tbW9ubHkgdXNlZCBQQ0EuDQoNCiMjIFRoZSBMb2dpYyBvZiBQQ0ENCg0KV2UgdXNlIGEgdHdvLXZhcmlhYmxlIGFuaW1hdGlvbiBhcyBhbiBleGFtcGxlIHRvIGlsbHVzdHJhdGUgdGhlIGlkZWEgb2YgcHJpbmNpcGFsIGNvbXBvbmVudCBhbmFseXNpcyAoUENBKS4NCg0KYGBge3IgZWNobz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjE2MCUiLCBmaWcuY2FwPSJGaWd1cmUgMTYuIElsbHVzdHJhdGlvbiBQQ0EuIn0NCmlmIChrbml0cjo6OmlzX2xhdGV4X291dHB1dCgpKSB7DQogIGtuaXRyOjphc2lzX291dHB1dCgnXFx1cmx7aHR0cHM6Ly9naXRodWIuY29tL3Blbmdkc2NpL1NUQTU1MS9ibG9iL21haW4vdzA4L2ltZy93MDgtUENBLUFuaW1hdGlvbi0wMS5naWZ9JykNCn0gZWxzZSB7DQogIGtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWcvdzA4LVBDQS1BbmltYXRpb24tMDEuZ2lmIikNCn0NCmBgYA0KDQpUaGUgYWJvdmUgYW5pbWF0ZWQgZ3JhcGggc2hvd3MgdGhhdCB0d28gb3IgbW9yZSBudW1lcmljYWwgZmVhdHVyZSB2YXJpYWJsZXMgYXJlIGhpZ2hseSBjb3JyZWxhdGVkLCB0aGUgUENBIGNhbiBiZSB1c2VkIHRvIGFnZ3JlZ2F0ZSB0aGUgaW5mb3JtYXRpb24gaW4gdGhlIGNvcnJlbGF0ZWQgZmVhdHVyZSB2YXJpYWJsZXMgYnkgdHJhbnNmb3JtaW5nIHRoZW0gaW50byBhIHNldCBvZiB1bmNvcnJlbGF0ZWQgKipuZXcgZmVhdHVyZSB2YXJpYWJsZXMqKiBzdWNoIHRoYXQgdGhlIG1ham9yaXR5IG9mIHRoZSB0b3RhbCBpbmZvcm1hdGlvbiBpcyBjYXB0dXJlZCBieSB0aGUgZmlyc3QgZmV3IG5ldyBmZWF0dXJlIHZhcmlhYmxlcy4NCg0KYGBge3IgZWNobz1GQUxTRSwgZmlnLmNhcD0iRmlndXJlIDE3LiBHcmFwaGljYWwgaW50ZXJwcmV0YXRpb24gb2YgUENBIHdpdGggdHdvIGNvcnJlbGF0ZWQgdmFyaWFibGVzIiwgb3V0LndpZHRoPSI2MCUiLCAgZmlnLmFsaWduID0gImNlbnRlciJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcvdzA4LTJWYXJQQ0FFeGFtcGxlLmpwZyIpDQpgYGANCg0KDQojIyBDYXNlIFN0dWR5IC0gSXJpcyBEYXRhDQoNCldlIGhhdmUgdXNlZCB0aGUgd2VsbC1rbm93biBJcmlzIERhdGEgc2V0IGluIGNsdXN0ZXJpbmcgYWxnb3JpdGhtcy4gVGhlIGRhdGEgc2V0IGhhcyA0IGNvcnJlbGF0ZWQgbnVtZXJpY2FsIHZhcmlhYmxlcyhzZXBhbCB3aWR0aCBhbmQgbGVuZ3RoLCBwZXRhbCB3aWR0aCBhbmQgbGVuZ3RoKSBhbmQgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZS4gVGhlIGZvdXIgdmFyaWFibGVzIG1lYXN1cmUgdGhlIHNpemUgb2YgdGhlIGZsb3dlcnMuIFdlIHVzZSBQQ0EgdG8gc2VlIHdoZXRoZXIgcmVkdWNpbmcgdGhlIG51bWJlciBvZiBmZWF0dXJlIHZhcmlhYmxlcyBmb3IgcmVsYXRlZCBtb2RlbGluZy4gIA0KDQoNCg0KIyMjIEZpdHRpbmcgUENBIG1vZGVsIHRvIElyaXMgZGF0YQ0KDQpXZSB3YW50IHRvIFBDQSBtZXRob2QgdG8gcmVkdWNlIHRoZSBkaW1lbnNpb25zIGZyb20gNCAobnVtZXJpY2FsIHZhcmlhYmxlcykgdG8gYSBzbWFsbGVyIG51bWJlci4gVGhlIFIgZnVuY3Rpb24gKipwcmNvbXAoKSoqIHRvIHRoZSBmYWN0b3IgbG9hZGluZ3MgYXNzb2NpYXRlZCB3aXRoIHRoZSBmb3VyIG51bWVyaWNhbCB2YXJpYWJsZXMuDQoNCmBgYHtyfQ0KbG9nLmlyaXMgPSBsb2coaXJpc1ssLTVdKSAgICMgZHJvcCB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGUgaW4gdGhlIG9yaWdpbmFsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgZGF0YSBzZXQgYW5kIHRyYW5zZm9ybSBhbGwgbnVtZXJpY2FsIHRvIHRoZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbG9nLXNjYWxlDQppci5wY2EgPC0gcHJjb21wKGxvZy5pcmlzLCBjZW50ZXIgPSBUUlVFLCBzY2FsZSA9IFRSVUUpDQojIHN1bW1hcnkoaXIucGNhKVs2XSAgICMgdXNlIHRoZSBjb21tYW5kIHRvIGV4cGxvcmUgdGhlIHBvc3NpYmxlIGluZm9ybWF0aW9uDQogICAgICAgICAgICAgICAgICAgICAgICMgYXZhaWxhYmxlIGluIHRoZSBvdXRwdXQgb2YgdGhlIHN1bW1hcnkuDQpgYGANCg0KSW4gdGhlIGFib3ZlIFIgZnVuY3Rpb24sIHRocmVlIGFyZ3VtZW50cyBhcmUgZXhwbGFpbmVkIGluIHRoZSBmb2xsb3dpbmcuDQoNCmBgYHt9DQpsb2cuaXJpcyA9IGxvZyBvZiB0aGUgZm91ciB2YXJpYWJsZXMNCmNhdGVyID0gVFJVRSwgdGhpcyBtZWFucyB0aGUgdmFyaWFibGVzIGFyZSBjZW50ZXJlZCwgaS5lLiwgIHlvdSBtb3ZlIHRoZSBvcmlnaW4gb2YgdGhlIG9yaWdpbmFsIGNvb3JkaW5hdGUgc3lzdGVtIHRvIHRoZSBjZW50ZXIgb2YgdGhlIGRhdGEgY2xvdWQuDQpzY2FsZSA9IFRSVUUsIGRpdmlkZSB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSB2YWx1ZSBvZiBlYWNoIHZhcmlhYmxlIGFuZCBpdHMgbWVhbiBieSB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIHRoZSBjb3JyZXNwb25kaW5nIHZhcmlhYmxlLiANCmBgYA0KDQpOZXh0LCB3ZSBmaW5kIHRoZSBmYWN0b3IgbG9hZGluZyBvZiB0aGUgYWJvdmUgZml0dGVkIFBDQS4gV2UgY2FuIHdyaXRlIGFuIGV4cGxpY2l0IHN5c3RlbSBvZiBsaW5lYXIgdHJhbnNmb3JtYXRpb24gYnkgdXNpbmcgdGhlIGxvYWRpbmdzLg0KDQpgYGB7cn0NCmthYmxlKHJvdW5kKGlyLnBjYSRyb3RhdGlvbiwgMiksIGNhcHRpb249IkZhY3RvciBsb2FkaW5ncyBvZiB0aGUgUENBIikNCmBgYA0KVGhlIGV4cGxpY2l0IGV4cHJlc3Npb24gb2YgdGhlIHByZWRpY3RpdmUgc3lzdGVtIG9mIFBDIGlzIGdpdmVuIGJ5DQoNCiQkDQogXGJlZ2lue2FsaWduZWR9DQpQQ18xICYgPSAwLjUwIFNlcGFsLkxlbmd0aCAtMC4zMCBTZXBhbC5XaWR0aCArIDAuNTggUGV0YWwuTGVuZ3RoICsgMC41NyBQZXRhbC5XaWR0aCAgXFwNClBDXzIgJiA9IC0wLjQ1IFNlcGFsLkxlbmd0aCAtIDAuODkgU2VwYWwuV2lkdGggLTAuMDMgUGV0YWwuTGVuZ3RoIC0gMC4wNCBQZXRhbC5XaWR0aCBcXA0KUENfMyAmID0gMC43MSBTZXBhbC5MZW5ndGggLTAuMzMgU2VwYWwuV2lkdGggLSAwLjIyICBQZXRhbC5MZW5ndGggLSAwLjU4IFBldGFsLldpZHRoIFxcDQpQQ180ICYgPSAwLjE5IFNlcGFsLkxlbmd0aCAtMC4wOSBTZXBhbC5XaWR0aCAtMC43OSBQZXRhbC5MZW5ndGggKyAwLjU4IFBldGFsLldpZHRoICAgXFwNClxlbmR7YWxpZ25lZH0NCiQkDQoNClRoZSBtYWduaXR1ZGUgb2YgZmFjdG9yIGxvYWRpbmdzIGluZGljYXRlcyB0aGUgYW1vdW50IG9mIGluZm9ybWF0aW9uIHRoYXQgb3JpZ2luYWwgdmFyaWFibGVzIGNvbnRyaWJ1dGUgdG8gdGhlIGNvcnJlc3BvbmRpbmcgcHJpbmNpcGFsIGNvbXBvbmVudHMuIEZvciBleGFtcGxlLCB0aGUgYWJzb2x1dGUgdmFsdWUgb2YgbG9hZGluZ3MgYXNzb2NpYXRlZCB3aXRoIHBldGFsIHdpZHRoIGFuZCBsZW5ndGggYW5kIHNlcGFsIGxlbmd0aCBpbiAkUENfMSQgaXMgZ3JlYXRlciB0aGFuIG9yIGVxdWFsIHRvIDAuNS4gV2UgY2FuIHNpbXBseSBjYWxsICRQQ18xJCB0aGUgKipzaXplKiogb2YgaXJpcyBmbG93ZXJzLiBTaW1pbGFybHksIHNlcGFsIGxlbmd0aCBhbmQgd2lkdGggYXJlIG1ham9yIGNvbnRyaWJ1dG9ycyB0byAkUENfMiQsIHdlIGNhbiBuYW1lICRQQ18yJCBhcyAqKnNlcGFsIHNpemUqKi4NCg0KDQojIyMgT3B0aW1hbCBudW1iZXIgb2YgUENzIHRvIGJlIHJldGFpbmVkDQoNClRoZSBvYmplY3Qgb2YgUENBIGlzIHRvIHJlZHVjZSB0aGUgZGltZW5zaW9uIHdpdGhvdXQgbG9zaW5nIGEgc2lnbmlmaWNhbnQgYW1vdW50IG9mIGluZm9ybWF0aW9uLiBJbiBQQ0EsIHdlIGxvb2sgYXQgaG93IG11Y2ggdG90YWwgdmFyaWF0aW9uIGlzIGNhcHR1cmVkIGJ5IGVhY2ggcHJpbmNpcGFsIGNvbXBvbmVudC4gTW9zdCBvZiB0aGUgbGlicmFyaWVzIHRoYXQgYXJlIGNhcGFibGUgb2YgcGVyZm9ybWluZyBQQ0EgYXV0b21hdGljYWxseSByYW5rIHRoZSBQQ0EgYmFzZWQgb24gdGhlIHZhcmlhdGlvbiBjYXB0dXJlZCBieSBlYWNoIHByaW5jaXBhbCBjb21wb25lbnQuDQoNClRoZSBmb2xsb3dpbmcgc3VtbWFyeSB0YWJsZSBnaXZlcyB0aGUgaW1wb3J0YW5jZSBvZiB0aGUgcHJpbmNpcGFsIGNvbXBvbmVudHMuDQoNCmBgYHtyfQ0Ka2FibGUoc3VtbWFyeShpci5wY2EpJGltcG9ydGFuY2UsIGNhcHRpb249IlRoZSBpbXBvcnRhbmNlIG9mIGVhY2ggcHJpbmNpcGFsIGNvbXBvbmVudCIpDQpgYGANCg0KRnJvbSB0aGUgYWJvdmUgdGFibGUsIHdlIGNhbiBzZWUgdGhhdCB0aGUgZmlyc3QgUEMgZXhwbGFpbnMgYWJvdXQgJDczLjMzXCUkIG9mIHRoZSB2YXJpYXRpb24uIEJ1dCB3ZSBmaXJzdCB0d28gcHJpbmNpcGFsIGNvbXBvbmVudHMgZXhwbGFpbiBhYm91dCAkOTZcJSQgb2YgdGhlIHRvdGFsIHZhcmlhdGlvbi4gSW4gdGhlIGRhdGEgYW5hbHlzaXMsIHlvdSBvbmx5IG5lZWQgdG8gdXNlIHRoZSBmaXJzdCB0d28gUENzIHRoYXQgbG9zZSBhYm91dCAkNFwlJCBvZiB0aGUgaW5mb3JtYXRpb24uDQoNCldlIGNhbiBhbHNvIG1ha2UgYSBzY3JlZSBwbG90IGFzIGEgdmlzdWFsIHRvb2wgdG8gc2hvdyB0aGUgbnVtYmVyIG9mIHByaW5jaXBhbCBjb21wb25lbnRzIHRvIHJldGFpbiBmb3IgZnV0dXJlIGFuYWx5c2lzLg0KDQpgYGB7ciwgZmlnLmFsaWduPSAnY2VudGVyJywgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NSwgZmlnLmNhcD0iRmlndXJlIDE4LiBTY3JlZSBwbG90IG9mIFBDQSBvbiBJcmlzIERhdGEifQ0Kc2NyZWVwbG90KGlyLnBjYSwgDQogICAgICAgICAgdHlwZSA9ICJsaW5lcyIsDQogICAgICAgICAgbWFpbiA9ICJTY3JlZSBQbG90IG9mIFBDQSBJcmlzIEZsb3dlciBTaXplcyIpDQpgYGANCg0KTm90ZSB0aGF0IHRoZSB2ZXJ0aWNhbCBheGlzIGluIHRoZSBhYm92ZSBzY3JlZSBwbG90IHVzZXMgdGhlIHZhcmlhbmNlcyBvZiBQQ3MuIFRoZSBzdGFuZGFyZCBkZXZpYXRpb24gd2FzIHVzZWQgaW4gdGhlIGFib3ZlIHN1bW1hcnkgdGFibGUuDQoNCg0KDQojIyMgRXh0cmFjdGluZyBQQyBTY29yZXMNCg0KVGhlIHByZWRpY3RpdmUgcHJpbmNpcGxlIHNjb3JlcyBhcmUgdmFsdWVzIG9mIHRoZSBuZXdseSB0cmFuc2Zvcm1lZCB2YXJpYWJsZXMuIFdlIGNhbiBjaG9vc2UgdGhlIGZpcnN0IGZldyBwcmluY2lwYWwgY29tcG9uZW50cyB0byB1c2UgYXMgcmVzcG9uc2UgdmFyaWFibGVzIHRvIGRvIHJlbGV2YW50IG1vZGVsaW5nLg0KDQpUaGUgY29tbWFuZCBgaXIucGNzJHhgIGV4dHJhY3RzIHRoZSBQQyBzY29yZXMgZnJvbSB0aGUgUENBIHByb2NlZHVyZS4gVGhlc2Ugc2NvcmVzIGFyZSB0aGUgdmFsdWVzIG9mIHRoZSBuZXcgdHJhbnNmb3JtZWQgdmFyaWFibGVzLiBUaGV5IGNhbiBiZSB1c2VkIGFzIHJlc3BvbnNlIG9yIHByZWRpY3RvciB2YXJpYWJsZXMgaW4gc3RhdGlzdGljYWwgbW9kZWxzLiBUaGUgZm9sbG93aW5nIHRhYmxlIHNob3dzIHRoZSANCg0KDQpgYGB7cn0NCmthYmxlKGlyLnBjYSR4WzE6MTUsXSwgY2FwdGlvbiA9ICJUaGUgZmlyc3QgMTUgUEMgc2NvcmVzIHRyYW5zZm9ybWVkIGZyb20gdGhlIG9yaWdpbmFsIHZhcmlhYmxlLiBJbiB0aGUgYW5hbHlzaXMsIHlvdSB3YW50IHRvIGVpdGhlciB0aGUgZmlyc3QgUEMgb3IgdGhlIGZpcnN0IHR3byBQQ3MuIikNCmBgYA0KDQpBcyB0aGUgZmluYWwgc3RlcCwgd2UgcmVuYW1lIHRoZSB0d28gUENzIGFuZCB0aGVuIGFkZCB0aGUgdHdvIG5ldyB2YXJpYWJsZXMgdG8gdGhlIG9yaWdpbmFsIGRhdGEgc2V0IGZvciBmdXR1cmUgYW5hbHlzaXMuIFNpbmNlICRQQ18xJCBjYXB0dXJlcyB2YXJpYXRpb24gb2YgYm90aCBzZXBhbCBhbmQgcGVkYWwsIHdlIHJlbmFtZSAkUENfMSQgYXMgKippcmlzLnNpemUqKi4gU2ltaWxhcmx5LCB3ZSByZW5hbWUgJFBDXzIkIGFzICoqc2VwYWwuc2l6ZSoqLg0KDQpgYGB7cn0NCm15LmZpbmFsLmlyaXMuZGF0YSA9IGlyaXMNCm15LmZpbmFsLmlyaXMuZGF0YSRpcmlzLnNpemUgPSBpci5wY2EkeFssIDFdDQpteS5maW5hbC5pcmlzLmRhdGEkc2VwYWwuc2l6ZSA9IGlyLnBjYSR4WywgMl0NCiMjIHdyaXRlIHRoZSBmaW5hbCBkYXRhIHNldCB0byBhIGxvY2FsIGZvbGRlcg0Kd3JpdGUuY3N2KG15LmZpbmFsLmlyaXMuZGF0YSwgZmlsZSA9ICJDOlxcVXNlcnNcXDc1Q1BFTkdcXE9uZURyaXZlIC0gV2VzdCBDaGVzdGVyIFVuaXZlcnNpdHkgb2YgUEFcXERlc2t0b3BcXGNwZW5nXFxXQ1UtVGVhY2hpbmdcXDIwMjNTdW1tZXJcXFNUQTU1MVxcdzA4XFxGaW5hbC1JcmlzLURhdGEuY3N2IikNCmBgYA0KDQpUaGUgZm9sbG93aW5nIHNjcmVlbnNob3Qgc2hvd3MgdGhlIGZpbmFsIGRhdGEgZmlsZSB3YXMgc2F2ZWQgaW4gYSBsb2NhbCBmb2xkZXIgYW5kIHRoZSB0d28gcmVuYW1lZCBwcmluY2lwYWwgY29tcG9uZW50cyB3ZXJlIGFkZGVkIHRvIHRoZSBmaW5hbCBkYXRhIHNldC4NCg0KYGBge3IgZWNobz1GQUxTRSwgZmlnLmNhcD0iRmlndXJlIDE5LiBTY3JlZW5zaG90IG9mIHRoZSBmaW5hbCBpcmlzIGRhdGEgc2V0IHdpdGggbmV3IHZhcmlhYmxlcyBkZWZpbmVkIGJhc2VkIG9uIHRoZSBwcmluY2lwYWwgY29tcG9uZW50cyIsIG91dC53aWR0aD0iODAlIiwgIGZpZy5hbGlnbiA9ICJjZW50ZXIifQ0KaW5jbHVkZV9ncmFwaGljcygiaW1nL3cwOC13cml0ZTJsb2NhbC1mb2xkZXIuanBnIikNCmBgYA0KDQoNCg==