From 10a6fcde192d409e775d0cfd0273506b83ef1548 Mon Sep 17 00:00:00 2001 From: Vidushi Gupta <55969597+Vidushi-Gupta@users.noreply.github.com> Date: Thu, 15 Jun 2023 15:28:11 +0530 Subject: [PATCH] Added html file --- .../2-K-Means/solution/R/lesson_15.html | 5495 +++++++++++++++++ 1 file changed, 5495 insertions(+) create mode 100644 5-Clustering/2-K-Means/solution/R/lesson_15.html diff --git a/5-Clustering/2-K-Means/solution/R/lesson_15.html b/5-Clustering/2-K-Means/solution/R/lesson_15.html new file mode 100644 index 000000000..db00cf0fa --- /dev/null +++ b/5-Clustering/2-K-Means/solution/R/lesson_15.html @@ -0,0 +1,5495 @@ + + + + + + + + + + + + + +K-Means Clustering using Tidymodels and friends + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +
+

Explore K-Means clustering using R and Tidy data principles.

+
+

Pre-lecture +quiz

+

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 +
Infographic by Jen Looper
+
+

K-Means clustering has the following steps:

+
    +
  1. The data scientist starts by specifying the desired number of +clusters to be created.

  2. +
  3. Next, the algorithm randomly selects K observations from the data +set to serve as the initial centers for the clusters (i.e., +centroids).

  4. +
  5. Next, each of the remaining observations is assigned to its +closest centroid.

  6. +
  7. Next, the new means of each cluster is computed and the centroid +is moved to the mean.

  8. +
  9. 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.

  10. +
+
+
+

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 +
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!

+
+
+ +
+

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

+
+
+

Post-lecture +quiz

+
+
+

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 +
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
+ + +
+
+ +
+ + + + + + + + + + + + + + + + +