Buckle up 🚀

In this learning path, we’ll learn how to create Machine learning models using R 😊. Machine learning is the foundation for predictive modeling and artificial intelligence. We’ll learn the core principles of machine learning and how to use common tools and frameworks to train, evaluate, and use machine learning models.

Modules that will be covered in this learning path include:

  • Explore and analyze data with R

  • Train and evaluate regression models

  • Train and evaluate classification models

  • Train and evaluate clustering models

  • Train and evaluate deep learning models (work in progress)

Prerequisites

This learning path assumes knowledge of basic mathematical concepts. Some experience with R and the tidyverse is also beneficial though we’ll try as much as possible to skim through the core concepts. To get started with R and the tidyverse, the best place would be R for Data Science an O’Reilly book written by Hadley Wickham and Garrett Grolemund. It’s designed to take you from knowing nothing about R or the tidyverse to having all the basic tools of data science at your fingertips.

The Python edition of the learning path can be found at this learning path.

Why R?

R has emerged over the last couple decades as a first-class tool for scientific computing tasks, and has been a consistent leader in implementing statistical methodologies for analyzing data. The usefulness of R for data science stems from the large, active, and growing ecosystem of third-party packages: tidyverse for common data analysis activities;h2o, ranger, xgboost, and others for fast and scalable machine learning; iml, pdp, vip, and others for machine learning interpretability; and many more tools will be mentioned throughout the pages that follow. - Boehmke & Greenwell (2019) Hands-On Machine Learning with R

Now, let’s get started!

Artwork by @allison_horst

A gentle introduction to clustering

Clustering is a form of unsupervised machine learning in which observations are grouped into clusters based on similarities in their data values, or features. This kind of machine learning is considered unsupervised because it does not make use of previously known label values to train a model; in a clustering model, the label is the cluster to which the observation is assigned, based purely on its features.

Clustering works by separating the training cases based on similarities that can be determined from their feature values. Perhaps the two best-known clustering approaches are: K-means clustering and hierarchical clustering. In K-means clustering, we seek to partition the observations into a pre-specified number of clusters. On the other hand, in hierarchical clustering, we do not know in advance how many clusters we want.

Think of it this way; the numeric features of a given entity can be thought of as vector coordinates that define the entity’s position in n-dimensional space. What a clustering model seeks to do is to identify groups, or clusters, of entities that are close to one another while being separated from other clusters.

For example, suppose a botanist observes a sample of flowers and records the number of petals and leaves on each flower.

It may be useful to group these flowers into clusters based on similarities between their features.

There are many ways this could be done. For example, if most flowers have the same number of leaves, they could be grouped into those with many vs few petals. Alternatively, if both petal and leaf counts vary considerably there may be a pattern to discover, such as those with many leaves also having many petals. The goal of the clustering algorithm is to find the optimal way to split the dataset into groups. What ‘optimal’ means depends on both the algorithm used and the dataset that is provided.

Although this flower example may be simple for a human to achieve with only a few samples, as the dataset grows to thousands of samples or to more than two features, clustering algorithms become very useful to quickly dissect a dataset into groups.

The best way to learn about clustering is to try it for yourself, so that’s what you’ll do in this exercise.

We’ll require some packages to knock-off this module. You can have them installed as: install.packages(c('tidyverse', 'tidymodels', 'skimr', 'here', 'plotly', 'factoextra', 'cluster'))

Alternatively, the script below checks whether you have the packages required to complete this module and installs them for you in case some are missing.

suppressWarnings(if(!require("pacman")) install.packages("pacman"))

pacman::p_load('tidyverse', 'tidymodels', 'skimr', 'here', 'plotly', 'factoextra', 'cluster')

1. Principal Component Analysis (PCA)

Let’s take a look at a dataset that contains measurements of different species of wheat seed.

Citation: The seeds dataset used in the this exercise was originally published by the Institute of Agrophysics of the Polish Academy of Sciences in Lublin, and can be downloaded from the UCI dataset repository (Dua, D. and Graff, C. (2019). UCI Machine Learning Repository. Irvine, CA: University of California, School of Information and Computer Science).

# Load the core tidyverse and make it available in your current R session
library(tidyverse)

# Read the csv file into a tibble
seeds <- read_csv(file = "https://raw.githubusercontent.com/MicrosoftDocs/ml-basics/master/data/seeds.csv")

# Print the first 10 rows of the data
seeds %>% 
  slice_head(n = 5)

Sometimes, we may want some little more information on our data. We can have a look at the data, its structure and the data type of its features by using the glimpse() function as below:

# Explore dimension and type of columns
seeds %>% 
  glimpse()
## Rows: 210
## Columns: 8
## $ area                  <dbl> 15.26, 14.88, 14.29, 13.84, 16.14, 14.38, 14.69,~
## $ perimeter             <dbl> 14.84, 14.57, 14.09, 13.94, 14.99, 14.21, 14.49,~
## $ compactness           <dbl> 0.8710, 0.8811, 0.9050, 0.8955, 0.9034, 0.8951, ~
## $ kernel_length         <dbl> 5.763, 5.554, 5.291, 5.324, 5.658, 5.386, 5.563,~
## $ kernel_width          <dbl> 3.312, 3.333, 3.337, 3.379, 3.562, 3.312, 3.259,~
## $ asymmetry_coefficient <dbl> 2.2210, 1.0180, 2.6990, 2.2590, 1.3550, 2.4620, ~
## $ groove_length         <dbl> 5.220, 4.956, 4.825, 4.805, 5.175, 4.956, 5.219,~
## $ species               <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~

While at it, let’s use skimr::skim() to take a look at the summary statistics for the data

library(skimr)

# Obtain Summary statistics
seeds %>% 
  skim()
Data summary
Name Piped data
Number of rows 210
Number of columns 8
_______________________
Column type frequency:
numeric 8
________________________
Group variables None

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
area 0 1 14.85 2.91 10.59 12.27 14.36 17.30 21.18 ▇▆▅▅▂
perimeter 0 1 14.56 1.31 12.41 13.45 14.32 15.71 17.25 ▆▇▆▅▅
compactness 0 1 0.87 0.02 0.81 0.86 0.87 0.89 0.92 ▂▃▇▇▃
kernel_length 0 1 5.63 0.44 4.90 5.26 5.52 5.98 6.68 ▆▇▅▅▂
kernel_width 0 1 3.26 0.38 2.63 2.94 3.24 3.56 4.03 ▇▇▇▆▅
asymmetry_coefficient 0 1 3.70 1.50 0.77 2.56 3.60 4.77 8.46 ▆▇▇▂▁
groove_length 0 1 5.41 0.49 4.52 5.04 5.22 5.88 6.55 ▂▇▂▃▂
species 0 1 1.00 0.82 0.00 0.00 1.00 2.00 2.00 ▇▁▇▁▇

🤩Take a moment and go through the quick data exploration we just performed. Do we have any missing values? What’s the dimension of our data (rows and columns)? What are the different column types? How are the values in our columns distributed?

For this module, we’ll work with the first 6 feature columns. For plotting purposes, let’s encode the label column as categorical. Tidymodels provides a neat way of excluding this variable when fitting a model to our data. Remember, we are dealing with unsupervised learning - which does not make use of previously known label values to train a model.

# Narrow down to desired features
seeds_select <- seeds %>% 
  select(!groove_length) %>% 
  mutate(species = factor(species))

# View first 5 rows of the data
seeds_select %>% 
  slice_head(n = 5)

As you can see, we now have six data points (or features) for each instance (observation) of a seed’s species. So you could interpret these as coordinates that describe each seed’s location in six-dimensional space.

Now, of course six-dimensional space is difficult to visualise in a three-dimensional world, or on a two-dimensional plot; so we’ll take advantage of a mathematical technique called Principal Component Analysis (PCA) to analyze the relationships between the features and summarize each observation as coordinates for two principal components - in other words, we’ll translate the six-dimensional feature values into two-dimensional coordinates.

Principal Component Analysis (PCA) is a dimension reduction method that aims at reducing the feature space, such that, most of the information or variability in the data set can be explained using fewer uncorrelated features.

PCA works by receiving as input P variables (in this case six) and calculating the normalized linear combination of the P variables. This new variable is the linear combination of the six variables that captures the greatest variance out of all of them. PCA continues to calculate other normalized linear combinations but with the constraint that they need to be completely uncorrelated to all the other normalized linear combinations. Please see:

for further reading.

Let’s see this in action by creating a specification of a recipe that will estimate the principal components based on our six variables. We’ll then prep andbake the recipe to apply the computations.

PCA works well when the variables are normalized (centered and scaled)

# Specify a recipe for pca
pca_rec <- recipe(~ ., data = seeds_select) %>% 
  update_role(species, new_role = "ID") %>% 
  step_normalize(all_predictors()) %>% 
  step_pca(all_predictors(), num_comp = 2, id = "pca")

# Print out recipe
pca_rec
## Data Recipe
## 
## Inputs:
## 
##       role #variables
##         ID          1
##  predictor          6
## 
## Operations:
## 
## Centering and scaling for all_predictors()
## No PCA components were extracted.

Compared to supervised learning techniques, we have no outcome variable in this recipe.

By updating the role of the species column to ID, this tells the recipe to keep the variable but not use it as either an outcome or predictor.

By calling prep() which estimates the statistics required by PCA and applying them to seeds_features using bake(new_data = NULL), we can get the fitted PC transformation of our features.

# Estimate required statistcs 
pca_estimates <- prep(pca_rec)

# Return preprocessed data using bake
features_2d <- pca_estimates %>% 
  bake(new_data = NULL)

# Print baked data set
features_2d %>% 
  slice_head(n = 5)

🤩 These two components capture the maximum amount of information (i.e. variance) in the original variables. From the output of our prepped recipe pca_estimates, we can examine how much variance each component accounts for:

# Examine how much variance each PC accounts for
pca_estimates %>% 
  tidy(id = "pca", type = "variance") %>% 
  filter(str_detect(terms, "percent"))
theme_set(theme_light())
# Plot how much variance each PC accounts for
pca_estimates %>% 
  tidy(id = "pca", type = "variance") %>% 
  filter(terms == "percent variance") %>% 
  ggplot(mapping = aes(x = component, y = value)) +
  geom_col(fill = "midnightblue", alpha = 0.7) +
  ylab("% of total variance")

This output tibbles and plots shows how well each principal component is explaining the original six variables. For example, the first principal component (PC1) explains about 72% of the variance of the six variables. The second principal component explains an additional 16.97%, giving a cumulative percent variance of 89.11%. This is certainly better. It means that the first two variables seem to have some power in summarizing the original six variables.

Naturally, the first PC (PC1) captures the most variance followed by PC2, then PC3, etc.

Now that we have the data points translated to two dimensions PC1 and PC2, we can visualize them in a plot:

# Visualize PC scores
features_2d %>% 
  ggplot(mapping = aes(x = PC1, y = PC2)) +
  geom_point(size = 2, color = "dodgerblue3")

Hopefully you can see at least two, arguably three, reasonably distinct groups of data points; but here lies one of the fundamental problems with clustering - without known class labels, how do you know how many clusters to separate your data into?

One way we can try to find out is to use a data sample to create a series of clustering models with an incrementing number of clusters, and measure how tightly the data points are grouped within each cluster. A metric often used to measure this tightness is the within cluster sum of squares (WCSS), with lower values meaning that the data points are closer. You can then plot the WCSS for each model.

We’ll use the built-in kmeans() function, which accepts a data frame with all numeric columns as it’s primary argument to perform clustering - means we’ll have to drop the species column. For clustering, it is recommended that the data have the same scale. We can use the recipes package to perform these transformations.

# Drop target column and normalize data
seeds_features<- recipe(~ ., data = seeds_select) %>% 
  step_rm(species) %>% 
  step_normalize(all_predictors()) %>% 
  prep() %>% 
  bake(new_data = NULL)

# Print out data
seeds_features %>% 
  slice_head(n = 5)

Now, let’s explore the WCSS of different numbers of clusters.

We’ll get to use map() from the purrr package to apply functions to each element in list.

map() functions allow you to replace many for loops with code that is both more succinct and easier to read. The best place to learn about the map() functions is the iteration chapter in R for data science.

broom::augment.kmeans() accepts a model object and returns a tibble with exactly one row of model summaries. The summaries are typically goodness of fit measures, p-values for hypothesis tests on residuals, or model convergence information.

set.seed(2056)
# Create 10 models with 1 to 10 clusters
kclusts <- tibble(k = 1:10) %>% 
  mutate(
    model = map(k, ~ kmeans(x = seeds_features, centers = .x, nstart = 20)),
    glanced = map(model, glance)) %>% 
  unnest(cols = c(glanced))

# View results
kclusts
# Plot Total within-cluster sum of squares (tot.withinss)
kclusts %>% 
  ggplot(mapping = aes(x = k, y = tot.withinss)) +
  geom_line(size = 1.2, alpha = 0.5, color = "dodgerblue3") +
  geom_point(size = 2, color = "dodgerblue3")

We seek to minimize the the total within-cluster sum of squares, by performing K-means clustering. The plot shows a large reduction in WCSS (so greater tightness) as the number of clusters increases from one to two, and a further noticable reduction from two to three clusters. After that, the reduction is less pronounced, resulting in an elbow 💪in the chart at around three clusters. This is a good indication that there are two to three reasonably well separated clusters of data points.

2. K-Means Clustering

The algorithm we used to approximate the number of clusters in our data set is called K-Means. Let’s get to the finer details, shall we?

K-Means is a commonly used clustering algorithm that separates a dataset into K clusters of equal variance such that observations within the same cluster are as similar as possible (i.e., high intra-class similarity), whereas observations from different clusters are as dissimilar as possible (i.e., low inter-class similarity). The number of clusters, K, is user defined.

The basic algorithm has the following steps:

  1. Specify the number of clusters to be created (this is done by the analyst). Taking the flowers example we used at the beginning of the lesson, this means deciding how many clusters you want to use to group the flowers.
  2. Next, the algorithm randomly selects K observations from the data set to serve as the initial centers for the clusters (i.e., centroids).
  3. Next, each of the remaining observations (in this case flowers) are assigned to its closest centroid.
  4. Next, the new means of each cluster is computed and the centroid is moved to the mean.
  5. Now that the centers have been recalculated, every observation is checked again to see if it might be closer to a different cluster. All the objects are reassigned again using the updated cluster means. The cluster assignment and centroid update steps are iteratively repeated until the cluster assignments stop changing (i.e., when convergence is achieved). Typically, the algorithm terminates when each new iteration results in negligible movement of centroids and the clusters become static.
  6. Note that due to randomization of the initial k observations used as the starting centroids, we can get slightly different results each time we apply the procedure. For this reason, most algorithms use several random starts and choose the iteration with the lowest WCSS. As such, it is strongly recommended to always run K-Means with several values of nstart to avoid an undesirable local optimum.

So training usually involves multiple iterations, reinitializing the centroids each time, and the model with the best (lowest) WCSS is selected. The following animation shows this process:

Now, back to our seeds example. After creating a series of clustering models with different numbers of clusters and plotting the WCSS across the clusters, we noticed a bend at around k = 3. This bend indicates that additional clusters beyond the third have little value and that there are two to three reasonably well separated clusters of data points.

So, let’s perform K-Means clustering specifying k = 3 clusters and add the classifications to the data set using augment.

set.seed(2056)
# Fit and predict clusters with k = 3
final_kmeans <- kmeans(seeds_features, centers = 3, nstart = 100, iter.max = 1000)

# Add cluster prediction to the data set
results <- augment(final_kmeans, seeds_features) %>% 
# Bind pca_data - features_2d
  bind_cols(features_2d)

results %>% 
  slice_head(n = 5)

Let’s see those cluster assignments with the two dimensional data points. We’ll add some touch of interactivity using the plotly package, so feel free to hover.

# Plot km_cluster assignmnet on the PC data
cluster_plot <- results %>% 
  ggplot(mapping = aes(x = PC1, y = PC2)) +
  geom_point(aes(shape = .cluster), size = 2) +
  scale_color_manual(values = c("darkorange","purple","cyan4"))

# Make plot interactive
ggplotly(cluster_plot)

🤩🤩 Hopefully, the data has been separated into three distinct clusters.

So what’s the practical use of clustering? In some cases, you may have data that you need to group into distict clusters without knowing how many clusters there are or what they indicate. For example a marketing organization might want to separate customers into distinct segments, and then investigate how those segments exhibit different purchasing behaviors.

Sometimes, clustering is used as an initial step towards creating a classification model. You start by identifying distinct groups of data points, and then assign class labels to those clusters. You can then use this labelled data to train a classification model.

In the case of the seeds data, the different species of seed are already known and encoded as 0 (Kama), 1 (Rosa), or 2 (Canadian), so we can use these identifiers to compare the species classifications to the clusters identified by our unsupervised algorithm

# Plot km_cluster assignmnet on the PC data
clust_spc_plot <- results %>% 
  ggplot(mapping = aes(x = PC1, y = PC2)) +
  geom_point(aes(shape = .cluster, color = species), size = 2, alpha = 0.8) +
  scale_color_manual(values = c("darkorange","purple","cyan4"))

# Make plot interactive
ggplotly(clust_spc_plot)

There may be some differences between the cluster assignments and class labels as shown by the different colors (species) within each cluster (shape). But the K-Means model should have done a reasonable job of clustering the observations so that seeds of the same species are generally in the same cluster. 💪

3. Hierarchical clustering

The first step in K-Means clustering is the data scientist specifying the number of clusters K to partition the observations into. Hierarchical clustering is an alternative approach which does not require the number of clusters to be defined in advance. Furthermore, hierarchical clustering results can be easily visualized using an attractive tree-based representation called a dendrogram. Once the dendrogram has been constructed, we slice this structure horizontally to identify the clusters formed.

Hierarchical clustering creates clusters by either a divisive method or agglomerative method. The divisive method is a top down approach starting with the entire dataset and then finding partitions in a stepwise manner. Agglomerative clustering is a bottom up approach. In this lab you will work with agglomerative clustering, commonly referred to as AGNES (AGglomerative NESting), which roughly works as follows:

  1. The linkage distances between each of the data points is computed.

  2. Points are clustered pairwise with their nearest neighbor.

  3. Linkage distances between the clusters are computed.

  4. Clusters are combined pairwise into larger clusters.

  5. Steps 3 and 4 are repeated until all data points are in a single cluster.

A fundamental question in hierarchical clustering is: How do we measure the dissimilarity between two clusters of observations? The linkage function/aggromeration methods can be computed in a number of ways:

  • Ward’s minimum variance method: Minimizes the total within-cluster variance. At each step the pair of clusters with the smallest between-cluster distance are merged. Tends to produce more compact clusters.

  • Average linkage uses the mean pairwise distance between the members of the two clusters. Can vary in the compactness of the clusters it creates.

  • Complete or Maximal linkage uses the maximum distance between the members of the two clusters. Tends to produce more compact clusters.

Several different distance metrics are used to compute linkage functions:

  • Euclidian or l2 distance is the most widely used. This is the only metric for the Ward linkage method.

  • Manhattan or l1 distance is robust to outliers and has other interesting properties.

  • Cosine similarity, is the dot product between the location vectors divided by the magnitudes of the vectors. Notice that this metric is a measure of similarity, whereas the other two metrics are measures of difference. Similarity can be quite useful when working with data such as images or text documents.

Please see:

for further reading.

Therefore, in Hierarchical clustering, the clusters themselves belong to a larger group, which belong to even larger groups, and so on. This is useful for not only breaking data into groups, but understanding the relationships between these groups.

For example, if we apply clustering to the meanings of words, we may get a group containing adjectives specific to emotions (‘angry’, ‘happy’, and so on), which itself belongs to a group containing all human-related adjectives (‘happy’, ‘handsome’, ‘young’), and this belongs to an even higher group containing all adjectives (‘happy’, ‘green’, ‘handsome’, ‘hard’ etc.).

Agglomerative Clustering

Let’s see an example of clustering the seeds data using an agglomerative clustering algorithm. There are many functions available in R for hierarchical clustering.

The hclust() function is one way to perform hierarchical clustering in R. It only needs one input and that is a distance matrix structure computed using distance metrics (e.g euclidean) as produced by dist(). hclust() also allows us to specify the agglomeration method to be used (i.e. "complete", "average", "single", or "ward.D").

Great! Let’s fit multiple hierarchical clustering models based on different aggromeration methods and see how the choice in aggromeration method changes the clustering.

# For reproducibility
set.seed(2056)

# Distance between observations matrix
d <- dist(x = seeds_features, method = "euclidean")

# Hierarchical clustering using Complete Linkage
seeds_hclust_complete <- hclust(d, method = "complete")

# Hierarchical clustering using Average Linkage
seeds_hclust_average <- hclust(d, method = "average")

# Hierarchical clustering using Ward Linkage
seeds_hclust_ward <- hclust(d, method = "ward.D2")

The factoextra provides functions (fviz_dend()) to visualize hierarchical clustering. Let’s visualize the dendrogram representation of the clusters starting with Complete aggromeration method.

library(factoextra)

# Visualize cluster separations
fviz_dend(seeds_hclust_complete, main = "Complete Linkage")

What about Average linkage?

# Visualize cluster separations
fviz_dend(seeds_hclust_average, main = "Average Linkage")

Lastly, the ward linkage.

# Visualize cluster separations
fviz_dend(seeds_hclust_ward, main = "Ward Linkage")

Note: If you are new to dendograms please see the following resources on how to interpret dendrograms:

Perfect! Take a moment and analyze the nature of the clusters.

This can be done mathematically by evaluating the aggromerative coefficient (AC), which measures the clustering structure of the dataset- with values closer to 1 suggest a more balanced clustering structure and values closer to 0 suggest less well-formed clusters. cluster::agnes() allows us to compute the hierarchical clustering as well as this metric too.

library(cluster)
#Compute ac values
ac_metric <- list(
  complete_ac = agnes(seeds_features, metric = "euclidean", method = "complete")$ac,
  average_ac = agnes(seeds_features, metric = "euclidean", method = "average")$ac,
  ward_ac = agnes(seeds_features, metric = "euclidean", method = "ward")$ac
)

ac_metric
## $complete_ac
## [1] 0.9344451
## 
## $average_ac
## [1] 0.8769204
## 
## $ward_ac
## [1] 0.9856365

As we explained earlier, complete and ward linkages tend to produce tight clustering of objects.

Now, let’s determine the optimal number of clusters. Although hierarchical clustering does not require one to pre-specify the number of clusters, one still needs to specify the number of clusters to extract. Let’s use the WCSS method to determine the optimal number of clusters.

# Determine and visuzalize optimal n.o of clusters
#  hcut (for hierarchical clustering)
fviz_nbclust(seeds_features, FUNcluster = hcut, method = "wss")

Just like in K-Means clustering, the optimal number of clusters for this data set is 3.

Let’s color our dendrogram according to k = 3 and observe how observations will be grouped. We’ll go with the ward linkage method.

# Visualize clustering structure for 3 groups
fviz_dend(seeds_hclust_ward, k = 3, main = "Ward Linkage")

Plausible enough 🤩!

We can now go ahead and cut the hierarchical clustering model into three clusters and extract the cluster labels for each observation associated with a given cut. This is done using cutree()

# Hierarchical clustering using Ward Linkage
seeds_hclust_ward <- hclust(d, method = "ward.D2")

# Group data into 3 clusters
results_hclust <- tibble(
  cluster_id = cutree(seeds_hclust_ward, k = 3)) %>% 
  mutate(cluster_id = factor(cluster_id)) %>% 
  bind_cols(features_2d)

results_hclust %>% 
  slice_head(n = 5)

We could probably do a little comparison between K-Means and Hierarchical clustering by counting the number of observations of each species in the corresponding clusters.

# Compare k-m and hc
results_hclust %>% 
  count(species, cluster_id) %>% 
  rename(n_hclust = n) %>% 
  bind_cols(results %>% 
              count(species, .cluster) %>%
              select(!species) %>% 
              rename(n_kmclust = n))

Ignoring the cluster_id and .cluster column since they are arbitrary, we can see that the observations were grouped quite similarly by the two algorithms. We could of course make a confusion matrix to better visualize this, but we’ll leave it at that for now.

Let’s wrap it up by making some plots showing how our observations were grouped into clusters.🥳

# Plot h-cluster assignmnet on the PC data
hclust_spc_plot <- results_hclust %>% 
  ggplot(mapping = aes(x = PC1, y = PC2)) +
  geom_point(aes(shape = cluster_id, color = species), size = 2, alpha = 0.8) +
  scale_color_manual(values = c("darkorange","purple","cyan4"))

# Make plot interactive
ggplotly(hclust_spc_plot)

4. Summary

In this module, you learned how clustering can be used to create unsupervised machine learning models that group data observations into clusters. You then used the Tidymodels framework in R to perform dimension reduction using PCA and various packages in the R ecosystem such as stats::kmeans(), stats::hclust(), cluster::agnes() to train K-Means and Hierarchical clustering models.

While Tidymodels (R) and scikit-learn (Python) are popular framework for writing code to train clustering models, you can also create machine learning solutions for clustering using the graphical tools in Microsoft Azure Machine Learning. You can learn more about no-code development of clustering models using Azure Machine Learning in the Create a clustering model with Azure Machine Learning designer module.

Challenge: Cluster Unlabelled Data

Now that you’ve seen how to create a clustering model, why not try for yourself? You’ll find a clustering challenge in the 04 - Clustering Challenge.ipynb notebook!

THANK YOU TO:

Allison Horst for creating the amazing illustrations that make R more welcoming and engaging. Find more illustrations at her gallery.

Bethany, Gold Microsoft Learn Student Ambassador, for her valuable feedback and suggestions.

FURTHER READING

Happy leaRning,

Eric (R_ic), Gold Microsoft Learn Student Ambassador.

LS0tDQp0aXRsZTogJ1RyYWluIGFuZCBFdmFsdWF0ZSBDbHVzdGVyaW5nIE1vZGVscyB1c2luZyBUaWR5bW9kZWxzIGFuZCBmcmllbmRzJw0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNzczogc3R5bGVfNy5jc3MNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0aGVtZTogZmxhdGx5DQogICAgaGlnaGxpZ2h0OiBicmVlemVkYXJrDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFLCBldmFsPVR9DQprbml0cjo6b3B0c19jaHVuayRzZXQod2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHRpZHltb2RlbHMpDQpsaWJyYXJ5KHNraW1yKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KGhlcmUpDQojIHNsaWNlIDwtIGRwbHlyOjpzbGljZQ0KIyBldmFsX21ldHJpY3MgPC0gbWV0cmljX3NldChybXNlLCByc3EpDQoNCmBgYA0KDQojIyBCdWNrbGUgdXAg8J+agA0KDQpJbiB0aGlzIGxlYXJuaW5nIHBhdGgsIHdlJ2xsIGxlYXJuIGhvdyB0byBjcmVhdGUgTWFjaGluZSBsZWFybmluZyBtb2RlbHMgdXNpbmcgYFJgIPCfmIouIE1hY2hpbmUgbGVhcm5pbmcgaXMgdGhlIGZvdW5kYXRpb24gZm9yIHByZWRpY3RpdmUgbW9kZWxpbmcgYW5kIGFydGlmaWNpYWwgaW50ZWxsaWdlbmNlLiBXZSdsbCBsZWFybiB0aGUgY29yZSBwcmluY2lwbGVzIG9mIG1hY2hpbmUgbGVhcm5pbmcgYW5kIGhvdyB0byB1c2UgY29tbW9uIHRvb2xzIGFuZCBmcmFtZXdvcmtzIHRvIHRyYWluLCBldmFsdWF0ZSwgYW5kIHVzZSBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscy4NCg0KTW9kdWxlcyB0aGF0IHdpbGwgYmUgY292ZXJlZCBpbiB0aGlzIGxlYXJuaW5nIHBhdGggaW5jbHVkZToNCg0KLSAgIEV4cGxvcmUgYW5kIGFuYWx5emUgZGF0YSB3aXRoIFINCg0KLSAgIFRyYWluIGFuZCBldmFsdWF0ZSByZWdyZXNzaW9uIG1vZGVscw0KDQotICAgVHJhaW4gYW5kIGV2YWx1YXRlIGNsYXNzaWZpY2F0aW9uIG1vZGVscw0KDQotICAgVHJhaW4gYW5kIGV2YWx1YXRlIGNsdXN0ZXJpbmcgbW9kZWxzDQoNCi0gICAqVHJhaW4gYW5kIGV2YWx1YXRlIGRlZXAgbGVhcm5pbmcgbW9kZWxzICh3b3JrIGluIHByb2dyZXNzKSoNCg0KIyMjICoqUHJlcmVxdWlzaXRlcyoqDQoNClRoaXMgbGVhcm5pbmcgcGF0aCBhc3N1bWVzIGtub3dsZWRnZSBvZiBiYXNpYyBtYXRoZW1hdGljYWwgY29uY2VwdHMuIFNvbWUgZXhwZXJpZW5jZSB3aXRoIGBSIGFuZCB0aGUgdGlkeXZlcnNlYCBpcyBhbHNvIGJlbmVmaWNpYWwgdGhvdWdoIHdlJ2xsIHRyeSBhcyBtdWNoIGFzIHBvc3NpYmxlIHRvIHNraW0gdGhyb3VnaCB0aGUgY29yZSBjb25jZXB0cy4gVG8gZ2V0IHN0YXJ0ZWQgd2l0aCBSIGFuZCB0aGUgdGlkeXZlcnNlLCB0aGUgYmVzdCBwbGFjZSB3b3VsZCBiZSBbUiBmb3IgRGF0YSBTY2llbmNlXShodHRwOi8vcjRkcy5oYWQuY28ubnovKSBhbiBPJ1JlaWxseSBib29rIHdyaXR0ZW4gYnkgSGFkbGV5IFdpY2toYW0gYW5kIEdhcnJldHQgR3JvbGVtdW5kLiBJdCdzIGRlc2lnbmVkIHRvIHRha2UgeW91IGZyb20ga25vd2luZyBub3RoaW5nIGFib3V0IFIgb3IgdGhlIHRpZHl2ZXJzZSB0byBoYXZpbmcgYWxsIHRoZSBiYXNpYyB0b29scyBvZiBkYXRhIHNjaWVuY2UgYXQgeW91ciBmaW5nZXJ0aXBzLg0KDQpUaGUgYFB5dGhvbmAgZWRpdGlvbiBvZiB0aGUgbGVhcm5pbmcgcGF0aCBjYW4gYmUgZm91bmQgYXQgW3RoaXMgbGVhcm5pbmcgcGF0aF0oaHR0cHM6Ly9kb2NzLm1pY3Jvc29mdC5jb20vZW4tdXMvbGVhcm4vcGF0aHMvY3JlYXRlLW1hY2hpbmUtbGVhcm4tbW9kZWxzLykuDQoNCioqV2h5IFI/KioNCg0KPiBSIGhhcyBlbWVyZ2VkIG92ZXIgdGhlIGxhc3QgY291cGxlIGRlY2FkZXMgYXMgYSBmaXJzdC1jbGFzcyB0b29sIGZvciBzY2llbnRpZmljIGNvbXB1dGluZyB0YXNrcywgYW5kIGhhcyBiZWVuIGEgY29uc2lzdGVudCBsZWFkZXIgaW4gaW1wbGVtZW50aW5nIHN0YXRpc3RpY2FsIG1ldGhvZG9sb2dpZXMgZm9yIGFuYWx5emluZyBkYXRhLiBUaGUgdXNlZnVsbmVzcyBvZiBSIGZvciBkYXRhIHNjaWVuY2Ugc3RlbXMgZnJvbSB0aGUgbGFyZ2UsIGFjdGl2ZSwgYW5kIGdyb3dpbmcgZWNvc3lzdGVtIG9mIHRoaXJkLXBhcnR5IHBhY2thZ2VzOiBgdGlkeXZlcnNlYCBmb3IgY29tbW9uIGRhdGEgYW5hbHlzaXMgYWN0aXZpdGllcztgaDJvYCwgYHJhbmdlcmAsIGB4Z2Jvb3N0YCwgYW5kIG90aGVycyBmb3IgZmFzdCBhbmQgc2NhbGFibGUgbWFjaGluZSBsZWFybmluZzsgYGltbGAsIGBwZHBgLCBgdmlwYCwgYW5kIG90aGVycyBmb3IgbWFjaGluZSBsZWFybmluZyBpbnRlcnByZXRhYmlsaXR5OyBhbmQgbWFueSBtb3JlIHRvb2xzIHdpbGwgYmUgbWVudGlvbmVkIHRocm91Z2hvdXQgdGhlIHBhZ2VzIHRoYXQgZm9sbG93LiAtIFtgQm9laG1rZSAmIEdyZWVud2VsbCAoMjAxOSkgSGFuZHMtT24gTWFjaGluZSBMZWFybmluZyB3aXRoIFJgXShodHRwczovL2JyYWRsZXlib2VobWtlLmdpdGh1Yi5pby9IT01MLykNCg0KTm93LCBsZXQncyBnZXQgc3RhcnRlZCENCg0KIVtBcnR3b3JrIGJ5IFxAYWxsaXNvbl9ob3JzdF0oaW1hZ2VzL3llZXIucG5nKXt3aWR0aD0iNTY5In0NCg0KIyMgQSBnZW50bGUgaW50cm9kdWN0aW9uIHRvIGNsdXN0ZXJpbmcNCg0KKmBDbHVzdGVyaW5nYCogaXMgYSBmb3JtIG9mICpgdW5zdXBlcnZpc2VkYCogbWFjaGluZSBsZWFybmluZyBpbiB3aGljaCBvYnNlcnZhdGlvbnMgYXJlIGBncm91cGVkIGludG8gY2x1c3RlcnNgIGJhc2VkIG9uIGBzaW1pbGFyaXRpZXNgIGluIHRoZWlyIGRhdGEgdmFsdWVzLCBvciAqZmVhdHVyZXMqLiBUaGlzIGtpbmQgb2YgbWFjaGluZSBsZWFybmluZyBpcyBjb25zaWRlcmVkIHVuc3VwZXJ2aXNlZCBiZWNhdXNlIGl0IGRvZXMgbm90IG1ha2UgdXNlIG9mIHByZXZpb3VzbHkga25vd24gKmxhYmVsKiB2YWx1ZXMgdG8gdHJhaW4gYSBtb2RlbDsgaW4gYSBjbHVzdGVyaW5nIG1vZGVsLCB0aGUgbGFiZWwgaXMgdGhlIGNsdXN0ZXIgdG8gd2hpY2ggdGhlIG9ic2VydmF0aW9uIGlzIGFzc2lnbmVkLCBiYXNlZCBwdXJlbHkgb24gaXRzIGZlYXR1cmVzLg0KDQpDbHVzdGVyaW5nIHdvcmtzIGJ5IHNlcGFyYXRpbmcgdGhlIHRyYWluaW5nIGNhc2VzIGJhc2VkIG9uIHNpbWlsYXJpdGllcyB0aGF0IGNhbiBiZSBkZXRlcm1pbmVkIGZyb20gdGhlaXIgZmVhdHVyZSB2YWx1ZXMuIFBlcmhhcHMgdGhlIHR3byBiZXN0LWtub3duIGNsdXN0ZXJpbmcgYXBwcm9hY2hlcyBhcmU6IGBLLW1lYW5zIGNsdXN0ZXJpbmdgIGFuZCBgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmdgLiBJbiBLLW1lYW5zIGNsdXN0ZXJpbmcsIHdlIHNlZWsgdG8gcGFydGl0aW9uIHRoZSBvYnNlcnZhdGlvbnMgaW50byBhIHByZS1zcGVjaWZpZWQgbnVtYmVyIG9mIGNsdXN0ZXJzLiBPbiB0aGUgb3RoZXIgaGFuZCwgaW4gaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcsIHdlIGRvIG5vdCBrbm93IGluIGFkdmFuY2UgaG93IG1hbnkgY2x1c3RlcnMgd2Ugd2FudC4NCg0KVGhpbmsgb2YgaXQgdGhpcyB3YXk7IHRoZSBudW1lcmljIGZlYXR1cmVzIG9mIGEgZ2l2ZW4gZW50aXR5IGNhbiBiZSB0aG91Z2h0IG9mIGFzIHZlY3RvciBjb29yZGluYXRlcyB0aGF0IGRlZmluZSB0aGUgZW50aXR5J3MgcG9zaXRpb24gaW4gbi1kaW1lbnNpb25hbCBzcGFjZS4gV2hhdCBhIGNsdXN0ZXJpbmcgbW9kZWwgc2Vla3MgdG8gZG8gaXMgdG8gaWRlbnRpZnkgZ3JvdXBzLCBvciAqY2x1c3RlcnMqLCBvZiBlbnRpdGllcyB0aGF0IGFyZSBjbG9zZSB0byBvbmUgYW5vdGhlciB3aGlsZSBiZWluZyBzZXBhcmF0ZWQgZnJvbSBvdGhlciBjbHVzdGVycy4NCg0KRm9yIGV4YW1wbGUsIHN1cHBvc2UgYSBib3RhbmlzdCBvYnNlcnZlcyBhIHNhbXBsZSBvZiBmbG93ZXJzIGFuZCByZWNvcmRzIHRoZSBudW1iZXIgb2YgcGV0YWxzIGFuZCBsZWF2ZXMgb24gZWFjaCBmbG93ZXIuDQoNClshW10oaW1hZ2VzL2Zsb3dlcnMucG5nKXt3aWR0aD0iNDAwIn1dKGh0dHBzOi8vZG9jcy5taWNyb3NvZnQuY29tL2VuLXVzL2xlYXJuL21vZHVsZXMvdHJhaW4tZXZhbHVhdGUtY2x1c3Rlci1tb2RlbHMvMi13aGF0LWlzLWNsdXN0ZXJpbmcpDQoNCkl0IG1heSBiZSB1c2VmdWwgdG8gZ3JvdXAgdGhlc2UgZmxvd2VycyBpbnRvIGNsdXN0ZXJzIGJhc2VkIG9uIHNpbWlsYXJpdGllcyBiZXR3ZWVuIHRoZWlyIGZlYXR1cmVzLg0KDQpUaGVyZSBhcmUgbWFueSB3YXlzIHRoaXMgY291bGQgYmUgZG9uZS4gRm9yIGV4YW1wbGUsIGlmIG1vc3QgZmxvd2VycyBoYXZlIHRoZSBzYW1lIG51bWJlciBvZiBsZWF2ZXMsIHRoZXkgY291bGQgYmUgZ3JvdXBlZCBpbnRvIHRob3NlIHdpdGggbWFueSB2cyBmZXcgcGV0YWxzLiBBbHRlcm5hdGl2ZWx5LCBpZiBib3RoIHBldGFsIGFuZCBsZWFmIGNvdW50cyB2YXJ5IGNvbnNpZGVyYWJseSB0aGVyZSBtYXkgYmUgYSBwYXR0ZXJuIHRvIGRpc2NvdmVyLCBzdWNoIGFzIHRob3NlIHdpdGggbWFueSBsZWF2ZXMgYWxzbyBoYXZpbmcgbWFueSBwZXRhbHMuIFRoZSBnb2FsIG9mIHRoZSBjbHVzdGVyaW5nIGFsZ29yaXRobSBpcyB0byBmaW5kIHRoZSBvcHRpbWFsIHdheSB0byBzcGxpdCB0aGUgZGF0YXNldCBpbnRvIGdyb3Vwcy4gV2hhdCAnb3B0aW1hbCcgbWVhbnMgZGVwZW5kcyBvbiBib3RoIHRoZSBhbGdvcml0aG0gdXNlZCBhbmQgdGhlIGRhdGFzZXQgdGhhdCBpcyBwcm92aWRlZC4NCg0KQWx0aG91Z2ggdGhpcyBmbG93ZXIgZXhhbXBsZSBtYXkgYmUgc2ltcGxlIGZvciBhIGh1bWFuIHRvIGFjaGlldmUgd2l0aCBvbmx5IGEgZmV3IHNhbXBsZXMsIGFzIHRoZSBkYXRhc2V0IGdyb3dzIHRvIHRob3VzYW5kcyBvZiBzYW1wbGVzIG9yIHRvIG1vcmUgdGhhbiB0d28gZmVhdHVyZXMsIGNsdXN0ZXJpbmcgYWxnb3JpdGhtcyBiZWNvbWUgdmVyeSB1c2VmdWwgdG8gcXVpY2tseSBkaXNzZWN0IGEgZGF0YXNldCBpbnRvIGdyb3Vwcy4NCg0KVGhlIGJlc3Qgd2F5IHRvIGxlYXJuIGFib3V0IGNsdXN0ZXJpbmcgaXMgdG8gdHJ5IGl0IGZvciB5b3Vyc2VsZiwgc28gdGhhdCdzIHdoYXQgeW91J2xsIGRvIGluIHRoaXMgZXhlcmNpc2UuDQoNCj4gV2UnbGwgcmVxdWlyZSBzb21lIHBhY2thZ2VzIHRvIGtub2NrLW9mZiB0aGlzIG1vZHVsZS4gWW91IGNhbiBoYXZlIHRoZW0gaW5zdGFsbGVkIGFzOiBgaW5zdGFsbC5wYWNrYWdlcyhjKCd0aWR5dmVyc2UnLCAndGlkeW1vZGVscycsICdza2ltcicsICdoZXJlJywgJ3Bsb3RseScsICdmYWN0b2V4dHJhJywgJ2NsdXN0ZXInKSlgDQoNCkFsdGVybmF0aXZlbHksIHRoZSBzY3JpcHQgYmVsb3cgY2hlY2tzIHdoZXRoZXIgeW91IGhhdmUgdGhlIHBhY2thZ2VzIHJlcXVpcmVkIHRvIGNvbXBsZXRlIHRoaXMgbW9kdWxlIGFuZCBpbnN0YWxscyB0aGVtIGZvciB5b3UgaW4gY2FzZSBzb21lIGFyZSBtaXNzaW5nLg0KDQpgYGB7cn0NCnN1cHByZXNzV2FybmluZ3MoaWYoIXJlcXVpcmUoInBhY21hbiIpKSBpbnN0YWxsLnBhY2thZ2VzKCJwYWNtYW4iKSkNCg0KcGFjbWFuOjpwX2xvYWQoJ3RpZHl2ZXJzZScsICd0aWR5bW9kZWxzJywgJ3NraW1yJywgJ2hlcmUnLCAncGxvdGx5JywgJ2ZhY3RvZXh0cmEnLCAnY2x1c3RlcicpDQoNCmBgYA0KDQojIyAxLiBQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzIChQQ0EpDQoNCkxldCdzIHRha2UgYSBsb29rIGF0IGEgZGF0YXNldCB0aGF0IGNvbnRhaW5zIG1lYXN1cmVtZW50cyBvZiBkaWZmZXJlbnQgc3BlY2llcyBvZiB3aGVhdCBzZWVkLg0KDQo+ICoqQ2l0YXRpb24qKjogVGhlIHNlZWRzIGRhdGFzZXQgdXNlZCBpbiB0aGUgdGhpcyBleGVyY2lzZSB3YXMgb3JpZ2luYWxseSBwdWJsaXNoZWQgYnkgdGhlIEluc3RpdHV0ZSBvZiBBZ3JvcGh5c2ljcyBvZiB0aGUgUG9saXNoIEFjYWRlbXkgb2YgU2NpZW5jZXMgaW4gTHVibGluLCBhbmQgY2FuIGJlIGRvd25sb2FkZWQgZnJvbSB0aGUgVUNJIGRhdGFzZXQgcmVwb3NpdG9yeSAoRHVhLCBELiBhbmQgR3JhZmYsIEMuICgyMDE5KS4gW1VDSSBNYWNoaW5lIExlYXJuaW5nIFJlcG9zaXRvcnldKGh0dHA6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sJTVEKS4gSXJ2aW5lLCBDQTogVW5pdmVyc2l0eSBvZiBDYWxpZm9ybmlhLCBTY2hvb2wgb2YgSW5mb3JtYXRpb24gYW5kIENvbXB1dGVyIFNjaWVuY2UpLg0KDQpgYGB7ciByZWFkX3VybCwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9DQojIExvYWQgdGhlIGNvcmUgdGlkeXZlcnNlIGFuZCBtYWtlIGl0IGF2YWlsYWJsZSBpbiB5b3VyIGN1cnJlbnQgUiBzZXNzaW9uDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCg0KIyBSZWFkIHRoZSBjc3YgZmlsZSBpbnRvIGEgdGliYmxlDQpzZWVkcyA8LSByZWFkX2NzdihmaWxlID0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9NaWNyb3NvZnREb2NzL21sLWJhc2ljcy9tYXN0ZXIvZGF0YS9zZWVkcy5jc3YiKQ0KDQojIFByaW50IHRoZSBmaXJzdCAxMCByb3dzIG9mIHRoZSBkYXRhDQpzZWVkcyAlPiUgDQogIHNsaWNlX2hlYWQobiA9IDUpDQoNCg0KDQpgYGANCg0KU29tZXRpbWVzLCB3ZSBtYXkgd2FudCBzb21lIGxpdHRsZSBtb3JlIGluZm9ybWF0aW9uIG9uIG91ciBkYXRhLiBXZSBjYW4gaGF2ZSBhIGxvb2sgYXQgdGhlIGBkYXRhYCwgYGl0cyBzdHJ1Y3R1cmVgIGFuZCB0aGUgYGRhdGEgdHlwZWAgb2YgaXRzIGZlYXR1cmVzIGJ5IHVzaW5nIHRoZSBbKmdsaW1wc2UoKSpdKGh0dHBzOi8vcGlsbGFyLnItbGliLm9yZy9yZWZlcmVuY2UvZ2xpbXBzZS5odG1sKSBmdW5jdGlvbiBhcyBiZWxvdzoNCg0KYGBge3J9DQojIEV4cGxvcmUgZGltZW5zaW9uIGFuZCB0eXBlIG9mIGNvbHVtbnMNCnNlZWRzICU+JSANCiAgZ2xpbXBzZSgpDQpgYGANCg0KV2hpbGUgYXQgaXQsIGxldCdzIHVzZSBgc2tpbXI6OnNraW0oKWAgdG8gdGFrZSBhIGxvb2sgYXQgdGhlIHN1bW1hcnkgc3RhdGlzdGljcyBmb3IgdGhlIGRhdGENCg0KYGBge3Igc3VtbWFyeV9zdGF0c30NCmxpYnJhcnkoc2tpbXIpDQoNCiMgT2J0YWluIFN1bW1hcnkgc3RhdGlzdGljcw0Kc2VlZHMgJT4lIA0KICBza2ltKCkNCg0KYGBgDQoNCvCfpKlUYWtlIGEgbW9tZW50IGFuZCBnbyB0aHJvdWdoIHRoZSBxdWljayBkYXRhIGV4cGxvcmF0aW9uIHdlIGp1c3QgcGVyZm9ybWVkLiBEbyB3ZSBoYXZlIGFueSBtaXNzaW5nIHZhbHVlcz8gV2hhdCdzIHRoZSBkaW1lbnNpb24gb2Ygb3VyIGRhdGEgKHJvd3MgYW5kIGNvbHVtbnMpPyBXaGF0IGFyZSB0aGUgZGlmZmVyZW50IGNvbHVtbiB0eXBlcz8gSG93IGFyZSB0aGUgdmFsdWVzIGluIG91ciBjb2x1bW5zIGRpc3RyaWJ1dGVkPw0KDQpGb3IgdGhpcyBtb2R1bGUsIHdlJ2xsIHdvcmsgd2l0aCB0aGUgZmlyc3QgNiBgZmVhdHVyZWAgY29sdW1ucy4gRm9yICoqcGxvdHRpbmcqKiBwdXJwb3NlcywgbGV0J3MgZW5jb2RlIHRoZSAqbGFiZWwqIGNvbHVtbiBhcyBjYXRlZ29yaWNhbC4gVGlkeW1vZGVscyBwcm92aWRlcyBhIG5lYXQgd2F5IG9mIGV4Y2x1ZGluZyB0aGlzIHZhcmlhYmxlIHdoZW4gZml0dGluZyBhIG1vZGVsIHRvIG91ciBkYXRhLiBSZW1lbWJlciwgd2UgYXJlIGRlYWxpbmcgd2l0aCB1bnN1cGVydmlzZWQgbGVhcm5pbmcgLSB3aGljaCBkb2VzIG5vdCBtYWtlIHVzZSBvZiBwcmV2aW91c2x5IGtub3duICpsYWJlbCogdmFsdWVzIHRvIHRyYWluIGEgbW9kZWwuDQoNCmBgYHtyIHNlbGVjdH0NCiMgTmFycm93IGRvd24gdG8gZGVzaXJlZCBmZWF0dXJlcw0Kc2VlZHNfc2VsZWN0IDwtIHNlZWRzICU+JSANCiAgc2VsZWN0KCFncm9vdmVfbGVuZ3RoKSAlPiUgDQogIG11dGF0ZShzcGVjaWVzID0gZmFjdG9yKHNwZWNpZXMpKQ0KDQojIFZpZXcgZmlyc3QgNSByb3dzIG9mIHRoZSBkYXRhDQpzZWVkc19zZWxlY3QgJT4lIA0KICBzbGljZV9oZWFkKG4gPSA1KQ0KDQpgYGANCg0KQXMgeW91IGNhbiBzZWUsIHdlIG5vdyBoYXZlIHNpeCBkYXRhIHBvaW50cyAob3IgKmZlYXR1cmVzKikgZm9yIGVhY2ggaW5zdGFuY2UgKCpvYnNlcnZhdGlvbiopIG9mIGEgc2VlZCdzIHNwZWNpZXMuIFNvIHlvdSBjb3VsZCBpbnRlcnByZXQgdGhlc2UgYXMgY29vcmRpbmF0ZXMgdGhhdCBkZXNjcmliZSBlYWNoIHNlZWQncyBsb2NhdGlvbiBpbiBzaXgtZGltZW5zaW9uYWwgc3BhY2UuDQoNCk5vdywgb2YgY291cnNlIHNpeC1kaW1lbnNpb25hbCBzcGFjZSBpcyBkaWZmaWN1bHQgdG8gdmlzdWFsaXNlIGluIGEgdGhyZWUtZGltZW5zaW9uYWwgd29ybGQsIG9yIG9uIGEgdHdvLWRpbWVuc2lvbmFsIHBsb3Q7IHNvIHdlJ2xsIHRha2UgYWR2YW50YWdlIG9mIGEgbWF0aGVtYXRpY2FsIHRlY2huaXF1ZSBjYWxsZWQgKmBQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzYCogKFBDQSkgdG8gYW5hbHl6ZSB0aGUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHRoZSBmZWF0dXJlcyBhbmQgc3VtbWFyaXplIGVhY2ggb2JzZXJ2YXRpb24gYXMgY29vcmRpbmF0ZXMgZm9yIHR3byBwcmluY2lwYWwgY29tcG9uZW50cyAtIGluIG90aGVyIHdvcmRzLCB3ZSdsbCB0cmFuc2xhdGUgdGhlIHNpeC1kaW1lbnNpb25hbCBmZWF0dXJlIHZhbHVlcyBpbnRvIHR3by1kaW1lbnNpb25hbCBjb29yZGluYXRlcy4NCg0KKmBQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzYCogKFBDQSkgaXMgYSBkaW1lbnNpb24gcmVkdWN0aW9uIG1ldGhvZCB0aGF0IGFpbXMgYXQgcmVkdWNpbmcgdGhlIGZlYXR1cmUgc3BhY2UsIHN1Y2ggdGhhdCwgbW9zdCBvZiB0aGUgaW5mb3JtYXRpb24gb3IgdmFyaWFiaWxpdHkgaW4gdGhlIGRhdGEgc2V0IGNhbiBiZSBleHBsYWluZWQgdXNpbmcgZmV3ZXIgdW5jb3JyZWxhdGVkIGZlYXR1cmVzLg0KDQo+IFBDQSB3b3JrcyBieSByZWNlaXZpbmcgYXMgaW5wdXQgUCB2YXJpYWJsZXMgKGluIHRoaXMgY2FzZSBzaXgpIGFuZCBjYWxjdWxhdGluZyB0aGUgbm9ybWFsaXplZCBsaW5lYXIgY29tYmluYXRpb24gb2YgdGhlIFAgdmFyaWFibGVzLiBUaGlzIG5ldyB2YXJpYWJsZSBpcyB0aGUgbGluZWFyIGNvbWJpbmF0aW9uIG9mIHRoZSBzaXggdmFyaWFibGVzIHRoYXQgY2FwdHVyZXMgdGhlIGdyZWF0ZXN0IHZhcmlhbmNlIG91dCBvZiBhbGwgb2YgdGhlbS4gUENBIGNvbnRpbnVlcyB0byBjYWxjdWxhdGUgb3RoZXIgbm9ybWFsaXplZCBsaW5lYXIgY29tYmluYXRpb25zICoqYnV0Kiogd2l0aCB0aGUgY29uc3RyYWludCB0aGF0IHRoZXkgbmVlZCB0byBiZSBgY29tcGxldGVseSB1bmNvcnJlbGF0ZWRgIHRvIGFsbCB0aGUgb3RoZXIgbm9ybWFsaXplZCBsaW5lYXIgY29tYmluYXRpb25zLiBQbGVhc2Ugc2VlOg0KPg0KPiAtICAgW1ByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXNdKGh0dHBzOi8vYnJhZGxleWJvZWhta2UuZ2l0aHViLmlvL0hPTUwvcGNhLmh0bWwpLCBCb2VobWtlICYgR3JlZW53ZWxsLCBIYW5kcy1PbiBNYWNoaW5lIExlYXJuaW5nIHdpdGggUg0KPg0KPiAtICAgW1Vuc3VwZXJ2aXNlZCBNZXRob2RzXShodHRwczovL2NpbWVudGFkYWouZ2l0aHViLmlvL21sX3NvY3NjaS91bnN1cGVydmlzZWQtbWV0aG9kcy5odG1sKSwgSm9yZ2UgQ2ltZW50YWRhIE1hY2hpbmUsIExlYXJuaW5nIGZvciBTb2NpYWwgU2NpZW50aXN0Lg0KPg0KPiBmb3IgZnVydGhlciByZWFkaW5nLg0KDQpMZXQncyBzZWUgdGhpcyBpbiBhY3Rpb24gYnkgY3JlYXRpbmcgYSBzcGVjaWZpY2F0aW9uIG9mIGEgYHJlY2lwZWAgdGhhdCB3aWxsIGVzdGltYXRlIHRoZSAqcHJpbmNpcGFsIGNvbXBvbmVudHMqIGJhc2VkIG9uIG91ciBzaXggdmFyaWFibGVzLiBXZSdsbCB0aGVuIGBwcmVwYCBhbmRgYmFrZWAgdGhlIHJlY2lwZSB0byBhcHBseSB0aGUgY29tcHV0YXRpb25zLg0KDQo+IFBDQSB3b3JrcyB3ZWxsIHdoZW4gdGhlIHZhcmlhYmxlcyBhcmUgbm9ybWFsaXplZCAoYGNlbnRlcmVkYCBhbmQgYHNjYWxlZGApDQoNCmBgYHtyIHBjYV9wcmVwfQ0KIyBTcGVjaWZ5IGEgcmVjaXBlIGZvciBwY2ENCnBjYV9yZWMgPC0gcmVjaXBlKH4gLiwgZGF0YSA9IHNlZWRzX3NlbGVjdCkgJT4lIA0KICB1cGRhdGVfcm9sZShzcGVjaWVzLCBuZXdfcm9sZSA9ICJJRCIpICU+JSANCiAgc3RlcF9ub3JtYWxpemUoYWxsX3ByZWRpY3RvcnMoKSkgJT4lIA0KICBzdGVwX3BjYShhbGxfcHJlZGljdG9ycygpLCBudW1fY29tcCA9IDIsIGlkID0gInBjYSIpDQoNCiMgUHJpbnQgb3V0IHJlY2lwZQ0KcGNhX3JlYw0KDQpgYGANCg0KQ29tcGFyZWQgdG8gc3VwZXJ2aXNlZCBsZWFybmluZyB0ZWNobmlxdWVzLCB3ZSBoYXZlIG5vIGBvdXRjb21lYCB2YXJpYWJsZSBpbiB0aGlzIHJlY2lwZS4NCg0KQnkgdXBkYXRpbmcgdGhlIHJvbGUgb2YgdGhlIGBzcGVjaWVzYCBjb2x1bW4gdG8gYElEYCwgdGhpcyB0ZWxscyB0aGUgcmVjaXBlIHRvIGtlZXAgdGhlIHZhcmlhYmxlIGJ1dCBub3QgdXNlIGl0IGFzIGVpdGhlciBhbiBvdXRjb21lIG9yIHByZWRpY3Rvci4NCg0KQnkgY2FsbGluZyBgcHJlcCgpYCB3aGljaCBlc3RpbWF0ZXMgdGhlIHN0YXRpc3RpY3MgcmVxdWlyZWQgYnkgUENBIGFuZCBhcHBseWluZyB0aGVtIHRvIGBzZWVkc19mZWF0dXJlc2AgdXNpbmcgYGJha2UobmV3X2RhdGEgPSBOVUxMKWAsIHdlIGNhbiBnZXQgdGhlIGZpdHRlZCBQQyB0cmFuc2Zvcm1hdGlvbiBvZiBvdXIgZmVhdHVyZXMuDQoNCmBgYHtyIGJha2V9DQojIEVzdGltYXRlIHJlcXVpcmVkIHN0YXRpc3RjcyANCnBjYV9lc3RpbWF0ZXMgPC0gcHJlcChwY2FfcmVjKQ0KDQojIFJldHVybiBwcmVwcm9jZXNzZWQgZGF0YSB1c2luZyBiYWtlDQpmZWF0dXJlc18yZCA8LSBwY2FfZXN0aW1hdGVzICU+JSANCiAgYmFrZShuZXdfZGF0YSA9IE5VTEwpDQoNCiMgUHJpbnQgYmFrZWQgZGF0YSBzZXQNCmZlYXR1cmVzXzJkICU+JSANCiAgc2xpY2VfaGVhZChuID0gNSkNCg0KYGBgDQoNCvCfpKkgVGhlc2UgdHdvIGNvbXBvbmVudHMgY2FwdHVyZSB0aGUgbWF4aW11bSBhbW91bnQgb2YgaW5mb3JtYXRpb24gKGkuZS4gdmFyaWFuY2UpIGluIHRoZSBvcmlnaW5hbCB2YXJpYWJsZXMuIEZyb20gdGhlIG91dHB1dCBvZiBvdXIgcHJlcHBlZCByZWNpcGUgYHBjYV9lc3RpbWF0ZXNgLCB3ZSBjYW4gZXhhbWluZSBob3cgbXVjaCB2YXJpYW5jZSBlYWNoIGNvbXBvbmVudCBhY2NvdW50cyBmb3I6DQoNCmBgYHtyIHZhcmlhbmNlfQ0KIyBFeGFtaW5lIGhvdyBtdWNoIHZhcmlhbmNlIGVhY2ggUEMgYWNjb3VudHMgZm9yDQpwY2FfZXN0aW1hdGVzICU+JSANCiAgdGlkeShpZCA9ICJwY2EiLCB0eXBlID0gInZhcmlhbmNlIikgJT4lIA0KICBmaWx0ZXIoc3RyX2RldGVjdCh0ZXJtcywgInBlcmNlbnQiKSkNCg0KDQp0aGVtZV9zZXQodGhlbWVfbGlnaHQoKSkNCiMgUGxvdCBob3cgbXVjaCB2YXJpYW5jZSBlYWNoIFBDIGFjY291bnRzIGZvcg0KcGNhX2VzdGltYXRlcyAlPiUgDQogIHRpZHkoaWQgPSAicGNhIiwgdHlwZSA9ICJ2YXJpYW5jZSIpICU+JSANCiAgZmlsdGVyKHRlcm1zID09ICJwZXJjZW50IHZhcmlhbmNlIikgJT4lIA0KICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gY29tcG9uZW50LCB5ID0gdmFsdWUpKSArDQogIGdlb21fY29sKGZpbGwgPSAibWlkbmlnaHRibHVlIiwgYWxwaGEgPSAwLjcpICsNCiAgeWxhYigiJSBvZiB0b3RhbCB2YXJpYW5jZSIpDQpgYGANCg0KVGhpcyBvdXRwdXQgdGliYmxlcyBhbmQgcGxvdHMgc2hvd3MgaG93IHdlbGwgZWFjaCBwcmluY2lwYWwgY29tcG9uZW50IGlzIGV4cGxhaW5pbmcgdGhlIG9yaWdpbmFsIHNpeCB2YXJpYWJsZXMuIEZvciBleGFtcGxlLCB0aGUgZmlyc3QgcHJpbmNpcGFsIGNvbXBvbmVudCAoUEMxKSBleHBsYWlucyBhYm91dCBgNzIlYCBvZiB0aGUgdmFyaWFuY2Ugb2YgdGhlIHNpeCB2YXJpYWJsZXMuIFRoZSBzZWNvbmQgcHJpbmNpcGFsIGNvbXBvbmVudCBleHBsYWlucyBhbiBhZGRpdGlvbmFsIGAxNi45NyVgLCBnaXZpbmcgYSBjdW11bGF0aXZlIHBlcmNlbnQgdmFyaWFuY2Ugb2YgYDg5LjExJWAuIFRoaXMgaXMgY2VydGFpbmx5IGJldHRlci4gSXQgbWVhbnMgdGhhdCB0aGUgZmlyc3QgdHdvIHZhcmlhYmxlcyBzZWVtIHRvIGhhdmUgc29tZSBwb3dlciBpbiBzdW1tYXJpemluZyB0aGUgb3JpZ2luYWwgc2l4IHZhcmlhYmxlcy4NCg0KTmF0dXJhbGx5LCB0aGUgZmlyc3QgUEMgKFBDMSkgY2FwdHVyZXMgdGhlIG1vc3QgdmFyaWFuY2UgZm9sbG93ZWQgYnkgUEMyLCB0aGVuIFBDMywgZXRjLg0KDQpOb3cgdGhhdCB3ZSBoYXZlIHRoZSBkYXRhIHBvaW50cyB0cmFuc2xhdGVkIHRvIHR3byBkaW1lbnNpb25zIFBDMSBhbmQgUEMyLCB3ZSBjYW4gdmlzdWFsaXplIHRoZW0gaW4gYSBwbG90Og0KDQpgYGB7ciBwY2FfcGxvdH0NCiMgVmlzdWFsaXplIFBDIHNjb3Jlcw0KZmVhdHVyZXNfMmQgJT4lIA0KICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gUEMxLCB5ID0gUEMyKSkgKw0KICBnZW9tX3BvaW50KHNpemUgPSAyLCBjb2xvciA9ICJkb2RnZXJibHVlMyIpDQoNCmBgYA0KDQpIb3BlZnVsbHkgeW91IGNhbiBzZWUgYXQgbGVhc3QgdHdvLCBhcmd1YWJseSB0aHJlZSwgcmVhc29uYWJseSBkaXN0aW5jdCBncm91cHMgb2YgZGF0YSBwb2ludHM7IGJ1dCBoZXJlIGxpZXMgb25lIG9mIHRoZSBmdW5kYW1lbnRhbCBwcm9ibGVtcyB3aXRoIGNsdXN0ZXJpbmcgLSB3aXRob3V0IGtub3duIGNsYXNzIGxhYmVscywgaG93IGRvIHlvdSBrbm93IGhvdyBtYW55IGNsdXN0ZXJzIHRvIHNlcGFyYXRlIHlvdXIgZGF0YSBpbnRvPw0KDQpPbmUgd2F5IHdlIGNhbiB0cnkgdG8gZmluZCBvdXQgaXMgdG8gdXNlIGEgZGF0YSBzYW1wbGUgdG8gY3JlYXRlIGEgc2VyaWVzIG9mIGNsdXN0ZXJpbmcgbW9kZWxzIHdpdGggYW4gaW5jcmVtZW50aW5nIG51bWJlciBvZiBjbHVzdGVycywgYW5kIG1lYXN1cmUgaG93IHRpZ2h0bHkgdGhlIGRhdGEgcG9pbnRzIGFyZSBncm91cGVkIHdpdGhpbiBlYWNoIGNsdXN0ZXIuIEEgbWV0cmljIG9mdGVuIHVzZWQgdG8gbWVhc3VyZSB0aGlzIHRpZ2h0bmVzcyBpcyB0aGUgKndpdGhpbiBjbHVzdGVyIHN1bSBvZiBzcXVhcmVzKiAoV0NTUyksIHdpdGggbG93ZXIgdmFsdWVzIG1lYW5pbmcgdGhhdCB0aGUgZGF0YSBwb2ludHMgYXJlIGNsb3Nlci4gWW91IGNhbiB0aGVuIHBsb3QgdGhlIFdDU1MgZm9yIGVhY2ggbW9kZWwuDQoNCldlJ2xsIHVzZSB0aGUgYnVpbHQtaW4gYGttZWFucygpYCBmdW5jdGlvbiwgd2hpY2ggYWNjZXB0cyBhIGRhdGEgZnJhbWUgd2l0aCBhbGwgbnVtZXJpYyBjb2x1bW5zIGFzIGl0J3MgcHJpbWFyeSBhcmd1bWVudCB0byBwZXJmb3JtIGNsdXN0ZXJpbmcgLSBtZWFucyB3ZSdsbCBoYXZlIHRvIGRyb3AgdGhlICpzcGVjaWVzKiBjb2x1bW4uIEZvciBjbHVzdGVyaW5nLCBpdCBpcyBbcmVjb21tZW5kZWRdKGh0dHBzOi8vZGV2ZWxvcGVycy5nb29nbGUuY29tL21hY2hpbmUtbGVhcm5pbmcvY2x1c3RlcmluZy9wcmVwYXJlLWRhdGEpIHRoYXQgdGhlIGRhdGEgaGF2ZSB0aGUgc2FtZSBzY2FsZS4gV2UgY2FuIHVzZSB0aGUgcmVjaXBlcyBwYWNrYWdlIHRvIHBlcmZvcm0gdGhlc2UgdHJhbnNmb3JtYXRpb25zLg0KDQpgYGB7ciBjbHVzdGVyaW5nfQ0KIyBEcm9wIHRhcmdldCBjb2x1bW4gYW5kIG5vcm1hbGl6ZSBkYXRhDQpzZWVkc19mZWF0dXJlczwtIHJlY2lwZSh+IC4sIGRhdGEgPSBzZWVkc19zZWxlY3QpICU+JSANCiAgc3RlcF9ybShzcGVjaWVzKSAlPiUgDQogIHN0ZXBfbm9ybWFsaXplKGFsbF9wcmVkaWN0b3JzKCkpICU+JSANCiAgcHJlcCgpICU+JSANCiAgYmFrZShuZXdfZGF0YSA9IE5VTEwpDQoNCiMgUHJpbnQgb3V0IGRhdGENCnNlZWRzX2ZlYXR1cmVzICU+JSANCiAgc2xpY2VfaGVhZChuID0gNSkNCmBgYA0KDQpOb3csIGxldCdzIGV4cGxvcmUgdGhlIFdDU1Mgb2YgZGlmZmVyZW50IG51bWJlcnMgb2YgY2x1c3RlcnMuDQoNCldlJ2xsIGdldCB0byB1c2UgYG1hcCgpYCBmcm9tIHRoZSBbcHVycnJdKGh0dHBzOi8vcHVycnIudGlkeXZlcnNlLm9yZy8pIHBhY2thZ2UgdG8gYXBwbHkgZnVuY3Rpb25zIHRvIGVhY2ggZWxlbWVudCBpbiBsaXN0Lg0KDQo+IFtgbWFwKClgXShodHRwczovL3B1cnJyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL21hcC5odG1sKSBmdW5jdGlvbnMgYWxsb3cgeW91IHRvIHJlcGxhY2UgbWFueSBmb3IgbG9vcHMgd2l0aCBjb2RlIHRoYXQgaXMgYm90aCBtb3JlIHN1Y2NpbmN0IGFuZCBlYXNpZXIgdG8gcmVhZC4gVGhlIGJlc3QgcGxhY2UgdG8gbGVhcm4gYWJvdXQgdGhlIFtgbWFwKClgXShodHRwczovL3B1cnJyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL21hcC5odG1sKSBmdW5jdGlvbnMgaXMgdGhlIFtpdGVyYXRpb24gY2hhcHRlcl0oaHR0cDovL3I0ZHMuaGFkLmNvLm56L2l0ZXJhdGlvbi5odG1sKSBpbiBSIGZvciBkYXRhIHNjaWVuY2UuDQo+DQo+IGBicm9vbTo6YXVnbWVudC5rbWVhbnMoKWAgYWNjZXB0cyBhIG1vZGVsIG9iamVjdCBhbmQgcmV0dXJucyBhIHRpYmJsZSB3aXRoIGV4YWN0bHkgb25lIHJvdyBvZiBtb2RlbCBzdW1tYXJpZXMuIFRoZSBzdW1tYXJpZXMgYXJlIHR5cGljYWxseSBnb29kbmVzcyBvZiBmaXQgbWVhc3VyZXMsIHAtdmFsdWVzIGZvciBoeXBvdGhlc2lzIHRlc3RzIG9uIHJlc2lkdWFscywgb3IgbW9kZWwgY29udmVyZ2VuY2UgaW5mb3JtYXRpb24uDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMjA1NikNCiMgQ3JlYXRlIDEwIG1vZGVscyB3aXRoIDEgdG8gMTAgY2x1c3RlcnMNCmtjbHVzdHMgPC0gdGliYmxlKGsgPSAxOjEwKSAlPiUgDQogIG11dGF0ZSgNCiAgICBtb2RlbCA9IG1hcChrLCB+IGttZWFucyh4ID0gc2VlZHNfZmVhdHVyZXMsIGNlbnRlcnMgPSAueCwgbnN0YXJ0ID0gMjApKSwNCiAgICBnbGFuY2VkID0gbWFwKG1vZGVsLCBnbGFuY2UpKSAlPiUgDQogIHVubmVzdChjb2xzID0gYyhnbGFuY2VkKSkNCg0KIyBWaWV3IHJlc3VsdHMNCmtjbHVzdHMNCg0KIyBQbG90IFRvdGFsIHdpdGhpbi1jbHVzdGVyIHN1bSBvZiBzcXVhcmVzICh0b3Qud2l0aGluc3MpDQprY2x1c3RzICU+JSANCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGssIHkgPSB0b3Qud2l0aGluc3MpKSArDQogIGdlb21fbGluZShzaXplID0gMS4yLCBhbHBoYSA9IDAuNSwgY29sb3IgPSAiZG9kZ2VyYmx1ZTMiKSArDQogIGdlb21fcG9pbnQoc2l6ZSA9IDIsIGNvbG9yID0gImRvZGdlcmJsdWUzIikNCmBgYA0KDQpXZSBzZWVrIHRvICoqbWluaW1pemUqKiB0aGUgdGhlIHRvdGFsIHdpdGhpbi1jbHVzdGVyIHN1bSBvZiBzcXVhcmVzLCBieSBwZXJmb3JtaW5nIEstbWVhbnMgY2x1c3RlcmluZy4gVGhlIHBsb3Qgc2hvd3MgYSBsYXJnZSByZWR1Y3Rpb24gaW4gV0NTUyAoc28gZ3JlYXRlciAqdGlnaHRuZXNzKikgYXMgdGhlIG51bWJlciBvZiBjbHVzdGVycyBpbmNyZWFzZXMgZnJvbSBvbmUgdG8gdHdvLCBhbmQgYSBmdXJ0aGVyIG5vdGljYWJsZSByZWR1Y3Rpb24gZnJvbSB0d28gdG8gdGhyZWUgY2x1c3RlcnMuIEFmdGVyIHRoYXQsIHRoZSByZWR1Y3Rpb24gaXMgbGVzcyBwcm9ub3VuY2VkLCByZXN1bHRpbmcgaW4gYW4gYGVsYm93YCDwn5KqaW4gdGhlIGNoYXJ0IGF0IGFyb3VuZCB0aHJlZSBjbHVzdGVycy4gVGhpcyBpcyBhIGdvb2QgaW5kaWNhdGlvbiB0aGF0IHRoZXJlIGFyZSB0d28gdG8gdGhyZWUgcmVhc29uYWJseSB3ZWxsIHNlcGFyYXRlZCBjbHVzdGVycyBvZiBkYXRhIHBvaW50cy4NCg0KIyMgMi4gSy1NZWFucyBDbHVzdGVyaW5nDQoNClRoZSBhbGdvcml0aG0gd2UgdXNlZCB0byBhcHByb3hpbWF0ZSB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIGluIG91ciBkYXRhIHNldCBpcyBjYWxsZWQgKkstTWVhbnMqLiBMZXQncyBnZXQgdG8gdGhlIGZpbmVyIGRldGFpbHMsIHNoYWxsIHdlPw0KDQpLLU1lYW5zIGlzIGEgY29tbW9ubHkgdXNlZCBjbHVzdGVyaW5nIGFsZ29yaXRobSB0aGF0IHNlcGFyYXRlcyBhIGRhdGFzZXQgaW50byAqSyogY2x1c3RlcnMgb2YgZXF1YWwgdmFyaWFuY2Ugc3VjaCB0aGF0IG9ic2VydmF0aW9ucyB3aXRoaW4gdGhlIHNhbWUgY2x1c3RlciBhcmUgYXMgc2ltaWxhciBhcyBwb3NzaWJsZSAoaS5lLiwgaGlnaCBpbnRyYS1jbGFzcyBzaW1pbGFyaXR5KSwgd2hlcmVhcyBvYnNlcnZhdGlvbnMgZnJvbSBkaWZmZXJlbnQgY2x1c3RlcnMgYXJlIGFzIGRpc3NpbWlsYXIgYXMgcG9zc2libGUgKGkuZS4sIGxvdyBpbnRlci1jbGFzcyBzaW1pbGFyaXR5KS4gVGhlIG51bWJlciBvZiBjbHVzdGVycywgKksqLCBpcyB1c2VyIGRlZmluZWQuDQoNClRoZSBiYXNpYyBhbGdvcml0aG0gaGFzIHRoZSBmb2xsb3dpbmcgc3RlcHM6DQoNCjEuICBTcGVjaWZ5IHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgdG8gYmUgY3JlYXRlZCAodGhpcyBpcyBkb25lIGJ5IHRoZSBhbmFseXN0KS4gVGFraW5nIHRoZSBmbG93ZXJzIGV4YW1wbGUgd2UgdXNlZCBhdCB0aGUgYmVnaW5uaW5nIG9mIHRoZSBsZXNzb24sIHRoaXMgbWVhbnMgZGVjaWRpbmcgaG93IG1hbnkgY2x1c3RlcnMgeW91IHdhbnQgdG8gdXNlIHRvIGdyb3VwIHRoZSBmbG93ZXJzLg0KMi4gIE5leHQsIHRoZSBhbGdvcml0aG0gcmFuZG9tbHkgc2VsZWN0cyBLIG9ic2VydmF0aW9ucyBmcm9tIHRoZSBkYXRhIHNldCB0byBzZXJ2ZSBhcyB0aGUgaW5pdGlhbCBjZW50ZXJzIGZvciB0aGUgY2x1c3RlcnMgKGkuZS4sIGNlbnRyb2lkcykuDQozLiAgTmV4dCwgZWFjaCBvZiB0aGUgcmVtYWluaW5nIG9ic2VydmF0aW9ucyAoaW4gdGhpcyBjYXNlIGZsb3dlcnMpIGFyZSBhc3NpZ25lZCB0byBpdHMgY2xvc2VzdCBjZW50cm9pZC4NCjQuICBOZXh0LCB0aGUgbmV3IG1lYW5zIG9mIGVhY2ggY2x1c3RlciBpcyBjb21wdXRlZCBhbmQgdGhlIGNlbnRyb2lkIGlzIG1vdmVkIHRvIHRoZSBtZWFuLg0KNS4gIE5vdyB0aGF0IHRoZSBjZW50ZXJzIGhhdmUgYmVlbiByZWNhbGN1bGF0ZWQsIGV2ZXJ5IG9ic2VydmF0aW9uIGlzIGNoZWNrZWQgYWdhaW4gdG8gc2VlIGlmIGl0IG1pZ2h0IGJlIGNsb3NlciB0byBhIGRpZmZlcmVudCBjbHVzdGVyLiBBbGwgdGhlIG9iamVjdHMgYXJlIHJlYXNzaWduZWQgYWdhaW4gdXNpbmcgdGhlIHVwZGF0ZWQgY2x1c3RlciBtZWFucy4gVGhlIGNsdXN0ZXIgYXNzaWdubWVudCBhbmQgY2VudHJvaWQgdXBkYXRlIHN0ZXBzIGFyZSBpdGVyYXRpdmVseSByZXBlYXRlZCB1bnRpbCB0aGUgY2x1c3RlciBhc3NpZ25tZW50cyBzdG9wIGNoYW5naW5nIChpLmUuLCB3aGVuIGNvbnZlcmdlbmNlIGlzIGFjaGlldmVkKS4gVHlwaWNhbGx5LCB0aGUgYWxnb3JpdGhtIHRlcm1pbmF0ZXMgd2hlbiBlYWNoIG5ldyBpdGVyYXRpb24gcmVzdWx0cyBpbiBuZWdsaWdpYmxlIG1vdmVtZW50IG9mIGNlbnRyb2lkcyBhbmQgdGhlIGNsdXN0ZXJzIGJlY29tZSBzdGF0aWMuDQo2LiAgTm90ZSB0aGF0IGR1ZSB0byByYW5kb21pemF0aW9uIG9mIHRoZSBpbml0aWFsIGsgb2JzZXJ2YXRpb25zIHVzZWQgYXMgdGhlIHN0YXJ0aW5nIGNlbnRyb2lkcywgd2UgY2FuIGdldCBzbGlnaHRseSBkaWZmZXJlbnQgcmVzdWx0cyBlYWNoIHRpbWUgd2UgYXBwbHkgdGhlIHByb2NlZHVyZS4gRm9yIHRoaXMgcmVhc29uLCBtb3N0IGFsZ29yaXRobXMgdXNlIHNldmVyYWwgKnJhbmRvbSBzdGFydHMqIGFuZCBjaG9vc2UgdGhlIGl0ZXJhdGlvbiB3aXRoIHRoZSBsb3dlc3QgV0NTUy4gQXMgc3VjaCwgaXQgaXMgc3Ryb25nbHkgcmVjb21tZW5kZWQgdG8gYWx3YXlzIHJ1biBLLU1lYW5zIHdpdGggc2V2ZXJhbCB2YWx1ZXMgb2YgKm5zdGFydCogdG8gYXZvaWQgYW4gKnVuZGVzaXJhYmxlIGxvY2FsIG9wdGltdW0uKg0KDQpTbyB0cmFpbmluZyB1c3VhbGx5IGludm9sdmVzIG11bHRpcGxlIGl0ZXJhdGlvbnMsIHJlaW5pdGlhbGl6aW5nIHRoZSBjZW50cm9pZHMgZWFjaCB0aW1lLCBhbmQgdGhlIG1vZGVsIHdpdGggdGhlIGJlc3QgKGxvd2VzdCkgV0NTUyBpcyBzZWxlY3RlZC4gVGhlIGZvbGxvd2luZyBhbmltYXRpb24gc2hvd3MgdGhpcyBwcm9jZXNzOg0KDQohW10oaW1hZ2VzL2stbWVhbnMuZ2lmKXt3aWR0aD0iNjAwIn0NCg0KTm93LCBiYWNrIHRvIG91ciBzZWVkcyBleGFtcGxlLiBBZnRlciBjcmVhdGluZyBhIHNlcmllcyBvZiBjbHVzdGVyaW5nIG1vZGVscyB3aXRoIGRpZmZlcmVudCBudW1iZXJzIG9mIGNsdXN0ZXJzIGFuZCBwbG90dGluZyB0aGUgV0NTUyBhY3Jvc3MgdGhlIGNsdXN0ZXJzLCB3ZSBub3RpY2VkIGEgYmVuZCBhdCBhcm91bmQgYGsgPSAzYC4gVGhpcyBiZW5kIGluZGljYXRlcyB0aGF0IGFkZGl0aW9uYWwgY2x1c3RlcnMgYmV5b25kIHRoZSB0aGlyZCBoYXZlIGxpdHRsZSB2YWx1ZSBhbmQgdGhhdCB0aGVyZSBhcmUgdHdvIHRvIHRocmVlIHJlYXNvbmFibHkgd2VsbCBzZXBhcmF0ZWQgY2x1c3RlcnMgb2YgZGF0YSBwb2ludHMuDQoNClNvLCBsZXQncyBwZXJmb3JtICpLLU1lYW5zKiBjbHVzdGVyaW5nIHNwZWNpZnlpbmcgYGsgPSAzYCBjbHVzdGVycyBhbmQgYWRkIHRoZSBjbGFzc2lmaWNhdGlvbnMgdG8gdGhlIGRhdGEgc2V0IHVzaW5nIGBhdWdtZW50YC4NCg0KYGBge3IgZmluYWxpemUgbW9kZWx9DQpzZXQuc2VlZCgyMDU2KQ0KIyBGaXQgYW5kIHByZWRpY3QgY2x1c3RlcnMgd2l0aCBrID0gMw0KZmluYWxfa21lYW5zIDwtIGttZWFucyhzZWVkc19mZWF0dXJlcywgY2VudGVycyA9IDMsIG5zdGFydCA9IDEwMCwgaXRlci5tYXggPSAxMDAwKQ0KDQojIEFkZCBjbHVzdGVyIHByZWRpY3Rpb24gdG8gdGhlIGRhdGEgc2V0DQpyZXN1bHRzIDwtIGF1Z21lbnQoZmluYWxfa21lYW5zLCBzZWVkc19mZWF0dXJlcykgJT4lIA0KIyBCaW5kIHBjYV9kYXRhIC0gZmVhdHVyZXNfMmQNCiAgYmluZF9jb2xzKGZlYXR1cmVzXzJkKQ0KDQpyZXN1bHRzICU+JSANCiAgc2xpY2VfaGVhZChuID0gNSkNCg0KYGBgDQoNCkxldCdzIHNlZSB0aG9zZSBjbHVzdGVyIGFzc2lnbm1lbnRzIHdpdGggdGhlIHR3byBkaW1lbnNpb25hbCBkYXRhIHBvaW50cy4gV2UnbGwgYWRkIHNvbWUgdG91Y2ggb2YgaW50ZXJhY3Rpdml0eSB1c2luZyB0aGUgW3Bsb3RseSBwYWNrYWdlXShodHRwczovL3Bsb3RseS5jb20vci9nZXR0aW5nLXN0YXJ0ZWQvKSwgc28gZmVlbCBmcmVlIHRvIGhvdmVyLg0KDQpgYGB7ciBjbHVzdGVyX3Bsb3QxfQ0KIyBQbG90IGttX2NsdXN0ZXIgYXNzaWdubW5ldCBvbiB0aGUgUEMgZGF0YQ0KY2x1c3Rlcl9wbG90IDwtIHJlc3VsdHMgJT4lIA0KICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gUEMxLCB5ID0gUEMyKSkgKw0KICBnZW9tX3BvaW50KGFlcyhzaGFwZSA9IC5jbHVzdGVyKSwgc2l6ZSA9IDIpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImRhcmtvcmFuZ2UiLCJwdXJwbGUiLCJjeWFuNCIpKQ0KDQojIE1ha2UgcGxvdCBpbnRlcmFjdGl2ZQ0KZ2dwbG90bHkoY2x1c3Rlcl9wbG90KQ0KDQpgYGANCg0K8J+kqfCfpKkgSG9wZWZ1bGx5LCB0aGUgZGF0YSBoYXMgYmVlbiBzZXBhcmF0ZWQgaW50byB0aHJlZSBkaXN0aW5jdCBjbHVzdGVycy4NCg0KU28gd2hhdCdzIHRoZSBwcmFjdGljYWwgdXNlIG9mIGNsdXN0ZXJpbmc/IEluIHNvbWUgY2FzZXMsIHlvdSBtYXkgaGF2ZSBkYXRhIHRoYXQgeW91IG5lZWQgdG8gZ3JvdXAgaW50byBkaXN0aWN0IGNsdXN0ZXJzIHdpdGhvdXQga25vd2luZyBob3cgbWFueSBjbHVzdGVycyB0aGVyZSBhcmUgb3Igd2hhdCB0aGV5IGluZGljYXRlLiBGb3IgZXhhbXBsZSBhIG1hcmtldGluZyBvcmdhbml6YXRpb24gbWlnaHQgd2FudCB0byBzZXBhcmF0ZSBjdXN0b21lcnMgaW50byBkaXN0aW5jdCBzZWdtZW50cywgYW5kIHRoZW4gaW52ZXN0aWdhdGUgaG93IHRob3NlIHNlZ21lbnRzIGV4aGliaXQgZGlmZmVyZW50IHB1cmNoYXNpbmcgYmVoYXZpb3JzLg0KDQpTb21ldGltZXMsIGNsdXN0ZXJpbmcgaXMgdXNlZCBhcyBhbiBpbml0aWFsIHN0ZXAgdG93YXJkcyBjcmVhdGluZyBhIGNsYXNzaWZpY2F0aW9uIG1vZGVsLiBZb3Ugc3RhcnQgYnkgaWRlbnRpZnlpbmcgZGlzdGluY3QgZ3JvdXBzIG9mIGRhdGEgcG9pbnRzLCBhbmQgdGhlbiBhc3NpZ24gY2xhc3MgbGFiZWxzIHRvIHRob3NlIGNsdXN0ZXJzLiBZb3UgY2FuIHRoZW4gdXNlIHRoaXMgbGFiZWxsZWQgZGF0YSB0byB0cmFpbiBhIGNsYXNzaWZpY2F0aW9uIG1vZGVsLg0KDQpJbiB0aGUgY2FzZSBvZiB0aGUgc2VlZHMgZGF0YSwgdGhlIGRpZmZlcmVudCBzcGVjaWVzIG9mIHNlZWQgYXJlIGFscmVhZHkga25vd24gYW5kIGVuY29kZWQgYXMgMCAoKkthbWEqKSwgMSAoKlJvc2EqKSwgb3IgMiAoKkNhbmFkaWFuKiksIHNvIHdlIGNhbiB1c2UgdGhlc2UgaWRlbnRpZmllcnMgdG8gY29tcGFyZSB0aGUgc3BlY2llcyBjbGFzc2lmaWNhdGlvbnMgdG8gdGhlIGNsdXN0ZXJzIGlkZW50aWZpZWQgYnkgb3VyIHVuc3VwZXJ2aXNlZCBhbGdvcml0aG0NCg0KYGBge3IgY2x1c3Rlcl9zcH0NCiMgUGxvdCBrbV9jbHVzdGVyIGFzc2lnbm1uZXQgb24gdGhlIFBDIGRhdGENCmNsdXN0X3NwY19wbG90IDwtIHJlc3VsdHMgJT4lIA0KICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gUEMxLCB5ID0gUEMyKSkgKw0KICBnZW9tX3BvaW50KGFlcyhzaGFwZSA9IC5jbHVzdGVyLCBjb2xvciA9IHNwZWNpZXMpLCBzaXplID0gMiwgYWxwaGEgPSAwLjgpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImRhcmtvcmFuZ2UiLCJwdXJwbGUiLCJjeWFuNCIpKQ0KDQojIE1ha2UgcGxvdCBpbnRlcmFjdGl2ZQ0KZ2dwbG90bHkoY2x1c3Rfc3BjX3Bsb3QpDQpgYGANCg0KVGhlcmUgbWF5IGJlIHNvbWUgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgY2x1c3RlciBhc3NpZ25tZW50cyBhbmQgY2xhc3MgbGFiZWxzIGFzIHNob3duIGJ5IHRoZSBkaWZmZXJlbnQgY29sb3JzIChzcGVjaWVzKSB3aXRoaW4gZWFjaCBjbHVzdGVyIChzaGFwZSkuIEJ1dCB0aGUgSy1NZWFucyBtb2RlbCBzaG91bGQgaGF2ZSBkb25lIGEgcmVhc29uYWJsZSBqb2Igb2YgY2x1c3RlcmluZyB0aGUgb2JzZXJ2YXRpb25zIHNvIHRoYXQgc2VlZHMgb2YgdGhlIHNhbWUgc3BlY2llcyBhcmUgZ2VuZXJhbGx5IGluIHRoZSBzYW1lIGNsdXN0ZXIuIPCfkqoNCg0KIyMgMy4gSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcNCg0KVGhlIGZpcnN0IHN0ZXAgaW4gSy1NZWFucyBjbHVzdGVyaW5nIGlzIHRoZSBkYXRhIHNjaWVudGlzdCBzcGVjaWZ5aW5nIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgKksqIHRvIHBhcnRpdGlvbiB0aGUgb2JzZXJ2YXRpb25zIGludG8uIEhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGlzIGFuIGFsdGVybmF0aXZlIGFwcHJvYWNoIHdoaWNoIGRvZXMgbm90IHJlcXVpcmUgdGhlIG51bWJlciBvZiBjbHVzdGVycyB0byBiZSBkZWZpbmVkIGluIGFkdmFuY2UuIEZ1cnRoZXJtb3JlLCBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyByZXN1bHRzIGNhbiBiZSBlYXNpbHkgdmlzdWFsaXplZCB1c2luZyBhbiBhdHRyYWN0aXZlIHRyZWUtYmFzZWQgcmVwcmVzZW50YXRpb24gY2FsbGVkIGEgKmRlbmRyb2dyYW0qLiBPbmNlIHRoZSBkZW5kcm9ncmFtIGhhcyBiZWVuIGNvbnN0cnVjdGVkLCB3ZSBzbGljZSB0aGlzIHN0cnVjdHVyZSBob3Jpem9udGFsbHkgdG8gaWRlbnRpZnkgdGhlIGNsdXN0ZXJzIGZvcm1lZC4NCg0KSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgY3JlYXRlcyBjbHVzdGVycyBieSBlaXRoZXIgYSAqZGl2aXNpdmUqIG1ldGhvZCBvciAqYWdnbG9tZXJhdGl2ZSogbWV0aG9kLiBUaGUgYGRpdmlzaXZlYCBtZXRob2QgaXMgYSBgdG9wIGRvd25gIGFwcHJvYWNoIHN0YXJ0aW5nIHdpdGggdGhlIGVudGlyZSBkYXRhc2V0IGFuZCB0aGVuIGZpbmRpbmcgcGFydGl0aW9ucyBpbiBhIHN0ZXB3aXNlIG1hbm5lci4gYEFnZ2xvbWVyYXRpdmUgY2x1c3RlcmluZ2AgaXMgYSBgYm90dG9tIHVwYCBhcHByb2FjaC4gSW4gdGhpcyBsYWIgeW91IHdpbGwgd29yayB3aXRoIGFnZ2xvbWVyYXRpdmUgY2x1c3RlcmluZywgY29tbW9ubHkgcmVmZXJyZWQgdG8gYXMgYEFHTkVTYCAoQUdnbG9tZXJhdGl2ZSBORVN0aW5nKSwgd2hpY2ggcm91Z2hseSB3b3JrcyBhcyBmb2xsb3dzOg0KDQoxLiAgVGhlIGxpbmthZ2UgZGlzdGFuY2VzIGJldHdlZW4gZWFjaCBvZiB0aGUgZGF0YSBwb2ludHMgaXMgY29tcHV0ZWQuDQoNCjIuICBQb2ludHMgYXJlIGNsdXN0ZXJlZCBwYWlyd2lzZSB3aXRoIHRoZWlyIG5lYXJlc3QgbmVpZ2hib3IuDQoNCjMuICBMaW5rYWdlIGRpc3RhbmNlcyBiZXR3ZWVuIHRoZSBjbHVzdGVycyBhcmUgY29tcHV0ZWQuDQoNCjQuICBDbHVzdGVycyBhcmUgY29tYmluZWQgcGFpcndpc2UgaW50byBsYXJnZXIgY2x1c3RlcnMuDQoNCjUuICBTdGVwcyAzIGFuZCA0IGFyZSByZXBlYXRlZCB1bnRpbCBhbGwgZGF0YSBwb2ludHMgYXJlIGluIGEgc2luZ2xlIGNsdXN0ZXIuDQoNCkEgZnVuZGFtZW50YWwgcXVlc3Rpb24gaW4gaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgaXM6ICpIb3cgZG8gd2UgbWVhc3VyZSB0aGUgZGlzc2ltaWxhcml0eSBiZXR3ZWVuIHR3byBjbHVzdGVycyBvZiBvYnNlcnZhdGlvbnM/KiBUaGUgbGlua2FnZSBmdW5jdGlvbi9hZ2dyb21lcmF0aW9uIG1ldGhvZHMgY2FuIGJlIGNvbXB1dGVkIGluIGEgbnVtYmVyIG9mIHdheXM6DQoNCi0gICBXYXJkJ3MgbWluaW11bSB2YXJpYW5jZSBtZXRob2Q6IE1pbmltaXplcyB0aGUgdG90YWwgd2l0aGluLWNsdXN0ZXIgdmFyaWFuY2UuIEF0IGVhY2ggc3RlcCB0aGUgcGFpciBvZiBjbHVzdGVycyB3aXRoIHRoZSBzbWFsbGVzdCBiZXR3ZWVuLWNsdXN0ZXIgZGlzdGFuY2UgYXJlIG1lcmdlZC4gVGVuZHMgdG8gcHJvZHVjZSBtb3JlIGNvbXBhY3QgY2x1c3RlcnMuDQoNCi0gICBBdmVyYWdlIGxpbmthZ2UgdXNlcyB0aGUgbWVhbiBwYWlyd2lzZSBkaXN0YW5jZSBiZXR3ZWVuIHRoZSBtZW1iZXJzIG9mIHRoZSB0d28gY2x1c3RlcnMuIENhbiB2YXJ5IGluIHRoZSBjb21wYWN0bmVzcyBvZiB0aGUgY2x1c3RlcnMgaXQgY3JlYXRlcy4NCg0KLSAgIENvbXBsZXRlIG9yIE1heGltYWwgbGlua2FnZSB1c2VzIHRoZSBtYXhpbXVtIGRpc3RhbmNlIGJldHdlZW4gdGhlIG1lbWJlcnMgb2YgdGhlIHR3byBjbHVzdGVycy4gVGVuZHMgdG8gcHJvZHVjZSBtb3JlIGNvbXBhY3QgY2x1c3RlcnMuDQoNClNldmVyYWwgZGlmZmVyZW50IGRpc3RhbmNlIG1ldHJpY3MgYXJlIHVzZWQgdG8gY29tcHV0ZSBsaW5rYWdlIGZ1bmN0aW9uczoNCg0KLSAgIEV1Y2xpZGlhbiBvciBsMiBkaXN0YW5jZSBpcyB0aGUgbW9zdCB3aWRlbHkgdXNlZC4gVGhpcyBpcyB0aGUgb25seSBtZXRyaWMgZm9yIHRoZSBXYXJkIGxpbmthZ2UgbWV0aG9kLg0KDQotICAgTWFuaGF0dGFuIG9yIGwxIGRpc3RhbmNlIGlzIHJvYnVzdCB0byBvdXRsaWVycyBhbmQgaGFzIG90aGVyIGludGVyZXN0aW5nIHByb3BlcnRpZXMuDQoNCi0gICBDb3NpbmUgc2ltaWxhcml0eSwgaXMgdGhlIGRvdCBwcm9kdWN0IGJldHdlZW4gdGhlIGxvY2F0aW9uIHZlY3RvcnMgZGl2aWRlZCBieSB0aGUgbWFnbml0dWRlcyBvZiB0aGUgdmVjdG9ycy4gTm90aWNlIHRoYXQgdGhpcyBtZXRyaWMgaXMgYSBtZWFzdXJlIG9mIHNpbWlsYXJpdHksIHdoZXJlYXMgdGhlIG90aGVyIHR3byBtZXRyaWNzIGFyZSBtZWFzdXJlcyBvZiBkaWZmZXJlbmNlLiBTaW1pbGFyaXR5IGNhbiBiZSBxdWl0ZSB1c2VmdWwgd2hlbiB3b3JraW5nIHdpdGggZGF0YSBzdWNoIGFzIGltYWdlcyBvciB0ZXh0IGRvY3VtZW50cy4NCg0KPiBQbGVhc2Ugc2VlOg0KPg0KPiAtICAgWypIaWVyYXJjaGljYWwgY2x1c3RlcmluZypdKGh0dHBzOi8vYnJhZGxleWJvZWhta2UuZ2l0aHViLmlvL0hPTUwvaGllcmFyY2hpY2FsLmh0bWwpLCBCb2VobWtlICYgR3JlZW53ZWxsLCBIYW5kcy1PbiBNYWNoaW5lIExlYXJuaW5nIHdpdGggUg0KPg0KPiAtICAgKlVuc3VwZXJ2aXNlZCBMZWFybmluZyosIFtBbiBJbnRyb2R1Y3Rpb24gdG8gU3RhdGlzdGljYWwgTGVhcm5pbmcgd2l0aCBBcHBsaWNhdGlvbnMgaW4gUl0oaHR0cHM6Ly93d3cuc3RhdGxlYXJuaW5nLmNvbS8pDQo+DQo+IC0gICBbVW5zdXBlcnZpc2VkIExlYXJuaW5nXShodHRwczovL2VtaWxodml0ZmVsZHQuZ2l0aHViLmlvL0lTTFItdGlkeW1vZGVscy1sYWJzL2luZGV4Lmh0bWwpLCBBbiBJbnRyb2R1Y3Rpb24gdG8gU3RhdGlzdGljYWwgTGVhcm5pbmcgd2l0aCBBcHBsaWNhdGlvbnMgaW4gUiwgVGlkeW1vZGVscyBzcGluLW9mZi4NCj4NCj4gZm9yIGZ1cnRoZXIgcmVhZGluZy4NCg0KVGhlcmVmb3JlLCBpbiBIaWVyYXJjaGljYWwgY2x1c3RlcmluZywgdGhlIGNsdXN0ZXJzIHRoZW1zZWx2ZXMgYmVsb25nIHRvIGEgbGFyZ2VyIGdyb3VwLCB3aGljaCBiZWxvbmcgdG8gZXZlbiBsYXJnZXIgZ3JvdXBzLCBhbmQgc28gb24uIFRoaXMgaXMgdXNlZnVsIGZvciBub3Qgb25seSBicmVha2luZyBkYXRhIGludG8gZ3JvdXBzLCBidXQgdW5kZXJzdGFuZGluZyB0aGUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHRoZXNlIGdyb3Vwcy4NCg0KRm9yIGV4YW1wbGUsIGlmIHdlIGFwcGx5IGNsdXN0ZXJpbmcgdG8gdGhlIG1lYW5pbmdzIG9mIHdvcmRzLCB3ZSBtYXkgZ2V0IGEgZ3JvdXAgY29udGFpbmluZyBhZGplY3RpdmVzIHNwZWNpZmljIHRvIGVtb3Rpb25zICgnYW5ncnknLCAnaGFwcHknLCBhbmQgc28gb24pLCB3aGljaCBpdHNlbGYgYmVsb25ncyB0byBhIGdyb3VwIGNvbnRhaW5pbmcgYWxsIGh1bWFuLXJlbGF0ZWQgYWRqZWN0aXZlcyAoJ2hhcHB5JywgJ2hhbmRzb21lJywgJ3lvdW5nJyksIGFuZCB0aGlzIGJlbG9uZ3MgdG8gYW4gZXZlbiBoaWdoZXIgZ3JvdXAgY29udGFpbmluZyBhbGwgYWRqZWN0aXZlcyAoJ2hhcHB5JywgJ2dyZWVuJywgJ2hhbmRzb21lJywgJ2hhcmQnIGV0Yy4pLg0KDQohW10oaW1hZ2VzLzQtaGllcmFyY2hpY2FsLWNsdXN0ZXJpbmcucG5nKXt3aWR0aD0iNTUwIn0NCg0KIyMjIEFnZ2xvbWVyYXRpdmUgQ2x1c3RlcmluZw0KDQpMZXQncyBzZWUgYW4gZXhhbXBsZSBvZiBjbHVzdGVyaW5nIHRoZSBzZWVkcyBkYXRhIHVzaW5nIGFuIGFnZ2xvbWVyYXRpdmUgY2x1c3RlcmluZyBhbGdvcml0aG0uIFRoZXJlIGFyZSBtYW55IGZ1bmN0aW9ucyBhdmFpbGFibGUgaW4gUiBmb3IgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcuDQoNClRoZSBbYGhjbHVzdCgpYF0oaHR0cHM6Ly9yZHJyLmlvL3Ivc3RhdHMvaGNsdXN0Lmh0bWwpIGZ1bmN0aW9uIGlzIG9uZSB3YXkgdG8gcGVyZm9ybSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyBpbiBSLiBJdCBvbmx5IG5lZWRzIG9uZSBpbnB1dCBhbmQgdGhhdCBpcyBhIGRpc3RhbmNlIG1hdHJpeCBzdHJ1Y3R1cmUgY29tcHV0ZWQgdXNpbmcgZGlzdGFuY2UgbWV0cmljcyAoZS5nIGV1Y2xpZGVhbikgYXMgcHJvZHVjZWQgYnkgW2BkaXN0KClgXShodHRwczovL3JkcnIuaW8vcGtnL2ZhY3RvZXh0cmEvbWFuL2Rpc3QuaHRtbCkuIGBoY2x1c3QoKWAgYWxzbyBhbGxvd3MgdXMgdG8gc3BlY2lmeSB0aGUgYWdnbG9tZXJhdGlvbiBtZXRob2QgdG8gYmUgdXNlZCAoaS5lLiBgImNvbXBsZXRlImAsIGAiYXZlcmFnZSJgLCBgInNpbmdsZSJgLCBvciBgIndhcmQuRCJgKS4NCg0KR3JlYXQhIExldCdzIGZpdCBtdWx0aXBsZSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyBtb2RlbHMgYmFzZWQgb24gZGlmZmVyZW50IGFnZ3JvbWVyYXRpb24gbWV0aG9kcyBhbmQgc2VlIGhvdyB0aGUgY2hvaWNlIGluIGFnZ3JvbWVyYXRpb24gbWV0aG9kIGNoYW5nZXMgdGhlIGNsdXN0ZXJpbmcuDQoNCmBgYHtyIGhjbHVzdH0NCiMgRm9yIHJlcHJvZHVjaWJpbGl0eQ0Kc2V0LnNlZWQoMjA1NikNCg0KIyBEaXN0YW5jZSBiZXR3ZWVuIG9ic2VydmF0aW9ucyBtYXRyaXgNCmQgPC0gZGlzdCh4ID0gc2VlZHNfZmVhdHVyZXMsIG1ldGhvZCA9ICJldWNsaWRlYW4iKQ0KDQojIEhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIHVzaW5nIENvbXBsZXRlIExpbmthZ2UNCnNlZWRzX2hjbHVzdF9jb21wbGV0ZSA8LSBoY2x1c3QoZCwgbWV0aG9kID0gImNvbXBsZXRlIikNCg0KIyBIaWVyYXJjaGljYWwgY2x1c3RlcmluZyB1c2luZyBBdmVyYWdlIExpbmthZ2UNCnNlZWRzX2hjbHVzdF9hdmVyYWdlIDwtIGhjbHVzdChkLCBtZXRob2QgPSAiYXZlcmFnZSIpDQoNCiMgSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgdXNpbmcgV2FyZCBMaW5rYWdlDQpzZWVkc19oY2x1c3Rfd2FyZCA8LSBoY2x1c3QoZCwgbWV0aG9kID0gIndhcmQuRDIiKQ0KYGBgDQoNClRoZSBbZmFjdG9leHRyYV0oaHR0cHM6Ly9ycGtncy5kYXRhbm92aWEuY29tL2ZhY3RvZXh0cmEvaW5kZXguaHRtbCkgcHJvdmlkZXMgZnVuY3Rpb25zIChbYGZ2aXpfZGVuZCgpYF0oaHR0cHM6Ly9yZHJyLmlvL3BrZy9mYWN0b2V4dHJhL21hbi9mdml6X2RlbmQuaHRtbCkpIHRvIHZpc3VhbGl6ZSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZy4gTGV0J3MgdmlzdWFsaXplIHRoZSBkZW5kcm9ncmFtIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBjbHVzdGVycyBzdGFydGluZyB3aXRoIENvbXBsZXRlIGFnZ3JvbWVyYXRpb24gbWV0aG9kLg0KDQpgYGB7ciBjb21wbGV0ZV92aXp9DQpsaWJyYXJ5KGZhY3RvZXh0cmEpDQoNCiMgVmlzdWFsaXplIGNsdXN0ZXIgc2VwYXJhdGlvbnMNCmZ2aXpfZGVuZChzZWVkc19oY2x1c3RfY29tcGxldGUsIG1haW4gPSAiQ29tcGxldGUgTGlua2FnZSIpDQoNCmBgYA0KDQpXaGF0IGFib3V0IEF2ZXJhZ2UgbGlua2FnZT8NCg0KYGBge3IgYXZlcmFnZV92aXp9DQojIFZpc3VhbGl6ZSBjbHVzdGVyIHNlcGFyYXRpb25zDQpmdml6X2RlbmQoc2VlZHNfaGNsdXN0X2F2ZXJhZ2UsIG1haW4gPSAiQXZlcmFnZSBMaW5rYWdlIikNCmBgYA0KDQpMYXN0bHksIHRoZSB3YXJkIGxpbmthZ2UuDQoNCmBgYHtyIHdhcmRfdml6fQ0KIyBWaXN1YWxpemUgY2x1c3RlciBzZXBhcmF0aW9ucw0KZnZpel9kZW5kKHNlZWRzX2hjbHVzdF93YXJkLCBtYWluID0gIldhcmQgTGlua2FnZSIpDQoNCmBgYA0KDQo+ICoqTm90ZToqKiBJZiB5b3UgYXJlIG5ldyB0byBkZW5kb2dyYW1zIHBsZWFzZSBzZWUgdGhlIGZvbGxvd2luZyByZXNvdXJjZXMgb24gaG93IHRvIGludGVycHJldCBkZW5kcm9ncmFtczoNCj4NCj4gLSAgIFsqV2hhdCBpcyBhIERlbmRyb2dyYW0/Kl0oaHR0cHM6Ly93d3cuZGlzcGxheXIuY29tL3doYXQtaXMtZGVuZHJvZ3JhbS8pLCBEaXNwbGF5IFIgQmxvZw0KPg0KPiAtICAgW0hpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGV4cGxhaW5lZF0oaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL2hpZXJhcmNoaWNhbC1jbHVzdGVyaW5nLWV4cGxhaW5lZC1lNTliMTM4NDZkYTgpDQoNClBlcmZlY3QhIFRha2UgYSBtb21lbnQgYW5kIGFuYWx5emUgdGhlIG5hdHVyZSBvZiB0aGUgY2x1c3RlcnMuDQoNClRoaXMgY2FuIGJlIGRvbmUgbWF0aGVtYXRpY2FsbHkgYnkgZXZhbHVhdGluZyB0aGUgKmFnZ3JvbWVyYXRpdmUgY29lZmZpY2llbnQgKEFDKSosIHdoaWNoIG1lYXN1cmVzIHRoZSBjbHVzdGVyaW5nIHN0cnVjdHVyZSBvZiB0aGUgZGF0YXNldC0gd2l0aCB2YWx1ZXMgY2xvc2VyIHRvIDEgc3VnZ2VzdCBhIG1vcmUgYmFsYW5jZWQgY2x1c3RlcmluZyBzdHJ1Y3R1cmUgYW5kIHZhbHVlcyBjbG9zZXIgdG8gMCBzdWdnZXN0IGxlc3Mgd2VsbC1mb3JtZWQgY2x1c3RlcnMuIGBjbHVzdGVyOjphZ25lcygpYCBhbGxvd3MgdXMgdG8gY29tcHV0ZSB0aGUgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgYXMgd2VsbCBhcyB0aGlzIG1ldHJpYyB0b28uDQoNCmBgYHtyIEFDfQ0KbGlicmFyeShjbHVzdGVyKQ0KI0NvbXB1dGUgYWMgdmFsdWVzDQphY19tZXRyaWMgPC0gbGlzdCgNCiAgY29tcGxldGVfYWMgPSBhZ25lcyhzZWVkc19mZWF0dXJlcywgbWV0cmljID0gImV1Y2xpZGVhbiIsIG1ldGhvZCA9ICJjb21wbGV0ZSIpJGFjLA0KICBhdmVyYWdlX2FjID0gYWduZXMoc2VlZHNfZmVhdHVyZXMsIG1ldHJpYyA9ICJldWNsaWRlYW4iLCBtZXRob2QgPSAiYXZlcmFnZSIpJGFjLA0KICB3YXJkX2FjID0gYWduZXMoc2VlZHNfZmVhdHVyZXMsIG1ldHJpYyA9ICJldWNsaWRlYW4iLCBtZXRob2QgPSAid2FyZCIpJGFjDQopDQoNCmFjX21ldHJpYw0KDQpgYGANCg0KQXMgd2UgZXhwbGFpbmVkIGVhcmxpZXIsIGNvbXBsZXRlIGFuZCB3YXJkIGxpbmthZ2VzIHRlbmQgdG8gcHJvZHVjZSB0aWdodCBjbHVzdGVyaW5nIG9mIG9iamVjdHMuDQoNCk5vdywgbGV0J3MgZGV0ZXJtaW5lIHRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycy4gQWx0aG91Z2ggaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgZG9lcyBub3QgcmVxdWlyZSBvbmUgdG8gcHJlLXNwZWNpZnkgdGhlIG51bWJlciBvZiBjbHVzdGVycywgb25lIHN0aWxsIG5lZWRzIHRvIHNwZWNpZnkgdGhlIG51bWJlciBvZiBjbHVzdGVycyB0byBleHRyYWN0LiBMZXQncyB1c2UgdGhlICpXQ1NTKiBtZXRob2QgdG8gZGV0ZXJtaW5lIHRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycy4NCg0KYGBge3IgaGN1dH0NCiMgRGV0ZXJtaW5lIGFuZCB2aXN1emFsaXplIG9wdGltYWwgbi5vIG9mIGNsdXN0ZXJzDQojICBoY3V0IChmb3IgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcpDQpmdml6X25iY2x1c3Qoc2VlZHNfZmVhdHVyZXMsIEZVTmNsdXN0ZXIgPSBoY3V0LCBtZXRob2QgPSAid3NzIikNCg0KYGBgDQoNCkp1c3QgbGlrZSBpbiBLLU1lYW5zIGNsdXN0ZXJpbmcsIHRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycyBmb3IgdGhpcyBkYXRhIHNldCBpcyAzLg0KDQpMZXQncyBjb2xvciBvdXIgZGVuZHJvZ3JhbSBhY2NvcmRpbmcgdG8gayA9IDMgYW5kIG9ic2VydmUgaG93IG9ic2VydmF0aW9ucyB3aWxsIGJlIGdyb3VwZWQuIFdlJ2xsIGdvIHdpdGggdGhlICp3YXJkKiBsaW5rYWdlIG1ldGhvZC4NCg0KYGBge3J9DQojIFZpc3VhbGl6ZSBjbHVzdGVyaW5nIHN0cnVjdHVyZSBmb3IgMyBncm91cHMNCmZ2aXpfZGVuZChzZWVkc19oY2x1c3Rfd2FyZCwgayA9IDMsIG1haW4gPSAiV2FyZCBMaW5rYWdlIikNCmBgYA0KDQpQbGF1c2libGUgZW5vdWdoIPCfpKkhDQoNCldlIGNhbiBub3cgZ28gYWhlYWQgYW5kIGBjdXRgIHRoZSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyBtb2RlbCBpbnRvIHRocmVlIGNsdXN0ZXJzIGFuZCBleHRyYWN0IHRoZSBjbHVzdGVyIGxhYmVscyBmb3IgZWFjaCBvYnNlcnZhdGlvbiBhc3NvY2lhdGVkIHdpdGggYSBnaXZlbiBjdXQuIFRoaXMgaXMgZG9uZSB1c2luZyBgY3V0cmVlKClgDQoNCmBgYHtyIGN1dHJlZX0NCiMgSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgdXNpbmcgV2FyZCBMaW5rYWdlDQpzZWVkc19oY2x1c3Rfd2FyZCA8LSBoY2x1c3QoZCwgbWV0aG9kID0gIndhcmQuRDIiKQ0KDQojIEdyb3VwIGRhdGEgaW50byAzIGNsdXN0ZXJzDQpyZXN1bHRzX2hjbHVzdCA8LSB0aWJibGUoDQogIGNsdXN0ZXJfaWQgPSBjdXRyZWUoc2VlZHNfaGNsdXN0X3dhcmQsIGsgPSAzKSkgJT4lIA0KICBtdXRhdGUoY2x1c3Rlcl9pZCA9IGZhY3RvcihjbHVzdGVyX2lkKSkgJT4lIA0KICBiaW5kX2NvbHMoZmVhdHVyZXNfMmQpDQoNCnJlc3VsdHNfaGNsdXN0ICU+JSANCiAgc2xpY2VfaGVhZChuID0gNSkNCg0KYGBgDQoNCldlIGNvdWxkIHByb2JhYmx5IGRvIGEgbGl0dGxlIGNvbXBhcmlzb24gYmV0d2VlbiAqSy1NZWFucyogYW5kICpIaWVyYXJjaGljYWwgY2x1c3RlcmluZyogYnkgY291bnRpbmcgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgb2YgZWFjaCBzcGVjaWVzIGluIHRoZSBjb3JyZXNwb25kaW5nIGNsdXN0ZXJzLg0KDQpgYGB7cn0NCiMgQ29tcGFyZSBrLW0gYW5kIGhjDQpyZXN1bHRzX2hjbHVzdCAlPiUgDQogIGNvdW50KHNwZWNpZXMsIGNsdXN0ZXJfaWQpICU+JSANCiAgcmVuYW1lKG5faGNsdXN0ID0gbikgJT4lIA0KICBiaW5kX2NvbHMocmVzdWx0cyAlPiUgDQogICAgICAgICAgICAgIGNvdW50KHNwZWNpZXMsIC5jbHVzdGVyKSAlPiUNCiAgICAgICAgICAgICAgc2VsZWN0KCFzcGVjaWVzKSAlPiUgDQogICAgICAgICAgICAgIHJlbmFtZShuX2ttY2x1c3QgPSBuKSkNCiAgICAgICAgICAgIA0KYGBgDQoNCklnbm9yaW5nIHRoZSBjbHVzdGVyX2lkIGFuZCAuY2x1c3RlciBjb2x1bW4gc2luY2UgdGhleSBhcmUgYXJiaXRyYXJ5LCB3ZSBjYW4gc2VlIHRoYXQgdGhlIG9ic2VydmF0aW9ucyB3ZXJlIGdyb3VwZWQgcXVpdGUgc2ltaWxhcmx5IGJ5IHRoZSB0d28gYWxnb3JpdGhtcy4gV2UgY291bGQgb2YgY291cnNlIG1ha2UgYSBjb25mdXNpb24gbWF0cml4IHRvIGJldHRlciB2aXN1YWxpemUgdGhpcywgYnV0IHdlJ2xsIGxlYXZlIGl0IGF0IHRoYXQgZm9yIG5vdy4NCg0KTGV0J3Mgd3JhcCBpdCB1cCBieSBtYWtpbmcgc29tZSBwbG90cyBzaG93aW5nIGhvdyBvdXIgb2JzZXJ2YXRpb25zIHdlcmUgZ3JvdXBlZCBpbnRvIGNsdXN0ZXJzLvCfpbMNCg0KYGBge3J9DQojIFBsb3QgaC1jbHVzdGVyIGFzc2lnbm1uZXQgb24gdGhlIFBDIGRhdGENCmhjbHVzdF9zcGNfcGxvdCA8LSByZXN1bHRzX2hjbHVzdCAlPiUgDQogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBQQzEsIHkgPSBQQzIpKSArDQogIGdlb21fcG9pbnQoYWVzKHNoYXBlID0gY2x1c3Rlcl9pZCwgY29sb3IgPSBzcGVjaWVzKSwgc2l6ZSA9IDIsIGFscGhhID0gMC44KSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJkYXJrb3JhbmdlIiwicHVycGxlIiwiY3lhbjQiKSkNCg0KIyBNYWtlIHBsb3QgaW50ZXJhY3RpdmUNCmdncGxvdGx5KGhjbHVzdF9zcGNfcGxvdCkNCmBgYA0KDQojIyAqKjQuIFN1bW1hcnkqKg0KDQpJbiB0aGlzIG1vZHVsZSwgeW91IGxlYXJuZWQgaG93IGNsdXN0ZXJpbmcgY2FuIGJlIHVzZWQgdG8gY3JlYXRlIHVuc3VwZXJ2aXNlZCBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscyB0aGF0IGdyb3VwIGRhdGEgb2JzZXJ2YXRpb25zIGludG8gY2x1c3RlcnMuIFlvdSB0aGVuIHVzZWQgdGhlIGBUaWR5bW9kZWxzYCBmcmFtZXdvcmsgaW4gUiB0byBwZXJmb3JtIGRpbWVuc2lvbiByZWR1Y3Rpb24gdXNpbmcgUENBIGFuZCB2YXJpb3VzIHBhY2thZ2VzIGluIHRoZSBSIGVjb3N5c3RlbSBzdWNoIGFzIGBzdGF0czo6a21lYW5zKClgLCBgc3RhdHM6OmhjbHVzdCgpYCwgYGNsdXN0ZXI6OmFnbmVzKClgIHRvIHRyYWluICpLLU1lYW5zKiBhbmQgKkhpZXJhcmNoaWNhbCogY2x1c3RlcmluZyBtb2RlbHMuDQoNCldoaWxlIGBUaWR5bW9kZWxzYCAoUikgYW5kIGBzY2lraXQtbGVhcm5gIChQeXRob24pIGFyZSBwb3B1bGFyIGZyYW1ld29yayBmb3Igd3JpdGluZyBjb2RlIHRvIHRyYWluIGNsdXN0ZXJpbmcgbW9kZWxzLCB5b3UgY2FuIGFsc28gY3JlYXRlIG1hY2hpbmUgbGVhcm5pbmcgc29sdXRpb25zIGZvciBjbHVzdGVyaW5nIHVzaW5nIHRoZSBncmFwaGljYWwgdG9vbHMgaW4gTWljcm9zb2Z0IEF6dXJlIE1hY2hpbmUgTGVhcm5pbmcuIFlvdSBjYW4gbGVhcm4gbW9yZSBhYm91dCBuby1jb2RlIGRldmVsb3BtZW50IG9mIGNsdXN0ZXJpbmcgbW9kZWxzIHVzaW5nIEF6dXJlIE1hY2hpbmUgTGVhcm5pbmcgaW4gdGhlIFtDcmVhdGUgYSBjbHVzdGVyaW5nIG1vZGVsIHdpdGggQXp1cmUgTWFjaGluZSBMZWFybmluZyBkZXNpZ25lcl0oaHR0cHM6Ly9kb2NzLm1pY3Jvc29mdC5jb20vZW4tdXMvbGVhcm4vbW9kdWxlcy9jcmVhdGUtY2x1c3RlcmluZy1tb2RlbC1henVyZS1tYWNoaW5lLWxlYXJuaW5nLWRlc2lnbmVyLykgbW9kdWxlLg0KDQojIyMjICoqQ2hhbGxlbmdlOiBDbHVzdGVyIFVubGFiZWxsZWQgRGF0YSoqDQoNCk5vdyB0aGF0IHlvdSd2ZSBzZWVuIGhvdyB0byBjcmVhdGUgYSBjbHVzdGVyaW5nIG1vZGVsLCB3aHkgbm90IHRyeSBmb3IgeW91cnNlbGY/IFlvdSdsbCBmaW5kIGEgY2x1c3RlcmluZyBjaGFsbGVuZ2UgaW4gdGhlIFswNCAtIENsdXN0ZXJpbmcgQ2hhbGxlbmdlLmlweW5iXShodHRwczovL2dpdGh1Yi5jb20vTWljcm9zb2Z0RG9jcy9tbC1iYXNpY3MvYmxvYi9tYXN0ZXIvY2hhbGxlbmdlcy8wNCUyMC0lMjBDbHVzdGVyaW5nJTIwQ2hhbGxlbmdlLmlweW5iKSBub3RlYm9vayENCg0KIyMjIyBUSEFOSyBZT1UgVE86DQoNCmBBbGxpc29uIEhvcnN0YCBmb3IgY3JlYXRpbmcgdGhlIGFtYXppbmcgaWxsdXN0cmF0aW9ucyB0aGF0IG1ha2UgUiBtb3JlIHdlbGNvbWluZyBhbmQgZW5nYWdpbmcuIEZpbmQgbW9yZSBpbGx1c3RyYXRpb25zIGF0IGhlciBbZ2FsbGVyeV0oaHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS91cmw/cT1odHRwczovL2dpdGh1Yi5jb20vYWxsaXNvbmhvcnN0L3N0YXRzLWlsbHVzdHJhdGlvbnMmc2E9RCZzb3VyY2U9ZWRpdG9ycyZ1c3Q9MTYyNjM4MDc3MjUzMDAwMCZ1c2c9QU92VmF3M3pjZnlDaXpGUVpwa1NMenhpaVFFTSkuDQoNCmBCZXRoYW55YCwgKkdvbGQgTWljcm9zb2Z0IExlYXJuIFN0dWRlbnQgQW1iYXNzYWRvciosIGZvciBoZXIgdmFsdWFibGUgZmVlZGJhY2sgYW5kIHN1Z2dlc3Rpb25zLg0KDQojIyMjIEZVUlRIRVIgUkVBRElORw0KDQotICAgQnJhZGxleSBCb2VobWtlICYgQnJhbmRvbiBHcmVlbndlbGwsIFsqSGFuZHMtT24gTWFjaGluZSBMZWFybmluZyB3aXRoIFIqXShodHRwczovL2JyYWRsZXlib2VobWtlLmdpdGh1Yi5pby9IT01MLykqLioNCg0KLSAgIEdhcmV0aCBKYW1lcywgRGFuaWVsYSBXaXR0ZW4sIFRyZXZvciBIYXN0aWUsIFJvYmVydCBUaWJzaGlyYW5pLiBbKkFuIGludHJvZHVjdGlvbiB0byBzdGF0aXN0aWNhbCBsZWFybmluZyA6IHdpdGggYXBwbGljYXRpb25zIGluIFIqXShodHRwczovL3d3dy5zdGF0bGVhcm5pbmcuY29tLykqLiogKipDb3JyZXNwb25kaW5nIFRpZHltb2RlbHMgbGFicyBieSBFbWlsIEh2aXRmZWxkdCBjYW4gYmUgZm91bmQqKiBbKmhlcmUqXShodHRwczovL2VtaWxodml0ZmVsZHQuZ2l0aHViLmlvL0lTTFItdGlkeW1vZGVscy1sYWJzL2luZGV4Lmh0bWwpKi4qDQoNCi0gICBKb3JnZSBDaW1lbnRhZGEsIFsqTWFjaGluZSBMZWFybmluZyBmb3IgU29jaWFsIFNjaWVudGlzdHMqXShodHRwczovL2NpbWVudGFkYWouZ2l0aHViLmlvL21sX3NvY3NjaS8pKi4qDQoNCi0gICBBbGxpc29uIEhvcnN0J3MgWypQQ0Egd2l0aCBwZW5ndWlucyBhbmQgcmVjaXBlcypdKGh0dHBzOi8vYWxsaXNvbmhvcnN0LmdpdGh1Yi5pby9wYWxtZXJwZW5ndWlucy9hcnRpY2xlcy9hcnRpY2xlcy9wY2EuaHRtbCkqLioNCg0KLSAgIE1heCBLdWhuIGFuZCBKdWxpYSBTaWxnZSwgWypUaWR5IE1vZGVsaW5nIHdpdGggUipdKGh0dHBzOi8vd3d3LnRtd3Iub3JnLykqLioNCg0KLSAgIFRpZHltb2RlbHMgW3JlZmVyZW5jZSB3ZWJzaXRlXShodHRwczovL3d3dy50aWR5bW9kZWxzLm9yZy9zdGFydC8pLg0KDQotICAgSC4gV2lja2hhbSBhbmQgRy4gR3JvbGVtdW5kLCBbKlIgZm9yIERhdGEgU2NpZW5jZTogVmlzdWFsaXplLCBNb2RlbCwgVHJhbnNmb3JtLCBUaWR5LCBhbmQgSW1wb3J0IERhdGEqXShodHRwczovL3I0ZHMuaGFkLmNvLm56LykuDQoNCkhhcHB5IGxlYVJuaW5nLA0KDQpbRXJpYyAoUl9pYyldKGh0dHBzOi8vdHdpdHRlci5jb20vZXJpY250YXkpLCAqR29sZCBNaWNyb3NvZnQgTGVhcm4gU3R1ZGVudCBBbWJhc3NhZG9yKi4NCg==