+
+
+
+
+
+
+
+
+
Explore K-Means clustering using R and Tidy data principles.
+
+
+
In this lesson, you will learn how to create clusters using the
+Tidymodels package and other packages in the R ecosystem (we’ll call
+them friends 🧑🤝🧑), and the Nigerian music dataset you imported earlier.
+We will cover the basics of K-Means for Clustering. Keep in mind that,
+as you learned in the earlier lesson, there are many ways to work with
+clusters and the method you use depends on your data. We will try
+K-Means as it’s the most common clustering technique. Let’s get
+started!
+
Terms you will learn about:
+
+Silhouette scoring
+Elbow method
+Inertia
+Variance
+
+
+
+
Introduction
+
K-Means
+Clustering is a method derived from the domain of signal processing.
+It is used to divide and partition groups of data into
+k clusters based on similarities in their features.
+
The clusters can be visualized as Voronoi diagrams,
+which include a point (or ‘seed’) and its corresponding region.
+
+

+
Infographic by Jen Looper
+
+
K-Means clustering has the following steps:
+
+The data scientist starts by specifying the desired number of
+clusters to be created.
+Next, the algorithm randomly selects K observations from the data
+set to serve as the initial centers for the clusters (i.e.,
+centroids).
+Next, each of the remaining observations is assigned to its
+closest centroid.
+Next, the new means of each cluster is computed and the centroid
+is moved to the mean.
+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.
+
+
+
+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.
+
+
+
This short animation using the artwork
+of Allison Horst explains the clustering process:
+
+

+
Artwork by @allison_horst
+
+
A fundamental question that arises in clustering is this: how do you
+know how many clusters to separate your data into? One drawback of using
+K-Means includes the fact that you will need to establish
+k, that is the number of centroids.
+Fortunately the elbow method helps to estimate a good
+starting value for k. You’ll try it in a minute.
+
+
+
+
Prerequisite
+
We’ll pick off right from where we stopped in the previous
+lesson, where we analysed the data set, made lots of visualizations
+and filtered the data set to observations of interest. Be sure to check
+it out!
+
We’ll require some packages to knock-off this module. You can have
+them installed as:
+install.packages(c('tidyverse', 'tidymodels', 'cluster', 'summarytools', 'plotly', 'paletteer', 'factoextra', 'patchwork'))
+
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",repos = "http://cran.us.r-project.org"))
+
## Loading required package: pacman
+
pacman::p_load('tidyverse', 'tidymodels', 'cluster', 'summarytools', 'plotly', 'paletteer', 'factoextra', 'patchwork')
+
##
+## The downloaded binary packages are in
+## /var/folders/c9/r3f6t3kj3wv9jrh50g63hp1r0000gn/T//RtmpHKd9vp/downloaded_packages
+
##
+## summarytools installed
+
## Warning in pacman::p_load("tidyverse", "tidymodels", "cluster", "summarytools", : Failed to install/load:
+## summarytools
+
Let’s hit the ground running!
+
+
+
+
1. A dance with data: Narrow down to the 3 most popular music
+genres
+
This is a recap of what we did in the previous lesson. Let’s slice
+and dice some data!
+
# Load the core tidyverse and make it available in your current R session
+library(tidyverse)
+
+# Import the data into a tibble
+df <- read_csv(file = "https://raw.githubusercontent.com/microsoft/ML-For-Beginners/main/5-Clustering/data/nigerian-songs.csv", show_col_types = FALSE)
+
+# Narrow down to top 3 popular genres
+nigerian_songs <- df %>%
+ # Concentrate on top 3 genres
+ filter(artist_top_genre %in% c("afro dancehall", "afropop","nigerian pop")) %>%
+ # Remove unclassified observations
+ filter(popularity != 0)
+
+
+
+# Visualize popular genres using bar plots
+theme_set(theme_light())
+nigerian_songs %>%
+ count(artist_top_genre) %>%
+ ggplot(mapping = aes(x = artist_top_genre, y = n,
+ fill = artist_top_genre)) +
+ geom_col(alpha = 0.8) +
+ paletteer::scale_fill_paletteer_d("ggsci::category10_d3") +
+ ggtitle("Top genres") +
+ theme(plot.title = element_text(hjust = 0.5))
+

+
🤩 That went well!
+
+
+
2. More data exploration.
+
How clean is this data? Let’s check for outliers using box plots. We
+will concentrate on numeric columns with fewer outliers (although you
+could clean out the outliers). Boxplots can show the range of the data
+and will help choose which columns to use. Note, Boxplots do not show
+variance, an important element of good clusterable data. Please see this
+discussion for further reading.
+
Boxplots are
+used to graphically depict the distribution of numeric
+data, so let’s start by selecting all numeric columns alongside
+the popular music genres.
+
# Select top genre column and all other numeric columns
+df_numeric <- nigerian_songs %>%
+ select(artist_top_genre, where(is.numeric))
+
+# Display the data
+df_numeric %>%
+ slice_head(n = 5)
+
+
+
+
See how the selection helper where makes this easy 💁?
+Explore such other functions here.
+
Since we’ll be making a boxplot for each numeric features and we want
+to avoid using loops, let’s reformat our data into a longer
+format that will allow us to take advantage of facets -
+subplots that each display one subset of the data.
+
# Pivot data from wide to long
+df_numeric_long <- df_numeric %>%
+ pivot_longer(!artist_top_genre, names_to = "feature_names", values_to = "values")
+
+# Print out data
+df_numeric_long %>%
+ slice_head(n = 15)
+
+
+
+
Much longer! Now time for some ggplots! So what
+geom will we use?
+
# Make a box plot
+df_numeric_long %>%
+ ggplot(mapping = aes(x = feature_names, y = values, fill = feature_names)) +
+ geom_boxplot() +
+ facet_wrap(~ feature_names, ncol = 4, scales = "free") +
+ theme(legend.position = "none")
+

+
Easy-gg!
+
Now we can see this data is a little noisy: by observing each column
+as a boxplot, you can see outliers. You could go through the dataset and
+remove these outliers, but that would make the data pretty minimal.
+
For now, let’s choose which columns we will use for our clustering
+exercise. Let’s pick the numeric columns with similar ranges. We could
+encode the artist_top_genre as numeric but we’ll drop it
+for now.
+
# Select variables with similar ranges
+df_numeric_select <- df_numeric %>%
+ select(popularity, danceability, acousticness, loudness, energy)
+
+# Normalize data
+# df_numeric_select <- scale(df_numeric_select)
+
+
+
3. Computing k-means clustering in R
+
We can compute k-means in R with the built-in kmeans
+function, see help("kmeans()"). kmeans()
+function accepts a data frame with all numeric columns as it’s primary
+argument.
+
The first step when using k-means clustering is to specify the number
+of clusters (k) that will be generated in the final solution. We know
+there are 3 song genres that we carved out of the dataset, so let’s try
+3:
+
set.seed(2056)
+# Kmeans clustering for 3 clusters
+kclust <- kmeans(
+ df_numeric_select,
+ # Specify the number of clusters
+ centers = 3,
+ # How many random initial configurations
+ nstart = 25
+)
+
+# Display clustering object
+kclust
+
## K-means clustering with 3 clusters of sizes 65, 111, 110
+##
+## Cluster means:
+## popularity danceability acousticness loudness energy
+## 1 53.40000 0.7698615 0.2684248 -5.081200 0.7167231
+## 2 31.28829 0.7310811 0.2558767 -5.159550 0.7589279
+## 3 10.12727 0.7458727 0.2720171 -4.586418 0.7906091
+##
+## Clustering vector:
+## [1] 2 3 2 2 2 2 2 2 2 3 2 2 3 2 1 2 3 3 1 3 1 1 1 3 1 2 1 1 2 2 3 3 1 2 2 2 2
+## [38] 3 3 1 2 1 2 1 2 1 1 3 3 2 3 1 1 2 2 2 2 3 3 1 3 2 2 3 2 2 3 2 3 2 2 3 3 3
+## [75] 3 3 2 3 2 2 1 2 3 3 3 2 2 2 2 3 2 2 2 2 3 3 2 3 3 2 3 2 3 2 3 2 2 3 2 1 3
+## [112] 3 2 3 3 2 2 2 2 2 2 2 1 3 3 3 3 1 3 2 3 2 3 2 2 2 1 2 3 3 3 2 3 1 3 2 2 3
+## [149] 3 3 1 3 2 2 2 3 3 1 3 2 3 3 3 3 2 1 1 1 3 1 1 1 1 1 1 2 1 3 1 1 3 1 1 2 1
+## [186] 1 3 3 2 1 2 2 1 2 2 3 3 1 3 3 1 1 3 1 2 1 3 1 2 1 1 2 2 2 3 3 3 3 3 1 2 2
+## [223] 2 2 2 3 3 3 3 3 2 2 3 3 1 3 3 3 1 2 2 2 3 3 1 1 3 3 2 1 1 1 1 1 2 1 1 2 3
+## [260] 3 3 2 2 2 3 2 3 2 3 3 3 1 2 2 2 3 2 3 1 3 2 3 3 3 2 3
+##
+## Within cluster sum of squares by cluster:
+## [1] 3550.293 4559.358 4889.010
+## (between_SS / total_SS = 85.8 %)
+##
+## Available components:
+##
+## [1] "cluster" "centers" "totss" "withinss" "tot.withinss"
+## [6] "betweenss" "size" "iter" "ifault"
+
The kmeans object contains several bits of information which is well
+explained in help("kmeans()"). For now, let’s focus on a
+few. We see that the data has been grouped into 3 clusters of sizes 65,
+110, 111. The output also contains the cluster centers (means) for the 3
+groups across the 5 variables.
+
The clustering vector is the cluster assignment for each observation.
+Let’s use the augment function to add the cluster
+assignment the original data set.
+
# Add predicted cluster assignment to data set
+augment(kclust, df_numeric_select) %>%
+ relocate(.cluster) %>%
+ slice_head(n = 10)
+
+
+
+
Perfect, we have just partitioned our data set into a set of 3
+groups. So, how good is our clustering 🤷? Let’s take a look at the
+Silhouette score
+
+
Silhouette score
+
Silhouette
+analysis can be used to study the separation distance between the
+resulting clusters. This score varies from -1 to 1, and if the score is
+near 1, the cluster is dense and well-separated from other clusters. A
+value near 0 represents overlapping clusters with samples very close to
+the decision boundary of the neighboring clusters. (Source).
+
The average silhouette method computes the average silhouette of
+observations for different values of k. A high average
+silhouette score indicates a good clustering.
+
The silhouette function in the cluster package to
+compuate the average silhouette width.
+
+The silhouette can be calculated with any distance metric, such as the Euclidean distance or the Manhattan distance which we discussed in
+the previous
+lesson.
+
+
# Load cluster package
+library(cluster)
+
+# Compute average silhouette score
+ss <- silhouette(kclust$cluster,
+ # Compute euclidean distance
+ dist = dist(df_numeric_select))
+mean(ss[, 3])
+
## [1] 0.5494668
+
Our score is .549, so right in the middle. This
+indicates that our data is not particularly well-suited to this type of
+clustering. Let’s see whether we can confirm this hunch visually. The factoextra
+package provides functions (fviz_cluster()) to
+visualize clustering.
+
library(factoextra)
+
+# Visualize clustering results
+fviz_cluster(kclust, df_numeric_select)
+

+
The overlap in clusters indicates that our data is not particularly
+well-suited to this type of clustering but let’s continue.
+
+
+
+
4. Determining optimal clusters
+
A fundamental question that often arises in K-Means clustering is
+this - 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 (e.g from 1-10), and evaluate clustering metrics such
+as the Silhouette score.
+
Let’s determine the optimal number of clusters by computing the
+clustering algorithm for different values of k and evaluating
+the Within Cluster Sum of Squares (WCSS). The total
+within-cluster sum of square (WCSS) measures the compactness of the
+clustering and we want it to be as small as possible, with lower values
+meaning that the data points are closer.
+
Let’s explore the effect of different choices of k, from
+1 to 10, on this clustering.
+
# Create a series of clustering models
+kclusts <- tibble(k = 1:10) %>%
+ # Perform kmeans clustering for 1,2,3 ... ,10 clusters
+ mutate(model = map(k, ~ kmeans(df_numeric_select, centers = .x, nstart = 25)),
+ # Farm out clustering metrics eg WCSS
+ glanced = map(model, ~ glance(.x))) %>%
+ unnest(cols = glanced)
+
+
+# View clustering rsulsts
+kclusts
+
+
+
+
Now that we have the total within-cluster sum-of-squares
+(tot.withinss) for each clustering algorithm with center k, we
+use the elbow
+method to find the optimal number of clusters. The method consists
+of plotting the WCSS as a function of the number of clusters, and
+picking the elbow of the curve as the number of
+clusters to use.
+
set.seed(2056)
+# Use elbow method to determine optimum number of clusters
+kclusts %>%
+ ggplot(mapping = aes(x = k, y = tot.withinss)) +
+ geom_line(size = 1.2, alpha = 0.8, color = "#FF7F0EFF") +
+ geom_point(size = 2, color = "#FF7F0EFF")
+
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
+## ℹ Please use `linewidth` instead.
+## This warning is displayed once every 8 hours.
+## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
+## generated.
+

+
The plot shows a large reduction in WCSS (so greater
+tightness) as the number of clusters increases from one to two,
+and a further noticeable 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.
+
We can now go ahead and extract the clustering model where
+k = 3:
+
+pull(): used to extract a single column
+pluck(): used to index data structures such as lists
+
+
# Extract k = 3 clustering
+final_kmeans <- kclusts %>%
+ filter(k == 3) %>%
+ pull(model) %>%
+ pluck(1)
+
+
+final_kmeans
+
## K-means clustering with 3 clusters of sizes 111, 110, 65
+##
+## Cluster means:
+## popularity danceability acousticness loudness energy
+## 1 31.28829 0.7310811 0.2558767 -5.159550 0.7589279
+## 2 10.12727 0.7458727 0.2720171 -4.586418 0.7906091
+## 3 53.40000 0.7698615 0.2684248 -5.081200 0.7167231
+##
+## Clustering vector:
+## [1] 1 2 1 1 1 1 1 1 1 2 1 1 2 1 3 1 2 2 3 2 3 3 3 2 3 1 3 3 1 1 2 2 3 1 1 1 1
+## [38] 2 2 3 1 3 1 3 1 3 3 2 2 1 2 3 3 1 1 1 1 2 2 3 2 1 1 2 1 1 2 1 2 1 1 2 2 2
+## [75] 2 2 1 2 1 1 3 1 2 2 2 1 1 1 1 2 1 1 1 1 2 2 1 2 2 1 2 1 2 1 2 1 1 2 1 3 2
+## [112] 2 1 2 2 1 1 1 1 1 1 1 3 2 2 2 2 3 2 1 2 1 2 1 1 1 3 1 2 2 2 1 2 3 2 1 1 2
+## [149] 2 2 3 2 1 1 1 2 2 3 2 1 2 2 2 2 1 3 3 3 2 3 3 3 3 3 3 1 3 2 3 3 2 3 3 1 3
+## [186] 3 2 2 1 3 1 1 3 1 1 2 2 3 2 2 3 3 2 3 1 3 2 3 1 3 3 1 1 1 2 2 2 2 2 3 1 1
+## [223] 1 1 1 2 2 2 2 2 1 1 2 2 3 2 2 2 3 1 1 1 2 2 3 3 2 2 1 3 3 3 3 3 1 3 3 1 2
+## [260] 2 2 1 1 1 2 1 2 1 2 2 2 3 1 1 1 2 1 2 3 2 1 2 2 2 1 2
+##
+## Within cluster sum of squares by cluster:
+## [1] 4559.358 4889.010 3550.293
+## (between_SS / total_SS = 85.8 %)
+##
+## Available components:
+##
+## [1] "cluster" "centers" "totss" "withinss" "tot.withinss"
+## [6] "betweenss" "size" "iter" "ifault"
+
Great! Let’s go ahead and visualize the clusters obtained. Care for
+some interactivity using plotly?
+
# Add predicted cluster assignment to data set
+results <- augment(final_kmeans, df_numeric_select) %>%
+ bind_cols(df_numeric %>% select(artist_top_genre))
+
+# Plot cluster assignments
+clust_plt <- results %>%
+ ggplot(mapping = aes(x = popularity, y = danceability, color = .cluster, shape = artist_top_genre)) +
+ geom_point(size = 2, alpha = 0.8) +
+ paletteer::scale_color_paletteer_d("ggthemes::Tableau_10")
+
+ggplotly(clust_plt)
+
+
+
Perhaps we would have expected that each cluster (represented by
+different colors) would have distinct genres (represented by different
+shapes).
+
Let’s take a look at the model’s accuracy.
+
# Assign genres to predefined integers
+label_count <- results %>%
+ group_by(artist_top_genre) %>%
+ mutate(id = cur_group_id()) %>%
+ ungroup() %>%
+ summarise(correct_labels = sum(.cluster == id))
+
+
+# Print results
+cat("Result:", label_count$correct_labels, "out of", nrow(results), "samples were correctly labeled.")
+
## Result: 109 out of 286 samples were correctly labeled.
+
cat("\nAccuracy score:", label_count$correct_labels/nrow(results))
+
##
+## Accuracy score: 0.3811189
+
This model’s accuracy is not bad, but not great. It may be that the
+data may not lend itself well to K-Means Clustering. This data is too
+imbalanced, too little correlated and there is too much variance between
+the column values to cluster well. In fact, the clusters that form are
+probably heavily influenced or skewed by the three genre categories we
+defined above.
+
Nevertheless, that was quite a learning process!
+
In Scikit-learn’s documentation, you can see that a model like this
+one, with clusters not very well demarcated, has a ‘variance’
+problem:
+
+

+
Infographic from Scikit-learn
+
+
+
+
Variance
+
Variance is defined as “the average of the squared differences from
+the Mean” (Source).
+In the context of this clustering problem, it refers to data that the
+numbers of our dataset tend to diverge a bit too much from the mean.
+
✅ This is a great moment to think about all the ways you could
+correct this issue. Tweak the data a bit more? Use different columns?
+Use a different algorithm? Hint: Try scaling
+your data to normalize it and test other columns.
+
+Try this ‘variance
+calculator’ to understand the concept a bit more.
+
+
+
+
+
🚀Challenge
+
Spend some time with this notebook, tweaking parameters. Can you
+improve the accuracy of the model by cleaning the data more (removing
+outliers, for example)? You can use weights to give more weight to given
+data samples. What else can you do to create better clusters?
+
Hint: Try to scale your data. There’s commented code in the notebook
+that adds standard scaling to make the data columns resemble each other
+more closely in terms of range. You’ll find that while the silhouette
+score goes down, the ‘kink’ in the elbow graph smooths out. This is
+because leaving the data unscaled allows data with less variance to
+carry more weight. Read a bit more on this problem here.
+
+
+
+
Review & Self Study
+
+Take a look at a K-Means Simulator such
+as this one. You can use this tool to visualize sample data points
+and determine its centroids. You can edit the data’s randomness, numbers
+of clusters and numbers of centroids. Does this help you get an idea of
+how the data can be grouped?
+Also, take a look at this
+handout on K-Means from Stanford.
+
+
Want to try out your newly acquired clustering skills to data sets
+that lend well to K-Means clustering? Please see:
+
+
+
+
+
THANK YOU TO:
+
Jen Looper for
+creating the original Python version of this module ♥️
+
Allison Horst
+for creating the amazing illustrations that make R more welcoming and
+engaging. Find more illustrations at her gallery.
+
Happy Learning,
+
Eric, Gold Microsoft Learn
+Student Ambassador.
+
+

+
Artwork by @allison_horst
+
+
#{r include=FALSE} #library(here) #library(rmd2jupyter) #rmd2jupyter("lesson_14.Rmd") #
+
+
+
LS0tCnRpdGxlOiAnSy1NZWFucyBDbHVzdGVyaW5nIHVzaW5nIFRpZHltb2RlbHMgYW5kIGZyaWVuZHMnCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgI2Nzczogc3R5bGVfNy5jc3MKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdGhlbWU6IGZsYXRseQogICAgaGlnaGxpZ2h0OiBicmVlemVkYXJrCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGNvZGVfZG93bmxvYWQ6IHllcwotLS0KCiMjIEV4cGxvcmUgSy1NZWFucyBjbHVzdGVyaW5nIHVzaW5nIFIgYW5kIFRpZHkgZGF0YSBwcmluY2lwbGVzLgoKIyMjIFsqKlByZS1sZWN0dXJlIHF1aXoqKl0oaHR0cHM6Ly9ncmF5LXNhbmQtMDdhMTBmNDAzLjEuYXp1cmVzdGF0aWNhcHBzLm5ldC9xdWl6LzI5LykKCkluIHRoaXMgbGVzc29uLCB5b3Ugd2lsbCBsZWFybiBob3cgdG8gY3JlYXRlIGNsdXN0ZXJzIHVzaW5nIHRoZSBUaWR5bW9kZWxzIHBhY2thZ2UgYW5kIG90aGVyIHBhY2thZ2VzIGluIHRoZSBSIGVjb3N5c3RlbSAod2UnbGwgY2FsbCB0aGVtIGZyaWVuZHMg8J+nkeKAjfCfpJ3igI3wn6eRKSwgYW5kIHRoZSBOaWdlcmlhbiBtdXNpYyBkYXRhc2V0IHlvdSBpbXBvcnRlZCBlYXJsaWVyLiBXZSB3aWxsIGNvdmVyIHRoZSBiYXNpY3Mgb2YgSy1NZWFucyBmb3IgQ2x1c3RlcmluZy4gS2VlcCBpbiBtaW5kIHRoYXQsIGFzIHlvdSBsZWFybmVkIGluIHRoZSBlYXJsaWVyIGxlc3NvbiwgdGhlcmUgYXJlIG1hbnkgd2F5cyB0byB3b3JrIHdpdGggY2x1c3RlcnMgYW5kIHRoZSBtZXRob2QgeW91IHVzZSBkZXBlbmRzIG9uIHlvdXIgZGF0YS4gV2Ugd2lsbCB0cnkgSy1NZWFucyBhcyBpdCdzIHRoZSBtb3N0IGNvbW1vbiBjbHVzdGVyaW5nIHRlY2huaXF1ZS4gTGV0J3MgZ2V0IHN0YXJ0ZWQhCgpUZXJtcyB5b3Ugd2lsbCBsZWFybiBhYm91dDoKCi0gICBTaWxob3VldHRlIHNjb3JpbmcKCi0gICBFbGJvdyBtZXRob2QKCi0gICBJbmVydGlhCgotICAgVmFyaWFuY2UKCiMjIyAqKkludHJvZHVjdGlvbioqCgpbSy1NZWFucyBDbHVzdGVyaW5nXShodHRwczovL3dpa2lwZWRpYS5vcmcvd2lraS9LLW1lYW5zX2NsdXN0ZXJpbmcpIGlzIGEgbWV0aG9kIGRlcml2ZWQgZnJvbSB0aGUgZG9tYWluIG9mIHNpZ25hbCBwcm9jZXNzaW5nLiBJdCBpcyB1c2VkIHRvIGRpdmlkZSBhbmQgcGFydGl0aW9uIGdyb3VwcyBvZiBkYXRhIGludG8gYGsgY2x1c3RlcnNgIGJhc2VkIG9uIHNpbWlsYXJpdGllcyBpbiB0aGVpciBmZWF0dXJlcy4KClRoZSBjbHVzdGVycyBjYW4gYmUgdmlzdWFsaXplZCBhcyBbVm9yb25vaSBkaWFncmFtc10oaHR0cHM6Ly93aWtpcGVkaWEub3JnL3dpa2kvVm9yb25vaV9kaWFncmFtKSwgd2hpY2ggaW5jbHVkZSBhIHBvaW50IChvciAnc2VlZCcpIGFuZCBpdHMgY29ycmVzcG9uZGluZyByZWdpb24uCgohW0luZm9ncmFwaGljIGJ5IEplbiBMb29wZXJdKC4uLy4uL2ltYWdlcy92b3Jvbm9pLnBuZykKCkstTWVhbnMgY2x1c3RlcmluZyBoYXMgdGhlIGZvbGxvd2luZyBzdGVwczoKCjEuICBUaGUgZGF0YSBzY2llbnRpc3Qgc3RhcnRzIGJ5IHNwZWNpZnlpbmcgdGhlIGRlc2lyZWQgbnVtYmVyIG9mIGNsdXN0ZXJzIHRvIGJlIGNyZWF0ZWQuCgoyLiAgTmV4dCwgdGhlIGFsZ29yaXRobSByYW5kb21seSBzZWxlY3RzIEsgb2JzZXJ2YXRpb25zIGZyb20gdGhlIGRhdGEgc2V0IHRvIHNlcnZlIGFzIHRoZSBpbml0aWFsIGNlbnRlcnMgZm9yIHRoZSBjbHVzdGVycyAoaS5lLiwgY2VudHJvaWRzKS4KCjMuICBOZXh0LCBlYWNoIG9mIHRoZSByZW1haW5pbmcgb2JzZXJ2YXRpb25zIGlzIGFzc2lnbmVkIHRvIGl0cyBjbG9zZXN0IGNlbnRyb2lkLgoKNC4gIE5leHQsIHRoZSBuZXcgbWVhbnMgb2YgZWFjaCBjbHVzdGVyIGlzIGNvbXB1dGVkIGFuZCB0aGUgY2VudHJvaWQgaXMgbW92ZWQgdG8gdGhlIG1lYW4uCgo1LiAgTm93IHRoYXQgdGhlIGNlbnRlcnMgaGF2ZSBiZWVuIHJlY2FsY3VsYXRlZCwgZXZlcnkgb2JzZXJ2YXRpb24gaXMgY2hlY2tlZCBhZ2FpbiB0byBzZWUgaWYgaXQgbWlnaHQgYmUgY2xvc2VyIHRvIGEgZGlmZmVyZW50IGNsdXN0ZXIuIEFsbCB0aGUgb2JqZWN0cyBhcmUgcmVhc3NpZ25lZCBhZ2FpbiB1c2luZyB0aGUgdXBkYXRlZCBjbHVzdGVyIG1lYW5zLiBUaGUgY2x1c3RlciBhc3NpZ25tZW50IGFuZCBjZW50cm9pZCB1cGRhdGUgc3RlcHMgYXJlIGl0ZXJhdGl2ZWx5IHJlcGVhdGVkIHVudGlsIHRoZSBjbHVzdGVyIGFzc2lnbm1lbnRzIHN0b3AgY2hhbmdpbmcgKGkuZS4sIHdoZW4gY29udmVyZ2VuY2UgaXMgYWNoaWV2ZWQpLiBUeXBpY2FsbHksIHRoZSBhbGdvcml0aG0gdGVybWluYXRlcyB3aGVuIGVhY2ggbmV3IGl0ZXJhdGlvbiByZXN1bHRzIGluIG5lZ2xpZ2libGUgbW92ZW1lbnQgb2YgY2VudHJvaWRzIGFuZCB0aGUgY2x1c3RlcnMgYmVjb21lIHN0YXRpYy4KCjxkaXY+Cgo+IE5vdGUgdGhhdCBkdWUgdG8gcmFuZG9taXphdGlvbiBvZiB0aGUgaW5pdGlhbCBrIG9ic2VydmF0aW9ucyB1c2VkIGFzIHRoZSBzdGFydGluZyBjZW50cm9pZHMsIHdlIGNhbiBnZXQgc2xpZ2h0bHkgZGlmZmVyZW50IHJlc3VsdHMgZWFjaCB0aW1lIHdlIGFwcGx5IHRoZSBwcm9jZWR1cmUuIEZvciB0aGlzIHJlYXNvbiwgbW9zdCBhbGdvcml0aG1zIHVzZSBzZXZlcmFsICpyYW5kb20gc3RhcnRzKiBhbmQgY2hvb3NlIHRoZSBpdGVyYXRpb24gd2l0aCB0aGUgbG93ZXN0IFdDU1MuIEFzIHN1Y2gsIGl0IGlzIHN0cm9uZ2x5IHJlY29tbWVuZGVkIHRvIGFsd2F5cyBydW4gSy1NZWFucyB3aXRoIHNldmVyYWwgdmFsdWVzIG9mICpuc3RhcnQqIHRvIGF2b2lkIGFuICp1bmRlc2lyYWJsZSBsb2NhbCBvcHRpbXVtLioKCjwvZGl2PgoKVGhpcyBzaG9ydCBhbmltYXRpb24gdXNpbmcgdGhlIFthcnR3b3JrXShodHRwczovL2dpdGh1Yi5jb20vYWxsaXNvbmhvcnN0L3N0YXRzLWlsbHVzdHJhdGlvbnMpIG9mIEFsbGlzb24gSG9yc3QgZXhwbGFpbnMgdGhlIGNsdXN0ZXJpbmcgcHJvY2VzczoKCiFbQXJ0d29yayBieSBcQGFsbGlzb25faG9yc3RdKC4uLy4uL2ltYWdlcy9rbWVhbnMuZ2lmKQoKQSBmdW5kYW1lbnRhbCBxdWVzdGlvbiB0aGF0IGFyaXNlcyBpbiBjbHVzdGVyaW5nIGlzIHRoaXM6IGhvdyBkbyB5b3Uga25vdyBob3cgbWFueSBjbHVzdGVycyB0byBzZXBhcmF0ZSB5b3VyIGRhdGEgaW50bz8gT25lIGRyYXdiYWNrIG9mIHVzaW5nIEstTWVhbnMgaW5jbHVkZXMgdGhlIGZhY3QgdGhhdCB5b3Ugd2lsbCBuZWVkIHRvIGVzdGFibGlzaCBga2AsIHRoYXQgaXMgdGhlIG51bWJlciBvZiBgY2VudHJvaWRzYC4gRm9ydHVuYXRlbHkgdGhlIGBlbGJvdyBtZXRob2RgIGhlbHBzIHRvIGVzdGltYXRlIGEgZ29vZCBzdGFydGluZyB2YWx1ZSBmb3IgYGtgLiBZb3UnbGwgdHJ5IGl0IGluIGEgbWludXRlLgoKIyMjIAoKKipQcmVyZXF1aXNpdGUqKgoKV2UnbGwgcGljayBvZmYgcmlnaHQgZnJvbSB3aGVyZSB3ZSBzdG9wcGVkIGluIHRoZSBbcHJldmlvdXMgbGVzc29uXShodHRwczovL2dpdGh1Yi5jb20vbWljcm9zb2Z0L01MLUZvci1CZWdpbm5lcnMvYmxvYi9tYWluLzUtQ2x1c3RlcmluZy8xLVZpc3VhbGl6ZS9zb2x1dGlvbi9SL2xlc3Nvbl8xNC1SLmlweW5iKSwgd2hlcmUgd2UgYW5hbHlzZWQgdGhlIGRhdGEgc2V0LCBtYWRlIGxvdHMgb2YgdmlzdWFsaXphdGlvbnMgYW5kIGZpbHRlcmVkIHRoZSBkYXRhIHNldCB0byBvYnNlcnZhdGlvbnMgb2YgaW50ZXJlc3QuIEJlIHN1cmUgdG8gY2hlY2sgaXQgb3V0IQoKV2UnbGwgcmVxdWlyZSBzb21lIHBhY2thZ2VzIHRvIGtub2NrLW9mZiB0aGlzIG1vZHVsZS4gWW91IGNhbiBoYXZlIHRoZW0gaW5zdGFsbGVkIGFzOiBgaW5zdGFsbC5wYWNrYWdlcyhjKCd0aWR5dmVyc2UnLCAndGlkeW1vZGVscycsICdjbHVzdGVyJywgJ3N1bW1hcnl0b29scycsICdwbG90bHknLCAncGFsZXR0ZWVyJywgJ2ZhY3RvZXh0cmEnLCAncGF0Y2h3b3JrJykpYAoKQWx0ZXJuYXRpdmVseSwgdGhlIHNjcmlwdCBiZWxvdyBjaGVja3Mgd2hldGhlciB5b3UgaGF2ZSB0aGUgcGFja2FnZXMgcmVxdWlyZWQgdG8gY29tcGxldGUgdGhpcyBtb2R1bGUgYW5kIGluc3RhbGxzIHRoZW0gZm9yIHlvdSBpbiBjYXNlIHNvbWUgYXJlIG1pc3NpbmcuCgpgYGB7cn0Kc3VwcHJlc3NXYXJuaW5ncyhpZighcmVxdWlyZSgicGFjbWFuIikpIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIscmVwb3MgPSAiaHR0cDovL2NyYW4udXMuci1wcm9qZWN0Lm9yZyIpKQoKcGFjbWFuOjpwX2xvYWQoJ3RpZHl2ZXJzZScsICd0aWR5bW9kZWxzJywgJ2NsdXN0ZXInLCAnc3VtbWFyeXRvb2xzJywgJ3Bsb3RseScsICdwYWxldHRlZXInLCAnZmFjdG9leHRyYScsICdwYXRjaHdvcmsnKQpgYGAKCkxldCdzIGhpdCB0aGUgZ3JvdW5kIHJ1bm5pbmchCgojIyAxLiBBIGRhbmNlIHdpdGggZGF0YTogTmFycm93IGRvd24gdG8gdGhlIDMgbW9zdCBwb3B1bGFyIG11c2ljIGdlbnJlcwoKVGhpcyBpcyBhIHJlY2FwIG9mIHdoYXQgd2UgZGlkIGluIHRoZSBwcmV2aW91cyBsZXNzb24uIExldCdzIHNsaWNlIGFuZCBkaWNlIHNvbWUgZGF0YSEKCmBgYHtyIG1lc3NhZ2U9Riwgd2FybmluZz1GfQojIExvYWQgdGhlIGNvcmUgdGlkeXZlcnNlIGFuZCBtYWtlIGl0IGF2YWlsYWJsZSBpbiB5b3VyIGN1cnJlbnQgUiBzZXNzaW9uCmxpYnJhcnkodGlkeXZlcnNlKQoKIyBJbXBvcnQgdGhlIGRhdGEgaW50byBhIHRpYmJsZQpkZiA8LSByZWFkX2NzdihmaWxlID0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9taWNyb3NvZnQvTUwtRm9yLUJlZ2lubmVycy9tYWluLzUtQ2x1c3RlcmluZy9kYXRhL25pZ2VyaWFuLXNvbmdzLmNzdiIsIHNob3dfY29sX3R5cGVzID0gRkFMU0UpCgojIE5hcnJvdyBkb3duIHRvIHRvcCAzIHBvcHVsYXIgZ2VucmVzCm5pZ2VyaWFuX3NvbmdzIDwtIGRmICU+JSAKICAjIENvbmNlbnRyYXRlIG9uIHRvcCAzIGdlbnJlcwogIGZpbHRlcihhcnRpc3RfdG9wX2dlbnJlICVpbiUgYygiYWZybyBkYW5jZWhhbGwiLCAiYWZyb3BvcCIsIm5pZ2VyaWFuIHBvcCIpKSAlPiUgCiAgIyBSZW1vdmUgdW5jbGFzc2lmaWVkIG9ic2VydmF0aW9ucwogIGZpbHRlcihwb3B1bGFyaXR5ICE9IDApCgoKCiMgVmlzdWFsaXplIHBvcHVsYXIgZ2VucmVzIHVzaW5nIGJhciBwbG90cwp0aGVtZV9zZXQodGhlbWVfbGlnaHQoKSkKbmlnZXJpYW5fc29uZ3MgJT4lCiAgY291bnQoYXJ0aXN0X3RvcF9nZW5yZSkgJT4lCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGFydGlzdF90b3BfZ2VucmUsIHkgPSBuLAogICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBhcnRpc3RfdG9wX2dlbnJlKSkgKwogIGdlb21fY29sKGFscGhhID0gMC44KSArCiAgcGFsZXR0ZWVyOjpzY2FsZV9maWxsX3BhbGV0dGVlcl9kKCJnZ3NjaTo6Y2F0ZWdvcnkxMF9kMyIpICsKICBnZ3RpdGxlKCJUb3AgZ2VucmVzIikgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQoKCmBgYAoK8J+kqSBUaGF0IHdlbnQgd2VsbCEKCiMjIDIuIE1vcmUgZGF0YSBleHBsb3JhdGlvbi4KCkhvdyBjbGVhbiBpcyB0aGlzIGRhdGE/IExldCdzIGNoZWNrIGZvciBvdXRsaWVycyB1c2luZyBib3ggcGxvdHMuIFdlIHdpbGwgY29uY2VudHJhdGUgb24gbnVtZXJpYyBjb2x1bW5zIHdpdGggZmV3ZXIgb3V0bGllcnMgKGFsdGhvdWdoIHlvdSBjb3VsZCBjbGVhbiBvdXQgdGhlIG91dGxpZXJzKS4gQm94cGxvdHMgY2FuIHNob3cgdGhlIHJhbmdlIG9mIHRoZSBkYXRhIGFuZCB3aWxsIGhlbHAgY2hvb3NlIHdoaWNoIGNvbHVtbnMgdG8gdXNlLiBOb3RlLCBCb3hwbG90cyBkbyBub3Qgc2hvdyB2YXJpYW5jZSwgYW4gaW1wb3J0YW50IGVsZW1lbnQgb2YgZ29vZCBjbHVzdGVyYWJsZSBkYXRhLiBQbGVhc2Ugc2VlIFt0aGlzIGRpc2N1c3Npb25dKGh0dHBzOi8vc3RhdHMuc3RhY2tleGNoYW5nZS5jb20vcXVlc3Rpb25zLzkxNTM2L2RlZHVjZS12YXJpYW5jZS1mcm9tLWJveHBsb3QpIGZvciBmdXJ0aGVyIHJlYWRpbmcuCgpbQm94cGxvdHNdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0JveF9wbG90KSBhcmUgdXNlZCB0byBncmFwaGljYWxseSBkZXBpY3QgdGhlIGRpc3RyaWJ1dGlvbiBvZiBgbnVtZXJpY2AgZGF0YSwgc28gbGV0J3Mgc3RhcnQgYnkgKnNlbGVjdGluZyogYWxsIG51bWVyaWMgY29sdW1ucyBhbG9uZ3NpZGUgdGhlIHBvcHVsYXIgbXVzaWMgZ2VucmVzLgoKYGBge3Igc2VsZWN0fQojIFNlbGVjdCB0b3AgZ2VucmUgY29sdW1uIGFuZCBhbGwgb3RoZXIgbnVtZXJpYyBjb2x1bW5zCmRmX251bWVyaWMgPC0gbmlnZXJpYW5fc29uZ3MgJT4lIAogIHNlbGVjdChhcnRpc3RfdG9wX2dlbnJlLCB3aGVyZShpcy5udW1lcmljKSkgCgojIERpc3BsYXkgdGhlIGRhdGEKZGZfbnVtZXJpYyAlPiUgCiAgc2xpY2VfaGVhZChuID0gNSkKCmBgYAoKU2VlIGhvdyB0aGUgc2VsZWN0aW9uIGhlbHBlciBgd2hlcmVgIG1ha2VzIHRoaXMgZWFzeSDwn5KBPyBFeHBsb3JlIHN1Y2ggb3RoZXIgZnVuY3Rpb25zIFtoZXJlXShodHRwczovL3RpZHlzZWxlY3Quci1saWIub3JnLykuCgpTaW5jZSB3ZSdsbCBiZSBtYWtpbmcgYSBib3hwbG90IGZvciBlYWNoIG51bWVyaWMgZmVhdHVyZXMgYW5kIHdlIHdhbnQgdG8gYXZvaWQgdXNpbmcgbG9vcHMsIGxldCdzIHJlZm9ybWF0IG91ciBkYXRhIGludG8gYSAqbG9uZ2VyKiBmb3JtYXQgdGhhdCB3aWxsIGFsbG93IHVzIHRvIHRha2UgYWR2YW50YWdlIG9mIGBmYWNldHNgIC0gc3VicGxvdHMgdGhhdCBlYWNoIGRpc3BsYXkgb25lIHN1YnNldCBvZiB0aGUgZGF0YS4KCmBgYHtyIHBpdm90X2xvbmdlcn0KIyBQaXZvdCBkYXRhIGZyb20gd2lkZSB0byBsb25nCmRmX251bWVyaWNfbG9uZyA8LSBkZl9udW1lcmljICU+JSAKICBwaXZvdF9sb25nZXIoIWFydGlzdF90b3BfZ2VucmUsIG5hbWVzX3RvID0gImZlYXR1cmVfbmFtZXMiLCB2YWx1ZXNfdG8gPSAidmFsdWVzIikgCgojIFByaW50IG91dCBkYXRhCmRmX251bWVyaWNfbG9uZyAlPiUgCiAgc2xpY2VfaGVhZChuID0gMTUpCmBgYAoKTXVjaCBsb25nZXIhIE5vdyB0aW1lIGZvciBzb21lIGBnZ3Bsb3RzYCEgU28gd2hhdCBgZ2VvbWAgd2lsbCB3ZSB1c2U/CgpgYGB7cn0KIyBNYWtlIGEgYm94IHBsb3QKZGZfbnVtZXJpY19sb25nICU+JSAKICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gZmVhdHVyZV9uYW1lcywgeSA9IHZhbHVlcywgZmlsbCA9IGZlYXR1cmVfbmFtZXMpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGZhY2V0X3dyYXAofiBmZWF0dXJlX25hbWVzLCBuY29sID0gNCwgc2NhbGVzID0gImZyZWUiKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCkVhc3ktZ2chCgpOb3cgd2UgY2FuIHNlZSB0aGlzIGRhdGEgaXMgYSBsaXR0bGUgbm9pc3k6IGJ5IG9ic2VydmluZyBlYWNoIGNvbHVtbiBhcyBhIGJveHBsb3QsIHlvdSBjYW4gc2VlIG91dGxpZXJzLiBZb3UgY291bGQgZ28gdGhyb3VnaCB0aGUgZGF0YXNldCBhbmQgcmVtb3ZlIHRoZXNlIG91dGxpZXJzLCBidXQgdGhhdCB3b3VsZCBtYWtlIHRoZSBkYXRhIHByZXR0eSBtaW5pbWFsLgoKRm9yIG5vdywgbGV0J3MgY2hvb3NlIHdoaWNoIGNvbHVtbnMgd2Ugd2lsbCB1c2UgZm9yIG91ciBjbHVzdGVyaW5nIGV4ZXJjaXNlLiBMZXQncyBwaWNrIHRoZSBudW1lcmljIGNvbHVtbnMgd2l0aCBzaW1pbGFyIHJhbmdlcy4gV2UgY291bGQgZW5jb2RlIHRoZSBgYXJ0aXN0X3RvcF9nZW5yZWAgYXMgbnVtZXJpYyBidXQgd2UnbGwgZHJvcCBpdCBmb3Igbm93LgoKYGBge3Igc2VsZWN0X2NvbHVtbnN9CiMgU2VsZWN0IHZhcmlhYmxlcyB3aXRoIHNpbWlsYXIgcmFuZ2VzCmRmX251bWVyaWNfc2VsZWN0IDwtIGRmX251bWVyaWMgJT4lIAogIHNlbGVjdChwb3B1bGFyaXR5LCBkYW5jZWFiaWxpdHksIGFjb3VzdGljbmVzcywgbG91ZG5lc3MsIGVuZXJneSkgCgojIE5vcm1hbGl6ZSBkYXRhCiMgZGZfbnVtZXJpY19zZWxlY3QgPC0gc2NhbGUoZGZfbnVtZXJpY19zZWxlY3QpCmBgYAoKIyMgMy4gQ29tcHV0aW5nIGstbWVhbnMgY2x1c3RlcmluZyBpbiBSCgpXZSBjYW4gY29tcHV0ZSBrLW1lYW5zIGluIFIgd2l0aCB0aGUgYnVpbHQtaW4gYGttZWFuc2AgZnVuY3Rpb24sIHNlZSBgaGVscCgia21lYW5zKCkiKWAuIGBrbWVhbnMoKWAgZnVuY3Rpb24gYWNjZXB0cyBhIGRhdGEgZnJhbWUgd2l0aCBhbGwgbnVtZXJpYyBjb2x1bW5zIGFzIGl0J3MgcHJpbWFyeSBhcmd1bWVudC4KClRoZSBmaXJzdCBzdGVwIHdoZW4gdXNpbmcgay1tZWFucyBjbHVzdGVyaW5nIGlzIHRvIHNwZWNpZnkgdGhlIG51bWJlciBvZiBjbHVzdGVycyAoaykgdGhhdCB3aWxsIGJlIGdlbmVyYXRlZCBpbiB0aGUgZmluYWwgc29sdXRpb24uIFdlIGtub3cgdGhlcmUgYXJlIDMgc29uZyBnZW5yZXMgdGhhdCB3ZSBjYXJ2ZWQgb3V0IG9mIHRoZSBkYXRhc2V0LCBzbyBsZXQncyB0cnkgMzoKCmBgYHtyIGttZWFuc30Kc2V0LnNlZWQoMjA1NikKIyBLbWVhbnMgY2x1c3RlcmluZyBmb3IgMyBjbHVzdGVycwprY2x1c3QgPC0ga21lYW5zKAogIGRmX251bWVyaWNfc2VsZWN0LAogICMgU3BlY2lmeSB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzCiAgY2VudGVycyA9IDMsCiAgIyBIb3cgbWFueSByYW5kb20gaW5pdGlhbCBjb25maWd1cmF0aW9ucwogIG5zdGFydCA9IDI1CikKCiMgRGlzcGxheSBjbHVzdGVyaW5nIG9iamVjdAprY2x1c3QKYGBgCgpUaGUga21lYW5zIG9iamVjdCBjb250YWlucyBzZXZlcmFsIGJpdHMgb2YgaW5mb3JtYXRpb24gd2hpY2ggaXMgd2VsbCBleHBsYWluZWQgaW4gYGhlbHAoImttZWFucygpIilgLiBGb3Igbm93LCBsZXQncyBmb2N1cyBvbiBhIGZldy4gV2Ugc2VlIHRoYXQgdGhlIGRhdGEgaGFzIGJlZW4gZ3JvdXBlZCBpbnRvIDMgY2x1c3RlcnMgb2Ygc2l6ZXMgNjUsIDExMCwgMTExLiBUaGUgb3V0cHV0IGFsc28gY29udGFpbnMgdGhlIGNsdXN0ZXIgY2VudGVycyAobWVhbnMpIGZvciB0aGUgMyBncm91cHMgYWNyb3NzIHRoZSA1IHZhcmlhYmxlcy4KClRoZSBjbHVzdGVyaW5nIHZlY3RvciBpcyB0aGUgY2x1c3RlciBhc3NpZ25tZW50IGZvciBlYWNoIG9ic2VydmF0aW9uLiBMZXQncyB1c2UgdGhlIGBhdWdtZW50YCBmdW5jdGlvbiB0byBhZGQgdGhlIGNsdXN0ZXIgYXNzaWdubWVudCB0aGUgb3JpZ2luYWwgZGF0YSBzZXQuCgpgYGB7ciBhdWdtZW50fQojIEFkZCBwcmVkaWN0ZWQgY2x1c3RlciBhc3NpZ25tZW50IHRvIGRhdGEgc2V0CmF1Z21lbnQoa2NsdXN0LCBkZl9udW1lcmljX3NlbGVjdCkgJT4lIAogIHJlbG9jYXRlKC5jbHVzdGVyKSAlPiUgCiAgc2xpY2VfaGVhZChuID0gMTApCmBgYAoKUGVyZmVjdCwgd2UgaGF2ZSBqdXN0IHBhcnRpdGlvbmVkIG91ciBkYXRhIHNldCBpbnRvIGEgc2V0IG9mIDMgZ3JvdXBzLiBTbywgaG93IGdvb2QgaXMgb3VyIGNsdXN0ZXJpbmcg8J+ktz8gTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGBTaWxob3VldHRlIHNjb3JlYAoKIyMjICoqU2lsaG91ZXR0ZSBzY29yZSoqCgpbU2lsaG91ZXR0ZSBhbmFseXNpc10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvU2lsaG91ZXR0ZV8oY2x1c3RlcmluZykpIGNhbiBiZSB1c2VkIHRvIHN0dWR5IHRoZSBzZXBhcmF0aW9uIGRpc3RhbmNlIGJldHdlZW4gdGhlIHJlc3VsdGluZyBjbHVzdGVycy4gVGhpcyBzY29yZSB2YXJpZXMgZnJvbSAtMSB0byAxLCBhbmQgaWYgdGhlIHNjb3JlIGlzIG5lYXIgMSwgdGhlIGNsdXN0ZXIgaXMgZGVuc2UgYW5kIHdlbGwtc2VwYXJhdGVkIGZyb20gb3RoZXIgY2x1c3RlcnMuIEEgdmFsdWUgbmVhciAwIHJlcHJlc2VudHMgb3ZlcmxhcHBpbmcgY2x1c3RlcnMgd2l0aCBzYW1wbGVzIHZlcnkgY2xvc2UgdG8gdGhlIGRlY2lzaW9uIGJvdW5kYXJ5IG9mIHRoZSBuZWlnaGJvcmluZyBjbHVzdGVycy4gWyhTb3VyY2UpXShodHRwczovL2R6b25lLmNvbS9hcnRpY2xlcy9rbWVhbnMtc2lsaG91ZXR0ZS1zY29yZS1leHBsYWluZWQtd2l0aC1weXRob24tZXhhbSkuCgpUaGUgYXZlcmFnZSBzaWxob3VldHRlIG1ldGhvZCBjb21wdXRlcyB0aGUgYXZlcmFnZSBzaWxob3VldHRlIG9mIG9ic2VydmF0aW9ucyBmb3IgZGlmZmVyZW50IHZhbHVlcyBvZiAqayouIEEgaGlnaCBhdmVyYWdlIHNpbGhvdWV0dGUgc2NvcmUgaW5kaWNhdGVzIGEgZ29vZCBjbHVzdGVyaW5nLgoKVGhlIGBzaWxob3VldHRlYCBmdW5jdGlvbiBpbiB0aGUgY2x1c3RlciBwYWNrYWdlIHRvIGNvbXB1YXRlIHRoZSBhdmVyYWdlIHNpbGhvdWV0dGUgd2lkdGguCgo+IFRoZSBzaWxob3VldHRlIGNhbiBiZSBjYWxjdWxhdGVkIHdpdGggYW55IFtkaXN0YW5jZV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRGlzdGFuY2UgIkRpc3RhbmNlIikgbWV0cmljLCBzdWNoIGFzIHRoZSBbRXVjbGlkZWFuIGRpc3RhbmNlXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9FdWNsaWRlYW5fZGlzdGFuY2UgIkV1Y2xpZGVhbiBkaXN0YW5jZSIpIG9yIHRoZSBbTWFuaGF0dGFuIGRpc3RhbmNlXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9NYW5oYXR0YW5fZGlzdGFuY2UgIk1hbmhhdHRhbiBkaXN0YW5jZSIpIHdoaWNoIHdlIGRpc2N1c3NlZCBpbiB0aGUgW3ByZXZpb3VzIGxlc3Nvbl0oaHR0cHM6Ly9naXRodWIuY29tL21pY3Jvc29mdC9NTC1Gb3ItQmVnaW5uZXJzL2Jsb2IvbWFpbi81LUNsdXN0ZXJpbmcvMS1WaXN1YWxpemUvc29sdXRpb24vUi9sZXNzb25fMTQtUi5pcHluYikuCgpgYGB7cn0KIyBMb2FkIGNsdXN0ZXIgcGFja2FnZQpsaWJyYXJ5KGNsdXN0ZXIpCgojIENvbXB1dGUgYXZlcmFnZSBzaWxob3VldHRlIHNjb3JlCnNzIDwtIHNpbGhvdWV0dGUoa2NsdXN0JGNsdXN0ZXIsCiAgICAgICAgICAgICAgICAgIyBDb21wdXRlIGV1Y2xpZGVhbiBkaXN0YW5jZQogICAgICAgICAgICAgICAgIGRpc3QgPSBkaXN0KGRmX251bWVyaWNfc2VsZWN0KSkKbWVhbihzc1ssIDNdKQoKYGBgCgpPdXIgc2NvcmUgaXMgKiouNTQ5KiosIHNvIHJpZ2h0IGluIHRoZSBtaWRkbGUuIFRoaXMgaW5kaWNhdGVzIHRoYXQgb3VyIGRhdGEgaXMgbm90IHBhcnRpY3VsYXJseSB3ZWxsLXN1aXRlZCB0byB0aGlzIHR5cGUgb2YgY2x1c3RlcmluZy4gTGV0J3Mgc2VlIHdoZXRoZXIgd2UgY2FuIGNvbmZpcm0gdGhpcyBodW5jaCB2aXN1YWxseS4gVGhlIFtmYWN0b2V4dHJhIHBhY2thZ2VdKGh0dHBzOi8vcnBrZ3MuZGF0YW5vdmlhLmNvbS9mYWN0b2V4dHJhL2luZGV4Lmh0bWwpIHByb3ZpZGVzIGZ1bmN0aW9ucyAoYGZ2aXpfY2x1c3RlcigpYCkgdG8gdmlzdWFsaXplIGNsdXN0ZXJpbmcuCgpgYGB7ciBmdml6X2NsdXN0ZXJ9CmxpYnJhcnkoZmFjdG9leHRyYSkKCiMgVmlzdWFsaXplIGNsdXN0ZXJpbmcgcmVzdWx0cwpmdml6X2NsdXN0ZXIoa2NsdXN0LCBkZl9udW1lcmljX3NlbGVjdCkKCmBgYAoKVGhlIG92ZXJsYXAgaW4gY2x1c3RlcnMgaW5kaWNhdGVzIHRoYXQgb3VyIGRhdGEgaXMgbm90IHBhcnRpY3VsYXJseSB3ZWxsLXN1aXRlZCB0byB0aGlzIHR5cGUgb2YgY2x1c3RlcmluZyBidXQgbGV0J3MgY29udGludWUuCgojIyA0LiBEZXRlcm1pbmluZyBvcHRpbWFsIGNsdXN0ZXJzCgpBIGZ1bmRhbWVudGFsIHF1ZXN0aW9uIHRoYXQgb2Z0ZW4gYXJpc2VzIGluIEstTWVhbnMgY2x1c3RlcmluZyBpcyB0aGlzIC0gd2l0aG91dCBrbm93biBjbGFzcyBsYWJlbHMsIGhvdyBkbyB5b3Uga25vdyBob3cgbWFueSBjbHVzdGVycyB0byBzZXBhcmF0ZSB5b3VyIGRhdGEgaW50bz8KCk9uZSB3YXkgd2UgY2FuIHRyeSB0byBmaW5kIG91dCBpcyB0byB1c2UgYSBkYXRhIHNhbXBsZSB0byBgY3JlYXRlIGEgc2VyaWVzIG9mIGNsdXN0ZXJpbmcgbW9kZWxzYCB3aXRoIGFuIGluY3JlbWVudGluZyBudW1iZXIgb2YgY2x1c3RlcnMgKGUuZyBmcm9tIDEtMTApLCBhbmQgZXZhbHVhdGUgY2x1c3RlcmluZyBtZXRyaWNzIHN1Y2ggYXMgdGhlICoqU2lsaG91ZXR0ZSBzY29yZS4qKgoKTGV0J3MgZGV0ZXJtaW5lIHRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycyBieSBjb21wdXRpbmcgdGhlIGNsdXN0ZXJpbmcgYWxnb3JpdGhtIGZvciBkaWZmZXJlbnQgdmFsdWVzIG9mICprKiBhbmQgZXZhbHVhdGluZyB0aGUgKipXaXRoaW4gQ2x1c3RlciBTdW0gb2YgU3F1YXJlcyoqIChXQ1NTKS4gVGhlIHRvdGFsIHdpdGhpbi1jbHVzdGVyIHN1bSBvZiBzcXVhcmUgKFdDU1MpIG1lYXN1cmVzIHRoZSBjb21wYWN0bmVzcyBvZiB0aGUgY2x1c3RlcmluZyBhbmQgd2Ugd2FudCBpdCB0byBiZSBhcyBzbWFsbCBhcyBwb3NzaWJsZSwgd2l0aCBsb3dlciB2YWx1ZXMgbWVhbmluZyB0aGF0IHRoZSBkYXRhIHBvaW50cyBhcmUgY2xvc2VyLgoKTGV0J3MgZXhwbG9yZSB0aGUgZWZmZWN0IG9mIGRpZmZlcmVudCBjaG9pY2VzIG9mIGBrYCwgZnJvbSAxIHRvIDEwLCBvbiB0aGlzIGNsdXN0ZXJpbmcuCgpgYGB7cn0KIyBDcmVhdGUgYSBzZXJpZXMgb2YgY2x1c3RlcmluZyBtb2RlbHMKa2NsdXN0cyA8LSB0aWJibGUoayA9IDE6MTApICU+JSAKICAjIFBlcmZvcm0ga21lYW5zIGNsdXN0ZXJpbmcgZm9yIDEsMiwzIC4uLiAsMTAgY2x1c3RlcnMKICBtdXRhdGUobW9kZWwgPSBtYXAoaywgfiBrbWVhbnMoZGZfbnVtZXJpY19zZWxlY3QsIGNlbnRlcnMgPSAueCwgbnN0YXJ0ID0gMjUpKSwKICAjIEZhcm0gb3V0IGNsdXN0ZXJpbmcgbWV0cmljcyBlZyBXQ1NTCiAgICAgICAgIGdsYW5jZWQgPSBtYXAobW9kZWwsIH4gZ2xhbmNlKC54KSkpICU+JSAKICB1bm5lc3QoY29scyA9IGdsYW5jZWQpCiAgCgojIFZpZXcgY2x1c3RlcmluZyByc3Vsc3RzCmtjbHVzdHMKYGBgCgpOb3cgdGhhdCB3ZSBoYXZlIHRoZSB0b3RhbCB3aXRoaW4tY2x1c3RlciBzdW0tb2Ytc3F1YXJlcyAodG90LndpdGhpbnNzKSBmb3IgZWFjaCBjbHVzdGVyaW5nIGFsZ29yaXRobSB3aXRoIGNlbnRlciAqayosIHdlIHVzZSB0aGUgW2VsYm93IG1ldGhvZF0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRWxib3dfbWV0aG9kXyhjbHVzdGVyaW5nKSkgdG8gZmluZCB0aGUgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMuIFRoZSBtZXRob2QgY29uc2lzdHMgb2YgcGxvdHRpbmcgdGhlIFdDU1MgYXMgYSBmdW5jdGlvbiBvZiB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzLCBhbmQgcGlja2luZyB0aGUgW2VsYm93IG9mIHRoZSBjdXJ2ZV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRWxib3dfb2ZfdGhlX2N1cnZlICJFbGJvdyBvZiB0aGUgY3VydmUiKSBhcyB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIHRvIHVzZS4KCmBgYHtyIGVsYm93X21ldGhvZH0Kc2V0LnNlZWQoMjA1NikKIyBVc2UgZWxib3cgbWV0aG9kIHRvIGRldGVybWluZSBvcHRpbXVtIG51bWJlciBvZiBjbHVzdGVycwprY2x1c3RzICU+JSAKICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gaywgeSA9IHRvdC53aXRoaW5zcykpICsKICBnZW9tX2xpbmUoc2l6ZSA9IDEuMiwgYWxwaGEgPSAwLjgsIGNvbG9yID0gIiNGRjdGMEVGRiIpICsKICBnZW9tX3BvaW50KHNpemUgPSAyLCBjb2xvciA9ICIjRkY3RjBFRkYiKQpgYGAKClRoZSBwbG90IHNob3dzIGEgbGFyZ2UgcmVkdWN0aW9uIGluIFdDU1MgKHNvIGdyZWF0ZXIgKnRpZ2h0bmVzcyopIGFzIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgaW5jcmVhc2VzIGZyb20gb25lIHRvIHR3bywgYW5kIGEgZnVydGhlciBub3RpY2VhYmxlIHJlZHVjdGlvbiBmcm9tIHR3byB0byB0aHJlZSBjbHVzdGVycy4gQWZ0ZXIgdGhhdCwgdGhlIHJlZHVjdGlvbiBpcyBsZXNzIHByb25vdW5jZWQsIHJlc3VsdGluZyBpbiBhbiBgZWxib3dgIPCfkqppbiB0aGUgY2hhcnQgYXQgYXJvdW5kIHRocmVlIGNsdXN0ZXJzLiBUaGlzIGlzIGEgZ29vZCBpbmRpY2F0aW9uIHRoYXQgdGhlcmUgYXJlIHR3byB0byB0aHJlZSByZWFzb25hYmx5IHdlbGwgc2VwYXJhdGVkIGNsdXN0ZXJzIG9mIGRhdGEgcG9pbnRzLgoKV2UgY2FuIG5vdyBnbyBhaGVhZCBhbmQgZXh0cmFjdCB0aGUgY2x1c3RlcmluZyBtb2RlbCB3aGVyZSBgayA9IDNgOgoKPiBgcHVsbCgpYDogdXNlZCB0byBleHRyYWN0IGEgc2luZ2xlIGNvbHVtbgo+Cj4gYHBsdWNrKClgOiB1c2VkIHRvIGluZGV4IGRhdGEgc3RydWN0dXJlcyBzdWNoIGFzIGxpc3RzCgpgYGB7ciBleHRyYWN0X21vZGVsfQojIEV4dHJhY3QgayA9IDMgY2x1c3RlcmluZwpmaW5hbF9rbWVhbnMgPC0ga2NsdXN0cyAlPiUgCiAgZmlsdGVyKGsgPT0gMykgJT4lIAogIHB1bGwobW9kZWwpICU+JSAKICBwbHVjaygxKQoKCmZpbmFsX2ttZWFucwpgYGAKCkdyZWF0ISBMZXQncyBnbyBhaGVhZCBhbmQgdmlzdWFsaXplIHRoZSBjbHVzdGVycyBvYnRhaW5lZC4gQ2FyZSBmb3Igc29tZSBpbnRlcmFjdGl2aXR5IHVzaW5nIGBwbG90bHlgPwoKYGBge3Igdml6X2NsdXN0fQojIEFkZCBwcmVkaWN0ZWQgY2x1c3RlciBhc3NpZ25tZW50IHRvIGRhdGEgc2V0CnJlc3VsdHMgPC0gIGF1Z21lbnQoZmluYWxfa21lYW5zLCBkZl9udW1lcmljX3NlbGVjdCkgJT4lIAogIGJpbmRfY29scyhkZl9udW1lcmljICU+JSBzZWxlY3QoYXJ0aXN0X3RvcF9nZW5yZSkpIAoKIyBQbG90IGNsdXN0ZXIgYXNzaWdubWVudHMKY2x1c3RfcGx0IDwtIHJlc3VsdHMgJT4lIAogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBwb3B1bGFyaXR5LCB5ID0gZGFuY2VhYmlsaXR5LCBjb2xvciA9IC5jbHVzdGVyLCBzaGFwZSA9IGFydGlzdF90b3BfZ2VucmUpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMiwgYWxwaGEgPSAwLjgpICsKICBwYWxldHRlZXI6OnNjYWxlX2NvbG9yX3BhbGV0dGVlcl9kKCJnZ3RoZW1lczo6VGFibGVhdV8xMCIpCgpnZ3Bsb3RseShjbHVzdF9wbHQpCgpgYGAKClBlcmhhcHMgd2Ugd291bGQgaGF2ZSBleHBlY3RlZCB0aGF0IGVhY2ggY2x1c3RlciAocmVwcmVzZW50ZWQgYnkgZGlmZmVyZW50IGNvbG9ycykgd291bGQgaGF2ZSBkaXN0aW5jdCBnZW5yZXMgKHJlcHJlc2VudGVkIGJ5IGRpZmZlcmVudCBzaGFwZXMpLgoKTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIG1vZGVsJ3MgYWNjdXJhY3kuCgpgYGB7ciBvcmRpbmFsX2VuY29kZX0KIyBBc3NpZ24gZ2VucmVzIHRvIHByZWRlZmluZWQgaW50ZWdlcnMKbGFiZWxfY291bnQgPC0gcmVzdWx0cyAlPiUgCiAgZ3JvdXBfYnkoYXJ0aXN0X3RvcF9nZW5yZSkgJT4lIAogIG11dGF0ZShpZCA9IGN1cl9ncm91cF9pZCgpKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBzdW1tYXJpc2UoY29ycmVjdF9sYWJlbHMgPSBzdW0oLmNsdXN0ZXIgPT0gaWQpKQoKCiMgUHJpbnQgcmVzdWx0cyAgCmNhdCgiUmVzdWx0OiIsIGxhYmVsX2NvdW50JGNvcnJlY3RfbGFiZWxzLCAib3V0IG9mIiwgbnJvdyhyZXN1bHRzKSwgInNhbXBsZXMgd2VyZSBjb3JyZWN0bHkgbGFiZWxlZC4iKQoKY2F0KCJcbkFjY3VyYWN5IHNjb3JlOiIsIGxhYmVsX2NvdW50JGNvcnJlY3RfbGFiZWxzL25yb3cocmVzdWx0cykpCgpgYGAKClRoaXMgbW9kZWwncyBhY2N1cmFjeSBpcyBub3QgYmFkLCBidXQgbm90IGdyZWF0LiBJdCBtYXkgYmUgdGhhdCB0aGUgZGF0YSBtYXkgbm90IGxlbmQgaXRzZWxmIHdlbGwgdG8gSy1NZWFucyBDbHVzdGVyaW5nLiBUaGlzIGRhdGEgaXMgdG9vIGltYmFsYW5jZWQsIHRvbyBsaXR0bGUgY29ycmVsYXRlZCBhbmQgdGhlcmUgaXMgdG9vIG11Y2ggdmFyaWFuY2UgYmV0d2VlbiB0aGUgY29sdW1uIHZhbHVlcyB0byBjbHVzdGVyIHdlbGwuIEluIGZhY3QsIHRoZSBjbHVzdGVycyB0aGF0IGZvcm0gYXJlIHByb2JhYmx5IGhlYXZpbHkgaW5mbHVlbmNlZCBvciBza2V3ZWQgYnkgdGhlIHRocmVlIGdlbnJlIGNhdGVnb3JpZXMgd2UgZGVmaW5lZCBhYm92ZS4KCk5ldmVydGhlbGVzcywgdGhhdCB3YXMgcXVpdGUgYSBsZWFybmluZyBwcm9jZXNzIQoKSW4gU2Npa2l0LWxlYXJuJ3MgZG9jdW1lbnRhdGlvbiwgeW91IGNhbiBzZWUgdGhhdCBhIG1vZGVsIGxpa2UgdGhpcyBvbmUsIHdpdGggY2x1c3RlcnMgbm90IHZlcnkgd2VsbCBkZW1hcmNhdGVkLCBoYXMgYSAndmFyaWFuY2UnIHByb2JsZW06CgohW0luZm9ncmFwaGljIGZyb20gU2Npa2l0LWxlYXJuXSguLi8uLi9pbWFnZXMvcHJvYmxlbXMucG5nKQoKIyMgKipWYXJpYW5jZSoqCgpWYXJpYW5jZSBpcyBkZWZpbmVkIGFzICJ0aGUgYXZlcmFnZSBvZiB0aGUgc3F1YXJlZCBkaWZmZXJlbmNlcyBmcm9tIHRoZSBNZWFuIiBbKFNvdXJjZSldKGh0dHBzOi8vd3d3Lm1hdGhzaXNmdW4uY29tL2RhdGEvc3RhbmRhcmQtZGV2aWF0aW9uLmh0bWwpLiBJbiB0aGUgY29udGV4dCBvZiB0aGlzIGNsdXN0ZXJpbmcgcHJvYmxlbSwgaXQgcmVmZXJzIHRvIGRhdGEgdGhhdCB0aGUgbnVtYmVycyBvZiBvdXIgZGF0YXNldCB0ZW5kIHRvIGRpdmVyZ2UgYSBiaXQgdG9vIG11Y2ggZnJvbSB0aGUgbWVhbi4KCuKchSBUaGlzIGlzIGEgZ3JlYXQgbW9tZW50IHRvIHRoaW5rIGFib3V0IGFsbCB0aGUgd2F5cyB5b3UgY291bGQgY29ycmVjdCB0aGlzIGlzc3VlLiBUd2VhayB0aGUgZGF0YSBhIGJpdCBtb3JlPyBVc2UgZGlmZmVyZW50IGNvbHVtbnM/IFVzZSBhIGRpZmZlcmVudCBhbGdvcml0aG0/IEhpbnQ6IFRyeSBbc2NhbGluZyB5b3VyIGRhdGFdKGh0dHBzOi8vd3d3Lm15Z3JlYXRsZWFybmluZy5jb20vYmxvZy9sZWFybmluZy1kYXRhLXNjaWVuY2Utd2l0aC1rLW1lYW5zLWNsdXN0ZXJpbmcvKSB0byBub3JtYWxpemUgaXQgYW5kIHRlc3Qgb3RoZXIgY29sdW1ucy4KCj4gVHJ5IHRoaXMgJ1t2YXJpYW5jZSBjYWxjdWxhdG9yXShodHRwczovL3d3dy5jYWxjdWxhdG9yc291cC5jb20vY2FsY3VsYXRvcnMvc3RhdGlzdGljcy92YXJpYW5jZS1jYWxjdWxhdG9yLnBocCknIHRvIHVuZGVyc3RhbmQgdGhlIGNvbmNlcHQgYSBiaXQgbW9yZS4KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMgKirwn5qAQ2hhbGxlbmdlKioKClNwZW5kIHNvbWUgdGltZSB3aXRoIHRoaXMgbm90ZWJvb2ssIHR3ZWFraW5nIHBhcmFtZXRlcnMuIENhbiB5b3UgaW1wcm92ZSB0aGUgYWNjdXJhY3kgb2YgdGhlIG1vZGVsIGJ5IGNsZWFuaW5nIHRoZSBkYXRhIG1vcmUgKHJlbW92aW5nIG91dGxpZXJzLCBmb3IgZXhhbXBsZSk/IFlvdSBjYW4gdXNlIHdlaWdodHMgdG8gZ2l2ZSBtb3JlIHdlaWdodCB0byBnaXZlbiBkYXRhIHNhbXBsZXMuIFdoYXQgZWxzZSBjYW4geW91IGRvIHRvIGNyZWF0ZSBiZXR0ZXIgY2x1c3RlcnM/CgpIaW50OiBUcnkgdG8gc2NhbGUgeW91ciBkYXRhLiBUaGVyZSdzIGNvbW1lbnRlZCBjb2RlIGluIHRoZSBub3RlYm9vayB0aGF0IGFkZHMgc3RhbmRhcmQgc2NhbGluZyB0byBtYWtlIHRoZSBkYXRhIGNvbHVtbnMgcmVzZW1ibGUgZWFjaCBvdGhlciBtb3JlIGNsb3NlbHkgaW4gdGVybXMgb2YgcmFuZ2UuIFlvdSdsbCBmaW5kIHRoYXQgd2hpbGUgdGhlIHNpbGhvdWV0dGUgc2NvcmUgZ29lcyBkb3duLCB0aGUgJ2tpbmsnIGluIHRoZSBlbGJvdyBncmFwaCBzbW9vdGhzIG91dC4gVGhpcyBpcyBiZWNhdXNlIGxlYXZpbmcgdGhlIGRhdGEgdW5zY2FsZWQgYWxsb3dzIGRhdGEgd2l0aCBsZXNzIHZhcmlhbmNlIHRvIGNhcnJ5IG1vcmUgd2VpZ2h0LiBSZWFkIGEgYml0IG1vcmUgb24gdGhpcyBwcm9ibGVtIFtoZXJlXShodHRwczovL3N0YXRzLnN0YWNrZXhjaGFuZ2UuY29tL3F1ZXN0aW9ucy8yMTIyMi9hcmUtbWVhbi1ub3JtYWxpemF0aW9uLWFuZC1mZWF0dXJlLXNjYWxpbmctbmVlZGVkLWZvci1rLW1lYW5zLWNsdXN0ZXJpbmcvMjEyMjYjMjEyMjYpLgoKIyMgWyoqUG9zdC1sZWN0dXJlIHF1aXoqKl0oaHR0cHM6Ly9ncmF5LXNhbmQtMDdhMTBmNDAzLjEuYXp1cmVzdGF0aWNhcHBzLm5ldC9xdWl6LzMwLykKCiMjICoqUmV2aWV3ICYgU2VsZiBTdHVkeSoqCgotICAgVGFrZSBhIGxvb2sgYXQgYSBLLU1lYW5zIFNpbXVsYXRvciBbc3VjaCBhcyB0aGlzIG9uZV0oaHR0cHM6Ly91c2VyLmNlbmcubWV0dS5lZHUudHIvfmFraWZha2t1cy9jb3Vyc2VzL2Nlbmc1NzQvay1tZWFucy8pLiBZb3UgY2FuIHVzZSB0aGlzIHRvb2wgdG8gdmlzdWFsaXplIHNhbXBsZSBkYXRhIHBvaW50cyBhbmQgZGV0ZXJtaW5lIGl0cyBjZW50cm9pZHMuIFlvdSBjYW4gZWRpdCB0aGUgZGF0YSdzIHJhbmRvbW5lc3MsIG51bWJlcnMgb2YgY2x1c3RlcnMgYW5kIG51bWJlcnMgb2YgY2VudHJvaWRzLiBEb2VzIHRoaXMgaGVscCB5b3UgZ2V0IGFuIGlkZWEgb2YgaG93IHRoZSBkYXRhIGNhbiBiZSBncm91cGVkPwoKLSAgIEFsc28sIHRha2UgYSBsb29rIGF0IFt0aGlzIGhhbmRvdXQgb24gSy1NZWFuc10oaHR0cHM6Ly9zdGFuZm9yZC5lZHUvfmNwaWVjaC9jczIyMS9oYW5kb3V0cy9rbWVhbnMuaHRtbCkgZnJvbSBTdGFuZm9yZC4KCldhbnQgdG8gdHJ5IG91dCB5b3VyIG5ld2x5IGFjcXVpcmVkIGNsdXN0ZXJpbmcgc2tpbGxzIHRvIGRhdGEgc2V0cyB0aGF0IGxlbmQgd2VsbCB0byBLLU1lYW5zIGNsdXN0ZXJpbmc/IFBsZWFzZSBzZWU6CgotICAgW1RyYWluIGFuZCBFdmFsdWF0ZSBDbHVzdGVyaW5nIE1vZGVsc10oaHR0cHM6Ly9ycHVicy5jb20vZVJfaWMvY2x1c3RlcmluZykgdXNpbmcgVGlkeW1vZGVscyBhbmQgZnJpZW5kcwoKLSAgIFtLLW1lYW5zIENsdXN0ZXIgQW5hbHlzaXNdKGh0dHBzOi8vdWMtci5naXRodWIuaW8va21lYW5zX2NsdXN0ZXJpbmcpLCBVQyBCdXNpbmVzcyBBbmFseXRpY3MgUiBQcm9ncmFtbWluZyBHdWlkZQoKLSAgIFtLLW1lYW5zwqBjbHVzdGVyaW5nwqB3aXRowqB0aWR5wqBkYXRhwqBwcmluY2lwbGVzXShodHRwczovL3d3dy50aWR5bW9kZWxzLm9yZy9sZWFybi9zdGF0aXN0aWNzL2stbWVhbnMvKQoKIyMgKipBc3NpZ25tZW50KioKCltUcnkgZGlmZmVyZW50IGNsdXN0ZXJpbmcgbWV0aG9kc10oaHR0cHM6Ly9naXRodWIuY29tL21pY3Jvc29mdC9NTC1Gb3ItQmVnaW5uZXJzL2Jsb2IvbWFpbi81LUNsdXN0ZXJpbmcvMi1LLU1lYW5zL2Fzc2lnbm1lbnQubWQpCgojIyBUSEFOSyBZT1UgVE86CgpbSmVuIExvb3Blcl0oaHR0cHM6Ly93d3cudHdpdHRlci5jb20vamVubG9vcGVyKSBmb3IgY3JlYXRpbmcgdGhlIG9yaWdpbmFsIFB5dGhvbiB2ZXJzaW9uIG9mIHRoaXMgbW9kdWxlIOKZpe+4jwoKW2BBbGxpc29uIEhvcnN0YF0oaHR0cHM6Ly90d2l0dGVyLmNvbS9hbGxpc29uX2hvcnN0LykgZm9yIGNyZWF0aW5nIHRoZSBhbWF6aW5nIGlsbHVzdHJhdGlvbnMgdGhhdCBtYWtlIFIgbW9yZSB3ZWxjb21pbmcgYW5kIGVuZ2FnaW5nLiBGaW5kIG1vcmUgaWxsdXN0cmF0aW9ucyBhdCBoZXIgW2dhbGxlcnldKGh0dHBzOi8vd3d3Lmdvb2dsZS5jb20vdXJsP3E9aHR0cHM6Ly9naXRodWIuY29tL2FsbGlzb25ob3JzdC9zdGF0cy1pbGx1c3RyYXRpb25zJnNhPUQmc291cmNlPWVkaXRvcnMmdXN0PTE2MjYzODA3NzI1MzAwMDAmdXNnPUFPdlZhdzN6Y2Z5Q2l6RlFacGtTTHp4aWlRRU0pLgoKSGFwcHkgTGVhcm5pbmcsCgpbRXJpY10oaHR0cHM6Ly90d2l0dGVyLmNvbS9lcmljbnRheSksIEdvbGQgTWljcm9zb2Z0IExlYXJuIFN0dWRlbnQgQW1iYXNzYWRvci4KCiFbQXJ0d29yayBieSBcQGFsbGlzb25faG9yc3RdKC4uLy4uL2ltYWdlcy9yX2xlYXJuZXJzX3NtLmpwZWcpCgojYGBge3IgaW5jbHVkZT1GQUxTRX0KI2xpYnJhcnkoaGVyZSkKI2xpYnJhcnkocm1kMmp1cHl0ZXIpCiNybWQyanVweXRlcigibGVzc29uXzE0LlJtZCIpCiNgYGAK
+
+
+